@medusajs/product 3.0.0-snapshot-20251202221811 → 3.0.0-snapshot-20251208164410

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 (66) 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/migrations/Migration20251110180907.d.ts +6 -0
  10. package/dist/migrations/Migration20251110180907.d.ts.map +1 -0
  11. package/dist/migrations/Migration20251110180907.js +21 -0
  12. package/dist/migrations/Migration20251110180907.js.map +1 -0
  13. package/dist/migrations/Migration20251113183352.d.ts +6 -0
  14. package/dist/migrations/Migration20251113183352.d.ts.map +1 -0
  15. package/dist/migrations/Migration20251113183352.js +32 -0
  16. package/dist/migrations/Migration20251113183352.js.map +1 -0
  17. package/dist/models/index.d.ts +2 -0
  18. package/dist/models/index.d.ts.map +1 -1
  19. package/dist/models/index.js +5 -1
  20. package/dist/models/index.js.map +1 -1
  21. package/dist/models/product-category.d.ts +155 -6
  22. package/dist/models/product-category.d.ts.map +1 -1
  23. package/dist/models/product-collection.d.ts +155 -6
  24. package/dist/models/product-collection.d.ts.map +1 -1
  25. package/dist/models/product-image.d.ts +418 -14
  26. package/dist/models/product-image.d.ts.map +1 -1
  27. package/dist/models/product-option-value.d.ts +369 -12
  28. package/dist/models/product-option-value.d.ts.map +1 -1
  29. package/dist/models/product-option-value.js +14 -3
  30. package/dist/models/product-option-value.js.map +1 -1
  31. package/dist/models/product-option.d.ts +233 -6
  32. package/dist/models/product-option.d.ts.map +1 -1
  33. package/dist/models/product-option.js +4 -12
  34. package/dist/models/product-option.js.map +1 -1
  35. package/dist/models/product-product-option-value.d.ts +2213 -0
  36. package/dist/models/product-product-option-value.d.ts.map +1 -0
  37. package/dist/models/product-product-option-value.js +19 -0
  38. package/dist/models/product-product-option-value.js.map +1 -0
  39. package/dist/models/product-product-option.d.ts +1272 -0
  40. package/dist/models/product-product-option.d.ts.map +1 -0
  41. package/dist/models/product-product-option.js +24 -0
  42. package/dist/models/product-product-option.js.map +1 -0
  43. package/dist/models/product-tag.d.ts +155 -6
  44. package/dist/models/product-tag.d.ts.map +1 -1
  45. package/dist/models/product-type.d.ts +155 -6
  46. package/dist/models/product-type.d.ts.map +1 -1
  47. package/dist/models/product-variant-product-image.d.ts +780 -26
  48. package/dist/models/product-variant-product-image.d.ts.map +1 -1
  49. package/dist/models/product-variant.d.ts +362 -12
  50. package/dist/models/product-variant.d.ts.map +1 -1
  51. package/dist/models/product.d.ts +155 -6
  52. package/dist/models/product.d.ts.map +1 -1
  53. package/dist/models/product.js +7 -2
  54. package/dist/models/product.js.map +1 -1
  55. package/dist/repositories/product-category.d.ts +621 -15
  56. package/dist/repositories/product-category.d.ts.map +1 -1
  57. package/dist/repositories/product.d.ts +171 -6
  58. package/dist/repositories/product.d.ts.map +1 -1
  59. package/dist/repositories/product.js +50 -5
  60. package/dist/repositories/product.js.map +1 -1
  61. package/dist/services/product-module-service.d.ts +35 -2
  62. package/dist/services/product-module-service.d.ts.map +1 -1
  63. package/dist/services/product-module-service.js +660 -76
  64. package/dist/services/product-module-service.js.map +1 -1
  65. package/dist/tsconfig.tsbuildinfo +1 -1
  66. package/package.json +4 -4
@@ -16,8 +16,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
16
16
  const types_1 = require("@medusajs/framework/types");
17
17
  const _models_1 = require("../models");
18
18
  const utils_1 = require("@medusajs/framework/utils");
19
- const events_1 = require("../utils/events");
20
19
  const joiner_config_1 = require("./../joiner-config");
