@medusajs/product 3.0.0-snapshot-20251104011621 → 3.0.0-snapshot-20251106143524

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/migrations/Migration20251022153442.d.ts +6 -0
  2. package/dist/migrations/Migration20251022153442.d.ts.map +1 -0
  3. package/dist/migrations/Migration20251022153442.js +104 -0
  4. package/dist/migrations/Migration20251022153442.js.map +1 -0
  5. package/dist/migrations/Migration20251029150809.d.ts +6 -0
  6. package/dist/migrations/Migration20251029150809.d.ts.map +1 -0
  7. package/dist/migrations/Migration20251029150809.js +14 -0
  8. package/dist/migrations/Migration20251029150809.js.map +1 -0
  9. package/dist/models/index.d.ts +1 -0
  10. package/dist/models/index.d.ts.map +1 -1
  11. package/dist/models/index.js +3 -1
  12. package/dist/models/index.js.map +1 -1
  13. package/dist/models/product-category.d.ts +14 -6
  14. package/dist/models/product-category.d.ts.map +1 -1
  15. package/dist/models/product-collection.d.ts +14 -6
  16. package/dist/models/product-collection.d.ts.map +1 -1
  17. package/dist/models/product-image.d.ts +26 -14
  18. package/dist/models/product-image.d.ts.map +1 -1
  19. package/dist/models/product-option-value.d.ts +18 -12
  20. package/dist/models/product-option-value.d.ts.map +1 -1
  21. package/dist/models/product-option-value.js +1 -0
  22. package/dist/models/product-option-value.js.map +1 -1
  23. package/dist/models/product-option.d.ts +10 -6
  24. package/dist/models/product-option.d.ts.map +1 -1
  25. package/dist/models/product-option.js +4 -12
  26. package/dist/models/product-option.js.map +1 -1
  27. package/dist/models/product-product-option.d.ts +668 -0
  28. package/dist/models/product-product-option.d.ts.map +1 -0
  29. package/dist/models/product-product-option.js +19 -0
  30. package/dist/models/product-product-option.js.map +1 -0
  31. package/dist/models/product-tag.d.ts +14 -6
  32. package/dist/models/product-tag.d.ts.map +1 -1
  33. package/dist/models/product-type.d.ts +14 -6
  34. package/dist/models/product-type.d.ts.map +1 -1
  35. package/dist/models/product-variant-product-image.d.ts +47 -26
  36. package/dist/models/product-variant-product-image.d.ts.map +1 -1
  37. package/dist/models/product-variant.d.ts +21 -12
  38. package/dist/models/product-variant.d.ts.map +1 -1
  39. package/dist/models/product.d.ts +14 -6
  40. package/dist/models/product.d.ts.map +1 -1
  41. package/dist/models/product.js +5 -3
  42. package/dist/models/product.js.map +1 -1
  43. package/dist/repositories/product-category.d.ts +36 -18
  44. package/dist/repositories/product-category.d.ts.map +1 -1
  45. package/dist/repositories/product.d.ts +14 -6
  46. package/dist/repositories/product.d.ts.map +1 -1
  47. package/dist/repositories/product.js +2 -5
  48. package/dist/repositories/product.js.map +1 -1
  49. package/dist/services/product-module-service.d.ts +20 -4
  50. package/dist/services/product-module-service.d.ts.map +1 -1
  51. package/dist/services/product-module-service.js +312 -80
  52. package/dist/services/product-module-service.js.map +1 -1
  53. package/dist/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +4 -4
@@ -11,7 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
- var _a;
14
+ var _a, _b;
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  const types_1 = require("@medusajs/framework/types");
17
17
  const _models_1 = require("../models");
@@ -29,7 +29,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
29
29
  ProductVariant: _models_1.ProductVariant,
30
30
  ProductImage: _models_1.ProductImage,
