@openlup/platform 0.17.0 → 0.18.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 (486) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +284 -6
  3. package/dist/catalog/activeSkuSelection.d.ts +2 -0
  4. package/dist/catalog/activeSkuSelection.js +20 -0
  5. package/dist/catalog/adminBindings.d.ts +72 -0
  6. package/dist/catalog/adminBindings.js +1 -0
  7. package/dist/catalog/adminBundleHandler.d.ts +26 -0
  8. package/dist/catalog/adminBundleHandler.js +111 -0
  9. package/dist/catalog/adminBundleReadHandler.d.ts +20 -0
  10. package/dist/catalog/adminBundleReadHandler.js +185 -0
  11. package/dist/catalog/adminBundleWritePort.d.ts +85 -0
  12. package/dist/catalog/adminBundleWritePort.js +1 -0
  13. package/dist/catalog/bundleAvailability.d.ts +63 -0
  14. package/dist/catalog/bundleAvailability.js +78 -0
  15. package/dist/catalog/bundleAvailabilityService.d.ts +65 -0
  16. package/dist/catalog/bundleAvailabilityService.js +89 -0
  17. package/dist/catalog/bundleBindings.d.ts +33 -0
  18. package/dist/catalog/bundleBindings.js +1 -0
  19. package/dist/catalog/bundleCatalogQueries.d.ts +28 -0
  20. package/dist/catalog/bundleCatalogQueries.js +84 -0
  21. package/dist/catalog/bundleCatalogReadPort.d.ts +166 -0
  22. package/dist/catalog/bundleCatalogReadPort.js +36 -0
  23. package/dist/catalog/bundleCatalogStore.d.ts +3 -0
  24. package/dist/catalog/bundleCatalogStore.js +152 -0
  25. package/dist/catalog/bundleRuleCodes.d.ts +41 -0
  26. package/dist/catalog/bundleRuleCodes.js +110 -0
  27. package/dist/catalog/bundleRules.d.ts +69 -0
  28. package/dist/catalog/bundleRules.js +102 -0
  29. package/dist/catalog/catalogDraftStore.d.ts +4 -0
  30. package/dist/catalog/catalogDraftStore.js +63 -0
  31. package/dist/catalog/catalogSkuEnvelope.d.ts +7 -0
  32. package/dist/catalog/catalogSkuEnvelope.js +142 -0
  33. package/dist/catalog/catalogSkuEnvelopeMapper.d.ts +38 -0
  34. package/dist/catalog/catalogSkuEnvelopeMapper.js +88 -0
  35. package/dist/catalog/contracts/bundleAdmin.d.ts +143 -0
  36. package/dist/catalog/contracts/bundleAdmin.js +1 -0
  37. package/dist/catalog/contracts/bundleAvailability.d.ts +61 -0
  38. package/dist/catalog/contracts/bundleAvailability.js +1 -0
  39. package/dist/catalog/contracts/bundleRead.d.ts +223 -0
  40. package/dist/catalog/contracts/bundleRead.js +1 -0
  41. package/dist/catalog/contracts/draft.d.ts +79 -0
  42. package/dist/catalog/contracts/draft.js +47 -0
  43. package/dist/catalog/contracts/foundation.d.ts +78 -0
  44. package/dist/catalog/contracts/foundation.js +53 -0
  45. package/dist/catalog/contracts/index.d.ts +14 -0
  46. package/dist/catalog/contracts/index.js +6 -0
  47. package/dist/catalog/contracts/lifecycle.d.ts +77 -0
  48. package/dist/catalog/contracts/lifecycle.js +1 -0
  49. package/dist/catalog/contracts/productMediaContracts.d.ts +77 -0
  50. package/dist/catalog/contracts/productMediaContracts.js +149 -0
  51. package/dist/catalog/contracts/productTypes.d.ts +54 -0
  52. package/dist/catalog/contracts/productTypes.js +16 -0
  53. package/dist/catalog/contracts/resolveProductMedia.d.ts +3 -0
  54. package/dist/catalog/contracts/resolveProductMedia.js +78 -0
  55. package/dist/catalog/contracts/sellable.d.ts +23 -0
  56. package/dist/catalog/contracts/sellable.js +1 -0
  57. package/dist/catalog/draftHandler.d.ts +25 -0
  58. package/dist/catalog/draftHandler.js +118 -0
  59. package/dist/catalog/draftPorts.d.ts +18 -0
  60. package/dist/catalog/draftPorts.js +8 -0
  61. package/dist/catalog/draftSchemas.d.ts +423 -0
  62. package/dist/catalog/draftSchemas.js +89 -0
  63. package/dist/catalog/draftValidation.d.ts +21 -0
  64. package/dist/catalog/draftValidation.js +125 -0
  65. package/dist/catalog/foundationSchemas.d.ts +98 -0
  66. package/dist/catalog/foundationSchemas.js +110 -0
  67. package/dist/catalog/index.d.ts +43 -0
  68. package/dist/catalog/index.js +25 -0
  69. package/dist/catalog/mediaSourcePack.d.ts +51 -0
  70. package/dist/catalog/mediaSourcePack.js +247 -0
  71. package/dist/catalog/productTypeSchemas.d.ts +46 -0
  72. package/dist/catalog/productTypeSchemas.js +72 -0
  73. package/dist/catalog/publicationPorts.d.ts +42 -0
  74. package/dist/catalog/publicationPorts.js +1 -0
  75. package/dist/catalog/readPorts.d.ts +51 -0
  76. package/dist/catalog/readPorts.js +1 -0
  77. package/dist/catalog/referenceCatalogHandler.d.ts +7 -0
  78. package/dist/catalog/referenceCatalogHandler.js +27 -0
  79. package/dist/catalog/resolvedSkuProjection.d.ts +88 -0
  80. package/dist/catalog/resolvedSkuProjection.js +60 -0
  81. package/dist/catalog/sellableBundleHandler.d.ts +46 -0
  82. package/dist/catalog/sellableBundleHandler.js +84 -0
  83. package/dist/catalog/sellableSchemas.d.ts +46 -0
  84. package/dist/catalog/sellableSchemas.js +27 -0
  85. package/dist/catalog/sql.d.ts +46 -0
  86. package/dist/catalog/sql.js +13 -0
  87. package/dist/catalog/validation.d.ts +2 -0
  88. package/dist/catalog/validation.js +3 -0
  89. package/dist/communications/contracts/index.d.ts +1 -0
  90. package/dist/communications/contracts/marketingSync.d.ts +194 -0
  91. package/dist/communications/contracts/marketingSync.js +1 -0
  92. package/dist/communications/index.d.ts +1 -0
  93. package/dist/communications/klaviyo/consentPush.d.ts +51 -0
  94. package/dist/communications/klaviyo/consentPush.js +19 -0
  95. package/dist/communications/klaviyo/index.d.ts +110 -0
  96. package/dist/communications/klaviyo/index.js +860 -0
  97. package/dist/communications/newsletterIntegrationContracts.d.ts +3 -3
  98. package/dist/communications/resend/index.d.ts +40 -0
  99. package/dist/communications/resend/index.js +310 -0
  100. package/dist/composition/catalogContribution.d.ts +18 -0
  101. package/dist/composition/catalogContribution.js +21 -0
  102. package/dist/composition/index.d.ts +1 -0
  103. package/dist/composition/index.js +1 -0
  104. package/dist/identity/customerContracts.d.ts +2 -2
  105. package/dist/inventory/adminInventory.d.ts +13 -0
  106. package/dist/inventory/adminInventory.js +214 -0
  107. package/dist/inventory/contracts/forecast.d.ts +86 -0
  108. package/dist/inventory/contracts/forecast.js +1 -0
  109. package/dist/inventory/contracts/index.d.ts +4 -0
  110. package/dist/inventory/contracts/index.js +1 -0
  111. package/dist/inventory/contracts/inventory.d.ts +157 -0
  112. package/dist/inventory/contracts/inventory.js +1 -0
  113. package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
  114. package/dist/inventory/contracts/newPurchaseStock.js +1 -0
  115. package/dist/inventory/contracts/reservation.d.ts +18 -0
  116. package/dist/inventory/contracts/reservation.js +1 -0
  117. package/dist/inventory/forecast.d.ts +24 -0
  118. package/dist/inventory/forecast.js +200 -0
  119. package/dist/inventory/index.d.ts +13 -0
  120. package/dist/inventory/index.js +10 -0
  121. package/dist/inventory/inventoryHandlers.d.ts +53 -0
  122. package/dist/inventory/inventoryHandlers.js +151 -0
  123. package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
  124. package/dist/inventory/newPurchaseStockAvailability.js +75 -0
  125. package/dist/inventory/ports.d.ts +21 -0
  126. package/dist/inventory/ports.js +16 -0
  127. package/dist/inventory/renewalReservation.d.ts +27 -0
  128. package/dist/inventory/renewalReservation.js +61 -0
  129. package/dist/inventory/reservationPolicy.d.ts +6 -0
  130. package/dist/inventory/reservationPolicy.js +19 -0
  131. package/dist/inventory/schemas.d.ts +137 -0
  132. package/dist/inventory/schemas.js +112 -0
  133. package/dist/inventory/sql.d.ts +14 -0
  134. package/dist/inventory/sql.js +5 -0
  135. package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
  136. package/dist/inventory/subscriptionForecastCalculation.js +133 -0
  137. package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
  138. package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
  139. package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
  140. package/dist/pricing/acquisitionOfferPricing.js +25 -0
  141. package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
  142. package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
  143. package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
  144. package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
  145. package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
  146. package/dist/pricing/acquisitionQuoteBinding.js +12 -0
  147. package/dist/pricing/acquisitionReadiness.d.ts +31 -0
  148. package/dist/pricing/acquisitionReadiness.js +117 -0
  149. package/dist/pricing/acquisitionTerms.d.ts +19 -0
  150. package/dist/pricing/acquisitionTerms.js +126 -0
  151. package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
  152. package/dist/pricing/acquisitionTermsSchemas.js +42 -0
  153. package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
  154. package/dist/pricing/catalogFactsProvenance.js +64 -0
  155. package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
  156. package/dist/pricing/catalogFactsSchemas.js +58 -0
  157. package/dist/pricing/commerceOfferPricing.d.ts +25 -0
  158. package/dist/pricing/commerceOfferPricing.js +139 -0
  159. package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
  160. package/dist/pricing/commerceOfferPricingCache.js +44 -0
  161. package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
  162. package/dist/pricing/commerceOfferPricingHandler.js +78 -0
  163. package/dist/pricing/commercePriceAuthority.d.ts +26 -0
  164. package/dist/pricing/commercePriceAuthority.js +79 -0
  165. package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
  166. package/dist/pricing/commercePricingPolicyErrors.js +67 -0
  167. package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
  168. package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
  169. package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
  170. package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
  171. package/dist/pricing/commerceQuoteHandler.d.ts +31 -0
  172. package/dist/pricing/commerceQuoteHandler.js +110 -0
  173. package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
  174. package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
  175. package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
  176. package/dist/pricing/contracts/acquisitionTerms.js +2 -0
  177. package/dist/pricing/contracts/authority.d.ts +27 -0
  178. package/dist/pricing/contracts/authority.js +1 -0
  179. package/dist/pricing/contracts/evidence.d.ts +33 -0
  180. package/dist/pricing/contracts/evidence.js +1 -0
  181. package/dist/pricing/contracts/index.d.ts +17 -0
  182. package/dist/pricing/contracts/index.js +7 -0
  183. package/dist/pricing/contracts/money.d.ts +27 -0
  184. package/dist/pricing/contracts/money.js +1 -0
  185. package/dist/pricing/contracts/offer.d.ts +52 -0
  186. package/dist/pricing/contracts/offer.js +4 -0
  187. package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
  188. package/dist/pricing/contracts/offerPolicy.js +5 -0
  189. package/dist/pricing/contracts/policy.d.ts +22 -0
  190. package/dist/pricing/contracts/policy.js +3 -0
  191. package/dist/pricing/contracts/provenance.d.ts +24 -0
  192. package/dist/pricing/contracts/provenance.js +2 -0
  193. package/dist/pricing/contracts/quote.d.ts +94 -0
  194. package/dist/pricing/contracts/quote.js +1 -0
  195. package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
  196. package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
  197. package/dist/pricing/dbBackedCommerceQuotePort.d.ts +66 -0
  198. package/dist/pricing/dbBackedCommerceQuotePort.js +206 -0
  199. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
  200. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
  201. package/dist/pricing/index.d.ts +51 -0
  202. package/dist/pricing/index.js +36 -0
  203. package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
  204. package/dist/pricing/offerPolicyReadiness.js +79 -0
  205. package/dist/pricing/offerPolicySchemas.d.ts +25 -0
  206. package/dist/pricing/offerPolicySchemas.js +15 -0
  207. package/dist/pricing/offerPricingSchemas.d.ts +315 -0
  208. package/dist/pricing/offerPricingSchemas.js +65 -0
  209. package/dist/pricing/ports.d.ts +13 -0
  210. package/dist/pricing/ports.js +9 -0
  211. package/dist/pricing/pricingPolicyToken.d.ts +39 -0
  212. package/dist/pricing/pricingPolicyToken.js +84 -0
  213. package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
  214. package/dist/pricing/quoteAcquisitionTerms.js +4 -0
  215. package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
  216. package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
  217. package/dist/pricing/quotePorts.d.ts +30 -0
  218. package/dist/pricing/quotePorts.js +20 -0
  219. package/dist/pricing/quotePrimitives.d.ts +13 -0
  220. package/dist/pricing/quotePrimitives.js +18 -0
  221. package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
  222. package/dist/pricing/quotePromotionEvidence.js +73 -0
  223. package/dist/pricing/quoteSchemas.d.ts +1176 -0
  224. package/dist/pricing/quoteSchemas.js +196 -0
  225. package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
  226. package/dist/pricing/subscriptionPricePolicy.js +96 -0
  227. package/dist/pricing/taxProfileGuard.d.ts +2 -0
  228. package/dist/pricing/taxProfileGuard.js +24 -0
  229. package/dist/pricing/validation.d.ts +2 -0
  230. package/dist/pricing/validation.js +3 -0
  231. package/dist/promotions/adminBindings.d.ts +72 -0
  232. package/dist/promotions/adminBindings.js +1 -0
  233. package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
  234. package/dist/promotions/adminPromotionCodesContracts.js +211 -0
  235. package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
  236. package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
  237. package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
  238. package/dist/promotions/adminPromotionCodesHandler.js +194 -0
  239. package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
  240. package/dist/promotions/adminPromotionsContracts.js +99 -0
  241. package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
  242. package/dist/promotions/adminPromotionsDataPort.js +1 -0
  243. package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
  244. package/dist/promotions/adminPromotionsHandler.js +47 -0
  245. package/dist/promotions/contracts/index.d.ts +387 -0
  246. package/dist/promotions/contracts/index.js +1 -0
  247. package/dist/promotions/index.d.ts +22 -0
  248. package/dist/promotions/index.js +21 -0
  249. package/dist/promotions/promoDataPort.d.ts +47 -0
  250. package/dist/promotions/promoDataPort.js +14 -0
  251. package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
  252. package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
  253. package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
  254. package/dist/promotions/promotionBenefitDefinition.js +33 -0
  255. package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
  256. package/dist/promotions/promotionClaimSweepPort.js +8 -0
  257. package/dist/promotions/promotionClaims.d.ts +5 -0
  258. package/dist/promotions/promotionClaims.js +57 -0
  259. package/dist/promotions/promotionCodePreview.d.ts +11 -0
  260. package/dist/promotions/promotionCodePreview.js +154 -0
  261. package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
  262. package/dist/promotions/promotionCodeQuotePort.js +1 -0
  263. package/dist/promotions/promotionQuoteAcceptance.d.ts +65 -0
  264. package/dist/promotions/promotionQuoteAcceptance.js +202 -0
  265. package/dist/promotions/promotionQuoteBinding.d.ts +71 -0
  266. package/dist/promotions/promotionQuoteBinding.js +85 -0
  267. package/dist/promotions/quoteAutomaticPromotions.d.ts +24 -0
  268. package/dist/promotions/quoteAutomaticPromotions.js +36 -0
  269. package/dist/promotions/quotePromoDiscounts.d.ts +32 -0
  270. package/dist/promotions/quotePromoDiscounts.js +140 -0
  271. package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
  272. package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
  273. package/dist/promotions/quotePromotionV2.d.ts +29 -0
  274. package/dist/promotions/quotePromotionV2.js +174 -0
  275. package/dist/promotions/sql.d.ts +14 -0
  276. package/dist/promotions/sql.js +5 -0
  277. package/dist/promotions/validation.d.ts +2 -0
  278. package/dist/promotions/validation.js +3 -0
  279. package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
  280. package/dist/shipping/addressCanonLookupHandler.js +105 -0
  281. package/dist/shipping/addressCanonical.d.ts +2 -0
  282. package/dist/shipping/addressCanonical.js +26 -0
  283. package/dist/shipping/addressPorts.d.ts +39 -0
  284. package/dist/shipping/addressPorts.js +1 -0
  285. package/dist/shipping/addressSchemas.d.ts +30 -0
  286. package/dist/shipping/addressSchemas.js +1 -0
  287. package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
  288. package/dist/shipping/adminShippingRateHandler.js +64 -0
  289. package/dist/shipping/contracts/address.d.ts +144 -0
  290. package/dist/shipping/contracts/address.js +1 -0
  291. package/dist/shipping/contracts/delivery.d.ts +110 -0
  292. package/dist/shipping/contracts/delivery.js +1 -0
  293. package/dist/shipping/contracts/index.d.ts +3 -0
  294. package/dist/shipping/contracts/index.js +1 -0
  295. package/dist/shipping/contracts/shippingRate.d.ts +11 -0
  296. package/dist/shipping/contracts/shippingRate.js +1 -0
  297. package/dist/shipping/customerAddressesHandler.d.ts +5 -0
  298. package/dist/shipping/customerAddressesHandler.js +80 -0
  299. package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
  300. package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
  301. package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
  302. package/dist/shipping/deliverySelectionHandler.js +77 -0
  303. package/dist/shipping/deliverySelectionPort.d.ts +8 -0
  304. package/dist/shipping/deliverySelectionPort.js +1 -0
  305. package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
  306. package/dist/shipping/deliverySelectionSummary.js +53 -0
  307. package/dist/shipping/handlerSchemas.d.ts +21 -0
  308. package/dist/shipping/handlerSchemas.js +1 -0
  309. package/dist/shipping/index.d.ts +17 -0
  310. package/dist/shipping/index.js +8 -0
  311. package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
  312. package/dist/shipping/pickupPointSearchPort.js +1 -0
  313. package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
  314. package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
  315. package/dist/shipping/shippingRatePorts.d.ts +52 -0
  316. package/dist/shipping/shippingRatePorts.js +1 -0
  317. package/package.json +62 -2
  318. package/release-gates.json +218 -10
  319. package/sql/20260807140000_read_parity_catalog.sql +94 -0
  320. package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
  321. package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
  322. package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
  323. package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
  324. package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
  325. package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
  326. package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
  327. package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
  328. package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
  329. package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
  330. package/src/catalog/activeSkuSelection.ts +20 -0
  331. package/src/catalog/adminBindings.ts +59 -0
  332. package/src/catalog/adminBundleHandler.ts +165 -0
  333. package/src/catalog/adminBundleReadHandler.ts +261 -0
  334. package/src/catalog/adminBundleWritePort.ts +102 -0
  335. package/src/catalog/bundleAvailability.ts +129 -0
  336. package/src/catalog/bundleAvailabilityService.ts +171 -0
  337. package/src/catalog/bundleBindings.ts +29 -0
  338. package/src/catalog/bundleCatalogQueries.ts +98 -0
  339. package/src/catalog/bundleCatalogReadPort.ts +177 -0
  340. package/src/catalog/bundleCatalogStore.ts +269 -0
  341. package/src/catalog/bundleRuleCodes.ts +134 -0
  342. package/src/catalog/bundleRules.ts +153 -0
  343. package/src/catalog/catalogDraftStore.ts +71 -0
  344. package/src/catalog/catalogSkuEnvelope.ts +214 -0
  345. package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
  346. package/src/catalog/contracts/bundleAdmin.ts +20 -0
  347. package/src/catalog/contracts/bundleAvailability.ts +5 -0
  348. package/src/catalog/contracts/bundleRead.ts +13 -0
  349. package/src/catalog/contracts/draft.ts +69 -0
  350. package/src/catalog/contracts/foundation.ts +104 -0
  351. package/src/catalog/contracts/index.ts +14 -0
  352. package/src/catalog/contracts/lifecycle.ts +74 -0
  353. package/src/catalog/contracts/productMediaContracts.ts +170 -0
  354. package/src/catalog/contracts/productTypes.ts +35 -0
  355. package/src/catalog/contracts/resolveProductMedia.ts +85 -0
  356. package/src/catalog/contracts/sellable.ts +12 -0
  357. package/src/catalog/draftHandler.ts +105 -0
  358. package/src/catalog/draftPorts.ts +18 -0
  359. package/src/catalog/draftSchemas.ts +88 -0
  360. package/src/catalog/draftValidation.ts +100 -0
  361. package/src/catalog/foundationSchemas.ts +123 -0
  362. package/src/catalog/index.ts +43 -0
  363. package/src/catalog/mediaSourcePack.ts +219 -0
  364. package/src/catalog/productTypeSchemas.ts +70 -0
  365. package/src/catalog/publicationPorts.ts +44 -0
  366. package/src/catalog/readPorts.ts +61 -0
  367. package/src/catalog/referenceCatalogHandler.ts +39 -0
  368. package/src/catalog/resolvedSkuProjection.ts +76 -0
  369. package/src/catalog/sellableBundleHandler.ts +133 -0
  370. package/src/catalog/sellableSchemas.ts +33 -0
  371. package/src/catalog/sql.ts +14 -0
  372. package/src/catalog/validation.ts +3 -0
  373. package/src/communications/contracts/index.ts +1 -0
  374. package/src/communications/contracts/marketingSync.ts +109 -0
  375. package/src/communications/index.ts +1 -0
  376. package/src/communications/klaviyo/consentPush.ts +19 -0
  377. package/src/communications/klaviyo/index.ts +649 -0
  378. package/src/communications/resend/index.ts +350 -0
  379. package/src/composition/catalogContribution.ts +32 -0
  380. package/src/composition/index.ts +1 -0
  381. package/src/inventory/adminInventory.ts +251 -0
  382. package/src/inventory/contracts/forecast.ts +96 -0
  383. package/src/inventory/contracts/index.ts +18 -0
  384. package/src/inventory/contracts/inventory.ts +14 -0
  385. package/src/inventory/contracts/newPurchaseStock.ts +23 -0
  386. package/src/inventory/contracts/reservation.ts +20 -0
  387. package/src/inventory/forecast.ts +239 -0
  388. package/src/inventory/index.ts +21 -0
  389. package/src/inventory/inventoryHandlers.ts +215 -0
  390. package/src/inventory/newPurchaseStockAvailability.ts +120 -0
  391. package/src/inventory/ports.ts +46 -0
  392. package/src/inventory/renewalReservation.ts +88 -0
  393. package/src/inventory/reservationPolicy.ts +27 -0
  394. package/src/inventory/schemas.ts +128 -0
  395. package/src/inventory/sql.ts +5 -0
  396. package/src/inventory/subscriptionForecastCalculation.ts +157 -0
  397. package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
  398. package/src/pricing/acquisitionOfferPricing.ts +38 -0
  399. package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
  400. package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
  401. package/src/pricing/acquisitionQuoteBinding.ts +12 -0
  402. package/src/pricing/acquisitionReadiness.ts +148 -0
  403. package/src/pricing/acquisitionTerms.ts +125 -0
  404. package/src/pricing/acquisitionTermsSchemas.ts +52 -0
  405. package/src/pricing/catalogFactsProvenance.ts +102 -0
  406. package/src/pricing/catalogFactsSchemas.ts +69 -0
  407. package/src/pricing/commerceOfferPricing.ts +224 -0
  408. package/src/pricing/commerceOfferPricingCache.ts +53 -0
  409. package/src/pricing/commerceOfferPricingHandler.ts +137 -0
  410. package/src/pricing/commercePriceAuthority.ts +104 -0
  411. package/src/pricing/commercePricingPolicyErrors.ts +88 -0
  412. package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
  413. package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
  414. package/src/pricing/commerceQuoteHandler.ts +184 -0
  415. package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
  416. package/src/pricing/contracts/acquisitionTerms.ts +26 -0
  417. package/src/pricing/contracts/authority.ts +44 -0
  418. package/src/pricing/contracts/evidence.ts +25 -0
  419. package/src/pricing/contracts/index.ts +17 -0
  420. package/src/pricing/contracts/money.ts +32 -0
  421. package/src/pricing/contracts/offer.ts +42 -0
  422. package/src/pricing/contracts/offerPolicy.ts +17 -0
  423. package/src/pricing/contracts/policy.ts +26 -0
  424. package/src/pricing/contracts/provenance.ts +19 -0
  425. package/src/pricing/contracts/quote.ts +106 -0
  426. package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
  427. package/src/pricing/dbBackedCommerceQuotePort.ts +260 -0
  428. package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
  429. package/src/pricing/index.ts +52 -0
  430. package/src/pricing/offerPolicyReadiness.ts +110 -0
  431. package/src/pricing/offerPolicySchemas.ts +23 -0
  432. package/src/pricing/offerPricingSchemas.ts +92 -0
  433. package/src/pricing/ports.ts +17 -0
  434. package/src/pricing/pricingPolicyToken.ts +119 -0
  435. package/src/pricing/quoteAcquisitionTerms.ts +10 -0
  436. package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
  437. package/src/pricing/quotePorts.ts +58 -0
  438. package/src/pricing/quotePrimitives.ts +21 -0
  439. package/src/pricing/quotePromotionEvidence.ts +82 -0
  440. package/src/pricing/quoteSchemas.ts +230 -0
  441. package/src/pricing/subscriptionPricePolicy.ts +113 -0
  442. package/src/pricing/taxProfileGuard.ts +33 -0
  443. package/src/pricing/validation.ts +3 -0
  444. package/src/promotions/adminBindings.ts +53 -0
  445. package/src/promotions/adminPromotionCodesContracts.ts +244 -0
  446. package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
  447. package/src/promotions/adminPromotionCodesHandler.ts +254 -0
  448. package/src/promotions/adminPromotionsContracts.ts +121 -0
  449. package/src/promotions/adminPromotionsDataPort.ts +48 -0
  450. package/src/promotions/adminPromotionsHandler.ts +72 -0
  451. package/src/promotions/contracts/index.ts +387 -0
  452. package/src/promotions/index.ts +22 -0
  453. package/src/promotions/promoDataPort.ts +65 -0
  454. package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
  455. package/src/promotions/promotionBenefitDefinition.ts +48 -0
  456. package/src/promotions/promotionClaimSweepPort.ts +23 -0
  457. package/src/promotions/promotionClaims.ts +70 -0
  458. package/src/promotions/promotionCodePreview.ts +183 -0
  459. package/src/promotions/promotionCodeQuotePort.ts +35 -0
  460. package/src/promotions/promotionQuoteAcceptance.ts +284 -0
  461. package/src/promotions/promotionQuoteBinding.ts +158 -0
  462. package/src/promotions/quoteAutomaticPromotions.ts +58 -0
  463. package/src/promotions/quotePromoDiscounts.ts +185 -0
  464. package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
  465. package/src/promotions/quotePromotionV2.ts +223 -0
  466. package/src/promotions/sql.ts +5 -0
  467. package/src/promotions/validation.ts +3 -0
  468. package/src/shipping/addressCanonLookupHandler.ts +114 -0
  469. package/src/shipping/addressCanonical.ts +31 -0
  470. package/src/shipping/addressPorts.ts +48 -0
  471. package/src/shipping/addressSchemas.ts +28 -0
  472. package/src/shipping/adminShippingRateHandler.ts +72 -0
  473. package/src/shipping/contracts/address.ts +71 -0
  474. package/src/shipping/contracts/delivery.ts +114 -0
  475. package/src/shipping/contracts/index.ts +18 -0
  476. package/src/shipping/contracts/shippingRate.ts +7 -0
  477. package/src/shipping/customerAddressesHandler.ts +100 -0
  478. package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
  479. package/src/shipping/deliverySelectionHandler.ts +94 -0
  480. package/src/shipping/deliverySelectionPort.ts +10 -0
  481. package/src/shipping/deliverySelectionSummary.ts +56 -0
  482. package/src/shipping/handlerSchemas.ts +19 -0
  483. package/src/shipping/index.ts +23 -0
  484. package/src/shipping/pickupPointSearchPort.ts +8 -0
  485. package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
  486. package/src/shipping/shippingRatePorts.ts +36 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Lifecycle observations are additive DTOs, not changes to existing wire status.