20
+ const events_1 = require("../utils/events");
21
21
  class ProductModuleService extends (0, utils_1.MedusaService)({
22
22
  Product: _models_1.Product,
23
23
  ProductCategory: _models_1.ProductCategory,
@@ -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, productProductOptionValueService, 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,8 @@ 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;
48
+ this.productProductOptionValueService_ = productProductOptionValueService;
47
49
  this.productOptionValueService_ = productOptionValueService;
48
50
  this.productVariantProductImageService_ = productVariantProductImageService;
49
51
  this.eventBusModuleService_ = eventBusModuleService;
@@ -55,10 +57,16 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
55
57
  async retrieveProduct(productId, config, sharedContext) {
56
58
  const relationsSet = new Set(config?.relations ?? []);
57
59
  const shouldLoadVariantImages = relationsSet.has("variants.images");
60
+ const shouldFilterOptionValues = relationsSet.has("options.values");
58
61
  if (shouldLoadVariantImages) {
59
62
  relationsSet.add("variants");
60
63
  relationsSet.add("images");
61
64
  }
65
+ if (shouldFilterOptionValues) {
66
+ relationsSet.add("options");
67
+ relationsSet.add("product_options");
68
+ relationsSet.add("product_options.values");
69
+ }
62
70
  const product = await this.productService_.retrieve(productId, this.getProductFindConfig_({
63
71
  ...config,
64
72
  relations: Array.from(relationsSet),
@@ -66,6 +74,9 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
66
74
  if (shouldLoadVariantImages && product.variants && product.images) {
67
75
  await this.buildVariantImagesFromProduct(product.variants, product.images, sharedContext);
68
76
  }
77
+ if (shouldFilterOptionValues) {
78
+ this.filterOptionValuesByProduct(product);
79
+ }
69
80
  return this.baseRepository_.serialize(product);
70
81
  }
71
82
  // @ts-ignore
@@ -76,6 +87,11 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
76
87
  relationsSet.add("variants");
77
88
  relationsSet.add("images");
78
89
  }
90
+ const shouldFilterOptionValues = relationsSet.has("options.values");
91
+ if (shouldFilterOptionValues) {
92
+ relationsSet.add("product_options");
93
+ relationsSet.add("product_options.values");
94
+ }
79
95
  const products = await this.productService_.list(filters, this.getProductFindConfig_({
80
96
  ...config,
81
97
  relations: Array.from(relationsSet),
@@ -87,11 +103,15 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
87
103
  }
88
104
  }
89
105
  }
106
+ if (shouldFilterOptionValues) {
107
+ this.filterOptionValuesByProducts(products);
108
+ }
90
109
  return this.baseRepository_.serialize(products);
91
110
  }
92
111
  // @ts-ignore
93
112
  async listAndCountProducts(filters, config, sharedContext) {
94
113
  const shouldLoadVariantImages = config?.relations?.includes("variants.images");
114
+ const shouldFilterOptionValues = config?.relations?.includes("options.values");
95
115
  // Ensure we load necessary relations
96
116
  const relations = [...(config?.relations || [])];
97
117
  if (shouldLoadVariantImages) {
@@ -102,6 +122,14 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
102
122
  relations.push("images");
103
123
  }
104
124
  }
125
+ if (shouldFilterOptionValues) {
126
+ if (!relations.includes("product_options")) {
127
+ relations.push("product_options");
128
+ }
129
+ if (!relations.includes("product_options.values")) {
130
+ relations.push("product_options.values");
131
+ }
132
+ }
105
133
  const [products, count] = await this.productService_.listAndCount(filters, this.getProductFindConfig_({ ...config, relations }), sharedContext);
106
134
  if (shouldLoadVariantImages) {
107
135
  for (const product of products) {
@@ -110,6 +138,9 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
110
138
  }
111
139
  }
112
140
  }
141
+ if (shouldFilterOptionValues) {
142
+ this.filterOptionValuesByProducts(products);
143
+ }
113
144
  const serializedProducts = await this.baseRepository_.serialize(products);
114
145
  return [serializedProducts, count];
115
146
  }
@@ -142,9 +173,11 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
142
173
  throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Unable to create variants without specifying a product_id");
143
174
  }
144
175
  const productOptions = await this.productOptionService_.list({
145
- product_id: [...new Set(data.map((v) => v.product_id))],
176
+ products: {
177
+ id: [...new Set(data.map((v) => v.product_id))],
178
+ },
146
179
  }, {
147
- relations: ["values"],
180
+ relations: ["values", "products"],
148
181
  }, sharedContext);
149
182
  const variants = await this.productVariantService_.list({
150
183
  product_id: [...new Set(data.map((v) => v.product_id))],
@@ -204,8 +237,10 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
204
237
  product_id: v.product_id,
205
238
  }));
206
239
  const productOptions = await this.productOptionService_.list({
207
- product_id: Array.from(new Set(variantsWithProductId.map((v) => v.product_id))),
208
- }, { relations: ["values"] }, sharedContext);
240
+ products: {
241
+ id: Array.from(new Set(variantsWithProductId.map((v) => v.product_id))),
242
+ },
243
+ }, { relations: ["values", "products"] }, sharedContext);
209
244
  const productVariantsWithOptions = ProductModuleService.assignOptionsToVariants(variantsWithProductId, productOptions);
210
245
  if (data.some((d) => !!d.options)) {
211
246
  ProductModuleService.checkIfVariantWithOptionsAlreadyExists(productVariantsWithOptions, allVariants);
@@ -313,18 +348,25 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
313
348
  return Array.isArray(data) ? createdOptions : createdOptions[0];
314
349
  }
315
350
  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
