@endora-commerce/contracts 0.100.0

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 (327) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -0
  3. package/dist/actor.d.ts +79 -0
  4. package/dist/actor.d.ts.map +1 -0
  5. package/dist/actor.js +41 -0
  6. package/dist/actor.js.map +1 -0
  7. package/dist/addresses.d.ts +134 -0
  8. package/dist/addresses.d.ts.map +1 -0
  9. package/dist/addresses.js +16 -0
  10. package/dist/addresses.js.map +1 -0
  11. package/dist/admin-actions.d.ts +367 -0
  12. package/dist/admin-actions.d.ts.map +1 -0
  13. package/dist/admin-actions.js +287 -0
  14. package/dist/admin-actions.js.map +1 -0
  15. package/dist/admin-contributions.d.ts +518 -0
  16. package/dist/admin-contributions.d.ts.map +1 -0
  17. package/dist/admin-contributions.js +495 -0
  18. package/dist/admin-contributions.js.map +1 -0
  19. package/dist/admin-i18n.d.ts +135 -0
  20. package/dist/admin-i18n.d.ts.map +1 -0
  21. package/dist/admin-i18n.js +72 -0
  22. package/dist/admin-i18n.js.map +1 -0
  23. package/dist/admin-notifications.d.ts +55 -0
  24. package/dist/admin-notifications.d.ts.map +1 -0
  25. package/dist/admin-notifications.js +16 -0
  26. package/dist/admin-notifications.js.map +1 -0
  27. package/dist/admin-roles.d.ts +125 -0
  28. package/dist/admin-roles.d.ts.map +1 -0
  29. package/dist/admin-roles.js +2 -0
  30. package/dist/admin-roles.js.map +1 -0
  31. package/dist/admin-users.d.ts +178 -0
  32. package/dist/admin-users.d.ts.map +1 -0
  33. package/dist/admin-users.js +14 -0
  34. package/dist/admin-users.js.map +1 -0
  35. package/dist/admin.d.ts +243 -0
  36. package/dist/admin.d.ts.map +1 -0
  37. package/dist/admin.js +246 -0
  38. package/dist/admin.js.map +1 -0
  39. package/dist/analytics.d.ts +123 -0
  40. package/dist/analytics.d.ts.map +1 -0
  41. package/dist/analytics.js +68 -0
  42. package/dist/analytics.js.map +1 -0
  43. package/dist/api-keys.d.ts +97 -0
  44. package/dist/api-keys.d.ts.map +1 -0
  45. package/dist/api-keys.js +64 -0
  46. package/dist/api-keys.js.map +1 -0
  47. package/dist/assets-library.d.ts +684 -0
  48. package/dist/assets-library.d.ts.map +1 -0
  49. package/dist/assets-library.js +181 -0
  50. package/dist/assets-library.js.map +1 -0
  51. package/dist/audit-logs.d.ts +141 -0
  52. package/dist/audit-logs.d.ts.map +1 -0
  53. package/dist/audit-logs.js +31 -0
  54. package/dist/audit-logs.js.map +1 -0
  55. package/dist/auth.d.ts +174 -0
  56. package/dist/auth.d.ts.map +1 -0
  57. package/dist/auth.js +27 -0
  58. package/dist/auth.js.map +1 -0
  59. package/dist/blog.d.ts +669 -0
  60. package/dist/blog.d.ts.map +1 -0
  61. package/dist/blog.js +360 -0
  62. package/dist/blog.js.map +1 -0
  63. package/dist/capabilities.d.ts +40 -0
  64. package/dist/capabilities.d.ts.map +1 -0
  65. package/dist/capabilities.js +38 -0
  66. package/dist/capabilities.js.map +1 -0
  67. package/dist/carts.d.ts +1367 -0
  68. package/dist/carts.d.ts.map +1 -0
  69. package/dist/carts.js +405 -0
  70. package/dist/carts.js.map +1 -0
  71. package/dist/catalog.d.ts +2855 -0
  72. package/dist/catalog.d.ts.map +1 -0
  73. package/dist/catalog.js +1543 -0
  74. package/dist/catalog.js.map +1 -0
  75. package/dist/cms.d.ts +872 -0
  76. package/dist/cms.d.ts.map +1 -0
  77. package/dist/cms.js +468 -0
  78. package/dist/cms.js.map +1 -0
  79. package/dist/common.d.ts +82 -0
  80. package/dist/common.d.ts.map +1 -0
  81. package/dist/common.js +72 -0
  82. package/dist/common.js.map +1 -0
  83. package/dist/comparisons.d.ts +487 -0
  84. package/dist/comparisons.d.ts.map +1 -0
  85. package/dist/comparisons.js +221 -0
  86. package/dist/comparisons.js.map +1 -0
  87. package/dist/credentials.d.ts +292 -0
  88. package/dist/credentials.d.ts.map +1 -0
  89. package/dist/credentials.js +142 -0
  90. package/dist/credentials.js.map +1 -0
  91. package/dist/credit-limits.d.ts +111 -0
  92. package/dist/credit-limits.d.ts.map +1 -0
  93. package/dist/credit-limits.js +35 -0
  94. package/dist/credit-limits.js.map +1 -0
  95. package/dist/currencies.d.ts +127 -0
  96. package/dist/currencies.d.ts.map +1 -0
  97. package/dist/currencies.js +20 -0
  98. package/dist/currencies.js.map +1 -0
  99. package/dist/custom-fields.d.ts +345 -0
  100. package/dist/custom-fields.d.ts.map +1 -0
  101. package/dist/custom-fields.js +185 -0
  102. package/dist/custom-fields.js.map +1 -0
  103. package/dist/customer-accounts.d.ts +690 -0
  104. package/dist/customer-accounts.d.ts.map +1 -0
  105. package/dist/customer-accounts.js +41 -0
  106. package/dist/customer-accounts.js.map +1 -0
  107. package/dist/customers.d.ts +305 -0
  108. package/dist/customers.d.ts.map +1 -0
  109. package/dist/customers.js +158 -0
  110. package/dist/customers.js.map +1 -0
  111. package/dist/dictionary.d.ts +580 -0
  112. package/dist/dictionary.d.ts.map +1 -0
  113. package/dist/dictionary.js +297 -0
  114. package/dist/dictionary.js.map +1 -0
  115. package/dist/email-address.d.ts +62 -0
  116. package/dist/email-address.d.ts.map +1 -0
  117. package/dist/email-address.js +64 -0
  118. package/dist/email-address.js.map +1 -0
  119. package/dist/email.d.ts +175 -0
  120. package/dist/email.d.ts.map +1 -0
  121. package/dist/email.js +45 -0
  122. package/dist/email.js.map +1 -0
  123. package/dist/envelopes.d.ts +15 -0
  124. package/dist/envelopes.d.ts.map +1 -0
  125. package/dist/envelopes.js +16 -0
  126. package/dist/envelopes.js.map +1 -0
  127. package/dist/environment-inputs.d.ts +306 -0
  128. package/dist/environment-inputs.d.ts.map +1 -0
  129. package/dist/environment-inputs.js +277 -0
  130. package/dist/environment-inputs.js.map +1 -0
  131. package/dist/erp-connector.d.ts +52 -0
  132. package/dist/erp-connector.d.ts.map +1 -0
  133. package/dist/erp-connector.js +34 -0
  134. package/dist/erp-connector.js.map +1 -0
  135. package/dist/errors.d.ts +455 -0
  136. package/dist/errors.d.ts.map +1 -0
  137. package/dist/errors.js +532 -0
  138. package/dist/errors.js.map +1 -0
  139. package/dist/google-analytics.d.ts +181 -0
  140. package/dist/google-analytics.d.ts.map +1 -0
  141. package/dist/google-analytics.js +176 -0
  142. package/dist/google-analytics.js.map +1 -0
  143. package/dist/google-tag-manager.d.ts +111 -0
  144. package/dist/google-tag-manager.d.ts.map +1 -0
  145. package/dist/google-tag-manager.js +129 -0
  146. package/dist/google-tag-manager.js.map +1 -0
  147. package/dist/i18n.d.ts +69 -0
  148. package/dist/i18n.d.ts.map +1 -0
  149. package/dist/i18n.js +59 -0
  150. package/dist/i18n.js.map +1 -0
  151. package/dist/import-export.d.ts +63 -0
  152. package/dist/import-export.d.ts.map +1 -0
  153. package/dist/import-export.js +37 -0
  154. package/dist/import-export.js.map +1 -0
  155. package/dist/index.d.ts +82 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +126 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/inventory.d.ts +673 -0
  160. package/dist/inventory.d.ts.map +1 -0
  161. package/dist/inventory.js +412 -0
  162. package/dist/inventory.js.map +1 -0
  163. package/dist/invoice-ledger.d.ts +366 -0
  164. package/dist/invoice-ledger.d.ts.map +1 -0
  165. package/dist/invoice-ledger.js +114 -0
  166. package/dist/invoice-ledger.js.map +1 -0
  167. package/dist/invoices.d.ts +845 -0
  168. package/dist/invoices.d.ts.map +1 -0
  169. package/dist/invoices.js +314 -0
  170. package/dist/invoices.js.map +1 -0
  171. package/dist/kernel.d.ts +49 -0
  172. package/dist/kernel.d.ts.map +1 -0
  173. package/dist/kernel.js +19 -0
  174. package/dist/kernel.js.map +1 -0
  175. package/dist/languages.d.ts +122 -0
  176. package/dist/languages.d.ts.map +1 -0
  177. package/dist/languages.js +24 -0
  178. package/dist/languages.js.map +1 -0
  179. package/dist/linkedin-ads.d.ts +167 -0
  180. package/dist/linkedin-ads.d.ts.map +1 -0
  181. package/dist/linkedin-ads.js +156 -0
  182. package/dist/linkedin-ads.js.map +1 -0
  183. package/dist/megamenu.d.ts +556 -0
  184. package/dist/megamenu.d.ts.map +1 -0
  185. package/dist/megamenu.js +186 -0
  186. package/dist/megamenu.js.map +1 -0
  187. package/dist/meta-ads.d.ts +126 -0
  188. package/dist/meta-ads.d.ts.map +1 -0
  189. package/dist/meta-ads.js +112 -0
  190. package/dist/meta-ads.js.map +1 -0
  191. package/dist/mfa.d.ts +274 -0
  192. package/dist/mfa.d.ts.map +1 -0
  193. package/dist/mfa.js +187 -0
  194. package/dist/mfa.js.map +1 -0
  195. package/dist/modules.d.ts +1706 -0
  196. package/dist/modules.d.ts.map +1 -0
  197. package/dist/modules.js +1390 -0
  198. package/dist/modules.js.map +1 -0
  199. package/dist/newsletter.d.ts +611 -0
  200. package/dist/newsletter.d.ts.map +1 -0
  201. package/dist/newsletter.js +345 -0
  202. package/dist/newsletter.js.map +1 -0
  203. package/dist/orders.d.ts +1175 -0
  204. package/dist/orders.d.ts.map +1 -0
  205. package/dist/orders.js +630 -0
  206. package/dist/orders.js.map +1 -0
  207. package/dist/organizations.d.ts +938 -0
  208. package/dist/organizations.d.ts.map +1 -0
  209. package/dist/organizations.js +418 -0
  210. package/dist/organizations.js.map +1 -0
  211. package/dist/pagination.d.ts +21 -0
  212. package/dist/pagination.d.ts.map +1 -0
  213. package/dist/pagination.js +22 -0
  214. package/dist/pagination.js.map +1 -0
  215. package/dist/payment-methods.d.ts +472 -0
  216. package/dist/payment-methods.d.ts.map +1 -0
  217. package/dist/payment-methods.js +175 -0
  218. package/dist/payment-methods.js.map +1 -0
  219. package/dist/payment-return-url.d.ts +53 -0
  220. package/dist/payment-return-url.d.ts.map +1 -0
  221. package/dist/payment-return-url.js +35 -0
  222. package/dist/payment-return-url.js.map +1 -0
  223. package/dist/payments.d.ts +386 -0
  224. package/dist/payments.d.ts.map +1 -0
  225. package/dist/payments.js +84 -0
  226. package/dist/payments.js.map +1 -0
  227. package/dist/pim-connector.d.ts +60 -0
  228. package/dist/pim-connector.d.ts.map +1 -0
  229. package/dist/pim-connector.js +43 -0
  230. package/dist/pim-connector.js.map +1 -0
  231. package/dist/pim-field-path.d.ts +6 -0
  232. package/dist/pim-field-path.d.ts.map +1 -0
  233. package/dist/pim-field-path.js +101 -0
  234. package/dist/pim-field-path.js.map +1 -0
  235. package/dist/platform-language.d.ts +20 -0
  236. package/dist/platform-language.d.ts.map +1 -0
  237. package/dist/platform-language.js +22 -0
  238. package/dist/platform-language.js.map +1 -0
  239. package/dist/price-lists.d.ts +685 -0
  240. package/dist/price-lists.d.ts.map +1 -0
  241. package/dist/price-lists.js +330 -0
  242. package/dist/price-lists.js.map +1 -0
  243. package/dist/product-feeds.d.ts +2837 -0
  244. package/dist/product-feeds.d.ts.map +1 -0
  245. package/dist/product-feeds.js +1504 -0
  246. package/dist/product-feeds.js.map +1 -0
  247. package/dist/product-scope-overrides.d.ts +134 -0
  248. package/dist/product-scope-overrides.d.ts.map +1 -0
  249. package/dist/product-scope-overrides.js +82 -0
  250. package/dist/product-scope-overrides.js.map +1 -0
  251. package/dist/product-value-resolver.d.ts +88 -0
  252. package/dist/product-value-resolver.d.ts.map +1 -0
  253. package/dist/product-value-resolver.js +128 -0
  254. package/dist/product-value-resolver.js.map +1 -0
  255. package/dist/promotions.d.ts +678 -0
  256. package/dist/promotions.d.ts.map +1 -0
  257. package/dist/promotions.js +479 -0
  258. package/dist/promotions.js.map +1 -0
  259. package/dist/prompt-actions.d.ts +582 -0
  260. package/dist/prompt-actions.d.ts.map +1 -0
  261. package/dist/prompt-actions.js +221 -0
  262. package/dist/prompt-actions.js.map +1 -0
  263. package/dist/pwa.d.ts +293 -0
  264. package/dist/pwa.d.ts.map +1 -0
  265. package/dist/pwa.js +204 -0
  266. package/dist/pwa.js.map +1 -0
  267. package/dist/quick-order.d.ts +340 -0
  268. package/dist/quick-order.d.ts.map +1 -0
  269. package/dist/quick-order.js +177 -0
  270. package/dist/quick-order.js.map +1 -0
  271. package/dist/quote-requests.d.ts +538 -0
  272. package/dist/quote-requests.d.ts.map +1 -0
  273. package/dist/quote-requests.js +308 -0
  274. package/dist/quote-requests.js.map +1 -0
  275. package/dist/returns.d.ts +774 -0
  276. package/dist/returns.d.ts.map +1 -0
  277. package/dist/returns.js +389 -0
  278. package/dist/returns.js.map +1 -0
  279. package/dist/sales-channels.d.ts +392 -0
  280. package/dist/sales-channels.d.ts.map +1 -0
  281. package/dist/sales-channels.js +285 -0
  282. package/dist/sales-channels.js.map +1 -0
  283. package/dist/scope-notice.d.ts +60 -0
  284. package/dist/scope-notice.d.ts.map +1 -0
  285. package/dist/scope-notice.js +56 -0
  286. package/dist/scope-notice.js.map +1 -0
  287. package/dist/search.d.ts +321 -0
  288. package/dist/search.d.ts.map +1 -0
  289. package/dist/search.js +160 -0
  290. package/dist/search.js.map +1 -0
  291. package/dist/seo.d.ts +113 -0
  292. package/dist/seo.d.ts.map +1 -0
  293. package/dist/seo.js +63 -0
  294. package/dist/seo.js.map +1 -0
  295. package/dist/settings.d.ts +453 -0
  296. package/dist/settings.d.ts.map +1 -0
  297. package/dist/settings.js +337 -0
  298. package/dist/settings.js.map +1 -0
  299. package/dist/shipments.d.ts +140 -0
  300. package/dist/shipments.d.ts.map +1 -0
  301. package/dist/shipments.js +14 -0
  302. package/dist/shipments.js.map +1 -0
  303. package/dist/shipping-methods.d.ts +350 -0
  304. package/dist/shipping-methods.d.ts.map +1 -0
  305. package/dist/shipping-methods.js +99 -0
  306. package/dist/shipping-methods.js.map +1 -0
  307. package/dist/shopping-lists.d.ts +122 -0
  308. package/dist/shopping-lists.d.ts.map +1 -0
  309. package/dist/shopping-lists.js +92 -0
  310. package/dist/shopping-lists.js.map +1 -0
  311. package/dist/taxes.d.ts +106 -0
  312. package/dist/taxes.d.ts.map +1 -0
  313. package/dist/taxes.js +80 -0
  314. package/dist/taxes.js.map +1 -0
  315. package/dist/text-normalization.d.ts +199 -0
  316. package/dist/text-normalization.d.ts.map +1 -0
  317. package/dist/text-normalization.js +205 -0
  318. package/dist/text-normalization.js.map +1 -0
  319. package/dist/transactional-emails.d.ts +459 -0
  320. package/dist/transactional-emails.d.ts.map +1 -0
  321. package/dist/transactional-emails.js +212 -0
  322. package/dist/transactional-emails.js.map +1 -0
  323. package/dist/webhooks.d.ts +69 -0
  324. package/dist/webhooks.d.ts.map +1 -0
  325. package/dist/webhooks.js +53 -0
  326. package/dist/webhooks.js.map +1 -0
  327. package/package.json +46 -0