31
31
  }) {
32
- constructor({ baseRepository, productRepository, productService, productVariantService, productTagService, productCategoryService, productCollectionService, productImageService, productTypeService, productOptionService, productOptionValueService, productVariantProductImageService, [utils_1.Modules.EVENT_BUS]: eventBusModuleService, }, moduleDeclaration) {
32
+ constructor({ baseRepository, productRepository, productService, productVariantService, productTagService, productCategoryService, productCollectionService, productImageService, productTypeService, productOptionService, productProductOptionService, productOptionValueService, productVariantProductImageService, [utils_1.Modules.EVENT_BUS]: eventBusModuleService, }, moduleDeclaration) {
33
33
  // @ts-ignore
34
34
  // eslint-disable-next-line prefer-rest-params
35
35
  super(...arguments);
@@ -44,6 +44,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
44
44
  this.productImageService_ = productImageService;
45
45
  this.productTypeService_ = productTypeService;
46
46
  this.productOptionService_ = productOptionService;
47
+ this.productProductOptionService_ = productProductOptionService;
47
48
  this.productOptionValueService_ = productOptionValueService;
48
49
  this.productVariantProductImageService_ = productVariantProductImageService;
49
50
  this.eventBusModuleService_ = eventBusModuleService;
@@ -142,9 +143,11 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
142
143
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Unable to create variants without specifying a product_id");
143
144
  }
144
145
  const productOptions = await this.productOptionService_.list({
145
- product_id: [...new Set(data.map((v) => v.product_id))],
146
+ products: {
147
+ id: [...new Set(data.map((v) => v.product_id))],
148
+ },
146
149
  }, {
147
- relations: ["values"],
150
+ relations: ["values", "products"],
148
151
  }, sharedContext);
149
152
  const variants = await this.productVariantService_.list({
150
153
  product_id: [...new Set(data.map((v) => v.product_id))],
@@ -204,8 +207,10 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
204
207
  product_id: v.product_id,
205
208
  }));
206
209
  const productOptions = await this.productOptionService_.list({
207
- product_id: Array.from(new Set(variantsWithProductId.map((v) => v.product_id))),
208
- }, { relations: ["values"] }, sharedContext);
210
+ products: {
211
+ id: Array.from(new Set(variantsWithProductId.map((v) => v.product_id))),
212
+ },
213
+ }, { relations: ["values", "products"] }, sharedContext);
209
214
  const productVariantsWithOptions = ProductModuleService.assignOptionsToVariants(variantsWithProductId, productOptions);
210
215
  if (data.some((d) => !!d.options)) {
211
216
  ProductModuleService.checkIfVariantWithOptionsAlreadyExists(productVariantsWithOptions, allVariants);
@@ -313,18 +318,25 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
313
318
  return Array.isArray(data) ? createdOptions : createdOptions[0];
314
319
  }
