@openlup/platform 0.18.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 (104) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +24 -9
  3. package/dist/catalog/adminBundleReadHandler.d.ts +2 -1
  4. package/dist/catalog/adminBundleReadHandler.js +4 -4
  5. package/dist/catalog/bundleCatalogReadPort.d.ts +8 -1
  6. package/dist/catalog/bundleCatalogStore.d.ts +3 -1
  7. package/dist/catalog/bundleCatalogStore.js +5 -1
  8. package/dist/catalog/bundlePriceSelection.d.ts +13 -0
  9. package/dist/catalog/bundlePriceSelection.js +26 -0
  10. package/dist/catalog/contracts/bundleRead.d.ts +18 -0
  11. package/dist/catalog/contracts/draft.js +1 -1
  12. package/dist/catalog/effectiveBundleCatalogQueries.d.ts +8 -0
  13. package/dist/catalog/effectiveBundleCatalogQueries.js +79 -0
  14. package/dist/catalog/effectiveBundleCatalogStore.d.ts +4 -0
  15. package/dist/catalog/effectiveBundleCatalogStore.js +156 -0
  16. package/dist/catalog/index.d.ts +1 -0
  17. package/dist/catalog/index.js +1 -0
  18. package/dist/composition/index.d.ts +2 -0
  19. package/dist/composition/index.js +2 -0
  20. package/dist/composition/inventoryContribution.d.ts +16 -0
  21. package/dist/composition/inventoryContribution.js +17 -0
  22. package/dist/composition/shippingContribution.d.ts +16 -0
  23. package/dist/composition/shippingContribution.js +17 -0
  24. package/dist/inventory/bindings.d.ts +35 -0
  25. package/dist/inventory/bindings.js +45 -0
  26. package/dist/inventory/index.d.ts +1 -0
  27. package/dist/inventory/index.js +1 -0
  28. package/dist/pricing/commercePriceAuthority.d.ts +2 -0
  29. package/dist/pricing/commercePriceAuthority.js +61 -0
  30. package/dist/pricing/commerceQuoteHandler.d.ts +7 -3
  31. package/dist/pricing/commerceQuoteHandler.js +16 -4
  32. package/dist/pricing/dbBackedCommerceQuotePort.d.ts +8 -0
  33. package/dist/pricing/dbBackedCommerceQuotePort.js +46 -29
  34. package/dist/pricing/index.d.ts +2 -1
  35. package/dist/pricing/index.js +1 -1
  36. package/dist/pricing/offerPolicySchemas.d.ts +2 -2
  37. package/dist/pricing/offerPricingSchemas.d.ts +2 -2
  38. package/dist/pricing/pricingPolicyToken.d.ts +2 -2
  39. package/dist/pricing/quoteSchemas.d.ts +18 -18
  40. package/dist/pricing/quoteShippingPromotions.d.ts +20 -0
  41. package/dist/pricing/quoteShippingPromotions.js +1 -0
  42. package/dist/promotions/adminPromotionCodesContracts.d.ts +7 -7
  43. package/dist/promotions/commerceQuoteReadPreparation.d.ts +3 -0
  44. package/dist/promotions/commerceQuoteReadPreparation.js +27 -0
  45. package/dist/promotions/index.d.ts +3 -0
  46. package/dist/promotions/index.js +1 -0
  47. package/dist/promotions/promotionQuoteAcceptance.d.ts +5 -0
  48. package/dist/promotions/promotionQuoteAcceptance.js +12 -12
  49. package/dist/promotions/promotionQuoteBinding.d.ts +4 -2
  50. package/dist/promotions/promotionQuoteBinding.js +5 -3
  51. package/dist/promotions/quoteAcquisitionBinding.d.ts +6 -0
  52. package/dist/promotions/quoteAcquisitionBinding.js +1 -0
  53. package/dist/promotions/quoteAutomaticPromotions.d.ts +3 -1
  54. package/dist/promotions/quoteAutomaticPromotions.js +28 -4
  55. package/dist/promotions/quotePromoDiscounts.d.ts +5 -1
  56. package/dist/promotions/quotePromoDiscounts.js +8 -2
  57. package/dist/promotions/quotePromotionV2.d.ts +7 -6
  58. package/dist/promotions/quotePromotionV2.js +15 -3
  59. package/dist/promotions/quoteShippingPolicy.d.ts +23 -0
  60. package/dist/promotions/quoteShippingPolicy.js +47 -0
  61. package/dist/shipping/bindings.d.ts +57 -0
  62. package/dist/shipping/bindings.js +46 -0
  63. package/dist/shipping/contracts/index.d.ts +1 -0
  64. package/dist/shipping/contracts/threshold.d.ts +22 -0
  65. package/dist/shipping/contracts/threshold.js +1 -0
  66. package/dist/shipping/index.d.ts +3 -0
  67. package/dist/shipping/index.js +2 -0
  68. package/dist/shipping/thresholdPolicy.d.ts +42 -0
  69. package/dist/shipping/thresholdPolicy.js +60 -0
  70. package/package.json +2 -2
  71. package/release-gates.json +136 -134
  72. package/src/catalog/adminBundleReadHandler.ts +6 -4
  73. package/src/catalog/bundleCatalogReadPort.ts +6 -2
  74. package/src/catalog/bundleCatalogStore.ts +5 -0
  75. package/src/catalog/bundlePriceSelection.ts +25 -0
  76. package/src/catalog/contracts/bundleRead.ts +2 -2
  77. package/src/catalog/contracts/draft.ts +1 -1
  78. package/src/catalog/effectiveBundleCatalogQueries.ts +80 -0
  79. package/src/catalog/effectiveBundleCatalogStore.ts +208 -0
  80. package/src/catalog/index.ts +1 -0
  81. package/src/composition/index.ts +2 -0
  82. package/src/composition/inventoryContribution.ts +24 -0
  83. package/src/composition/shippingContribution.ts +24 -0
  84. package/src/inventory/bindings.ts +61 -0
  85. package/src/inventory/index.ts +1 -0
  86. package/src/pricing/commercePriceAuthority.ts +65 -0
  87. package/src/pricing/commerceQuoteHandler.ts +20 -5
  88. package/src/pricing/dbBackedCommerceQuotePort.ts +50 -30
  89. package/src/pricing/index.ts +2 -1
  90. package/src/pricing/quoteShippingPromotions.ts +21 -0
  91. package/src/promotions/commerceQuoteReadPreparation.ts +29 -0
  92. package/src/promotions/index.ts +3 -0
  93. package/src/promotions/promotionQuoteAcceptance.ts +17 -10
  94. package/src/promotions/promotionQuoteBinding.ts +7 -2
  95. package/src/promotions/quoteAcquisitionBinding.ts +7 -0
  96. package/src/promotions/quoteAutomaticPromotions.ts +30 -4
  97. package/src/promotions/quotePromoDiscounts.ts +11 -2
  98. package/src/promotions/quotePromotionV2.ts +20 -4
  99. package/src/promotions/quoteShippingPolicy.ts +61 -0
  100. package/src/shipping/bindings.ts +74 -0
  101. package/src/shipping/contracts/index.ts +1 -0
  102. package/src/shipping/contracts/threshold.ts +22 -0
  103. package/src/shipping/index.ts +3 -0
  104. package/src/shipping/thresholdPolicy.ts +65 -0
