chore(): Module Internal Events (#13296)

* chore(): Ensure the product module emits all necessary events

* chore(): Ensure the product module emits all necessary events

* Update events tests

* more events and fixes

* more tests and category fixes

* more tests and category fixes

* Add todo

* update updateProduct_ event emitting and adjust test

* Adjust update products implementation to rely on already computed events

* rm unnecessary update variants events

* Fix formatting in changeset for product events

* refactor: Manage event emitting automatically (WIP)

* refactor: Manage event emitting automatically (WIP)

* chore(api-key): Add missing emit events and refactoring

* chore(cart): Add missing emit events and refactoring

* chore(customer): Add missing emit events and refactoring

* chore(fufillment, utils): Add missing emit events and refactoring

* chore(fufillment, utils): Add missing emit events and refactoring

* chore(inventory): Add missing emit events and refactoring

* chore(notification): Add missing emit events and refactoring

* chore(utils): Remove medusa service event handling legacy

* chore(product): Add missing emit events and refactoring

* chore(order): Add missing emit events and refactoring

* chore(payment): Add missing emit events and refactoring

* chore(pricing, util): Add missing emit events and refactoring, fix internal service upsertWithReplace event dispatching

* chore(promotions): Add missing emit events and refactoring

* chore(region): Add missing emit events and refactoring

* chore(sales-channel): Add missing emit events and refactoring

* chore(settings): Add missing emit events and refactoring

* chore(stock-location): Add missing emit events and refactoring

* chore(store): Add missing emit events and refactoring

* chore(taxes): Add missing emit events and refactoring

* chore(user): Add missing emit events and refactoring

* fix unit tests

* rm changeset for regeneration

* Create changeset for Medusa.js patch updates

Add a changeset for patch updates to multiple Medusa.js modules.

* rm unused product event builders

* address feedback

* remove old changeset

* fix event action for token generated

* fix user module events

* fix import

* fix promotion events

* add new module integration tests shard

* fix medusa service

* revert shard

* fix event action

* fix pipeline

* fix pipeline

---------

Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2025-09-10 14:37:38 +02:00
committed by GitHub
parent afe21741c4
commit e8822f3e69
55 changed files with 3614 additions and 2353 deletions

View File

@@ -79,7 +79,10 @@ export const buildProductAndRelationsData = ({
title: faker.commerce.productName(),
sku: faker.commerce.productName(),
options: options
? { [options[0].title]: options[0].values[0] }
? options.reduce((acc, option) => {
acc[option.title] = option.values[0]
return acc
}, {} as Record<string, string>)
: {
[defaultOptionTitle]: defaultOptionValue,
},

View File

@@ -1,15 +1,11 @@
import { IProductModuleService } from "@medusajs/framework/types"
import {
CommonEvents,
composeMessage,
Modules,
ProductEvents,
ProductStatus,
toMikroORMEntity,
} from "@medusajs/framework/utils"
import { Product, ProductCategory } from "@models"
import {
MockEventBusService,
moduleIntegrationTestRunner,
} from "@medusajs/test-utils"
import { productCategoriesRankData } from "../../__fixtures__/product-category/data"
@@ -18,9 +14,6 @@ jest.setTimeout(30000)
moduleIntegrationTestRunner<IProductModuleService>({
moduleName: Modules.PRODUCT,
injectedDependencies: {
[Modules.EVENT_BUS]: new MockEventBusService(),
},
testSuite: ({ MikroOrmWrapper, service }) => {
describe("ProductModuleService product categories", () => {
let productOne: Product
@@ -404,29 +397,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
)
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const category = await service.createProductCategories({
name: "New Category",
parent_category_id: productCategoryOne.id,
})
expect(eventBusSpy.mock.calls[0][0]).toHaveLength(1)
expect(eventBusSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_CATEGORY_CREATED, {
data: { id: category.id },
object: "product_category",
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
}),
],
{
internal: true,
}
)
})
it("should append rank from an existing category depending on parent", async () => {
await service.createProductCategories({
@@ -504,29 +474,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
productCategoryZeroTwo = categories[5]
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
eventBusSpy.mockClear()
await service.updateProductCategories(productCategoryZero.id, {
name: "New Category",
})
expect(eventBusSpy.mock.calls[0][0]).toHaveLength(1)
expect(eventBusSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_CATEGORY_UPDATED, {
data: { id: productCategoryZero.id },
object: "product_category",
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
}),
],
{
internal: true,
}
)
})
it("should update the name of the category successfully", async () => {
await service.updateProductCategories(productCategoryZero.id, {
@@ -683,30 +630,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
productCategoryTwo = categories[2]
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
eventBusSpy.mockClear()
await service.deleteProductCategories([productCategoryOne.id])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith(
[
expect.objectContaining({
data: { id: productCategoryOne.id },
name: "product.product-category.deleted",
metadata: {
action: CommonEvents.DELETED,
object: "product_category",
source: Modules.PRODUCT,
},
}),
],
{
internal: true,
}
)
})
it("should throw an error when an id does not exist", async () => {
let error

View File

@@ -1,14 +1,10 @@
import { IProductModuleService } from "@medusajs/framework/types"
import {
CommonEvents,
composeMessage,
Modules,
ProductEvents,
ProductStatus,
toMikroORMEntity,
} from "@medusajs/framework/utils"
import {
MockEventBusService,
moduleIntegrationTestRunner,
} from "@medusajs/test-utils"
import { Product, ProductCollection } from "@models"
@@ -18,9 +14,6 @@ jest.setTimeout(30000)
moduleIntegrationTestRunner<IProductModuleService>({
moduleName: Modules.PRODUCT,
injectedDependencies: {
[Modules.EVENT_BUS]: new MockEventBusService(),
},
testSuite: ({ MikroOrmWrapper, service }) => {
describe("ProductModuleService product collections", () => {
let productOne: Product
@@ -284,59 +277,11 @@ moduleIntegrationTestRunner<IProductModuleService>({
expect(collections).toHaveLength(0)
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
await service.deleteProductCollections([collectionId])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith(
[
{
name: "product.product-collection.deleted",
data: { id: collectionId },
metadata: {
action: CommonEvents.DELETED,
object: "product_collection",
source: Modules.PRODUCT,
},
},
],
{
internal: true,
}
)
})
})
describe("updateCollections", () => {
const collectionId = "test-1"
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
await service.upsertProductCollections([
{
id: collectionId,
title: "New Collection",
product_ids: ["product_id"],
},
])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_COLLECTION_UPDATED, {
data: { id: collectionId },
object: "product_collection",
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
}),
],
{
internal: true,
}
)
})
it("should update the value of the collection successfully", async () => {
await service.upsertProductCollections([
@@ -534,28 +479,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
)
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const collections = await service.createProductCollections([
{ title: "New Collection" },
])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_COLLECTION_CREATED, {
data: { id: collections[0].id },
object: "product_collection",
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
}),
],
{
internal: true,
}
)
})
})
})
},