@@ -0,0 +1,2855 @@
1
+ import { z } from 'zod';
2
+ import { type ProductVisibility } from './common.js';
3
+ import type { FulfilmentStrategy } from './inventory.js';
4
+ import type { BulkImportReport } from './import-export.js';
5
+ import type { AttributeScope } from './product-value-resolver.js';
6
+ /**
7
+ * Catalog module contracts — Source of truth per Principle V.
8
+ * See specs/001-b2b-platform-foundation/contracts/catalog.contract.md.
9
+ */
10
+ /**
11
+ * Product types. Foundation 001 used `simple | variant | grouped |
12
+ * virtual`; feature 002 (data-model.md §1.1) renames `variant` →
13
+ * `configurable` and adds `bundle`. The application enum is the only
14
+ * source of truth — the DB column is `varchar(16)` with no CHECK
15
+ * constraint (research.md R-1).
16
+ */
17
+ export declare const productTypeSchema: z.ZodEnum<{
18
+ simple: "simple";
19
+ configurable: "configurable";
20
+ grouped: "grouped";
21
+ bundle: "bundle";
22
+ virtual: "virtual";
23
+ }>;
24
+ export type ProductType = z.infer<typeof productTypeSchema>;
25
+ export declare const productStatusSchema: z.ZodEnum<{
26
+ active: "active";
27
+ inactive: "inactive";
28
+ draft: "draft";
29
+ }>;
30
+ export type ProductStatus = z.infer<typeof productStatusSchema>;
31
+ /** Maps legacy `archived` writes to `inactive` (feature 032). */
32
+ export declare function coerceProductStatusWrite(value: unknown): 'draft' | 'active' | 'inactive' | unknown;
33
+ export declare const productStatusWriteSchema: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodEnum<{
34
+ active: "active";
35
+ inactive: "inactive";
36
+ draft: "draft";
37
+ }>>;
38
+ export declare const stockModeSchema: z.ZodEnum<{
39
+ categorical: "categorical";
40
+ numeric: "numeric";
41
+ }>;
42
+ export type StockMode = z.infer<typeof stockModeSchema>;
43
+ export declare const stockIndicatorSchema: z.ZodEnum<{
44
+ available: "available";
45
+ out_of_stock: "out_of_stock";
46
+ to_order: "to_order";
47
+ }>;
48
+ export type StockIndicator = z.infer<typeof stockIndicatorSchema>;
49
+ /**
50
+ * DB-level attribute value types. Foundation 001 introduced the original
51
+ * 5-element enum (`string | number | boolean | enum | date`). Feature 002
52
+ * adds `multiselect` and `price` per data-model.md §1.2.
53
+ *
54
+ * The API-facing presentation form (`apiAttributeTypeSchema` below)
55
+ * surfaces additional affordances (`input`, `select`, `slider`) that
56
+ * map to this DB enum + the sibling `displayAsSlider` flag — see
57
+ * research.md R-4 / R-7.
58
+ */
59
+ export declare const attributeValueTypeSchema: z.ZodEnum<{
60
+ string: "string";
61
+ number: "number";
62
+ boolean: "boolean";
63
+ date: "date";
64
+ enum: "enum";
65
+ select: "select";
66
+ price: "price";
67
+ multiselect: "multiselect";
68
+ }>;
69
+ export type AttributeValueType = z.infer<typeof attributeValueTypeSchema>;
70
+ /**
71
+ * API-facing attribute type form for feature 002 contracts. Maps onto
72
+ * `attributeValueTypeSchema` + `displayAsSlider` in the service layer.
73
+ */
74
+ export declare const apiAttributeTypeSchema: z.ZodEnum<{
75
+ number: "number";
76
+ input: "input";
77
+ select: "select";
78
+ price: "price";
79
+ multiselect: "multiselect";
80
+ slider: "slider";
81
+ }>;
82
+ export type ApiAttributeType = z.infer<typeof apiAttributeTypeSchema>;
83
+ /**
84
+ * The API-form type of a stored attribute, derived from the pair the catalogue
85
+ * persists (`valueType`, `displayAsSlider`). This is the single definition:
86
+ * the catalogue reports every attribute's `type` through it, and a connector
87
+ * that binds a source attribute to an existing one compares against it — two
88
+ * derivations of one rule drift, and a drift creates attributes the catalogue
89
+ * then reports as another kind.
90
+ *
91
+ * A slider flag only means something on a numeric type (`number`, `price`);
92
+ * elsewhere it is ignored. `string`, `boolean` and `date` have no richer API
93
+ * form and surface as `input`; `enum` and `select` share one affordance.
94
+ */
95
+ export declare function apiAttributeTypeOf(valueType: AttributeValueType, displayAsSlider: boolean): ApiAttributeType;
96
+ export declare const assetKindSchema: z.ZodEnum<{
97
+ image: "image";
98
+ video: "video";
99
+ pdf: "pdf";
100
+ certificate: "certificate";
101
+ other: "other";
102
+ }>;
103
+ export type AssetKind = z.infer<typeof assetKindSchema>;
104
+ export declare const attributeOptionSchema: z.ZodObject<{
105
+ id: z.ZodString;
106
+ attributeId: z.ZodString;
107
+ value: z.ZodString;
108
+ label: z.ZodRecord<z.ZodString, z.ZodString>;
109
+ labelDefault: z.ZodString;
110
+ isDefault: z.ZodBoolean;
111
+ sortOrder: z.ZodNumber;
112
+ createdAt: z.ZodString;
113
+ updatedAt: z.ZodString;
114
+ }, z.core.$strip>;
115
+ export type AttributeOption = z.infer<typeof attributeOptionSchema>;
116
+ export declare const createAttributeOptionRequestSchema: z.ZodObject<{
117
+ value: z.ZodString;
118
+ label: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
119
+ labelDefault: z.ZodString;
120
+ isDefault: z.ZodOptional<z.ZodBoolean>;
121
+ sortOrder: z.ZodOptional<z.ZodNumber>;
122
+ }, z.core.$strip>;
123
+ export type CreateAttributeOptionRequest = z.infer<typeof createAttributeOptionRequestSchema>;
124
+ export declare const replaceAttributeOptionsRequestSchema: z.ZodObject<{
125
+ options: z.ZodArray<z.ZodObject<{
126
+ value: z.ZodString;
127
+ label: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
128
+ labelDefault: z.ZodString;
129
+ isDefault: z.ZodOptional<z.ZodBoolean>;
130
+ sortOrder: z.ZodOptional<z.ZodNumber>;
131
+ }, z.core.$strip>>;
132
+ }, z.core.$strip>;
133
+ export type ReplaceAttributeOptionsRequest = z.infer<typeof replaceAttributeOptionsRequestSchema>;
134
+ export declare const patchAttributeOptionRequestSchema: z.ZodObject<{
135
+ label: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
136
+ labelDefault: z.ZodOptional<z.ZodString>;
137
+ isDefault: z.ZodOptional<z.ZodBoolean>;
138
+ sortOrder: z.ZodOptional<z.ZodNumber>;
139
+ }, z.core.$strict>;
140
+ export type PatchAttributeOptionRequest = z.infer<typeof patchAttributeOptionRequestSchema>;
141
+ export declare const productAssetSchema: z.ZodObject<{
142
+ id: z.ZodString;
143
+ kind: z.ZodEnum<{
144
+ image: "image";
145
+ video: "video";
146
+ pdf: "pdf";
147
+ certificate: "certificate";
148
+ other: "other";
149
+ }>;
150
+ url: z.ZodString;
151
+ altText: z.ZodNullable<z.ZodString>;
152
+ }, z.core.$strip>;
153
+ export type ProductAsset = z.infer<typeof productAssetSchema>;
154
+ /**
155
+ * Availability indication mirrored from the inventory display bands the
156
+ * storefront shows (`available` = stock not managed for the product).
157
+ */
158
+ export declare const productAvailabilityBandSchema: z.ZodEnum<{
159
+ available: "available";
160
+ out_of_stock: "out_of_stock";
161
+ high: "high";
162
+ medium: "medium";
163
+ low: "low";
164
+ }>;
165
+ export type ProductAvailabilityBand = z.infer<typeof productAvailabilityBandSchema>;
166
+ export declare const productAvailabilitySchema: z.ZodObject<{
167
+ band: z.ZodEnum<{
168
+ available: "available";
169
+ out_of_stock: "out_of_stock";
170
+ high: "high";
171
+ medium: "medium";
172
+ low: "low";
173
+ }>;
174
+ inStock: z.ZodBoolean;
175
+ }, z.core.$strip>;
176
+ export type ProductAvailability = z.infer<typeof productAvailabilitySchema>;
177
+ /**
178
+ * One rung of the bound Organization's resolved quantity-bracket price
179
+ * ladder (external product detail only).
180
+ */
181
+ export declare const productPriceTierSchema: z.ZodObject<{
182
+ minQuantity: z.ZodNumber;
183
+ amount: z.ZodNumber;
184
+ currency: z.ZodString;
185
+ isSale: z.ZodBoolean;
186
+ }, z.core.$strip>;
187
+ export type ProductPriceTier = z.infer<typeof productPriceTierSchema>;
188
+ export declare const productSummarySchema: z.ZodObject<{
189
+ id: z.ZodString;
190
+ sku: z.ZodString;
191
+ type: z.ZodEnum<{
192
+ simple: "simple";
193
+ configurable: "configurable";
194
+ grouped: "grouped";
195
+ bundle: "bundle";
196
+ virtual: "virtual";
197
+ }>;
198
+ name: z.ZodString;
199
+ slug: z.ZodString;
200
+ categorySlugs: z.ZodArray<z.ZodString>;
201
+ primaryAssetUrl: z.ZodNullable<z.ZodString>;
202
+ price: z.ZodNullable<z.ZodObject<{
203
+ amount: z.ZodNumber;
204
+ currency: z.ZodString;
205
+ }, z.core.$strip>>;
206
+ stockIndicator: z.ZodNullable<z.ZodEnum<{
207
+ available: "available";
208
+ out_of_stock: "out_of_stock";
209
+ to_order: "to_order";
210
+ }>>;
211
+ stockLevel: z.ZodNullable<z.ZodNumber>;
212
+ priceUnavailable: z.ZodOptional<z.ZodBoolean>;
213
+ availability: z.ZodOptional<z.ZodObject<{
214
+ band: z.ZodEnum<{
215
+ available: "available";
216
+ out_of_stock: "out_of_stock";
217
+ high: "high";
218
+ medium: "medium";
219
+ low: "low";
220
+ }>;
221
+ inStock: z.ZodBoolean;
222
+ }, z.core.$strip>>;
223
+ }, z.core.$strip>;
224
+ export type ProductSummary = z.infer<typeof productSummarySchema>;
225
+ export declare const productVariantSchema: z.ZodObject<{
226
+ id: z.ZodString;
227
+ sku: z.ZodString;
228
+ variantAttributeValues: z.ZodRecord<z.ZodString, z.ZodUnknown>;
229
+ priceOverride: z.ZodNullable<z.ZodNumber>;
230
+ stockLevel: z.ZodNullable<z.ZodNumber>;
231
+ }, z.core.$strip>;
232
+ export type ProductVariant = z.infer<typeof productVariantSchema>;
233
+ export declare const seoMetaSchema: z.ZodObject<{
234
+ metaTitle: z.ZodString;
235
+ metaDescription: z.ZodString;
236
+ openGraph: z.ZodObject<{
237
+ title: z.ZodString;
238
+ description: z.ZodString;
239
+ imageUrl: z.ZodNullable<z.ZodString>;
240
+ }, z.core.$strip>;
241
+ }, z.core.$strip>;
242
+ export type SeoMeta = z.infer<typeof seoMetaSchema>;
243
+ export declare const productDetailSchema: z.ZodObject<{
244
+ id: z.ZodString;
245
+ sku: z.ZodString;
246
+ type: z.ZodEnum<{
247
+ simple: "simple";
248
+ configurable: "configurable";
249
+ grouped: "grouped";
250
+ bundle: "bundle";
251
+ virtual: "virtual";
252
+ }>;
253
+ name: z.ZodString;
254
+ slug: z.ZodString;
255
+ categorySlugs: z.ZodArray<z.ZodString>;
256
+ primaryAssetUrl: z.ZodNullable<z.ZodString>;
257
+ price: z.ZodNullable<z.ZodObject<{
258
+ amount: z.ZodNumber;
259
+ currency: z.ZodString;
260
+ }, z.core.$strip>>;
261
+ stockIndicator: z.ZodNullable<z.ZodEnum<{
262
+ available: "available";
263
+ out_of_stock: "out_of_stock";
264
+ to_order: "to_order";
265
+ }>>;
266
+ stockLevel: z.ZodNullable<z.ZodNumber>;
267
+ priceUnavailable: z.ZodOptional<z.ZodBoolean>;
268
+ availability: z.ZodOptional<z.ZodObject<{
269
+ band: z.ZodEnum<{
270
+ available: "available";
271
+ out_of_stock: "out_of_stock";
272
+ high: "high";
273
+ medium: "medium";
274
+ low: "low";
275
+ }>;
276
+ inStock: z.ZodBoolean;
277
+ }, z.core.$strip>>;
278
+ description: z.ZodString;
279
+ attributeValues: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
280
+ assets: z.ZodArray<z.ZodObject<{
281
+ id: z.ZodString;
282
+ kind: z.ZodEnum<{
283
+ image: "image";
284
+ video: "video";
285
+ pdf: "pdf";
286
+ certificate: "certificate";
287
+ other: "other";
288
+ }>;
289
+ url: z.ZodString;
290
+ altText: z.ZodNullable<z.ZodString>;
291
+ }, z.core.$strip>>;
292
+ variants: z.ZodArray<z.ZodObject<{
293
+ id: z.ZodString;
294
+ sku: z.ZodString;
295
+ variantAttributeValues: z.ZodRecord<z.ZodString, z.ZodUnknown>;
296
+ priceOverride: z.ZodNullable<z.ZodNumber>;
297
+ stockLevel: z.ZodNullable<z.ZodNumber>;
298
+ }, z.core.$strip>>;
299
+ categories: z.ZodArray<z.ZodObject<{
300
+ id: z.ZodString;
301
+ name: z.ZodString;
302
+ slug: z.ZodString;
303
+ }, z.core.$strip>>;
304
+ seo: z.ZodObject<{
305
+ metaTitle: z.ZodString;
306
+ metaDescription: z.ZodString;
307
+ openGraph: z.ZodObject<{
308
+ title: z.ZodString;
309
+ description: z.ZodString;
310
+ imageUrl: z.ZodNullable<z.ZodString>;
311
+ }, z.core.$strip>;
312
+ }, z.core.$strip>;
313
+ structuredDataJsonLd: z.ZodRecord<z.ZodString, z.ZodUnknown>;
314
+ attributeSet: z.ZodOptional<z.ZodObject<{
315
+ id: z.ZodString;
316
+ code: z.ZodString;
317
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
318
+ }, z.core.$strip>>;
319
+ gallery: z.ZodOptional<z.ZodArray<z.ZodObject<{
320
+ id: z.ZodString;
321
+ position: z.ZodNumber;
322
+ labels: z.ZodArray<z.ZodEnum<{
323
+ base_image: "base_image";
324
+ small_image: "small_image";
325
+ thumbnail: "thumbnail";
326
+ }>>;
327
+ asset: z.ZodObject<{
328
+ id: z.ZodString;
329
+ kind: z.ZodString;
330
+ url: z.ZodString;
331
+ }, z.core.$strip>;
332
+ }, z.core.$strip>>>;
333
+ groupedItems: z.ZodOptional<z.ZodArray<z.ZodObject<{
334
+ id: z.ZodString;
335
+ position: z.ZodNumber;
336
+ quantity: z.ZodNumber;
337
+ product: z.ZodObject<{
338
+ id: z.ZodString;
339
+ sku: z.ZodString;
340
+ slug: z.ZodString;
341
+ name: z.ZodString;
342
+ primaryAssetUrl: z.ZodNullable<z.ZodString>;
343
+ price: z.ZodNullable<z.ZodObject<{
344
+ amount: z.ZodNumber;
345
+ currency: z.ZodString;
346
+ }, z.core.$strip>>;
347
+ }, z.core.$strip>;
348
+ }, z.core.$strip>>>;
349
+ bundleSlots: z.ZodOptional<z.ZodArray<z.ZodObject<{
350
+ id: z.ZodString;
351
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
352
+ minQuantity: z.ZodNumber;
353
+ maxQuantity: z.ZodNumber;
354
+ position: z.ZodNumber;
355
+ options: z.ZodArray<z.ZodObject<{
356
+ id: z.ZodString;
357
+ defaultQuantity: z.ZodNumber;
358
+ position: z.ZodNumber;
359
+ product: z.ZodObject<{
360
+ id: z.ZodString;
361
+ sku: z.ZodString;
362
+ slug: z.ZodString;
363
+ name: z.ZodString;
364
+ primaryAssetUrl: z.ZodNullable<z.ZodString>;
365
+ price: z.ZodNullable<z.ZodObject<{
366
+ amount: z.ZodNumber;
367
+ currency: z.ZodString;
368
+ }, z.core.$strip>>;
369
+ }, z.core.$strip>;
370
+ }, z.core.$strip>>;
371
+ }, z.core.$strip>>>;
372
+ virtual: z.ZodOptional<z.ZodObject<{
373
+ downloadAssetId: z.ZodNullable<z.ZodString>;
374
+ downloadUrl: z.ZodNullable<z.ZodString>;
375
+ }, z.core.$strip>>;
376
+ links: z.ZodOptional<z.ZodObject<{
377
+ related: z.ZodArray<z.ZodObject<{
378
+ id: z.ZodString;
379
+ kind: z.ZodEnum<{
380
+ related: "related";
381
+ up_sell: "up_sell";
382
+ cross_sell: "cross_sell";
383
+ }>;
384
+ position: z.ZodNumber;
385
+ product: z.ZodObject<{
386
+ id: z.ZodString;
387
+ sku: z.ZodString;
388
+ slug: z.ZodString;
389
+ name: z.ZodString;
390
+ primaryAssetUrl: z.ZodNullable<z.ZodString>;
391
+ price: z.ZodNullable<z.ZodObject<{
392
+ amount: z.ZodNumber;
393
+ currency: z.ZodString;
394
+ }, z.core.$strip>>;
395
+ }, z.core.$strip>;
396
+ }, z.core.$strip>>;
397
+ upSell: z.ZodArray<z.ZodObject<{
398
+ id: z.ZodString;
399
+ kind: z.ZodEnum<{
400
+ related: "related";
401
+ up_sell: "up_sell";
402
+ cross_sell: "cross_sell";
403
+ }>;
404
+ position: z.ZodNumber;
405
+ product: z.ZodObject<{
406
+ id: z.ZodString;
407
+ sku: z.ZodString;
408
+ slug: z.ZodString;
409
+ name: z.ZodString;
410
+ primaryAssetUrl: z.ZodNullable<z.ZodString>;
411
+ price: z.ZodNullable<z.ZodObject<{
412
+ amount: z.ZodNumber;
413
+ currency: z.ZodString;
414
+ }, z.core.$strip>>;
415
+ }, z.core.$strip>;
416
+ }, z.core.$strip>>;
417
+ crossSell: z.ZodArray<z.ZodObject<{
418
+ id: z.ZodString;
419
+ kind: z.ZodEnum<{
420
+ related: "related";
421
+ up_sell: "up_sell";
422
+ cross_sell: "cross_sell";
423
+ }>;
424
+ position: z.ZodNumber;
425
+ product: z.ZodObject<{
426
+ id: z.ZodString;
427
+ sku: z.ZodString;
428
+ slug: z.ZodString;
429
+ name: z.ZodString;
430
+ primaryAssetUrl: z.ZodNullable<z.ZodString>;
431
+ price: z.ZodNullable<z.ZodObject<{
432
+ amount: z.ZodNumber;
433
+ currency: z.ZodString;
434
+ }, z.core.$strip>>;
435
+ }, z.core.$strip>;
436
+ }, z.core.$strip>>;
437
+ }, z.core.$strip>>;
438
+ attachments: z.ZodOptional<z.ZodArray<z.ZodObject<{
439
+ id: z.ZodString;
440
+ position: z.ZodNumber;
441
+ name: z.ZodString;
442
+ description: z.ZodNullable<z.ZodString>;
443
+ type: z.ZodObject<{
444
+ id: z.ZodString;
445
+ code: z.ZodString;
446
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
447
+ }, z.core.$strip>;
448
+ asset: z.ZodObject<{
449
+ id: z.ZodString;
450
+ kind: z.ZodString;
451
+ url: z.ZodString;
452
+ filename: z.ZodString;
453
+ sizeBytes: z.ZodNumber;
454
+ mimeType: z.ZodString;
455
+ }, z.core.$strip>;
456
+ }, z.core.$strip>>>;
457
+ visibleAttributes: z.ZodOptional<z.ZodArray<z.ZodObject<{
458
+ key: z.ZodString;
459
+ label: z.ZodString;
460
+ valueType: z.ZodEnum<{
461
+ string: "string";
462
+ number: "number";
463
+ boolean: "boolean";
464
+ date: "date";
465
+ enum: "enum";
466
+ select: "select";
467
+ price: "price";
468
+ multiselect: "multiselect";
469
+ }>;
470
+ valueRendered: z.ZodString;
471
+ }, z.core.$strip>>>;
472
+ packagingUnits: z.ZodOptional<z.ZodArray<z.ZodObject<{
473
+ id: z.ZodString;
474
+ name: z.ZodString;
475
+ baseQuantity: z.ZodNumber;
476
+ position: z.ZodNumber;
477
+ isDefault: z.ZodBoolean;
478
+ }, z.core.$strip>>>;
479
+ priceTiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
480
+ minQuantity: z.ZodNumber;
481
+ amount: z.ZodNumber;
482
+ currency: z.ZodString;
483
+ isSale: z.ZodBoolean;
484
+ }, z.core.$strip>>>;
485
+ }, z.core.$strip>;
486
+ export type ProductDetail = z.infer<typeof productDetailSchema>;
487
+ export declare const catalogBulkPriceRequestSchema: z.ZodObject<{
488
+ lines: z.ZodArray<z.ZodObject<{
489
+ sku: z.ZodString;
490
+ quantity: z.ZodNumber;
491
+ }, z.core.$strip>>;
492
+ }, z.core.$strip>;
493
+ export type CatalogBulkPriceRequest = z.infer<typeof catalogBulkPriceRequestSchema>;
494
+ export declare const catalogBulkPriceMissReasonSchema: z.ZodEnum<{
495
+ sku_not_in_assortment: "sku_not_in_assortment";
496
+ price_unavailable: "price_unavailable";
497
+ }>;
498
+ export type CatalogBulkPriceMissReason = z.infer<typeof catalogBulkPriceMissReasonSchema>;
499
+ /**
500
+ * Per-line result, order-preserving. Misses are data, not errors — the
501
+ * partner needs a total answer for a basket. `amount` is the exact decimal
502
+ * string the pricing resolver charges the bound org on the bound channel at
503
+ * that quantity (SC-001 parity with cart pricing).
504
+ */
505
+ export declare const catalogBulkPriceLineSchema: z.ZodUnion<readonly [z.ZodObject<{
506
+ sku: z.ZodString;
507
+ quantity: z.ZodNumber;
508
+ price: z.ZodObject<{
509
+ amount: z.ZodString;
510
+ currency: z.ZodString;
511
+ isSale: z.ZodBoolean;
512
+ bracketStartQuantity: z.ZodNumber;
513
+ priceListId: z.ZodString;
514
+ }, z.core.$strip>;
515
+ }, z.core.$strip>, z.ZodObject<{
516
+ sku: z.ZodString;
517
+ quantity: z.ZodNumber;
518
+ price: z.ZodNull;
519
+ reason: z.ZodEnum<{
520
+ sku_not_in_assortment: "sku_not_in_assortment";
521
+ price_unavailable: "price_unavailable";
522
+ }>;
523
+ }, z.core.$strip>]>;
524
+ export type CatalogBulkPriceLine = z.infer<typeof catalogBulkPriceLineSchema>;
525
+ export declare const catalogBulkPriceResponseSchema: z.ZodObject<{
526
+ data: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
527
+ sku: z.ZodString;
528
+ quantity: z.ZodNumber;
529
+ price: z.ZodObject<{
530
+ amount: z.ZodString;
531
+ currency: z.ZodString;
532
+ isSale: z.ZodBoolean;
533
+ bracketStartQuantity: z.ZodNumber;
534
+ priceListId: z.ZodString;
535
+ }, z.core.$strip>;
536
+ }, z.core.$strip>, z.ZodObject<{
537
+ sku: z.ZodString;
538
+ quantity: z.ZodNumber;
539
+ price: z.ZodNull;
540
+ reason: z.ZodEnum<{
541
+ sku_not_in_assortment: "sku_not_in_assortment";
542
+ price_unavailable: "price_unavailable";
543
+ }>;
544
+ }, z.core.$strip>]>>;
545
+ }, z.core.$strip>;
546
+ export type CatalogBulkPriceResponse = z.infer<typeof catalogBulkPriceResponseSchema>;
547
+ export interface CategoryNode {
548
+ id: string;
549
+ name: string;
550
+ slug: string;
551
+ sortOrder: number;
552
+ productCount: number;
553
+ children: CategoryNode[];
554
+ }
555
+ export declare const categoryNodeSchema: z.ZodType<CategoryNode>;
556
+ export declare const filterOptionSchema: z.ZodObject<{
557
+ value: z.ZodString;
558
+ label: z.ZodString;
559
+ count: z.ZodNumber;
560
+ }, z.core.$strip>;
561
+ export type FilterOption = z.infer<typeof filterOptionSchema>;
562
+ export declare const filterRangeSchema: z.ZodObject<{
563
+ min: z.ZodNumber;
564
+ max: z.ZodNumber;
565
+ }, z.core.$strip>;
566
+ export type FilterRange = z.infer<typeof filterRangeSchema>;
567
+ export declare const filterDefinitionSchema: z.ZodObject<{
568
+ attributeKey: z.ZodString;
569
+ label: z.ZodString;
570
+ valueType: z.ZodEnum<{
571
+ string: "string";
572
+ number: "number";
573
+ boolean: "boolean";
574
+ date: "date";
575
+ enum: "enum";
576
+ select: "select";
577
+ price: "price";
578
+ multiselect: "multiselect";
579
+ }>;
580
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
581
+ value: z.ZodString;
582
+ label: z.ZodString;
583
+ count: z.ZodNumber;
584
+ }, z.core.$strip>>>;
585
+ range: z.ZodOptional<z.ZodObject<{
586
+ min: z.ZodNumber;
587
+ max: z.ZodNumber;
588
+ }, z.core.$strip>>;
589
+ filterPosition: z.ZodDefault<z.ZodNumber>;
590
+ }, z.core.$strip>;
591
+ export type FilterDefinition = z.infer<typeof filterDefinitionSchema>;
592
+ export declare const createProductRequestSchema: z.ZodObject<{
593
+ sku: z.ZodString;
594
+ type: z.ZodEnum<{
595
+ simple: "simple";
596
+ configurable: "configurable";
597
+ grouped: "grouped";
598
+ bundle: "bundle";
599
+ virtual: "virtual";
600
+ }>;
601
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
602
+ description: z.ZodRecord<z.ZodString, z.ZodString>;
603
+ categoryIds: z.ZodArray<z.ZodString>;
604
+ attributeValues: z.ZodRecord<z.ZodString, z.ZodUnknown>;
605
+ stockMode: z.ZodOptional<z.ZodEnum<{
606
+ categorical: "categorical";
607
+ numeric: "numeric";
608
+ }>>;
609
+ visibility: z.ZodEnum<{
610
+ public: "public";
611
+ logged_in_only: "logged_in_only";
612
+ organization_restricted: "organization_restricted";
613
+ }>;
614
+ status: z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodEnum<{
615
+ active: "active";
616
+ inactive: "inactive";
617
+ draft: "draft";
618
+ }>>>;
619
+ allowedOrganizationIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
620
+ assetIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
621
+ initialStock: z.ZodOptional<z.ZodNumber>;
622
+ attributeSetId: z.ZodOptional<z.ZodString>;
623
+ downloadAssetId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
624
+ downloadUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
625
+ manageStock: z.ZodOptional<z.ZodBoolean>;
626
+ backorderEnabled: z.ZodOptional<z.ZodBoolean>;
627
+ lowStockThreshold: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
628
+ lowStockThresholdMode: z.ZodOptional<z.ZodEnum<{
629
+ cumulative: "cumulative";
630
+ per_warehouse: "per_warehouse";
631
+ }>>;
632
+ fulfilmentStrategy: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
633
+ any: "any";
634
+ default_first: "default_first";
635
+ lowest_stock_first: "lowest_stock_first";
636
+ highest_stock_first: "highest_stock_first";
637
+ defined_order: "defined_order";
638
+ }>>>;
639
+ fulfilmentStrategyWarehouseOrder: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
640
+ }, z.core.$strip>;
641
+ export type CreateProductRequest = z.infer<typeof createProductRequestSchema>;
642
+ export declare const updateProductRequestSchema: z.ZodObject<{
643
+ description: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
644
+ name: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
645
+ status: z.ZodOptional<z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodEnum<{
646
+ active: "active";
647
+ inactive: "inactive";
648
+ draft: "draft";
649
+ }>>>>;
650
+ visibility: z.ZodOptional<z.ZodEnum<{
651
+ public: "public";
652
+ logged_in_only: "logged_in_only";
653
+ organization_restricted: "organization_restricted";
654
+ }>>;
655
+ assetIds: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
656
+ categoryIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
657
+ sku: z.ZodOptional<z.ZodString>;
658
+ manageStock: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
659
+ backorderEnabled: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
660
+ lowStockThreshold: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
661
+ lowStockThresholdMode: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
662
+ cumulative: "cumulative";
663
+ per_warehouse: "per_warehouse";
664
+ }>>>;
665
+ attributeValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
666
+ downloadAssetId: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
667
+ downloadUrl: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
668
+ stockMode: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
669
+ categorical: "categorical";
670
+ numeric: "numeric";
671
+ }>>>;
672
+ allowedOrganizationIds: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
673
+ initialStock: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
674
+ attributeSetId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
675
+ fulfilmentStrategy: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
676
+ any: "any";
677
+ default_first: "default_first";
678
+ lowest_stock_first: "lowest_stock_first";
679
+ highest_stock_first: "highest_stock_first";
680
+ defined_order: "defined_order";
681
+ }>>>>;
682
+ fulfilmentStrategyWarehouseOrder: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>>;
683
+ }, z.core.$strip>;
684
+ export type UpdateProductRequest = z.infer<typeof updateProductRequestSchema>;
685
+ export declare const batchByIdProductsRequestSchema: z.ZodObject<{
686
+ ids: z.ZodArray<z.ZodString>;
687
+ page: z.ZodOptional<z.ZodNumber>;
688
+ pageSize: z.ZodOptional<z.ZodNumber>;
689
+ }, z.core.$strip>;
690
+ export type BatchByIdProductsRequest = z.infer<typeof batchByIdProductsRequestSchema>;
691
+ export declare const MAX_RESOLVE_SELECTION_SIZE = 10000;
692
+ export declare const resolveProductIdsRequestSchema: z.ZodObject<{
693
+ status: z.ZodOptional<z.ZodEnum<{
694
+ active: "active";
695
+ inactive: "inactive";
696
+ draft: "draft";
697
+ }>>;
698
+ type: z.ZodOptional<z.ZodEnum<{
699
+ simple: "simple";
700
+ configurable: "configurable";
701
+ grouped: "grouped";
702
+ bundle: "bundle";
703
+ virtual: "virtual";
704
+ }>>;
705
+ q: z.ZodOptional<z.ZodString>;
706
+ includeArchived: z.ZodOptional<z.ZodBoolean>;
707
+ }, z.core.$strip>;
708
+ export type ResolveProductIdsRequest = z.infer<typeof resolveProductIdsRequestSchema>;
709
+ export declare const resolveProductIdsResponseSchema: z.ZodObject<{
710
+ data: z.ZodObject<{
711
+ productIds: z.ZodArray<z.ZodString>;
712
+ total: z.ZodNumber;
713
+ }, z.core.$strip>;
714
+ }, z.core.$strip>;
715
+ export type ResolveProductIdsResponse = z.infer<typeof resolveProductIdsResponseSchema>;
716
+ export declare const bulkUpdateProductsRequestSchema: z.ZodObject<{
717
+ productIds: z.ZodArray<z.ZodString>;
718
+ fields: z.ZodObject<{
719
+ status: z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodEnum<{
720
+ active: "active";
721
+ inactive: "inactive";
722
+ draft: "draft";
723
+ }>>>;
724
+ visibility: z.ZodOptional<z.ZodEnum<{
725
+ public: "public";
726
+ logged_in_only: "logged_in_only";
727
+ organization_restricted: "organization_restricted";
728
+ }>>;
729
+ salesChannels: z.ZodOptional<z.ZodObject<{
730
+ mode: z.ZodEnum<{
731
+ replace: "replace";
732
+ add: "add";
733
+ }>;
734
+ channelIds: z.ZodArray<z.ZodString>;
735
+ }, z.core.$strip>>;
736
+ categories: z.ZodOptional<z.ZodObject<{
737
+ mode: z.ZodEnum<{
738
+ replace: "replace";
739
+ add: "add";
740
+ remove: "remove";
741
+ }>;
742
+ categoryIds: z.ZodArray<z.ZodString>;
743
+ }, z.core.$strip>>;
744
+ attributeSetId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
745
+ attributeValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
746
+ }, z.core.$strip>;
747
+ }, z.core.$strip>;
748
+ export type BulkUpdateProductsRequest = z.infer<typeof bulkUpdateProductsRequestSchema>;
749
+ export declare const bulkUpdateProductResultSchema: z.ZodObject<{
750
+ productId: z.ZodString;
751
+ status: z.ZodEnum<{
752
+ failed: "failed";
753
+ skipped: "skipped";
754
+ succeeded: "succeeded";
755
+ }>;
756
+ reason: z.ZodOptional<z.ZodEnum<{
757
+ product_not_found: "product_not_found";
758
+ attribute_not_in_set: "attribute_not_in_set";
759
+ validation_failed: "validation_failed";
760
+ permission_denied: "permission_denied";
761
+ concurrent_modification: "concurrent_modification";
762
+ }>>;
763
+ details: z.ZodOptional<z.ZodObject<{
764
+ code: z.ZodOptional<z.ZodString>;
765
+ message: z.ZodOptional<z.ZodString>;
766
+ attribute: z.ZodOptional<z.ZodString>;
767
+ }, z.core.$strip>>;
768
+ changedFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
769
+ }, z.core.$strip>;
770
+ export type BulkUpdateProductResult = z.infer<typeof bulkUpdateProductResultSchema>;
771
+ export declare const bulkUpdateProductsResponseSchema: z.ZodObject<{
772
+ data: z.ZodObject<{
773
+ bulkOperationId: z.ZodString;
774
+ summary: z.ZodObject<{
775
+ succeeded: z.ZodNumber;
776
+ skipped: z.ZodNumber;
777
+ failed: z.ZodNumber;
778
+ total: z.ZodNumber;
779
+ }, z.core.$strip>;
780
+ results: z.ZodArray<z.ZodObject<{
781
+ productId: z.ZodString;
782
+ status: z.ZodEnum<{
783
+ failed: "failed";
784
+ skipped: "skipped";
785
+ succeeded: "succeeded";
786
+ }>;
787
+ reason: z.ZodOptional<z.ZodEnum<{
788
+ product_not_found: "product_not_found";
789
+ attribute_not_in_set: "attribute_not_in_set";
790
+ validation_failed: "validation_failed";
791
+ permission_denied: "permission_denied";
792
+ concurrent_modification: "concurrent_modification";
793
+ }>>;
794
+ details: z.ZodOptional<z.ZodObject<{
795
+ code: z.ZodOptional<z.ZodString>;
796
+ message: z.ZodOptional<z.ZodString>;
797
+ attribute: z.ZodOptional<z.ZodString>;
798
+ }, z.core.$strip>>;
799
+ changedFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
800
+ }, z.core.$strip>>;
801
+ }, z.core.$strip>;
802
+ }, z.core.$strip>;
803
+ export type BulkUpdateProductsResponse = z.infer<typeof bulkUpdateProductsResponseSchema>;
804
+ export declare const bulkUpdateQueuedResponseSchema: z.ZodObject<{
805
+ data: z.ZodObject<{
806
+ queued: z.ZodLiteral<true>;
807
+ bulkOperationId: z.ZodString;
808
+ total: z.ZodNumber;
809
+ }, z.core.$strip>;
810
+ }, z.core.$strip>;
811
+ export type BulkUpdateQueuedResponse = z.infer<typeof bulkUpdateQueuedResponseSchema>;
812
+ export declare const bulkOperationStatusSchema: z.ZodEnum<{
813
+ pending: "pending";
814
+ completed: "completed";
815
+ failed: "failed";
816
+ running: "running";
817
+ }>;
818
+ export type BulkOperationStatus = z.infer<typeof bulkOperationStatusSchema>;
819
+ /**
820
+ * One timestamped lifecycle event in a bulk operation's log trail, surfaced
821
+ * in the bulk-operations detail view next to the per-element results.
822
+ */
823
+ export declare const bulkOperationLogEntrySchema: z.ZodObject<{
824
+ ts: z.ZodString;
825
+ level: z.ZodEnum<{
826
+ error: "error";
827
+ info: "info";
828
+ warn: "warn";
829
+ }>;
830
+ message: z.ZodString;
831
+ }, z.core.$strip>;
832
+ export type BulkOperationLogEntry = z.infer<typeof bulkOperationLogEntrySchema>;
833
+ /**
834
+ * Known bulk-operation kinds. `type` on the record is an open string (the
835
+ * queue is generic), but these are the kinds the platform ships:
836
+ * - `product_bulk_update` — the queued large product bulk-edit (feature 022).
837
+ * - `search_reindex` — a full Meilisearch reindex (the `search:reindex`
838
+ * CLI equivalent), enqueued when an attribute's `searchable` flag changes.
839
+ */
840
+ export declare const BULK_OPERATION_TYPES: {
841
+ readonly PRODUCT_BULK_UPDATE: "product_bulk_update";
842
+ readonly SEARCH_REINDEX: "search_reindex";
843
+ };
844
+ export type BulkOperationType = (typeof BULK_OPERATION_TYPES)[keyof typeof BULK_OPERATION_TYPES];
845
+ export declare const bulkOperationSchema: z.ZodObject<{
846
+ id: z.ZodString;
847
+ type: z.ZodString;
848
+ status: z.ZodEnum<{
849
+ pending: "pending";
850
+ completed: "completed";
851
+ failed: "failed";
852
+ running: "running";
853
+ }>;
854
+ requestedByAdminUserId: z.ZodString;
855
+ total: z.ZodNumber;
856
+ processed: z.ZodNumber;
857
+ succeeded: z.ZodNumber;
858
+ skipped: z.ZodNumber;
859
+ failed: z.ZodNumber;
860
+ touchedFields: z.ZodArray<z.ZodString>;
861
+ results: z.ZodNullable<z.ZodArray<z.ZodObject<{
862
+ productId: z.ZodString;
863
+ status: z.ZodEnum<{
864
+ failed: "failed";
865
+ skipped: "skipped";
866
+ succeeded: "succeeded";
867
+ }>;
868
+ reason: z.ZodOptional<z.ZodEnum<{
869
+ product_not_found: "product_not_found";
870
+ attribute_not_in_set: "attribute_not_in_set";
871
+ validation_failed: "validation_failed";
872
+ permission_denied: "permission_denied";
873
+ concurrent_modification: "concurrent_modification";
874
+ }>>;
875
+ details: z.ZodOptional<z.ZodObject<{
876
+ code: z.ZodOptional<z.ZodString>;
877
+ message: z.ZodOptional<z.ZodString>;
878
+ attribute: z.ZodOptional<z.ZodString>;
879
+ }, z.core.$strip>>;
880
+ changedFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
881
+ }, z.core.$strip>>>;
882
+ logs: z.ZodNullable<z.ZodArray<z.ZodObject<{
883
+ ts: z.ZodString;
884
+ level: z.ZodEnum<{
885
+ error: "error";
886
+ info: "info";
887
+ warn: "warn";
888
+ }>;
889
+ message: z.ZodString;
890
+ }, z.core.$strip>>>;
891
+ error: z.ZodNullable<z.ZodString>;
892
+ createdAt: z.ZodString;
893
+ startedAt: z.ZodNullable<z.ZodString>;
894
+ finishedAt: z.ZodNullable<z.ZodString>;
895
+ reversible: z.ZodBoolean;
896
+ undoStatus: z.ZodEnum<{
897
+ none: "none";
898
+ reverted: "reverted";
899
+ partially_reverted: "partially_reverted";
900
+ }>;
901
+ undoneAt: z.ZodNullable<z.ZodString>;
902
+ }, z.core.$strip>;
903
+ export type BulkOperation = z.infer<typeof bulkOperationSchema>;
904
+ /** Response of POST /admin/catalog/bulk-operations/:id/undo. */
905
+ export declare const bulkOperationUndoResponseSchema: z.ZodObject<{
906
+ data: z.ZodObject<{
907
+ undoStatus: z.ZodEnum<{
908
+ none: "none";
909
+ reverted: "reverted";
910
+ partially_reverted: "partially_reverted";
911
+ }>;
912
+ reverted: z.ZodNumber;
913
+ conflicts: z.ZodArray<z.ZodObject<{
914
+ recordId: z.ZodString;
915
+ reason: z.ZodString;
916
+ }, z.core.$strip>>;
917
+ }, z.core.$strip>;
918
+ }, z.core.$strip>;
919
+ export type BulkOperationUndoResponse = z.infer<typeof bulkOperationUndoResponseSchema>;
920
+ export declare const bulkOperationsListResponseSchema: z.ZodObject<{
921
+ data: z.ZodArray<z.ZodObject<{
922
+ id: z.ZodString;
923
+ type: z.ZodString;
924
+ status: z.ZodEnum<{
925
+ pending: "pending";
926
+ completed: "completed";
927
+ failed: "failed";
928
+ running: "running";
929
+ }>;
930
+ requestedByAdminUserId: z.ZodString;
931
+ total: z.ZodNumber;
932
+ processed: z.ZodNumber;
933
+ succeeded: z.ZodNumber;
934
+ skipped: z.ZodNumber;
935
+ failed: z.ZodNumber;
936
+ touchedFields: z.ZodArray<z.ZodString>;
937
+ results: z.ZodNullable<z.ZodArray<z.ZodObject<{
938
+ productId: z.ZodString;
939
+ status: z.ZodEnum<{
940
+ failed: "failed";
941
+ skipped: "skipped";
942
+ succeeded: "succeeded";
943
+ }>;
944
+ reason: z.ZodOptional<z.ZodEnum<{
945
+ product_not_found: "product_not_found";
946
+ attribute_not_in_set: "attribute_not_in_set";
947
+ validation_failed: "validation_failed";
948
+ permission_denied: "permission_denied";
949
+ concurrent_modification: "concurrent_modification";
950
+ }>>;
951
+ details: z.ZodOptional<z.ZodObject<{
952
+ code: z.ZodOptional<z.ZodString>;
953
+ message: z.ZodOptional<z.ZodString>;
954
+ attribute: z.ZodOptional<z.ZodString>;
955
+ }, z.core.$strip>>;
956
+ changedFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
957
+ }, z.core.$strip>>>;
958
+ logs: z.ZodNullable<z.ZodArray<z.ZodObject<{
959
+ ts: z.ZodString;
960
+ level: z.ZodEnum<{
961
+ error: "error";
962
+ info: "info";
963
+ warn: "warn";
964
+ }>;
965
+ message: z.ZodString;
966
+ }, z.core.$strip>>>;
967
+ error: z.ZodNullable<z.ZodString>;
968
+ createdAt: z.ZodString;
969
+ startedAt: z.ZodNullable<z.ZodString>;
970
+ finishedAt: z.ZodNullable<z.ZodString>;
971
+ reversible: z.ZodBoolean;
972
+ undoStatus: z.ZodEnum<{
973
+ none: "none";
974
+ reverted: "reverted";
975
+ partially_reverted: "partially_reverted";
976
+ }>;
977
+ undoneAt: z.ZodNullable<z.ZodString>;
978
+ }, z.core.$strip>>;
979
+ pagination: z.ZodObject<{
980
+ total: z.ZodNumber;
981
+ limit: z.ZodNumber;
982
+ offset: z.ZodNumber;
983
+ }, z.core.$strip>;
984
+ }, z.core.$strip>;
985
+ export type BulkOperationsListResponse = z.infer<typeof bulkOperationsListResponseSchema>;
986
+ export declare const bulkOperationResponseSchema: z.ZodObject<{
987
+ data: z.ZodObject<{
988
+ id: z.ZodString;
989
+ type: z.ZodString;
990
+ status: z.ZodEnum<{
991
+ pending: "pending";
992
+ completed: "completed";
993
+ failed: "failed";
994
+ running: "running";
995
+ }>;
996
+ requestedByAdminUserId: z.ZodString;
997
+ total: z.ZodNumber;
998
+ processed: z.ZodNumber;
999
+ succeeded: z.ZodNumber;
1000
+ skipped: z.ZodNumber;
1001
+ failed: z.ZodNumber;
1002
+ touchedFields: z.ZodArray<z.ZodString>;
1003
+ results: z.ZodNullable<z.ZodArray<z.ZodObject<{
1004
+ productId: z.ZodString;
1005
+ status: z.ZodEnum<{
1006
+ failed: "failed";
1007
+ skipped: "skipped";
1008
+ succeeded: "succeeded";
1009
+ }>;
1010
+ reason: z.ZodOptional<z.ZodEnum<{
1011
+ product_not_found: "product_not_found";
1012
+ attribute_not_in_set: "attribute_not_in_set";
1013
+ validation_failed: "validation_failed";
1014
+ permission_denied: "permission_denied";
1015
+ concurrent_modification: "concurrent_modification";
1016
+ }>>;
1017
+ details: z.ZodOptional<z.ZodObject<{
1018
+ code: z.ZodOptional<z.ZodString>;
1019
+ message: z.ZodOptional<z.ZodString>;
1020
+ attribute: z.ZodOptional<z.ZodString>;
1021
+ }, z.core.$strip>>;
1022
+ changedFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
1023
+ }, z.core.$strip>>>;
1024
+ logs: z.ZodNullable<z.ZodArray<z.ZodObject<{
1025
+ ts: z.ZodString;
1026
+ level: z.ZodEnum<{
1027
+ error: "error";
1028
+ info: "info";
1029
+ warn: "warn";
1030
+ }>;
1031
+ message: z.ZodString;
1032
+ }, z.core.$strip>>>;
1033
+ error: z.ZodNullable<z.ZodString>;
1034
+ createdAt: z.ZodString;
1035
+ startedAt: z.ZodNullable<z.ZodString>;
1036
+ finishedAt: z.ZodNullable<z.ZodString>;
1037
+ reversible: z.ZodBoolean;
1038
+ undoStatus: z.ZodEnum<{
1039
+ none: "none";
1040
+ reverted: "reverted";
1041
+ partially_reverted: "partially_reverted";
1042
+ }>;
1043
+ undoneAt: z.ZodNullable<z.ZodString>;
1044
+ }, z.core.$strip>;
1045
+ }, z.core.$strip>;
1046
+ export type BulkOperationResponse = z.infer<typeof bulkOperationResponseSchema>;
1047
+ export declare const createVariantRequestSchema: z.ZodObject<{
1048
+ sku: z.ZodString;
1049
+ variantAttributeValues: z.ZodRecord<z.ZodString, z.ZodUnknown>;
1050
+ priceOverride: z.ZodOptional<z.ZodNumber>;
1051
+ stockLevel: z.ZodOptional<z.ZodNumber>;
1052
+ }, z.core.$strip>;
1053
+ export type CreateVariantRequest = z.infer<typeof createVariantRequestSchema>;
1054
+ export declare const updateVariantRequestSchema: z.ZodObject<{
1055
+ stockLevel: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
1056
+ variantAttributeValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1057
+ priceOverride: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
1058
+ }, z.core.$strip>;
1059
+ export type UpdateVariantRequest = z.infer<typeof updateVariantRequestSchema>;
1060
+ /**
1061
+ * Slider-numeric-kind discriminant (feature 002 T013/T021/T022). The API
1062
+ * `type=slider` form needs an extra hint so the service knows whether the
1063
+ * underlying DB `valueType` is `number` or `price`.
1064
+ */
1065
+ export declare const numericKindSchema: z.ZodEnum<{
1066
+ number: "number";
1067
+ price: "price";
1068
+ }>;
1069
+ export type NumericKind = z.infer<typeof numericKindSchema>;
1070
+ export declare const createAttributeRequestSchema: z.ZodObject<{
1071
+ key: z.ZodString;
1072
+ label: z.ZodRecord<z.ZodString, z.ZodString>;
1073
+ type: z.ZodOptional<z.ZodEnum<{
1074
+ number: "number";
1075
+ input: "input";
1076
+ select: "select";
1077
+ price: "price";
1078
+ multiselect: "multiselect";
1079
+ slider: "slider";
1080
+ }>>;
1081
+ numericKind: z.ZodOptional<z.ZodEnum<{
1082
+ number: "number";
1083
+ price: "price";
1084
+ }>>;
1085
+ valueType: z.ZodOptional<z.ZodEnum<{
1086
+ string: "string";
1087
+ number: "number";
1088
+ boolean: "boolean";
1089
+ date: "date";
1090
+ enum: "enum";
1091
+ select: "select";
1092
+ price: "price";
1093
+ multiselect: "multiselect";
1094
+ }>>;
1095
+ enumValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
1096
+ isSearchable: z.ZodBoolean;
1097
+ isFilterable: z.ZodBoolean;
1098
+ isVariantAxis: z.ZodBoolean;
1099
+ displayAsSlider: z.ZodOptional<z.ZodBoolean>;
1100
+ isComparable: z.ZodOptional<z.ZodBoolean>;
1101
+ labelDefault: z.ZodOptional<z.ZodString>;
1102
+ isRequired: z.ZodOptional<z.ZodBoolean>;
1103
+ isPromoRule: z.ZodOptional<z.ZodBoolean>;
1104
+ filterPosition: z.ZodOptional<z.ZodNumber>;
1105
+ isVisibleOnProductPage: z.ZodOptional<z.ZodBoolean>;
1106
+ channelScoped: z.ZodOptional<z.ZodBoolean>;
1107
+ languageScoped: z.ZodOptional<z.ZodBoolean>;
1108
+ massEditable: z.ZodOptional<z.ZodBoolean>;
1109
+ quickSearchable: z.ZodOptional<z.ZodBoolean>;
1110
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
1111
+ value: z.ZodString;
1112
+ label: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1113
+ labelDefault: z.ZodString;
1114
+ isDefault: z.ZodOptional<z.ZodBoolean>;
1115
+ sortOrder: z.ZodOptional<z.ZodNumber>;
1116
+ }, z.core.$strip>>>;
1117
+ }, z.core.$strip>;
1118
+ export type CreateAttributeRequest = z.infer<typeof createAttributeRequestSchema>;
1119
+ export declare const updateAttributeRequestSchema: z.ZodObject<{
1120
+ label: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1121
+ type: z.ZodOptional<z.ZodEnum<{
1122
+ number: "number";
1123
+ input: "input";
1124
+ select: "select";
1125
+ price: "price";
1126
+ multiselect: "multiselect";
1127
+ slider: "slider";
1128
+ }>>;
1129
+ numericKind: z.ZodOptional<z.ZodEnum<{
1130
+ number: "number";
1131
+ price: "price";
1132
+ }>>;
1133
+ enumValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
1134
+ isSearchable: z.ZodOptional<z.ZodBoolean>;
1135
+ isFilterable: z.ZodOptional<z.ZodBoolean>;
1136
+ isVariantAxis: z.ZodOptional<z.ZodBoolean>;
1137
+ displayAsSlider: z.ZodOptional<z.ZodBoolean>;
1138
+ isComparable: z.ZodOptional<z.ZodBoolean>;
1139
+ labelDefault: z.ZodOptional<z.ZodString>;
1140
+ isRequired: z.ZodOptional<z.ZodBoolean>;
1141
+ isPromoRule: z.ZodOptional<z.ZodBoolean>;
1142
+ filterPosition: z.ZodOptional<z.ZodNumber>;
1143
+ isVisibleOnProductPage: z.ZodOptional<z.ZodBoolean>;
1144
+ channelScoped: z.ZodOptional<z.ZodBoolean>;
1145
+ languageScoped: z.ZodOptional<z.ZodBoolean>;
1146
+ massEditable: z.ZodOptional<z.ZodBoolean>;
1147
+ quickSearchable: z.ZodOptional<z.ZodBoolean>;
1148
+ }, z.core.$strict>;
1149
+ export type UpdateAttributeRequest = z.infer<typeof updateAttributeRequestSchema>;
1150
+ /**
1151
+ * Feature 061 — admin attribute payload returned by
1152
+ * `GET/POST/PATCH /api/v1/admin/catalog/attributes*`. Formalizes the shape
1153
+ * `serializeAdminAttribute` has emitted since features 002/012/022/023/039 and
1154
+ * adds the single additive field `customFieldDefinitionId` — the backing
1155
+ * product-host Custom Field definition (contracts/attribute-admin-api.md).
1156
+ * All request schemas above are byte-compatible and unchanged (FR-007, SC-003).
1157
+ */
1158
+ export declare const adminAttributeResponseSchema: z.ZodObject<{
1159
+ id: z.ZodString;
1160
+ key: z.ZodString;
1161
+ label: z.ZodRecord<z.ZodString, z.ZodString>;
1162
+ labelDefault: z.ZodString;
1163
+ type: z.ZodEnum<{
1164
+ number: "number";
1165
+ input: "input";
1166
+ select: "select";
1167
+ price: "price";
1168
+ multiselect: "multiselect";
1169
+ slider: "slider";
1170
+ }>;
1171
+ numericKind: z.ZodOptional<z.ZodEnum<{
1172
+ number: "number";
1173
+ price: "price";
1174
+ }>>;
1175
+ valueType: z.ZodEnum<{
1176
+ string: "string";
1177
+ number: "number";
1178
+ boolean: "boolean";
1179
+ date: "date";
1180
+ enum: "enum";
1181
+ select: "select";
1182
+ price: "price";
1183
+ multiselect: "multiselect";
1184
+ }>;
1185
+ enumValues: z.ZodNullable<z.ZodArray<z.ZodString>>;
1186
+ isSearchable: z.ZodBoolean;
1187
+ isFilterable: z.ZodBoolean;
1188
+ isVariantAxis: z.ZodBoolean;
1189
+ displayAsSlider: z.ZodBoolean;
1190
+ isComparable: z.ZodBoolean;
1191
+ isRequired: z.ZodBoolean;
1192
+ isPromoRule: z.ZodBoolean;
1193
+ filterPosition: z.ZodNumber;
1194
+ isVisibleOnProductPage: z.ZodBoolean;
1195
+ massEditable: z.ZodBoolean;
1196
+ quickSearchable: z.ZodBoolean;
1197
+ customFieldDefinitionId: z.ZodString;
1198
+ createdAt: z.ZodString;
1199
+ updatedAt: z.ZodString;
1200
+ }, z.core.$strip>;
1201
+ export type AdminAttributeResponse = z.infer<typeof adminAttributeResponseSchema>;
1202
+ export declare const createCategoryRequestSchema: z.ZodObject<{
1203
+ parentCategoryId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1204
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
1205
+ slug: z.ZodString;
1206
+ sortOrder: z.ZodOptional<z.ZodNumber>;
1207
+ isActive: z.ZodOptional<z.ZodBoolean>;
1208
+ }, z.core.$strip>;
1209
+ export type CreateCategoryRequest = z.infer<typeof createCategoryRequestSchema>;
1210
+ export declare const updateCategoryRequestSchema: z.ZodObject<{
1211
+ parentCategoryId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1212
+ name: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1213
+ slug: z.ZodOptional<z.ZodString>;
1214
+ sortOrder: z.ZodOptional<z.ZodNumber>;
1215
+ isActive: z.ZodOptional<z.ZodBoolean>;
1216
+ mainImageAssetId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1217
+ customFieldValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
1218
+ }, z.core.$strict>;
1219
+ export type UpdateCategoryRequest = z.infer<typeof updateCategoryRequestSchema>;
1220
+ /**
1221
+ * The orderings the storefront listing accepts.
1222
+ *
1223
+ * Feature 086 adds `price` / `-price`, following the convention the two `name`
1224
+ * members set: the bare member ascends, the `-` prefix descends. "Price" is the
1225
+ * **viewer's own** resolved unit price at quantity 1 — the figure the card
1226
+ * renders — never a stored base price, a channel price or anything a search
1227
+ * index carries.
1228
+ */
1229
+ export declare const productListSortSchema: z.ZodEnum<{
1230
+ name: "name";
1231
+ price: "price";
1232
+ relevance: "relevance";
1233
+ "-createdAt": "-createdAt";
1234
+ "-name": "-name";
1235
+ "-price": "-price";
1236
+ }>;
1237
+ export type ProductListSort = z.infer<typeof productListSortSchema>;
1238
+ /** The two orderings feature 086 added, as a narrowing a consumer can reuse. */
1239
+ export declare function isPriceSort(sort: ProductListSort | undefined): sort is 'price' | '-price';
1240
+ export declare const productListQuerySchema: z.ZodObject<{
1241
+ q: z.ZodOptional<z.ZodString>;
1242
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
1243
+ cursor: z.ZodOptional<z.ZodString>;
1244
+ sort: z.ZodOptional<z.ZodEnum<{
1245
+ name: "name";
1246
+ price: "price";
1247
+ relevance: "relevance";
1248
+ "-createdAt": "-createdAt";
1249
+ "-name": "-name";
1250
+ "-price": "-price";
1251
+ }>>;
1252
+ changedSince: z.ZodOptional<z.ZodString>;
1253
+ minPrice: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1254
+ maxPrice: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
1255
+ }, z.core.$strip>;
1256
+ export type ProductListQuery = z.infer<typeof productListQuerySchema>;
1257
+ /**
1258
+ * What the listing surface may offer this viewer on this page — feature 086 /
1259
+ * FR-023.
1260
+ *
1261
+ * It rides on the listing response because the storefront has to decide whether
1262
+ * to *render* the price controls, and the answer depends on the viewer and the
1263
+ * channel: a non-public channel publishes no prices, and
1264
+ * `pricing.unauthenticated_display_mode = none` hides them until login. A
1265
+ * storefront that guessed would guess wrong on exactly the deployments that
1266
+ * care, and a control that offers an ordering the API refuses is a worse defect
1267
+ * than no control.
1268
+ *
1269
+ * It is **not** on the filter-definitions endpoint, which is anonymous and
1270
+ * shared (FR-010): a per-viewer answer may not travel on a response whose cache
1271
+ * key omits the viewer.
1272
+ */
1273
+ export declare const productListCapabilitiesSchema: z.ZodObject<{
1274
+ priceOrdering: z.ZodBoolean;
1275
+ }, z.core.$strip>;
1276
+ export type ProductListCapabilities = z.infer<typeof productListCapabilitiesSchema>;
1277
+ export declare const notifyWhenAvailableRequestSchema: z.ZodObject<{
1278
+ variantId: z.ZodOptional<z.ZodString>;
1279
+ }, z.core.$strip>;
1280
+ export type NotifyWhenAvailableRequest = z.infer<typeof notifyWhenAvailableRequestSchema>;
1281
+ export declare const notifyWhenAvailableResponseSchema: z.ZodObject<{
1282
+ subscriptionId: z.ZodString;
1283
+ requestedAt: z.ZodString;
1284
+ }, z.core.$strip>;
1285
+ export type NotifyWhenAvailableResponse = z.infer<typeof notifyWhenAvailableResponseSchema>;
1286
+ export declare const attributeSetSchema: z.ZodObject<{
1287
+ id: z.ZodString;
1288
+ code: z.ZodString;
1289
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
1290
+ description: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>;
1291
+ isSystem: z.ZodBoolean;
1292
+ attributeCount: z.ZodNumber;
1293
+ productCount: z.ZodNumber;
1294
+ createdAt: z.ZodString;
1295
+ updatedAt: z.ZodString;
1296
+ }, z.core.$strip>;
1297
+ export type AttributeSet = z.infer<typeof attributeSetSchema>;
1298
+ export declare const attributeSetAssignedAttributeSchema: z.ZodObject<{
1299
+ id: z.ZodString;
1300
+ key: z.ZodString;
1301
+ label: z.ZodRecord<z.ZodString, z.ZodString>;
1302
+ valueType: z.ZodEnum<{
1303
+ string: "string";
1304
+ number: "number";
1305
+ boolean: "boolean";
1306
+ date: "date";
1307
+ enum: "enum";
1308
+ select: "select";
1309
+ price: "price";
1310
+ multiselect: "multiselect";
1311
+ }>;
1312
+ position: z.ZodNumber;
1313
+ languageScoped: z.ZodBoolean;
1314
+ }, z.core.$strip>;
1315
+ export type AttributeSetAssignedAttribute = z.infer<typeof attributeSetAssignedAttributeSchema>;
1316
+ export declare const attributeSetDetailSchema: z.ZodObject<{
1317
+ id: z.ZodString;
1318
+ code: z.ZodString;
1319
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
1320
+ description: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>;
1321
+ isSystem: z.ZodBoolean;
1322
+ attributeCount: z.ZodNumber;
1323
+ productCount: z.ZodNumber;
1324
+ createdAt: z.ZodString;
1325
+ updatedAt: z.ZodString;
1326
+ attributes: z.ZodArray<z.ZodObject<{
1327
+ id: z.ZodString;
1328
+ key: z.ZodString;
1329
+ label: z.ZodRecord<z.ZodString, z.ZodString>;
1330
+ valueType: z.ZodEnum<{
1331
+ string: "string";
1332
+ number: "number";
1333
+ boolean: "boolean";
1334
+ date: "date";
1335
+ enum: "enum";
1336
+ select: "select";
1337
+ price: "price";
1338
+ multiselect: "multiselect";
1339
+ }>;
1340
+ position: z.ZodNumber;
1341
+ languageScoped: z.ZodBoolean;
1342
+ }, z.core.$strip>>;
1343
+ }, z.core.$strip>;
1344
+ export type AttributeSetDetail = z.infer<typeof attributeSetDetailSchema>;
1345
+ export declare const createAttributeSetRequestSchema: z.ZodObject<{
1346
+ code: z.ZodString;
1347
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
1348
+ description: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1349
+ attributeIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
1350
+ }, z.core.$strict>;
1351
+ export type CreateAttributeSetRequest = z.infer<typeof createAttributeSetRequestSchema>;
1352
+ export declare const updateAttributeSetRequestSchema: z.ZodObject<{
1353
+ code: z.ZodOptional<z.ZodString>;
1354
+ name: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1355
+ description: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodString>>>;
1356
+ }, z.core.$strict>;
1357
+ export type UpdateAttributeSetRequest = z.infer<typeof updateAttributeSetRequestSchema>;
1358
+ export declare const assignAttributesRequestSchema: z.ZodObject<{
1359
+ assignments: z.ZodArray<z.ZodObject<{
1360
+ attributeId: z.ZodString;
1361
+ position: z.ZodOptional<z.ZodNumber>;
1362
+ }, z.core.$strip>>;
1363
+ }, z.core.$strict>;
1364
+ export type AssignAttributesRequest = z.infer<typeof assignAttributesRequestSchema>;
1365
+ export declare const galleryLabelSchema: z.ZodEnum<{
1366
+ base_image: "base_image";
1367
+ small_image: "small_image";
1368
+ thumbnail: "thumbnail";
1369
+ }>;
1370
+ export type GalleryLabel = z.infer<typeof galleryLabelSchema>;
1371
+ export declare const galleryItemSchema: z.ZodObject<{
1372
+ id: z.ZodString;
1373
+ productId: z.ZodString;
1374
+ assetId: z.ZodString;
1375
+ position: z.ZodNumber;
1376
+ labels: z.ZodArray<z.ZodEnum<{
1377
+ base_image: "base_image";
1378
+ small_image: "small_image";
1379
+ thumbnail: "thumbnail";
1380
+ }>>;
1381
+ createdAt: z.ZodString;
1382
+ updatedAt: z.ZodString;
1383
+ }, z.core.$strip>;
1384
+ export type GalleryItem = z.infer<typeof galleryItemSchema>;
1385
+ export declare const createGalleryItemRequestSchema: z.ZodObject<{
1386
+ assetId: z.ZodString;
1387
+ position: z.ZodOptional<z.ZodNumber>;
1388
+ labels: z.ZodOptional<z.ZodArray<z.ZodEnum<{
1389
+ base_image: "base_image";
1390
+ small_image: "small_image";
1391
+ thumbnail: "thumbnail";
1392
+ }>>>;
1393
+ }, z.core.$strict>;
1394
+ export type CreateGalleryItemRequest = z.infer<typeof createGalleryItemRequestSchema>;
1395
+ export declare const updateGalleryItemRequestSchema: z.ZodObject<{
1396
+ position: z.ZodOptional<z.ZodNumber>;
1397
+ labels: z.ZodOptional<z.ZodArray<z.ZodEnum<{
1398
+ base_image: "base_image";
1399
+ small_image: "small_image";
1400
+ thumbnail: "thumbnail";
1401
+ }>>>;
1402
+ }, z.core.$strict>;
1403
+ export type UpdateGalleryItemRequest = z.infer<typeof updateGalleryItemRequestSchema>;
1404
+ export declare const reorderGalleryRequestSchema: z.ZodObject<{
1405
+ orderedGalleryItemIds: z.ZodArray<z.ZodString>;
1406
+ }, z.core.$strict>;
1407
+ export type ReorderGalleryRequest = z.infer<typeof reorderGalleryRequestSchema>;
1408
+ export declare const attachmentTypeSchema: z.ZodObject<{
1409
+ id: z.ZodString;
1410
+ code: z.ZodString;
1411
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
1412
+ position: z.ZodNumber;
1413
+ usageCount: z.ZodNumber;
1414
+ createdAt: z.ZodString;
1415
+ updatedAt: z.ZodString;
1416
+ }, z.core.$strip>;
1417
+ export type AttachmentType = z.infer<typeof attachmentTypeSchema>;
1418
+ export declare const createAttachmentTypeRequestSchema: z.ZodObject<{
1419
+ code: z.ZodString;
1420
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
1421
+ position: z.ZodOptional<z.ZodNumber>;
1422
+ }, z.core.$strict>;
1423
+ export type CreateAttachmentTypeRequest = z.infer<typeof createAttachmentTypeRequestSchema>;
1424
+ export declare const updateAttachmentTypeRequestSchema: z.ZodObject<{
1425
+ code: z.ZodOptional<z.ZodString>;
1426
+ name: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1427
+ position: z.ZodOptional<z.ZodNumber>;
1428
+ }, z.core.$strict>;
1429
+ export type UpdateAttachmentTypeRequest = z.infer<typeof updateAttachmentTypeRequestSchema>;
1430
+ export declare const productAttachmentSchema: z.ZodObject<{
1431
+ id: z.ZodString;
1432
+ productId: z.ZodString;
1433
+ assetId: z.ZodString;
1434
+ attachmentTypeId: z.ZodString;
1435
+ name: z.ZodString;
1436
+ description: z.ZodNullable<z.ZodString>;
1437
+ position: z.ZodNumber;
1438
+ createdAt: z.ZodString;
1439
+ updatedAt: z.ZodString;
1440
+ }, z.core.$strip>;
1441
+ export type ProductAttachment = z.infer<typeof productAttachmentSchema>;
1442
+ export declare const createAttachmentRequestSchema: z.ZodObject<{
1443
+ assetId: z.ZodString;
1444
+ attachmentTypeId: z.ZodString;
1445
+ name: z.ZodString;
1446
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1447
+ position: z.ZodOptional<z.ZodNumber>;
1448
+ }, z.core.$strict>;
1449
+ export type CreateAttachmentRequest = z.infer<typeof createAttachmentRequestSchema>;
1450
+ export declare const updateAttachmentRequestSchema: z.ZodObject<{
1451
+ attachmentTypeId: z.ZodOptional<z.ZodString>;
1452
+ name: z.ZodOptional<z.ZodString>;
1453
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1454
+ position: z.ZodOptional<z.ZodNumber>;
1455
+ }, z.core.$strict>;
1456
+ export type UpdateAttachmentRequest = z.infer<typeof updateAttachmentRequestSchema>;
1457
+ /**
1458
+ * A named ordering unit attached to a product (e.g. "Paleta" = 480 pieces).
1459
+ * Managed in the Inventory section of the admin product card; surfaced on the
1460
+ * storefront product page so buyers can order by the unit.
1461
+ */
1462
+ export declare const packagingUnitSchema: z.ZodObject<{
1463
+ id: z.ZodString;
1464
+ productId: z.ZodString;
1465
+ name: z.ZodString;
1466
+ baseQuantity: z.ZodNumber;
1467
+ position: z.ZodNumber;
1468
+ isDefault: z.ZodBoolean;
1469
+ createdAt: z.ZodString;
1470
+ updatedAt: z.ZodString;
1471
+ }, z.core.$strip>;
1472
+ export type PackagingUnitDto = z.infer<typeof packagingUnitSchema>;
1473
+ export declare const createPackagingUnitRequestSchema: z.ZodObject<{
1474
+ name: z.ZodString;
1475
+ baseQuantity: z.ZodNumber;
1476
+ isDefault: z.ZodOptional<z.ZodBoolean>;
1477
+ position: z.ZodOptional<z.ZodNumber>;
1478
+ }, z.core.$strict>;
1479
+ export type CreatePackagingUnitRequest = z.infer<typeof createPackagingUnitRequestSchema>;
1480
+ export declare const updatePackagingUnitRequestSchema: z.ZodObject<{
1481
+ name: z.ZodOptional<z.ZodString>;
1482
+ baseQuantity: z.ZodOptional<z.ZodNumber>;
1483
+ isDefault: z.ZodOptional<z.ZodBoolean>;
1484
+ position: z.ZodOptional<z.ZodNumber>;
1485
+ }, z.core.$strict>;
1486
+ export type UpdatePackagingUnitRequest = z.infer<typeof updatePackagingUnitRequestSchema>;
1487
+ export declare const reorderPackagingUnitsRequestSchema: z.ZodObject<{
1488
+ orderedIds: z.ZodArray<z.ZodString>;
1489
+ }, z.core.$strict>;
1490
+ export type ReorderPackagingUnitsRequest = z.infer<typeof reorderPackagingUnitsRequestSchema>;
1491
+ export declare const productLinkKindSchema: z.ZodEnum<{
1492
+ related: "related";
1493
+ up_sell: "up_sell";
1494
+ cross_sell: "cross_sell";
1495
+ }>;
1496
+ export type ProductLinkKind = z.infer<typeof productLinkKindSchema>;
1497
+ export declare const productLinkSchema: z.ZodObject<{
1498
+ id: z.ZodString;
1499
+ sourceProductId: z.ZodString;
1500
+ targetProductId: z.ZodString;
1501
+ kind: z.ZodEnum<{
1502
+ related: "related";
1503
+ up_sell: "up_sell";
1504
+ cross_sell: "cross_sell";
1505
+ }>;
1506
+ position: z.ZodNumber;
1507
+ }, z.core.$strip>;
1508
+ export type ProductLink = z.infer<typeof productLinkSchema>;
1509
+ /**
1510
+ * Bulk-create payload (T104). One transaction, all-or-nothing —
1511
+ * partial inserts on a duplicate or self-link MUST roll back the
1512
+ * entire batch (FR + research). Each entry pins its kind so admins
1513
+ * can submit a mixed batch in a single round trip.
1514
+ */
1515
+ export declare const bulkCreateLinksRequestSchema: z.ZodObject<{
1516
+ links: z.ZodArray<z.ZodObject<{
1517
+ targetProductId: z.ZodString;
1518
+ kind: z.ZodEnum<{
1519
+ related: "related";
1520
+ up_sell: "up_sell";
1521
+ cross_sell: "cross_sell";
1522
+ }>;
1523
+ position: z.ZodOptional<z.ZodNumber>;
1524
+ }, z.core.$strip>>;
1525
+ }, z.core.$strip>;
1526
+ export type BulkCreateLinksRequest = z.infer<typeof bulkCreateLinksRequestSchema>;
1527
+ export declare const reorderLinksRequestSchema: z.ZodObject<{
1528
+ linkIds: z.ZodArray<z.ZodString>;
1529
+ }, z.core.$strip>;
1530
+ export type ReorderLinksRequest = z.infer<typeof reorderLinksRequestSchema>;
1531
+ /**
1532
+ * Storefront-shape link entry (T107) — the listing carries enough Product
1533
+ * fields for a card render without a follow-up fetch. Inactive targets
1534
+ * are filtered out by `listForStorefront` so the storefront never sees
1535
+ * `status='archived'` rows.
1536
+ */
1537
+ export declare const groupedItemSchema: z.ZodObject<{
1538
+ id: z.ZodString;
1539
+ parentProductId: z.ZodString;
1540
+ childProductId: z.ZodString;
1541
+ quantity: z.ZodNumber;
1542
+ position: z.ZodNumber;
1543
+ }, z.core.$strip>;
1544
+ export type GroupedItem = z.infer<typeof groupedItemSchema>;
1545
+ export declare const createGroupedItemRequestSchema: z.ZodObject<{
1546
+ childProductId: z.ZodString;
1547
+ quantity: z.ZodNumber;
1548
+ position: z.ZodOptional<z.ZodNumber>;
1549
+ }, z.core.$strict>;
1550
+ export type CreateGroupedItemRequest = z.infer<typeof createGroupedItemRequestSchema>;
1551
+ export declare const updateGroupedItemRequestSchema: z.ZodObject<{
1552
+ quantity: z.ZodOptional<z.ZodNumber>;
1553
+ position: z.ZodOptional<z.ZodNumber>;
1554
+ }, z.core.$strict>;
1555
+ export type UpdateGroupedItemRequest = z.infer<typeof updateGroupedItemRequestSchema>;
1556
+ export declare const bundleSlotOptionSchema: z.ZodObject<{
1557
+ id: z.ZodString;
1558
+ slotId: z.ZodString;
1559
+ optionProductId: z.ZodString;
1560
+ defaultQuantity: z.ZodNumber;
1561
+ position: z.ZodNumber;
1562
+ }, z.core.$strip>;
1563
+ export type BundleSlotOption = z.infer<typeof bundleSlotOptionSchema>;
1564
+ export declare const bundleSlotSchema: z.ZodObject<{
1565
+ id: z.ZodString;
1566
+ parentProductId: z.ZodString;
1567
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
1568
+ minQuantity: z.ZodNumber;
1569
+ maxQuantity: z.ZodNumber;
1570
+ position: z.ZodNumber;
1571
+ options: z.ZodArray<z.ZodObject<{
1572
+ id: z.ZodString;
1573
+ slotId: z.ZodString;
1574
+ optionProductId: z.ZodString;
1575
+ defaultQuantity: z.ZodNumber;
1576
+ position: z.ZodNumber;
1577
+ }, z.core.$strip>>;
1578
+ }, z.core.$strip>;
1579
+ export type BundleSlot = z.infer<typeof bundleSlotSchema>;
1580
+ export declare const createBundleSlotRequestSchema: z.ZodObject<{
1581
+ name: z.ZodRecord<z.ZodString, z.ZodString>;
1582
+ minQuantity: z.ZodOptional<z.ZodNumber>;
1583
+ maxQuantity: z.ZodNumber;
1584
+ position: z.ZodOptional<z.ZodNumber>;
1585
+ }, z.core.$strict>;
1586
+ export type CreateBundleSlotRequest = z.infer<typeof createBundleSlotRequestSchema>;
1587
+ export declare const updateBundleSlotRequestSchema: z.ZodObject<{
1588
+ name: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
1589
+ minQuantity: z.ZodOptional<z.ZodNumber>;
1590
+ maxQuantity: z.ZodOptional<z.ZodNumber>;
1591
+ position: z.ZodOptional<z.ZodNumber>;
1592
+ }, z.core.$strict>;
1593
+ export type UpdateBundleSlotRequest = z.infer<typeof updateBundleSlotRequestSchema>;
1594
+ export declare const createBundleSlotOptionRequestSchema: z.ZodObject<{
1595
+ optionProductId: z.ZodString;
1596
+ defaultQuantity: z.ZodOptional<z.ZodNumber>;
1597
+ position: z.ZodOptional<z.ZodNumber>;
1598
+ }, z.core.$strict>;
1599
+ export type CreateBundleSlotOptionRequest = z.infer<typeof createBundleSlotOptionRequestSchema>;
1600
+ /**
1601
+ * Buyer's bundle configuration — what the storefront posts to the
1602
+ * `/bundle-configuration/validate` endpoint. One selection per slot,
1603
+ * referencing the chosen option's id and the buyer-picked quantity.
1604
+ */
1605
+ export declare const bundleConfigurationSelectionSchema: z.ZodObject<{
1606
+ slotId: z.ZodString;
1607
+ optionId: z.ZodString;
1608
+ quantity: z.ZodNumber;
1609
+ }, z.core.$strip>;
1610
+ export type BundleConfigurationSelection = z.infer<typeof bundleConfigurationSelectionSchema>;
1611
+ export declare const validateBundleConfigurationRequestSchema: z.ZodObject<{
1612
+ selections: z.ZodArray<z.ZodObject<{
1613
+ slotId: z.ZodString;
1614
+ optionId: z.ZodString;
1615
+ quantity: z.ZodNumber;
1616
+ }, z.core.$strip>>;
1617
+ }, z.core.$strict>;
1618
+ export type ValidateBundleConfigurationRequest = z.infer<typeof validateBundleConfigurationRequestSchema>;
1619
+ export declare const bundleValidationErrorSchema: z.ZodObject<{
1620
+ code: z.ZodEnum<{
1621
+ MIN_NOT_MET: "MIN_NOT_MET";
1622
+ MAX_EXCEEDED: "MAX_EXCEEDED";
1623
+ UNKNOWN_OPTION: "UNKNOWN_OPTION";
1624
+ }>;
1625
+ slotId: z.ZodOptional<z.ZodString>;
1626
+ message: z.ZodString;
1627
+ }, z.core.$strip>;
1628
+ export type BundleValidationError = z.infer<typeof bundleValidationErrorSchema>;
1629
+ export declare const bundleValidationResultSchema: z.ZodObject<{
1630
+ valid: z.ZodBoolean;
1631
+ errors: z.ZodArray<z.ZodObject<{
1632
+ code: z.ZodEnum<{
1633
+ MIN_NOT_MET: "MIN_NOT_MET";
1634
+ MAX_EXCEEDED: "MAX_EXCEEDED";
1635
+ UNKNOWN_OPTION: "UNKNOWN_OPTION";
1636
+ }>;
1637
+ slotId: z.ZodOptional<z.ZodString>;
1638
+ message: z.ZodString;
1639
+ }, z.core.$strip>>;
1640
+ resolvedSelections: z.ZodArray<z.ZodObject<{
1641
+ slotId: z.ZodString;
1642
+ optionId: z.ZodString;
1643
+ optionProductId: z.ZodString;
1644
+ quantity: z.ZodNumber;
1645
+ }, z.core.$strip>>;
1646
+ }, z.core.$strip>;
1647
+ export type BundleValidationResult = z.infer<typeof bundleValidationResultSchema>;
1648
+ export declare const productLinkSummarySchema: z.ZodObject<{
1649
+ id: z.ZodString;
1650
+ kind: z.ZodEnum<{
1651
+ related: "related";
1652
+ up_sell: "up_sell";
1653
+ cross_sell: "cross_sell";
1654
+ }>;
1655
+ position: z.ZodNumber;
1656
+ product: z.ZodObject<{
1657
+ id: z.ZodString;
1658
+ sku: z.ZodString;
1659
+ slug: z.ZodString;
1660
+ name: z.ZodString;
1661
+ primaryAssetUrl: z.ZodNullable<z.ZodString>;
1662
+ price: z.ZodNullable<z.ZodObject<{
1663
+ amount: z.ZodNumber;
1664
+ currency: z.ZodString;
1665
+ }, z.core.$strip>>;
1666
+ }, z.core.$strip>;
1667
+ }, z.core.$strip>;
1668
+ export type ProductLinkSummary = z.infer<typeof productLinkSummarySchema>;
1669
+ /**
1670
+ * A product as it crosses a module boundary — a plain shape, never the ORM
1671
+ * entity (FR-011). Twenty modules read this row; between them they touch
1672
+ * nearly every column, which is why the record mirrors the table rather than
1673
+ * narrowing it. What it is not is the *class*: a consumer cannot call a method
1674
+ * on it, cannot persist it, and cannot pull a relation off it.
1675
+ */
1676
+ export interface CatalogProductRecord {
1677
+ id: string;
1678
+ sku: string;
1679
+ slug: string;
1680
+ type: ProductType;
1681
+ status: ProductStatus;
1682
+ /** Per-locale JSONB. Resolve with the caller's language chain. */
1683
+ name: Record<string, string>;
1684
+ description: Record<string, string>;
1685
+ /** Per-product override of the global stock mode; `null` ⇒ inherit. */
1686
+ stockMode: StockMode | null;
1687
+ visibility: ProductVisibility;
1688
+ /** JSONB `{ attributeKey: value }`, validated against the attribute set. */
1689
+ attributeValues: Record<string, unknown>;
1690
+ allowedOrganizationIds: string[];
1691
+ attributeSetId: string;
1692
+ downloadAssetId: string | null;
1693
+ downloadUrl: string | null;
1694
+ createdAt: Date;
1695
+ updatedAt: Date;
1696
+ archivedAt: Date | null;
1697
+ deletedAt: Date | null;
1698
+ manageStock: boolean;
1699
+ backorderEnabled: boolean;
1700
+ lowStockThreshold: number | null;
1701
+ lowStockThresholdMode: 'cumulative' | 'per_warehouse';
1702
+ fulfilmentStrategy: FulfilmentStrategy | null;
1703
+ fulfilmentStrategyWarehouseOrder: string[] | null;
1704
+ }
1705
+ /**
1706
+ * Who is asking for a product — the single input every enforcing read path
1707
+ * needs beyond the row itself (issue #227).
1708
+ *
1709
+ * Two fields rather than one, because `organizationId === null` conflates two
1710
+ * callers that the `logged_in_only` visibility distinguishes: an anonymous
1711
+ * crawler, and a signed-in buyer whose account carries no Organization (the
1712
+ * guest-style Customer of feature 026). The first must not see a
1713
+ * `logged_in_only` row; the second must.
1714
+ *
1715
+ * An `organizationId` is never asserted by the caller: it comes off the
1716
+ * resolved actor — the customer session's Organization, or a bound API key's.
1717
+ */
1718
+ export interface ProductAudience {
1719
+ /**
1720
+ * The asking buyer's Organization, or `null` when the caller has none.
1721
+ * Matched against `allowed_organization_ids` by membership, never by
1722
+ * prefix or by comparing the array to anything.
1723
+ */
1724
+ readonly organizationId: string | null;
1725
+ /** `true` for any caller the platform has identified; `false` for the public. */
1726
+ readonly authenticated: boolean;
1727
+ }
1728
+ /**
1729
+ * The most restrictive audience there is. Anything it may see, every other
1730
+ * audience may see too — which is what makes it the right default for a path
1731
+ * that has not yet been taught to resolve its caller, and the right constant
1732
+ * for a test that means "the public".
1733
+ */
1734
+ export declare const ANONYMOUS_PRODUCT_AUDIENCE: ProductAudience;
1735
+ /**
1736
+ * Does this audience get to see this product?
1737
+ *
1738
+ * **This is the platform's one answer.** `Product.visibility` and
1739
+ * `Product.allowedOrganizationIds` have been persisted, defaulted and
1740
+ * operator-editable since the foundation migration, and until issue #227 a
1741
+ * single read path out of two dozen enforced them — `catalog`'s quick-search,
1742
+ * repaired for issue #174 after a buyer's type-ahead disclosed products
1743
+ * restricted to other organisations. Every other surface answered the question
1744
+ * its own way or not at all, so the repair starts by making the question have
1745
+ * one answer that a listing, a PDP, a search hit, a cart line, a comparison and
1746
+ * a feed row can all reach.
1747
+ *
1748
+ * It lives in `@endora-commerce/contracts` rather than in `catalog` because the record it
1749
+ * reads is already published here: twenty modules hold a
1750
+ * {@link CatalogProductRecord}, both columns are on it, and a predicate over a
1751
+ * published shape needs no port, no manifest edge and no `catalog` on the other
1752
+ * end of a call. A module that holds the row can enforce; a module that cannot
1753
+ * hold the row has nothing to enforce over.
1754
+ *
1755
+ * The rule, in the order it is decided:
1756
+ *
1757
+ * 1. **A non-empty `allowedOrganizationIds` decides alone**, and it restricts
1758
+ * whatever `visibility` says — `public` included. `public` with an
1759
+ * allow-list naming three organisations is a state an operator can save
1760
+ * today, and reading it as "public wins" discloses exactly the rows the
1761
+ * operator named someone else on.
1762
+ * 2. Otherwise the allow-list is empty and the answer is `visibility`'s alone:
1763
+ * `public` to everybody; `logged_in_only` to any authenticated caller;
1764
+ * `organization_restricted` **to nobody**. That last one is the reading
1765
+ * that surprises: the restriction was asked for and names no organisation,
1766
+ * so the permissive reading of it would disclose the row to the whole
1767
+ * world.
1768
+ *
1769
+ * ## The two SQL restatements of this rule, and how they differ
1770
+ *
1771
+ * A predicate over a record cannot be pushed into a query, and two read paths
1772
+ * must filter in SQL rather than after it. So `catalog` states this rule twice
1773
+ * more, in SQL, and the two statements are **not** copies of each other — they
1774
+ * answer for different audiences and are meant to differ (issue #262):
1775
+ *
1776
+ * - **`catalog-quick-search.service.ts`** answers for a **signed-in buyer**.
1777
+ * `CatalogQuickSearchParams.organizationId` is required and there is no
1778
+ * anonymous spelling, so the audience is
1779
+ * `{ organizationId, authenticated: true }` by construction. It is the
1780
+ * restatement where `@>` containment over the JSONB array does real work:
1781
+ * the buyer's id has to be an *element* of the allow-list rather than a
1782
+ * substring of the serialised bag. It is SQL because the statement carries
1783
+ * a `limit`, and a post-filter would hand a buyer a short page — or an
1784
+ * empty one — while visible rows waited behind the restricted ones.
1785
+ * - **`catalog-product-filter.service.ts`'s `sellableFloor`** answers for
1786
+ * {@link ANONYMOUS_PRODUCT_AUDIENCE}, because the port's only consumer is a
1787
+ * product feed and a feed is read by Google. With no organisation to
1788
+ * contain, the containment branch can never match, so that restatement
1789
+ * collapses to two equalities — `visibility = 'public'` and an empty
1790
+ * allow-list. It is SQL because `countSellable` is the number an operator
1791
+ * is shown before saving and `listSellable` is what the next run emits, and
1792
+ * the two must be one query's answer.
1793
+ *
1794
+ * Neither is licensed to drift toward the other: the containment clause would
1795
+ * be dead weight in the feed floor, and the two equalities would hide from a
1796
+ * buyer every row his own organisation is named on. What keeps both honest is
1797
+ * that each has a parity test which **derives** its expectation from this
1798
+ * function over `productVisibilitySchema.options`, so a fourth visibility value
1799
+ * forces every side to be decided rather than letting one keep an accidental
1800
+ * default — this predicate falls through to `return true`, both SQL sites fail
1801
+ * closed:
1802
+ *
1803
+ * - `backend/test/integration/catalog/quick-search-audience-parity.test.ts`
1804
+ * — visibility × (empty, own org, another org, several including own,
1805
+ * a near-miss string) × three viewers;
1806
+ * - `backend/test/integration/catalog/product-filter-port.test.ts`
1807
+ * — visibility × (empty, non-empty) for the anonymous audience, on
1808
+ * `listSellable` and `countSellable` alike.
1809
+ *
1810
+ * `backend/test/unit/catalog/product-visibility-predicate.test.ts` is the truth
1811
+ * table all three are read against.
1812
+ *
1813
+ * Unifying the three into one shared SQL fragment was considered and refused:
1814
+ * the builder would have to live here, and a fragment knows table and column
1815
+ * names — the persistence shape. This package knows API shapes and depends on
1816
+ * `zod` alone (FR-034). Keeping that line is worth more than removing the
1817
+ * duplication, so the duplication is kept and pinned instead.
1818
+ *
1819
+ * What this predicate is **not** is the channel answer. Channel scoping is
1820
+ * Principle XII's, travels through `sales_channel_products` and the sanctioned
1821
+ * bridge accessors, and is a second filter every buyer-facing path owes on top
1822
+ * of this one.
1823
+ *
1824
+ * That second filter has two spellings and neither is here, by an owner ruling
1825
+ * of 2026-08-21 (issue #259): the channel is a property of the **request**, not
1826
+ * of the viewer's relationship to the product, so folding it in would merge two
1827
+ * questions and make this predicate asynchronous. The **view** side spells it
1828
+ * as `CatalogQueryService.filterByChannel`; the **acquisition** side — cart
1829
+ * add, comparison add, a quote line, a saved list, a pasted quick-order SKU —
1830
+ * spells it as `productIdsInRequestChannel` in
1831
+ * `backend/src/kernel/sales-channels/request-channel-assortment.ts`. Every one
1832
+ * of those refuses out-of-assortment with the *same* answer it gives a
1833
+ * restricted row and an absent one, so the pair cannot be used to enumerate an
1834
+ * operator's private assortment.
1835
+ *
1836
+ * **Two re-acquisition paths are exempt, by the same ruling**: `orders`'
1837
+ * reorder and `quote_requests`' `convertToOrder`. Neither names a product the
1838
+ * caller supplied — each rebuilds a cart from lines the buyer already holds a
1839
+ * commitment on, a placed order or a quote the seller approved at agreed
1840
+ * prices — and refusing would strand a buyer holding an approved quote they
1841
+ * cannot act on. Read that as decided, not as the two seams that were missed;
1842
+ * each carries the reason at its own call site, including the second-order
1843
+ * consequence that makes the obvious repair of the first one wrong. Two
1844
+ * operator surfaces are exempt on the operator's-permission ground instead:
1845
+ * `RfqAdminService.createOnBehalf` and `quick_order`'s `'unrestricted'` import
1846
+ * arm, both of which say so where they stand.
1847
+ */
1848
+ export declare function isProductVisibleTo(product: Pick<CatalogProductRecord, 'visibility' | 'allowedOrganizationIds'>, audience: ProductAudience): boolean;
1849
+ /** A category row, as the eight modules that read one see it. */
1850
+ export interface CatalogCategoryRecord {
1851
+ id: string;
1852
+ parentCategoryId: string | null;
1853
+ name: Record<string, string>;
1854
+ slug: string;
1855
+ sortOrder: number;
1856
+ metaTitleOverride: Record<string, string> | null;
1857
+ metaDescriptionOverride: Record<string, string> | null;
1858
+ customFieldValues: Record<string, unknown>;
1859
+ isActive: boolean;
1860
+ inventoryThresholdHigh: number | null;
1861
+ inventoryThresholdMedium: number | null;
1862
+ inventoryThresholdLow: number | null;
1863
+ mainImageAssetId: string | null;
1864
+ createdAt: Date;
1865
+ updatedAt: Date;
1866
+ deletedAt: Date | null;
1867
+ }
1868
+ /**
1869
+ * One `product ↔ category` assignment, with the category's slug alongside.
1870
+ *
1871
+ * The slug rides on the row because every caller that reads assignments in
1872
+ * bulk is building a projection keyed by product — a search document, a feed
1873
+ * line — and would otherwise follow every assignment with a category lookup.
1874
+ */
1875
+ export interface CatalogCategoryAssignmentRecord {
1876
+ productId: string;
1877
+ categoryId: string;
1878
+ slug: string;
1879
+ }
1880
+ /** A configurable product's variant row. */
1881
+ export interface CatalogProductVariantRecord {
1882
+ id: string;
1883
+ parentProductId: string;
1884
+ sku: string;
1885
+ variantAttributeValues: Record<string, unknown>;
1886
+ /** Decimal string, or `null` when the variant inherits the parent's price. */
1887
+ priceOverride: string | null;
1888
+ stockLevel: number | null;
1889
+ createdAt: Date;
1890
+ updatedAt: Date;
1891
+ }
1892
+ /** A packaging unit — "box of 12" — a cart line may be placed in. */
1893
+ export interface CatalogPackagingUnitRecord {
1894
+ id: string;
1895
+ productId: string;
1896
+ name: string;
1897
+ baseQuantity: number;
1898
+ position: number;
1899
+ isDefault: boolean;
1900
+ createdAt: Date;
1901
+ updatedAt: Date;
1902
+ }
1903
+ /** A directed product↔product relation (cross-sell, up-sell, related). */
1904
+ export interface CatalogProductLinkRow {
1905
+ id: string;
1906
+ sourceProductId: string;
1907
+ targetProductId: string;
1908
+ kind: ProductLinkKind;
1909
+ position: number;
1910
+ }
1911
+ /** A per-channel / per-language override of one attribute value (feature 022). */
1912
+ export interface CatalogProductValueOverrideRecord {
1913
+ id: string;
1914
+ productId: string;
1915
+ attributeKey: string;
1916
+ channelId: string;
1917
+ languageCode: string | null;
1918
+ /** The stored value, wrapped so `null` and "absent" stay distinguishable. */
1919
+ value: {
1920
+ v: unknown;
1921
+ };
1922
+ }
1923
+ /**
1924
+ * Which products a lookup should consider.
1925
+ *
1926
+ * `liveOnly` excludes soft-deleted rows and `activeOnly` narrows further to
1927
+ * `status === 'active'`. Both default to `false`, which is the wider read —
1928
+ * and the correct default, because an inactive or soft-deleted product still
1929
+ * has to resolve from a historical order, invoice, RFQ or shopping list.
1930
+ */
1931
+ export interface CatalogProductLookupOptions {
1932
+ liveOnly?: boolean;
1933
+ activeOnly?: boolean;
1934
+ }
1935
+ /**
1936
+ * Container name: `catalogProductReadPort`. Owner: `catalog`.
1937
+ *
1938
+ * Forty-six of `catalog`'s inbound sites are a read of the `Product` entity,
1939
+ * and they reduce to four questions: by id, by ids, by sku, by skus. The rest
1940
+ * of this port is the three neighbouring tables the same callers reach for in
1941
+ * the same breath — variants, packaging units and links — plus the value
1942
+ * overrides the search indexer reads.
1943
+ *
1944
+ * When `catalog` is off every method fails closed, and that is the answer a
1945
+ * cart or an order line should get: pricing a line for a product the platform
1946
+ * will not read is worse than refusing the line.
1947
+ *
1948
+ * Whether `catalog` has an off state at all is its manifest's `activation` to
1949
+ * say, not this line's: a module declaring `nonDeactivatable` never enters one.
1950
+ */
1951
+ export interface CatalogProductReadPort {
1952
+ findById(id: string, options?: CatalogProductLookupOptions): Promise<CatalogProductRecord | null>;
1953
+ findByIds(ids: readonly string[], options?: CatalogProductLookupOptions): Promise<CatalogProductRecord[]>;
1954
+ findBySku(sku: string, options?: CatalogProductLookupOptions): Promise<CatalogProductRecord | null>;
1955
+ findBySkus(skus: readonly string[], options?: CatalogProductLookupOptions): Promise<CatalogProductRecord[]>;
1956
+ /** `count === ids.length` existence check, without loading the rows. */
1957
+ countByIds(ids: readonly string[]): Promise<number>;
1958
+ /** Every product, ordered by sku — the bulk export and the price-list backfill. */
1959
+ listAll(options?: CatalogProductLookupOptions): Promise<CatalogProductRecord[]>;
1960
+ /** Variants of the given parent products, ordered by sku. */
1961
+ listVariantsByProductIds(productIds: readonly string[]): Promise<CatalogProductVariantRecord[]>;
1962
+ findVariantsBySkus(skus: readonly string[]): Promise<CatalogProductVariantRecord[]>;
1963
+ /** A variant, but only if it belongs to that parent. */
1964
+ findVariantInProduct(parentProductId: string, variantId: string): Promise<CatalogProductVariantRecord | null>;
1965
+ /** A packaging unit, but only if it belongs to that product. */
1966
+ findPackagingUnitInProduct(productId: string, packagingUnitId: string): Promise<CatalogPackagingUnitRecord | null>;
1967
+ /** Links out of the given products, optionally narrowed to one kind. */
1968
+ listLinksBySourceIds(sourceProductIds: readonly string[], kind?: ProductLinkKind): Promise<CatalogProductLinkRow[]>;
1969
+ /** Attribute-value overrides for the given products. */
1970
+ listValueOverridesByProductIds(productIds: readonly string[]): Promise<CatalogProductValueOverrideRecord[]>;
1971
+ }
1972
+ /**
1973
+ * Container name: `catalogCategoryReadPort`. Owner: `catalog`.
1974
+ *
1975
+ * Fourteen inbound sites read the `Category` entity: the inventory threshold
1976
+ * resolver walks it, `price_lists` walks the ancestor chain to resolve a
1977
+ * category rule, `product_feeds` maps the whole tree onto an external
1978
+ * taxonomy, `seo` renders a category page's meta tags.
1979
+ *
1980
+ * `ancestorsOf` is here rather than in `price_lists` because that module walks
1981
+ * the chain with a `findOne` per level today — a loop over the parent pointer
1982
+ * whose depth is data, in a module that does not own the table.
1983
+ *
1984
+ * **Owner off:** the seam fails closed — resolving this port throws
1985
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
1986
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
1987
+ * `activation` to say, not this line's: a module declaring
1988
+ * `nonDeactivatable` never enters one.
1989
+ */
1990
+ export interface CatalogCategoryReadPort {
1991
+ findById(id: string, options?: {
1992
+ liveOnly?: boolean;
1993
+ }): Promise<CatalogCategoryRecord | null>;
1994
+ findByIds(ids: readonly string[], options?: {
1995
+ liveOnly?: boolean;
1996
+ }): Promise<CatalogCategoryRecord[]>;
1997
+ findBySlug(slug: string): Promise<CatalogCategoryRecord | null>;
1998
+ /** `count === ids.length` existence check. */
1999
+ countByIds(ids: readonly string[]): Promise<number>;
2000
+ /** The whole tree, ordered by sort order then slug. */
2001
+ listAll(options?: {
2002
+ liveOnly?: boolean;
2003
+ }): Promise<CatalogCategoryRecord[]>;
2004
+ /** Categories carrying at least one inventory threshold override. */
2005
+ listWithInventoryThresholds(): Promise<CatalogCategoryRecord[]>;
2006
+ /**
2007
+ * The category and its ancestors, nearest-first. Empty when the id does not
2008
+ * resolve. A cycle is impossible — the write path enforces it — but the walk
2009
+ * is bounded anyway, because a corrupt `parent_id` should not hang a request.
2010
+ */
2011
+ ancestorsOf(categoryId: string): Promise<CatalogCategoryRecord[]>;
2012
+ /**
2013
+ * The category assignments of the given products (feature 075 / D-87).
2014
+ *
2015
+ * `product_categories` is a `catalog` table with no entity class, so the two
2016
+ * modules that needed it joined it in raw SQL — a boundary crossing that
2017
+ * names no import specifier and therefore compiled, gated by nothing.
2018
+ *
2019
+ * `activeOnly` drops assignments to a deactivated category. That is the
2020
+ * narrowing a customer-facing projection wants and the reason the option
2021
+ * exists rather than a hidden filter: an inactive category left in a search
2022
+ * document keeps working as a storefront PLP filter.
2023
+ */
2024
+ listAssignmentsForProducts(productIds: readonly string[], options?: {
2025
+ activeOnly?: boolean;
2026
+ }): Promise<CatalogCategoryAssignmentRecord[]>;
2027
+ /**
2028
+ * Distinct ids of the products assigned to `categoryId` or to any category
2029
+ * below it.
2030
+ *
2031
+ * The walk is **structural**: it filters neither `isActive` nor `deletedAt`.
2032
+ * The caller is re-projecting a subtree because a category just changed, and
2033
+ * the change that matters most is a deactivation — narrowing to live rows
2034
+ * would return nothing exactly when the stale projections need rewriting.
2035
+ * A caller that wants the live set narrows the rows it gets back.
2036
+ */
2037
+ listProductIdsInSubtree(categoryId: string): Promise<string[]>;
2038
+ /**
2039
+ * Ids of the products assigned to `categoryId` itself — not to anything below
2040
+ * it (feature 075 / D-87).
2041
+ *
2042
+ * The narrow sibling of {@link listProductIdsInSubtree}, and the two are not
2043
+ * interchangeable: a caller detaching products because one category left the
2044
+ * source tree must touch that category's own assignments and no descendant's,
2045
+ * because a descendant that is still mapped keeps its products.
2046
+ *
2047
+ * **Structural**, like the subtree walk: it filters neither `isActive` nor
2048
+ * `deletedAt`. The assignment is the fact being asked about, and a caller
2049
+ * removing one wants it gone whatever state the product is in.
2050
+ */
2051
+ listProductIdsInCategory(categoryId: string): Promise<string[]>;
2052
+ /**
2053
+ * Product ids assigned to each requested category **or to any live category
2054
+ * below it**, keyed by the requested id. Every id asked for is present, with
2055
+ * an empty set when nothing matches — so a criterion naming a deleted
2056
+ * category narrows to nothing rather than silently disappearing.
2057
+ *
2058
+ * **Not a batched {@link listProductIdsInSubtree}, and the two are not
2059
+ * interchangeable.** That one is structural, for a caller re-projecting
2060
+ * because a category changed. This one is a *selection*: the descendant walk
2061
+ * skips soft-deleted categories, because a rule that says "everything under
2062
+ * Footwear" must not keep publishing the products of a branch an operator
2063
+ * deleted. It is also **cycle-tolerant** — the walk carries a visited set per
2064
+ * root — where the structural read is a recursive CTE with no guard. The
2065
+ * write path forbids a cycle in `parent_category_id`; its guard is written to
2066
+ * tolerate a *pre-existing* one, so corrupt data is reachable here, and a
2067
+ * caller that assembled this from the structural read would hang.
2068
+ *
2069
+ * No channel scoping: the caller applies its own eligibility floor, which for
2070
+ * a feed is stricter than the storefront's.
2071
+ *
2072
+ * `product_feeds`' criteria compiler is the requirement (FR-025) — it must
2073
+ * evaluate category membership including descendants without learning the
2074
+ * shape of `product_categories` or of the tree. It reached this through a
2075
+ * composition root's closure over an unpublished container name until
2076
+ * `specs/110-instance-repository/` T118c.
2077
+ */
2078
+ expandCategoryProductIds(categoryIds: readonly string[]): Promise<Map<string, Set<string>>>;
2079
+ /**
2080
+ * How many **live** products sit in each of `categoryIds` — one grouped read,
2081
+ * not a count per id (feature 075 / D-87).
2082
+ *
2083
+ * Live means not soft-deleted: a deleted product is not in the category any
2084
+ * more in any sense an operator means, which is the filter the caller this
2085
+ * replaced had written into its own join. Categories with no live product are
2086
+ * omitted rather than returned as zero, so the caller decides what an absent
2087
+ * count renders as.
2088
+ */
2089
+ countLiveProductsByCategory(categoryIds: readonly string[]): Promise<CatalogCategoryProductCount[]>;
2090
+ }
2091
+ /** One category's live product count — see `countLiveProductsByCategory`. */
2092
+ export interface CatalogCategoryProductCount {
2093
+ categoryId: string;
2094
+ productCount: number;
2095
+ }
2096
+ /**
2097
+ * One row of a categories import.
2098
+ *
2099
+ * Absent fields are left unchanged on an existing row; `parentSlug: null` means
2100
+ * root. `slug` addresses the row: a slug that exists is updated, one that does
2101
+ * not is created, and a slug introduced earlier in the same call resolves as a
2102
+ * parent for a later one.
2103
+ */
2104
+ export interface CategoryImportRow {
2105
+ slug: string;
2106
+ parentSlug?: string | null;
2107
+ sortOrder?: number;
2108
+ /** Per-locale, merged into the stored JSONB rather than replacing it. */
2109
+ name?: Record<string, string>;
2110
+ isActive?: boolean;
2111
+ }
2112
+ /**
2113
+ * One row of a products import.
2114
+ *
2115
+ * `sku` addresses an existing product; the import creates none, because a
2116
+ * product needs an attribute set, a type and a slug that a flat sheet does not
2117
+ * carry.
2118
+ */
2119
+ export interface ProductImportRow {
2120
+ sku: string;
2121
+ status?: ProductStatus;
2122
+ visibility?: ProductVisibility;
2123
+ /** Per-locale, merged into the stored JSONB rather than replacing it. */
2124
+ name?: Record<string, string>;
2125
+ description?: Record<string, string>;
2126
+ }
2127
+ /**
2128
+ * Container name: `catalogBulkImportPort`. Owner: `catalog`.
2129
+ *
2130
+ * All-or-nothing per call: one transaction, one audit row, no partial commits.
2131
+ * A rejected row leaves the whole call applying nothing, which is what makes a
2132
+ * corrected re-upload safe — see {@link BulkImportReport}.
2133
+ *
2134
+ * With `catalog` off the call answers 503 `MODULE_DISABLED`; the caller is
2135
+ * expected to decide presence before offering the surface at all.
2136
+ *
2137
+ * Whether `catalog` has an off state at all is its manifest's `activation` to
2138
+ * say, not this line's: a module declaring `nonDeactivatable` never enters one.
2139
+ */
2140
+ export interface CatalogBulkImportPort {
2141
+ importCategories(rows: readonly CategoryImportRow[]): Promise<BulkImportReport>;
2142
+ importProducts(rows: readonly ProductImportRow[]): Promise<BulkImportReport>;
2143
+ }
2144
+ export interface CatalogAttributeOptionView {
2145
+ /** `custom_field_options` id. */
2146
+ id: string;
2147
+ value: string;
2148
+ label: Record<string, string>;
2149
+ labelDefault: string;
2150
+ isDefault: boolean;
2151
+ sortOrder: number;
2152
+ createdAt: Date;
2153
+ updatedAt: Date;
2154
+ }
2155
+ /**
2156
+ * The composed attribute view (feature 061,
2157
+ * `contracts/catalog-attribute-view.md`): the `custom_fields` definition and
2158
+ * the `product_attributes` extension row, joined, shaped like the pre-061
2159
+ * `ProductAttribute` so consumer rewires stay mechanical.
2160
+ */
2161
+ export interface CatalogAttributeView {
2162
+ /** Extension row id — the id the admin API has always exposed. */
2163
+ id: string;
2164
+ /** Backing `custom_field_definitions` id (host `product`). */
2165
+ customFieldDefinitionId: string;
2166
+ key: string;
2167
+ label: Record<string, string>;
2168
+ labelDefault: string;
2169
+ /** Legacy 8-value form, derived bijectively (feature 061 research §R7). */
2170
+ valueType: AttributeValueType;
2171
+ isRequired: boolean;
2172
+ options: CatalogAttributeOptionView[];
2173
+ isSearchable: boolean;
2174
+ isFilterable: boolean;
2175
+ isVariantAxis: boolean;
2176
+ displayAsSlider: boolean;
2177
+ isComparable: boolean;
2178
+ quickSearchable: boolean;
2179
+ isPromoRule: boolean;
2180
+ filterPosition: number;
2181
+ isVisibleOnProductPage: boolean;
2182
+ channelScoped: boolean;
2183
+ languageScoped: boolean;
2184
+ massEditable: boolean;
2185
+ createdAt: Date;
2186
+ updatedAt: Date;
2187
+ }
2188
+ export type CatalogAttributeFlag = 'isSearchable' | 'isFilterable' | 'isVariantAxis' | 'isComparable' | 'quickSearchable' | 'isPromoRule' | 'massEditable' | 'isVisibleOnProductPage';
2189
+ /**
2190
+ * Container name: `catalogAttributeReadPort`. Owner: `catalog`.
2191
+ *
2192
+ * The **only** sanctioned way any module — including `catalog`'s own route
2193
+ * serializers — reads product-attribute definitions (Principle I). `search`,
2194
+ * `comparisons`, `quick_order` and `pim_ergonode` all read it today by
2195
+ * importing the class.
2196
+ *
2197
+ * Freshness: the definitions half rides the custom-fields cache (invalidated
2198
+ * by every committed attribute Command, 5 s TTL fallback); the extension half
2199
+ * is a live query, so flag reads are always fresh.
2200
+ *
2201
+ * **Owner off:** the seam fails closed — resolving this port throws
2202
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2203
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2204
+ * `activation` to say, not this line's: a module declaring
2205
+ * `nonDeactivatable` never enters one.
2206
+ */
2207
+ export interface CatalogAttributeReadPort {
2208
+ listAll(): Promise<CatalogAttributeView[]>;
2209
+ getByIdOrKey(idOrKey: string): Promise<CatalogAttributeView | null>;
2210
+ listByFlag(flag: CatalogAttributeFlag): Promise<CatalogAttributeView[]>;
2211
+ /** `attributeKey → optionValue → labels`, for label resolution on read. */
2212
+ optionLabelIndex(): Promise<Map<string, Map<string, CatalogAttributeOptionLabels>>>;
2213
+ }
2214
+ /** The two label forms an option carries: per-locale, and the fallback. */
2215
+ export interface CatalogAttributeOptionLabels {
2216
+ label: Record<string, string>;
2217
+ labelDefault: string;
2218
+ }
2219
+ /** Which of the three quick-search predicates a hit satisfied. */
2220
+ export type CatalogQuickSearchField = 'sku' | 'name' | 'attribute';
2221
+ export interface CatalogQuickSearchParams {
2222
+ /** The raw needle. Matched case-insensitively, as a substring. */
2223
+ q: string;
2224
+ limit: number;
2225
+ /**
2226
+ * The channel the caller resolved for this request. Required, and there is
2227
+ * no "all channels" spelling: a channel is always resolved (feature 053), so
2228
+ * an optional parameter here could only mean "the caller forgot", and the
2229
+ * answer to that must not be the cross-channel catalogue.
2230
+ */
2231
+ salesChannelId: string;
2232
+ /**
2233
+ * The organisation the buyer is shopping on behalf of. Required, and — like
2234
+ * `salesChannelId` — there is no anonymous spelling: every transacting
2235
+ * customer has an Organization (Constitution XI), and the alternative to
2236
+ * requiring one is a caller that forgets and gets the unrestricted
2237
+ * catalogue. A surface with no signed-in buyer must not call this port.
2238
+ *
2239
+ * See {@link CatalogQuickSearchPort} for what it restricts.
2240
+ */
2241
+ organizationId: string;
2242
+ }
2243
+ export interface CatalogQuickSearchHit {
2244
+ productId: string;
2245
+ sku: string;
2246
+ slug: string;
2247
+ /** Per-locale JSONB as stored — the caller picks its own language. */
2248
+ name: Record<string, string>;
2249
+ status: ProductStatus;
2250
+ matchedOn: CatalogQuickSearchField[];
2251
+ }
2252
+ /**
2253
+ * Container name: `catalogQuickSearchPort`. Owner: `catalog`.
2254
+ *
2255
+ * The buyer-facing type-ahead behind `quick_order`'s CSV-free entry path
2256
+ * (feature 039 FR-011/FR-013), published here because the predicate is a
2257
+ * catalogue question in every part: which products are active, which are
2258
+ * visible on the channel being shopped, and which attribute values are
2259
+ * searchable at all — the last decided by the `quickSearchable` flag this
2260
+ * module owns, over the `attribute_values` JSONB layout this module owns.
2261
+ *
2262
+ * It exists because `quick_order` was answering it with a hand-written knex
2263
+ * `select` against `products`, which filtered `status = 'active'` and nothing
2264
+ * else (issue #174). No import specifier, so `check:module-boundary` read
2265
+ * clean; and no channel predicate, so a signed-in buyer's type-ahead returned
2266
+ * every active product on the platform whatever channel they were shopping —
2267
+ * Constitution XII, in the one place no static check was looking.
2268
+ *
2269
+ * ## What it restricts, and how strictly
2270
+ *
2271
+ * Channel scoping is the same `sales_channel_products` membership filter this
2272
+ * module's own public listing applies, and it fails closed to the empty set.
2273
+ *
2274
+ * `visibility` and `allowed_organization_ids` are applied too, on the
2275
+ * **restrictive** reading of both columns. A row is disclosed when
2276
+ *
2277
+ * - the buyer's `organizationId` appears in `allowed_organization_ids`; or
2278
+ * - `allowed_organization_ids` is empty **and** `visibility` is not
2279
+ * `organization_restricted`.
2280
+ *
2281
+ * Two consequences are deliberate. A non-empty allow-list restricts **whatever
2282
+ * the `visibility` column says** — `public` with an allow-list naming three
2283
+ * organisations is a state an operator can save today, and reading it as
2284
+ * "public wins" would let a type-ahead disclose exactly the rows the operator
2285
+ * named someone else on. And `organization_restricted` with an **empty**
2286
+ * allow-list is visible to nobody, rather than to everybody.
2287
+ * `logged_in_only` *is* disclosed, because `organizationId` is required: there
2288
+ * is no caller of this port that is not a signed-in buyer.
2289
+ *
2290
+ * A looser reading would differ on precisely those two rows, and only there.
2291
+ *
2292
+ * This is stricter than the rest of the module, and knowingly so. No other read
2293
+ * path in `catalog` enforces either column (the standing platform-wide gap),
2294
+ * but this surface requires a signed-in buyer, discloses SKU, slug and name for
2295
+ * every hit, and hands back an id that `POST /quick-order/build` accepts — so a
2296
+ * restriction an operator set on the product would otherwise be bypassed by
2297
+ * typing three characters. Closing the gap on the remaining surfaces is its own
2298
+ * change; leaving this one open until then is not the safe half of the choice.
2299
+ *
2300
+ * When `catalog` is off the call fails closed: `quick_order` declares `catalog`
2301
+ * in `dependencies`, and a type-ahead that cannot ask the catalogue has nothing
2302
+ * true to answer.
2303
+ *
2304
+ * Whether `catalog` has an off state at all is its manifest's `activation` to
2305
+ * say, not this line's: a module declaring `nonDeactivatable` never enters one.
2306
+ */
2307
+ export interface CatalogQuickSearchPort {
2308
+ quickSearch(params: CatalogQuickSearchParams): Promise<CatalogQuickSearchHit[]>;
2309
+ }
2310
+ /**
2311
+ * Scope flags for the **system** product attributes — the ones that are not
2312
+ * rows in `product_attributes` and therefore carry no DB-stored scope flags.
2313
+ *
2314
+ * Published as a **constant, not a port** (FR-013): `name` and `description`
2315
+ * are channel- and language-scoped because the product table stores them as
2316
+ * per-locale JSONB, which is a fact about the schema rather than about whether
2317
+ * a module is switched on. `search`'s indexer reads it to decide which
2318
+ * overrides to resolve.
2319
+ *
2320
+ * Adding another system attribute is a one-line change here plus a resolver
2321
+ * consumer. The reserved keys MUST NOT collide with `product_attributes.key`
2322
+ * — enforced at write time by the override-service validator.
2323
+ */
2324
+ export declare const SYSTEM_ATTRIBUTE_SCOPES: Readonly<Record<string, AttributeScope>>;
2325
+ export type SystemAttributeKey = keyof typeof SYSTEM_ATTRIBUTE_SCOPES;
2326
+ export declare function isSystemAttributeKey(key: string): key is SystemAttributeKey;
2327
+ /**
2328
+ * Resolve the effective scope of an attribute given its key and (for
2329
+ * user-defined attributes) its scope flags. Returns the system-pinned scope
2330
+ * when the key is reserved; falls back to the row's flags otherwise; returns
2331
+ * `{ false, false }` when neither applies (the caller should treat that as
2332
+ * global-only).
2333
+ */
2334
+ export declare function getAttributeScope(attributeKey: string, productAttributeRow?: {
2335
+ channelScoped: boolean;
2336
+ languageScoped: boolean;
2337
+ } | null): AttributeScope;
2338
+ /** Optional metadata attaching an audit entry to an admin mutation. */
2339
+ export interface CatalogAdminAuditContext {
2340
+ actorAdminUserId: string;
2341
+ impersonatedCustomerAccountId?: string | null;
2342
+ ipAddress?: string | null;
2343
+ userAgent?: string | null;
2344
+ requestId?: string | null;
2345
+ }
2346
+ /**
2347
+ * An attribute option as the write path returns it.
2348
+ *
2349
+ * Distinct from `AttributeOption`, the API DTO above, on one axis: the two
2350
+ * timestamps are `Date`, not an ISO string. That is what an in-process call
2351
+ * hands back, and serialising them here would mean every consumer parsing them
2352
+ * again.
2353
+ */
2354
+ export interface CatalogAttributeOptionResult {
2355
+ id: string;
2356
+ attributeId: string;
2357
+ value: string;
2358
+ label: Record<string, string>;
2359
+ labelDefault: string;
2360
+ isDefault: boolean;
2361
+ sortOrder: number;
2362
+ createdAt: Date;
2363
+ updatedAt: Date;
2364
+ }
2365
+ /** The eight flags `listAttributesByFlag` accepts — a superset of the read model's. */
2366
+ export type CatalogAdminAttributeFlag = 'isSearchable' | 'isFilterable' | 'isComparable' | 'isVariantAxis' | 'isPromoRule' | 'isVisibleOnProductPage' | 'isRequired' | 'isMassEditable';
2367
+ /**
2368
+ * Container name: `catalogProductWritePort`. Owner: `catalog`.
2369
+ *
2370
+ * `pim_ergonode` is the only consumer, and it is the whole reason this port is
2371
+ * narrow: an import run creates and updates products, attributes and variants,
2372
+ * and touches nothing else on `CatalogAdminService`'s considerable surface.
2373
+ *
2374
+ * `createProduct` and the two variant writes return **records**, where the
2375
+ * service returns entities. That substitution is the point of the port.
2376
+ *
2377
+ * **Owner off:** the seam fails closed — resolving this port throws
2378
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2379
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2380
+ * `activation` to say, not this line's: a module declaring
2381
+ * `nonDeactivatable` never enters one.
2382
+ */
2383
+ export interface CatalogProductWritePort {
2384
+ createProduct(req: CreateProductRequest, auditCtx?: CatalogAdminAuditContext): Promise<CatalogProductRecord>;
2385
+ updateProduct(id: string, req: UpdateProductRequest): Promise<CatalogProductRecord>;
2386
+ listAttributes(): Promise<CatalogAttributeView[]>;
2387
+ listAttributesByFlag(flag: CatalogAdminAttributeFlag): Promise<CatalogAttributeView[]>;
2388
+ createAttribute(req: CreateAttributeRequest): Promise<CatalogAttributeView>;
2389
+ updateAttributeByIdOrKey(idOrKey: string, req: UpdateAttributeRequest, auditCtx?: CatalogAdminAuditContext): Promise<CatalogAttributeView>;
2390
+ addAttributeOption(attributeIdOrKey: string, input: {
2391
+ value: string;
2392
+ label?: Record<string, string>;
2393
+ labelDefault: string;
2394
+ isDefault?: boolean;
2395
+ sortOrder?: number;
2396
+ }): Promise<CatalogAttributeOptionResult>;
2397
+ patchAttributeOption(optionId: string, input: {
2398
+ label?: Record<string, string>;
2399
+ labelDefault?: string;
2400
+ isDefault?: boolean;
2401
+ sortOrder?: number;
2402
+ }): Promise<CatalogAttributeOptionResult>;
2403
+ createVariant(parentProductId: string, req: CreateVariantRequest): Promise<CatalogProductVariantRecord>;
2404
+ updateVariant(parentProductId: string, variantId: string, req: UpdateVariantRequest): Promise<CatalogProductVariantRecord>;
2405
+ deleteVariant(parentProductId: string, variantId: string): Promise<void>;
2406
+ }
2407
+ /**
2408
+ * One channel/language override upsert — feature 022 / product-scope-overrides.
2409
+ * Used by PIM importers (feature 089) to land channel-locale-scoped names and
2410
+ * descriptions without reaching `product_value_overrides` directly (Principle XII).
2411
+ */
2412
+ export interface CatalogProductValueOverrideUpsert {
2413
+ attributeKey: string;
2414
+ channelId: string;
2415
+ languageCode: string | null;
2416
+ value: {
2417
+ v: unknown;
2418
+ };
2419
+ }
2420
+ /**
2421
+ * Container name: `catalogProductValueOverrideWritePort`. Owner: `catalog`.
2422
+ *
2423
+ * Bulk upsert of per-channel / per-language product attribute overrides. The
2424
+ * only consumer today is `pim_unopim` (feature 089 / FR-023); the admin SPA
2425
+ * still goes through `ProductOverridesService` inside catalog's own routes.
2426
+ *
2427
+ * **Owner off:** resolving this port throws `ModuleDisabledError` (503).
2428
+ */
2429
+ export interface CatalogProductValueOverrideWritePort {
2430
+ applyBulk(productId: string, input: {
2431
+ upserts: readonly CatalogProductValueOverrideUpsert[];
2432
+ }): Promise<void>;
2433
+ }
2434
+ /** The patch `updateCategory` accepts. Absent keys are left alone. */
2435
+ export interface UpdateCategoryInput {
2436
+ parentCategoryId?: string | null;
2437
+ name?: Record<string, string>;
2438
+ slug?: string;
2439
+ sortOrder?: number;
2440
+ /** Feature 013 / US5 — library asset rendered as the storefront main image. */
2441
+ mainImageAssetId?: string | null;
2442
+ /** Feature 055 — validated and merged against the definitions on write. */
2443
+ customFieldValues?: Record<string, unknown>;
2444
+ /**
2445
+ * Feature 068 — activation switch. `false` hides the category from every
2446
+ * customer-facing read while the admin tree keeps listing it.
2447
+ */
2448
+ isActive?: boolean;
2449
+ }
2450
+ /**
2451
+ * The three display-band thresholds a category may override. Absent keys are
2452
+ * left alone; an explicit `null` clears the override.
2453
+ */
2454
+ export interface CategoryInventoryThresholdPatch {
2455
+ high?: number | null;
2456
+ medium?: number | null;
2457
+ low?: number | null;
2458
+ }
2459
+ /**
2460
+ * Container name: `catalogCategoryWritePort`. Owner: `catalog`.
2461
+ *
2462
+ * `pim_ergonode` again, and again narrow: an import run lists the tree,
2463
+ * creates the categories it is missing and updates the ones that moved.
2464
+ *
2465
+ * `setInventoryThresholds` is the fourth method and belongs to a different
2466
+ * consumer: `inventory` stores the per-category half of its display-band
2467
+ * thresholds in three columns on this module's `categories` table, and wrote
2468
+ * them by holding the entity. It is deliberately **not** a key on
2469
+ * {@link UpdateCategoryInput}: `update` runs the `category.update` Command,
2470
+ * emits the search-reindex event and writes an audit row, none of which a
2471
+ * threshold patch did or should — `inventory` records one
2472
+ * `low_stock_threshold.update` summary row for the whole patch, and an
2473
+ * operator reading a category's history should not find a rename-shaped entry
2474
+ * for it. The columns' owner is still the question underneath, and the answer
2475
+ * that retires this method is moving them into `inventory_thresholds` with
2476
+ * `scopeKind = 'category'`, which is a data migration and not a cut.
2477
+ *
2478
+ * **Owner off:** the seam fails closed — resolving this port throws
2479
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2480
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2481
+ * `activation` to say, not this line's: a module declaring
2482
+ * `nonDeactivatable` never enters one.
2483
+ */
2484
+ export interface CatalogCategoryWritePort {
2485
+ listAll(): Promise<CatalogCategoryRecord[]>;
2486
+ create(input: CreateCategoryRequest): Promise<CatalogCategoryRecord>;
2487
+ update(id: string, input: UpdateCategoryInput): Promise<CatalogCategoryRecord>;
2488
+ /** Rejects an unknown or soft-deleted category with 404 `NOT_FOUND`. */
2489
+ setInventoryThresholds(id: string, patch: CategoryInventoryThresholdPatch): Promise<void>;
2490
+ }
2491
+ /**
2492
+ * Container name: `attributeSetService`. Owner: `catalog`.
2493
+ *
2494
+ * Already returns contract DTOs, so the port is the four methods
2495
+ * `pim_ergonode` calls and nothing else.
2496
+ *
2497
+ * **Owner off:** the seam fails closed — resolving this port throws
2498
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2499
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2500
+ * `activation` to say, not this line's: a module declaring
2501
+ * `nonDeactivatable` never enters one.
2502
+ */
2503
+ export interface CatalogAttributeSetPort {
2504
+ listSets(): Promise<AttributeSet[]>;
2505
+ getSetDetail(id: string): Promise<AttributeSetDetail>;
2506
+ createSet(input: CreateAttributeSetRequest): Promise<AttributeSetDetail>;
2507
+ assignAttributes(id: string, input: AssignAttributesRequest): Promise<AttributeSetDetail>;
2508
+ }
2509
+ /**
2510
+ * Container name: `attachmentService`. Owner: `catalog`.
2511
+ *
2512
+ * **Owner off:** the seam fails closed — resolving this port throws
2513
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2514
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2515
+ * `activation` to say, not this line's: a module declaring
2516
+ * `nonDeactivatable` never enters one.
2517
+ */
2518
+ export interface CatalogAttachmentPort {
2519
+ listTypes(): Promise<AttachmentType[]>;
2520
+ createType(req: CreateAttachmentTypeRequest): Promise<AttachmentType>;
2521
+ listAttachments(productId: string): Promise<ProductAttachment[]>;
2522
+ createAttachment(productId: string, req: CreateAttachmentRequest): Promise<ProductAttachment>;
2523
+ updateAttachment(productId: string, attachmentId: string, req: UpdateAttachmentRequest): Promise<ProductAttachment>;
2524
+ deleteAttachment(productId: string, attachmentId: string): Promise<void>;
2525
+ }
2526
+ /** Whether a gallery write may silently move a conflicting label off another item. */
2527
+ export interface CatalogGalleryWriteOptions {
2528
+ replaceConflictingLabels: boolean;
2529
+ }
2530
+ /**
2531
+ * One gallery item of a batch read, flattened to the three fields a caller
2532
+ * outside `catalog` builds an image list from: which product it belongs to,
2533
+ * which asset it points at, and where it sits in the operator's order.
2534
+ *
2535
+ * Deliberately not `GalleryItem`. The batch read exists for callers that walk a
2536
+ * page — or a whole sellable catalogue — and the label set, the timestamps and
2537
+ * the item id are all rows or columns those callers do not read; carrying them
2538
+ * would cost a second statement per batch for a field nobody looks at.
2539
+ */
2540
+ export interface CatalogGalleryBatchItem {
2541
+ productId: string;
2542
+ assetId: string;
2543
+ position: number;
2544
+ }
2545
+ /**
2546
+ * Container name: `galleryService`. Owner: `catalog`.
2547
+ *
2548
+ * **Owner off:** the seam fails closed — resolving this port throws
2549
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2550
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2551
+ * `activation` to say, not this line's: a module declaring
2552
+ * `nonDeactivatable` never enters one.
2553
+ */
2554
+ export interface CatalogGalleryPort {
2555
+ list(productId: string): Promise<GalleryItem[]>;
2556
+ /**
2557
+ * The gallery items of a **batch** of products, ordered by product and then
2558
+ * by the operator's position, in one statement.
2559
+ *
2560
+ * `list` cannot serve this caller and it is not a matter of taste: it takes a
2561
+ * single product, verifies it exists, and then runs two more queries, so a
2562
+ * page of 500 products costs 1500 round-trips where this costs one. That is
2563
+ * what kept a raw `select … from gallery_items` inside `product_feeds` — a
2564
+ * boundary crossing that names no import specifier and so compiled (feature
2565
+ * 075 / D-87).
2566
+ *
2567
+ * **A product id that resolves to nothing is data, not an error.** No row
2568
+ * comes back for it and the method does not throw: a caller holding an id
2569
+ * whose product has since been removed asks about the batch it has, not about
2570
+ * the batch it wishes it had. The two absences — "this product has an empty
2571
+ * gallery" and "this product is gone" — are therefore not distinguished here,
2572
+ * because both consumers render them identically (no image) and telling them
2573
+ * apart would need exactly the product-existence probe that makes `list`
2574
+ * unusable for a batch.
2575
+ */
2576
+ listForProducts(productIds: readonly string[]): Promise<CatalogGalleryBatchItem[]>;
2577
+ /**
2578
+ * The `base_image` url of each of a batch of products — `base_image` only,
2579
+ * with no fallback to `thumbnail` or to the first item by position.
2580
+ *
2581
+ * The returned map holds **one entry per requested id**, `null` where this
2582
+ * module has no `base_image` for it: the product carries no such label, the
2583
+ * product is gone, or the asset row behind the label no longer resolves. A
2584
+ * caller that renders a placeholder for all three (both of today's do) reads
2585
+ * one branch; the map's key set answering the request exactly is what lets it
2586
+ * index without re-checking membership.
2587
+ *
2588
+ * `catalog` answers this from its own `gallery_item_labels` and
2589
+ * `gallery_items` plus the asset read port it already holds, so a consumer
2590
+ * gets the url without joining `assets_library`'s table itself — which is the
2591
+ * third of the three reaches this method retires (feature 075 / D-87).
2592
+ */
2593
+ baseImageUrls(productIds: readonly string[]): Promise<Map<string, string | null>>;
2594
+ create(productId: string, req: CreateGalleryItemRequest, options: CatalogGalleryWriteOptions): Promise<GalleryItem>;
2595
+ delete(productId: string, itemId: string): Promise<void>;
2596
+ reorder(productId: string, orderedIds: string[]): Promise<void>;
2597
+ }
2598
+ /** One child line of a grouped product. */
2599
+ export interface CatalogGroupedItemRow {
2600
+ id: string;
2601
+ parentProductId: string;
2602
+ childProductId: string;
2603
+ quantity: number;
2604
+ position: number;
2605
+ }
2606
+ /**
2607
+ * Container name: `groupedService`. Owner: `catalog`.
2608
+ *
2609
+ * **Owner off:** the seam fails closed — resolving this port throws
2610
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2611
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2612
+ * `activation` to say, not this line's: a module declaring
2613
+ * `nonDeactivatable` never enters one.
2614
+ */
2615
+ export interface CatalogGroupedPort {
2616
+ list(parentProductId: string): Promise<CatalogGroupedItemRow[]>;
2617
+ addItem(parentProductId: string, input: {
2618
+ childProductId: string;
2619
+ quantity: number;
2620
+ position?: number | undefined;
2621
+ }): Promise<CatalogGroupedItemRow>;
2622
+ updateItem(parentProductId: string, itemId: string, input: {
2623
+ quantity?: number | undefined;
2624
+ position?: number | undefined;
2625
+ }): Promise<CatalogGroupedItemRow>;
2626
+ removeItem(parentProductId: string, itemId: string): Promise<void>;
2627
+ }
2628
+ /** A link to create, as the bulk writer takes it. */
2629
+ export interface CatalogCreateProductLinkInput {
2630
+ targetProductId: string;
2631
+ kind: ProductLinkKind;
2632
+ position?: number | undefined;
2633
+ }
2634
+ /**
2635
+ * Container name: `productLinkService`. Owner: `catalog`.
2636
+ *
2637
+ * **Owner off:** the seam fails closed — resolving this port throws
2638
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2639
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2640
+ * `activation` to say, not this line's: a module declaring
2641
+ * `nonDeactivatable` never enters one.
2642
+ */
2643
+ export interface CatalogProductLinkPort {
2644
+ listForAdmin(sourceProductId: string, kind?: ProductLinkKind): Promise<CatalogProductLinkRow[]>;
2645
+ bulkCreate(sourceProductId: string, inputs: CatalogCreateProductLinkInput[]): Promise<CatalogProductLinkRow[]>;
2646
+ removeLink(sourceProductId: string, linkId: string): Promise<void>;
2647
+ }
2648
+ /** One option inside a bundle slot, as the admin CRUD surface returns it. */
2649
+ export interface CatalogBundleSlotOptionRow {
2650
+ id: string;
2651
+ slotId: string;
2652
+ optionProductId: string;
2653
+ defaultQuantity: number;
2654
+ position: number;
2655
+ }
2656
+ /** One bundle slot with its options. */
2657
+ export interface CatalogBundleSlotRow {
2658
+ id: string;
2659
+ parentProductId: string;
2660
+ name: Record<string, string>;
2661
+ minQuantity: number;
2662
+ maxQuantity: number;
2663
+ position: number;
2664
+ options: CatalogBundleSlotOptionRow[];
2665
+ }
2666
+ /**
2667
+ * Container name: `bundleService`. Owner: `catalog`.
2668
+ *
2669
+ * `pim_pimcore` writes mappable bundle compositions through this seam
2670
+ * (feature 089 / FR-022) — never against catalog's tables.
2671
+ */
2672
+ export interface CatalogBundlePort {
2673
+ listSlots(parentProductId: string): Promise<CatalogBundleSlotRow[]>;
2674
+ createSlot(parentProductId: string, input: {
2675
+ name: Record<string, string>;
2676
+ minQuantity?: number | undefined;
2677
+ maxQuantity: number;
2678
+ position?: number | undefined;
2679
+ }): Promise<CatalogBundleSlotRow>;
2680
+ updateSlot(parentProductId: string, slotId: string, input: {
2681
+ name?: Record<string, string> | undefined;
2682
+ minQuantity?: number | undefined;
2683
+ maxQuantity?: number | undefined;
2684
+ position?: number | undefined;
2685
+ }): Promise<CatalogBundleSlotRow>;
2686
+ deleteSlot(parentProductId: string, slotId: string): Promise<void>;
2687
+ addOption(parentProductId: string, slotId: string, input: {
2688
+ optionProductId: string;
2689
+ defaultQuantity?: number | undefined;
2690
+ position?: number | undefined;
2691
+ }): Promise<CatalogBundleSlotOptionRow>;
2692
+ removeOption(parentProductId: string, slotId: string, optionId: string): Promise<void>;
2693
+ }
2694
+ /**
2695
+ * Container name: `packagingUnitService`. Owner: `catalog`.
2696
+ *
2697
+ * `pim_pimcore` writes mappable packaging units through this seam
2698
+ * (feature 089 / FR-026).
2699
+ */
2700
+ export interface CatalogPackagingPort {
2701
+ list(productId: string): Promise<PackagingUnitDto[]>;
2702
+ create(productId: string, req: CreatePackagingUnitRequest): Promise<PackagingUnitDto>;
2703
+ update(productId: string, unitId: string, req: UpdatePackagingUnitRequest): Promise<PackagingUnitDto>;
2704
+ delete(productId: string, unitId: string): Promise<void>;
2705
+ }
2706
+ /** One attribute with its options, as the promotion rule builder renders it. */
2707
+ export interface CatalogAttributeWithOptions {
2708
+ id: string;
2709
+ key: string;
2710
+ label: Record<string, string>;
2711
+ labelDefault: string;
2712
+ valueType: AttributeValueType;
2713
+ isPromoRule: boolean;
2714
+ options: Array<{
2715
+ value: string;
2716
+ label: Record<string, string>;
2717
+ labelDefault: string;
2718
+ }>;
2719
+ }
2720
+ /**
2721
+ * Container name: `catalogPromoAttributePort`. Owner: `catalog`.
2722
+ *
2723
+ * `promotions` builds its rule editor from these two answers, and reaches
2724
+ * `CatalogQueryService` — the storefront query service, 1400 lines — for them.
2725
+ * The port is the two questions.
2726
+ *
2727
+ * **Owner off:** the seam fails closed — resolving this port throws
2728
+ * `ModuleDisabledError` and the call answers 503 `MODULE_DISABLED`, so nothing
2729
+ * half-executes. Whether `catalog` has an off state at all is its manifest's
2730
+ * `activation` to say, not this line's: a module declaring
2731
+ * `nonDeactivatable` never enters one.
2732
+ */
2733
+ export interface CatalogPromoAttributePort {
2734
+ promoRuleAttributeKeys(): Promise<string[]>;
2735
+ getAttributeWithOptions(key: string): Promise<CatalogAttributeWithOptions | null>;
2736
+ }
2737
+ /** A scalar a filter condition compares against. */
2738
+ export type CatalogProductFilterValue = string | number | boolean | Date | null;
2739
+ /**
2740
+ * What a condition addresses.
2741
+ *
2742
+ * `column` names one of the six product columns a selection may filter on;
2743
+ * `attribute` addresses one key of `products.attribute_values`, the JSONB bag
2744
+ * that has held product attributes and product custom fields alike since
2745
+ * feature 061.
2746
+ */
2747
+ export type CatalogProductFilterField = {
2748
+ kind: 'column';
2749
+ column: 'id' | 'sku' | 'type' | 'status' | 'createdAt' | 'updatedAt';
2750
+ } | {
2751
+ kind: 'attribute';
2752
+ key: string;
2753
+ };
2754
+ /**
2755
+ * The operators a condition may use.
2756
+ *
2757
+ * `contains` and `startsWith` are patterns the **owner** builds, so a caller
2758
+ * never writes SQL `LIKE` syntax and the escaping rule has one home. Both match
2759
+ * case-insensitively, as the queries they replace already did.
2760
+ *
2761
+ * **Their value is matched literally.** `%`, `_` and the escape character are
2762
+ * characters, not wildcards: the owner escapes them before building the
2763
+ * pattern, so `contains "50%"` selects the products whose text carries the
2764
+ * three characters `5`, `0`, `%` and not every product with "50" followed by
2765
+ * anything. That is part of this shape rather than one provider's detail — a
2766
+ * caller passes the value as the operator's user typed it and never
2767
+ * pre-escapes, and any provider of {@link CatalogProductFilterPort} owes the
2768
+ * same semantics.
2769
+ */
2770
+ export type CatalogProductFilterOperator = 'eq' | 'ne' | 'in' | 'nin' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'startsWith' | 'isNull' | 'isNotNull';
2771
+ export interface CatalogProductFilterCondition {
2772
+ kind: 'condition';
2773
+ field: CatalogProductFilterField;
2774
+ op: CatalogProductFilterOperator;
2775
+ /**
2776
+ * The comparison values. `in` / `nin` read all of them; a range is expressed
2777
+ * as two conditions under an `and` group; every other operator reads the
2778
+ * first; `isNull` and `isNotNull` read none.
2779
+ */
2780
+ values: readonly CatalogProductFilterValue[];
2781
+ }
2782
+ export interface CatalogProductFilterGroup {
2783
+ kind: 'group';
2784
+ op: 'and' | 'or';
2785
+ children: readonly CatalogProductFilter[];
2786
+ }
2787
+ /**
2788
+ * The two constants a compiler needs and an empty object cannot express.
2789
+ *
2790
+ * `all` constrains nothing; `none` can never be satisfied. They are named
2791
+ * rather than left to `{}`, because inside an `or` branch an empty predicate
2792
+ * collapses the branch instead of matching everything — a superset silently
2793
+ * becoming a subset, which is how a filter drops the rows it was meant to keep.
2794
+ *
2795
+ * Two interfaces rather than one with a two-value `kind`, so `kind` stays a
2796
+ * discriminant a translator can narrow the whole union on.
2797
+ */
2798
+ export interface CatalogProductFilterAll {
2799
+ kind: 'all';
2800
+ }
2801
+ export interface CatalogProductFilterNone {
2802
+ kind: 'none';
2803
+ }
2804
+ export type CatalogProductFilterConstant = CatalogProductFilterAll | CatalogProductFilterNone;
2805
+ export type CatalogProductFilter = CatalogProductFilterCondition | CatalogProductFilterGroup | CatalogProductFilterAll | CatalogProductFilterNone;
2806
+ /** One keyset page of the products a filter selects. */
2807
+ export interface CatalogSellableProductQuery {
2808
+ /**
2809
+ * The only ids the query may consider — for a feed, the sales channel's
2810
+ * membership, resolved by the caller through the sanctioned bridge accessor
2811
+ * (Principle XII). Required, and an empty list selects nothing: this port has
2812
+ * no "every product in the platform" reading.
2813
+ */
2814
+ productIds: readonly string[];
2815
+ filter: CatalogProductFilter;
2816
+ /** Keyset cursor. Only ids strictly greater come back; `null` starts at the first. */
2817
+ afterId?: string | null;
2818
+ /** Page size. Defaults to 500, the size the feed pipeline already walks in. */
2819
+ limit?: number;
2820
+ }
2821
+ /**
2822
+ * Container name: `catalogProductFilterPort`. Owner: `catalog`.
2823
+ *
2824
+ * **Sellable** is this module's floor and this module applies it: `status`
2825
+ * `active`, not archived, not soft-deleted, and **visible to
2826
+ * {@link ANONYMOUS_PRODUCT_AUDIENCE}** — `isProductVisibleTo`'s answer for a
2827
+ * caller with no session and no organisation, which is what a product feed's
2828
+ * reader is (issue #259). It is conjoined *with* the caller's filter here, so
2829
+ * no filter a caller can construct widens past it.
2830
+ *
2831
+ * The audience is fixed rather than a parameter because this port has one
2832
+ * consumer and that consumer is a feed: an operator ruled that a feed shows the
2833
+ * prices of the sales channel it is generated for and takes no organisation
2834
+ * into account, so there is no per-buyer reading of it to ask for. A caller
2835
+ * that needs another audience needs a different port, not a wider floor —
2836
+ * `visibility` said `public` alone until issue #259, and a `public` product
2837
+ * carrying a non-empty `allowed_organization_ids` went into a Google Shopping
2838
+ * feed for as long as that was the whole test.
2839
+ *
2840
+ * Rows come back as {@link CatalogProductRecord} in ascending id order, which
2841
+ * is what makes the cursor a keyset rather than an offset: a catalogue that
2842
+ * moves under a long walk cannot make the walk skip a row or repeat one.
2843
+ *
2844
+ * When `catalog` is off both methods fail closed. A feed assembled from a
2845
+ * catalogue the platform is refusing to serve is worse than a run that stops
2846
+ * and says why.
2847
+ *
2848
+ * Whether `catalog` has an off state at all is its manifest's `activation` to
2849
+ * say, not this line's: a module declaring `nonDeactivatable` never enters one.
2850
+ */
2851
+ export interface CatalogProductFilterPort {
2852
+ listSellable(query: CatalogSellableProductQuery): Promise<CatalogProductRecord[]>;
2853
+ countSellable(query: Omit<CatalogSellableProductQuery, 'afterId' | 'limit'>): Promise<number>;
2854
+ }
2855
+ //# sourceMappingURL=catalog.d.ts.map