@@ -5,94 +5,117 @@
5
5
  "internal-candidate": "Development-preview API; no stable compatibility claim."
6
6
  },
7
7
  "packageSurface": {
8
- "./http": {
8
+ "./bff": {
9
9
  "role": "rail",
10
10
  "maturity": "experimental",
11
11
  "compatibilityPolicy": "internal-candidate",
12
12
  "packageSmokeEvidence": [
13
13
  "test/packageBoundary.test.ts",
14
- "test/http/runtime.test.ts",
15
- "test/http/webhookSignature.test.ts"
14
+ "test/bff/dispatch.test.ts"
16
15
  ],
17
- "entrypoint": "dist/http/index.d.ts",
18
- "snapshot": "api/http.api.md"
16
+ "entrypoint": "dist/bff/index.d.ts",
17
+ "snapshot": "api/bff.api.md"
19
18
  },
20
- "./bff": {
19
+ "./blob": {
21
20
  "role": "rail",
22
21
  "maturity": "experimental",
23
22
  "compatibilityPolicy": "internal-candidate",
24
23
  "packageSmokeEvidence": [
25
24
  "test/packageBoundary.test.ts",
26
- "test/bff/dispatch.test.ts"
25
+ "test/blob/binding.test.ts"
27
26
  ],
28
- "entrypoint": "dist/bff/index.d.ts",
29
- "snapshot": "api/bff.api.md"
27
+ "entrypoint": "dist/blob/index.d.ts",
28
+ "snapshot": "api/blob.api.md"
30
29
  },
31
- "./scheduler": {
32
- "role": "rail",
30
+ "./catalog": {
31
+ "role": "capability",
33
32
  "maturity": "experimental",
34
33
  "compatibilityPolicy": "internal-candidate",
35
34
  "packageSmokeEvidence": [
36
35
  "test/packageBoundary.test.ts",
37
- "test/scheduler/jobs.test.ts"
36
+ "test/catalog/catalogDraftHandler.test.ts",
37
+ "test/catalog/catalogDraftValidation.test.ts",
38
+ "test/catalog/resolvedSkuProjection.test.ts",
39
+ "test/catalog/referenceCatalogHandler.test.ts",
40
+ "test/catalog/catalogMediaResolution.test.ts",
41
+ "test/catalog/catalogLifecycleContracts.test.ts",
42
+ "test/catalog/bundleRules.test.ts",
43
+ "test/catalog/bundleAvailability.test.ts",
44
+ "test/catalog/bundleAvailabilityService.test.ts",
45
+ "test/catalog/bundleCatalogReadPort.test.ts",
46
+ "test/catalog/sellableBundleHandler.test.ts",
47
+ "test/catalog/catalogFoundationContracts.test.ts",
48
+ "test/catalog/catalogDraftContracts.test.ts",
49
+ "test/catalog/bundlePriceSelectionParity.test.ts"
38
50
  ],
39
- "entrypoint": "dist/scheduler/index.d.ts",
40
- "snapshot": "api/scheduler.api.md"
51
+ "entrypoint": "dist/catalog/index.d.ts",
52
+ "snapshot": "api/catalog.api.md"
41
53
  },
42
- "./blob": {
43
- "role": "rail",
54
+ "./catalog/contracts": {
55
+ "role": "capability",
44
56
  "maturity": "experimental",
45
57
  "compatibilityPolicy": "internal-candidate",
46
58
  "packageSmokeEvidence": [
47
59
  "test/packageBoundary.test.ts",
48
- "test/blob/binding.test.ts"
60
+ "test/catalog/catalogFoundationContracts.test.ts",
61
+ "test/catalog/catalogProductTypeContracts.test.ts",
62
+ "test/catalog/catalogDraftContracts.test.ts",
63
+ "test/catalog/catalogMediaResolution.test.ts",
64
+ "test/catalog/catalogLifecycleContracts.test.ts"
49
65
  ],
50
- "entrypoint": "dist/blob/index.d.ts",
51
- "snapshot": "api/blob.api.md"
66
+ "entrypoint": "dist/catalog/contracts/index.d.ts",
67
+ "snapshot": "api/catalog-contracts.api.md"
52
68
  },
53
- "./data": {
54
- "role": "rail",
69
+ "./communications": {
70
+ "role": "capability",
55
71
  "maturity": "experimental",
56
72
  "compatibilityPolicy": "internal-candidate",
57
73
  "packageSmokeEvidence": [
58
74
  "test/packageBoundary.test.ts",
59
- "test/data/binding.test.ts"
75
+ "test/communications/sql.test.ts",
76
+ "test/communications/contribution.test.ts",
77
+ "test/communications/transportConformance.test.ts",
78
+ "test/communications/marketingSyncConformance.test.ts",
79
+ "test/communications/marketingSyncCompatibility.test.ts"
60
80
  ],
61
- "entrypoint": "dist/data/index.d.ts",
62
- "snapshot": "api/data.api.md"
81
+ "entrypoint": "dist/communications/index.d.ts",
82
+ "snapshot": "api/communications.api.md"
63
83
  },
64
- "./migrations": {
65
- "role": "rail",
84
+ "./communications/contracts": {
85
+ "role": "capability",
66
86
  "maturity": "experimental",
67
87
  "compatibilityPolicy": "internal-candidate",
68
88
  "packageSmokeEvidence": [
69
89
  "test/packageBoundary.test.ts",
70
- "test/migrations/postgresRunner.test.ts"
90
+ "test/communications/contracts.test.ts",
91
+ "test/communications/marketingSyncConformance.test.ts",
92
+ "test/communications/marketingSyncCompatibility.test.ts"
71
93
  ],
72
- "entrypoint": "dist/migrations/index.d.ts",
73
- "snapshot": "api/migrations.api.md"
94
+ "entrypoint": "dist/communications/contracts/index.d.ts",
95
+ "snapshot": "api/communications-contracts.api.md"
74
96
  },
75
- "./job-ledger": {
76
- "role": "rail",
97
+ "./communications/klaviyo": {
98
+ "role": "capability",
77
99
  "maturity": "experimental",
78
100
  "compatibilityPolicy": "internal-candidate",
79
101
  "packageSmokeEvidence": [
80
- "test/packageBoundary.test.ts",
81
- "test/job-ledger/postgres.test.ts"
102
+ "test/communications/klaviyo/portConformance.test.ts",
103
+ "test/communications/klaviyo/protocol.test.ts",
104
+ "test/communications/klaviyo/permissions.test.ts"
82
105
  ],
83
- "entrypoint": "dist/job-ledger/index.d.ts",
84
- "snapshot": "api/job-ledger.api.md"
106
+ "entrypoint": "dist/communications/klaviyo/index.d.ts",
107
+ "snapshot": "api/communications-klaviyo.api.md"
85
108
  },
86
- "./kernel": {
87
- "role": "rail",
109
+ "./communications/resend": {
110
+ "role": "capability",
88
111
  "maturity": "experimental",
89
112
  "compatibilityPolicy": "internal-candidate",
90
113
  "packageSmokeEvidence": [
91
- "test/packageBoundary.test.ts",
92
- "test/kernel/platformKernel.test.ts"
114
+ "test/communications/resend/egress.test.ts",
115
+ "test/communications/resend/protocol.test.ts"
93
116
  ],
94
- "entrypoint": "dist/kernel/index.d.ts",
95
- "snapshot": "api/kernel.api.md"
117
+ "entrypoint": "dist/communications/resend/index.d.ts",
118
+ "snapshot": "api/communications-resend.api.md"
96
119
  },
97
120
  "./composition": {
98
121
  "role": "rail",
@@ -105,6 +128,29 @@
105
128
  "entrypoint": "dist/composition/index.d.ts",
106
129
  "snapshot": "api/composition.api.md"
107
130
  },
131
+ "./data": {
132
+ "role": "rail",
133
+ "maturity": "experimental",
134
+ "compatibilityPolicy": "internal-candidate",
135
+ "packageSmokeEvidence": [
136
+ "test/packageBoundary.test.ts",
137
+ "test/data/binding.test.ts"
138
+ ],
139
+ "entrypoint": "dist/data/index.d.ts",
140
+ "snapshot": "api/data.api.md"
141
+ },
142
+ "./http": {
143
+ "role": "rail",
144
+ "maturity": "experimental",
145
+ "compatibilityPolicy": "internal-candidate",
146
+ "packageSmokeEvidence": [
147
+ "test/packageBoundary.test.ts",
148
+ "test/http/runtime.test.ts",
149
+ "test/http/webhookSignature.test.ts"
150
+ ],
151
+ "entrypoint": "dist/http/index.d.ts",
152
+ "snapshot": "api/http.api.md"
153
+ },
108
154
  "./identity": {
109
155
  "role": "capability",
110
156
  "maturity": "experimental",
@@ -127,71 +173,68 @@
127
173
  "entrypoint": "dist/identity/contracts/index.d.ts",
128
174
  "snapshot": "api/identity-contracts.api.md"
129
175
  },
130
- "./communications": {
176
+ "./inventory": {
131
177
  "role": "capability",
132
178
  "maturity": "experimental",
133
179
  "compatibilityPolicy": "internal-candidate",
134
180
  "packageSmokeEvidence": [
135
181
  "test/packageBoundary.test.ts",
136
- "test/communications/sql.test.ts",
137
- "test/communications/contribution.test.ts",
138
- "test/communications/transportConformance.test.ts",
139
- "test/communications/marketingSyncConformance.test.ts",
140
- "test/communications/marketingSyncCompatibility.test.ts"
182
+ "test/inventory/contracts.test.ts",
183
+ "test/inventory/adminInventoryConformance.test.ts",
184
+ "test/inventory/inventoryAvailabilityCharacterization.test.ts",
185
+ "test/inventory/newPurchaseStockBoundary.test.ts",
186
+ "test/inventory/reservationPolicyConformance.test.ts",
187
+ "test/inventory/subscriptionForecastCalculation.test.ts"
141
188
  ],
142
- "entrypoint": "dist/communications/index.d.ts",
143
- "snapshot": "api/communications.api.md"
189
+ "entrypoint": "dist/inventory/index.d.ts",
190
+ "snapshot": "api/inventory.api.md"
144
191
  },
145
- "./communications/contracts": {
192
+ "./inventory/contracts": {
146
193
  "role": "capability",
147
194
  "maturity": "experimental",
148
195
  "compatibilityPolicy": "internal-candidate",
149
196
  "packageSmokeEvidence": [
150
197
  "test/packageBoundary.test.ts",
151
- "test/communications/contracts.test.ts",
152
- "test/communications/marketingSyncConformance.test.ts",
153
- "test/communications/marketingSyncCompatibility.test.ts"
198
+ "test/inventory/contracts.test.ts",
199
+ "test/inventory/newPurchaseStockBoundary.test.ts",
200
+ "test/inventory/reservationPolicyConformance.test.ts",
201
+ "test/inventory/subscriptionForecastCalculation.test.ts"
154
202
  ],
155
- "entrypoint": "dist/communications/contracts/index.d.ts",
156
- "snapshot": "api/communications-contracts.api.md"
203
+ "entrypoint": "dist/inventory/contracts/index.d.ts",
204
+ "snapshot": "api/inventory-contracts.api.md"
157
205
  },
158
- "./catalog": {
159
- "role": "capability",
206
+ "./job-ledger": {
207
+ "role": "rail",
160
208
  "maturity": "experimental",
161
209
  "compatibilityPolicy": "internal-candidate",
162
210
  "packageSmokeEvidence": [
163
211
  "test/packageBoundary.test.ts",
164
- "test/catalog/catalogDraftHandler.test.ts",
165
- "test/catalog/catalogDraftValidation.test.ts",
166
- "test/catalog/resolvedSkuProjection.test.ts",
167
- "test/catalog/referenceCatalogHandler.test.ts",
168
- "test/catalog/catalogMediaResolution.test.ts",
169
- "test/catalog/catalogLifecycleContracts.test.ts",
170
- "test/catalog/bundleRules.test.ts",
171
- "test/catalog/bundleAvailability.test.ts",
172
- "test/catalog/bundleAvailabilityService.test.ts",
173
- "test/catalog/bundleCatalogReadPort.test.ts",
174
- "test/catalog/sellableBundleHandler.test.ts",
175
- "test/catalog/catalogFoundationContracts.test.ts",
176
- "test/catalog/catalogDraftContracts.test.ts"
212
+ "test/job-ledger/postgres.test.ts"
177
213
  ],
178
- "entrypoint": "dist/catalog/index.d.ts",
179
- "snapshot": "api/catalog.api.md"
214
+ "entrypoint": "dist/job-ledger/index.d.ts",
215
+ "snapshot": "api/job-ledger.api.md"
180
216
  },
181
- "./catalog/contracts": {
182
- "role": "capability",
217
+ "./kernel": {
218
+ "role": "rail",
183
219
  "maturity": "experimental",
184
220
  "compatibilityPolicy": "internal-candidate",
185
221
  "packageSmokeEvidence": [
186
222
  "test/packageBoundary.test.ts",
187
- "test/catalog/catalogFoundationContracts.test.ts",
188
- "test/catalog/catalogProductTypeContracts.test.ts",
189
- "test/catalog/catalogDraftContracts.test.ts",
190
- "test/catalog/catalogMediaResolution.test.ts",
191
- "test/catalog/catalogLifecycleContracts.test.ts"
223
+ "test/kernel/platformKernel.test.ts"
192
224
  ],
193
- "entrypoint": "dist/catalog/contracts/index.d.ts",
194
- "snapshot": "api/catalog-contracts.api.md"
225
+ "entrypoint": "dist/kernel/index.d.ts",
226
+ "snapshot": "api/kernel.api.md"
227
+ },
228
+ "./migrations": {
229
+ "role": "rail",
230
+ "maturity": "experimental",
231
+ "compatibilityPolicy": "internal-candidate",
232
+ "packageSmokeEvidence": [
233
+ "test/packageBoundary.test.ts",
234
+ "test/migrations/postgresRunner.test.ts"
235
+ ],
236
+ "entrypoint": "dist/migrations/index.d.ts",
237
+ "snapshot": "api/migrations.api.md"
195
238
  },
196
239
  "./pricing": {
197
240
  "role": "capability",
@@ -217,7 +260,8 @@
217
260
  "test/pricing/commerceOfferPricingCache.test.ts",
218
261
  "test/pricing/offerPolicyReadiness.test.ts",
219
262
  "test/pricing/acquisition.test.ts",
220
- "test/pricing/offerPricingContracts.test.ts"
263
+ "test/pricing/offerPricingContracts.test.ts",
264
+ "test/pricing/concurrentCommercePriceAuthorityParity.test.ts"
221
265
  ],
222
266
  "entrypoint": "dist/pricing/index.d.ts",
223
267
  "snapshot": "api/pricing.api.md"
@@ -275,58 +319,16 @@
275
319
  "entrypoint": "dist/promotions/contracts/index.d.ts",
276
320
  "snapshot": "api/promotions-contracts.api.md"
277
321
  },
278
- "./communications/resend": {
279
- "role": "capability",
280
- "maturity": "experimental",
281
- "compatibilityPolicy": "internal-candidate",
282
- "packageSmokeEvidence": [
283
- "test/communications/resend/egress.test.ts",
284
- "test/communications/resend/protocol.test.ts"
285
- ],
286
- "entrypoint": "dist/communications/resend/index.d.ts",
287
- "snapshot": "api/communications-resend.api.md"
288
- },
289
- "./communications/klaviyo": {
290
- "role": "capability",
291
- "maturity": "experimental",
292
- "compatibilityPolicy": "internal-candidate",
293
- "packageSmokeEvidence": [
294
- "test/communications/klaviyo/portConformance.test.ts",
295
- "test/communications/klaviyo/protocol.test.ts",
296
- "test/communications/klaviyo/permissions.test.ts"
297
- ],
298
- "entrypoint": "dist/communications/klaviyo/index.d.ts",
299
- "snapshot": "api/communications-klaviyo.api.md"
300
- },
301
- "./inventory": {
302
- "role": "capability",
303
- "maturity": "experimental",
304
- "compatibilityPolicy": "internal-candidate",
305
- "packageSmokeEvidence": [
306
- "test/packageBoundary.test.ts",
307
- "test/inventory/contracts.test.ts",
308
- "test/inventory/adminInventoryConformance.test.ts",
309
- "test/inventory/inventoryAvailabilityCharacterization.test.ts",
310
- "test/inventory/newPurchaseStockBoundary.test.ts",
311
- "test/inventory/reservationPolicyConformance.test.ts",
312
- "test/inventory/subscriptionForecastCalculation.test.ts"
313
- ],
314
- "entrypoint": "dist/inventory/index.d.ts",
315
- "snapshot": "api/inventory.api.md"
316
- },
317
- "./inventory/contracts": {
318
- "role": "capability",
322
+ "./scheduler": {
323
+ "role": "rail",
319
324
  "maturity": "experimental",
320
325
  "compatibilityPolicy": "internal-candidate",
321
326
  "packageSmokeEvidence": [
322
327
  "test/packageBoundary.test.ts",
323
- "test/inventory/contracts.test.ts",
324
- "test/inventory/newPurchaseStockBoundary.test.ts",
325
- "test/inventory/reservationPolicyConformance.test.ts",
326
- "test/inventory/subscriptionForecastCalculation.test.ts"
328
+ "test/scheduler/jobs.test.ts"
327
329
  ],
328
- "entrypoint": "dist/inventory/contracts/index.d.ts",
329
- "snapshot": "api/inventory-contracts.api.md"
330
+ "entrypoint": "dist/scheduler/index.d.ts",
331
+ "snapshot": "api/scheduler.api.md"
330
332
  },
331
333
  "./shipping": {
332
334
  "role": "capability",
@@ -361,23 +363,23 @@
361
363
  "maxFiles": 2000
362
364
  },
363
365
  "docs": {
364
- "maxPackedBytes": 40960,
366
+ "maxPackedBytes": 49152,
365
367
  "maxFiles": 10
366
368
  },
367
369
  "maxPackedBytes": 1351680,
368
370
  "maxUnpackedBytes": 5734400,
369
371
  "maxFiles": 2100,
370
372
  "baseline": {
371
- "version": "0.17.0",
372
- "packedBytes": 414220
373
+ "version": "0.18.0",
374
+ "packedBytes": 779849
373
375
  },
374
376
  "browserEntries": {
377
+ "./catalog/contracts": 4096,
375
378
  "./communications/contracts": 11776,
376
379
  "./identity/contracts": 4800,
377
- "./catalog/contracts": 4096,
380
+ "./inventory/contracts": 32,
378
381
  "./pricing/contracts": 416,
379
382
  "./promotions/contracts": 32,
380
- "./inventory/contracts": 32,
381
383
  "./shipping/contracts": 32
382
384
  }
383
385
  }
@@ -2,9 +2,10 @@ import type { BundleAdminBindings, AuthorizeAdmin, AgentDomainQuerySpec, BundleQ
2
2
  import { allocateBundleTargetPrice, type BundleAllocationFailure } from "@openlup/core/pricing";
3
3
  import type { BundleHistoryRequest, BundlePricePreview, GetBundleRequest, ListBundlesRequest, PreviewBundlePriceRequest } from "./contracts/bundleRead.js";
4
4
  import type { BundleAvailabilityRow, BundleAvailabilityService } from "./bundleAvailabilityService.js";
5
- import type { BundleCatalogReadPort, BundleDetail, BundleHistoryResult, ListBundlesResult } from "./bundleCatalogReadPort.js";
5
+ import type { BundleCatalogReadPort, BundleDetail, BundleHistoryResult, BundleTargetPriceRow, ListBundlesResult } from "./bundleCatalogReadPort.js";
6
6
 
7
7
  export interface AdminBundleReadHandlerDeps {
8
+ effectiveTarget?: (bundle: BundleDetail) => BundleTargetPriceRow | null;
8
9
  readPort: BundleCatalogReadPort;
9
10
  authorizeAdmin: AuthorizeAdmin;
10
11
  readsEnabled: boolean;
@@ -171,7 +172,7 @@ function createAdminBundlePreviewPriceHandler(deps: AdminBundleReadHandlerDeps)
171
172
  mode: input.mode,
172
173
  });
173
174
  if (!bundle) throw new BundleRpcError("P0002", "bundle_not_found");
174
- return previewBundlePrice(bundle, input);
175
+ return previewBundlePrice(bundle, input, deps.effectiveTarget);
175
176
  },
176
177
  (preview) => ({ contractVersion: COMMERCE_CONTRACT_VERSION, preview }),
177
178
  previewBundlePriceResponseSchema,
@@ -213,6 +214,7 @@ function toDetailView(bundle: BundleDetail, stock: BundleAvailabilityRow | null)
213
214
  function previewBundlePrice(
214
215
  bundle: BundleDetail,
215
216
  input: PreviewBundlePriceRequest,
217
+ effectiveTarget?: (bundle: BundleDetail) => BundleTargetPriceRow | null,
216
218
  ): BundlePricePreview {
217
219
  if (!bundle.resolvedPriceListId || !bundle.resolvedCurrency) {
218
220
  throw new BundleRpcError("P0002", "no_active_price_list");
@@ -224,7 +226,7 @@ function previewBundlePrice(
224
226
  throw new BundleRpcError("P0001", `component_unpriced:${unpriced[0].sku}`);
225
227
  }
226
228
 
227
- const activePrice = bundle.prices.find(
229
+ const activePrice = effectiveTarget ? effectiveTarget(bundle) : bundle.prices.find(
228
230
  (price) => price.active && (!input.mode || price.mode === input.mode),
229
231
  );
230
232
  const targetPriceMinor = input.targetPriceMinor ?? activePrice?.targetPriceMinor;
@@ -245,7 +247,7 @@ function previewBundlePrice(
245
247
  return {
246
248
  bundleCode: bundle.code,
247
249
  priceListId: bundle.resolvedPriceListId,
248
- mode: input.mode ?? activePrice?.mode ?? "any",
250
+ mode: input.mode ?? activePrice?.mode ?? (effectiveTarget ? "one_time" : "any"),
249
251
  currency: bundle.resolvedCurrency,
250
252
  referenceTotalMinor: allocation.referenceTotalMinor,
251
253
  targetPriceMinor: allocation.targetPriceMinor,
@@ -98,6 +98,8 @@ export interface BundleDetail extends BundleSummary {
98
98
  metadata: Record<string, unknown>;
99
99
  components: BundleComponentRow[];
100
100
  prices: BundleTargetPriceRow[];
101
+ /** Present only when the host selects effective price authority. */
102
+ resolvedTargetPrice?: BundleTargetPriceRow | null;
101
103
  /** The currency whose list resolved `referenceUnitPriceMinor`; `null` if none did. */
102
104
  resolvedCurrency: string | null;
103
105
  /**
@@ -116,8 +118,10 @@ export interface GetBundleInput {
116
118
  * only currency an operator has actually chosen for it.
117
119
  */
118
120
  currency?: string;
119
- /** Narrow the target price whose list is selected; omitted means any mode. */
121
+ /** Narrow the target price whose list is selected; omitted means any mode (the opt-in effective store defaults to one_time). */
120
122
  mode?: string;
123
+ regionCode?: string;
124
+ atTime?: string;
121
125
  }
122
126
 
123
127
  /**
@@ -167,7 +171,7 @@ export interface BundleCatalogReadPort {
167
171
  * Every bundle a storefront may sell right now. Never returns a draft or an
168
172
  * archived bundle, and never a bundle without an active target price.
169
173
  */
170
- listActiveBundleCompositions(input?: { currency?: string }): Promise<ActiveBundleComposition[]>;
174
+ listActiveBundleCompositions(input?: { currency?: string; regionCode?: string; mode?: "one_time" | "subscription"; atTime?: string }): Promise<ActiveBundleComposition[]>;
171
175
  /**
172
176
  * The operator trail for one bundle. The two chains answer from different
173
177
  * ledgers — a managed audit table and the platform's neutral write-event
@@ -1,3 +1,4 @@
1
+ import { createEffectiveBundleCatalogStore } from "./effectiveBundleCatalogStore.js";
1
2
  import {
2
3
  BundleCatalogReadError,
3
4
  } from "./bundleCatalogReadPort.js";
@@ -101,7 +102,11 @@ interface WriteEventRow {
101
102
 
102
103
  export function createPostgresBundleCatalogStore(
103
104
  executor: PgQueryExecutor,
105
+ options?: { priceAuthority?: "effective" },
104
106
  ): BundleCatalogReadPort {
107
+ if (options?.priceAuthority === "effective") {
108
+ return createEffectiveBundleCatalogStore(executor, createPostgresBundleCatalogStore(executor));
109
+ }
105
110
  async function rows<T>(sql: string, values: unknown[]): Promise<T[]> {
106
111
  // A driver failure becomes the port's own read error, so a caller sees the
107
112
  // same distinction between "found nothing" and "could not ask" on both chains.
@@ -0,0 +1,25 @@
1
+ import { selectUniqueEffectivePriceList } from "../pricing/index.js";
2
+ import { BundleCatalogReadError, type BundleTargetPriceRow } from "./bundleCatalogReadPort.js";
3
+
4
+ /** Half-open windows share the price authority's effective-time rule. */
5
+ function bundlePriceEffective(row: { validFrom: string; validTo: string | null }, atTime: string): boolean {
6
+ try {
7
+ selectUniqueEffectivePriceList([{ value: "row", ...row }], atTime);
8
+ return true;
9
+ } catch { return false; }
10
+ }
11
+ export function uniqueBundlePrice<T extends { mode: string }>(rows: readonly T[], mode: string): T | null {
12
+ const exact = rows.filter(row => row.mode === mode);
13
+ const candidates = exact.length ? exact : rows.filter(row => row.mode === "any");
14
+ if (candidates.length > 1) throw new BundleCatalogReadError("bundle_price_ambiguous");
15
+ return candidates[0] ?? null;
16
+ }
17
+ export function selectEffectiveBundleTargetPrice(
18
+ rows: readonly { price: BundleTargetPriceRow; priceListId: string }[],
19
+ context: { priceListId: string | null; currency: string | null; mode: string; atTime: string },
20
+ ): BundleTargetPriceRow | null {
21
+ if (context.priceListId === null || context.currency === null) return null;
22
+ return uniqueBundlePrice(rows.filter(row => row.priceListId === context.priceListId
23
+ && row.price.currency === context.currency && row.price.active && row.price.amountKind === "gross"
24
+ && row.price.targetPriceMinor > 0 && bundlePriceEffective(row.price, context.atTime)).map(row => row.price), context.mode);
25
+ }
@@ -3,8 +3,8 @@ export type ListBundlesRequest = { limit: number; offset: number; status: "draft
3
3
  export type ListBundlesResponse = { bundles: { code: string; title: string; status: string; fulfillmentMode: string; componentCount: number; hasActiveTargetPrice: boolean; updatedAt: string; }[]; total: number; contractVersion: "commerce.v0"; };
4
4
  export type BundleSummaryView = { code: string; title: string; status: string; fulfillmentMode: string; componentCount: number; hasActiveTargetPrice: boolean; updatedAt: string; };
5
5
  export type GetBundleRequest = { code: string; currency?: string | undefined; mode?: "any" | "one_time" | "subscription" | undefined; };
6
- export type GetBundleResponse = { bundle: { code: string; title: string; status: string; fulfillmentMode: string; componentCount: number; hasActiveTargetPrice: boolean; updatedAt: string; compositionConstraint: { kind: string; version: number; data: Record<string, unknown>; } | null; metadata: Record<string, unknown>; components: { sku: string; title: string | null; productSlug: string; quantity: number; isAddon: boolean; sortOrder: number; referenceUnitPriceMinor: number | null; }[]; prices: { mode: string; targetPriceMinor: number; currency: string; amountKind: string; active: boolean; validFrom: string; validTo: string | null; }[]; resolvedCurrency: string | null; availability: { sellableNow: number | null; status: string; reasonCode: string; limitingSku: string | null; components: { sku: string; quantity: number; isAddon: boolean; sellableNow: number | null; }[]; } | null; }; contractVersion: "commerce.v0"; };
7
- export type BundleDetailView = { code: string; title: string; status: string; fulfillmentMode: string; componentCount: number; hasActiveTargetPrice: boolean; updatedAt: string; compositionConstraint: { kind: string; version: number; data: Record<string, unknown>; } | null; metadata: Record<string, unknown>; components: { sku: string; title: string | null; productSlug: string; quantity: number; isAddon: boolean; sortOrder: number; referenceUnitPriceMinor: number | null; }[]; prices: { mode: string; targetPriceMinor: number; currency: string; amountKind: string; active: boolean; validFrom: string; validTo: string | null; }[]; resolvedCurrency: string | null; availability: { sellableNow: number | null; status: string; reasonCode: string; limitingSku: string | null; components: { sku: string; quantity: number; isAddon: boolean; sellableNow: number | null; }[]; } | null; };
6
+ export type GetBundleResponse = { bundle: { code: string; title: string; status: string; fulfillmentMode: string; componentCount: number; hasActiveTargetPrice: boolean; updatedAt: string; compositionConstraint: { kind: string; version: number; data: Record<string, unknown>; } | null; metadata: Record<string, unknown>; components: { sku: string; title: string | null; productSlug: string; quantity: number; isAddon: boolean; sortOrder: number; referenceUnitPriceMinor: number | null; }[]; prices: { mode: string; targetPriceMinor: number; currency: string; amountKind: string; active: boolean; validFrom: string; validTo: string | null; }[]; resolvedTargetPrice?: { mode: string; targetPriceMinor: number; currency: string; amountKind: string; active: boolean; validFrom: string; validTo: string | null; } | null; resolvedCurrency: string | null; availability: { sellableNow: number | null; status: string; reasonCode: string; limitingSku: string | null; components: { sku: string; quantity: number; isAddon: boolean; sellableNow: number | null; }[]; } | null; }; contractVersion: "commerce.v0"; };
7
+ export type BundleDetailView = { code: string; title: string; status: string; fulfillmentMode: string; componentCount: number; hasActiveTargetPrice: boolean; updatedAt: string; compositionConstraint: { kind: string; version: number; data: Record<string, unknown>; } | null; metadata: Record<string, unknown>; components: { sku: string; title: string | null; productSlug: string; quantity: number; isAddon: boolean; sortOrder: number; referenceUnitPriceMinor: number | null; }[]; prices: { mode: string; targetPriceMinor: number; currency: string; amountKind: string; active: boolean; validFrom: string; validTo: string | null; }[]; resolvedTargetPrice?: { mode: string; targetPriceMinor: number; currency: string; amountKind: string; active: boolean; validFrom: string; validTo: string | null; } | null; resolvedCurrency: string | null; availability: { sellableNow: number | null; status: string; reasonCode: string; limitingSku: string | null; components: { sku: string; quantity: number; isAddon: boolean; sellableNow: number | null; }[]; } | null; };
8
8
  export type BundleAvailabilityView = { sellableNow: number | null; status: string; reasonCode: string; limitingSku: string | null; components: { sku: string; quantity: number; isAddon: boolean; sellableNow: number | null; }[]; };
9
9
  export type BundleHistoryRequest = { limit: number; offset: number; code: string; };
10
10
  export type BundleHistoryResponse = { events: { id: string; action: string; actorKind: string | null; actorEmail: string | null; entityId: string | null; oldValue: unknown; newValue: unknown; occurredAt: string; }[]; total: number; contractVersion: "commerce.v0"; };
@@ -39,7 +39,7 @@ export function canonicalCatalogDraftJson(input: unknown): string {
39
39
  function visit(value: unknown, depth: number): unknown {
40
40
  if (depth > 48) throw new Error("draft_json_depth");
41
41
  if (typeof value === "string") {
42
- if (value.includes("\u0000") || /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value)) throw new Error("draft_json_invalid");
42
+ if (value.includes("\u0000") || /[\uD800-\uDFFF]/u.test(value)) throw new Error("draft_json_invalid");
43
43
  return value;
44
44
  }
45
45
  if (value === null || typeof value === "boolean") return value;
@@ -0,0 +1,80 @@
1
+ /** Effective-authority SQL is isolated from the incumbent exported statements. */
2
+ const ANCHORS = `SELECT pe.unit_price_minor FROM public.price_entries pe
3
+ WHERE pe.variant_id = s.id AND pe.price_list_id = %LIST% AND pe.active
4
+ AND pe.min_qty = 1 AND pe.amount_kind = 'gross'
5
+ AND pe.valid_from <= $3::timestamptz AND (pe.valid_to IS NULL OR pe.valid_to > $3::timestamptz)
6
+ AND (pe.mode = $4::text OR (pe.mode = 'any' AND NOT EXISTS (
7
+ SELECT 1 FROM public.price_entries exact WHERE exact.variant_id = s.id
8
+ AND exact.price_list_id = pe.price_list_id AND exact.active AND exact.min_qty = 1
9
+ AND exact.amount_kind = 'gross' AND exact.mode = $4::text
10
+ AND exact.valid_from <= $3::timestamptz AND (exact.valid_to IS NULL OR exact.valid_to > $3::timestamptz))))`;
11
+ const FACTS = `s.status AS sku_status, s.sellable_standalone, s.sellable_in_subscription,
12
+ s.net_weight_g, pr.status AS product_status,
13
+ (pr.primary_sku_id IS NOT NULL AND revision.id IS NOT NULL) AS document_ready,
14
+ COALESCE((SELECT jsonb_agg(jsonb_build_object('issuer', identifier.issuer, 'normalizedValue', identifier.normalized_value,
15
+ 'kind', identifier.identifier_kind, 'packKind', identifier.pack_kind, 'packQuantity', identifier.quantity, 'isPrimary', identifier.is_primary))
16
+ FROM public.catalog_sku_identifiers identifier WHERE identifier.catalog_sku_id = s.id), '[]'::jsonb) AS identifiers`;
17
+ const REFERENCE = `(SELECT (array_agg(candidate.unit_price_minor))[1] FROM (${ANCHORS}) candidate)`;
18
+ const COUNT = `(SELECT count(*) FROM (${ANCHORS}) candidate)`;
19
+ export const COMPONENT_SQL = `
20
+ SELECT c.bundle_id, s.sku, s.title, pr.slug AS product_slug, s.id AS variant_id,
21
+ c.quantity, c.is_addon, c.sort_order, ${FACTS},
22
+ ${REFERENCE.replace("%LIST%", "$2::uuid")} AS reference_unit_price_minor,
23
+ ${COUNT.replace("%LIST%", "$2::uuid")} AS reference_price_count
24
+ FROM public.catalog_bundle_components c
25
+ JOIN public.catalog_skus s ON s.id = c.catalog_sku_id
26
+ JOIN public.catalog_products pr ON pr.id = s.product_id
27
+ LEFT JOIN public.catalog_product_document_revisions revision ON revision.id = pr.current_document_revision_id AND revision.product_id = pr.id
28
+ WHERE c.bundle_id = $1::uuid ORDER BY c.sort_order, s.sku`;
29
+ export const FEED_COMPONENT_SQL = `
30
+ SELECT c.bundle_id, s.sku, s.title, pr.slug AS product_slug, s.id AS variant_id,
31
+ c.quantity, c.is_addon, c.sort_order, ${FACTS},
32
+ ${REFERENCE.replace("%LIST%", "selected.price_list_id")} AS reference_unit_price_minor,
33
+ ${COUNT.replace("%LIST%", "selected.price_list_id")} AS reference_price_count
34
+ FROM unnest($1::uuid[], $2::uuid[]) AS selected(bundle_id, price_list_id)
35
+ JOIN public.catalog_bundle_components c ON c.bundle_id = selected.bundle_id
36
+ JOIN public.catalog_skus s ON s.id = c.catalog_sku_id
37
+ JOIN public.catalog_products pr ON pr.id = s.product_id
38
+ LEFT JOIN public.catalog_product_document_revisions revision ON revision.id = pr.current_document_revision_id AND revision.product_id = pr.id
39
+ ORDER BY c.bundle_id, c.sort_order, s.sku`;
40
+ export const PRICE_SQL = `
41
+ SELECT p.mode, p.price_list_id, p.target_price_minor, list.currency, p.amount_kind, p.active, p.valid_from, p.valid_to
42
+ FROM public.catalog_bundle_prices p JOIN public.price_lists list ON list.id = p.price_list_id
43
+ WHERE p.bundle_id = $1::uuid ORDER BY p.valid_from DESC`;
44
+ export const OWN_LIST_SQL = `
45
+ SELECT list.id, list.currency, list.region_code, p.mode
46
+ FROM public.catalog_bundle_prices p JOIN public.price_lists list ON list.id = p.price_list_id
47
+ WHERE p.bundle_id = $1::uuid AND p.active AND p.amount_kind = 'gross'
48
+ AND (p.mode = $2::text OR p.mode = 'any') AND list.status = 'active'
49
+ AND p.valid_from <= $3::timestamptz AND (p.valid_to IS NULL OR p.valid_to > $3::timestamptz)
50
+ AND list.valid_from <= $3::timestamptz AND (list.valid_to IS NULL OR list.valid_to > $3::timestamptz)
51
+ ORDER BY p.valid_from DESC`;
52
+ export const CURRENCY_LIST_SQL = `
53
+ SELECT id, currency, region_code FROM public.price_lists
54
+ WHERE currency = $1::text AND status = 'active' AND ($2::text IS NULL OR region_code = $2::text)
55
+ AND valid_from <= $3::timestamptz AND (valid_to IS NULL OR valid_to > $3::timestamptz)`;
56
+ export const FEED_SQL = `
57
+ SELECT b.id, b.code, b.title, b.fulfillment_mode, p.mode, p.target_price_minor, p.price_list_id, list.currency, list.region_code
58
+ FROM public.catalog_bundles b
59
+ JOIN public.catalog_bundle_prices p ON p.bundle_id = b.id AND p.active
60
+ JOIN public.price_lists list ON list.id = p.price_list_id
61
+ WHERE b.status = 'active' AND p.amount_kind = 'gross' AND p.target_price_minor > 0
62
+ AND list.status = 'active' AND ($1::text IS NULL OR list.currency = $1::text)
63
+ AND ($2::text IS NULL OR list.region_code = $2::text) AND (p.mode = $4::text OR p.mode = 'any')
64
+ AND p.valid_from <= $3::timestamptz AND (p.valid_to IS NULL OR p.valid_to > $3::timestamptz)
65
+ AND list.valid_from <= $3::timestamptz AND (list.valid_to IS NULL OR list.valid_to > $3::timestamptz)
66
+ ORDER BY b.code, p.valid_from DESC`;
67
+
68
+ const SUMMARY_COLUMNS = `b.id, b.code, b.title, b.status, b.fulfillment_mode, b.updated_at,
69
+ b.composition_constraint, b.metadata,
70
+ (SELECT count(*) FROM public.catalog_bundle_components c WHERE c.bundle_id = b.id) AS component_count,
71
+ EXISTS (SELECT 1 FROM public.catalog_bundle_prices p JOIN public.price_lists list ON list.id = p.price_list_id
72
+ WHERE p.bundle_id = b.id AND p.active AND p.amount_kind = 'gross' AND p.target_price_minor > 0
73
+ AND p.mode IN ('one_time', 'any') AND list.status = 'active'
74
+ AND p.valid_from <= CURRENT_TIMESTAMP AND (p.valid_to IS NULL OR p.valid_to > CURRENT_TIMESTAMP)
75
+ AND list.valid_from <= CURRENT_TIMESTAMP AND (list.valid_to IS NULL OR list.valid_to > CURRENT_TIMESTAMP)) AS has_active_target_price`;
76
+ export const DETAIL_SQL = `SELECT ${SUMMARY_COLUMNS} FROM public.catalog_bundles b WHERE b.code = $1`;
77
+ export const LIST_SQL = `SELECT ${SUMMARY_COLUMNS} FROM public.catalog_bundles b
78
+ WHERE ($1::text = 'all' OR b.status = $1::text)
79
+ AND ($2::text IS NULL OR b.code ILIKE '%' || $2 || '%' OR b.title ILIKE '%' || $2 || '%')
80
+ ORDER BY b.code LIMIT $3 OFFSET $4`;