View File

@@ -1,15 +1,11 @@
import { IProductModuleService } from "@medusajs/framework/types"
import {
CommonEvents,
composeMessage,
Modules,
ProductEvents,
ProductStatus,
toMikroORMEntity,
} from "@medusajs/framework/utils"
import { Product, ProductTag } from "@models"
import {
MockEventBusService,
moduleIntegrationTestRunner,
} from "@medusajs/test-utils"
@@ -18,15 +14,6 @@ jest.setTimeout(30000)
moduleIntegrationTestRunner<IProductModuleService>({
moduleName: Modules.PRODUCT,
testSuite: ({ MikroOrmWrapper, service }) => {
let eventBusEmitSpy
beforeEach(() => {
eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit")
})
afterEach(() => {
jest.clearAllMocks()
})
describe("ProductModuleService product tags", () => {
let tagOne: ProductTag
@@ -303,20 +290,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
expect(productTag.value).toEqual("UK")
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1)
expect(eventBusEmitSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_TAG_UPDATED, {
data: { id: productTag.id },
object: "product_tag",
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
}),
],
{
internal: true,
}
)
})
it("should throw an error when an id does not exist", async () => {
@@ -350,20 +323,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
expect(productTag[0]?.value).toEqual("UK")
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1)
expect(eventBusEmitSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_TAG_CREATED, {
data: { id: productTag[0].id },
object: "product_tag",
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
}),
],
{
internal: true,
}
)
})
})
@@ -409,26 +368,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
const newTag = productTags.find((t) => t.value === "new")!
const updatedTag = productTags.find((t) => t.value === "updated")!
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(2)
expect(eventBusEmitSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_TAG_CREATED, {
data: { id: newTag.id },
object: "product_tag",
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
}),
composeMessage(ProductEvents.PRODUCT_TAG_UPDATED, {
data: { id: updatedTag.id },
object: "product_tag",
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
}),
],
{
internal: true,
}
)
})
})
})

View File

