@unchainedshop/api 4.0.0-rc.5 → 4.0.0-rc.7

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 (205) hide show
  1. package/README.md +3 -1
  2. package/lib/errors.d.ts +2 -0
  3. package/lib/errors.d.ts.map +1 -1
  4. package/lib/errors.js +2 -0
  5. package/lib/errors.js.map +1 -1
  6. package/lib/loaders/index.d.ts +5 -0
  7. package/lib/loaders/index.d.ts.map +1 -1
  8. package/lib/loaders/index.js +2 -0
  9. package/lib/loaders/index.js.map +1 -1
  10. package/lib/loaders/productProxiesLoader.d.ts +10 -0
  11. package/lib/loaders/productProxiesLoader.d.ts.map +1 -0
  12. package/lib/loaders/productProxiesLoader.js +30 -0
  13. package/lib/loaders/productProxiesLoader.js.map +1 -0
  14. package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.d.ts.map +1 -1
  15. package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.js +2 -1
  16. package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.js.map +1 -1
  17. package/lib/mcp/tools/assortment/assortmentMediaHandler.d.ts +32 -0
  18. package/lib/mcp/tools/assortment/assortmentMediaHandler.d.ts.map +1 -0
  19. package/lib/mcp/tools/assortment/assortmentMediaHandler.js +48 -0
  20. package/lib/mcp/tools/assortment/assortmentMediaHandler.js.map +1 -0
  21. package/lib/mcp/tools/assortment/index.d.ts.map +1 -1
  22. package/lib/mcp/tools/assortment/index.js +3 -1
  23. package/lib/mcp/tools/assortment/index.js.map +1 -1
  24. package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.d.ts.map +1 -1
  25. package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.js +3 -1
  26. package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.js.map +1 -1
  27. package/lib/mcp/tools/assortment/reorderAssortmentMediaHandler.js +2 -2
  28. package/lib/mcp/tools/assortment/reorderAssortmentMediaHandler.js.map +1 -1
  29. package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.d.ts.map +1 -1
  30. package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.js +7 -1
  31. package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.js.map +1 -1
  32. package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.d.ts.map +1 -1
  33. package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.js +4 -1
  34. package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.js.map +1 -1
  35. package/lib/mcp/tools/product/addProductAssignmentHandler.d.ts.map +1 -1
  36. package/lib/mcp/tools/product/addProductAssignmentHandler.js +1 -1
  37. package/lib/mcp/tools/product/addProductAssignmentHandler.js.map +1 -1
  38. package/lib/mcp/tools/product/addProductBundleItemHandler.d.ts +48 -0
  39. package/lib/mcp/tools/product/addProductBundleItemHandler.d.ts.map +1 -0
  40. package/lib/mcp/tools/product/addProductBundleItemHandler.js +52 -0
  41. package/lib/mcp/tools/product/addProductBundleItemHandler.js.map +1 -0
  42. package/lib/mcp/tools/product/addProductMediaUploadHandler.d.ts.map +1 -1
  43. package/lib/mcp/tools/product/addProductMediaUploadHandler.js +3 -2
  44. package/lib/mcp/tools/product/addProductMediaUploadHandler.js.map +1 -1
  45. package/lib/mcp/tools/product/catalogPriceHandler.d.ts +28 -0
  46. package/lib/mcp/tools/product/catalogPriceHandler.d.ts.map +1 -0
  47. package/lib/mcp/tools/product/catalogPriceHandler.js +57 -0
  48. package/lib/mcp/tools/product/catalogPriceHandler.js.map +1 -0
  49. package/lib/mcp/tools/product/index.d.ts.map +1 -1
  50. package/lib/mcp/tools/product/index.js +27 -3
  51. package/lib/mcp/tools/product/index.js.map +1 -1
  52. package/lib/mcp/tools/product/productAssignmentsHandler.d.ts +24 -0
  53. package/lib/mcp/tools/product/productAssignmentsHandler.d.ts.map +1 -0
  54. package/lib/mcp/tools/product/productAssignmentsHandler.js +79 -0
  55. package/lib/mcp/tools/product/productAssignmentsHandler.js.map +1 -0
  56. package/lib/mcp/tools/product/productBundleItemsHandler.d.ts +20 -0
  57. package/lib/mcp/tools/product/productBundleItemsHandler.d.ts.map +1 -0
  58. package/lib/mcp/tools/product/productBundleItemsHandler.js +45 -0
  59. package/lib/mcp/tools/product/productBundleItemsHandler.js.map +1 -0
  60. package/lib/mcp/tools/product/productMediaHandler.d.ts +32 -0
  61. package/lib/mcp/tools/product/productMediaHandler.d.ts.map +1 -0
  62. package/lib/mcp/tools/product/productMediaHandler.js +46 -0
  63. package/lib/mcp/tools/product/productMediaHandler.js.map +1 -0
  64. package/lib/mcp/tools/product/productSiblingsHandler.d.ts +36 -0
  65. package/lib/mcp/tools/product/productSiblingsHandler.d.ts.map +1 -0
  66. package/lib/mcp/tools/product/productSiblingsHandler.js +66 -0
  67. package/lib/mcp/tools/product/productSiblingsHandler.js.map +1 -0
  68. package/lib/mcp/tools/product/productVariationsHandler.d.ts +20 -0
  69. package/lib/mcp/tools/product/productVariationsHandler.d.ts.map +1 -0
  70. package/lib/mcp/tools/product/productVariationsHandler.js +47 -0
  71. package/lib/mcp/tools/product/productVariationsHandler.js.map +1 -0
  72. package/lib/mcp/tools/product/removeProductBundleItemHandler.d.ts +24 -0
  73. package/lib/mcp/tools/product/removeProductBundleItemHandler.d.ts.map +1 -0
  74. package/lib/mcp/tools/product/removeProductBundleItemHandler.js +46 -0
  75. package/lib/mcp/tools/product/removeProductBundleItemHandler.js.map +1 -0
  76. package/lib/mcp/tools/product/removeProductMediaHandler.js +1 -1
  77. package/lib/mcp/tools/product/removeProductMediaHandler.js.map +1 -1
  78. package/lib/mcp/tools/product/reorderProductMediaHandler.js +2 -2
  79. package/lib/mcp/tools/product/reorderProductMediaHandler.js.map +1 -1
  80. package/lib/mcp/tools/product/simulatedPriceHandler.d.ts +70 -0
  81. package/lib/mcp/tools/product/simulatedPriceHandler.d.ts.map +1 -0
  82. package/lib/mcp/tools/product/simulatedPriceHandler.js +74 -0
  83. package/lib/mcp/tools/product/simulatedPriceHandler.js.map +1 -0
  84. package/lib/mcp/tools/product/simulatedPriceRangeHandler.d.ts +74 -0
  85. package/lib/mcp/tools/product/simulatedPriceRangeHandler.d.ts.map +1 -0
  86. package/lib/mcp/tools/product/simulatedPriceRangeHandler.js +103 -0
  87. package/lib/mcp/tools/product/simulatedPriceRangeHandler.js.map +1 -0
  88. package/lib/mcp/tools/product/translatedProductMediaTextsHandler.d.ts.map +1 -1
  89. package/lib/mcp/tools/product/translatedProductMediaTextsHandler.js +7 -1
  90. package/lib/mcp/tools/product/translatedProductMediaTextsHandler.js.map +1 -1
  91. package/lib/mcp/tools/product/translatedProductVariationTextsHandler.d.ts.map +1 -1
  92. package/lib/mcp/tools/product/translatedProductVariationTextsHandler.js +7 -1
  93. package/lib/mcp/tools/product/translatedProductVariationTextsHandler.js.map +1 -1
  94. package/lib/mcp/tools/product/updateProductMediaTextsHandler.js +1 -1
  95. package/lib/mcp/tools/product/updateProductMediaTextsHandler.js.map +1 -1
  96. package/lib/mcp/tools/product/updateProductPlanHandler.d.ts +47 -26
  97. package/lib/mcp/tools/product/updateProductPlanHandler.d.ts.map +1 -1
  98. package/lib/mcp/tools/product/updateProductPlanHandler.js +23 -7
  99. package/lib/mcp/tools/product/updateProductPlanHandler.js.map +1 -1
  100. package/lib/mcp/tools/product/updateProductVariationTextsHandler.d.ts +73 -0
  101. package/lib/mcp/tools/product/updateProductVariationTextsHandler.d.ts.map +1 -0
  102. package/lib/mcp/tools/product/updateProductVariationTextsHandler.js +52 -0
  103. package/lib/mcp/tools/product/updateProductVariationTextsHandler.js.map +1 -0
  104. package/lib/mcp/tools/product/variationProductsHandler.d.ts +52 -0
  105. package/lib/mcp/tools/product/variationProductsHandler.d.ts.map +1 -0
  106. package/lib/mcp/tools/product/variationProductsHandler.js +54 -0
  107. package/lib/mcp/tools/product/variationProductsHandler.js.map +1 -0
  108. package/lib/mcp/tools/shopInfoHandler.d.ts.map +1 -1
  109. package/lib/mcp/tools/shopInfoHandler.js +4 -2
  110. package/lib/mcp/tools/shopInfoHandler.js.map +1 -1
  111. package/lib/mcp/utils/getNormalizedProductDetails.d.ts +1 -1
  112. package/lib/mcp/utils/getNormalizedProductDetails.d.ts.map +1 -1
  113. package/lib/mcp/utils/getNormalizedProductDetails.js +25 -0
  114. package/lib/mcp/utils/getNormalizedProductDetails.js.map +1 -1
  115. package/lib/resolvers/mutations/filters/createFilter.d.ts.map +1 -1
  116. package/lib/resolvers/mutations/filters/createFilter.js +14 -5
  117. package/lib/resolvers/mutations/filters/createFilter.js.map +1 -1
  118. package/lib/resolvers/mutations/products/createProductBundleItem.d.ts.map +1 -1
  119. package/lib/resolvers/mutations/products/createProductBundleItem.js +7 -2
  120. package/lib/resolvers/mutations/products/createProductBundleItem.js.map +1 -1
  121. package/lib/resolvers/type/index.d.ts +9 -0
  122. package/lib/resolvers/type/index.d.ts.map +1 -1
  123. package/lib/resolvers/type/index.js +2 -0
  124. package/lib/resolvers/type/index.js.map +1 -1
  125. package/lib/resolvers/type/product/product-bundle-types.d.ts +1 -0
  126. package/lib/resolvers/type/product/product-bundle-types.d.ts.map +1 -1
  127. package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.d.ts +5 -0
  128. package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.d.ts.map +1 -0
  129. package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.js +9 -0
  130. package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.js.map +1 -0
  131. package/lib/resolvers/type/product/product-configurable-types.d.ts +1 -0
  132. package/lib/resolvers/type/product/product-configurable-types.d.ts.map +1 -1
  133. package/lib/resolvers/type/product/product-plan-types.d.ts +1 -0
  134. package/lib/resolvers/type/product/product-plan-types.d.ts.map +1 -1
  135. package/lib/resolvers/type/product/product-simple-types.d.ts +1 -0
  136. package/lib/resolvers/type/product/product-simple-types.d.ts.map +1 -1
  137. package/lib/resolvers/type/product/product-tokenized-types.d.ts +1 -0
  138. package/lib/resolvers/type/product/product-tokenized-types.d.ts.map +1 -1
  139. package/lib/resolvers/type/product/product-types.d.ts +1 -0
  140. package/lib/resolvers/type/product/product-types.d.ts.map +1 -1
  141. package/lib/resolvers/type/product/product-types.js +3 -0
  142. package/lib/resolvers/type/product/product-types.js.map +1 -1
  143. package/lib/schema/types/product/bundle-product.d.ts.map +1 -1
  144. package/lib/schema/types/product/bundle-product.js +2 -1
  145. package/lib/schema/types/product/bundle-product.js.map +1 -1
  146. package/lib/schema/types/product/configurable-product.d.ts.map +1 -1
  147. package/lib/schema/types/product/configurable-product.js +1 -0
  148. package/lib/schema/types/product/configurable-product.js.map +1 -1
  149. package/lib/schema/types/product/plan-product.d.ts.map +1 -1
  150. package/lib/schema/types/product/plan-product.js +1 -0
  151. package/lib/schema/types/product/plan-product.js.map +1 -1
  152. package/lib/schema/types/product/product.d.ts.map +1 -1
  153. package/lib/schema/types/product/product.js +3 -0
  154. package/lib/schema/types/product/product.js.map +1 -1
  155. package/lib/schema/types/product/simple-product.d.ts.map +1 -1
  156. package/lib/schema/types/product/simple-product.js +1 -0
  157. package/lib/schema/types/product/simple-product.js.map +1 -1
  158. package/lib/schema/types/product/tokenized-product.d.ts.map +1 -1
  159. package/lib/schema/types/product/tokenized-product.js +1 -0
  160. package/lib/schema/types/product/tokenized-product.js.map +1 -1
  161. package/package.json +6 -6
  162. package/src/errors.ts +10 -0
  163. package/src/loaders/index.ts +2 -0
  164. package/src/loaders/productProxiesLoader.ts +37 -0
  165. package/src/mcp/tools/assortment/addAssortmentMediaUploadHandler.ts +2 -1
  166. package/src/mcp/tools/assortment/assortmentMediaHandler.ts +57 -0
  167. package/src/mcp/tools/assortment/index.ts +9 -1
  168. package/src/mcp/tools/assortment/removeAssortmentMediaHandler.ts +3 -1
  169. package/src/mcp/tools/assortment/reorderAssortmentMediaHandler.ts +2 -2
  170. package/src/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.ts +8 -1
  171. package/src/mcp/tools/assortment/updateAssortmentMediaTextsHandler.ts +4 -1
  172. package/src/mcp/tools/product/addProductAssignmentHandler.ts +3 -1
  173. package/src/mcp/tools/product/addProductBundleItemHandler.ts +64 -0
  174. package/src/mcp/tools/product/addProductMediaUploadHandler.ts +3 -2
  175. package/src/mcp/tools/product/catalogPriceHandler.ts +61 -0
  176. package/src/mcp/tools/product/index.ts +104 -3
  177. package/src/mcp/tools/product/productAssignmentsHandler.ts +86 -0
  178. package/src/mcp/tools/product/productBundleItemsHandler.ts +50 -0
  179. package/src/mcp/tools/product/productMediaHandler.ts +53 -0
  180. package/src/mcp/tools/product/productSiblingsHandler.ts +82 -0
  181. package/src/mcp/tools/product/productVariationsHandler.ts +53 -0
  182. package/src/mcp/tools/product/removeProductBundleItemHandler.ts +55 -0
  183. package/src/mcp/tools/product/removeProductMediaHandler.ts +1 -1
  184. package/src/mcp/tools/product/reorderProductMediaHandler.ts +2 -2
  185. package/src/mcp/tools/product/simulatedPriceHandler.ts +82 -0
  186. package/src/mcp/tools/product/simulatedPriceRangeHandler.ts +126 -0
  187. package/src/mcp/tools/product/translatedProductMediaTextsHandler.ts +6 -2
  188. package/src/mcp/tools/product/translatedProductVariationTextsHandler.ts +6 -1
  189. package/src/mcp/tools/product/updateProductMediaTextsHandler.ts +1 -1
  190. package/src/mcp/tools/product/updateProductPlanHandler.ts +37 -7
  191. package/src/mcp/tools/product/updateProductVariationTextsHandler.ts +65 -0
  192. package/src/mcp/tools/product/variationProductsHandler.ts +61 -0
  193. package/src/mcp/tools/shopInfoHandler.ts +4 -2
  194. package/src/mcp/utils/getNormalizedProductDetails.ts +28 -0
  195. package/src/resolvers/mutations/filters/createFilter.ts +11 -6
  196. package/src/resolvers/mutations/products/createProductBundleItem.ts +13 -3
  197. package/src/resolvers/type/index.ts +2 -0
  198. package/src/resolvers/type/product/product-configurable-or-bundle-product-types.ts +10 -0
  199. package/src/resolvers/type/product/product-types.ts +4 -0
  200. package/src/schema/types/product/bundle-product.ts +2 -1
  201. package/src/schema/types/product/configurable-product.ts +1 -0
  202. package/src/schema/types/product/plan-product.ts +1 -0
  203. package/src/schema/types/product/product.ts +3 -0
  204. package/src/schema/types/product/simple-product.ts +1 -0
  205. package/src/schema/types/product/tokenized-product.ts +1 -0
