@lostgradient/weft 0.8.0 → 0.9.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 +7 -3
- package/dist/cli/generated/operation-client.generated.d.ts +1 -0
- package/dist/cli-main.js +78 -78
- package/dist/core/context/durable-operations.js +1 -1
- package/dist/core/context/index.d.ts +1 -0
- package/dist/core/context/index.js +2 -0
- package/dist/core/context/operation-request.d.ts +2 -0
- package/dist/core/context/run-operation.js +3 -2
- package/dist/core/context/speculative-child.js +1 -0
- package/dist/core/context/types.d.ts +1 -0
- package/dist/core/engine/activity-heartbeat-tracking.d.ts +1 -1
- package/dist/core/engine/activity-heartbeat-tracking.js +5 -1
- package/dist/core/engine/anonymous-signal-sequence.js +0 -2
- package/dist/core/engine/bulk-operations.js +10 -29
- package/dist/core/engine/callback-creators-bundles.js +1 -1
- package/dist/core/engine/disposal.js +1 -0
- package/dist/core/engine/engine-internal-types.d.ts +1 -0
- package/dist/core/engine/index.js +13 -9
- package/dist/core/engine/inline-launch-queue.js +1 -1
- package/dist/core/engine/internals.d.ts +23 -1
- package/dist/core/engine/lifecycle/fork-helpers.js +1 -0
- package/dist/core/engine/lifecycle/resume.js +6 -0
- package/dist/core/engine/lifecycle/start-exec.d.ts +2 -2
- package/dist/core/engine/lifecycle/start-exec.js +6 -4
- package/dist/core/engine/lifecycle/start.js +1 -0
- package/dist/core/engine/lifecycle/transition.js +6 -0
- package/dist/core/engine/memo-durable-activity.js +1 -1
- package/dist/core/engine/operations-activity.d.ts +1 -1
- package/dist/core/engine/operations-activity.js +7 -5
- package/dist/core/engine/operations-time.d.ts +2 -2
- package/dist/core/engine/operations-time.js +40 -6
- package/dist/core/engine/schedules.js +3 -0
- package/dist/core/engine/termination/cleanup.js +15 -13
- package/dist/core/engine/termination/finalizer-activity.d.ts +1 -1
- package/dist/core/engine/termination/finalizer-activity.js +5 -1
- package/dist/core/engine/termination/finalizer.js +1 -1
- package/dist/core/engine/validation/schedule-options.d.ts +9 -0
- package/dist/core/engine/validation/schedule-options.js +59 -0
- package/dist/core/engine/validation/schedule.d.ts +3 -8
- package/dist/core/engine/validation/schedule.js +40 -56
- package/dist/core/engine/validation.js +1 -0
- package/dist/core/execution-strategy.d.ts +1 -0
- package/dist/core/inline-execution-strategy.context-options.d.ts +1 -0
- package/dist/core/inline-execution-strategy.context-options.js +3 -0
- package/dist/core/types/activity.d.ts +11 -0
- package/dist/core/types/checkpoint.d.ts +1 -0
- package/dist/core/types/schedules.d.ts +10 -0
- package/dist/core/types/state.d.ts +6 -0
- package/dist/core/types/workflow-context.d.ts +13 -0
- package/dist/core/worker-execution-strategy.d.ts +1 -0
- package/dist/core/worker-inbound-message.d.ts +1 -0
- package/dist/core/worker-inbound-message.js +3 -0
- package/dist/json-schema.js +2 -2
- package/dist/mcp/cli.js +32 -32
- package/dist/observability/index.js +2 -2
- package/dist/runtime/portable.d.ts +1 -1
- package/dist/server/handler.js +27 -27
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +30 -30
- package/dist/server/operations/create-schedule.d.ts +2 -0
- package/dist/server/operations/create-schedule.js +13 -2
- package/dist/server/rest-binding.d.ts +1 -1
- package/dist/server/runtime/task-dispatch.js +6 -0
- package/dist/server/runtime/task-polling.js +9 -1
- package/dist/server/runtime/task-reconciliation.js +2 -1
- package/dist/server/task-queue-types.d.ts +2 -0
- package/dist/server/task-state.d.ts +4 -0
- package/dist/service-worker/index.d.ts +15 -14
- package/dist/service-worker/index.js +28 -28
- package/dist/service-worker/scheduler.d.ts +41 -1
- package/dist/storage/auto.d.ts +36 -18
- package/dist/storage/auto.js +1 -1
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/indexeddb.d.ts +6 -1
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.js +1 -1
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +1 -1
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +1 -1
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.d.ts +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/index.js +33 -33
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/execute-with-interceptors.d.ts +9 -3
- package/dist/worker/execute-with-interceptors.js +14 -2
- package/dist/worker/long-poll.d.ts +2 -1
- package/dist/worker/long-poll.js +1 -1
- package/dist/worker/protocol-messages.d.ts +2 -0
- package/dist/worker/protocol-schemas.d.ts +8 -0
- package/dist/worker/protocol-schemas.js +1 -0
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/remote-activity-context.d.ts +2 -0
- package/dist/workers/activity-runner.d.ts +2 -0
- package/dist/workers/activity-runner.js +9 -1
- package/dist/workers/workflow-runner.d.ts +2 -1
- package/dist/workers/workflow-runner.js +1 -0
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ export function prepareSleepOperation(internals, duration) {
|
|
|
11
11
|
console.log(` \u2192 Creating checkpoint at step ${step}`);
|
|
12
12
|
console.log(` \u2192 Scheduling timer for ${milliseconds}ms`);
|
|
13
13
|
}
|
|
14
|
-
const operationId =
|
|
14
|
+
const operationId = `${internals.context.workflowId}:${step}`, callerStack = captureCallerStack(), referenceTime = internals.sleepReferenceTime ?? internals.getNow();
|
|
15
15
|
internals.sleepReferenceTime = void 0;
|
|
16
16
|
return {
|
|
17
17
|
cached: !1,
|
|
@@ -23,6 +23,7 @@ export declare function setContextWorkflowInterceptor(context: Context, workflow
|
|
|
23
23
|
export declare class Context implements WorkflowContext {
|
|
24
24
|
#private;
|
|
25
25
|
readonly workflowId: string;
|
|
26
|
+
readonly workflowExecutionToken: string | undefined;
|
|
26
27
|
readonly workflowType: string;
|
|
27
28
|
readonly startedAt: number;
|
|
28
29
|
readonly signal: AbortSignal;
|
|
@@ -21,12 +21,14 @@ export function setContextWorkflowInterceptor(context, workflowInterceptor) {
|
|
|
21
21
|
|
|
22
22
|
export class Context {
|
|
23
23
|
workflowId;
|
|
24
|
+
workflowExecutionToken;
|
|
24
25
|
workflowType;
|
|
25
26
|
startedAt;
|
|
26
27
|
signal;
|
|
27
28
|
#log;
|
|
28
29
|
constructor(options) {
|
|
29
30
|
this.workflowId = options.workflowId;
|
|
31
|
+
this.workflowExecutionToken = options.workflowExecutionToken;
|
|
30
32
|
this.workflowType = options.workflowType;
|
|
31
33
|
this.startedAt = options.startedAt;
|
|
32
34
|
this.signal = options.abortController.signal;
|
|
@@ -30,6 +30,8 @@ export type ContextOperationRequest = {
|
|
|
30
30
|
activityName: string;
|
|
31
31
|
fn?: (input: unknown, context?: unknown) => unknown;
|
|
32
32
|
input: unknown;
|
|
33
|
+
/** Immutable owner token for this workflow run, used to fence external side effects. */
|
|
34
|
+
workflowExecutionToken?: string;
|
|
33
35
|
callerStack?: string;
|
|
34
36
|
options?: Record<string, unknown>;
|
|
35
37
|
/** Dispatch attempt used for worker payloads, interceptors, and reconciliation verifiers. */
|
|
@@ -74,7 +74,7 @@ function getActivityName(activity, explicitName) {
|
|
|
74
74
|
function getActivityFunction(activity) {
|
|
75
75
|
return typeof activity === "function" ? activity : void 0;
|
|
76
76
|
}
|
|
77
|
-
function createFreshRunActivityRequest(internals, step, activityName, activityFunction, input, options, activityStateKey) {
|
|
77
|
+
function createFreshRunActivityRequest(internals, step, activityName, activityFunction, input, options, activityStateKey, workflowExecutionToken) {
|
|
78
78
|
const queue = options?.queue ?? "default";
|
|
79
79
|
if (internals.explainMode) {
|
|
80
80
|
console.log(`[weft] ctx.run(${activityName}, ${JSON.stringify(input)})`);
|
|
@@ -87,6 +87,7 @@ function createFreshRunActivityRequest(internals, step, activityName, activityFu
|
|
|
87
87
|
activityName,
|
|
88
88
|
step,
|
|
89
89
|
...activityStateKey === void 0 ? {} : { activityStateKey },
|
|
90
|
+
...workflowExecutionToken === void 0 ? {} : { workflowExecutionToken },
|
|
90
91
|
...activityFunction !== void 0 ? { fn: activityFunction } : {},
|
|
91
92
|
input,
|
|
92
93
|
callerStack: captureCallerStack(),
|
|
@@ -141,7 +142,7 @@ export function createRunActivityRequestAtStep(context, activity, rest, step, co
|
|
|
141
142
|
return cachedRequest;
|
|
142
143
|
const retryPolicy = resolveActivityRetryPolicy(activity, options), scheduleToCloseTimeout = resolveActivityScheduleToCloseTimeout(activity, options);
|
|
143
144
|
return {
|
|
144
|
-
request: createFreshRunActivityRequest(internals, step, activityName, activityFunction, input, resolveDispatchedActivityOptions(activity, options), configuration.activityStateKey),
|
|
145
|
+
request: createFreshRunActivityRequest(internals, step, activityName, activityFunction, input, resolveDispatchedActivityOptions(activity, options), configuration.activityStateKey, context.workflowExecutionToken),
|
|
145
146
|
step,
|
|
146
147
|
retryStateKey,
|
|
147
148
|
...cacheResultStep === void 0 ? {} : { cacheResultStep },
|
|
@@ -24,6 +24,7 @@ function assignOptionalContextOptions(options, internals) {
|
|
|
24
24
|
function createSpeculativeChildOptions(parent, internals) {
|
|
25
25
|
const options = {
|
|
26
26
|
workflowId: parent.workflowId,
|
|
27
|
+
...parent.workflowExecutionToken === void 0 ? {} : { workflowExecutionToken: parent.workflowExecutionToken },
|
|
27
28
|
workflowType: parent.workflowType,
|
|
28
29
|
startedAt: parent.startedAt,
|
|
29
30
|
abortController: internals.abortController,
|
|
@@ -52,4 +52,4 @@ export declare function warnIfRetryMissingHeartbeat(internals: EngineInternals,
|
|
|
52
52
|
* tracking above; `completeAsync` is supplied by the caller (it owns the
|
|
53
53
|
* async-completion token machinery).
|
|
54
54
|
*/
|
|
55
|
-
export declare function buildActivityContext(internals: EngineInternals, workflowId: string, step: ActivityHeartbeatKey, signal: AbortSignal, completeAsync: () => never): ActivityContext;
|
|
55
|
+
export declare function buildActivityContext(internals: EngineInternals, workflowId: string, workflowExecutionToken: string | undefined, step: ActivityHeartbeatKey, attempt: number, signal: AbortSignal, completeAsync: () => never): ActivityContext;
|
|
@@ -35,9 +35,13 @@ export function warnIfRetryMissingHeartbeat(internals, workflowId, step, attempt
|
|
|
35
35
|
return;
|
|
36
36
|
internals.engine.dispatchEvent(new DevelopmentWarningEvent(workflowId, `Activity retry (attempt ${attempt}) at ${activityHeartbeatKeyLabel(step)} has no lastHeartbeatDetails. Either the previous attempt never recorded heartbeat details (it never called heartbeat(), or called it with no details), or the engine process restarted and discarded the in-memory heartbeat (it is not durable). The resumable-batch pattern only resumes across in-process retries; design the activity to restart cleanly when lastHeartbeatDetails is undefined.`, [activityHeartbeatFieldPath(step)]));
|
|
37
37
|
}
|
|
38
|
-
export function buildActivityContext(internals, workflowId, step, signal, completeAsync) {
|
|
38
|
+
export function buildActivityContext(internals, workflowId, workflowExecutionToken, step, attempt, signal, completeAsync) {
|
|
39
39
|
return {
|
|
40
40
|
signal,
|
|
41
|
+
...workflowExecutionToken !== void 0 && {
|
|
42
|
+
workflowExecutionToken,
|
|
43
|
+
activityAttemptToken: `${workflowExecutionToken}:activity:${String(step)}:${String(attempt)}`
|
|
44
|
+
},
|
|
41
45
|
lastHeartbeatDetails: readLastHeartbeatForStep(internals, workflowId, step),
|
|
42
46
|
heartbeat: (details) => {
|
|
43
47
|
internals.heartbeatDetails.set(workflowId, details);
|
|
@@ -94,8 +94,6 @@ function extractAnonymousSignalSequence(key) {
|
|
|
94
94
|
return Number.isSafeInteger(sequence) && sequence >= 0 ? sequence : null;
|
|
95
95
|
}
|
|
96
96
|
function decodeSignalSequence(bytes) {
|
|
97
|
-
if (bytes === null)
|
|
98
|
-
return 0;
|
|
99
97
|
const value = decode(bytes);
|
|
100
98
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
101
99
|
throw Error("Stored anonymous signal sequence must be a non-negative safe integer");
|
|
@@ -62,20 +62,13 @@ async function runBulkCancellation(internals, filter, options = {}) {
|
|
|
62
62
|
const workflowIdsToCancel = preparation.workflowIds, bulkConcurrency = resolveBulkOperationConcurrency(options);
|
|
63
63
|
let cancelled = 0;
|
|
64
64
|
const errors = [], cancellationResults = await runBulkWorkflowPool(workflowIdsToCancel, bulkConcurrency, async (workflowId) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return {
|
|
75
|
-
status: "failed",
|
|
76
|
-
error: toBulkOperationError(internals, workflowId, error)
|
|
77
|
-
};
|
|
78
|
-
}
|
|
65
|
+
await internals.engine.cancel(workflowId);
|
|
66
|
+
if ((await loadWorkflowState(internals, workflowId))?.status === "cancelled")
|
|
67
|
+
return { status: "cancelled" };
|
|
68
|
+
return {
|
|
69
|
+
status: "failed",
|
|
70
|
+
error: { id: workflowId, error: "Workflow no longer cancellable" }
|
|
71
|
+
};
|
|
79
72
|
});
|
|
80
73
|
for (const cancellationResult of cancellationResults) {
|
|
81
74
|
if (cancellationResult.status === "rejected") {
|
|
@@ -107,27 +100,15 @@ async function runBulkFailedWorkflowRetry(internals, filter, options = {}) {
|
|
|
107
100
|
const bulkConcurrency = resolveBulkOperationConcurrency(options);
|
|
108
101
|
let retried = 0;
|
|
109
102
|
const errors = [], retryResults = await runBulkWorkflowPool(preparation.workflowIds, bulkConcurrency, async (workflowId) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return { status: "retried" };
|
|
113
|
-
} catch (error) {
|
|
114
|
-
return {
|
|
115
|
-
status: "failed",
|
|
116
|
-
error: toBulkOperationError(internals, workflowId, error)
|
|
117
|
-
};
|
|
118
|
-
}
|
|
103
|
+
await retryFailedWorkflow(internals, workflowId);
|
|
104
|
+
return { status: "retried" };
|
|
119
105
|
});
|
|
120
106
|
for (const retryResult of retryResults) {
|
|
121
107
|
if (retryResult.status === "rejected") {
|
|
122
108
|
errors.push(toBulkOperationError(internals, retryResult.item, retryResult.reason));
|
|
123
109
|
continue;
|
|
124
110
|
}
|
|
125
|
-
|
|
126
|
-
if (value.status === "retried") {
|
|
127
|
-
retried += 1;
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
errors.push(value.error);
|
|
111
|
+
retried += 1;
|
|
131
112
|
}
|
|
132
113
|
const result = { retried, failed: errors.length, errors };
|
|
133
114
|
if (!shouldPersistBulkAudit(options))
|
|
@@ -122,7 +122,7 @@ export function createTimeOperationCallbacks(engine) {
|
|
|
122
122
|
loadWorkflowState: (workflowId) => loadWorkflowState(getInternals(engine), workflowId),
|
|
123
123
|
failWorkflow: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine)),
|
|
124
124
|
runSerializedWorkflowStateWrite: (workflowId, writeOperation) => runSerializedWorkflowStateWrite(getInternals(engine), workflowId, writeOperation),
|
|
125
|
-
beginWorkflowExecution: (workflowId, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, registration) => beginWorkflowExecution(getInternals(engine), workflowId, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, registration, createLifecycleCallbacks(engine)),
|
|
125
|
+
beginWorkflowExecution: (workflowId, workflowExecutionToken, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, registration) => beginWorkflowExecution(getInternals(engine), workflowId, workflowExecutionToken, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, registration, createLifecycleCallbacks(engine)),
|
|
126
126
|
workflowVersionTupleFromState: (state) => workflowVersionTupleFromState(getInternals(engine), state, createLifecycleCallbacks(engine)),
|
|
127
127
|
setWorkflowStartHeaders: (workflowId, headers) => setWorkflowStartHeaders(getInternals(engine), workflowId, headers, createLifecycleCallbacks(engine)),
|
|
128
128
|
loadWorkflowStartHeaders: (workflowId) => loadWorkflowStartHeaders(getInternals(engine), workflowId, createLifecycleCallbacks(engine)),
|
|
@@ -46,6 +46,7 @@ export function disposeEngine(internals) {
|
|
|
46
46
|
internals.pendingWebhooks.clear();
|
|
47
47
|
internals.sleepResolvers.clear();
|
|
48
48
|
internals.sleepResolversByWorkflow.clear();
|
|
49
|
+
internals.sleepTimersFiredWithoutResolver.clear();
|
|
49
50
|
internals.checkpoints.clear();
|
|
50
51
|
internals.pendingExecutionStateOwnerId = void 0;
|
|
51
52
|
internals.workflowNestingDepths.clear();
|
|
@@ -209,6 +209,16 @@ export { assertCompatiblePersistedDataVersion };
|
|
|
209
209
|
export const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING = Symbol("engineParkedWorkflowCountForTesting"), ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING = Symbol("engineSignalWaiterCountForTesting"), ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING = Symbol("engineSleepResolverCountForTesting"), ENGINE_LEASE_SYNCHRONOUS_DISPOSE_WARNING_NAME = "WeftEngineLeaseSynchronousDisposeWarning";
|
|
210
210
|
|
|
211
211
|
export { ENGINE_LEASE_LOST_WARNING_NAME };
|
|
212
|
+
function scheduleDefinitionFromInternals(internals, definition) {
|
|
213
|
+
const workflowType = typeof definition.workflow === "string" ? definition.workflow : definition.workflow.name, definitionSpec = definition.every !== void 0 ? { every: definition.every } : { cron: definition.cron ?? "" };
|
|
214
|
+
return scheduleFromInternals(internals, workflowType, definition.input, definitionSpec, {
|
|
215
|
+
...definition.id !== void 0 && { id: definition.id },
|
|
216
|
+
...definition.description !== void 0 && { description: definition.description },
|
|
217
|
+
...definition.overlapPolicy !== void 0 && { overlap: definition.overlapPolicy },
|
|
218
|
+
...definition.backfill !== void 0 && { backfill: definition.backfill },
|
|
219
|
+
...definition.jitter !== void 0 && { jitter: definition.jitter }
|
|
220
|
+
});
|
|
221
|
+
}
|
|
212
222
|
|
|
213
223
|
export class Engine extends EventTarget {
|
|
214
224
|
static async create(options) {
|
|
@@ -267,6 +277,7 @@ export class Engine extends EventTarget {
|
|
|
267
277
|
getInternals(this).updateWaitersByWorkflow = new Map;
|
|
268
278
|
getInternals(this).sleepResolvers = new Map;
|
|
269
279
|
getInternals(this).sleepResolversByWorkflow = new Map;
|
|
280
|
+
getInternals(this).sleepTimersFiredWithoutResolver = new Map;
|
|
270
281
|
getInternals(this).interceptors = resolveEngineInterceptors(options);
|
|
271
282
|
getInternals(this).composedWorkflowInterceptor = void 0;
|
|
272
283
|
getInternals(this).composedActivityInterceptor = void 0;
|
|
@@ -599,15 +610,8 @@ export class Engine extends EventTarget {
|
|
|
599
610
|
return untagAllWorkflows(getInternals(this), filter, tags, options);
|
|
600
611
|
}
|
|
601
612
|
async schedule(typeOrDefinition, input, spec, options) {
|
|
602
|
-
if (typeof typeOrDefinition === "object")
|
|
603
|
-
|
|
604
|
-
return scheduleFromInternals(getInternals(this), workflowType, definition.input, definitionSpec, {
|
|
605
|
-
...definition.id !== void 0 && { id: definition.id },
|
|
606
|
-
...definition.overlapPolicy !== void 0 && { overlap: definition.overlapPolicy },
|
|
607
|
-
...definition.backfill !== void 0 && { backfill: definition.backfill },
|
|
608
|
-
...definition.jitter !== void 0 && { jitter: definition.jitter }
|
|
609
|
-
});
|
|
610
|
-
}
|
|
613
|
+
if (typeof typeOrDefinition === "object")
|
|
614
|
+
return scheduleDefinitionFromInternals(getInternals(this), typeOrDefinition);
|
|
611
615
|
if (spec === void 0)
|
|
612
616
|
throw Error("A cron string or schedule spec must be provided when scheduling by workflow type.");
|
|
613
617
|
return scheduleFromInternals(getInternals(this), typeOrDefinition, input, spec, options);
|
|
@@ -58,7 +58,7 @@ async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
|
|
|
58
58
|
return;
|
|
59
59
|
internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
|
|
60
60
|
internals.engine.dispatchEvent(new WorkflowStartedEvent(start.workflowId, start.workflowType, start.input));
|
|
61
|
-
startWorkflowExecution(internals, start.workflowId, start.workflowType, start.input, start.checkpoint, start.nestingDepth, start.executionDeadline, start.executionStateOwnerId);
|
|
61
|
+
startWorkflowExecution(internals, start.workflowId, state.workflowExecutionToken, start.workflowType, start.input, start.checkpoint, start.nestingDepth, start.executionDeadline, start.executionStateOwnerId);
|
|
62
62
|
await callbacks.processPendingUpdatesAfterInlineAdvance(start.workflowId);
|
|
63
63
|
} finally {
|
|
64
64
|
internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
|
|
@@ -90,8 +90,30 @@ export interface EngineInternals {
|
|
|
90
90
|
conditionWaiters: Map<string, () => void>;
|
|
91
91
|
updateWaiters: Map<string, (payload: unknown) => void>;
|
|
92
92
|
updateWaitersByWorkflow: Map<string, TrackedWaiterKeys>;
|
|
93
|
-
|
|
93
|
+
/**
|
|
94
|
+
* In-process sleep resolvers keyed by `${workflowId}:${operationId}`. Each
|
|
95
|
+
* carries the run's expected `fireAt` so a fired timer only settles the sleep
|
|
96
|
+
* whose deadline it represents — a stale timer from a terminated run that
|
|
97
|
+
* reused the same deterministic operationId (its durable timer outlives
|
|
98
|
+
* terminal cleanup) is ignored rather than resolving a replacement run early.
|
|
99
|
+
*/
|
|
100
|
+
sleepResolvers: Map<string, {
|
|
101
|
+
resolve: () => void;
|
|
102
|
+
fireAt: number;
|
|
103
|
+
}>;
|
|
94
104
|
sleepResolversByWorkflow: Map<string, Set<string>>;
|
|
105
|
+
/**
|
|
106
|
+
* Per-workflow maps of `operationId` → fired timer `fireAt` for sleep timers
|
|
107
|
+
* the scheduler tick fired before the resolver was registered. Keyed by
|
|
108
|
+
* `workflowId` so per-workflow cleanup (`cleanupSleepResolvers`,
|
|
109
|
+
* `evictSleepResolversWithoutResolving`) can call `.delete(workflowId)` and
|
|
110
|
+
* sweep all markers in O(1) — including orphaned ones where the timer fired
|
|
111
|
+
* while the workflow was suspended (no `sleepResolversByWorkflow` entry) or
|
|
112
|
+
* during recovery of an already-elapsed sleep that takes the early-return
|
|
113
|
+
* path. The `fireAt` lets `processSleepOperation` ignore a marker left by a
|
|
114
|
+
* stale earlier-run timer. Cleared entirely at engine disposal.
|
|
115
|
+
*/
|
|
116
|
+
sleepTimersFiredWithoutResolver: Map<string, Map<string, number>>;
|
|
95
117
|
interceptors: Interceptor[];
|
|
96
118
|
composedWorkflowInterceptor: ComposedWorkflowInterceptor | null | undefined;
|
|
97
119
|
composedActivityInterceptor: ComposedActivityInterceptor | null | undefined;
|
|
@@ -22,6 +22,7 @@ export function createForkedWorkflowState(_internals, workflowId, sourceState, v
|
|
|
22
22
|
type: sourceState.type,
|
|
23
23
|
status: "running",
|
|
24
24
|
input: sourceState.input,
|
|
25
|
+
workflowExecutionToken: crypto.randomUUID(),
|
|
25
26
|
versionTuple,
|
|
26
27
|
executionStateOwnerId: workflowId,
|
|
27
28
|
createdAt: forkedAt,
|
|
@@ -51,6 +51,9 @@ async function relaunchInlineWorkflowAfterResume(internals, latestState, args) {
|
|
|
51
51
|
await rehydrateChildCancellationHandlers(internals, workflowId, callbacks);
|
|
52
52
|
const context = new Context({
|
|
53
53
|
workflowId,
|
|
54
|
+
...latestState.workflowExecutionToken !== void 0 && {
|
|
55
|
+
workflowExecutionToken: latestState.workflowExecutionToken
|
|
56
|
+
},
|
|
54
57
|
workflowType: latestState.type,
|
|
55
58
|
startedAt: getWorkflowExecutionStartedAt(latestState),
|
|
56
59
|
abortController: workflowAbort,
|
|
@@ -85,6 +88,9 @@ async function relaunchWorkerWorkflowAfterResume(internals, latestState, args) {
|
|
|
85
88
|
const serialized = serializeCheckpoint(resumeCheckpoint);
|
|
86
89
|
internals.strategy.startWorkflow({
|
|
87
90
|
workflowId,
|
|
91
|
+
...latestState.workflowExecutionToken !== void 0 && {
|
|
92
|
+
workflowExecutionToken: latestState.workflowExecutionToken
|
|
93
|
+
},
|
|
88
94
|
workflowType: latestState.type,
|
|
89
95
|
input: latestState.input,
|
|
90
96
|
checkpoint: serialized,
|
|
@@ -2,8 +2,8 @@ import type { Checkpoint, StartOptions, WorkflowState } from '../../types.ts';
|
|
|
2
2
|
import type { EngineInternals } from '../internals.ts';
|
|
3
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
|
-
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;
|
|
5
|
+
export declare function startWorkflowExecution(internals: EngineInternals, workflowId: string, workflowExecutionToken: string | undefined, 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, workflowExecutionToken: string | undefined, workflowType: string, input: unknown, checkpoint: Checkpoint, executionDeadline: number | undefined, executionStateOwnerId: string, _registration: RegistrationEntry, callbacks: LifecycleCallbacks, onStarted?: () => void): void;
|
|
7
7
|
/**
|
|
8
8
|
* `defer: false` is an inline-only liveness gate: it awaits the moment the
|
|
9
9
|
* generator is first driven. A worker-mode start queues to the Worker transport
|
|
@@ -20,12 +20,13 @@ export function runWorkflowStartInterceptor(_internals, workflowId, workflowType
|
|
|
20
20
|
});
|
|
21
21
|
return capturedHeaders;
|
|
22
22
|
}
|
|
23
|
-
export function startWorkflowExecution(internals, workflowId, workflowType, input, checkpoint, nestingDepth, executionDeadline, executionStateOwnerId, _callbacks) {
|
|
23
|
+
export function startWorkflowExecution(internals, workflowId, workflowExecutionToken, workflowType, input, checkpoint, nestingDepth, executionDeadline, executionStateOwnerId, _callbacks) {
|
|
24
24
|
if (nestingDepth !== 0)
|
|
25
25
|
internals.workflowNestingDepths.set(workflowId, nestingDepth);
|
|
26
26
|
internals.workflowTypeByWorkflowId.set(workflowId, workflowType);
|
|
27
27
|
internals.strategy.startWorkflow({
|
|
28
28
|
workflowId,
|
|
29
|
+
...workflowExecutionToken !== void 0 && { workflowExecutionToken },
|
|
29
30
|
workflowType,
|
|
30
31
|
input,
|
|
31
32
|
checkpoint: serializeCheckpoint(checkpoint),
|
|
@@ -39,12 +40,13 @@ export function startWorkflowExecution(internals, workflowId, workflowType, inpu
|
|
|
39
40
|
}
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
|
-
export function beginWorkflowExecution(internals, workflowId, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, _registration, callbacks, onStarted) {
|
|
43
|
+
export function beginWorkflowExecution(internals, workflowId, workflowExecutionToken, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, _registration, callbacks, onStarted) {
|
|
43
44
|
const nestingDepth = internals.pendingNestingDepth ?? 0;
|
|
44
45
|
internals.pendingNestingDepth = void 0;
|
|
45
46
|
if (internals.inlineStrategy !== null) {
|
|
46
47
|
callbacks.queueInlineWorkflowExecutionStart({
|
|
47
48
|
workflowId,
|
|
49
|
+
...workflowExecutionToken !== void 0 && { workflowExecutionToken },
|
|
48
50
|
workflowType,
|
|
49
51
|
input,
|
|
50
52
|
checkpoint,
|
|
@@ -56,7 +58,7 @@ export function beginWorkflowExecution(internals, workflowId, workflowType, inpu
|
|
|
56
58
|
return;
|
|
57
59
|
}
|
|
58
60
|
callbacks.dispatchEvent(new WorkflowStartedEvent(workflowId, workflowType, input));
|
|
59
|
-
startWorkflowExecution(internals, workflowId, workflowType, input, checkpoint, nestingDepth, executionDeadline, executionStateOwnerId, callbacks);
|
|
61
|
+
startWorkflowExecution(internals, workflowId, workflowExecutionToken, workflowType, input, checkpoint, nestingDepth, executionDeadline, executionStateOwnerId, callbacks);
|
|
60
62
|
onStarted?.();
|
|
61
63
|
}
|
|
62
64
|
export function assertDeferSupported(internals, options, isDelayedStart) {
|
|
@@ -71,7 +73,7 @@ export async function beginExecutionAwaitingLiveness(internals, params, workflow
|
|
|
71
73
|
if (params.isDelayed)
|
|
72
74
|
return;
|
|
73
75
|
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);
|
|
76
|
+
beginWorkflowExecution(internals, workflowId, params.state.workflowExecutionToken, params.type, params.input, params.checkpoint, params.state.executionDeadline, params.state.executionStateOwnerId ?? workflowId, params.registration, callbacks, liveness ? () => liveness.resolve() : void 0);
|
|
75
77
|
if (liveness)
|
|
76
78
|
await liveness.promise;
|
|
77
79
|
}
|
|
@@ -145,6 +145,7 @@ function buildInitialIdentitySlice(workflowId, type, input, versionTuple, execut
|
|
|
145
145
|
status: delayedStartTimer ? "pending" : "running",
|
|
146
146
|
input,
|
|
147
147
|
versionTuple,
|
|
148
|
+
workflowExecutionToken: crypto.randomUUID(),
|
|
148
149
|
...executionStateOwnerId !== void 0 && { executionStateOwnerId },
|
|
149
150
|
createdAt: now,
|
|
150
151
|
...!delayedStartTimer && { startedAt: now },
|
|
@@ -144,6 +144,9 @@ function launchInlineWorkflowFromCheckpoint(internals, workflowId, state, checkp
|
|
|
144
144
|
resetCancelHandlers(internals, workflowId);
|
|
145
145
|
const context = new Context({
|
|
146
146
|
workflowId,
|
|
147
|
+
...state.workflowExecutionToken !== void 0 && {
|
|
148
|
+
workflowExecutionToken: state.workflowExecutionToken
|
|
149
|
+
},
|
|
147
150
|
workflowType: state.type,
|
|
148
151
|
startedAt: getWorkflowExecutionStartedAt(state),
|
|
149
152
|
abortController: workflowAbort,
|
|
@@ -172,6 +175,9 @@ function launchWorkerWorkflowFromCheckpoint(internals, workflowId, state, checkp
|
|
|
172
175
|
const serialized = serializeCheckpoint(checkpoint);
|
|
173
176
|
internals.strategy.startWorkflow({
|
|
174
177
|
workflowId,
|
|
178
|
+
...state.workflowExecutionToken !== void 0 && {
|
|
179
|
+
workflowExecutionToken: state.workflowExecutionToken
|
|
180
|
+
},
|
|
175
181
|
workflowType: state.type,
|
|
176
182
|
input: state.input,
|
|
177
183
|
checkpoint: serialized,
|
|
@@ -149,7 +149,7 @@ class MemoDurableActivityScope {
|
|
|
149
149
|
finishTimerWrite();
|
|
150
150
|
}
|
|
151
151
|
this.#throwIfClosed();
|
|
152
|
-
registerSleepResolver(this.#internals, this.#workflowId, operation.operationId, resolve);
|
|
152
|
+
registerSleepResolver(this.#internals, this.#workflowId, operation.operationId, resolve, operation.scheduledFireAt);
|
|
153
153
|
await promise;
|
|
154
154
|
}
|
|
155
155
|
#activityOperationId(ordinal, attempt) {
|
|
@@ -25,7 +25,7 @@ export type ActivityOperationCallbacks = {
|
|
|
25
25
|
};
|
|
26
26
|
export declare function buildActivityVerification(_internals: EngineInternals, activityName: string, verify: ActivityFunctionWithMetadata['verify'], context: ReturnType<typeof buildActivityVerificationContext>, result: unknown): Promise<void>;
|
|
27
27
|
export declare function buildActivityCompensation(internals: EngineInternals, workflowId: string, operation: ActivityOperation, result: unknown): (() => Promise<void>) | undefined;
|
|
28
|
-
export declare function invokeWorkerActivity(internals: EngineInternals, operationId: string, activityName: string, input: unknown, attempt: number): Promise<unknown>;
|
|
28
|
+
export declare function invokeWorkerActivity(internals: EngineInternals, operationId: string, activityName: string, input: unknown, attempt: number, workflowExecutionToken: string | undefined, activityAttemptToken: string | undefined): Promise<unknown>;
|
|
29
29
|
export declare function invokeInlineActivity(internals: EngineInternals, workflowId: string, operation: ActivityOperation, activityContext: ActivityContext, _activityName: string, input: unknown): unknown;
|
|
30
30
|
/**
|
|
31
31
|
* Execute an activity function, dispatching to a Web Worker pool when
|
|
@@ -40,7 +40,7 @@ export function buildActivityCompensation(internals, workflowId, operation, resu
|
|
|
40
40
|
await activity.compensate?.(operation.input, result);
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
export async function invokeWorkerActivity(internals, operationId, activityName, input, attempt) {
|
|
43
|
+
export async function invokeWorkerActivity(internals, operationId, activityName, input, attempt, workflowExecutionToken, activityAttemptToken) {
|
|
44
44
|
const dispatcher = internals.activityWorkerDispatcher;
|
|
45
45
|
if (!dispatcher)
|
|
46
46
|
throw Error(`No activity worker dispatcher available for "${activityName}"`);
|
|
@@ -48,7 +48,9 @@ export async function invokeWorkerActivity(internals, operationId, activityName,
|
|
|
48
48
|
operationId,
|
|
49
49
|
activityName,
|
|
50
50
|
input,
|
|
51
|
-
attempt
|
|
51
|
+
attempt,
|
|
52
|
+
...workflowExecutionToken !== void 0 && { workflowExecutionToken },
|
|
53
|
+
...activityAttemptToken !== void 0 && { activityAttemptToken }
|
|
52
54
|
});
|
|
53
55
|
if (result.status === "failed") {
|
|
54
56
|
const error = Error(result.error);
|
|
@@ -74,11 +76,11 @@ function getActivityStateKey(operation) {
|
|
|
74
76
|
return operation.activityStateKey ?? operation.step ?? 0;
|
|
75
77
|
}
|
|
76
78
|
export async function executeActivity(internals, workflowId, operation, callbacks, attempt = getActivityAttempt(operation), coordinatorSignal) {
|
|
77
|
-
const activityInput = operation.input, activityStateKey = getActivityStateKey(operation);
|
|
79
|
+
const activityInput = operation.input, activityStateKey = getActivityStateKey(operation), workflowExecutionToken = operation.workflowExecutionToken;
|
|
78
80
|
warnIfRetryMissingHeartbeat(internals, workflowId, activityStateKey, attempt);
|
|
79
|
-
const asyncToken = deriveAsyncActivityToken(workflowId, activityStateKey, attempt), { perAttemptTimeoutMs, attemptAbortController, activitySignal } = resolvePerAttemptTimeout(internals, workflowId, operation, coordinatorSignal), activityContext = buildActivityContext(internals, workflowId, activityStateKey, activitySignal, () => {
|
|
81
|
+
const asyncToken = deriveAsyncActivityToken(workflowId, activityStateKey, attempt), { perAttemptTimeoutMs, attemptAbortController, activitySignal } = resolvePerAttemptTimeout(internals, workflowId, operation, coordinatorSignal), activityContext = buildActivityContext(internals, workflowId, workflowExecutionToken, activityStateKey, attempt, activitySignal, () => {
|
|
80
82
|
throw new AsyncActivityDeferral(asyncToken);
|
|
81
|
-
}), invokeActivity = internals.activityWorkerDispatcher ? (activityName, input) => invokeWorkerActivity(internals, operation.operationId, activityName, input, attempt) : (activityName, input) => withPerAttemptTimeout(invokeInlineActivity(internals, workflowId, operation, activityContext, activityName, input), perAttemptTimeoutMs, activityName, attempt, attemptAbortController), composedActivity = callbacks.getComposedActivityInterceptor(), executeWithActivityInterceptors = async (activityName, input, headers) => {
|
|
83
|
+
}), activityAttemptToken = activityContext.activityAttemptToken, invokeActivity = internals.activityWorkerDispatcher ? (activityName, input) => invokeWorkerActivity(internals, operation.operationId, activityName, input, attempt, workflowExecutionToken, activityAttemptToken) : (activityName, input) => withPerAttemptTimeout(invokeInlineActivity(internals, workflowId, operation, activityContext, activityName, input), perAttemptTimeoutMs, activityName, attempt, attemptAbortController), composedActivity = callbacks.getComposedActivityInterceptor(), executeWithActivityInterceptors = async (activityName, input, headers) => {
|
|
82
84
|
if (!composedActivity)
|
|
83
85
|
return invokeActivity(activityName, input);
|
|
84
86
|
const activityInterception = {
|
|
@@ -12,7 +12,7 @@ export type TimeOperationCallbacks = {
|
|
|
12
12
|
loadWorkflowState: (workflowId: string) => Promise<WorkflowState | null>;
|
|
13
13
|
failWorkflow: (workflowId: string, error: Error) => Promise<void>;
|
|
14
14
|
runSerializedWorkflowStateWrite: <Result>(workflowId: string, writeOperation: () => Promise<Result>) => Promise<Result>;
|
|
15
|
-
beginWorkflowExecution: (workflowId: string, workflowType: string, input: unknown, checkpoint: Checkpoint, executionDeadline: number | undefined, executionStateOwnerId: string, registration: RegistrationEntry) => void;
|
|
15
|
+
beginWorkflowExecution: (workflowId: string, workflowExecutionToken: string | undefined, workflowType: string, input: unknown, checkpoint: Checkpoint, executionDeadline: number | undefined, executionStateOwnerId: string, registration: RegistrationEntry) => void;
|
|
16
16
|
workflowVersionTupleFromState: (state: WorkflowState) => WorkflowVersionTuple;
|
|
17
17
|
setWorkflowStartHeaders: (workflowId: string, headers: Map<string, string> | undefined) => void;
|
|
18
18
|
loadWorkflowStartHeaders: (workflowId: string) => Promise<Map<string, string> | undefined>;
|
|
@@ -25,7 +25,7 @@ export type TimeOperationCallbacks = {
|
|
|
25
25
|
};
|
|
26
26
|
export declare function createDelayedStartTimerEntry(_internals: EngineInternals, workflowId: string, scheduledStartAt: number, options: StartOptions | undefined, callbacks: Pick<TimeOperationCallbacks, 'parseStartOptionDuration'>): TimerEntry;
|
|
27
27
|
export declare function processSleepOperation(internals: EngineInternals, workflowId: string, operation: SleepOperation, callbacks: Pick<TimeOperationCallbacks, 'completeOperation' | 'loadWorkflowState'>): Promise<void>;
|
|
28
|
-
export declare function registerSleepResolver(internals: EngineInternals, workflowId: string, operationId: string, resolve: () => void): void;
|
|
28
|
+
export declare function registerSleepResolver(internals: EngineInternals, workflowId: string, operationId: string, resolve: () => void, scheduledFireAt: number): void;
|
|
29
29
|
export declare function startDelayedWorkflow(internals: EngineInternals, entry: TimerEntry, callbacks: Pick<TimeOperationCallbacks, 'beginWorkflowExecution' | 'dispatchEvent' | 'failWorkflow' | 'handleCleanupError' | 'loadWorkflowStartHeaders' | 'loadWorkflowState' | 'runSerializedWorkflowStateWrite' | 'setWorkflowStartHeaders' | 'workflowVersionTupleFromState'>): Promise<void>;
|
|
30
30
|
export declare function handleTimerFired(internals: EngineInternals, entry: TimerEntry, callbacks: Pick<TimeOperationCallbacks, 'failWorkflow' | 'handleCleanupError' | 'loadWorkflowStartHeaders' | 'loadWorkflowState' | 'runDeferredTerminalCleanup' | 'runWorkflowFinalizer' | 'runSerializedWorkflowStateWrite' | 'handleScheduleTimer' | 'setWorkflowStartHeaders' | 'timeout' | 'beginWorkflowExecution' | 'dispatchEvent' | 'workflowVersionTupleFromState'>): Promise<void>;
|
|
31
31
|
export {};
|
|
@@ -16,6 +16,18 @@ export function createDelayedStartTimerEntry(_internals, workflowId, scheduledSt
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
function sleepTimerFiredEarly(internals, workflowId, operation) {
|
|
20
|
+
const workflowMarkers = internals.sleepTimersFiredWithoutResolver.get(workflowId);
|
|
21
|
+
if (!workflowMarkers)
|
|
22
|
+
return !1;
|
|
23
|
+
const markedFireAt = workflowMarkers.get(operation.operationId);
|
|
24
|
+
if (markedFireAt === void 0)
|
|
25
|
+
return !1;
|
|
26
|
+
workflowMarkers.delete(operation.operationId);
|
|
27
|
+
if (workflowMarkers.size === 0)
|
|
28
|
+
internals.sleepTimersFiredWithoutResolver.delete(workflowId);
|
|
29
|
+
return markedFireAt >= operation.scheduledFireAt;
|
|
30
|
+
}
|
|
19
31
|
export async function processSleepOperation(internals, workflowId, operation, callbacks) {
|
|
20
32
|
if (operation.scheduledFireAt <= internals.options.getNow()) {
|
|
21
33
|
callbacks.completeOperation(workflowId, void 0);
|
|
@@ -28,13 +40,24 @@ export async function processSleepOperation(internals, workflowId, operation, ca
|
|
|
28
40
|
fireAt: operation.scheduledFireAt,
|
|
29
41
|
kind: "sleep"
|
|
30
42
|
});
|
|
31
|
-
registerSleepResolver(internals, workflowId, operation.operationId, resolve);
|
|
43
|
+
registerSleepResolver(internals, workflowId, operation.operationId, resolve, operation.scheduledFireAt);
|
|
44
|
+
const resolverKey = `${workflowId}:${operation.operationId}`;
|
|
45
|
+
if (sleepTimerFiredEarly(internals, workflowId, operation) && internals.sleepResolvers.has(resolverKey))
|
|
46
|
+
resolveSleepTimer(internals, {
|
|
47
|
+
id: `sleep:${operation.operationId}`,
|
|
48
|
+
workflowId,
|
|
49
|
+
fireAt: operation.scheduledFireAt,
|
|
50
|
+
kind: "sleep"
|
|
51
|
+
});
|
|
32
52
|
await promise;
|
|
33
53
|
if ((await callbacks.loadWorkflowState(workflowId))?.status === "running")
|
|
34
54
|
callbacks.completeOperation(workflowId, void 0);
|
|
35
55
|
}
|
|
36
|
-
export function registerSleepResolver(internals, workflowId, operationId, resolve) {
|
|
37
|
-
internals.sleepResolvers.set(`${workflowId}:${operationId}`,
|
|
56
|
+
export function registerSleepResolver(internals, workflowId, operationId, resolve, scheduledFireAt) {
|
|
57
|
+
internals.sleepResolvers.set(`${workflowId}:${operationId}`, {
|
|
58
|
+
resolve,
|
|
59
|
+
fireAt: scheduledFireAt
|
|
60
|
+
});
|
|
38
61
|
let workflowOperations = internals.sleepResolversByWorkflow.get(workflowId);
|
|
39
62
|
if (!workflowOperations) {
|
|
40
63
|
workflowOperations = new Set;
|
|
@@ -94,7 +117,7 @@ export async function startDelayedWorkflow(internals, entry, callbacks) {
|
|
|
94
117
|
internals.checkpoints.set(entry.workflowId, checkpoint);
|
|
95
118
|
internals.workflowVersionTuples.set(entry.workflowId, callbacks.workflowVersionTupleFromState(runningState));
|
|
96
119
|
callbacks.setWorkflowStartHeaders(entry.workflowId, await callbacks.loadWorkflowStartHeaders(entry.workflowId));
|
|
97
|
-
callbacks.beginWorkflowExecution(entry.workflowId, runningState.type, runningState.input, checkpoint, executionDeadline, runningState.executionStateOwnerId ?? entry.workflowId, registration);
|
|
120
|
+
callbacks.beginWorkflowExecution(entry.workflowId, runningState.workflowExecutionToken, runningState.type, runningState.input, checkpoint, executionDeadline, runningState.executionStateOwnerId ?? entry.workflowId, registration);
|
|
98
121
|
}
|
|
99
122
|
async function loadDelayedWorkflowCheckpoint(internals, entry, callbacks) {
|
|
100
123
|
const checkpointBytes = await internals.storage.get(KEYS.checkpoint(entry.workflowId));
|
|
@@ -168,11 +191,22 @@ async function handleReviewTimer(internals, entry, callbacks) {
|
|
|
168
191
|
}
|
|
169
192
|
function resolveSleepTimer(internals, entry) {
|
|
170
193
|
const operationId = entry.id.replace("sleep:", ""), resolverKey = `${entry.workflowId}:${operationId}`, resolver = internals.sleepResolvers.get(resolverKey);
|
|
171
|
-
if (!resolver)
|
|
194
|
+
if (!resolver) {
|
|
195
|
+
let workflowMarkers = internals.sleepTimersFiredWithoutResolver.get(entry.workflowId);
|
|
196
|
+
if (!workflowMarkers) {
|
|
197
|
+
workflowMarkers = new Map;
|
|
198
|
+
internals.sleepTimersFiredWithoutResolver.set(entry.workflowId, workflowMarkers);
|
|
199
|
+
}
|
|
200
|
+
const existing = workflowMarkers.get(operationId);
|
|
201
|
+
if (existing === void 0 || entry.fireAt > existing)
|
|
202
|
+
workflowMarkers.set(operationId, entry.fireAt);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (entry.fireAt < resolver.fireAt)
|
|
172
206
|
return;
|
|
173
207
|
internals.sleepResolvers.delete(resolverKey);
|
|
174
208
|
untrackSleepResolver(internals, entry.workflowId, operationId);
|
|
175
|
-
resolver();
|
|
209
|
+
resolver.resolve();
|
|
176
210
|
}
|
|
177
211
|
function untrackSleepResolver(internals, workflowId, operationId) {
|
|
178
212
|
const workflowOperations = internals.sleepResolversByWorkflow.get(workflowId);
|
|
@@ -35,6 +35,9 @@ export async function schedule(internals, type, input, spec, options) {
|
|
|
35
35
|
id: scheduleId,
|
|
36
36
|
workflowType: type,
|
|
37
37
|
input,
|
|
38
|
+
...normalizedOptions.description !== void 0 && {
|
|
39
|
+
description: normalizedOptions.description
|
|
40
|
+
},
|
|
38
41
|
...cadenceFields,
|
|
39
42
|
status: "active",
|
|
40
43
|
overlap: normalizedOptions.overlap,
|