@lostgradient/weft 0.23.0 → 0.24.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/README.md +72 -4
- package/dist/cli/codegen-emit-dedup.d.ts +81 -0
- package/dist/cli/codegen-emit-dedup.js +45 -0
- package/dist/cli/codegen-emit-registry.d.ts +90 -0
- package/dist/cli/codegen-emit-registry.js +50 -0
- package/dist/cli/codegen-emit.d.ts +26 -39
- package/dist/cli/codegen-emit.js +4 -27
- package/dist/cli/codegen-validate.d.ts +49 -0
- package/dist/cli/codegen-validate.js +186 -0
- package/dist/cli/codegen.js +6 -87
- package/dist/cli/generated/operation-catalog.snapshot.json +287 -3
- package/dist/cli/generated/operation-client.generated.d.ts +324 -10
- package/dist/cli/generated/operation-client.generated.js +12 -0
- package/dist/cli/output.js +1 -1
- package/dist/cli/storage-factory.js +2 -0
- package/dist/cli/workflow-commands.js +2 -0
- package/dist/cli-main.js +1 -1
- package/dist/core/application-payload-digest.d.ts +42 -0
- package/dist/core/application-payload-digest.js +76 -0
- package/dist/core/application-primitive-abort.d.ts +50 -0
- package/dist/core/application-primitive-abort.js +40 -0
- package/dist/core/application-primitive-attempt-registry.d.ts +74 -0
- package/dist/core/application-primitive-attempt-registry.js +73 -0
- package/dist/core/application-primitive-codec.d.ts +36 -0
- package/dist/core/application-primitive-codec.js +56 -0
- package/dist/core/application-primitive-commit.d.ts +67 -0
- package/dist/core/application-primitive-commit.js +66 -0
- package/dist/core/application-primitive-guards.d.ts +62 -0
- package/dist/core/application-primitive-guards.js +89 -0
- package/dist/core/application-primitive-payload.d.ts +29 -0
- package/dist/core/application-primitive-payload.js +65 -0
- package/dist/core/application-primitive-timing.d.ts +18 -0
- package/dist/core/application-primitive-timing.js +22 -0
- package/dist/core/atomic-state.js +2 -0
- package/dist/core/catalog/codec.d.ts +40 -0
- package/dist/core/catalog/codec.js +45 -0
- package/dist/core/catalog/errors.d.ts +104 -0
- package/dist/core/catalog/errors.js +41 -0
- package/dist/core/catalog/index.d.ts +25 -0
- package/dist/core/catalog/index.js +15 -0
- package/dist/core/catalog/reference-counts.d.ts +105 -0
- package/dist/core/catalog/reference-counts.js +28 -0
- package/dist/core/catalog/removal.d.ts +52 -0
- package/dist/core/catalog/removal.js +14 -0
- package/dist/core/catalog/storage-io.d.ts +78 -0
- package/dist/core/catalog/storage-io.js +106 -0
- package/dist/core/catalog/types.d.ts +108 -0
- package/dist/core/catalog/types.js +0 -0
- package/dist/core/catalog/workflow-catalog.d.ts +165 -0
- package/dist/core/catalog/workflow-catalog.js +198 -0
- package/dist/core/compare-codepoint.d.ts +13 -0
- package/dist/core/compare-codepoint.js +7 -0
- package/dist/core/compression.js +2 -0
- package/dist/core/context/attributes.d.ts +1 -1
- package/dist/core/context/durable-operations.d.ts +2 -2
- package/dist/core/context/index.js +5 -3
- package/dist/core/context/state-namespace.d.ts +1 -1
- package/dist/core/contract/build.d.ts +64 -0
- package/dist/core/contract/build.js +99 -0
- package/dist/core/contract/compatibility.d.ts +187 -0
- package/dist/core/contract/compatibility.js +37 -0
- package/dist/core/contract/failure.d.ts +64 -0
- package/dist/core/contract/failure.js +4 -0
- package/dist/core/contract/hash.d.ts +92 -0
- package/dist/core/contract/hash.js +34 -0
- package/dist/core/contract/index.d.ts +21 -0
- package/dist/core/contract/index.js +22 -0
- package/dist/core/contract/limits.d.ts +74 -0
- package/dist/core/contract/limits.js +1 -0
- package/dist/core/contract/manifest-parse-schema.d.ts +37 -0
- package/dist/core/contract/manifest-parse-schema.js +106 -0
- package/dist/core/contract/manifest-parse.d.ts +76 -0
- package/dist/core/contract/manifest-parse.js +189 -0
- package/dist/core/contract/manifest.d.ts +56 -0
- package/dist/core/contract/manifest.js +31 -0
- package/dist/core/contract/normalize.d.ts +97 -0
- package/dist/core/contract/normalize.js +127 -0
- package/dist/core/contract/revision.d.ts +29 -0
- package/dist/core/contract/revision.js +5 -0
- package/dist/core/contract/types.d.ts +274 -0
- package/dist/core/contract/types.js +1 -0
- package/dist/core/engine/catalog-activation.d.ts +40 -0
- package/dist/core/engine/catalog-activation.js +17 -0
- package/dist/core/engine/catalog-events.d.ts +36 -0
- package/dist/core/engine/catalog-events.js +15 -0
- package/dist/core/engine/catalog-readiness.d.ts +49 -0
- package/dist/core/engine/catalog-readiness.js +59 -0
- package/dist/core/engine/catalog-removal.d.ts +132 -0
- package/dist/core/engine/catalog-removal.js +70 -0
- package/dist/core/engine/construction.d.ts +1 -1
- package/dist/core/engine/construction.js +4 -2
- package/dist/core/engine/disposal.js +9 -0
- package/dist/core/engine/engine-workflows-namespace.d.ts +130 -0
- package/dist/core/engine/engine-workflows-namespace.js +49 -0
- package/dist/core/engine/finalizer-metadata.d.ts +22 -0
- package/dist/core/engine/finalizer-metadata.js +12 -0
- package/dist/core/engine/handle-result.js +2 -0
- package/dist/core/engine/index.d.ts +83 -0
- package/dist/core/engine/index.js +61 -0
- package/dist/core/engine/internals.d.ts +44 -1
- package/dist/core/engine/internals.js +6 -0
- package/dist/core/engine/lifecycle/start.js +3 -0
- package/dist/core/engine/registration.d.ts +76 -0
- package/dist/core/engine/registration.js +12 -6
- package/dist/core/engine/source-registration.d.ts +42 -0
- package/dist/core/engine/source-registration.js +36 -0
- package/dist/core/engine/source-resolution.d.ts +85 -0
- package/dist/core/engine/source-resolution.js +119 -0
- package/dist/core/engine/validation.js +2 -2
- package/dist/core/events/catalog-events.d.ts +117 -0
- package/dist/core/events/catalog-events.js +62 -0
- package/dist/core/events/event-map.d.ts +6 -0
- package/dist/core/events/index.d.ts +1 -0
- package/dist/core/events/index.js +1 -0
- package/dist/core/mailbox-admission.d.ts +26 -0
- package/dist/core/mailbox-admission.js +130 -0
- package/dist/core/mailbox-claims.d.ts +251 -0
- package/dist/core/mailbox-claims.js +0 -0
- package/dist/core/mailbox-codec.d.ts +32 -0
- package/dist/core/mailbox-codec.js +221 -0
- package/dist/core/mailbox-contract.d.ts +290 -0
- package/dist/core/mailbox-contract.js +0 -0
- package/dist/core/mailbox-delivery.d.ts +37 -0
- package/dist/core/mailbox-delivery.js +193 -0
- package/dist/core/mailbox-guards.d.ts +78 -0
- package/dist/core/mailbox-guards.js +58 -0
- package/dist/core/mailbox-index-codec.d.ts +41 -0
- package/dist/core/mailbox-index-codec.js +71 -0
- package/dist/core/mailbox-internals.d.ts +156 -0
- package/dist/core/mailbox-internals.js +154 -0
- package/dist/core/mailbox-maintenance.d.ts +29 -0
- package/dist/core/mailbox-maintenance.js +196 -0
- package/dist/core/mailbox-settlement.d.ts +69 -0
- package/dist/core/mailbox-settlement.js +206 -0
- package/dist/core/mailbox-storage.d.ts +117 -0
- package/dist/core/mailbox-storage.js +115 -0
- package/dist/core/mailbox-transition-helpers.d.ts +70 -0
- package/dist/core/mailbox-transition-helpers.js +43 -0
- package/dist/core/mailbox-transitions-recovery.d.ts +28 -0
- package/dist/core/mailbox-transitions-recovery.js +64 -0
- package/dist/core/mailbox-transitions.d.ts +138 -0
- package/dist/core/mailbox-transitions.js +180 -0
- package/dist/core/mailbox-types.d.ts +406 -0
- package/dist/core/mailbox-types.js +16 -0
- package/dist/core/mailbox-validation.d.ts +65 -0
- package/dist/core/mailbox-validation.js +73 -0
- package/dist/core/mailbox-waits.d.ts +50 -0
- package/dist/core/mailbox-waits.js +97 -0
- package/dist/core/mailbox.d.ts +163 -0
- package/dist/core/mailbox.js +211 -0
- package/dist/core/outbox-claims.d.ts +307 -0
- package/dist/core/outbox-claims.js +0 -0
- package/dist/core/outbox-codec.d.ts +24 -0
- package/dist/core/outbox-codec.js +257 -0
- package/dist/core/outbox-contract.d.ts +346 -0
- package/dist/core/outbox-contract.js +0 -0
- package/dist/core/outbox-delivery.d.ts +34 -0
- package/dist/core/outbox-delivery.js +149 -0
- package/dist/core/outbox-drain.d.ts +29 -0
- package/dist/core/outbox-drain.js +158 -0
- package/dist/core/outbox-enqueue.d.ts +24 -0
- package/dist/core/outbox-enqueue.js +115 -0
- package/dist/core/outbox-guards.d.ts +76 -0
- package/dist/core/outbox-guards.js +60 -0
- package/dist/core/outbox-index-codec.d.ts +33 -0
- package/dist/core/outbox-index-codec.js +71 -0
- package/dist/core/outbox-internals.d.ts +99 -0
- package/dist/core/outbox-internals.js +159 -0
- package/dist/core/outbox-listing.d.ts +12 -0
- package/dist/core/outbox-listing.js +38 -0
- package/dist/core/outbox-maintenance.d.ts +28 -0
- package/dist/core/outbox-maintenance.js +191 -0
- package/dist/core/outbox-operations.d.ts +18 -0
- package/dist/core/outbox-operations.js +46 -0
- package/dist/core/outbox-runner.d.ts +30 -0
- package/dist/core/outbox-runner.js +217 -0
- package/dist/core/outbox-settlement.d.ts +57 -0
- package/dist/core/outbox-settlement.js +183 -0
- package/dist/core/outbox-storage.d.ts +107 -0
- package/dist/core/outbox-storage.js +127 -0
- package/dist/core/outbox-transition-helpers.d.ts +83 -0
- package/dist/core/outbox-transition-helpers.js +63 -0
- package/dist/core/outbox-transitions-recovery.d.ts +30 -0
- package/dist/core/outbox-transitions-recovery.js +42 -0
- package/dist/core/outbox-transitions.d.ts +145 -0
- package/dist/core/outbox-transitions.js +236 -0
- package/dist/core/outbox-types.d.ts +400 -0
- package/dist/core/outbox-types.js +20 -0
- package/dist/core/outbox-validation.d.ts +101 -0
- package/dist/core/outbox-validation.js +127 -0
- package/dist/core/outbox-waits.d.ts +39 -0
- package/dist/core/outbox-waits.js +88 -0
- package/dist/core/outbox.d.ts +156 -0
- package/dist/core/outbox.js +234 -0
- package/dist/core/registry-limits.d.ts +49 -0
- package/dist/core/registry-limits.js +10 -0
- package/dist/core/registry-schema-conversion.d.ts +20 -0
- package/dist/core/registry-schema-conversion.js +22 -0
- package/dist/core/registry-snapshot.d.ts +91 -42
- package/dist/core/registry-snapshot.js +39 -77
- package/dist/core/registry-workflow-contract-draft.d.ts +32 -0
- package/dist/core/registry-workflow-contract-draft.js +28 -0
- package/dist/core/registry-workflow-manifest.d.ts +122 -0
- package/dist/core/registry-workflow-manifest.js +92 -0
- package/dist/core/source/errors.d.ts +55 -0
- package/dist/core/source/errors.js +13 -0
- package/dist/core/source/index.d.ts +19 -0
- package/dist/core/source/index.js +4 -0
- package/dist/core/source/resolvers.d.ts +45 -0
- package/dist/core/source/resolvers.js +9 -0
- package/dist/core/source/types.d.ts +211 -0
- package/dist/core/source/types.js +0 -0
- package/dist/core/source/validate.d.ts +91 -0
- package/dist/core/source/validate.js +62 -0
- package/dist/core/source/workflow-source.d.ts +43 -0
- package/dist/core/source/workflow-source.js +16 -0
- package/dist/core/types/workflow-registries.d.ts +7 -0
- package/dist/core/types/workflow-registry.d.ts +12 -0
- package/dist/core/versioning.js +2 -0
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +9 -1
- package/dist/http.js +2 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +50 -0
- package/dist/indexeddb.js +1 -1
- package/dist/json-schema.js +3 -3
- package/dist/mcp/resources.js +2 -0
- package/dist/runtime/portable.d.ts +9 -1
- package/dist/server/fault-to-json-rpc.js +3 -1
- package/dist/server/handler/auth-context-principal.js +4 -0
- package/dist/server/json-rpc-http.js +2 -0
- package/dist/server/json-rpc-websocket.js +6 -4
- package/dist/server/operation-catalog/index.d.ts +1 -1
- package/dist/server/operation-catalog/pipeline-helpers.js +2 -0
- package/dist/server/operation-catalog/types.d.ts +17 -0
- package/dist/server/operation-fault.d.ts +3 -0
- package/dist/server/operation-fault.js +3 -1
- package/dist/server/operations/activate-workflow-revision.d.ts +30 -0
- package/dist/server/operations/activate-workflow-revision.js +69 -0
- package/dist/server/operations/fleet-events-subscription.js +1 -1
- package/dist/server/operations/fork-workflow.js +1 -1
- package/dist/server/operations/get-active-workflow-revision.d.ts +32 -0
- package/dist/server/operations/get-active-workflow-revision.js +46 -0
- package/dist/server/operations/get-catalog-diagnostics.d.ts +85 -0
- package/dist/server/operations/get-catalog-diagnostics.js +57 -0
- package/dist/server/operations/get-registry.js +17 -4
- package/dist/server/operations/get-workflow-revision.d.ts +25 -0
- package/dist/server/operations/get-workflow-revision.js +50 -0
- package/dist/server/operations/install-workflow-revision.d.ts +27 -0
- package/dist/server/operations/install-workflow-revision.js +46 -0
- package/dist/server/operations/list-workflow-revisions.d.ts +20 -0
- package/dist/server/operations/list-workflow-revisions.js +40 -0
- package/dist/server/operations/start-or-signal-workflow.js +1 -1
- package/dist/server/operations/start-workflow.js +1 -1
- package/dist/server/operations/static-registrations.js +36 -0
- package/dist/server/operations/storage.js +2 -2
- package/dist/server/operations/submit-review-decision.js +1 -1
- package/dist/server/operations/workflow-catalog-operation-helpers.d.ts +59 -0
- package/dist/server/operations/workflow-catalog-operation-helpers.js +135 -0
- package/dist/server/runtime/websocket-upgrade.js +2 -0
- package/dist/storage/bun-sql.js +49 -6
- package/dist/storage/catalog-keys.d.ts +45 -0
- package/dist/storage/catalog-keys.js +6 -0
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/derived-operations.js +1 -1
- package/dist/storage/index.d.ts +26 -1
- package/dist/storage/interface.d.ts +26 -1
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-encoding.d.ts +15 -0
- package/dist/storage/key-encoding.js +3 -0
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +15 -0
- package/dist/storage/lmdb.d.ts +26 -1
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/mailbox-keys.d.ts +80 -0
- package/dist/storage/mailbox-keys.js +14 -0
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +2 -2
- package/dist/storage/node-sqlite-loader.d.ts +1 -1
- package/dist/storage/node-sqlite-loader.js +2 -2
- package/dist/storage/node-sqlite.js +51 -8
- package/dist/storage/outbox-keys.d.ts +77 -0
- package/dist/storage/outbox-keys.js +14 -0
- package/dist/storage/postgres.js +2 -2
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/storage-configuration.d.ts +6 -0
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/dist/worker/manifest/registry-contract-builder.d.ts +21 -3
- package/dist/worker/manifest/registry-contract-builder.js +40 -31
- package/package.json +1 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
commitOutboxTransition,
|
|
3
|
+
headerOperation,
|
|
4
|
+
loadOutboxHeader,
|
|
5
|
+
planDeliveryTransition
|
|
6
|
+
} from "./outbox-storage.js";
|
|
7
|
+
import { isTerminalDeliveryRecord } from "./outbox-transition-helpers.js";
|
|
8
|
+
import { isApplicationDeliveryLeased } from "./outbox-types.js";
|
|
9
|
+
import { WeftError } from "./weft-error.js";
|
|
10
|
+
export const MAX_OUTBOX_TRANSITION_ATTEMPTS = 25, OUTBOX_MAINTENANCE_MAX_PAGES = 200, OUTBOX_PRIMITIVE = "outbox";
|
|
11
|
+
|
|
12
|
+
export class OutboxContentionError extends WeftError {
|
|
13
|
+
operation;
|
|
14
|
+
deliveryId;
|
|
15
|
+
constructor(operation, deliveryId) {
|
|
16
|
+
super("OutboxContentionError", `Application outbox ${operation}${deliveryId === null ? "" : ` for delivery "${deliveryId}"`} lost its storage precondition after ${MAX_OUTBOX_TRANSITION_ATTEMPTS} attempts.`);
|
|
17
|
+
this.operation = operation;
|
|
18
|
+
this.deliveryId = deliveryId;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function receiptLeaseFields(record) {
|
|
22
|
+
if (!isApplicationDeliveryLeased(record))
|
|
23
|
+
return {};
|
|
24
|
+
return {
|
|
25
|
+
claimedAt: record.claimedAt,
|
|
26
|
+
visibilityExpiresAt: record.visibilityExpiresAt,
|
|
27
|
+
attemptDeadlineAt: record.attemptDeadlineAt,
|
|
28
|
+
lastActivityAt: record.lastActivityAt,
|
|
29
|
+
transportActivity: record.transportActivity,
|
|
30
|
+
...record.state === "claimed" ? {} : { attemptStartedAt: record.attemptStartedAt },
|
|
31
|
+
...record.state === "cancellation-requested" ? {
|
|
32
|
+
cancellationRequestedAt: record.cancellationRequestedAt,
|
|
33
|
+
cancellationReason: record.cancellationReason
|
|
34
|
+
} : {}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function receiptTerminalFields(record) {
|
|
38
|
+
if (record.state === "retry-scheduled")
|
|
39
|
+
return { lastFailure: record.lastFailure };
|
|
40
|
+
if (!isTerminalDeliveryRecord(record))
|
|
41
|
+
return {};
|
|
42
|
+
return {
|
|
43
|
+
cancellationRequestedAt: record.cancellationRequestedAt,
|
|
44
|
+
cancellationReason: record.cancellationReason,
|
|
45
|
+
terminalAt: record.terminalAt,
|
|
46
|
+
evidence: record.evidence,
|
|
47
|
+
failure: record.failure,
|
|
48
|
+
cleanupPending: record.cleanupPending
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function deepFreeze(value) {
|
|
52
|
+
if (typeof value !== "object" || value === null || Object.isFrozen(value))
|
|
53
|
+
return value;
|
|
54
|
+
Object.freeze(value);
|
|
55
|
+
for (const nested of Object.values(value))
|
|
56
|
+
deepFreeze(nested);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
export function toApplicationDeliveryReceipt(record) {
|
|
60
|
+
return deepFreeze({
|
|
61
|
+
deliveryId: record.deliveryId,
|
|
62
|
+
namespace: record.namespace,
|
|
63
|
+
ownerId: record.ownerId,
|
|
64
|
+
sequence: record.sequence,
|
|
65
|
+
state: record.state,
|
|
66
|
+
destinationRef: record.destinationRef,
|
|
67
|
+
kind: record.kind,
|
|
68
|
+
payloadDigest: record.payloadDigest,
|
|
69
|
+
payloadForm: record.payload.form,
|
|
70
|
+
payloadMediaType: record.payloadMediaType,
|
|
71
|
+
payloadSchema: record.payloadSchema,
|
|
72
|
+
idempotencyKey: record.idempotencyKey,
|
|
73
|
+
externalIdempotencyKey: record.externalIdempotencyKey,
|
|
74
|
+
unknownOutcomePolicy: record.unknownOutcomePolicy,
|
|
75
|
+
causation: record.causation,
|
|
76
|
+
enqueuedAt: record.enqueuedAt,
|
|
77
|
+
availableAt: record.availableAt,
|
|
78
|
+
attempt: record.attempt,
|
|
79
|
+
retryCount: record.retryCount,
|
|
80
|
+
maxAttempts: record.maxAttempts,
|
|
81
|
+
generation: record.generation,
|
|
82
|
+
...receiptLeaseFields(record),
|
|
83
|
+
...receiptTerminalFields(record)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const STATE_EVENT_KINDS = {
|
|
87
|
+
queued: "outbox:delivery-queued",
|
|
88
|
+
"retry-scheduled": "outbox:delivery-retry-scheduled",
|
|
89
|
+
claimed: "outbox:delivery-claimed",
|
|
90
|
+
attempting: "outbox:delivery-attempting",
|
|
91
|
+
"cancellation-requested": "outbox:delivery-cancellation-requested",
|
|
92
|
+
acknowledged: "outbox:delivery-acknowledged",
|
|
93
|
+
rejected: "outbox:delivery-rejected",
|
|
94
|
+
cancelled: "outbox:delivery-cancelled",
|
|
95
|
+
"unknown-outcome": "outbox:delivery-unknown-outcome",
|
|
96
|
+
"dead-lettered": "outbox:delivery-dead-lettered"
|
|
97
|
+
};
|
|
98
|
+
export function describeDeliveryTransition(previous, next) {
|
|
99
|
+
return {
|
|
100
|
+
kind: previous !== null && isTerminalDeliveryRecord(previous) && next.state === "queued" ? "outbox:delivery-retried" : STATE_EVENT_KINDS[next.state],
|
|
101
|
+
payload: {
|
|
102
|
+
namespace: next.namespace,
|
|
103
|
+
ownerId: next.ownerId,
|
|
104
|
+
deliveryId: next.deliveryId,
|
|
105
|
+
sequence: next.sequence,
|
|
106
|
+
state: next.state,
|
|
107
|
+
deliveryKind: next.kind,
|
|
108
|
+
attempt: next.attempt,
|
|
109
|
+
retryCount: next.retryCount,
|
|
110
|
+
generation: next.generation,
|
|
111
|
+
previousState: previous === null ? null : previous.state
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function releaseAttemptController(runtime, attemptToken, reason, deliveryId) {
|
|
116
|
+
const registration = runtime.attemptControllers.get(attemptToken);
|
|
117
|
+
if (registration === void 0)
|
|
118
|
+
return;
|
|
119
|
+
if (deliveryId !== void 0 && registration.subjectId !== deliveryId)
|
|
120
|
+
return;
|
|
121
|
+
runtime.attemptControllers.delete(attemptToken);
|
|
122
|
+
registration.release();
|
|
123
|
+
if (!registration.controller.signal.aborted)
|
|
124
|
+
registration.controller.abort(Error(reason));
|
|
125
|
+
}
|
|
126
|
+
export function releaseAttemptsForDelivery(runtime, deliveryId, reason, currentToken, observedAt) {
|
|
127
|
+
for (const attemptToken of runtime.attemptControllers.tokensFor(deliveryId)) {
|
|
128
|
+
const registration = runtime.attemptControllers.get(attemptToken);
|
|
129
|
+
if (registration === void 0 || attemptToken === currentToken)
|
|
130
|
+
continue;
|
|
131
|
+
if (registration.committedSerial === null)
|
|
132
|
+
continue;
|
|
133
|
+
if (observedAt !== void 0 && registration.committedSerial > observedAt)
|
|
134
|
+
continue;
|
|
135
|
+
releaseAttemptController(runtime, attemptToken, reason);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export async function commitDeliveryTransition(runtime, options) {
|
|
139
|
+
const previousTerminal = options.previous !== null && isTerminalDeliveryRecord(options.previous), nextTerminal = isTerminalDeliveryRecord(options.next), delta = options.previous === null ? 0 : Number(previousTerminal) - Number(nextTerminal), extraConditions = [...options.extraConditions ?? []], extraOperations = [...options.extraOperations ?? []];
|
|
140
|
+
if (delta !== 0) {
|
|
141
|
+
const header = options.header ?? await loadOutboxHeader(runtime.storage, runtime.keys, runtime.policy.namespace, runtime.policy.ownerId);
|
|
142
|
+
if (runtime.disposal.aborted)
|
|
143
|
+
return !1;
|
|
144
|
+
extraConditions.push({ key: runtime.keys.header, expectedValue: header.bytes });
|
|
145
|
+
extraOperations.push(headerOperation(runtime.keys, {
|
|
146
|
+
...header.record,
|
|
147
|
+
openCount: Math.max(0, header.record.openCount + delta)
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
return commitOutboxTransition(runtime.storage, runtime.events, planDeliveryTransition(runtime.keys, {
|
|
151
|
+
previous: options.previous,
|
|
152
|
+
expectedBytes: options.expectedBytes,
|
|
153
|
+
next: options.next,
|
|
154
|
+
event: describeDeliveryTransition(options.previous, options.next),
|
|
155
|
+
now: options.now,
|
|
156
|
+
extraConditions,
|
|
157
|
+
extraOperations
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded, non-consuming listing for the application delivery outbox
|
|
3
|
+
* (WFT-85): walking the sequence-ordered index rather than the delivery
|
|
4
|
+
* records, so `limit` bounds the storage reads and not only the returned
|
|
5
|
+
* slice.
|
|
6
|
+
*
|
|
7
|
+
* @module core/outbox-listing
|
|
8
|
+
*/
|
|
9
|
+
import type { ApplicationDeliveryReceipt, OutboxListOptions } from './outbox-contract.ts';
|
|
10
|
+
import { type OutboxRuntime } from './outbox-internals.ts';
|
|
11
|
+
/** List receipts in enqueue order, bounded by `limit` and the scan ceiling. */
|
|
12
|
+
export declare function listDeliveries(runtime: OutboxRuntime, options: OutboxListOptions | undefined): Promise<ApplicationDeliveryReceipt[]>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { clampListLimit } from "./outbox-guards.js";
|
|
2
|
+
import { decodeApplicationDeliveryEntry } from "./outbox-index-codec.js";
|
|
3
|
+
import { toApplicationDeliveryReceipt } from "./outbox-internals.js";
|
|
4
|
+
import { loadDelivery } from "./outbox-storage.js";
|
|
5
|
+
import { PersistedDataCorruptError } from "./persisted-data-incompatible-error.js";
|
|
6
|
+
const OUTBOX_LIST_PAGE_SIZE = 200, OUTBOX_LIST_SCAN_CEILING = 5000;
|
|
7
|
+
export async function listDeliveries(runtime, options) {
|
|
8
|
+
const limit = clampListLimit(options?.limit), states = options?.states === void 0 ? null : new Set(options.states), receipts = [];
|
|
9
|
+
let cursor, examined = 0;
|
|
10
|
+
while (receipts.length < limit && examined < OUTBOX_LIST_SCAN_CEILING) {
|
|
11
|
+
const page = await readListingPage(runtime, cursor, limit - receipts.length, states);
|
|
12
|
+
receipts.push(...page.receipts);
|
|
13
|
+
examined += page.examined;
|
|
14
|
+
cursor = page.cursor;
|
|
15
|
+
if (page.exhausted)
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
return receipts;
|
|
19
|
+
}
|
|
20
|
+
async function readListingPage(runtime, cursor, remaining, states) {
|
|
21
|
+
const receipts = [], scanOptions = cursor === void 0 ? { limit: OUTBOX_LIST_PAGE_SIZE } : { limit: OUTBOX_LIST_PAGE_SIZE, gt: cursor };
|
|
22
|
+
let seen = 0, nextCursor = cursor;
|
|
23
|
+
for await (const [indexKey, indexValue] of runtime.storage.scan(runtime.keys.bySequencePrefix, scanOptions)) {
|
|
24
|
+
seen += 1;
|
|
25
|
+
nextCursor = indexKey;
|
|
26
|
+
const deliveryId = decodeApplicationDeliveryEntry(indexValue, indexKey), loaded = await loadDelivery(runtime.storage, runtime.keys, deliveryId);
|
|
27
|
+
if (loaded === null)
|
|
28
|
+
continue;
|
|
29
|
+
if (indexKey !== runtime.keys.bySequence(loaded.record.sequence))
|
|
30
|
+
throw new PersistedDataCorruptError(indexKey);
|
|
31
|
+
if (states !== null && !states.has(loaded.record.state))
|
|
32
|
+
continue;
|
|
33
|
+
receipts.push(toApplicationDeliveryReceipt(loaded.record));
|
|
34
|
+
if (receipts.length >= remaining)
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
return { receipts, cursor: nextCursor, exhausted: seen < OUTBOX_LIST_PAGE_SIZE, examined: seen };
|
|
38
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bounded maintenance pass for the application delivery outbox (WFT-85):
|
|
3
|
+
* recovering lapsed leases and retiring terminal receipts past retention.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here runs on a hidden timer under `backgroundTasks: 'manual'`. A
|
|
6
|
+
* pass walks the delivery keyspace in pages of `maintenanceBatchSize`, up to
|
|
7
|
+
* {@link OUTBOX_MAINTENANCE_MAX_PAGES} pages per call, carrying its cursor to
|
|
8
|
+
* the next call when the cap cuts it short.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the mailbox, the outbox has no `accepted → available` release step:
|
|
11
|
+
* the due index is time-keyed, so a delivery becomes claimable by the clock
|
|
12
|
+
* alone, without a maintenance write.
|
|
13
|
+
*
|
|
14
|
+
* @module core/outbox-maintenance
|
|
15
|
+
*/
|
|
16
|
+
import type { OutboxMaintenanceReport } from './outbox-contract.ts';
|
|
17
|
+
import { type OutboxRuntime } from './outbox-internals.ts';
|
|
18
|
+
/** The time-keyed indexes encode `…:<16-digit instant>:<encoded deliveryId>`. */
|
|
19
|
+
export declare function parseSortableInstant(key: string, prefix: string): number | null;
|
|
20
|
+
/**
|
|
21
|
+
* Run one bounded maintenance pass: recover lapsed leases and retire terminal
|
|
22
|
+
* receipts past retention.
|
|
23
|
+
*
|
|
24
|
+
* A corrupt record halts the pass with `PersistedDataCorruptError`: an outbox
|
|
25
|
+
* whose durable state is untrustworthy must not keep recovering and parking
|
|
26
|
+
* deliveries around the damage.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runOutboxMaintenance(runtime: OutboxRuntime, now: number, stop?: AbortSignal): Promise<OutboxMaintenanceReport>;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { formatSortableStorageTimestamp, storageConditionalBatch } from "../storage/interface.js";
|
|
2
|
+
import { leaseCommitSerial } from "./application-primitive-attempt-registry.js";
|
|
3
|
+
import { decodeApplicationDeliveryRecord } from "./outbox-codec.js";
|
|
4
|
+
import {
|
|
5
|
+
decodeApplicationDeliveryEntry,
|
|
6
|
+
decodeApplicationDeliveryIdempotencyRecord
|
|
7
|
+
} from "./outbox-index-codec.js";
|
|
8
|
+
import {
|
|
9
|
+
commitDeliveryTransition,
|
|
10
|
+
MAX_OUTBOX_TRANSITION_ATTEMPTS,
|
|
11
|
+
OUTBOX_MAINTENANCE_MAX_PAGES,
|
|
12
|
+
OutboxContentionError,
|
|
13
|
+
releaseAttemptController,
|
|
14
|
+
releaseAttemptsForDelivery
|
|
15
|
+
} from "./outbox-internals.js";
|
|
16
|
+
import { loadDelivery } from "./outbox-storage.js";
|
|
17
|
+
import { isTerminalDeliveryRecord } from "./outbox-transition-helpers.js";
|
|
18
|
+
import { isDeliveryLeaseExpired, recoverExpiredDelivery } from "./outbox-transitions-recovery.js";
|
|
19
|
+
import { isApplicationDeliveryLeased } from "./outbox-types.js";
|
|
20
|
+
function halted(runtime, stop) {
|
|
21
|
+
return runtime.disposal.aborted || stop?.aborted === !0;
|
|
22
|
+
}
|
|
23
|
+
function countTransition(counters, next) {
|
|
24
|
+
if (next.state === "retry-scheduled")
|
|
25
|
+
counters.rescheduled += 1;
|
|
26
|
+
else if (next.state === "unknown-outcome")
|
|
27
|
+
counters.parked += 1;
|
|
28
|
+
else if (next.state === "dead-lettered")
|
|
29
|
+
counters.deadLettered += 1;
|
|
30
|
+
}
|
|
31
|
+
function reconcileLocalAttempts(runtime, deliveryId, record, observedAt) {
|
|
32
|
+
releaseAttemptsForDelivery(runtime, deliveryId, "This attempt is no longer the current lease on its delivery.", record !== void 0 && isApplicationDeliveryLeased(record) ? record.attemptToken : void 0, observedAt);
|
|
33
|
+
}
|
|
34
|
+
async function recoverDelivery(runtime, deliveryId, now, counters, stop) {
|
|
35
|
+
for (let attempt = 1;attempt <= MAX_OUTBOX_TRANSITION_ATTEMPTS; attempt += 1) {
|
|
36
|
+
if (halted(runtime, stop))
|
|
37
|
+
return;
|
|
38
|
+
const observedAt = leaseCommitSerial(), loaded = await loadDelivery(runtime.storage, runtime.keys, deliveryId);
|
|
39
|
+
if (loaded === null) {
|
|
40
|
+
reconcileLocalAttempts(runtime, deliveryId, void 0, observedAt);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (halted(runtime, stop))
|
|
44
|
+
return;
|
|
45
|
+
if (!isDeliveryLeaseExpired(loaded.record, now)) {
|
|
46
|
+
reconcileLocalAttempts(runtime, deliveryId, loaded.record, observedAt);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const transition = recoverExpiredDelivery(loaded.record, {
|
|
50
|
+
now,
|
|
51
|
+
retryBackoffMs: runtime.policy.retryBackoffMs,
|
|
52
|
+
maxRetryBackoffMs: runtime.policy.maxRetryBackoffMs
|
|
53
|
+
});
|
|
54
|
+
if (!transition.ok)
|
|
55
|
+
return;
|
|
56
|
+
if (!await commitDeliveryTransition(runtime, {
|
|
57
|
+
previous: loaded.record,
|
|
58
|
+
expectedBytes: loaded.bytes,
|
|
59
|
+
next: transition.next,
|
|
60
|
+
now
|
|
61
|
+
}))
|
|
62
|
+
continue;
|
|
63
|
+
if (isApplicationDeliveryLeased(loaded.record))
|
|
64
|
+
releaseAttemptController(runtime, loaded.record.attemptToken, "The application outbox recovered this attempt after its lease lapsed.");
|
|
65
|
+
countTransition(counters, transition.next);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
throw new OutboxContentionError("maintenance", deliveryId);
|
|
69
|
+
}
|
|
70
|
+
async function retireOneReceipt(runtime, indexKey, indexBytes) {
|
|
71
|
+
const deliveryId = deliveryIdFromTerminalKey(indexKey), observedAt = leaseCommitSerial(), loaded = deliveryId === null ? null : await loadDelivery(runtime.storage, runtime.keys, deliveryId);
|
|
72
|
+
if (deliveryId === null || loaded !== null && !ownsTerminalEntry(runtime, indexKey, loaded)) {
|
|
73
|
+
await discardTerminalEntry(runtime, indexKey, indexBytes);
|
|
74
|
+
return !1;
|
|
75
|
+
}
|
|
76
|
+
const operations = [{ type: "delete", key: indexKey }];
|
|
77
|
+
if (loaded === null)
|
|
78
|
+
return storageConditionalBatch(runtime.storage, [{ key: indexKey, expectedValue: indexBytes }], operations);
|
|
79
|
+
operations.push({ type: "delete", key: runtime.keys.delivery(deliveryId) });
|
|
80
|
+
const auxiliary = await ownedAuxiliaryEntries(runtime, loaded);
|
|
81
|
+
operations.push(...auxiliary.operations);
|
|
82
|
+
const retired = await storageConditionalBatch(runtime.storage, [
|
|
83
|
+
{ key: runtime.keys.delivery(deliveryId), expectedValue: loaded.bytes },
|
|
84
|
+
...auxiliary.conditions
|
|
85
|
+
], operations);
|
|
86
|
+
if (retired)
|
|
87
|
+
releaseAttemptsForDelivery(runtime, deliveryId, "This delivery was retired; its attempt is over.", void 0, observedAt);
|
|
88
|
+
return retired;
|
|
89
|
+
}
|
|
90
|
+
async function ownedAuxiliaryEntries(runtime, loaded) {
|
|
91
|
+
const conditions = [], operations = [], sequenceKey = runtime.keys.bySequence(loaded.record.sequence), sequenceBytes = await runtime.storage.get(sequenceKey);
|
|
92
|
+
if (sequenceBytes !== null && decodeApplicationDeliveryEntry(sequenceBytes, sequenceKey) === loaded.record.deliveryId) {
|
|
93
|
+
conditions.push({ key: sequenceKey, expectedValue: sequenceBytes });
|
|
94
|
+
operations.push({ type: "delete", key: sequenceKey });
|
|
95
|
+
}
|
|
96
|
+
if (loaded.record.idempotencyKey === void 0)
|
|
97
|
+
return { conditions, operations };
|
|
98
|
+
const bindingKey = runtime.keys.idempotency(loaded.record.idempotencyKey), bindingBytes = await runtime.storage.get(bindingKey);
|
|
99
|
+
if (bindingBytes !== null && decodeApplicationDeliveryIdempotencyRecord(bindingBytes, bindingKey).deliveryId === loaded.record.deliveryId) {
|
|
100
|
+
conditions.push({ key: bindingKey, expectedValue: bindingBytes });
|
|
101
|
+
operations.push({ type: "delete", key: bindingKey });
|
|
102
|
+
}
|
|
103
|
+
return { conditions, operations };
|
|
104
|
+
}
|
|
105
|
+
function ownsTerminalEntry(runtime, indexKey, loaded) {
|
|
106
|
+
return isTerminalDeliveryRecord(loaded.record) && runtime.keys.terminal(loaded.record.terminalAt, loaded.record.deliveryId) === indexKey;
|
|
107
|
+
}
|
|
108
|
+
async function discardTerminalEntry(runtime, indexKey, indexBytes) {
|
|
109
|
+
await storageConditionalBatch(runtime.storage, [{ key: indexKey, expectedValue: indexBytes }], [{ type: "delete", key: indexKey }]);
|
|
110
|
+
}
|
|
111
|
+
async function retireTerminalReceipts(runtime, now, counters, stop) {
|
|
112
|
+
const horizon = now - runtime.policy.terminalRetentionMs;
|
|
113
|
+
if (horizon < 0)
|
|
114
|
+
return;
|
|
115
|
+
const expired = [], malformed = [];
|
|
116
|
+
for await (const [key, value] of runtime.storage.scan(runtime.keys.terminalPrefix, {
|
|
117
|
+
limit: runtime.policy.maintenanceBatchSize
|
|
118
|
+
})) {
|
|
119
|
+
const terminalAt = parseSortableInstant(key, runtime.keys.terminalPrefix);
|
|
120
|
+
if (terminalAt === null) {
|
|
121
|
+
malformed.push([key, value]);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (terminalAt >= horizon)
|
|
125
|
+
break;
|
|
126
|
+
expired.push([key, value]);
|
|
127
|
+
}
|
|
128
|
+
for (const [indexKey, bytes] of malformed) {
|
|
129
|
+
if (halted(runtime, stop))
|
|
130
|
+
return;
|
|
131
|
+
await discardTerminalEntry(runtime, indexKey, bytes);
|
|
132
|
+
}
|
|
133
|
+
for (const [indexKey, bytes] of expired) {
|
|
134
|
+
if (halted(runtime, stop))
|
|
135
|
+
return;
|
|
136
|
+
if (await retireOneReceipt(runtime, indexKey, bytes))
|
|
137
|
+
counters.retired += 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function parseSortableInstant(key, prefix) {
|
|
141
|
+
const suffix = key.slice(prefix.length), separator = suffix.indexOf(":");
|
|
142
|
+
if (separator === -1)
|
|
143
|
+
return null;
|
|
144
|
+
const segment = suffix.slice(0, separator), parsed = Number(segment);
|
|
145
|
+
return Number.isSafeInteger(parsed) && formatSortableStorageTimestamp(parsed) === segment ? parsed : null;
|
|
146
|
+
}
|
|
147
|
+
function deliveryIdFromTerminalKey(key) {
|
|
148
|
+
try {
|
|
149
|
+
return decodeURIComponent(key.slice(key.lastIndexOf(":") + 1));
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function collectLapsedDeliveries(runtime, now, startAfter, stop) {
|
|
155
|
+
const batchSize = runtime.policy.maintenanceBatchSize, lapsed = [];
|
|
156
|
+
let cursor = startAfter;
|
|
157
|
+
for (let page = 0;page < OUTBOX_MAINTENANCE_MAX_PAGES; page += 1) {
|
|
158
|
+
let seen = 0;
|
|
159
|
+
const options = cursor === void 0 ? { limit: batchSize } : { limit: batchSize, gt: cursor }, observedAt = leaseCommitSerial();
|
|
160
|
+
if (halted(runtime, stop))
|
|
161
|
+
return { lapsed: [], nextCursor: startAfter };
|
|
162
|
+
for await (const [key, value] of runtime.storage.scan(runtime.keys.deliveryPrefix, options)) {
|
|
163
|
+
seen += 1;
|
|
164
|
+
cursor = key;
|
|
165
|
+
const record = decodeApplicationDeliveryRecord(value, key);
|
|
166
|
+
reconcileLocalAttempts(runtime, record.deliveryId, record, observedAt);
|
|
167
|
+
if (isDeliveryLeaseExpired(record, now))
|
|
168
|
+
lapsed.push(record.deliveryId);
|
|
169
|
+
}
|
|
170
|
+
if (seen < batchSize)
|
|
171
|
+
return { lapsed, nextCursor: void 0 };
|
|
172
|
+
}
|
|
173
|
+
return { lapsed, nextCursor: cursor };
|
|
174
|
+
}
|
|
175
|
+
export async function runOutboxMaintenance(runtime, now, stop) {
|
|
176
|
+
const counters = { rescheduled: 0, parked: 0, deadLettered: 0, retired: 0 }, previousCursor = runtime.readMaintenanceCursor(), scan = await collectLapsedDeliveries(runtime, now, previousCursor, stop);
|
|
177
|
+
try {
|
|
178
|
+
for (const deliveryId of scan.lapsed) {
|
|
179
|
+
if (halted(runtime, stop))
|
|
180
|
+
return Object.freeze({ ...counters });
|
|
181
|
+
await recoverDelivery(runtime, deliveryId, now, counters, stop);
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
runtime.writeMaintenanceCursor(previousCursor);
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
runtime.writeMaintenanceCursor(scan.nextCursor);
|
|
188
|
+
if (!halted(runtime, stop))
|
|
189
|
+
await retireTerminalReceipts(runtime, now, counters, stop);
|
|
190
|
+
return Object.freeze({ ...counters });
|
|
191
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator transitions for the application delivery outbox (WFT-85): retrying
|
|
3
|
+
* a parked, dead-lettered, or rejected delivery, and closing a parked one for
|
|
4
|
+
* good.
|
|
5
|
+
*
|
|
6
|
+
* Both are ordinary compare-and-swap transitions on a terminal record. A retry
|
|
7
|
+
* reopens the delivery — its open-count and due-index maintenance ride in the
|
|
8
|
+
* same batch — and a dead-letter moves its retention entry to the new terminal
|
|
9
|
+
* instant.
|
|
10
|
+
*
|
|
11
|
+
* @module core/outbox-operations
|
|
12
|
+
*/
|
|
13
|
+
import type { ApplicationDeliveryOperatorResult } from './outbox-contract.ts';
|
|
14
|
+
import { type OutboxRuntime } from './outbox-internals.ts';
|
|
15
|
+
/** Return a parked, dead-lettered, or rejected delivery to the due index with at least one more attempt: an unspent budget is kept, a spent one is raised by one. */
|
|
16
|
+
export declare function retryDelivery(runtime: OutboxRuntime, deliveryId: string): Promise<ApplicationDeliveryOperatorResult>;
|
|
17
|
+
/** Close a parked `unknown-outcome` delivery as dead-lettered. */
|
|
18
|
+
export declare function deadLetterDelivery(runtime: OutboxRuntime, deliveryId: string, reason: string | undefined): Promise<ApplicationDeliveryOperatorResult>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { capacityOf } from "./outbox-enqueue.js";
|
|
2
|
+
import {
|
|
3
|
+
commitDeliveryTransition,
|
|
4
|
+
MAX_OUTBOX_TRANSITION_ATTEMPTS,
|
|
5
|
+
OutboxContentionError,
|
|
6
|
+
toApplicationDeliveryReceipt
|
|
7
|
+
} from "./outbox-internals.js";
|
|
8
|
+
import { loadDelivery, loadOutboxHeader } from "./outbox-storage.js";
|
|
9
|
+
import { isTerminalDeliveryRecord } from "./outbox-transition-helpers.js";
|
|
10
|
+
import {
|
|
11
|
+
deadLetterDeliveryByOperator,
|
|
12
|
+
retryDeliveryByOperator
|
|
13
|
+
} from "./outbox-transitions.js";
|
|
14
|
+
async function operate(runtime, deliveryId, operation, decide) {
|
|
15
|
+
for (let attempt = 1;attempt <= MAX_OUTBOX_TRANSITION_ATTEMPTS; attempt += 1) {
|
|
16
|
+
const loaded = await loadDelivery(runtime.storage, runtime.keys, deliveryId);
|
|
17
|
+
if (loaded === null)
|
|
18
|
+
return { status: "unknown" };
|
|
19
|
+
const now = runtime.now(), transition = decide(loaded.record, now);
|
|
20
|
+
if (!transition.ok)
|
|
21
|
+
return { status: "not-applicable", receipt: toApplicationDeliveryReceipt(loaded.record) };
|
|
22
|
+
const header = await loadOutboxHeader(runtime.storage, runtime.keys, runtime.policy.namespace, runtime.policy.ownerId);
|
|
23
|
+
if (!isTerminalDeliveryRecord(transition.next) && header.record.openCount >= runtime.policy.maxBacklog)
|
|
24
|
+
return {
|
|
25
|
+
status: "rejected",
|
|
26
|
+
reason: "backlog-full",
|
|
27
|
+
capacity: capacityOf(runtime, header.record.openCount, header.record.enqueuedCount)
|
|
28
|
+
};
|
|
29
|
+
if (!await commitDeliveryTransition(runtime, {
|
|
30
|
+
previous: loaded.record,
|
|
31
|
+
expectedBytes: loaded.bytes,
|
|
32
|
+
next: transition.next,
|
|
33
|
+
now,
|
|
34
|
+
header
|
|
35
|
+
}))
|
|
36
|
+
continue;
|
|
37
|
+
return { status: "applied", receipt: toApplicationDeliveryReceipt(transition.next) };
|
|
38
|
+
}
|
|
39
|
+
throw new OutboxContentionError(operation, deliveryId);
|
|
40
|
+
}
|
|
41
|
+
export function retryDelivery(runtime, deliveryId) {
|
|
42
|
+
return operate(runtime, deliveryId, "retry", (record, now) => retryDeliveryByOperator(record, { now }));
|
|
43
|
+
}
|
|
44
|
+
export function deadLetterDelivery(runtime, deliveryId, reason) {
|
|
45
|
+
return operate(runtime, deliveryId, "deadLetter", (record, now) => deadLetterDeliveryByOperator(record, { now, reason }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The adapter runner for the application delivery outbox (WFT-85): one due
|
|
3
|
+
* delivery claimed, durably marked `attempting`, handed to the configured
|
|
4
|
+
* transport, and settled on what the transport reported. The bounded drain
|
|
5
|
+
* that repeats it lives in `outbox-drain.ts`.
|
|
6
|
+
*
|
|
7
|
+
* The runner never trusts the adapter's completion as a disposition. It
|
|
8
|
+
* validates the outcome, then commits the matching transition fenced on the
|
|
9
|
+
* attempt token and the `attempting` bytes; only that commit moves the record.
|
|
10
|
+
* A thrown adapter error is an unknown outcome, because the request may have
|
|
11
|
+
* left the process. When the attempt deadline elapses while the send is in
|
|
12
|
+
* flight the runner stops waiting, treats the result as unknown, and aborts
|
|
13
|
+
* the adapter's signal as it releases the attempt.
|
|
14
|
+
*
|
|
15
|
+
* @module core/outbox-runner
|
|
16
|
+
*/
|
|
17
|
+
import type { ApplicationDeliveryAdapter, OutboxDeliverResult } from './outbox-contract.ts';
|
|
18
|
+
import { type OutboxRuntime } from './outbox-internals.ts';
|
|
19
|
+
/**
|
|
20
|
+
* Claim the earliest due delivery, run it through the adapter, and settle it.
|
|
21
|
+
*
|
|
22
|
+
* The `attempting` commit happens before the adapter is called; a claim whose
|
|
23
|
+
* begin is refused (cancelled or reclaimed between the two) is released
|
|
24
|
+
* without calling the transport.
|
|
25
|
+
*/
|
|
26
|
+
export declare function deliverNext(runtime: OutboxRuntime, options?: {
|
|
27
|
+
readonly signal?: AbortSignal | undefined;
|
|
28
|
+
}): Promise<OutboxDeliverResult>;
|
|
29
|
+
/** The configured adapter, or the caller-mistake diagnostic when there is none. */
|
|
30
|
+
export declare function requireAdapter(runtime: OutboxRuntime): ApplicationDeliveryAdapter;
|