@@ -0,0 +1,50 @@
1
+ import { Context } from '../../../context.js';
2
+ import { ProductNotFoundError, ProductWrongTypeError } from '../../../errors.js';
3
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
4
+ import { ProductTypes } from '@unchainedshop/core-products';
5
+ import { log } from '@unchainedshop/logger';
6
+ import { z } from 'zod';
7
+
8
+ export const ProductBundleItemsSchema = {
9
+ productId: z.string().min(1).describe('ID of the product of type BUNDLE to retrieve its bundle items'),
10
+ };
11
+
12
+ export const ProductBundleItemsZodSchema = z.object(ProductBundleItemsSchema);
13
+ export type ProductBundleItemsParams = z.infer<typeof ProductBundleItemsZodSchema>;
14
+
15
+ export const productBundleItemsHandler = async (context: Context, params: ProductBundleItemsParams) => {
16
+ const { modules, userId } = context;
17
+ const { productId } = params;
18
+
19
+ log('handler productBundleItemsHandler', { userId, params });
20
+ try {
21
+ const product = await modules.products.findProduct({ productId });
22
+ if (!product) throw new ProductNotFoundError({ productId });
23
+
24
+ if (product.type !== ProductTypes.BundleProduct) {
25
+ throw new ProductWrongTypeError({
26
+ productId,
27
+ received: product.type,
28
+ required: ProductTypes.BundleProduct,
29
+ });
30
+ }
31
+
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text' as const,
36
+ text: JSON.stringify({ product: await getNormalizedProductDetails(productId, context) }),
37
+ },
38
+ ],
39
+ };
40
+ } catch (error) {
41
+ return {
42
+ content: [
43
+ {
44
+ type: 'text' as const,
45
+ text: `Error fetching bundle items: ${(error as Error).message}`,
46
+ },
47
+ ],
48
+ };
49
+ }
50
+ };
@@ -0,0 +1,53 @@
1
+ import { Context } from '../../../context.js';
2
+ import { log } from '@unchainedshop/logger';
3
+ import { z } from 'zod';
4
+ import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
5
+
6
+ export const ProductMediaSchema = {
7
+ productId: z.string().min(1).describe('ID of the product to retrieve media for'),
8
+ limit: z.number().int().min(1).max(100).default(10).describe('Number of media items to return'),
9
+ offset: z.number().int().min(0).default(0).describe('Pagination offset'),
10
+ tags: z.array(z.string().min(1)).optional().describe('Filter media by lowercase tags'),
11
+ };
12
+
13
+ export const ProductMediaZodSchema = z
14
+ .object(ProductMediaSchema)
15
+ .describe(
16
+ 'Retrieve media assets associated with a product, optionally filtered by tags and paginated.',
17
+ );
18
+
19
+ export type ProductMediaParams = z.infer<typeof ProductMediaZodSchema>;
20
+
21
+ export async function productMediaHandler(context: Context, params: ProductMediaParams) {
22
+ const { productId, limit, offset, tags } = params;
23
+ const { modules, loaders, userId } = context;
24
+
25
+ try {
26
+ log('handler productMediaHandler', { userId, params });
27
+ let media = [];
28
+ if (offset || tags) {
29
+ media = await modules.products.media.findProductMedias({
30
+ productId,
31
+ ...params,
32
+ });
33
+ }
34
+ media = (await loaders.productMediasLoader.load({ productId })).slice(offset, offset + limit);
35
+ return {
36
+ content: [
37
+ {
38
+ type: 'text' as const,
39
+ text: JSON.stringify({ media: await normalizeMediaUrl(media, context) }),
40
+ },
41
+ ],
42
+ };
43
+ } catch (error) {
44
+ return {
45
+ content: [
46
+ {
47
+ type: 'text' as const,
48
+ text: `Error retrieving product media: ${(error as Error).message}`,
49
+ },
50
+ ],
51
+ };
52
+ }
53
+ }
@@ -0,0 +1,82 @@
1
+ import { Context } from '../../../context.js';
2
+ import { log } from '@unchainedshop/logger';
3
+
4
+ import { z } from 'zod';
5
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
6
+
7
+ export const ProductSiblingsSchema = {
8
+ productId: z.string().describe('Product ID to get siblings of'),
9
+ assortmentId: z.string().optional().describe('Assortment ID to filter siblings (optional)'),
10
+ limit: z.number().int().min(1).max(100).default(10).describe('Number of siblings to return'),
11
+ offset: z.number().int().min(0).default(0).describe('Pagination offset'),
12
+ includeInactive: z
13
+ .boolean()
14
+ .optional()
15
+ .default(false)
16
+ .describe('Whether to include inactive sibling products'),
17
+ };
18
+
19
+ export const ProductSiblingsZodSchema = z
20
+ .object(ProductSiblingsSchema)
21
+ .describe(
22
+ 'Retrieve sibling products of a given product, optionally filtered by assortment ID, with support for pagination and inclusion of inactive products.',
23
+ );
24
+
25
+ export type ProductSiblingsParams = z.infer<typeof ProductSiblingsZodSchema>;
26
+
27
+ export async function productSiblingsHandler(context: Context, params: ProductSiblingsParams) {
28
+ const { modules, userId } = context;
29
+ const { productId, assortmentId, limit, offset, includeInactive } = params;
30
+
31
+ try {
32
+ log('handler productSiblingsHandler', { userId, params });
33
+
34
+ const assortmentIds = assortmentId
35
+ ? [assortmentId]
36
+ : await modules.assortments.products.findAssortmentIds({ productId });
37
+
38
+ if (!assortmentIds.length)
39
+ return {
40
+ content: [
41
+ {
42
+ type: 'text' as const,
43
+ text: JSON.stringify({ products: [] }),
44
+ },
45
+ ],
46
+ };
47
+
48
+ const productIds = await modules.assortments.products.findSiblings({
49
+ productId,
50
+ assortmentIds,
51
+ });
52
+
53
+ const products = await modules.products.findProducts({
54
+ productIds,
55
+ includeDrafts: includeInactive,
56
+ limit,
57
+ offset,
58
+ });
59
+
60
+ const normalizedProducts = (
61
+ await Promise.allSettled(products.map(({ _id }) => getNormalizedProductDetails(_id, context)))
62
+ ).flatMap((r) => (r.status === 'fulfilled' ? [r.value] : []));
63
+
64
+ return {
65
+ content: [
66
+ {
67
+ type: 'text' as const,
68
+ text: JSON.stringify({ products: normalizedProducts }),
69
+ },
70
+ ],
71
+ };
72
+ } catch (error) {
73
+ return {
74
+ content: [
75
+ {
76
+ type: 'text' as const,
77
+ text: `Error retrieving product siblings: ${(error as Error).message}`,
78
+ },
79
+ ],
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ import { Context } from '../../../context.js';
3
+ import { ProductNotFoundError, ProductWrongTypeError } from '../../../errors.js';
4
+ import { log } from '@unchainedshop/logger';
5
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
6
+
7
+ export const ProductVariationsSchema = {
8
+ productId: z.string().min(1).describe('ID of the CONFIGURABLE_PRODUCT to retrieve variations from'),
9
+ };
10
+
11
+ export const ProductVariationsZodSchema = z.object(ProductVariationsSchema);
12
+ export type ProductVariationsParams = z.infer<typeof ProductVariationsZodSchema>;
13
+
14
+ export const productVariationsHandler = async (context: Context, params: ProductVariationsParams) => {
15
+ const { productId } = params;
16
+ const { modules, userId } = context;
17
+
18
+ try {
19
+ log('handler productVariationsHandler', { userId, productId });
20
+
21
+ const product = await getNormalizedProductDetails(productId, context);
22
+ if (!product) throw new ProductNotFoundError({ productId });
23
+
24
+ if (product.type !== 'CONFIGURABLE_PRODUCT') {
25
+ throw new ProductWrongTypeError({
26
+ productId,
27
+ received: product.type,
28
+ required: 'CONFIGURABLE_PRODUCT',
29
+ });
30
+ }
31
+ const variations = await modules.products.variations.findProductVariations({
32
+ productId,
33
+ });
34
+
35
+ return {
36
+ content: [
37
+ {
38
+ type: 'text' as const,
39
+ text: JSON.stringify({ product, variations }),
40
+ },
41
+ ],
42
+ };
43
+ } catch (error) {
44
+ return {
45
+ content: [
46
+ {
47
+ type: 'text' as const,
48
+ text: `Error fetching variations: ${(error as Error).message}`,
49
+ },
50
+ ],
51
+ };
52
+ }
53
+ };
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ import { Context } from '../../../context.js';
3
+ import { ProductNotFoundError, ProductWrongTypeError } from '../../../errors.js';
4
+ import { log } from '@unchainedshop/logger';
5
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
6
+ import { ProductTypes } from '@unchainedshop/core-products';
7
+
8
+ export const RemoveProductBundleItemSchema = {
9
+ productId: z.string().min(1).describe('ID of the BUNDLE_PRODUCT from which the item will be removed'),
10
+ index: z.number().int().min(0).describe('0-based index of the bundle item to remove'),
11
+ };
12
+
13
+ export const RemoveProductBundleItemZodSchema = z.object(RemoveProductBundleItemSchema);
14
+
15
+ export type RemoveProductBundleItemParams = z.infer<typeof RemoveProductBundleItemZodSchema>;
16
+
17
+ export const removeProductBundleItemHandler = async (
18
+ context: Context,
19
+ params: RemoveProductBundleItemParams,
20
+ ) => {
21
+ const { modules, userId } = context;
22
+ const { productId, index } = params;
23
+
24
+ log('handler removeProductBundleItemHandler', { userId, params });
25
+ try {
26
+ const product = await modules.products.findProduct({ productId });
27
+ if (!product) throw new ProductNotFoundError({ productId });
28
+
29
+ if (product.type !== ProductTypes.BundleProduct)
30
+ throw new ProductWrongTypeError({
31
+ productId,
32
+ received: product.type,
33
+ required: ProductTypes.BundleProduct,
34
+ });
35
+
36
+ await modules.products.bundleItems.removeBundleItem(productId, index);
37
+ return {
38
+ content: [
39
+ {
40
+ type: 'text' as const,
41
+ text: JSON.stringify({ product: await getNormalizedProductDetails(productId, context) }),
42
+ },
43
+ ],
44
+ };
45
+ } catch (error) {
46
+ return {
47
+ content: [
48
+ {
49
+ type: 'text' as const,
50
+ text: `Error removing bundle item: ${(error as Error).message}`,
51
+ },
52
+ ],
53
+ };
54
+ }
55
+ };
@@ -31,7 +31,7 @@ export async function removeProductMediaHandler(context: Context, params: Remove
31
31
  {
32
32
  type: 'text' as const,
33
33
  text: JSON.stringify({
34
- media: productMedia,
34
+ success: true,
35
35
  }),
36
36
  },
37
37
  ],
@@ -25,13 +25,13 @@ export async function reorderProductMediaHandler(context: Context, params: Reord
25
25
  try {
26
26
  log('handler reorderProductMediaHandler', { userId, params });
27
27
 
28
- const reorderedMedia = await modules.products.media.updateManualOrder({ sortKeys: sortKeys as any });
28
+ const media = await modules.products.media.updateManualOrder({ sortKeys: sortKeys as any });
29
29
 
30
30
  return {
31
31
  content: [
32
32
  {
33
33
  type: 'text' as const,
34
- text: JSON.stringify({ reorderedMedia }, null, 2),
34
+ text: JSON.stringify({ media }, null, 2),
35
35
  },
36
36
  ],
37
37
  };
@@ -0,0 +1,82 @@
1
+ import { Context } from '../../../context.js';
2
+ import { ProductNotFoundError } from '../../../errors.js';
3
+ import { log } from '@unchainedshop/logger';
4
+ import { z } from 'zod';
5
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
6
+
7
+ export const ProductConfigurationParameterInputSchema = z.object({
8
+ key: z.string().min(1).describe('Configuration attribute key'),
9
+ value: z.string().min(1).describe('Configuration attribute value'),
10
+ });
11
+
12
+ export const SimulatedPriceSchema = {
13
+ productId: z.string().min(1).describe('ID of the product for which to simulate pricing'),
14
+ quantity: z
15
+ .number()
16
+ .int()
17
+ .positive()
18
+ .default(1)
19
+ .describe('Quantity to simulate price for (default is 1)'),
20
+ currencyCode: z.string().optional().describe('Currency code for price simulation (e.g., "USD")'),
21
+ useNetPrice: z
22
+ .boolean()
23
+ .optional()
24
+ .default(false)
25
+ .describe('If true, returns net price; otherwise, returns gross price'),
26
+ configuration: z
27
+ .array(ProductConfigurationParameterInputSchema)
28
+ .optional()
29
+ .describe('Complete configuration vector (e.g., Color: Red, Size: M)'),
30
+ };
31
+
32
+ export const SimulatedPriceZodSchema = z.object(SimulatedPriceSchema);
33
+
34
+ export type SimulatedPriceParams = z.infer<typeof SimulatedPriceZodSchema>;
35
+
36
+ export async function simulatedPriceHandler(context: Context, params: SimulatedPriceParams) {
37
+ const { userId, countryCode, user, services } = context;
38
+ const { productId, quantity, configuration, currencyCode: forcedCurrencyCode, useNetPrice } = params;
39
+
40
+ try {
41
+ log('handler simulatedPriceHandler', { userId, params });
42
+
43
+ const product = await getNormalizedProductDetails(productId, context);
44
+ if (!product) throw new ProductNotFoundError({ productId });
45
+ const currencyCode = forcedCurrencyCode || context.currencyCode;
46
+ const pricingContext = {
47
+ product,
48
+ user,
49
+ countryCode,
50
+ currencyCode,
51
+ quantity,
52
+ configuration,
53
+ };
54
+ const pricing = await services.products.simulateProductPricing(pricingContext as any);
55
+ const unitPrice = pricing.unitPrice({ useNetPrice });
56
+
57
+ const price = {
58
+ ...unitPrice,
59
+ isNetPrice: useNetPrice,
60
+ isTaxable: pricing.taxSum() > 0,
61
+ currencyCode: pricing.currencyCode,
62
+ };
63
+
64
+ return {
65
+ content: [
66
+ {
67
+ type: 'text' as const,
68
+ text: JSON.stringify({ product, price }),
69
+ },
70
+ ],
71
+ };
72
+ } catch (error) {
73
+ return {
74
+ content: [
75
+ {
76
+ type: 'text' as const,
77
+ text: `Error simulating price: ${(error as Error).message}`,
78
+ },
79
+ ],
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,126 @@
1
+ import { Context } from '../../../context.js';
2
+ import { log } from '@unchainedshop/logger';
3
+ import { z } from 'zod';
4
+ import { ProductWrongTypeError } from '../../../errors.js';
5
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
6
+
7
+ export const ProductAssignmentVectorInputSchema = z.object({
8
+ key: z.string().min(1).describe('Variation attribute key (e.g., "Color")'),
9
+ value: z.string().min(1).describe('Variation attribute value (e.g., "Red")'),
10
+ });
11
+
12
+ export const SimulatedPriceRangeSchema = {
13
+ productId: z.string().min(1).describe('ID of the CONFIGURABLE_PRODUCT to simulate price range for'),
14
+ quantity: z
15
+ .number()
16
+ .int()
17
+ .positive()
18
+ .optional()
19
+ .describe('Desired quantity for price simulation (default is 1)'),
20
+ vectors: z
21
+ .array(ProductAssignmentVectorInputSchema)
22
+ .optional()
23
+ .describe('Optional variation key-value pairs (e.g., Color: Red) to simulate specific variant'),
24
+ includeInactive: z
25
+ .boolean()
26
+ .optional()
27
+ .default(false)
28
+ .describe('Whether to include inactive variants'),
29
+ currencyCode: z
30
+ .string()
31
+ .optional()
32
+ .describe('Currency code to simulate pricing with (e.g., "USD", "EUR")'),
33
+ useNetPrice: z
34
+ .boolean()
35
+ .optional()
36
+ .default(false)
37
+ .describe('Return net price if true, otherwise gross price'),
38
+ };
39
+
40
+ export const SimulatedPriceRangeZodSchema = z.object(SimulatedPriceRangeSchema);
41
+
42
+ export type SimulatedPriceRangeParams = z.infer<typeof SimulatedPriceRangeZodSchema>;
43
+
44
+ export async function simulatedPriceRangeHandler(context: Context, params: SimulatedPriceRangeParams) {
45
+ const {
46
+ productId,
47
+ quantity,
48
+ vectors,
49
+ includeInactive,
50
+ currencyCode: forcedCurrencyCode,
51
+ useNetPrice,
52
+ } = params;
53
+ const { modules, userId, countryCode, services } = context;
54
+
55
+ try {
56
+ log('handler simulatedPriceRangeHandler', { userId, params });
57
+
58
+ const currencyCode = forcedCurrencyCode || context.currencyCode;
59
+ const product = await getNormalizedProductDetails(productId, context);
60
+
61
+ if (product.type !== 'CONFIGURABLE_PRODUCT') {
62
+ throw new ProductWrongTypeError({
63
+ productId,
64
+ received: product.type,
65
+ required: 'CONFIGURABLE_PRODUCT',
66
+ });
67
+ }
68
+ const products = await modules.products.proxyProducts(product, vectors as any, {
69
+ includeInactive,
70
+ });
71
+
72
+ const filteredPrices = (
73
+ await Promise.all(
74
+ products.map(async (proxyProduct) => {
75
+ const pricingContext = {
76
+ product: proxyProduct,
77
+ user: context.user,
78
+ countryCode,
79
+ currencyCode,
80
+ quantity,
81
+ };
82
+
83
+ const pricing = await services.products.simulateProductPricing(pricingContext);
84
+ if (!pricing) return null;
85
+ const unitPrice = pricing.unitPrice({ useNetPrice });
86
+
87
+ return {
88
+ ...unitPrice,
89
+ isNetPrice: useNetPrice,
90
+ isTaxable: pricing.taxSum() > 0,
91
+ currencyCode: pricing.currencyCode,
92
+ };
93
+ }),
94
+ )
95
+ ).filter(Boolean);
96
+
97
+ if (!filteredPrices.length) return null;
98
+
99
+ const { minPrice, maxPrice } = modules.products.prices.priceRange({
100
+ productId: product._id as string,
101
+ prices: filteredPrices,
102
+ });
103
+
104
+ const priceRange = {
105
+ minPrice,
106
+ maxPrice,
107
+ };
108
+ return {
109
+ content: [
110
+ {
111
+ type: 'text' as const,
112
+ text: JSON.stringify({ product, priceRange }),
113
+ },
114
+ ],
115
+ };
116
+ } catch (error) {
117
+ return {
118
+ content: [
119
+ {
120
+ type: 'text' as const,
121
+ text: `Error simulating price range: ${(error as Error).message}`,
122
+ },
123
+ ],
124
+ };
125
+ }
126
+ }
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { Context } from '../../../context.js';
3
3
  import { log } from '@unchainedshop/logger';
4
+ import { ProductMediaNotFoundError } from '../../../errors.js';
4
5
 
5
6
  export const TranslatedProductMediaTextsSchema = {
6
7
  productMediaId: z.string().min(1).describe('ID of the product media to fetch translated texts for'),
@@ -19,14 +20,17 @@ export async function translatedProductMediaTextsHandler(
19
20
 
20
21
  try {
21
22
  log(`handler translatedProductMediaTexts`, { userId, params });
22
-
23
+ const productMedia = await modules.products.media.findProductMedia({
24
+ productMediaId,
25
+ });
26
+ if (!productMedia) throw new ProductMediaNotFoundError({ productMediaId });
23
27
  const texts = await modules.products.media.texts.findMediaTexts({ productMediaId });
24
28
 
25
29
  return {
26
30
  content: [
27
31
  {
28
32
  type: 'text' as const,
29
- text: JSON.stringify({ texts }),
33
+ text: JSON.stringify({ texts, productId: productMedia.productId }),
30
34
  },
31
35
  ],
32
36
  };
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { Context } from '../../../context.js';
3
3
  import { log } from '@unchainedshop/logger';
4
+ import { ProductVariationNotFoundError } from '../../../errors.js';
4
5
 
5
6
  export const TranslatedProductVariationTextsSchema = {
6
7
  productVariationId: z.string().min(1).describe('ID of the product variation'),
@@ -28,6 +29,10 @@ export async function translatedProductVariationTextsHandler(
28
29
  userId,
29
30
  params,
30
31
  });
32
+ const productVariation = await modules.products.variations.findProductVariation({
33
+ productVariationId,
34
+ });
35
+ if (!productVariation) throw new ProductVariationNotFoundError({ productVariationId });
31
36
 
32
37
  const texts = await modules.products.variations.texts.findVariationTexts({
33
38
  productVariationId,
@@ -38,7 +43,7 @@ export async function translatedProductVariationTextsHandler(
38
43
  content: [
39
44
  {
40
45
  type: 'text' as const,
41
- text: JSON.stringify({ texts }),
46
+ text: JSON.stringify({ texts, productId: productVariation.productId }),
42
47
  },
43
48
  ],
44
49
  };
@@ -46,7 +46,7 @@ export async function updateProductMediaTextsHandler(
46
46
  content: [
47
47
  {
48
48
  type: 'text' as const,
49
- text: JSON.stringify({ texts: updatedTexts }),
49
+ text: JSON.stringify({ texts: updatedTexts, productId: productMedia.productId }),
50
50
  },
51
51
  ],
52
52
  };
@@ -5,15 +5,45 @@ import { ProductTypes } from '@unchainedshop/core-products';
5
5
  import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
6
6
  import { log } from '@unchainedshop/logger';
7
7
 
8
+ export const ProductPlanUsageCalculationTypeEnum = z.enum(['LICENSED', 'METERED'], {
9
+ description:
10
+ 'Determines how usage is calculated for the product plan. LICENSED means a fixed license count, METERED means usage is tracked dynamically.',
11
+ });
12
+
13
+ export const ProductPlanConfigurationIntervalEnum = z.enum(
14
+ ['HOURS', 'DAYS', 'WEEKS', 'MONTHS', 'YEARS'],
15
+ {
16
+ description:
17
+ 'Defines the time unit for billing intervals and trial periods (e.g., MONTHS means billing every month).',
18
+ },
19
+ );
20
+
21
+ export const UpdateProductPlanInputSchema = z.object({
22
+ usageCalculationType: ProductPlanUsageCalculationTypeEnum.describe(
23
+ 'The billing usage calculation method for the product plan.',
24
+ ),
25
+ billingInterval: ProductPlanConfigurationIntervalEnum.describe(
26
+ 'The interval unit used for billing cycles.',
27
+ ),
28
+ billingIntervalCount: z
29
+ .number()
30
+ .int()
31
+ .positive()
32
+ .describe('The number of billing intervals per billing cycle (e.g., 3 for every 3 months).'),
33
+ trialInterval: ProductPlanConfigurationIntervalEnum.optional().describe(
34
+ 'Optional trial period interval unit before billing starts.',
35
+ ),
36
+ trialIntervalCount: z
37
+ .number()
38
+ .int()
39
+ .positive()
40
+ .optional()
41
+ .describe('Number of trial intervals before the billing cycle starts.'),
42
+ });
43
+
8
44
  export const UpdateProductPlanSchema = {
9
45
  productId: z.string().min(1).describe('ID of the product of type PLAN_PRODUCT to update only'),
10
- plan: z.object({
11
- usageCalculationType: z.string().describe('Type of usage calculation'),
12
- billingInterval: z.string().describe('Billing interval, e.g., "month", "year"'),
13
- billingIntervalCount: z.number().int().positive().optional().describe('Count of billing intervals'),
14
- trialInterval: z.string().optional().describe('Trial period interval'),
15
- trialIntervalCount: z.number().int().positive().optional().describe('Count of trial intervals'),
16
- }),
46
+ plan: UpdateProductPlanInputSchema.describe('Configuration settings for the product plan.'),
17
47
  };
18
48
 
19
49
  export const UpdateProductPlanZodSchema = z.object(UpdateProductPlanSchema);