@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,100 @@
1
+ import { isValidGtin } from "./foundationSchemas.js";
2
+ import type { CatalogDraftPayload } from "./draftSchemas.js";
3
+ import {
4
+ canonicalCatalogOption, validateCatalogQuantity,
5
+ type CatalogDraftIssue, type CatalogProductTypeRegistry,
6
+ } from "./productTypeSchemas.js";
7
+
8
+ export interface CatalogDraftReadiness {
9
+ publication: { status: "incomplete" | "not_assessed"; issues: CatalogDraftIssue[] };
10
+ commercial: { status: "inactive"; issues: CatalogDraftIssue[] };
11
+ }
12
+ export type CatalogDraftValidation =
13
+ | { valid: false; issues: CatalogDraftIssue[] }
14
+ | { valid: true; readiness: CatalogDraftReadiness };
15
+
16
+ /** Supplied facts must be true; absent publish-time facts remain draft readiness issues. */
17
+ export function validateCatalogDraft(payload: CatalogDraftPayload, registry: CatalogProductTypeRegistry): CatalogDraftValidation {
18
+ const issues: CatalogDraftIssue[] = [];
19
+ const publication: CatalogDraftIssue[] = [];
20
+ const commercial: CatalogDraftIssue[] = [];
21
+ const add = (path: string, code: string) => issues.push({ path, code });
22
+ const missing = (path: string, code: string) => publication.push({ path, code });
23
+ const { product, skus } = payload;
24
+ const definition = registry.resolve(product.type);
25
+ if (!definition) return { valid: false, issues: [{ path: "product.type", code: "type_not_installed" }] };
26
+ const dimensions = new Map(definition.dimensions.map((dimension) => [dimension.key, dimension]));
27
+ const netContentKey = definition.dimensions.find((dimension) => dimension.kind === "quantity" && dimension.role === "net_content")?.key;
28
+ if (new Set(product.dimensions).size !== product.dimensions.length) add("product.dimensions", "duplicate_dimension");
29
+ product.dimensions.forEach((key) => { if (!dimensions.has(key)) add(`product.dimensions.${key}`, "dimension_not_installed"); });
30
+ if (product.sharedContent !== undefined) {
31
+ issues.push(...definition.validateContent(product.sharedContent).map((issue) => ({ ...issue, path: `product.sharedContent${issue.path ? `.${issue.path}` : ""}` })));
32
+ } else if (!product.documentRevision) missing("product.sharedContent", "content_missing");
33
+ if (product.documentRevision && product.documentRevision.productId !== product.id) add("product.documentRevision.productId", "foreign_product_reference");
34
+ if (product.primarySkuId && !skus.some((sku) => sku.id === product.primarySkuId)) add("product.primarySkuId", "primary_sku_not_in_product");
35
+ if (!product.primarySkuId) missing("product.primarySkuId", "primary_sku_missing");
36
+ if (!product.slug) missing("product.slug", "slug_missing");
37
+ if (!skus.length) missing("skus", "sku_missing");
38
+ const skuIds = new Set<string>();
39
+ const skuCodes = new Set<string>();
40
+ const combinations = new Set<string>();
41
+ const identifiers = new Set<string>();
42
+ skus.forEach((sku, index) => {
43
+ const path = `skus.${index}`;
44
+ if (sku.productId !== product.id) add(`${path}.productId`, "foreign_product_reference");
45
+ if (skuIds.has(sku.id)) add(`${path}.id`, "duplicate_sku_id");
46
+ skuIds.add(sku.id);
47
+ if (sku.code) {
48
+ if (skuCodes.has(sku.code)) add(`${path}.code`, "duplicate_sku_code");
49
+ skuCodes.add(sku.code);
50
+ } else missing(`${path}.code`, "sku_code_missing");
51
+ const optionKeys = Object.keys(sku.options);
52
+ for (const key of optionKeys) {
53
+ const option = sku.options[key];
54
+ const dimension = dimensions.get(key);
55
+ if (!product.dimensions.includes(key) || !dimension) { add(`${path}.options.${key}`, "dimension_not_declared"); continue; }
56
+ if (dimension.kind !== option.kind) { add(`${path}.options.${key}`, "option_type_invalid"); continue; }
57
+ if (dimension.kind === "choice" && option.kind === "choice" && !dimension.values.includes(option.value)) add(`${path}.options.${key}`, "option_value_invalid");
58
+ if (dimension.kind === "quantity" && option.kind === "quantity"
59
+ && (option.value.dimension !== dimension.dimension || !dimension.units.includes(option.value.unit)
60
+ || !validateCatalogQuantity(option.value, definition))) add(`${path}.options.${key}`, "option_quantity_invalid");
61
+ }
62
+ const absent = product.dimensions.filter((key) => !Object.prototype.hasOwnProperty.call(sku.options, key));
63
+ absent.forEach((key) => missing(`${path}.options.${key}`, "option_missing"));
64
+ if (!absent.length && optionKeys.length === product.dimensions.length) {
65
+ const combination = JSON.stringify([...product.dimensions].sort().map((key) => [key, canonicalCatalogOption(sku.options[key])]));
66
+ if (combinations.has(combination)) add(`${path}.options`, "duplicate_option_combination");
67
+ combinations.add(combination);
68
+ }
69
+ if (sku.netContent) {
70
+ if (!validateCatalogQuantity(sku.netContent, definition) || !definition.netContentUnits.includes(sku.netContent.unit)) add(`${path}.netContent`, "net_content_invalid");
71
+ } else missing(`${path}.netContent`, "net_content_missing");
72
+ const netContentOption = netContentKey === undefined ? undefined : sku.options[netContentKey];
73
+ if (netContentOption?.kind === "quantity" && sku.netContent
74
+ && canonicalCatalogOption(netContentOption) !== canonicalCatalogOption({ kind: "quantity", value: sku.netContent })) add(`${path}.options.${netContentKey}`, "net_content_option_mismatch");
75
+ if (sku.grossMass && (sku.grossMass.dimension !== "mass" || !validateCatalogQuantity(sku.grossMass, definition))) add(`${path}.grossMass`, "gross_mass_invalid");
76
+ (sku.identifiers ?? []).forEach((identifier, identifierIndex) => {
77
+ const at = `${path}.identifiers.${identifierIndex}`;
78
+ // One GTIN is one identity in any 8-, 12-, 13- or 14-digit spelling, whatever its issuer text.
79
+ const gtin = identifier.scheme === "gs1:gtin" && isValidGtin(identifier.value) ? identifier.value.padStart(14, "0") : null;
80
+ const identity = JSON.stringify(gtin === null ? [identifier.scheme, identifier.issuer, identifier.value] : [identifier.scheme, gtin]);
81
+ if (identifiers.has(identity)) add(at, "duplicate_trade_identifier");
82
+ identifiers.add(identity);
83
+ if (identifier.scheme === "gs1:gtin" && gtin === null) add(at, "trade_identifier_invalid");
84
+ if (identifier.scheme !== "gs1:gtin" && identifier.scheme.startsWith("gs1:")) add(at, "trade_identifier_scheme_unsupported");
85
+ // Checked here, not in the wire schema, so a stored revision without a quantity stays readable.
86
+ if (identifier.packagingLevel === "case" ? (identifier.quantity ?? 0) < 2 : (identifier.quantity ?? 1) !== 1) add(`${at}.quantity`, "trade_identifier_quantity_invalid");
87
+ });
88
+ if (!sku.identifiers?.length) commercial.push({ path: `${path}.identifiers`, code: "channel_identity_not_assessed" });
89
+ if (!sku.identifiers?.some((identifier) => identifier.scheme === "gs1:gtin" && identifier.packagingLevel === "unit")) commercial.push({ path: `${path}.identifiers`, code: "unit_trade_identifier_missing" });
90
+ if (!sku.priceRef) commercial.push({ path: `${path}.priceRef`, code: "price_reference_missing" });
91
+ if (!sku.logisticsRef) commercial.push({ path: `${path}.logisticsRef`, code: "logistics_reference_missing" });
92
+ });
93
+ if (issues.length) return { valid: false, issues };
94
+ // Opaque references are not current pricing, stock, logistics or publication proof.
95
+ commercial.push({ path: "skus", code: "commercial_activation_not_assessed" });
96
+ return { valid: true, readiness: {
97
+ publication: { status: publication.length ? "incomplete" : "not_assessed", issues: publication },
98
+ commercial: { status: "inactive", issues: commercial },
99
+ } };
100
+ }
@@ -0,0 +1,123 @@
1
+ import { z } from "./validation.js";
2
+ import { CATALOG_FOUNDATION_CONTRACT_VERSION, isValidGtin } from "./contracts/foundation.js";
3
+ export { CATALOG_FOUNDATION_CONTRACT_VERSION, CatalogSkuAuthorityError, isValidGtin, assertPrimaryUnitGtinAuthority, requireCurrentCatalogDocumentAuthority } from "./contracts/foundation.js";
4
+ export type { CatalogDocumentRevisionRef, CatalogSkuIdentifier, CatalogSkuEnvelopeSelector, NeutralSkuEnvelopeV1, CatalogPriceContext, CatalogSkuAuthorityRefusal } from "./contracts/foundation.js";
5
+
6
+ /**
7
+ * Portable, payload-free catalog shape. Document interpretation remains an
8
+ * adopter concern; this module can only carry immutable document references.
9
+ */
10
+
11
+ const entityIdSchema = z.guid();
12
+ const opaqueReferenceSchema = z.string().trim().min(1).max(240);
13
+ const codeSchema = z.string().trim().min(1).max(160).regex(/^[A-Za-z0-9._:-]+$/);
14
+ const sha256DigestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
15
+ const currencyCodeSchema = z.string().regex(/^[A-Z]{3}$/u);
16
+
17
+ export const catalogFixedPointSchema = z.object({
18
+ /** SKU net content is a physical, positive quantity; signed values need a distinct contract. */
19
+ unscaled: z.number().int().positive().safe(),
20
+ scale: z.number().int().min(0).max(12).safe(),
21
+ unit: z.string().trim().min(1).max(64).regex(/^[A-Z][A-Z0-9_]*$/),
22
+ }).strict();
23
+
24
+ export const catalogDocumentRevisionRefSchema = z.object({
25
+ id: entityIdSchema,
26
+ productId: entityIdSchema,
27
+ revisionNo: z.number().int().positive(),
28
+ schemaId: opaqueReferenceSchema,
29
+ documentRef: opaqueReferenceSchema,
30
+ sourceRef: opaqueReferenceSchema,
31
+ digest: sha256DigestSchema,
32
+ }).strict();
33
+
34
+ export const catalogSkuIdentifierSchema = z.object({
35
+ issuer: opaqueReferenceSchema,
36
+ normalizedValue: z.string().trim().min(1).max(128),
37
+ kind: z.enum(["gtin", "other"]),
38
+ packKind: z.enum(["unit", "collective"]),
39
+ packQuantity: z.number().int().positive(),
40
+ isPrimary: z.boolean(),
41
+ }).strict();
42
+
43
+ /** Stable, payload-free detail selectors shared by both catalog adapters. */
44
+ export const catalogSkuEnvelopeSelectorSchema = z.discriminatedUnion("kind", [
45
+ z.object({
46
+ kind: z.literal("sku_code"),
47
+ skuCode: codeSchema,
48
+ }).strict(),
49
+ z.object({
50
+ kind: z.literal("primary_product_slug"),
51
+ productSlug: codeSchema,
52
+ }).strict(),
53
+ ]);
54
+
55
+ export const neutralSkuEnvelopeV1Schema = z.object({
56
+ contractVersion: z.literal(CATALOG_FOUNDATION_CONTRACT_VERSION),
57
+ product: z.object({
58
+ id: entityIdSchema,
59
+ slug: codeSchema,
60
+ primarySkuId: entityIdSchema,
61
+ }).strict(),
62
+ sku: z.object({
63
+ id: entityIdSchema,
64
+ productId: entityIdSchema,
65
+ code: codeSchema,
66
+ netContent: catalogFixedPointSchema,
67
+ sellability: z.object({
68
+ status: z.enum(["draft", "active", "archived"]),
69
+ oneTime: z.boolean(),
70
+ subscription: z.boolean(),
71
+ }).strict(),
72
+ /** Structural SKU classification shared by both bundle schemas. */
73
+ isAddon: z.boolean(),
74
+ assetRef: opaqueReferenceSchema.nullable(),
75
+ }).strict(),
76
+ documentRevision: catalogDocumentRevisionRefSchema,
77
+ identifiers: z.array(catalogSkuIdentifierSchema).max(32),
78
+ }).strict().superRefine((envelope, context) => {
79
+ if (envelope.product.id !== envelope.sku.productId) {
80
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["sku", "productId"], message: "sku must belong to envelope product" });
81
+ }
82
+ if (envelope.product.id !== envelope.documentRevision.productId) {
83
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["documentRevision", "productId"], message: "document revision must belong to envelope product" });
84
+ }
85
+
86
+ const seen = new Set<string>();
87
+ let primaryGtinCount = 0;
88
+ envelope.identifiers.forEach((identifier, index) => {
89
+ const identity = `${identifier.issuer}\u0000${identifier.normalizedValue}`;
90
+ if (seen.has(identity)) {
91
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["identifiers", index], message: "identifier issuer/value pairs must be unique" });
92
+ }
93
+ seen.add(identity);
94
+ if (identifier.packKind === "unit" && identifier.packQuantity !== 1) {
95
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["identifiers", index, "packQuantity"], message: "unit identifiers require pack quantity 1" });
96
+ }
97
+ if (identifier.packKind === "collective" && identifier.packQuantity <= 1) {
98
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["identifiers", index, "packQuantity"], message: "collective identifiers require pack quantity above 1" });
99
+ }
100
+ if (identifier.isPrimary) {
101
+ primaryGtinCount += 1;
102
+ if (
103
+ identifier.kind !== "gtin"
104
+ || identifier.packKind !== "unit"
105
+ || !isValidGtin(identifier.normalizedValue)
106
+ ) {
107
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["identifiers", index], message: "a primary identifier must be a unit GTIN" });
108
+ }
109
+ }
110
+ });
111
+ if (primaryGtinCount > 1) {
112
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ["identifiers"], message: "a SKU may have at most one primary GTIN" });
113
+ }
114
+ });
115
+
116
+ export const catalogPriceContextSchema = z.object({
117
+ priceListId: entityIdSchema,
118
+ regionCode: z.string().regex(/^[A-Z]{2,3}$/u),
119
+ currency: currencyCodeSchema,
120
+ channel: z.string().trim().min(1).max(64).regex(/^[A-Za-z0-9._:-]+$/),
121
+ atTime: z.string().datetime({ offset: true }),
122
+ }).strict();
123
+
@@ -0,0 +1,43 @@
1
+ export * from "./contracts/index.js";
2
+ export { parseCatalogActiveSkuSelection } from "./activeSkuSelection.js";
3
+ export { createCatalogDraftHandler } from "./draftHandler.js";
4
+ export type { CatalogDraftHandlerDeps, CatalogDraftCommandResult } from "./draftHandler.js";
5
+ export { CatalogDraftStoreError } from "./draftPorts.js";
6
+ export type { CatalogDraftOperator, CatalogDraftStorePort } from "./draftPorts.js";
7
+ export { catalogDraftPayloadSchema, catalogDraftCommandSchema, catalogDraftGetQuerySchema, catalogDraftListQuerySchema, catalogDraftSummarySchema, catalogDraftRecordSchema, catalogDraftPageSchema, catalogDraftRefusalSchema, catalogDraftApplyResultSchema } from "./draftSchemas.js";
8
+ export { validateCatalogDraft } from "./draftValidation.js";
9
+ export type { CatalogDraftReadiness, CatalogDraftValidation } from "./draftValidation.js";
10
+ export { catalogFixedPointSchema, catalogDocumentRevisionRefSchema, catalogSkuIdentifierSchema, catalogSkuEnvelopeSelectorSchema, neutralSkuEnvelopeV1Schema, catalogPriceContextSchema } from "./foundationSchemas.js";
11
+ export { catalogNamespacedKeySchema, catalogDimensionKeySchema, catalogQuantityDimensionSchema, catalogQuantitySchema, catalogOptionValueSchema, catalogProductTypeRefSchema, createCatalogProductTypeRegistry } from "./productTypeSchemas.js";
12
+ export type { SellableCatalogPort, CatalogSkuEnvelopeReadPort, PublicCatalogSkuEnvelopeReadPort, CatalogSkuEnvelopeListQuery, CatalogActiveSkuEnvelopeListQuery, CatalogSkuEnvelopePage } from "./readPorts.js";
13
+ export { createReferenceCatalogItemsHandler } from "./referenceCatalogHandler.js";
14
+ export { RESOLVED_SKU_PROJECTION_VERSION, resolvedSkuProjectionSchema, buildResolvedSkuProjection } from "./resolvedSkuProjection.js";
15
+ export type { ResolvedSkuProjection, ResolvedSkuProjectionInput } from "./resolvedSkuProjection.js";
16
+ export { sellableCatalogItemSchema, sellableCatalogProfileSchema, sellableCatalogListResponseSchema } from "./sellableSchemas.js";
17
+ export { SOURCE_PACK_LIMITS, CatalogMediaSourcePackError, readCatalogMediaSourceManifest, validateCatalogMediaSourcePack, exportCatalogMediaSourcePack, restoreCatalogMediaSourcePack, importCatalogMediaSourcePack } from "./mediaSourcePack.js";
18
+ export type { CatalogMediaSourcePack, CatalogMediaImportOutcome } from "./mediaSourcePack.js";
19
+ export type { CatalogValidationPort, CatalogPublicationReadPort, CatalogPublicationPort, VerifiedCatalogPublisher, CatalogCapabilityDescriptor } from "./publicationPorts.js";
20
+ export { SUPPORTED_BUNDLE_FULFILLMENT_MODES, createBundleWriteRules, evaluateBundleComposition, evaluateBundleFulfillmentMode, evaluateBundleTargetPrice, isEmptyCompositionConstraint, evaluateBundleCompositionConstraint } from "./bundleRules.js";
21
+ export type { BundleCompositionEntry, SupportedBundleFulfillmentMode } from "./bundleRules.js";
22
+ export { BUNDLE_RULE_CODES, BUNDLE_ALLOCATION_RULE_CODE, BUNDLE_RULE_ENFORCEMENT } from "./bundleRuleCodes.js";
23
+ export type { BundleRuleCode, BundleActorKind, BundleWriteOperation, BundleRuleResult } from "./bundleRuleCodes.js";
24
+ export { DEFAULT_BUNDLE_LOW_STOCK_THRESHOLD, BUNDLE_COMPONENT_STOCK_UNKNOWN, BUNDLE_COMPOSITION_EMPTY, createBundleAvailability } from "./bundleAvailability.js";
25
+ export type { BundleAvailabilityComponent, BundleAvailability, DeriveBundleAvailabilityInput } from "./bundleAvailability.js";
26
+ export { BundleCatalogReadError } from "./bundleCatalogReadPort.js";
27
+ export type { BundleListStatusFilter, ListBundlesInput, BundleSummary, ListBundlesResult, BundleComponentRow, BundleTargetPriceRow, BundleDetail, GetBundleInput, ActiveBundleComposition, BundleHistoryEvent, BundleHistoryInput, BundleHistoryResult, BundleCatalogReadPort } from "./bundleCatalogReadPort.js";
28
+ export { createBundleAvailabilityServices, createUnknownOfferAvailabilityPort } from "./bundleAvailabilityService.js";
29
+ export type { BundleAvailabilityServiceDeps, BundleAvailabilityRow, BundleAvailabilityService } from "./bundleAvailabilityService.js";
30
+ export type { BundleWriteResult, BundleCompositionConstraintEnvelope, AdminBundleWritePort } from "./adminBundleWritePort.js";
31
+ export { createAdminBundleHandlers } from "./adminBundleHandler.js";
32
+ export type { AdminBundleHandlerDeps } from "./adminBundleHandler.js";
33
+ export { createAdminBundleReadHandlers } from "./adminBundleReadHandler.js";
34
+ export type { AdminBundleReadHandlerDeps } from "./adminBundleReadHandler.js";
35
+ export { createSellableBundleHandlers, toSellableBundle } from "./sellableBundleHandler.js";
36
+ export type { SellableBundleHandlerDeps } from "./sellableBundleHandler.js";
37
+ export type { BundleStockStatus, BundleStockRequestItem, BundleStockAvailability, BundleStockPort, BundleRuleBindings } from "./bundleBindings.js";
38
+ export type { AuthorizeAdmin, AgentDomainQuerySpec, AgentDomainMutationSpec, BundleMutationKey, BundleQueryKey, BundleResponseSchema, BundleAdminBindings } from "./adminBindings.js";
39
+ export { createPostgresBundleCatalogStore } from "./bundleCatalogStore.js";
40
+ export { BUNDLE_MIGRATIONS, CATALOG_MIGRATIONS } from "./sql.js";
41
+ export { SUMMARY_COLUMNS, LIST_FILTER, LIST_SQL, COUNT_SQL, DETAIL_SQL, REFERENCE_PRICE, COMPONENT_SQL, FEED_COMPONENT_SQL, PRICE_SQL, OWN_LIST_SQL, CURRENCY_LIST_SQL, FEED_SQL, HISTORY_SQL, HISTORY_COUNT_SQL } from "./bundleCatalogQueries.js";
42
+ export { createPostgresCatalogSkuEnvelopeReadPort } from "./catalogSkuEnvelope.js";
43
+ export { createPostgresCatalogDraftStore } from "./catalogDraftStore.js";
@@ -0,0 +1,219 @@
1
+ /** Local, bounded source-resource transfer. Uploading bytes never activates associations. */
2
+ import { constants, promises as fs } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { isAbsolute, resolve, sep } from "node:path";
5
+ import { resolveProductMedia } from "./contracts/resolveProductMedia.js";
6
+ import type { BlobStoragePort } from "@openlup/core/platform-runtime";
7
+ import {
8
+ validateProductMediaManifest,
9
+ PRODUCT_MEDIA_LIMITS,
10
+ type ProductMediaManifest,
11
+ type ProductMediaAsset,
12
+ } from "./contracts/productMediaContracts.js";
13
+
14
+ export const SOURCE_PACK_LIMITS = PRODUCT_MEDIA_LIMITS;
15
+ export interface CatalogMediaSourcePack {
16
+ schemaVersion: "catalog.media-source-pack.v1";
17
+ manifest: ProductMediaManifest;
18
+ }
19
+ export class CatalogMediaSourcePackError extends Error {
20
+ constructor(public readonly code: string, public readonly reference?: string) {
21
+ super(reference ? `${code}:${reference}` : code);
22
+ this.name = "CatalogMediaSourcePackError";
23
+ }
24
+ }
25
+ const refuse = (code: string, reference?: string): never => {
26
+ throw new CatalogMediaSourcePackError(code, reference);
27
+ };
28
+ const digest = (bytes: Uint8Array): string => createHash("sha256").update(bytes).digest("hex");
29
+ const extensions: Record<string, string> = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp" };
30
+ const keyFor = (asset: ProductMediaAsset): string => `assets/${asset.sha256}.${extensions[asset.contentType]}`;
31
+
32
+ function normalized(manifest: ProductMediaManifest): ProductMediaManifest {
33
+ return {
34
+ schemaVersion: manifest.schemaVersion,
35
+ products: manifest.products.map(({ productId, variantIds, primaryVariantId }) => ({ productId, variantIds: [...variantIds], primaryVariantId })),
36
+ assets: manifest.assets.map(({ assetRef, source, contentType, byteLength, sha256, artworkLocale }) => ({ assetRef, source, contentType, byteLength, sha256, artworkLocale })),
37
+ associations: manifest.associations.map(({ itemId, assetRef, productId, scope, variantId, locale, role, viewKey, order, label }) => ({
38
+ itemId, assetRef, productId, scope, ...(variantId === undefined ? {} : { variantId }),
39
+ locale, role, ...(viewKey === undefined ? {} : { viewKey }), order, label,
40
+ })),
41
+ };
42
+ }
43
+ function checkedManifest(value: unknown): ProductMediaManifest {
44
+ const manifest = normalized(validateProductMediaManifest(value));
45
+ const envelope = { schemaVersion: "catalog.media-source-pack.v1", manifest: {
46
+ ...manifest, assets: manifest.assets.map((asset) => ({ ...asset, source: keyFor(asset) })),
47
+ } };
48
+ if (Buffer.byteLength(JSON.stringify(manifest)) > SOURCE_PACK_LIMITS.manifestBytes ||
49
+ Buffer.byteLength(JSON.stringify(envelope, null, 2) + "\n") > SOURCE_PACK_LIMITS.manifestBytes) refuse("pack_limit_exceeded");
50
+ const checks = manifest.products.map((product) => {
51
+ const items = manifest.associations.filter((item) => item.productId === product.productId);
52
+ const locales = new Set(items.flatMap((item) => item.locale === null ? [] : [item.locale]));
53
+ let neutralIndex = 0;
54
+ while (locales.has(`source-pack-neutral-${neutralIndex}`)) neutralIndex++;
55
+ locales.add(`source-pack-neutral-${neutralIndex}`);
56
+ return { product, locales };
57
+ });
58
+ // Bound processing before any full resolver pass, resource read or upload.
59
+ const expandedChecks = checks.reduce((total, { product, locales }) => total + product.variantIds.length * locales.size, 0);
60
+ if (expandedChecks > SOURCE_PACK_LIMITS.associations) refuse("pack_limit_exceeded");
61
+ const urls = Object.fromEntries(manifest.assets.map((asset) => [asset.assetRef, asset.source]));
62
+ for (const { product, locales } of checks) {
63
+ for (const variantId of product.variantIds) for (const locale of locales) {
64
+ resolveProductMedia(manifest, { productId: product.productId, variantId, locale }, urls);
65
+ }
66
+ }
67
+ return manifest;
68
+ }
69
+
70
+ async function contained(root: string, key: string, reference?: string): Promise<string> {
71
+ if (!key || isAbsolute(key) || key.includes("\\") || key.includes(":" ) || key.includes("%") ||
72
+ key.split("/").some((part) => !part || part === "." || part === "..")) refuse("unsafe_path", reference);
73
+ try {
74
+ const canonicalRoot = await fs.realpath(root);
75
+ const target = await fs.realpath(resolve(canonicalRoot, key));
76
+ if (!target.startsWith(canonicalRoot + sep)) refuse("unsafe_path", reference);
77
+ return target;
78
+ } catch (error) {
79
+ if (error instanceof CatalogMediaSourcePackError) throw error;
80
+ return refuse("missing_resource", reference);
81
+ }
82
+ }
83
+ /** Open only contained regular files, allocate a fixed bound and read at most limit + 1. */
84
+ async function boundedRead(path: string, limit: number, reference?: string): Promise<Buffer> {
85
+ const handle = await fs.open(path, constants.O_RDONLY | constants.O_NOFOLLOW).catch(() => refuse("missing_resource", reference));
86
+ try {
87
+ const stat = await handle.stat();
88
+ if (!stat.isFile()) refuse("unsafe_path", reference);
89
+ if (stat.size > limit) refuse("pack_limit_exceeded", reference);
90
+ const bytes = Buffer.alloc(limit + 1);
91
+ let length = 0;
92
+ while (length < bytes.length) {
93
+ const { bytesRead } = await handle.read(bytes, length, bytes.length - length, null);
94
+ if (!bytesRead) break;
95
+ length += bytesRead;
96
+ }
97
+ if (length > limit) refuse("pack_limit_exceeded", reference);
98
+ return bytes.subarray(0, length);
99
+ } finally { await handle.close(); }
100
+ }
101
+ function matchesMime(bytes: Buffer, mime: string): boolean {
102
+ if (mime === "image/png") return bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
103
+ if (mime === "image/jpeg") return bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
104
+ return bytes.length >= 16 && bytes.toString("ascii", 0, 4) === "RIFF" &&
105
+ bytes.toString("ascii", 8, 12) === "WEBP" && bytes.readUInt32LE(4) + 8 === bytes.length &&
106
+ ["VP8 ", "VP8L", "VP8X"].includes(bytes.toString("ascii", 12, 16));
107
+ }
108
+ async function loadResources(manifest: ProductMediaManifest, root: string): Promise<Map<string, Buffer>> {
109
+ const paths = new Map<string, string>();
110
+ // Stat every resource before reading any payload; declared and actual totals are bounded.
111
+ for (const asset of manifest.assets) {
112
+ const path = await contained(root, asset.source, asset.assetRef);
113
+ const stat = await fs.stat(path);
114
+ if (!stat.isFile()) refuse("unsafe_path", asset.assetRef);
115
+ if (stat.size > SOURCE_PACK_LIMITS.imageBytes) refuse("pack_limit_exceeded", asset.assetRef);
116
+ if (stat.size !== asset.byteLength) refuse("size_mismatch", asset.assetRef);
117
+ paths.set(asset.assetRef, path);
118
+ }
119
+ const resources = new Map<string, Buffer>();
120
+ for (const asset of manifest.assets) {
121
+ const bytes = await boundedRead(paths.get(asset.assetRef)!, asset.byteLength, asset.assetRef);
122
+ if (bytes.length !== asset.byteLength) refuse("size_mismatch", asset.assetRef);
123
+ if (!matchesMime(bytes, asset.contentType)) refuse("mime_mismatch", asset.assetRef);
124
+ if (digest(bytes) !== asset.sha256) refuse("digest_mismatch", asset.assetRef);
125
+ resources.set(asset.assetRef, bytes);
126
+ }
127
+ return resources;
128
+ }
129
+
130
+ export async function readCatalogMediaSourceManifest(path: string): Promise<unknown> {
131
+ const bytes = await boundedRead(path, SOURCE_PACK_LIMITS.manifestBytes);
132
+ try { return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)); }
133
+ catch { return refuse("invalid_schema"); }
134
+ }
135
+ async function checkInventory(root: string, manifest: ProductMediaManifest) {
136
+ const expected = new Set(manifest.assets.map((asset) => keyFor(asset).slice(7)));
137
+ const rootEntries = await fs.opendir(root);
138
+ let rootCount = 0;
139
+ for await (const entry of rootEntries) {
140
+ if (++rootCount > 2 || !["assets", "manifest.json"].includes(entry.name)) refuse("reference_mismatch");
141
+ }
142
+ const assetRoot = await contained(root, "assets");
143
+ const entries = await fs.opendir(assetRoot);
144
+ let count = 0;
145
+ for await (const entry of entries) {
146
+ if (++count > SOURCE_PACK_LIMITS.assets) refuse("pack_limit_exceeded");
147
+ if (!expected.delete(entry.name)) refuse("reference_mismatch");
148
+ }
149
+ if (expected.size) refuse("missing_resource");
150
+ }
151
+ export async function validateCatalogMediaSourcePack(root: string) {
152
+ const input = await readCatalogMediaSourceManifest(await contained(root, "manifest.json"));
153
+ if (!input || typeof input !== "object" || (input as CatalogMediaSourcePack).schemaVersion !== "catalog.media-source-pack.v1") return refuse("invalid_schema");
154
+ if (Object.keys(input).some((key) => !["schemaVersion", "manifest"].includes(key))) refuse("invalid_schema");
155
+ const manifest = checkedManifest((input as CatalogMediaSourcePack).manifest);
156
+ for (const asset of manifest.assets) if (asset.source !== keyFor(asset)) refuse("unsafe_path", asset.assetRef);
157
+ await checkInventory(root, manifest);
158
+ const resources = await loadResources(manifest, root);
159
+ return { pack: { schemaVersion: "catalog.media-source-pack.v1", manifest } as CatalogMediaSourcePack, resources };
160
+ }
161
+ async function writeNewPack(pack: CatalogMediaSourcePack, resources: Map<string, Buffer>, targetRoot: string) {
162
+ const encoded = JSON.stringify(pack, null, 2) + "\n";
163
+ if (Buffer.byteLength(encoded) > SOURCE_PACK_LIMITS.manifestBytes) refuse("pack_limit_exceeded");
164
+ // mkdir without recursive is the atomic no-overwrite boundary.
165
+ await fs.mkdir(targetRoot).catch((error: NodeJS.ErrnoException) => {
166
+ if (error.code === "EEXIST") refuse("target_exists");
167
+ refuse("dependency_unavailable");
168
+ });
169
+ const root = await fs.realpath(targetRoot);
170
+ await fs.mkdir(resolve(root, "assets"));
171
+ const written = new Set<string>();
172
+ for (const asset of pack.manifest.assets) {
173
+ if (written.has(asset.source)) continue;
174
+ await fs.writeFile(resolve(root, asset.source), resources.get(asset.assetRef)!, { flag: "wx" });
175
+ written.add(asset.source);
176
+ }
177
+ await fs.writeFile(resolve(root, "manifest.json"), encoded, { flag: "wx" });
178
+ return pack;
179
+ }
180
+ export async function exportCatalogMediaSourcePack(options: { manifest: unknown; sourceRoot: string; targetRoot: string }) {
181
+ const manifest = checkedManifest(options.manifest);
182
+ const resources = await loadResources(manifest, options.sourceRoot);
183
+ const portable = { ...manifest, assets: manifest.assets.map((asset) => ({ ...asset, source: keyFor(asset) })) };
184
+ return writeNewPack({ schemaVersion: "catalog.media-source-pack.v1", manifest: portable }, resources, options.targetRoot);
185
+ }
186
+ export async function restoreCatalogMediaSourcePack(packRoot: string, targetRoot: string) {
187
+ const { pack, resources } = await validateCatalogMediaSourcePack(packRoot);
188
+ return writeNewPack(pack, resources, targetRoot);
189
+ }
190
+ export type CatalogMediaImportOutcome =
191
+ | { status: "complete"; successfulKeys: string[]; assets: Array<{ assetRef: string; key: string; url: string }> }
192
+ | { status: "validation_refusal"; code: string; reference?: string }
193
+ | { status: "dependency_unavailable" | "partial_upload"; successfulKeys: string[]; failedAssetRef: string; failedKey: string };
194
+ export async function importCatalogMediaSourcePack(root: string, storage: BlobStoragePort): Promise<CatalogMediaImportOutcome> {
195
+ let validated: Awaited<ReturnType<typeof validateCatalogMediaSourcePack>>;
196
+ try { validated = await validateCatalogMediaSourcePack(root); }
197
+ catch (error) {
198
+ const refusal = error as { code?: string; reference?: string };
199
+ return { status: "validation_refusal", code: refusal.code ?? "invalid_schema", ...(refusal.reference ? { reference: refusal.reference } : {}) };
200
+ }
201
+ const successfulKeys: string[] = [];
202
+ const assets: Array<{ assetRef: string; key: string; url: string }> = [];
203
+ const urlsByKey = new Map<string, string>();
204
+ for (const asset of validated.pack.manifest.assets) {
205
+ const key = keyFor(asset);
206
+ try {
207
+ if (!urlsByKey.has(key)) {
208
+ const result = await storage.upload(key, validated.resources.get(asset.assetRef)!, { contentType: asset.contentType });
209
+ if (result.pathname !== key) refuse("storage_path_mismatch", asset.assetRef);
210
+ successfulKeys.push(key);
211
+ urlsByKey.set(key, storage.urlFor(key));
212
+ }
213
+ assets.push({ assetRef: asset.assetRef, key, url: urlsByKey.get(key)! });
214
+ } catch {
215
+ return { status: successfulKeys.length ? "partial_upload" : "dependency_unavailable", successfulKeys, failedAssetRef: asset.assetRef, failedKey: key };
216
+ }
217
+ }
218
+ return { status: "complete", successfulKeys, assets };
219
+ }
@@ -0,0 +1,70 @@
1
+ import { z } from "./validation.js";
2
+ import type { CatalogProductTypeDefinition, CatalogProductTypeRegistry } from "./contracts/productTypes.js";
3
+ export { canonicalCatalogOption, validateCatalogQuantity } from "./contracts/productTypes.js";
4
+ export type { CatalogQuantity, CatalogOptionValue, CatalogDraftIssue, CatalogProductTypeDefinition, CatalogProductTypeRegistry } from "./contracts/productTypes.js";
5
+
6
+ export const catalogNamespacedKeySchema = z.string().max(120).regex(/^[a-z][a-z0-9.-]*:[a-z][a-z0-9._-]*$/u);
7
+ export const catalogDimensionKeySchema = z.string().max(64).regex(/^[a-z][a-z0-9_-]*$/u)
8
+ .refine((key) => !["__proto__", "constructor", "prototype"].includes(key));
9
+ export const catalogQuantityDimensionSchema = z.enum(["mass", "volume", "count"]);
10
+ export const catalogQuantitySchema = z.object({
11
+ dimension: catalogQuantityDimensionSchema,
12
+ unscaled: z.string().regex(/^[1-9][0-9]{0,39}$/u),
13
+ scale: z.number().int().min(0).max(12),
14
+ unit: catalogNamespacedKeySchema,
15
+ }).strict();
16
+ export const catalogOptionValueSchema = z.discriminatedUnion("kind", [
17
+ z.object({ kind: z.literal("choice"), value: z.string().min(1).max(120) }).strict(),
18
+ z.object({ kind: z.literal("quantity"), value: catalogQuantitySchema }).strict(),
19
+ z.object({ kind: z.literal("boolean"), value: z.boolean() }).strict(),
20
+ ]);
21
+ export const catalogProductTypeRefSchema = z.object({
22
+ key: catalogNamespacedKeySchema,
23
+ version: z.number().int().positive().max(2_147_483_647),
24
+ }).strict();
25
+ const unitSchema = z.object({
26
+ code: catalogNamespacedKeySchema,
27
+ dimension: catalogQuantityDimensionSchema,
28
+ integral: z.boolean(),
29
+ }).strict();
30
+ const dimensionSchema = z.discriminatedUnion("kind", [
31
+ z.object({ key: catalogDimensionKeySchema, kind: z.literal("choice"), values: z.array(z.string().min(1).max(120)).min(1).max(1000) }).strict(),
32
+ z.object({ key: catalogDimensionKeySchema, kind: z.literal("quantity"), dimension: catalogQuantityDimensionSchema,
33
+ units: z.array(catalogNamespacedKeySchema).min(1).max(32), role: z.literal("net_content").optional() }).strict(),
34
+ z.object({ key: catalogDimensionKeySchema, kind: z.literal("boolean") }).strict(),
35
+ ]);
36
+ const definitionSchema = catalogProductTypeRefSchema.extend({
37
+ units: z.array(unitSchema).max(64),
38
+ netContentUnits: z.array(catalogNamespacedKeySchema).min(1).max(64),
39
+ dimensions: z.array(dimensionSchema).max(16),
40
+ }).strict();
41
+ /** Definitions are installed by composition, never supplied as executable draft data. */
42
+ export function createCatalogProductTypeRegistry(definitions: readonly CatalogProductTypeDefinition[]): CatalogProductTypeRegistry {
43
+ const installed = new Map<string, CatalogProductTypeDefinition>();
44
+ for (const definition of definitions) {
45
+ const { validateContent, ...metadata } = definition;
46
+ const parsed = definitionSchema.parse(metadata);
47
+ const units = new Map(parsed.units.map((unit) => [unit.code, unit]));
48
+ const identity = `${parsed.key}@${parsed.version}`;
49
+ if (typeof validateContent !== "function" || installed.has(identity)
50
+ || units.size !== parsed.units.length
51
+ || new Set(parsed.dimensions.map(({ key }) => key)).size !== parsed.dimensions.length
52
+ || new Set(parsed.netContentUnits).size !== parsed.netContentUnits.length
53
+ || parsed.netContentUnits.some((code) => !units.has(code))) throw new Error("invalid_catalog_type_definition");
54
+ for (const dimension of parsed.dimensions) {
55
+ if (dimension.kind === "choice" && new Set(dimension.values).size !== dimension.values.length) throw new Error("invalid_catalog_type_definition");
56
+ if (dimension.kind === "quantity" && (new Set(dimension.units).size !== dimension.units.length
57
+ || dimension.units.some((code) => units.get(code)?.dimension !== dimension.dimension)
58
+ || (dimension.role === "net_content" && dimension.units.some((code) => !parsed.netContentUnits.includes(code))))) throw new Error("invalid_catalog_type_definition");
59
+ }
60
+ if (parsed.dimensions.filter((dimension) => dimension.kind === "quantity" && dimension.role === "net_content").length > 1) throw new Error("invalid_catalog_type_definition");
61
+ function freeze(value: object): void {
62
+ for (const child of Object.values(value)) if (child && typeof child === "object") freeze(child);
63
+ Object.freeze(value);
64
+ }
65
+ freeze(parsed);
66
+ installed.set(identity, Object.freeze({ ...parsed, validateContent }));
67
+ }
68
+ return Object.freeze({ resolve: (ref: z.infer<typeof catalogProductTypeRefSchema>) => installed.get(`${ref.key}@${ref.version}`) });
69
+ }
70
+
@@ -0,0 +1,44 @@
1
+ import type {
2
+ CatalogHeadRef, CatalogHistoryResult, CatalogPublicationReadResult,
3
+ CatalogPublicationReceipt, CatalogPublicationReceiptResult,
4
+ CatalogPublicationRefusal, CatalogPublishCommand,
5
+ } from "./contracts/lifecycle.js";
6
+
7
+ declare const verifiedCatalogPublisher: unique symbol;
8
+ /** Server authorization result. P1 exposes no constructor or actor-string conversion. */
9
+ export interface VerifiedCatalogPublisher {
10
+ readonly [verifiedCatalogPublisher]: true;
11
+ }
12
+
13
+ export interface CatalogValidationPort {
14
+ validate(candidateRef: string): Promise<{
15
+ candidateDigest: string;
16
+ issues: readonly { code: string; path: string }[];
17
+ }>;
18
+ }
19
+
20
+ /** No default binding: missing authority is unsupported; configured outage is dependency_unavailable. */
21
+ export interface CatalogPublicationReadPort {
22
+ current(productId: string): Promise<CatalogPublicationReadResult>;
23
+ readback(head: CatalogHeadRef): Promise<CatalogPublicationReadResult>;
24
+ receipt(scope: { productId: string; commandKey: string }): Promise<CatalogPublicationReceiptResult>;
25
+ /** Null starts a bounded history page; cursors retain the authority's stable ordering. */
26
+ history(productId: string, cursor: string | null): Promise<CatalogHistoryResult>;
27
+ }
28
+
29
+ /** Types only: proposal submission does not implement publication or lifecycle writes. */
30
+ export interface CatalogPublicationPort {
31
+ publish(command: CatalogPublishCommand, actor: VerifiedCatalogPublisher): Promise<
32
+ | { outcome: "committed" | "replayed"; receipt: CatalogPublicationReceipt }
33
+ | CatalogPublicationRefusal
34
+ >;
35
+ }
36
+
37
+ /** Describes a host's actual support; exporting ports supplies no capabilities. */
38
+ export interface CatalogCapabilityDescriptor {
39
+ read: boolean;
40
+ draft: boolean;
41
+ publicationRead: boolean;
42
+ publish: boolean;
43
+ lifecycleWrite: boolean;
44
+ }