3
+ * SKU draft/active/archived and draft receipt open/abandoned remain unchanged.
4
+ * Map archived to withdrawn only where an authority establishes that meaning;
5
+ * abandoning a draft never implies withdrawing the active variant.
6
+ */
7
+ export type CatalogVariantState = "draft" | "active" | "withdrawn";
8
+
9
+ export interface CatalogHeadRef {
10
+ publicationId: string;
11
+ revision: number;
12
+ documentDigest: string;
13
+ }
14
+
15
+ export interface CatalogLifecycleObservation {
16
+ productId: string;
17
+ variantId: string;
18
+ sku: string;
19
+ state: CatalogVariantState;
20
+ variantRevision: number;
21
+ /** Null means known unpublished, never unknown publication lineage. */
22
+ publicationHead: CatalogHeadRef | null;
23
+ draftRef: { draftId: string; revision: number } | null;
24
+ }
25
+
26
+ export interface CatalogPublishCommand {
27
+ commandKey: string;
28
+ productId: string;
29
+ candidateRef: string;
30
+ candidateDigest: string;
31
+ expectedHead: CatalogHeadRef | null;
32
+ changes: readonly {
33
+ variantId: string;
34
+ expectedRevision: number;
35
+ state: "active" | "withdrawn";
36
+ }[];
37
+ }
38
+
39
+ export interface CatalogPublicationReceipt {
40
+ commandKey: string;
41
+ fingerprint: string;
42
+ actorId: string;
43
+ previousHead: CatalogHeadRef | null;
44
+ head: CatalogHeadRef;
45
+ changes: readonly CatalogLifecycleObservation[];
46
+ }
47
+
48
+ /** Named refusals contain no arbitrary provider diagnostics. */
49
+ export interface CatalogPublicationRefusal {
50
+ outcome: "unauthorized" | "validation_issue" | "conflict" | "dependency_unavailable" | "unsupported";
51
+ reason: "publisher_not_authorized" | "candidate_invalid" | "head_conflict"
52
+ | "publication_authority_unavailable" | "publication_not_supported";
53
+ }
54
+
55
+ /** Success requires coherent authoritative document and lifecycle facts. */
56
+ export type CatalogPublicationReadResult =
57
+ | { outcome: "found"; head: CatalogHeadRef; documentRef: string; observations: readonly CatalogLifecycleObservation[] }
58
+ | { outcome: "unpublished" }
59
+ | CatalogPublicationRefusal;
60
+
61
+ /** Absence is distinct from an unavailable or unsupported authority. */
62
+ export type CatalogPublicationReceiptResult =
63
+ | { outcome: "found"; receipt: CatalogPublicationReceipt }
64
+ | { outcome: "absent" }
65
+ | CatalogPublicationRefusal;
66
+
67
+ /**
68
+ * Authority-owned history, newest publication revision first with stable ordering.
69
+ * Limit is an integer from 1 through 100; receipts never exceed limit.
70
+ * nextCursor is opaque, and null only when no further page exists.
71
+ */
72
+ export type CatalogHistoryResult =
73
+ | { outcome: "page"; receipts: readonly CatalogPublicationReceipt[]; limit: number; nextCursor: string | null }
74
+ | CatalogPublicationRefusal;
@@ -0,0 +1,170 @@
1
+ /** Provider-free source metadata. Delivery URLs are supplied projections. */
2
+ export type ProductMediaRole = "primary" | "view" | "illustration";
3
+ export interface ProductMediaAsset {
4
+ assetRef: string;
5
+ source: string;
6
+ contentType: "image/jpeg" | "image/png" | "image/webp";
7
+ byteLength: number;
8
+ sha256: string;
9
+ artworkLocale: string | null;
10
+ }
11
+ export interface ProductMediaProduct {
12
+ productId: string;
13
+ variantIds: readonly string[];
14
+ primaryVariantId: string;
15
+ }
16
+ export interface ProductMediaAssociation {
17
+ itemId: string;
18
+ assetRef: string;
19
+ productId: string;
20
+ scope: "product" | "variant";
21
+ variantId?: string;
22
+ locale: string | null;
23
+ role: ProductMediaRole;
24
+ viewKey?: string;
25
+ order: number;
26
+ label: string;
27
+ }
28
+ export interface ProductMediaManifest {
29
+ schemaVersion: 1;
30
+ assets: readonly ProductMediaAsset[];
31
+ products: readonly ProductMediaProduct[];
32
+ associations: readonly ProductMediaAssociation[];
33
+ }
34
+ export interface ProductMediaRequest { productId: string; variantId: string; locale: string }
35
+ export interface ResolvedProductMediaItem {
36
+ id: string; assetRef: string; src: string; label: string; role: ProductMediaRole;
37
+ }
38
+ export interface ResolvedProductMedia {
39
+ key: string;
40
+ revision: string;
41
+ items: readonly ResolvedProductMediaItem[];
42
+ primaryItemId: string | null;
43
+ }
44
+ export interface StorefrontProductMedia {
45
+ productId: string;
46
+ locale: string;
47
+ primaryVariantId: string;
48
+ collections: Readonly<Record<string, ResolvedProductMedia>>;
49
+ }
50
+ export const PRODUCT_MEDIA_LIMITS = Object.freeze({
51
+ assets: 512, products: 256, variants: 1024, associations: 8192,
52
+ imageBytes: 358400, totalBytes: 128 * 1024 * 1024,
53
+ manifestBytes: 2 * 1024 * 1024, resolvedItems: 32,
54
+ });
55
+ export type ProductMediaValidationCode = "invalid_schema" | "duplicate_identity" |
56
+ "unsafe_path" | "undeclared_variant" | "missing_resource" | "conflicting_primary" |
57
+ "collection_limit_exceeded" | "pack_limit_exceeded";
58
+ export class ProductMediaValidationError extends Error {
59
+ constructor(readonly code: ProductMediaValidationCode, readonly reference: string) {
60
+ super(`${code}: ${reference}`);
61
+ this.name = "ProductMediaValidationError";
62
+ }
63
+ }
64
+ function refuse(code: ProductMediaValidationCode, reference: string): never {
65
+ throw new ProductMediaValidationError(code, reference);
66
+ }
67
+ function record(value: unknown, reference: string): Record<string, unknown> {
68
+ if (!value || typeof value !== "object" || Array.isArray(value)) refuse("invalid_schema", reference);
69
+ return value as Record<string, unknown>;
70
+ }
71
+ function fields(value: Record<string, unknown>, allowed: readonly string[], reference: string): void {
72
+ if (Object.keys(value).some((key) => !allowed.includes(key))) refuse("invalid_schema", reference);
73
+ }
74
+ function text(value: unknown, reference: string, max = 256): string {
75
+ if (typeof value !== "string" || !value.trim() || value.length > max || Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127)) {
76
+ refuse("invalid_schema", reference);
77
+ }
78
+ return value;
79
+ }
80
+ function locale(value: unknown, reference: string): void {
81
+ if (value !== null) text(value, reference, 64);
82
+ }
83
+ function array(value: unknown, reference: string, limit: number): unknown[] {
84
+ if (!Array.isArray(value)) refuse("invalid_schema", reference);
85
+ if (value.length > limit) refuse("pack_limit_exceeded", reference);
86
+ return value;
87
+ }
88
+ function unique(set: Set<string>, id: string): void {
89
+ if (set.has(id)) refuse("duplicate_identity", id);
90
+ set.add(id);
91
+ }
92
+ /** Checks contained relative resource bindings; the resource owner also checks symlinks. */
93
+ export function validateProductMediaSourcePath(value: unknown): string {
94
+ const source = text(value, "source", 512);
95
+ if (source.startsWith("/") || /[:\\%?#]/u.test(source) || source.split("/").some((part) => !part || part === "." || part === "..")) {
96
+ refuse("unsafe_path", "source");
97
+ }
98
+ return source;
99
+ }
100
+ /** Structural authoring/pack validation. Signatures and digests require the source bytes. */
101
+ export function validateProductMediaManifest(value: unknown): ProductMediaManifest {
102
+ const manifest = record(value, "manifest");
103
+ fields(manifest, ["schemaVersion", "assets", "products", "associations"], "manifest");
104
+ if (manifest.schemaVersion !== 1) refuse("invalid_schema", "schemaVersion");
105
+ const assets = array(manifest.assets, "assets", PRODUCT_MEDIA_LIMITS.assets);
106
+ const products = array(manifest.products, "products", PRODUCT_MEDIA_LIMITS.products);
107
+ const associations = array(manifest.associations, "associations", PRODUCT_MEDIA_LIMITS.associations);
108
+ const assetIds = new Set<string>();
109
+ let totalBytes = 0;
110
+ for (const entry of assets) {
111
+ const asset = record(entry, "asset");
112
+ fields(asset, ["assetRef", "source", "contentType", "byteLength", "sha256", "artworkLocale"], "asset");
113
+ const id = text(asset.assetRef, "assetRef");
114
+ unique(assetIds, id);
115
+ validateProductMediaSourcePath(asset.source);
116
+ if (!["image/jpeg", "image/png", "image/webp"].includes(asset.contentType as string)) refuse("invalid_schema", id);
117
+ if (!Number.isSafeInteger(asset.byteLength) || (asset.byteLength as number) <= 0) refuse("invalid_schema", id);
118
+ if ((asset.byteLength as number) > PRODUCT_MEDIA_LIMITS.imageBytes) refuse("pack_limit_exceeded", id);
119
+ totalBytes += asset.byteLength as number;
120
+ if (totalBytes > PRODUCT_MEDIA_LIMITS.totalBytes) refuse("pack_limit_exceeded", "totalBytes");
121
+ if (typeof asset.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(asset.sha256)) refuse("invalid_schema", id);
122
+ locale(asset.artworkLocale, id);
123
+ }
124
+ const productVariants = new Map<string, Set<string>>();
125
+ let variantCount = 0;
126
+ for (const entry of products) {
127
+ const product = record(entry, "product");
128
+ fields(product, ["productId", "variantIds", "primaryVariantId"], "product");
129
+ const id = text(product.productId, "productId");
130
+ if (productVariants.has(id)) refuse("duplicate_identity", id);
131
+ const variants = new Set<string>();
132
+ for (const variant of array(product.variantIds, id, PRODUCT_MEDIA_LIMITS.variants)) unique(variants, text(variant, id));
133
+ variantCount += variants.size;
134
+ if (variantCount > PRODUCT_MEDIA_LIMITS.variants) refuse("pack_limit_exceeded", "variants");
135
+ const primary = text(product.primaryVariantId, id);
136
+ if (!variants.has(primary)) refuse("undeclared_variant", primary);
137
+ productVariants.set(id, variants);
138
+ }
139
+ const referencedAssets = new Set<string>();
140
+ const identities = new Map<string, string>();
141
+ const localizedIds = new Set<string>();
142
+ for (const entry of associations) {
143
+ const item = record(entry, "association");
144
+ fields(item, ["itemId", "assetRef", "productId", "scope", "variantId", "locale", "role", "viewKey", "order", "label"], "association");
145
+ const id = text(item.itemId, "itemId");
146
+ const assetRef = text(item.assetRef, id);
147
+ if (!assetIds.has(assetRef)) refuse("missing_resource", assetRef);
148
+ referencedAssets.add(assetRef);
149
+ const productId = text(item.productId, id);
150
+ const variants = productVariants.get(productId);
151
+ if (!variants) refuse("invalid_schema", productId);
152
+ if (item.scope !== "product" && item.scope !== "variant") refuse("invalid_schema", id);
153
+ if (item.scope === "product" && item.variantId !== undefined) refuse("invalid_schema", id);
154
+ if (item.scope === "variant") {
155
+ const variantId = text(item.variantId, id);
156
+ if (!variants.has(variantId)) refuse("undeclared_variant", variantId);
157
+ }
158
+ locale(item.locale, id);
159
+ if (!["primary", "view", "illustration"].includes(item.role as string)) refuse("invalid_schema", id);
160
+ if (item.viewKey !== undefined) text(item.viewKey, id);
161
+ if (!Number.isSafeInteger(item.order)) refuse("invalid_schema", id);
162
+ text(item.label, id, 1024);
163
+ const identity = JSON.stringify([productId, item.scope, item.variantId, item.role, item.viewKey]);
164
+ if (identities.has(id) && identities.get(id) !== identity) refuse("duplicate_identity", id);
165
+ identities.set(id, identity);
166
+ unique(localizedIds, JSON.stringify([id, item.locale]));
167
+ }
168
+ for (const id of assetIds) if (!referencedAssets.has(id)) refuse("missing_resource", id);
169
+ return value as ProductMediaManifest;
170
+ }
@@ -0,0 +1,35 @@
1
+ export type CatalogQuantityDimension = "mass" | "volume" | "count";
2
+ export interface CatalogQuantity { dimension: CatalogQuantityDimension; unscaled: string; scale: number; unit: string }
3
+ export type CatalogOptionValue =
4
+ | { kind: "choice"; value: string }
5
+ | { kind: "quantity"; value: CatalogQuantity }
6
+ | { kind: "boolean"; value: boolean };
7
+ export type CatalogDraftIssue = { path: string; code: string };
8
+ export interface CatalogProductTypeRef { key: string; version: number }
9
+ export interface CatalogProductTypeDefinition extends CatalogProductTypeRef {
10
+ units: { code: string; dimension: CatalogQuantityDimension; integral: boolean }[];
11
+ netContentUnits: string[];
12
+ dimensions: (
13
+ | { key: string; kind: "choice"; values: string[] }
14
+ | { key: string; kind: "quantity"; dimension: CatalogQuantityDimension; units: string[]; role?: "net_content" }
15
+ | { key: string; kind: "boolean" }
16
+ )[];
17
+ validateContent: (content: unknown) => readonly CatalogDraftIssue[];
18
+ }
19
+ export interface CatalogProductTypeRegistry {
20
+ resolve(ref: CatalogProductTypeRef): CatalogProductTypeDefinition | undefined;
21
+ }
22
+
23
+ export function validateCatalogQuantity(quantity: CatalogQuantity, definition: CatalogProductTypeDefinition): boolean {
24
+ const unit = definition.units.find(({ code }) => code === quantity.unit);
25
+ return !!unit && unit.dimension === quantity.dimension
26
+ && (!unit.integral || BigInt(quantity.unscaled) % (10n ** BigInt(quantity.scale)) === 0n);
27
+ }
28
+
29
+ /** Decimal spelling cannot make two equal option values distinct. No unit conversion is inferred. */
30
+ export function canonicalCatalogOption(value: CatalogOptionValue): string {
31
+ if (value.kind !== "quantity") return JSON.stringify(value);
32
+ let { unscaled, scale } = value.value;
33
+ while (scale > 0 && unscaled.endsWith("0")) { unscaled = unscaled.slice(0, -1); scale -= 1; }
34
+ return JSON.stringify(["quantity", value.value.dimension, value.value.unit, unscaled, scale]);
35
+ }
@@ -0,0 +1,85 @@
1
+ import {
2
+ PRODUCT_MEDIA_LIMITS, ProductMediaValidationError, validateProductMediaManifest,
3
+ type ProductMediaAssociation, type ProductMediaManifest, type ProductMediaRequest,
4
+ type ResolvedProductMedia,
5
+ } from "./productMediaContracts.js";
6
+
7
+ const hasControls = (value: string): boolean => Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127);
8
+ const compareText = (a: string, b: string): number => a < b ? -1 : a > b ? 1 : 0;
9
+ const ordered = (a: ProductMediaAssociation, b: ProductMediaAssociation): number =>
10
+ a.order - b.order || compareText(a.itemId, b.itemId);
11
+
12
+ /** A deterministic browser-safe fingerprint, not a cryptographic integrity receipt. */
13
+ function fingerprint(value: string): string {
14
+ return [0x811c9dc5, 0x9e3779b9, 0x85ebca6b, 0xc2b2ae35].map((seed) => {
15
+ let hash = seed;
16
+ for (let index = 0; index < value.length; index++) hash = Math.imul(hash ^ value.charCodeAt(index), 0x01000193);
17
+ return (hash >>> 0).toString(16).padStart(8, "0");
18
+ }).join("");
19
+ }
20
+ function revision(source: ProductMediaManifest, urls: Readonly<Record<string, string>>): string {
21
+ const assets = [...source.assets].sort((a, b) => compareText(a.assetRef, b.assetRef)).map((a) =>
22
+ [a.assetRef, a.source, a.contentType, a.byteLength, a.sha256, a.artworkLocale, Object.prototype.hasOwnProperty.call(urls, a.assetRef) ? urls[a.assetRef] : null]);
23
+ const products = [...source.products].sort((a, b) => compareText(a.productId, b.productId)).map((p) =>
24
+ [p.productId, [...p.variantIds].sort(compareText), p.primaryVariantId]);
25
+ const associations = source.associations.map((a) =>
26
+ [a.itemId, a.assetRef, a.productId, a.scope, a.variantId ?? null, a.locale, a.role, a.viewKey ?? null, a.order, a.label]
27
+ ).sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b)));
28
+ return fingerprint(JSON.stringify([source.schemaVersion, assets, products, associations]));
29
+ }
30
+ function survives(a: ProductMediaAssociation, b: ProductMediaAssociation, locale: string): boolean {
31
+ const ranks = (item: ProductMediaAssociation) => [item.role === "primary" ? 1 : 0,
32
+ item.scope === "variant" ? 1 : 0, item.locale === locale ? 1 : 0];
33
+ const ar = ranks(a), br = ranks(b);
34
+ for (let index = 0; index < ar.length; index++) if (ar[index] !== br[index]) return ar[index]! > br[index]!;
35
+ return ordered(a, b) < 0;
36
+ }
37
+ /** Resolve only declared variant/product material; no filename, SKU or provider inference. */
38
+ export function resolveProductMedia(
39
+ value: ProductMediaManifest,
40
+ request: ProductMediaRequest,
41
+ assetUrls: Readonly<Record<string, string>>,
42
+ ): ResolvedProductMedia {
43
+ const source = validateProductMediaManifest(value);
44
+ const product = source.products.find((p) => p.productId === request.productId);
45
+ if (!product || !product.variantIds.includes(request.variantId)) {
46
+ throw new ProductMediaValidationError("undeclared_variant", request.variantId);
47
+ }
48
+ if (typeof request.locale !== "string" || !request.locale.trim() || request.locale.length > 64 || hasControls(request.locale)) {
49
+ throw new ProductMediaValidationError("invalid_schema", "locale");
50
+ }
51
+ const logicalItems = new Map<string, ProductMediaAssociation>();
52
+ for (const item of source.associations) {
53
+ if (item.productId !== request.productId || (item.scope === "variant" && item.variantId !== request.variantId)) continue;
54
+ if (item.locale !== null && item.locale !== request.locale) continue;
55
+ const current = logicalItems.get(item.itemId);
56
+ if (!current || item.locale === request.locale) logicalItems.set(item.itemId, item);
57
+ }
58
+ const applicable = [...logicalItems.values()];
59
+ if (applicable.filter((item) => item.role === "primary").length > 1) {
60
+ throw new ProductMediaValidationError("conflicting_primary", request.productId);
61
+ }
62
+ const uniqueAssets = new Map<string, ProductMediaAssociation>();
63
+ for (const item of applicable) {
64
+ const current = uniqueAssets.get(item.assetRef);
65
+ if (!current || survives(item, current, request.locale)) uniqueAssets.set(item.assetRef, item);
66
+ }
67
+ if (uniqueAssets.size > PRODUCT_MEDIA_LIMITS.resolvedItems) {
68
+ throw new ProductMediaValidationError("collection_limit_exceeded", request.productId);
69
+ }
70
+ const items = [...uniqueAssets.values()].sort(ordered).map((item) => {
71
+ const src = Object.prototype.hasOwnProperty.call(assetUrls, item.assetRef) ? assetUrls[item.assetRef] : undefined;
72
+ if (typeof src !== "string" || !src.trim()) throw new ProductMediaValidationError("missing_resource", item.assetRef);
73
+ // The projection may contain opaque relative delivery paths or HTTP(S) URLs only.
74
+ if (/^[a-z][a-z\d+.-]*:/iu.test(src) && !/^https?:\/\//iu.test(src) || /^\s/u.test(src) || hasControls(src)) {
75
+ throw new ProductMediaValidationError("unsafe_path", item.assetRef);
76
+ }
77
+ return { id: item.itemId, assetRef: item.assetRef, src, label: item.label, role: item.role };
78
+ });
79
+ const mediaRevision = revision(source, assetUrls);
80
+ return {
81
+ key: JSON.stringify([request.productId, request.variantId, request.locale, mediaRevision]),
82
+ revision: mediaRevision, items,
83
+ primaryItemId: items.find((item) => item.role === "primary")?.id ?? null,
84
+ };
85
+ }
@@ -0,0 +1,12 @@
1
+ export const SELLABLE_CATALOG_CONTRACT_VERSION = "catalog.sellable.v1";
2
+ export interface SellableCatalogItem {
3
+ sku: string; title: string; unitPrice: { amountMinor: number; currency: string };
4
+ permittedPurchaseModes: ("one_time" | "subscription")[];
5
+ }
6
+ export interface SellableCatalogProfile {
7
+ id: string; brand: string; country: string; currency: string; locale: string; timezone: string;
8
+ }
9
+ export interface SellableCatalogListResponse {
10
+ contractVersion: typeof SELLABLE_CATALOG_CONTRACT_VERSION;
11
+ profile: SellableCatalogProfile; items: SellableCatalogItem[];
12
+ }
@@ -0,0 +1,105 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ canonicalCatalogDraftJson, catalogDraftApplyResultSchema, catalogDraftCommandSchema,
4
+ catalogDraftGetQuerySchema, catalogDraftListQuerySchema, catalogDraftPageSchema,
5
+ catalogDraftRecordSchema, catalogDraftRefusalSchema,
6
+ type CatalogDraftApplyResult, type CatalogDraftPage, type CatalogDraftRecord, type CatalogDraftRefusal,
7
+ } from "./draftSchemas.js";
8
+ import {
9
+ CatalogDraftStoreError, type CatalogDraftOperator, type CatalogDraftStorePort,
10
+ } from "./draftPorts.js";
11
+ import type { CatalogDraftIssue, CatalogProductTypeRegistry } from "./productTypeSchemas.js";
12
+ import { validateCatalogDraft, type CatalogDraftReadiness } from "./draftValidation.js";
13
+
14
+ export interface CatalogDraftHandlerDeps<Context> {
15
+ store: CatalogDraftStorePort;
16
+ registry: CatalogProductTypeRegistry;
17
+ authorizeOperator: (context: Context) => Promise<CatalogDraftOperator | null>;
18
+ }
19
+ export type CatalogDraftCommandResult = CatalogDraftApplyResult & {
20
+ issues?: readonly CatalogDraftIssue[]; readiness?: CatalogDraftReadiness;
21
+ };
22
+ const unavailable = (): CatalogDraftRefusal => ({ outcome: "dependency_unavailable", reason: "draft_dependency_unavailable" });
23
+ const invalid = (): CatalogDraftRefusal => ({ outcome: "validation_issue", reason: "draft_input_invalid" });
24
+ function failure(error: unknown): CatalogDraftRefusal {
25
+ if (error instanceof CatalogDraftStoreError) {
26
+ const parsed = catalogDraftRefusalSchema.safeParse(error.refusal);
27
+ if (parsed.success) return parsed.data;
28
+ }
29
+ return unavailable();
30
+ }
31
+
32
+ /** Dark use case: mounting requires a verified operator authorizer at composition. */
33
+ export function createCatalogDraftHandler<Context>(deps: CatalogDraftHandlerDeps<Context>) {
34
+ async function actor(context: Context): Promise<CatalogDraftOperator | CatalogDraftRefusal> {
35
+ try {
36
+ const operator = await deps.authorizeOperator(context);
37
+ if (!operator || !/^[^\s].{0,239}$/u.test(operator.actorId) || operator.actorId.trim() !== operator.actorId) {
38
+ return { outcome: "unauthorized", reason: "draft_operator_required" };
39
+ }
40
+ return operator;
41
+ } catch { return unavailable(); }
42
+ }
43
+ return {
44
+ async execute(input: unknown, context: Context): Promise<CatalogDraftCommandResult> {
45
+ const operator = await actor(context);
46
+ if ("outcome" in operator) return operator;
47
+ let canonicalCommand: string;
48
+ try { canonicalCommand = canonicalCatalogDraftJson(input); } catch { return invalid(); }
49
+ const parsed = catalogDraftCommandSchema.safeParse(JSON.parse(canonicalCommand));
50
+ if (!parsed.success) return invalid();
51
+ const command = parsed.data;
52
+ const fingerprint = createHash("sha256").update(canonicalCommand).digest("hex");
53
+ try {
54
+ // Read receipts first: changed registry, moved head or abandonment cannot erase a committed command.
55
+ const original = await deps.store.get({ draftId: command.draftId, commandKey: command.commandKey }, operator.actorId);
56
+ if (original !== null) {
57
+ const record = catalogDraftRecordSchema.parse(original);
58
+ if (record.draftId !== command.draftId || record.commandKey !== command.commandKey) return unavailable();
59
+ if (record.actorId !== operator.actorId) return { outcome: "unauthorized", reason: "draft_command_actor_mismatch" };
60
+ if (record.fingerprint !== fingerprint) return { outcome: "conflict", reason: "draft_command_key_reused" };
61
+ return { outcome: "replayed", record };
62
+ }
63
+ let readiness: CatalogDraftReadiness | undefined;
64
+ if (command.payload) {
65
+ const validation = validateCatalogDraft(command.payload, deps.registry);
66
+ if (!validation.valid) return { outcome: "validation_issue", reason: "draft_payload_invalid", issues: validation.issues };
67
+ readiness = validation.readiness;
68
+ }
69
+ const result = catalogDraftApplyResultSchema.parse(await deps.store.apply({ canonicalCommand, fingerprint, actorId: operator.actorId }));
70
+ if (result.outcome === "committed" || result.outcome === "replayed") {
71
+ if (result.record.draftId !== command.draftId || result.record.commandKey !== command.commandKey
72
+ || result.record.fingerprint !== fingerprint || result.record.actorId !== operator.actorId) return unavailable();
73
+ return { ...result, ...(readiness ? { readiness } : {}) };
74
+ }
75
+ return result;
76
+ } catch (error) { return failure(error); }
77
+ },
78
+ async get(input: unknown, context: Context): Promise<{ outcome: "found"; record: CatalogDraftRecord | null } | CatalogDraftRefusal> {
79
+ const operator = await actor(context);
80
+ if ("outcome" in operator) return operator;
81
+ const parsed = catalogDraftGetQuerySchema.safeParse(input);
82
+ if (!parsed.success) return invalid();
83
+ try {
84
+ const value = await deps.store.get(parsed.data, operator.actorId);
85
+ const record = value === null ? null : catalogDraftRecordSchema.parse(value);
86
+ if (record && (record.draftId !== parsed.data.draftId
87
+ || (parsed.data.revision !== undefined && record.revision !== parsed.data.revision)
88
+ || (parsed.data.commandKey !== undefined && record.commandKey !== parsed.data.commandKey))) return unavailable();
89
+ return { outcome: "found", record };
90
+ } catch (error) { return failure(error); }
91
+ },
92
+ async list(input: unknown, context: Context): Promise<{ outcome: "listed"; page: CatalogDraftPage } | CatalogDraftRefusal> {
93
+ const operator = await actor(context);
94
+ if ("outcome" in operator) return operator;
95
+ const parsed = catalogDraftListQuerySchema.safeParse(input);
96
+ if (!parsed.success) return invalid();
97
+ try {
98
+ const page = catalogDraftPageSchema.parse(await deps.store.list(parsed.data, operator.actorId));
99
+ if (page.items.length > parsed.data.limit || (parsed.data.afterDraftId
100
+ && page.items.some(({ draftId }) => draftId <= parsed.data.afterDraftId!))) return unavailable();
101
+ return { outcome: "listed", page };
102
+ } catch (error) { return failure(error); }
103
+ },
104
+ };
105
+ }
@@ -0,0 +1,18 @@
1
+ import type {
2
+ CatalogDraftApplyResult, CatalogDraftGetQuery, CatalogDraftListQuery,
3
+ CatalogDraftPage, CatalogDraftRecord, CatalogDraftRefusal,
4
+ } from "./draftSchemas.js";
5
+
6
+ /** Portable adapters accept this actor only after the injected operator authorizer succeeds. */
7
+ export interface CatalogDraftOperator { actorId: string }
8
+ export interface CatalogDraftStorePort {
9
+ apply(input: { canonicalCommand: string; fingerprint: string; actorId: string }): Promise<CatalogDraftApplyResult>;
10
+ get(query: CatalogDraftGetQuery, actorId: string): Promise<CatalogDraftRecord | null>;
11
+ list(query: CatalogDraftListQuery, actorId: string): Promise<CatalogDraftPage>;
12
+ }
13
+ export class CatalogDraftStoreError extends Error {
14
+ constructor(readonly refusal: CatalogDraftRefusal) {
15
+ super(refusal.reason);
16
+ this.name = "CatalogDraftStoreError";
17
+ }
18
+ }
@@ -0,0 +1,88 @@
1
+ import { z } from "./validation.js";
2
+ import { CATALOG_DRAFT_MAX_PAGE, canonicalCatalogDraftJson } from "./contracts/draft.js";
3
+ export { CATALOG_DRAFT_MAX_BYTES, CATALOG_DRAFT_MAX_PAGE, canonicalCatalogDraftJson } from "./contracts/draft.js";
4
+ export type { CatalogDraftPayload, CatalogDraftCommand, CatalogDraftRecord, CatalogDraftSummary, CatalogDraftPage, CatalogDraftRefusal, CatalogDraftApplyResult, CatalogDraftGetQuery, CatalogDraftListQuery } from "./contracts/draft.js";
5
+ import { catalogDocumentRevisionRefSchema } from "./foundationSchemas.js";
6
+ import {
7
+ catalogDimensionKeySchema, catalogNamespacedKeySchema, catalogOptionValueSchema,
8
+ catalogProductTypeRefSchema, catalogQuantitySchema,
9
+ } from "./productTypeSchemas.js";
10
+
11
+ const id = z.guid().refine((value) => value === value.toLowerCase(), "UUID must be lowercase");
12
+ const reference = z.string().min(1).max(240).refine((value) => value.trim() === value);
13
+ const code = z.string().min(1).max(160).regex(/^[A-Za-z0-9._:-]+$/u);
14
+ const revision = z.number().int().min(0).max(2_147_483_646);
15
+ const persistedRevision = z.number().int().positive().max(2_147_483_647);
16
+ const commandKey = reference;
17
+ const jsonContent = z.unknown().refine((value) => value !== undefined, "content must be JSON");
18
+ export const catalogDraftPayloadSchema = z.object({
19
+ schemaVersion: z.literal(1),
20
+ product: z.object({
21
+ id, type: catalogProductTypeRefSchema, slug: code.optional(), primarySkuId: id.optional(),
22
+ dimensions: z.array(catalogDimensionKeySchema).max(16),
23
+ sharedContent: jsonContent.optional(), documentRevision: catalogDocumentRevisionRefSchema.optional(),
24
+ }).strict(),
25
+ skus: z.array(z.object({
26
+ id, productId: id, code: code.optional(),
27
+ options: z.record(catalogDimensionKeySchema, catalogOptionValueSchema),
28
+ netContent: catalogQuantitySchema.optional(),
29
+ identifiers: z.array(z.object({
30
+ scheme: catalogNamespacedKeySchema, issuer: reference, value: z.string().min(1).max(128),
31
+ packagingLevel: z.enum(["unit", "case"]), quantity: z.number().int().min(1).max(2_147_483_647).optional(),
32
+ }).strict()).max(32).optional(),
33
+ assetRefs: z.array(reference).max(32).optional(), grossMass: catalogQuantitySchema.optional(),
34
+ priceRef: reference.optional(), logisticsRef: reference.optional(),
35
+ }).strict()).max(10_000),
36
+ }).strict().superRefine((payload, context) => {
37
+ try { canonicalCatalogDraftJson(payload); } catch { context.addIssue({ code: "custom", message: "payload must be bounded safe JSON" }); }
38
+ if (payload.product.documentRevision && payload.product.documentRevision.productId !== payload.product.id) {
39
+ context.addIssue({ code: "custom", path: ["product", "documentRevision", "productId"], message: "document must belong to product" });
40
+ }
41
+ if (payload.product.primarySkuId && !payload.skus.some((sku) => sku.id === payload.product.primarySkuId)) {
42
+ context.addIssue({ code: "custom", path: ["product", "primarySkuId"], message: "primary must belong to product" });
43
+ }
44
+ payload.skus.forEach((sku, index) => {
45
+ if (sku.productId !== payload.product.id) context.addIssue({ code: "custom", path: ["skus", index, "productId"], message: "SKU must belong to product" });
46
+ });
47
+ });
48
+ export const catalogDraftCommandSchema = z.object({
49
+ schemaVersion: z.literal(1), action: z.enum(["create", "revise", "abandon"]),
50
+ draftId: id, commandKey, expectedRevision: revision, payload: catalogDraftPayloadSchema.optional(),
51
+ }).strict().superRefine((command, context) => {
52
+ if ((command.action === "abandon") === (command.payload !== undefined)) {
53
+ context.addIssue({ code: "custom", path: ["payload"], message: "payload is required only for create and revise" });
54
+ }
55
+ if ((command.action === "create") !== (command.expectedRevision === 0)) {
56
+ context.addIssue({ code: "custom", path: ["expectedRevision"], message: "create starts at revision zero; other commands require a persisted revision" });
57
+ }
58
+ });
59
+ export const catalogDraftGetQuerySchema = z.object({
60
+ draftId: id, revision: persistedRevision.optional(), commandKey: commandKey.optional(),
61
+ }).strict().refine((query) => query.revision === undefined || query.commandKey === undefined, "selectors are mutually exclusive");
62
+ export const catalogDraftListQuerySchema = z.object({
63
+ afterDraftId: id.optional(), limit: z.number().int().min(1).max(CATALOG_DRAFT_MAX_PAGE),
64
+ }).strict();
65
+ export const catalogDraftSummarySchema = z.object({
66
+ draftId: id, productId: id, revision: persistedRevision, status: z.enum(["open", "abandoned"]),
67
+ commandKey, fingerprint: z.string().regex(/^[a-f0-9]{64}$/u), actorId: reference,
68
+ createdAt: z.string().datetime({ offset: true }),
69
+ }).strict();
70
+ export const catalogDraftRecordSchema = catalogDraftSummarySchema.extend({ payload: catalogDraftPayloadSchema }).strict()
71
+ .refine((record) => record.productId === record.payload.product.id, "record product must own payload");
72
+ export const catalogDraftPageSchema = z.object({
73
+ items: z.array(catalogDraftSummarySchema).max(CATALOG_DRAFT_MAX_PAGE), nextCursor: id.nullable(),
74
+ }).strict().superRefine((page, context) => {
75
+ const ids = page.items.map((item) => item.draftId);
76
+ if (ids.some((value, index) => index > 0 && value <= ids[index - 1])
77
+ || (page.nextCursor !== null && page.nextCursor !== ids[ids.length - 1])) {
78
+ context.addIssue({ code: "custom", message: "page must have ordered unique heads and a matching cursor" });
79
+ }
80
+ });
81
+ export const catalogDraftRefusalSchema = z.object({
82
+ outcome: z.enum(["conflict", "validation_issue", "unauthorized", "dependency_unavailable"]),
83
+ reason: z.string().min(1).max(160).regex(/^[a-z][a-z0-9_]*$/u), currentRevision: z.number().int().min(0).max(2_147_483_647).optional(),
84
+ }).strict();
85
+ export const catalogDraftApplyResultSchema = z.union([
86
+ z.object({ outcome: z.enum(["committed", "replayed"]), record: catalogDraftRecordSchema }).strict(),
87
+ catalogDraftRefusalSchema,
88
+ ]);