@openlup/platform 0.17.0 → 0.19.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 (525) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +299 -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 +21 -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 +173 -0
  22. package/dist/catalog/bundleCatalogReadPort.js +36 -0
  23. package/dist/catalog/bundleCatalogStore.d.ts +5 -0
  24. package/dist/catalog/bundleCatalogStore.js +156 -0
  25. package/dist/catalog/bundlePriceSelection.d.ts +13 -0
  26. package/dist/catalog/bundlePriceSelection.js +26 -0
  27. package/dist/catalog/bundleRuleCodes.d.ts +41 -0
  28. package/dist/catalog/bundleRuleCodes.js +110 -0
  29. package/dist/catalog/bundleRules.d.ts +69 -0
  30. package/dist/catalog/bundleRules.js +102 -0
  31. package/dist/catalog/catalogDraftStore.d.ts +4 -0
  32. package/dist/catalog/catalogDraftStore.js +63 -0
  33. package/dist/catalog/catalogSkuEnvelope.d.ts +7 -0
  34. package/dist/catalog/catalogSkuEnvelope.js +142 -0
  35. package/dist/catalog/catalogSkuEnvelopeMapper.d.ts +38 -0
  36. package/dist/catalog/catalogSkuEnvelopeMapper.js +88 -0
  37. package/dist/catalog/contracts/bundleAdmin.d.ts +143 -0
  38. package/dist/catalog/contracts/bundleAdmin.js +1 -0
  39. package/dist/catalog/contracts/bundleAvailability.d.ts +61 -0
  40. package/dist/catalog/contracts/bundleAvailability.js +1 -0
  41. package/dist/catalog/contracts/bundleRead.d.ts +241 -0
  42. package/dist/catalog/contracts/bundleRead.js +1 -0
  43. package/dist/catalog/contracts/draft.d.ts +79 -0
  44. package/dist/catalog/contracts/draft.js +47 -0
  45. package/dist/catalog/contracts/foundation.d.ts +78 -0
  46. package/dist/catalog/contracts/foundation.js +53 -0
  47. package/dist/catalog/contracts/index.d.ts +14 -0
  48. package/dist/catalog/contracts/index.js +6 -0
  49. package/dist/catalog/contracts/lifecycle.d.ts +77 -0
  50. package/dist/catalog/contracts/lifecycle.js +1 -0
  51. package/dist/catalog/contracts/productMediaContracts.d.ts +77 -0
  52. package/dist/catalog/contracts/productMediaContracts.js +149 -0
  53. package/dist/catalog/contracts/productTypes.d.ts +54 -0
  54. package/dist/catalog/contracts/productTypes.js +16 -0
  55. package/dist/catalog/contracts/resolveProductMedia.d.ts +3 -0
  56. package/dist/catalog/contracts/resolveProductMedia.js +78 -0
  57. package/dist/catalog/contracts/sellable.d.ts +23 -0
  58. package/dist/catalog/contracts/sellable.js +1 -0
  59. package/dist/catalog/draftHandler.d.ts +25 -0
  60. package/dist/catalog/draftHandler.js +118 -0
  61. package/dist/catalog/draftPorts.d.ts +18 -0
  62. package/dist/catalog/draftPorts.js +8 -0
  63. package/dist/catalog/draftSchemas.d.ts +423 -0
  64. package/dist/catalog/draftSchemas.js +89 -0
  65. package/dist/catalog/draftValidation.d.ts +21 -0
  66. package/dist/catalog/draftValidation.js +125 -0
  67. package/dist/catalog/effectiveBundleCatalogQueries.d.ts +8 -0
  68. package/dist/catalog/effectiveBundleCatalogQueries.js +79 -0
  69. package/dist/catalog/effectiveBundleCatalogStore.d.ts +4 -0
  70. package/dist/catalog/effectiveBundleCatalogStore.js +156 -0
  71. package/dist/catalog/foundationSchemas.d.ts +98 -0
  72. package/dist/catalog/foundationSchemas.js +110 -0
  73. package/dist/catalog/index.d.ts +44 -0
  74. package/dist/catalog/index.js +26 -0
  75. package/dist/catalog/mediaSourcePack.d.ts +51 -0
  76. package/dist/catalog/mediaSourcePack.js +247 -0
  77. package/dist/catalog/productTypeSchemas.d.ts +46 -0
  78. package/dist/catalog/productTypeSchemas.js +72 -0
  79. package/dist/catalog/publicationPorts.d.ts +42 -0
  80. package/dist/catalog/publicationPorts.js +1 -0
  81. package/dist/catalog/readPorts.d.ts +51 -0
  82. package/dist/catalog/readPorts.js +1 -0
  83. package/dist/catalog/referenceCatalogHandler.d.ts +7 -0
  84. package/dist/catalog/referenceCatalogHandler.js +27 -0
  85. package/dist/catalog/resolvedSkuProjection.d.ts +88 -0
  86. package/dist/catalog/resolvedSkuProjection.js +60 -0
  87. package/dist/catalog/sellableBundleHandler.d.ts +46 -0
  88. package/dist/catalog/sellableBundleHandler.js +84 -0
  89. package/dist/catalog/sellableSchemas.d.ts +46 -0
  90. package/dist/catalog/sellableSchemas.js +27 -0
  91. package/dist/catalog/sql.d.ts +46 -0
  92. package/dist/catalog/sql.js +13 -0
  93. package/dist/catalog/validation.d.ts +2 -0
  94. package/dist/catalog/validation.js +3 -0
  95. package/dist/communications/contracts/index.d.ts +1 -0
  96. package/dist/communications/contracts/marketingSync.d.ts +194 -0
  97. package/dist/communications/contracts/marketingSync.js +1 -0
  98. package/dist/communications/index.d.ts +1 -0
  99. package/dist/communications/klaviyo/consentPush.d.ts +51 -0
  100. package/dist/communications/klaviyo/consentPush.js +19 -0
  101. package/dist/communications/klaviyo/index.d.ts +110 -0
  102. package/dist/communications/klaviyo/index.js +860 -0
  103. package/dist/communications/newsletterIntegrationContracts.d.ts +3 -3
  104. package/dist/communications/resend/index.d.ts +40 -0
  105. package/dist/communications/resend/index.js +310 -0
  106. package/dist/composition/catalogContribution.d.ts +18 -0
  107. package/dist/composition/catalogContribution.js +21 -0
  108. package/dist/composition/index.d.ts +3 -0
  109. package/dist/composition/index.js +3 -0
  110. package/dist/composition/inventoryContribution.d.ts +16 -0
  111. package/dist/composition/inventoryContribution.js +17 -0
  112. package/dist/composition/shippingContribution.d.ts +16 -0
  113. package/dist/composition/shippingContribution.js +17 -0
  114. package/dist/identity/customerContracts.d.ts +2 -2
  115. package/dist/inventory/adminInventory.d.ts +13 -0
  116. package/dist/inventory/adminInventory.js +214 -0
  117. package/dist/inventory/bindings.d.ts +35 -0
  118. package/dist/inventory/bindings.js +45 -0
  119. package/dist/inventory/contracts/forecast.d.ts +86 -0
  120. package/dist/inventory/contracts/forecast.js +1 -0
  121. package/dist/inventory/contracts/index.d.ts +4 -0
  122. package/dist/inventory/contracts/index.js +1 -0
  123. package/dist/inventory/contracts/inventory.d.ts +157 -0
  124. package/dist/inventory/contracts/inventory.js +1 -0
  125. package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
  126. package/dist/inventory/contracts/newPurchaseStock.js +1 -0
  127. package/dist/inventory/contracts/reservation.d.ts +18 -0
  128. package/dist/inventory/contracts/reservation.js +1 -0
  129. package/dist/inventory/forecast.d.ts +24 -0
  130. package/dist/inventory/forecast.js +200 -0
  131. package/dist/inventory/index.d.ts +14 -0
  132. package/dist/inventory/index.js +11 -0
  133. package/dist/inventory/inventoryHandlers.d.ts +53 -0
  134. package/dist/inventory/inventoryHandlers.js +151 -0
  135. package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
  136. package/dist/inventory/newPurchaseStockAvailability.js +75 -0
  137. package/dist/inventory/ports.d.ts +21 -0
  138. package/dist/inventory/ports.js +16 -0
  139. package/dist/inventory/renewalReservation.d.ts +27 -0
  140. package/dist/inventory/renewalReservation.js +61 -0
  141. package/dist/inventory/reservationPolicy.d.ts +6 -0
  142. package/dist/inventory/reservationPolicy.js +19 -0
  143. package/dist/inventory/schemas.d.ts +137 -0
  144. package/dist/inventory/schemas.js +112 -0
  145. package/dist/inventory/sql.d.ts +14 -0
  146. package/dist/inventory/sql.js +5 -0
  147. package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
  148. package/dist/inventory/subscriptionForecastCalculation.js +133 -0
  149. package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
  150. package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
  151. package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
  152. package/dist/pricing/acquisitionOfferPricing.js +25 -0
  153. package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
  154. package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
  155. package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
  156. package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
  157. package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
  158. package/dist/pricing/acquisitionQuoteBinding.js +12 -0
  159. package/dist/pricing/acquisitionReadiness.d.ts +31 -0
  160. package/dist/pricing/acquisitionReadiness.js +117 -0
  161. package/dist/pricing/acquisitionTerms.d.ts +19 -0
  162. package/dist/pricing/acquisitionTerms.js +126 -0
  163. package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
  164. package/dist/pricing/acquisitionTermsSchemas.js +42 -0
  165. package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
  166. package/dist/pricing/catalogFactsProvenance.js +64 -0
  167. package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
  168. package/dist/pricing/catalogFactsSchemas.js +58 -0
  169. package/dist/pricing/commerceOfferPricing.d.ts +25 -0
  170. package/dist/pricing/commerceOfferPricing.js +139 -0
  171. package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
  172. package/dist/pricing/commerceOfferPricingCache.js +44 -0
  173. package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
  174. package/dist/pricing/commerceOfferPricingHandler.js +78 -0
  175. package/dist/pricing/commercePriceAuthority.d.ts +28 -0
  176. package/dist/pricing/commercePriceAuthority.js +140 -0
  177. package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
  178. package/dist/pricing/commercePricingPolicyErrors.js +67 -0
  179. package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
  180. package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
  181. package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
  182. package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
  183. package/dist/pricing/commerceQuoteHandler.d.ts +35 -0
  184. package/dist/pricing/commerceQuoteHandler.js +122 -0
  185. package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
  186. package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
  187. package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
  188. package/dist/pricing/contracts/acquisitionTerms.js +2 -0
  189. package/dist/pricing/contracts/authority.d.ts +27 -0
  190. package/dist/pricing/contracts/authority.js +1 -0
  191. package/dist/pricing/contracts/evidence.d.ts +33 -0
  192. package/dist/pricing/contracts/evidence.js +1 -0
  193. package/dist/pricing/contracts/index.d.ts +17 -0
  194. package/dist/pricing/contracts/index.js +7 -0
  195. package/dist/pricing/contracts/money.d.ts +27 -0
  196. package/dist/pricing/contracts/money.js +1 -0
  197. package/dist/pricing/contracts/offer.d.ts +52 -0
  198. package/dist/pricing/contracts/offer.js +4 -0
  199. package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
  200. package/dist/pricing/contracts/offerPolicy.js +5 -0
  201. package/dist/pricing/contracts/policy.d.ts +22 -0
  202. package/dist/pricing/contracts/policy.js +3 -0
  203. package/dist/pricing/contracts/provenance.d.ts +24 -0
  204. package/dist/pricing/contracts/provenance.js +2 -0
  205. package/dist/pricing/contracts/quote.d.ts +94 -0
  206. package/dist/pricing/contracts/quote.js +1 -0
  207. package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
  208. package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
  209. package/dist/pricing/dbBackedCommerceQuotePort.d.ts +74 -0
  210. package/dist/pricing/dbBackedCommerceQuotePort.js +223 -0
  211. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
  212. package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
  213. package/dist/pricing/index.d.ts +52 -0
  214. package/dist/pricing/index.js +36 -0
  215. package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
  216. package/dist/pricing/offerPolicyReadiness.js +79 -0
  217. package/dist/pricing/offerPolicySchemas.d.ts +25 -0
  218. package/dist/pricing/offerPolicySchemas.js +15 -0
  219. package/dist/pricing/offerPricingSchemas.d.ts +315 -0
  220. package/dist/pricing/offerPricingSchemas.js +65 -0
  221. package/dist/pricing/ports.d.ts +13 -0
  222. package/dist/pricing/ports.js +9 -0
  223. package/dist/pricing/pricingPolicyToken.d.ts +39 -0
  224. package/dist/pricing/pricingPolicyToken.js +84 -0
  225. package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
  226. package/dist/pricing/quoteAcquisitionTerms.js +4 -0
  227. package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
  228. package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
  229. package/dist/pricing/quotePorts.d.ts +30 -0
  230. package/dist/pricing/quotePorts.js +20 -0
  231. package/dist/pricing/quotePrimitives.d.ts +13 -0
  232. package/dist/pricing/quotePrimitives.js +18 -0
  233. package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
  234. package/dist/pricing/quotePromotionEvidence.js +73 -0
  235. package/dist/pricing/quoteSchemas.d.ts +1176 -0
  236. package/dist/pricing/quoteSchemas.js +196 -0
  237. package/dist/pricing/quoteShippingPromotions.d.ts +20 -0
  238. package/dist/pricing/quoteShippingPromotions.js +1 -0
  239. package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
  240. package/dist/pricing/subscriptionPricePolicy.js +96 -0
  241. package/dist/pricing/taxProfileGuard.d.ts +2 -0
  242. package/dist/pricing/taxProfileGuard.js +24 -0
  243. package/dist/pricing/validation.d.ts +2 -0
  244. package/dist/pricing/validation.js +3 -0
  245. package/dist/promotions/adminBindings.d.ts +72 -0
  246. package/dist/promotions/adminBindings.js +1 -0
  247. package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
  248. package/dist/promotions/adminPromotionCodesContracts.js +211 -0
  249. package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
  250. package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
  251. package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
  252. package/dist/promotions/adminPromotionCodesHandler.js +194 -0
  253. package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
  254. package/dist/promotions/adminPromotionsContracts.js +99 -0
  255. package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
  256. package/dist/promotions/adminPromotionsDataPort.js +1 -0
  257. package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
  258. package/dist/promotions/adminPromotionsHandler.js +47 -0
  259. package/dist/promotions/commerceQuoteReadPreparation.d.ts +3 -0
  260. package/dist/promotions/commerceQuoteReadPreparation.js +27 -0
  261. package/dist/promotions/contracts/index.d.ts +387 -0
  262. package/dist/promotions/contracts/index.js +1 -0
  263. package/dist/promotions/index.d.ts +25 -0
  264. package/dist/promotions/index.js +22 -0
  265. package/dist/promotions/promoDataPort.d.ts +47 -0
  266. package/dist/promotions/promoDataPort.js +14 -0
  267. package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
  268. package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
  269. package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
  270. package/dist/promotions/promotionBenefitDefinition.js +33 -0
  271. package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
  272. package/dist/promotions/promotionClaimSweepPort.js +8 -0
  273. package/dist/promotions/promotionClaims.d.ts +5 -0
  274. package/dist/promotions/promotionClaims.js +57 -0
  275. package/dist/promotions/promotionCodePreview.d.ts +11 -0
  276. package/dist/promotions/promotionCodePreview.js +154 -0
  277. package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
  278. package/dist/promotions/promotionCodeQuotePort.js +1 -0
  279. package/dist/promotions/promotionQuoteAcceptance.d.ts +70 -0
  280. package/dist/promotions/promotionQuoteAcceptance.js +202 -0
  281. package/dist/promotions/promotionQuoteBinding.d.ts +73 -0
  282. package/dist/promotions/promotionQuoteBinding.js +87 -0
  283. package/dist/promotions/quoteAcquisitionBinding.d.ts +6 -0
  284. package/dist/promotions/quoteAcquisitionBinding.js +1 -0
  285. package/dist/promotions/quoteAutomaticPromotions.d.ts +26 -0
  286. package/dist/promotions/quoteAutomaticPromotions.js +60 -0
  287. package/dist/promotions/quotePromoDiscounts.d.ts +36 -0
  288. package/dist/promotions/quotePromoDiscounts.js +146 -0
  289. package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
  290. package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
  291. package/dist/promotions/quotePromotionV2.d.ts +30 -0
  292. package/dist/promotions/quotePromotionV2.js +186 -0
  293. package/dist/promotions/quoteShippingPolicy.d.ts +23 -0
  294. package/dist/promotions/quoteShippingPolicy.js +47 -0
  295. package/dist/promotions/sql.d.ts +14 -0
  296. package/dist/promotions/sql.js +5 -0
  297. package/dist/promotions/validation.d.ts +2 -0
  298. package/dist/promotions/validation.js +3 -0
  299. package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
  300. package/dist/shipping/addressCanonLookupHandler.js +105 -0
  301. package/dist/shipping/addressCanonical.d.ts +2 -0
  302. package/dist/shipping/addressCanonical.js +26 -0
  303. package/dist/shipping/addressPorts.d.ts +39 -0
  304. package/dist/shipping/addressPorts.js +1 -0
  305. package/dist/shipping/addressSchemas.d.ts +30 -0
  306. package/dist/shipping/addressSchemas.js +1 -0
  307. package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
  308. package/dist/shipping/adminShippingRateHandler.js +64 -0
  309. package/dist/shipping/bindings.d.ts +57 -0
  310. package/dist/shipping/bindings.js +46 -0
  311. package/dist/shipping/contracts/address.d.ts +144 -0
  312. package/dist/shipping/contracts/address.js +1 -0
  313. package/dist/shipping/contracts/delivery.d.ts +110 -0
  314. package/dist/shipping/contracts/delivery.js +1 -0
  315. package/dist/shipping/contracts/index.d.ts +4 -0
  316. package/dist/shipping/contracts/index.js +1 -0
  317. package/dist/shipping/contracts/shippingRate.d.ts +11 -0
  318. package/dist/shipping/contracts/shippingRate.js +1 -0
  319. package/dist/shipping/contracts/threshold.d.ts +22 -0
  320. package/dist/shipping/contracts/threshold.js +1 -0
  321. package/dist/shipping/customerAddressesHandler.d.ts +5 -0
  322. package/dist/shipping/customerAddressesHandler.js +80 -0
  323. package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
  324. package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
  325. package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
  326. package/dist/shipping/deliverySelectionHandler.js +77 -0
  327. package/dist/shipping/deliverySelectionPort.d.ts +8 -0
  328. package/dist/shipping/deliverySelectionPort.js +1 -0
  329. package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
  330. package/dist/shipping/deliverySelectionSummary.js +53 -0
  331. package/dist/shipping/handlerSchemas.d.ts +21 -0
  332. package/dist/shipping/handlerSchemas.js +1 -0
  333. package/dist/shipping/index.d.ts +20 -0
  334. package/dist/shipping/index.js +10 -0
  335. package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
  336. package/dist/shipping/pickupPointSearchPort.js +1 -0
  337. package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
  338. package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
  339. package/dist/shipping/shippingRatePorts.d.ts +52 -0
  340. package/dist/shipping/shippingRatePorts.js +1 -0
  341. package/dist/shipping/thresholdPolicy.d.ts +42 -0
  342. package/dist/shipping/thresholdPolicy.js +60 -0
  343. package/package.json +62 -2
  344. package/release-gates.json +263 -53
  345. package/sql/20260807140000_read_parity_catalog.sql +94 -0
  346. package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
  347. package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
  348. package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
  349. package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
  350. package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
  351. package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
  352. package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
  353. package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
  354. package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
  355. package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
  356. package/src/catalog/activeSkuSelection.ts +20 -0
  357. package/src/catalog/adminBindings.ts +59 -0
  358. package/src/catalog/adminBundleHandler.ts +165 -0
  359. package/src/catalog/adminBundleReadHandler.ts +263 -0
  360. package/src/catalog/adminBundleWritePort.ts +102 -0
  361. package/src/catalog/bundleAvailability.ts +129 -0
  362. package/src/catalog/bundleAvailabilityService.ts +171 -0
  363. package/src/catalog/bundleBindings.ts +29 -0
  364. package/src/catalog/bundleCatalogQueries.ts +98 -0
  365. package/src/catalog/bundleCatalogReadPort.ts +181 -0
  366. package/src/catalog/bundleCatalogStore.ts +274 -0
  367. package/src/catalog/bundlePriceSelection.ts +25 -0
  368. package/src/catalog/bundleRuleCodes.ts +134 -0
  369. package/src/catalog/bundleRules.ts +153 -0
  370. package/src/catalog/catalogDraftStore.ts +71 -0
  371. package/src/catalog/catalogSkuEnvelope.ts +214 -0
  372. package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
  373. package/src/catalog/contracts/bundleAdmin.ts +20 -0
  374. package/src/catalog/contracts/bundleAvailability.ts +5 -0
  375. package/src/catalog/contracts/bundleRead.ts +13 -0
  376. package/src/catalog/contracts/draft.ts +69 -0
  377. package/src/catalog/contracts/foundation.ts +104 -0
  378. package/src/catalog/contracts/index.ts +14 -0
  379. package/src/catalog/contracts/lifecycle.ts +74 -0
  380. package/src/catalog/contracts/productMediaContracts.ts +170 -0
  381. package/src/catalog/contracts/productTypes.ts +35 -0
  382. package/src/catalog/contracts/resolveProductMedia.ts +85 -0
  383. package/src/catalog/contracts/sellable.ts +12 -0
  384. package/src/catalog/draftHandler.ts +105 -0
  385. package/src/catalog/draftPorts.ts +18 -0
  386. package/src/catalog/draftSchemas.ts +88 -0
  387. package/src/catalog/draftValidation.ts +100 -0
  388. package/src/catalog/effectiveBundleCatalogQueries.ts +80 -0
  389. package/src/catalog/effectiveBundleCatalogStore.ts +208 -0
  390. package/src/catalog/foundationSchemas.ts +123 -0
  391. package/src/catalog/index.ts +44 -0
  392. package/src/catalog/mediaSourcePack.ts +219 -0
  393. package/src/catalog/productTypeSchemas.ts +70 -0
  394. package/src/catalog/publicationPorts.ts +44 -0
  395. package/src/catalog/readPorts.ts +61 -0
  396. package/src/catalog/referenceCatalogHandler.ts +39 -0
  397. package/src/catalog/resolvedSkuProjection.ts +76 -0
  398. package/src/catalog/sellableBundleHandler.ts +133 -0
  399. package/src/catalog/sellableSchemas.ts +33 -0
  400. package/src/catalog/sql.ts +14 -0
  401. package/src/catalog/validation.ts +3 -0
  402. package/src/communications/contracts/index.ts +1 -0
  403. package/src/communications/contracts/marketingSync.ts +109 -0
  404. package/src/communications/index.ts +1 -0
  405. package/src/communications/klaviyo/consentPush.ts +19 -0
  406. package/src/communications/klaviyo/index.ts +649 -0
  407. package/src/communications/resend/index.ts +350 -0
  408. package/src/composition/catalogContribution.ts +32 -0
  409. package/src/composition/index.ts +3 -0
  410. package/src/composition/inventoryContribution.ts +24 -0
  411. package/src/composition/shippingContribution.ts +24 -0
  412. package/src/inventory/adminInventory.ts +251 -0
  413. package/src/inventory/bindings.ts +61 -0
  414. package/src/inventory/contracts/forecast.ts +96 -0
  415. package/src/inventory/contracts/index.ts +18 -0
  416. package/src/inventory/contracts/inventory.ts +14 -0
  417. package/src/inventory/contracts/newPurchaseStock.ts +23 -0
  418. package/src/inventory/contracts/reservation.ts +20 -0
  419. package/src/inventory/forecast.ts +239 -0
  420. package/src/inventory/index.ts +22 -0
  421. package/src/inventory/inventoryHandlers.ts +215 -0
  422. package/src/inventory/newPurchaseStockAvailability.ts +120 -0
  423. package/src/inventory/ports.ts +46 -0
  424. package/src/inventory/renewalReservation.ts +88 -0
  425. package/src/inventory/reservationPolicy.ts +27 -0
  426. package/src/inventory/schemas.ts +128 -0
  427. package/src/inventory/sql.ts +5 -0
  428. package/src/inventory/subscriptionForecastCalculation.ts +157 -0
  429. package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
  430. package/src/pricing/acquisitionOfferPricing.ts +38 -0
  431. package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
  432. package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
  433. package/src/pricing/acquisitionQuoteBinding.ts +12 -0
  434. package/src/pricing/acquisitionReadiness.ts +148 -0
  435. package/src/pricing/acquisitionTerms.ts +125 -0
  436. package/src/pricing/acquisitionTermsSchemas.ts +52 -0
  437. package/src/pricing/catalogFactsProvenance.ts +102 -0
  438. package/src/pricing/catalogFactsSchemas.ts +69 -0
  439. package/src/pricing/commerceOfferPricing.ts +224 -0
  440. package/src/pricing/commerceOfferPricingCache.ts +53 -0
  441. package/src/pricing/commerceOfferPricingHandler.ts +137 -0
  442. package/src/pricing/commercePriceAuthority.ts +169 -0
  443. package/src/pricing/commercePricingPolicyErrors.ts +88 -0
  444. package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
  445. package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
  446. package/src/pricing/commerceQuoteHandler.ts +199 -0
  447. package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
  448. package/src/pricing/contracts/acquisitionTerms.ts +26 -0
  449. package/src/pricing/contracts/authority.ts +44 -0
  450. package/src/pricing/contracts/evidence.ts +25 -0
  451. package/src/pricing/contracts/index.ts +17 -0
  452. package/src/pricing/contracts/money.ts +32 -0
  453. package/src/pricing/contracts/offer.ts +42 -0
  454. package/src/pricing/contracts/offerPolicy.ts +17 -0
  455. package/src/pricing/contracts/policy.ts +26 -0
  456. package/src/pricing/contracts/provenance.ts +19 -0
  457. package/src/pricing/contracts/quote.ts +106 -0
  458. package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
  459. package/src/pricing/dbBackedCommerceQuotePort.ts +280 -0
  460. package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
  461. package/src/pricing/index.ts +53 -0
  462. package/src/pricing/offerPolicyReadiness.ts +110 -0
  463. package/src/pricing/offerPolicySchemas.ts +23 -0
  464. package/src/pricing/offerPricingSchemas.ts +92 -0
  465. package/src/pricing/ports.ts +17 -0
  466. package/src/pricing/pricingPolicyToken.ts +119 -0
  467. package/src/pricing/quoteAcquisitionTerms.ts +10 -0
  468. package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
  469. package/src/pricing/quotePorts.ts +58 -0
  470. package/src/pricing/quotePrimitives.ts +21 -0
  471. package/src/pricing/quotePromotionEvidence.ts +82 -0
  472. package/src/pricing/quoteSchemas.ts +230 -0
  473. package/src/pricing/quoteShippingPromotions.ts +21 -0
  474. package/src/pricing/subscriptionPricePolicy.ts +113 -0
  475. package/src/pricing/taxProfileGuard.ts +33 -0
  476. package/src/pricing/validation.ts +3 -0
  477. package/src/promotions/adminBindings.ts +53 -0
  478. package/src/promotions/adminPromotionCodesContracts.ts +244 -0
  479. package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
  480. package/src/promotions/adminPromotionCodesHandler.ts +254 -0
  481. package/src/promotions/adminPromotionsContracts.ts +121 -0
  482. package/src/promotions/adminPromotionsDataPort.ts +48 -0
  483. package/src/promotions/adminPromotionsHandler.ts +72 -0
  484. package/src/promotions/commerceQuoteReadPreparation.ts +29 -0
  485. package/src/promotions/contracts/index.ts +387 -0
  486. package/src/promotions/index.ts +25 -0
  487. package/src/promotions/promoDataPort.ts +65 -0
  488. package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
  489. package/src/promotions/promotionBenefitDefinition.ts +48 -0
  490. package/src/promotions/promotionClaimSweepPort.ts +23 -0
  491. package/src/promotions/promotionClaims.ts +70 -0
  492. package/src/promotions/promotionCodePreview.ts +183 -0
  493. package/src/promotions/promotionCodeQuotePort.ts +35 -0
  494. package/src/promotions/promotionQuoteAcceptance.ts +291 -0
  495. package/src/promotions/promotionQuoteBinding.ts +163 -0
  496. package/src/promotions/quoteAcquisitionBinding.ts +7 -0
  497. package/src/promotions/quoteAutomaticPromotions.ts +84 -0
  498. package/src/promotions/quotePromoDiscounts.ts +194 -0
  499. package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
  500. package/src/promotions/quotePromotionV2.ts +239 -0
  501. package/src/promotions/quoteShippingPolicy.ts +61 -0
  502. package/src/promotions/sql.ts +5 -0
  503. package/src/promotions/validation.ts +3 -0
  504. package/src/shipping/addressCanonLookupHandler.ts +114 -0
  505. package/src/shipping/addressCanonical.ts +31 -0
  506. package/src/shipping/addressPorts.ts +48 -0
  507. package/src/shipping/addressSchemas.ts +28 -0
  508. package/src/shipping/adminShippingRateHandler.ts +72 -0
  509. package/src/shipping/bindings.ts +74 -0
  510. package/src/shipping/contracts/address.ts +71 -0
  511. package/src/shipping/contracts/delivery.ts +114 -0
  512. package/src/shipping/contracts/index.ts +19 -0
  513. package/src/shipping/contracts/shippingRate.ts +7 -0
  514. package/src/shipping/contracts/threshold.ts +22 -0
  515. package/src/shipping/customerAddressesHandler.ts +100 -0
  516. package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
  517. package/src/shipping/deliverySelectionHandler.ts +94 -0
  518. package/src/shipping/deliverySelectionPort.ts +10 -0
  519. package/src/shipping/deliverySelectionSummary.ts +56 -0
  520. package/src/shipping/handlerSchemas.ts +19 -0
  521. package/src/shipping/index.ts +26 -0
  522. package/src/shipping/pickupPointSearchPort.ts +8 -0
  523. package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
  524. package/src/shipping/shippingRatePorts.ts +36 -0
  525. package/src/shipping/thresholdPolicy.ts +65 -0
