@lostgradient/weft 0.18.0 → 0.20.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 +3 -1
- package/dist/cli/conformance.js +14 -18
- package/dist/cli/generated/operation-catalog.snapshot.json +1597 -21
- package/dist/cli/generated/operation-client.generated.d.ts +37 -6
- package/dist/cli/generated/operation-client.generated.js +6 -0
- package/dist/core/context/child-workflow-pipe.d.ts +1 -1
- package/dist/core/context/child-workflow-pipe.js +1 -1
- package/dist/core/engine/construction.d.ts +2 -0
- package/dist/core/engine/construction.js +3 -0
- package/dist/core/engine/index.js +1 -0
- package/dist/core/engine/listing.d.ts +1 -0
- package/dist/core/engine/listing.js +6 -4
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +2 -1
- package/dist/core/worker-execution-dispatcher.d.ts +8 -0
- package/dist/core/worker-execution-dispatcher.js +4 -0
- package/dist/core/worker-execution-strategy-options.d.ts +16 -0
- package/dist/core/worker-execution-strategy.js +71 -89
- package/dist/core/worker-fault-handling.d.ts +45 -0
- package/dist/core/worker-fault-handling.js +88 -0
- package/dist/core/worker-protocol.d.ts +18 -1
- package/dist/core/worker-protocol.js +1 -1
- package/dist/core/worker-realm-readiness.d.ts +78 -0
- package/dist/core/worker-realm-readiness.js +127 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +28 -1
- package/dist/json-schema.js +1 -1
- package/dist/runtime/portable.d.ts +14 -0
- package/dist/runtime/portable.js +10 -0
- package/dist/server/index.d.ts +71 -8
- package/dist/server/index.js +7 -3
- package/dist/server/operations/get-task-detail-schema.d.ts +341 -0
- package/dist/server/operations/get-task-detail-schema.js +118 -0
- package/dist/server/operations/get-task-detail.d.ts +346 -0
- package/dist/server/operations/get-task-detail.js +215 -0
- package/dist/server/operations/get-task-diagnostics.d.ts +43 -18
- package/dist/server/operations/get-task-diagnostics.js +87 -126
- package/dist/server/operations/get-worker-diagnostics.d.ts +177 -0
- package/dist/server/operations/get-worker-diagnostics.js +117 -0
- package/dist/server/operations/list-worker-registration-rejections.d.ts +79 -0
- package/dist/server/operations/list-worker-registration-rejections.js +60 -0
- package/dist/server/operations/list-workers.js +1 -3
- package/dist/server/operations/static-registrations.js +8 -20
- package/dist/server/rest-bindings.js +31 -2
- package/dist/server/runtime/authentication-bridge.js +6 -14
- package/dist/server/runtime/context.d.ts +23 -0
- package/dist/server/runtime/task-dispatch.d.ts +7 -1
- package/dist/server/runtime/task-dispatch.js +126 -55
- package/dist/server/runtime/task-ledger-completion.d.ts +68 -0
- package/dist/server/runtime/task-ledger-completion.js +72 -0
- package/dist/server/runtime/task-ledger-recovery.d.ts +55 -0
- package/dist/server/runtime/task-ledger-recovery.js +73 -0
- package/dist/server/runtime/task-ledger-runtime.d.ts +64 -0
- package/dist/server/runtime/task-ledger-runtime.js +34 -0
- package/dist/server/runtime/task-metrics.d.ts +6 -4
- package/dist/server/runtime/task-polling.d.ts +14 -12
- package/dist/server/runtime/task-polling.js +106 -82
- package/dist/server/runtime/task-reconciliation.d.ts +35 -8
- package/dist/server/runtime/task-reconciliation.js +75 -74
- package/dist/server/runtime/task-result-resolution.d.ts +2 -10
- package/dist/server/runtime/task-result-resolution.js +0 -67
- package/dist/server/runtime/task-result-view.d.ts +82 -0
- package/dist/server/runtime/task-result-view.js +48 -0
- package/dist/server/runtime/websocket-worker-messaging.d.ts +16 -0
- package/dist/server/runtime/websocket-worker-messaging.js +32 -0
- package/dist/server/runtime/websocket-worker-registration.d.ts +41 -0
- package/dist/server/runtime/websocket-worker-registration.js +134 -0
- package/dist/server/runtime/websocket-worker.d.ts +0 -1
- package/dist/server/runtime/websocket-worker.js +64 -116
- package/dist/server/serve-internals.d.ts +9 -6
- package/dist/server/serve-internals.js +14 -40
- package/dist/server/task-ledger-codec.d.ts +40 -0
- package/dist/server/task-ledger-codec.js +195 -0
- package/dist/server/task-ledger-keys.d.ts +13 -0
- package/dist/server/task-ledger-keys.js +5 -0
- package/dist/server/task-ledger-limits.d.ts +34 -0
- package/dist/server/task-ledger-limits.js +5 -0
- package/dist/server/task-ledger-transition-helpers.d.ts +38 -0
- package/dist/server/task-ledger-transition-helpers.js +42 -0
- package/dist/server/task-ledger-transitions-cancellation.d.ts +31 -0
- package/dist/server/task-ledger-transitions-cancellation.js +73 -0
- package/dist/server/task-ledger-transitions.d.ts +129 -0
- package/dist/server/task-ledger-transitions.js +162 -0
- package/dist/server/task-ledger-types.d.ts +220 -0
- package/dist/server/task-ledger-types.js +1 -0
- package/dist/server/task-ledger.d.ts +17 -0
- package/dist/server/task-ledger.js +24 -0
- package/dist/server/task-state.d.ts +25 -152
- package/dist/server/task-state.js +0 -188
- package/dist/server/worker-admission-policy.d.ts +70 -0
- package/dist/server/worker-admission-policy.js +0 -0
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/index.d.ts +0 -2
- package/dist/storage/interface.d.ts +0 -2
- package/dist/storage/interface.js +1 -1
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +1 -1
- package/dist/storage/postgres.js +1 -1
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +1 -1
- package/dist/storage/web-extension.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/index.d.ts +2 -0
- package/dist/worker/index.js +30 -4
- package/dist/worker/manifest/canonical-json.d.ts +20 -0
- package/dist/worker/manifest/canonical-json.js +13 -0
- package/dist/worker/manifest/capabilities.d.ts +24 -0
- package/dist/worker/manifest/capabilities.js +74 -0
- package/dist/worker/manifest/content-digest.d.ts +20 -0
- package/dist/worker/manifest/content-digest.js +13 -0
- package/dist/worker/manifest/declared-shape-digest.d.ts +14 -0
- package/dist/worker/manifest/declared-shape-digest.js +4 -0
- package/dist/worker/manifest/digest.d.ts +82 -0
- package/dist/worker/manifest/digest.js +9 -0
- package/dist/worker/manifest/execution-identity.d.ts +92 -0
- package/dist/worker/manifest/execution-identity.js +36 -0
- package/dist/worker/manifest/failure.d.ts +59 -0
- package/dist/worker/manifest/failure.js +4 -0
- package/dist/worker/manifest/index.d.ts +22 -0
- package/dist/worker/manifest/index.js +23 -0
- package/dist/worker/manifest/internal-realm.d.ts +50 -0
- package/dist/worker/manifest/internal-realm.js +39 -0
- package/dist/worker/manifest/is-record.d.ts +12 -0
- package/dist/worker/manifest/is-record.js +6 -0
- package/dist/worker/manifest/json-scan.d.ts +26 -0
- package/dist/worker/manifest/json-scan.js +88 -0
- package/dist/worker/manifest/limits.d.ts +138 -0
- package/dist/worker/manifest/limits.js +1 -0
- package/dist/worker/manifest/normalize.d.ts +63 -0
- package/dist/worker/manifest/normalize.js +73 -0
- package/dist/worker/manifest/parse-json.d.ts +24 -0
- package/dist/worker/manifest/parse-json.js +15 -0
- package/dist/worker/manifest/parse.d.ts +86 -0
- package/dist/worker/manifest/parse.js +188 -0
- package/dist/worker/manifest/registry-contract-builder.d.ts +120 -0
- package/dist/worker/manifest/registry-contract-builder.js +65 -0
- package/dist/worker/manifest/types.d.ts +263 -0
- package/dist/worker/manifest/types.js +1 -0
- package/dist/worker/manifest/utf8.d.ts +13 -0
- package/dist/worker/manifest/utf8.js +7 -0
- package/dist/worker/options.d.ts +55 -8
- package/dist/worker/options.js +65 -9
- package/dist/worker/protocol-internals.d.ts +1 -2
- package/dist/worker/protocol-internals.js +0 -5
- package/dist/worker/protocol-messages.d.ts +33 -17
- package/dist/worker/protocol-schemas.d.ts +36 -74
- package/dist/worker/protocol-schemas.js +25 -18
- package/dist/worker/protocol-version.d.ts +18 -14
- package/dist/worker/protocol-version.js +1 -1
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/deployment-consistency.d.ts +78 -0
- package/dist/worker/registry/deployment-consistency.js +27 -0
- package/dist/worker/registry/drain.d.ts +14 -0
- package/dist/worker/registry/drain.js +24 -0
- package/dist/worker/registry/rejections.d.ts +31 -0
- package/dist/worker/registry/rejections.js +10 -0
- package/dist/worker/registry/summary.d.ts +0 -3
- package/dist/worker/registry/summary.js +2 -5
- package/dist/worker/registry/types.d.ts +10 -3
- package/dist/worker/registry.d.ts +47 -2
- package/dist/worker/registry.js +45 -32
- package/dist/workers/workflow-worker-entry.d.ts +12 -7
- package/dist/workers/workflow-worker-entry.js +9 -2
- package/package.json +3 -3
- package/dist/server/task-resolved-record.d.ts +0 -12
- package/dist/server/task-resolved-record.js +0 -35
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export class WorkerFaultHandler {
|
|
2
|
+
#dependencies;
|
|
3
|
+
constructor(dependencies) {
|
|
4
|
+
this.#dependencies = dependencies;
|
|
5
|
+
}
|
|
6
|
+
assertHostToWorkerMessageWithinLimit(workflowId, message, worker) {
|
|
7
|
+
const failure = this.#dependencies.protocolGuard.validateHostToWorkerMessage(message);
|
|
8
|
+
if (!failure)
|
|
9
|
+
return !0;
|
|
10
|
+
if (worker) {
|
|
11
|
+
this.discardWorkerAndFailWorkflows(worker, {
|
|
12
|
+
targetWorkflowId: workflowId,
|
|
13
|
+
targetCategory: failure.failureCategory,
|
|
14
|
+
targetError: failure.error,
|
|
15
|
+
otherCategory: "system",
|
|
16
|
+
otherError: `Worker discarded after protocol send failure for workflow: ${workflowId}`
|
|
17
|
+
});
|
|
18
|
+
return !1;
|
|
19
|
+
}
|
|
20
|
+
this.#dependencies.emit({
|
|
21
|
+
type: "failed",
|
|
22
|
+
workflowId,
|
|
23
|
+
error: failure.error,
|
|
24
|
+
failureCategory: failure.failureCategory
|
|
25
|
+
});
|
|
26
|
+
return !1;
|
|
27
|
+
}
|
|
28
|
+
acceptWorkerMessage(worker, message) {
|
|
29
|
+
const result = this.#dependencies.protocolGuard.acceptWorkerMessage(worker, message);
|
|
30
|
+
if (result.accepted)
|
|
31
|
+
return !0;
|
|
32
|
+
this.discardWorkerAndFailWorkflows(worker, {
|
|
33
|
+
...result.failure.targetWorkflowId === void 0 ? {} : { targetWorkflowId: result.failure.targetWorkflowId },
|
|
34
|
+
targetCategory: result.failure.failureCategory,
|
|
35
|
+
targetError: result.failure.error,
|
|
36
|
+
otherCategory: "system",
|
|
37
|
+
otherError: result.failure.otherError
|
|
38
|
+
});
|
|
39
|
+
return !1;
|
|
40
|
+
}
|
|
41
|
+
handleTurnTimeout(turn) {
|
|
42
|
+
this.discardWorkerAndFailWorkflows(turn.worker, {
|
|
43
|
+
targetWorkflowId: turn.workflowId,
|
|
44
|
+
targetCategory: "timeout",
|
|
45
|
+
targetError: `Worker workflow turn timed out after ${turn.timeoutMs}ms`,
|
|
46
|
+
otherCategory: "timeout",
|
|
47
|
+
otherError: `Worker discarded after workflow turn timed out: ${turn.workflowId}`
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
handleWorkerError(worker, errorEvent) {
|
|
51
|
+
this.discardWorkerAndFailWorkflows(worker, {
|
|
52
|
+
targetCategory: "system",
|
|
53
|
+
targetError: `Worker crashed: ${errorEvent.message ?? "unknown error"}`,
|
|
54
|
+
otherCategory: "system",
|
|
55
|
+
otherError: `Worker crashed: ${errorEvent.message ?? "unknown error"}`
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
discardWorkerAndFailWorkflows(worker, options) {
|
|
59
|
+
const { ownership } = this.#dependencies, workflowIds = ownership.workflowIdsForWorker(worker);
|
|
60
|
+
if (workflowIds.length === 0) {
|
|
61
|
+
this.#clearWorkerState(worker);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const workflowId of workflowIds)
|
|
65
|
+
this.#failOwnedWorkflow(workflowId, workflowId === options.targetWorkflowId, options);
|
|
66
|
+
this.#clearWorkerState(worker);
|
|
67
|
+
this.#dependencies.workerListeners.detach(worker);
|
|
68
|
+
this.#dependencies.pool.discard(worker);
|
|
69
|
+
}
|
|
70
|
+
#failOwnedWorkflow(workflowId, isTarget, options) {
|
|
71
|
+
const { ownership, checkpointResumeState } = this.#dependencies;
|
|
72
|
+
ownership.forgetWorkflow(workflowId);
|
|
73
|
+
checkpointResumeState.forgetWorkflowIfClosed(workflowId, !0);
|
|
74
|
+
if (isTarget && options.skipTarget)
|
|
75
|
+
return;
|
|
76
|
+
this.#dependencies.emit({
|
|
77
|
+
type: "failed",
|
|
78
|
+
workflowId,
|
|
79
|
+
error: isTarget ? options.targetError ?? options.otherError : options.otherError,
|
|
80
|
+
failureCategory: isTarget ? options.targetCategory ?? options.otherCategory : options.otherCategory
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
#clearWorkerState(worker) {
|
|
84
|
+
this.#dependencies.turnWatchdog.clear(worker);
|
|
85
|
+
this.#dependencies.forwardedLogGate.forget(worker);
|
|
86
|
+
this.#dependencies.realmReadiness?.forget(worker);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -2,10 +2,27 @@ import type { ContextOperationRequest } from './context.ts';
|
|
|
2
2
|
import type { FailureCategory, OperationRequest, WorkerOutboundMessage } from './types.ts';
|
|
3
3
|
import { type WorkerReplayOperationSignature } from './types/checkpoint.ts';
|
|
4
4
|
export { WORKER_REPLAY_SIGNATURE_FORMAT, type WorkerReplayOperationSignature, } from './types/checkpoint.ts';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Bumped 1 -> 2 for the realm-ready handshake (WFT-28): a worker realm must
|
|
7
|
+
* now send a `ready` message before its first turn, a required pre-turn
|
|
8
|
+
* message the wire semantics didn't previously have. Host and worker are
|
|
9
|
+
* always built from the same package version — there is no independent peer
|
|
10
|
+
* to stay backward compatible with — so this is a clean-break bump, the same
|
|
11
|
+
* reasoning the RemoteWorker protocol v2 -> v3 bump used.
|
|
12
|
+
*/
|
|
13
|
+
export declare const WORKER_PROTOCOL_VERSION = 2;
|
|
6
14
|
export declare const DEFAULT_WORKER_TURN_TIMEOUT_MS = 1000;
|
|
7
15
|
export declare const DEFAULT_WORKER_PROTOCOL_MESSAGE_BYTES = 1048576;
|
|
8
16
|
export declare const MIN_WORKER_PROTOCOL_MESSAGE_BYTES = 4096;
|
|
17
|
+
/**
|
|
18
|
+
* Default bound (ms) on how long the host waits for a freshly acquired
|
|
19
|
+
* worker's `ready` handshake message before discarding it (WFT-28). Worker boot
|
|
20
|
+
* is module load plus evaluation with no I/O, but a cold Bun Worker spinning
|
|
21
|
+
* up a large bundled module graph can take meaningfully longer than a single
|
|
22
|
+
* workflow turn, so this is deliberately more generous than
|
|
23
|
+
* {@link DEFAULT_WORKER_TURN_TIMEOUT_MS}.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_WORKER_REALM_READY_TIMEOUT_MS = 5000;
|
|
9
26
|
/**
|
|
10
27
|
* Default fixed-window length (ms) for the forwarded-log flood budget (#545). Paired
|
|
11
28
|
* with {@link DEFAULT_FORWARDED_LOG_FLOOD_THRESHOLD} over this window.
|
|
@@ -7,7 +7,7 @@ import { isValidWorkerLogRecord } from "./worker-protocol-log.js";
|
|
|
7
7
|
export {
|
|
8
8
|
WORKER_REPLAY_SIGNATURE_FORMAT
|
|
9
9
|
} from "./types/checkpoint.js";
|
|
10
|
-
export const WORKER_PROTOCOL_VERSION =
|
|
10
|
+
export const WORKER_PROTOCOL_VERSION = 2, DEFAULT_WORKER_TURN_TIMEOUT_MS = 1000, DEFAULT_WORKER_PROTOCOL_MESSAGE_BYTES = 1048576, MIN_WORKER_PROTOCOL_MESSAGE_BYTES = 4096, DEFAULT_WORKER_REALM_READY_TIMEOUT_MS = 5000, DEFAULT_FORWARDED_LOG_FLOOD_WINDOW_MS = 60000, DEFAULT_FORWARDED_LOG_FLOOD_THRESHOLD = 5000, FORWARDED_LOG_STRIKE_THRESHOLD = 5;
|
|
11
11
|
const BINARY_MARKER = "__weftBinaryBytes", DATE_MARKER = "__weftDate", ERROR_MARKER = "__weftError", MAP_MARKER = "__weftMap", SET_MARKER = "__weftSet", MAX_BOUNDED_ERROR_LENGTH = 512, WORKER_SIGNATURE_EXCLUDED_FIELDS = new Set([
|
|
12
12
|
"attempt",
|
|
13
13
|
"callerStack",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realm-ready handshake for internal `workflowExecutionMode: 'worker'`
|
|
3
|
+
* Workers (WFT-28).
|
|
4
|
+
*
|
|
5
|
+
* An internal Worker realm sends a `ready` message — its own manifest, built
|
|
6
|
+
* from exactly the workflow types its bootstrap script registered — before
|
|
7
|
+
* it can receive a `run` turn. The host checks that every workflow type it
|
|
8
|
+
* has registered appears in that manifest with a matching contract (a
|
|
9
|
+
* subset check, not exact-set equality — a realm may legitimately advertise
|
|
10
|
+
* more types than any one host dispatches to it, e.g. a shared worker pool
|
|
11
|
+
* serving several engines). A missing or mismatched type means the realm's
|
|
12
|
+
* bundle disagrees with the host about a workflow it needs (a stale build, a
|
|
13
|
+
* bootstrap script that fell out of sync), and the realm is discarded before
|
|
14
|
+
* it can ever execute a turn, rather than failing opaquely mid-dispatch.
|
|
15
|
+
*
|
|
16
|
+
* `ready` has no `workflowId` — it is a per-worker-lifetime handshake, not a
|
|
17
|
+
* per-workflow-turn message — so it cannot fit {@link WorkerOutboundMessage}'s
|
|
18
|
+
* shape (every variant there requires one) and is validated on its own path
|
|
19
|
+
* instead of through {@link assertWorkerOutboundMessageShape}, the same way
|
|
20
|
+
* `log` is handled outside the strict turn gate.
|
|
21
|
+
*
|
|
22
|
+
* @module core/worker-realm-readiness
|
|
23
|
+
*/
|
|
24
|
+
import type { WorkerManifest } from '../worker/manifest/types.ts';
|
|
25
|
+
import type { FailureCategory } from './types.ts';
|
|
26
|
+
/** Sent once by a Worker realm, before its first `run` turn. See {@link WorkerRealmReadiness}. */
|
|
27
|
+
export type WorkerRealmReadyMessage = Readonly<{
|
|
28
|
+
type: 'ready';
|
|
29
|
+
protocolVersion: number;
|
|
30
|
+
realmGeneration: string;
|
|
31
|
+
manifest: WorkerManifest;
|
|
32
|
+
}>;
|
|
33
|
+
/** Shallow type guard — deep field validation happens in {@link WorkerRealmReadiness.noteReadyMessage}. */
|
|
34
|
+
export declare function isWorkerRealmReadyMessage(message: unknown): message is WorkerRealmReadyMessage;
|
|
35
|
+
export type RealmReadyOutcome = {
|
|
36
|
+
ok: true;
|
|
37
|
+
realmGeneration: string;
|
|
38
|
+
manifestDigest: string;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
error: string;
|
|
42
|
+
failureCategory: FailureCategory;
|
|
43
|
+
};
|
|
44
|
+
export interface WorkerRealmReadinessDependencies {
|
|
45
|
+
/**
|
|
46
|
+
* Live accessor for the host's registered workflow types, called fresh on
|
|
47
|
+
* every handshake rather than snapshotted at construction — the engine's
|
|
48
|
+
* registrations map is still empty when the strategy is constructed (the
|
|
49
|
+
* registration loop runs later in `Engine.create()`).
|
|
50
|
+
*/
|
|
51
|
+
getExpectedWorkflowTypes: () => readonly string[];
|
|
52
|
+
timeoutMs: number;
|
|
53
|
+
maxProtocolMessageBytes: number | undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Tracks, per pooled `Worker` instance, whether its one-time ready handshake
|
|
57
|
+
* has completed. A worker is validated at most once per lifetime — recycled
|
|
58
|
+
* workers skip straight to {@link isReady}.
|
|
59
|
+
*/
|
|
60
|
+
export declare class WorkerRealmReadiness {
|
|
61
|
+
#private;
|
|
62
|
+
constructor(dependencies: WorkerRealmReadinessDependencies);
|
|
63
|
+
isReady(worker: Worker): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Wait for `worker`'s ready handshake. Resolves immediately if the worker
|
|
66
|
+
* already completed it. The caller must attach its message listener (which
|
|
67
|
+
* routes into {@link noteReadyMessage}) before calling this, in the same
|
|
68
|
+
* synchronous continuation — `acquireAndSend` does exactly that, so no
|
|
69
|
+
* `ready` message can arrive before this registers its pending waiter.
|
|
70
|
+
*/
|
|
71
|
+
waitForReady(worker: Worker): Promise<RealmReadyOutcome>;
|
|
72
|
+
/** Validate an inbound `ready` message and settle any pending {@link waitForReady} call. */
|
|
73
|
+
noteReadyMessage(worker: Worker, message: unknown): Promise<void>;
|
|
74
|
+
/** Drop a discarded worker's state, settling any pending waiter with a failure. */
|
|
75
|
+
forget(worker: Worker): void;
|
|
76
|
+
/** Settle every pending waiter on strategy disposal so no wait hangs past teardown. */
|
|
77
|
+
clear(): void;
|
|
78
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { computeWorkerManifestDigest, parseWorkerManifest } from "../worker/manifest/index.js";
|
|
2
|
+
import {
|
|
3
|
+
buildDeclaredWorkflowContract,
|
|
4
|
+
declaredWorkflowContractsMatch
|
|
5
|
+
} from "../worker/manifest/internal-realm.js";
|
|
6
|
+
import {
|
|
7
|
+
assertWorkerProtocolMessageWithinLimit,
|
|
8
|
+
WORKER_PROTOCOL_VERSION
|
|
9
|
+
} from "./worker-protocol.js";
|
|
10
|
+
export function isWorkerRealmReadyMessage(message) {
|
|
11
|
+
return typeof message === "object" && message !== null && message.type === "ready";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class WorkerRealmReadiness {
|
|
15
|
+
#dependencies;
|
|
16
|
+
#readyByWorker = new Map;
|
|
17
|
+
#pendingByWorker = new Map;
|
|
18
|
+
constructor(dependencies) {
|
|
19
|
+
this.#dependencies = dependencies;
|
|
20
|
+
}
|
|
21
|
+
isReady(worker) {
|
|
22
|
+
return this.#readyByWorker.has(worker);
|
|
23
|
+
}
|
|
24
|
+
async waitForReady(worker) {
|
|
25
|
+
const known = this.#readyByWorker.get(worker);
|
|
26
|
+
if (known)
|
|
27
|
+
return { ok: !0, ...known };
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
let settled = !1;
|
|
30
|
+
const settleOnce = (outcome) => {
|
|
31
|
+
if (settled)
|
|
32
|
+
return;
|
|
33
|
+
settled = !0;
|
|
34
|
+
resolve(outcome);
|
|
35
|
+
}, timeout = setTimeout(() => {
|
|
36
|
+
this.#pendingByWorker.delete(worker);
|
|
37
|
+
settleOnce({
|
|
38
|
+
ok: !1,
|
|
39
|
+
error: `Worker realm did not send a ready message within ${this.#dependencies.timeoutMs}ms`,
|
|
40
|
+
failureCategory: "timeout"
|
|
41
|
+
});
|
|
42
|
+
}, this.#dependencies.timeoutMs);
|
|
43
|
+
this.#pendingByWorker.set(worker, (outcome) => {
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
settleOnce(outcome);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async noteReadyMessage(worker, message) {
|
|
50
|
+
const outcome = await this.#validate(message);
|
|
51
|
+
if (outcome.ok)
|
|
52
|
+
this.#readyByWorker.set(worker, {
|
|
53
|
+
realmGeneration: outcome.realmGeneration,
|
|
54
|
+
manifestDigest: outcome.manifestDigest
|
|
55
|
+
});
|
|
56
|
+
const pending = this.#pendingByWorker.get(worker);
|
|
57
|
+
if (pending) {
|
|
58
|
+
this.#pendingByWorker.delete(worker);
|
|
59
|
+
pending(outcome);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
forget(worker) {
|
|
63
|
+
this.#readyByWorker.delete(worker);
|
|
64
|
+
const pending = this.#pendingByWorker.get(worker);
|
|
65
|
+
if (pending) {
|
|
66
|
+
this.#pendingByWorker.delete(worker);
|
|
67
|
+
pending({
|
|
68
|
+
ok: !1,
|
|
69
|
+
error: "Worker was discarded before its ready handshake completed",
|
|
70
|
+
failureCategory: "system"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
clear() {
|
|
75
|
+
for (const resolve of this.#pendingByWorker.values())
|
|
76
|
+
resolve({
|
|
77
|
+
ok: !1,
|
|
78
|
+
error: "Worker execution strategy was disposed before the ready handshake completed",
|
|
79
|
+
failureCategory: "system"
|
|
80
|
+
});
|
|
81
|
+
this.#pendingByWorker.clear();
|
|
82
|
+
this.#readyByWorker.clear();
|
|
83
|
+
}
|
|
84
|
+
async#validate(message) {
|
|
85
|
+
try {
|
|
86
|
+
assertWorkerProtocolMessageWithinLimit(message, this.#dependencies.maxProtocolMessageBytes);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
ok: !1,
|
|
90
|
+
error: error instanceof Error ? error.message : String(error),
|
|
91
|
+
failureCategory: "resource"
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const record = message;
|
|
95
|
+
if (record.protocolVersion !== WORKER_PROTOCOL_VERSION)
|
|
96
|
+
return {
|
|
97
|
+
ok: !1,
|
|
98
|
+
error: `Worker realm ready message protocol version mismatch: expected ${WORKER_PROTOCOL_VERSION}, got ${String(record.protocolVersion)}`,
|
|
99
|
+
failureCategory: "system"
|
|
100
|
+
};
|
|
101
|
+
if (typeof record.realmGeneration !== "string" || record.realmGeneration.length === 0)
|
|
102
|
+
return {
|
|
103
|
+
ok: !1,
|
|
104
|
+
error: "Worker realm ready message must include a non-empty realmGeneration string",
|
|
105
|
+
failureCategory: "system"
|
|
106
|
+
};
|
|
107
|
+
const parsed = parseWorkerManifest(record.manifest);
|
|
108
|
+
if (!parsed.ok)
|
|
109
|
+
return {
|
|
110
|
+
ok: !1,
|
|
111
|
+
error: `Worker realm manifest rejected: ${parsed.message}`,
|
|
112
|
+
failureCategory: "system"
|
|
113
|
+
};
|
|
114
|
+
const missing = this.#dependencies.getExpectedWorkflowTypes().filter((workflowType) => {
|
|
115
|
+
const reported = parsed.manifest.workflows[workflowType];
|
|
116
|
+
return !reported || !declaredWorkflowContractsMatch(reported, buildDeclaredWorkflowContract(workflowType));
|
|
117
|
+
}).toSorted();
|
|
118
|
+
if (missing.length > 0)
|
|
119
|
+
return {
|
|
120
|
+
ok: !1,
|
|
121
|
+
error: `Worker realm manifest is missing or disagrees on workflow type(s) the host expects: ${missing.join(", ")}`,
|
|
122
|
+
failureCategory: "system"
|
|
123
|
+
};
|
|
124
|
+
const manifestDigest = await computeWorkerManifestDigest(parsed.manifest);
|
|
125
|
+
return { ok: !0, realmGeneration: record.realmGeneration, manifestDigest };
|
|
126
|
+
}
|
|
127
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { AlertManager } from './alerting/index';
|
|
|
29
29
|
export type { AlertAction, AlertMetric, AlertRule, AlertStateSnapshot, AlertStatus, AlertingOptions, WebhookTarget, } from './alerting/types';
|
|
30
30
|
export { ActivityAsyncPendingEvent, ActivityCompletedEvent, ActivityFailedEvent, ActivityStartedEvent, AlertFiredEvent, AlertResolvedEvent, AttributesChangedEvent, CheckpointSizeWarningEvent, ConstraintViolatedEvent, DevelopmentWarningEvent, ScheduleFiredEvent, ScheduleMissedFireEvent, SignalDeliveredEvent, SignalReceivedEvent, StorageSizeReportedEvent, TaskResultDeadLetteredEvent, UpdateCompletedEvent, UpdateReceivedEvent, WorkflowCancelledEvent, WorkflowCompletedEvent, WorkflowDefinitionRegisteredEvent, WorkflowFailedEvent, WorkflowRecoverySkippedEvent, WorkflowResumedEvent, WorkflowStartedEvent, WorkflowSuspendedEvent, WorkflowTeardownEvent, WorkflowTimedOutEvent, } from './core/events';
|
|
31
31
|
export type { TypedEventTarget, WeftEventMap, WorkflowRecoverySkippedReason, WorkflowTeardownStatus, } from './core/events';
|
|
32
|
-
export { detectRuntime, hashBytes, hashString, sleep } from './runtime/portable';
|
|
32
|
+
export { detectRuntime, detectRuntimeVersion, hashBytes, hashString, sleep, } from './runtime/portable';
|
|
33
33
|
export type { RuntimeKind } from './runtime/portable';
|
|
34
34
|
export { createBunCompressor, createCompressor } from './core/compression';
|
|
35
35
|
export type { CompressionAlgorithm, CompressionOptions, Compressor } from './core/compression';
|
|
@@ -102,6 +102,8 @@ export type { WorkerPoolOptions } from './workers/pool';
|
|
|
102
102
|
export { HeartbeatManager } from './worker/heartbeat';
|
|
103
103
|
export { RemoteWorker } from './worker/index';
|
|
104
104
|
export { LongPollWorker } from './worker/long-poll';
|
|
105
|
+
export { MAX_MANIFEST_ACTIVITY_COUNT, MAX_MANIFEST_CAPABILITY_COUNT, MAX_MANIFEST_CAPABILITY_DEPTH, MAX_MANIFEST_CAPABILITY_STRING_BYTES, MAX_MANIFEST_IDENTIFIER_BYTES, MAX_MANIFEST_WORKFLOW_COUNT, MAX_NORMALIZED_MANIFEST_BYTES, WORKER_MANIFEST_DIGEST_ALGORITHM, WORKER_MANIFEST_VERSION, WorkerManifestBuildError, buildWorkerExecutionIdentity, buildWorkerManifestFromRegistry, canonicalWorkerManifestJson, computeWorkerManifestDigest, digestCanonicalWorkerManifest, executionIdentitySatisfies, normalizeWorkerManifest, parseWorkerManifest, parseWorkerManifestJson, } from './worker/manifest/index.ts';
|
|
106
|
+
export type { ManifestValidationFailure, WorkerActivityContract, WorkerDeploymentIdentity, WorkerExecutionIdentity, WorkerExecutionRequirement, WorkerManifest, WorkerManifestFromRegistryOptions, WorkerManifestParseResult, WorkerManifestParseSuccess, WorkerManifestRejectionReason, WorkerRuntimeIdentity, WorkerWorkflowContract, } from './worker/manifest/index.ts';
|
|
105
107
|
export { WorkerRegistry } from './worker/registry';
|
|
106
108
|
export { WorkerProtocolIncompatibleError, workerProtocolIncompatibleMessage, } from './worker/worker-protocol-incompatible-error';
|
|
107
109
|
export { buildQualifiedActivityTable } from './worker/workflow-activity-binding';
|
package/dist/index.js
CHANGED
|
@@ -91,7 +91,13 @@ export {
|
|
|
91
91
|
WorkflowTeardownEvent,
|
|
92
92
|
WorkflowTimedOutEvent
|
|
93
93
|
} from "./core/events.js";
|
|
94
|
-
export {
|
|
94
|
+
export {
|
|
95
|
+
detectRuntime,
|
|
96
|
+
detectRuntimeVersion,
|
|
97
|
+
hashBytes,
|
|
98
|
+
hashString,
|
|
99
|
+
sleep
|
|
100
|
+
} from "./runtime/portable.js";
|
|
95
101
|
export { createBunCompressor, createCompressor } from "./core/compression.js";
|
|
96
102
|
export { CompressedStorage } from "./storage/compressed-storage.js";
|
|
97
103
|
export { storageDeleteRange } from "./storage/delete-range.js";
|
|
@@ -209,6 +215,27 @@ export { WorkerPool } from "./workers/pool.js";
|
|
|
209
215
|
export { HeartbeatManager } from "./worker/heartbeat.js";
|
|
210
216
|
export { RemoteWorker } from "./worker/index.js";
|
|
211
217
|
export { LongPollWorker } from "./worker/long-poll.js";
|
|
218
|
+
export {
|
|
219
|
+
MAX_MANIFEST_ACTIVITY_COUNT,
|
|
220
|
+
MAX_MANIFEST_CAPABILITY_COUNT,
|
|
221
|
+
MAX_MANIFEST_CAPABILITY_DEPTH,
|
|
222
|
+
MAX_MANIFEST_CAPABILITY_STRING_BYTES,
|
|
223
|
+
MAX_MANIFEST_IDENTIFIER_BYTES,
|
|
224
|
+
MAX_MANIFEST_WORKFLOW_COUNT,
|
|
225
|
+
MAX_NORMALIZED_MANIFEST_BYTES,
|
|
226
|
+
WORKER_MANIFEST_DIGEST_ALGORITHM,
|
|
227
|
+
WORKER_MANIFEST_VERSION,
|
|
228
|
+
WorkerManifestBuildError,
|
|
229
|
+
buildWorkerExecutionIdentity,
|
|
230
|
+
buildWorkerManifestFromRegistry,
|
|
231
|
+
canonicalWorkerManifestJson,
|
|
232
|
+
computeWorkerManifestDigest,
|
|
233
|
+
digestCanonicalWorkerManifest,
|
|
234
|
+
executionIdentitySatisfies,
|
|
235
|
+
normalizeWorkerManifest,
|
|
236
|
+
parseWorkerManifest,
|
|
237
|
+
parseWorkerManifestJson
|
|
238
|
+
} from "./worker/manifest/index.js";
|
|
212
239
|
export { WorkerRegistry } from "./worker/registry.js";
|
|
213
240
|
export {
|
|
214
241
|
WorkerProtocolIncompatibleError,
|
package/dist/json-schema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var O=import.meta.require,T=(j,q,z)=>{if(q!=null){if(typeof q!=="object"&&typeof q!=="function")throw TypeError('Object expected to be assigned to "using" declaration');let A;if(z)A=q[Symbol.asyncDispose];if(A===void 0)A=q[Symbol.dispose];if(typeof A!=="function")throw TypeError("Object not disposable");j.push([z,A,q])}else if(z)j.push([z]);return q},y=(j,q,z)=>{let A=(G)=>q=z?new SuppressedError(G,q,"An error was suppressed during disposal"):(z=!0,G),H=(G)=>{while(G=j.pop())try{var $=G[1]&&G[1].call(G[2]);if(G[0])return Promise.resolve($).then(H,(Q)=>(A(Q),H()))}catch(Q){A(Q)}if(z)throw q};return H()};class X extends Error{code;constructor(j,q,z){super(q,z);this.code=j,this.name=j}}var B={WorkflowAlreadyExistsError:!0,BulkDeleteRequiresTerminalWorkflowsError:!0,BulkOperationConfirmationError:!0,WorkflowTypeNotRegisteredForRecoveryError:!0,EngineCreateNameMismatchError:!0,EngineDisposedError:!0,EngineDisposalError:!0,WorkflowNotFoundError:!0,WorkflowNotRegisteredError:!0,WorkflowConcurrencyLimitExceededError:!0,WorkflowSuspendNotSupportedError:!0,ActivityResolutionError:!0,BranchTopologyChangedError:!0,PersistedDataIncompatibleError:!0,WorkflowTimeoutError:!0,HttpClientError:!0,WorkerProtocolIncompatibleError:!0,UpdateTimeoutError:!0,UpdateValidationError:!0,WorkflowTerminalError:!0,WorkflowBuilderError:!0,VersionMismatchError:!0,EffectReplayConflictError:!0,ReviewTimeoutError:!0,AtomicStateConflictError:!0,StandardSchemaValidationError:!0,ActivityReconciliationCapabilityError:!0,ActivityReconciliationConflictError:!0,ActivityReconciliationIndeterminateError:!0,DurableActivityScopeError:!0,DurableActivityUnsupportedError:!0,AsyncActivityTokenNotFoundError:!0,ActivityScheduleToCloseTimeoutError:!0,ActivityPerAttemptTimeoutError:!0,PayloadSizeExceededError:!0,StartOrSignalConflictError:!0,WorkflowTeardownPendingError:!0,IdempotencyKeyPurgedError:!0},R=new Set(Object.keys(B));class K extends X{fieldName;operation;issues;constructor(j){super("StandardSchemaValidationError",M(j.fieldName,j.operation,j.issues));this.fieldName=j.fieldName,this.operation=j.operation,this.issues=j.issues}}async function Z(j,q,z){let A=j["~standard"];if(!D(A))throw TypeError(`Schema for ${z.fieldName} does not provide runtime validation. Attach a Standard Schema validator (Zod, Valibot, or another vendor) or supply a runtime-validating schema at this boundary.`);let H=await A.validate(q);if(H.issues===void 0)return H.value;throw new K({fieldName:z.fieldName,operation:z.operation,issues:H.issues.map(U)})}function Y(j){return j.map((q)=>q.path===""?q.message:`${q.path}: ${q.message}`).join(`
|
|
2
|
+
var O=import.meta.require,T=(j,q,z)=>{if(q!=null){if(typeof q!=="object"&&typeof q!=="function")throw TypeError('Object expected to be assigned to "using" declaration');let A;if(z)A=q[Symbol.asyncDispose];if(A===void 0)A=q[Symbol.dispose];if(typeof A!=="function")throw TypeError("Object not disposable");j.push([z,A,q])}else if(z)j.push([z]);return q},y=(j,q,z)=>{let A=(G)=>q=z?new SuppressedError(G,q,"An error was suppressed during disposal"):(z=!0,G),H=(G)=>{while(G=j.pop())try{var $=G[1]&&G[1].call(G[2]);if(G[0])return Promise.resolve($).then(H,(Q)=>(A(Q),H()))}catch(Q){A(Q)}if(z)throw q};return H()};class X extends Error{code;constructor(j,q,z){super(q,z);this.code=j,this.name=j}}var B={WorkflowAlreadyExistsError:!0,BulkDeleteRequiresTerminalWorkflowsError:!0,BulkOperationConfirmationError:!0,WorkflowTypeNotRegisteredForRecoveryError:!0,EngineCreateNameMismatchError:!0,EngineDisposedError:!0,EngineDisposalError:!0,WorkflowNotFoundError:!0,WorkflowNotRegisteredError:!0,WorkflowConcurrencyLimitExceededError:!0,WorkflowSuspendNotSupportedError:!0,ActivityResolutionError:!0,BranchTopologyChangedError:!0,PersistedDataIncompatibleError:!0,WorkflowTimeoutError:!0,HttpClientError:!0,WorkerProtocolIncompatibleError:!0,UpdateTimeoutError:!0,UpdateValidationError:!0,WorkflowTerminalError:!0,WorkflowBuilderError:!0,VersionMismatchError:!0,EffectReplayConflictError:!0,ReviewTimeoutError:!0,AtomicStateConflictError:!0,StandardSchemaValidationError:!0,ActivityReconciliationCapabilityError:!0,ActivityReconciliationConflictError:!0,ActivityReconciliationIndeterminateError:!0,DurableActivityScopeError:!0,DurableActivityUnsupportedError:!0,AsyncActivityTokenNotFoundError:!0,ActivityScheduleToCloseTimeoutError:!0,ActivityPerAttemptTimeoutError:!0,PayloadSizeExceededError:!0,StartOrSignalConflictError:!0,WorkflowTeardownPendingError:!0,IdempotencyKeyPurgedError:!0,WorkerManifestBuildError:!0},R=new Set(Object.keys(B));class K extends X{fieldName;operation;issues;constructor(j){super("StandardSchemaValidationError",M(j.fieldName,j.operation,j.issues));this.fieldName=j.fieldName,this.operation=j.operation,this.issues=j.issues}}async function Z(j,q,z){let A=j["~standard"];if(!D(A))throw TypeError(`Schema for ${z.fieldName} does not provide runtime validation. Attach a Standard Schema validator (Zod, Valibot, or another vendor) or supply a runtime-validating schema at this boundary.`);let H=await A.validate(q);if(H.issues===void 0)return H.value;throw new K({fieldName:z.fieldName,operation:z.operation,issues:H.issues.map(U)})}function Y(j){return j.map((q)=>q.path===""?q.message:`${q.path}: ${q.message}`).join(`
|
|
3
3
|
`)}function D(j){return typeof j.validate==="function"}function U(j){return{message:j.message,path:b(j.path)}}function b(j){if(j===void 0||j.length===0)return"";let q="";for(let z of j)q+="/",q+=w(z);return q}function w(j){let q=F(j)?j.key:j;return J(String(q))}function F(j){return j!==null&&typeof j==="object"&&"key"in j}function J(j){return j.replace(/~/g,"~0").replace(/\//g,"~1")}function M(j,q,z){return`Validation failed for ${q===void 0?j:`${q} ${j}`}:
|
|
4
4
|
${Y(z)}`}var V=Y,k=K,P=Z;export{P as validateStandardSchema,V as formatStandardSchemaIssues,k as StandardSchemaValidationError};
|
|
@@ -40,6 +40,20 @@ export declare function isBunRuntime(): boolean;
|
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
42
|
export declare function detectRuntime(): RuntimeKind;
|
|
43
|
+
/**
|
|
44
|
+
* Detect the current runtime's version string, matching {@link detectRuntime}'s
|
|
45
|
+
* detection precedence. A browser or edge runtime exposes no version, so an
|
|
46
|
+
* empty string is a truthful answer rather than a missing field.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { detectRuntimeVersion } from '@lostgradient/weft';
|
|
51
|
+
*
|
|
52
|
+
* const version = detectRuntimeVersion();
|
|
53
|
+
* console.log(typeof version); // 'string'
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function detectRuntimeVersion(): string;
|
|
43
57
|
/**
|
|
44
58
|
* Read an environment variable without assuming a runtime.
|
|
45
59
|
*
|
package/dist/runtime/portable.js
CHANGED
|
@@ -33,6 +33,16 @@ export function detectRuntime() {
|
|
|
33
33
|
return detector.kind;
|
|
34
34
|
return "edge";
|
|
35
35
|
}
|
|
36
|
+
export function detectRuntimeVersion() {
|
|
37
|
+
switch (detectRuntime()) {
|
|
38
|
+
case "bun":
|
|
39
|
+
return getBunGlobal()?.version ?? "";
|
|
40
|
+
case "node":
|
|
41
|
+
return getProcess()?.versions?.node ?? "";
|
|
42
|
+
default:
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
36
46
|
export function readEnvironmentVariable(name) {
|
|
37
47
|
const bun = getBunGlobal();
|
|
38
48
|
if (bun !== void 0)
|
package/dist/server/index.d.ts
CHANGED
|
@@ -13,11 +13,14 @@ import type { AuthConfig, RateLimitConfig } from './authentication.ts';
|
|
|
13
13
|
import { type DashboardAssets } from './dashboard-assets.ts';
|
|
14
14
|
import type { DiscoveryInfo } from './discovery-info.ts';
|
|
15
15
|
import type { CorsOptions } from './runtime/cors.ts';
|
|
16
|
+
import type { TaskResultView } from './runtime/task-result-view.ts';
|
|
16
17
|
import type { SchedulingPolicy } from './task-queue-types.ts';
|
|
17
18
|
import { TaskQueue } from './task-queue.ts';
|
|
19
|
+
import type { WorkerAdmissionPolicy } from './worker-admission-policy.ts';
|
|
18
20
|
export { wireEventBroadcasting, type EventBroadcastingHandle, } from './runtime/event-broadcasting.ts';
|
|
19
21
|
export type { DashboardAssets } from './dashboard-assets.ts';
|
|
20
22
|
export type { CorsOptions } from './runtime/cors.ts';
|
|
23
|
+
export type { WorkerAdmissionDecision, WorkerAdmissionPolicy, WorkerAdmissionRequest, } from './worker-admission-policy.ts';
|
|
21
24
|
export { AUTHORIZATION_SCOPES, isAuthorizationScope, type AuthorizationScope, } from './authorization-scope.ts';
|
|
22
25
|
export type { GetPrincipalOutput } from './operations/get-principal.ts';
|
|
23
26
|
export { createRateLimiter, createRotatingApiKeyStore, defaultAuthAuditSink, isSensitiveHeader, redactCredential, redactHeaders, validateRateLimitConfig, type ApiKeyRegistration, type AuthAuditEvent, type AuthAuditSink, type AuthConfig, type RateLimitConfig, type RateLimitDecision, type RateLimiter, type RotatingApiKeyStore, } from './authentication.ts';
|
|
@@ -26,6 +29,7 @@ export type { PrometheusExporter } from '../observability/metrics.ts';
|
|
|
26
29
|
export { WorkerRegistry } from '../worker/registry.ts';
|
|
27
30
|
export type { RoutingPolicy } from '../worker/registry.ts';
|
|
28
31
|
export type { DiscoveryInfo } from './discovery-info.ts';
|
|
32
|
+
export type { TaskResultView } from './runtime/task-result-view.ts';
|
|
29
33
|
export type { SchedulingPolicy } from './task-queue-types.ts';
|
|
30
34
|
export { TaskQueue } from './task-queue.ts';
|
|
31
35
|
/**
|
|
@@ -190,7 +194,7 @@ export interface ServeOptions {
|
|
|
190
194
|
* workflow SSE requests return `429`.
|
|
191
195
|
*/
|
|
192
196
|
maxStreamConnectionsPerWorkflow?: number;
|
|
193
|
-
/** How often (in ms) the server scans
|
|
197
|
+
/** How often (in ms) the server scans the task ledger for expired visibility deadlines. Defaults to 5 000. */
|
|
194
198
|
visibilityPollIntervalMs?: number;
|
|
195
199
|
/**
|
|
196
200
|
* Grace period (in ms) between a worker WebSocket close and the requeue of
|
|
@@ -204,6 +208,19 @@ export interface ServeOptions {
|
|
|
204
208
|
* `[0, 5_000]`.
|
|
205
209
|
*/
|
|
206
210
|
workerReconnectGracePeriodMs?: number;
|
|
211
|
+
/**
|
|
212
|
+
* How long (in ms) an adopted terminal task-ledger record is retained
|
|
213
|
+
* before the periodic reconciliation scan reaps it. Defaults to `undefined`
|
|
214
|
+
* — retention is opt-in, and unset means terminal records are kept forever
|
|
215
|
+
* regardless of adoption, matching the durable ledger's default posture of
|
|
216
|
+
* never discarding a task's authoritative record without an explicit
|
|
217
|
+
* policy. Only adopted records are ever eligible; a terminal record whose
|
|
218
|
+
* result no caller has adopted via {@link WeftServer.adoptTaskResult} is
|
|
219
|
+
* retained indefinitely no matter how old it is. Unlike workflow history's
|
|
220
|
+
* count-based `history.retentionWindow`, this is time-based — a terminal
|
|
221
|
+
* task record has no natural sequence axis to bound by count.
|
|
222
|
+
*/
|
|
223
|
+
taskRetentionWindowMs?: number;
|
|
207
224
|
/**
|
|
208
225
|
* Maximum time (in ms) `server.stop()` waits for connected remote workers to
|
|
209
226
|
* drain in-flight task results after receiving a shutdown frame before the
|
|
@@ -223,6 +240,14 @@ export interface ServeOptions {
|
|
|
223
240
|
* least-loaded for that single call.
|
|
224
241
|
*/
|
|
225
242
|
routingPolicy?: RoutingPolicy;
|
|
243
|
+
/**
|
|
244
|
+
* Gate on which workers may become routing-eligible, evaluated after the
|
|
245
|
+
* manifest is validated and checked for deployment consistency and before
|
|
246
|
+
* registry insertion. Defaults to `undefined`, which accepts every worker
|
|
247
|
+
* that already passed authentication — the behavior before this option
|
|
248
|
+
* existed.
|
|
249
|
+
*/
|
|
250
|
+
workerAdmissionPolicy?: WorkerAdmissionPolicy;
|
|
226
251
|
/**
|
|
227
252
|
* Scheduling policy used by the {@link TaskQueue} when ordering pending tasks
|
|
228
253
|
* within a queue. Defaults to `'priority'`.
|
|
@@ -263,10 +288,10 @@ export interface ServeOptions {
|
|
|
263
288
|
* Descriptor for a task dispatched to a remote worker via
|
|
264
289
|
* {@link WeftServer.dispatchTask}.
|
|
265
290
|
*
|
|
266
|
-
* `operationId` and `
|
|
267
|
-
* routing, retry behaviour, and priority. Set `sticky: true`
|
|
268
|
-
* `workflowId` to route the task to the worker that last
|
|
269
|
-
* that workflow.
|
|
291
|
+
* `operationId`, `activityName`, and `workflowType` are required; all other
|
|
292
|
+
* fields refine routing, retry behaviour, and priority. Set `sticky: true`
|
|
293
|
+
* together with `workflowId` to route the task to the worker that last
|
|
294
|
+
* handled tasks for that workflow.
|
|
270
295
|
*
|
|
271
296
|
* @example
|
|
272
297
|
* ```ts
|
|
@@ -275,6 +300,7 @@ export interface ServeOptions {
|
|
|
275
300
|
* const task: TaskDispatch = {
|
|
276
301
|
* operationId: crypto.randomUUID(),
|
|
277
302
|
* activityName: 'sendEmail',
|
|
303
|
+
* workflowType: 'notifications',
|
|
278
304
|
* input: { to: 'user@example.com', subject: 'Hello' },
|
|
279
305
|
* queue: 'email',
|
|
280
306
|
* retryPolicy: { maxAttempts: 3, initialBackoff: '1s', backoffMultiplier: 2, maxBackoff: '30s' },
|
|
@@ -286,9 +312,17 @@ export interface TaskDispatch {
|
|
|
286
312
|
operationId: string;
|
|
287
313
|
activityName: string;
|
|
288
314
|
input: unknown;
|
|
289
|
-
attempt?: number;
|
|
290
315
|
/** Queue to dispatch the task to. Defaults to `'default'`. */
|
|
291
316
|
queue?: string;
|
|
317
|
+
/**
|
|
318
|
+
* The dispatching workflow's registered type. Required — the durable task
|
|
319
|
+
* ledger's envelope (`RemoteTaskBase.workflowType`) is required, and
|
|
320
|
+
* `buildWorkerExecutionIdentity` needs it to look up the claiming worker's
|
|
321
|
+
* manifest entry (`manifest.workflows[workflowType].activities[activityName]`).
|
|
322
|
+
* `dispatchTaskImpl` rejects a call missing this field with an actionable
|
|
323
|
+
* error rather than defaulting it — there is no safe placeholder value.
|
|
324
|
+
*/
|
|
325
|
+
workflowType: string;
|
|
292
326
|
/** Workflow ID. Required for sticky routing to track worker affinity. */
|
|
293
327
|
workflowId?: string | undefined;
|
|
294
328
|
/** Durable token for the workflow run that launched this task, when known. */
|
|
@@ -345,6 +379,16 @@ export interface WeftServer extends AsyncDisposable {
|
|
|
345
379
|
readonly url: string;
|
|
346
380
|
readonly registry: WorkerRegistry;
|
|
347
381
|
readonly taskQueue: TaskQueue;
|
|
382
|
+
/**
|
|
383
|
+
* Resolves once startup task-ledger recovery (WFT-23) has reconstructed
|
|
384
|
+
* every non-terminal task's in-memory indexes from durable storage;
|
|
385
|
+
* rejects if the recovery scan itself failed. `dispatchTask`, long-poll
|
|
386
|
+
* claim/result handling, and worker registration all await this
|
|
387
|
+
* internally before touching the ledger, so awaiting it explicitly is
|
|
388
|
+
* optional — it exists for callers (health checks, orchestration) that
|
|
389
|
+
* want to observe readiness without dispatching a probe task.
|
|
390
|
+
*/
|
|
391
|
+
readonly ready: Promise<void>;
|
|
348
392
|
/**
|
|
349
393
|
* Drain connected remote workers, then stop the underlying Bun server.
|
|
350
394
|
*
|
|
@@ -356,6 +400,22 @@ export interface WeftServer extends AsyncDisposable {
|
|
|
356
400
|
stop(): Promise<void>;
|
|
357
401
|
/** Dispatch a task to the best available worker. Returns true if dispatched. */
|
|
358
402
|
dispatchTask(task: TaskDispatch): Promise<boolean>;
|
|
403
|
+
/**
|
|
404
|
+
* Read the current public view of a dispatched task's ledger record.
|
|
405
|
+
* Returns `null` if no record exists — never dispatched, or a retained
|
|
406
|
+
* terminal record was already reaped. See {@link TaskResultView}.
|
|
407
|
+
*/
|
|
408
|
+
getTaskResult(operationId: string): Promise<TaskResultView | null>;
|
|
409
|
+
/**
|
|
410
|
+
* Mark a terminal task's result as adopted — the durable assertion that
|
|
411
|
+
* whatever consumed the result (a workflow's own checkpoint, or other
|
|
412
|
+
* application logic) has durably incorporated it. `resultDigest` must
|
|
413
|
+
* match the terminal record's `resultDigest` from {@link getTaskResult}.
|
|
414
|
+
* Returns `true` once adopted; `false` if the record is not currently
|
|
415
|
+
* terminal or the digest does not match. Only adopted terminal records
|
|
416
|
+
* become eligible for {@link ServeOptions.taskRetentionWindowMs} reaping.
|
|
417
|
+
*/
|
|
418
|
+
adoptTaskResult(operationId: string, resultDigest: string): Promise<boolean>;
|
|
359
419
|
/** Send a shutdown message to a specific worker and wait for it to disconnect. Returns true if the worker was found. */
|
|
360
420
|
shutdownWorker(workerId: string, options?: {
|
|
361
421
|
timeoutMs?: number;
|
|
@@ -372,8 +432,11 @@ export interface WeftServer extends AsyncDisposable {
|
|
|
372
432
|
*
|
|
373
433
|
* `serve()` validates the supplied `auth` configuration synchronously and
|
|
374
434
|
* throws `Error` before binding the port if any auth setting is invalid.
|
|
375
|
-
*
|
|
376
|
-
* on startup so no
|
|
435
|
+
* Every non-terminal task record from previous server runs is recovered from
|
|
436
|
+
* durable storage on startup so no task is silently lost across restarts —
|
|
437
|
+
* see `WeftServer.ready`. Task dispatch, claim, completion, and worker
|
|
438
|
+
* registration all wait for that recovery to finish (or fail loudly if it
|
|
439
|
+
* doesn't) before touching the ledger.
|
|
377
440
|
*
|
|
378
441
|
* The returned `WeftServer.taskQueue` is exposed for inspection, not as a stable
|
|
379
442
|
* mutation surface — prefer `WeftServer` methods (`dispatchTask`,
|