@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,97 @@
|
|
|
1
|
+
import { WaitBudgetElapsedError, raceAbortWithin } from "./application-primitive-abort.js";
|
|
2
|
+
import { delayUnlessAborted } from "./application-primitive-timing.js";
|
|
3
|
+
import { decodeApplicationReadyEntry } from "./mailbox-index-codec.js";
|
|
4
|
+
import { readCleanupState } from "./mailbox-settlement.js";
|
|
5
|
+
import { isWaitingState, loadCommand } from "./mailbox-storage.js";
|
|
6
|
+
import {
|
|
7
|
+
requireClockInstant,
|
|
8
|
+
requireDerivedInstant,
|
|
9
|
+
requireWaitBudget
|
|
10
|
+
} from "./mailbox-validation.js";
|
|
11
|
+
export { DEFAULT_WAIT_POLL_INTERVAL_MS as DEFAULT_MAILBOX_POLL_INTERVAL_MS } from "./mailbox-validation.js";
|
|
12
|
+
export { delayUnlessAborted };
|
|
13
|
+
export async function hasDueWork(runtime) {
|
|
14
|
+
for await (const [key, value] of runtime.storage.scan(runtime.keys.readyPrefix, {
|
|
15
|
+
limit: DUE_WORK_LOOKAHEAD
|
|
16
|
+
})) {
|
|
17
|
+
const loaded = await loadCommand(runtime.storage, runtime.keys, decodeApplicationReadyEntry(value, key));
|
|
18
|
+
if (loaded === null || !isWaitingState(loaded.record))
|
|
19
|
+
continue;
|
|
20
|
+
if (key !== runtime.keys.ready(loaded.record.sequence))
|
|
21
|
+
continue;
|
|
22
|
+
const now = runtime.now();
|
|
23
|
+
if (now >= loaded.record.absoluteDeadlineAt)
|
|
24
|
+
continue;
|
|
25
|
+
return now >= loaded.record.availableAt;
|
|
26
|
+
}
|
|
27
|
+
return !1;
|
|
28
|
+
}
|
|
29
|
+
const DUE_WORK_LOOKAHEAD = 8;
|
|
30
|
+
export async function waitForAvailableWork(runtime, disposal, options) {
|
|
31
|
+
const { timeoutMs, pollIntervalMs } = requireWaitBudget(options ?? {}), deadline = requireDerivedInstant(requireClockInstant(runtime.now()) + timeoutMs, "deadline");
|
|
32
|
+
let remaining = 0;
|
|
33
|
+
do {
|
|
34
|
+
const observed = await observeDueWork(runtime, disposal, options?.signal, timeoutMs, deadline);
|
|
35
|
+
if (observed === null || isAborted(disposal, options?.signal))
|
|
36
|
+
return !1;
|
|
37
|
+
if (observed)
|
|
38
|
+
return observedInTime(timeoutMs, deadline, runtime.now());
|
|
39
|
+
remaining = deadline - runtime.now();
|
|
40
|
+
if (remaining <= 0)
|
|
41
|
+
return !1;
|
|
42
|
+
} while (await delayUnlessAborted(Math.min(pollIntervalMs, remaining), disposal, options?.signal));
|
|
43
|
+
return !1;
|
|
44
|
+
}
|
|
45
|
+
async function observeDueWork(runtime, disposal, signal, timeoutMs, deadline) {
|
|
46
|
+
if (budgetSpent(timeoutMs, deadline, runtime.now()))
|
|
47
|
+
return null;
|
|
48
|
+
const observed = await raceAbortWithin(() => hasDueWork(runtime), budgetFor(timeoutMs, deadline, runtime.now()), disposal, signal);
|
|
49
|
+
return observed.aborted ? null : observed.value;
|
|
50
|
+
}
|
|
51
|
+
function budgetSpent(timeoutMs, deadline, now) {
|
|
52
|
+
return timeoutMs > 0 && now >= deadline;
|
|
53
|
+
}
|
|
54
|
+
function budgetFor(timeoutMs, deadline, now) {
|
|
55
|
+
if (timeoutMs === 0)
|
|
56
|
+
return null;
|
|
57
|
+
const remaining = deadline - now;
|
|
58
|
+
return remaining > 0 ? remaining : null;
|
|
59
|
+
}
|
|
60
|
+
function observedInTime(timeoutMs, deadline, now) {
|
|
61
|
+
return timeoutMs === 0 || now <= deadline;
|
|
62
|
+
}
|
|
63
|
+
function isAborted(disposal, signal) {
|
|
64
|
+
return disposal.aborted || signal?.aborted === !0;
|
|
65
|
+
}
|
|
66
|
+
export async function waitForCleanup(runtime, disposal, options) {
|
|
67
|
+
const { timeoutMs, pollIntervalMs } = requireWaitBudget(options), deadline = requireDerivedInstant(requireClockInstant(runtime.now()) + timeoutMs, "deadline"), first = await readUnlessAborted(runtime, options.commandId, disposal, options.signal, budgetFor(timeoutMs, deadline, runtime.now()));
|
|
68
|
+
if (first === null)
|
|
69
|
+
throw new WaitBudgetElapsedError;
|
|
70
|
+
let latest = first;
|
|
71
|
+
if (latest.status === "pending" && latest.receipt.terminalAt !== void 0)
|
|
72
|
+
return latest;
|
|
73
|
+
while (latest.status === "pending") {
|
|
74
|
+
const remaining = deadline - runtime.now();
|
|
75
|
+
if (remaining <= 0)
|
|
76
|
+
break;
|
|
77
|
+
if (!await delayUnlessAborted(Math.min(pollIntervalMs, remaining), disposal, options.signal)) {
|
|
78
|
+
options.signal?.throwIfAborted();
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (budgetSpent(timeoutMs, deadline, runtime.now()))
|
|
82
|
+
break;
|
|
83
|
+
const next = await readUnlessAborted(runtime, options.commandId, disposal, options.signal, budgetFor(timeoutMs, deadline, runtime.now()));
|
|
84
|
+
if (next === null)
|
|
85
|
+
break;
|
|
86
|
+
latest = next;
|
|
87
|
+
}
|
|
88
|
+
return latest;
|
|
89
|
+
}
|
|
90
|
+
async function readUnlessAborted(runtime, commandId, disposal, signal, budgetMs) {
|
|
91
|
+
const raced = await raceAbortWithin(() => readCleanupState(runtime, commandId), budgetMs, disposal, signal);
|
|
92
|
+
if (!raced.aborted)
|
|
93
|
+
return raced.value;
|
|
94
|
+
if (raced.reason instanceof WaitBudgetElapsedError)
|
|
95
|
+
return null;
|
|
96
|
+
throw raced.reason;
|
|
97
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The durable application command mailbox (WFT-84).
|
|
3
|
+
*
|
|
4
|
+
* A mailbox is a storage-backed, strictly FIFO command queue scoped to one
|
|
5
|
+
* opaque `(namespace, resourceId)` pair. Admission returns a durable receipt
|
|
6
|
+
* that survives process restart; delivery is attempt-fenced so two consumers
|
|
7
|
+
* can never both hold a valid claim; cancellation is durable before it reaches
|
|
8
|
+
* anyone; and every state transition commits atomically with its fleet event
|
|
9
|
+
* when an event sink is configured.
|
|
10
|
+
*
|
|
11
|
+
* What it is not: a message broker. There are no topics, no consumer groups, no
|
|
12
|
+
* cross-mailbox ordering, and no cross-region replication. Ordering is defined
|
|
13
|
+
* within one mailbox and nowhere else.
|
|
14
|
+
*
|
|
15
|
+
* @module core/mailbox
|
|
16
|
+
*/
|
|
17
|
+
import type { Storage } from '../storage/interface.ts';
|
|
18
|
+
import type { JSONValue } from './json.ts';
|
|
19
|
+
import type { ApplicationCommandAdmission, ApplicationCommandCancellationResult, ApplicationCommandCleanupResult, ApplicationCommandInput, ApplicationCommandReceipt, ApplicationCommandRejection, ApplicationCommandRenewalResult, ApplicationCommandSettleResult, MailboxCapacity, MailboxClaimResult, MailboxListOptions, MailboxMaintenanceReport, MailboxOptions, MailboxWaitOptions } from './mailbox-contract.ts';
|
|
20
|
+
/**
|
|
21
|
+
* A durable, strictly FIFO application command mailbox.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { Mailbox, MemoryStorage } from '@lostgradient/weft';
|
|
26
|
+
*
|
|
27
|
+
* await using storage = new MemoryStorage();
|
|
28
|
+
* using mailbox = new Mailbox({ storage, namespace: 'bureau', resourceId: 'agent-7' });
|
|
29
|
+
*
|
|
30
|
+
* const admission = await mailbox.admit({
|
|
31
|
+
* caller: 'user:42',
|
|
32
|
+
* target: 'agent:7',
|
|
33
|
+
* kind: 'steer',
|
|
34
|
+
* payload: { form: 'inline', value: { text: 'stop' } },
|
|
35
|
+
* idempotencyKey: 'steer-1',
|
|
36
|
+
* });
|
|
37
|
+
* console.log(admission.status); // 'admitted'
|
|
38
|
+
*
|
|
39
|
+
* const claimed = await mailbox.claim();
|
|
40
|
+
* console.log(claimed.status); // 'claimed'
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare class Mailbox {
|
|
44
|
+
#private;
|
|
45
|
+
constructor(options: MailboxOptions);
|
|
46
|
+
/** The opaque application namespace this mailbox is scoped to. */
|
|
47
|
+
get namespace(): string;
|
|
48
|
+
/** The opaque resource identifier this mailbox is scoped to. */
|
|
49
|
+
get resourceId(): string;
|
|
50
|
+
/** The durable backend every transition compares and swaps against. */
|
|
51
|
+
get storage(): Storage;
|
|
52
|
+
/**
|
|
53
|
+
* Offer a command to the mailbox.
|
|
54
|
+
*
|
|
55
|
+
* An exact retry of the same idempotency identity returns the original
|
|
56
|
+
* receipt without creating a second command. Reusing the key with a different
|
|
57
|
+
* caller, target, kind, or payload digest returns a conflict and leaves the
|
|
58
|
+
* original command untouched. A full backlog is rejected before anything is
|
|
59
|
+
* persisted.
|
|
60
|
+
*/
|
|
61
|
+
admit(command: ApplicationCommandInput): Promise<ApplicationCommandAdmission>;
|
|
62
|
+
/** Read one command's immutable receipt, or `null` when it is unknown or retired. */
|
|
63
|
+
receipt(commandId: string): Promise<ApplicationCommandReceipt | null>;
|
|
64
|
+
/**
|
|
65
|
+
* List receipts in this mailbox, bounded and non-consuming.
|
|
66
|
+
*
|
|
67
|
+
* Listing never claims, starts, or advances work, so any number of observers
|
|
68
|
+
* can call it concurrently without interfering with delivery.
|
|
69
|
+
*/
|
|
70
|
+
list(options?: MailboxListOptions): Promise<ApplicationCommandReceipt[]>;
|
|
71
|
+
/** Current backlog accounting. Deliberately low-cardinality: counts only. */
|
|
72
|
+
capacity(): Promise<MailboxCapacity>;
|
|
73
|
+
/**
|
|
74
|
+
* Lease the FIFO head of this mailbox to one attempt.
|
|
75
|
+
*
|
|
76
|
+
* Strict FIFO is the contract: when the head is not due yet, `claim()` reports
|
|
77
|
+
* `held` rather than skipping ahead to a later command. The returned claim
|
|
78
|
+
* carries an attempt-scoped `AbortSignal` that fires on cancellation, lease
|
|
79
|
+
* release, or mailbox disposal.
|
|
80
|
+
*/
|
|
81
|
+
claim(options?: {
|
|
82
|
+
readonly signal?: AbortSignal | undefined;
|
|
83
|
+
}): Promise<MailboxClaimResult>;
|
|
84
|
+
/** Extend a lease and report liveness for the current attempt. */
|
|
85
|
+
renew(options: {
|
|
86
|
+
readonly commandId: string;
|
|
87
|
+
readonly attemptToken: string;
|
|
88
|
+
readonly progress?: JSONValue | undefined;
|
|
89
|
+
}): Promise<ApplicationCommandRenewalResult>;
|
|
90
|
+
/** Settle a claimed command successfully. */
|
|
91
|
+
acknowledge(options: {
|
|
92
|
+
readonly commandId: string;
|
|
93
|
+
readonly attemptToken: string;
|
|
94
|
+
readonly outcome?: JSONValue | undefined;
|
|
95
|
+
}): Promise<ApplicationCommandSettleResult>;
|
|
96
|
+
/** Settle a claimed command as failed, optionally scheduling a retry. */
|
|
97
|
+
reject(options: {
|
|
98
|
+
readonly commandId: string;
|
|
99
|
+
readonly attemptToken: string;
|
|
100
|
+
readonly failure: ApplicationCommandRejection;
|
|
101
|
+
readonly retry?: boolean | undefined;
|
|
102
|
+
}): Promise<ApplicationCommandSettleResult>;
|
|
103
|
+
/** Durably request cancellation and abort an in-process claimant. */
|
|
104
|
+
requestCancellation(options: {
|
|
105
|
+
readonly commandId: string;
|
|
106
|
+
readonly reason?: string | undefined;
|
|
107
|
+
}): Promise<ApplicationCommandCancellationResult>;
|
|
108
|
+
/**
|
|
109
|
+
* Read whether a cancelled command's claimant has finished. Non-consuming and
|
|
110
|
+
* safe to call from any number of observers.
|
|
111
|
+
*/
|
|
112
|
+
cleanupState(commandId: string): Promise<ApplicationCommandCleanupResult>;
|
|
113
|
+
/**
|
|
114
|
+
* Wait, bounded, for a cancelled command's claimant to settle.
|
|
115
|
+
*
|
|
116
|
+
* A `pending` result means this mailbox stopped waiting — never that the
|
|
117
|
+
* handler stopped. A caller signal that aborts at any point — before a read,
|
|
118
|
+
* during one, or during the sleep between polls — rejects the wait with that
|
|
119
|
+
* signal's reason; disposing the mailbox during a read rejects with the
|
|
120
|
+
* disposal error, and during a sleep returns the last observation. The
|
|
121
|
+
* budget bounds the reads themselves as well as the sleeps between them: a
|
|
122
|
+
* budget that runs out during a later read returns the last observation, and
|
|
123
|
+
* one that runs out during the first read, with nothing observed yet,
|
|
124
|
+
* rejects with `WaitBudgetElapsedError`.
|
|
125
|
+
*/
|
|
126
|
+
awaitCleanup(options: {
|
|
127
|
+
readonly commandId: string;
|
|
128
|
+
readonly timeoutMs: number;
|
|
129
|
+
readonly signal?: AbortSignal | undefined;
|
|
130
|
+
readonly pollIntervalMs?: number | undefined;
|
|
131
|
+
}): Promise<ApplicationCommandCleanupResult>;
|
|
132
|
+
/**
|
|
133
|
+
* Wait, bounded and abortably, until this mailbox has work due for delivery.
|
|
134
|
+
*
|
|
135
|
+
* Returns `true` when the FIFO head is claimable. Aborting, disposal, or the
|
|
136
|
+
* timeout returns `false` and releases every process-local resource without
|
|
137
|
+
* touching durable work.
|
|
138
|
+
*
|
|
139
|
+
* `timeoutMs` defaults to `0`, so calling this with no options checks once and
|
|
140
|
+
* returns immediately rather than blocking. Pass a timeout to actually wait.
|
|
141
|
+
*/
|
|
142
|
+
waitForAvailable(options?: MailboxWaitOptions): Promise<boolean>;
|
|
143
|
+
/**
|
|
144
|
+
* Run one bounded maintenance pass: release due commands, reclaim expired
|
|
145
|
+
* leases at their original FIFO position, dead-letter commands past their
|
|
146
|
+
* absolute deadline, and retire terminal receipts past retention.
|
|
147
|
+
*
|
|
148
|
+
* Nothing in this mailbox runs on a hidden timer, so a host configured with
|
|
149
|
+
* `backgroundTasks: 'manual'` drives every time-based transition through this
|
|
150
|
+
* one call.
|
|
151
|
+
*/
|
|
152
|
+
runMaintenance(now?: number): Promise<MailboxMaintenanceReport>;
|
|
153
|
+
/**
|
|
154
|
+
* Release every process-local resource: abort in-flight waits and every
|
|
155
|
+
* attempt-scoped signal this process holds.
|
|
156
|
+
*
|
|
157
|
+
* Disposal never deletes durable work. A claim this process held stays leased
|
|
158
|
+
* until its visibility expires and maintenance reclaims it.
|
|
159
|
+
*/
|
|
160
|
+
dispose(): void;
|
|
161
|
+
/** `using`-compatible disposal. */
|
|
162
|
+
[Symbol.dispose](): void;
|
|
163
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attemptControllerRegistry,
|
|
3
|
+
releaseAttemptControllerRegistry
|
|
4
|
+
} from "./application-primitive-attempt-registry.js";
|
|
5
|
+
import { admitCommand, capacityOf } from "./mailbox-admission.js";
|
|
6
|
+
import { claimNextCommand } from "./mailbox-delivery.js";
|
|
7
|
+
import { decodeApplicationReadyEntry } from "./mailbox-index-codec.js";
|
|
8
|
+
import {
|
|
9
|
+
MAILBOX_PRIMITIVE,
|
|
10
|
+
toApplicationCommandReceipt
|
|
11
|
+
} from "./mailbox-internals.js";
|
|
12
|
+
import { runMailboxMaintenance } from "./mailbox-maintenance.js";
|
|
13
|
+
import {
|
|
14
|
+
acknowledgeClaim,
|
|
15
|
+
readCleanupState,
|
|
16
|
+
rejectClaim,
|
|
17
|
+
renewClaim,
|
|
18
|
+
requestCancellation
|
|
19
|
+
} from "./mailbox-settlement.js";
|
|
20
|
+
import { createMailboxKeys, loadCommand, loadMailboxHeader } from "./mailbox-storage.js";
|
|
21
|
+
import {
|
|
22
|
+
ApplicationCommandValidationError,
|
|
23
|
+
clampListLimit,
|
|
24
|
+
requireClockInstant,
|
|
25
|
+
requireMaintenanceInstant,
|
|
26
|
+
resolveMailboxPolicy,
|
|
27
|
+
validateCancellationReason,
|
|
28
|
+
validateCommandIdentifier,
|
|
29
|
+
validateDurableJSONValue,
|
|
30
|
+
validateFailure
|
|
31
|
+
} from "./mailbox-validation.js";
|
|
32
|
+
import { waitForAvailableWork, waitForCleanup } from "./mailbox-waits.js";
|
|
33
|
+
import { PersistedDataCorruptError } from "./persisted-data-incompatible-error.js";
|
|
34
|
+
const MAILBOX_LIST_PAGE_SIZE = 200, MAILBOX_LIST_SCAN_CEILING = 5000;
|
|
35
|
+
|
|
36
|
+
export class Mailbox {
|
|
37
|
+
#runtime;
|
|
38
|
+
#disposal = new AbortController;
|
|
39
|
+
#ownAttempts = new Set;
|
|
40
|
+
#maintenanceCursor;
|
|
41
|
+
#disposed = !1;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
const policy = resolveMailboxPolicy(options), capabilities = options.storage.capabilities();
|
|
44
|
+
if (!capabilities.conditionalBatch)
|
|
45
|
+
throw new ApplicationCommandValidationError("Application mailboxes require storage with conditional batch support: every transition is a compare-and-swap.");
|
|
46
|
+
if (capabilities.scanConsistency !== "snapshot")
|
|
47
|
+
throw new ApplicationCommandValidationError("Application mailboxes require storage with snapshot scan consistency: strict FIFO delivery reads the index head, and a best-effort scan can return a later command ahead of an earlier one.");
|
|
48
|
+
if (capabilities.readAfterWrite !== "linearizable")
|
|
49
|
+
throw new ApplicationCommandValidationError("Application mailboxes require storage with linearizable read-after-write: strict FIFO delivery across processes needs every consumer to see every durable admission.");
|
|
50
|
+
this.#runtime = {
|
|
51
|
+
storage: options.storage,
|
|
52
|
+
events: options.events,
|
|
53
|
+
policy,
|
|
54
|
+
keys: createMailboxKeys(policy.namespace, policy.resourceId),
|
|
55
|
+
now: () => requireClockInstant((options.now ?? Date.now)()),
|
|
56
|
+
generateId: options.generateId ?? (() => crypto.randomUUID()),
|
|
57
|
+
attemptControllers: attemptControllerRegistry(options.storage, MAILBOX_PRIMITIVE, policy.namespace, policy.resourceId),
|
|
58
|
+
adoptAttempt: (attemptToken) => {
|
|
59
|
+
if (this.#disposed)
|
|
60
|
+
return null;
|
|
61
|
+
this.#ownAttempts.add(attemptToken);
|
|
62
|
+
return () => {
|
|
63
|
+
this.#ownAttempts.delete(attemptToken);
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
readMaintenanceCursor: () => this.#maintenanceCursor,
|
|
67
|
+
writeMaintenanceCursor: (cursor) => {
|
|
68
|
+
this.#maintenanceCursor = cursor;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
get namespace() {
|
|
73
|
+
return this.#runtime.policy.namespace;
|
|
74
|
+
}
|
|
75
|
+
get resourceId() {
|
|
76
|
+
return this.#runtime.policy.resourceId;
|
|
77
|
+
}
|
|
78
|
+
get storage() {
|
|
79
|
+
return this.#runtime.storage;
|
|
80
|
+
}
|
|
81
|
+
#assertLive() {
|
|
82
|
+
if (this.#disposed)
|
|
83
|
+
throw new ApplicationCommandValidationError("This application mailbox has been disposed.");
|
|
84
|
+
}
|
|
85
|
+
async admit(command) {
|
|
86
|
+
this.#assertLive();
|
|
87
|
+
return admitCommand(this.#runtime, command);
|
|
88
|
+
}
|
|
89
|
+
async receipt(commandId) {
|
|
90
|
+
this.#assertLive();
|
|
91
|
+
const loaded = await loadCommand(this.#runtime.storage, this.#runtime.keys, validateCommandIdentifier(commandId));
|
|
92
|
+
return loaded === null ? null : toApplicationCommandReceipt(loaded.record);
|
|
93
|
+
}
|
|
94
|
+
async list(options) {
|
|
95
|
+
this.#assertLive();
|
|
96
|
+
const limit = clampListLimit(options?.limit), states = options?.states === void 0 ? null : new Set(options.states), receipts = [];
|
|
97
|
+
let cursor, examined = 0;
|
|
98
|
+
while (receipts.length < limit && examined < MAILBOX_LIST_SCAN_CEILING) {
|
|
99
|
+
const page = await this.#readListingPage(cursor, limit - receipts.length, states);
|
|
100
|
+
receipts.push(...page.receipts);
|
|
101
|
+
examined += page.examined;
|
|
102
|
+
cursor = page.cursor;
|
|
103
|
+
if (page.exhausted)
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
return receipts;
|
|
107
|
+
}
|
|
108
|
+
async#readListingPage(cursor, remaining, states) {
|
|
109
|
+
const receipts = [], scanOptions = cursor === void 0 ? { limit: MAILBOX_LIST_PAGE_SIZE } : { limit: MAILBOX_LIST_PAGE_SIZE, gt: cursor };
|
|
110
|
+
let seen = 0, nextCursor = cursor;
|
|
111
|
+
for await (const [indexKey, indexValue] of this.#runtime.storage.scan(this.#runtime.keys.bySequencePrefix, scanOptions)) {
|
|
112
|
+
seen += 1;
|
|
113
|
+
nextCursor = indexKey;
|
|
114
|
+
const commandId = decodeApplicationReadyEntry(indexValue, indexKey), loaded = await loadCommand(this.#runtime.storage, this.#runtime.keys, commandId);
|
|
115
|
+
if (loaded === null)
|
|
116
|
+
continue;
|
|
117
|
+
if (indexKey !== this.#runtime.keys.bySequence(loaded.record.sequence))
|
|
118
|
+
throw new PersistedDataCorruptError(indexKey);
|
|
119
|
+
if (states !== null && !states.has(loaded.record.state))
|
|
120
|
+
continue;
|
|
121
|
+
receipts.push(toApplicationCommandReceipt(loaded.record));
|
|
122
|
+
if (receipts.length >= remaining)
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
receipts,
|
|
127
|
+
cursor: nextCursor,
|
|
128
|
+
exhausted: seen < MAILBOX_LIST_PAGE_SIZE,
|
|
129
|
+
examined: seen
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async capacity() {
|
|
133
|
+
this.#assertLive();
|
|
134
|
+
const header = await loadMailboxHeader(this.#runtime.storage, this.#runtime.keys, this.#runtime.policy.namespace, this.#runtime.policy.resourceId);
|
|
135
|
+
return capacityOf(this.#runtime, header.record.openCount, header.record.admittedCount);
|
|
136
|
+
}
|
|
137
|
+
async claim(options) {
|
|
138
|
+
this.#assertLive();
|
|
139
|
+
return claimNextCommand(this.#runtime, options);
|
|
140
|
+
}
|
|
141
|
+
async renew(options) {
|
|
142
|
+
this.#assertLive();
|
|
143
|
+
return renewClaim(this.#runtime, {
|
|
144
|
+
commandId: validateCommandIdentifier(options.commandId),
|
|
145
|
+
attemptToken: options.attemptToken,
|
|
146
|
+
progress: validateDurableJSONValue(options.progress, "progress")
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async acknowledge(options) {
|
|
150
|
+
this.#assertLive();
|
|
151
|
+
return acknowledgeClaim(this.#runtime, {
|
|
152
|
+
commandId: validateCommandIdentifier(options.commandId),
|
|
153
|
+
attemptToken: options.attemptToken,
|
|
154
|
+
outcome: validateDurableJSONValue(options.outcome, "outcome")
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async reject(options) {
|
|
158
|
+
this.#assertLive();
|
|
159
|
+
return rejectClaim(this.#runtime, {
|
|
160
|
+
commandId: validateCommandIdentifier(options.commandId),
|
|
161
|
+
attemptToken: options.attemptToken,
|
|
162
|
+
failure: validateFailure(options.failure),
|
|
163
|
+
retry: options.retry ?? !1
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async requestCancellation(options) {
|
|
167
|
+
this.#assertLive();
|
|
168
|
+
return requestCancellation(this.#runtime, {
|
|
169
|
+
commandId: validateCommandIdentifier(options.commandId),
|
|
170
|
+
reason: validateCancellationReason(options.reason)
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async cleanupState(commandId) {
|
|
174
|
+
this.#assertLive();
|
|
175
|
+
return readCleanupState(this.#runtime, validateCommandIdentifier(commandId));
|
|
176
|
+
}
|
|
177
|
+
async awaitCleanup(options) {
|
|
178
|
+
this.#assertLive();
|
|
179
|
+
return waitForCleanup(this.#runtime, this.#disposal.signal, {
|
|
180
|
+
...options,
|
|
181
|
+
commandId: validateCommandIdentifier(options.commandId)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
async waitForAvailable(options) {
|
|
185
|
+
this.#assertLive();
|
|
186
|
+
return waitForAvailableWork(this.#runtime, this.#disposal.signal, options);
|
|
187
|
+
}
|
|
188
|
+
async runMaintenance(now = this.#runtime.now()) {
|
|
189
|
+
this.#assertLive();
|
|
190
|
+
return runMailboxMaintenance(this.#runtime, requireMaintenanceInstant(now));
|
|
191
|
+
}
|
|
192
|
+
dispose() {
|
|
193
|
+
if (this.#disposed)
|
|
194
|
+
return;
|
|
195
|
+
this.#disposed = !0;
|
|
196
|
+
for (const attemptToken of this.#ownAttempts) {
|
|
197
|
+
const registration = this.#runtime.attemptControllers.get(attemptToken);
|
|
198
|
+
if (registration === void 0)
|
|
199
|
+
continue;
|
|
200
|
+
this.#runtime.attemptControllers.delete(attemptToken);
|
|
201
|
+
if (!registration.controller.signal.aborted)
|
|
202
|
+
registration.controller.abort(Error("The application mailbox was disposed while this attempt was open."));
|
|
203
|
+
}
|
|
204
|
+
this.#ownAttempts.clear();
|
|
205
|
+
this.#disposal.abort(Error("The application mailbox was disposed."));
|
|
206
|
+
releaseAttemptControllerRegistry(this.#runtime.storage, MAILBOX_PRIMITIVE, this.#runtime.policy.namespace, this.#runtime.policy.resourceId);
|
|
207
|
+
}
|
|
208
|
+
[Symbol.dispose]() {
|
|
209
|
+
this.dispose();
|
|
210
|
+
}
|
|
211
|
+
}
|