@@ -0,0 +1,89 @@
1
+ export function createBundleAvailabilityServices({ deriveBundleAvailability }) {
2
+ function createBundleAvailabilityService(deps) {
3
+ async function describeCompositions(compositions) {
4
+ if (compositions.length === 0)
5
+ return [];
6
+ const items = requestItems(compositions);
7
+ const stock = items.length === 0
8
+ ? new Map()
9
+ : sellableBySku(await deps.offerAvailabilityPort.getAvailability({ items }));
10
+ return compositions.map((composition) => {
11
+ const components = composition.components.map((component) => ({
12
+ sku: component.sku,
13
+ quantity: component.quantity,
14
+ isAddon: component.isAddon,
15
+ // `has` distinguishes "answered null" from "never answered"; both fold to
16
+ // null, and neither is allowed to become a number here.
17
+ sellableNow: stock.has(component.sku) ? (stock.get(component.sku) ?? null) : null,
18
+ }));
19
+ return {
20
+ composition,
21
+ components,
22
+ availability: deriveBundleAvailability({
23
+ components,
24
+ lowStockThreshold: deps.lowStockThreshold,
25
+ includeAddonsInStock: deps.includeAddonsInStock,
26
+ }),
27
+ };
28
+ });
29
+ }
30
+ return {
31
+ describeCompositions,
32
+ async describeActiveBundles(input = {}) {
33
+ return describeCompositions(await deps.bundleCatalogReadPort.listActiveBundleCompositions(input));
34
+ },
35
+ };
36
+ }
37
+ return { createBundleAvailabilityService };
38
+ }
39
+ /**
40
+ * The port to use where no stock source is bound at all.
41
+ *
42
+ * It answers every item with `sellableNow: null`, which the kernel reads as
43
+ * unknown — so a deployment without an inventory rail publishes bundles whose
44
+ * price is exact and whose stock is honestly unstated. The alternative, omitting
45
+ * the availability block, would have made "we do not know" indistinguishable from
46
+ * "we did not ask", and the alternative to THAT — defaulting to available —
47
+ * oversells on a deployment that never counted anything.
48
+ */
49
+ export function createUnknownOfferAvailabilityPort() {
50
+ return {
51
+ getAvailability({ items }) {
52
+ return Promise.resolve(items.map((item) => ({
53
+ sku: item.sku,
54
+ productSlug: item.productSlug,
55
+ variantId: item.variantId,
56
+ status: "unknown",
57
+ visibleInConfigurator: true,
58
+ sellableNow: null,
59
+ reasonCode: "stock_unknown",
60
+ source: "unbound",
61
+ })));
62
+ },
63
+ };
64
+ }
65
+ /**
66
+ * One request item per distinct sku across every bundle. `requestedQuantity` stays
67
+ * at the port's own default of one: the question asked is "how many units exist",
68
+ * and the multiplication by the bill of materials belongs to the pure kernel,
69
+ * which is the only place that knows how many bundles a figure buys.
70
+ */
71
+ function requestItems(compositions) {
72
+ const bySku = new Map();
73
+ for (const composition of compositions) {
74
+ for (const component of composition.components) {
75
+ if (!bySku.has(component.sku))
76
+ bySku.set(component.sku, component);
77
+ }
78
+ }
79
+ return [...bySku.values()].map((component) => ({
80
+ sku: component.sku,
81
+ productSlug: component.productSlug,
82
+ variantId: component.variantId,
83
+ requestedQuantity: 1,
84
+ checkoutMode: "one_time",
85
+ }));
86
+ }
87
+ function sellableBySku(answers) {
88
+ return new Map(answers.map((answer) => [answer.sku, answer.sellableNow ?? null]));
89
+ }
@@ -0,0 +1,33 @@
1
+ import type { BundleActorKind } from "./bundleRuleCodes.js";
2
+ /** The host's normalized stock seam; no inventory implementation is owned here. */
3
+ export type BundleStockStatus = "available" | "low_stock" | "out_of_stock" | "unknown";
4
+ export interface BundleStockRequestItem {
5
+ sku: string;
6
+ productSlug: string;
7
+ variantId: string;
8
+ requestedQuantity: number;
9
+ checkoutMode: "one_time" | "subscription";
10
+ }
11
+ export interface BundleStockAvailability {
12
+ sku: string;
13
+ productSlug: string;
14
+ variantId: string;
15
+ status: BundleStockStatus;
16
+ visibleInConfigurator: boolean;
17
+ sellableNow: number | null;
18
+ reasonCode: string;
19
+ source: string;
20
+ }
21
+ export interface BundleStockPort {
22
+ getAvailability(input: {
23
+ items: readonly BundleStockRequestItem[];
24
+ }): Promise<BundleStockAvailability[]>;
25
+ }
26
+ export interface BundleRuleBindings {
27
+ assertActivateNotMachineActor(input: {
28
+ operation: string;
29
+ actorKind: BundleActorKind;
30
+ }): "DRAFT_ONLY_FOR_MACHINE" | null;
31
+ offerAvailabilityStatusFor(sellableNow: number | null, lowStockThreshold?: number): BundleStockStatus;
32
+ offerAvailabilityReasonCode(status: BundleStockStatus): string;
33
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The statements the direct-Postgres bundle read adapter renders.
3
+ *
4
+ * They live beside the adapter rather than inside it because they are the part a
5
+ * reader comes here to check — which relations are touched, which columns are
6
+ * asked for, and how a component's reference price is resolved — and reading them
7
+ * should not mean scrolling past the row marshalling that consumes them.
8
+ */
9
+ export declare const SUMMARY_COLUMNS = "\n b.id, b.code, b.title, b.status, b.fulfillment_mode, b.updated_at,\n b.composition_constraint, b.metadata,\n (SELECT count(*) FROM public.catalog_bundle_components c WHERE c.bundle_id = b.id)\n AS component_count,\n EXISTS (SELECT 1 FROM public.catalog_bundle_prices p WHERE p.bundle_id = b.id AND p.active)\n AS has_active_target_price";
10
+ export declare const LIST_FILTER = "\n WHERE ($1::text = 'all' OR b.status = $1::text)\n AND ($2::text IS NULL OR b.code ILIKE '%' || $2 || '%' OR b.title ILIKE '%' || $2 || '%')";
11
+ export declare const LIST_SQL = "SELECT \n b.id, b.code, b.title, b.status, b.fulfillment_mode, b.updated_at,\n b.composition_constraint, b.metadata,\n (SELECT count(*) FROM public.catalog_bundle_components c WHERE c.bundle_id = b.id)\n AS component_count,\n EXISTS (SELECT 1 FROM public.catalog_bundle_prices p WHERE p.bundle_id = b.id AND p.active)\n AS has_active_target_price FROM public.catalog_bundles b \n WHERE ($1::text = 'all' OR b.status = $1::text)\n AND ($2::text IS NULL OR b.code ILIKE '%' || $2 || '%' OR b.title ILIKE '%' || $2 || '%')\n ORDER BY b.code LIMIT $3 OFFSET $4";
12
+ export declare const COUNT_SQL = "SELECT count(*) AS total FROM public.catalog_bundles b \n WHERE ($1::text = 'all' OR b.status = $1::text)\n AND ($2::text IS NULL OR b.code ILIKE '%' || $2 || '%' OR b.title ILIKE '%' || $2 || '%')";
13
+ export declare const DETAIL_SQL = "SELECT \n b.id, b.code, b.title, b.status, b.fulfillment_mode, b.updated_at,\n b.composition_constraint, b.metadata,\n (SELECT count(*) FROM public.catalog_bundle_components c WHERE c.bundle_id = b.id)\n AS component_count,\n EXISTS (SELECT 1 FROM public.catalog_bundle_prices p WHERE p.bundle_id = b.id AND p.active)\n AS has_active_target_price FROM public.catalog_bundles b WHERE b.code = $1";
14
+ /**
15
+ * The unit's reference price in ONE named list: the lowest tier that is active,
16
+ * newest window first. A unit the list prices nothing for resolves to NULL rather
17
+ * than dropping the component, so a caller sees an unpriced part instead of a
18
+ * shorter bill of materials.
19
+ */
20
+ export declare const REFERENCE_PRICE = "(\n SELECT pe.unit_price_minor FROM public.price_entries pe\n WHERE pe.variant_id = s.id AND pe.price_list_id = %LIST% AND pe.active\n ORDER BY pe.min_qty ASC, pe.valid_from DESC LIMIT 1)";
21
+ export declare const COMPONENT_SQL: string;
22
+ export declare const FEED_COMPONENT_SQL: string;
23
+ export declare const PRICE_SQL = "\n SELECT p.mode, p.target_price_minor, list.currency, p.amount_kind, p.active,\n p.valid_from, p.valid_to\n FROM public.catalog_bundle_prices p\n JOIN public.price_lists AS list ON list.id = p.price_list_id\n WHERE p.bundle_id = $1::uuid\n ORDER BY p.valid_from DESC";
24
+ export declare const OWN_LIST_SQL = "\n SELECT list.id, list.currency\n FROM public.catalog_bundle_prices p\n JOIN public.price_lists AS list ON list.id = p.price_list_id\n WHERE p.bundle_id = $1::uuid AND p.active AND ($2::text IS NULL OR p.mode = $2::text)\n ORDER BY p.valid_from DESC LIMIT 1";
25
+ export declare const CURRENCY_LIST_SQL = "\n SELECT id, currency FROM public.price_lists\n WHERE currency = $1::text AND status = 'active'\n ORDER BY created_at DESC LIMIT 1";
26
+ export declare const FEED_SQL = "\n SELECT b.id, b.code, b.title, b.fulfillment_mode, p.mode, p.target_price_minor,\n p.price_list_id, list.currency\n FROM public.catalog_bundles b\n JOIN public.catalog_bundle_prices p ON p.bundle_id = b.id AND p.active\n JOIN public.price_lists AS list ON list.id = p.price_list_id\n WHERE b.status = 'active' AND ($1::text IS NULL OR list.currency = $1::text)\n ORDER BY b.code, p.valid_from DESC";
27
+ export declare const HISTORY_SQL = "\n SELECT id, bundle_code, action, before_state, after_state, created_at\n FROM public.catalog_bundle_write_events\n WHERE bundle_code = $1::text\n ORDER BY created_at DESC LIMIT $2 OFFSET $3";
28
+ export declare const HISTORY_COUNT_SQL = "\n SELECT count(*) AS total FROM public.catalog_bundle_write_events WHERE bundle_code = $1::text";
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The statements the direct-Postgres bundle read adapter renders.
3
+ *
4
+ * They live beside the adapter rather than inside it because they are the part a
5
+ * reader comes here to check — which relations are touched, which columns are
6
+ * asked for, and how a component's reference price is resolved — and reading them
7
+ * should not mean scrolling past the row marshalling that consumes them.
8
+ */
9
+ export const SUMMARY_COLUMNS = `
10
+ b.id, b.code, b.title, b.status, b.fulfillment_mode, b.updated_at,
11
+ b.composition_constraint, b.metadata,
12
+ (SELECT count(*) FROM public.catalog_bundle_components c WHERE c.bundle_id = b.id)
13
+ AS component_count,
14
+ EXISTS (SELECT 1 FROM public.catalog_bundle_prices p WHERE p.bundle_id = b.id AND p.active)
15
+ AS has_active_target_price`;
16
+ export const LIST_FILTER = `
17
+ WHERE ($1::text = 'all' OR b.status = $1::text)
18
+ AND ($2::text IS NULL OR b.code ILIKE '%' || $2 || '%' OR b.title ILIKE '%' || $2 || '%')`;
19
+ export const LIST_SQL = `SELECT ${SUMMARY_COLUMNS} FROM public.catalog_bundles b ${LIST_FILTER}
20
+ ORDER BY b.code LIMIT $3 OFFSET $4`;
21
+ export const COUNT_SQL = `SELECT count(*) AS total FROM public.catalog_bundles b ${LIST_FILTER}`;
22
+ export const DETAIL_SQL = `SELECT ${SUMMARY_COLUMNS} FROM public.catalog_bundles b WHERE b.code = $1`;
23
+ /**
24
+ * The unit's reference price in ONE named list: the lowest tier that is active,
25
+ * newest window first. A unit the list prices nothing for resolves to NULL rather
26
+ * than dropping the component, so a caller sees an unpriced part instead of a
27
+ * shorter bill of materials.
28
+ */
29
+ export const REFERENCE_PRICE = `(
30
+ SELECT pe.unit_price_minor FROM public.price_entries pe
31
+ WHERE pe.variant_id = s.id AND pe.price_list_id = %LIST% AND pe.active
32
+ ORDER BY pe.min_qty ASC, pe.valid_from DESC LIMIT 1)`;
33
+ export const COMPONENT_SQL = `
34
+ SELECT c.bundle_id, s.sku, s.title, pr.slug AS product_slug, s.id AS variant_id,
35
+ c.quantity, c.is_addon, c.sort_order,
36
+ ${REFERENCE_PRICE.replace("%LIST%", "$2::uuid")} AS reference_unit_price_minor
37
+ FROM public.catalog_bundle_components c
38
+ JOIN public.catalog_skus s ON s.id = c.catalog_sku_id
39
+ JOIN public.catalog_products pr ON pr.id = s.product_id
40
+ WHERE c.bundle_id = $1::uuid
41
+ ORDER BY c.sort_order, s.sku`;
42
+ // One statement for the whole feed: each bundle carries its own price list, so the
43
+ // per-bundle list is zipped in rather than looped over.
44
+ export const FEED_COMPONENT_SQL = `
45
+ SELECT c.bundle_id, s.sku, s.title, pr.slug AS product_slug, s.id AS variant_id,
46
+ c.quantity, c.is_addon, c.sort_order,
47
+ ${REFERENCE_PRICE.replace("%LIST%", "selected.price_list_id")} AS reference_unit_price_minor
48
+ FROM unnest($1::uuid[], $2::uuid[]) AS selected(bundle_id, price_list_id)
49
+ JOIN public.catalog_bundle_components c ON c.bundle_id = selected.bundle_id
50
+ JOIN public.catalog_skus s ON s.id = c.catalog_sku_id
51
+ JOIN public.catalog_products pr ON pr.id = s.product_id
52
+ ORDER BY c.bundle_id, c.sort_order, s.sku`;
53
+ export const PRICE_SQL = `
54
+ SELECT p.mode, p.target_price_minor, list.currency, p.amount_kind, p.active,
55
+ p.valid_from, p.valid_to
56
+ FROM public.catalog_bundle_prices p
57
+ JOIN public.price_lists AS list ON list.id = p.price_list_id
58
+ WHERE p.bundle_id = $1::uuid
59
+ ORDER BY p.valid_from DESC`;
60
+ export const OWN_LIST_SQL = `
61
+ SELECT list.id, list.currency
62
+ FROM public.catalog_bundle_prices p
63
+ JOIN public.price_lists AS list ON list.id = p.price_list_id
64
+ WHERE p.bundle_id = $1::uuid AND p.active AND ($2::text IS NULL OR p.mode = $2::text)
65
+ ORDER BY p.valid_from DESC LIMIT 1`;
66
+ export const CURRENCY_LIST_SQL = `
67
+ SELECT id, currency FROM public.price_lists
68
+ WHERE currency = $1::text AND status = 'active'
69
+ ORDER BY created_at DESC LIMIT 1`;
70
+ export const FEED_SQL = `
71
+ SELECT b.id, b.code, b.title, b.fulfillment_mode, p.mode, p.target_price_minor,
72
+ p.price_list_id, list.currency
73
+ FROM public.catalog_bundles b
74
+ JOIN public.catalog_bundle_prices p ON p.bundle_id = b.id AND p.active
75
+ JOIN public.price_lists AS list ON list.id = p.price_list_id
76
+ WHERE b.status = 'active' AND ($1::text IS NULL OR list.currency = $1::text)
77
+ ORDER BY b.code, p.valid_from DESC`;
78
+ export const HISTORY_SQL = `
79
+ SELECT id, bundle_code, action, before_state, after_state, created_at
80
+ FROM public.catalog_bundle_write_events
81
+ WHERE bundle_code = $1::text
82
+ ORDER BY created_at DESC LIMIT $2 OFFSET $3`;
83
+ export const HISTORY_COUNT_SQL = `
84
+ SELECT count(*) AS total FROM public.catalog_bundle_write_events WHERE bundle_code = $1::text`;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The semantic READ port for bundles.
3
+ *
4
+ * This file names the CAPABILITY, never the storage: no table, no routine, no
5
+ * data-client type and no vendor appears here, and the domain layer opens no
6
+ * database handle of its own. Both shipped read adapters implement exactly this
7
+ * interface and are proved against ONE shared scenario table, so a caller cannot
8
+ * tell them apart. It is the same shape the write port
9
+ * (`adminBundleWritePort.ts`) established in the previous wave.
10
+ *
11
+ * Reads are plain SELECTs rather than security-definer routines: the write
12
+ * boundary is where authority lives, and a read that re-implemented it would be a
13
+ * second place for the same rules to be written down. The admin surface reads the
14
+ * whole lifecycle (draft, active, archived); the sellable feed asks a separate
15
+ * question and gets only what is live.
16
+ *
17
+ * COMPONENT PRICES ARE RESOLVED, NOT STORED. A bundle stores one target price for
18
+ * the whole set; a component's REFERENCE unit price is whatever the selected
19
+ * price list says today. The adapters therefore join the catalogue and the price
20
+ * list themselves, and hand back a resolved figure (or `null` when the list
21
+ * prices nothing for that unit) so the allocator upstream never re-reads storage.
22
+ */
23
+ /**
24
+ * A read that FAILED, as opposed to a read that found nothing.
25
+ *
26
+ * Both adapters raise this rather than returning an empty page, because the two
27
+ * outcomes are opposite: an empty catalogue is a fact a storefront may cache and
28
+ * show, and a failed query is not. The class lives on the port so neither chain
29
+ * owns the distinction.
30
+ */
31
+ export declare class BundleCatalogReadError extends Error {
32
+ constructor(message: string);
33
+ }
34
+ /** Which lifecycle rows an admin list asks for. `all` is the admin default. */
35
+ export type BundleListStatusFilter = "draft" | "active" | "archived" | "all";
36
+ export interface ListBundlesInput {
37
+ status: BundleListStatusFilter;
38
+ /** Free-text match over code and title; absent means no narrowing. */
39
+ query?: string;
40
+ limit: number;
41
+ offset: number;
42
+ }
43
+ export interface BundleSummary {
44
+ code: string;
45
+ title: string;
46
+ status: string;
47
+ fulfillmentMode: string;
48
+ componentCount: number;
49
+ hasActiveTargetPrice: boolean;
50
+ updatedAt: string;
51
+ }
52
+ export interface ListBundlesResult {
53
+ bundles: BundleSummary[];
54
+ total: number;
55
+ }
56
+ /**
57
+ * One line of the bill of materials, already joined to the catalogue.
58
+ *
59
+ * `productSlug` and `variantId` are carried because the availability port
60
+ * addresses stock by all three coordinates; a caller that had only the sku would
61
+ * have to re-open the catalogue to ask about stock, which is exactly the
62
+ * round-trip this DTO exists to remove.
63
+ */
64
+ export interface BundleComponentRow {
65
+ sku: string;
66
+ title: string | null;
67
+ productSlug: string;
68
+ variantId: string;
69
+ quantity: number;
70
+ isAddon: boolean;
71
+ sortOrder: number;
72
+ /** Resolved from the selected price list; `null` when it prices no such unit. */
73
+ referenceUnitPriceMinor: number | null;
74
+ }
75
+ export interface BundleTargetPriceRow {
76
+ mode: string;
77
+ targetPriceMinor: number;
78
+ currency: string;
79
+ amountKind: string;
80
+ active: boolean;
81
+ validFrom: string;
82
+ validTo: string | null;
83
+ }
84
+ export interface BundleDetail extends BundleSummary {
85
+ /** The opaque `{kind, version, data}` envelope; `null` for an unconstrained bundle. */
86
+ compositionConstraint: {
87
+ kind: string;
88
+ version: number;
89
+ data: Record<string, unknown>;
90
+ } | null;
91
+ metadata: Record<string, unknown>;
92
+ components: BundleComponentRow[];
93
+ prices: BundleTargetPriceRow[];
94
+ /** Present only when the host selects effective price authority. */
95
+ resolvedTargetPrice?: BundleTargetPriceRow | null;
96
+ /** The currency whose list resolved `referenceUnitPriceMinor`; `null` if none did. */
97
+ resolvedCurrency: string | null;
98
+ /**
99
+ * The price list those reference figures came from. Carried because a price
100
+ * preview has to name the list it priced against — an amount without the list
101
+ * that gave it meaning is not reproducible.
102
+ */
103
+ resolvedPriceListId: string | null;
104
+ }
105
+ export interface GetBundleInput {
106
+ code: string;
107
+ /**
108
+ * Price the components against THIS currency's active list. Omitted means "the
109
+ * list this bundle's own active target price is written into", which is the
110
+ * only currency an operator has actually chosen for it.
111
+ */
112
+ currency?: string;
113
+ /** Narrow the target price whose list is selected; omitted means any mode (the opt-in effective store defaults to one_time). */
114
+ mode?: string;
115
+ regionCode?: string;
116
+ atTime?: string;
117
+ }
118
+ /**
119
+ * One live, sellable bundle: active status, an active target price, and its
120
+ * components priced in that price's own currency. This is the feed's row, and it
121
+ * is deliberately a different question from `getBundle` — the storefront must
122
+ * never be able to see a draft by asking the read port nicely.
123
+ */
124
+ export interface ActiveBundleComposition {
125
+ code: string;
126
+ title: string;
127
+ fulfillmentMode: string;
128
+ currency: string;
129
+ targetPriceMinor: number;
130
+ mode: string;
131
+ components: BundleComponentRow[];
132
+ }
133
+ export interface BundleHistoryEvent {
134
+ id: string;
135
+ action: string;
136
+ actorKind: string | null;
137
+ actorEmail: string | null;
138
+ entityId: string | null;
139
+ oldValue: unknown;
140
+ newValue: unknown;
141
+ occurredAt: string;
142
+ }
143
+ export interface BundleHistoryInput {
144
+ code: string;
145
+ limit: number;
146
+ offset: number;
147
+ }
148
+ export interface BundleHistoryResult {
149
+ events: BundleHistoryEvent[];
150
+ total: number;
151
+ }
152
+ export interface BundleCatalogReadPort {
153
+ /** Admin list across the whole lifecycle, newest-agnostic and code-ordered. */
154
+ listBundles(input: ListBundlesInput): Promise<ListBundlesResult>;
155
+ /** Full admin detail, or `null` when no bundle carries that code. */
156
+ getBundle(input: GetBundleInput): Promise<BundleDetail | null>;
157
+ /**
158
+ * Every bundle a storefront may sell right now. Never returns a draft or an
159
+ * archived bundle, and never a bundle without an active target price.
160
+ */
161
+ listActiveBundleCompositions(input?: {
162
+ currency?: string;
163
+ regionCode?: string;
164
+ mode?: "one_time" | "subscription";
165
+ atTime?: string;
166
+ }): Promise<ActiveBundleComposition[]>;
167
+ /**
168
+ * The operator trail for one bundle. The two chains answer from different
169
+ * ledgers — a managed audit table and the platform's neutral write-event
170
+ * relation — which is why this is a port method rather than a shared query.
171
+ */
172
+ listBundleHistory(input: BundleHistoryInput): Promise<BundleHistoryResult>;
173
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The semantic READ port for bundles.
3
+ *
4
+ * This file names the CAPABILITY, never the storage: no table, no routine, no
5
+ * data-client type and no vendor appears here, and the domain layer opens no
6
+ * database handle of its own. Both shipped read adapters implement exactly this
7
+ * interface and are proved against ONE shared scenario table, so a caller cannot
8
+ * tell them apart. It is the same shape the write port
9
+ * (`adminBundleWritePort.ts`) established in the previous wave.
10
+ *
11
+ * Reads are plain SELECTs rather than security-definer routines: the write
12
+ * boundary is where authority lives, and a read that re-implemented it would be a
13
+ * second place for the same rules to be written down. The admin surface reads the
14
+ * whole lifecycle (draft, active, archived); the sellable feed asks a separate
15
+ * question and gets only what is live.
16
+ *
17
+ * COMPONENT PRICES ARE RESOLVED, NOT STORED. A bundle stores one target price for
18
+ * the whole set; a component's REFERENCE unit price is whatever the selected
19
+ * price list says today. The adapters therefore join the catalogue and the price
20
+ * list themselves, and hand back a resolved figure (or `null` when the list
21
+ * prices nothing for that unit) so the allocator upstream never re-reads storage.
22
+ */
23
+ /**
24
+ * A read that FAILED, as opposed to a read that found nothing.
25
+ *
26
+ * Both adapters raise this rather than returning an empty page, because the two
27
+ * outcomes are opposite: an empty catalogue is a fact a storefront may cache and
28
+ * show, and a failed query is not. The class lives on the port so neither chain
29
+ * owns the distinction.
30
+ */
31
+ export class BundleCatalogReadError extends Error {
32
+ constructor(message) {
33
+ super(message);
34
+ this.name = "BundleCatalogReadError";
35
+ }
36
+ }
@@ -0,0 +1,5 @@
1
+ import type { BundleCatalogReadPort } from "./bundleCatalogReadPort.js";
2
+ import type { PgQueryExecutor } from "../data/index.js";
3
+ export declare function createPostgresBundleCatalogStore(executor: PgQueryExecutor, options?: {
4
+ priceAuthority?: "effective";
5
+ }): BundleCatalogReadPort;
@@ -0,0 +1,156 @@
1
+ import { createEffectiveBundleCatalogStore } from "./effectiveBundleCatalogStore.js";
2
+ import { BundleCatalogReadError, } from "./bundleCatalogReadPort.js";
3
+ import { LIST_SQL, COUNT_SQL, DETAIL_SQL, COMPONENT_SQL, FEED_COMPONENT_SQL, PRICE_SQL, OWN_LIST_SQL, CURRENCY_LIST_SQL, FEED_SQL, HISTORY_SQL, HISTORY_COUNT_SQL, } from "./bundleCatalogQueries.js";
4
+ export function createPostgresBundleCatalogStore(executor, options) {
5
+ if (options?.priceAuthority === "effective") {
6
+ return createEffectiveBundleCatalogStore(executor, createPostgresBundleCatalogStore(executor));
7
+ }
8
+ async function rows(sql, values) {
9
+ // A driver failure becomes the port's own read error, so a caller sees the
10
+ // same distinction between "found nothing" and "could not ask" on both chains.
11
+ const result = await executor.query(sql, values).catch((error) => {
12
+ throw new BundleCatalogReadError(error instanceof Error ? error.message : String(error));
13
+ });
14
+ return (result.rows ?? []);
15
+ }
16
+ async function components(bundleId, priceListId) {
17
+ return (await rows(COMPONENT_SQL, [bundleId, priceListId])).map(toComponent);
18
+ }
19
+ async function selectPriceList(bundleId, input) {
20
+ const list = input.currency
21
+ ? await rows(CURRENCY_LIST_SQL, [input.currency])
22
+ : await rows(OWN_LIST_SQL, [bundleId, input.mode ?? null]);
23
+ return list[0] ?? null;
24
+ }
25
+ return {
26
+ async listBundles(input) {
27
+ const filter = [input.status, input.query ?? null];
28
+ const [page, counted] = await Promise.all([
29
+ rows(LIST_SQL, [...filter, input.limit, input.offset]),
30
+ rows(COUNT_SQL, filter),
31
+ ]);
32
+ return { bundles: page.map(toSummary), total: Number(counted[0]?.total ?? page.length) };
33
+ },
34
+ async getBundle(input) {
35
+ const bundle = (await rows(DETAIL_SQL, [input.code]))[0];
36
+ if (!bundle)
37
+ return null;
38
+ const list = await selectPriceList(bundle.id, input);
39
+ const [componentRows, priceRows] = await Promise.all([
40
+ components(bundle.id, list?.id ?? null),
41
+ rows(PRICE_SQL, [bundle.id]),
42
+ ]);
43
+ return {
44
+ ...toSummary(bundle),
45
+ compositionConstraint: toConstraint(bundle.composition_constraint),
46
+ metadata: bundle.metadata ?? {},
47
+ components: componentRows,
48
+ prices: priceRows.map(toPrice),
49
+ resolvedCurrency: list?.currency ?? null,
50
+ resolvedPriceListId: list?.id ?? null,
51
+ };
52
+ },
53
+ async listActiveBundleCompositions(input = {}) {
54
+ // A bundle may carry several active prices (a scheduled successor); the feed
55
+ // takes the newest window per bundle, which is what the ORDER BY already put first.
56
+ const selected = new Map();
57
+ for (const row of await rows(FEED_SQL, [input.currency ?? null])) {
58
+ if (!selected.has(row.id))
59
+ selected.set(row.id, row);
60
+ }
61
+ if (selected.size === 0)
62
+ return [];
63
+ const chosen = [...selected.values()];
64
+ const byBundle = groupComponents(await rows(FEED_COMPONENT_SQL, [
65
+ chosen.map((row) => row.id),
66
+ chosen.map((row) => row.price_list_id),
67
+ ]));
68
+ return chosen.map((row) => ({
69
+ code: row.code,
70
+ title: row.title,
71
+ fulfillmentMode: row.fulfillment_mode,
72
+ currency: row.currency,
73
+ targetPriceMinor: row.target_price_minor,
74
+ mode: row.mode,
75
+ components: byBundle.get(row.id) ?? [],
76
+ }));
77
+ },
78
+ async listBundleHistory(input) {
79
+ const [page, counted] = await Promise.all([
80
+ rows(HISTORY_SQL, [input.code, input.limit, input.offset]),
81
+ rows(HISTORY_COUNT_SQL, [input.code]),
82
+ ]);
83
+ return {
84
+ // This chain has no operator registry, so it can record WHICH principal
85
+ // wrote but never what kind of actor it was; both fields stay null rather
86
+ // than being invented from the principal id.
87
+ events: page.map((row) => ({
88
+ id: row.id,
89
+ action: row.action,
90
+ actorKind: null,
91
+ actorEmail: null,
92
+ entityId: row.bundle_code,
93
+ oldValue: row.before_state ?? null,
94
+ newValue: row.after_state ?? null,
95
+ occurredAt: row.created_at,
96
+ })),
97
+ total: Number(counted[0]?.total ?? page.length),
98
+ };
99
+ },
100
+ };
101
+ }
102
+ function groupComponents(rows) {
103
+ const grouped = new Map();
104
+ for (const row of rows) {
105
+ const bucket = grouped.get(row.bundle_id) ?? [];
106
+ bucket.push(toComponent(row));
107
+ grouped.set(row.bundle_id, bucket);
108
+ }
109
+ return grouped;
110
+ }
111
+ function toComponent(row) {
112
+ return {
113
+ sku: row.sku,
114
+ title: row.title ?? null,
115
+ productSlug: row.product_slug,
116
+ variantId: row.variant_id,
117
+ quantity: Number(row.quantity),
118
+ isAddon: row.is_addon === true,
119
+ sortOrder: Number(row.sort_order),
120
+ referenceUnitPriceMinor: row.reference_unit_price_minor === null || row.reference_unit_price_minor === undefined
121
+ ? null
122
+ : Number(row.reference_unit_price_minor),
123
+ };
124
+ }
125
+ function toSummary(row) {
126
+ return {
127
+ code: row.code,
128
+ title: row.title,
129
+ status: row.status,
130
+ fulfillmentMode: row.fulfillment_mode,
131
+ componentCount: Number(row.component_count ?? 0),
132
+ hasActiveTargetPrice: row.has_active_target_price === true,
133
+ updatedAt: row.updated_at,
134
+ };
135
+ }
136
+ function toPrice(row) {
137
+ return {
138
+ mode: row.mode,
139
+ targetPriceMinor: Number(row.target_price_minor),
140
+ currency: row.currency,
141
+ amountKind: row.amount_kind,
142
+ active: row.active === true,
143
+ validFrom: row.valid_from,
144
+ validTo: row.valid_to ?? null,
145
+ };
146
+ }
147
+ /** An empty envelope is stored as `{}`; that is "unconstrained", not a constraint. */
148
+ function toConstraint(value) {
149
+ if (!value || typeof value.kind !== "string" || value.kind.trim() === "")
150
+ return null;
151
+ return {
152
+ kind: value.kind,
153
+ version: typeof value.version === "number" ? value.version : 0,
154
+ data: (value.data ?? {}),
155
+ };
156
+ }
@@ -0,0 +1,13 @@
1
+ import { type BundleTargetPriceRow } from "./bundleCatalogReadPort.js";
2
+ export declare function uniqueBundlePrice<T extends {
3
+ mode: string;
4
+ }>(rows: readonly T[], mode: string): T | null;
5
+ export declare function selectEffectiveBundleTargetPrice(rows: readonly {
6
+ price: BundleTargetPriceRow;
7
+ priceListId: string;
8
+ }[], context: {
9
+ priceListId: string | null;
10
+ currency: string | null;
11
+ mode: string;
12
+ atTime: string;
13
+ }): BundleTargetPriceRow | null;
@@ -0,0 +1,26 @@
1
+ import { selectUniqueEffectivePriceList } from "../pricing/index.js";
2
+ import { BundleCatalogReadError } from "./bundleCatalogReadPort.js";
3
+ /** Half-open windows share the price authority's effective-time rule. */
4
+ function bundlePriceEffective(row, atTime) {
5
+ try {
6
+ selectUniqueEffectivePriceList([{ value: "row", ...row }], atTime);
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ export function uniqueBundlePrice(rows, mode) {
14
+ const exact = rows.filter(row => row.mode === mode);
15
+ const candidates = exact.length ? exact : rows.filter(row => row.mode === "any");
16
+ if (candidates.length > 1)
17
+ throw new BundleCatalogReadError("bundle_price_ambiguous");
18
+ return candidates[0] ?? null;
19
+ }
20
+ export function selectEffectiveBundleTargetPrice(rows, context) {
21
+ if (context.priceListId === null || context.currency === null)
22
+ return null;
23
+ return uniqueBundlePrice(rows.filter(row => row.priceListId === context.priceListId
24
+ && row.price.currency === context.currency && row.price.active && row.price.amountKind === "gross"
25
+ && row.price.targetPriceMinor > 0 && bundlePriceEffective(row.price, context.atTime)).map(row => row.price), context.mode);
26
+ }