feat(medusa, stock-location, inventory): Allow modules to integrate with core (#2997)

* feat: module shared resources
This commit is contained in:
Carlos R. L. Rodrigues authored and GitHub committed 2023-01-13 10:39:43 -03:00
1 parent b3e4be7208
commit 9dbccd9ca7
24 files changed
+515 -265

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
---
"@medusajs/inventory": patch
"@medusajs/medusa": patch
"@medusajs/stock-location": patch
---
feat(medusa, stock-location, inventory): Allow modules to integrate with core
-7
View File
@@ -1,7 +0,0 @@
import ConnectionLoader from "./loaders/connection"
import InventoryService from "./services/inventory"
import * as SchemaMigration from "./migrations/schema-migrations/1665748086258-inventory_setup"
export const service = InventoryService
export const migrations = [SchemaMigration]
export const loaders = [ConnectionLoader]
+19
View File
@@ -0,0 +1,19 @@
import ConnectionLoader from "./loaders/connection"
import InventoryService from "./services/inventory"
import * as InventoryModels from "./models"
import * as SchemaMigration from "./migrations/schema-migrations/1665748086258-inventory_setup"
import { ModuleExports } from "@medusajs/medusa"
const service = InventoryService
const migrations = [SchemaMigration]
const loaders = [ConnectionLoader]
const models = Object.values(InventoryModels)
const moduleDefinition: ModuleExports = {
service,
migrations,
loaders,
models,
}
export default moduleDefinition
+5 -21
View File
@@ -1,22 +1,6 @@
import { ConfigModule } from "@medusajs/medusa" import { ConfigurableModuleDeclaration, LoaderOptions } from "@medusajs/medusa"
import { ConnectionOptions, createConnection } from "typeorm"
import { CONNECTION_NAME } from "../config"
import { ReservationItem, InventoryItem, InventoryLevel } from "../models" export default async (
{ configModule }: LoaderOptions,
export default async ({ moduleDeclaration?: ConfigurableModuleDeclaration
configModule, ): Promise<void> => {}
}: {
configModule: ConfigModule
}): Promise<void> => {
await createConnection({
name: CONNECTION_NAME,
type: configModule.projectConfig.database_type,
url: configModule.projectConfig.database_url,
database: configModule.projectConfig.database_database,
schema: configModule.projectConfig.database_schema,
extra: configModule.projectConfig.database_extra || {},
entities: [ReservationItem, InventoryLevel, InventoryItem],
logging: configModule.projectConfig.database_logging || false,
} as ConnectionOptions)
}
@@ -1,4 +1,4 @@
import { ILike, In, getConnection, DeepPartial, EntityManager } from "typeorm" import { DeepPartial, EntityManager } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils" import { isDefined, MedusaError } from "medusa-core-utils"
import { import {
FindConfig, FindConfig,
@@ -6,16 +6,19 @@ import {
IEventBusService, IEventBusService,
FilterableInventoryItemProps, FilterableInventoryItemProps,
CreateInventoryItemInput, CreateInventoryItemInput,
InventoryItemDTO,
TransactionBaseService,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { InventoryItem } from "../models" import { InventoryItem } from "../models"
import { CONNECTION_NAME } from "../config" import { getListQuery } from "../utils/query"
type InjectedDependencies = { type InjectedDependencies = {
eventBusService: IEventBusService eventBusService: IEventBusService
manager: EntityManager
} }
export default class InventoryItemService { export default class InventoryItemService extends TransactionBaseService {
static Events = { static Events = {
CREATED: "inventory-item.created", CREATED: "inventory-item.created",
UPDATED: "inventory-item.updated", UPDATED: "inventory-item.updated",
@@ -23,14 +26,18 @@ export default class InventoryItemService {
} }
protected readonly eventBusService_: IEventBusService protected readonly eventBusService_: IEventBusService
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
constructor({ eventBusService, manager }: InjectedDependencies) {
super(arguments[0])
constructor({ eventBusService }: InjectedDependencies) {
this.eventBusService_ = eventBusService this.eventBusService_ = eventBusService
this.manager_ = manager
} }
private getManager(): EntityManager { private getManager(): EntityManager {
const connection = getConnection(CONNECTION_NAME) return this.transactionManager_ ?? this.manager_
return connection.manager
} }
/** /**
@@ -41,73 +48,11 @@ export default class InventoryItemService {
async list( async list(
selector: FilterableInventoryItemProps = {}, selector: FilterableInventoryItemProps = {},
config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 } config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 }
): Promise<InventoryItem[]> { ): Promise<InventoryItemDTO[]> {
const queryBuilder = this.getListQuery(selector, config) const queryBuilder = getListQuery(this.getManager(), selector, config)
return await queryBuilder.getMany() return await queryBuilder.getMany()
} }
private getListQuery(
selector: FilterableInventoryItemProps = {},
config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 }
) {
const manager = this.getManager()
const inventoryItemRepository = manager.getRepository(InventoryItem)
const query = buildQuery(selector, config)
const queryBuilder = inventoryItemRepository.createQueryBuilder("inv_item")
if (query.where.q) {
query.where.sku = ILike(`%${query.where.q as string}%`)
delete query.where.q
}
if ("location_id" in query.where) {
const locationIds = Array.isArray(selector.location_id)
? selector.location_id
: [selector.location_id]
queryBuilder.innerJoin(
"inventory_level",
"level",
"level.inventory_item_id = inv_item.id AND level.location_id IN (:...locationIds)",
{ locationIds }
)
delete query.where.location_id
}
if (query.take) {
queryBuilder.take(query.take)
}
if (query.skip) {
queryBuilder.skip(query.skip)
}
if (query.where) {
queryBuilder.where(query.where)
}
if (query.select) {
queryBuilder.select(query.select.map((s) => "inv_item." + s))
}
if (query.order) {
const toSelect: string[] = []
const parsed = Object.entries(query.order).reduce((acc, [k, v]) => {
const key = `inv_item.${k}`
toSelect.push(key)
acc[key] = v
return acc
}, {})
queryBuilder.addSelect(toSelect)
queryBuilder.orderBy(parsed)
}
return queryBuilder
}
/** /**
* @param selector - Filter options for inventory items. * @param selector - Filter options for inventory items.
* @param config - Configuration for query. * @param config - Configuration for query.
@@ -116,8 +61,8 @@ export default class InventoryItemService {
async listAndCount( async listAndCount(
selector: FilterableInventoryItemProps = {}, selector: FilterableInventoryItemProps = {},
config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 } config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 }
): Promise<[InventoryItem[], number]> { ): Promise<[InventoryItemDTO[], number]> {
const queryBuilder = this.getListQuery(selector, config) const queryBuilder = getListQuery(this.getManager(), selector, config)
return await queryBuilder.getManyAndCount() return await queryBuilder.getManyAndCount()
} }
@@ -160,7 +105,7 @@ export default class InventoryItemService {
* @return The newly created inventory item. * @return The newly created inventory item.
*/ */
async create(data: CreateInventoryItemInput): Promise<InventoryItem> { async create(data: CreateInventoryItemInput): Promise<InventoryItem> {
const manager = this.getManager() return await this.atomicPhase_(async (manager) => {
const itemRepository = manager.getRepository(InventoryItem) const itemRepository = manager.getRepository(InventoryItem)
const inventoryItem = itemRepository.create({ const inventoryItem = itemRepository.create({
@@ -179,11 +124,14 @@ export default class InventoryItemService {
const result = await itemRepository.save(inventoryItem) const result = await itemRepository.save(inventoryItem)
await this.eventBusService_.emit(InventoryItemService.Events.CREATED, { await this.eventBusService_
.withTransaction(manager)
.emit(InventoryItemService.Events.CREATED, {
id: result.id, id: result.id,
}) })
return result return result
})
} }
/** /**
@@ -198,7 +146,7 @@ export default class InventoryItemService {
"id" | "created_at" | "metadata" | "deleted_at" "id" | "created_at" | "metadata" | "deleted_at"
> >
): Promise<InventoryItem> { ): Promise<InventoryItem> {
const manager = this.getManager() return await this.atomicPhase_(async (manager) => {
const itemRepository = manager.getRepository(InventoryItem) const itemRepository = manager.getRepository(InventoryItem)
const item = await this.retrieve(inventoryItemId) const item = await this.retrieve(inventoryItemId)
@@ -211,25 +159,31 @@ export default class InventoryItemService {
itemRepository.merge(item, data) itemRepository.merge(item, data)
await itemRepository.save(item) await itemRepository.save(item)
await this.eventBusService_.emit(InventoryItemService.Events.UPDATED, { await this.eventBusService_
.withTransaction(manager)
.emit(InventoryItemService.Events.UPDATED, {
id: item.id, id: item.id,
}) })
} }
return item return item
})
} }
/** /**
* @param inventoryItemId - The id of the inventory item to delete. * @param inventoryItemId - The id of the inventory item to delete.
*/ */
async delete(inventoryItemId: string): Promise<void> { async delete(inventoryItemId: string): Promise<void> {
const manager = this.getManager() await this.atomicPhase_(async (manager) => {
const itemRepository = manager.getRepository(InventoryItem) const itemRepository = manager.getRepository(InventoryItem)
await itemRepository.softRemove({ id: inventoryItemId }) await itemRepository.softRemove({ id: inventoryItemId })
await this.eventBusService_.emit(InventoryItemService.Events.DELETED, { await this.eventBusService_
.withTransaction(manager)
.emit(InventoryItemService.Events.DELETED, {
id: inventoryItemId, id: inventoryItemId,
}) })
})
} }
} }
@@ -1,4 +1,4 @@
import { getConnection, DeepPartial, EntityManager } from "typeorm" import { DeepPartial, EntityManager } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils" import { isDefined, MedusaError } from "medusa-core-utils"
import { import {
FindConfig, FindConfig,
@@ -10,10 +10,10 @@ import {
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { InventoryLevel } from "../models" import { InventoryLevel } from "../models"
import { CONNECTION_NAME } from "../config"
type InjectedDependencies = { type InjectedDependencies = {
eventBusService: IEventBusService eventBusService: IEventBusService
manager: EntityManager
} }
export default class InventoryLevelService extends TransactionBaseService { export default class InventoryLevelService extends TransactionBaseService {
@@ -28,22 +28,17 @@ export default class InventoryLevelService extends TransactionBaseService {
protected readonly eventBusService_: IEventBusService protected readonly eventBusService_: IEventBusService
constructor({ eventBusService }: InjectedDependencies) { constructor({ eventBusService, manager }: InjectedDependencies) {
super(arguments[0]) super(arguments[0])
this.eventBusService_ = eventBusService this.eventBusService_ = eventBusService
this.manager_ = this.getManager() this.manager_ = manager
} }
private getManager(): EntityManager { private getManager(): EntityManager {
if (this.manager_) {
return this.transactionManager_ ?? this.manager_ return this.transactionManager_ ?? this.manager_
} }
const connection = getConnection(CONNECTION_NAME)
return connection.manager
}
/** /**
* Retrieves a list of inventory levels based on the provided selector and configuration. * Retrieves a list of inventory levels based on the provided selector and configuration.
* @param selector - An object containing filterable properties for inventory levels. * @param selector - An object containing filterable properties for inventory levels.
@@ -118,7 +113,7 @@ export default class InventoryLevelService extends TransactionBaseService {
* @return The created inventory level. * @return The created inventory level.
*/ */
async create(data: CreateInventoryLevelInput): Promise<InventoryLevel> { async create(data: CreateInventoryLevelInput): Promise<InventoryLevel> {
const result = await this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const levelRepository = manager.getRepository(InventoryLevel) const levelRepository = manager.getRepository(InventoryLevel)
const inventoryLevel = levelRepository.create({ const inventoryLevel = levelRepository.create({
@@ -129,14 +124,15 @@ export default class InventoryLevelService extends TransactionBaseService {
incoming_quantity: data.incoming_quantity, incoming_quantity: data.incoming_quantity,
}) })
return await levelRepository.save(inventoryLevel) const saved = await levelRepository.save(inventoryLevel)
await this.eventBusService_
.withTransaction(manager)
.emit(InventoryLevelService.Events.CREATED, {
id: saved.id,
}) })
await this.eventBusService_.emit(InventoryLevelService.Events.CREATED, { return saved
id: result.id,
}) })
return result
} }
/** /**
@@ -167,7 +163,9 @@ export default class InventoryLevelService extends TransactionBaseService {
levelRepository.merge(item, data) levelRepository.merge(item, data)
await levelRepository.save(item) await levelRepository.save(item)
await this.eventBusService_.emit(InventoryLevelService.Events.UPDATED, { await this.eventBusService_
.withTransaction(manager)
.emit(InventoryLevelService.Events.UPDATED, {
id: item.id, id: item.id,
}) })
} }
@@ -209,11 +207,13 @@ export default class InventoryLevelService extends TransactionBaseService {
const levelRepository = manager.getRepository(InventoryLevel) const levelRepository = manager.getRepository(InventoryLevel)
await levelRepository.delete({ id: inventoryLevelId }) await levelRepository.delete({ id: inventoryLevelId })
})
await this.eventBusService_.emit(InventoryLevelService.Events.DELETED, { await this.eventBusService_
.withTransaction(manager)
.emit(InventoryLevelService.Events.DELETED, {
id: inventoryLevelId, id: inventoryLevelId,
}) })
})
} }
/** /**
+33 -4
View File
@@ -14,6 +14,9 @@ import {
InventoryItemDTO, InventoryItemDTO,
ReservationItemDTO, ReservationItemDTO,
InventoryLevelDTO, InventoryLevelDTO,
TransactionBaseService,
ConfigurableModuleDeclaration,
MODULE_RESOURCE_TYPE,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { import {
@@ -21,26 +24,52 @@ import {
ReservationItemService, ReservationItemService,
InventoryLevelService, InventoryLevelService,
} from "./" } from "./"
import { EntityManager } from "typeorm"
type InjectedDependencies = { type InjectedDependencies = {
manager: EntityManager
eventBusService: IEventBusService eventBusService: IEventBusService
} }
export default class InventoryService implements IInventoryService { export default class InventoryService
extends TransactionBaseService
implements IInventoryService
{
protected readonly eventBusService_: IEventBusService protected readonly eventBusService_: IEventBusService
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly inventoryItemService_: InventoryItemService protected readonly inventoryItemService_: InventoryItemService
protected readonly reservationItemService_: ReservationItemService protected readonly reservationItemService_: ReservationItemService
protected readonly inventoryLevelService_: InventoryLevelService protected readonly inventoryLevelService_: InventoryLevelService
constructor({ eventBusService }: InjectedDependencies) { constructor(
this.eventBusService_ = eventBusService { eventBusService, manager }: InjectedDependencies,
options?: unknown,
moduleDeclaration?: ConfigurableModuleDeclaration
) {
super(arguments[0])
this.inventoryItemService_ = new InventoryItemService({ eventBusService }) if (moduleDeclaration?.resources !== MODULE_RESOURCE_TYPE.SHARED) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"At the moment this module can only be used with shared resources"
)
}
this.eventBusService_ = eventBusService
this.manager_ = manager
this.inventoryItemService_ = new InventoryItemService({
eventBusService,
manager,
})
this.inventoryLevelService_ = new InventoryLevelService({ this.inventoryLevelService_ = new InventoryLevelService({
eventBusService, eventBusService,
manager,
}) })
this.reservationItemService_ = new ReservationItemService({ this.reservationItemService_ = new ReservationItemService({
eventBusService, eventBusService,
manager,
inventoryLevelService: this.inventoryLevelService_, inventoryLevelService: this.inventoryLevelService_,
}) })
} }
@@ -1,4 +1,4 @@
import { getConnection, DeepPartial, EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils" import { isDefined, MedusaError } from "medusa-core-utils"
import { import {
FindConfig, FindConfig,
@@ -16,6 +16,7 @@ import { InventoryLevelService } from "."
type InjectedDependencies = { type InjectedDependencies = {
eventBusService: IEventBusService eventBusService: IEventBusService
manager: EntityManager
inventoryLevelService: InventoryLevelService inventoryLevelService: InventoryLevelService
} }
@@ -34,24 +35,20 @@ export default class ReservationItemService extends TransactionBaseService {
constructor({ constructor({
eventBusService, eventBusService,
manager,
inventoryLevelService, inventoryLevelService,
}: InjectedDependencies) { }: InjectedDependencies) {
super(arguments[0]) super(arguments[0])
this.manager_ = this.getManager() this.manager_ = manager
this.eventBusService_ = eventBusService this.eventBusService_ = eventBusService
this.inventoryLevelService_ = inventoryLevelService this.inventoryLevelService_ = inventoryLevelService
} }
private getManager(): EntityManager { private getManager(): EntityManager {
if (this.manager_) {
return this.transactionManager_ ?? this.manager_ return this.transactionManager_ ?? this.manager_
} }
const connection = getConnection(CONNECTION_NAME)
return connection.manager
}
/** /**
* Lists reservation items that match the provided filter. * Lists reservation items that match the provided filter.
* @param selector - Filters to apply to the reservation items. * @param selector - Filters to apply to the reservation items.
@@ -126,7 +123,7 @@ export default class ReservationItemService extends TransactionBaseService {
* @return The created reservation item. * @return The created reservation item.
*/ */
async create(data: CreateReservationItemInput): Promise<ReservationItem> { async create(data: CreateReservationItemInput): Promise<ReservationItem> {
const result = await this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const itemRepository = manager.getRepository(ReservationItem) const itemRepository = manager.getRepository(ReservationItem)
const inventoryItem = itemRepository.create({ const inventoryItem = itemRepository.create({
@@ -148,14 +145,14 @@ export default class ReservationItemService extends TransactionBaseService {
), ),
]) ])
await this.eventBusService_
.withTransaction(manager)
.emit(ReservationItemService.Events.CREATED, {
id: newInventoryItem.id,
})
return newInventoryItem return newInventoryItem
}) })
await this.eventBusService_.emit(ReservationItemService.Events.CREATED, {
id: result.id,
})
return result
} }
/** /**
@@ -168,7 +165,7 @@ export default class ReservationItemService extends TransactionBaseService {
reservationItemId: string, reservationItemId: string,
data: UpdateReservationItemInput data: UpdateReservationItemInput
): Promise<ReservationItem> { ): Promise<ReservationItem> {
const updatedItem = await this.atomicPhase_(async (manager) => { return await this.atomicPhase_(async (manager) => {
const itemRepository = manager.getRepository(ReservationItem) const itemRepository = manager.getRepository(ReservationItem)
const item = await this.retrieve(reservationItemId) const item = await this.retrieve(reservationItemId)
@@ -196,14 +193,14 @@ export default class ReservationItemService extends TransactionBaseService {
await Promise.all(ops) await Promise.all(ops)
await this.eventBusService_
.withTransaction(manager)
.emit(ReservationItemService.Events.UPDATED, {
id: mergedItem.id,
})
return mergedItem return mergedItem
}) })
await this.eventBusService_.emit(ReservationItemService.Events.UPDATED, {
id: updatedItem.id,
})
return updatedItem
} }
/** /**
@@ -230,14 +227,13 @@ export default class ReservationItemService extends TransactionBaseService {
) )
} }
await Promise.all(ops) await Promise.all(ops)
})
await this.eventBusService_.emit( await this.eventBusService_
ReservationItemService.Events.DELETED_BY_LINE_ITEM, .withTransaction(manager)
{ .emit(ReservationItemService.Events.DELETED_BY_LINE_ITEM, {
line_item_id: lineItemId, line_item_id: lineItemId,
} })
) })
} }
/** /**
+69
View File
@@ -0,0 +1,69 @@
import { EntityManager, ILike } from "typeorm"
import {
buildQuery,
FilterableInventoryItemProps,
FindConfig,
} from "@medusajs/medusa"
import { InventoryItem } from "../models"
export function getListQuery(
manager: EntityManager,
selector: FilterableInventoryItemProps = {},
config: FindConfig<InventoryItem> = { relations: [], skip: 0, take: 10 }
) {
const inventoryItemRepository = manager.getRepository(InventoryItem)
const query = buildQuery(selector, config)
const queryBuilder = inventoryItemRepository.createQueryBuilder("inv_item")
if (query.where.q) {
query.where.sku = ILike(`%${query.where.q as string}%`)
delete query.where.q
}
if ("location_id" in query.where) {
const locationIds = Array.isArray(selector.location_id)
? selector.location_id
: [selector.location_id]
queryBuilder.innerJoin(
"inventory_level",
"level",
"level.inventory_item_id = inv_item.id AND level.location_id IN (:...locationIds)",
{ locationIds }
)
delete query.where.location_id
}
if (query.take) {
queryBuilder.take(query.take)
}
if (query.skip) {
queryBuilder.skip(query.skip)
}
if (query.where) {
queryBuilder.where(query.where)
}
if (query.select) {
queryBuilder.select(query.select.map((s) => "inv_item." + s))
}
if (query.order) {
const toSelect: string[] = []
const parsed = Object.entries(query.order).reduce((acc, [k, v]) => {
const key = `inv_item.${k}`
toSelect.push(key)
acc[key] = v
return acc
}, {})
queryBuilder.addSelect(toSelect)
queryBuilder.orderBy(parsed)
}
return queryBuilder
}
@@ -1,3 +1,6 @@
import { EntityManager } from "typeorm"
export interface IEventBusService { export interface IEventBusService {
emit(event: string, data: any): Promise<void> emit(event: string, data: any): Promise<void>
withTransaction(transactionManager?: EntityManager): this
} }
@@ -2,6 +2,12 @@ const loader = ({}) => {
throw new Error("loader") throw new Error("loader")
} }
export const service = class TestService {} const service = class TestService {}
export const migrations = [] const migrations = []
export const loaders = [loader] const loaders = [loader]
export default {
service,
migrations,
loaders,
}
@@ -1,3 +1,11 @@
export const service = class TestService {} const service = class TestService {}
export const migrations = [] const migrations = []
export const loaders = [] const loaders = []
const models = []
export default {
service,
migrations,
loaders,
models,
}
@@ -1,2 +1,7 @@
export const migrations = [] const migrations = []
export const loaders = [] const loaders = []
export default {
migrations,
loaders,
}
@@ -1,5 +1,9 @@
// import resolveCwd from "resolve-cwd" import {
import { ConfigModule } from "../../types/global" ConfigModule,
ModuleDefinition,
MODULE_RESOURCE_TYPE,
MODULE_SCOPE,
} from "../../types/global"
import ModuleDefinitionLoader from "../module-definitions" import ModuleDefinitionLoader from "../module-definitions"
import MODULE_DEFINITIONS from "../module-definitions/definitions" import MODULE_DEFINITIONS from "../module-definitions/definitions"
@@ -7,13 +11,17 @@ const RESOLVED_PACKAGE = "@medusajs/test-service-resolved"
jest.mock("resolve-cwd", () => jest.fn(() => RESOLVED_PACKAGE)) jest.mock("resolve-cwd", () => jest.fn(() => RESOLVED_PACKAGE))
describe("module definitions loader", () => { describe("module definitions loader", () => {
const defaultDefinition = { const defaultDefinition: ModuleDefinition = {
key: "testService", key: "testService",
registrationName: "testService", registrationName: "testService",
defaultPackage: "@medusajs/test-service", defaultPackage: "@medusajs/test-service",
label: "TestService", label: "TestService",
isRequired: false, isRequired: false,
canOverride: true, canOverride: true,
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
} }
beforeEach(() => { beforeEach(() => {
@@ -33,6 +41,10 @@ describe("module definitions loader", () => {
resolutionPath: defaultDefinition.defaultPackage, resolutionPath: defaultDefinition.defaultPackage,
definition: defaultDefinition, definition: defaultDefinition,
options: {}, options: {},
moduleDeclaration: {
scope: "internal",
resources: "shared",
},
}) })
}) })
@@ -82,6 +94,10 @@ describe("module definitions loader", () => {
resolutionPath: false, resolutionPath: false,
definition: definition, definition: definition,
options: {}, options: {},
moduleDeclaration: {
scope: "internal",
resources: "shared",
},
}) })
}) })
}) })
@@ -100,6 +116,10 @@ describe("module definitions loader", () => {
resolutionPath: RESOLVED_PACKAGE, resolutionPath: RESOLVED_PACKAGE,
definition: defaultDefinition, definition: defaultDefinition,
options: {}, options: {},
moduleDeclaration: {
scope: "internal",
resources: "shared",
},
}) })
}) })
}) })
@@ -112,6 +132,7 @@ describe("module definitions loader", () => {
modules: { modules: {
[defaultDefinition.key]: { [defaultDefinition.key]: {
resolve: defaultDefinition.defaultPackage, resolve: defaultDefinition.defaultPackage,
resources: MODULE_RESOURCE_TYPE.ISOLATED,
}, },
}, },
} as ConfigModule) } as ConfigModule)
@@ -120,6 +141,11 @@ describe("module definitions loader", () => {
resolutionPath: RESOLVED_PACKAGE, resolutionPath: RESOLVED_PACKAGE,
definition: defaultDefinition, definition: defaultDefinition,
options: {}, options: {},
moduleDeclaration: {
scope: "internal",
resources: "isolated",
resolve: defaultDefinition.defaultPackage,
},
}) })
}) })
@@ -138,6 +164,11 @@ describe("module definitions loader", () => {
resolutionPath: defaultDefinition.defaultPackage, resolutionPath: defaultDefinition.defaultPackage,
definition: defaultDefinition, definition: defaultDefinition,
options: { test: 123 }, options: { test: 123 },
moduleDeclaration: {
scope: "internal",
resources: "shared",
options: { test: 123 },
},
}) })
}) })
@@ -149,6 +180,8 @@ describe("module definitions loader", () => {
[defaultDefinition.key]: { [defaultDefinition.key]: {
resolve: defaultDefinition.defaultPackage, resolve: defaultDefinition.defaultPackage,
options: { test: 123 }, options: { test: 123 },
scope: "internal",
resources: "isolated",
}, },
}, },
} as unknown as ConfigModule) } as unknown as ConfigModule)
@@ -157,6 +190,12 @@ describe("module definitions loader", () => {
resolutionPath: RESOLVED_PACKAGE, resolutionPath: RESOLVED_PACKAGE,
definition: defaultDefinition, definition: defaultDefinition,
options: { test: 123 }, options: { test: 123 },
moduleDeclaration: {
scope: "internal",
resources: "isolated",
resolve: defaultDefinition.defaultPackage,
options: { test: 123 },
},
}) })
}) })
}) })
@@ -6,13 +6,13 @@ import {
createContainer, createContainer,
Resolver, Resolver,
} from "awilix" } from "awilix"
import { mkdirSync, rmSync, writeFileSync } from "fs"
import Logger from "../logger" import Logger from "../logger"
import { resolve } from "path"
import { import {
ConfigModule, ConfigModule,
MedusaContainer, MedusaContainer,
ModuleResolution, ModuleResolution,
MODULE_RESOURCE_TYPE,
MODULE_SCOPE,
} from "../../types/global" } from "../../types/global"
import registerModules from "../module" import registerModules from "../module"
import { trackInstallation } from "../__mocks__/medusa-telemetry" import { trackInstallation } from "../__mocks__/medusa-telemetry"
@@ -90,6 +90,10 @@ describe("modules loader", () => {
key: "testService", key: "testService",
defaultPackage: "testService", defaultPackage: "testService",
label: "TestService", label: "TestService",
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
}, },
}, },
} }
@@ -114,6 +118,10 @@ describe("modules loader", () => {
key: "testService", key: "testService",
defaultPackage: "testService", defaultPackage: "testService",
label: "TestService", label: "TestService",
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
}, },
}, },
} }
@@ -149,6 +157,10 @@ describe("modules loader", () => {
key: "testService", key: "testService",
defaultPackage: "testService", defaultPackage: "testService",
label: "TestService", label: "TestService",
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
}, },
}, },
} }
@@ -177,6 +189,10 @@ describe("modules loader", () => {
key: "testService", key: "testService",
defaultPackage: "testService", defaultPackage: "testService",
label: "TestService", label: "TestService",
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
}, },
}, },
} }
@@ -207,6 +223,10 @@ describe("modules loader", () => {
defaultPackage: "testService", defaultPackage: "testService",
label: "TestService", label: "TestService",
isRequired: true, isRequired: true,
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
}, },
}, },
} }
+11 -11
View File
@@ -115,17 +115,6 @@ export default async ({
const rAct = Logger.success(repoActivity, "Repositories initialized") || {} const rAct = Logger.success(repoActivity, "Repositories initialized") || {}
track("REPOSITORIES_INIT_COMPLETED", { duration: rAct.duration }) track("REPOSITORIES_INIT_COMPLETED", { duration: rAct.duration })
const dbActivity = Logger.activity(`Initializing database${EOL}`)
track("DATABASE_INIT_STARTED")
const dbConnection = await databaseLoader({
container,
configModule,
})
const dbAct = Logger.success(dbActivity, "Database initialized") || {}
track("DATABASE_INIT_COMPLETED", { duration: dbAct.duration })
container.register({ manager: asValue(dbConnection.manager) })
const stratActivity = Logger.activity(`Initializing strategies${EOL}`) const stratActivity = Logger.activity(`Initializing strategies${EOL}`)
track("STRATEGIES_INIT_STARTED") track("STRATEGIES_INIT_STARTED")
strategiesLoader({ container, configModule, isTest }) strategiesLoader({ container, configModule, isTest })
@@ -138,6 +127,17 @@ export default async ({
const modAct = Logger.success(modulesActivity, "Modules initialized") || {} const modAct = Logger.success(modulesActivity, "Modules initialized") || {}
track("MODULES_INIT_COMPLETED", { duration: modAct.duration }) track("MODULES_INIT_COMPLETED", { duration: modAct.duration })
const dbActivity = Logger.activity(`Initializing database${EOL}`)
track("DATABASE_INIT_STARTED")
const dbConnection = await databaseLoader({
container,
configModule,
})
const dbAct = Logger.success(dbActivity, "Database initialized") || {}
track("DATABASE_INIT_COMPLETED", { duration: dbAct.duration })
container.register({ manager: asValue(dbConnection.manager) })
const servicesActivity = Logger.activity(`Initializing services${EOL}`) const servicesActivity = Logger.activity(`Initializing services${EOL}`)
track("SERVICES_INIT_STARTED") track("SERVICES_INIT_STARTED")
servicesLoader({ container, configModule, isTest }) servicesLoader({ container, configModule, isTest })
@@ -1,4 +1,8 @@
import { ModuleDefinition } from "../../types/global" import {
ModuleDefinition,
MODULE_RESOURCE_TYPE,
MODULE_SCOPE,
} from "../../types/global"
export const MODULE_DEFINITIONS: ModuleDefinition[] = [ export const MODULE_DEFINITIONS: ModuleDefinition[] = [
{ {
@@ -8,6 +12,10 @@ export const MODULE_DEFINITIONS: ModuleDefinition[] = [
label: "StockLocationService", label: "StockLocationService",
isRequired: false, isRequired: false,
canOverride: true, canOverride: true,
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
}, },
{ {
key: "inventoryService", key: "inventoryService",
@@ -16,6 +24,10 @@ export const MODULE_DEFINITIONS: ModuleDefinition[] = [
label: "InventoryService", label: "InventoryService",
isRequired: false, isRequired: false,
canOverride: true, canOverride: true,
defaultModuleDeclaration: {
scope: MODULE_SCOPE.INTERNAL,
resources: MODULE_RESOURCE_TYPE.SHARED,
},
}, },
] ]
@@ -40,9 +40,16 @@ export default ({ modules }: ConfigModule) => {
) )
} }
const moduleDeclaration =
typeof moduleConfiguration === "object" ? moduleConfiguration : {}
moduleResolutions[definition.key] = { moduleResolutions[definition.key] = {
resolutionPath, resolutionPath,
definition, definition,
moduleDeclaration: {
...definition.defaultModuleDeclaration,
...moduleDeclaration,
},
options: options:
typeof moduleConfiguration === "object" typeof moduleConfiguration === "object"
? moduleConfiguration.options ?? {} ? moduleConfiguration.options ?? {}
+51 -26
View File
@@ -1,21 +1,26 @@
import { asFunction, asValue } from "awilix" import { asClass, asFunction, asValue } from "awilix"
import { trackInstallation } from "medusa-telemetry" import { trackInstallation } from "medusa-telemetry"
import { ConfigModule, Logger, MedusaContainer } from "../types/global" import { EntitySchema } from "typeorm"
import {
ClassConstructor,
ConfigModule,
LoaderOptions,
Logger,
MedusaContainer,
ModuleExports,
ModuleResolution,
MODULE_RESOURCE_TYPE,
MODULE_SCOPE,
} from "../types/global"
import { ModulesHelper } from "../utils/module-helper" import { ModulesHelper } from "../utils/module-helper"
type Options = {
container: MedusaContainer
configModule: ConfigModule
logger: Logger
}
export const moduleHelper = new ModulesHelper() export const moduleHelper = new ModulesHelper()
const registerModule = async ( const registerModule = async (
container, container: MedusaContainer,
resolution, resolution: ModuleResolution,
configModule, configModule: ConfigModule,
logger logger: Logger
): Promise<{ error?: Error } | void> => { ): Promise<{ error?: Error } | void> => {
if (!resolution.resolutionPath) { if (!resolution.resolutionPath) {
container.register({ container.register({
@@ -25,9 +30,9 @@ const registerModule = async (
return return
} }
let loadedModule let loadedModule: ModuleExports
try { try {
loadedModule = await import(resolution.resolutionPath!) loadedModule = (await import(resolution.resolutionPath!)).default
} catch (error) { } catch (error) {
return { error } return { error }
} }
@@ -42,15 +47,41 @@ const registerModule = async (
} }
} }
if (
resolution.moduleDeclaration?.scope === MODULE_SCOPE.INTERNAL &&
resolution.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.SHARED
) {
const moduleModels = loadedModule?.models || null
if (moduleModels) {
moduleModels.map((val: ClassConstructor<unknown>) => {
container.registerAdd("db_entities", asValue(val))
})
}
}
// TODO: "cradle" should only contain dependent Modules and the EntityManager if module scope is shared
container.register({
[resolution.definition.registrationName]: asFunction((cradle) => {
return new moduleService(
cradle,
resolution.options,
resolution.moduleDeclaration
)
}).singleton(),
})
const moduleLoaders = loadedModule?.loaders || [] const moduleLoaders = loadedModule?.loaders || []
try { try {
for (const loader of moduleLoaders) { for (const loader of moduleLoaders) {
await loader({ await loader(
{
container, container,
configModule, configModule,
logger, logger,
options: resolution.options, options: resolution.options,
}) },
resolution.moduleDeclaration
)
} }
} catch (err) { } catch (err) {
return { return {
@@ -60,12 +91,6 @@ const registerModule = async (
} }
} }
container.register({
[resolution.definition.registrationName]: asFunction(
(cradle) => new moduleService(cradle, resolution.options)
).singleton(),
})
trackInstallation( trackInstallation(
{ {
module: resolution.definition.key, module: resolution.definition.key,
@@ -79,7 +104,7 @@ export default async ({
container, container,
configModule, configModule,
logger, logger,
}: Options): Promise<void> => { }: LoaderOptions): Promise<void> => {
const moduleResolutions = configModule?.moduleResolutions ?? {} const moduleResolutions = configModule?.moduleResolutions ?? {}
for (const resolution of Object.values(moduleResolutions)) { for (const resolution of Object.values(moduleResolutions)) {
@@ -87,18 +112,18 @@ export default async ({
container, container,
resolution, resolution,
configModule, configModule,
logger logger!
) )
if (registrationResult?.error) { if (registrationResult?.error) {
const { error } = registrationResult const { error } = registrationResult
if (resolution.definition.isRequired) { if (resolution.definition.isRequired) {
logger.warn( logger?.warn(
`Could not resolve required module: ${resolution.definition.label}. Error: ${error.message}` `Could not resolve required module: ${resolution.definition.label}. Error: ${error.message}`
) )
throw error throw error
} }
logger.warn( logger?.warn(
`Could not resolve module: ${resolution.definition.label}. Error: ${error.message}` `Could not resolve module: ${resolution.definition.label}. Error: ${error.message}`
) )
} }
+49 -3
View File
@@ -37,10 +37,38 @@ export type Logger = _Logger & {
warn: (msg: string) => void warn: (msg: string) => void
} }
export enum MODULE_SCOPE {
INTERNAL = "internal",
EXTERNAL = "external",
}
export enum MODULE_RESOURCE_TYPE {
SHARED = "shared",
ISOLATED = "isolated",
}
export type ConfigurableModuleDeclaration = {
scope: MODULE_SCOPE.INTERNAL
resources: MODULE_RESOURCE_TYPE
resolve?: string
options?: Record<string, unknown>
}
/*
| {
scope: MODULE_SCOPE.external
server: {
type: "built-in" | "rest" | "tsrpc" | "grpc" | "gql"
url: string
options?: Record<string, unknown>
}
}
*/
export type ModuleResolution = { export type ModuleResolution = {
resolutionPath: string | false resolutionPath: string | false
definition: ModuleDefinition definition: ModuleDefinition
options?: Record<string, unknown> options?: Record<string, unknown>
moduleDeclaration?: ConfigurableModuleDeclaration
} }
export type ModuleDefinition = { export type ModuleDefinition = {
@@ -50,11 +78,26 @@ export type ModuleDefinition = {
label: string label: string
canOverride?: boolean canOverride?: boolean
isRequired?: boolean isRequired?: boolean
defaultModuleDeclaration: ConfigurableModuleDeclaration
} }
export type ConfigurableModuleDeclaration = { export type LoaderOptions = {
resolve?: string container: MedusaContainer
configModule: ConfigModule
options?: Record<string, unknown> options?: Record<string, unknown>
logger?: Logger
}
export type Constructor<T> = new (...args: any[]) => T
export type ModuleExports = {
loaders: ((
options: LoaderOptions,
moduleDeclaration?: ConfigurableModuleDeclaration
) => Promise<void>)[]
service: Constructor<any>
migrations?: any[] // TODO: revisit migrations type
models?: Constructor<any>[]
} }
export type ConfigModule = { export type ConfigModule = {
@@ -77,7 +120,10 @@ export type ConfigModule = {
admin_cors?: string admin_cors?: string
} }
featureFlags: Record<string, boolean | string> featureFlags: Record<string, boolean | string>
modules?: Record<string, false | string | ConfigurableModuleDeclaration> modules?: Record<
string,
false | string | Partial<ConfigurableModuleDeclaration>
>
moduleResolutions?: Record<string, ModuleResolution> moduleResolutions?: Record<string, ModuleResolution>
plugins: ( plugins: (
| { | {
-7
View File
@@ -1,7 +0,0 @@
import ConnectionLoader from "./loaders/connection"
import StockLocationService from "./services/stock-location"
import * as SchemaMigration from "./migrations/schema-migrations/1665749860179-setup"
export const service = StockLocationService
export const migrations = [SchemaMigration]
export const loaders = [ConnectionLoader]
+20
View File
@@ -0,0 +1,20 @@
import ConnectionLoader from "./loaders/connection"
import StockLocationService from "./services/stock-location"
import * as SchemaMigration from "./migrations/schema-migrations/1665749860179-setup"
import * as StockLocationModels from "./models"
import { ModuleExports } from "@medusajs/medusa"
const service = StockLocationService
const migrations = [SchemaMigration]
const loaders = [ConnectionLoader]
const models = Object.values(StockLocationModels)
const moduleDefinition: ModuleExports = {
service,
migrations,
loaders,
models,
}
export default moduleDefinition
@@ -1,22 +1,6 @@
import { ConfigModule } from "@medusajs/medusa" import { ConfigurableModuleDeclaration, LoaderOptions } from "@medusajs/medusa"
import { ConnectionOptions, createConnection } from "typeorm"
import { CONNECTION_NAME } from "../config"
import { StockLocation, StockLocationAddress } from "../models" export default async (
{ configModule }: LoaderOptions,
export default async ({ moduleDeclaration?: ConfigurableModuleDeclaration
configModule, ): Promise<void> => {}
}: {
configModule: ConfigModule
}): Promise<void> => {
await createConnection({
name: CONNECTION_NAME,
type: configModule.projectConfig.database_type,
url: configModule.projectConfig.database_url,
database: configModule.projectConfig.database_database,
schema: configModule.projectConfig.database_schema,
extra: configModule.projectConfig.database_extra || {},
entities: [StockLocation, StockLocationAddress],
logging: configModule.projectConfig.database_logging || false,
} as ConnectionOptions)
}
@@ -1,4 +1,4 @@
import { getConnection, EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { isDefined, MedusaError } from "medusa-core-utils" import { isDefined, MedusaError } from "medusa-core-utils"
import { import {
FindConfig, FindConfig,
@@ -9,12 +9,15 @@ import {
StockLocationAddressInput, StockLocationAddressInput,
IEventBusService, IEventBusService,
setMetadata, setMetadata,
TransactionBaseService,
ConfigurableModuleDeclaration,
MODULE_RESOURCE_TYPE,
} from "@medusajs/medusa" } from "@medusajs/medusa"
import { StockLocation, StockLocationAddress } from "../models" import { StockLocation, StockLocationAddress } from "../models"
import { CONNECTION_NAME } from "../config"
type InjectedDependencies = { type InjectedDependencies = {
manager: EntityManager
eventBusService: IEventBusService eventBusService: IEventBusService
} }
@@ -22,24 +25,38 @@ type InjectedDependencies = {
* Service for managing stock locations. * Service for managing stock locations.
*/ */
export default class StockLocationService { export default class StockLocationService extends TransactionBaseService {
static Events = { static Events = {
CREATED: "stock-location.created", CREATED: "stock-location.created",
UPDATED: "stock-location.updated", UPDATED: "stock-location.updated",
DELETED: "stock-location.deleted", DELETED: "stock-location.deleted",
} }
protected readonly manager_: EntityManager protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly eventBusService_: IEventBusService protected readonly eventBusService_: IEventBusService
constructor({ eventBusService }: InjectedDependencies) { constructor(
{ eventBusService, manager }: InjectedDependencies,
options?: unknown,
moduleDeclaration?: ConfigurableModuleDeclaration
) {
super(arguments[0])
if (moduleDeclaration?.resources !== MODULE_RESOURCE_TYPE.SHARED) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"At the moment this module can only be used with shared resources"
)
}
this.eventBusService_ = eventBusService this.eventBusService_ = eventBusService
this.manager_ = manager
} }
private getManager(): EntityManager { private getManager(): EntityManager {
const connection = getConnection(CONNECTION_NAME) return this.transactionManager_ ?? this.manager_
return connection.manager
} }
/** /**
@@ -99,7 +116,7 @@ export default class StockLocationService {
const locationRepo = manager.getRepository(StockLocation) const locationRepo = manager.getRepository(StockLocation)
const query = buildQuery({ id: stockLocationId }, config) const query = buildQuery({ id: stockLocationId }, config)
const loc = await locationRepo.findOne(query) const [loc] = await locationRepo.find(query)
if (!loc) { if (!loc) {
throw new MedusaError( throw new MedusaError(
@@ -117,8 +134,7 @@ export default class StockLocationService {
* @returns {Promise<StockLocation>} - The created stock location. * @returns {Promise<StockLocation>} - The created stock location.
*/ */
async create(data: CreateStockLocationInput): Promise<StockLocation> { async create(data: CreateStockLocationInput): Promise<StockLocation> {
const defaultManager = this.getManager() return await this.atomicPhase_(async (manager) => {
return await defaultManager.transaction(async (manager) => {
const locationRepo = manager.getRepository(StockLocation) const locationRepo = manager.getRepository(StockLocation)
const loc = locationRepo.create({ const loc = locationRepo.create({
@@ -147,7 +163,9 @@ export default class StockLocationService {
const result = await locationRepo.save(loc) const result = await locationRepo.save(loc)
await this.eventBusService_.emit(StockLocationService.Events.CREATED, { await this.eventBusService_
.withTransaction(manager)
.emit(StockLocationService.Events.CREATED, {
id: result.id, id: result.id,
}) })
@@ -166,17 +184,16 @@ export default class StockLocationService {
stockLocationId: string, stockLocationId: string,
updateData: UpdateStockLocationInput updateData: UpdateStockLocationInput
): Promise<StockLocation> { ): Promise<StockLocation> {
const defaultManager = this.getManager() return await this.atomicPhase_(async (manager) => {
return await defaultManager.transaction(async (manager) => {
const locationRepo = manager.getRepository(StockLocation) const locationRepo = manager.getRepository(StockLocation)
const item = await this.retrieve(stockLocationId) const item = await this.retrieve(stockLocationId)
const { address, metadata, ...data } = updateData const { address, ...data } = updateData
if (address) { if (address) {
if (item.address_id) { if (item.address_id) {
await this.updateAddress(item.address_id, address, { manager }) await this.updateAddress(item.address_id, address)
} else { } else {
const locAddressRepo = manager.getRepository(StockLocationAddress) const locAddressRepo = manager.getRepository(StockLocationAddress)
const locAddress = locAddressRepo.create(address) const locAddress = locAddressRepo.create(address)
@@ -185,14 +202,18 @@ export default class StockLocationService {
} }
} }
const { metadata, ...fields } = updateData
const toSave = locationRepo.merge(item, fields)
if (metadata) { if (metadata) {
item.metadata = setMetadata(item, metadata) toSave.metadata = setMetadata(toSave, metadata)
} }
const toSave = locationRepo.merge(item, data)
await locationRepo.save(toSave) await locationRepo.save(toSave)
await this.eventBusService_.emit(StockLocationService.Events.UPDATED, { await this.eventBusService_
.withTransaction(manager)
.emit(StockLocationService.Events.UPDATED, {
id: stockLocationId, id: stockLocationId,
}) })
@@ -204,20 +225,26 @@ export default class StockLocationService {
* Updates an address for a stock location. * Updates an address for a stock location.
* @param {string} addressId - The ID of the address to update. * @param {string} addressId - The ID of the address to update.
* @param {StockLocationAddressInput} address - The update data for the address. * @param {StockLocationAddressInput} address - The update data for the address.
* @param {Object} context - Context for the update.
* @param {EntityManager} context.manager - The entity manager to use for the update.
* @returns {Promise<StockLocationAddress>} - The updated stock location address. * @returns {Promise<StockLocationAddress>} - The updated stock location address.
*/ */
protected async updateAddress( protected async updateAddress(
addressId: string, addressId: string,
address: StockLocationAddressInput, address: StockLocationAddressInput
context: { manager?: EntityManager } = {}
): Promise<StockLocationAddress> { ): Promise<StockLocationAddress> {
const manager = context.manager || this.getManager() if (!isDefined(addressId)) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`"addressId" must be defined`
)
}
return await this.atomicPhase_(async (manager) => {
const locationAddressRepo = manager.getRepository(StockLocationAddress) const locationAddressRepo = manager.getRepository(StockLocationAddress)
const existingAddress = await locationAddressRepo.findOne(addressId) const existingAddress = await locationAddressRepo.findOne({
id: addressId,
})
if (!existingAddress) { if (!existingAddress) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
@@ -225,14 +252,15 @@ export default class StockLocationService {
) )
} }
const toSave = locationAddressRepo.merge(existingAddress, address) const { metadata, ...fields } = address
const { metadata } = address const toSave = locationAddressRepo.merge(existingAddress, fields)
if (metadata) { if (metadata) {
toSave.metadata = setMetadata(toSave, metadata) toSave.metadata = setMetadata(toSave, metadata)
} }
return await locationAddressRepo.save(toSave) return await locationAddressRepo.save(toSave)
})
} }
/** /**
@@ -241,13 +269,16 @@ export default class StockLocationService {
* @returns {Promise<void>} - An empty promise. * @returns {Promise<void>} - An empty promise.
*/ */
async delete(id: string): Promise<void> { async delete(id: string): Promise<void> {
const manager = this.getManager() return await this.atomicPhase_(async (manager) => {
const locationRepo = manager.getRepository(StockLocation) const locationRepo = manager.getRepository(StockLocation)
await locationRepo.softRemove({ id }) await locationRepo.softRemove({ id })
await this.eventBusService_.emit(StockLocationService.Events.DELETED, { await this.eventBusService_
.withTransaction(manager)
.emit(StockLocationService.Events.DELETED, {
id, id,
}) })
})
} }
} }