From da3837f2bccf3184fbb0bbb69a93b31d1cffcd4a Mon Sep 17 00:00:00 2001 From: Adrien de Peretti Date: Wed, 5 Jun 2024 22:04:45 +0200 Subject: [PATCH] chore: return meta information on created/updated/deleted object from upsertWithReplace (#7616) **What** - Return the updated/deleted/created entities from the upsert with replace according to the configuration This will help to emit the events from the product module as it rely on this abstraction in many places --- .../core/types/src/dal/repository-service.ts | 11 +- .../modules-sdk/internal-module-service.ts | 3 +- .../__tests__/mikro-orm-repository.spec.ts | 219 +++++++++++++++--- .../src/dal/mikro-orm/mikro-orm-repository.ts | 123 ++++++++-- .../internal-module-service-factory.ts | 23 +- .../pricing/src/services/pricing-module.ts | 28 ++- .../src/services/product-module-service.ts | 95 ++++---- 7 files changed, 388 insertions(+), 114 deletions(-) diff --git a/packages/core/types/src/dal/repository-service.ts b/packages/core/types/src/dal/repository-service.ts index 55d4a575c4..b1d5e9ef5a 100644 --- a/packages/core/types/src/dal/repository-service.ts +++ b/packages/core/types/src/dal/repository-service.ts @@ -8,6 +8,15 @@ import { UpsertWithReplaceConfig, } from "./index" +type EntityClassName = string +type EntityValues = { id: string }[] + +export type PerformedActions = { + created: Record + updated: Record + deleted: Record +} + /** * Data access layer (DAL) interface to implements for any repository service. * This layer helps to separate the business logic (service layer) from accessing the @@ -78,7 +87,7 @@ export interface RepositoryService extends BaseRepositoryService { data: any[], config?: UpsertWithReplaceConfig, context?: Context - ): Promise + ): Promise<{ entities: T[]; performedActions: PerformedActions }> } export interface TreeRepositoryService diff --git a/packages/core/types/src/modules-sdk/internal-module-service.ts b/packages/core/types/src/modules-sdk/internal-module-service.ts index 784dea01fe..a16955c388 100644 --- a/packages/core/types/src/modules-sdk/internal-module-service.ts +++ b/packages/core/types/src/modules-sdk/internal-module-service.ts @@ -4,6 +4,7 @@ import { BaseFilterable, FilterQuery as InternalFilterQuery, FilterQuery, + PerformedActions, UpsertWithReplaceConfig, } from "../dal" @@ -88,5 +89,5 @@ export interface InternalModuleService< data: any[], config?: UpsertWithReplaceConfig, sharedContext?: Context - ): Promise + ): Promise<{ entities: TEntity[]; performedActions: PerformedActions }> } diff --git a/packages/core/utils/src/dal/mikro-orm/integration-tests/__tests__/mikro-orm-repository.spec.ts b/packages/core/utils/src/dal/mikro-orm/integration-tests/__tests__/mikro-orm-repository.spec.ts index 20ff7e3d0b..4c2b6d776e 100644 --- a/packages/core/utils/src/dal/mikro-orm/integration-tests/__tests__/mikro-orm-repository.spec.ts +++ b/packages/core/utils/src/dal/mikro-orm/integration-tests/__tests__/mikro-orm-repository.spec.ts @@ -176,7 +176,17 @@ describe("mikroOrmRepository", () => { it("should successfully create a flat entity", async () => { const entity1 = { id: "1", title: "en1", amount: 100 } - const resp = await manager1().upsertWithReplace([entity1]) + const { entities: resp, performedActions } = + await manager1().upsertWithReplace([entity1]) + + expect(performedActions).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + }, + updated: {}, + deleted: {}, + }) + const listedEntities = await manager1().find() expect(listedEntities).toHaveLength(1) @@ -193,9 +203,29 @@ describe("mikroOrmRepository", () => { it("should successfully update a flat entity", async () => { const entity1 = { id: "1", title: "en1" } - await manager1().upsertWithReplace([entity1]) + const { performedActions: performedActions1 } = + await manager1().upsertWithReplace([entity1]) + + expect(performedActions1).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + }, + updated: {}, + deleted: {}, + }) + entity1.title = "newen1" - await manager1().upsertWithReplace([entity1]) + const { performedActions: performedActions2 } = + await manager1().upsertWithReplace([entity1]) + + expect(performedActions2).toEqual({ + created: {}, + updated: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + }, + deleted: {}, + }) + const listedEntities = await manager1().find() expect(listedEntities).toHaveLength(1) @@ -210,9 +240,30 @@ describe("mikroOrmRepository", () => { it("should successfully do a partial update a flat entity", async () => { const entity1 = { id: "1", title: "en1" } - await manager1().upsertWithReplace([entity1]) + const { performedActions: performedActions1 } = + await manager1().upsertWithReplace([entity1]) + + expect(performedActions1).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + }, + updated: {}, + deleted: {}, + }) + entity1.title = undefined as any - await manager1().upsertWithReplace([entity1]) + + const { performedActions: performedActions2 } = + await manager1().upsertWithReplace([entity1]) + + expect(performedActions2).toEqual({ + created: {}, + updated: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + }, + deleted: {}, + }) + const listedEntities = await manager1().find() expect(listedEntities).toHaveLength(1) @@ -240,6 +291,7 @@ describe("mikroOrmRepository", () => { ) }) + // TODO: I believe this should not be allowed it("should successfully create the parent entity of a many-to-one", async () => { const entity2 = { id: "2", @@ -343,9 +395,19 @@ describe("mikroOrmRepository", () => { entity2: [{ title: "en2-1" }, { title: "en2-2" }], } - await manager1().upsertWithReplace([entity1], { - relations: [], + const { performedActions: performedActions1 } = + await manager1().upsertWithReplace([entity1], { + relations: [], + }) + + expect(performedActions1).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + }, + updated: {}, + deleted: {}, }) + const listedEntities = await manager1().find({ where: { id: "1" }, options: { populate: ["entity2"] }, @@ -368,14 +430,29 @@ describe("mikroOrmRepository", () => { entity2: [{ title: "en2-1" }, { title: "en2-2" }], } - await manager1().upsertWithReplace([entity1], { - relations: ["entity2"], - }) + const { performedActions: performedActions1 } = + await manager1().upsertWithReplace([entity1], { + relations: ["entity2"], + }) + const listedEntities = await manager1().find({ where: { id: "1" }, options: { populate: ["entity2"] }, }) + const entities2 = listedEntities.flatMap((entity1) => + entity1.entity2.getItems() + ) + + expect(performedActions1).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + [Entity2.name]: entities2.map((entity2) => ({ id: entity2.id })), + }, + updated: {}, + deleted: {}, + }) + expect(listedEntities).toHaveLength(1) expect(listedEntities[0]).toEqual( expect.objectContaining({ @@ -403,14 +480,26 @@ describe("mikroOrmRepository", () => { entity2: [{ title: "en2-1", entity1: null }], } - await manager1().upsertWithReplace([entity1], { - relations: ["entity2"], - }) + const { performedActions: performedActions1 } = + await manager1().upsertWithReplace([entity1], { + relations: ["entity2"], + }) const listedEntities = await manager1().find({ where: { id: "1" }, options: { populate: ["entity2"] }, }) + expect(performedActions1).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + [Entity2.name]: [ + expect.objectContaining({ id: listedEntities[0].entity2[0].id }), + ], + }, + updated: {}, + deleted: {}, + }) + expect(listedEntities).toHaveLength(1) expect(listedEntities[0]).toEqual( expect.objectContaining({ @@ -435,12 +524,35 @@ describe("mikroOrmRepository", () => { entity2: [{ title: "en2-1" }, { title: "en2-2" }], } - await manager1().upsertWithReplace([entity1], { - relations: ["entity2"], + const { entities: entities1, performedActions: performedActions1 } = + await manager1().upsertWithReplace([entity1], { + relations: ["entity2"], + }) + + expect(performedActions1).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + [Entity2.name]: entities1[0].entity2.map((entity2) => + expect.objectContaining({ id: entity2.id }) + ), + }, + updated: {}, + deleted: {}, }) + entity1.entity2.push({ title: "en2-3" }) - await manager1().upsertWithReplace([entity1], { - relations: [], + + const { performedActions: performedActions2 } = + await manager1().upsertWithReplace([entity1], { + relations: [], + }) + + expect(performedActions2).toEqual({ + created: {}, + updated: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + }, + deleted: {}, }) const listedEntities = await manager1().find({ @@ -478,14 +590,42 @@ describe("mikroOrmRepository", () => { ] as any[], } - await manager1().upsertWithReplace([entity1], { - relations: ["entity2"], + const { entities: entities1, performedActions: performedActions1 } = + await manager1().upsertWithReplace([entity1], { + relations: ["entity2"], + }) + + expect(performedActions1).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + [Entity2.name]: entities1[0].entity2.map((entity2) => + expect.objectContaining({ id: entity2.id }) + ), + }, + updated: {}, + deleted: {}, }) entity1.entity2 = [{ id: "2", title: "newen2-1" }, { title: "en2-3" }] - await manager1().upsertWithReplace([entity1], { - relations: ["entity2"], + const { entities: entities2, performedActions: performedActions2 } = + await manager1().upsertWithReplace([entity1], { + relations: ["entity2"], + }) + + const entity2En23 = entities2[0].entity2.find((e) => e.title === "en2-3")! + + expect(performedActions2).toEqual({ + created: { + [Entity2.name]: [expect.objectContaining({ id: entity2En23.id })], + }, + updated: { + [Entity1.name]: [expect.objectContaining({ id: entity1.id })], + [Entity2.name]: [expect.objectContaining({ id: "2" })], + }, + deleted: { + [Entity2.name]: [expect.objectContaining({ id: "3" })], + }, }) const listedEntities = await manager1().find({ @@ -640,7 +780,7 @@ describe("mikroOrmRepository", () => { ] as any, } - let resp = await manager1().upsertWithReplace([entity1], { + await manager1().upsertWithReplace([entity1], { relations: ["entity3"], }) @@ -648,7 +788,7 @@ describe("mikroOrmRepository", () => { entity1.entity3 = [{ id: "4", title: "newen3-1" }, { title: "en3-4" }] // We don't do many-to-many updates, so id: 4 entity should remain unchanged - resp = await manager1().upsertWithReplace([entity1], { + await manager1().upsertWithReplace([entity1], { relations: ["entity3"], }) @@ -718,9 +858,12 @@ describe("mikroOrmRepository", () => { ] as any, } - const mainEntity = await manager1().upsertWithReplace([entity1], { - relations: ["entity3"], - }) + const { entities: mainEntity } = await manager1().upsertWithReplace( + [entity1], + { + relations: ["entity3"], + } + ) entity1.title = "newen1" entity1.entity3 = [{ id: "4", title: "newen3-1" }, { title: "en3-4" }] @@ -771,11 +914,31 @@ describe("mikroOrmRepository", () => { entity3: [{ title: "en3-1" }, { title: "en3-2" }] as any, } - const [createResp] = await manager1().upsertWithReplace([entity1], { + const { + entities: [createResp], + performedActions: performedActions1, + } = await manager1().upsertWithReplace([entity1], { relations: ["entity2", "entity3"], }) + + expect(performedActions1).toEqual({ + created: { + [Entity1.name]: [expect.objectContaining({ id: createResp.id })], + [Entity2.name]: [ + expect.objectContaining({ id: createResp.entity2[0].id }), + ], + [Entity3.name]: createResp.entity3.map((entity3) => + expect.objectContaining({ id: entity3.id }) + ), + }, + updated: {}, + deleted: {}, + }) + createResp.title = "newen1" - const [updateResp] = await manager1().upsertWithReplace([createResp], { + const { + entities: [updateResp], + } = await manager1().upsertWithReplace([createResp], { relations: ["entity2", "entity3"], }) diff --git a/packages/core/utils/src/dal/mikro-orm/mikro-orm-repository.ts b/packages/core/utils/src/dal/mikro-orm/mikro-orm-repository.ts index 5274ed4445..e0df164607 100644 --- a/packages/core/utils/src/dal/mikro-orm/mikro-orm-repository.ts +++ b/packages/core/utils/src/dal/mikro-orm/mikro-orm-repository.ts @@ -4,6 +4,7 @@ import { DAL, FilterQuery, FilterQuery as InternalFilterQuery, + PerformedActions, RepositoryService, RepositoryTransformOptions, UpsertWithReplaceConfig, @@ -147,7 +148,7 @@ export class MikroOrmBaseRepository relations: [], }, context: Context = {} - ): Promise { + ): Promise<{ entities: T[]; performedActions: PerformedActions }> { throw new Error("Method not implemented.") } @@ -457,10 +458,17 @@ export function mikroOrmBaseRepositoryFactory( relations: [], }, context: Context = {} - ): Promise { - if (!data.length) { - return [] + ): Promise<{ entities: T[]; performedActions: PerformedActions }> { + const performedActions: PerformedActions = { + created: {}, + updated: {}, + deleted: {}, } + + if (!data.length) { + return { entities: [], performedActions } + } + // We want to convert a potential ORM model to a POJO const normalizedData: any[] = await this.serialize(data) @@ -507,11 +515,12 @@ export function mikroOrmBaseRepositoryFactory( return mainEntity }) - const upsertedTopLevelEntities = await this.upsertMany_( - manager, - entity.name, - toUpsert - ) + let { + orderedEntities: upsertedTopLevelEntities, + performedActions: performedActions_, + } = await this.upsertMany_(manager, entity.name, toUpsert) + + this.mergePerformedActions(performedActions, performedActions_) await promiseAll( upsertedTopLevelEntities @@ -534,12 +543,15 @@ export function mikroOrmBaseRepositoryFactory( return } - reconstructedEntry[relationName] = + const { entities, performedActions: performedActions_ } = await this.assignCollectionRelation_( manager, { ...originalEntry, id: (entityEntry as any).id }, relation ) + + this.mergePerformedActions(performedActions, performedActions_) + reconstructedEntry[relationName] = entities return }) }) @@ -551,7 +563,21 @@ export function mikroOrmBaseRepositoryFactory( // manager.create(entity, r, { persist: false }) // ) - return reconstructedResponse + return { entities: reconstructedResponse, performedActions } + } + + private mergePerformedActions( + performedActions: PerformedActions, + newPerformedActions: PerformedActions + ) { + Object.entries(newPerformedActions).forEach(([action, entities]) => { + Object.entries(entities as Record).forEach( + ([entityName, entityData]) => { + performedActions[action][entityName] ??= [] + performedActions[action][entityName].push(...entityData) + } + ) + }) } // FUTURE: We can make this performant by only aggregating the operations, but only executing them at the end. @@ -559,11 +585,18 @@ export function mikroOrmBaseRepositoryFactory( manager: SqlEntityManager, data: T, relation: EntityProperty - ) { + ): Promise<{ entities: any[]; performedActions: PerformedActions }> { const dataForRelation = data[relation.name] + + const performedActions: PerformedActions = { + created: {}, + updated: {}, + deleted: {}, + } + // If the field is not set, we ignore it. Null and empty arrays are a valid input and are handled below if (dataForRelation === undefined) { - return undefined + return { entities: [], performedActions } } // Make sure the data is correctly initialized with IDs before using it @@ -580,10 +613,16 @@ export function mikroOrmBaseRepositoryFactory( [parentPivotColumn]: (data as any).id, }) - return normalizedData + return { entities: normalizedData, performedActions } } - await this.upsertMany_(manager, relation.type, normalizedData, true) + const { performedActions: performedActions_ } = await this.upsertMany_( + manager, + relation.type, + normalizedData, + true + ) + this.mergePerformedActions(performedActions, performedActions_) const pivotData = normalizedData.map((currModel) => { return { @@ -602,7 +641,7 @@ export function mikroOrmBaseRepositoryFactory( }, }) - return normalizedData + return { entities: normalizedData, performedActions } } if (relation.reference === ReferenceType.ONE_TO_MANY) { @@ -622,18 +661,38 @@ export function mikroOrmBaseRepositoryFactory( }) }) - await this.upsertMany_(manager, relation.type, normalizedData) + const { performedActions: performedActions_ } = + await this.upsertMany_(manager, relation.type, normalizedData) + this.mergePerformedActions(performedActions, performedActions_) } + const toDeleteEntities = await manager.find( + relation.type, + { + id: { $nin: normalizedData.map((d: any) => d.id) }, + }, + { + fields: ["id"], + } + ) + const toDeleteIds = toDeleteEntities.map((d: any) => d.id) + await manager.nativeDelete(relation.type, { ...joinColumnsConstraints, - id: { $nin: normalizedData.map((d: any) => d.id) }, + id: { $in: toDeleteIds }, }) - return normalizedData + if (toDeleteEntities.length) { + performedActions.deleted[relation.type] ??= [] + performedActions.deleted[relation.type].push( + ...toDeleteEntities.map((d) => ({ id: d.id })) + ) + } + + return { entities: normalizedData, performedActions } } - return normalizedData + return { entities: normalizedData, performedActions } } protected handleRelationAssignment_( @@ -714,7 +773,7 @@ export function mikroOrmBaseRepositoryFactory( entityName: string, entries: any[], skipUpdate: boolean = false - ) { + ): Promise<{ orderedEntities: any[]; performedActions: PerformedActions }> { const selectQb = manager.qb(entityName) const existingEntities: any[] = await selectQb.select("*").where({ id: { $in: entries.map((d) => d.id) }, @@ -726,6 +785,12 @@ export function mikroOrmBaseRepositoryFactory( const orderedEntities: T[] = [] + const performedActions = { + created: {}, + updated: {}, + deleted: {}, + } + await promiseAll( entries.map(async (data) => { const existingEntity = existingEntitiesMap.get(data.id) @@ -735,19 +800,31 @@ export function mikroOrmBaseRepositoryFactory( return } await manager.nativeUpdate(entityName, { id: data.id }, data) + performedActions.updated[entityName] ??= [] + performedActions.updated[entityName].push({ id: data.id }) } else { const qb = manager.qb(entityName) if (skipUpdate) { - await qb.insert(data).onConflict().ignore().execute() + const res = await qb + .insert(data) + .onConflict() + .ignore() + .execute("all", true) + if (res) { + performedActions.created[entityName] ??= [] + performedActions.created[entityName].push({ id: data.id }) + } } else { await manager.insert(entityName, data) + performedActions.created[entityName] ??= [] + performedActions.created[entityName].push({ id: data.id }) // await manager.insert(entityName, data) } } }) ) - return orderedEntities + return { orderedEntities, performedActions } } async restore( diff --git a/packages/core/utils/src/modules-sdk/internal-module-service-factory.ts b/packages/core/utils/src/modules-sdk/internal-module-service-factory.ts index 302854410a..10f871e49e 100644 --- a/packages/core/utils/src/modules-sdk/internal-module-service-factory.ts +++ b/packages/core/utils/src/modules-sdk/internal-module-service-factory.ts @@ -5,6 +5,7 @@ import { FilterQuery as InternalFilterQuery, FindConfig, ModulesSdkTypes, + PerformedActions, UpsertWithReplaceConfig, } from "@medusajs/types" import { EntitySchema } from "@mikro-orm/core" @@ -493,12 +494,12 @@ export function internalModuleServiceFactory< data: any[], config?: UpsertWithReplaceConfig, sharedContext?: Context - ): Promise + ): Promise<{ entities: TEntity[]; performedActions: PerformedActions }> upsertWithReplace( data: any, config?: UpsertWithReplaceConfig, sharedContext?: Context - ): Promise + ): Promise<{ entities: TEntity; performedActions: PerformedActions }> @InjectTransactionManager(propertyRepositoryName) async upsertWithReplace( @@ -507,14 +508,18 @@ export function internalModuleServiceFactory< relations: [], }, @MedusaContext() sharedContext: Context = {} - ): Promise { + ): Promise<{ + entities: TEntity | TEntity[] + performedActions: PerformedActions + }> { const data_ = Array.isArray(data) ? data : [data] - const entities = await this[propertyRepositoryName].upsertWithReplace( - data_, - config, - sharedContext - ) - return Array.isArray(data) ? entities : entities[0] + const { entities, performedActions } = await this[ + propertyRepositoryName + ].upsertWithReplace(data_, config, sharedContext) + return { + entities: Array.isArray(data) ? entities : entities[0], + performedActions, + } } } diff --git a/packages/modules/pricing/src/services/pricing-module.ts b/packages/modules/pricing/src/services/pricing-module.ts index 67fe6dc687..164e3b657b 100644 --- a/packages/modules/pricing/src/services/pricing-module.ts +++ b/packages/modules/pricing/src/services/pricing-module.ts @@ -498,13 +498,14 @@ export default class PricingModuleService< const normalizedData = await this.normalizeUpdateData(data, sharedContext) const prices = normalizedData.flatMap((priceSet) => priceSet.prices || []) - const upsertedPrices = await this.priceService_.upsertWithReplace( - prices, - { - relations: ["price_rules"], - }, - sharedContext - ) + const { entities: upsertedPrices } = + await this.priceService_.upsertWithReplace( + prices, + { + relations: ["price_rules"], + }, + sharedContext + ) const priceSetsToUpsert = normalizedData.map((priceSet) => { const { prices, ...rest } = priceSet @@ -520,11 +521,14 @@ export default class PricingModuleService< } }) - return await this.priceSetService_.upsertWithReplace( - priceSetsToUpsert, - { relations: ["prices"] }, - sharedContext - ) + const { entities: priceSets } = + await this.priceSetService_.upsertWithReplace( + priceSetsToUpsert, + { relations: ["prices"] }, + sharedContext + ) + + return priceSets } async addRules( diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index 0e06eb1b7b..471f03e932 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -24,7 +24,9 @@ import { arrayDifference, InjectManager, InjectTransactionManager, + isPresent, isString, + isValidHandle, kebabCase, MedusaContext, MedusaError, @@ -32,9 +34,7 @@ import { ProductStatus, promiseAll, removeUndefined, - isValidHandle, toHandle, - isPresent, } from "@medusajs/utils" import { ProductCategoryEventData, @@ -373,16 +373,19 @@ export default class ProductModuleService< sharedContext ) - return this.productVariantService_.upsertWithReplace( - ProductModuleService.assignOptionsToVariants( - variantsWithProductId, - productOptions - ), - { - relations: ["options"], - }, - sharedContext - ) + const { entities: productVariants } = + await this.productVariantService_.upsertWithReplace( + ProductModuleService.assignOptionsToVariants( + variantsWithProductId, + productOptions + ), + { + relations: ["options"], + }, + sharedContext + ) + + return productVariants } createTags( @@ -811,11 +814,14 @@ export default class ProductModuleService< } as UpdateProductOptionInput }) - return await this.productOptionService_.upsertWithReplace( - normalizedInput, - { relations: ["values"] }, - sharedContext - ) + const { entities: productOptions } = + await this.productOptionService_.upsertWithReplace( + normalizedInput, + { relations: ["values"] }, + sharedContext + ) + + return productOptions } createCollections( @@ -865,11 +871,14 @@ export default class ProductModuleService< // It's safe to use upsertWithReplace here since we only have product IDs and the only operation to do is update the product // with the collection ID - return await this.productCollectionService_.upsertWithReplace( - normalizedInput, - { relations: ["products"] }, - sharedContext - ) + const { entities: productCollections } = + await this.productCollectionService_.upsertWithReplace( + normalizedInput, + { relations: ["products"] }, + sharedContext + ) + + return productCollections } async upsertCollections( @@ -1279,13 +1288,14 @@ export default class ProductModuleService< }) ) - const productData = await this.productService_.upsertWithReplace( - normalizedInput, - { - relations: ["images", "tags", "categories"], - }, - sharedContext - ) + const { entities: productData } = + await this.productService_.upsertWithReplace( + normalizedInput, + { + relations: ["images", "tags", "categories"], + }, + sharedContext + ) await promiseAll( // Note: It's safe to rely on the order here as `upsertWithReplace` preserves the order of the input @@ -1295,7 +1305,7 @@ export default class ProductModuleService< upsertedProduct.variants = [] if (product.options?.length) { - upsertedProduct.options = + const { entities: productOptions } = await this.productOptionService_.upsertWithReplace( product.options?.map((option) => ({ ...option, @@ -1304,10 +1314,11 @@ export default class ProductModuleService< { relations: ["values"] }, sharedContext ) + upsertedProduct.options = productOptions } if (product.variants?.length) { - upsertedProduct.variants = + const { entities: productVariants } = await this.productVariantService_.upsertWithReplace( ProductModuleService.assignOptionsToVariants( product.variants?.map((v) => ({ @@ -1319,6 +1330,7 @@ export default class ProductModuleService< { relations: ["options"] }, sharedContext ) + upsertedProduct.variants = productVariants } }) ) @@ -1342,13 +1354,14 @@ export default class ProductModuleService< }) ) - const productData = await this.productService_.upsertWithReplace( - normalizedInput, - { - relations: ["images", "tags", "categories"], - }, - sharedContext - ) + const { entities: productData } = + await this.productService_.upsertWithReplace( + normalizedInput, + { + relations: ["images", "tags", "categories"], + }, + sharedContext + ) // There is more than 1-level depth of relations here, so we need to handle the options and variants manually await promiseAll( @@ -1358,7 +1371,7 @@ export default class ProductModuleService< let allOptions: any[] = [] if (product.options?.length) { - upsertedProduct.options = + const { entities: productOptions } = await this.productOptionService_.upsertWithReplace( product.options?.map((option) => ({ ...option, @@ -1367,6 +1380,7 @@ export default class ProductModuleService< { relations: ["values"] }, sharedContext ) + upsertedProduct.options = productOptions // Since we handle the options and variants outside of the product upsert, we need to clean up manually await this.productOptionService_.delete( @@ -1391,7 +1405,7 @@ export default class ProductModuleService< } if (product.variants?.length) { - upsertedProduct.variants = + const { entities: productVariants } = await this.productVariantService_.upsertWithReplace( ProductModuleService.assignOptionsToVariants( product.variants?.map((v) => ({ @@ -1403,6 +1417,7 @@ export default class ProductModuleService< { relations: ["options"] }, sharedContext ) + upsertedProduct.variants = productVariants await this.productVariantService_.delete( {