@lostgradient/weft 0.19.0 → 0.21.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 +2 -2
- package/dist/cli/generated/operation-catalog.snapshot.json +1334 -0
- package/dist/cli/generated/operation-client.generated.d.ts +9 -2
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/core/engine/activity-reconciliation.d.ts +2 -2
- package/dist/core/engine/activity-reconciliation.js +12 -8
- package/dist/core/engine/async-activity-completion.js +12 -4
- package/dist/core/engine/async-activity-records.d.ts +11 -1
- package/dist/core/engine/async-activity-records.js +4 -3
- package/dist/core/engine/attributes-tags.d.ts +11 -1
- package/dist/core/engine/attributes-tags.js +4 -3
- package/dist/core/engine/bulk-operations-purge.js +5 -2
- package/dist/core/engine/bulk-operations.js +17 -5
- package/dist/core/engine/callback-creators-bundles.js +1 -1
- package/dist/core/engine/callback-creators-core.js +4 -2
- package/dist/core/engine/checkpoint-io.js +1 -1
- package/dist/core/engine/child-workflow.js +2 -1
- package/dist/core/engine/completed-review-storage.js +1 -1
- package/dist/core/engine/condition-waiters.d.ts +45 -3
- package/dist/core/engine/condition-waiters.js +12 -3
- package/dist/core/engine/disposal.js +6 -0
- package/dist/core/engine/engine-internal-types.d.ts +12 -4
- package/dist/core/engine/fenced-write.d.ts +46 -15
- package/dist/core/engine/fenced-write.js +39 -6
- package/dist/core/engine/finalizer-state.d.ts +3 -1
- package/dist/core/engine/handle-result.d.ts +49 -1
- package/dist/core/engine/handle-result.js +95 -20
- package/dist/core/engine/index.d.ts +13 -5
- package/dist/core/engine/index.js +155 -5
- package/dist/core/engine/inline-launch-queue.js +9 -0
- package/dist/core/engine/internals.d.ts +73 -0
- package/dist/core/engine/invoke-update-handler.d.ts +19 -0
- package/dist/core/engine/invoke-update-handler.js +7 -0
- package/dist/core/engine/lease-deposition.d.ts +132 -0
- package/dist/core/engine/lease-deposition.js +31 -0
- package/dist/core/engine/lease-errors.d.ts +80 -9
- package/dist/core/engine/lease-errors.js +28 -2
- package/dist/core/engine/lifecycle/resume.js +3 -1
- package/dist/core/engine/lifecycle/standalone-claim-acquire.d.ts +33 -0
- package/dist/core/engine/lifecycle/standalone-claim-acquire.js +25 -0
- package/dist/core/engine/lifecycle/start-commit.js +20 -6
- package/dist/core/engine/lifecycle/transition.d.ts +15 -1
- package/dist/core/engine/lifecycle/transition.js +6 -3
- package/dist/core/engine/operations-activity.js +1 -1
- package/dist/core/engine/operations-time.js +21 -7
- package/dist/core/engine/owner-side-signal-poll.d.ts +132 -0
- package/dist/core/engine/owner-side-signal-poll.js +22 -0
- package/dist/core/engine/owner-side-update-poll.d.ts +116 -0
- package/dist/core/engine/owner-side-update-poll.js +22 -0
- package/dist/core/engine/ownership-bootstrap.d.ts +280 -0
- package/dist/core/engine/ownership-bootstrap.js +89 -0
- package/dist/core/engine/ownership-mode-marker.d.ts +82 -0
- package/dist/core/engine/ownership-mode-marker.js +52 -0
- package/dist/core/engine/ownership-options.d.ts +22 -6
- package/dist/core/engine/ownership-options.js +42 -12
- package/dist/core/engine/pending-updates.js +35 -24
- package/dist/core/engine/queries.d.ts +70 -0
- package/dist/core/engine/queries.js +58 -4
- package/dist/core/engine/schedules.js +1 -1
- package/dist/core/engine/signals.d.ts +11 -1
- package/dist/core/engine/signals.js +11 -7
- package/dist/core/engine/sleep-timer-acknowledgements.d.ts +68 -0
- package/dist/core/engine/sleep-timer-acknowledgements.js +19 -0
- package/dist/core/engine/storage-io.d.ts +58 -10
- package/dist/core/engine/storage-io.js +27 -9
- package/dist/core/engine/termination/cleanup.d.ts +5 -4
- package/dist/core/engine/termination/complete.js +23 -4
- package/dist/core/engine/termination/finalizer-claim.js +4 -4
- package/dist/core/engine/termination/state-commit-callbacks.d.ts +15 -0
- package/dist/core/engine/termination/state-commit-callbacks.js +0 -0
- package/dist/core/engine/termination/suspend.js +1 -1
- package/dist/core/engine/update-validation.d.ts +28 -0
- package/dist/core/engine/update-validation.js +37 -0
- package/dist/core/engine/updates.d.ts +17 -16
- package/dist/core/engine/updates.js +24 -47
- package/dist/core/engine/wake-ownership-check.d.ts +86 -0
- package/dist/core/engine/wake-ownership-check.js +19 -0
- package/dist/core/engine/wake-ownership-guard.d.ts +46 -0
- package/dist/core/engine/wake-ownership-guard.js +23 -0
- package/dist/core/engine/workflow-claim-codec.d.ts +64 -0
- package/dist/core/engine/workflow-claim-codec.js +44 -0
- package/dist/core/engine/workflow-claim-cooldown.d.ts +60 -0
- package/dist/core/engine/workflow-claim-cooldown.js +17 -0
- package/dist/core/engine/workflow-claim-fold.d.ts +82 -0
- package/dist/core/engine/workflow-claim-fold.js +53 -0
- package/dist/core/engine/workflow-claim-metrics.d.ts +131 -0
- package/dist/core/engine/workflow-claim-metrics.js +62 -0
- package/dist/core/engine/workflow-claim-reclaim-scan.d.ts +96 -0
- package/dist/core/engine/workflow-claim-reclaim-scan.js +66 -0
- package/dist/core/engine/workflow-claim-reclaim-target.d.ts +107 -0
- package/dist/core/engine/workflow-claim-reclaim-target.js +155 -0
- package/dist/core/engine/workflow-claim-registry.d.ts +227 -0
- package/dist/core/engine/workflow-claim-registry.js +198 -0
- package/dist/core/engine/workflow-claim-renewal-interval.d.ts +50 -0
- package/dist/core/engine/workflow-claim-renewal-interval.js +132 -0
- package/dist/core/engine/workflow-claim-renewal-subpasses.d.ts +265 -0
- package/dist/core/engine/workflow-claim-renewal-subpasses.js +104 -0
- package/dist/core/engine/workflow-claim-renewal-task.d.ts +159 -0
- package/dist/core/engine/workflow-claim-renewal-task.js +65 -0
- package/dist/core/engine/workflow-claim-transitions.d.ts +186 -0
- package/dist/core/engine/workflow-claim-transitions.js +120 -0
- package/dist/core/inline-execution-strategy.d.ts +20 -0
- package/dist/core/inline-execution-strategy.js +15 -4
- package/dist/core/scheduler/scheduler-class.js +3 -3
- package/dist/core/scheduler/timer-sources.d.ts +13 -0
- package/dist/core/scheduler/timer-sources.js +1 -1
- package/dist/core/types/options.d.ts +17 -1
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +2 -1
- package/dist/http.js +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8 -0
- package/dist/indexeddb.js +1 -1
- package/dist/json-schema.js +3 -3
- package/dist/observability/index.js +2 -2
- 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/static-registrations.js +3 -0
- package/dist/storage/auto.js +1 -1
- package/dist/storage/bun-sql.js +113 -0
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/index.d.ts +38 -35
- package/dist/storage/interface.d.ts +87 -125
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-encoding.d.ts +51 -0
- package/dist/storage/key-encoding.js +13 -0
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +3 -0
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +2 -2
- package/dist/storage/node-sqlite.js +113 -0
- package/dist/storage/ownership-keys.d.ts +55 -0
- package/dist/storage/ownership-keys.js +6 -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/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/protocol.js +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BatchOperation } from '../../storage/interface.ts';
|
|
2
2
|
import type { ScheduleState, WorkflowState } from '../types.ts';
|
|
3
3
|
import type { EngineInternals } from './internals.ts';
|
|
4
|
+
import { type WorkflowClaimTransitionFragment } from './workflow-claim-transitions.ts';
|
|
4
5
|
/** Run a workflow-state write after any earlier write for that workflow has settled. */
|
|
5
6
|
export declare function runSerializedWorkflowStateWrite<TResult>(internals: EngineInternals, workflowId: string, writeOperation: () => Promise<TResult>): Promise<TResult>;
|
|
6
7
|
/**
|
|
@@ -15,23 +16,70 @@ export declare function runSerializedWorkflowStateWrite<TResult>(internals: Engi
|
|
|
15
16
|
export declare function runSerializedScheduleStateOperation<TResult>(internals: EngineInternals, scheduleId: string, operation: () => Promise<TResult>): Promise<TResult>;
|
|
16
17
|
/** Load and decode persisted workflow state by workflow ID. */
|
|
17
18
|
export declare function loadWorkflowState(internals: EngineInternals, workflowId: string): Promise<WorkflowState | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Derive a terminal result (or throw the persisted terminal error) from an
|
|
21
|
+
* ALREADY-LOADED `WorkflowState` — pure, no storage read. Callers that hold a
|
|
22
|
+
* state snapshot they have already validated as terminal (e.g.
|
|
23
|
+
* `bootstrapWorkflowResultResolver`) must use this instead of
|
|
24
|
+
* {@link loadWorkflowResult}: a second independent `loadWorkflowState` read
|
|
25
|
+
* can observe a DIFFERENT run than the one the caller validated, if
|
|
26
|
+
* `onTerminalConflict: 'start-new'` replaces the workflow between the two
|
|
27
|
+
* reads — attributing a replacement run's result (or a spurious "still
|
|
28
|
+
* running") to a waiter that was made terminal by the original run.
|
|
29
|
+
*/
|
|
30
|
+
export declare function deriveWorkflowResultFromState(state: WorkflowState): unknown;
|
|
18
31
|
/** Load a terminal workflow result or throw the persisted terminal error. */
|
|
19
32
|
export declare function loadWorkflowResult(internals: EngineInternals, workflowId: string): Promise<unknown>;
|
|
20
33
|
type WorkflowStateCommitOptions = {
|
|
21
34
|
includePendingAtomicSideEffects?: boolean;
|
|
22
35
|
};
|
|
23
36
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
37
|
+
* Build the `wf-owner-epoch:<id>` / `wf-owner-holder:<id>` ROTATION fragment
|
|
38
|
+
* for an EXTERNAL terminal transition — cancel, timeout, suspend, purge (ADR
|
|
39
|
+
* 0002 § "External terminal transitions must rotate the epoch"). Any engine
|
|
40
|
+
* may commit these against a workflow it does not own; deleting the holder
|
|
41
|
+
* alone is not sufficient to make that safe, so the epoch is rotated in the
|
|
42
|
+
* SAME atomic batch that writes the terminal/suspended state, deposing a
|
|
43
|
+
* still-running owner — its next write carries the now-stale epoch and loses
|
|
44
|
+
* its CAS. Meant to be folded (via `[...fragment.operations]` /
|
|
45
|
+
* `[...fragment.conditions]`) into the caller's own operations/conditions,
|
|
46
|
+
* never committed standalone.
|
|
47
|
+
*
|
|
48
|
+
* Under `ownership: 'none'` or `'lease'` this returns an EMPTY fragment with
|
|
49
|
+
* NO storage read: the `wf-owner-*` keyspace is not in play under those
|
|
50
|
+
* modes, and this function must not touch it — that is what keeps external
|
|
51
|
+
* terminal commits byte-for-byte unchanged there.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildExternalTerminalRotationFragment(internals: EngineInternals, workflowId: string): Promise<WorkflowClaimTransitionFragment>;
|
|
54
|
+
/**
|
|
55
|
+
* Commit a SELF-transition workflow-state advance (complete, fail): this
|
|
56
|
+
* engine is finishing its OWN workflow, so the write is workflow-scoped and
|
|
57
|
+
* fenced on THIS engine's claim epoch under `ownership: 'workflow-lease'`
|
|
58
|
+
* (via {@link commitFencedEngineWrite}'s `workflowId` parameter), or the
|
|
59
|
+
* global lease epoch under `ownership: 'lease'`. A deposed engine's write
|
|
60
|
+
* loses its CAS instead of corrupting the successor's state; the deposition
|
|
61
|
+
* is detected and the engine (or, under `workflow-lease`, just this one
|
|
62
|
+
* workflow) halts. Under `ownership: 'none'` this is byte-for-byte the
|
|
63
|
+
* pre-ADR commit shape. Never rotates `wf-owner-epoch:<id>` — that is the
|
|
64
|
+
* external-transition shape below. Operator/external mutations
|
|
65
|
+
* (search-attribute and tag edits) do NOT use this helper; they batch
|
|
66
|
+
* directly and are intentionally never fenced.
|
|
67
|
+
*/
|
|
68
|
+
export declare function commitSelfWorkflowStateOperations(internals: EngineInternals, state: WorkflowState, operations: BatchOperation[], options?: WorkflowStateCommitOptions): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Commit an EXTERNAL terminal workflow-state transition — cancel, timeout, or
|
|
71
|
+
* suspend (ADR 0002 § "External terminal transitions must rotate the
|
|
72
|
+
* epoch"). ANY engine may commit these against a workflow it does not own, so
|
|
73
|
+
* — unlike {@link commitSelfWorkflowStateOperations} — this is never fenced
|
|
74
|
+
* on this engine's own workflow claim (`workflowId: null` is passed to
|
|
75
|
+
* {@link commitFencedEngineWrite}). Instead {@link
|
|
76
|
+
* buildExternalTerminalRotationFragment} folds a claim ROTATION into the same
|
|
77
|
+
* atomic batch under `ownership: 'workflow-lease'`, deposing a still-running
|
|
78
|
+
* owner. Under `ownership: 'lease'` the write still carries the global lease
|
|
79
|
+
* epoch condition (via `commitFencedEngineWrite`'s `workflowId: null` path);
|
|
80
|
+
* under `'none'` it is byte-for-byte unchanged.
|
|
33
81
|
*/
|
|
34
|
-
export declare function
|
|
82
|
+
export declare function commitExternalTerminalWorkflowStateOperations(internals: EngineInternals, state: WorkflowState, operations: BatchOperation[], options?: WorkflowStateCommitOptions): Promise<void>;
|
|
35
83
|
/** Load and decode persisted schedule state by schedule ID. */
|
|
36
84
|
export declare function loadScheduleState(internals: EngineInternals, scheduleId: string): Promise<ScheduleState | null>;
|
|
37
85
|
/** Load a schedule state. */
|
|
@@ -14,6 +14,9 @@ import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
|
14
14
|
import { createScheduleTimerId, decodeWorkflowStartHeaders } from "./state-utilities.js";
|
|
15
15
|
import { decodeWorkflowState } from "./validation.js";
|
|
16
16
|
import { decodeScheduleState } from "./validation/schedule.js";
|
|
17
|
+
import {
|
|
18
|
+
buildWorkflowClaimExternalTerminalRotationTransition
|
|
19
|
+
} from "./workflow-claim-transitions.js";
|
|
17
20
|
export async function runSerializedWorkflowStateWrite(internals, workflowId, writeOperation) {
|
|
18
21
|
const execution = (internals.workflowStateWriteChains.get(workflowId) ?? Promise.resolve()).catch(() => {
|
|
19
22
|
return;
|
|
@@ -51,10 +54,7 @@ export async function loadWorkflowState(internals, workflowId) {
|
|
|
51
54
|
return null;
|
|
52
55
|
return decodeWorkflowState(bytes);
|
|
53
56
|
}
|
|
54
|
-
export
|
|
55
|
-
const state = await loadWorkflowState(internals, workflowId);
|
|
56
|
-
if (!state)
|
|
57
|
-
throw Error(`Workflow "${workflowId}" not found`);
|
|
57
|
+
export function deriveWorkflowResultFromState(state) {
|
|
58
58
|
if (state.status === "completed")
|
|
59
59
|
return state.result;
|
|
60
60
|
if (state.status === "failed") {
|
|
@@ -67,9 +67,15 @@ export async function loadWorkflowResult(internals, workflowId) {
|
|
|
67
67
|
throw Error("Workflow cancelled");
|
|
68
68
|
if (state.status === "timed-out") {
|
|
69
69
|
const elapsed = state.executionDeadline ? state.executionDeadline - getWorkflowExecutionStartedAt(state) : 0;
|
|
70
|
-
throw new WorkflowTimeoutError(
|
|
70
|
+
throw new WorkflowTimeoutError(state.id, "execution", elapsed, state.terminationReason);
|
|
71
71
|
}
|
|
72
|
-
throw Error(`Workflow "${
|
|
72
|
+
throw Error(`Workflow "${state.id}" is still ${state.status}`);
|
|
73
|
+
}
|
|
74
|
+
export async function loadWorkflowResult(internals, workflowId) {
|
|
75
|
+
const state = await loadWorkflowState(internals, workflowId);
|
|
76
|
+
if (!state)
|
|
77
|
+
throw Error(`Workflow "${workflowId}" not found`);
|
|
78
|
+
return deriveWorkflowResultFromState(state);
|
|
73
79
|
}
|
|
74
80
|
function buildWorkflowStateCommit(internals, workflowId, operations, options) {
|
|
75
81
|
const pendingSideEffects = options.includePendingAtomicSideEffects ? takePendingAtomicWorkflowCommitSideEffects(internals, workflowId) : void 0, operationsWithSideEffects = pendingSideEffects === void 0 ? operations : [...operations, ...pendingSideEffects.operations], conditions = internals.storage.capabilities().conditionalBatch ? pendingSideEffects?.conditions ?? [] : [];
|
|
@@ -79,9 +85,21 @@ function buildWorkflowStateCommit(internals, workflowId, operations, options) {
|
|
|
79
85
|
hasPendingSideEffects: pendingSideEffects !== void 0
|
|
80
86
|
};
|
|
81
87
|
}
|
|
82
|
-
export async function
|
|
88
|
+
export async function buildExternalTerminalRotationFragment(internals, workflowId) {
|
|
89
|
+
if (internals.options.ownershipMode !== "workflow-lease")
|
|
90
|
+
return { conditions: [], operations: [] };
|
|
91
|
+
const observedEpochBytes = await internals.storage.get(KEYS.workflowOwnerEpoch(workflowId));
|
|
92
|
+
return buildWorkflowClaimExternalTerminalRotationTransition({ workflowId, observedEpochBytes });
|
|
93
|
+
}
|
|
94
|
+
export async function commitSelfWorkflowStateOperations(internals, state, operations, options = {}) {
|
|
83
95
|
const commit = buildWorkflowStateCommit(internals, state.id, operations, options);
|
|
84
|
-
await commitFencedEngineWrite(internals, commit.operations, commit.conditions, () => Error(`Workflow state commit for workflow "${state.id}" lost its atomic side-effect precondition.`));
|
|
96
|
+
await commitFencedEngineWrite(internals, state.id, commit.operations, commit.conditions, () => Error(`Workflow state commit for workflow "${state.id}" lost its atomic side-effect precondition.`));
|
|
97
|
+
if (commit.hasPendingSideEffects)
|
|
98
|
+
clearPendingAtomicWorkflowCommitSideEffects(internals, state.id);
|
|
99
|
+
}
|
|
100
|
+
export async function commitExternalTerminalWorkflowStateOperations(internals, state, operations, options = {}) {
|
|
101
|
+
const commit = buildWorkflowStateCommit(internals, state.id, operations, options), rotation = await buildExternalTerminalRotationFragment(internals, state.id);
|
|
102
|
+
await commitFencedEngineWrite(internals, null, [...commit.operations, ...rotation.operations], [...commit.conditions, ...rotation.conditions], () => Error(`External terminal commit for workflow "${state.id}" lost a commit precondition (staged side effects, or the ownership epoch rotation under \`workflow-lease\`).`));
|
|
85
103
|
if (commit.hasPendingSideEffects)
|
|
86
104
|
clearPendingAtomicWorkflowCommitSideEffects(internals, state.id);
|
|
87
105
|
}
|
|
@@ -132,7 +150,7 @@ export async function writeScheduleState(internals, state, options) {
|
|
|
132
150
|
], includeTimer = options?.includeTimer ?? state.status === "active";
|
|
133
151
|
operations.push(...buildScheduleTimerReplacementOperations(state, includeTimer, options?.replaceTimerFrom));
|
|
134
152
|
operations.push(...options?.additionalOperations ?? []);
|
|
135
|
-
await commitFencedEngineWrite(internals, operations, [], () => Error(`Schedule state commit for schedule "${state.id}" lost its precondition.`));
|
|
153
|
+
await commitFencedEngineWrite(internals, null, operations, [], () => Error(`Schedule state commit for schedule "${state.id}" lost its precondition.`));
|
|
136
154
|
}
|
|
137
155
|
export async function loadWorkflowStartHeaders(internals, workflowId) {
|
|
138
156
|
const bytes = await internals.storage.get(KEYS.workflowHeaders(workflowId));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { BatchOperation } from '../../../storage/interface.ts';
|
|
2
1
|
import type { WorkflowState, WorkflowStatus } from '../../types.ts';
|
|
3
2
|
import type { EngineInternals } from '../internals.ts';
|
|
3
|
+
import type { WorkflowStateCommitCallback } from './state-commit-callbacks.ts';
|
|
4
4
|
export type TerminationCallbacks = {
|
|
5
5
|
dispatchEvent: (event: Event) => void;
|
|
6
6
|
forwardEventToHandle: (workflowId: string, event: Event) => void;
|
|
@@ -13,9 +13,10 @@ export type TerminationCallbacks = {
|
|
|
13
13
|
handleScheduledWorkflowTerminal: (workflowId: string) => Promise<void>;
|
|
14
14
|
loadWorkflowState: (workflowId: string) => Promise<WorkflowState | null>;
|
|
15
15
|
runSerializedWorkflowStateWrite: <Result>(workflowId: string, writeOperation: () => Promise<Result>) => Promise<Result>;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/** SELF-transition (complete): fences on this engine's own claim. See ADR 0002. */
|
|
17
|
+
commitSelfWorkflowStateOperations: WorkflowStateCommitCallback;
|
|
18
|
+
/** EXTERNAL terminal transition (suspend): rotates the claim epoch. See ADR 0002. */
|
|
19
|
+
commitExternalTerminalWorkflowStateOperations: WorkflowStateCommitCallback;
|
|
19
20
|
cleanupReviews: (workflowId: string) => Promise<void>;
|
|
20
21
|
};
|
|
21
22
|
export declare const TERMINAL_WORKFLOW_STATUSES: ReadonlySet<WorkflowStatus>;
|
|
@@ -64,7 +64,7 @@ export async function terminateWorkflow(internals, workflowId, status, callbacks
|
|
|
64
64
|
status,
|
|
65
65
|
...reason !== void 0 ? { terminationReason: reason } : {},
|
|
66
66
|
...terminalCleanupToken !== void 0 ? { terminalCleanupToken } : {}
|
|
67
|
-
}, {
|
|
67
|
+
}, "external-terminal", {
|
|
68
68
|
allowedStatuses: FORCIBLY_TERMINABLE_STATUSES,
|
|
69
69
|
buildAdditionalOperations: (_previousState, updatedAt) => {
|
|
70
70
|
finalizePendingTimelineEntry(internals, workflowId, status, terminationMessage, updatedAt);
|
|
@@ -78,6 +78,7 @@ export async function terminateWorkflow(internals, workflowId, status, callbacks
|
|
|
78
78
|
});
|
|
79
79
|
if (!terminationResult)
|
|
80
80
|
return;
|
|
81
|
+
const claimEpoch = captureCurrentClaimEpoch(internals, workflowId);
|
|
81
82
|
await runCancellationHandlersForStatus(internals, workflowId, status, callbacks);
|
|
82
83
|
await releaseWorkflowConcurrencySlot(internals, workflowId);
|
|
83
84
|
const { previousState, updatedAt } = terminationResult, elapsed = updatedAt - getWorkflowExecutionStartedAt(previousState);
|
|
@@ -87,6 +88,7 @@ export async function terminateWorkflow(internals, workflowId, status, callbacks
|
|
|
87
88
|
if (previousState.status === "pending")
|
|
88
89
|
callbacks.swallowPromiseRejection(internals.scheduler.cancel(`delayed-start:${workflowId}`, workflowId));
|
|
89
90
|
const resolver = internals.resultResolvers.get(workflowId), terminalError = buildTerminalError(workflowId, status, elapsed, reason);
|
|
91
|
+
await releaseWorkflowClaimAfterTerminalSettlement(internals, workflowId, claimEpoch);
|
|
90
92
|
try {
|
|
91
93
|
await cleanupTerminalWorkflowSynchronously(internals, workflowId, !0, callbacks);
|
|
92
94
|
const event = buildTerminalEvent(workflowId, status, elapsed, reason);
|
|
@@ -165,6 +167,19 @@ function notifyCompletionWaiters(internals, workflowId, result, duration, callba
|
|
|
165
167
|
internals.resultResolvers.delete(workflowId);
|
|
166
168
|
}
|
|
167
169
|
}
|
|
170
|
+
function captureCurrentClaimEpoch(internals, workflowId) {
|
|
171
|
+
return internals.workflowClaimRegistry?.currentEpoch(workflowId) ?? null;
|
|
172
|
+
}
|
|
173
|
+
async function releaseWorkflowClaimAfterTerminalSettlement(internals, workflowId, capturedEpoch) {
|
|
174
|
+
const registry = internals.workflowClaimRegistry;
|
|
175
|
+
if (registry === null || capturedEpoch === null)
|
|
176
|
+
return;
|
|
177
|
+
if (registry.currentEpoch(workflowId) !== capturedEpoch)
|
|
178
|
+
return;
|
|
179
|
+
try {
|
|
180
|
+
await registry.release(workflowId);
|
|
181
|
+
} catch {}
|
|
182
|
+
}
|
|
168
183
|
export async function completeWorkflow(internals, workflowId, result, callbacks) {
|
|
169
184
|
const completionMetadata = await callbacks.runSerializedWorkflowStateWrite(workflowId, async () => {
|
|
170
185
|
const state = await callbacks.loadWorkflowState(workflowId);
|
|
@@ -180,14 +195,16 @@ export async function completeWorkflow(internals, workflowId, result, callbacks)
|
|
|
180
195
|
appendSearchAttributeOperations(completionOperations, workflowId, currentAttributes);
|
|
181
196
|
if (terminalCleanupToken !== void 0)
|
|
182
197
|
completionOperations.push(...buildTerminalCleanupTimerOperations(internals, workflowId, !1, now, terminalCleanupToken));
|
|
183
|
-
await callbacks.
|
|
198
|
+
await callbacks.commitSelfWorkflowStateOperations(state, completionOperations, {
|
|
184
199
|
includePendingAtomicSideEffects: !0
|
|
185
200
|
});
|
|
186
|
-
|
|
201
|
+
const claimEpoch = captureCurrentClaimEpoch(internals, workflowId);
|
|
202
|
+
return { duration, claimEpoch };
|
|
187
203
|
});
|
|
188
204
|
if (!completionMetadata)
|
|
189
205
|
return;
|
|
190
206
|
await releaseWorkflowConcurrencySlot(internals, workflowId);
|
|
207
|
+
await releaseWorkflowClaimAfterTerminalSettlement(internals, workflowId, completionMetadata.claimEpoch);
|
|
191
208
|
notifyCompletionWaiters(internals, workflowId, result, completionMetadata.duration, callbacks);
|
|
192
209
|
}
|
|
193
210
|
export async function failWorkflow(internals, workflowId, error, callbacks, failureCategory = "system") {
|
|
@@ -202,7 +219,7 @@ export async function failWorkflow(internals, workflowId, error, callbacks, fail
|
|
|
202
219
|
stateUpdate.errorStack = error.stack;
|
|
203
220
|
if (terminalCleanupToken !== void 0)
|
|
204
221
|
stateUpdate.terminalCleanupToken = terminalCleanupToken;
|
|
205
|
-
if (!await updateWorkflowState(internals, workflowId, stateUpdate, {
|
|
222
|
+
if (!await updateWorkflowState(internals, workflowId, stateUpdate, "self", {
|
|
206
223
|
allowedStatuses: FORCIBLY_TERMINABLE_STATUSES,
|
|
207
224
|
buildAdditionalOperations: (_previousState, updatedAt) => {
|
|
208
225
|
finalizePendingTimelineEntry(internals, workflowId, "failed", error.message, updatedAt);
|
|
@@ -214,11 +231,13 @@ export async function failWorkflow(internals, workflowId, error, callbacks, fail
|
|
|
214
231
|
}
|
|
215
232
|
}))
|
|
216
233
|
return;
|
|
234
|
+
const claimEpoch = captureCurrentClaimEpoch(internals, workflowId);
|
|
217
235
|
await releaseWorkflowConcurrencySlot(internals, workflowId);
|
|
218
236
|
await cleanupAttributeIndex(internals, workflowId, attributes);
|
|
219
237
|
callbacks.swallowPromiseRejection(internals.scheduler.cancel(`deadline:${workflowId}`, workflowId));
|
|
220
238
|
await writeRetainedTerminalSearchAttributes(internals, workflowId, retainedAttributes);
|
|
221
239
|
const resolver = internals.resultResolvers.get(workflowId);
|
|
240
|
+
await releaseWorkflowClaimAfterTerminalSettlement(internals, workflowId, claimEpoch);
|
|
222
241
|
try {
|
|
223
242
|
await cleanupTerminalWorkflowSynchronously(internals, workflowId, !1, callbacks);
|
|
224
243
|
const event = new WorkflowFailedEvent(workflowId, error);
|
|
@@ -45,22 +45,22 @@ export function encodeRunningClaim(attempts, token, claimedAt) {
|
|
|
45
45
|
export async function rearmTeardownTimer(internals, workflowId, token, delayMs) {
|
|
46
46
|
const fireAt = internals.options.getNow() + delayMs;
|
|
47
47
|
try {
|
|
48
|
-
await commitFencedEngineWrite(internals, teardownTimerOperations(token, workflowId, fireAt), [], () => Error("teardown self-heal re-arm lost the lease fence"));
|
|
48
|
+
await commitFencedEngineWrite(internals, workflowId, teardownTimerOperations(token, workflowId, fireAt), [], () => Error("teardown self-heal re-arm lost the lease fence"));
|
|
49
49
|
} catch {}
|
|
50
50
|
}
|
|
51
51
|
export async function clearTeardownMarker(internals, workflowId, expectedBytes) {
|
|
52
52
|
try {
|
|
53
|
-
return await commitFencedEngineWriteAllowingPreconditionFailure(internals, [{ type: "delete", key: KEYS.teardownOwed(workflowId) }], [{ key: KEYS.teardownOwed(workflowId), expectedValue: expectedBytes }]);
|
|
53
|
+
return await commitFencedEngineWriteAllowingPreconditionFailure(internals, workflowId, [{ type: "delete", key: KEYS.teardownOwed(workflowId) }], [{ key: KEYS.teardownOwed(workflowId), expectedValue: expectedBytes }]);
|
|
54
54
|
} catch {
|
|
55
55
|
return !1;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
export async function claimTeardownMarker(internals, workflowId, expectedBytes, attempts, token) {
|
|
59
59
|
const runningBytes = encodeRunningClaim(attempts, token, internals.options.getNow());
|
|
60
|
-
return await commitFencedEngineWriteAllowingPreconditionFailure(internals, [{ type: "put", key: KEYS.teardownOwed(workflowId), value: runningBytes }], [{ key: KEYS.teardownOwed(workflowId), expectedValue: expectedBytes }]) ? runningBytes : null;
|
|
60
|
+
return await commitFencedEngineWriteAllowingPreconditionFailure(internals, workflowId, [{ type: "put", key: KEYS.teardownOwed(workflowId), value: runningBytes }], [{ key: KEYS.teardownOwed(workflowId), expectedValue: expectedBytes }]) ? runningBytes : null;
|
|
61
61
|
}
|
|
62
62
|
export async function settleOnRunningClaim(internals, workflowId, runningBytes, operations) {
|
|
63
|
-
return commitFencedEngineWriteAllowingPreconditionFailure(internals, operations, [
|
|
63
|
+
return commitFencedEngineWriteAllowingPreconditionFailure(internals, workflowId, operations, [
|
|
64
64
|
{ key: KEYS.teardownOwed(workflowId), expectedValue: runningBytes }
|
|
65
65
|
]);
|
|
66
66
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BatchOperation } from '../../../storage/interface.ts';
|
|
2
|
+
import type { WorkflowState } from '../../types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Shared function shape for the two workflow-state commit callbacks
|
|
5
|
+
* {@link TerminationCallbacks} exposes, split by ADR 0002's self/
|
|
6
|
+
* external-terminal transition classification (see
|
|
7
|
+
* `documentation/contributing/architecture-decisions/0002-multiengine-per-workflow-ownership.md`).
|
|
8
|
+
* A self-transition (complete, fail) fences on this engine's own claim; an
|
|
9
|
+
* external terminal transition (cancel, timeout, suspend) rotates the claim
|
|
10
|
+
* epoch instead, so any engine may commit it. Kept as one shared type so the
|
|
11
|
+
* two `TerminationCallbacks` fields cannot drift in shape.
|
|
12
|
+
*/
|
|
13
|
+
export type WorkflowStateCommitCallback = (state: WorkflowState, operations: BatchOperation[], options?: {
|
|
14
|
+
includePendingAtomicSideEffects?: boolean;
|
|
15
|
+
}) => Promise<void>;
|
|
File without changes
|
|
@@ -18,7 +18,7 @@ export async function suspendWorkflow(internals, workflowId, callbacks) {
|
|
|
18
18
|
internals.parkedInlineWorkflows.delete(workflowId);
|
|
19
19
|
evictSuspendedWorkflowWaiters(internals, workflowId, callbacks);
|
|
20
20
|
const updatedAt = internals.options.getNow(), updatedState = { ...state, status: "suspended", updatedAt };
|
|
21
|
-
await callbacks.
|
|
21
|
+
await callbacks.commitExternalTerminalWorkflowStateOperations(state, [
|
|
22
22
|
{ type: "put", key: KEYS.workflow(workflowId), value: encode(updatedState) },
|
|
23
23
|
...buildWorkflowVisibilityIndexTransition(workflowId, state, updatedState).batchOps,
|
|
24
24
|
...buildDeadlineTimerDeleteOperations(workflowId, state.executionDeadline)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-acceptance validation for workflow updates: running a registered
|
|
3
|
+
* `updateValidators` entry and normalizing a Standard Schema v1 failure result
|
|
4
|
+
* into the issue list `UpdateValidationError` carries.
|
|
5
|
+
*
|
|
6
|
+
* Lives apart from `updates.ts` because it is shared — `pending-updates.ts`
|
|
7
|
+
* normalizes validator results through the same helper — and because it is
|
|
8
|
+
* pure payload validation with no coupling to the in-memory waiter maps or the
|
|
9
|
+
* durable coordinated-update protocol that make up the rest of update delivery.
|
|
10
|
+
*
|
|
11
|
+
* @module core/engine/update-validation
|
|
12
|
+
*/
|
|
13
|
+
import type { EngineInternals } from './internals.ts';
|
|
14
|
+
/**
|
|
15
|
+
* Run the pre-acceptance validator for an update, if one is registered.
|
|
16
|
+
* Throws `UpdateValidationError` if the validator rejects (by throwing or by
|
|
17
|
+
* returning a Standard Schema `{ issues: [...] }` failure result).
|
|
18
|
+
*/
|
|
19
|
+
export declare function runUpdateValidator(internals: EngineInternals, workflowId: string, name: string, payload: unknown): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Extract issues from a Standard Schema v1 failure result, or null if absent.
|
|
22
|
+
* No string-`message` entries yields `[]`; callers reject only on a non-empty
|
|
23
|
+
* array, so `null` and `[]` both mean acceptance. Preserves `path` (RFC 6901).
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractStandardSchemaIssues(result: unknown): Array<{
|
|
26
|
+
message: string;
|
|
27
|
+
path?: string;
|
|
28
|
+
}> | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { UpdateValidationError } from "../updates.js";
|
|
2
|
+
export async function runUpdateValidator(internals, workflowId, name, payload) {
|
|
3
|
+
const validator = internals.inlineStrategy?.getContext(workflowId)?.updateValidators.get(name);
|
|
4
|
+
if (validator === void 0)
|
|
5
|
+
return;
|
|
6
|
+
let result;
|
|
7
|
+
try {
|
|
8
|
+
result = await validator(payload);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11
|
+
throw new UpdateValidationError(name, [{ message }]);
|
|
12
|
+
}
|
|
13
|
+
const issues = extractStandardSchemaIssues(result);
|
|
14
|
+
if (issues !== null && issues.length > 0)
|
|
15
|
+
throw new UpdateValidationError(name, issues);
|
|
16
|
+
}
|
|
17
|
+
export function extractStandardSchemaIssues(result) {
|
|
18
|
+
if (result === null || typeof result !== "object" || !("issues" in result))
|
|
19
|
+
return null;
|
|
20
|
+
const { issues } = result;
|
|
21
|
+
if (!Array.isArray(issues))
|
|
22
|
+
return null;
|
|
23
|
+
return issues.flatMap((issue) => {
|
|
24
|
+
if (issue === null || typeof issue !== "object")
|
|
25
|
+
return [];
|
|
26
|
+
const obj = issue;
|
|
27
|
+
if (typeof obj.message !== "string")
|
|
28
|
+
return [];
|
|
29
|
+
const entry = { message: obj.message };
|
|
30
|
+
if (Array.isArray(obj.path) && obj.path.length > 0)
|
|
31
|
+
entry.path = obj.path.reduce((p, seg) => {
|
|
32
|
+
const k = seg !== null && typeof seg === "object" && "key" in seg ? String(seg.key) : String(seg);
|
|
33
|
+
return p + "/" + k.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
34
|
+
}, "");
|
|
35
|
+
return [entry];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ContextOperationRequest } from '../context.ts';
|
|
2
2
|
import type { CoordinatedUpdateResult } from '../types.ts';
|
|
3
|
-
import {
|
|
3
|
+
import type { UpdateRequest, UpdateResponse } from '../updates.ts';
|
|
4
4
|
import type { EngineInternals } from './internals.ts';
|
|
5
5
|
export type UpdateCallbacks = {
|
|
6
6
|
dispatchEvent: (event: Event) => boolean;
|
|
@@ -22,6 +22,22 @@ export type UpdateCallbacks = {
|
|
|
22
22
|
export declare function update(internals: EngineInternals, workflowId: string, name: string, payload: unknown, options: {
|
|
23
23
|
timeout?: number;
|
|
24
24
|
} | undefined, callbacks: UpdateCallbacks): Promise<unknown>;
|
|
25
|
+
/**
|
|
26
|
+
* `handled: false` used to conflate two reasons: no live local context at all
|
|
27
|
+
* (`'not-owned-locally'`, possible under `ownership: 'workflow-lease'` when
|
|
28
|
+
* another engine holds the claim) versus a live context with no handler
|
|
29
|
+
* registered for `name` (`'no-handler'`). See the `update()` call site for
|
|
30
|
+
* why neither is routed differently — the reason is exposed so a caller that
|
|
31
|
+
* DOES care (tests, future routing) can tell them apart.
|
|
32
|
+
*/
|
|
33
|
+
export type InlineUpdateAttemptResult = {
|
|
34
|
+
handled: true;
|
|
35
|
+
value: unknown;
|
|
36
|
+
} | {
|
|
37
|
+
handled: false;
|
|
38
|
+
reason: 'not-owned-locally' | 'no-handler';
|
|
39
|
+
};
|
|
40
|
+
export declare function tryInlineUpdateHandler(internals: EngineInternals, workflowId: string, name: string, payload: unknown, callbacks: UpdateCallbacks): Promise<InlineUpdateAttemptResult>;
|
|
25
41
|
/** Retrieve the result of a coordinated update by its ID. */
|
|
26
42
|
export declare function getUpdateResult(internals: EngineInternals, updateId: string): Promise<UpdateResponse | null>;
|
|
27
43
|
/**
|
|
@@ -39,18 +55,3 @@ export declare function dispatchPendingUpdateReceived(_internals: EngineInternal
|
|
|
39
55
|
export declare function createCoordinatedUpdateResponder(_internals: EngineInternals, workflowId: string, updateName: string, updateRequest: UpdateRequest, callbacks: Pick<UpdateCallbacks, 'persistCoordinatedUpdateResponse'>): (value: unknown) => void;
|
|
40
56
|
export declare function deliverCoordinatedUpdateToWaiterIfAvailable(internals: EngineInternals, workflowId: string, updateRequest: UpdateRequest, dispatchReceivedEvent: boolean | undefined, callbacks: UpdateCallbacks): Promise<boolean>;
|
|
41
57
|
export declare function findPendingUpdateByName(internals: EngineInternals, workflowId: string, name: string): Promise<UpdateRequest | undefined>;
|
|
42
|
-
/**
|
|
43
|
-
* Invoke an update handler, checking that it does not return a generator.
|
|
44
|
-
* Centralises the runtime generator guard for both the inline-handler path
|
|
45
|
-
* in `update()` and the pending-drain path on resume.
|
|
46
|
-
*/
|
|
47
|
-
export declare function invokeUpdateHandler(_internals: EngineInternals, name: string, handler: (payload: unknown) => unknown, payload: unknown): Promise<unknown>;
|
|
48
|
-
/**
|
|
49
|
-
* Extract issues from a Standard Schema v1 failure result, or null if absent.
|
|
50
|
-
* No string-`message` entries yields `[]`; callers reject only on a non-empty
|
|
51
|
-
* array, so `null` and `[]` both mean acceptance. Preserves `path` (RFC 6901).
|
|
52
|
-
*/
|
|
53
|
-
export declare function extractStandardSchemaIssues(result: unknown): Array<{
|
|
54
|
-
message: string;
|
|
55
|
-
path?: string;
|
|
56
|
-
}> | null;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { UpdateCompletedEvent, UpdateReceivedEvent } from "../events.js";
|
|
2
|
-
import { isGeneratorResult } from "../step-context.js";
|
|
3
|
-
import { UpdateValidationError } from "../updates.js";
|
|
4
2
|
import { notifyConditionWaiters } from "./condition-waiters.js";
|
|
3
|
+
import { invokeUpdateHandler } from "./invoke-update-handler.js";
|
|
4
|
+
import { isLiveContextStale, isWorkflowClaimedByAnotherEngine } from "./queries.js";
|
|
5
5
|
import { trackWaiterKey, untrackWaiterKey } from "./signals.js";
|
|
6
|
+
import { runUpdateValidator } from "./update-validation.js";
|
|
6
7
|
import { waitForUpdateResponse } from "./waiting-update-response.js";
|
|
8
|
+
import { confirmWakeOwnership } from "./wake-ownership-guard.js";
|
|
7
9
|
export async function update(internals, workflowId, name, payload, options, callbacks) {
|
|
8
10
|
const timeout = options?.timeout ?? 30000;
|
|
9
11
|
await runUpdateValidator(internals, workflowId, name, payload);
|
|
@@ -11,19 +13,32 @@ export async function update(internals, workflowId, name, payload, options, call
|
|
|
11
13
|
const inlineResult = await tryInlineUpdateHandler(internals, workflowId, name, payload, callbacks);
|
|
12
14
|
if (inlineResult.handled)
|
|
13
15
|
return inlineResult.value;
|
|
16
|
+
if (inlineResult.reason === "not-owned-locally")
|
|
17
|
+
return await runCoordinatedUpdate(internals, workflowId, name, payload, timeout, callbacks);
|
|
14
18
|
const waitingResult = await tryWaitingUpdateHandler(internals, workflowId, name, payload, timeout, callbacks);
|
|
15
19
|
if (waitingResult.handled)
|
|
16
20
|
return waitingResult.value;
|
|
17
21
|
return await runCoordinatedUpdate(internals, workflowId, name, payload, timeout, callbacks);
|
|
18
22
|
}
|
|
19
|
-
|
|
23
|
+
function refreshUpdateHandlerAfterAwait(internals, workflowId, name, capturedHandler, stale) {
|
|
24
|
+
if (stale === !1)
|
|
25
|
+
return capturedHandler;
|
|
26
|
+
return internals.inlineStrategy?.getContext(workflowId)?.updateHandlers.get(name);
|
|
27
|
+
}
|
|
28
|
+
export async function tryInlineUpdateHandler(internals, workflowId, name, payload, callbacks) {
|
|
20
29
|
const handler = internals.inlineStrategy?.getContext(workflowId)?.updateHandlers.get(name);
|
|
21
30
|
if (!handler)
|
|
22
|
-
return { handled: !1 };
|
|
31
|
+
return { handled: !1, reason: await isWorkflowClaimedByAnotherEngine(internals, workflowId) ? "not-owned-locally" : "no-handler" };
|
|
32
|
+
const stale = isLiveContextStale(internals, workflowId);
|
|
33
|
+
if (stale !== !1 && await stale)
|
|
34
|
+
return { handled: !1, reason: "not-owned-locally" };
|
|
35
|
+
const liveHandler = refreshUpdateHandlerAfterAwait(internals, workflowId, name, handler, stale);
|
|
36
|
+
if (!liveHandler)
|
|
37
|
+
return { handled: !1, reason: "no-handler" };
|
|
23
38
|
const updateId = crypto.randomUUID();
|
|
24
39
|
callbacks.dispatchEvent(new UpdateReceivedEvent(updateId, workflowId, name, payload));
|
|
25
40
|
try {
|
|
26
|
-
const result = await invokeUpdateHandler(internals, name,
|
|
41
|
+
const result = await invokeUpdateHandler(internals, name, liveHandler, payload);
|
|
27
42
|
callbacks.dispatchEvent(new UpdateCompletedEvent(updateId, workflowId, name, result));
|
|
28
43
|
callbacks.broadcast({ type: "update:completed", workflowId, updateId });
|
|
29
44
|
notifyConditionWaiters(internals, workflowId);
|
|
@@ -43,6 +58,8 @@ async function tryWaitingUpdateHandler(internals, workflowId, name, payload, tim
|
|
|
43
58
|
const existingPendingUpdate = await callbacks.findPendingUpdateByName(workflowId, name);
|
|
44
59
|
if (internals.updateWaiters.get(waiterKey) !== updateWaiter || existingPendingUpdate)
|
|
45
60
|
return { handled: !1 };
|
|
61
|
+
if (await confirmWakeOwnership(internals, workflowId, "update") === "discard")
|
|
62
|
+
return { handled: !1 };
|
|
46
63
|
internals.updateWaiters.delete(waiterKey);
|
|
47
64
|
untrackWaiterKey(internals.updateWaitersByWorkflow, workflowId, waiterKey);
|
|
48
65
|
const updateId = crypto.randomUUID();
|
|
@@ -152,6 +169,8 @@ export async function deliverCoordinatedUpdateToWaiterIfAvailable(internals, wor
|
|
|
152
169
|
const oldestPendingUpdate = await callbacks.findPendingUpdateByName(workflowId, updateRequest.name);
|
|
153
170
|
if (!oldestPendingUpdate || oldestPendingUpdate.updateId !== updateRequest.updateId)
|
|
154
171
|
return !1;
|
|
172
|
+
if (await confirmWakeOwnership(internals, workflowId, "update") === "discard")
|
|
173
|
+
return !1;
|
|
155
174
|
await internals.updateCoordinator.deleteRequest(workflowId, updateRequest.updateId);
|
|
156
175
|
internals.updateWaiters.delete(waiterKey);
|
|
157
176
|
untrackWaiterKey(internals.updateWaitersByWorkflow, workflowId, waiterKey);
|
|
@@ -166,45 +185,3 @@ export async function deliverCoordinatedUpdateToWaiterIfAvailable(internals, wor
|
|
|
166
185
|
export async function findPendingUpdateByName(internals, workflowId, name) {
|
|
167
186
|
return (await internals.updateCoordinator.getPendingUpdates(workflowId)).find((updateRequest) => updateRequest.name === name);
|
|
168
187
|
}
|
|
169
|
-
export async function invokeUpdateHandler(_internals, name, handler, payload) {
|
|
170
|
-
const result = handler(payload);
|
|
171
|
-
if (isGeneratorResult(result))
|
|
172
|
-
throw TypeError(`Update handler "${name}" returned a generator. Update handlers must return a plain value or a Promise, not a generator.`);
|
|
173
|
-
return await result;
|
|
174
|
-
}
|
|
175
|
-
async function runUpdateValidator(internals, workflowId, name, payload) {
|
|
176
|
-
const validator = internals.inlineStrategy?.getContext(workflowId)?.updateValidators.get(name);
|
|
177
|
-
if (validator === void 0)
|
|
178
|
-
return;
|
|
179
|
-
let result;
|
|
180
|
-
try {
|
|
181
|
-
result = await validator(payload);
|
|
182
|
-
} catch (error) {
|
|
183
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
184
|
-
throw new UpdateValidationError(name, [{ message }]);
|
|
185
|
-
}
|
|
186
|
-
const issues = extractStandardSchemaIssues(result);
|
|
187
|
-
if (issues !== null && issues.length > 0)
|
|
188
|
-
throw new UpdateValidationError(name, issues);
|
|
189
|
-
}
|
|
190
|
-
export function extractStandardSchemaIssues(result) {
|
|
191
|
-
if (result === null || typeof result !== "object" || !("issues" in result))
|
|
192
|
-
return null;
|
|
193
|
-
const { issues } = result;
|
|
194
|
-
if (!Array.isArray(issues))
|
|
195
|
-
return null;
|
|
196
|
-
return issues.flatMap((issue) => {
|
|
197
|
-
if (issue === null || typeof issue !== "object")
|
|
198
|
-
return [];
|
|
199
|
-
const obj = issue;
|
|
200
|
-
if (typeof obj.message !== "string")
|
|
201
|
-
return [];
|
|
202
|
-
const entry = { message: obj.message };
|
|
203
|
-
if (Array.isArray(obj.path) && obj.path.length > 0)
|
|
204
|
-
entry.path = obj.path.reduce((p, seg) => {
|
|
205
|
-
const k = seg !== null && typeof seg === "object" && "key" in seg ? String(seg.key) : String(seg);
|
|
206
|
-
return p + "/" + k.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
207
|
-
}, "");
|
|
208
|
-
return [entry];
|
|
209
|
-
});
|
|
210
|
-
}
|