@openlup/core 0.0.0 → 0.7.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 (192) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/CODE_OF_CONDUCT.md +39 -0
  3. package/CONTRIBUTING.md +34 -0
  4. package/LICENSE +204 -0
  5. package/MAINTAINERS.md +33 -0
  6. package/README.md +85 -3
  7. package/SECURITY.md +29 -0
  8. package/dist/bundle/contracts.d.ts +9 -0
  9. package/dist/bundle/contracts.js +1 -0
  10. package/dist/bundle/ports.d.ts +41 -0
  11. package/dist/bundle/ports.js +1 -0
  12. package/dist/catalog/identifiers.d.ts +31 -0
  13. package/dist/catalog/identifiers.js +46 -0
  14. package/dist/catalog/index.d.ts +2 -0
  15. package/dist/catalog/index.js +2 -0
  16. package/dist/checkout/idempotency.d.ts +29 -0
  17. package/dist/checkout/idempotency.js +37 -0
  18. package/dist/checkout/index.d.ts +2 -0
  19. package/dist/checkout/index.js +2 -0
  20. package/dist/company-identity/contracts.d.ts +220 -0
  21. package/dist/company-identity/contracts.js +167 -0
  22. package/dist/company-identity/index.d.ts +6 -0
  23. package/dist/company-identity/index.js +2 -0
  24. package/dist/company-identity/ports.d.ts +13 -0
  25. package/dist/company-identity/ports.js +1 -0
  26. package/dist/fulfillment/facts.d.ts +103 -0
  27. package/dist/fulfillment/facts.js +195 -0
  28. package/dist/fulfillment/index.d.ts +4 -0
  29. package/dist/fulfillment/index.js +2 -0
  30. package/dist/inventory/atp.d.ts +172 -0
  31. package/dist/inventory/atp.js +211 -0
  32. package/dist/inventory/index.d.ts +2 -0
  33. package/dist/inventory/index.js +2 -0
  34. package/dist/marketing/research/contracts.d.ts +75 -0
  35. package/dist/marketing/research/contracts.js +60 -0
  36. package/dist/marketing/research/index.d.ts +4 -0
  37. package/dist/marketing/research/index.js +4 -0
  38. package/dist/marketing/research/ports.d.ts +15 -0
  39. package/dist/marketing/research/ports.js +1 -0
  40. package/dist/partners/index.d.ts +4 -0
  41. package/dist/partners/index.js +4 -0
  42. package/dist/partners/ports.d.ts +12 -0
  43. package/dist/partners/ports.js +1 -0
  44. package/dist/partners/status.d.ts +48 -0
  45. package/dist/partners/status.js +35 -0
  46. package/dist/payment/executionBaseContracts.d.ts +81 -0
  47. package/dist/payment/executionBaseContracts.js +8 -0
  48. package/dist/payment/index.d.ts +36 -0
  49. package/dist/payment/index.js +18 -0
  50. package/dist/payment/methodLifecycle.d.ts +91 -0
  51. package/dist/payment/methodLifecycle.js +76 -0
  52. package/dist/payment/paymentControlTypes.d.ts +135 -0
  53. package/dist/payment/paymentControlTypes.js +110 -0
  54. package/dist/payment/paymentFailureTaxonomy.d.ts +80 -0
  55. package/dist/payment/paymentFailureTaxonomy.js +155 -0
  56. package/dist/payment/paymentFailureTaxonomyContracts.d.ts +106 -0
  57. package/dist/payment/paymentFailureTaxonomyContracts.js +177 -0
  58. package/dist/payment/paymentRecovery.d.ts +26 -0
  59. package/dist/payment/paymentRecovery.js +73 -0
  60. package/dist/payment/paymentRecoveryContracts.d.ts +53 -0
  61. package/dist/payment/paymentRecoveryContracts.js +10 -0
  62. package/dist/payment/paymentStateMachine.d.ts +28 -0
  63. package/dist/payment/paymentStateMachine.js +184 -0
  64. package/dist/payment/providerAttemptIdempotency.d.ts +23 -0
  65. package/dist/payment/providerAttemptIdempotency.js +31 -0
  66. package/dist/payment/providerCapability.d.ts +148 -0
  67. package/dist/payment/providerCapability.js +13 -0
  68. package/dist/platform-runtime/contracts.d.ts +9 -0
  69. package/dist/platform-runtime/contracts.js +13 -0
  70. package/dist/platform-runtime/index.d.ts +10 -0
  71. package/dist/platform-runtime/index.js +4 -0
  72. package/dist/platform-runtime/platformKernel.d.ts +44 -0
  73. package/dist/platform-runtime/platformKernel.js +39 -0
  74. package/dist/platform-runtime/ports.d.ts +100 -0
  75. package/dist/platform-runtime/ports.js +1 -0
  76. package/dist/pricing/index.d.ts +8 -0
  77. package/dist/pricing/index.js +8 -0
  78. package/dist/pricing/ports.d.ts +22 -0
  79. package/dist/pricing/ports.js +7 -0
  80. package/dist/pricing/pricingBreakdown.d.ts +86 -0
  81. package/dist/pricing/pricingBreakdown.js +155 -0
  82. package/dist/pricing/targetPriceAllocation.d.ts +97 -0
  83. package/dist/pricing/targetPriceAllocation.js +202 -0
  84. package/dist/pricing/types.d.ts +32 -0
  85. package/dist/pricing/types.js +4 -0
  86. package/dist/promo/adjustmentEngine.d.ts +4 -0
  87. package/dist/promo/adjustmentEngine.js +194 -0
  88. package/dist/promo/adjustmentTypes.d.ts +103 -0
  89. package/dist/promo/adjustmentTypes.js +13 -0
  90. package/dist/promo/index.d.ts +8 -0
  91. package/dist/promo/index.js +8 -0
  92. package/dist/promo/promoEvaluator.d.ts +25 -0
  93. package/dist/promo/promoEvaluator.js +135 -0
  94. package/dist/promo/types.d.ts +86 -0
  95. package/dist/promo/types.js +12 -0
  96. package/dist/risk/core/evaluator.d.ts +3 -0
  97. package/dist/risk/core/evaluator.js +32 -0
  98. package/dist/risk/core/rules.d.ts +3 -0
  99. package/dist/risk/core/rules.js +118 -0
  100. package/dist/risk/core/types.d.ts +65 -0
  101. package/dist/risk/core/types.js +1 -0
  102. package/dist/risk/index.d.ts +8 -0
  103. package/dist/risk/index.js +8 -0
  104. package/dist/risk/types.d.ts +32 -0
  105. package/dist/risk/types.js +43 -0
  106. package/dist/shipping/index.d.ts +4 -0
  107. package/dist/shipping/index.js +4 -0
  108. package/dist/shipping/shippingResolver.d.ts +26 -0
  109. package/dist/shipping/shippingResolver.js +88 -0
  110. package/dist/shipping/types.d.ts +45 -0
  111. package/dist/shipping/types.js +4 -0
  112. package/dist/subscription/cycleHardening.d.ts +95 -0
  113. package/dist/subscription/cycleHardening.js +118 -0
  114. package/dist/subscription/index.d.ts +14 -0
  115. package/dist/subscription/index.js +14 -0
  116. package/dist/subscription/subscriptionEngineCore.d.ts +13 -0
  117. package/dist/subscription/subscriptionEngineCore.js +145 -0
  118. package/dist/subscription/subscriptionEngineLifecycle.d.ts +150 -0
  119. package/dist/subscription/subscriptionEngineLifecycle.js +433 -0
  120. package/dist/subscription/subscriptionEnginePayment.d.ts +25 -0
  121. package/dist/subscription/subscriptionEnginePayment.js +127 -0
  122. package/dist/subscription/subscriptionEngineTypes.d.ts +94 -0
  123. package/dist/subscription/subscriptionEngineTypes.js +4 -0
  124. package/dist/subscription/subscriptionEngineUtils.d.ts +32 -0
  125. package/dist/subscription/subscriptionEngineUtils.js +74 -0
  126. package/dist/subscription/types.d.ts +58 -0
  127. package/dist/subscription/types.js +47 -0
  128. package/dist/testing.d.ts +48 -0
  129. package/dist/testing.js +105 -0
  130. package/package.json +215 -4
  131. package/release-gates.json +232 -0
  132. package/src/bundle/contracts.ts +15 -0
  133. package/src/bundle/ports.ts +32 -0
  134. package/src/catalog/identifiers.ts +50 -0
  135. package/src/catalog/index.ts +2 -0
  136. package/src/checkout/idempotency.ts +49 -0
  137. package/src/checkout/index.ts +2 -0
  138. package/src/company-identity/contracts.ts +208 -0
  139. package/src/company-identity/index.ts +28 -0
  140. package/src/company-identity/ports.ts +19 -0
  141. package/src/fulfillment/facts.ts +300 -0
  142. package/src/fulfillment/index.ts +34 -0
  143. package/src/inventory/atp.ts +249 -0
  144. package/src/inventory/index.ts +2 -0
  145. package/src/marketing/research/contracts.ts +91 -0
  146. package/src/marketing/research/index.ts +4 -0
  147. package/src/marketing/research/ports.ts +26 -0
  148. package/src/partners/index.ts +4 -0
  149. package/src/partners/ports.ts +18 -0
  150. package/src/partners/status.ts +49 -0
  151. package/src/payment/executionBaseContracts.ts +93 -0
  152. package/src/payment/index.ts +128 -0
  153. package/src/payment/methodLifecycle.ts +130 -0
  154. package/src/payment/paymentControlTypes.ts +197 -0
  155. package/src/payment/paymentFailureTaxonomy.ts +201 -0
  156. package/src/payment/paymentFailureTaxonomyContracts.ts +228 -0
  157. package/src/payment/paymentRecovery.ts +95 -0
  158. package/src/payment/paymentRecoveryContracts.ts +61 -0
  159. package/src/payment/paymentStateMachine.ts +263 -0
  160. package/src/payment/providerAttemptIdempotency.ts +57 -0
  161. package/src/payment/providerCapability.ts +172 -0
  162. package/src/platform-runtime/contracts.ts +33 -0
  163. package/src/platform-runtime/index.ts +36 -0
  164. package/src/platform-runtime/platformKernel.ts +86 -0
  165. package/src/platform-runtime/ports.ts +88 -0
  166. package/src/pricing/index.ts +8 -0
  167. package/src/pricing/ports.ts +27 -0
  168. package/src/pricing/pricingBreakdown.ts +243 -0
  169. package/src/pricing/targetPriceAllocation.ts +293 -0
  170. package/src/pricing/types.ts +36 -0
  171. package/src/promo/adjustmentEngine.ts +253 -0
  172. package/src/promo/adjustmentTypes.ts +94 -0
  173. package/src/promo/index.ts +8 -0
  174. package/src/promo/promoEvaluator.ts +137 -0
  175. package/src/promo/types.ts +97 -0
  176. package/src/risk/core/evaluator.ts +41 -0
  177. package/src/risk/core/rules.ts +128 -0
  178. package/src/risk/core/types.ts +73 -0
  179. package/src/risk/index.ts +8 -0
  180. package/src/risk/types.ts +60 -0
  181. package/src/shipping/index.ts +4 -0
  182. package/src/shipping/shippingResolver.ts +101 -0
  183. package/src/shipping/types.ts +49 -0
  184. package/src/subscription/cycleHardening.ts +174 -0
  185. package/src/subscription/index.ts +14 -0
  186. package/src/subscription/subscriptionEngineCore.ts +181 -0
  187. package/src/subscription/subscriptionEngineLifecycle.ts +574 -0
  188. package/src/subscription/subscriptionEnginePayment.ts +179 -0
  189. package/src/subscription/subscriptionEngineTypes.ts +114 -0
  190. package/src/subscription/subscriptionEngineUtils.ts +110 -0
  191. package/src/subscription/types.ts +104 -0
  192. package/src/testing.ts +169 -0
