@lostgradient/weft 0.2.0 → 0.3.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 +47 -22
- package/dist/cli/generated/operation-client.generated.d.ts +28 -1
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/cli-main.js +79 -79
- package/dist/client/handle-delegation.d.ts +4 -0
- package/dist/client/handle-delegation.js +6 -0
- package/dist/client/http-client-requests.d.ts +2 -0
- package/dist/client/http-client-requests.js +3 -0
- package/dist/client/http-client.d.ts +4 -1
- package/dist/client/http-client.js +9 -1
- package/dist/client/interface.d.ts +57 -2
- package/dist/client/local.d.ts +4 -1
- package/dist/client/local.js +7 -0
- package/dist/client/start-body.d.ts +7 -1
- package/dist/client/start-body.js +13 -4
- package/dist/core/codec/extension-codec.js +4 -2
- package/dist/core/codec/index.d.ts +1 -0
- package/dist/core/codec/index.js +1 -0
- package/dist/core/codec/serializer-registry.d.ts +122 -0
- package/dist/core/codec/serializer-registry.js +51 -0
- package/dist/core/context/index.d.ts +10 -0
- package/dist/core/context/index.js +3 -0
- package/dist/core/context/internals.d.ts +10 -0
- package/dist/core/context/internals.js +5 -1
- package/dist/core/context/run-operation.d.ts +16 -3
- package/dist/core/context/run-operation.js +16 -7
- package/dist/core/context/speculative-child.js +2 -0
- package/dist/core/context/types.d.ts +6 -0
- package/dist/core/engine/bulk-operations-purge.js +1 -0
- package/dist/core/engine/bulk-operations.js +1 -1
- package/dist/core/engine/callback-creators-bundles.js +2 -1
- package/dist/core/engine/callback-creators-core.js +2 -1
- package/dist/core/engine/construction.d.ts +1 -1
- package/dist/core/engine/construction.js +15 -3
- package/dist/core/engine/disposal.js +12 -0
- package/dist/core/engine/engine-create-types.d.ts +0 -14
- package/dist/core/engine/engine-internal-types.d.ts +17 -0
- package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
- package/dist/core/engine/engine-leak-warnings.js +4 -0
- package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
- package/dist/core/engine/engine-runtime-helpers.js +26 -5
- package/dist/core/engine/errors.d.ts +74 -0
- package/dist/core/engine/errors.js +25 -1
- package/dist/core/engine/handle-result.js +1 -1
- package/dist/core/engine/handles.d.ts +89 -40
- package/dist/core/engine/handles.js +25 -27
- package/dist/core/engine/index.d.ts +122 -4
- package/dist/core/engine/index.js +82 -5
- package/dist/core/engine/inline-launch-queue.d.ts +14 -0
- package/dist/core/engine/inline-launch-queue.js +32 -7
- package/dist/core/engine/internals.d.ts +26 -10
- package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
- package/dist/core/engine/lifecycle/persist.js +5 -20
- package/dist/core/engine/lifecycle/recovered-services.d.ts +45 -0
- package/dist/core/engine/lifecycle/recovered-services.js +34 -0
- package/dist/core/engine/lifecycle/resume.js +33 -5
- package/dist/core/engine/lifecycle/shared.d.ts +8 -0
- package/dist/core/engine/lifecycle/start-batch.js +23 -12
- package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
- package/dist/core/engine/lifecycle/start-commit.js +27 -0
- package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
- package/dist/core/engine/lifecycle/start-exec.js +38 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
- package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
- package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
- package/dist/core/engine/lifecycle/start.d.ts +3 -3
- package/dist/core/engine/lifecycle/start.js +42 -37
- package/dist/core/engine/lifecycle.d.ts +3 -2
- package/dist/core/engine/lifecycle.js +9 -2
- package/dist/core/engine/listing.js +1 -1
- package/dist/core/engine/operations-data.d.ts +16 -0
- package/dist/core/engine/operations-data.js +6 -0
- package/dist/core/engine/operations-time.d.ts +3 -2
- package/dist/core/engine/operations-time.js +6 -1
- package/dist/core/engine/persisted-data-version.d.ts +5 -9
- package/dist/core/engine/persisted-data-version.js +4 -5
- package/dist/core/engine/schedule-handle.d.ts +45 -0
- package/dist/core/engine/schedule-handle.js +26 -0
- package/dist/core/engine/schedules.d.ts +1 -1
- package/dist/core/engine/schedules.js +7 -3
- package/dist/core/engine/second-instance-detector.d.ts +96 -0
- package/dist/core/engine/second-instance-detector.js +108 -0
- package/dist/core/engine/signals.d.ts +22 -0
- package/dist/core/engine/signals.js +15 -0
- package/dist/core/engine/termination/cleanup.d.ts +25 -0
- package/dist/core/engine/termination/cleanup.js +21 -1
- package/dist/core/engine/termination/complete.js +4 -3
- package/dist/core/engine/termination/suspend.d.ts +68 -0
- package/dist/core/engine/termination/suspend.js +41 -0
- package/dist/core/engine/termination.d.ts +4 -2
- package/dist/core/engine/termination.js +2 -0
- package/dist/core/engine/validation.js +25 -1
- package/dist/core/engine/workflow-feed.d.ts +5 -3
- package/dist/core/events/event-map.d.ts +2 -1
- package/dist/core/events/workflow-events.d.ts +23 -0
- package/dist/core/events/workflow-events.js +9 -0
- package/dist/core/inline-execution-strategy.d.ts +5 -0
- package/dist/core/inline-execution-strategy.js +2 -1
- package/dist/core/list-filter-validation.js +2 -1
- package/dist/core/start-workflow-validation.d.ts +22 -0
- package/dist/core/start-workflow-validation.js +11 -1
- package/dist/core/step-context.d.ts +10 -6
- package/dist/core/step-context.js +7 -15
- package/dist/core/types/activity.d.ts +6 -3
- package/dist/core/types/identity.d.ts +8 -1
- package/dist/core/types/launch-metadata.d.ts +33 -0
- package/dist/core/types/launch-metadata.js +0 -0
- package/dist/core/types/message-handles.d.ts +25 -0
- package/dist/core/types/options.d.ts +90 -7
- package/dist/core/types/reviews.d.ts +2 -1
- package/dist/core/types/services-resolution.d.ts +47 -0
- package/dist/core/types/services-resolution.js +0 -0
- package/dist/core/types/state.d.ts +11 -11
- package/dist/core/types/workflow-builder.d.ts +5 -4
- package/dist/core/types/workflow-context.d.ts +25 -0
- package/dist/core/types/workflow-function.d.ts +17 -0
- package/dist/core/types/workflow-snapshot.d.ts +29 -0
- package/dist/core/types/workflow-snapshot.js +0 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.js +3 -0
- package/dist/core/weft-error.d.ts +46 -14
- package/dist/core/weft-error.js +12 -1
- package/dist/diagnostics/doctor.js +6 -3
- package/dist/diagnostics/format.js +2 -2
- package/dist/diagnostics/types.d.ts +1 -0
- package/dist/diagnostics/version-check.js +6 -4
- package/dist/index.d.ts +10 -5
- package/dist/index.js +11 -2
- package/dist/json-schema.js +3 -3
- package/dist/mcp/cli.js +35 -35
- package/dist/mcp/list-filter.js +2 -1
- package/dist/mcp/session.js +1 -0
- package/dist/observability/index.js +2 -2
- package/dist/server/handler.js +30 -30
- package/dist/server/index.js +33 -33
- package/dist/server/interactive-operations.js +1 -0
- package/dist/server/operations/resume-workflow.js +2 -2
- package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
- package/dist/server/operations/start-or-signal-workflow.js +140 -0
- package/dist/server/operations/start-workflow-options.d.ts +32 -0
- package/dist/server/operations/start-workflow-options.js +63 -0
- package/dist/server/operations/start-workflow.js +7 -69
- package/dist/server/operations/suspend-workflow.d.ts +13 -0
- package/dist/server/operations/suspend-workflow.js +36 -0
- package/dist/server/rest-binding.d.ts +18 -7
- package/dist/server/rest-bindings.js +12 -0
- package/dist/server/runtime/task-dispatch.js +5 -3
- package/dist/server/runtime/task-polling.d.ts +16 -2
- package/dist/server/runtime/task-polling.js +20 -5
- package/dist/server/runtime/websocket-worker.js +8 -0
- package/dist/server/serve-internals.d.ts +8 -0
- package/dist/server/serve-internals.js +4 -2
- package/dist/server/task-state.d.ts +8 -0
- package/dist/service-worker/index.js +28 -28
- package/dist/storage/capabilities.d.ts +10 -2
- package/dist/storage/capabilities.js +2 -2
- package/dist/storage/http.js +2 -2
- package/dist/storage/index.d.ts +7 -1
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +40 -0
- package/dist/storage/interface.js +1 -1
- 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-value-mapping.d.ts +47 -0
- package/dist/storage/neon-value-mapping.js +11 -0
- package/dist/storage/neon.d.ts +108 -0
- package/dist/storage/neon.js +10 -0
- package/dist/storage/node-sqlite-loader.d.ts +71 -0
- package/dist/storage/node-sqlite-loader.js +41 -0
- package/dist/storage/node-sqlite.d.ts +1 -19
- package/dist/storage/node-sqlite.js +38 -32
- package/dist/storage/postgres-key-value-queries.d.ts +79 -0
- package/dist/storage/postgres-key-value-queries.js +63 -0
- package/dist/storage/resolve.d.ts +2 -165
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/storage-configuration.d.ts +209 -0
- package/dist/storage/storage-configuration.js +0 -0
- package/dist/storage/text-value-store.d.ts +13 -10
- package/dist/storage/turso.js +2 -2
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/event-loop.d.ts +36 -2
- package/dist/testing/index.d.ts +31 -1
- package/dist/testing/index.js +33 -33
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/index.js +9 -5
- package/dist/worker/long-poll.js +4 -0
- package/dist/worker/protocol-messages.d.ts +20 -0
- package/dist/worker/protocol-schemas.d.ts +32 -0
- package/dist/worker/protocol-schemas.js +8 -4
- package/dist/worker/protocol-task-result.d.ts +28 -0
- package/dist/worker/protocol-task-result.js +76 -0
- package/dist/worker/protocol.d.ts +4 -15
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/fair-share.d.ts +29 -0
- package/dist/worker/registry/fair-share.js +30 -0
- package/dist/worker/registry/routing.d.ts +18 -0
- package/dist/worker/registry/routing.js +14 -0
- package/dist/worker/registry/types.d.ts +7 -0
- package/dist/worker/registry.d.ts +16 -1
- package/dist/worker/registry.js +24 -36
- package/package.json +17 -4
|
@@ -9,6 +9,20 @@ export declare function queueInlineWorkflowExecutionStart(internals: EngineInter
|
|
|
9
9
|
export declare function flushQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
|
|
10
10
|
/** Used by scheduler-driven direct backfill flushes. Clears the scheduled flag first. */
|
|
11
11
|
export declare function flushQueuedInlineWorkflowStartsDirectly(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Drain every pending inline launch before engine teardown. Called from
|
|
14
|
+
* `[Symbol.asyncDispose]` *before* `disposeEngine` aborts the signal, so the
|
|
15
|
+
* flush actually executes the queued starts (the abort check in
|
|
16
|
+
* {@link flushQueuedInlineWorkflowStarts} would otherwise discard them). This
|
|
17
|
+
* turns a deferred-launch macrotask into work that completes before
|
|
18
|
+
* `asyncDispose` returns, so a disposed engine leaves no dangling pending
|
|
19
|
+
* launch — the fix for the test-runner macrotask-starvation footgun.
|
|
20
|
+
*
|
|
21
|
+
* A queued start that was already aborted (signal set before this is reached)
|
|
22
|
+
* is left for the synchronous `disposeQueuedInlineWorkflowStarts` path, which
|
|
23
|
+
* discards it and settles its `defer: false` awaiter.
|
|
24
|
+
*/
|
|
25
|
+
export declare function drainQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
|
|
12
26
|
export declare function dropQueuedInlineWorkflowStart(internals: EngineInternals, workflowId: string): boolean;
|
|
13
27
|
export declare function disposeQueuedInlineWorkflowStarts(internals: EngineInternals): void;
|
|
14
28
|
export declare function hasQueuedInlineWorkflowStart(internals: EngineInternals, workflowId: string): boolean;
|
|
@@ -17,9 +17,18 @@ export function queueInlineWorkflowExecutionStart(internals, start, callbacks) {
|
|
|
17
17
|
callbacks.swallowPromiseRejection(flushQueuedInlineWorkflowStarts(internals, callbacks));
|
|
18
18
|
}, 0);
|
|
19
19
|
}
|
|
20
|
+
function settleDiscardedInlineStarts(internals, discarded) {
|
|
21
|
+
for (const start of discarded) {
|
|
22
|
+
internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
|
|
23
|
+
internals.queuedOrLaunchingInlineWorkflowStartIds.delete(start.workflowId);
|
|
24
|
+
start.onStarted?.();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
20
27
|
export async function flushQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
21
28
|
if (internals.abortController.signal.aborted) {
|
|
29
|
+
const discarded = internals.queuedInlineWorkflowStarts;
|
|
22
30
|
internals.queuedInlineWorkflowStarts = [];
|
|
31
|
+
settleDiscardedInlineStarts(internals, discarded);
|
|
23
32
|
return;
|
|
24
33
|
}
|
|
25
34
|
if (internals.queuedInlineWorkflowStarts.length === 0)
|
|
@@ -27,12 +36,21 @@ export async function flushQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
|
27
36
|
const pendingStarts = internals.queuedInlineWorkflowStarts;
|
|
28
37
|
internals.queuedInlineWorkflowStarts = [];
|
|
29
38
|
for (const start of pendingStarts)
|
|
30
|
-
await startQueuedInlineWorkflowExecution(internals, start, callbacks);
|
|
39
|
+
await callbacks.swallowPromiseRejection(startQueuedInlineWorkflowExecution(internals, start, callbacks));
|
|
31
40
|
}
|
|
32
41
|
export async function flushQueuedInlineWorkflowStartsDirectly(internals, callbacks) {
|
|
33
42
|
internals.queuedInlineWorkflowStartFlushScheduled = !1;
|
|
34
43
|
await flushQueuedInlineWorkflowStarts(internals, callbacks);
|
|
35
44
|
}
|
|
45
|
+
export async function drainQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
46
|
+
internals.queuedInlineWorkflowStartFlushScheduled = !1;
|
|
47
|
+
let passes = 0;
|
|
48
|
+
const maxPasses = 1000;
|
|
49
|
+
while (internals.queuedInlineWorkflowStarts.length > 0 && !internals.abortController.signal.aborted && passes < maxPasses) {
|
|
50
|
+
passes += 1;
|
|
51
|
+
await callbacks.swallowPromiseRejection(flushQueuedInlineWorkflowStarts(internals, callbacks));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
36
54
|
async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
|
|
37
55
|
try {
|
|
38
56
|
const state = await loadWorkflowState(internals, start.workflowId);
|
|
@@ -45,22 +63,29 @@ async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
|
|
|
45
63
|
} finally {
|
|
46
64
|
internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
|
|
47
65
|
internals.queuedOrLaunchingInlineWorkflowStartIds.delete(start.workflowId);
|
|
66
|
+
start.onStarted?.();
|
|
48
67
|
}
|
|
49
68
|
}
|
|
50
69
|
export function dropQueuedInlineWorkflowStart(internals, workflowId) {
|
|
51
70
|
if (internals.queuedInlineWorkflowStarts.length === 0)
|
|
52
71
|
return !1;
|
|
53
|
-
const initialLength = internals.queuedInlineWorkflowStarts.length;
|
|
54
|
-
internals.queuedInlineWorkflowStarts = internals.queuedInlineWorkflowStarts.filter((start) =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
const initialLength = internals.queuedInlineWorkflowStarts.length, dropped = [];
|
|
73
|
+
internals.queuedInlineWorkflowStarts = internals.queuedInlineWorkflowStarts.filter((start) => {
|
|
74
|
+
if (start.workflowId === workflowId) {
|
|
75
|
+
dropped.push(start);
|
|
76
|
+
return !1;
|
|
77
|
+
}
|
|
78
|
+
return !0;
|
|
79
|
+
});
|
|
80
|
+
if (internals.queuedInlineWorkflowStarts.length !== initialLength)
|
|
81
|
+
settleDiscardedInlineStarts(internals, dropped);
|
|
59
82
|
return internals.queuedInlineWorkflowStarts.length !== initialLength;
|
|
60
83
|
}
|
|
61
84
|
export function disposeQueuedInlineWorkflowStarts(internals) {
|
|
62
85
|
internals.queuedInlineWorkflowStartFlushScheduled = !1;
|
|
86
|
+
const discarded = internals.queuedInlineWorkflowStarts;
|
|
63
87
|
internals.queuedInlineWorkflowStarts = [];
|
|
88
|
+
settleDiscardedInlineStarts(internals, discarded);
|
|
64
89
|
internals.queuedInlineWorkflowStartIds.clear();
|
|
65
90
|
internals.queuedOrLaunchingInlineWorkflowStartIds.clear();
|
|
66
91
|
const channel = internals.queuedInlineWorkflowStartChannel;
|
|
@@ -32,8 +32,11 @@ import type { Checkpoint } from '../types.ts';
|
|
|
32
32
|
import type { UpdateCoordinator } from '../updates.ts';
|
|
33
33
|
import type { WorkflowVersionTuple } from '../workflow-version-tuple.ts';
|
|
34
34
|
import type { PendingTimelineEntry, QueuedInlineWorkflowExecutionStart, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter } from './engine-internal-types.ts';
|
|
35
|
-
import type {
|
|
35
|
+
import type { EngineCleanupIntervalDisposalTracker } from './engine-leak-warnings.ts';
|
|
36
|
+
import type { WorkflowHandle, WorkflowHandleEngine } from './handles.ts';
|
|
36
37
|
import type { WorkflowFeedListener } from './index.ts';
|
|
38
|
+
import type { ScheduleHandleEngine } from './schedule-handle.ts';
|
|
39
|
+
import type { SecondInstanceDetector } from './second-instance-detector.ts';
|
|
37
40
|
type EngineRuntime = WorkflowHandleEngine & ScheduleHandleEngine;
|
|
38
41
|
export interface EngineInternals {
|
|
39
42
|
engine: EngineRuntime;
|
|
@@ -75,11 +78,15 @@ export interface EngineInternals {
|
|
|
75
78
|
activityRegistry: ActivityRegistry;
|
|
76
79
|
/**
|
|
77
80
|
* Per-workflow activity registries built from
|
|
78
|
-
* `workflow({ name }).activities({ ... }).execute(...)
|
|
79
|
-
* type.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
81
|
+
* `workflow({ name }).activities({ ... }).execute(...)`, indexed by workflow
|
|
82
|
+
* type. Activity lookup is per-activity: it consults the workflow's
|
|
83
|
+
* `activityRegistriesByWorkflow.get(type)` registry first and, for any activity
|
|
84
|
+
* that registry does not contain, falls back to the engine-wide
|
|
85
|
+
* {@link EngineInternals.activityRegistry}. Both sources are first-class — a
|
|
86
|
+
* workflow can resolve some activities from its own `activities(...)` map and
|
|
87
|
+
* others (shared/globally-registered) from the global registry, and a workflow
|
|
88
|
+
* with no per-workflow map resolves entirely from the global one. Each
|
|
89
|
+
* per-workflow registry is a defensive deep clone+freeze of the workflow's
|
|
83
90
|
* `activities` map so post-registration mutation cannot reach the engine.
|
|
84
91
|
*/
|
|
85
92
|
activityRegistriesByWorkflow: Map<string, ActivityRegistry>;
|
|
@@ -108,6 +115,14 @@ export interface EngineInternals {
|
|
|
108
115
|
workflowHeaders: Map<string, Map<string, string>>;
|
|
109
116
|
workflowStateWriteChains: Map<string, Promise<void>>;
|
|
110
117
|
heartbeatDetails: Map<string, unknown>;
|
|
118
|
+
/**
|
|
119
|
+
* Per-run, non-serialized `services` value exposed to the workflow body as
|
|
120
|
+
* `ctx.services`. Set at `engine.start({ services })` and re-provided on
|
|
121
|
+
* recovery by `resolveWorkflowServices`. Never checkpointed — held only in
|
|
122
|
+
* engine memory, keyed by workflowId, and cleared on terminal cleanup (the
|
|
123
|
+
* same lifecycle as {@link heartbeatDetails}).
|
|
124
|
+
*/
|
|
125
|
+
workflowServices: Map<string, unknown>;
|
|
111
126
|
/**
|
|
112
127
|
* Activities that called `ctx.completeAsync()` and are awaiting out-of-band
|
|
113
128
|
* completion via `engine.completeAsyncActivity` / `failAsyncActivity`, keyed
|
|
@@ -119,13 +134,14 @@ export interface EngineInternals {
|
|
|
119
134
|
pendingScheduleCreations: Set<string>;
|
|
120
135
|
workflowsNeedingTerminalCleanup: Set<string>;
|
|
121
136
|
cleanupInterval: ReturnType<typeof setInterval> | null;
|
|
122
|
-
cleanupIntervalDisposalTracker:
|
|
123
|
-
disposed: boolean;
|
|
124
|
-
cleanupInterval: ReturnType<typeof setInterval> | null;
|
|
125
|
-
} | null;
|
|
137
|
+
cleanupIntervalDisposalTracker: EngineCleanupIntervalDisposalTracker | null;
|
|
126
138
|
retentionSweepInterval: ReturnType<typeof setInterval> | null;
|
|
127
139
|
retentionSweepInFlight: Promise<void> | null;
|
|
128
140
|
nextRetentionSweepAt: number | null;
|
|
141
|
+
/** Interval driving the best-effort second-instance detector; `null` when off. */
|
|
142
|
+
secondInstanceDetectionInterval: ReturnType<typeof setInterval> | null;
|
|
143
|
+
/** The active second-instance detector; `null` when detection is disabled. */
|
|
144
|
+
secondInstanceDetector: SecondInstanceDetector | null;
|
|
129
145
|
reviewCoordinator: ReviewCoordinator;
|
|
130
146
|
reviewWaiters: Map<string, (decision: HumanReviewResult) => void>;
|
|
131
147
|
reviewWaitersByWorkflow: Map<string, TrackedWaiterKeys>;
|
|
@@ -22,17 +22,11 @@ export function createForkedWorkflowState(_internals, workflowId, sourceState, v
|
|
|
22
22
|
type: sourceState.type,
|
|
23
23
|
status: "running",
|
|
24
24
|
input: sourceState.input,
|
|
25
|
-
|
|
25
|
+
versionTuple,
|
|
26
26
|
executionStateOwnerId: workflowId,
|
|
27
27
|
createdAt: forkedAt,
|
|
28
28
|
startedAt: forkedAt,
|
|
29
29
|
updatedAt: forkedAt,
|
|
30
|
-
...versionTuple.agentVersion !== void 0 && {
|
|
31
|
-
agentVersion: versionTuple.agentVersion
|
|
32
|
-
},
|
|
33
|
-
...versionTuple.toolVersions !== void 0 && {
|
|
34
|
-
toolVersions: versionTuple.toolVersions
|
|
35
|
-
},
|
|
36
30
|
forkedFrom: lineage
|
|
37
31
|
};
|
|
38
32
|
}
|
|
@@ -16,28 +16,13 @@ export function createWorkflowVersionTuple(_internals, registration, _callbacks)
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
export function workflowVersionTupleFromState(_internals, state, _callbacks) {
|
|
19
|
-
return
|
|
20
|
-
workflowVersion: state.version,
|
|
21
|
-
...state.agentVersion !== void 0 && { agentVersion: state.agentVersion },
|
|
22
|
-
...state.toolVersions !== void 0 && { toolVersions: state.toolVersions }
|
|
23
|
-
};
|
|
19
|
+
return state.versionTuple;
|
|
24
20
|
}
|
|
25
21
|
export function workflowStateWithVersionTuple(internals, state, versionTuple, _callbacks) {
|
|
26
|
-
const {
|
|
27
|
-
agentVersion: _existingAgentVersion,
|
|
28
|
-
toolVersions: _existingToolVersions,
|
|
29
|
-
...rest
|
|
30
|
-
} = state;
|
|
31
22
|
return {
|
|
32
|
-
...
|
|
33
|
-
|
|
34
|
-
updatedAt: internals.options.getNow()
|
|
35
|
-
...versionTuple.agentVersion !== void 0 && {
|
|
36
|
-
agentVersion: versionTuple.agentVersion
|
|
37
|
-
},
|
|
38
|
-
...versionTuple.toolVersions !== void 0 && {
|
|
39
|
-
toolVersions: versionTuple.toolVersions
|
|
40
|
-
}
|
|
23
|
+
...state,
|
|
24
|
+
versionTuple,
|
|
25
|
+
updatedAt: internals.options.getNow()
|
|
41
26
|
};
|
|
42
27
|
}
|
|
43
28
|
export function derivePreparedExecutionState(internals, workflowId, state, checkpoint, registration, callbacks) {
|
|
@@ -76,5 +61,5 @@ export async function prepareResumeState(internals, workflowId, state, checkpoin
|
|
|
76
61
|
};
|
|
77
62
|
}
|
|
78
63
|
export function throwVersionMismatch(_internals, workflowId, state, registration, versionDiff, _callbacks) {
|
|
79
|
-
throw new VersionMismatchError(workflowId, state.type, state.
|
|
64
|
+
throw new VersionMismatchError(workflowId, state.type, state.versionTuple.workflowVersion, registration.version, void 0, versionDiff);
|
|
80
65
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { WorkflowState } from '../../types.ts';
|
|
2
|
+
import type { EngineInternals } from '../internals.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Re-provide a recovered inline workflow's non-serialized `services` before its
|
|
5
|
+
* generator is driven forward, and decide whether execution should proceed.
|
|
6
|
+
*
|
|
7
|
+
* Both recovery entry points use this: `resumeWorkflowFromStorage` (for a run
|
|
8
|
+
* left `running`) and the delayed-start timer handler (for a `startAfter`/
|
|
9
|
+
* `startAt` run that crashed `pending` before its timer fired). On a fresh
|
|
10
|
+
* process the in-memory `workflowServices` map is empty, so without this a
|
|
11
|
+
* recovered run that originally had services would silently execute with
|
|
12
|
+
* `ctx.services === undefined`.
|
|
13
|
+
*
|
|
14
|
+
* The resolver is only consulted for runs launched WITH services, detected by
|
|
15
|
+
* the durable `KEYS.workflowHasServices` marker. A run that never had services
|
|
16
|
+
* has no marker and proceeds without touching the resolver — so a fail-closed
|
|
17
|
+
* resolver does not fail healthy no-services runs.
|
|
18
|
+
*
|
|
19
|
+
* Returns `false` to proceed (services available, none expected, or no resolver
|
|
20
|
+
* is configured). Returns `true` to STOP — the run was terminally
|
|
21
|
+
* failed (services unavailable), or the terminal commit faulted and the run was
|
|
22
|
+
* left for a later boot to retry. Either way the generator must not advance:
|
|
23
|
+
* driving it without services would crash the body and that throw would escape
|
|
24
|
+
* into the recovery loop, aborting sibling runs.
|
|
25
|
+
*
|
|
26
|
+
* Worker mode skips this (services are inline-only, rejected at `engine.start()`).
|
|
27
|
+
* A resolver throw is treated as `unavailable` with the error as the reason,
|
|
28
|
+
* for the same sibling-isolation reason.
|
|
29
|
+
*
|
|
30
|
+
* @param failRun - Terminally fails just this run with `reason`. Supplied by the
|
|
31
|
+
* caller because the two entry points reach the termination machinery through
|
|
32
|
+
* different callback bundles. It receives the canonical terminal error built
|
|
33
|
+
* by {@link unavailableServicesError}, so both recovery paths fail the run
|
|
34
|
+
* with an identical message and (via `failWorkflow`'s default) the `system`
|
|
35
|
+
* failure category.
|
|
36
|
+
* @param onCommitError - Records a fail-warn when `failRun` itself throws, so the
|
|
37
|
+
* swallowed terminal-commit fault is still observable.
|
|
38
|
+
*/
|
|
39
|
+
export declare function reprovideRecoveredServices(internals: EngineInternals, state: WorkflowState, failRun: (workflowId: string, error: Error) => Promise<void>, onCommitError: (source: string, error: unknown, workflowId: string) => void): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* The canonical terminal error for a recovered run whose services could not be
|
|
42
|
+
* re-provided. Shared by both recovery paths so they fail with an identical
|
|
43
|
+
* message (the failure category is `system`, the default for `failWorkflow`).
|
|
44
|
+
*/
|
|
45
|
+
export declare function unavailableServicesError(workflowId: string, reason: string): Error;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { KEYS } from "../../../storage/interface.js";
|
|
2
|
+
export async function reprovideRecoveredServices(internals, state, failRun, onCommitError) {
|
|
3
|
+
const resolver = internals.options.resolveWorkflowServices;
|
|
4
|
+
if (internals.inlineStrategy === null || !resolver)
|
|
5
|
+
return !1;
|
|
6
|
+
if (internals.workflowServices.has(state.id))
|
|
7
|
+
return !1;
|
|
8
|
+
if (await internals.storage.get(KEYS.workflowHasServices(state.id)) === null)
|
|
9
|
+
return !1;
|
|
10
|
+
let reason;
|
|
11
|
+
try {
|
|
12
|
+
const resolution = await resolver({
|
|
13
|
+
workflowId: state.id,
|
|
14
|
+
workflowType: state.type,
|
|
15
|
+
input: state.input
|
|
16
|
+
});
|
|
17
|
+
if (resolution.status === "available") {
|
|
18
|
+
internals.workflowServices.set(state.id, resolution.services);
|
|
19
|
+
return !1;
|
|
20
|
+
}
|
|
21
|
+
reason = resolution.reason;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
reason = error instanceof Error ? error.message : String(error);
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await failRun(state.id, unavailableServicesError(state.id, reason));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
onCommitError("reprovideRecoveredServices", error, state.id);
|
|
29
|
+
}
|
|
30
|
+
return !0;
|
|
31
|
+
}
|
|
32
|
+
export function unavailableServicesError(workflowId, reason) {
|
|
33
|
+
return Error(`Recovered workflow "${workflowId}" services unavailable: ${reason}`);
|
|
34
|
+
}
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import { KEYS } from "../../../storage/interface.js";
|
|
2
2
|
import { deserializeCheckpoint, serializeCheckpoint } from "../../checkpoint.js";
|
|
3
|
+
import { encode } from "../../codec.js";
|
|
3
4
|
import { Context, setContextWorkflowInterceptor } from "../../context.js";
|
|
4
5
|
import { EventLog } from "../../event-log.js";
|
|
5
6
|
import { WorkflowResumedEvent } from "../../events.js";
|
|
7
|
+
import { buildTimerBatchOperations } from "../../scheduler.js";
|
|
6
8
|
import { createCancelHandlerRegistration, resetCancelHandlers } from "../cancel-handlers.js";
|
|
7
9
|
import { rememberCommittedCheckpointBytes } from "../checkpoint-commit-snapshots.js";
|
|
8
10
|
import { getWorkflowExecutionStartedAt } from "../handles.js";
|
|
9
11
|
import { loadWorkflowState } from "../storage-io.js";
|
|
10
12
|
import { getComposedWorkflowInterceptor } from "../strategy-helpers.js";
|
|
11
13
|
import { decodeWorkflowState } from "../validation.js";
|
|
14
|
+
import { buildWorkflowVisibilityIndexTransition } from "../workflow-indexes.js";
|
|
12
15
|
import { prepareResumeState } from "./persist.js";
|
|
16
|
+
import { reprovideRecoveredServices } from "./recovered-services.js";
|
|
13
17
|
import {
|
|
14
18
|
enforceHistoryPolicyBeforeReplay,
|
|
15
19
|
loadTerminalCleanupTrackedState,
|
|
16
20
|
loadWorkflowStartHeaders,
|
|
17
21
|
setWorkflowStartHeaders
|
|
18
22
|
} from "./shared.js";
|
|
23
|
+
async function prepareRecoveredServicesOrFail(internals, state, callbacks) {
|
|
24
|
+
return reprovideRecoveredServices(internals, state, callbacks.failWorkflowForUnavailableServices, callbacks.handleCleanupError);
|
|
25
|
+
}
|
|
19
26
|
function assertResumeNotTerminating(internals, workflowId) {
|
|
20
27
|
if (internals.terminalizingWorkflows.has(workflowId))
|
|
21
28
|
throw Error(`Cannot resume workflow "${workflowId}": termination is in progress`);
|
|
@@ -57,7 +64,8 @@ function relaunchInlineWorkflowAfterResume(internals, latestState, args) {
|
|
|
57
64
|
sleepReferenceTime: resumeCheckpoint.createdAt,
|
|
58
65
|
...latestState.executionDeadline !== void 0 && {
|
|
59
66
|
deadline: latestState.executionDeadline
|
|
60
|
-
}
|
|
67
|
+
},
|
|
68
|
+
services: internals.workflowServices.get(workflowId)
|
|
61
69
|
});
|
|
62
70
|
setContextWorkflowInterceptor(context, getComposedWorkflowInterceptor(internals));
|
|
63
71
|
if (internals.options.development)
|
|
@@ -90,8 +98,9 @@ async function performSerializedResume(internals, args) {
|
|
|
90
98
|
assertResumeNotTerminating(internals, workflowId);
|
|
91
99
|
if (!latestState)
|
|
92
100
|
throw Error(`Workflow "${workflowId}" not found in storage`);
|
|
93
|
-
if (latestState.status !== "running")
|
|
94
|
-
throw Error(`Cannot resume workflow "${workflowId}": status is "${latestState.status}", expected "running"`);
|
|
101
|
+
if (latestState.status !== "running" && latestState.status !== "suspended")
|
|
102
|
+
throw Error(`Cannot resume workflow "${workflowId}": status is "${latestState.status}", expected "running" or "suspended"`);
|
|
103
|
+
await reactivateSuspendedWorkflowState(internals, latestState);
|
|
95
104
|
commitSerializedResumeState(internals, args);
|
|
96
105
|
if (internals.inlineStrategy) {
|
|
97
106
|
relaunchInlineWorkflowAfterResume(internals, latestState, args);
|
|
@@ -99,13 +108,30 @@ async function performSerializedResume(internals, args) {
|
|
|
99
108
|
}
|
|
100
109
|
relaunchWorkerWorkflowAfterResume(internals, latestState, args);
|
|
101
110
|
}
|
|
111
|
+
async function reactivateSuspendedWorkflowState(internals, state) {
|
|
112
|
+
if (state.status !== "suspended")
|
|
113
|
+
return;
|
|
114
|
+
const previousState = { ...state };
|
|
115
|
+
state.status = "running";
|
|
116
|
+
state.updatedAt = internals.options.getNow();
|
|
117
|
+
await internals.storage.batch([
|
|
118
|
+
{ type: "put", key: KEYS.workflow(state.id), value: encode(state) },
|
|
119
|
+
...buildWorkflowVisibilityIndexTransition(state.id, previousState, state).batchOps,
|
|
120
|
+
...state.executionDeadline !== void 0 ? buildTimerBatchOperations({
|
|
121
|
+
id: `deadline:${state.id}`,
|
|
122
|
+
workflowId: state.id,
|
|
123
|
+
fireAt: state.executionDeadline,
|
|
124
|
+
kind: "execution-deadline"
|
|
125
|
+
}) : []
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
102
128
|
export async function resumeWorkflowFromStorage(internals, workflowId, dispatchResumedEvent, callbacks) {
|
|
103
129
|
const stateBytes = await internals.storage.get(KEYS.workflow(workflowId));
|
|
104
130
|
if (!stateBytes)
|
|
105
131
|
throw Error(`Workflow "${workflowId}" not found in storage`);
|
|
106
132
|
const state = decodeWorkflowState(stateBytes);
|
|
107
|
-
if (state.status !== "running")
|
|
108
|
-
throw Error(`Cannot resume workflow "${workflowId}": status is "${state.status}", expected "running"`);
|
|
133
|
+
if (state.status !== "running" && state.status !== "suspended")
|
|
134
|
+
throw Error(`Cannot resume workflow "${workflowId}": status is "${state.status}", expected "running" or "suspended"`);
|
|
109
135
|
const checkpointBytes = await internals.storage.get(KEYS.checkpoint(workflowId));
|
|
110
136
|
if (!checkpointBytes)
|
|
111
137
|
throw Error(`Checkpoint not found for workflow "${workflowId}"`);
|
|
@@ -117,6 +143,8 @@ export async function resumeWorkflowFromStorage(internals, workflowId, dispatchR
|
|
|
117
143
|
return callbacks.getHandle(workflowId);
|
|
118
144
|
const workflowStartHeaders = await loadWorkflowStartHeaders(internals, workflowId, callbacks);
|
|
119
145
|
await loadTerminalCleanupTrackedState(internals, workflowId, callbacks);
|
|
146
|
+
if (await prepareRecoveredServicesOrFail(internals, state, callbacks))
|
|
147
|
+
return callbacks.getHandle(workflowId);
|
|
120
148
|
const handle = callbacks.getHandle(workflowId);
|
|
121
149
|
await callbacks.runSerializedWorkflowStateWrite(workflowId, () => performSerializedResume(internals, {
|
|
122
150
|
workflowId,
|
|
@@ -48,6 +48,14 @@ export type LifecycleCallbacks = {
|
|
|
48
48
|
* history is terminated without being replayed.
|
|
49
49
|
*/
|
|
50
50
|
enforceHistoryCircuitBreaker: (workflowId: string) => Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Force a recovered workflow to a terminal `failed` state because its
|
|
53
|
+
* non-serialized `services` could not be re-provided (the engine's
|
|
54
|
+
* `resolveWorkflowServices` reported `unavailable`). Fails just this run with
|
|
55
|
+
* a `system` failure category; the engine and other recovered runs continue.
|
|
56
|
+
* `error` is the canonical {@link unavailableServicesError}.
|
|
57
|
+
*/
|
|
58
|
+
failWorkflowForUnavailableServices: (workflowId: string, error: Error) => Promise<void>;
|
|
51
59
|
};
|
|
52
60
|
/**
|
|
53
61
|
* Pre-replay history circuit breaker. Called at every restore-from-checkpoint
|
|
@@ -19,18 +19,7 @@ export function buildStartBatchOperations(_internals, workflowId, state, checkpo
|
|
|
19
19
|
...visibilityIndexOperations,
|
|
20
20
|
...buildWorkflowTagIndexOperations(workflowId, void 0, state.tags),
|
|
21
21
|
...buildInitialSearchAttributeOperations(_internals, workflowId, registration, options?.searchAttributes, callbacks),
|
|
22
|
-
...
|
|
23
|
-
{
|
|
24
|
-
type: "put",
|
|
25
|
-
key: KEYS.workflowHeaders(workflowId),
|
|
26
|
-
value: encodeWorkflowStartHeaders(workflowStartHeaders)
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
type: "put",
|
|
30
|
-
key: KEYS.terminalCleanupNeeded(workflowId),
|
|
31
|
-
value: EMPTY_STORAGE_VALUE
|
|
32
|
-
}
|
|
33
|
-
] : [],
|
|
22
|
+
...buildPerRunScratchOperations(workflowId, options, workflowStartHeaders),
|
|
34
23
|
...additionalOperations ?? []
|
|
35
24
|
];
|
|
36
25
|
if (executionDeadline !== void 0)
|
|
@@ -44,6 +33,28 @@ export function buildStartBatchOperations(_internals, workflowId, state, checkpo
|
|
|
44
33
|
operations.push(...buildTimerBatchOperations(delayedStartTimer));
|
|
45
34
|
return operations;
|
|
46
35
|
}
|
|
36
|
+
function buildPerRunScratchOperations(workflowId, options, workflowStartHeaders) {
|
|
37
|
+
const hasHeaders = workflowStartHeaders !== void 0 && workflowStartHeaders.size > 0, hasServices = options?.services !== void 0, operations = [];
|
|
38
|
+
if (hasHeaders)
|
|
39
|
+
operations.push({
|
|
40
|
+
type: "put",
|
|
41
|
+
key: KEYS.workflowHeaders(workflowId),
|
|
42
|
+
value: encodeWorkflowStartHeaders(workflowStartHeaders)
|
|
43
|
+
});
|
|
44
|
+
if (hasServices)
|
|
45
|
+
operations.push({
|
|
46
|
+
type: "put",
|
|
47
|
+
key: KEYS.workflowHasServices(workflowId),
|
|
48
|
+
value: EMPTY_STORAGE_VALUE
|
|
49
|
+
});
|
|
50
|
+
if (hasHeaders || hasServices)
|
|
51
|
+
operations.push({
|
|
52
|
+
type: "put",
|
|
53
|
+
key: KEYS.terminalCleanupNeeded(workflowId),
|
|
54
|
+
value: EMPTY_STORAGE_VALUE
|
|
55
|
+
});
|
|
56
|
+
return operations;
|
|
57
|
+
}
|
|
47
58
|
export function buildInitialSearchAttributeOperations(_internals, workflowId, registration, searchAttributes, callbacks) {
|
|
48
59
|
if (!searchAttributes || Object.keys(searchAttributes).length === 0)
|
|
49
60
|
return [];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { BatchOperation, ConditionalBatchCondition } from '../../../storage/interface.ts';
|
|
2
|
+
import type { Checkpoint, StartOptions, TimerEntry, WorkflowState } from '../../types.ts';
|
|
3
|
+
import type { EngineInternals } from '../internals.ts';
|
|
4
|
+
import { type LifecycleCallbacks, type RegistrationEntry } from './shared.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Builds the id-dependent operations and compare-and-swap preconditions for an
|
|
7
|
+
* idempotent start or `startOrSignal`. Invoked by `startWorkflow` with the real
|
|
8
|
+
* `workflowId` once it has been generated, so the idempotency mapping put (and
|
|
9
|
+
* any create-batch signal) can carry that id. The whole start batch then commits
|
|
10
|
+
* through a single `storageConditionalBatch` gated on the returned conditions; a
|
|
11
|
+
* lost CAS rolls back the start and throws {@link StartIdempotencyRaceLostError}
|
|
12
|
+
* so the caller resolves to the winner.
|
|
13
|
+
*/
|
|
14
|
+
export type BuildIdempotentStartOperations = (workflowId: string) => {
|
|
15
|
+
operations: BatchOperation[];
|
|
16
|
+
conditions: ConditionalBatchCondition[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Internal sentinel: the idempotent create batch lost its compare-and-swap to a
|
|
20
|
+
* concurrent caller holding the same idempotency key. Never surfaced to users —
|
|
21
|
+
* `start` / `startOrSignal` catch it and resolve to the winning run's handle.
|
|
22
|
+
*/
|
|
23
|
+
export declare class StartIdempotencyRaceLostError extends Error {
|
|
24
|
+
constructor();
|
|
25
|
+
}
|
|
26
|
+
/** Everything {@link buildAndCommitStartBatch} needs to assemble the start batch. */
|
|
27
|
+
export type StartBatchContext = {
|
|
28
|
+
internals: EngineInternals;
|
|
29
|
+
workflowId: string;
|
|
30
|
+
state: WorkflowState;
|
|
31
|
+
checkpoint: Checkpoint;
|
|
32
|
+
registration: RegistrationEntry;
|
|
33
|
+
options: StartOptions | undefined;
|
|
34
|
+
delayedStartTimer: TimerEntry | undefined;
|
|
35
|
+
persistedWorkflowStartHeaders: Map<string, string> | undefined;
|
|
36
|
+
additionalStartOperations: BatchOperation[] | undefined;
|
|
37
|
+
callbacks: LifecycleCallbacks;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Assemble the start batch — folding in the id-dependent idempotency mapping and
|
|
41
|
+
* create-batch signal once the real workflow id exists — and commit it, gated on
|
|
42
|
+
* any idempotency preconditions. Throws {@link StartIdempotencyRaceLostError}
|
|
43
|
+
* when a concurrent same-key caller won the compare-and-swap, so the calling
|
|
44
|
+
* `startWorkflow` rolls back its transient state and the wrapper resolves to the
|
|
45
|
+
* winning run.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildAndCommitStartBatch(context: StartBatchContext, buildIdempotentStartOperations: BuildIdempotentStartOperations | undefined): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { requireStorageCapability, storageConditionalBatch } from "../../../storage/interface.js";
|
|
2
|
+
import { buildStartBatchOperations } from "./start-batch.js";
|
|
3
|
+
|
|
4
|
+
export class StartIdempotencyRaceLostError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super("start idempotency compare-and-swap lost to a concurrent caller");
|
|
7
|
+
this.name = "StartIdempotencyRaceLostError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
async function persistStartBatch(internals, startOperations, conditions) {
|
|
11
|
+
if (conditions === void 0) {
|
|
12
|
+
await internals.storage.batch(startOperations);
|
|
13
|
+
return !0;
|
|
14
|
+
}
|
|
15
|
+
requireStorageCapability(internals.storage, "conditionalBatch", "start idempotency");
|
|
16
|
+
return storageConditionalBatch(internals.storage, conditions, startOperations);
|
|
17
|
+
}
|
|
18
|
+
function mergeAdditionalStartOperations(additional, idempotent) {
|
|
19
|
+
if (idempotent === void 0 || idempotent.length === 0)
|
|
20
|
+
return additional;
|
|
21
|
+
return [...additional ?? [], ...idempotent];
|
|
22
|
+
}
|
|
23
|
+
export async function buildAndCommitStartBatch(context, buildIdempotentStartOperations) {
|
|
24
|
+
const { internals, workflowId, state, checkpoint, registration, options } = context, idempotent = buildIdempotentStartOperations?.(workflowId), startOperations = buildStartBatchOperations(internals, workflowId, state, checkpoint, registration, options, state.executionDeadline, context.delayedStartTimer, context.persistedWorkflowStartHeaders, mergeAdditionalStartOperations(context.additionalStartOperations, idempotent?.operations), context.callbacks);
|
|
25
|
+
if (!await persistStartBatch(internals, startOperations, idempotent?.conditions))
|
|
26
|
+
throw new StartIdempotencyRaceLostError;
|
|
27
|
+
}
|
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
import type { Checkpoint } from '../../types.ts';
|
|
1
|
+
import type { Checkpoint, StartOptions, WorkflowState } from '../../types.ts';
|
|
2
2
|
import type { EngineInternals } from '../internals.ts';
|
|
3
|
-
import { type LifecycleCallbacks } from './shared.ts';
|
|
3
|
+
import { type LifecycleCallbacks, type RegistrationEntry } from './shared.ts';
|
|
4
4
|
export declare function runWorkflowStartInterceptor(_internals: EngineInternals, workflowId: string, workflowType: string, input: unknown, parentHeaders: Map<string, string> | undefined, callbacks: LifecycleCallbacks): Map<string, string> | undefined;
|
|
5
5
|
export declare function startWorkflowExecution(internals: EngineInternals, workflowId: string, workflowType: string, input: unknown, checkpoint: Checkpoint, nestingDepth: number, executionDeadline: number | undefined, executionStateOwnerId: string, _callbacks?: LifecycleCallbacks): void;
|
|
6
|
+
export declare function beginWorkflowExecution(internals: EngineInternals, workflowId: string, workflowType: string, input: unknown, checkpoint: Checkpoint, executionDeadline: number | undefined, executionStateOwnerId: string, _registration: RegistrationEntry, callbacks: LifecycleCallbacks, onStarted?: () => void): void;
|
|
7
|
+
/**
|
|
8
|
+
* `defer: false` is an inline-only liveness gate: it awaits the moment the
|
|
9
|
+
* generator is first driven. A worker-mode start queues to the Worker transport
|
|
10
|
+
* (no inline liveness to await), and a delayed start has not begun executing at
|
|
11
|
+
* all — so both reject rather than silently behaving like `defer: true`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertDeferSupported(internals: EngineInternals, options: StartOptions | undefined, isDelayedStart: boolean): void;
|
|
14
|
+
type BeginExecutionParams = {
|
|
15
|
+
type: string;
|
|
16
|
+
input: unknown;
|
|
17
|
+
checkpoint: Checkpoint;
|
|
18
|
+
state: WorkflowState;
|
|
19
|
+
registration: RegistrationEntry;
|
|
20
|
+
options: StartOptions | undefined;
|
|
21
|
+
isDelayed: boolean;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Drive the initial execution for a freshly-started workflow and, when
|
|
25
|
+
* `defer: false`, await the run actually beginning before resolving. A delayed
|
|
26
|
+
* start does not begin executing now, so it neither begins execution here nor
|
|
27
|
+
* awaits liveness. The liveness promise is settled by the inline-launch queue
|
|
28
|
+
* once the generator is driven (or by dispose if the queued start is discarded),
|
|
29
|
+
* so a `defer: false` caller can rely on the run being live without a macrotask
|
|
30
|
+
* round-trip the moment `engine.start()` resolves.
|
|
31
|
+
*/
|
|
32
|
+
export declare function beginExecutionAwaitingLiveness(internals: EngineInternals, params: BeginExecutionParams, workflowId: string, callbacks: LifecycleCallbacks): Promise<void>;
|
|
33
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { serializeCheckpoint } from "../../checkpoint.js";
|
|
2
|
+
import { WorkflowStartedEvent } from "../../events.js";
|
|
3
|
+
import { StartWorkflowValidationError } from "../../start-workflow-validation.js";
|
|
2
4
|
export function runWorkflowStartInterceptor(_internals, workflowId, workflowType, input, parentHeaders, callbacks) {
|
|
3
5
|
const composedInterceptor = callbacks.getComposedWorkflowInterceptor();
|
|
4
6
|
if (!composedInterceptor)
|
|
@@ -37,3 +39,39 @@ export function startWorkflowExecution(internals, workflowId, workflowType, inpu
|
|
|
37
39
|
}
|
|
38
40
|
});
|
|
39
41
|
}
|
|
42
|
+
export function beginWorkflowExecution(internals, workflowId, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, _registration, callbacks, onStarted) {
|
|
43
|
+
const nestingDepth = internals.pendingNestingDepth ?? 0;
|
|
44
|
+
internals.pendingNestingDepth = void 0;
|
|
45
|
+
if (internals.inlineStrategy !== null) {
|
|
46
|
+
callbacks.queueInlineWorkflowExecutionStart({
|
|
47
|
+
workflowId,
|
|
48
|
+
workflowType,
|
|
49
|
+
input,
|
|
50
|
+
checkpoint,
|
|
51
|
+
nestingDepth,
|
|
52
|
+
executionDeadline,
|
|
53
|
+
executionStateOwnerId,
|
|
54
|
+
...onStarted !== void 0 && { onStarted }
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
callbacks.dispatchEvent(new WorkflowStartedEvent(workflowId, workflowType, input));
|
|
59
|
+
startWorkflowExecution(internals, workflowId, workflowType, input, checkpoint, nestingDepth, executionDeadline, executionStateOwnerId, callbacks);
|
|
60
|
+
onStarted?.();
|
|
61
|
+
}
|
|
62
|
+
export function assertDeferSupported(internals, options, isDelayedStart) {
|
|
63
|
+
if (options?.defer !== !1)
|
|
64
|
+
return;
|
|
65
|
+
if (internals.inlineStrategy === null)
|
|
66
|
+
throw new StartWorkflowValidationError('options.defer: false is only supported in inline execution mode; a worker-mode start cannot be awaited for inline liveness. Use workflowExecutionMode: "inline" or remove defer.');
|
|
67
|
+
if (isDelayedStart)
|
|
68
|
+
throw new StartWorkflowValidationError("options.defer: false is incompatible with a delayed start (startAt/startAfter): a scheduled run has not begun executing, so there is no liveness to await. Remove defer or the delayed-start option.");
|
|
69
|
+
}
|
|
70
|
+
export async function beginExecutionAwaitingLiveness(internals, params, workflowId, callbacks) {
|
|
71
|
+
if (params.isDelayed)
|
|
72
|
+
return;
|
|
73
|
+
const liveness = params.options?.defer === !1 ? Promise.withResolvers() : void 0;
|
|
74
|
+
beginWorkflowExecution(internals, workflowId, params.type, params.input, params.checkpoint, params.state.executionDeadline, params.state.executionStateOwnerId ?? workflowId, params.registration, callbacks, liveness ? () => liveness.resolve() : void 0);
|
|
75
|
+
if (liveness)
|
|
76
|
+
await liveness.promise;
|
|
77
|
+
}
|