315
320
  async createOptions_(data, sharedContext = {}) {
316
- if (data.some((v) => !v.product_id)) {
317
- throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Tried to create options without specifying a product_id");
318
- }
319
321
  const normalizedInput = data.map((opt) => {
322
+ Object.keys(opt.ranks ?? []).forEach((value) => {
323
+ if (!opt.values.includes(value)) {
324
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Value "${value}" is assigned a rank but is not defined in the list of values.`);
325
+ }
326
+ });
320
327
  return {
321
328
  ...opt,
322
329
  values: opt.values?.map((v) => {
323
- return typeof v === "string" ? { value: v } : v;
330
+ // Normalize each value into an object and attach rank if available
331
+ const valueObj = (0, utils_1.isString)(v) ? { value: v } : v;
332
+ const rank = opt.ranks && (0, utils_1.isString)(v)
333
+ ? opt.ranks[v]
334
+ : opt.ranks?.[valueObj.value];
335
+ return rank !== undefined ? { ...valueObj, rank } : valueObj;
324
336
  }),
325
337
  };
326
338
  });
327
- return await this.productOptionService_.create(normalizedInput, sharedContext);
339
+ return this.productOptionService_.create(normalizedInput, sharedContext);
328
340
  }
329
341
  async upsertProductOptions(data, sharedContext = {}) {
330
342
  const input = Array.isArray(data) ? data : [data];
@@ -371,35 +383,77 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
371
383
  }
372
384
  // Data normalization
373
385
  const normalizedInput = data.map((opt) => {
374
- const dbValues = dbOptions.find(({ id }) => id === opt.id)?.values || [];
375
- const normalizedValues = opt.values?.map((v) => {
376
- return typeof v === "string" ? { value: v } : v;
377
- });
378
- return {
379
- ...opt,
380
- ...(normalizedValues
381
- ? {
382
- // Oftentimes the options are only passed by value without an id, even if they exist in the DB
383
- values: normalizedValues.map((normVal) => {
384
- if ("id" in normVal) {
385
- return normVal;
386
- }
387
- const dbVal = dbValues.find((dbVal) => dbVal.value === normVal.value);
388
- if (!dbVal) {
389
- return normVal;
390
- }
391
- return {
392
- id: dbVal.id,
393
- value: normVal.value,
394
- };
395
- }),
386
+ const dbOption = dbOptions.find(({ id }) => id === opt.id);
387
+ const dbValues = dbOption?.values || [];
388
+ if (opt.ranks) {
389
+ const validValues = opt.values ?? dbValues.map((v) => v.value);
390
+ Object.keys(opt.ranks).forEach((value) => {
391
+ if (!validValues.includes(value)) {
392
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Value "${value}" is assigned a rank but is not defined in the list of values.`);
396
393
  }
397
- : {}),
394
+ });
395
+ }
396
+ let normalizedValues;
397
+ if (opt.values) {
398
+ // If new values are provided → normalize and apply ranks
399
+ normalizedValues = opt.values.map((v) => {
400
+ const valueObj = (0, utils_1.isString)(v) ? { value: v } : v;
401
+ const rank = opt.ranks && (0, utils_1.isString)(v)
402
+ ? opt.ranks[v]
403
+ : opt.ranks?.[valueObj.value];
404
+ const rankedValue = rank !== undefined ? { ...valueObj, rank } : valueObj;
405
+ if ("id" in rankedValue) {
406
+ return rankedValue;
407
+ }
408
+ const dbVal = dbValues.find((dbVal) => dbVal.value === rankedValue.value);
409
+ if (!dbVal) {
410
+ return rankedValue;
411
+ }
412
+ return {
413
+ id: dbVal.id,
414
+ ...rankedValue,
415
+ };
416
+ });
417
+ }
418
+ else if (opt.ranks) {
419
+ // If only ranks were provided → update existing DB values with ranks
420
+ normalizedValues = dbValues.map((dbVal) => {
421
+ const rank = opt.ranks[dbVal.value];
422
+ return rank !== undefined
423
+ ? { id: dbVal.id, value: dbVal.value, rank }
424
+ : { id: dbVal.id, value: dbVal.value };
425
+ });
426
+ }
427
+ const { ranks, ...cleanOpt } = opt;
428
+ return {
429
+ ...cleanOpt,
430
+ ...(normalizedValues ? { values: normalizedValues } : {}),
398
431
  };
399
432
  });
400
433
  const { entities: productOptions } = await this.productOptionService_.upsertWithReplace(normalizedInput, { relations: ["values"] }, sharedContext);
401
434
  return productOptions;
402
435
  }
436
+ async addProductOptionToProduct(data, sharedContext = {}) {
437
+ const productOptionProducts = await this.addProductOptionToProduct_(data, sharedContext);
438
+ return productOptionProducts;
439
+ }
440
+ async addProductOptionToProduct_(data, sharedContext = {}) {
441
+ const productOptionProducts = await this.productProductOptionService_.create(data, sharedContext);
442
+ if (Array.isArray(data)) {
443
+ return productOptionProducts.map((ppo) => ({ id: ppo.id }));
444
+ }
445
+ return { id: productOptionProducts.id };
446
+ }
447
+ async removeProductOptionFromProduct(data, sharedContext = {}) {
448
+ await this.removeProductOptionFromProduct_(data, sharedContext);
449
+ }
450
+ async removeProductOptionFromProduct_(data, sharedContext = {}) {
451
+ const pairs = Array.isArray(data) ? data : [data];
452
+ const productOptionsProducts = await this.productProductOptionService_.list({
453
+ $or: pairs,
454
+ });
455
+ await this.productProductOptionService_.delete(productOptionsProducts.map(({ id }) => id), sharedContext);
456
+ }
403
457
  // @ts-expect-error
404
458
  async createProductCollections(data, sharedContext = {}) {
405
459
  const input = Array.isArray(data) ? data : [data];
@@ -624,7 +678,40 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
624
678
  return (0, utils_1.isString)(idOrSelector) ? updatedProducts[0] : updatedProducts;
625
679
  }
