@openlup/platform 0.24.0 → 0.26.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 +127 -432
- package/README.md +46 -11
- package/dist/communications/contracts/subscriptionPaymentExpired.d.ts +3 -2
- package/dist/communications/contracts/subscriptionPaymentExpired.js +1 -1
- package/dist/communications/contracts/subscriptionPaymentFailed.d.ts +4 -3
- package/dist/communications/contracts/subscriptionPaymentFailed.js +4 -5
- package/dist/composition/index.d.ts +1 -0
- package/dist/composition/index.js +1 -0
- package/dist/composition/subscriptionContribution.d.ts +18 -0
- package/dist/composition/subscriptionContribution.js +19 -0
- package/dist/inventory/index.d.ts +2 -0
- package/dist/inventory/index.js +1 -0
- package/dist/inventory/postgresStockAvailability.d.ts +24 -0
- package/dist/inventory/postgresStockAvailability.js +48 -0
- package/dist/subscriptions/commandExamples.d.ts +6 -0
- package/dist/subscriptions/commandExamples.js +20 -0
- package/dist/subscriptions/contracts/commands.d.ts +8 -1
- package/dist/subscriptions/contracts/index.d.ts +2 -2
- package/dist/subscriptions/contracts/renewal.d.ts +2 -0
- package/dist/subscriptions/contracts/stockWait.d.ts +12 -1
- package/dist/subscriptions/index.d.ts +22 -4
- package/dist/subscriptions/index.js +22 -3
- package/dist/subscriptions/lifecycleMessages.d.ts +9 -0
- package/dist/subscriptions/lifecycleMessages.js +38 -0
- package/dist/subscriptions/paymentOutcomes.js +1 -1
- package/dist/subscriptions/postgresCommandStore.d.ts +44 -0
- package/dist/subscriptions/postgresCommandStore.js +172 -0
- package/dist/subscriptions/postgresRetryStore.d.ts +6 -0
- package/dist/subscriptions/postgresRetryStore.js +11 -0
- package/dist/subscriptions/postgresStockWaitStore.d.ts +62 -0
- package/dist/subscriptions/postgresStockWaitStore.js +174 -0
- package/dist/subscriptions/renewalCycleFailureMechanism.js +3 -2
- package/dist/subscriptions/services.d.ts +31 -0
- package/dist/subscriptions/services.js +15 -0
- package/package.json +2 -2
- package/release-gates.json +2 -2
- package/sql/20261024170000_subscription_command_lifecycle.sql +404 -0
- package/sql/20261024180000_subscription_stock_wait.sql +330 -0
- package/sql/20261024183000_inventory_available_to_promise.sql +70 -0
- package/sql/20261024184000_subscription_renewal_reserve_uses_seam.sql +147 -0
- package/sql/20261024184500_subscription_retry_late_positive_stock.sql +59 -0
- package/sql/20261024184600_subscription_retry_candidate_payment_filter.sql +22 -0
- package/src/communications/contracts/subscriptionPaymentExpired.ts +4 -3
- package/src/communications/contracts/subscriptionPaymentFailed.ts +8 -8
- package/src/composition/index.ts +1 -0
- package/src/composition/subscriptionContribution.ts +25 -0
- package/src/inventory/index.ts +2 -0
- package/src/inventory/postgresStockAvailability.ts +54 -0
- package/src/subscriptions/commandExamples.ts +19 -0
- package/src/subscriptions/contracts/commands.ts +35 -2
- package/src/subscriptions/contracts/index.ts +2 -2
- package/src/subscriptions/contracts/renewal.ts +2 -1
- package/src/subscriptions/contracts/stockWait.ts +14 -2
- package/src/subscriptions/index.ts +30 -3
- package/src/subscriptions/lifecycleMessages.ts +37 -0
- package/src/subscriptions/paymentOutcomes.ts +1 -1
- package/src/subscriptions/postgresCommandStore.ts +183 -0
- package/src/subscriptions/postgresRetryStore.ts +11 -0
- package/src/subscriptions/postgresStockWaitStore.ts +170 -0
- package/src/subscriptions/renewalCycleFailureMechanism.ts +3 -1
- package/src/subscriptions/services.ts +21 -0
package/README.md
CHANGED
|
@@ -10,16 +10,27 @@ Development preview. This package is part of the publishable set with core and o
|
|
|
10
10
|
|
|
11
11
|
It holds the runnable server substrate an OpenLup application composes: the Node HTTP runtime, BFF dispatch, scheduling and job leases, blob storage, data gateways, the node-postgres migration runner and bundle composition. Ports and their contracts stay in `@openlup/core/platform-runtime`.
|
|
12
12
|
|
|
13
|
-
For the prepared `0.
|
|
14
|
-
limitations](CHANGELOG.md#
|
|
15
|
-
Install the complete native chain through position
|
|
16
|
-
|
|
17
|
-
are not an install chain.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
For the prepared `0.26.0` set, read the [adjacent-release migration and known
|
|
14
|
+
limitations](CHANGELOG.md#0260) before selecting optional services.
|
|
15
|
+
Install the complete native chain through position 86, including N2 commands,
|
|
16
|
+
N3 stock wait, the ATP seam/allocator and late-payment/retry-selection forwards;
|
|
17
|
+
sparse SQL projections are not an install chain. Native runtime roles need the
|
|
18
|
+
ATP/exposure EXECUTE and underlying SELECT grants described in the release notes.
|
|
19
|
+
Managed hosts apply the complete prefix through `20261025090100` for attributed
|
|
20
|
+
dunning attempt replay. `openDunningCase` requires nullable
|
|
21
|
+
`failedPaymentAttemptId`; pass the durable applied attempt through renewal failure
|
|
22
|
+
propagation, including replay. Fresh missing attribution refuses; never substitute
|
|
23
|
+
a current or prepared attempt. Read the migration notes for the new overloads
|
|
24
|
+
and existing donor schema/backfill mapping. Native attempt-replay parity remains
|
|
25
|
+
registered and these managed forwards authorize no host activation.
|
|
26
|
+
Preserve captured obligations and receipts on rollback.
|
|
27
|
+
M10 subscription services and contributions remain **EXPERIMENTAL and DEFAULT-OFF**.
|
|
28
|
+
Hosts explicitly supply readiness, execution, commands, mandatory G23 admission,
|
|
29
|
+
a population lease and M04 delivery. Factories activate no serving work. The inert
|
|
30
|
+
native reference composes renewal, dunning and stock wait; managed TypeScript
|
|
31
|
+
stock recovery, native multi-cycle prepaid and M07 prepared-attempt-store
|
|
32
|
+
capabilities retain their named unsupported boundaries. Follow the older native-row
|
|
33
|
+
migration precautions when upgrading `0.22.0` data.
|
|
23
34
|
M09 invoicing remains **EXPERIMENTAL and DEFAULT-OFF**, available for explicit
|
|
24
35
|
provider/capability selection and ready host composition, including issue,
|
|
25
36
|
correction, independent duties, delivery/read-side and seller revision helpers.
|
|
@@ -27,7 +38,7 @@ Adopters must not activate unselected or unready invoicing; installation and
|
|
|
27
38
|
inert contributions register no runner, worker, route or schedule.
|
|
28
39
|
M06 managed-host proof classes remain pending in the
|
|
29
40
|
[M06 parity ledger](../../docs/platform/plans/oss-m06-stock-reservations-delivery.md#parity-pending-rows).
|
|
30
|
-
Size comparison uses the published `0.
|
|
41
|
+
Size comparison uses the published `0.25.0` tarball. Preparation and offline
|
|
31
42
|
adapter evidence grant no publication or provider-account certification.
|
|
32
43
|
|
|
33
44
|
Longer module recipes live in the [platform package reference](../../docs/platform/PLATFORM_PACKAGE_REFERENCE.md).
|
|
@@ -724,6 +735,30 @@ The initial binding preserves case-before-outcome ordering with atomic bookkeepi
|
|
|
724
735
|
N1's same-obligation recovery repairs the native decline stall (F16); it does not
|
|
725
736
|
supply M07 prepared-store parity or activate a runner.
|
|
726
737
|
|
|
738
|
+
With N1 then N2 installed, `createPostgresSubscriptionCommandStore` and
|
|
739
|
+
`createPostgresSubscriptionCommands` support native generic bundle commands and
|
|
740
|
+
command-bound receipts. Preview persists a quote; apply atomically updates future
|
|
741
|
+
terms, event and receipt with no proration or current-cycle repricing. Hosts own
|
|
742
|
+
authorization, address validation, SKU/composition translation and the transaction
|
|
743
|
+
connection. Prepaid remains unsupported; the serving adapter refuses pause.
|
|
744
|
+
|
|
745
|
+
Native N3 plus the ATP seam/allocator and X/Y suffix provide
|
|
746
|
+
`createPostgresStockWaitStore` and `createPostgresStockAvailability` (the latter
|
|
747
|
+
at `./inventory`). Hosts supply transactions, clock, source and the existing
|
|
748
|
+
wire codec. Candidate discovery preserves uncaptured frozen operations; shortage
|
|
749
|
+
creates a durable wait, and elapsed stock-only refusals may reacquire on the same
|
|
750
|
+
identity with a new claim generation. Resumed means paid realization admitted.
|
|
751
|
+
The fixed 14-day soft priority is distinct from durable advance reservations.
|
|
752
|
+
The unbound descriptor keeps stock recovery unavailable. The opt-in native M10
|
|
753
|
+
reference composition selects it after composed PostgreSQL proof; managed
|
|
754
|
+
TypeScript recovery remains unavailable.
|
|
755
|
+
|
|
756
|
+
`createSubscriptionServices`, lifecycle-message and neutral command facades are
|
|
757
|
+
in `./subscriptions`; `createSubscriptionContribution` is in `./composition`.
|
|
758
|
+
They are default-off and describe one host executor/job. The
|
|
759
|
+
[composed module recipe](../../docs/platform/ARCHITECTURE_AND_EXTENSIONS.md#composed-subscription-module)
|
|
760
|
+
owns explicit selection, frozen mandate/replay, M04 delivery and M12 boundaries.
|
|
761
|
+
|
|
727
762
|
## Invoicing contracts
|
|
728
763
|
|
|
729
764
|
Experimental `@openlup/platform/invoicing` exposes `readInvoiceCapabilities`;
|
|
@@ -14,8 +14,9 @@ export interface SubscriptionPaymentExpiredEmailVars {
|
|
|
14
14
|
*/
|
|
15
15
|
cause?: PaymentFailureCustomerCause | null;
|
|
16
16
|
/**
|
|
17
|
-
* Recovery-link URL for the case's
|
|
18
|
-
*
|
|
17
|
+
* Recovery-link URL for the case's payment-recovery token. Current exhaustion
|
|
18
|
+
* uses `repair_payment`; legacy expired cases may carry `resume_subscription`.
|
|
19
|
+
* When present the template renders the recovery CTA; otherwise it keeps the support-led
|
|
19
20
|
* wording, because a button with no destination is worse than no button.
|
|
20
21
|
*/
|
|
21
22
|
recoveryUrl?: string | null;
|
|
@@ -8,7 +8,7 @@ export function createSubscriptionPaymentExpiredContentFactory(input) {
|
|
|
8
8
|
const causeSentence = subscriptionPaymentCauseSentence(locale, vars.cause ?? "unknown");
|
|
9
9
|
const detail = [
|
|
10
10
|
// Leads the box for the same reason as in the failed notice: it answers the
|
|
11
|
-
// question the
|
|
11
|
+
// question the unsuccessful payment raises, before the figures.
|
|
12
12
|
...(causeSentence ? [causeSentence] : []),
|
|
13
13
|
...(recoveryUrl
|
|
14
14
|
? [
|
|
@@ -12,9 +12,10 @@ export interface SubscriptionPaymentFailedEmailVars {
|
|
|
12
12
|
recoveryUrl?: string | null;
|
|
13
13
|
/**
|
|
14
14
|
* Pre-formatted calendar day of the scheduled next charge, in the merchant's
|
|
15
|
-
* zone (e.g. "11.08.2026").
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* zone (e.g. "11.08.2026"). Present only when a retry is actually scheduled:
|
|
16
|
+
* a first-rung hard decline selects this template with no next retry, and
|
|
17
|
+
* `retryScheduled: false` renders the no-retry copy. null/absent keeps the
|
|
18
|
+
* sentence without a date rather than printing a placeholder date.
|
|
18
19
|
*/
|
|
19
20
|
nextRetryDateLabel?: string | null;
|
|
20
21
|
/** Card scheme as stored on the method's consent snapshot, e.g. "visa". */
|
|
@@ -14,11 +14,10 @@ export function createSubscriptionPaymentFailedContentFactory(input) {
|
|
|
14
14
|
// also why it is PREPENDED and never appended — the retry sentence must stay
|
|
15
15
|
// last (see below).
|
|
16
16
|
const causeSentence = subscriptionPaymentCauseSentence(locale, vars.cause ?? "unknown");
|
|
17
|
-
// Only an explicit `false` turns the promises off.
|
|
18
|
-
//
|
|
19
|
-
// must
|
|
20
|
-
//
|
|
21
|
-
// cancel a subscription that was about to recover on its own.
|
|
17
|
+
// Only an explicit `false` turns the retry promises off. Absence preserves
|
|
18
|
+
// the legacy rendering default; it does not prove a retry is scheduled.
|
|
19
|
+
// Producers must pass the actual schedule fact when retries have stopped,
|
|
20
|
+
// including class termination or exhaustion.
|
|
22
21
|
const retryScheduled = vars.retryScheduled !== false;
|
|
23
22
|
const detail = [
|
|
24
23
|
...(causeSentence ? [causeSentence] : []),
|
|
@@ -5,3 +5,4 @@ export { createCatalogContribution, type CatalogContributionInput } from "./cata
|
|
|
5
5
|
export { createInventoryContribution } from "./inventoryContribution.js";
|
|
6
6
|
export { createShippingContribution } from "./shippingContribution.js";
|
|
7
7
|
export { createFulfillmentContribution } from "./fulfillmentContribution.js";
|
|
8
|
+
export { createSubscriptionContribution } from "./subscriptionContribution.js";
|
|
@@ -6,3 +6,4 @@ export { createCatalogContribution } from "./catalogContribution.js";
|
|
|
6
6
|
export { createInventoryContribution } from "./inventoryContribution.js";
|
|
7
7
|
export { createShippingContribution } from "./shippingContribution.js";
|
|
8
8
|
export { createFulfillmentContribution } from "./fulfillmentContribution.js";
|
|
9
|
+
export { createSubscriptionContribution } from "./subscriptionContribution.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Contribution, RequiredSchema } from "@openlup/core/readiness";
|
|
2
|
+
/** One selected subscription job; no second scheduler or automatic registration. */
|
|
3
|
+
export declare function createSubscriptionContribution(input: {
|
|
4
|
+
version: string;
|
|
5
|
+
requiredSchema: RequiredSchema;
|
|
6
|
+
run: (() => Promise<unknown>) | null;
|
|
7
|
+
cadenceSeconds: number;
|
|
8
|
+
}): {
|
|
9
|
+
contributions: Contribution[];
|
|
10
|
+
ports: {
|
|
11
|
+
"@openlup/platform/subscriptions:run": () => Promise<unknown>;
|
|
12
|
+
} | {
|
|
13
|
+
"@openlup/platform/subscriptions:run"?: undefined;
|
|
14
|
+
};
|
|
15
|
+
triggers: {
|
|
16
|
+
[k: string]: () => Promise<unknown>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** One selected subscription job; no second scheduler or automatic registration. */
|
|
2
|
+
export function createSubscriptionContribution(input) {
|
|
3
|
+
if (!Number.isFinite(input.cadenceSeconds) || input.cadenceSeconds <= 0)
|
|
4
|
+
throw new Error("subscription_cadence_invalid");
|
|
5
|
+
const schedules = input.run ? [{ id: "subscriptions-renewal", cadenceSeconds: input.cadenceSeconds, run: input.run }] : [];
|
|
6
|
+
const contribution = {
|
|
7
|
+
manifest: {
|
|
8
|
+
name: "@openlup/platform", version: input.version, kind: "rail",
|
|
9
|
+
module: { id: "subscriptions", kind: "capability", provides: ["./subscriptions", "./subscriptions/contracts"], dependsOn: [] },
|
|
10
|
+
emits: [], handles: [], schedules: schedules.map(({ id, cadenceSeconds }) => ({ id, cadenceSeconds })),
|
|
11
|
+
env: [], requiredPorts: ["run"], routes: [], requiredSchema: input.requiredSchema,
|
|
12
|
+
}, handlers: [], schedules, routes: [],
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
contributions: [contribution],
|
|
16
|
+
ports: input.run ? { "@openlup/platform/subscriptions:run": input.run } : {},
|
|
17
|
+
triggers: Object.fromEntries(schedules.map(schedule => [`@openlup/platform/subscriptions:${schedule.id}`, schedule.run])),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -12,3 +12,5 @@ export { forecastSubscriptionInventoryShortages, buildSubscriptionInventoryForec
|
|
|
12
12
|
export { calculateSubscriptionForecastSkus, calculateSubscriptionForecastTotals } from "./subscriptionForecastCalculation.js";
|
|
13
13
|
export { INVENTORY_MIGRATIONS } from "./sql.js";
|
|
14
14
|
export { createInventoryBindings, type InventoryBindingDependencies, type InventoryBindings } from "./bindings.js";
|
|
15
|
+
export { createPostgresStockAvailability } from "./postgresStockAvailability.js";
|
|
16
|
+
export type { NativeStockPromise } from "./postgresStockAvailability.js";
|
package/dist/inventory/index.js
CHANGED
|
@@ -9,3 +9,4 @@ export { forecastSubscriptionInventoryShortages, buildSubscriptionInventoryForec
|
|
|
9
9
|
export { calculateSubscriptionForecastSkus, calculateSubscriptionForecastTotals } from "./subscriptionForecastCalculation.js";
|
|
10
10
|
export { INVENTORY_MIGRATIONS } from "./sql.js";
|
|
11
11
|
export { createInventoryBindings } from "./bindings.js";
|
|
12
|
+
export { createPostgresStockAvailability } from "./postgresStockAvailability.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NewPurchaseStockReadPort } from "./newPurchaseStockAvailability.js";
|
|
2
|
+
interface StockQueryExecutor {
|
|
3
|
+
query(text: string, values?: unknown[]): Promise<{
|
|
4
|
+
rows: Record<string, unknown>[];
|
|
5
|
+
}>;
|
|
6
|
+
}
|
|
7
|
+
export interface NativeStockPromise {
|
|
8
|
+
availableQuantity: number;
|
|
9
|
+
forSaleQuantity: number;
|
|
10
|
+
hardHoldQuantity: number;
|
|
11
|
+
safetyQuantity: number;
|
|
12
|
+
softSubscriptionDemandQuantity: number;
|
|
13
|
+
channelExposureQuantity: number;
|
|
14
|
+
}
|
|
15
|
+
/** Native read-only ATP. The fixed 14-day priority cannot be waived by a new
|
|
16
|
+
* purchase caller. Only the fenced native SQL allocator uses the renewal exemption. */
|
|
17
|
+
export declare function createPostgresStockAvailability(executor: StockQueryExecutor, options: {
|
|
18
|
+
sourceKey: string;
|
|
19
|
+
now: () => string;
|
|
20
|
+
}): {
|
|
21
|
+
readPort: NewPurchaseStockReadPort;
|
|
22
|
+
availableToPromise(sku: string, asOf: string): Promise<NativeStockPromise>;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function object(value) {
|
|
2
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3
|
+
throw new Error("inventory_stock_response_invalid");
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
function quantity(value) {
|
|
7
|
+
if ((typeof value !== "number" && typeof value !== "string") || String(value).trim() === "")
|
|
8
|
+
throw new Error("inventory_stock_quantity_invalid");
|
|
9
|
+
const n = Number(value);
|
|
10
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
11
|
+
throw new Error("inventory_stock_quantity_invalid");
|
|
12
|
+
return n;
|
|
13
|
+
}
|
|
14
|
+
/** Native read-only ATP. The fixed 14-day priority cannot be waived by a new
|
|
15
|
+
* purchase caller. Only the fenced native SQL allocator uses the renewal exemption. */
|
|
16
|
+
export function createPostgresStockAvailability(executor, options) {
|
|
17
|
+
return {
|
|
18
|
+
async availableToPromise(sku, asOf) {
|
|
19
|
+
const row = object((await executor.query(`SELECT public.inventory_available_to_promise(
|
|
20
|
+
$1::text,$2::text,$3::timestamptz,false) AS response`, [options.sourceKey, sku, asOf])).rows[0]?.response);
|
|
21
|
+
return { availableQuantity: quantity(row.available_quantity), forSaleQuantity: quantity(row.for_sale_quantity),
|
|
22
|
+
hardHoldQuantity: quantity(row.hard_hold_quantity), safetyQuantity: quantity(row.safety_quantity),
|
|
23
|
+
softSubscriptionDemandQuantity: quantity(row.soft_subscription_demand_quantity), channelExposureQuantity: quantity(row.channel_exposure_quantity) };
|
|
24
|
+
},
|
|
25
|
+
readPort: {
|
|
26
|
+
async read(input) {
|
|
27
|
+
if (input.horizonDays !== 14 || input.excludeOrderId !== null)
|
|
28
|
+
throw new Error("native_stock_read_options_unsupported");
|
|
29
|
+
if (!input.skus.length)
|
|
30
|
+
return new Map();
|
|
31
|
+
const value = (await executor.query(`SELECT public.inventory_new_purchase_stock_read(
|
|
32
|
+
$1::text,$2::jsonb,$3::timestamptz) AS response`, [options.sourceKey, JSON.stringify(input.skus), options.now()])).rows[0]?.response;
|
|
33
|
+
if (!Array.isArray(value))
|
|
34
|
+
throw new Error("inventory_stock_response_invalid");
|
|
35
|
+
return new Map(value.map(value => {
|
|
36
|
+
const row = object(value);
|
|
37
|
+
if (typeof row.sku !== "string" || !["provider", "missing"].includes(String(row.source)) || typeof row.stale !== "boolean")
|
|
38
|
+
throw new Error("inventory_stock_response_invalid");
|
|
39
|
+
const result = { sku: row.sku, catalogSkuId: row.catalogSkuId == null ? null : String(row.catalogSkuId),
|
|
40
|
+
source: row.source, providerForSale: row.providerForSale == null ? null : quantity(row.providerForSale),
|
|
41
|
+
activeHolds: quantity(row.activeHolds), safetyStock: quantity(row.safetyStock), available: quantity(row.available), stale: row.stale,
|
|
42
|
+
renewalDemand: quantity(row.renewalDemand), availableForNewPurchases: quantity(row.availableForNewPurchases), excludedOrderHolds: quantity(row.excludedOrderHolds) };
|
|
43
|
+
return [result.sku, result];
|
|
44
|
+
}));
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SubscriptionCommandContext, SubscriptionCommandPort } from "./contracts/commands.js";
|
|
2
|
+
/** Neutral examples. The host authenticates and authorizes actor/client ownership first. */
|
|
3
|
+
export declare function createSubscriptionCommandExamples(commands: SubscriptionCommandPort): {
|
|
4
|
+
customerSkip(context: SubscriptionCommandContext): Promise<import("./contracts/commands.js").SubscriptionCommandReceipt | import("./contracts/commands.js").SubscriptionCommandRefusal | import("./contracts/commands.js").SubscriptionUnsupported>;
|
|
5
|
+
operatorSlide(context: SubscriptionCommandContext, newNextCycleAt: string): Promise<import("./contracts/commands.js").SubscriptionCommandReceipt | import("./contracts/commands.js").SubscriptionCommandRefusal | import("./contracts/commands.js").SubscriptionUnsupported>;
|
|
6
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Neutral examples. The host authenticates and authorizes actor/client ownership first. */
|
|
2
|
+
export function createSubscriptionCommandExamples(commands) {
|
|
3
|
+
const apply = (context, command) => {
|
|
4
|
+
if (context.actor.actorId.trim() === "" || context.actor.clientId.trim() === "")
|
|
5
|
+
throw new Error("subscription_command_actor_required");
|
|
6
|
+
return commands.apply(command);
|
|
7
|
+
};
|
|
8
|
+
return {
|
|
9
|
+
customerSkip(context) {
|
|
10
|
+
if (context.actor.kind !== "customer")
|
|
11
|
+
throw new Error("subscription_command_customer_required");
|
|
12
|
+
return apply(context, { ...context, action: "skip", reason: null, confirmedChargeTiming: true });
|
|
13
|
+
},
|
|
14
|
+
operatorSlide(context, newNextCycleAt) {
|
|
15
|
+
if (context.actor.kind !== "operator")
|
|
16
|
+
throw new Error("subscription_command_operator_required");
|
|
17
|
+
return apply(context, { ...context, action: "slide", newNextCycleAt, confirmedChargeTiming: true });
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -28,6 +28,7 @@ export type SubscriptionCommand = SubscriptionCommandContext & ({
|
|
|
28
28
|
planDays: number;
|
|
29
29
|
cadenceDays: number;
|
|
30
30
|
expectedTemplateVersion: number;
|
|
31
|
+
/** Empty string means no quote (semantic no-op). */
|
|
31
32
|
acceptedQuoteHash: string;
|
|
32
33
|
confirmedChargeTiming: boolean;
|
|
33
34
|
coreLines: readonly SubscriptionBundleLine[];
|
|
@@ -49,6 +50,12 @@ export interface SubscriptionCommandReceipt {
|
|
|
49
50
|
revision: string;
|
|
50
51
|
eventId: string | null;
|
|
51
52
|
}
|
|
53
|
+
export interface SubscriptionCommandRefusal {
|
|
54
|
+
outcome: "refused";
|
|
55
|
+
/** Stable machine code; never provider, PostgreSQL diagnostic or customer text.
|
|
56
|
+
* Unrecognized native or host diagnostics map to subscription_command_invalid_input. */
|
|
57
|
+
reason: "multi_cycle_prepaid_unsupported" | "subscription_command_addon_not_found" | "subscription_command_bundle_total_mismatch" | "subscription_command_charge_timing_not_confirmed" | "subscription_command_idempotency_conflict" | "subscription_command_invalid_composition_constraint" | "subscription_command_invalid_input" | "subscription_command_not_active" | "subscription_command_operation_busy" | "subscription_command_pause_forbidden" | "subscription_command_payment_blocked" | "subscription_command_preview_unsupported" | "subscription_command_renewal_terms_missing" | "subscription_command_unsupported_action" | "subscription_edit_catalog_unavailable" | "subscription_edit_invalid_lines" | "subscription_edit_invalid_request" | "subscription_edit_quote_expired" | "subscription_edit_quote_required" | "subscription_edit_stale_template" | "subscription_edit_window_closed" | "subscription_lifecycle_invalid_transition" | "subscription_lifecycle_invalid_skip" | "subscription_lifecycle_invalid_slide" | "subscription_lifecycle_payment_blocked";
|
|
58
|
+
}
|
|
52
59
|
export type SubscriptionUnsupportedCode = "multi_cycle_prepaid_unsupported" | "native_generic_bundle_commands_unsupported" | "native_command_bound_receipts_unsupported" | "native_prepared_attempt_store_unsupported";
|
|
53
60
|
export interface SubscriptionUnsupported {
|
|
54
61
|
supported: false;
|
|
@@ -88,7 +95,7 @@ export interface SubscriptionCapabilities {
|
|
|
88
95
|
export interface SubscriptionCommandPort {
|
|
89
96
|
readonly capabilities: SubscriptionCapabilities;
|
|
90
97
|
/** Future term writes, actor/command receipt and events must commit atomically. */
|
|
91
|
-
apply(command: SubscriptionCommand): Promise<SubscriptionCommandReceipt | SubscriptionUnsupported>;
|
|
98
|
+
apply(command: SubscriptionCommand): Promise<SubscriptionCommandReceipt | SubscriptionCommandRefusal | SubscriptionUnsupported>;
|
|
92
99
|
}
|
|
93
100
|
export interface SubscriptionActivationPort {
|
|
94
101
|
activate(input: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { RenewalSchedule, RenewalIdentity, RenewalAdmission, RenewalLine, RenewalCycleCorrelation, RenewalTemplateSnapshot, RenewalPricingSnapshot, RenewalOrderSnapshot, RenewalFrozenSnapshot, RenewalDue, RenewalOperationState, RenewalReadback, RenewalPaymentEvidence, RenewalTransactionUnits, RenewalReservationResult, RenewalPreparedSettlement, RenewalStorePort, RenewalSettlementPort, RenewalExecutionRequest, RenewalPaymentRail, RenewalResult } from "./renewal.js";
|
|
2
2
|
export type { DunningCaseOpenInput, DunningCaseOpened, DunningCaseAdvanced, DunningRefusalCode, DunningCaseLifecyclePort, RenewalRetryState, RenewalRetryObligation, RenewalRecoveryAuthorization, RenewalFailureHandoffPort } from "./dunning.js";
|
|
3
|
-
export type { StockWaitRecoveryBinding, StockWaitDeficit, StockWaitCase, StockWaitStorePort } from "./stockWait.js";
|
|
4
|
-
export type { SubscriptionCommandContext, SubscriptionCommand, SubscriptionBundleLine, SubscriptionCommandReceipt, SubscriptionUnsupportedCode, SubscriptionUnsupported, SubscriptionCapabilities, SubscriptionCommandPort, SubscriptionActivationPort } from "./commands.js";
|
|
3
|
+
export type { StockWaitRecoveryBinding, StockWaitDeficit, StockWaitCase, StockWaitFrozenCase, StockWaitStorePort } from "./stockWait.js";
|
|
4
|
+
export type { SubscriptionCommandContext, SubscriptionCommand, SubscriptionBundleLine, SubscriptionCommandReceipt, SubscriptionCommandRefusal, SubscriptionUnsupportedCode, SubscriptionUnsupported, SubscriptionCapabilities, SubscriptionCommandPort, SubscriptionActivationPort } from "./commands.js";
|
|
5
5
|
export type { SubscriptionLifecycleFact, SubscriptionLifecycleMessageDescriptor, SubscriptionLifecycleMessagePort } from "./messages.js";
|
|
6
6
|
export type { SubscriptionRenewalOutcome, HostedRenewalDue, SubscriptionRenewalChargeResult, SubscriptionCycleSnapshotResult, SubscriptionCycleOrderInput, SubscriptionCycleOrderResult, SubscriptionReservationPreflightResult, SubscriptionRenewalPersistencePort, FailurePropagationKind, FailurePropagationContext, CycleRetryState, DunningCaseOpenOutcome, CycleChargeFailurePropagationPort, FailurePropagationResult, RenewalPrepareInput, RenewalAttemptResult, RenewalIntentResult, RenewalPaymentPort, RenewalExecutionPort, RenewalDeliveryAdmission, RenewalAllowedAdmission, RenewalDeliveryClient, RenewalFailureDeps, RenewalPreflightDeps, RenewalCycleDeps, RenewalExecutionContext, RenewalOutcomeContext, RenewalReplayContext, RenewalPreparedAttemptPrepareInput, RenewalPreparedAttemptPersistenceResult, RenewalPreparedAttemptFinalizeInput, RenewalPreparedAttemptPersistencePort } from "./renewal.js";
|
|
@@ -129,9 +129,11 @@ export interface CycleChargeFailurePropagationPort {
|
|
|
129
129
|
};
|
|
130
130
|
}): Promise<{
|
|
131
131
|
replayed: boolean;
|
|
132
|
+
paymentAttemptId?: string | null;
|
|
132
133
|
}>;
|
|
133
134
|
readCycleRetryState(cycleId: string): Promise<CycleRetryState>;
|
|
134
135
|
openDunningCase(input: {
|
|
136
|
+
failedPaymentAttemptId: string | null;
|
|
135
137
|
idempotencyKey: string;
|
|
136
138
|
cycleId: string;
|
|
137
139
|
subscriptionId: string;
|
|
@@ -12,8 +12,11 @@ export interface StockWaitDeficit {
|
|
|
12
12
|
displayName: string;
|
|
13
13
|
size: string | null;
|
|
14
14
|
required: number;
|
|
15
|
+
/** This operation's own reserved units. */
|
|
15
16
|
reserved: number;
|
|
17
|
+
/** Free ATP, excluding this operation's own reservation. */
|
|
16
18
|
available: number;
|
|
19
|
+
/** Required units minus both reserved and available units. */
|
|
17
20
|
missing: number;
|
|
18
21
|
}
|
|
19
22
|
export interface StockWaitCase extends RenewalSchedule {
|
|
@@ -36,6 +39,14 @@ export interface StockWaitCase extends RenewalSchedule {
|
|
|
36
39
|
resumedNoticeKey: string;
|
|
37
40
|
noticeEligible: boolean;
|
|
38
41
|
}
|
|
42
|
+
/** Discovery can precede the durable waiting case; never invent a case ID. */
|
|
43
|
+
export interface StockWaitFrozenCase extends RenewalSchedule {
|
|
44
|
+
operationId: string;
|
|
45
|
+
case: StockWaitCase | null;
|
|
46
|
+
frozenSnapshot: RenewalFrozenSnapshot;
|
|
47
|
+
frozenFingerprint: string;
|
|
48
|
+
binding: StockWaitRecoveryBinding;
|
|
49
|
+
}
|
|
39
50
|
/** All-line reserve/reacquire and the wait transition share a DB transaction.
|
|
40
51
|
* A shortage rolls back the subtransaction, retaining previous commitments;
|
|
41
52
|
* record the wait outside that failed subtransaction. Technical failure is unknown.
|
|
@@ -44,7 +55,7 @@ export interface StockWaitStorePort {
|
|
|
44
55
|
listCandidates(input: {
|
|
45
56
|
limit: number;
|
|
46
57
|
asOf: string;
|
|
47
|
-
}): Promise<
|
|
58
|
+
}): Promise<StockWaitFrozenCase[]>;
|
|
48
59
|
readFrozenCase(input: StockWaitRecoveryBinding): Promise<StockWaitCase | null>;
|
|
49
60
|
reserveAndTransition(input: {
|
|
50
61
|
caseId: string;
|
|
@@ -6,12 +6,10 @@ export declare const NATIVE_SUBSCRIPTION_CAPABILITIES: Readonly<{
|
|
|
6
6
|
refusalCode: "multi_cycle_prepaid_unsupported";
|
|
7
7
|
}>;
|
|
8
8
|
readonly genericBundleCommands: Readonly<{
|
|
9
|
-
supported:
|
|
10
|
-
refusalCode: "native_generic_bundle_commands_unsupported";
|
|
9
|
+
supported: true;
|
|
11
10
|
}>;
|
|
12
11
|
readonly commandBoundReceipts: Readonly<{
|
|
13
|
-
supported:
|
|
14
|
-
refusalCode: "native_command_bound_receipts_unsupported";
|
|
12
|
+
supported: true;
|
|
15
13
|
}>;
|
|
16
14
|
readonly preparedAttemptStore: Readonly<{
|
|
17
15
|
supported: false;
|
|
@@ -37,3 +35,23 @@ export { createPostgresRetryStore } from "./postgresRetryStore.js";
|
|
|
37
35
|
export type { NativeRetryFailure, NativeRetryRequest, NativeRetryPositive, NativeRetryPreparation, NativeRetryRow } from "./postgresRetryStore.js";
|
|
38
36
|
export { createPostgresRetryLifecycleBinding } from "./postgresRetryStore.js";
|
|
39
37
|
export type { NativeRetryLifecycleBinding } from "./postgresRetryStore.js";
|
|
38
|
+
export { createPostgresSubscriptionCommandStore, createPostgresSubscriptionCommands } from "./postgresCommandStore.js";
|
|
39
|
+
export type { NativeSubscriptionCommand, NativeSubscriptionCommandBinding } from "./postgresCommandStore.js";
|
|
40
|
+
export { createPostgresStockWaitStore } from "./postgresStockWaitStore.js";
|
|
41
|
+
export type { NativeStockWaitBinding, NativeStockWaitAdmission } from "./postgresStockWaitStore.js";
|
|
42
|
+
/** Managed SQL is exposed for service-role hosts; no StockWaitStorePort or
|
|
43
|
+
* NewPurchaseStockReadPort TypeScript adapter is composed for this graph.
|
|
44
|
+
* SQL behavior is proved by the managed pgTAP/real-PostgreSQL witnesses.
|
|
45
|
+
*/
|
|
46
|
+
export declare const MANAGED_SUBSCRIPTION_STOCK_CAPABILITIES: Readonly<{
|
|
47
|
+
readonly graph: "managed";
|
|
48
|
+
readonly stockRecovery: "unavailable";
|
|
49
|
+
readonly stockWaitSurface: "sql";
|
|
50
|
+
readonly stockAvailabilitySurface: "sql";
|
|
51
|
+
readonly defaultEnabled: false;
|
|
52
|
+
}>;
|
|
53
|
+
export { runRenewalBatchRows } from "./automaticRenewalExecution.js";
|
|
54
|
+
export type { SubscriptionLifecycleFact } from "./contracts/messages.js";
|
|
55
|
+
export { subscriptionLifecycleTemplate, createSubscriptionLifecycleMessages } from "./lifecycleMessages.js";
|
|
56
|
+
export { createSubscriptionCommandExamples } from "./commandExamples.js";
|
|
57
|
+
export { createSubscriptionServices } from "./services.js";
|
|
@@ -2,10 +2,12 @@ export { renewalIdentity } from "./renewalIdentity.js";
|
|
|
2
2
|
export const NATIVE_SUBSCRIPTION_CAPABILITIES = Object.freeze({
|
|
3
3
|
graph: "native",
|
|
4
4
|
multiCyclePrepaid: Object.freeze({ supported: false, refusalCode: "multi_cycle_prepaid_unsupported" }),
|
|
5
|
-
genericBundleCommands: Object.freeze({ supported:
|
|
6
|
-
commandBoundReceipts: Object.freeze({ supported:
|
|
5
|
+
genericBundleCommands: Object.freeze({ supported: true }),
|
|
6
|
+
commandBoundReceipts: Object.freeze({ supported: true }),
|
|
7
7
|
preparedAttemptStore: Object.freeze({ supported: false, refusalCode: "native_prepared_attempt_store_unsupported" }),
|
|
8
|
-
retryRecovery: "selected",
|
|
8
|
+
retryRecovery: "selected",
|
|
9
|
+
// Native N3 adapters exist; selection requires passing real-PG evidence.
|
|
10
|
+
stockRecovery: "unavailable",
|
|
9
11
|
deliveryAdmission: "required", executorCount: 1, defaultEnabled: false,
|
|
10
12
|
});
|
|
11
13
|
export { executeRenewalCycle, prepareRenewalAttempt, checkRenewalDeliveryAdmission } from "./renewalCycleMechanism.js";
|
|
@@ -15,3 +17,20 @@ export { runAutomaticSubscriptionRenewals, runOneAutomaticSubscriptionRenewal, c
|
|
|
15
17
|
export { createPostgresRenewalStore } from "./postgresRenewalStore.js";
|
|
16
18
|
export { createPostgresRetryStore } from "./postgresRetryStore.js";
|
|
17
19
|
export { createPostgresRetryLifecycleBinding } from "./postgresRetryStore.js";
|
|
20
|
+
export { createPostgresSubscriptionCommandStore, createPostgresSubscriptionCommands } from "./postgresCommandStore.js";
|
|
21
|
+
export { createPostgresStockWaitStore } from "./postgresStockWaitStore.js";
|
|
22
|
+
/** Managed SQL is exposed for service-role hosts; no StockWaitStorePort or
|
|
23
|
+
* NewPurchaseStockReadPort TypeScript adapter is composed for this graph.
|
|
24
|
+
* SQL behavior is proved by the managed pgTAP/real-PostgreSQL witnesses.
|
|
25
|
+
*/
|
|
26
|
+
export const MANAGED_SUBSCRIPTION_STOCK_CAPABILITIES = Object.freeze({
|
|
27
|
+
graph: "managed",
|
|
28
|
+
stockRecovery: "unavailable",
|
|
29
|
+
stockWaitSurface: "sql",
|
|
30
|
+
stockAvailabilitySurface: "sql",
|
|
31
|
+
defaultEnabled: false,
|
|
32
|
+
});
|
|
33
|
+
export { runRenewalBatchRows } from "./automaticRenewalExecution.js";
|
|
34
|
+
export { subscriptionLifecycleTemplate, createSubscriptionLifecycleMessages } from "./lifecycleMessages.js";
|
|
35
|
+
export { createSubscriptionCommandExamples } from "./commandExamples.js";
|
|
36
|
+
export { createSubscriptionServices } from "./services.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SubscriptionLifecycleMessageDescriptor, SubscriptionLifecycleMessagePort } from "./contracts/messages.js";
|
|
2
|
+
export declare function subscriptionLifecycleTemplate(eventType: string, retryAttempt?: number | null): string | null;
|
|
3
|
+
/** M04 owns delivery, consent, rendering and durable replay. M10 never synthesizes a pause. */
|
|
4
|
+
export declare function createSubscriptionLifecycleMessages(delivery: SubscriptionLifecycleMessagePort): {
|
|
5
|
+
deliver(input: Omit<SubscriptionLifecycleMessageDescriptor, "templateSlug">): Promise<{
|
|
6
|
+
notificationId: string;
|
|
7
|
+
replayed: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Matches M5 and the existing M04 lifecycle templates. A mapping is not a send. */
|
|
2
|
+
const templates = Object.freeze({
|
|
3
|
+
"subscription.created": "subscription-welcome",
|
|
4
|
+
"subscription.activation_action_required": "subscription-activation-action-required",
|
|
5
|
+
"subscription.cancelled": "subscription-cancelled",
|
|
6
|
+
"subscription.paused": "subscription-paused",
|
|
7
|
+
"subscription.resumed": "subscription-resumed",
|
|
8
|
+
"subscription.pause_reminder_due": "subscription-pause-reminder",
|
|
9
|
+
"subscription.delivery_rescheduled": "subscription-delivery-rescheduled",
|
|
10
|
+
"subscription.cycle_skipped": "subscription-cycle-skipped",
|
|
11
|
+
"subscription.address_changed": "subscription-address-changed",
|
|
12
|
+
"subscription.package_changed": "subscription-package-changed",
|
|
13
|
+
"subscription.renewal_upcoming": "subscription-renewal-upcoming",
|
|
14
|
+
"subscription_stock_wait.started": "subscription-stock-wait-started",
|
|
15
|
+
"subscription_stock_wait.resumed": "subscription-stock-wait-resumed",
|
|
16
|
+
payment_expired: "subscription-payment-expired",
|
|
17
|
+
payment_recovered: "subscription-payment-recovered", winback_eligible: "subscription-winback",
|
|
18
|
+
stock_wait_started: "subscription-stock-wait-started", stock_wait_resumed: "subscription-stock-wait-resumed",
|
|
19
|
+
});
|
|
20
|
+
export function subscriptionLifecycleTemplate(eventType, retryAttempt) {
|
|
21
|
+
// Installed dunning SQL uses rung 3 for every value other than exactly 1 or 2.
|
|
22
|
+
if (eventType === "payment_failed")
|
|
23
|
+
return `subscription-payment-failed-${retryAttempt === 1 ? 1 : retryAttempt === 2 ? 2 : 3}`;
|
|
24
|
+
return Object.hasOwn(templates, eventType) ? templates[eventType] : null;
|
|
25
|
+
}
|
|
26
|
+
/** M04 owns delivery, consent, rendering and durable replay. M10 never synthesizes a pause. */
|
|
27
|
+
export function createSubscriptionLifecycleMessages(delivery) {
|
|
28
|
+
return {
|
|
29
|
+
deliver(input) {
|
|
30
|
+
const templateSlug = subscriptionLifecycleTemplate(input.fact.kind, input.fact.kind === "payment_failed" ? input.fact.retryAttempt : null);
|
|
31
|
+
if (!templateSlug)
|
|
32
|
+
throw new Error("subscription_lifecycle_message_unsupported");
|
|
33
|
+
if (input.messageKey !== input.fact.idempotencyKey)
|
|
34
|
+
throw new Error("subscription_lifecycle_message_identity_mismatch");
|
|
35
|
+
return delivery.enqueue({ ...input, templateSlug });
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -7,7 +7,7 @@ export async function readAndApplyProviderOutcome(deps, operation, prepared, rai
|
|
|
7
7
|
throw new Error("subscription_renewal_provider_amount_mismatch");
|
|
8
8
|
if (observed.currency !== null && observed.currency.toUpperCase() !== money.currency.toUpperCase())
|
|
9
9
|
throw new Error("subscription_renewal_provider_currency_mismatch");
|
|
10
|
-
if (observed.status
|
|
10
|
+
if (observed.status !== "succeeded" && observed.status !== "failed")
|
|
11
11
|
return automaticRenewalResult(operation, "awaiting_provider", `provider_${observed.status}`);
|
|
12
12
|
if (observed.operationId !== operation.operationId || observed.requestFingerprint !== operation.operationFingerprint
|
|
13
13
|
|| observed.externalAttemptRef !== externalAttemptRef || !observed.verifiedEvidenceRef || !observed.eventKey
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SubscriptionCommand, SubscriptionCommandPort, SubscriptionCommandRefusal } from "./contracts/commands.js";
|
|
2
|
+
import { type SubscriptionQueryExecutor } from "./postgresRenewalStore.js";
|
|
3
|
+
export interface NativeSubscriptionCommand {
|
|
4
|
+
actorId: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
idempotencyKey: string;
|
|
7
|
+
subscriptionId: string;
|
|
8
|
+
action: string;
|
|
9
|
+
payload: Record<string, unknown>;
|
|
10
|
+
now: string;
|
|
11
|
+
}
|
|
12
|
+
export interface NativeSubscriptionCommandBinding {
|
|
13
|
+
/** The callback must use one connection and commit/rollback the complete unit. */
|
|
14
|
+
withTransaction<T>(work: (executor: SubscriptionQueryExecutor) => Promise<T>): Promise<T>;
|
|
15
|
+
/** Validate address ownership and refuse address mutation; resolve SKUs and
|
|
16
|
+
* composition policy using trusted host evidence. No current-cycle write. */
|
|
17
|
+
futureTerms(command: Extract<SubscriptionCommand, {
|
|
18
|
+
action: "edit_future_terms";
|
|
19
|
+
}>): Promise<{
|
|
20
|
+
coreLines: readonly {
|
|
21
|
+
variantId: string;
|
|
22
|
+
qty: number;
|
|
23
|
+
}[];
|
|
24
|
+
addonLines: readonly {
|
|
25
|
+
variantId: string;
|
|
26
|
+
qty: number;
|
|
27
|
+
}[];
|
|
28
|
+
compositionConstraint: Record<string, unknown>;
|
|
29
|
+
} | SubscriptionCommandRefusal>;
|
|
30
|
+
}
|
|
31
|
+
/** N2 owns normalization, locking, quote acceptance, receipts and all writes.
|
|
32
|
+
* Preview is a separate persisted transaction; apply never executes payment. */
|
|
33
|
+
export declare function createPostgresSubscriptionCommandStore(binding: Pick<NativeSubscriptionCommandBinding, "withTransaction">): {
|
|
34
|
+
preview(input: NativeSubscriptionCommand): Promise<Record<string, unknown>>;
|
|
35
|
+
apply(input: NativeSubscriptionCommand): Promise<Record<string, unknown>>;
|
|
36
|
+
};
|
|
37
|
+
/** Trusted customer/operator authorization precedes this adapter. The native
|
|
38
|
+
* result's subscriptionStatus is the state recorded by the original execution,
|
|
39
|
+
* including on replay; it is not a fresh read of mutable subscription state. */
|
|
40
|
+
export declare function createPostgresSubscriptionCommands(binding: NativeSubscriptionCommandBinding): SubscriptionCommandPort & {
|
|
41
|
+
preview(command: Extract<SubscriptionCommand, {
|
|
42
|
+
action: "edit_future_terms";
|
|
43
|
+
}>): Promise<Record<string, unknown> | SubscriptionCommandRefusal>;
|
|
44
|
+
};
|