@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.
- package/CHANGELOG.md +143 -0
- package/README.md +299 -6
- package/dist/catalog/activeSkuSelection.d.ts +2 -0
- package/dist/catalog/activeSkuSelection.js +20 -0
- package/dist/catalog/adminBindings.d.ts +72 -0
- package/dist/catalog/adminBindings.js +1 -0
- package/dist/catalog/adminBundleHandler.d.ts +26 -0
- package/dist/catalog/adminBundleHandler.js +111 -0
- package/dist/catalog/adminBundleReadHandler.d.ts +21 -0
- package/dist/catalog/adminBundleReadHandler.js +185 -0
- package/dist/catalog/adminBundleWritePort.d.ts +85 -0
- package/dist/catalog/adminBundleWritePort.js +1 -0
- package/dist/catalog/bundleAvailability.d.ts +63 -0
- package/dist/catalog/bundleAvailability.js +78 -0
- package/dist/catalog/bundleAvailabilityService.d.ts +65 -0
- package/dist/catalog/bundleAvailabilityService.js +89 -0
- package/dist/catalog/bundleBindings.d.ts +33 -0
- package/dist/catalog/bundleBindings.js +1 -0
- package/dist/catalog/bundleCatalogQueries.d.ts +28 -0
- package/dist/catalog/bundleCatalogQueries.js +84 -0
- package/dist/catalog/bundleCatalogReadPort.d.ts +173 -0
- package/dist/catalog/bundleCatalogReadPort.js +36 -0
- package/dist/catalog/bundleCatalogStore.d.ts +5 -0
- package/dist/catalog/bundleCatalogStore.js +156 -0
- package/dist/catalog/bundlePriceSelection.d.ts +13 -0
- package/dist/catalog/bundlePriceSelection.js +26 -0
- package/dist/catalog/bundleRuleCodes.d.ts +41 -0
- package/dist/catalog/bundleRuleCodes.js +110 -0
- package/dist/catalog/bundleRules.d.ts +69 -0
- package/dist/catalog/bundleRules.js +102 -0
- package/dist/catalog/catalogDraftStore.d.ts +4 -0
- package/dist/catalog/catalogDraftStore.js +63 -0
- package/dist/catalog/catalogSkuEnvelope.d.ts +7 -0
- package/dist/catalog/catalogSkuEnvelope.js +142 -0
- package/dist/catalog/catalogSkuEnvelopeMapper.d.ts +38 -0
- package/dist/catalog/catalogSkuEnvelopeMapper.js +88 -0
- package/dist/catalog/contracts/bundleAdmin.d.ts +143 -0
- package/dist/catalog/contracts/bundleAdmin.js +1 -0
- package/dist/catalog/contracts/bundleAvailability.d.ts +61 -0
- package/dist/catalog/contracts/bundleAvailability.js +1 -0
- package/dist/catalog/contracts/bundleRead.d.ts +241 -0
- package/dist/catalog/contracts/bundleRead.js +1 -0
- package/dist/catalog/contracts/draft.d.ts +79 -0
- package/dist/catalog/contracts/draft.js +47 -0
- package/dist/catalog/contracts/foundation.d.ts +78 -0
- package/dist/catalog/contracts/foundation.js +53 -0
- package/dist/catalog/contracts/index.d.ts +14 -0
- package/dist/catalog/contracts/index.js +6 -0
- package/dist/catalog/contracts/lifecycle.d.ts +77 -0
- package/dist/catalog/contracts/lifecycle.js +1 -0
- package/dist/catalog/contracts/productMediaContracts.d.ts +77 -0
- package/dist/catalog/contracts/productMediaContracts.js +149 -0
- package/dist/catalog/contracts/productTypes.d.ts +54 -0
- package/dist/catalog/contracts/productTypes.js +16 -0
- package/dist/catalog/contracts/resolveProductMedia.d.ts +3 -0
- package/dist/catalog/contracts/resolveProductMedia.js +78 -0
- package/dist/catalog/contracts/sellable.d.ts +23 -0
- package/dist/catalog/contracts/sellable.js +1 -0
- package/dist/catalog/draftHandler.d.ts +25 -0
- package/dist/catalog/draftHandler.js +118 -0
- package/dist/catalog/draftPorts.d.ts +18 -0
- package/dist/catalog/draftPorts.js +8 -0
- package/dist/catalog/draftSchemas.d.ts +423 -0
- package/dist/catalog/draftSchemas.js +89 -0
- package/dist/catalog/draftValidation.d.ts +21 -0
- package/dist/catalog/draftValidation.js +125 -0
- package/dist/catalog/effectiveBundleCatalogQueries.d.ts +8 -0
- package/dist/catalog/effectiveBundleCatalogQueries.js +79 -0
- package/dist/catalog/effectiveBundleCatalogStore.d.ts +4 -0
- package/dist/catalog/effectiveBundleCatalogStore.js +156 -0
- package/dist/catalog/foundationSchemas.d.ts +98 -0
- package/dist/catalog/foundationSchemas.js +110 -0
- package/dist/catalog/index.d.ts +44 -0
- package/dist/catalog/index.js +26 -0
- package/dist/catalog/mediaSourcePack.d.ts +51 -0
- package/dist/catalog/mediaSourcePack.js +247 -0
- package/dist/catalog/productTypeSchemas.d.ts +46 -0
- package/dist/catalog/productTypeSchemas.js +72 -0
- package/dist/catalog/publicationPorts.d.ts +42 -0
- package/dist/catalog/publicationPorts.js +1 -0
- package/dist/catalog/readPorts.d.ts +51 -0
- package/dist/catalog/readPorts.js +1 -0
- package/dist/catalog/referenceCatalogHandler.d.ts +7 -0
- package/dist/catalog/referenceCatalogHandler.js +27 -0
- package/dist/catalog/resolvedSkuProjection.d.ts +88 -0
- package/dist/catalog/resolvedSkuProjection.js +60 -0
- package/dist/catalog/sellableBundleHandler.d.ts +46 -0
- package/dist/catalog/sellableBundleHandler.js +84 -0
- package/dist/catalog/sellableSchemas.d.ts +46 -0
- package/dist/catalog/sellableSchemas.js +27 -0
- package/dist/catalog/sql.d.ts +46 -0
- package/dist/catalog/sql.js +13 -0
- package/dist/catalog/validation.d.ts +2 -0
- package/dist/catalog/validation.js +3 -0
- package/dist/communications/contracts/index.d.ts +1 -0
- package/dist/communications/contracts/marketingSync.d.ts +194 -0
- package/dist/communications/contracts/marketingSync.js +1 -0
- package/dist/communications/index.d.ts +1 -0
- package/dist/communications/klaviyo/consentPush.d.ts +51 -0
- package/dist/communications/klaviyo/consentPush.js +19 -0
- package/dist/communications/klaviyo/index.d.ts +110 -0
- package/dist/communications/klaviyo/index.js +860 -0
- package/dist/communications/newsletterIntegrationContracts.d.ts +3 -3
- package/dist/communications/resend/index.d.ts +40 -0
- package/dist/communications/resend/index.js +310 -0
- package/dist/composition/catalogContribution.d.ts +18 -0
- package/dist/composition/catalogContribution.js +21 -0
- package/dist/composition/index.d.ts +3 -0
- package/dist/composition/index.js +3 -0
- package/dist/composition/inventoryContribution.d.ts +16 -0
- package/dist/composition/inventoryContribution.js +17 -0
- package/dist/composition/shippingContribution.d.ts +16 -0
- package/dist/composition/shippingContribution.js +17 -0
- package/dist/identity/customerContracts.d.ts +2 -2
- package/dist/inventory/adminInventory.d.ts +13 -0
- package/dist/inventory/adminInventory.js +214 -0
- package/dist/inventory/bindings.d.ts +35 -0
- package/dist/inventory/bindings.js +45 -0
- package/dist/inventory/contracts/forecast.d.ts +86 -0
- package/dist/inventory/contracts/forecast.js +1 -0
- package/dist/inventory/contracts/index.d.ts +4 -0
- package/dist/inventory/contracts/index.js +1 -0
- package/dist/inventory/contracts/inventory.d.ts +157 -0
- package/dist/inventory/contracts/inventory.js +1 -0
- package/dist/inventory/contracts/newPurchaseStock.d.ts +20 -0
- package/dist/inventory/contracts/newPurchaseStock.js +1 -0
- package/dist/inventory/contracts/reservation.d.ts +18 -0
- package/dist/inventory/contracts/reservation.js +1 -0
- package/dist/inventory/forecast.d.ts +24 -0
- package/dist/inventory/forecast.js +200 -0
- package/dist/inventory/index.d.ts +14 -0
- package/dist/inventory/index.js +11 -0
- package/dist/inventory/inventoryHandlers.d.ts +53 -0
- package/dist/inventory/inventoryHandlers.js +151 -0
- package/dist/inventory/newPurchaseStockAvailability.d.ts +47 -0
- package/dist/inventory/newPurchaseStockAvailability.js +75 -0
- package/dist/inventory/ports.d.ts +21 -0
- package/dist/inventory/ports.js +16 -0
- package/dist/inventory/renewalReservation.d.ts +27 -0
- package/dist/inventory/renewalReservation.js +61 -0
- package/dist/inventory/reservationPolicy.d.ts +6 -0
- package/dist/inventory/reservationPolicy.js +19 -0
- package/dist/inventory/schemas.d.ts +137 -0
- package/dist/inventory/schemas.js +112 -0
- package/dist/inventory/sql.d.ts +14 -0
- package/dist/inventory/sql.js +5 -0
- package/dist/inventory/subscriptionForecastCalculation.d.ts +4 -0
- package/dist/inventory/subscriptionForecastCalculation.js +133 -0
- package/dist/pricing/acquisitionFinancialPolicy.d.ts +26 -0
- package/dist/pricing/acquisitionFinancialPolicy.js +1 -0
- package/dist/pricing/acquisitionOfferPricing.d.ts +27 -0
- package/dist/pricing/acquisitionOfferPricing.js +25 -0
- package/dist/pricing/acquisitionOfferPricingHandler.d.ts +20 -0
- package/dist/pricing/acquisitionOfferPricingHandler.js +105 -0
- package/dist/pricing/acquisitionOfferPricingSchemas.d.ts +31 -0
- package/dist/pricing/acquisitionOfferPricingSchemas.js +33 -0
- package/dist/pricing/acquisitionQuoteBinding.d.ts +12 -0
- package/dist/pricing/acquisitionQuoteBinding.js +12 -0
- package/dist/pricing/acquisitionReadiness.d.ts +31 -0
- package/dist/pricing/acquisitionReadiness.js +117 -0
- package/dist/pricing/acquisitionTerms.d.ts +19 -0
- package/dist/pricing/acquisitionTerms.js +126 -0
- package/dist/pricing/acquisitionTermsSchemas.d.ts +59 -0
- package/dist/pricing/acquisitionTermsSchemas.js +42 -0
- package/dist/pricing/catalogFactsProvenance.d.ts +33 -0
- package/dist/pricing/catalogFactsProvenance.js +64 -0
- package/dist/pricing/catalogFactsSchemas.d.ts +45 -0
- package/dist/pricing/catalogFactsSchemas.js +58 -0
- package/dist/pricing/commerceOfferPricing.d.ts +25 -0
- package/dist/pricing/commerceOfferPricing.js +139 -0
- package/dist/pricing/commerceOfferPricingCache.d.ts +10 -0
- package/dist/pricing/commerceOfferPricingCache.js +44 -0
- package/dist/pricing/commerceOfferPricingHandler.d.ts +21 -0
- package/dist/pricing/commerceOfferPricingHandler.js +78 -0
- package/dist/pricing/commercePriceAuthority.d.ts +28 -0
- package/dist/pricing/commercePriceAuthority.js +140 -0
- package/dist/pricing/commercePricingPolicyErrors.d.ts +40 -0
- package/dist/pricing/commercePricingPolicyErrors.js +67 -0
- package/dist/pricing/commerceQuoteBatchHandler.d.ts +37 -0
- package/dist/pricing/commerceQuoteBatchHandler.js +83 -0
- package/dist/pricing/commerceQuoteCatalogReadPort.d.ts +27 -0
- package/dist/pricing/commerceQuoteCatalogReadPort.js +8 -0
- package/dist/pricing/commerceQuoteHandler.d.ts +35 -0
- package/dist/pricing/commerceQuoteHandler.js +122 -0
- package/dist/pricing/commerceQuoteSnapshotExpect.d.ts +9 -0
- package/dist/pricing/commerceQuoteSnapshotExpect.js +22 -0
- package/dist/pricing/contracts/acquisitionTerms.d.ts +32 -0
- package/dist/pricing/contracts/acquisitionTerms.js +2 -0
- package/dist/pricing/contracts/authority.d.ts +27 -0
- package/dist/pricing/contracts/authority.js +1 -0
- package/dist/pricing/contracts/evidence.d.ts +33 -0
- package/dist/pricing/contracts/evidence.js +1 -0
- package/dist/pricing/contracts/index.d.ts +17 -0
- package/dist/pricing/contracts/index.js +7 -0
- package/dist/pricing/contracts/money.d.ts +27 -0
- package/dist/pricing/contracts/money.js +1 -0
- package/dist/pricing/contracts/offer.d.ts +52 -0
- package/dist/pricing/contracts/offer.js +4 -0
- package/dist/pricing/contracts/offerPolicy.d.ts +16 -0
- package/dist/pricing/contracts/offerPolicy.js +5 -0
- package/dist/pricing/contracts/policy.d.ts +22 -0
- package/dist/pricing/contracts/policy.js +3 -0
- package/dist/pricing/contracts/provenance.d.ts +24 -0
- package/dist/pricing/contracts/provenance.js +2 -0
- package/dist/pricing/contracts/quote.d.ts +94 -0
- package/dist/pricing/contracts/quote.js +1 -0
- package/dist/pricing/dbBackedCommerceQuoteHelpers.d.ts +78 -0
- package/dist/pricing/dbBackedCommerceQuoteHelpers.js +146 -0
- package/dist/pricing/dbBackedCommerceQuotePort.d.ts +74 -0
- package/dist/pricing/dbBackedCommerceQuotePort.js +223 -0
- package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.d.ts +4 -0
- package/dist/pricing/dbBackedCommerceQuoteSnapshotVerifier.js +25 -0
- package/dist/pricing/index.d.ts +52 -0
- package/dist/pricing/index.js +36 -0
- package/dist/pricing/offerPolicyReadiness.d.ts +21 -0
- package/dist/pricing/offerPolicyReadiness.js +79 -0
- package/dist/pricing/offerPolicySchemas.d.ts +25 -0
- package/dist/pricing/offerPolicySchemas.js +15 -0
- package/dist/pricing/offerPricingSchemas.d.ts +315 -0
- package/dist/pricing/offerPricingSchemas.js +65 -0
- package/dist/pricing/ports.d.ts +13 -0
- package/dist/pricing/ports.js +9 -0
- package/dist/pricing/pricingPolicyToken.d.ts +39 -0
- package/dist/pricing/pricingPolicyToken.js +84 -0
- package/dist/pricing/quoteAcquisitionTerms.d.ts +2 -0
- package/dist/pricing/quoteAcquisitionTerms.js +4 -0
- package/dist/pricing/quoteCodeRejectionDetails.d.ts +20 -0
- package/dist/pricing/quoteCodeRejectionDetails.js +22 -0
- package/dist/pricing/quotePorts.d.ts +30 -0
- package/dist/pricing/quotePorts.js +20 -0
- package/dist/pricing/quotePrimitives.d.ts +13 -0
- package/dist/pricing/quotePrimitives.js +18 -0
- package/dist/pricing/quotePromotionEvidence.d.ts +39 -0
- package/dist/pricing/quotePromotionEvidence.js +73 -0
- package/dist/pricing/quoteSchemas.d.ts +1176 -0
- package/dist/pricing/quoteSchemas.js +196 -0
- package/dist/pricing/quoteShippingPromotions.d.ts +20 -0
- package/dist/pricing/quoteShippingPromotions.js +1 -0
- package/dist/pricing/subscriptionPricePolicy.d.ts +52 -0
- package/dist/pricing/subscriptionPricePolicy.js +96 -0
- package/dist/pricing/taxProfileGuard.d.ts +2 -0
- package/dist/pricing/taxProfileGuard.js +24 -0
- package/dist/pricing/validation.d.ts +2 -0
- package/dist/pricing/validation.js +3 -0
- package/dist/promotions/adminBindings.d.ts +72 -0
- package/dist/promotions/adminBindings.js +1 -0
- package/dist/promotions/adminPromotionCodesContracts.d.ts +498 -0
- package/dist/promotions/adminPromotionCodesContracts.js +211 -0
- package/dist/promotions/adminPromotionCodesDataPort.d.ts +47 -0
- package/dist/promotions/adminPromotionCodesDataPort.js +1 -0
- package/dist/promotions/adminPromotionCodesHandler.d.ts +14 -0
- package/dist/promotions/adminPromotionCodesHandler.js +194 -0
- package/dist/promotions/adminPromotionsContracts.d.ts +235 -0
- package/dist/promotions/adminPromotionsContracts.js +99 -0
- package/dist/promotions/adminPromotionsDataPort.d.ts +42 -0
- package/dist/promotions/adminPromotionsDataPort.js +1 -0
- package/dist/promotions/adminPromotionsHandler.d.ts +16 -0
- package/dist/promotions/adminPromotionsHandler.js +47 -0
- package/dist/promotions/commerceQuoteReadPreparation.d.ts +3 -0
- package/dist/promotions/commerceQuoteReadPreparation.js +27 -0
- package/dist/promotions/contracts/index.d.ts +387 -0
- package/dist/promotions/contracts/index.js +1 -0
- package/dist/promotions/index.d.ts +25 -0
- package/dist/promotions/index.js +22 -0
- package/dist/promotions/promoDataPort.d.ts +47 -0
- package/dist/promotions/promoDataPort.js +14 -0
- package/dist/promotions/promotionAcceptanceTelemetry.d.ts +29 -0
- package/dist/promotions/promotionAcceptanceTelemetry.js +45 -0
- package/dist/promotions/promotionBenefitDefinition.d.ts +37 -0
- package/dist/promotions/promotionBenefitDefinition.js +33 -0
- package/dist/promotions/promotionClaimSweepPort.d.ts +14 -0
- package/dist/promotions/promotionClaimSweepPort.js +8 -0
- package/dist/promotions/promotionClaims.d.ts +5 -0
- package/dist/promotions/promotionClaims.js +57 -0
- package/dist/promotions/promotionCodePreview.d.ts +11 -0
- package/dist/promotions/promotionCodePreview.js +154 -0
- package/dist/promotions/promotionCodeQuotePort.d.ts +25 -0
- package/dist/promotions/promotionCodeQuotePort.js +1 -0
- package/dist/promotions/promotionQuoteAcceptance.d.ts +70 -0
- package/dist/promotions/promotionQuoteAcceptance.js +202 -0
- package/dist/promotions/promotionQuoteBinding.d.ts +73 -0
- package/dist/promotions/promotionQuoteBinding.js +87 -0
- package/dist/promotions/quoteAcquisitionBinding.d.ts +6 -0
- package/dist/promotions/quoteAcquisitionBinding.js +1 -0
- package/dist/promotions/quoteAutomaticPromotions.d.ts +26 -0
- package/dist/promotions/quoteAutomaticPromotions.js +60 -0
- package/dist/promotions/quotePromoDiscounts.d.ts +36 -0
- package/dist/promotions/quotePromoDiscounts.js +146 -0
- package/dist/promotions/quotePromotionCodeAdjustments.d.ts +27 -0
- package/dist/promotions/quotePromotionCodeAdjustments.js +223 -0
- package/dist/promotions/quotePromotionV2.d.ts +30 -0
- package/dist/promotions/quotePromotionV2.js +186 -0
- package/dist/promotions/quoteShippingPolicy.d.ts +23 -0
- package/dist/promotions/quoteShippingPolicy.js +47 -0
- package/dist/promotions/sql.d.ts +14 -0
- package/dist/promotions/sql.js +5 -0
- package/dist/promotions/validation.d.ts +2 -0
- package/dist/promotions/validation.js +3 -0
- package/dist/shipping/addressCanonLookupHandler.d.ts +7 -0
- package/dist/shipping/addressCanonLookupHandler.js +105 -0
- package/dist/shipping/addressCanonical.d.ts +2 -0
- package/dist/shipping/addressCanonical.js +26 -0
- package/dist/shipping/addressPorts.d.ts +39 -0
- package/dist/shipping/addressPorts.js +1 -0
- package/dist/shipping/addressSchemas.d.ts +30 -0
- package/dist/shipping/addressSchemas.js +1 -0
- package/dist/shipping/adminShippingRateHandler.d.ts +4 -0
- package/dist/shipping/adminShippingRateHandler.js +64 -0
- package/dist/shipping/bindings.d.ts +57 -0
- package/dist/shipping/bindings.js +46 -0
- package/dist/shipping/contracts/address.d.ts +144 -0
- package/dist/shipping/contracts/address.js +1 -0
- package/dist/shipping/contracts/delivery.d.ts +110 -0
- package/dist/shipping/contracts/delivery.js +1 -0
- package/dist/shipping/contracts/index.d.ts +4 -0
- package/dist/shipping/contracts/index.js +1 -0
- package/dist/shipping/contracts/shippingRate.d.ts +11 -0
- package/dist/shipping/contracts/shippingRate.js +1 -0
- package/dist/shipping/contracts/threshold.d.ts +22 -0
- package/dist/shipping/contracts/threshold.js +1 -0
- package/dist/shipping/customerAddressesHandler.d.ts +5 -0
- package/dist/shipping/customerAddressesHandler.js +80 -0
- package/dist/shipping/customerSelfServiceAddressModels.d.ts +27 -0
- package/dist/shipping/customerSelfServiceAddressModels.js +101 -0
- package/dist/shipping/deliverySelectionHandler.d.ts +10 -0
- package/dist/shipping/deliverySelectionHandler.js +77 -0
- package/dist/shipping/deliverySelectionPort.d.ts +8 -0
- package/dist/shipping/deliverySelectionPort.js +1 -0
- package/dist/shipping/deliverySelectionSummary.d.ts +2 -0
- package/dist/shipping/deliverySelectionSummary.js +53 -0
- package/dist/shipping/handlerSchemas.d.ts +21 -0
- package/dist/shipping/handlerSchemas.js +1 -0
- package/dist/shipping/index.d.ts +20 -0
- package/dist/shipping/index.js +10 -0
- package/dist/shipping/pickupPointSearchPort.d.ts +4 -0
- package/dist/shipping/pickupPointSearchPort.js +1 -0
- package/dist/shipping/readDeliverySelectionEvidence.d.ts +2 -0
- package/dist/shipping/readDeliverySelectionEvidence.js +44 -0
- package/dist/shipping/shippingRatePorts.d.ts +52 -0
- package/dist/shipping/shippingRatePorts.js +1 -0
- package/dist/shipping/thresholdPolicy.d.ts +42 -0
- package/dist/shipping/thresholdPolicy.js +60 -0
- package/package.json +62 -2
- package/release-gates.json +263 -53
- package/sql/20260807140000_read_parity_catalog.sql +94 -0
- package/sql/20260810120000_storefront_catalog_attributes.sql +51 -0
- package/sql/20260812140000_sellable_bundle_catalog.sql +124 -0
- package/sql/20260812210000_bundle_operator_write_boundary.sql +567 -0
- package/sql/20260814000000_fulfillment_stock_evidence.sql +239 -0
- package/sql/20260815120000_automatic_subscription_renewal.sql +1083 -0
- package/sql/20260817180000_promotion_claim_lifecycle.sql +591 -0
- package/sql/20260817180100_promotion_claim_lifecycle_validation.sql +23 -0
- package/sql/20260827220000_catalog_document_revision_foundation.sql +226 -0
- package/sql/20260827220001_catalog_document_revision_foundation_validate.sql +10 -0
- package/sql/20260908100000_catalog_draft_foundation.sql +217 -0
- package/src/catalog/activeSkuSelection.ts +20 -0
- package/src/catalog/adminBindings.ts +59 -0
- package/src/catalog/adminBundleHandler.ts +165 -0
- package/src/catalog/adminBundleReadHandler.ts +263 -0
- package/src/catalog/adminBundleWritePort.ts +102 -0
- package/src/catalog/bundleAvailability.ts +129 -0
- package/src/catalog/bundleAvailabilityService.ts +171 -0
- package/src/catalog/bundleBindings.ts +29 -0
- package/src/catalog/bundleCatalogQueries.ts +98 -0
- package/src/catalog/bundleCatalogReadPort.ts +181 -0
- package/src/catalog/bundleCatalogStore.ts +274 -0
- package/src/catalog/bundlePriceSelection.ts +25 -0
- package/src/catalog/bundleRuleCodes.ts +134 -0
- package/src/catalog/bundleRules.ts +153 -0
- package/src/catalog/catalogDraftStore.ts +71 -0
- package/src/catalog/catalogSkuEnvelope.ts +214 -0
- package/src/catalog/catalogSkuEnvelopeMapper.ts +160 -0
- package/src/catalog/contracts/bundleAdmin.ts +20 -0
- package/src/catalog/contracts/bundleAvailability.ts +5 -0
- package/src/catalog/contracts/bundleRead.ts +13 -0
- package/src/catalog/contracts/draft.ts +69 -0
- package/src/catalog/contracts/foundation.ts +104 -0
- package/src/catalog/contracts/index.ts +14 -0
- package/src/catalog/contracts/lifecycle.ts +74 -0
- package/src/catalog/contracts/productMediaContracts.ts +170 -0
- package/src/catalog/contracts/productTypes.ts +35 -0
- package/src/catalog/contracts/resolveProductMedia.ts +85 -0
- package/src/catalog/contracts/sellable.ts +12 -0
- package/src/catalog/draftHandler.ts +105 -0
- package/src/catalog/draftPorts.ts +18 -0
- package/src/catalog/draftSchemas.ts +88 -0
- package/src/catalog/draftValidation.ts +100 -0
- package/src/catalog/effectiveBundleCatalogQueries.ts +80 -0
- package/src/catalog/effectiveBundleCatalogStore.ts +208 -0
- package/src/catalog/foundationSchemas.ts +123 -0
- package/src/catalog/index.ts +44 -0
- package/src/catalog/mediaSourcePack.ts +219 -0
- package/src/catalog/productTypeSchemas.ts +70 -0
- package/src/catalog/publicationPorts.ts +44 -0
- package/src/catalog/readPorts.ts +61 -0
- package/src/catalog/referenceCatalogHandler.ts +39 -0
- package/src/catalog/resolvedSkuProjection.ts +76 -0
- package/src/catalog/sellableBundleHandler.ts +133 -0
- package/src/catalog/sellableSchemas.ts +33 -0
- package/src/catalog/sql.ts +14 -0
- package/src/catalog/validation.ts +3 -0
- package/src/communications/contracts/index.ts +1 -0
- package/src/communications/contracts/marketingSync.ts +109 -0
- package/src/communications/index.ts +1 -0
- package/src/communications/klaviyo/consentPush.ts +19 -0
- package/src/communications/klaviyo/index.ts +649 -0
- package/src/communications/resend/index.ts +350 -0
- package/src/composition/catalogContribution.ts +32 -0
- package/src/composition/index.ts +3 -0
- package/src/composition/inventoryContribution.ts +24 -0
- package/src/composition/shippingContribution.ts +24 -0
- package/src/inventory/adminInventory.ts +251 -0
- package/src/inventory/bindings.ts +61 -0
- package/src/inventory/contracts/forecast.ts +96 -0
- package/src/inventory/contracts/index.ts +18 -0
- package/src/inventory/contracts/inventory.ts +14 -0
- package/src/inventory/contracts/newPurchaseStock.ts +23 -0
- package/src/inventory/contracts/reservation.ts +20 -0
- package/src/inventory/forecast.ts +239 -0
- package/src/inventory/index.ts +22 -0
- package/src/inventory/inventoryHandlers.ts +215 -0
- package/src/inventory/newPurchaseStockAvailability.ts +120 -0
- package/src/inventory/ports.ts +46 -0
- package/src/inventory/renewalReservation.ts +88 -0
- package/src/inventory/reservationPolicy.ts +27 -0
- package/src/inventory/schemas.ts +128 -0
- package/src/inventory/sql.ts +5 -0
- package/src/inventory/subscriptionForecastCalculation.ts +157 -0
- package/src/pricing/acquisitionFinancialPolicy.ts +26 -0
- package/src/pricing/acquisitionOfferPricing.ts +38 -0
- package/src/pricing/acquisitionOfferPricingHandler.ts +157 -0
- package/src/pricing/acquisitionOfferPricingSchemas.ts +35 -0
- package/src/pricing/acquisitionQuoteBinding.ts +12 -0
- package/src/pricing/acquisitionReadiness.ts +148 -0
- package/src/pricing/acquisitionTerms.ts +125 -0
- package/src/pricing/acquisitionTermsSchemas.ts +52 -0
- package/src/pricing/catalogFactsProvenance.ts +102 -0
- package/src/pricing/catalogFactsSchemas.ts +69 -0
- package/src/pricing/commerceOfferPricing.ts +224 -0
- package/src/pricing/commerceOfferPricingCache.ts +53 -0
- package/src/pricing/commerceOfferPricingHandler.ts +137 -0
- package/src/pricing/commercePriceAuthority.ts +169 -0
- package/src/pricing/commercePricingPolicyErrors.ts +88 -0
- package/src/pricing/commerceQuoteBatchHandler.ts +129 -0
- package/src/pricing/commerceQuoteCatalogReadPort.ts +42 -0
- package/src/pricing/commerceQuoteHandler.ts +199 -0
- package/src/pricing/commerceQuoteSnapshotExpect.ts +48 -0
- package/src/pricing/contracts/acquisitionTerms.ts +26 -0
- package/src/pricing/contracts/authority.ts +44 -0
- package/src/pricing/contracts/evidence.ts +25 -0
- package/src/pricing/contracts/index.ts +17 -0
- package/src/pricing/contracts/money.ts +32 -0
- package/src/pricing/contracts/offer.ts +42 -0
- package/src/pricing/contracts/offerPolicy.ts +17 -0
- package/src/pricing/contracts/policy.ts +26 -0
- package/src/pricing/contracts/provenance.ts +19 -0
- package/src/pricing/contracts/quote.ts +106 -0
- package/src/pricing/dbBackedCommerceQuoteHelpers.ts +256 -0
- package/src/pricing/dbBackedCommerceQuotePort.ts +280 -0
- package/src/pricing/dbBackedCommerceQuoteSnapshotVerifier.ts +49 -0
- package/src/pricing/index.ts +53 -0
- package/src/pricing/offerPolicyReadiness.ts +110 -0
- package/src/pricing/offerPolicySchemas.ts +23 -0
- package/src/pricing/offerPricingSchemas.ts +92 -0
- package/src/pricing/ports.ts +17 -0
- package/src/pricing/pricingPolicyToken.ts +119 -0
- package/src/pricing/quoteAcquisitionTerms.ts +10 -0
- package/src/pricing/quoteCodeRejectionDetails.ts +23 -0
- package/src/pricing/quotePorts.ts +58 -0
- package/src/pricing/quotePrimitives.ts +21 -0
- package/src/pricing/quotePromotionEvidence.ts +82 -0
- package/src/pricing/quoteSchemas.ts +230 -0
- package/src/pricing/quoteShippingPromotions.ts +21 -0
- package/src/pricing/subscriptionPricePolicy.ts +113 -0
- package/src/pricing/taxProfileGuard.ts +33 -0
- package/src/pricing/validation.ts +3 -0
- package/src/promotions/adminBindings.ts +53 -0
- package/src/promotions/adminPromotionCodesContracts.ts +244 -0
- package/src/promotions/adminPromotionCodesDataPort.ts +56 -0
- package/src/promotions/adminPromotionCodesHandler.ts +254 -0
- package/src/promotions/adminPromotionsContracts.ts +121 -0
- package/src/promotions/adminPromotionsDataPort.ts +48 -0
- package/src/promotions/adminPromotionsHandler.ts +72 -0
- package/src/promotions/commerceQuoteReadPreparation.ts +29 -0
- package/src/promotions/contracts/index.ts +387 -0
- package/src/promotions/index.ts +25 -0
- package/src/promotions/promoDataPort.ts +65 -0
- package/src/promotions/promotionAcceptanceTelemetry.ts +73 -0
- package/src/promotions/promotionBenefitDefinition.ts +48 -0
- package/src/promotions/promotionClaimSweepPort.ts +23 -0
- package/src/promotions/promotionClaims.ts +70 -0
- package/src/promotions/promotionCodePreview.ts +183 -0
- package/src/promotions/promotionCodeQuotePort.ts +35 -0
- package/src/promotions/promotionQuoteAcceptance.ts +291 -0
- package/src/promotions/promotionQuoteBinding.ts +163 -0
- package/src/promotions/quoteAcquisitionBinding.ts +7 -0
- package/src/promotions/quoteAutomaticPromotions.ts +84 -0
- package/src/promotions/quotePromoDiscounts.ts +194 -0
- package/src/promotions/quotePromotionCodeAdjustments.ts +277 -0
- package/src/promotions/quotePromotionV2.ts +239 -0
- package/src/promotions/quoteShippingPolicy.ts +61 -0
- package/src/promotions/sql.ts +5 -0
- package/src/promotions/validation.ts +3 -0
- package/src/shipping/addressCanonLookupHandler.ts +114 -0
- package/src/shipping/addressCanonical.ts +31 -0
- package/src/shipping/addressPorts.ts +48 -0
- package/src/shipping/addressSchemas.ts +28 -0
- package/src/shipping/adminShippingRateHandler.ts +72 -0
- package/src/shipping/bindings.ts +74 -0
- package/src/shipping/contracts/address.ts +71 -0
- package/src/shipping/contracts/delivery.ts +114 -0
- package/src/shipping/contracts/index.ts +19 -0
- package/src/shipping/contracts/shippingRate.ts +7 -0
- package/src/shipping/contracts/threshold.ts +22 -0
- package/src/shipping/customerAddressesHandler.ts +100 -0
- package/src/shipping/customerSelfServiceAddressModels.ts +124 -0
- package/src/shipping/deliverySelectionHandler.ts +94 -0
- package/src/shipping/deliverySelectionPort.ts +10 -0
- package/src/shipping/deliverySelectionSummary.ts +56 -0
- package/src/shipping/handlerSchemas.ts +19 -0
- package/src/shipping/index.ts +26 -0
- package/src/shipping/pickupPointSearchPort.ts +8 -0
- package/src/shipping/readDeliverySelectionEvidence.ts +51 -0
- package/src/shipping/shippingRatePorts.ts +36 -0
- package/src/shipping/thresholdPolicy.ts +65 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ResolvedAcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
2
|
+
import { AcquisitionTermsError } from "./acquisitionTerms.js";
|
|
3
|
+
/** Server-selected terms only. Absence contributes no property or token bytes. */
|
|
4
|
+
export function acquisitionQuoteBinding(acquisitionTerms?: ResolvedAcquisitionTerms): { acquisitionTerms?: ResolvedAcquisitionTerms } {
|
|
5
|
+
return acquisitionTerms ? { acquisitionTerms } : {};
|
|
6
|
+
}
|
|
7
|
+
export function mapAcquisitionTermsFailure(error: unknown) {
|
|
8
|
+
if (!(error instanceof AcquisitionTermsError)) return null;
|
|
9
|
+
const refreshRequired = error.reason === "acquisition_terms_capability_required" || error.reason === "acquisition_terms_v2_required";
|
|
10
|
+
return { code: refreshRequired ? "CONFLICT" as const : "UPSTREAM_UNAVAILABLE" as const,
|
|
11
|
+
details: { reason: error.reason, refreshRequired } };
|
|
12
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ACQUISITION_TERMS_CAPABILITY, type AcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
3
|
+
import { acquisitionTermsSchema } from "./acquisitionTermsSchemas.js";
|
|
4
|
+
import type { AcquisitionFinancialPolicy } from "./acquisitionFinancialPolicy.js";
|
|
5
|
+
import { AcquisitionTermsError } from "./acquisitionTerms.js";
|
|
6
|
+
import { OFFER_POLICY_V2_READINESS_CONTRACT } from "./offerPolicyReadiness.js";
|
|
7
|
+
export const ACQUISITION_READINESS_CONTRACT = "commerce-acquisition-readiness.v1";
|
|
8
|
+
|
|
9
|
+
export interface AcquisitionReadinessEvidence extends Omit<AcquisitionTerms, "schemaVersion"> {
|
|
10
|
+
contractVersion: typeof ACQUISITION_READINESS_CONTRACT;
|
|
11
|
+
kind: "legacy_pair" | "v2_standalone";
|
|
12
|
+
legacyPairCount: number;
|
|
13
|
+
standaloneCount: number;
|
|
14
|
+
nextBoundaryAt: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AcquisitionOfferPolicyReadiness {
|
|
18
|
+
contractVersion: typeof OFFER_POLICY_V2_READINESS_CONTRACT;
|
|
19
|
+
ready: boolean;
|
|
20
|
+
checkedAt: string;
|
|
21
|
+
reasons: string[];
|
|
22
|
+
evidence?: { acquisition: AcquisitionReadinessEvidence | null };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
import type { OfferPolicyReadinessPort } from "./offerPolicyReadiness.js";
|
|
26
|
+
|
|
27
|
+
interface CacheEntry {
|
|
28
|
+
expiresAtMs: number;
|
|
29
|
+
readiness: AcquisitionOfferPolicyReadiness;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AcquisitionReadinessCache {
|
|
33
|
+
read(port: OfferPolicyReadinessPort): Promise<AcquisitionOfferPolicyReadiness>;
|
|
34
|
+
clear(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createAcquisitionReadinessCache<Row>(input: {
|
|
38
|
+
policy: AcquisitionFinancialPolicy<Row>;
|
|
39
|
+
now?: () => number;
|
|
40
|
+
positiveTtlMs?: number;
|
|
41
|
+
negativeTtlMs?: number;
|
|
42
|
+
}): AcquisitionReadinessCache {
|
|
43
|
+
if (!input?.policy) throw new AcquisitionTermsError("acquisition_terms_policy_unconfigured");
|
|
44
|
+
const now = input.now ?? Date.now;
|
|
45
|
+
const positiveTtlMs = Math.min(60_000, Math.max(0, input.positiveTtlMs ?? 60_000));
|
|
46
|
+
const negativeTtlMs = Math.min(5_000, Math.max(0, input.negativeTtlMs ?? 5_000));
|
|
47
|
+
let cached: CacheEntry | null = null;
|
|
48
|
+
let inFlight: Promise<AcquisitionOfferPolicyReadiness> | null = null;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
read(port) {
|
|
52
|
+
const current = now();
|
|
53
|
+
if (cached && cached.expiresAtMs > current) return Promise.resolve(cached.readiness);
|
|
54
|
+
if (inFlight) return inFlight;
|
|
55
|
+
const startedAtMs = current;
|
|
56
|
+
inFlight = loadFailClosed(port)
|
|
57
|
+
.then(async (readiness) => {
|
|
58
|
+
// An RPC started before the boundary can finish after it. Re-read once;
|
|
59
|
+
// never lend the old green result a fresh positive cache window.
|
|
60
|
+
if (readiness.ready && boundaryMs(readiness) <= now()) readiness = await loadFailClosed(port);
|
|
61
|
+
if (readiness.ready && boundaryMs(readiness) <= now()) readiness = unavailable("readiness_acquisition_boundary_elapsed");
|
|
62
|
+
cached = {
|
|
63
|
+
readiness,
|
|
64
|
+
// Bound staleness from the start of the database read. A slow RPC
|
|
65
|
+
// must consume the TTL, not extend a green decision past 60s.
|
|
66
|
+
expiresAtMs: readiness.ready ? Math.min(startedAtMs + positiveTtlMs, boundaryMs(readiness))
|
|
67
|
+
: startedAtMs + negativeTtlMs,
|
|
68
|
+
};
|
|
69
|
+
return readiness;
|
|
70
|
+
})
|
|
71
|
+
.finally(() => {
|
|
72
|
+
inFlight = null;
|
|
73
|
+
});
|
|
74
|
+
return inFlight;
|
|
75
|
+
},
|
|
76
|
+
clear() {
|
|
77
|
+
cached = null;
|
|
78
|
+
inFlight = null;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function loadFailClosed(port: OfferPolicyReadinessPort): Promise<AcquisitionOfferPolicyReadiness> {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = parseReadiness(await port.readOfferPolicyV2Readiness());
|
|
86
|
+
return parsed ?? unavailable("readiness_contract_invalid");
|
|
87
|
+
} catch {
|
|
88
|
+
return unavailable("readiness_rpc_unavailable");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseReadiness(value: unknown): AcquisitionOfferPolicyReadiness | null {
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
94
|
+
const record = value as Record<string, unknown>;
|
|
95
|
+
if (
|
|
96
|
+
record.contractVersion !== OFFER_POLICY_V2_READINESS_CONTRACT ||
|
|
97
|
+
typeof record.ready !== "boolean" ||
|
|
98
|
+
typeof record.checkedAt !== "string" ||
|
|
99
|
+
!Number.isFinite(Date.parse(record.checkedAt)) ||
|
|
100
|
+
!Array.isArray(record.reasons) ||
|
|
101
|
+
!record.reasons.every((reason) => typeof reason === "string")
|
|
102
|
+
) return null;
|
|
103
|
+
const evidence = record.evidence;
|
|
104
|
+
let acquisition: AcquisitionReadinessEvidence | null | undefined;
|
|
105
|
+
if (evidence && typeof evidence === "object" && !Array.isArray(evidence) && "acquisition" in evidence) {
|
|
106
|
+
const raw = (evidence as Record<string, unknown>).acquisition;
|
|
107
|
+
acquisition = raw === null && !record.ready ? null : parseAcquisitionEvidence(raw);
|
|
108
|
+
if (acquisition === undefined) return null;
|
|
109
|
+
if (record.ready && acquisition && (Date.parse(acquisition.effectiveFrom) > Date.parse(record.checkedAt)
|
|
110
|
+
|| acquisition.effectiveTo !== null && Date.parse(acquisition.effectiveTo) <= Date.parse(record.checkedAt)
|
|
111
|
+
|| acquisition.nextBoundaryAt !== null && Date.parse(acquisition.nextBoundaryAt) <= Date.parse(record.checkedAt))) return null;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
contractVersion: OFFER_POLICY_V2_READINESS_CONTRACT,
|
|
115
|
+
ready: record.ready,
|
|
116
|
+
checkedAt: record.checkedAt,
|
|
117
|
+
reasons: record.reasons,
|
|
118
|
+
...(acquisition !== undefined ? { evidence: { acquisition } } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseAcquisitionEvidence(value: unknown): AcquisitionReadinessEvidence | undefined {
|
|
123
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
124
|
+
const { contractVersion, kind, legacyPairCount, standaloneCount, nextBoundaryAt, ...terms } = value as Record<string, unknown>;
|
|
125
|
+
const parsed = acquisitionTermsSchema.safeParse({ ...terms, schemaVersion: ACQUISITION_TERMS_CAPABILITY });
|
|
126
|
+
if (!parsed.success || contractVersion !== ACQUISITION_READINESS_CONTRACT
|
|
127
|
+
|| !(kind === "legacy_pair" && legacyPairCount === 1 && standaloneCount === 0
|
|
128
|
+
|| kind === "v2_standalone" && legacyPairCount === 0 && standaloneCount === 1)
|
|
129
|
+
|| !(nextBoundaryAt === null || z.string().datetime({ offset: true }).safeParse(nextBoundaryAt).success)
|
|
130
|
+
|| parsed.data.effectiveTo !== null && (nextBoundaryAt === null
|
|
131
|
+
|| Date.parse(nextBoundaryAt as string) > Date.parse(parsed.data.effectiveTo))) return undefined;
|
|
132
|
+
const { schemaVersion: _schema, ...publicTerms } = parsed.data;
|
|
133
|
+
return { ...publicTerms, contractVersion, kind, legacyPairCount, standaloneCount, nextBoundaryAt } as AcquisitionReadinessEvidence;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function boundaryMs(readiness: AcquisitionOfferPolicyReadiness): number {
|
|
137
|
+
const boundary = readiness.evidence?.acquisition?.nextBoundaryAt;
|
|
138
|
+
return boundary ? Date.parse(boundary) : Infinity;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function unavailable(reason: string): AcquisitionOfferPolicyReadiness {
|
|
142
|
+
return {
|
|
143
|
+
contractVersion: OFFER_POLICY_V2_READINESS_CONTRACT,
|
|
144
|
+
ready: false,
|
|
145
|
+
checkedAt: new Date(0).toISOString(),
|
|
146
|
+
reasons: [reason],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { ACQUISITION_TERMS_CAPABILITY, type AcquisitionTermsMetadata, type ResolvedAcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
2
|
+
import type { AcquisitionFinancialPolicy } from "./acquisitionFinancialPolicy.js";
|
|
3
|
+
import { acquisitionTermsMetadataSchema, resolvedAcquisitionTermsSchema } from "./acquisitionTermsSchemas.js";
|
|
4
|
+
export type AcquisitionTermsRefusal = "acquisition_terms_policy_unconfigured" | "acquisition_terms_invalid" | "acquisition_terms_ambiguous"
|
|
5
|
+
| "acquisition_terms_not_effective" | "acquisition_terms_capability_required" | "acquisition_terms_v2_required";
|
|
6
|
+
export class AcquisitionTermsError extends Error {
|
|
7
|
+
constructor(readonly reason: AcquisitionTermsRefusal) { super(reason); this.name = "AcquisitionTermsError"; }
|
|
8
|
+
}
|
|
9
|
+
export interface AcquisitionTermsSelection {
|
|
10
|
+
kind: "legacy_pair" | "v2_standalone" | "legacy_unconfigured";
|
|
11
|
+
terms: ResolvedAcquisitionTerms;
|
|
12
|
+
sourcePromotionId: string;
|
|
13
|
+
legacyPromotionId?: string;
|
|
14
|
+
nextBoundaryAt: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function requirePolicy<Row>(policy: AcquisitionFinancialPolicy<Row>): void {
|
|
18
|
+
if (!policy) throw new AcquisitionTermsError("acquisition_terms_policy_unconfigured");
|
|
19
|
+
}
|
|
20
|
+
function effective<Row>(row: Row, at: number, policy: AcquisitionFinancialPolicy<Row>): boolean {
|
|
21
|
+
const facts = policy.facts(row);
|
|
22
|
+
return Date.parse(facts.validFrom) <= at && (facts.validTo === null || Date.parse(facts.validTo) > at);
|
|
23
|
+
}
|
|
24
|
+
function metadata<Row>(row: Row, policy: AcquisitionFinancialPolicy<Row>): AcquisitionTermsMetadata {
|
|
25
|
+
const parsed = acquisitionTermsMetadataSchema.safeParse(policy.facts(row).metadata);
|
|
26
|
+
if (!parsed.success) throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
27
|
+
policy.validateMetadata(row, parsed.data);
|
|
28
|
+
return parsed.data;
|
|
29
|
+
}
|
|
30
|
+
function resolved<Row>(row: Row, terms: AcquisitionTermsMetadata, policy: AcquisitionFinancialPolicy<Row>,
|
|
31
|
+
purpose: "resolved_target" | "filter_validation" = "resolved_target"): ResolvedAcquisitionTerms {
|
|
32
|
+
const facts = policy.facts(row);
|
|
33
|
+
const parsed = resolvedAcquisitionTermsSchema.safeParse({ ...terms,
|
|
34
|
+
initialDiscountBps: policy.initialDiscountBps(row, purpose), effectiveFrom: facts.validFrom,
|
|
35
|
+
effectiveTo: facts.validTo, sourcePromotionId: facts.id });
|
|
36
|
+
if (!parsed.success) throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
37
|
+
return { ...parsed.data, effectiveFrom: new Date(parsed.data.effectiveFrom).toISOString(),
|
|
38
|
+
effectiveTo: parsed.data.effectiveTo === null ? null : new Date(parsed.data.effectiveTo).toISOString() };
|
|
39
|
+
}
|
|
40
|
+
/** Selection never falls back from a configured but malformed or incomplete schedule. */
|
|
41
|
+
export function selectAcquisitionTerms<Row>(
|
|
42
|
+
rows: readonly Row[], atTime: string, regionCode: string, policy: AcquisitionFinancialPolicy<Row>,
|
|
43
|
+
): AcquisitionTermsSelection | null {
|
|
44
|
+
requirePolicy(policy);
|
|
45
|
+
const at = Date.parse(atTime);
|
|
46
|
+
if (!Number.isFinite(at)) throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
47
|
+
const scoped = rows.filter((row) => policy.facts(row).active && policy.facts(row).regionCodes.includes(regionCode));
|
|
48
|
+
const tagged = scoped.filter((row) => policy.facts(row).metadataPresent);
|
|
49
|
+
if (tagged.length === 0) {
|
|
50
|
+
const legacyRows = scoped.filter((row) => policy.classify(row).unconfiguredLegacyEligible);
|
|
51
|
+
const knownPairs = legacyRows.flatMap((legacy) => scoped.filter((mirror) => policy.classify(mirror).target
|
|
52
|
+
&& policy.pairMatches(legacy, mirror)).map((mirror) => ({ legacy, mirror })));
|
|
53
|
+
const pairs = knownPairs.filter(({ mirror }) => effective(mirror, at, policy));
|
|
54
|
+
if (pairs.length > 1) throw new AcquisitionTermsError("acquisition_terms_ambiguous");
|
|
55
|
+
if (pairs.length === 0) {
|
|
56
|
+
if (knownPairs.length) throw new AcquisitionTermsError("acquisition_terms_not_effective");
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const { legacy, mirror } = pairs[0]!;
|
|
60
|
+
const terms = resolved(mirror, policy.unconfiguredTerms(legacy, mirror), policy);
|
|
61
|
+
return { kind: "legacy_unconfigured", terms, sourcePromotionId: policy.facts(mirror).id,
|
|
62
|
+
legacyPromotionId: policy.facts(legacy).id, nextBoundaryAt: terms.effectiveTo };
|
|
63
|
+
}
|
|
64
|
+
const grouped = new Map<string, Row[]>();
|
|
65
|
+
for (const row of tagged) {
|
|
66
|
+
const parsed = metadata(row, policy);
|
|
67
|
+
const group = grouped.get(parsed.revision) ?? [];
|
|
68
|
+
group.push(row); grouped.set(parsed.revision, group);
|
|
69
|
+
}
|
|
70
|
+
const selections: AcquisitionTermsSelection[] = [];
|
|
71
|
+
for (const group of grouped.values()) {
|
|
72
|
+
const mirrors = group.filter((row) => policy.classify(row).target);
|
|
73
|
+
const legacy = group.filter((row) => policy.classify(row).baselineLegacy);
|
|
74
|
+
if (mirrors.length !== 1 || legacy.length > 1 || group.length !== mirrors.length + legacy.length) {
|
|
75
|
+
throw new AcquisitionTermsError("acquisition_terms_ambiguous");
|
|
76
|
+
}
|
|
77
|
+
const mirror = mirrors[0]!;
|
|
78
|
+
if (legacy.length === 1 ? !policy.pairMatches(legacy[0]!, mirror) : policy.facts(mirror).mirrorOf != null) {
|
|
79
|
+
throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
80
|
+
}
|
|
81
|
+
const terms = resolved(mirror, metadata(mirror, policy), policy);
|
|
82
|
+
selections.push({ kind: legacy.length ? "legacy_pair" : "v2_standalone", terms,
|
|
83
|
+
sourcePromotionId: policy.facts(mirror).id, ...(legacy.length ? { legacyPromotionId: policy.facts(legacy[0]!).id } : {}), nextBoundaryAt: null });
|
|
84
|
+
}
|
|
85
|
+
selections.sort((a, b) => Date.parse(a.terms.effectiveFrom) - Date.parse(b.terms.effectiveFrom));
|
|
86
|
+
for (let i = 1; i < selections.length; i++) {
|
|
87
|
+
const previousEnd = selections[i - 1]!.terms.effectiveTo;
|
|
88
|
+
const start = Date.parse(selections[i]!.terms.effectiveFrom);
|
|
89
|
+
if (previousEnd === null || Date.parse(previousEnd) > start) throw new AcquisitionTermsError("acquisition_terms_ambiguous");
|
|
90
|
+
if (Date.parse(previousEnd) < start) throw new AcquisitionTermsError("acquisition_terms_not_effective");
|
|
91
|
+
}
|
|
92
|
+
const current = selections.filter(({ terms }) => Date.parse(terms.effectiveFrom) <= at
|
|
93
|
+
&& (terms.effectiveTo === null || Date.parse(terms.effectiveTo) > at));
|
|
94
|
+
if (current.length !== 1) throw new AcquisitionTermsError("acquisition_terms_not_effective");
|
|
95
|
+
if (scoped.some((row) => !policy.facts(row).metadataPresent && policy.classify(row).canonicalRow && effective(row, at, policy))) {
|
|
96
|
+
throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
97
|
+
}
|
|
98
|
+
const boundaries = selections.flatMap(({ terms }) => [terms.effectiveFrom, terms.effectiveTo])
|
|
99
|
+
.filter((instant): instant is string => instant !== null && Date.parse(instant) > at).sort();
|
|
100
|
+
return { ...current[0]!, nextBoundaryAt: boundaries[0] ?? null };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Ordinary coupon validity and inactive rows remain owned by their evaluator. */
|
|
104
|
+
export function filterAcquisitionPromotions<Row>(rows: readonly Row[], atTime: string, regionCode: string,
|
|
105
|
+
policy: AcquisitionFinancialPolicy<Row>): Row[] {
|
|
106
|
+
requirePolicy(policy);
|
|
107
|
+
const at = Date.parse(atTime);
|
|
108
|
+
if (!Number.isFinite(at)) throw new AcquisitionTermsError("acquisition_terms_invalid");
|
|
109
|
+
return rows.filter((row) => {
|
|
110
|
+
if (!policy.facts(row).regionCodes.includes(regionCode) || !policy.classify(row).canonicalRow) return true;
|
|
111
|
+
if (policy.facts(row).metadataPresent) {
|
|
112
|
+
const parsed = metadata(row, policy);
|
|
113
|
+
resolved(row, parsed, policy, "filter_validation");
|
|
114
|
+
}
|
|
115
|
+
return effective(row, at, policy);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function assertAcquisitionTermsCapability(
|
|
120
|
+
selection: AcquisitionTermsSelection | null, capability: unknown, promotionEngineVersion: unknown,
|
|
121
|
+
): void {
|
|
122
|
+
if (!selection || selection.kind !== "v2_standalone") return;
|
|
123
|
+
if (capability !== ACQUISITION_TERMS_CAPABILITY) throw new AcquisitionTermsError("acquisition_terms_capability_required");
|
|
124
|
+
if (promotionEngineVersion !== "promotion-engine.v2") throw new AcquisitionTermsError("acquisition_terms_v2_required");
|
|
125
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ACQUISITION_TERMS_CAPABILITY, type AcquisitionTerms, type ResolvedAcquisitionTerms } from "./contracts/acquisitionTerms.js";
|
|
3
|
+
export const acquisitionCapabilitySchema = z.literal(ACQUISITION_TERMS_CAPABILITY);
|
|
4
|
+
const revisionSchema = z.string().min(1).max(80).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
5
|
+
const bpsSchema = z.number().int().min(0).max(10_000);
|
|
6
|
+
const instantSchema = z.string().datetime({ offset: true });
|
|
7
|
+
|
|
8
|
+
/** Only these extra terms are stored; promotion benefit and validity remain authoritative. */
|
|
9
|
+
export const acquisitionTermsMetadataSchema = z.object({
|
|
10
|
+
schemaVersion: acquisitionCapabilitySchema,
|
|
11
|
+
revision: revisionSchema,
|
|
12
|
+
delivery2DiscountBps: bpsSchema,
|
|
13
|
+
}).strict();
|
|
14
|
+
|
|
15
|
+
const termsShape = {
|
|
16
|
+
...acquisitionTermsMetadataSchema.shape,
|
|
17
|
+
initialDiscountBps: bpsSchema,
|
|
18
|
+
effectiveFrom: instantSchema,
|
|
19
|
+
effectiveTo: instantSchema.nullable(),
|
|
20
|
+
};
|
|
21
|
+
const validWindow = (terms: { effectiveFrom?: string; effectiveTo?: string | null }) =>
|
|
22
|
+
typeof terms.effectiveFrom === "string" && (terms.effectiveTo === null
|
|
23
|
+
|| typeof terms.effectiveTo === "string" && Date.parse(terms.effectiveTo) > Date.parse(terms.effectiveFrom));
|
|
24
|
+
|
|
25
|
+
export const acquisitionTermsSchema = z.object(termsShape).strict()
|
|
26
|
+
.refine(validWindow, { message: "Acquisition terms require a nonempty effective interval" });
|
|
27
|
+
export const resolvedAcquisitionTermsSchema = z.object({
|
|
28
|
+
...termsShape,
|
|
29
|
+
sourcePromotionId: z.string().min(1).max(120),
|
|
30
|
+
}).strict().refine(validWindow, { message: "Acquisition terms require a nonempty effective interval" });
|
|
31
|
+
|
|
32
|
+
export const acquisitionTermsExpectationSchema = z.object({
|
|
33
|
+
revision: revisionSchema,
|
|
34
|
+
delivery2DiscountBps: bpsSchema,
|
|
35
|
+
}).strict();
|
|
36
|
+
|
|
37
|
+
export function projectAcquisitionTerms(terms: ResolvedAcquisitionTerms): AcquisitionTerms {
|
|
38
|
+
const { sourcePromotionId: _source, ...publicTerms } = resolvedAcquisitionTermsSchema.parse(terms);
|
|
39
|
+
return publicTerms;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
/** Compose only at an opted-in host boundary; preserve its existing refinements. */
|
|
44
|
+
export function createAcquisitionQuoteSchemas<Request extends z.ZodObject, Context extends z.ZodObject, Quote extends z.ZodObject>(input: {
|
|
45
|
+
request: Request; context: Context; quote: Quote;
|
|
46
|
+
}) {
|
|
47
|
+
return {
|
|
48
|
+
request: input.request.safeExtend({ acquisitionCapability: acquisitionCapabilitySchema.optional() }),
|
|
49
|
+
context: input.context.safeExtend({ acquisitionCapability: acquisitionCapabilitySchema.optional(), atTime: instantSchema.optional() }),
|
|
50
|
+
quote: input.quote.safeExtend({ acquisitionTerms: resolvedAcquisitionTermsSchema.optional() }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { CATALOG_FACTS_POLICY_PROVENANCE_VERSION, CATALOG_FACTS_PROVENANCE_VERSION } from "./contracts/provenance.js";
|
|
2
|
+
import type { CatalogFactsProvenance } from "./contracts/provenance.js";
|
|
3
|
+
import {
|
|
4
|
+
catalogFactsProvenanceSchema,
|
|
5
|
+
} from "./catalogFactsSchemas.js";
|
|
6
|
+
import type { CreateQuoteResponse } from "./contracts/quote.js";
|
|
7
|
+
import { CommerceQuoteError } from "./quotePorts.js";
|
|
8
|
+
import type { CommerceQuoteCatalogItem } from "./commerceQuoteCatalogReadPort.js";
|
|
9
|
+
|
|
10
|
+
type ResolvedPriceObservation = {
|
|
11
|
+
priceEntryId: string;
|
|
12
|
+
unitPriceMinor: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type StrictCatalogFactsItem = CommerceQuoteCatalogItem & {
|
|
16
|
+
documentRevision: { id: string; digest: string };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type PolicyDerivedSubscriptionFacts = {
|
|
20
|
+
policyRevisionId: string;
|
|
21
|
+
policyDigest: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function createCatalogFactsProvenance(input: {
|
|
25
|
+
item: StrictCatalogFactsItem;
|
|
26
|
+
resolved: ResolvedPriceObservation;
|
|
27
|
+
base: ResolvedPriceObservation;
|
|
28
|
+
mode: "one_time" | "subscription";
|
|
29
|
+
atTime: string;
|
|
30
|
+
currency: string;
|
|
31
|
+
resolvedUnitAmountMinor: number;
|
|
32
|
+
resolvedLineAmountMinor: number;
|
|
33
|
+
baseUnitAmountMinor: number;
|
|
34
|
+
policy?: PolicyDerivedSubscriptionFacts;
|
|
35
|
+
}): CatalogFactsProvenance {
|
|
36
|
+
if (input.policy) {
|
|
37
|
+
if (input.mode !== "subscription") {
|
|
38
|
+
throw new CommerceQuoteError("PRICING_INVARIANT_VIOLATION", "One-time quote facts cannot carry a subscription policy");
|
|
39
|
+
}
|
|
40
|
+
return catalogFactsProvenanceSchema.parse({
|
|
41
|
+
version: CATALOG_FACTS_POLICY_PROVENANCE_VERSION,
|
|
42
|
+
skuId: input.item.skuId,
|
|
43
|
+
documentRevisionId: input.item.documentRevision.id,
|
|
44
|
+
documentDigest: input.item.documentRevision.digest,
|
|
45
|
+
basePriceEntryId: input.base.priceEntryId,
|
|
46
|
+
policyRevisionId: input.policy.policyRevisionId,
|
|
47
|
+
policyDigest: input.policy.policyDigest,
|
|
48
|
+
mode: input.mode,
|
|
49
|
+
atTime: input.atTime,
|
|
50
|
+
currency: input.currency,
|
|
51
|
+
resolvedUnitAmountMinor: input.resolvedUnitAmountMinor,
|
|
52
|
+
resolvedLineAmountMinor: input.resolvedLineAmountMinor,
|
|
53
|
+
baseUnitAmountMinor: input.baseUnitAmountMinor,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return catalogFactsProvenanceSchema.parse({
|
|
57
|
+
version: CATALOG_FACTS_PROVENANCE_VERSION,
|
|
58
|
+
skuId: input.item.skuId,
|
|
59
|
+
documentRevisionId: input.item.documentRevision.id,
|
|
60
|
+
documentDigest: input.item.documentRevision.digest,
|
|
61
|
+
resolvedPriceEntryId: input.resolved.priceEntryId,
|
|
62
|
+
basePriceEntryId: input.base.priceEntryId,
|
|
63
|
+
mode: input.mode,
|
|
64
|
+
atTime: input.atTime,
|
|
65
|
+
currency: input.currency,
|
|
66
|
+
resolvedUnitAmountMinor: input.resolvedUnitAmountMinor,
|
|
67
|
+
resolvedLineAmountMinor: input.resolvedLineAmountMinor,
|
|
68
|
+
baseUnitAmountMinor: input.baseUnitAmountMinor,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function catalogFactsForQuoteLine(
|
|
73
|
+
enabled: boolean, item: CommerceQuoteCatalogItem, resolved: ResolvedPriceObservation,
|
|
74
|
+
base: ResolvedPriceObservation, mode: "one_time" | "subscription",
|
|
75
|
+
atTime: string, currency: string, resolvedUnitAmountMinor: number,
|
|
76
|
+
resolvedLineAmountMinor: number, baseUnitAmountMinor: number,
|
|
77
|
+
policy?: PolicyDerivedSubscriptionFacts,
|
|
78
|
+
): CatalogFactsProvenance | undefined {
|
|
79
|
+
if (!enabled) return undefined;
|
|
80
|
+
const { id, digest } = item.documentRevision;
|
|
81
|
+
if (id === null || digest === null) {
|
|
82
|
+
throw new CommerceQuoteError("PRICE_NOT_CONFIGURED", "Strict catalog facts are unavailable", {
|
|
83
|
+
sku: item.skuCode,
|
|
84
|
+
variantId: item.variantId,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return createCatalogFactsProvenance({
|
|
88
|
+
item: { ...item, documentRevision: { id, digest } },
|
|
89
|
+
resolved, base, mode, atTime, currency, resolvedUnitAmountMinor, resolvedLineAmountMinor, baseUnitAmountMinor, policy,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Public responses retain every money field but never server-only catalog identifiers. */
|
|
94
|
+
export function projectPublicQuoteSnapshot<Snapshot extends CreateQuoteResponse>(snapshot: Snapshot): Snapshot {
|
|
95
|
+
return {
|
|
96
|
+
...snapshot,
|
|
97
|
+
quote: {
|
|
98
|
+
...snapshot.quote,
|
|
99
|
+
lines: snapshot.quote.lines.map(({ catalogFacts: _catalogFacts, ...line }) => line),
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { CATALOG_FACTS_PROVENANCE_VERSION, CATALOG_FACTS_POLICY_PROVENANCE_VERSION } from "./contracts/provenance.js";
|
|
4
|
+
const catalogFactsFields = {
|
|
5
|
+
skuId: z.string().uuid(),
|
|
6
|
+
documentRevisionId: z.string().uuid(),
|
|
7
|
+
documentDigest: z.string().regex(/^[a-f0-9]{64}$/iu),
|
|
8
|
+
basePriceEntryId: z.string().trim().min(1).max(120),
|
|
9
|
+
atTime: z.string().datetime({ offset: true }),
|
|
10
|
+
currency: z.string().regex(/^[A-Z]{3}$/u),
|
|
11
|
+
resolvedUnitAmountMinor: z.number().int().min(0),
|
|
12
|
+
resolvedLineAmountMinor: z.number().int().min(0),
|
|
13
|
+
baseUnitAmountMinor: z.number().int().min(0),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Server-only evidence for a quote line resolved from the strict current
|
|
18
|
+
* catalog facts port. V1 records authored price-entry observations; v2
|
|
19
|
+
* records policy-derived subscription money without inventing such an entry.
|
|
20
|
+
*/
|
|
21
|
+
const catalogFactsV1ProvenanceSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
version: z.literal(CATALOG_FACTS_PROVENANCE_VERSION),
|
|
24
|
+
resolvedPriceEntryId: z.string().trim().min(1).max(120),
|
|
25
|
+
mode: z.enum(["one_time", "subscription"]),
|
|
26
|
+
...catalogFactsFields,
|
|
27
|
+
})
|
|
28
|
+
.strict();
|
|
29
|
+
|
|
30
|
+
const catalogFactsV2ProvenanceSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
version: z.literal(CATALOG_FACTS_POLICY_PROVENANCE_VERSION),
|
|
33
|
+
// A policy-derived subscription price has no authored subscription price
|
|
34
|
+
// entry. Its immutable evidence is the exact base anchor plus this policy.
|
|
35
|
+
policyRevisionId: z.string().uuid(),
|
|
36
|
+
policyDigest: z.string().regex(/^[a-f0-9]{64}$/iu),
|
|
37
|
+
mode: z.literal("subscription"),
|
|
38
|
+
...catalogFactsFields,
|
|
39
|
+
})
|
|
40
|
+
.strict();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Server-only evidence for a strict quote line. V1 remains readable for
|
|
44
|
+
* historical and explicitly pre-cutover legacy subscription observations; v2
|
|
45
|
+
* records the policy which derived a current subscription amount.
|
|
46
|
+
*/
|
|
47
|
+
export const catalogFactsProvenanceSchema = z.union([
|
|
48
|
+
catalogFactsV1ProvenanceSchema,
|
|
49
|
+
catalogFactsV2ProvenanceSchema,
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
/** Reject server-only provenance at every HTTP response schema boundary. */
|
|
55
|
+
export function rejectCatalogFactsFromPublicQuote(
|
|
56
|
+
response: { quote: { lines: readonly { catalogFacts?: unknown }[] } },
|
|
57
|
+
ctx: z.RefinementCtx,
|
|
58
|
+
pathPrefix: Array<string | number> = ["quote"],
|
|
59
|
+
): void {
|
|
60
|
+
response.quote.lines.forEach((line, index) => {
|
|
61
|
+
if (line.catalogFacts !== undefined) {
|
|
62
|
+
ctx.addIssue({
|
|
63
|
+
code: z.ZodIssueCode.custom,
|
|
64
|
+
message: "catalog facts are server-only",
|
|
65
|
+
path: [...pathPrefix, "lines", index, "catalogFacts"],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|