@@ -0,0 +1,23 @@
1
+ /** @beta */
2
+ export interface ProviderAttemptIdentityInput {
3
+ namespace: string;
4
+ provider: string;
5
+ localExecutionIdempotencyKey: string;
6
+ paymentIntentId: string;
7
+ amountMinor: number;
8
+ currency: string;
9
+ mode: "one_time" | "subscription_cycle";
10
+ orderRef: string;
11
+ }
12
+ /** @beta */
13
+ export interface ProviderAttemptIdentity {
14
+ providerIdempotencyKey: string;
15
+ providerRequestFingerprint: string;
16
+ }
17
+ /** @beta */
18
+ export declare function buildProviderAttemptIdentity(input: ProviderAttemptIdentityInput): ProviderAttemptIdentity;
19
+ /** @beta */
20
+ export declare function isProviderAttemptReplaySafe(input: {
21
+ existingFingerprint: string;
22
+ nextFingerprint: string;
23
+ }): boolean;
@@ -0,0 +1,31 @@
1
+ /** @beta */
2
+ export function buildProviderAttemptIdentity(input) {
3
+ const normalizedNamespace = normalizeSegment(input.namespace);
4
+ const normalizedProvider = normalizeSegment(input.provider);
5
+ const normalizedLocalKey = normalizeSegment(input.localExecutionIdempotencyKey);
6
+ const normalizedCurrency = input.currency.trim().toUpperCase();
7
+ const providerIdempotencyKey = [
8
+ normalizedNamespace,
9
+ normalizedProvider,
10
+ input.paymentIntentId,
11
+ normalizedLocalKey,
12
+ ].join(":");
13
+ return {
14
+ providerIdempotencyKey,
15
+ providerRequestFingerprint: [
16
+ normalizedProvider,
17
+ input.paymentIntentId,
18
+ input.amountMinor.toString(),
19
+ normalizedCurrency,
20
+ input.mode,
21
+ input.orderRef,
22
+ ].join("|"),
23
+ };
24
+ }
25
+ /** @beta */
26
+ export function isProviderAttemptReplaySafe(input) {
27
+ return input.existingFingerprint === input.nextFingerprint;
28
+ }
29
+ function normalizeSegment(value) {
30
+ return value.trim().toLowerCase().replace(/[^a-z0-9_.:-]+/g, "_");
31
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Payment provider capability contracts.
3
+ *
4
+ * Renewal and dunning orchestration must decide what it can do with a stored
5
+ * consent by reading CAPABILITIES, never by comparing a provider identity
6
+ * string. This module is the neutral vocabulary those decisions are expressed
7
+ * in; the values live in each adapter, so nothing here names a provider.
8
+ *
9
+ * Every field exists because a live branch consumes it. Capabilities without a
10
+ * consumer (capture flows, status reads, limits) are deliberately absent and
11
+ * arrive with the code that needs them.
12
+ */
13
+ /**
14
+ * Why a stored consent cannot back an unattended (off-session) charge.
15
+ *
16
+ * Neutral by construction: a provider-native refusal code never reaches a
17
+ * caller through this contract, so the orchestrator can route a blocked
18
+ * renewal into customer repair without learning provider vocabulary.
19
+ */
20
+ export type UnattendedChargeBlockReason = "mandate_model_unsupported_for_unattended_charge";
21
+ /**
22
+ * The stored consent evidence a capability judges, as read from local records
23
+ * rather than from a provider call. Optional everywhere: a provider whose
24
+ * consent carries no model simply ignores the field.
25
+ */
26
+ export interface StoredMandateSnapshot {
27
+ /** Recurring/mandate model recorded when the payer gave consent. */
28
+ recurringModel?: string | null;
29
+ }
30
+ /** The verdict on whether a stored consent may be charged unattended. */
31
+ export interface UnattendedMandateAssessment {
32
+ chargeable: boolean;
33
+ blockReason?: UnattendedChargeBlockReason;
34
+ }
35
+ /**
36
+ * What an unattended charge request must carry about the payer.
37
+ *
38
+ * Models the two payer-shaped branches on the renewal path: whether the
39
+ * request needs a payer block at all, and whether the customer reference may
40
+ * fall back to the stored contact email when no provider customer exists.
41
+ */
42
+ export interface PayerContextRequirements {
43
+ requiresPayerBlock: boolean;
44
+ customerRefFallsBackToContactEmail: boolean;
45
+ }
46
+ /**
47
+ * What the stored-method health check must see before a charge is attempted.
48
+ *
49
+ * Models the per-provider preconditions the payment-method lifecycle applies
50
+ * to a stored method; the lifecycle keeps owning the reason vocabulary it
51
+ * reports, this contract owns only the expectations.
52
+ */
53
+ export interface PaymentMethodHealthExpectations {
54
+ /** A provider-side customer reference is mandatory for a charge. */
55
+ requiresCustomerRef: boolean;
56
+ /** The stored method must be of exactly this kind, or null when any kind charges. */
57
+ requiredMethodKind: string | null;
58
+ /** A stored payer contact (email) is mandatory for a charge. */
59
+ requiresPayerContact: boolean;
60
+ }
61
+ /**
62
+ * How a customer-present client must drive one method-capture flow.
63
+ *
64
+ * Closed on purpose: a client can only offer a flow it knows how to drive, so a
65
+ * provider that invents a handoff no client implements must widen this union
66
+ * together with the surface that renders it, rather than have a repair page
67
+ * present a control that leads nowhere.
68
+ */
69
+ export type PaymentMethodCaptureHandoff = "embedded_client_secret" | "payer_supplied_code";
70
+ /**
71
+ * One way a payer can hand this provider a reusable payment consent while
72
+ * present at the keyboard — the repair path out of a failed unattended charge.
73
+ *
74
+ * `kind` is neutral vocabulary the renderer may label and key on; it is never a
75
+ * provider identity. A declared flow is an offer of capability, not a promise
76
+ * that any given client implements it: the client filters by `handoff`.
77
+ */
78
+ export interface PaymentMethodCaptureFlow {
79
+ kind: string;
80
+ handoff: PaymentMethodCaptureHandoff;
81
+ }
82
+ /**
83
+ * Where a terminal outcome becomes visible on a rail, and how long the absence
84
+ * of one is still normal.
85
+ *
86
+ * Reconciliation cannot tell "still running" from "stuck" by polling alone: a
87
+ * rail that answers non-terminally forever looks identical to a healthy slow
88
+ * one. Declaring the shape makes silence measurable per rail instead of
89
+ * guessed at globally, and makes the question something a new adapter must
90
+ * answer up front rather than discover through an incident.
91
+ */
92
+ export interface TerminalOutcomeReporting {
93
+ /**
94
+ * Where the outcome lands. Line comments on purpose: a member-level doc
95
+ * comment makes the extractor emit a trailing-whitespace alias line the diff
96
+ * guard refuses.
97
+ */
98
+ kind: "status_field" | "attempt_records" | "inbound_event";
99
+ /**
100
+ * Minutes of non-terminal answers after which this rail is behaving
101
+ * abnormally and the attempt deserves a human, not another poll. This is a
102
+ * suspicion threshold, never a verdict: elapsed time is not evidence about
103
+ * money, so nothing may terminalize a charge from this value alone.
104
+ */
105
+ silenceBecomesSuspectAfterMinutes: number;
106
+ }
107
+ /**
108
+ * One provider's unattended-charge capabilities, published by its adapter.
109
+ *
110
+ * `providerKind` is the registry key only. Consumers must read the capability
111
+ * fields; branching on this value re-creates the coupling the contract exists
112
+ * to remove.
113
+ */
114
+ export interface PaymentProviderCapabilityDescriptor {
115
+ providerKind: string;
116
+ /**
117
+ * Every customer-present capture flow this provider can complete. Empty means
118
+ * a payer cannot repair this rail's consent without leaving the product.
119
+ */
120
+ captureFlows: readonly PaymentMethodCaptureFlow[];
121
+ /**
122
+ * The stored consent evidence must be loaded from local records before a
123
+ * charge is prepared. False means `assessMandate` decides without it, and the
124
+ * caller must not pay for the read.
125
+ */
126
+ requiresStoredMandateEvidence: boolean;
127
+ /** Judges stored consent evidence before any durable provider attempt is created. */
128
+ assessMandate(snapshot: StoredMandateSnapshot): UnattendedMandateAssessment;
129
+ /** The provider flow an unattended renewal charge must be dispatched under. */
130
+ unattendedChargeFlow: string;
131
+ payerContext: PayerContextRequirements;
132
+ methodHealth: PaymentMethodHealthExpectations;
133
+ /** How this rail reports a terminal outcome, and when its silence stops being normal. */
134
+ terminalOutcomeReporting: TerminalOutcomeReporting;
135
+ /**
136
+ * The stored consent belongs to one subscription rather than to the payer
137
+ * account, so persisting it from a webhook must use the subscription-scoped
138
+ * guarded write.
139
+ */
140
+ mandateUpsertIsSubscriptionScoped: boolean;
141
+ }
142
+ /** Lookup of published capabilities, keyed by provider kind. */
143
+ export interface PaymentProviderCapabilityRegistry {
144
+ /** The descriptor published for this kind, or null when the kind is unknown. */
145
+ get(providerKind: string): PaymentProviderCapabilityDescriptor | null;
146
+ /** Every published kind, for inventory and conformance proofs. */
147
+ kinds(): readonly string[];
148
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Payment provider capability contracts.
3
+ *
4
+ * Renewal and dunning orchestration must decide what it can do with a stored
5
+ * consent by reading CAPABILITIES, never by comparing a provider identity
6
+ * string. This module is the neutral vocabulary those decisions are expressed
7
+ * in; the values live in each adapter, so nothing here names a provider.
8
+ *
9
+ * Every field exists because a live branch consumes it. Capabilities without a
10
+ * consumer (capture flows, status reads, limits) are deliberately absent and
11
+ * arrive with the code that needs them.
12
+ */
13
+ export {};
@@ -0,0 +1,9 @@
1
+ /** @beta */
2
+ export type PlatformBundleId = string;
3
+ /** Capability surfaces a runtime bundle can bind. */
4
+ /** @beta */
5
+ export type PlatformCapability = "http" | "scheduler" | "blob" | "data" | "migrations" | "analytics" | "transactional";
6
+ /** @beta */
7
+ export type PlatformBundleIdGuard<TId extends string = PlatformBundleId> = (value: unknown) => value is TId;
8
+ /** @beta */
9
+ export declare function createPlatformBundleIdGuard<const TIds extends readonly string[]>(ids: TIds): PlatformBundleIdGuard<TIds[number]>;
@@ -0,0 +1,13 @@
1
+ // Platform-runtime shared contracts.
2
+ // Provider-neutral types only: no I/O, no concrete host/provider presets. Host
3
+ // composition and bundle catalogs stay downstream.
4
+ /** @beta */
5
+ export function createPlatformBundleIdGuard(ids) {
6
+ for (const id of ids) {
7
+ if (!id || id.trim() !== id) {
8
+ throw new Error("Platform bundle ids must be non-empty and trimmed");
9
+ }
10
+ }
11
+ const allowed = new Set(ids);
12
+ return (value) => typeof value === "string" && allowed.has(value);
13
+ }
@@ -0,0 +1,10 @@
1
+ /** @beta */
2
+ export { createPlatformBundleIdGuard, } from "./contracts.js";
3
+ /** @beta */
4
+ export type { PlatformBundleId, PlatformBundleIdGuard, PlatformCapability, } from "./contracts.js";
5
+ /** @beta */
6
+ export { createPlatformBundleRegistry, platformEnvSchema, } from "./platformKernel.js";
7
+ /** @beta */
8
+ export type { CreatePlatformBundleRegistryInput, PlatformBundleDescriptor, PlatformBundleRegistry, PlatformBundleReadiness, PlatformEnv, PlatformEnvInput, } from "./platformKernel.js";
9
+ /** @beta */
10
+ export type { AnalyticsPort, BlobStoragePort, DataGatewayPort, HttpRuntimePort, MigrationRunnerPort, PlatformHttpRequest, PlatformHttpResponse, SchedulerPort, TransactionalRuntimePort, } from "./ports.js";
@@ -0,0 +1,4 @@
1
+ /** @beta */
2
+ export { createPlatformBundleIdGuard, } from "./contracts.js";
3
+ /** @beta */
4
+ export { createPlatformBundleRegistry, platformEnvSchema, } from "./platformKernel.js";
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ import { type PlatformBundleId, type PlatformCapability } from "./contracts.js";
3
+ /** @beta */
4
+ export interface PlatformBundleReadiness {
5
+ ok: boolean;
6
+ error?: string;
7
+ }
8
+ /** @beta */
9
+ export interface PlatformBundleDescriptor {
10
+ id: PlatformBundleId;
11
+ /** Declarative capability provider kind, bound by downstream composition. */
12
+ capabilities: Record<PlatformCapability, string>;
13
+ readReadiness(env: PlatformEnvInput): PlatformBundleReadiness;
14
+ }
15
+ /** @beta */
16
+ export type PlatformEnvInput = Record<string, string | undefined>;
17
+ /** @beta */
18
+ export declare const platformEnvSchema: z.ZodObject<{
19
+ PLATFORM_BUNDLE: z.ZodOptional<z.ZodString>;
20
+ APP_BASE_URL: z.ZodOptional<z.ZodString>;
21
+ }, z.core.$strip>;
22
+ /** @beta */
23
+ export type PlatformEnv = z.infer<typeof platformEnvSchema>;
24
+ /** @beta */
25
+ export interface PlatformBundleRegistry<TId extends string = PlatformBundleId> {
26
+ readonly defaultBundleId: TId;
27
+ readonly bundleIds: readonly TId[];
28
+ readonly descriptors: readonly PlatformBundleDescriptor[];
29
+ isBundleId(value: unknown): value is TId;
30
+ resolveBundleId(env: PlatformEnvInput): TId;
31
+ getBundleDescriptor(id: TId): PlatformBundleDescriptor;
32
+ }
33
+ /** @beta */
34
+ export interface CreatePlatformBundleRegistryInput<TId extends string = PlatformBundleId> {
35
+ descriptors: readonly PlatformBundleDescriptor[];
36
+ defaultBundleId: TId;
37
+ }
38
+ /** @beta */
39
+ export declare function createPlatformBundleRegistry<const TIds extends readonly string[]>(input: {
40
+ descriptors: readonly (PlatformBundleDescriptor & {
41
+ id: TIds[number];
42
+ })[];
43
+ defaultBundleId: TIds[number];
44
+ }): PlatformBundleRegistry<TIds[number]>;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { createPlatformBundleIdGuard } from "./contracts.js";
3
+ /** @beta */
4
+ export const platformEnvSchema = z.object({
5
+ PLATFORM_BUNDLE: z.string().trim().min(1).optional(),
6
+ APP_BASE_URL: z.string().url().optional(),
7
+ });
8
+ /** @beta */
9
+ export function createPlatformBundleRegistry(input) {
10
+ const bundleIds = input.descriptors.map((descriptor) => descriptor.id);
11
+ const isBundleId = createPlatformBundleIdGuard(bundleIds);
12
+ const descriptorsById = new Map();
13
+ for (const descriptor of input.descriptors) {
14
+ if (descriptorsById.has(descriptor.id)) {
15
+ throw new Error(`Duplicate platform bundle descriptor: ${descriptor.id}`);
16
+ }
17
+ descriptorsById.set(descriptor.id, descriptor);
18
+ }
19
+ if (!descriptorsById.has(input.defaultBundleId)) {
20
+ throw new Error(`Default platform bundle is not declared: ${input.defaultBundleId}`);
21
+ }
22
+ return {
23
+ defaultBundleId: input.defaultBundleId,
24
+ bundleIds,
25
+ descriptors: input.descriptors,
26
+ isBundleId,
27
+ resolveBundleId(env) {
28
+ const raw = env.PLATFORM_BUNDLE;
29
+ return isBundleId(raw) ? raw : input.defaultBundleId;
30
+ },
31
+ getBundleDescriptor(id) {
32
+ const descriptor = descriptorsById.get(id);
33
+ if (!descriptor) {
34
+ throw new Error(`Unknown platform bundle descriptor: ${id}`);
35
+ }
36
+ return descriptor;
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,100 @@
1
+ /** Structural request shape for host HTTP adapters. */
2
+ /** @beta */
3
+ export interface PlatformHttpRequest {
4
+ url?: string;
5
+ method?: string;
6
+ headers: Record<string, string | string[] | undefined>;
7
+ query: Record<string, string | string[] | undefined>;
8
+ cookies?: Record<string, string>;
9
+ body?: unknown;
10
+ }
11
+ /** Structural response shape for host HTTP adapters. */
12
+ /** @beta */
13
+ export interface PlatformHttpResponse {
14
+ statusCode?: number;
15
+ headersSent?: boolean;
16
+ writableEnded?: boolean;
17
+ status(code: number): this;
18
+ setHeader?(name: string, value: string | number | readonly string[]): this;
19
+ json(body: unknown): this;
20
+ send(body: unknown): this;
21
+ end?(body?: unknown): this;
22
+ redirect(url: string): this;
23
+ redirect(status: number, url: string): this;
24
+ }
25
+ /** Host runtime: turn an incoming request into a handled response + mount a server. */
26
+ /** @beta */
27
+ export interface HttpRuntimePort {
28
+ /** Dispatch a single request through downstream routes. */
29
+ handle(req: PlatformHttpRequest, res: PlatformHttpResponse): Promise<unknown> | unknown;
30
+ /** Start listening. Serverless adapters may no-op. */
31
+ listen?(options: {
32
+ port: number;
33
+ host?: string;
34
+ }): Promise<void> | void;
35
+ }
36
+ /** Scheduled jobs: register cron-like work. */
37
+ /** @beta */
38
+ export interface SchedulerPort {
39
+ register(input: {
40
+ jobId: string;
41
+ schedule: string;
42
+ handler: () => Promise<void> | void;
43
+ }): void;
44
+ /** Verify a scheduler invocation is authentic. */
45
+ verifyInvocation(req: PlatformHttpRequest): boolean;
46
+ }
47
+ /** Object storage. */
48
+ /** @beta */
49
+ export interface BlobStoragePort {
50
+ upload(key: string, data: Uint8Array, options?: {
51
+ contentType?: string;
52
+ }): Promise<{
53
+ url: string;
54
+ pathname: string;
55
+ }>;
56
+ exists(key: string): Promise<boolean>;
57
+ delete(keyOrUrl: string): Promise<void>;
58
+ list(prefix?: string): Promise<{
59
+ keys: string[];
60
+ }>;
61
+ urlFor(key: string): string;
62
+ }
63
+ /** Persistence gateway: run reads/writes as a given actor. */
64
+ /** @beta */
65
+ export interface DataGatewayPort {
66
+ /** Execute a unit of work bound to an actor's claims. */
67
+ asActor<T>(claims: {
68
+ sub?: string;
69
+ role?: string;
70
+ } | null, work: (gateway: unknown) => Promise<T>): Promise<T>;
71
+ /** Service-role/elevated access. */
72
+ asService<T>(work: (gateway: unknown) => Promise<T>): Promise<T>;
73
+ }
74
+ /** Schema apply. */
75
+ /** @beta */
76
+ export interface MigrationRunnerPort {
77
+ status(): Promise<{
78
+ pending: string[];
79
+ applied: string[];
80
+ }>;
81
+ apply(options?: {
82
+ dryRun?: boolean;
83
+ }): Promise<{
84
+ applied: string[];
85
+ }>;
86
+ }
87
+ /** Frontend analytics. */
88
+ /** @beta */
89
+ export interface AnalyticsPort {
90
+ trackPageView(pathname: string): void;
91
+ trackEvent(name: string, properties?: Record<string, unknown>): void;
92
+ }
93
+ /** Transactional runtime for queue/function dispatch. */
94
+ /** @beta */
95
+ export interface TransactionalRuntimePort {
96
+ invoke(functionName: string, payload: unknown): Promise<{
97
+ ok: boolean;
98
+ status: number;
99
+ }>;
100
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ /** @beta */
2
+ export * from "./types.js";
3
+ /** @beta */
4
+ export * from "./ports.js";
5
+ /** @beta */
6
+ export * from "./pricingBreakdown.js";
7
+ /** @beta */
8
+ export * from "./targetPriceAllocation.js";
@@ -0,0 +1,8 @@
1
+ /** @beta */
2
+ export * from "./types.js";
3
+ /** @beta */
4
+ export * from "./ports.js";
5
+ /** @beta */
6
+ export * from "./pricingBreakdown.js";
7
+ /** @beta */
8
+ export * from "./targetPriceAllocation.js";
@@ -0,0 +1,22 @@
1
+ import type { ResolvedPrice, ResolvePriceQuery } from "./types.js";
2
+ /**
3
+ * Pricing resolver port - deterministic price resolution from a data-driven
4
+ * price-list / price-entry table.
5
+ *
6
+ * Caller computes eligible cart quantity for the line's pricing mode and
7
+ * passes it in alongside the line's own quantity. Implementations select the
8
+ * matching price entry for variant, mode, region, currency, and request time.
9
+ * Any fallback policy, including fallback to a generic mode, is owned by the
10
+ * adapter implementation; the port preserves the requested mode.
11
+ *
12
+ * Returns null when no entry matches; callers must surface a meaningful error
13
+ * rather than silently quote zero.
14
+ */
15
+ /** @beta */
16
+ export interface PricingResolverPort {
17
+ resolvePrice(query: ResolvePriceQuery): Promise<ResolvedPrice | null>;
18
+ }
19
+ /** @beta */
20
+ export declare class PricingResolverNotConfiguredError extends Error {
21
+ constructor(query: ResolvePriceQuery);
22
+ }
@@ -0,0 +1,7 @@
1
+ /** @beta */
2
+ export class PricingResolverNotConfiguredError extends Error {
3
+ constructor(query) {
4
+ super(`No price entry for ${query.variantId} (${query.mode}) in ${query.regionCode}/${query.currency}`);
5
+ this.name = "PricingResolverNotConfiguredError";
6
+ }
7
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Pricing breakdown builder — pure functions that emit signed pricing components for
3
+ * a given (cart, resolved unit price) tuple. Sums always reconcile to the order /
4
+ * order_line totals via two persistence-ready invariants:
5
+ *
6
+ * perLine: sum(line_components.amount) = order_line.line_total_minor
7
+ * perOrder: sum(all_components.amount) = order.total_minor (including shipping)
8
+ *
9
+ * Current components: base_unit (always), mode_discount (when subscription resolves
10
+ * below the base unit price), qty_tier (when a quantity tier above 1 matched),
11
+ * bundle (line-scoped and negative, carrying the share of a composed bundle's
12
+ * target-price discount that the allocator assigned to this line), shipping
13
+ * (order-scoped, when a rule or non-zero amount applies), and promo (one negative
14
+ * order-scoped component per applied discount). loyalty remains a reserved
15
+ * component type for downstream engines.
16
+ */
17
+ /** @beta */
18
+ export declare const PRICING_COMPONENT_TYPES: readonly ["base_unit", "mode_discount", "qty_tier", "bundle", "promo", "loyalty", "shipping"];
19
+ /** @beta */
20
+ export type PricingComponentType = (typeof PRICING_COMPONENT_TYPES)[number];
21
+ /** @beta */
22
+ export interface PricingComponent {
23
+ sequence: number;
24
+ component_type: PricingComponentType;
25
+ amount_minor: number;
26
+ reason_code: string;
27
+ reason_payload: Record<string, unknown>;
28
+ }
29
+ /** @beta */
30
+ export interface LinePricingInput {
31
+ variant_id: string;
32
+ line_qty: number;
33
+ base_unit_price_minor: number;
34
+ resolved_unit_price_minor: number;
35
+ matched_tier_min_qty: number;
36
+ mode_at_line: "one_time" | "subscription";
37
+ mode_resolved_via_fallback: boolean;
38
+ /**
39
+ * Positive amount-off this line carries because it is a component of a bundle sold
40
+ * at one target price — `discountAllocatedMinor` from the target-price allocator,
41
+ * already summed over the one or two lines a component may split into. Positive on
42
+ * the way in, exactly like an order discount; the emitted component carries it as a
43
+ * negative adjustment. Absent or zero emits nothing.
44
+ */
45
+ bundle_allocated_discount_minor?: number;
46
+ }
47
+ /** @beta */
48
+ export interface OrderShippingInput {
49
+ amount_minor: number;
50
+ applied_rule_id: string | null;
51
+ carrier_kind: string | null;
52
+ vat_rate_bps: number | null;
53
+ }
54
+ /** @beta */
55
+ export interface OrderDiscountInput {
56
+ promotionId: string;
57
+ amountOffMinor: number;
58
+ reasonCode: string;
59
+ }
60
+ /** @beta */
61
+ export interface BuildOrderBreakdownInput {
62
+ lines: ReadonlyArray<LinePricingInput>;
63
+ shipping: OrderShippingInput;
64
+ discounts?: ReadonlyArray<OrderDiscountInput>;
65
+ }
66
+ /** @beta */
67
+ export interface BuildOrderBreakdownResult {
68
+ lineBreakdowns: Array<{
69
+ variant_id: string;
70
+ line_total_minor: number;
71
+ components: PricingComponent[];
72
+ }>;
73
+ orderComponents: PricingComponent[];
74
+ orderTotalMinor: number;
75
+ }
76
+ /** @beta */
77
+ export declare function buildLineBreakdown(input: LinePricingInput): PricingComponent[];
78
+ /** @beta */
79
+ export declare function buildOrderBreakdown(input: BuildOrderBreakdownInput): BuildOrderBreakdownResult;
80
+ /**
81
+ * Cart-level invariant: every order's total_minor equals the sum of all components
82
+ * (line + order-level). Callers must assert this before persisting to commerce_orders
83
+ * to prevent a sale from going out with a hidden price-snapshot drift.
84
+ */
85
+ /** @beta */
86
+ export declare function assertBreakdownInvariant(result: BuildOrderBreakdownResult, orderTotalMinor: number): void;