From aa690beed775646cbc86b445fb5dc90dcac087d5 Mon Sep 17 00:00:00 2001 From: "Carlos R. L. Rodrigues" <37986729+carlos-r-l-rodrigues@users.noreply.github.com> Date: Fri, 17 Mar 2023 12:18:52 -0300 Subject: [PATCH] feat(medusa): Modules initializer (#3352) --- .changeset/grumpy-buses-pump.md | 27 ++ .../development/use-db-development.js | 15 +- packages/inventory/package.json | 6 +- packages/inventory/src/index.ts | 7 + packages/inventory/src/initialize/index.ts | 24 ++ packages/inventory/src/loaders/connection.ts | 20 +- packages/inventory/src/loaders/container.ts | 2 +- .../inventory/src/migrations/run-migration.ts | 63 +++ .../inventory/src/models/reservation-item.ts | 2 +- .../inventory/src/services/inventory-item.ts | 155 +++---- .../inventory/src/services/inventory-level.ts | 237 ++++++----- packages/inventory/src/services/inventory.ts | 381 +++++++++++------- .../src/services/reservation-item.ts | 303 +++++++------- packages/inventory/src/types/index.ts | 12 + .../admin/draft-orders/register-payment.ts | 5 +- .../inventory-items/delete-inventory-item.ts | 4 +- .../inventory-items/delete-location-level.ts | 6 +- .../inventory-items/update-inventory-item.ts | 12 +- .../inventory-items/update-location-level.ts | 6 +- .../transaction/create-product-variant.ts | 22 +- .../admin/reservations/create-reservation.ts | 11 +- .../admin/reservations/delete-reservation.ts | 4 +- .../admin/reservations/update-reservation.ts | 4 +- .../stock-locations/delete-stock-location.ts | 8 +- packages/medusa/src/commands/migrate.js | 9 +- packages/medusa/src/commands/seed.ts | 10 +- .../src/commands/utils/get-migrations.js | 46 ++- .../src/interfaces/services/inventory.ts | 3 - packages/medusa/src/loaders/index.ts | 2 +- .../src/services/__tests__/event-bus.js | 106 +++-- packages/medusa/src/services/event-bus.ts | 4 +- .../medusa/src/services/product-variant.ts | 34 +- .../src/services/sales-channel-inventory.ts | 7 +- .../src/services/sales-channel-location.ts | 14 +- packages/medusa/src/types/global.ts | 14 +- packages/modules-sdk/src/index.ts | 1 + .../src/loaders/__tests__/module-loader.ts | 6 +- .../src/loaders/__tests__/register-modules.ts | 50 +-- .../src/loaders/register-modules.ts | 45 ++- .../src/loaders/utils/load-internal.ts | 14 +- packages/modules-sdk/src/medusa-module.ts | 108 +++++ packages/modules-sdk/src/types/index.ts | 21 +- packages/stock-location/src/index.ts | 7 +- .../stock-location/src/initialize/index.ts | 24 ++ .../stock-location/src/loaders/connection.ts | 25 +- .../src/migrations/run-migration.ts | 65 +++ .../src/services/stock-location.ts | 24 +- packages/stock-location/src/types/index.ts | 12 + .../utils/src/decorators/context-parameter.ts | 8 +- .../src/decorators/inject-entity-manager.ts | 4 +- yarn.lock | 6 +- 51 files changed, 1290 insertions(+), 715 deletions(-) create mode 100644 .changeset/grumpy-buses-pump.md create mode 100644 packages/inventory/src/initialize/index.ts create mode 100644 packages/inventory/src/migrations/run-migration.ts create mode 100644 packages/inventory/src/types/index.ts create mode 100644 packages/modules-sdk/src/medusa-module.ts create mode 100644 packages/stock-location/src/initialize/index.ts create mode 100644 packages/stock-location/src/migrations/run-migration.ts create mode 100644 packages/stock-location/src/types/index.ts diff --git a/.changeset/grumpy-buses-pump.md b/.changeset/grumpy-buses-pump.md new file mode 100644 index 0000000000..e31a68204c --- /dev/null +++ b/.changeset/grumpy-buses-pump.md @@ -0,0 +1,27 @@ +--- +"@medusajs/inventory": patch +"medusa-core-utils": patch +"@medusajs/medusa": patch +--- + +feat(medusa): Modules initializer + +### Loading modules in a project + +Example + +``` typescript +import { InventoryServiceInitializeOptions, initialize } from "@medusajs/inventory" + +const options: InventoryServiceInitializeOptions = { + database: { + type: "postgres", + url: DB_URL, + }, +} + +const inventoryService = await initialize(options) +const newInventoryItem = await inventoryService.createInventoryItem({ + sku: "sku_123", +}) +``` diff --git a/integration-tests/development/use-db-development.js b/integration-tests/development/use-db-development.js index 1ae65fac42..8cc2634b6a 100644 --- a/integration-tests/development/use-db-development.js +++ b/integration-tests/development/use-db-development.js @@ -1,6 +1,6 @@ const path = require("path") -const { createConnection } = require("typeorm") +const { DataSource } = require("typeorm") const { getConfigFile } = require("medusa-core-utils") const DB_HOST = process.env.DB_HOST @@ -14,14 +14,16 @@ process.env.NODE_ENV = "development" require("./dev-require") async function createDB() { - const connection = await createConnection({ + const connection = new DataSource({ type: "postgres", url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}`, }) + await connection.initialize() + await connection.query(`DROP DATABASE IF EXISTS "${DB_NAME}";`) await connection.query(`CREATE DATABASE "${DB_NAME}";`) - await connection.close() + await connection.destroy() } module.exports = { @@ -47,6 +49,7 @@ module.exports = { const { getEnabledMigrations, getModuleSharedResources, + runIsolatedModulesMigration, } = require("@medusajs/medusa/dist/commands/utils/get-migrations") const entities = modelsLoader({}, { register: false }) @@ -72,7 +75,7 @@ module.exports = { await createDB() - const dbConnection = await createConnection({ + const dbConnection = new DataSource({ type: "postgres", url: DB_URL, entities: enabledEntities.concat(moduleModels), @@ -80,8 +83,12 @@ module.exports = { // logging: true, }) + await dbConnection.initialize() + await dbConnection.runMigrations() + await runIsolatedModulesMigration(configModule) + return dbConnection }, } diff --git a/packages/inventory/package.json b/packages/inventory/package.json index 72cdb68e14..06c06aed7a 100644 --- a/packages/inventory/package.json +++ b/packages/inventory/package.json @@ -17,14 +17,16 @@ "author": "Medusa", "license": "MIT", "devDependencies": { - "@medusajs/medusa": "*", + "@medusajs/types": "*", "cross-env": "^5.2.1", "jest": "^25.5.4", "ts-jest": "^25.5.1", "typescript": "^4.4.4" }, "dependencies": { + "@medusajs/medusa": "^1.7.7", "@medusajs/modules-sdk": "*", + "@medusajs/utils": "^0.0.1", "awilix": "^8.0.0", "typeorm": "^0.3.11" }, @@ -36,6 +38,6 @@ "test:unit": "jest --passWithNoTests" }, "peerDependencies": { - "@medusajs/medusa": "1.7.13" + "@medusajs/types": "^0.0.1" } } diff --git a/packages/inventory/src/index.ts b/packages/inventory/src/index.ts index 084596647c..b2088f7b11 100644 --- a/packages/inventory/src/index.ts +++ b/packages/inventory/src/index.ts @@ -2,6 +2,7 @@ import loadConnection from "./loaders/connection" import loadContainer from "./loaders/container" import migrations from "./migrations" +import { revertMigration, runMigrations } from "./migrations/run-migration" import * as InventoryModels from "./models" import InventoryService from "./services/inventory" @@ -16,6 +17,12 @@ const moduleDefinition: ModuleExports = { migrations, loaders, models, + runMigrations, + revertMigration, } export default moduleDefinition + +export * from "./initialize" +export * from "./types" + diff --git a/packages/inventory/src/initialize/index.ts b/packages/inventory/src/initialize/index.ts new file mode 100644 index 0000000000..48102e3a9e --- /dev/null +++ b/packages/inventory/src/initialize/index.ts @@ -0,0 +1,24 @@ +import { IEventBusService, IInventoryService } from "@medusajs/medusa" +import { + ExternalModuleDeclaration, + InternalModuleDeclaration, + MedusaModule, +} from "@medusajs/modules-sdk" +import { InventoryServiceInitializeOptions } from "../types" + +export const initialize = async ( + options?: InventoryServiceInitializeOptions | ExternalModuleDeclaration, + injectedDependencies?: { + eventBusService: IEventBusService + } +): Promise => { + const serviceKey = "inventoryService" + const loaded = await MedusaModule.bootstrap( + serviceKey, + "@medusajs/inventory", + options as InternalModuleDeclaration | ExternalModuleDeclaration, + injectedDependencies + ) + + return loaded[serviceKey] as IInventoryService +} diff --git a/packages/inventory/src/loaders/connection.ts b/packages/inventory/src/loaders/connection.ts index 096239d8ca..d45e4889a9 100644 --- a/packages/inventory/src/loaders/connection.ts +++ b/packages/inventory/src/loaders/connection.ts @@ -6,9 +6,10 @@ import { } from "@medusajs/modules-sdk" import { DataSource, DataSourceOptions } from "typeorm" -import * as InventoryModels from "../models" -import { MedusaError } from "medusa-core-utils" import { asValue } from "awilix" +import { MedusaError } from "medusa-core-utils" +import * as InventoryModels from "../models" +import { InventoryServiceInitializeOptions } from "../types" export default async ( { options, container }: LoaderOptions, @@ -21,7 +22,8 @@ export default async ( return } - const dbData = options?.database as Record + const dbData = + options?.database as InventoryServiceInitializeOptions["database"] if (!dbData) { throw new MedusaError( @@ -32,13 +34,13 @@ export default async ( const entities = Object.values(InventoryModels) const dataSource = new DataSource({ - type: dbData.database_type, - url: dbData.database_url, - database: dbData.database_database, - extra: dbData.database_extra || {}, - schema: dbData.database_schema, + type: dbData.type, + url: dbData.url, + database: dbData.database, + extra: dbData.extra || {}, + schema: dbData.schema, entities, - logging: dbData.database_logging, + logging: dbData.logging, } as DataSourceOptions) await dataSource.initialize() diff --git a/packages/inventory/src/loaders/container.ts b/packages/inventory/src/loaders/container.ts index 4b4bef1cb3..b57a630e03 100644 --- a/packages/inventory/src/loaders/container.ts +++ b/packages/inventory/src/loaders/container.ts @@ -3,7 +3,7 @@ import { InternalModuleDeclaration, LoaderOptions } from "@medusajs/modules-sdk" import { InventoryItemService, InventoryLevelService, - ReservationItemService, + ReservationItemService } from "../services" import { asClass } from "awilix" diff --git a/packages/inventory/src/migrations/run-migration.ts b/packages/inventory/src/migrations/run-migration.ts new file mode 100644 index 0000000000..e6d2ffcd83 --- /dev/null +++ b/packages/inventory/src/migrations/run-migration.ts @@ -0,0 +1,63 @@ +import { InternalModuleDeclaration, LoaderOptions } from "@medusajs/modules-sdk" +import { DataSource, DataSourceOptions } from "typeorm" +import { InventoryServiceInitializeOptions } from "../types" + +import migrations from "./index" + +function getDataSource( + dbData: InventoryServiceInitializeOptions["database"] +): DataSource { + return new DataSource({ + type: dbData!.type, + url: dbData!.url, + database: dbData!.database, + extra: dbData!.extra || {}, + migrations: migrations + .map((migration: any): Function[] => { + return Object.values(migration).filter( + (fn) => typeof fn === "function" + ) as Function[] + }) + .flat(), + schema: dbData!.schema, + logging: dbData!.logging, + } as DataSourceOptions) +} + +export async function runMigrations( + { options, logger }: Omit, + moduleDeclaration?: InternalModuleDeclaration +) { + const dbData = + options?.database as InventoryServiceInitializeOptions["database"] + + try { + const dataSource = getDataSource(dbData) + await dataSource.initialize() + await dataSource.runMigrations() + + logger?.info("Inventory module migration executed") + } catch (error) { + logger?.error(`Inventory module migration failed to run - Error: ${error}`) + } +} + +export async function revertMigration( + { options, logger }: Omit, + moduleDeclaration?: InternalModuleDeclaration +) { + const dbData = + options?.database as InventoryServiceInitializeOptions["database"] + + try { + const dataSource = getDataSource(dbData) + await dataSource.initialize() + await dataSource.undoLastMigration() + + logger?.info("Inventory module migration reverted") + } catch (error) { + logger?.error( + `Inventory module migration failed to revert - Error: ${error}` + ) + } +} diff --git a/packages/inventory/src/models/reservation-item.ts b/packages/inventory/src/models/reservation-item.ts index d2d46ced08..84e17386a1 100644 --- a/packages/inventory/src/models/reservation-item.ts +++ b/packages/inventory/src/models/reservation-item.ts @@ -1,4 +1,4 @@ -import { Index, Unique, BeforeInsert, Column, Entity } from "typeorm" +import { Index, BeforeInsert, Column, Entity } from "typeorm" import { SoftDeletableEntity, generateEntityId } from "@medusajs/medusa" @Entity() diff --git a/packages/inventory/src/services/inventory-item.ts b/packages/inventory/src/services/inventory-item.ts index 803b3006c0..8523ebdde9 100644 --- a/packages/inventory/src/services/inventory-item.ts +++ b/packages/inventory/src/services/inventory-item.ts @@ -1,5 +1,3 @@ -import { DeepPartial, EntityManager, FindManyOptions } from "typeorm" -import { isDefined, MedusaError } from "medusa-core-utils" import { buildQuery, CreateInventoryItemInput, @@ -7,9 +5,11 @@ import { FindConfig, IEventBusService, InventoryItemDTO, - TransactionBaseService, } from "@medusajs/medusa" - +import { SharedContext } from "@medusajs/types" +import { InjectEntityManager, MedusaContext } from "@medusajs/utils" +import { isDefined, MedusaError } from "medusa-core-utils" +import { DeepPartial, EntityManager, FindManyOptions } from "typeorm" import { InventoryItem } from "../models" import { getListQuery } from "../utils/query" @@ -18,18 +18,18 @@ type InjectedDependencies = { manager: EntityManager } -export default class InventoryItemService extends TransactionBaseService { +export default class InventoryItemService { static Events = { CREATED: "inventory-item.created", UPDATED: "inventory-item.updated", DELETED: "inventory-item.deleted", } - protected readonly eventBusService_: IEventBusService - - constructor({ eventBusService }: InjectedDependencies) { - super(arguments[0]) + protected readonly manager_: EntityManager + protected readonly eventBusService_: IEventBusService | undefined + constructor({ eventBusService, manager }: InjectedDependencies) { + this.manager_ = manager this.eventBusService_ = eventBusService } @@ -38,11 +38,17 @@ export default class InventoryItemService extends TransactionBaseService { * @param config - Configuration for query. * @return Resolves to the list of inventory items that match the filter. */ + @InjectEntityManager() async list( selector: FilterableInventoryItemProps = {}, - config: FindConfig = { relations: [], skip: 0, take: 10 } + config: FindConfig = { relations: [], skip: 0, take: 10 }, + @MedusaContext() context: SharedContext = {} ): Promise { - const queryBuilder = getListQuery(this.activeManager_, selector, config) + const queryBuilder = getListQuery( + context.transactionManager!, + selector, + config + ) return await queryBuilder.getMany() } @@ -51,11 +57,17 @@ export default class InventoryItemService extends TransactionBaseService { * @param config - Configuration for query. * @return - Resolves to the list of inventory items that match the filter and the count of all matching items. */ + @InjectEntityManager() async listAndCount( selector: FilterableInventoryItemProps = {}, - config: FindConfig = { relations: [], skip: 0, take: 10 } + config: FindConfig = { relations: [], skip: 0, take: 10 }, + @MedusaContext() context: SharedContext = {} ): Promise<[InventoryItemDTO[], number]> { - const queryBuilder = getListQuery(this.activeManager_, selector, config) + const queryBuilder = getListQuery( + context.transactionManager!, + selector, + config + ) return await queryBuilder.getManyAndCount() } @@ -66,9 +78,11 @@ export default class InventoryItemService extends TransactionBaseService { * @return The retrieved inventory item. * @throws If the inventory item id is not defined or if the inventory item is not found. */ + @InjectEntityManager() async retrieve( inventoryItemId: string, - config: FindConfig = {} + config: FindConfig = {}, + @MedusaContext() context: SharedContext = {} ): Promise { if (!isDefined(inventoryItemId)) { throw new MedusaError( @@ -77,7 +91,7 @@ export default class InventoryItemService extends TransactionBaseService { ) } - const manager = this.activeManager_ + const manager = context.transactionManager! const itemRepository = manager.getRepository(InventoryItem) const query = buildQuery({ id: inventoryItemId }, config) as FindManyOptions @@ -97,34 +111,35 @@ export default class InventoryItemService extends TransactionBaseService { * @param input - Input for creating a new inventory item. * @return The newly created inventory item. */ - async create(data: CreateInventoryItemInput): Promise { - return await this.atomicPhase_(async (manager) => { - const itemRepository = manager.getRepository(InventoryItem) + @InjectEntityManager() + async create( + data: CreateInventoryItemInput, + @MedusaContext() context: SharedContext = {} + ): Promise { + const manager = context.transactionManager! + const itemRepository = manager.getRepository(InventoryItem) - const inventoryItem = itemRepository.create({ - sku: data.sku, - origin_country: data.origin_country, - metadata: data.metadata, - hs_code: data.hs_code, - mid_code: data.mid_code, - material: data.material, - weight: data.weight, - length: data.length, - height: data.height, - width: data.width, - requires_shipping: data.requires_shipping, - }) - - const result = await itemRepository.save(inventoryItem) - - await this.eventBusService_ - .withTransaction(manager) - .emit(InventoryItemService.Events.CREATED, { - id: result.id, - }) - - return result + const inventoryItem = itemRepository.create({ + sku: data.sku, + origin_country: data.origin_country, + metadata: data.metadata, + hs_code: data.hs_code, + mid_code: data.mid_code, + material: data.material, + weight: data.weight, + length: data.length, + height: data.height, + width: data.width, + requires_shipping: data.requires_shipping, }) + + const result = await itemRepository.save(inventoryItem) + + await this.eventBusService_?.emit?.(InventoryItemService.Events.CREATED, { + id: result.id, + }) + + return result } /** @@ -132,51 +147,51 @@ export default class InventoryItemService extends TransactionBaseService { * @param update - The updates to apply to the inventory item. * @return The updated inventory item. */ + @InjectEntityManager() async update( inventoryItemId: string, data: Omit< DeepPartial, "id" | "created_at" | "metadata" | "deleted_at" - > + >, + @MedusaContext() context: SharedContext = {} ): Promise { - return await this.atomicPhase_(async (manager) => { - const itemRepository = manager.getRepository(InventoryItem) + const manager = context.transactionManager! + const itemRepository = manager.getRepository(InventoryItem) - const item = await this.retrieve(inventoryItemId) + const item = await this.retrieve(inventoryItemId, undefined, context) - const shouldUpdate = Object.keys(data).some((key) => { - return item[key] !== data[key] - }) - - if (shouldUpdate) { - itemRepository.merge(item, data) - await itemRepository.save(item) - - await this.eventBusService_ - .withTransaction(manager) - .emit(InventoryItemService.Events.UPDATED, { - id: item.id, - }) - } - - return item + const shouldUpdate = Object.keys(data).some((key) => { + return item[key] !== data[key] }) + + if (shouldUpdate) { + itemRepository.merge(item, data) + await itemRepository.save(item) + + await this.eventBusService_?.emit?.(InventoryItemService.Events.UPDATED, { + id: item.id, + }) + } + + return item } /** * @param inventoryItemId - The id of the inventory item to delete. */ - async delete(inventoryItemId: string): Promise { - await this.atomicPhase_(async (manager) => { - const itemRepository = manager.getRepository(InventoryItem) + @InjectEntityManager() + async delete( + inventoryItemId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + const manager = context.transactionManager! + const itemRepository = manager.getRepository(InventoryItem) - await itemRepository.softRemove({ id: inventoryItemId }) + await itemRepository.softRemove({ id: inventoryItemId }) - await this.eventBusService_ - .withTransaction(manager) - .emit(InventoryItemService.Events.DELETED, { - id: inventoryItemId, - }) + await this.eventBusService_?.emit?.(InventoryItemService.Events.DELETED, { + id: inventoryItemId, }) } } diff --git a/packages/inventory/src/services/inventory-level.ts b/packages/inventory/src/services/inventory-level.ts index eee5337320..c9fa49d0f3 100644 --- a/packages/inventory/src/services/inventory-level.ts +++ b/packages/inventory/src/services/inventory-level.ts @@ -1,14 +1,14 @@ -import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm" -import { isDefined, MedusaError } from "medusa-core-utils" import { buildQuery, CreateInventoryLevelInput, FilterableInventoryLevelProps, FindConfig, IEventBusService, - TransactionBaseService, } from "@medusajs/medusa" - +import { SharedContext } from "@medusajs/types" +import { InjectEntityManager, MedusaContext } from "@medusajs/utils" +import { isDefined, MedusaError } from "medusa-core-utils" +import { DeepPartial, EntityManager, FindManyOptions, In } from "typeorm" import { InventoryLevel } from "../models" type InjectedDependencies = { @@ -16,18 +16,18 @@ type InjectedDependencies = { manager: EntityManager } -export default class InventoryLevelService extends TransactionBaseService { +export default class InventoryLevelService { static Events = { CREATED: "inventory-level.created", UPDATED: "inventory-level.updated", DELETED: "inventory-level.deleted", } - protected readonly eventBusService_: IEventBusService - - constructor({ eventBusService }: InjectedDependencies) { - super(arguments[0]) + protected readonly manager_: EntityManager + protected readonly eventBusService_: IEventBusService | undefined + constructor({ eventBusService, manager }: InjectedDependencies) { + this.manager_ = manager this.eventBusService_ = eventBusService } @@ -37,11 +37,13 @@ export default class InventoryLevelService extends TransactionBaseService { * @param config - An object containing configuration options for the query. * @return Array of inventory levels. */ + @InjectEntityManager() async list( selector: FilterableInventoryLevelProps = {}, - config: FindConfig = { relations: [], skip: 0, take: 10 } + config: FindConfig = { relations: [], skip: 0, take: 10 }, + @MedusaContext() context: SharedContext = {} ): Promise { - const manager = this.activeManager_ + const manager = context.transactionManager! const levelRepository = manager.getRepository(InventoryLevel) const query = buildQuery(selector, config) as FindManyOptions @@ -54,11 +56,13 @@ export default class InventoryLevelService extends TransactionBaseService { * @param config - An object containing configuration options for the query. * @return An array of inventory levels and a count. */ + @InjectEntityManager() async listAndCount( selector: FilterableInventoryLevelProps = {}, - config: FindConfig = { relations: [], skip: 0, take: 10 } + config: FindConfig = { relations: [], skip: 0, take: 10 }, + @MedusaContext() context: SharedContext = {} ): Promise<[InventoryLevel[], number]> { - const manager = this.activeManager_ + const manager = context.transactionManager! const levelRepository = manager.getRepository(InventoryLevel) const query = buildQuery(selector, config) as FindManyOptions @@ -72,9 +76,11 @@ export default class InventoryLevelService extends TransactionBaseService { * @return A inventory level. * @throws If the inventory level ID is not defined or the given ID was not found. */ + @InjectEntityManager() async retrieve( inventoryLevelId: string, - config: FindConfig = {} + config: FindConfig = {}, + @MedusaContext() context: SharedContext = {} ): Promise { if (!isDefined(inventoryLevelId)) { throw new MedusaError( @@ -83,10 +89,13 @@ export default class InventoryLevelService extends TransactionBaseService { ) } - const manager = this.activeManager_ + const manager = context.transactionManager! const levelRepository = manager.getRepository(InventoryLevel) - const query = buildQuery({ id: inventoryLevelId }, config) as FindManyOptions + const query = buildQuery( + { id: inventoryLevelId }, + config + ) as FindManyOptions const [inventoryLevel] = await levelRepository.find(query) if (!inventoryLevel) { @@ -104,27 +113,29 @@ export default class InventoryLevelService extends TransactionBaseService { * @param data - An object containing the properties for the new inventory level. * @return The created inventory level. */ - async create(data: CreateInventoryLevelInput): Promise { - return await this.atomicPhase_(async (manager) => { - const levelRepository = manager.getRepository(InventoryLevel) + @InjectEntityManager() + async create( + data: CreateInventoryLevelInput, + @MedusaContext() context: SharedContext = {} + ): Promise { + const manager = context.transactionManager! - const inventoryLevel = levelRepository.create({ - location_id: data.location_id, - inventory_item_id: data.inventory_item_id, - stocked_quantity: data.stocked_quantity, - reserved_quantity: data.reserved_quantity, - incoming_quantity: data.incoming_quantity, - }) + const levelRepository = manager.getRepository(InventoryLevel) - const saved = await levelRepository.save(inventoryLevel) - await this.eventBusService_ - .withTransaction(manager) - .emit(InventoryLevelService.Events.CREATED, { - id: saved.id, - }) - - return saved + const inventoryLevel = levelRepository.create({ + location_id: data.location_id, + inventory_item_id: data.inventory_item_id, + stocked_quantity: data.stocked_quantity, + reserved_quantity: data.reserved_quantity, + incoming_quantity: data.incoming_quantity, }) + + const saved = await levelRepository.save(inventoryLevel) + await this.eventBusService_?.emit?.(InventoryLevelService.Events.CREATED, { + id: saved.id, + }) + + return saved } /** @@ -134,35 +145,37 @@ export default class InventoryLevelService extends TransactionBaseService { * @return The updated inventory level. * @throws If the inventory level ID is not defined or the given ID was not found. */ + @InjectEntityManager() async update( inventoryLevelId: string, data: Omit< DeepPartial, "id" | "created_at" | "metadata" | "deleted_at" - > + >, + @MedusaContext() context: SharedContext = {} ): Promise { - return await this.atomicPhase_(async (manager) => { - const levelRepository = manager.getRepository(InventoryLevel) + const manager = context.transactionManager! + const levelRepository = manager.getRepository(InventoryLevel) - const item = await this.retrieve(inventoryLevelId) + const item = await this.retrieve(inventoryLevelId, undefined, context) - const shouldUpdate = Object.keys(data).some((key) => { - return item[key] !== data[key] - }) - - if (shouldUpdate) { - levelRepository.merge(item, data) - await levelRepository.save(item) - - await this.eventBusService_ - .withTransaction(manager) - .emit(InventoryLevelService.Events.UPDATED, { - id: item.id, - }) - } - - return item + const shouldUpdate = Object.keys(data).some((key) => { + return item[key] !== data[key] }) + + if (shouldUpdate) { + levelRepository.merge(item, data) + await levelRepository.save(item) + + await this.eventBusService_?.emit?.( + InventoryLevelService.Events.UPDATED, + { + id: item.id, + } + ) + } + + return item } /** @@ -171,40 +184,45 @@ export default class InventoryLevelService extends TransactionBaseService { * @param locationId - The ID of the location. * @param quantity - The quantity to adjust from the reserved quantity. */ + @InjectEntityManager() async adjustReservedQuantity( inventoryItemId: string, locationId: string, - quantity: number + quantity: number, + @MedusaContext() context: SharedContext = {} ): Promise { - await this.atomicPhase_(async (manager) => { - await manager - .createQueryBuilder() - .update(InventoryLevel) - .set({ reserved_quantity: () => `reserved_quantity + ${quantity}` }) - .where( - "inventory_item_id = :inventoryItemId AND location_id = :locationId", - { inventoryItemId, locationId } - ) - .execute() - }) + const manager = context.transactionManager! + await manager + .createQueryBuilder() + .update(InventoryLevel) + .set({ reserved_quantity: () => `reserved_quantity + ${quantity}` }) + .where( + "inventory_item_id = :inventoryItemId AND location_id = :locationId", + { inventoryItemId, locationId } + ) + .execute() } /** - * Deletes inventory levels by inventory Item ID. - * @param inventoryItemId - The ID or IDs of the inventory item to delete inventory levels for. - */ - async deleteByInventoryItemId(inventoryItemId: string | string[]): Promise { - const ids = Array.isArray(inventoryItemId) ? inventoryItemId : [inventoryItemId] - await this.atomicPhase_(async (manager) => { - const levelRepository = manager.getRepository(InventoryLevel) + * Deletes inventory levels by inventory Item ID. + * @param inventoryItemId - The ID or IDs of the inventory item to delete inventory levels for. + */ + @InjectEntityManager() + async deleteByInventoryItemId( + inventoryItemId: string | string[], + @MedusaContext() context: SharedContext = {} + ): Promise { + const ids = Array.isArray(inventoryItemId) + ? inventoryItemId + : [inventoryItemId] - await levelRepository.delete({ inventory_item_id: In(ids) }) + const manager = context.transactionManager! + const levelRepository = manager.getRepository(InventoryLevel) - await this.eventBusService_ - .withTransaction(manager) - .emit(InventoryLevelService.Events.DELETED, { - inventory_item_id: inventoryItemId, - }) + await levelRepository.delete({ inventory_item_id: In(ids) }) + + await this.eventBusService_?.emit?.(InventoryLevelService.Events.DELETED, { + inventory_item_id: inventoryItemId, }) } @@ -212,18 +230,22 @@ export default class InventoryLevelService extends TransactionBaseService { * Deletes an inventory level by ID. * @param inventoryLevelId - The ID or IDs of the inventory level to delete. */ - async delete(inventoryLevelId: string | string[]): Promise { - const ids = Array.isArray(inventoryLevelId) ? inventoryLevelId : [inventoryLevelId] - await this.atomicPhase_(async (manager) => { - const levelRepository = manager.getRepository(InventoryLevel) + @InjectEntityManager() + async delete( + inventoryLevelId: string | string[], + @MedusaContext() context: SharedContext = {} + ): Promise { + const ids = Array.isArray(inventoryLevelId) + ? inventoryLevelId + : [inventoryLevelId] - await levelRepository.delete({ id: In(ids) }) + const manager = context.transactionManager! + const levelRepository = manager.getRepository(InventoryLevel) - await this.eventBusService_ - .withTransaction(manager) - .emit(InventoryLevelService.Events.DELETED, { - id: inventoryLevelId, - }) + await levelRepository.delete({ id: In(ids) }) + + await this.eventBusService_?.emit?.(InventoryLevelService.Events.DELETED, { + id: inventoryLevelId, }) } @@ -231,17 +253,18 @@ export default class InventoryLevelService extends TransactionBaseService { * Deletes inventory levels by location ID. * @param locationId - The ID of the location to delete inventory levels for. */ - async deleteByLocationId(locationId: string): Promise { - return await this.atomicPhase_(async (manager) => { - const levelRepository = manager.getRepository(InventoryLevel) + @InjectEntityManager() + async deleteByLocationId( + locationId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + const manager = context.transactionManager! + const levelRepository = manager.getRepository(InventoryLevel) - await levelRepository.delete({ location_id: locationId }) + await levelRepository.delete({ location_id: locationId }) - await this.eventBusService_ - .withTransaction(manager) - .emit(InventoryLevelService.Events.DELETED, { - location_id: locationId, - }) + await this.eventBusService_?.emit?.(InventoryLevelService.Events.DELETED, { + location_id: locationId, }) } @@ -251,15 +274,17 @@ export default class InventoryLevelService extends TransactionBaseService { * @param locationIds - The IDs of the locations. * @return The total stocked quantity. */ + @InjectEntityManager() async getStockedQuantity( inventoryItemId: string, - locationIds: string[] | string + locationIds: string[] | string, + @MedusaContext() context: SharedContext = {} ): Promise { if (!Array.isArray(locationIds)) { locationIds = [locationIds] } - const manager = this.activeManager_ + const manager = context.transactionManager! const levelRepository = manager.getRepository(InventoryLevel) const result = await levelRepository @@ -278,15 +303,17 @@ export default class InventoryLevelService extends TransactionBaseService { * @param locationIds - The IDs of the locations. * @return The total available quantity. */ + @InjectEntityManager() async getAvailableQuantity( inventoryItemId: string, - locationIds: string[] | string + locationIds: string[] | string, + @MedusaContext() context: SharedContext = {} ): Promise { if (!Array.isArray(locationIds)) { locationIds = [locationIds] } - const manager = this.activeManager_ + const manager = context.transactionManager! const levelRepository = manager.getRepository(InventoryLevel) const result = await levelRepository @@ -305,15 +332,17 @@ export default class InventoryLevelService extends TransactionBaseService { * @param locationIds - The IDs of the locations. * @return The total reserved quantity. */ + @InjectEntityManager() async getReservedQuantity( inventoryItemId: string, - locationIds: string[] | string + locationIds: string[] | string, + @MedusaContext() context: SharedContext = {} ): Promise { if (!Array.isArray(locationIds)) { locationIds = [locationIds] } - const manager = this.activeManager_ + const manager = context.transactionManager! const levelRepository = manager.getRepository(InventoryLevel) const result = await levelRepository diff --git a/packages/inventory/src/services/inventory.ts b/packages/inventory/src/services/inventory.ts index 22fb3cd14f..a8629073a1 100644 --- a/packages/inventory/src/services/inventory.ts +++ b/packages/inventory/src/services/inventory.ts @@ -13,18 +13,18 @@ import { InventoryItemDTO, InventoryLevelDTO, ReservationItemDTO, - TransactionBaseService, UpdateInventoryLevelInput, UpdateReservationItemInput, } from "@medusajs/medusa" +import { SharedContext } from "@medusajs/types" +import { InjectEntityManager, MedusaContext } from "@medusajs/utils" import { MedusaError } from "medusa-core-utils" - +import { EntityManager } from "typeorm" import { InventoryItemService, InventoryLevelService, ReservationItemService, } from "./" -import { EntityManager } from "typeorm" type InjectedDependencies = { manager: EntityManager @@ -33,11 +33,9 @@ type InjectedDependencies = { inventoryLevelService: InventoryLevelService reservationItemService: ReservationItemService } -export default class InventoryService - extends TransactionBaseService - implements IInventoryService -{ - protected readonly eventBusService_: IEventBusService +export default class InventoryService implements IInventoryService { + protected readonly manager_: EntityManager + protected readonly eventBusService_: IEventBusService | undefined protected readonly inventoryItemService_: InventoryItemService protected readonly reservationItemService_: ReservationItemService protected readonly inventoryLevelService_: InventoryLevelService @@ -53,9 +51,7 @@ export default class InventoryService options?: unknown, moduleDeclaration?: InternalModuleDeclaration ) { - // @ts-ignore - super(...arguments) - + this.manager_ = manager this.eventBusService_ = eventBusService this.inventoryItemService_ = inventoryItemService this.inventoryLevelService_ = inventoryLevelService @@ -68,13 +64,17 @@ export default class InventoryService * @param config - the find configuration to use * @return A tuple of inventory items and their total count */ + @InjectEntityManager() async listInventoryItems( selector: FilterableInventoryItemProps, - config: FindConfig = { relations: [], skip: 0, take: 10 } + config: FindConfig = { relations: [], skip: 0, take: 10 }, + @MedusaContext() context: SharedContext = {} ): Promise<[InventoryItemDTO[], number]> { - return await this.inventoryItemService_ - .withTransaction(this.activeManager_) - .listAndCount(selector, config) + return await this.inventoryItemService_.listAndCount( + selector, + config, + context + ) } /** @@ -83,13 +83,21 @@ export default class InventoryService * @param config - the find configuration to use * @return A tuple of inventory levels and their total count */ + @InjectEntityManager() async listInventoryLevels( selector: FilterableInventoryLevelProps, - config: FindConfig = { relations: [], skip: 0, take: 10 } + config: FindConfig = { + relations: [], + skip: 0, + take: 10, + }, + @MedusaContext() context: SharedContext = {} ): Promise<[InventoryLevelDTO[], number]> { - return await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .listAndCount(selector, config) + return await this.inventoryLevelService_.listAndCount( + selector, + config, + context + ) } /** @@ -98,17 +106,21 @@ export default class InventoryService * @param config - the find configuration to use * @return A tuple of reservation items and their total count */ + @InjectEntityManager() async listReservationItems( selector: FilterableReservationItemProps, config: FindConfig = { relations: [], skip: 0, take: 10, - } + }, + @MedusaContext() context: SharedContext = {} ): Promise<[ReservationItemDTO[], number]> { - return await this.reservationItemService_ - .withTransaction(this.activeManager_) - .listAndCount(selector, config) + return await this.reservationItemService_.listAndCount( + selector, + config, + context + ) } /** @@ -117,13 +129,17 @@ export default class InventoryService * @param config - the find configuration to use * @return The retrieved inventory item */ + @InjectEntityManager() async retrieveInventoryItem( inventoryItemId: string, - config?: FindConfig + config?: FindConfig, + @MedusaContext() context: SharedContext = {} ): Promise { - const inventoryItem = await this.inventoryItemService_ - .withTransaction(this.activeManager_) - .retrieve(inventoryItemId, config) + const inventoryItem = await this.inventoryItemService_.retrieve( + inventoryItemId, + config, + context + ) return { ...inventoryItem } } @@ -133,16 +149,17 @@ export default class InventoryService * @param locationId - the id of the location * @return the retrieved inventory level */ + @InjectEntityManager() async retrieveInventoryLevel( inventoryItemId: string, - locationId: string + locationId: string, + @MedusaContext() context: SharedContext = {} ): Promise { - const [inventoryLevel] = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .list( - { inventory_item_id: inventoryItemId, location_id: locationId }, - { take: 1 } - ) + const [inventoryLevel] = await this.inventoryLevelService_.list( + { inventory_item_id: inventoryItemId, location_id: locationId }, + { take: 1 }, + context + ) if (!inventoryLevel) { throw new MedusaError( MedusaError.Types.NOT_FOUND, @@ -152,15 +169,21 @@ export default class InventoryService return inventoryLevel } - /** + /** * Retrieves a reservation item * @param inventoryItemId - the id of the reservation item * @return the retrieved reservation level */ - async retrieveReservationItem(reservationId: string): Promise { - return await this.reservationItemService_ - .withTransaction(this.activeManager_) - .retrieve(reservationId) + @InjectEntityManager() + async retrieveReservationItem( + reservationId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + return await this.reservationItemService_.retrieve( + reservationId, + undefined, + context + ) } /** @@ -168,19 +191,20 @@ export default class InventoryService * @param input - the input object * @return The created reservation item */ + @InjectEntityManager() async createReservationItem( - input: CreateReservationItemInput + input: CreateReservationItemInput, + @MedusaContext() context: SharedContext = {} ): Promise { // Verify that the item is stocked at the location - const [inventoryLevel] = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .list( - { - inventory_item_id: input.inventory_item_id, - location_id: input.location_id, - }, - { take: 1 } - ) + const [inventoryLevel] = await this.inventoryLevelService_.list( + { + inventory_item_id: input.inventory_item_id, + location_id: input.location_id, + }, + { take: 1 }, + context + ) if (!inventoryLevel) { throw new MedusaError( @@ -189,9 +213,10 @@ export default class InventoryService ) } - const reservationItem = await this.reservationItemService_ - .withTransaction(this.activeManager_) - .create(input) + const reservationItem = await this.reservationItemService_.create( + input, + context + ) return { ...reservationItem } } @@ -201,12 +226,15 @@ export default class InventoryService * @param input - the input object * @return The created inventory item */ + @InjectEntityManager() async createInventoryItem( - input: CreateInventoryItemInput + input: CreateInventoryItemInput, + @MedusaContext() context: SharedContext = {} ): Promise { - const inventoryItem = await this.inventoryItemService_ - .withTransaction(this.activeManager_) - .create(input) + const inventoryItem = await this.inventoryItemService_.create( + input, + context + ) return { ...inventoryItem } } @@ -215,12 +243,12 @@ export default class InventoryService * @param input - the input object * @return The created inventory level */ + @InjectEntityManager() async createInventoryLevel( - input: CreateInventoryLevelInput + input: CreateInventoryLevelInput, + @MedusaContext() context: SharedContext = {} ): Promise { - return await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .create(input) + return await this.inventoryLevelService_.create(input, context) } /** @@ -229,13 +257,17 @@ export default class InventoryService * @param input - the input object * @return The updated inventory item */ + @InjectEntityManager() async updateInventoryItem( inventoryItemId: string, - input: Partial + input: Partial, + @MedusaContext() context: SharedContext = {} ): Promise { - const inventoryItem = await this.inventoryItemService_ - .withTransaction(this.activeManager_) - .update(inventoryItemId, input) + const inventoryItem = await this.inventoryItemService_.update( + inventoryItemId, + input, + context + ) return { ...inventoryItem } } @@ -243,26 +275,39 @@ export default class InventoryService * Deletes an inventory item * @param inventoryItemId - the id of the inventory item to delete */ - async deleteInventoryItem(inventoryItemId: string): Promise { - await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .deleteByInventoryItemId(inventoryItemId) + @InjectEntityManager() + async deleteInventoryItem( + inventoryItemId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + await this.inventoryLevelService_.deleteByInventoryItemId( + inventoryItemId, + context + ) - return await this.inventoryItemService_ - .withTransaction(this.activeManager_) - .delete(inventoryItemId) + return await this.inventoryItemService_.delete(inventoryItemId, context) } - async deleteInventoryItemLevelByLocationId(locationId: string): Promise { - return await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .deleteByLocationId(locationId) + @InjectEntityManager() + async deleteInventoryItemLevelByLocationId( + locationId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + return await this.inventoryLevelService_.deleteByLocationId( + locationId, + context + ) } - async deleteReservationItemByLocationId(locationId: string): Promise { - return await this.reservationItemService_ - .withTransaction(this.activeManager_) - .deleteByLocationId(locationId) + @InjectEntityManager() + async deleteReservationItemByLocationId( + locationId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + return await this.reservationItemService_.deleteByLocationId( + locationId, + context + ) } /** @@ -270,24 +315,23 @@ export default class InventoryService * @param inventoryItemId - the id of the inventory item associated with the level * @param locationId - the id of the location associated with the level */ + @InjectEntityManager() async deleteInventoryLevel( inventoryItemId: string, - locationId: string + locationId: string, + @MedusaContext() context: SharedContext = {} ): Promise { - const [inventoryLevel] = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .list( - { inventory_item_id: inventoryItemId, location_id: locationId }, - { take: 1 } - ) + const [inventoryLevel] = await this.inventoryLevelService_.list( + { inventory_item_id: inventoryItemId, location_id: locationId }, + { take: 1 }, + context + ) if (!inventoryLevel) { return } - return await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .delete(inventoryLevel.id) + return await this.inventoryLevelService_.delete(inventoryLevel.id, context) } /** @@ -297,17 +341,18 @@ export default class InventoryService * @param input - the input object * @return The updated inventory level */ + @InjectEntityManager() async updateInventoryLevel( inventoryItemId: string, locationId: string, - input: UpdateInventoryLevelInput + input: UpdateInventoryLevelInput, + @MedusaContext() context: SharedContext = {} ): Promise { - const [inventoryLevel] = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .list( - { inventory_item_id: inventoryItemId, location_id: locationId }, - { take: 1 } - ) + const [inventoryLevel] = await this.inventoryLevelService_.list( + { inventory_item_id: inventoryItemId, location_id: locationId }, + { take: 1 }, + context + ) if (!inventoryLevel) { throw new MedusaError( @@ -316,9 +361,11 @@ export default class InventoryService ) } - return await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .update(inventoryLevel.id, input) + return await this.inventoryLevelService_.update( + inventoryLevel.id, + input, + context + ) } /** @@ -327,33 +374,44 @@ export default class InventoryService * @param input - the input object * @return The updated inventory level */ + @InjectEntityManager() async updateReservationItem( reservationItemId: string, - input: UpdateReservationItemInput + input: UpdateReservationItemInput, + @MedusaContext() context: SharedContext = {} ): Promise { - return await this.reservationItemService_ - .withTransaction(this.activeManager_) - .update(reservationItemId, input) + return await this.reservationItemService_.update( + reservationItemId, + input, + context + ) } /** * Deletes reservation items by line item * @param lineItemId - the id of the line item associated with the reservation item */ - async deleteReservationItemsByLineItem(lineItemId: string): Promise { - return await this.reservationItemService_ - .withTransaction(this.activeManager_) - .deleteByLineItem(lineItemId) + @InjectEntityManager() + async deleteReservationItemsByLineItem( + lineItemId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + return await this.reservationItemService_.deleteByLineItem( + lineItemId, + context + ) } /** * Deletes a reservation item * @param reservationItemId - the id of the reservation item to delete */ - async deleteReservationItem(reservationItemId: string | string[]): Promise { - return await this.reservationItemService_ - .withTransaction(this.activeManager_) - .delete(reservationItemId) + @InjectEntityManager() + async deleteReservationItem( + reservationItemId: string | string[], + @MedusaContext() context: SharedContext = {} + ): Promise { + return await this.reservationItemService_.delete(reservationItemId, context) } /** @@ -364,17 +422,18 @@ export default class InventoryService * @return The updated inventory level * @throws when the inventory level is not found */ + @InjectEntityManager() async adjustInventory( inventoryItemId: string, locationId: string, - adjustment: number + adjustment: number, + @MedusaContext() context: SharedContext = {} ): Promise { - const [inventoryLevel] = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .list( - { inventory_item_id: inventoryItemId, location_id: locationId }, - { take: 1 } - ) + const [inventoryLevel] = await this.inventoryLevelService_.list( + { inventory_item_id: inventoryItemId, location_id: locationId }, + { take: 1 }, + context + ) if (!inventoryLevel) { throw new MedusaError( MedusaError.Types.NOT_FOUND, @@ -382,11 +441,13 @@ export default class InventoryService ) } - const updatedInventoryLevel = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .update(inventoryLevel.id, { + const updatedInventoryLevel = await this.inventoryLevelService_.update( + inventoryLevel.id, + { stocked_quantity: inventoryLevel.stocked_quantity + adjustment, - }) + }, + context + ) return { ...updatedInventoryLevel } } @@ -398,20 +459,27 @@ export default class InventoryService * @return The available quantity * @throws when the inventory item is not found */ + @InjectEntityManager() async retrieveAvailableQuantity( inventoryItemId: string, - locationIds: string[] + locationIds: string[], + @MedusaContext() context: SharedContext = {} ): Promise { // Throws if item does not exist - await this.inventoryItemService_ - .withTransaction(this.activeManager_) - .retrieve(inventoryItemId, { + await this.inventoryItemService_.retrieve( + inventoryItemId, + { select: ["id"], - }) + }, + context + ) - const availableQuantity = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .getAvailableQuantity(inventoryItemId, locationIds) + const availableQuantity = + await this.inventoryLevelService_.getAvailableQuantity( + inventoryItemId, + locationIds, + context + ) return availableQuantity } @@ -423,20 +491,27 @@ export default class InventoryService * @return The stocked quantity * @throws when the inventory item is not found */ + @InjectEntityManager() async retrieveStockedQuantity( inventoryItemId: string, - locationIds: string[] + locationIds: string[], + @MedusaContext() context: SharedContext = {} ): Promise { // Throws if item does not exist - await this.inventoryItemService_ - .withTransaction(this.activeManager_) - .retrieve(inventoryItemId, { + await this.inventoryItemService_.retrieve( + inventoryItemId, + { select: ["id"], - }) + }, + context + ) - const stockedQuantity = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .getStockedQuantity(inventoryItemId, locationIds) + const stockedQuantity = + await this.inventoryLevelService_.getStockedQuantity( + inventoryItemId, + locationIds, + context + ) return stockedQuantity } @@ -448,20 +523,27 @@ export default class InventoryService * @return The reserved quantity * @throws when the inventory item is not found */ + @InjectEntityManager() async retrieveReservedQuantity( inventoryItemId: string, - locationIds: string[] + locationIds: string[], + @MedusaContext() context: SharedContext = {} ): Promise { // Throws if item does not exist - await this.inventoryItemService_ - .withTransaction(this.activeManager_) - .retrieve(inventoryItemId, { + await this.inventoryItemService_.retrieve( + inventoryItemId, + { select: ["id"], - }) + }, + context + ) - const reservedQuantity = await this.inventoryLevelService_ - .withTransaction(this.activeManager_) - .getReservedQuantity(inventoryItemId, locationIds) + const reservedQuantity = + await this.inventoryLevelService_.getReservedQuantity( + inventoryItemId, + locationIds, + context + ) return reservedQuantity } @@ -473,14 +555,17 @@ export default class InventoryService * @param quantity - the quantity to check * @return Whether there is sufficient inventory */ + @InjectEntityManager() async confirmInventory( inventoryItemId: string, locationIds: string[], - quantity: number + quantity: number, + @MedusaContext() context: SharedContext = {} ): Promise { const availableQuantity = await this.retrieveAvailableQuantity( inventoryItemId, - locationIds + locationIds, + context ) return availableQuantity >= quantity } diff --git a/packages/inventory/src/services/reservation-item.ts b/packages/inventory/src/services/reservation-item.ts index fd6703d57e..406c911af0 100644 --- a/packages/inventory/src/services/reservation-item.ts +++ b/packages/inventory/src/services/reservation-item.ts @@ -1,17 +1,17 @@ -import { EntityManager, FindManyOptions } from "typeorm" -import { isDefined, MedusaError } from "medusa-core-utils" import { buildQuery, CreateReservationItemInput, FilterableReservationItemProps, FindConfig, IEventBusService, - TransactionBaseService, UpdateReservationItemInput, } from "@medusajs/medusa" - -import { ReservationItem } from "../models" +import { SharedContext } from "@medusajs/types" +import { InjectEntityManager, MedusaContext } from "@medusajs/utils" +import { isDefined, MedusaError } from "medusa-core-utils" +import { EntityManager, FindManyOptions } from "typeorm" import { InventoryLevelService } from "." +import { ReservationItem } from "../models" type InjectedDependencies = { eventBusService: IEventBusService @@ -19,22 +19,23 @@ type InjectedDependencies = { inventoryLevelService: InventoryLevelService } -export default class ReservationItemService extends TransactionBaseService { +export default class ReservationItemService { static Events = { CREATED: "reservation-item.created", UPDATED: "reservation-item.updated", DELETED: "reservation-item.deleted", } - protected readonly eventBusService_: IEventBusService + protected readonly manager_: EntityManager + protected readonly eventBusService_: IEventBusService | undefined protected readonly inventoryLevelService_: InventoryLevelService constructor({ eventBusService, inventoryLevelService, + manager, }: InjectedDependencies) { - super(arguments[0]) - + this.manager_ = manager this.eventBusService_ = eventBusService this.inventoryLevelService_ = inventoryLevelService } @@ -45,11 +46,13 @@ export default class ReservationItemService extends TransactionBaseService { * @param config - Configuration for the query. * @return Array of reservation items that match the selector. */ + @InjectEntityManager() async list( selector: FilterableReservationItemProps = {}, - config: FindConfig = { relations: [], skip: 0, take: 10 } + config: FindConfig = { relations: [], skip: 0, take: 10 }, + @MedusaContext() context: SharedContext = {} ): Promise { - const manager = this.activeManager_ + const manager = context.transactionManager! const itemRepository = manager.getRepository(ReservationItem) const query = buildQuery(selector, config) as FindManyOptions @@ -62,11 +65,13 @@ export default class ReservationItemService extends TransactionBaseService { * @param config - Configuration for the query. * @return Array of reservation items that match the selector and the total count. */ + @InjectEntityManager() async listAndCount( selector: FilterableReservationItemProps = {}, - config: FindConfig = { relations: [], skip: 0, take: 10 } + config: FindConfig = { relations: [], skip: 0, take: 10 }, + @MedusaContext() context: SharedContext = {} ): Promise<[ReservationItem[], number]> { - const manager = this.activeManager_ + const manager = context.transactionManager! const itemRepository = manager.getRepository(ReservationItem) const query = buildQuery(selector, config) as FindManyOptions @@ -80,9 +85,11 @@ export default class ReservationItemService extends TransactionBaseService { * @return The reservation item with the provided id. * @throws If reservationItemId is not defined or if the reservation item was not found. */ + @InjectEntityManager() async retrieve( reservationItemId: string, - config: FindConfig = {} + config: FindConfig = {}, + @MedusaContext() context: SharedContext = {} ): Promise { if (!isDefined(reservationItemId)) { throw new MedusaError( @@ -91,7 +98,7 @@ export default class ReservationItemService extends TransactionBaseService { ) } - const manager = this.activeManager_ + const manager = context.transactionManager! const reservationItemRepository = manager.getRepository(ReservationItem) const query = buildQuery( @@ -115,37 +122,37 @@ export default class ReservationItemService extends TransactionBaseService { * @param data - The reservation item data. * @return The created reservation item. */ - async create(data: CreateReservationItemInput): Promise { - return await this.atomicPhase_(async (manager) => { - const itemRepository = manager.getRepository(ReservationItem) + @InjectEntityManager() + async create( + data: CreateReservationItemInput, + @MedusaContext() context: SharedContext = {} + ): Promise { + const manager = context.transactionManager! + const itemRepository = manager.getRepository(ReservationItem) - const inventoryItem = itemRepository.create({ - inventory_item_id: data.inventory_item_id, - line_item_id: data.line_item_id, - location_id: data.location_id, - quantity: data.quantity, - metadata: data.metadata, - }) - - const [newInventoryItem] = await Promise.all([ - itemRepository.save(inventoryItem), - this.inventoryLevelService_ - .withTransaction(manager) - .adjustReservedQuantity( - data.inventory_item_id, - data.location_id, - data.quantity - ), - ]) - - await this.eventBusService_ - .withTransaction(manager) - .emit(ReservationItemService.Events.CREATED, { - id: newInventoryItem.id, - }) - - return newInventoryItem + const inventoryItem = itemRepository.create({ + inventory_item_id: data.inventory_item_id, + line_item_id: data.line_item_id, + location_id: data.location_id, + quantity: data.quantity, + metadata: data.metadata, }) + + const [newInventoryItem] = await Promise.all([ + itemRepository.save(inventoryItem), + this.inventoryLevelService_.adjustReservedQuantity( + data.inventory_item_id, + data.location_id, + data.quantity, + context + ), + ]) + + await this.eventBusService_?.emit?.(ReservationItemService.Events.CREATED, { + id: newInventoryItem.id, + }) + + return newInventoryItem } /** @@ -154,99 +161,99 @@ export default class ReservationItemService extends TransactionBaseService { * @param data - The reservation item data to update. * @return The updated reservation item. */ + @InjectEntityManager() async update( reservationItemId: string, - data: UpdateReservationItemInput + data: UpdateReservationItemInput, + @MedusaContext() context: SharedContext = {} ): Promise { - return await this.atomicPhase_(async (manager) => { - const itemRepository = manager.getRepository(ReservationItem) + const manager = context.transactionManager! + const itemRepository = manager.getRepository(ReservationItem) - const item = await this.retrieve(reservationItemId) + const item = await this.retrieve(reservationItemId) - const shouldUpdateQuantity = - isDefined(data.quantity) && data.quantity !== item.quantity + const shouldUpdateQuantity = + isDefined(data.quantity) && data.quantity !== item.quantity - const shouldUpdateLocation = - isDefined(data.location_id) && data.location_id !== item.location_id + const shouldUpdateLocation = + isDefined(data.location_id) && data.location_id !== item.location_id - const ops: Promise[] = [] + const ops: Promise[] = [] - if (shouldUpdateLocation) { - ops.push( - this.inventoryLevelService_ - .withTransaction(manager) - .adjustReservedQuantity( - item.inventory_item_id, - item.location_id, - item.quantity * -1 - ), - this.inventoryLevelService_ - .withTransaction(manager) - .adjustReservedQuantity( - item.inventory_item_id, - data.location_id!, - data.quantity || item.quantity! - ) + if (shouldUpdateLocation) { + ops.push( + this.inventoryLevelService_.adjustReservedQuantity( + item.inventory_item_id, + item.location_id, + item.quantity * -1, + context + ), + this.inventoryLevelService_.adjustReservedQuantity( + item.inventory_item_id, + data.location_id!, + data.quantity || item.quantity!, + context ) - } else if (shouldUpdateQuantity) { - const quantityDiff = data.quantity! - item.quantity - ops.push( - this.inventoryLevelService_ - .withTransaction(manager) - .adjustReservedQuantity( - item.inventory_item_id, - item.location_id, - quantityDiff - ) + ) + } else if (shouldUpdateQuantity) { + const quantityDiff = data.quantity! - item.quantity + ops.push( + this.inventoryLevelService_.adjustReservedQuantity( + item.inventory_item_id, + item.location_id, + quantityDiff, + context ) - } + ) + } - const mergedItem = itemRepository.merge(item, data) + const mergedItem = itemRepository.merge(item, data) - ops.push(itemRepository.save(item)) + ops.push(itemRepository.save(item)) - await Promise.all(ops) + await Promise.all(ops) - await this.eventBusService_ - .withTransaction(manager) - .emit(ReservationItemService.Events.UPDATED, { - id: mergedItem.id, - }) - - return mergedItem + await this.eventBusService_?.emit?.(ReservationItemService.Events.UPDATED, { + id: mergedItem.id, }) + + return mergedItem } /** * Deletes a reservation item by line item id. * @param lineItemId - the id of the line item to delete. */ - async deleteByLineItem(lineItemId: string): Promise { - await this.atomicPhase_(async (manager) => { - const itemRepository = manager.getRepository(ReservationItem) + @InjectEntityManager() + async deleteByLineItem( + lineItemId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + const manager = context.transactionManager! + const itemRepository = manager.getRepository(ReservationItem) - const items = await this.list({ line_item_id: lineItemId }) + const items = await this.list( + { line_item_id: lineItemId }, + undefined, + context + ) - const ops: Promise[] = [] - for (const item of items) { - ops.push(itemRepository.softRemove({ line_item_id: lineItemId })) - ops.push( - this.inventoryLevelService_ - .withTransaction(manager) - .adjustReservedQuantity( - item.inventory_item_id, - item.location_id, - item.quantity * -1 - ) + const ops: Promise[] = [] + for (const item of items) { + ops.push(itemRepository.softRemove({ line_item_id: lineItemId })) + ops.push( + this.inventoryLevelService_.adjustReservedQuantity( + item.inventory_item_id, + item.location_id, + item.quantity * -1, + context ) - } - await Promise.all(ops) + ) + } + await Promise.all(ops) - await this.eventBusService_ - .withTransaction(manager) - .emit(ReservationItemService.Events.DELETED, { - line_item_id: lineItemId, - }) + await this.eventBusService_?.emit?.(ReservationItemService.Events.DELETED, { + line_item_id: lineItemId, }) } @@ -254,22 +261,23 @@ export default class ReservationItemService extends TransactionBaseService { * Deletes reservation items by location ID. * @param locationId - The ID of the location to delete reservations for. */ - async deleteByLocationId(locationId: string): Promise { - return await this.atomicPhase_(async (manager) => { - const itemRepository = manager.getRepository(ReservationItem) + @InjectEntityManager() + async deleteByLocationId( + locationId: string, + @MedusaContext() context: SharedContext = {} + ): Promise { + const manager = context.transactionManager! + const itemRepository = manager.getRepository(ReservationItem) - await itemRepository - .createQueryBuilder("reservation_item") - .softDelete() - .where("location_id = :locationId", { locationId }) - .andWhere("deleted_at IS NULL") - .execute() + await itemRepository + .createQueryBuilder("reservation_item") + .softDelete() + .where("location_id = :locationId", { locationId }) + .andWhere("deleted_at IS NULL") + .execute() - await this.eventBusService_ - .withTransaction(manager) - .emit(ReservationItemService.Events.DELETED, { - location_id: locationId, - }) + await this.eventBusService_?.emit?.(ReservationItemService.Events.DELETED, { + location_id: locationId, }) } @@ -277,35 +285,32 @@ export default class ReservationItemService extends TransactionBaseService { * Deletes a reservation item by id. * @param reservationItemId - the id of the reservation item to delete. */ - async delete(reservationItemId: string | string[]): Promise { + async delete( + reservationItemId: string | string[], + @MedusaContext() context: SharedContext = {} + ): Promise { const ids = Array.isArray(reservationItemId) ? reservationItemId : [reservationItemId] - return await this.atomicPhase_(async (manager) => { - const itemRepository = manager.getRepository(ReservationItem) + const manager = context.transactionManager! + const itemRepository = manager.getRepository(ReservationItem) + const items = await this.list({ id: ids }) - const items = await this.list({ id: ids }) - - await itemRepository.softRemove(items) - - const inventoryServiceTx = - this.inventoryLevelService_.withTransaction(manager) - - await Promise.all( - items.map(async (item) => { - return inventoryServiceTx.adjustReservedQuantity( - item.inventory_item_id, - item.location_id, - item.quantity * -1 - ) - }) + const promises: Promise[] = items.map(async (item) => { + this.inventoryLevelService_.adjustReservedQuantity( + item.inventory_item_id, + item.location_id, + item.quantity * -1, + context ) + }) - await this.eventBusService_ - .withTransaction(manager) - .emit(ReservationItemService.Events.DELETED, { - id: reservationItemId, - }) + promises.push(itemRepository.softRemove(items)) + + await Promise.all(promises) + + await this.eventBusService_?.emit?.(ReservationItemService.Events.DELETED, { + id: reservationItemId, }) } } diff --git a/packages/inventory/src/types/index.ts b/packages/inventory/src/types/index.ts new file mode 100644 index 0000000000..adaa82e6d6 --- /dev/null +++ b/packages/inventory/src/types/index.ts @@ -0,0 +1,12 @@ +import { DatabaseType } from "typeorm" + +export type InventoryServiceInitializeOptions = { + database?: { + type?: DatabaseType | string + url?: string + database?: string + extra?: Record + schema?: string + logging?: boolean + } +} diff --git a/packages/medusa/src/api/routes/admin/draft-orders/register-payment.ts b/packages/medusa/src/api/routes/admin/draft-orders/register-payment.ts index 068435f7b9..8d8b4bb026 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/register-payment.ts +++ b/packages/medusa/src/api/routes/admin/draft-orders/register-payment.ts @@ -85,9 +85,6 @@ export default async (req, res) => { const orderServiceTx = orderService.withTransaction(manager) const cartServiceTx = cartService.withTransaction(manager) - const productVariantInventoryServiceTx = - productVariantInventoryService.withTransaction(manager) - const draftOrder = await draftOrderServiceTx.retrieve(id) const cart = await cartServiceTx.retrieveWithTotals(draftOrder.cart_id) @@ -116,7 +113,7 @@ export default async (req, res) => { }) await reserveQuantityForDraftOrder(order, { - productVariantInventoryService: productVariantInventoryServiceTx, + productVariantInventoryService, }) return order diff --git a/packages/medusa/src/api/routes/admin/inventory-items/delete-inventory-item.ts b/packages/medusa/src/api/routes/admin/inventory-items/delete-inventory-item.ts index ba6e4fb76e..e69149b384 100644 --- a/packages/medusa/src/api/routes/admin/inventory-items/delete-inventory-item.ts +++ b/packages/medusa/src/api/routes/admin/inventory-items/delete-inventory-item.ts @@ -59,9 +59,7 @@ export default async (req: Request, res: Response) => { .withTransaction(transactionManager) .detachInventoryItem(id) - await inventoryService - .withTransaction(transactionManager) - .deleteInventoryItem(id) + await inventoryService.deleteInventoryItem(id) }) res.status(200).send({ diff --git a/packages/medusa/src/api/routes/admin/inventory-items/delete-location-level.ts b/packages/medusa/src/api/routes/admin/inventory-items/delete-location-level.ts index f26cb8e784..1be7b226aa 100644 --- a/packages/medusa/src/api/routes/admin/inventory-items/delete-location-level.ts +++ b/packages/medusa/src/api/routes/admin/inventory-items/delete-location-level.ts @@ -74,11 +74,7 @@ export default async (req: Request, res: Response) => { ) } - await manager.transaction(async (transactionManager) => { - await inventoryService - .withTransaction(transactionManager) - .deleteInventoryLevel(id, location_id) - }) + await inventoryService.deleteInventoryLevel(id, location_id) const inventoryItem = await inventoryService.retrieveInventoryItem( id, diff --git a/packages/medusa/src/api/routes/admin/inventory-items/update-inventory-item.ts b/packages/medusa/src/api/routes/admin/inventory-items/update-inventory-item.ts index 32d5a28749..5f2028972d 100644 --- a/packages/medusa/src/api/routes/admin/inventory-items/update-inventory-item.ts +++ b/packages/medusa/src/api/routes/admin/inventory-items/update-inventory-item.ts @@ -77,14 +77,10 @@ export default async (req: Request, res: Response) => { req.scope.resolve("inventoryService") const manager: EntityManager = req.scope.resolve("manager") - await manager.transaction(async (transactionManager) => { - await inventoryService - .withTransaction(transactionManager) - .updateInventoryItem( - id, - req.validatedBody as AdminPostInventoryItemsInventoryItemReq - ) - }) + await inventoryService.updateInventoryItem( + id, + req.validatedBody as AdminPostInventoryItemsInventoryItemReq + ) const inventoryItem = await inventoryService.retrieveInventoryItem( id, diff --git a/packages/medusa/src/api/routes/admin/inventory-items/update-location-level.ts b/packages/medusa/src/api/routes/admin/inventory-items/update-location-level.ts index 543ca67137..4730a71f84 100644 --- a/packages/medusa/src/api/routes/admin/inventory-items/update-location-level.ts +++ b/packages/medusa/src/api/routes/admin/inventory-items/update-location-level.ts @@ -81,11 +81,7 @@ export default async (req: Request, res: Response) => { const validatedBody = req.validatedBody as AdminPostInventoryItemsItemLocationLevelsLevelReq - await manager.transaction(async (transactionManager) => { - await inventoryService - .withTransaction(transactionManager) - .updateInventoryLevel(id, location_id, validatedBody) - }) + await inventoryService.updateInventoryLevel(id, location_id, validatedBody) const inventoryItem = await inventoryService.retrieveInventoryItem( id, diff --git a/packages/medusa/src/api/routes/admin/products/transaction/create-product-variant.ts b/packages/medusa/src/api/routes/admin/products/transaction/create-product-variant.ts index d3710c54b8..786507c6b5 100644 --- a/packages/medusa/src/api/routes/admin/products/transaction/create-product-variant.ts +++ b/packages/medusa/src/api/routes/admin/products/transaction/create-product-variant.ts @@ -1,3 +1,14 @@ +import { MedusaError } from "medusa-core-utils" +import { EntityManager } from "typeorm" +import { ulid } from "ulid" +import { IInventoryService } from "../../../../../interfaces" +import { ProductVariant } from "../../../../../models" +import { + ProductVariantInventoryService, + ProductVariantService, +} from "../../../../../services" +import { InventoryItemDTO } from "../../../../../types/inventory" +import { CreateProductVariantInput } from "../../../../../types/product-variant" import { DistributedTransaction, TransactionHandlerType, @@ -6,17 +17,6 @@ import { TransactionState, TransactionStepsDefinition, } from "../../../../../utils/transaction" -import { ulid } from "ulid" -import { EntityManager } from "typeorm" -import { IInventoryService } from "../../../../../interfaces" -import { - ProductVariantInventoryService, - ProductVariantService, -} from "../../../../../services" -import { CreateProductVariantInput } from "../../../../../types/product-variant" -import { InventoryItemDTO } from "../../../../../types/inventory" -import { ProductVariant } from "../../../../../models" -import { MedusaError } from "medusa-core-utils" enum actions { createVariant = "createVariant", diff --git a/packages/medusa/src/api/routes/admin/reservations/create-reservation.ts b/packages/medusa/src/api/routes/admin/reservations/create-reservation.ts index 594528a9fb..0a0f486ee1 100644 --- a/packages/medusa/src/api/routes/admin/reservations/create-reservation.ts +++ b/packages/medusa/src/api/routes/admin/reservations/create-reservation.ts @@ -1,6 +1,5 @@ import { IsNumber, IsObject, IsOptional, IsString } from "class-validator" import { isDefined } from "medusa-core-utils" -import { EntityManager } from "typeorm" import { IInventoryService } from "../../../../interfaces" import { validateUpdateReservationQuantity } from "./utils/validate-reservation-quantity" @@ -66,8 +65,6 @@ import { validateUpdateReservationQuantity } from "./utils/validate-reservation- export default async (req, res) => { const { validatedBody } = req as { validatedBody: AdminPostReservationsReq } - const manager: EntityManager = req.scope.resolve("manager") - const inventoryService: IInventoryService = req.scope.resolve("inventoryService") @@ -82,11 +79,9 @@ export default async (req, res) => { ) } - const reservation = await manager.transaction(async (manager) => { - return await inventoryService - .withTransaction(manager) - .createReservationItem(validatedBody) - }) + const reservation = await inventoryService.createReservationItem( + validatedBody + ) res.status(200).json({ reservation }) } diff --git a/packages/medusa/src/api/routes/admin/reservations/delete-reservation.ts b/packages/medusa/src/api/routes/admin/reservations/delete-reservation.ts index a08dfb7ed0..46226280c0 100644 --- a/packages/medusa/src/api/routes/admin/reservations/delete-reservation.ts +++ b/packages/medusa/src/api/routes/admin/reservations/delete-reservation.ts @@ -58,9 +58,7 @@ export default async (req, res) => { req.scope.resolve("inventoryService") const manager: EntityManager = req.scope.resolve("manager") - await manager.transaction(async (manager) => { - await inventoryService.withTransaction(manager).deleteReservationItem(id) - }) + await inventoryService.deleteReservationItem(id) res.json({ id, diff --git a/packages/medusa/src/api/routes/admin/reservations/update-reservation.ts b/packages/medusa/src/api/routes/admin/reservations/update-reservation.ts index e0c9a5db0a..1312b69e17 100644 --- a/packages/medusa/src/api/routes/admin/reservations/update-reservation.ts +++ b/packages/medusa/src/api/routes/admin/reservations/update-reservation.ts @@ -91,9 +91,7 @@ export default async (req, res) => { } const result = await manager.transaction(async (manager) => { - await inventoryService - .withTransaction(manager) - .updateReservationItem(id, validatedBody) + await inventoryService.updateReservationItem(id, validatedBody) }) res.status(200).json({ reservation: result }) diff --git a/packages/medusa/src/api/routes/admin/stock-locations/delete-stock-location.ts b/packages/medusa/src/api/routes/admin/stock-locations/delete-stock-location.ts index c389e57727..a2682cf38d 100644 --- a/packages/medusa/src/api/routes/admin/stock-locations/delete-stock-location.ts +++ b/packages/medusa/src/api/routes/admin/stock-locations/delete-stock-location.ts @@ -79,12 +79,8 @@ export default async (req, res) => { if (inventoryService) { await Promise.all([ - inventoryService - .withTransaction(transactionManager) - .deleteInventoryItemLevelByLocationId(id), - inventoryService - .withTransaction(transactionManager) - .deleteReservationItemByLocationId(id), + inventoryService.deleteInventoryItemLevelByLocationId(id), + inventoryService.deleteReservationItemByLocationId(id), ]) } }) diff --git a/packages/medusa/src/commands/migrate.js b/packages/medusa/src/commands/migrate.js index d0a49f7a04..3f85a13dd6 100644 --- a/packages/medusa/src/commands/migrate.js +++ b/packages/medusa/src/commands/migrate.js @@ -3,7 +3,11 @@ import featureFlagLoader from "../loaders/feature-flags" import Logger from "../loaders/logger" import databaseLoader from "../loaders/database" import configModuleLoader from "../loaders/config" -import getMigrations, { getModuleSharedResources } from "./utils/get-migrations" +import getMigrations, { + getModuleSharedResources, + revertIsolatedModulesMigration, + runIsolatedModulesMigration, +} from "./utils/get-migrations" const getDataSource = async (directory) => { const configModule = configModuleLoader(directory) @@ -34,16 +38,19 @@ const main = async function ({ directory }) { args.shift() args.shift() + const configModule = configModuleLoader(directory) const dataSource = await getDataSource(directory) if (args[0] === "run") { await dataSource.runMigrations() await dataSource.destroy() + await runIsolatedModulesMigration(configModule) Logger.info("Migrations completed.") process.exit() } else if (args[0] === "revert") { await dataSource.undoLastMigration({ transaction: "all" }) await dataSource.destroy() + await revertIsolatedModulesMigration(configModule) Logger.info("Migrations reverted.") process.exit() } else if (args[0] === "show") { diff --git a/packages/medusa/src/commands/seed.ts b/packages/medusa/src/commands/seed.ts index db6d9754ec..d3ed1ce56d 100644 --- a/packages/medusa/src/commands/seed.ts +++ b/packages/medusa/src/commands/seed.ts @@ -4,7 +4,7 @@ import { sync as existsSync } from "fs-exists-cached" import { getConfigFile } from "medusa-core-utils" import { track } from "medusa-telemetry" import path from "path" -import { ConnectionOptions, createConnection } from "typeorm" +import { DataSource, DataSourceOptions } from "typeorm" import loaders from "../loaders" import { handleConfigError } from "../loaders/config" @@ -73,12 +73,14 @@ const seed = async function ({ directory, migrate, seedFile }: SeedOptions) { extra: configModule.projectConfig.database_extra || {}, migrations: coreMigrations.concat(moduleMigrations), logging: true, - } as ConnectionOptions + } as DataSourceOptions - const connection = await createConnection(connectionOptions) + const connection = new DataSource(connectionOptions) + await connection.initialize() await connection.runMigrations() - await connection.close() + await connection.destroy() + Logger.info("Migrations completed.") } diff --git a/packages/medusa/src/commands/utils/get-migrations.js b/packages/medusa/src/commands/utils/get-migrations.js index f635e634f1..eb1fe0594b 100644 --- a/packages/medusa/src/commands/utils/get-migrations.js +++ b/packages/medusa/src/commands/utils/get-migrations.js @@ -1,4 +1,4 @@ -import { registerModules } from "@medusajs/modules-sdk" +import { MedusaModule, registerModules } from "@medusajs/modules-sdk" import fs from "fs" import { sync as existsSync } from "fs-exists-cached" import glob from "glob" @@ -96,7 +96,7 @@ function resolvePlugin(pluginName) { export function getInternalModules(configModule) { const modules = [] - const moduleResolutions = registerModules(configModule) + const moduleResolutions = registerModules(configModule.modules) for (const moduleResolution of Object.values(moduleResolutions)) { if ( @@ -200,7 +200,6 @@ export const getModuleMigrations = (configModule, isFlagEnabled) => { for (const loadedModule of loadedModules) { const mod = loadedModule.loadedModule - const isolatedMigrations = {} const moduleMigrations = (mod.migrations ?? []) .map((migration) => { if ( @@ -218,7 +217,6 @@ export const getModuleMigrations = (configModule, isFlagEnabled) => { moduleDeclaration: loadedModule.moduleDeclaration, models: mod.models ?? [], migrations: moduleMigrations, - externalMigrations: isolatedMigrations, }) } @@ -249,3 +247,43 @@ export const getModuleSharedResources = (configModule, featureFlagsRouter) => { migrations, } } + +export const runIsolatedModulesMigration = async (configModule) => { + const moduleResolutions = registerModules(configModule.modules) + + for (const moduleResolution of Object.values(moduleResolutions)) { + if ( + !moduleResolution.resolutionPath || + moduleResolution.moduleDeclaration.scope !== "internal" || + moduleResolution.moduleDeclaration.resources !== "isolated" + ) { + continue + } + + await MedusaModule.migrateUp( + moduleResolution.definition.key, + moduleResolution.resolutionPath, + moduleResolution.options + ) + } +} + +export const revertIsolatedModulesMigration = async (configModule) => { + const moduleResolutions = registerModules(configModule.modules) + + for (const moduleResolution of Object.values(moduleResolutions)) { + if ( + !moduleResolution.resolutionPath || + moduleResolution.moduleDeclaration.scope !== "internal" || + moduleResolution.moduleDeclaration.resources !== "isolated" + ) { + continue + } + + await MedusaModule.migrateDown( + moduleResolution.definition.key, + moduleResolution.resolutionPath, + moduleResolution.options + ) + } +} diff --git a/packages/medusa/src/interfaces/services/inventory.ts b/packages/medusa/src/interfaces/services/inventory.ts index b6a85e7cec..03c602545c 100644 --- a/packages/medusa/src/interfaces/services/inventory.ts +++ b/packages/medusa/src/interfaces/services/inventory.ts @@ -1,4 +1,3 @@ -import { EntityManager } from "typeorm" import { FindConfig } from "../../types/common" import { @@ -16,8 +15,6 @@ import { } from "../../types/inventory" export interface IInventoryService { - withTransaction(transactionManager?: EntityManager): this - listInventoryItems( selector: FilterableInventoryItemProps, config?: FindConfig diff --git a/packages/medusa/src/loaders/index.ts b/packages/medusa/src/loaders/index.ts index 8bb96ca9e8..9aa98c2e8b 100644 --- a/packages/medusa/src/loaders/index.ts +++ b/packages/medusa/src/loaders/index.ts @@ -91,7 +91,7 @@ export default async ({ track("MODULES_INIT_STARTED") await moduleLoader({ container, - moduleResolutions: registerModules(configModule), + moduleResolutions: registerModules(configModule?.modules), logger: Logger, }) const modAct = Logger.success(modulesActivity, "Modules initialized") || {} diff --git a/packages/medusa/src/services/__tests__/event-bus.js b/packages/medusa/src/services/__tests__/event-bus.js index 97ac67ea98..ad7a0a4295 100644 --- a/packages/medusa/src/services/__tests__/event-bus.js +++ b/packages/medusa/src/services/__tests__/event-bus.js @@ -24,11 +24,18 @@ describe("EventBusService", () => { find: () => Promise.resolve([]), }) - eventBus = new EventBusService({ - manager: MockManager, - stagedJobRepository, - logger: loggerMock, - }) + eventBus = new EventBusService( + { + manager: MockManager, + stagedJobRepository, + logger: loggerMock, + }, + { + projectConfig: { + redis_url: "localhost", + }, + } + ) }) afterAll(async () => { @@ -49,10 +56,17 @@ describe("EventBusService", () => { beforeEach(() => { jest.resetAllMocks() - eventBus = new EventBusService({ - manager: MockManager, - logger: loggerMock, - }) + eventBus = new EventBusService( + { + manager: MockManager, + logger: loggerMock, + }, + { + projectConfig: { + redis_url: "localhost", + }, + } + ) }) afterAll(async () => { @@ -135,11 +149,18 @@ describe("EventBusService", () => { create: (data) => data, }) - eventBus = new EventBusService({ - logger: loggerMock, - manager: mockManager, - stagedJobRepository, - }) + eventBus = new EventBusService( + { + logger: loggerMock, + manager: mockManager, + stagedJobRepository, + }, + { + projectConfig: { + redis_url: "localhost", + }, + } + ) eventBus.queue_.addBulk.mockImplementationOnce(() => "hi") }) @@ -195,11 +216,18 @@ describe("EventBusService", () => { create: (data) => data, }) - eventBus = new EventBusService({ - logger: loggerMock, - manager: mockManager, - stagedJobRepository, - }) + eventBus = new EventBusService( + { + logger: loggerMock, + manager: mockManager, + stagedJobRepository, + }, + { + projectConfig: { + redis_url: "localhost", + }, + } + ) eventBus.queue_.addBulk.mockImplementationOnce(() => "hi") }) @@ -285,7 +313,10 @@ describe("EventBusService", () => { stagedJobRepository, }, { - projectConfig: { event_options: { removeOnComplete: 10 } }, + projectConfig: { + event_options: { removeOnComplete: 10 }, + redis_url: "localhost", + }, } ) @@ -323,11 +354,18 @@ describe("EventBusService", () => { create: (data) => data, }) - eventBus = new EventBusService({ - logger: loggerMock, - manager: mockManager, - stagedJobRepository, - }) + eventBus = new EventBusService( + { + logger: loggerMock, + manager: mockManager, + stagedJobRepository, + }, + { + projectConfig: { + redis_url: "localhost", + }, + } + ) eventBus.queue_.addBulk.mockImplementationOnce(() => "hi") @@ -370,7 +408,10 @@ describe("EventBusService", () => { stagedJobRepository, }, { - projectConfig: { event_options: { removeOnComplete: 10 } }, + projectConfig: { + event_options: { removeOnComplete: 10 }, + redis_url: "localhost", + }, } ) @@ -418,14 +459,11 @@ describe("EventBusService", () => { find: () => Promise.resolve([]), }) - eventBus = new EventBusService( - { - manager: MockManager, - stagedJobRepository, - logger: loggerMock, - }, - {} - ) + eventBus = new EventBusService({ + manager: MockManager, + stagedJobRepository, + logger: loggerMock, + }) eventBus.subscribe("eventName", () => Promise.resolve("hi")) result = await eventBus.worker_({ data: { eventName: "eventName", data: {} }, diff --git a/packages/medusa/src/services/event-bus.ts b/packages/medusa/src/services/event-bus.ts index 8e068ed8ae..a32fc269a9 100644 --- a/packages/medusa/src/services/event-bus.ts +++ b/packages/medusa/src/services/event-bus.ts @@ -308,7 +308,9 @@ export default class EventBusService { return (!isBulkEmit ? stagedJobs[0] : stagedJobs) as unknown as TResult } - await this.queue_.addBulk(events) + if (this.config_?.projectConfig?.redis_url) { + await this.queue_.addBulk(events) + } } startEnqueuer(): void { diff --git a/packages/medusa/src/services/product-variant.ts b/packages/medusa/src/services/product-variant.ts index 12a06f7720..cf7b97537f 100644 --- a/packages/medusa/src/services/product-variant.ts +++ b/packages/medusa/src/services/product-variant.ts @@ -1,3 +1,4 @@ +import { isDefined, MedusaError } from "medusa-core-utils" import { Brackets, EntityManager, @@ -9,37 +10,36 @@ import { IsNull, SelectQueryBuilder, } from "typeorm" -import { - CreateProductVariantInput, - FilterableProductVariantProps, - GetRegionPriceContext, - ProductVariantPrice, - UpdateProductVariantInput, -} from "../types/product-variant" -import { - FindWithRelationsOptions, - ProductVariantRepository, -} from "../repositories/product-variant" import { IPriceSelectionStrategy, PriceSelectionContext, TransactionBaseService, } from "../interfaces" -import { MedusaError, isDefined } from "medusa-core-utils" import { MoneyAmount, Product, ProductOptionValue, ProductVariant, } from "../models" +import { + FindWithRelationsOptions, + ProductVariantRepository, +} from "../repositories/product-variant" +import { + CreateProductVariantInput, + FilterableProductVariantProps, + GetRegionPriceContext, + ProductVariantPrice, + UpdateProductVariantInput, +} from "../types/product-variant" import { buildQuery, buildRelations, setMetadata } from "../utils" import { CartRepository } from "../repositories/cart" -import EventBusService from "./event-bus" -import { FindConfig } from "../types/common" import { MoneyAmountRepository } from "../repositories/money-amount" -import { ProductOptionValueRepository } from "../repositories/product-option-value" import { ProductRepository } from "../repositories/product" +import { ProductOptionValueRepository } from "../repositories/product-option-value" +import { FindConfig } from "../types/common" +import EventBusService from "./event-bus" import RegionService from "./region" class ProductVariantService extends TransactionBaseService { @@ -173,7 +173,9 @@ class ProductVariantService extends TransactionBaseService { "options", ]), })) as Product - } else if (!product.id) { + } + + if (!product?.id) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `Product id missing` diff --git a/packages/medusa/src/services/sales-channel-inventory.ts b/packages/medusa/src/services/sales-channel-inventory.ts index 1dd066e1c8..91f9526771 100644 --- a/packages/medusa/src/services/sales-channel-inventory.ts +++ b/packages/medusa/src/services/sales-channel-inventory.ts @@ -42,9 +42,10 @@ class SalesChannelInventoryService extends TransactionBaseService { .withTransaction(this.activeManager_) .listLocationIds(salesChannelId) - return await this.inventoryService_ - .withTransaction(this.activeManager_) - .retrieveAvailableQuantity(inventoryItemId, locationIds) + return await this.inventoryService_.retrieveAvailableQuantity( + inventoryItemId, + locationIds + ) } } diff --git a/packages/medusa/src/services/sales-channel-location.ts b/packages/medusa/src/services/sales-channel-location.ts index 1eb3c54fdf..756a269813 100644 --- a/packages/medusa/src/services/sales-channel-location.ts +++ b/packages/medusa/src/services/sales-channel-location.ts @@ -18,8 +18,8 @@ type InjectedDependencies = { class SalesChannelLocationService extends TransactionBaseService { protected readonly salesChannelService_: SalesChannelService - protected readonly eventBusService: EventBusService - protected readonly stockLocationService: IStockLocationService + protected readonly eventBusService_: EventBusService + protected readonly stockLocationService_: IStockLocationService constructor({ salesChannelService, @@ -30,8 +30,8 @@ class SalesChannelLocationService extends TransactionBaseService { super(arguments[0]) this.salesChannelService_ = salesChannelService - this.eventBusService = eventBusService - this.stockLocationService = stockLocationService + this.eventBusService_ = eventBusService + this.stockLocationService_ = stockLocationService } /** @@ -77,9 +77,9 @@ class SalesChannelLocationService extends TransactionBaseService { .withTransaction(this.activeManager_) .retrieve(salesChannelId) - if (this.stockLocationService) { + if (this.stockLocationService_) { // trhows error if not found - await this.stockLocationService.retrieve(locationId) + await this.stockLocationService_.retrieve(locationId) } const salesChannelLocation = this.activeManager_.create( @@ -129,7 +129,7 @@ class SalesChannelLocationService extends TransactionBaseService { */ async listSalesChannelIds(locationId: string): Promise { const manager = this.transactionManager_ || this.manager_ - const location = await this.stockLocationService.retrieve(locationId) + const location = await this.stockLocationService_.retrieve(locationId) const salesChannelLocations = await manager.find(SalesChannelLocation, { where: { location_id: location.id }, diff --git a/packages/medusa/src/types/global.ts b/packages/medusa/src/types/global.ts index f6f4bf55d6..375536c8ec 100644 --- a/packages/medusa/src/types/global.ts +++ b/packages/medusa/src/types/global.ts @@ -1,6 +1,9 @@ -import { InternalModuleDeclaration } from "@medusajs/modules-sdk" -import { MedusaContainer as coreMedusaContainer } from "medusa-core-utils" +import { + ExternalModuleDeclaration, + InternalModuleDeclaration, +} from "@medusajs/modules-sdk" import { Request } from "express" +import { MedusaContainer as coreMedusaContainer } from "medusa-core-utils" import { LoggerOptions } from "typeorm" import { Logger as _Logger } from "winston" import { Customer, User } from "../models" @@ -91,7 +94,12 @@ export type ConfigModule = { admin_cors?: string } featureFlags: Record - modules?: Record> + modules?: Record< + string, + | false + | string + | Partial + > plugins: ( | { resolve: string diff --git a/packages/modules-sdk/src/index.ts b/packages/modules-sdk/src/index.ts index 6fc5746d50..fdf583e7b0 100644 --- a/packages/modules-sdk/src/index.ts +++ b/packages/modules-sdk/src/index.ts @@ -1,4 +1,5 @@ export * from "./definitions" export * from "./loaders" +export * from "./medusa-module" export * from "./module-helper" export * from "./types" diff --git a/packages/modules-sdk/src/loaders/__tests__/module-loader.ts b/packages/modules-sdk/src/loaders/__tests__/module-loader.ts index ece2e92f8d..1c1d1f3ed7 100644 --- a/packages/modules-sdk/src/loaders/__tests__/module-loader.ts +++ b/packages/modules-sdk/src/loaders/__tests__/module-loader.ts @@ -1,6 +1,6 @@ -import { EOL } from "os" import { AwilixContainer, ClassOrFunctionReturning, Resolver } from "awilix" import { createMedusaContainer } from "medusa-core-utils" +import { EOL } from "os" import { ModuleResolution, MODULE_RESOURCE_TYPE, @@ -155,7 +155,7 @@ describe("modules loader", () => { await moduleLoader({ container, moduleResolutions, logger }) expect(logger.warn).toHaveBeenCalledWith( - `Could not resolve module: TestService. Error: No service found in module. Make sure your module exports at least one service.${EOL}` + `Could not resolve module: TestService. Error: No service found in module. Make sure your module exports a service.${EOL}` ) }) @@ -186,7 +186,7 @@ describe("modules loader", () => { await moduleLoader({ container, moduleResolutions, logger }) } catch (err) { expect(err.message).toEqual( - "No service found in module. Make sure your module exports at least one service." + "No service found in module. Make sure your module exports a service." ) } }) diff --git a/packages/modules-sdk/src/loaders/__tests__/register-modules.ts b/packages/modules-sdk/src/loaders/__tests__/register-modules.ts index c7577f4c93..adfed43564 100644 --- a/packages/modules-sdk/src/loaders/__tests__/register-modules.ts +++ b/packages/modules-sdk/src/loaders/__tests__/register-modules.ts @@ -1,3 +1,4 @@ +import MODULE_DEFINITIONS from "../../definitions" import { InternalModuleDeclaration, ModuleDefinition, @@ -5,7 +6,6 @@ import { MODULE_SCOPE, } from "../../types" import { registerModules } from "../register-modules" -import MODULE_DEFINITIONS from "../../definitions" const RESOLVED_PACKAGE = "@medusajs/test-service-resolved" jest.mock("resolve-cwd", () => jest.fn(() => RESOLVED_PACKAGE)) @@ -35,7 +35,7 @@ describe("module definitions loader", () => { it("Resolves module with default definition given empty config", () => { MODULE_DEFINITIONS.push({ ...defaultDefinition }) - const res = registerModules({ modules: {} }) + const res = registerModules({}) expect(res[defaultDefinition.key]).toEqual( expect.objectContaining({ @@ -54,9 +54,7 @@ describe("module definitions loader", () => { it("Resolves module with no resolution path when given false", () => { MODULE_DEFINITIONS.push({ ...defaultDefinition }) - const res = registerModules({ - modules: { [defaultDefinition.key]: false }, - }) + const res = registerModules({ [defaultDefinition.key]: false }) expect(res[defaultDefinition.key]).toEqual( expect.objectContaining({ @@ -72,9 +70,7 @@ describe("module definitions loader", () => { MODULE_DEFINITIONS.push({ ...defaultDefinition, isRequired: true }) try { - registerModules({ - modules: { [defaultDefinition.key]: false }, - }) + registerModules({ [defaultDefinition.key]: false }) } catch (err) { expect(err.message).toEqual( `Module: ${defaultDefinition.label} is required` @@ -90,9 +86,7 @@ describe("module definitions loader", () => { MODULE_DEFINITIONS.push(definition) - const res = registerModules({ - modules: {}, - }) + const res = registerModules({}) expect(res[defaultDefinition.key]).toEqual( expect.objectContaining({ @@ -113,9 +107,7 @@ describe("module definitions loader", () => { MODULE_DEFINITIONS.push({ ...defaultDefinition }) const res = registerModules({ - modules: { - [defaultDefinition.key]: defaultDefinition.defaultPackage, - }, + [defaultDefinition.key]: defaultDefinition.defaultPackage, }) expect(res[defaultDefinition.key]).toEqual( @@ -137,13 +129,11 @@ describe("module definitions loader", () => { MODULE_DEFINITIONS.push({ ...defaultDefinition }) const res = registerModules({ - modules: { - [defaultDefinition.key]: { - scope: MODULE_SCOPE.INTERNAL, - resolve: defaultDefinition.defaultPackage, - resources: MODULE_RESOURCE_TYPE.ISOLATED, - } as InternalModuleDeclaration, - }, + [defaultDefinition.key]: { + scope: MODULE_SCOPE.INTERNAL, + resolve: defaultDefinition.defaultPackage, + resources: MODULE_RESOURCE_TYPE.ISOLATED, + } as InternalModuleDeclaration, }) expect(res[defaultDefinition.key]).toEqual( @@ -164,10 +154,8 @@ describe("module definitions loader", () => { MODULE_DEFINITIONS.push({ ...defaultDefinition }) const res = registerModules({ - modules: { - [defaultDefinition.key]: { - options: { test: 123 }, - }, + [defaultDefinition.key]: { + options: { test: 123 }, }, } as any) @@ -189,13 +177,11 @@ describe("module definitions loader", () => { MODULE_DEFINITIONS.push({ ...defaultDefinition }) const res = registerModules({ - modules: { - [defaultDefinition.key]: { - resolve: defaultDefinition.defaultPackage, - options: { test: 123 }, - scope: "internal", - resources: "isolated", - }, + [defaultDefinition.key]: { + resolve: defaultDefinition.defaultPackage, + options: { test: 123 }, + scope: "internal", + resources: "isolated", }, } as any) diff --git a/packages/modules-sdk/src/loaders/register-modules.ts b/packages/modules-sdk/src/loaders/register-modules.ts index 64b05cef06..1e21b2f9ca 100644 --- a/packages/modules-sdk/src/loaders/register-modules.ts +++ b/packages/modules-sdk/src/loaders/register-modules.ts @@ -1,17 +1,22 @@ import resolveCwd from "resolve-cwd" +import MODULE_DEFINITIONS from "../definitions" import { - MedusaModuleConfig, + ExternalModuleDeclaration, InternalModuleDeclaration, ModuleDefinition, ModuleResolution, MODULE_SCOPE, } from "../types" -import MODULE_DEFINITIONS from "../definitions" -export const registerModules = ({ - modules, -}: MedusaModuleConfig): Record => { +export const registerModules = ( + modules?: Record< + string, + | false + | string + | Partial + > +): Record => { const moduleResolutions = {} as Record const projectModules = modules ?? {} @@ -26,10 +31,32 @@ export const registerModules = ({ moduleResolutions[definition.key] = getInternalModuleResolution( definition, - projectModules[definition.key] as - | InternalModuleDeclaration - | false - | string + customConfig as InternalModuleDeclaration + ) + } + + return moduleResolutions +} + +export const registerMedusaModule = ( + moduleKey: string, + moduleDeclaration: InternalModuleDeclaration | ExternalModuleDeclaration +): Record => { + const moduleResolutions = {} as Record + + for (const definition of MODULE_DEFINITIONS) { + if (definition.key !== moduleKey) { + continue + } + + if (moduleDeclaration.scope === MODULE_SCOPE.EXTERNAL) { + // TODO: getExternalModuleResolution(...)a + throw new Error("External Modules are not supported yet.") + } + + moduleResolutions[definition.key] = getInternalModuleResolution( + definition, + moduleDeclaration as InternalModuleDeclaration ) } diff --git a/packages/modules-sdk/src/loaders/utils/load-internal.ts b/packages/modules-sdk/src/loaders/utils/load-internal.ts index 1142c2e9d9..91b7641e17 100644 --- a/packages/modules-sdk/src/loaders/utils/load-internal.ts +++ b/packages/modules-sdk/src/loaders/utils/load-internal.ts @@ -36,7 +36,7 @@ export async function loadInternalModule( return { error: new Error( - "No service found in module. Make sure your module exports at least one service." + "No service found in module. Make sure your module exports a service." ), } } @@ -112,3 +112,15 @@ export async function loadInternalModule( "module" ) } + +export async function loadModuleMigrations( + resolution: ModuleResolution +): Promise<[Function | undefined, Function | undefined]> { + let loadedModule: ModuleExports + try { + loadedModule = (await import(resolution.resolutionPath as string)).default + return [loadedModule.runMigrations, loadedModule.revertMigration] + } catch { + return [undefined, undefined] + } +} diff --git a/packages/modules-sdk/src/medusa-module.ts b/packages/modules-sdk/src/medusa-module.ts new file mode 100644 index 0000000000..29d1c31cbd --- /dev/null +++ b/packages/modules-sdk/src/medusa-module.ts @@ -0,0 +1,108 @@ +import { asValue } from "awilix" +import { createMedusaContainer } from "medusa-core-utils" +import { moduleLoader, registerMedusaModule } from "./loaders" +import { loadModuleMigrations } from "./loaders/utils" +import { + ExternalModuleDeclaration, + InternalModuleDeclaration, + MODULE_RESOURCE_TYPE, + MODULE_SCOPE, +} from "./types" + +const logger: any = { + log: (a) => console.log(a), + info: (a) => console.log(a), + warn: (a) => console.warn(a), + error: (a) => console.error(a), +} + +export class MedusaModule { + public static async bootstrap( + moduleKey: string, + defaultPath: string, + declaration?: InternalModuleDeclaration | ExternalModuleDeclaration, + injectedDependencies?: Record + ): Promise<{ + [key: string]: any + }> { + let modDeclaration = declaration + if (declaration?.scope !== MODULE_SCOPE.EXTERNAL) { + modDeclaration = { + scope: MODULE_SCOPE.INTERNAL, + resources: MODULE_RESOURCE_TYPE.ISOLATED, + resolve: defaultPath, + options: declaration, + } + } + + const container = createMedusaContainer() + + if (injectedDependencies) { + for (const service in injectedDependencies) { + container.register(service, asValue(injectedDependencies[service])) + } + } + + const moduleResolutions = registerMedusaModule(moduleKey, modDeclaration!) + + await moduleLoader({ container, moduleResolutions, logger }) + + const services = {} + + for (const resolution of Object.values(moduleResolutions)) { + const registrationName = resolution.definition.registrationName + + services[registrationName] = container.resolve(registrationName) + } + + return services + } + + public static async migrateUp( + moduleKey: string, + modulePath: string, + options?: Record + ): Promise { + const moduleResolutions = registerMedusaModule(moduleKey, { + scope: MODULE_SCOPE.INTERNAL, + resources: MODULE_RESOURCE_TYPE.ISOLATED, + resolve: modulePath, + options, + }) + + for (const mod in moduleResolutions) { + const [migrateUp] = await loadModuleMigrations(moduleResolutions[mod]) + + if (typeof migrateUp === "function") { + await migrateUp({ + options, + logger, + }) + } + } + } + + public static async migrateDown( + moduleKey: string, + modulePath: string, + options?: Record + ): Promise { + const moduleResolutions = registerMedusaModule(moduleKey, { + scope: MODULE_SCOPE.INTERNAL, + resources: MODULE_RESOURCE_TYPE.ISOLATED, + resolve: modulePath, + options, + }) + + for (const mod in moduleResolutions) { + const [, migrateDown] = await loadModuleMigrations(moduleResolutions[mod]) + + if (typeof migrateDown === "function") { + await migrateDown({ + options, + logger, + }) + } + } + } +} diff --git a/packages/modules-sdk/src/types/index.ts b/packages/modules-sdk/src/types/index.ts index bbef887c38..9d24396d82 100644 --- a/packages/modules-sdk/src/types/index.ts +++ b/packages/modules-sdk/src/types/index.ts @@ -68,9 +68,9 @@ export type ModuleDefinition = { | ExternalModuleDeclaration } -export type LoaderOptions = { +export type LoaderOptions> = { container: MedusaContainer - options?: Record + options?: TOptions logger?: Logger } @@ -89,13 +89,12 @@ export type ModuleExports = { loaders?: ModuleLoaderFunction[] migrations?: any[] models?: Constructor[] -} - -export type MedusaModuleConfig = { - modules?: Record< - string, - | false - | string - | Partial - > + runMigrations?( + options: LoaderOptions, + moduleDeclaration: InternalModuleDeclaration + ): Promise + revertMigration?( + options: LoaderOptions, + moduleDeclaration: InternalModuleDeclaration + ): Promise } diff --git a/packages/stock-location/src/index.ts b/packages/stock-location/src/index.ts index 42740ff607..c452dc4b81 100644 --- a/packages/stock-location/src/index.ts +++ b/packages/stock-location/src/index.ts @@ -1,11 +1,10 @@ +import { ModuleExports } from "@medusajs/modules-sdk" import loadConnection from "./loaders/connection" - import migrations from "./migrations" +import { revertMigration, runMigrations } from "./migrations/run-migration" import * as StockLocationModels from "./models" import StockLocationService from "./services/stock-location" -import { ModuleExports } from "@medusajs/modules-sdk" - const service = StockLocationService const loaders = [loadConnection] const models = Object.values(StockLocationModels) @@ -15,6 +14,8 @@ const moduleDefinition: ModuleExports = { migrations, loaders, models, + runMigrations, + revertMigration, } export default moduleDefinition diff --git a/packages/stock-location/src/initialize/index.ts b/packages/stock-location/src/initialize/index.ts new file mode 100644 index 0000000000..64a72a451e --- /dev/null +++ b/packages/stock-location/src/initialize/index.ts @@ -0,0 +1,24 @@ +import { IEventBusService, IStockLocationService } from "@medusajs/medusa" +import { + ExternalModuleDeclaration, + InternalModuleDeclaration, + MedusaModule, +} from "@medusajs/modules-sdk" +import { StockLocationServiceInitializeOptions } from "../types" + +export const initialize = async ( + options?: StockLocationServiceInitializeOptions | ExternalModuleDeclaration, + injectedDependencies?: { + eventBusService: IEventBusService + } +): Promise => { + const serviceKey = "stockLocationService" + const loaded = await MedusaModule.bootstrap( + serviceKey, + "@medusajs/stock-location", + options as InternalModuleDeclaration | ExternalModuleDeclaration, + injectedDependencies + ) + + return loaded[serviceKey] as IStockLocationService +} diff --git a/packages/stock-location/src/loaders/connection.ts b/packages/stock-location/src/loaders/connection.ts index 1c248b5178..2d5c6f7c23 100644 --- a/packages/stock-location/src/loaders/connection.ts +++ b/packages/stock-location/src/loaders/connection.ts @@ -2,13 +2,13 @@ import { InternalModuleDeclaration, LoaderOptions, MODULE_RESOURCE_TYPE, - MODULE_SCOPE, + MODULE_SCOPE } from "@medusajs/modules-sdk" -import { DataSource, DataSourceOptions } from "typeorm" - -import * as StockLocationModels from "../models" -import { MedusaError } from "medusa-core-utils" import { asValue } from "awilix" +import { MedusaError } from "medusa-core-utils" +import { DataSource, DataSourceOptions } from "typeorm" +import * as StockLocationModels from "../models" +import { StockLocationServiceInitializeOptions } from "../types" export default async ( { options, container }: LoaderOptions, @@ -21,7 +21,8 @@ export default async ( return } - const dbData = options?.database as Record + const dbData = + options?.database as StockLocationServiceInitializeOptions["database"] if (!dbData) { throw new MedusaError( @@ -32,13 +33,13 @@ export default async ( const entities = Object.values(StockLocationModels) const dataSource = new DataSource({ - type: dbData.database_type, - url: dbData.database_url, - database: dbData.database_database, - extra: dbData.database_extra || {}, - schema: dbData.database_schema, + type: dbData.type, + url: dbData.url, + database: dbData.database, + extra: dbData.extra || {}, + schema: dbData.schema, entities, - logging: dbData.database_logging, + logging: dbData.logging, } as DataSourceOptions) await dataSource.initialize() diff --git a/packages/stock-location/src/migrations/run-migration.ts b/packages/stock-location/src/migrations/run-migration.ts new file mode 100644 index 0000000000..1ecfd0b1b1 --- /dev/null +++ b/packages/stock-location/src/migrations/run-migration.ts @@ -0,0 +1,65 @@ +import { InternalModuleDeclaration, LoaderOptions } from "@medusajs/modules-sdk" +import { DataSource, DataSourceOptions } from "typeorm" +import { StockLocationServiceInitializeOptions } from "../types" + +import migrations from "./index" + +function getDataSource( + dbData: StockLocationServiceInitializeOptions["database"] +): DataSource { + return new DataSource({ + type: dbData!.type, + url: dbData!.url, + database: dbData!.database, + extra: dbData!.extra || {}, + migrations: migrations + .map((migration: any): Function[] => { + return Object.values(migration).filter( + (fn) => typeof fn === "function" + ) as Function[] + }) + .flat(), + schema: dbData!.schema, + logging: dbData!.logging, + } as DataSourceOptions) +} + +export async function runMigrations( + { options, logger }: Omit, + moduleDeclaration?: InternalModuleDeclaration +) { + const dbData = + options?.database as StockLocationServiceInitializeOptions["database"] + + try { + const dataSource = getDataSource(dbData) + await dataSource.initialize() + await dataSource.runMigrations() + + logger?.info("Stock Location module migration executed") + } catch (error) { + logger?.error( + `Stock Location module migration failed to run - Error: ${error}` + ) + } +} + +export async function revertMigration( + { options, logger }: Omit, + moduleDeclaration?: InternalModuleDeclaration +) { + const dbData = + options?.database as StockLocationServiceInitializeOptions["database"] + + try { + const dataSource = getDataSource(dbData) + await dataSource.initialize() + await dataSource.undoLastMigration() + + logger?.info("Stock Location module migration reverted") + } catch (error) { + logger?.error( + `Stock Location module migration failed to revert - Error: ${error}` + ) + } +} diff --git a/packages/stock-location/src/services/stock-location.ts b/packages/stock-location/src/services/stock-location.ts index 7b208ecc31..4732cf73fe 100644 --- a/packages/stock-location/src/services/stock-location.ts +++ b/packages/stock-location/src/services/stock-location.ts @@ -153,11 +153,9 @@ export default class StockLocationService { } const result = await locationRepo.save(loc) - await this.eventBusService_ - .withTransaction(manager) - .emit(StockLocationService.Events.CREATED, { - id: result.id, - }) + await this.eventBusService_?.emit?.(StockLocationService.Events.CREATED, { + id: result.id, + }) return result } @@ -201,11 +199,9 @@ export default class StockLocationService { await locationRepo.save(toSave) - await this.eventBusService_ - .withTransaction(manager) - .emit(StockLocationService.Events.UPDATED, { - id: stockLocationId, - }) + await this.eventBusService_?.emit?.(StockLocationService.Events.UPDATED, { + id: stockLocationId, + }) return item } @@ -267,10 +263,8 @@ export default class StockLocationService { await locationRepo.softRemove({ id }) - await this.eventBusService_ - .withTransaction(manager) - .emit(StockLocationService.Events.DELETED, { - id, - }) + await this.eventBusService_?.emit?.(StockLocationService.Events.DELETED, { + id, + }) } } diff --git a/packages/stock-location/src/types/index.ts b/packages/stock-location/src/types/index.ts new file mode 100644 index 0000000000..88f8150eb6 --- /dev/null +++ b/packages/stock-location/src/types/index.ts @@ -0,0 +1,12 @@ +import { DatabaseType } from "typeorm" + +export type StockLocationServiceInitializeOptions = { + database?: { + type?: DatabaseType | string + url?: string + database?: string + extra?: Record + schema?: string + logging?: boolean + } +} diff --git a/packages/utils/src/decorators/context-parameter.ts b/packages/utils/src/decorators/context-parameter.ts index c959ede0e5..15144b842a 100644 --- a/packages/utils/src/decorators/context-parameter.ts +++ b/packages/utils/src/decorators/context-parameter.ts @@ -4,16 +4,16 @@ export function MedusaContext() { propertyKey: string | symbol, parameterIndex: number ) { - if (!target.MedusaContext_) { - target.MedusaContext_ = {} + if (!target.MedusaContextIndex_) { + target.MedusaContextIndex_ = {} } - if (propertyKey in target.MedusaContext_) { + if (propertyKey in target.MedusaContextIndex_) { throw new Error( `Only one MedusaContext is allowed on method "${String(propertyKey)}".` ) } - target.MedusaContext_[propertyKey] = parameterIndex + target.MedusaContextIndex_[propertyKey] = parameterIndex } } diff --git a/packages/utils/src/decorators/inject-entity-manager.ts b/packages/utils/src/decorators/inject-entity-manager.ts index 30b5c2752f..a34f1321bf 100644 --- a/packages/utils/src/decorators/inject-entity-manager.ts +++ b/packages/utils/src/decorators/inject-entity-manager.ts @@ -8,7 +8,7 @@ export function InjectEntityManager( propertyKey: string | symbol, descriptor: any ): void { - if (!target.MedusaContext_) { + if (!target.MedusaContextIndex_) { throw new Error( `To apply @InjectEntityManager you have to flag a parameter using @MedusaContext` ) @@ -16,7 +16,7 @@ export function InjectEntityManager( const originalMethod = descriptor.value - const argIndex = target.MedusaContext_[propertyKey] + const argIndex = target.MedusaContextIndex_[propertyKey] descriptor.value = async function (...args: any[]) { const context: SharedContext = args[argIndex] ?? {} diff --git a/yarn.lock b/yarn.lock index 51a47aeb20..b796d313be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5741,8 +5741,10 @@ __metadata: version: 0.0.0-use.local resolution: "@medusajs/inventory@workspace:packages/inventory" dependencies: - "@medusajs/medusa": "*" + "@medusajs/medusa": ^1.7.7 "@medusajs/modules-sdk": "*" + "@medusajs/types": "*" + "@medusajs/utils": ^0.0.1 awilix: ^8.0.0 cross-env: ^5.2.1 jest: ^25.5.4 @@ -5750,7 +5752,7 @@ __metadata: typeorm: ^0.3.11 typescript: ^4.4.4 peerDependencies: - "@medusajs/medusa": 1.7.13 + "@medusajs/types": ^0.0.1 languageName: unknown linkType: soft