@@ -7,15 +7,11 @@ import {
UpdateProductVariantDTO,
} from "@medusajs/framework/types"
import {
CommonEvents,
composeMessage,
Modules,
ProductEvents,
ProductStatus,
} from "@medusajs/framework/utils"
import {
MockEventBusService,
moduleIntegrationTestRunner,
} from "@medusajs/test-utils"
@@ -24,15 +20,6 @@ jest.setTimeout(30000)
moduleIntegrationTestRunner<IProductModuleService>({
moduleName: Modules.PRODUCT,
testSuite: ({ service }) => {
let eventBusEmitSpy
beforeEach(() => {
eventBusEmitSpy = jest.spyOn(MockEventBusService.prototype, "emit")
})
afterEach(() => {
jest.clearAllMocks()
})
describe("ProductModuleService product variants", () => {
let variantOne: ProductVariantDTO
@@ -212,20 +199,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
)
expect(productVariant.title).toEqual("new test")
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1)
expect(eventBusEmitSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_VARIANT_UPDATED, {
data: { id: variantOne.id },
object: "product_variant",
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
}),
],
{
internal: true,
}
)
})
it("should do a partial update on the options of a variant successfully", async () => {
@@ -294,20 +267,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
})
)
expect(eventBusEmitSpy.mock.calls[0][0]).toHaveLength(1)
expect(eventBusEmitSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_VARIANT_CREATED, {
data: { id: variant.id },
object: "product_variant",
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
}),
],
{
internal: true,
}
)
})
it("should correctly associate variants with own product options", async () => {

View File

@@ -4,11 +4,8 @@ import {
ProductTagDTO,
} from "@medusajs/framework/types"
import {
CommonEvents,
composeMessage,
kebabCase,
Modules,
ProductEvents,
ProductStatus,
} from "@medusajs/framework/utils"
import {
@@ -20,7 +17,6 @@ import {
} from "@models"
import {
MockEventBusService,
moduleIntegrationTestRunner,
} from "@medusajs/test-utils"
import { UpdateProductInput } from "@types"
@@ -34,9 +30,6 @@ jest.setTimeout(300000)
moduleIntegrationTestRunner<IProductModuleService>({
moduleName: Modules.PRODUCT,
injectedDependencies: {
[Modules.EVENT_BUS]: new MockEventBusService(),
},
testSuite: ({ MikroOrmWrapper, service }) => {
describe("ProductModuleService products", function () {
let productCollectionOne: ProductCollection
@@ -573,37 +566,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
)
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const updateData = {
...data,
options: data.options,
id: productOne.id,
title: "updated title",
}
await service.upsertProducts([updateData])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_UPDATED, {
data: { id: productOne.id },
object: "product",
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
}),
],
{
internal: true,
}
)
})
it("should add relationships to a product", async () => {
const updateData = {
@@ -1086,29 +1048,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
)
})
it("should emit events through eventBus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const products = await service.createProducts([data])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith(
[
composeMessage(ProductEvents.PRODUCT_CREATED, {
data: { id: products[0].id },
object: "product",
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
}),
],
{
internal: true,
}
)
})
it("should throw because variant doesn't have all options set", async () => {
const error = await service
@@ -1289,75 +1228,6 @@ moduleIntegrationTestRunner<IProductModuleService>({
expect(softDeleted).toHaveLength(1)
})
it("should emit events through eventBus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const products = await service.createProducts([data])
await service.softDeleteProducts([products[0].id])
expect(eventBusSpy).toHaveBeenNthCalledWith(
1,
[
composeMessage(ProductEvents.PRODUCT_CREATED, {
data: { id: products[0].id },
object: "product",
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
}),
],
{
internal: true,
}
)
expect(eventBusSpy).toHaveBeenNthCalledWith(
2,
[
composeMessage(ProductEvents.PRODUCT_DELETED, {
data: { id: [products[0].id] },
object: "product",
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
}),
composeMessage(ProductEvents.PRODUCT_VARIANT_DELETED, {
data: { id: [products[0].variants[0].id] },
object: "product_variant",
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
}),
composeMessage(ProductEvents.PRODUCT_OPTION_DELETED, {
data: { id: [products[0].options[0].id] },
object: "product_option",
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
}),
composeMessage(ProductEvents.PRODUCT_IMAGE_DELETED, {
data: {
id: [products[0].images[0].id],
},
object: "product_image",
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
}),
composeMessage(ProductEvents.PRODUCT_OPTION_VALUE_DELETED, {
data: {
id: [products[0].options[0].values[0].id],
},
object: "product_option_value",
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
}),
],
{
internal: true,
}
)
})
})
describe("restore", function () {

View File

@@ -29,7 +29,7 @@
"resolve:aliases": "tsc --showConfig -p tsconfig.json > tsconfig.resolved.json && tsc-alias -p tsconfig.resolved.json && rimraf tsconfig.resolved.json",
"build": "rimraf dist && tsc --build && npm run resolve:aliases",
"test": "jest --runInBand --bail --forceExit -- src/**/__tests__/**/*.ts",
"test:integration": "jest --bail --forceExit -- integration-tests/__tests__/**/*.ts",
"test:integration": "jest --bail --forceExit -- integration-tests/__tests__/**/*.spec.ts",
"migration:initial": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:create --initial",
"migration:create": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:create",
"migration:up": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts medusa-mikro-orm migration:up",

View File

@@ -452,9 +452,7 @@ export class ProductCategoryRepository extends DALUtils.MikroOrmBaseTreeReposito
}
let productCategory = await manager.findOne<
InferEntityType<typeof ProductCategory>
>(ProductCategory.name, {
id: categoryData.id,
})
>(ProductCategory.name, categoryData.id!)
if (!productCategory) {
throw new MedusaError(

View File

@@ -9,6 +9,7 @@ import {
isPresent,
mergeMetadata,
isDefined,
deepCopy,
} from "@medusajs/framework/utils"
import { SqlEntityManager, wrap } from "@mikro-orm/postgresql"
@@ -80,21 +81,22 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
) => void,
context: Context = {}
): Promise<InferEntityType<typeof Product>[]> {
const productsToUpdate_ = deepCopy(productsToUpdate)
const productIdsToUpdate: string[] = []
productsToUpdate.forEach((productToUpdate) => {
productsToUpdate_.forEach((productToUpdate) => {
ProductRepository.#correctUpdateDTOTypes(productToUpdate)
productIdsToUpdate.push(productToUpdate.id)
})
const relationsToLoad =
ProductRepository.#getProductDeepUpdateRelationsToLoad(productsToUpdate)
ProductRepository.#getProductDeepUpdateRelationsToLoad(productsToUpdate_)
const findOptions = buildQuery(
{ id: productIdsToUpdate },
{
relations: relationsToLoad,
take: productsToUpdate.length,
take: productsToUpdate_.length,
}
)
@@ -111,7 +113,7 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
)
}
for (const productToUpdate of productsToUpdate) {
for (const productToUpdate of productsToUpdate_) {
const product = productsMap.get(productToUpdate.id)!
const wrappedProduct = wrap(product)
@@ -173,7 +175,7 @@ export class ProductRepository extends DALUtils.mikroOrmBaseRepositoryFactory(
// Doing this to ensure updates are returned in the same order they were provided,
// since some core flows rely on this.
// This is a high level of coupling though.
return productsToUpdate.map(
return productsToUpdate_.map(
(productToUpdate) => productsMap.get(productToUpdate.id)!
)
}

View File

@@ -6,30 +6,45 @@ import {
ProductTypes,
} from "@medusajs/framework/types"
import {
createMedusaMikroOrmEventSubscriber,
FreeTextSearchFilterKeyPrefix,
InjectManager,
InjectTransactionManager,
isDefined,
MedusaContext,
MedusaError,
MedusaInternalService,
MedusaService,
ModulesSdkUtils,
registerInternalServiceEventSubscriber,
} from "@medusajs/framework/utils"
import { EntityManager, EventType } from "@mikro-orm/core"
import { ProductCategory } from "@models"
import { ProductCategoryRepository } from "@repositories"
import { UpdateCategoryInput } from "@types"
type InjectedDependencies = {
productCategoryRepository: DAL.TreeRepositoryService
productModuleService: ReturnType<typeof MedusaService>
}
export default class ProductCategoryService {
protected readonly productCategoryRepository_: DAL.TreeRepositoryService
constructor({ productCategoryRepository }: InjectedDependencies) {
this.productCategoryRepository_ = productCategoryRepository
export default class ProductCategoryService extends MedusaInternalService<
InjectedDependencies,
typeof ProductCategory
>(ProductCategory) {
protected readonly productCategoryRepository_: DAL.TreeRepositoryService
protected readonly container: InjectedDependencies
constructor(container: InjectedDependencies) {
// @ts-expect-error
super(...arguments)
this.container = container
this.productCategoryRepository_ = container.productCategoryRepository
}
// TODO: Add support for object filter
@InjectManager("productCategoryRepository_")
// @ts-expect-error
async retrieve(
productCategoryId: string,
config: FindConfig<ProductTypes.ProductCategoryDTO> = {},
@@ -150,6 +165,7 @@ export default class ProductCategoryService {
}
@InjectTransactionManager("productCategoryRepository_")
// @ts-expect-error
async update(
data: UpdateCategoryInput[],
@MedusaContext() sharedContext: Context = {}
@@ -160,14 +176,45 @@ export default class ProductCategoryService {
}
@InjectTransactionManager("productCategoryRepository_")
// @ts-expect-error
async delete(
ids: string[],
@MedusaContext() sharedContext: Context = {}
): Promise<string[]> {
return await this.productCategoryRepository_.delete(ids, sharedContext)
const subscriber = createMedusaMikroOrmEventSubscriber(
[ProductCategory.name],
this.container["productModuleService"]
)
registerInternalServiceEventSubscriber(sharedContext, subscriber)
const deletedIds = await this.productCategoryRepository_.delete(
ids,
sharedContext
)
// Delete are handled a bit differently since we are going to the DB directly, therefore
// just like upsert with replace, we need to dispatch the events manually.
if (deletedIds.length) {
const manager = (sharedContext.transactionManager ??
sharedContext.manager) as EntityManager
const eventManager = manager.getEventManager()
deletedIds.forEach((id) => {
eventManager.dispatchEvent(EventType.afterDelete, {
entity: { id },
meta: {
className: ProductCategory.name,
} as Parameters<typeof eventManager.dispatchEvent>[2],
})
})
}
return deletedIds
}
@InjectTransactionManager("productCategoryRepository_")
// @ts-expect-error
async softDelete(
ids: string[],
@MedusaContext() sharedContext?: Context
@@ -178,6 +225,7 @@ export default class ProductCategoryService {
}
@InjectTransactionManager("productCategoryRepository_")
// @ts-expect-error
async restore(
ids: string[],
@MedusaContext() sharedContext?: Context

View File

@@ -25,6 +25,7 @@ import { ProductCategoryService } from "@services"
import {
arrayDifference,
createMedusaMikroOrmEventSubscriber,
EmitEvents,
generateEntityId,
InjectManager,
@@ -37,11 +38,13 @@ import {
MedusaContext,
MedusaError,
MedusaService,
MessageAggregator,
Modules,
ProductStatus,
removeUndefined,
toHandle,
} from "@medusajs/framework/utils"
import { EntityManager } from "@mikro-orm/core"
import { ProductRepository } from "../repositories"
import {
UpdateCategoryInput,
@@ -52,8 +55,8 @@ import {
UpdateTagInput,
UpdateTypeInput,
} from "../types"
import { eventBuilders } from "../utils"
import { joinerConfig } from "./../joiner-config"
import { eventBuilders } from "../utils/events"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
@@ -331,11 +334,6 @@ export default class ProductModuleService
sharedContext
)
eventBuilders.createdProductVariant({
data: createdVariants,
sharedContext,
})
return createdVariants
}
@@ -366,11 +364,11 @@ export default class ProductModuleService
(variant): variant is ProductTypes.CreateProductVariantDTO => !variant.id
)
let created: InferEntityType<typeof ProductVariant>[] = []
let created: ProductTypes.ProductVariantDTO[] = []
let updated: InferEntityType<typeof ProductVariant>[] = []
if (forCreate.length) {
created = await this.createVariants_(forCreate, sharedContext)
created = await this.createProductVariants(forCreate, sharedContext)
}
if (forUpdate.length) {
updated = await this.updateVariants_(forUpdate, sharedContext)
@@ -493,7 +491,7 @@ export default class ProductModuleService
)
}
const { entities: productVariants, performedActions } =
const { entities: productVariants } =
await this.productVariantService_.upsertWithReplace(
productVariantsWithOptions,
{
@@ -502,19 +500,6 @@ export default class ProductModuleService
sharedContext
)
eventBuilders.createdProductVariant({
data: performedActions.created[ProductVariant.name] ?? [],
sharedContext,
})
eventBuilders.updatedProductVariant({
data: performedActions.updated[ProductVariant.name] ?? [],
sharedContext,
})
eventBuilders.deletedProductVariant({
data: performedActions.deleted[ProductVariant.name] ?? [],
sharedContext,
})
return productVariants
}
@@ -544,11 +529,6 @@ export default class ProductModuleService
ProductTypes.ProductTagDTO[]
>(tags)
eventBuilders.createdProductTag({
data: createdTags,
sharedContext,
})
return Array.isArray(data) ? createdTags : createdTags[0]
}
@@ -561,12 +541,26 @@ export default class ProductModuleService
sharedContext?: Context
): Promise<ProductTypes.ProductTagDTO>
@InjectTransactionManager()
@InjectManager()
@EmitEvents()
async upsertProductTags(
data: ProductTypes.UpsertProductTagDTO[] | ProductTypes.UpsertProductTagDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<ProductTypes.ProductTagDTO[] | ProductTypes.ProductTagDTO> {
const tags = await this.upsertProductTags_(data, sharedContext)
const allTags = await this.baseRepository_.serialize<
ProductTypes.ProductTagDTO[] | ProductTypes.ProductTagDTO
>(Array.isArray(data) ? tags : tags[0])
return allTags
}
@InjectTransactionManager()
protected async upsertProductTags_(
data: ProductTypes.UpsertProductTagDTO[] | ProductTypes.UpsertProductTagDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<InferEntityType<typeof ProductTag>[]> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter((tag): tag is UpdateTagInput => !!tag.id)
const forCreate = input.filter(
@@ -578,25 +572,12 @@ export default class ProductModuleService
if (forCreate.length) {
created = await this.productTagService_.create(forCreate, sharedContext)
eventBuilders.createdProductTag({
data: created,
sharedContext,
})
}
if (forUpdate.length) {
updated = await this.productTagService_.update(forUpdate, sharedContext)
eventBuilders.updatedProductTag({
data: updated,
sharedContext,
})
}
const result = [...created, ...updated]
const allTags = await this.baseRepository_.serialize<
ProductTypes.ProductTagDTO[] | ProductTypes.ProductTagDTO
>(result)
return Array.isArray(data) ? allTags : allTags[0]
return [...created, ...updated]
}
// @ts-expect-error
@@ -647,11 +628,6 @@ export default class ProductModuleService
ProductTypes.ProductTagDTO[]
>(tags)
eventBuilders.updatedProductTag({
data: updatedTags,
sharedContext,
})
return isString(idOrSelector) ? updatedTags[0] : updatedTags
}
@@ -667,6 +643,7 @@ export default class ProductModuleService
): Promise<ProductTypes.ProductTypeDTO>
@InjectManager()
@EmitEvents()
// @ts-expect-error
async createProductTypes(
data:
@@ -694,13 +671,30 @@ export default class ProductModuleService
sharedContext?: Context
): Promise<ProductTypes.ProductTypeDTO>
@InjectTransactionManager()
@InjectManager()
@EmitEvents()
async upsertProductTypes(
data:
| ProductTypes.UpsertProductTypeDTO[]
| ProductTypes.UpsertProductTypeDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO> {
const types = await this.upsertProductTypes_(data, sharedContext)
const result = await this.baseRepository_.serialize<
ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO
>(types)
return Array.isArray(data) ? result : result[0]
}
@InjectTransactionManager()
protected async upsertProductTypes_(
data:
| ProductTypes.UpsertProductTypeDTO
| ProductTypes.UpsertProductTypeDTO[],
sharedContext?: Context
): Promise<InferEntityType<typeof ProductType>[]> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter((type): type is UpdateTypeInput => !!type.id)
const forCreate = input.filter(
@@ -717,12 +711,7 @@ export default class ProductModuleService
updated = await this.productTypeService_.update(forUpdate, sharedContext)
}
const result = [...created, ...updated]
const allTypes = await this.baseRepository_.serialize<
ProductTypes.ProductTypeDTO[] | ProductTypes.ProductTypeDTO
>(result)
return Array.isArray(data) ? allTypes : allTypes[0]
return [...created, ...updated]
}
// @ts-expect-error
@@ -739,6 +728,7 @@ export default class ProductModuleService
): Promise<ProductTypes.ProductTypeDTO[]>
@InjectManager()
@EmitEvents()
// @ts-expect-error
async updateProductTypes(
idOrSelector: string | ProductTypes.FilterableProductTypeProps,
@@ -787,6 +777,7 @@ export default class ProductModuleService
): Promise<ProductTypes.ProductOptionDTO>
@InjectManager()
@EmitEvents()
// @ts-expect-error
async createProductOptions(
data:
@@ -842,6 +833,7 @@ export default class ProductModuleService
): Promise<ProductTypes.ProductOptionDTO>
@InjectTransactionManager()
@EmitEvents()
async upsertProductOptions(
data:
| ProductTypes.UpsertProductOptionDTO[]
@@ -888,6 +880,7 @@ export default class ProductModuleService
): Promise<ProductTypes.ProductOptionDTO[]>
@InjectManager()
@EmitEvents()
// @ts-expect-error
async updateProductOptions(
idOrSelector: string | ProductTypes.FilterableProductOptionProps,
@@ -1023,11 +1016,6 @@ export default class ProductModuleService
ProductTypes.ProductCollectionDTO[]
>(collections)
eventBuilders.createdProductCollection({
data: collections,
sharedContext,
})
return Array.isArray(data) ? createdCollections : createdCollections[0]
}
@@ -1061,7 +1049,7 @@ export default class ProductModuleService
sharedContext?: Context
): Promise<ProductTypes.ProductCollectionDTO>
@InjectTransactionManager()
@InjectManager()
@EmitEvents()
async upsertProductCollections(
data:
@@ -1071,6 +1059,24 @@ export default class ProductModuleService
): Promise<
ProductTypes.ProductCollectionDTO[] | ProductTypes.ProductCollectionDTO
> {
const collections = await this.upsertCollections_(data, sharedContext)
const serializedCollections = await this.baseRepository_.serialize<
ProductTypes.ProductCollectionDTO[]
>(collections)
return Array.isArray(data)
? serializedCollections
: serializedCollections[0]
}
@InjectTransactionManager()
protected async upsertCollections_(
data:
| ProductTypes.UpsertProductCollectionDTO[]
| ProductTypes.UpsertProductCollectionDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<InferEntityType<typeof ProductCollection>[]> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter(
(collection): collection is UpdateCollectionInput => !!collection.id
@@ -1091,26 +1097,7 @@ export default class ProductModuleService
updated = await this.updateCollections_(forUpdate, sharedContext)
}
const result = [...created, ...updated]
const allCollections = await this.baseRepository_.serialize<
ProductTypes.ProductCollectionDTO[] | ProductTypes.ProductCollectionDTO
>(result)
if (created.length) {
eventBuilders.createdProductCollection({
data: created,
sharedContext,
})
}
if (updated.length) {
eventBuilders.updatedProductCollection({
data: updated,
sharedContext,
})
}
return Array.isArray(data) ? allCollections : allCollections[0]
return [...created, ...updated]
}
// @ts-expect-error
@@ -1166,11 +1153,6 @@ export default class ProductModuleService
ProductTypes.ProductCollectionDTO[]
>(collections)
eventBuilders.updatedProductCollection({
data: updatedCollections,
sharedContext,
})
return isString(idOrSelector) ? updatedCollections[0] : updatedCollections
}
@@ -1283,6 +1265,7 @@ export default class ProductModuleService
ProductTypes.ProductCategoryDTO[]
>(categories)
// TODO: Same as the update categories, for some reason I cant get the tree repository update
eventBuilders.createdProductCategory({
data: createdCategories,
sharedContext,
@@ -1300,7 +1283,7 @@ export default class ProductModuleService
sharedContext?: Context
): Promise<ProductTypes.ProductCategoryDTO>
@InjectTransactionManager()
@InjectManager()
@EmitEvents()
async upsertProductCategories(
data:
@@ -1310,11 +1293,27 @@ export default class ProductModuleService
): Promise<
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
> {
const categories = await this.upsertProductCategories_(data, sharedContext)
const serializedCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(categories)
return Array.isArray(data) ? serializedCategories : serializedCategories[0]
}
@InjectTransactionManager()
protected async upsertProductCategories_(
data:
| ProductTypes.UpsertProductCategoryDTO[]
| ProductTypes.UpsertProductCategoryDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<InferEntityType<typeof ProductCategory>[]> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter(
(category): category is UpdateCategoryInput => !!category.id
)
const forCreate = input.filter(
let forCreate = input.filter(
(category): category is ProductTypes.CreateProductCategoryDTO =>
!category.id
)
@@ -1323,6 +1322,11 @@ export default class ProductModuleService
let updated: InferEntityType<typeof ProductCategory>[] = []
if (forCreate.length) {
forCreate = forCreate.map((productCategory) => {
productCategory.handle ??= kebabCase(productCategory.name)
return productCategory
})
created = await this.productCategoryService_.create(
forCreate,
sharedContext
@@ -1335,25 +1339,22 @@ export default class ProductModuleService
)
}
const createdCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(created)
const updatedCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(updated)
// TODO: Same as the update categories, for some reason I cant get the tree repository update
// event. I ll need to investigate this
if (created.length) {
eventBuilders.createdProductCategory({
data: created,
sharedContext,
})
}
if (updated.length) {
eventBuilders.updatedProductCategory({
data: updated,
sharedContext,
})
}
eventBuilders.createdProductCategory({
data: createdCategories,
sharedContext,
})
eventBuilders.updatedProductCategory({
data: updatedCategories,
sharedContext,
})
const result = [...createdCategories, ...updatedCategories]
return Array.isArray(data) ? result : result[0]
return [...created, ...updated]
}
// @ts-expect-error
@@ -1377,8 +1378,36 @@ export default class ProductModuleService
data: ProductTypes.UpdateProductCategoryDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<
ProductTypes.ProductCategoryDTO[] | ProductTypes.ProductCategoryDTO
ProductTypes.ProductCategoryDTO | ProductTypes.ProductCategoryDTO[]
> {
const categories = await this.updateProductCategories_(
idOrSelector,
data,
sharedContext
)
const serializedCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(categories)
// TODO: for some reason I cant get the tree repository update
// event. I ll need to investigate this
eventBuilders.updatedProductCategory({
data: serializedCategories,
sharedContext,
})
return isString(idOrSelector)
? serializedCategories[0]
: serializedCategories
}
@InjectTransactionManager()
protected async updateProductCategories_(
idOrSelector: string | ProductTypes.FilterableProductTypeProps,
data: ProductTypes.UpdateProductCategoryDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<InferEntityType<typeof ProductCategory>[]> {
let normalizedInput: UpdateCategoryInput[] = []
if (isString(idOrSelector)) {
// Check if the type exists in the first place
@@ -1406,16 +1435,7 @@ export default class ProductModuleService
sharedContext
)
const updatedCategories = await this.baseRepository_.serialize<
ProductTypes.ProductCategoryDTO[]
>(categories)
eventBuilders.updatedProductCategory({
data: updatedCategories,
sharedContext,
})
return isString(idOrSelector) ? updatedCategories[0] : updatedCategories
return categories
}
//@ts-expect-error
@@ -1443,11 +1463,6 @@ export default class ProductModuleService
ProductTypes.ProductDTO[]
>(products)
eventBuilders.createdProduct({
data: createdProducts,
sharedContext,
})
return Array.isArray(data) ? createdProducts : createdProducts[0]
}
@@ -1474,11 +1489,11 @@ export default class ProductModuleService
(product): product is ProductTypes.CreateProductDTO => !product.id
)
let created: InferEntityType<typeof Product>[] = []
let created: ProductTypes.ProductDTO[] = []
let updated: InferEntityType<typeof Product>[] = []
if (forCreate.length) {
created = await this.createProducts_(forCreate, sharedContext)
created = await this.createProducts(forCreate, sharedContext)
}
if (forUpdate.length) {
updated = await this.updateProducts_(forUpdate, sharedContext)
@@ -1489,20 +1504,6 @@ export default class ProductModuleService
ProductTypes.ProductDTO[] | ProductTypes.ProductDTO
>(result)
if (created.length) {
eventBuilders.createdProduct({
data: created,
sharedContext,
})
}
if (updated.length) {
eventBuilders.updatedProduct({
data: updated,
sharedContext,
})
}
return Array.isArray(data) ? allProducts : allProducts[0]
}
@@ -1552,11 +1553,6 @@ export default class ProductModuleService
ProductTypes.ProductDTO[]
>(products)
eventBuilders.updatedProduct({
data: updatedProducts,
sharedContext,
})
return isString(idOrSelector) ? updatedProducts[0] : updatedProducts
}
@@ -1657,20 +1653,47 @@ export default class ProductModuleService
data: UpdateProductInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<InferEntityType<typeof Product>[]> {
// We have to do that manually because this method is bypassing the product service and goes
// directly to the custom product repository
const manager = (sharedContext.transactionManager ??
sharedContext.manager) as EntityManager
const subscriber = createMedusaMikroOrmEventSubscriber(
["updateProducts_"],
this as unknown as ReturnType<typeof MedusaService<any>>
)
if (manager && subscriber) {
manager
.getEventManager()
.registerSubscriber(new subscriber(sharedContext))
}
const originalProducts = await this.productService_.list(
{
id: data.map((d) => d.id),
},
{
relations: ["options", "options.values", "variants", "images", "tags"],
},
sharedContext
)
const normalizedProducts = await this.normalizeUpdateProductInput(
data,
sharedContext
originalProducts
)
for (const product of normalizedProducts) {
this.validateProductUpdatePayload(product)
}
return this.productRepository_.deepUpdate(
const updatedProducts = await this.productRepository_.deepUpdate(
normalizedProducts,
ProductModuleService.validateVariantOptions,
sharedContext
)
return updatedProducts
}
// @ts-expect-error
@@ -1685,6 +1708,7 @@ export default class ProductModuleService
data: ProductTypes.UpdateProductOptionValueDTO,
sharedContext?: Context
): Promise<ProductTypes.ProductOptionValueDTO[]>
// @ts-expect-error
async updateProductOptionValues(
idOrSelector: string | FilterableProductOptionValueProps,
@@ -1693,6 +1717,11 @@ export default class ProductModuleService
): Promise<
ProductTypes.ProductOptionValueDTO | ProductTypes.ProductOptionValueDTO[]
> {
// TODO: There is a missmatch in the API which lead to function with different number of
// arguments. Therefore, applying the MedusaContext() decorator to the function will not work
// because the context arg index will differ from method to method.
sharedContext.messageAggregator ??= new MessageAggregator()
let normalizedInput: ({
id: string
} & ProductTypes.UpdateProductOptionValueDTO)[] = []
@@ -1718,7 +1747,7 @@ export default class ProductModuleService
}))
}
const productOptionValues = await super.updateProductOptionValues(
const productOptionValues = await this.updateProductOptionValues_(
normalizedInput,
sharedContext
)
@@ -1727,16 +1756,45 @@ export default class ProductModuleService
ProductTypes.ProductOptionValueDTO[]
>(productOptionValues)
eventBuilders.updatedProductOptionValue({
data: updatedProductOptionValues,
sharedContext: sharedContext,
})
// TODO: Because of the wrong method override, we have to compensate to prevent breaking
// changes right now
const groupedEvents = sharedContext.messageAggregator!.getMessages()
if (
Object.values(groupedEvents).flat().length > 0 &&
this.eventBusModuleService_
) {
const promises: Promise<void>[] = []
for (const group of Object.keys(groupedEvents)) {
promises.push(
this.eventBusModuleService_!.emit(groupedEvents[group], {
internal: true,
})
)
}
await Promise.all(promises)
sharedContext.messageAggregator.clearMessages()
}
return isString(idOrSelector)
? updatedProductOptionValues[0]
: updatedProductOptionValues
}
@InjectTransactionManager()
protected async updateProductOptionValues_(
normalizedInput: ({
id: string
} & ProductTypes.UpdateProductOptionValueDTO)[],
@MedusaContext() sharedContext: Context = {}
): Promise<InferEntityType<typeof ProductOptionValue>[]> {
return await this.productOptionValueService_.update(
normalizedInput,
sharedContext
)
}
/**
* Validates the manually provided handle value of the product
* to be URL-safe
@@ -1805,8 +1863,7 @@ export default class ProductModuleService
const products_ = Array.isArray(products) ? products : [products]
const normalizedProducts = (await this.normalizeUpdateProductInput(
products_ as UpdateProductInput[],
sharedContext
products_ as UpdateProductInput[]
)) as ProductTypes.CreateProductDTO[]
for (const productData of normalizedProducts) {
@@ -1839,6 +1896,12 @@ export default class ProductModuleService
) as TOutput
}
/**
* Normalizes the input for the update product input
* @param products - The products to normalize
* @param originalProducts - The original products to use for the normalization (must include options and option values relations)
* @returns The normalized products
*/
protected async normalizeUpdateProductInput<
T extends UpdateProductInput | UpdateProductInput[],
TOutput = T extends UpdateProductInput[]
@@ -1846,7 +1909,7 @@ export default class ProductModuleService
: UpdateProductInput
>(
products: T,
@MedusaContext() sharedContext: Context = {}
originalProducts?: InferEntityType<typeof Product>[]
): Promise<TOutput> {
const products_ = Array.isArray(products) ? products : [products]
const productsIds = products_.map((p) => p.id).filter(Boolean)
@@ -1854,11 +1917,14 @@ export default class ProductModuleService
let dbOptions: InferEntityType<typeof ProductOption>[] = []
if (productsIds.length) {
dbOptions = await this.productOptionService_.list(
{ product_id: productsIds },
{ relations: ["values"] },
sharedContext
)
// Re map options to handle non serialized data as well
dbOptions =
originalProducts
?.map((originalProduct) =>
originalProduct.options.map((option) => option)
)
.flat()
.filter(Boolean) ?? []
}
const normalizedProducts: UpdateProductInput[] = []
@@ -1872,7 +1938,9 @@ export default class ProductModuleService
if (productData.options?.length) {
;(productData as any).options = productData.options?.map((option) => {
const dbOption = dbOptions.find(
(o) => o.title === option.title && o.product_id === productData.id
(o) =>
(o.title === option.title || o.id === option.id) &&
o.product_id === productData.id
)
return {
title: option.title,

View File

@@ -6,96 +6,6 @@ import {
} from "@medusajs/framework/utils"
export const eventBuilders = {
createdProduct: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
object: "product",
eventName: ProductEvents.PRODUCT_CREATED,
}),
updatedProduct: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
object: "product",
eventName: ProductEvents.PRODUCT_UPDATED,
}),
deletedProduct: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
object: "product",
eventName: ProductEvents.PRODUCT_DELETED,
}),
createdProductVariant: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
object: "product_variant",
eventName: ProductEvents.PRODUCT_VARIANT_CREATED,
}),
updatedProductVariant: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
object: "product_variant",
eventName: ProductEvents.PRODUCT_VARIANT_UPDATED,
}),
deletedProductVariant: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
object: "product_variant",
eventName: ProductEvents.PRODUCT_VARIANT_DELETED,
}),
createdProductOption: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
object: "product_option",
eventName: ProductEvents.PRODUCT_OPTION_CREATED,
}),
updatedProductOption: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
object: "product_option",
eventName: ProductEvents.PRODUCT_OPTION_UPDATED,
}),
deletedProductOption: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
object: "product_option",
eventName: ProductEvents.PRODUCT_OPTION_DELETED,
}),
createdProductType: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
object: "product_type",
eventName: ProductEvents.PRODUCT_TYPE_CREATED,
}),
updatedProductType: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
object: "product_type",
eventName: ProductEvents.PRODUCT_TYPE_UPDATED,
}),
deletedProductType: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
object: "product_type",
eventName: ProductEvents.PRODUCT_TYPE_DELETED,
}),
createdProductTag: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
object: "product_tag",
eventName: ProductEvents.PRODUCT_TAG_CREATED,
}),
updatedProductTag: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
object: "product_tag",
eventName: ProductEvents.PRODUCT_TAG_UPDATED,
}),
deletedProductTag: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
object: "product_tag",
eventName: ProductEvents.PRODUCT_TAG_DELETED,
}),
createdProductCategory: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
@@ -114,40 +24,4 @@ export const eventBuilders = {
object: "product_category",
eventName: ProductEvents.PRODUCT_CATEGORY_DELETED,
}),
createdProductCollection: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
object: "product_collection",
eventName: ProductEvents.PRODUCT_COLLECTION_CREATED,
}),
updatedProductCollection: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
object: "product_collection",
eventName: ProductEvents.PRODUCT_COLLECTION_UPDATED,
}),
deletedProductCollection: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
object: "product_collection",
eventName: ProductEvents.PRODUCT_COLLECTION_DELETED,
}),
createdProductOptionValue: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.CREATED,
object: "product_option_value",
eventName: ProductEvents.PRODUCT_OPTION_VALUE_CREATED,
}),
updatedProductOptionValue: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.UPDATED,
object: "product_option_value",
eventName: ProductEvents.PRODUCT_OPTION_VALUE_UPDATED,
}),
deletedProductOptionValue: moduleEventBuilderFactory({
source: Modules.PRODUCT,
action: CommonEvents.DELETED,
object: "product_option_value",
eventName: ProductEvents.PRODUCT_OPTION_VALUE_DELETED,
}),
}