626
680
  async createProducts_(data, sharedContext = {}) {
627
- const normalizedProducts = await this.normalizeCreateProductInput(data, sharedContext);
681
+ const existingOptionIds = data
682
+ .flatMap((p) => p.options ?? [])
683
+ .filter((o) => "id" in o)
684
+ .map((o) => o.id);
685
+ let existingOptions = [];
686
+ if (existingOptionIds.length > 0) {
687
+ existingOptions = await this.productOptionService_.list({ id: existingOptionIds }, { relations: ["values"] }, sharedContext);
688
+ const fetchedIds = new Set(existingOptions.map((opt) => opt.id));
689
+ const missingIds = existingOptionIds.filter((id) => !fetchedIds.has(id));
690
+ if (missingIds.length) {
691
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Some product options were not found: [${missingIds.join(", ")}]`);
692
+ }
693
+ }
694
+ const existingOptionsMap = new Map(existingOptions.map((opt) => [opt.id, opt]));
695
+ const hydratedData = data.map((product) => {
696
+ if (!product.options?.length)
697
+ return product;
698
+ const hydratedOptions = product.options.map((option) => {
699
+ if ("id" in option) {
700
+ const dbOption = existingOptionsMap.get(option.id);
701
+ if (!dbOption) {
702
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Product option with id ${option.id} not found.`);
703
+ }
704
+ return {
705
+ id: dbOption.id,
706
+ title: dbOption.title,
707
+ values: dbOption.values?.map((v) => ({ value: v.value })),
708
+ };
709
+ }
710
+ return option;
711
+ });
712
+ return { ...product, options: hydratedOptions };
713
+ });
714
+ const normalizedProducts = this.normalizeCreateProductInput(hydratedData, sharedContext);
628
715
  for (const product of normalizedProducts) {
629
716
  this.validateProductCreatePayload(product);
630
717
  }
@@ -638,6 +725,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
638
725
  }, {}, sharedContext);
639
726
  }
640
727
  const existingTagsMap = new Map(existingTags.map((tag) => [tag.id, tag]));