351
  const normalizedInput = data.map((opt) => {
352
+ Object.keys(opt.ranks ?? []).forEach((value) => {
353
+ if (!opt.values.includes(value)) {
354
+ 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.`);
355
+ }
356
+ });
320
357
  return {
321
358
  ...opt,
322
359
  values: opt.values?.map((v) => {
323
- return typeof v === "string" ? { value: v } : v;
360
+ // Normalize each value into an object and attach rank if available
361
+ const valueObj = (0, utils_1.isString)(v) ? { value: v } : v;
362
+ const rank = opt.ranks && (0, utils_1.isString)(v)
363
+ ? opt.ranks[v]
364
+ : opt.ranks?.[valueObj.value];
365
+ return rank !== undefined ? { ...valueObj, rank } : valueObj;
324
366
  }),
325
367
  };
326
368
  });
327
- return await this.productOptionService_.create(normalizedInput, sharedContext);
369
+ return this.productOptionService_.create(normalizedInput, sharedContext);
328
370
  }
329
371
  async upsertProductOptions(data, sharedContext = {}) {
330
372
  const input = Array.isArray(data) ? data : [data];
@@ -371,35 +413,255 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
371
413
  }
372
414
  // Data normalization
373
415
  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
- }),
416
+ const dbOption = dbOptions.find(({ id }) => id === opt.id);
417
+ const dbValues = dbOption?.values || [];
418
+ if (opt.ranks) {
419
+ const validValues = opt.values ?? dbValues.map((v) => v.value);
420
+ Object.keys(opt.ranks).forEach((value) => {
421
+ if (!validValues.includes(value)) {
422
+ 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
423
  }
397
- : {}),
424
+ });
425
+ }
426
+ let normalizedValues;
427
+ if (opt.values) {
428
+ // If new values are provided → normalize and apply ranks
429
+ normalizedValues = opt.values.map((v) => {
430
+ const valueObj = (0, utils_1.isString)(v) ? { value: v } : v;
431
+ const rank = opt.ranks && (0, utils_1.isString)(v)
432
+ ? opt.ranks[v]
433
+ : opt.ranks?.[valueObj.value];
434
+ const rankedValue = rank !== undefined ? { ...valueObj, rank } : valueObj;
435
+ if ("id" in rankedValue) {
436
+ return rankedValue;
437
+ }
438
+ const dbVal = dbValues.find((dbVal) => dbVal.value === rankedValue.value);
439
+ if (!dbVal) {
440
+ return rankedValue;
441
+ }
442
+ return {
443
+ id: dbVal.id,
444
+ ...rankedValue,
445
+ };
446
+ });
447
+ }
448
+ else if (opt.ranks) {
449
+ // If only ranks were provided → update existing DB values with ranks
450
+ normalizedValues = dbValues.map((dbVal) => {
451
+ const rank = opt.ranks[dbVal.value];
452
+ return rank !== undefined
453
+ ? { id: dbVal.id, value: dbVal.value, rank }
454
+ : { id: dbVal.id, value: dbVal.value };
455
+ });
456
+ }
457
+ const { ranks, ...cleanOpt } = opt;
458
+ return {
459
+ ...cleanOpt,
460
+ ...(normalizedValues ? { values: normalizedValues } : {}),
398
461
  };
399
462
  });
400
463
  const { entities: productOptions } = await this.productOptionService_.upsertWithReplace(normalizedInput, { relations: ["values"] }, sharedContext);
401
464
  return productOptions;
402
465
  }
466
+ async addProductOptionToProduct(data, sharedContext = {}) {
467
+ const productOptionProducts = await this.addProductOptionToProduct_(data, sharedContext);
468
+ return productOptionProducts;
469
+ }
470
+ async addProductOptionToProduct_(data, sharedContext = {}) {
471
+ const pairs = Array.isArray(data) ? data : [data];
472
+ const existingProductOptions = await this.productProductOptionService_.list({
473
+ $or: pairs.map((pair) => ({
474
+ product_id: pair.product_id,
475
+ product_option_id: pair.product_option_id,
476
+ })),
477
+ }, { relations: ["values"] }, sharedContext);
478
+ // Validate value removals when product_option_value_ids are provided - this sets new values for the product <> options meaning some values may be removed thus we need removal here as well
479
+ const validationPairs = [];
480
+ for (const pair of pairs) {
481
+ if (pair.product_option_value_ids) {
482
+ const existingProductOption = existingProductOptions.find((epo) => epo.product_id === pair.product_id &&
483
+ epo.product_option_id === pair.product_option_id);
484
+ if (existingProductOption) {
485
+ const currentValues = Array.isArray(existingProductOption.values)
486
+ ? existingProductOption.values
487
+ : existingProductOption.values?.toArray?.() ?? [];
488
+ const currentValueIds = new Set(currentValues.map((v) => v.id));
489
+ const newValueIds = new Set(pair.product_option_value_ids);
490
+ // Find values being removed
491
+ const removedValueIds = Array.from(currentValueIds).filter((id) => !newValueIds.has(id));
492
+ if (removedValueIds.length > 0) {
493
+ validationPairs.push({
494
+ productId: pair.product_id,
495
+ optionId: pair.product_option_id,
496
+ valueIdsToCheck: removedValueIds,
497
+ });
498
+ }
499
+ }
500
+ }
501
+ }
502
+ if (validationPairs.length > 0) {
503
+ await this.validateOptionRemoval_(validationPairs, sharedContext);
504
+ }
505
+ // Separate pairs: those that need PPO creation vs those that just need value updates
506
+ const pairsNeedingPPOCreation = [];
507
+ for (const pair of pairs) {
508
+ const hasExistingPPO = existingProductOptions.some((epo) => epo.product_id === pair.product_id &&
509
+ epo.product_option_id === pair.product_option_id);
510
+ // Only create PPO if it doesn't exist
511
+ if (!hasExistingPPO) {
512
+ pairsNeedingPPOCreation.push(pair);
513
+ }
514
+ }
515
+ let createdPPOs = [];
516
+ if (pairsNeedingPPOCreation.length > 0) {
517
+ const productProductOptions = await this.productProductOptionService_.create(pairsNeedingPPOCreation, sharedContext);
518
+ createdPPOs = (Array.isArray(productProductOptions)
519
+ ? productProductOptions
520
+ : [productProductOptions]);
521
+ }
522
+ // Map all PPOs (existing and newly created) to their pairs
523
+ const ppoMap = new Map();
524
+ // First, add existing PPOs (these take precedence for value updates)
525
+ for (const existingPPO of existingProductOptions) {
526
+ const key = `${existingPPO.product_id}_${existingPPO.product_option_id}`;
527
+ ppoMap.set(key, existingPPO);
528
+ }
529
+ // Then add newly created PPOs (only if they don't already exist in the map)
530
+ for (const ppo of createdPPOs) {
531
+ const key = `${ppo.product_id}_${ppo.product_option_id}`;
532
+ if (!ppoMap.has(key)) {
533
+ ppoMap.set(key, ppo);
534
+ }
535
+ }
536
+ const uniqueOptionIds = [...new Set(pairs.map((p) => p.product_option_id))];
537
+ const options = await this.productOptionService_.list({ id: uniqueOptionIds }, { relations: ["values"] }, sharedContext);
538
+ const optionValuesMap = new Map(options.map((opt) => [opt.id, opt.values || []]));
539
+ const valuePairsToCreate = [];
540
+ for (const pair of pairs) {
541
+ const key = `${pair.product_id}_${pair.product_option_id}`;
542
+ const ppo = ppoMap.get(key);
543
+ if (!ppo)
544
+ continue;
545
+ const originalPair = pair;
546
+ if (originalPair) {
547
+ const allValues = optionValuesMap.get(originalPair.product_option_id) || [];
548
+ // If specific value IDs were provided, use only those; otherwise use all values
549
+ const valuesToLink = originalPair.product_option_value_ids
550
+ ? allValues.filter((v) => originalPair.product_option_value_ids.includes(v.id))
551
+ : allValues;
552
+ // If updating with specific value_ids, remove old values that aren't in the new list
553
+ if (originalPair.product_option_value_ids) {
554
+ const existingProductOption = existingProductOptions.find((epo) => epo.product_id === originalPair.product_id &&
555
+ epo.product_option_id === originalPair.product_option_id);
556
+ if (existingProductOption) {
557
+ const currentValues = Array.isArray(existingProductOption.values)
558
+ ? existingProductOption.values
559
+ : existingProductOption.values?.toArray?.() ?? [];
560
+ const currentValueIds = new Set(currentValues.map((v) => v.id));
561
+ const newValueIds = new Set(originalPair.product_option_value_ids);
562
+ // Find values to remove
563
+ const valuesToRemove = Array.from(currentValueIds).filter((id) => !newValueIds.has(id));
564
+ // Delete removed value links
565
+ if (valuesToRemove.length > 0) {
566
+ await this.productProductOptionValueService_.delete({
567
+ product_product_option_id: existingProductOption.id,
568
+ product_option_value_id: valuesToRemove,
569
+ }, sharedContext);
570
+ // Flush to ensure deletion is persisted
571
+ await sharedContext.transactionManager?.flush?.();
572
+ }
573
+ // refetch PPOs after deletion
574
+ const [reloadedPPO] = await this.productProductOptionService_.list({
575
+ id: existingProductOption.id,
576
+ }, { relations: ["values"] }, sharedContext);
577
+ const reloadedValues = reloadedPPO
578
+ ? Array.isArray(reloadedPPO.values)
579
+ ? reloadedPPO.values
580
+ : reloadedPPO.values?.toArray?.() ?? []
581
+ : [];
582
+ const reloadedValueIds = new Set(reloadedValues.map((v) => v.id));
583
+ // Only create links for values that don't already exist (check against reloaded values)
584
+ const existingValueIdsSet = reloadedValueIds;
585
+ for (const value of valuesToLink) {
586
+ if (!existingValueIdsSet.has(value.id)) {
587
+ valuePairsToCreate.push({
588
+ product_product_option_id: existingProductOption.id,
589
+ product_option_value_id: value.id,
590
+ });
591
+ }
592
+ }
593
+ }
594
+ else {
595
+ // No existing link, create all value links
596
+ for (const value of valuesToLink) {
597
+ valuePairsToCreate.push({
598
+ product_product_option_id: ppo.id,
599
+ product_option_value_id: value.id,
600
+ });
601
+ }
602
+ }
603
+ }
604
+ else {
605
+ // No specific value_ids, create all value links (only if PPO is newly created)
606
+ if (!existingProductOptions.find((epo) => epo.product_id === pair.product_id &&
607
+ epo.product_option_id === pair.product_option_id)) {
608
+ for (const value of valuesToLink) {
609
+ valuePairsToCreate.push({
610
+ product_product_option_id: ppo.id,
611
+ product_option_value_id: value.id,
612
+ });
613
+ }
614
+ }
615
+ }
616
+ }
617
+ }
618
+ if (valuePairsToCreate.length > 0) {
619
+ await this.productProductOptionValueService_.create(valuePairsToCreate, sharedContext);
620
+ }
621
+ // Get all PPOs (existing + created) for return value
622
+ const allPPOsForReturn = [];
623
+ for (const pair of pairs) {
624
+ const key = `${pair.product_id}_${pair.product_option_id}`;
625
+ const ppo = ppoMap.get(key);
626
+ if (ppo) {
627
+ allPPOsForReturn.push(ppo);
628
+ }
629
+ }
630
+ if (Array.isArray(data)) {
631
+ return allPPOsForReturn.map((ppo) => ({ id: ppo.id }));
632
+ }
633
+ return { id: allPPOsForReturn[0]?.id || createdPPOs[0]?.id };
634
+ }
635
+ async removeProductOptionFromProduct(data, sharedContext = {}) {
636
+ await this.removeProductOptionFromProduct_(data, sharedContext);
637
+ }
638
+ async removeProductOptionFromProduct_(data, sharedContext = {}) {
639
+ const pairs = Array.isArray(data) ? data : [data];
640
+ const productOptionsProducts = await this.productProductOptionService_.list({
641
+ $or: pairs,
642
+ }, { relations: [] }, sharedContext);
643
+ // Validate that no variants are using the options before removal
644
+ const validationPairs = productOptionsProducts
645
+ .map((productOptionProduct) => {
646
+ const productId = productOptionProduct.product_id;
647
+ const optionId = productOptionProduct.product_option_id;
648
+ if (productId && optionId) {
649
+ return {
650
+ productId,
651
+ optionId,
652
+ // Check all values of the option
653
+ };
654
+ }
655
+ return null;
656
+ })
657
+ .filter((p) => p !== null);
658
+ if (validationPairs.length > 0) {
659
+ await this.validateOptionRemoval_(validationPairs, sharedContext);
660
+ }
661
+ const productOptionsProductIds = productOptionsProducts.map(({ id }) => id);
662
+ await this.productProductOptionValueService_.delete(productOptionsProductIds.map((id) => ({ product_product_option_id: id })), sharedContext);
663
+ await this.productProductOptionService_.delete(productOptionsProductIds, sharedContext);
664
+ }
403
665
  // @ts-expect-error
404
666
  async createProductCollections(data, sharedContext = {}) {
405
667
  const input = Array.isArray(data) ? data : [data];
@@ -624,7 +886,41 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
624
886
  return (0, utils_1.isString)(idOrSelector) ? updatedProducts[0] : updatedProducts;
625
887
  }
626
888
  async createProducts_(data, sharedContext = {}) {
627
- const normalizedProducts = await this.normalizeCreateProductInput(data, sharedContext);
889
+ const existingOptionIds = data
890
+ .flatMap((p) => p.options ?? [])
891
+ .filter((o) => "id" in o)
892
+ .map((o) => o.id);
893
+ let existingOptions = [];
894
+ if (existingOptionIds.length > 0) {
895
+ existingOptions = await this.productOptionService_.list({ id: existingOptionIds }, { relations: ["values"] }, sharedContext);
896
+ const fetchedIds = new Set(existingOptions.map((opt) => opt.id));
897
+ const missingIds = existingOptionIds.filter((id) => !fetchedIds.has(id));
898
+ if (missingIds.length) {
899
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Some product options were not found: [${missingIds.join(", ")}]`);
900
+ }
901
+ }
902
+ const existingOptionsMap = new Map(existingOptions.map((opt) => [opt.id, opt]));
903
+ const hydratedData = data.map((product) => {
904
+ if (!product.options?.length)
905
+ return product;
906
+ const hydratedOptions = product.options.map((option) => {
907
+ if ("id" in option) {
908
+ const dbOption = existingOptionsMap.get(option.id);
909
+ if (!dbOption) {
910
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Product option with id ${option.id} not found.`);
911
+ }
912
+ return {
913
+ id: dbOption.id,
914
+ title: dbOption.title,
915
+ values: dbOption.values?.map((v) => ({ value: v.value })),
916
+ value_ids: option.value_ids,
917
+ };
918
+ }
919
+ return option;
920
+ });
921
+ return { ...product, options: hydratedOptions };
922
+ });
923
+ const normalizedProducts = (await this.normalizeCreateProductInput(hydratedData, sharedContext));
628
924
  for (const product of normalizedProducts) {
629
925
  this.validateProductCreatePayload(product);
630
926
  }
@@ -638,6 +934,7 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
638
934
  }, {}, sharedContext);
639
935
  }
640
936
  const existingTagsMap = new Map(existingTags.map((tag) => [tag.id, tag]));
937
+ const productOptionsToCreate = new Map();
641
938
  const productsToCreate = normalizedProducts.map((product) => {
642
939
  const productId = (0, utils_1.generateEntityId)(product.id, "prod");
643
940
  product.id = productId;
@@ -645,6 +942,12 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
645
942
  ;
646
943
  product.categories = product.categories.map((category) => category.id);
647
944
  }
945
+ if (product.options?.length) {
946
+ const newOptions = product.options.filter((o) => !("id" in o));
947
+ if (newOptions.length) {
948
+ productOptionsToCreate.set(productId, newOptions);
949
+ }
950
+ }
648
951
  if (product.variants?.length) {
649
952
  const normalizedVariants = product.variants.map((variant) => {
650
953
  const variantId = (0, utils_1.generateEntityId)(variant.id, "variant");
@@ -670,10 +973,79 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
670
973
  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
974
  });
672
975
  }
976
+ delete product.options;
673
977
  return product;
674
978
  });
675
- const createdProducts = await this.productService_.create(productsToCreate, sharedContext);
676
- return createdProducts;
979
+ const productToOptionIdsMap = new Map();
980
+ const allOptionsWithIds = [];
981
+ for (const [productId, options] of productOptionsToCreate.entries()) {
982
+ const optionIds = [];
983
+ for (const option of options) {
984
+ const optionId = (0, utils_1.generateEntityId)(undefined, "opt");
985
+ optionIds.push(optionId);
986
+ allOptionsWithIds.push({
987
+ ...option,
988
+ id: optionId,
989
+ });
990
+ }
991
+ productToOptionIdsMap.set(productId, optionIds);
992
+ }
993
+ const [createdProducts] = await Promise.all([
994
+ this.productService_.create(productsToCreate, sharedContext),
995
+ allOptionsWithIds.length > 0
996
+ ? this.createOptions_(allOptionsWithIds, sharedContext)
997
+ : Promise.resolve([]),
998
+ ]);
999
+ const linkPairs = [];
1000
+ for (const product of createdProducts) {
1001
+ const hydratedProduct = hydratedData.find((p) => p.title === product.title);
1002
+ const existingOptions = [];
1003
+ if (hydratedProduct?.options?.length) {
1004
+ for (const option of hydratedProduct.options) {
1005
+ if ("id" in option) {
1006
+ existingOptions.push({
1007
+ id: option.id,
1008
+ value_ids: option.value_ids,
1009
+ });
1010
+ }
1011
+ }
1012
+ }
1013
+ const newOptionIds = productToOptionIdsMap.get(product.id) ?? [];
1014
+ const newOptions = newOptionIds.map((id) => ({ id }));
1015
+ const allOptions = [...existingOptions, ...newOptions];
1016
+ for (const option of allOptions) {
1017
+ const pair = {
1018
+ product_id: product.id,
1019
+ product_option_id: option.id,
1020
+ product_option_value_ids: option.value_ids
1021
+ ? option.value_ids
1022
+ : undefined,
1023
+ };
1024
+ linkPairs.push(pair);
1025
+ }
1026
+ }
1027
+ if (linkPairs.length > 0) {
1028
+ await this.addProductOptionToProduct_(linkPairs, sharedContext);
1029
+ }
1030
+ await sharedContext.transactionManager.flush();
1031
+ const productIds = createdProducts.map((p) => p.id);
1032
+ const productsWithOptions = await this.productService_.list({ id: productIds }, {
1033
+ relations: [
1034
+ "options",
1035
+ "options.values",
1036
+ "options.products",
1037
+ "product_options",
1038
+ "product_options.values",
1039
+ "variants",
1040
+ "images",
1041
+ "tags",
1042
+ ],
1043
+ }, sharedContext);
1044
+ // Filter option values to only include those associated with each product
1045
+ this.filterOptionValuesByProducts(productsWithOptions);
1046
+ const productIdOrder = new Map(productIds.map((id, index) => [id, index]));
1047
+ const orderedProductsWithOptions = [...productsWithOptions].sort((a, b) => (productIdOrder.get(a.id) ?? 0) - (productIdOrder.get(b.id) ?? 0));
1048
+ return orderedProductsWithOptions;
677
1049
  }
678
1050
  async updateProducts_(data, sharedContext = {}) {
679
1051
  // We have to do that manually because this method is bypassing the product service and goes
@@ -686,23 +1058,73 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
686
1058
  .getEventManager()
687
1059
  .registerSubscriber(new subscriber(sharedContext));
688
1060
  }
689
- const productIds = data.map((d) => d.id).filter(Boolean);
690
- const originalProducts = await this.productService_.list({
691
- id: productIds,
692
- }, {
693
- relations: ["options", "options.values", "tags"],
694
- take: productIds.length,
695
- }, sharedContext);
696
- const normalizedProducts = await this.normalizeUpdateProductInput(data, originalProducts);
1061
+ const allOptionIds = data
1062
+ .flatMap((p) => p.option_ids ?? [])
1063
+ .filter((id) => !!id);
1064
+ const [originalProducts, existingOptions] = await Promise.all([
1065
+ this.productService_.list({ id: data.map((d) => d.id) }, {
1066
+ relations: ["options", "options.values", "options.products", "tags"],
1067
+ }, sharedContext),
1068
+ allOptionIds.length
1069
+ ? this.productOptionService_.list({ id: allOptionIds }, {
1070
+ relations: ["values", "products"],
1071
+ }, sharedContext)
1072
+ : Promise.resolve([]),
1073
+ ]);
1074
+ if (allOptionIds.length && existingOptions.length !== allOptionIds.length) {
1075
+ const found = new Set(existingOptions.map((opt) => opt.id));
1076
+ const missing = allOptionIds.filter((id) => !found.has(id));
1077
+ if (missing.length) {
1078
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Some product options were not found: [${missing.join(", ")}]`);
1079
+ }
1080
+ }
1081
+ const linkPairs = [];
1082
+ const unlinkPairs = [];
1083
+ for (const product of data) {
1084
+ if (!product.option_ids) {
1085
+ continue;
1086
+ }
1087
+ const newOptionIds = new Set(product.option_ids);
1088
+ const existingOptionIds = new Set(originalProducts
1089
+ .find((p) => p.id === product.id)
1090
+ ?.options?.map((o) => o.id) ?? []);
1091
+ for (const optionId of newOptionIds) {
1092
+ if (!existingOptionIds.has(optionId)) {
1093
+ linkPairs.push({
1094
+ product_id: product.id,
1095
+ product_option_id: optionId,
1096
+ });
1097
+ }
1098
+ }
1099
+ for (const optionId of existingOptionIds) {
1100
+ if (!newOptionIds.has(optionId)) {
1101
+ unlinkPairs.push({
1102
+ product_id: product.id,
1103
+ product_option_id: optionId,
1104
+ });
1105
+ }
1106
+ }
1107
+ delete product.option_ids;
1108
+ }
1109
+ await Promise.all([
1110
+ linkPairs.length &&
1111
+ this.addProductOptionToProduct_(linkPairs, sharedContext),
1112
+ unlinkPairs.length &&
1113
+ this.removeProductOptionFromProduct_(unlinkPairs, sharedContext),
1114
+ ]);
1115
+ await sharedContext.transactionManager.flush();
1116
+ const normalizedProducts = (await this.normalizeUpdateProductInput(data));
697
1117
  for (const product of normalizedProducts) {
698
1118
  this.validateProductUpdatePayload(product);
699
1119
  }
700
1120
  const updatedProducts = await this.productRepository_.deepUpdate(normalizedProducts, ProductModuleService.validateVariantOptions, sharedContext);
1121
+ // Filter option values to only include those associated with each product
1122
+ this.filterOptionValuesByProducts(updatedProducts);
701
1123
  return updatedProducts;
702
1124
  }
703
1125
  // @ts-expect-error
704
1126
  async updateProductOptionValues(idOrSelector, data, sharedContext = {}) {
705
- // TODO: There is a missmatch in the API which lead to function with different number of
1127
+ // TODO: There is a mismatch in the API which lead to function with different number of
706
1128
  // arguments. Therefore, applying the MedusaContext() decorator to the function will not work
707
1129
  // because the context arg index will differ from method to method.
708
1130
  sharedContext.messageAggregator ??= new utils_1.MessageAggregator();
@@ -777,7 +1199,22 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
777
1199
  async normalizeCreateProductInput(products, sharedContext = {}) {
778
1200
  const products_ = Array.isArray(products) ? products : [products];
779
1201
  const normalizedProducts = (await this.normalizeUpdateProductInput(products_));
780
- for (const productData of normalizedProducts) {
1202
+ for await (const productData of normalizedProducts) {
1203
+ if (productData.options?.length) {
1204
+ ;
1205
+ productData.options = productData.options?.map((option) => {
1206
+ return {
1207
+ title: option.title,
1208
+ values: option.values?.map((value) => {
1209
+ return {
1210
+ value: value,
1211
+ };
1212
+ }),
1213
+ is_exclusive: option.is_exclusive ?? true, // Always default to true for options created from product creation
1214
+ ...(option.id ? { id: option.id } : {}),
1215
+ };
1216
+ });
1217
+ }
781
1218
  if (!productData.handle && productData.title) {
782
1219
  productData.handle = (0, utils_1.toHandle)(productData.title);
783
1220
  }
@@ -820,39 +1257,12 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
820
1257
  */
821
1258
  async normalizeUpdateProductInput(products, originalProducts) {
822
1259
  const products_ = Array.isArray(products) ? products : [products];
823
- const productsIds = products_.map((p) => p.id).filter(Boolean);
824
- let dbOptions = [];
825
- if (productsIds.length) {
826
- // Re map options to handle non serialized data as well
827
- dbOptions =
828
- originalProducts
829
- ?.flatMap((originalProduct) => originalProduct.options.map((option) => option))
830
- .filter(Boolean) ?? [];
831
- }
832
1260
  const normalizedProducts = [];
833
1261
  for (const product of products_) {
834
1262
  const productData = { ...product };
835
1263
  if (productData.is_giftcard) {
836
1264
  productData.discountable = false;
837
1265
  }
838
- if (productData.options?.length) {
839
- ;
840
- productData.options = productData.options?.map((option) => {
841
- const dbOption = dbOptions.find((o) => (o.title === option.title || o.id === option.id) &&
842
- o.product_id === productData.id);
843
- return {
844
- title: option.title,
845
- values: option.values?.map((value) => {
846
- const dbValue = dbOption?.values?.find((val) => val.value === value);
847
- return {
848
- value: value,
849
- ...(dbValue ? { id: dbValue.id } : {}),
850
- };
851
- }),
852
- ...(dbOption ? { id: dbOption.id } : {}),
853
- };
854
- });
855
- }
856
1266
  if (productData.tag_ids) {
857
1267
  ;
858
1268
  productData.tags = productData.tag_ids.map((cid) => ({
@@ -891,7 +1301,10 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
891
1301
  const variantsWithOptions = ProductModuleService.assignOptionsToVariants(variants.map((v) => ({
892
1302
  ...v,
893
1303
  // adding product_id to the variant to make it valid for the assignOptionsToVariants function
894
- ...(options.length ? { product_id: options[0].product_id } : {}),
1304
+ // get product_id from the first product in the products array of the first option
1305
+ ...(options.length && options[0].products?.length
1306
+ ? { product_id: options[0].products[0].id }
1307
+ : {}),
895
1308
  })), options);
896
1309
  ProductModuleService.checkIfVariantsHaveUniqueOptionsCombinations(variantsWithOptions);
897
1310
  }
@@ -901,7 +1314,13 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
901
1314
  }
902
1315
  const variantsWithOptions = variants.map((variant) => {
903
1316
  const numOfProvidedVariantOptionValues = Object.keys(variant.options || {}).length;
904
- const productsOptions = options.filter((o) => o.product_id === variant.product_id);
1317
+ const productsOptions = options.filter((o) => {
1318
+ // products could be a Collection object or array, normalize to array
1319
+ const productsArray = Array.isArray(o.products)
1320
+ ? o.products
1321
+ : o.products?.toArray?.() ?? [];
1322
+ return productsArray.some((p) => p.id === variant.product_id);
1323
+ });
905
1324
  if (numOfProvidedVariantOptionValues &&
906
1325
  productsOptions.length !== numOfProvidedVariantOptionValues) {
907
1326
  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}.`);
@@ -1100,6 +1519,134 @@ class ProductModuleService extends (0, utils_1.MedusaService)({
1100
1519
  }
1101
1520
  return result;
1102
1521
  }
1522
+ /**
1523
+ * Validates that no variants are using the specified option or option values
1524
+ * before they are removed from a product.
1525
+ *
1526
+ * @param pairs - Array of validation pairs: { productId, optionId, valueIdsToCheck? }
1527
+ * @param sharedContext - The shared context
1528
+ * @throws MedusaError if any variants are using the option/values
1529
+ */
1530
+ async validateOptionRemoval_(pairs, sharedContext = {}) {
1531
+ if (pairs.length === 0) {
1532
+ return;
1533
+ }
1534
+ // Filter pairs that need validation (for option removal, check if option is linked)
1535
+ const pairsToValidate = [];
1536
+ // For option removals (no valueIdsToCheck), check if options are linked to products
1537
+ const optionRemovalPairs = pairs.filter((p) => !p.valueIdsToCheck);
1538
+ if (optionRemovalPairs.length > 0) {
1539
+ const existingProductOptions = await this.productProductOptionService_.list({
1540
+ $or: optionRemovalPairs.map((p) => ({
1541
+ product_id: p.productId,
1542
+ product_option_id: p.optionId,
1543
+ })),
1544
+ }, {}, sharedContext);
1545
+ const existingPairsSet = new Set(existingProductOptions.map((epo) => `${epo.product_id}_${epo.product_option_id}`));
1546
+ // Only validate pairs that are actually linked
1547
+ for (const pair of optionRemovalPairs) {
1548
+ const key = `${pair.productId}_${pair.optionId}`;
1549
+ if (existingPairsSet.has(key)) {
1550
+ pairsToValidate.push(pair);
1551
+ }
1552
+ }
1553
+ }
1554
+ // For value removals (with valueIdsToCheck), always validate
1555
+ const valueRemovalPairs = pairs.filter((p) => p.valueIdsToCheck);
1556
+ pairsToValidate.push(...valueRemovalPairs);
1557
+ if (pairsToValidate.length === 0) {
1558
+ return; // Nothing to validate
1559
+ }
1560
+ // Get all unique option IDs to fetch options with their values
1561
+ const uniqueOptionIds = [...new Set(pairsToValidate.map((p) => p.optionId))];
1562
+ const options = await this.productOptionService_.list({ id: uniqueOptionIds }, { relations: ["values"] }, sharedContext);
1563
+ const optionsMap = new Map(options.map((opt) => [opt.id, opt]));
1564
+ const bulkValidationPairs = [];
1565
+ for (const pair of pairsToValidate) {
1566
+ const option = optionsMap.get(pair.optionId);
1567
+ if (!option) {
1568
+ continue; // Option doesn't exist, skip
1569
+ }
1570
+ // if no subset is provided we check the whole option values
1571
+ const valueIdsToValidate = pair.valueIdsToCheck
1572
+ ? pair.valueIdsToCheck
1573
+ : (option.values || []).map((v) => v.id);
1574
+ if (valueIdsToValidate.length === 0) {
1575
+ continue; // No values to check
1576
+ }
1577
+ bulkValidationPairs.push({
1578
+ productId: pair.productId,
1579
+ optionValueIds: valueIdsToValidate,
1580
+ pair,
1581
+ option,
1582
+ });
1583
+ }
1584
+ if (bulkValidationPairs.length > 0) {
1585
+ const conflictingVariantsMap = await this.productRepository_.checkVariantsUsingOptionValues(bulkValidationPairs.map((p) => ({
1586
+ productId: p.productId,
1587
+ optionValueIds: p.optionValueIds,
1588
+ })), sharedContext);
1589
+ for (const bulkPair of bulkValidationPairs) {
1590
+ const allConflictingVariants = [];
1591
+ for (const valueId of bulkPair.optionValueIds) {
1592
+ const key = `${bulkPair.productId}_${valueId}`;
1593
+ const variants = conflictingVariantsMap.get(key) || [];
1594
+ // Deduplicate variants by variant_id
1595
+ for (const variant of variants) {
1596
+ if (!allConflictingVariants.some((v) => v.variant_id === variant.variant_id)) {
1597
+ allConflictingVariants.push({
1598
+ variant_id: variant.variant_id,
1599
+ title: variant.title,
1600
+ });
1601
+ }
1602
+ }
1603
+ }
1604
+ if (allConflictingVariants.length > 0) {
1605
+ const variantNames = allConflictingVariants.map((v) => v.title || v.variant_id);
1606
+ if (bulkPair.pair.valueIdsToCheck) {
1607
+ // Specific values being removed
1608
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Cannot unassign option values from product because the following variant(s) are using it: ${variantNames.join(", ")}`);
1609
+ }
1610
+ else {
1611
+ // Entire option being removed
1612
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, `Cannot unassign product option from product which has variants for that option`);
1613
+ }
1614
+ }
1615
+ }
1616
+ }
1617
+ }
1618
+ filterOptionValuesByProducts(products) {
1619
+ for (const product of products) {
1620
+ this.filterOptionValuesByProduct(product);
1621
+ }
1622
+ }
1623
+ filterOptionValuesByProduct(product) {
1624
+ if (!product.options || !product.product_options) {
1625
+ return;
1626
+ }
1627
+ const productOptions = Array.isArray(product.product_options)
1628
+ ? product.product_options
1629
+ : product.product_options?.toArray?.() ?? [];
1630
+ // Build a Set of value IDs that are actually associated with this product
1631
+ const allowedValueIds = new Set();
1632
+ for (const productOption of productOptions) {
1633
+ const values = Array.isArray(productOption.values)
1634
+ ? productOption.values
1635
+ : productOption.values?.toArray?.() ?? [];
1636
+ for (const value of values) {
1637
+ allowedValueIds.add(value.id);
1638
+ }
1639
+ }
1640
+ // Filter the values in each option to only include allowed ones
1641
+ if (product.options) {
1642
+ for (const option of product.options) {
1643
+ if (option.values) {
1644
+ option.values = option.values.filter((value) => allowedValueIds.has(value.id));
1645
+ }
1646
+ }
1647
+ }
1648
+ delete product.product_options;
1649
+ }
1103
1650
  async buildVariantImagesFromProduct(variants, productImages, sharedContext = {}) {
1104
1651
  // Create a clean map of images without problematic collections
1105
1652
  const imagesMap = new Map();
@@ -1313,6 +1860,36 @@ __decorate([
1313
1860
  __metadata("design:paramtypes", [Array, Object]),
1314
1861
  __metadata("design:returntype", Promise)
1315
1862
  ], ProductModuleService.prototype, "updateOptions_", null);
1863
+ __decorate([
1864
+ (0, utils_1.InjectManager)(),
1865
+ (0, utils_1.EmitEvents)(),
1866
+ __param(1, (0, utils_1.MedusaContext)()),
1867
+ __metadata("design:type", Function),
1868
+ __metadata("design:paramtypes", [Object, Object]),
1869
+ __metadata("design:returntype", Promise)
1870
+ ], ProductModuleService.prototype, "addProductOptionToProduct", null);
1871
+ __decorate([
1872
+ (0, utils_1.InjectTransactionManager)(),
1873
+ __param(1, (0, utils_1.MedusaContext)()),
1874
+ __metadata("design:type", Function),
1875
+ __metadata("design:paramtypes", [Object, Object]),
1876
+ __metadata("design:returntype", Promise)
1877
+ ], ProductModuleService.prototype, "addProductOptionToProduct_", null);
1878
+ __decorate([
1879
+ (0, utils_1.InjectManager)(),
1880
+ (0, utils_1.EmitEvents)(),
1881
+ __param(1, (0, utils_1.MedusaContext)()),
1882
+ __metadata("design:type", Function),
1883
+ __metadata("design:paramtypes", [Object, Object]),
1884
+ __metadata("design:returntype", Promise)
1885
+ ], ProductModuleService.prototype, "removeProductOptionFromProduct", null);
1886
+ __decorate([
1887
+ (0, utils_1.InjectTransactionManager)(),
1888
+ __param(1, (0, utils_1.MedusaContext)()),
1889
+ __metadata("design:type", Function),
1890
+ __metadata("design:paramtypes", [Object, Object]),
1891
+ __metadata("design:returntype", Promise)
1892
+ ], ProductModuleService.prototype, "removeProductOptionFromProduct_", null);
1316
1893
  __decorate([
1317
1894
  (0, utils_1.InjectManager)(),
1318
1895
  (0, utils_1.EmitEvents)()
@@ -1530,4 +2107,11 @@ __decorate([
1530
2107
  __metadata("design:paramtypes", [Array, Object]),
1531
2108
  __metadata("design:returntype", Promise)
1532
2109
  ], ProductModuleService.prototype, "getVariantImages", null);
2110
+ __decorate([
2111
+ (0, utils_1.InjectTransactionManager)(),
2112
+ __param(1, (0, utils_1.MedusaContext)()),
2113
+ __metadata("design:type", Function),
2114
+ __metadata("design:paramtypes", [Array, Object]),
2115
+ __metadata("design:returntype", Promise)
2116
+ ], ProductModuleService.prototype, "validateOptionRemoval_", null);
1533
2117
  //# sourceMappingURL=product-module-service.js.map