728
+ const productOptionsToCreate = new Map();
641
729
  const productsToCreate = normalizedProducts.map((product) => {
642
730
  const productId = (0, utils_1.generateEntityId)(product.id, "prod");
643
731
  product.id = productId;
@@ -645,6 +733,12 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
645
733
  ;
646
734
  product.categories = product.categories.map((category) => category.id);
647
735
  }
736
+ if (product.options?.length) {
737
+ const newOptions = product.options.filter((o) => !("id" in o));
738
+ if (newOptions.length) {
739
+ productOptionsToCreate.set(productId, newOptions);
740
+ }
741
+ }
648
742
  if (product.variants?.length) {
649
743
  const normalizedVariants = product.variants.map((variant) => {
650
744
  const variantId = (0, utils_1.generateEntityId)(variant.id, "variant");
@@ -670,10 +764,66 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
670
764
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Tag with id ${tag.id} not found. Please create the tag before associating it with the product.`);
671
765
  });
672
766
  }
767
+ delete product.options;
673
768
  return product;
674
769
  });
675
- const createdProducts = await this.productService_.create(productsToCreate, sharedContext);
676
- return createdProducts;
770
+ const productToOptionIdsMap = new Map();
771
+ const allOptionsWithIds = [];
772
+ for (const [productId, options] of productOptionsToCreate.entries()) {
773
+ const optionIds = [];
774
+ for (const option of options) {
775
+ const optionId = (0, utils_1.generateEntityId)(undefined, "opt");
776
+ optionIds.push(optionId);
777
+ allOptionsWithIds.push({
778
+ ...option,
779
+ id: optionId,
780
+ });
781
+ }
782
+ productToOptionIdsMap.set(productId, optionIds);
783
+ }
784
+ const [createdProducts] = await Promise.all([
785
+ this.productService_.create(productsToCreate, sharedContext),
786
+ allOptionsWithIds.length > 0
787
+ ? this.createOptions_(allOptionsWithIds, sharedContext)
788
+ : Promise.resolve([]),
789
+ ]);
790
+ const linkPairs = [];
791
+ for (const product of createdProducts) {
792
+ const hydratedProduct = hydratedData.find((p) => p.title === product.title);
793
+ const allOptionIds = [];
794
+ if (hydratedProduct?.options?.length) {
795
+ for (const option of hydratedProduct.options) {
796
+ if ("id" in option) {
797
+ allOptionIds.push(option.id);
798
+ }
799
+ }
800
+ }
801
+ const newOptionIds = productToOptionIdsMap.get(product.id) ?? [];
802
+ const optionIds = [...new Set([...allOptionIds, ...newOptionIds])];
803
+ for (const optionId of optionIds) {
804
+ linkPairs.push({
805
+ product_id: product.id,
806
+ product_option_id: optionId,
807
+ });
808
+ }
809
+ }
810
+ if (linkPairs.length > 0) {
811
+ await this.addProductOptionToProduct_(linkPairs, sharedContext);
812
+ }
813
+ const productIds = createdProducts.map((p) => p.id);
814
+ const productsWithOptions = await this.productService_.list({ id: productIds }, {
815
+ relations: [
816
+ "options",
817
+ "options.values",
818
+ "options.products",
819
+ "variants",
820
+ "images",
821
+ "tags",
822
+ ],
823
+ }, sharedContext);
824
+ const productIdOrder = new Map(productIds.map((id, index) => [id, index]));
825
+ const orderedProductsWithOptions = [...productsWithOptions].sort((a, b) => (productIdOrder.get(a.id) ?? 0) - (productIdOrder.get(b.id) ?? 0));
826
+ return orderedProductsWithOptions;
677
827
  }
678
828
  async updateProducts_(data, sharedContext = {}) {
679
829
  // We have to do that manually because this method is bypassing the product service and goes
@@ -686,12 +836,69 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
686
836
  .getEventManager()
687
837
  .registerSubscriber(new subscriber(sharedContext));
688
838
  }
689
- const originalProducts = await this.productService_.list({
690
- id: data.map((d) => d.id),
691
- }, {
692
- relations: ["options", "options.values", "variants", "images", "tags"],
693
- }, sharedContext);
694
- const normalizedProducts = await this.normalizeUpdateProductInput(data, originalProducts);
839
+ const allOptionIds = data
840
+ .flatMap((p) => p.option_ids ?? [])
841
+ .filter((id) => !!id);
842
+ const [originalProducts, existingOptions] = await Promise.all([
843
+ this.productService_.list({ id: data.map((d) => d.id) }, {
844
+ relations: [
845
+ "options",
846
+ "options.values",
847
+ "options.products",
848
+ "variants",
849
+ "images",
850
+ "tags",
851
+ ],
852
+ }, sharedContext),
853
+ allOptionIds.length
854
+ ? this.productOptionService_.list({ id: allOptionIds }, {
855
+ relations: ["values", "products"],
856
+ }, sharedContext)
857
+ : Promise.resolve([]),
858
+ ]);
859
+ if (allOptionIds.length && existingOptions.length !== allOptionIds.length) {
860
+ const found = new Set(existingOptions.map((opt) => opt.id));
861
+ const missing = allOptionIds.filter((id) => !found.has(id));
862
+ if (missing.length) {
863
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Some product options were not found: [${missing.join(", ")}]`);
864
+ }
865
+ }
866
+ const linkPairs = [];
867
+ const unlinkPairs = [];
868
+ for (const product of data) {
869
+ if (!product.option_ids) {
870
+ continue;
871
+ }
872
+ const newOptionIds = new Set(product.option_ids);
873
+ const existingOptionIds = new Set(originalProducts
874
+ .find((p) => p.id === product.id)
875
+ ?.options?.map((o) => o.id) ?? []);
876
+ for (const optionId of newOptionIds) {
877
+ if (!existingOptionIds.has(optionId)) {
878
+ linkPairs.push({
879
+ product_id: product.id,
880
+ product_option_id: optionId,
881
+ });
882
+ }
883
+ }
884
+ for (const optionId of existingOptionIds) {
885
+ if (!newOptionIds.has(optionId)) {
886
+ unlinkPairs.push({
887
+ product_id: product.id,
888
+ product_option_id: optionId,
889
+ });
890
+ }
891
+ }
892
+ delete product.option_ids;
893
+ }
894
+ await Promise.all([
895
+ linkPairs.length &&
896
+ this.addProductOptionToProduct_(linkPairs, sharedContext),
897
+ unlinkPairs.length &&
898
+ this.removeProductOptionFromProduct_(unlinkPairs, sharedContext),
899
+ ]);
900
+ await sharedContext.transactionManager.flush();
901
+ const normalizedProducts = this.normalizeUpdateProductInput(data);
695
902
  for (const product of normalizedProducts) {
696
903
  this.validateProductUpdatePayload(product);
697
904
  }
@@ -700,7 +907,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
700
907
  }
701
908
  // @ts-expect-error
702
909
  async updateProductOptionValues(idOrSelector, data, sharedContext = {}) {
703
- // TODO: There is a missmatch in the API which lead to function with different number of
910
+ // TODO: There is a mismatch in the API which lead to function with different number of
704
911
  // arguments. Therefore, applying the MedusaContext() decorator to the function will not work
705
912
  // because the context arg index will differ from method to method.
706
913
  sharedContext.messageAggregator ??= new utils_1.MessageAggregator();
@@ -772,10 +979,24 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
772
979
  validateProductUpdatePayload(productData) {
773
980
  this.validateProductPayload(productData);
774
981
  }
775
- async normalizeCreateProductInput(products, sharedContext = {}) {
982
+ normalizeCreateProductInput(products, sharedContext = {}) {
776
983
  const products_ = Array.isArray(products) ? products : [products];
777
- const normalizedProducts = (await this.normalizeUpdateProductInput(products_));
984
+ const normalizedProducts = this.normalizeUpdateProductInput(products_);
778
985
  for (const productData of normalizedProducts) {
986
+ if (productData.options?.length) {
987
+ ;
988
+ productData.options = productData.options?.map((option) => {
989
+ return {
990
+ title: option.title,
991
+ values: option.values?.map((value) => {
992
+ return {
993
+ value: value,
994
+ };
995
+ }),
996
+ ...(option.id ? { id: option.id } : {}),
997
+ };
998
+ });
999
+ }
779
1000
  if (!productData.handle && productData.title) {
780
1001
  productData.handle = (0, utils_1.toHandle)(productData.title);
781
1002
  }
@@ -816,46 +1037,18 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
816
1037
  * @param originalProducts - The original products to use for the normalization (must include options and option values relations)
817
1038
  * @returns The normalized products
818
1039
  */
819
- async normalizeUpdateProductInput(products, originalProducts) {
1040
+ normalizeUpdateProductInput(products) {
820
1041
  const products_ = Array.isArray(products) ? products : [products];
821
- const productsIds = products_.map((p) => p.id).filter(Boolean);
822
- let dbOptions = [];
823
- if (productsIds.length) {
824
- // Re map options to handle non serialized data as well
825
- dbOptions =
826
- originalProducts
827
- ?.map((originalProduct) => originalProduct.options.map((option) => option))
828
- .flat()
829
- .filter(Boolean) ?? [];
830
- }
831
1042
  const normalizedProducts = [];
832
1043
  for (const product of products_) {
833
1044
  const productData = { ...product };
834
1045
  if (productData.is_giftcard) {
835
1046
  productData.discountable = false;
836
1047
  }
837
- if (productData.options?.length) {
838
- ;
839
- productData.options = productData.options?.map((option) => {
840
- const dbOption = dbOptions.find((o) => (o.title === option.title || o.id === option.id) &&
841
- o.product_id === productData.id);
842
- return {
843
- title: option.title,
844
- values: option.values?.map((value) => {
845
- const dbValue = dbOption?.values?.find((val) => val.value === value);
846
- return {
847
- value: value,
848
- ...(dbValue ? { id: dbValue.id } : {}),
849
- };
850
- }),
851
- ...(dbOption ? { id: dbOption.id } : {}),
852
- };
853
- });
854
- }
855
1048
  if (productData.tag_ids) {
856
1049
  ;
857
- productData.tags = productData.tag_ids.map((cid) => ({
858
- id: cid,
1050
+ productData.tags = productData.tag_ids.map((tid) => ({
1051
+ id: tid,
859
1052
  }));
860
1053
  delete productData.tag_ids;
861
1054
  }
@@ -890,7 +1083,10 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
890
1083
  const variantsWithOptions = ProductModuleService.assignOptionsToVariants(variants.map((v) => ({
891
1084
  ...v,
892
1085
  // adding product_id to the variant to make it valid for the assignOptionsToVariants function
893
- ...(options.length ? { product_id: options[0].product_id } : {}),
1086
+ // get product_id from the first product in the products array of the first option
1087
+ ...(options.length && options[0].products?.length
1088
+ ? { product_id: options[0].products[0].id }
1089
+ : {}),
894
1090
  })), options);
895
1091
  ProductModuleService.checkIfVariantsHaveUniqueOptionsCombinations(variantsWithOptions);
896
1092
  }
@@ -900,7 +1096,13 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
900
1096
  }
901
1097
  const variantsWithOptions = variants.map((variant) => {
902
1098
  const numOfProvidedVariantOptionValues = Object.keys(variant.options || {}).length;
903
- const productsOptions = options.filter((o) => o.product_id === variant.product_id);
1099
+ const productsOptions = options.filter((o) => {
1100
+ // products could be a Collection object or array, normalize to array
1101
+ const productsArray = Array.isArray(o.products)
1102
+ ? o.products
1103
+ : o.products?.toArray?.() ?? [];
1104
+ return productsArray.some((p) => p.id === variant.product_id);
1105
+ });
904
1106
  if (numOfProvidedVariantOptionValues &&
905
1107
  productsOptions.length !== numOfProvidedVariantOptionValues) {
906
1108
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Product has ${productsOptions.length} option values but there were ${numOfProvidedVariantOptionValues} provided option values for the variant: ${variant.title}.`);
@@ -1312,6 +1514,36 @@ __decorate([
1312
1514
  __metadata("design:paramtypes", [Array, Object]),
1313
1515
  __metadata("design:returntype", Promise)
1314
1516
  ], ProductModuleService.prototype, "updateOptions_", null);
1517
+ __decorate([
1518
+ (0, utils_1.InjectManager)(),
1519
+ (0, utils_1.EmitEvents)(),
1520
+ __param(1, (0, utils_1.MedusaContext)()),
1521
+ __metadata("design:type", Function),
1522
+ __metadata("design:paramtypes", [Object, Object]),
1523
+ __metadata("design:returntype", Promise)
1524
+ ], ProductModuleService.prototype, "addProductOptionToProduct", null);
1525
+ __decorate([
1526
+ (0, utils_1.InjectTransactionManager)(),
1527
+ __param(1, (0, utils_1.MedusaContext)()),
1528
+ __metadata("design:type", Function),
1529
+ __metadata("design:paramtypes", [Object, Object]),
1530
+ __metadata("design:returntype", Promise)
1531
+ ], ProductModuleService.prototype, "addProductOptionToProduct_", null);
1532
+ __decorate([
1533
+ (0, utils_1.InjectManager)(),
1534
+ (0, utils_1.EmitEvents)(),
1535
+ __param(1, (0, utils_1.MedusaContext)()),
1536
+ __metadata("design:type", Function),
1537
+ __metadata("design:paramtypes", [Object, Object]),
1538
+ __metadata("design:returntype", Promise)
1539
+ ], ProductModuleService.prototype, "removeProductOptionFromProduct", null);
1540
+ __decorate([
1541
+ (0, utils_1.InjectTransactionManager)(),
1542
+ __param(1, (0, utils_1.MedusaContext)()),
1543
+ __metadata("design:type", Function),
1544
+ __metadata("design:paramtypes", [Object, Object]),
1545
+ __metadata("design:returntype", Promise)
1546
+ ], ProductModuleService.prototype, "removeProductOptionFromProduct_", null);
1315
1547
  __decorate([
1316
1548
  (0, utils_1.InjectManager)(),
1317
1549
  (0, utils_1.EmitEvents)()
@@ -1456,7 +1688,7 @@ __decorate([
1456
1688
  __param(1, (0, utils_1.MedusaContext)()),
1457
1689
  __metadata("design:type", Function),
1458
1690
  __metadata("design:paramtypes", [typeof (_a = typeof T !== "undefined" && T) === "function" ? _a : Object, Object]),
1459
- __metadata("design:returntype", Promise)
1691
+ __metadata("design:returntype", typeof (_b = typeof TOutput !== "undefined" && TOutput) === "function" ? _b : Object)
1460
1692
  ], ProductModuleService.prototype, "normalizeCreateProductInput", null);
1461
1693
  __decorate([
1462
1694
  (0, utils_1.InjectManager)()