@lostgradient/weft 0.8.0 → 0.10.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 +9 -5
- package/dist/alerting/alert-manager.d.ts +3 -1
- package/dist/alerting/alert-manager.js +5 -2
- 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 +3 -1
- package/dist/core/context/index.js +10 -0
- package/dist/core/context/operation-request.d.ts +4 -0
- package/dist/core/context/parallel-cache-entry.d.ts +1 -1
- package/dist/core/context/parallel-cache-entry.js +12 -8
- package/dist/core/context/parallel-operations.d.ts +1 -1
- package/dist/core/context/parallel-operations.js +50 -8
- 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/async-activity-completion.d.ts +24 -54
- package/dist/core/engine/async-activity-completion.js +25 -90
- package/dist/core/engine/async-activity-records.d.ts +106 -0
- package/dist/core/engine/async-activity-records.js +124 -0
- package/dist/core/engine/bulk-operations-purge.js +2 -1
- package/dist/core/engine/bulk-operations.js +10 -29
- package/dist/core/engine/callback-creators-bundles.js +1 -1
- package/dist/core/engine/callback-creators-core.js +1 -0
- package/dist/core/engine/construction.d.ts +1 -3
- package/dist/core/engine/construction.js +2 -5
- package/dist/core/engine/deferred-consume-envelope.d.ts +10 -1
- package/dist/core/engine/deferred-consume-envelope.js +9 -1
- package/dist/core/engine/disposal.js +1 -0
- package/dist/core/engine/engine-internal-types.d.ts +2 -0
- package/dist/core/engine/engine-runtime-helpers.d.ts +8 -0
- package/dist/core/engine/engine-runtime-helpers.js +9 -0
- package/dist/core/engine/errors.js +1 -1
- package/dist/core/engine/index.d.ts +12 -2
- package/dist/core/engine/index.js +35 -16
- package/dist/core/engine/inline-launch-queue.js +1 -1
- package/dist/core/engine/internals.d.ts +25 -3
- package/dist/core/engine/lifecycle/fork-helpers.js +1 -0
- package/dist/core/engine/lifecycle/recovered-services.d.ts +4 -2
- package/dist/core/engine/lifecycle/recovered-services.js +37 -7
- package/dist/core/engine/lifecycle/resume.d.ts +2 -2
- package/dist/core/engine/lifecycle/resume.js +45 -7
- package/dist/core/engine/lifecycle/shared.d.ts +34 -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.d.ts +1 -1
- package/dist/core/engine/lifecycle/transition.js +9 -3
- package/dist/core/engine/lifecycle.d.ts +1 -1
- package/dist/core/engine/memo-durable-activity.js +8 -10
- package/dist/core/engine/operations-activity.d.ts +1 -1
- package/dist/core/engine/operations-activity.js +8 -6
- package/dist/core/engine/operations-coordination.d.ts +15 -1
- package/dist/core/engine/operations-coordination.js +43 -10
- package/dist/core/engine/operations-time.d.ts +2 -2
- package/dist/core/engine/operations-time.js +40 -6
- package/dist/core/engine/ownership-options.d.ts +1 -0
- package/dist/core/engine/ownership-options.js +14 -0
- package/dist/core/engine/retention.js +4 -0
- package/dist/core/engine/schedule-run-metadata.d.ts +3 -0
- package/dist/core/engine/schedule-run-metadata.js +29 -0
- package/dist/core/engine/schedule-run.js +19 -4
- package/dist/core/engine/schedules.js +7 -3
- package/dist/core/engine/sub-operation.js +6 -13
- package/dist/core/engine/termination/cleanup.js +18 -14
- 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/options.d.ts +12 -0
- package/dist/core/types/schedules.d.ts +10 -0
- package/dist/core/types/services-resolution.d.ts +8 -3
- package/dist/core/types/state.d.ts +6 -0
- package/dist/core/types/workflow-builder.d.ts +2 -2
- package/dist/core/types/workflow-context.d.ts +40 -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/index.d.ts +2 -2
- 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/authorization.d.ts +3 -3
- package/dist/server/fault-to-json-rpc.d.ts +2 -1
- package/dist/server/handler.js +28 -28
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +30 -30
- package/dist/server/json-rpc-dispatch.d.ts +2 -2
- package/dist/server/json-rpc-parse.d.ts +1 -1
- package/dist/server/json-rpc-protocol.d.ts +2 -2
- package/dist/server/json-rpc-websocket.d.ts +1 -1
- package/dist/server/operation-fault.d.ts +3 -1
- package/dist/server/operations/async-activity.js +2 -2
- package/dist/server/operations/create-schedule.d.ts +2 -0
- package/dist/server/operations/create-schedule.js +13 -2
- package/dist/server/principal.d.ts +2 -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/stdio-session.d.ts +4 -4
- 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/http.js +2 -2
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/indexeddb.d.ts +6 -1
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +1 -0
- 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 +2 -2
|
@@ -18,10 +18,10 @@ import {
|
|
|
18
18
|
import { getActivityFunctionWithMetadata, resolveActivityFunction } from "./activity-resolution.js";
|
|
19
19
|
import {
|
|
20
20
|
AsyncActivityDeferral,
|
|
21
|
-
deriveAsyncActivityToken,
|
|
22
21
|
driveWorkflowInterceptorGenerator,
|
|
23
22
|
parkDeferredAsyncActivity
|
|
24
23
|
} from "./async-activity-completion.js";
|
|
24
|
+
import { deriveAsyncActivityToken } from "./async-activity-records.js";
|
|
25
25
|
import { callActivityFunction } from "./state-utilities.js";
|
|
26
26
|
export function buildActivityVerification(_internals, activityName, verify, context, result) {
|
|
27
27
|
return (async () => {
|
|
@@ -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 = {
|
|
@@ -33,7 +33,21 @@ export type CoordinationOperationCallbacks = {
|
|
|
33
33
|
};
|
|
34
34
|
export declare function processWaitSignalOperation(internals: EngineInternals, workflowId: string, operation: WaitSignalOperation, callbacks: Pick<CoordinationOperationCallbacks, 'completeOperation'>): Promise<void>;
|
|
35
35
|
export declare function processParallelOperation(internals: EngineInternals, workflowId: string, operation: ParallelOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
|
|
36
|
-
export declare function processRaceOperation(
|
|
36
|
+
export declare function processRaceOperation(internals: EngineInternals, workflowId: string, operation: RaceOperation, callbacks: Pick<CoordinationOperationCallbacks, 'executeSubOperation' | 'runOperationWithResult'>): Promise<void>;
|
|
37
|
+
export declare function assertValidRaceBranchNames(operation: RaceOperation): void;
|
|
38
|
+
export declare function wrapRaceWinner(operation: RaceOperation, winnerIndex: number, value: unknown): unknown;
|
|
39
|
+
/**
|
|
40
|
+
* Execute race branches while giving an already-buffered signal priority over a
|
|
41
|
+
* literal zero-duration sleep. The preflight is non-destructive: the selected
|
|
42
|
+
* wait-signal branch still returns its deferred-consume envelope, so the owning
|
|
43
|
+
* coordinator remains the only place that consumes the durable record.
|
|
44
|
+
*
|
|
45
|
+
* Positive-duration sleeps deliberately bypass this preflight, including when
|
|
46
|
+
* their deadline becomes past-due before dispatch. That keeps ordinary timeout
|
|
47
|
+
* races on the existing Promise.race path and limits the stronger ordering
|
|
48
|
+
* guarantee to the explicit non-blocking-drain idiom.
|
|
49
|
+
*/
|
|
50
|
+
export declare function executeRaceSubOperations(internals: EngineInternals, workflowId: string, operations: readonly ContextOperationRequest[], execute: (operation: ContextOperationRequest, signal: AbortSignal) => Promise<unknown>, parentSignal?: AbortSignal, wrapWinner?: (winnerIndex: number, value: unknown) => unknown): Promise<unknown>;
|
|
37
51
|
export declare function processRunAllOperation(internals: EngineInternals, workflowId: string, operation: RunAllOperation, callbacks: Pick<CoordinationOperationCallbacks, 'runOperationWithResult'>): Promise<void>;
|
|
38
52
|
export declare function isConfiguredInlineActivity(fn: Function): fn is RunAllOperation['branches'][string][0] & ActivityFunctionWithMetadata;
|
|
39
53
|
/**
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
executeRunAllBranches,
|
|
6
6
|
executeRunAllBranchesSettled
|
|
7
7
|
} from "../engine-helpers.js";
|
|
8
|
-
import { finalizeAndUnwrap } from "./deferred-consume-envelope.js";
|
|
8
|
+
import { createKeyedRaceResultEnvelope, finalizeAndUnwrap } from "./deferred-consume-envelope.js";
|
|
9
9
|
import {
|
|
10
10
|
executeActivityOperationResult as executeActivityOperationResultFromInternals
|
|
11
11
|
} from "./operations-activity.js";
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "./parallel-dispatch.js";
|
|
17
17
|
import {
|
|
18
18
|
consumeSignalWithAtomicWorkflowCommit,
|
|
19
|
+
peekSignal,
|
|
19
20
|
trackWaiterKey,
|
|
20
21
|
untrackWaiterKey
|
|
21
22
|
} from "./signals.js";
|
|
@@ -106,20 +107,52 @@ function assertPartialFailurePersistenceSupported(partialEntryWritten, slots, op
|
|
|
106
107
|
return;
|
|
107
108
|
throw Error(`${operationName} partial-failure preservation is not supported in ${executionMode}: the engine cannot persist fulfilled branch slots after a sibling branch fails. Run the workflow inline or make branch side effects idempotent.`);
|
|
108
109
|
}
|
|
109
|
-
export async function processRaceOperation(
|
|
110
|
+
export async function processRaceOperation(internals, workflowId, operation, callbacks) {
|
|
110
111
|
return callbacks.runOperationWithResult(workflowId, operation, async () => {
|
|
111
112
|
assertSupportedSignalBranches(operation.operations);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
let winner;
|
|
115
|
-
try {
|
|
116
|
-
winner = await Promise.race(subOperations);
|
|
117
|
-
} finally {
|
|
118
|
-
controller.abort();
|
|
119
|
-
}
|
|
113
|
+
assertValidRaceBranchNames(operation);
|
|
114
|
+
const winner = await executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, signal) => callbacks.executeSubOperation(workflowId, subOperation, signal), void 0, (winnerIndex, value) => wrapRaceWinner(operation, winnerIndex, value));
|
|
120
115
|
return finalizeAndUnwrap(winner);
|
|
121
116
|
});
|
|
122
117
|
}
|
|
118
|
+
export function assertValidRaceBranchNames(operation) {
|
|
119
|
+
if (operation.branchNames !== void 0 && operation.branchNames.length !== operation.operations.length)
|
|
120
|
+
throw Error("ctx.raceKeyed branch names must match its operation count");
|
|
121
|
+
}
|
|
122
|
+
export function wrapRaceWinner(operation, winnerIndex, value) {
|
|
123
|
+
const key = operation.branchNames?.[winnerIndex];
|
|
124
|
+
return key === void 0 ? value : createKeyedRaceResultEnvelope(key, value);
|
|
125
|
+
}
|
|
126
|
+
export async function executeRaceSubOperations(internals, workflowId, operations, execute, parentSignal, wrapWinner) {
|
|
127
|
+
parentSignal?.throwIfAborted();
|
|
128
|
+
const controller = new AbortController, abortFromParent = () => controller.abort(parentSignal?.reason);
|
|
129
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: !0 });
|
|
130
|
+
try {
|
|
131
|
+
const bufferedSignal = await findBufferedSignalDrainBranch(internals, workflowId, operations);
|
|
132
|
+
parentSignal?.throwIfAborted();
|
|
133
|
+
if (bufferedSignal !== void 0) {
|
|
134
|
+
const value = await execute(bufferedSignal.operation, controller.signal);
|
|
135
|
+
return wrapWinner === void 0 ? value : wrapWinner(bufferedSignal.index, value);
|
|
136
|
+
}
|
|
137
|
+
const subOperations = operations.map((operation, index) => execute(operation, controller.signal).then((value) => ({ index, value })));
|
|
138
|
+
Promise.allSettled(subOperations);
|
|
139
|
+
const winner = await Promise.race(subOperations);
|
|
140
|
+
return wrapWinner === void 0 ? winner.value : wrapWinner(winner.index, winner.value);
|
|
141
|
+
} finally {
|
|
142
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
143
|
+
controller.abort();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function findBufferedSignalDrainBranch(internals, workflowId, operations) {
|
|
147
|
+
if (operations.length !== 2)
|
|
148
|
+
return;
|
|
149
|
+
const waitSignalOperation = operations.find((operation) => operation.type === "wait-signal"), zeroDurationSleep = operations.find((operation) => operation.type === "sleep" && operation.duration === 0);
|
|
150
|
+
if (waitSignalOperation === void 0 || zeroDurationSleep === void 0)
|
|
151
|
+
return;
|
|
152
|
+
if (!(await peekSignal(internals, workflowId, waitSignalOperation.signalName)).found)
|
|
153
|
+
return;
|
|
154
|
+
return { index: operations.indexOf(waitSignalOperation), operation: waitSignalOperation };
|
|
155
|
+
}
|
|
123
156
|
export async function processRunAllOperation(internals, workflowId, operation, callbacks) {
|
|
124
157
|
return callbacks.runOperationWithResult(workflowId, operation, async () => {
|
|
125
158
|
const branchNames = Object.keys(operation.branches), operationIds = branchNames.map((name) => `run-all:${operation.step}:${name}`), resumedSlots = extractResumedSlots(operation.resumedCacheEntry), resumedSlotsByName = mapResumedSlotsByName(resumedSlots, branchNames), branchesToRun = filterBranchesToRun(operation.branches, branchNames, resumedSlotsByName), { outcomes, hasFirstError, firstError } = await executeRunAllBranchesSettled(branchesToRun, (fn, input) => callActivityFunction(fn, input)), slots = mergeRunAllSlots(branchNames, operationIds, resumedSlotsByName, outcomes), entry = buildEntryFromSlots("run-all", slots, branchNames), partialEntryWritten = writePartialEntry(internals, workflowId, operation.step, entry);
|
|
@@ -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);
|
|
@@ -13,6 +13,7 @@ export declare const DEFAULT_LEASE_TTL_MS = 30000;
|
|
|
13
13
|
export declare const DEFAULT_LEASE_RENEW_INTERVAL_MS = 5000;
|
|
14
14
|
/** Default boot-time lease acquisition wait window for `ownership: 'lease'`. */
|
|
15
15
|
export declare const DEFAULT_LEASE_WAIT_TIMEOUT_MS = 60000;
|
|
16
|
+
export declare function resolveBackgroundTaskMode(options: EngineConstructorOptions | undefined): ResolvedOptions['backgroundTaskMode'];
|
|
16
17
|
/**
|
|
17
18
|
* Resolve the ownership posture and lease tuning into their `ResolvedOptions`
|
|
18
19
|
* fields. Defaults to `'none'`. The lease durations are documented as "ignored
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { normalizeRetentionDuration } from "./validation.js";
|
|
2
2
|
export const DEFAULT_LEASE_TTL_MS = 30000, DEFAULT_LEASE_RENEW_INTERVAL_MS = 5000, DEFAULT_LEASE_WAIT_TIMEOUT_MS = 60000;
|
|
3
|
+
export function resolveBackgroundTaskMode(options) {
|
|
4
|
+
const mode = options?.backgroundTasks ?? "automatic";
|
|
5
|
+
if (mode !== "automatic" && mode !== "manual")
|
|
6
|
+
throw Error('options.backgroundTasks must be "automatic" or "manual" when provided');
|
|
7
|
+
if (mode === "manual")
|
|
8
|
+
assertManualBackgroundTaskCompatibility(options);
|
|
9
|
+
return mode;
|
|
10
|
+
}
|
|
11
|
+
function assertManualBackgroundTaskCompatibility(options) {
|
|
12
|
+
if (options?.detectSecondInstance === !0)
|
|
13
|
+
throw Error('detectSecondInstance cannot be enabled when backgroundTasks is "manual"');
|
|
14
|
+
if (options?.ownership === "lease")
|
|
15
|
+
throw Error('ownership cannot be "lease" when backgroundTasks is "manual"');
|
|
16
|
+
}
|
|
3
17
|
export function resolveOwnershipFields(options) {
|
|
4
18
|
const ownershipMode = assertKnownOwnershipMode(options?.ownership ?? "none");
|
|
5
19
|
if (ownershipMode !== "lease")
|
|
@@ -11,6 +11,10 @@ export function setNextRetentionSweepAt(internals) {
|
|
|
11
11
|
internals.nextRetentionSweepAt = internals.options.getNow() + internals.options.retentionSweepIntervalMs;
|
|
12
12
|
}
|
|
13
13
|
export function ensureRetentionSweepInterval(internals, callbacks) {
|
|
14
|
+
if (internals.options.backgroundTaskMode === "manual") {
|
|
15
|
+
internals.nextRetentionSweepAt = null;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
14
18
|
if (!callbacks.hasConfiguredRetention()) {
|
|
15
19
|
if (internals.retentionSweepInterval !== null) {
|
|
16
20
|
clearInterval(internals.retentionSweepInterval ?? void 0);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WorkflowServicesResolverScheduleInfo } from '../types.ts';
|
|
2
|
+
export declare function encodeScheduleRunMetadata(scheduleId: string, occurrence: number | undefined): Uint8Array;
|
|
3
|
+
export declare function decodeScheduleRunMetadata(bytes: Uint8Array): WorkflowServicesResolverScheduleInfo | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { decode, encode } from "../codec.js";
|
|
2
|
+
import { isRecord } from "../debug-output.js";
|
|
3
|
+
import { isValidScheduleIdentifier } from "./validation/schedule.js";
|
|
4
|
+
export function encodeScheduleRunMetadata(scheduleId, occurrence) {
|
|
5
|
+
return encode({
|
|
6
|
+
id: scheduleId,
|
|
7
|
+
...occurrence !== void 0 ? { occurrence } : {}
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function decodeScheduleRunMetadata(bytes) {
|
|
11
|
+
let decoded;
|
|
12
|
+
try {
|
|
13
|
+
decoded = decode(bytes);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (typeof decoded === "string")
|
|
18
|
+
return isValidScheduleIdentifier(decoded) ? { id: decoded } : null;
|
|
19
|
+
if (!isRecord(decoded))
|
|
20
|
+
return null;
|
|
21
|
+
const { id, occurrence } = decoded;
|
|
22
|
+
if (!isValidScheduleIdentifier(id))
|
|
23
|
+
return null;
|
|
24
|
+
if (occurrence === void 0)
|
|
25
|
+
return { id };
|
|
26
|
+
if (typeof occurrence !== "number" || !Number.isSafeInteger(occurrence))
|
|
27
|
+
return null;
|
|
28
|
+
return { id, occurrence };
|
|
29
|
+
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { KEYS } from "../../storage/interface.js";
|
|
2
|
-
import { encode } from "../codec.js";
|
|
3
2
|
import { ScheduleFiredEvent } from "../events.js";
|
|
4
3
|
import { unavailableServicesError } from "./lifecycle/recovered-services.js";
|
|
5
4
|
import { EMPTY_STORAGE_VALUE } from "./lifecycle/shared.js";
|
|
5
|
+
import { encodeScheduleRunMetadata } from "./schedule-run-metadata.js";
|
|
6
6
|
export async function startScheduledRun(internals, state, callbacks, occurrence) {
|
|
7
|
-
const workflowId = crypto.randomUUID(), scheduleRunOperations =
|
|
7
|
+
const workflowId = crypto.randomUUID(), scheduleRunOperations = [
|
|
8
|
+
{
|
|
9
|
+
type: "put",
|
|
10
|
+
key: KEYS.scheduleRun(workflowId),
|
|
11
|
+
value: encodeScheduleRunMetadata(state.id, occurrence)
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: "put",
|
|
15
|
+
key: KEYS.terminalCleanupNeeded(workflowId),
|
|
16
|
+
value: EMPTY_STORAGE_VALUE
|
|
17
|
+
}
|
|
18
|
+
], resolution = await resolveScheduledRunServices(internals, workflowId, state, occurrence);
|
|
8
19
|
if (resolution !== null) {
|
|
9
|
-
scheduleRunOperations.push({
|
|
10
|
-
|
|
20
|
+
scheduleRunOperations.push({
|
|
21
|
+
type: "put",
|
|
22
|
+
key: KEYS.workflowHasServices(workflowId),
|
|
23
|
+
value: EMPTY_STORAGE_VALUE
|
|
24
|
+
});
|
|
11
25
|
if (resolution.status === "available")
|
|
12
26
|
internals.workflowServices.set(workflowId, resolution.services);
|
|
13
27
|
}
|
|
28
|
+
internals.workflowsNeedingTerminalCleanup.add(workflowId);
|
|
14
29
|
await callbacks.startWorkflow(state.workflowType, state.input, { id: workflowId }, scheduleRunOperations);
|
|
15
30
|
internals.engine.dispatchEvent(new ScheduleFiredEvent(state.id, workflowId, internals.options.getNow(), occurrence));
|
|
16
31
|
if (resolution !== null && resolution.status === "unavailable") {
|
|
@@ -4,6 +4,7 @@ import { WorkflowNotRegisteredError } from "./errors.js";
|
|
|
4
4
|
import { ScheduleHandle } from "./schedule-handle.js";
|
|
5
5
|
import { resolveEffectiveScheduleFireAt } from "./schedule-jitter.js";
|
|
6
6
|
import { getNextScheduleOccurrence } from "./schedule-occurrence.js";
|
|
7
|
+
import { decodeScheduleRunMetadata } from "./schedule-run-metadata.js";
|
|
7
8
|
import {
|
|
8
9
|
clearScheduleCurrentWorkflow,
|
|
9
10
|
createScheduleTimerId,
|
|
@@ -35,6 +36,9 @@ export async function schedule(internals, type, input, spec, options) {
|
|
|
35
36
|
id: scheduleId,
|
|
36
37
|
workflowType: type,
|
|
37
38
|
input,
|
|
39
|
+
...normalizedOptions.description !== void 0 && {
|
|
40
|
+
description: normalizedOptions.description
|
|
41
|
+
},
|
|
38
42
|
...cadenceFields,
|
|
39
43
|
status: "active",
|
|
40
44
|
overlap: normalizedOptions.overlap,
|
|
@@ -205,10 +209,10 @@ export async function handleScheduledWorkflowTerminal(internals, workflowId, cal
|
|
|
205
209
|
if (!scheduleRunBytes)
|
|
206
210
|
return;
|
|
207
211
|
await internals.storage.delete(KEYS.scheduleRun(workflowId));
|
|
208
|
-
const
|
|
209
|
-
if (!isValidScheduleIdentifier(
|
|
212
|
+
const metadata = decodeScheduleRunMetadata(scheduleRunBytes);
|
|
213
|
+
if (metadata === null || !isValidScheduleIdentifier(metadata.id))
|
|
210
214
|
return;
|
|
211
|
-
const state = await loadScheduleState(internals,
|
|
215
|
+
const scheduleId = metadata.id, state = await loadScheduleState(internals, scheduleId);
|
|
212
216
|
if (!state || state.currentWorkflowId !== workflowId)
|
|
213
217
|
return;
|
|
214
218
|
const now = internals.options.getNow(), clearedState = {
|
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
executeActivityOperationResult
|
|
17
17
|
} from "./operations-activity.js";
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
assertValidRaceBranchNames,
|
|
20
|
+
executeRaceSubOperations,
|
|
21
|
+
executeRunAllOperationResult,
|
|
22
|
+
wrapRaceWinner
|
|
20
23
|
} from "./operations-coordination.js";
|
|
21
24
|
import { callMemoFunction } from "./state-utilities.js";
|
|
22
25
|
export async function processWaitReviewOperation(_internals, workflowId, operation, callbacks) {
|
|
@@ -82,16 +85,6 @@ async function executeParallelSubOperation(internals, workflowId, operation, cal
|
|
|
82
85
|
}
|
|
83
86
|
async function executeRaceSubOperation(internals, workflowId, operation, callbacks, signal, speculativeState) {
|
|
84
87
|
signal?.throwIfAborted();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
signal?.addEventListener("abort", abortNestedRace, { once: !0 });
|
|
89
|
-
const subOperations = operation.operations.map((subOperation) => executeSubOperation(internals, workflowId, subOperation, callbacks, controller.signal, speculativeState));
|
|
90
|
-
Promise.allSettled(subOperations);
|
|
91
|
-
try {
|
|
92
|
-
return await Promise.race(subOperations);
|
|
93
|
-
} finally {
|
|
94
|
-
signal?.removeEventListener("abort", abortNestedRace);
|
|
95
|
-
controller.abort();
|
|
96
|
-
}
|
|
88
|
+
assertValidRaceBranchNames(operation);
|
|
89
|
+
return executeRaceSubOperations(internals, workflowId, operation.operations, (subOperation, branchSignal) => executeSubOperation(internals, workflowId, subOperation, callbacks, branchSignal, speculativeState), signal, (winnerIndex, value) => wrapRaceWinner(operation, winnerIndex, value));
|
|
97
90
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KEYS, encodeStorageKeyComponent, storageHas } from "../../../storage/interface.js";
|
|
2
2
|
import { CleanupWarningEvent } from "../../events.js";
|
|
3
|
-
import { asyncActivityWorkflowPrefix } from "../async-activity-
|
|
3
|
+
import { asyncActivityWorkflowPrefix } from "../async-activity-records.js";
|
|
4
4
|
import { forgetCommittedCheckpointBytes } from "../checkpoint-commit-snapshots.js";
|
|
5
5
|
import { parseTerminalCleanupTimerId, workflowFeedListenerKey } from "../state-utilities.js";
|
|
6
6
|
import { releaseWorkflowConcurrencySlot } from "../workflow-concurrency.js";
|
|
@@ -43,23 +43,25 @@ function cleanupTrackedWaiter(internals, workflowId, kind) {
|
|
|
43
43
|
}
|
|
44
44
|
function cleanupSleepResolvers(internals, workflowId) {
|
|
45
45
|
const sleepOps = internals.sleepResolversByWorkflow.get(workflowId);
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
if (sleepOps) {
|
|
47
|
+
for (const operationId of sleepOps) {
|
|
48
|
+
const key = `${workflowId}:${operationId}`, resolver = internals.sleepResolvers.get(key);
|
|
49
|
+
if (resolver)
|
|
50
|
+
resolver.resolve();
|
|
51
|
+
internals.sleepResolvers.delete(key);
|
|
52
|
+
}
|
|
53
|
+
internals.sleepResolversByWorkflow.delete(workflowId);
|
|
53
54
|
}
|
|
54
|
-
internals.
|
|
55
|
+
internals.sleepTimersFiredWithoutResolver.delete(workflowId);
|
|
55
56
|
}
|
|
56
57
|
function evictSleepResolversWithoutResolving(internals, workflowId) {
|
|
57
58
|
const sleepOps = internals.sleepResolversByWorkflow.get(workflowId);
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
internals.
|
|
62
|
-
|
|
59
|
+
if (sleepOps) {
|
|
60
|
+
for (const operationId of sleepOps)
|
|
61
|
+
internals.sleepResolvers.delete(`${workflowId}:${operationId}`);
|
|
62
|
+
internals.sleepResolversByWorkflow.delete(workflowId);
|
|
63
|
+
}
|
|
64
|
+
internals.sleepTimersFiredWithoutResolver.delete(workflowId);
|
|
63
65
|
}
|
|
64
66
|
function cleanupConditionWaiters(internals, workflowId) {
|
|
65
67
|
const resolver = internals.conditionWaiters.get(workflowId);
|
|
@@ -192,7 +194,9 @@ export async function runDeferredTerminalCleanup(internals, workflowId, timerId,
|
|
|
192
194
|
if (state.terminalCleanupToken !== parsedTimer.terminalCleanupToken)
|
|
193
195
|
return;
|
|
194
196
|
try {
|
|
197
|
+
await finalizeScheduledWorkflowTerminal(internals, workflowId, callbacks);
|
|
195
198
|
await cleanupTerminalWorkflowDurableState(internals, workflowId, parsedTimer.includeOutputArtifacts, callbacks);
|
|
199
|
+
await internals.storage.delete(KEYS.scheduleRun(workflowId));
|
|
196
200
|
} catch (error) {
|
|
197
201
|
callbacks.handleCleanupError("cleanupTerminalWorkflowDurableState", error, workflowId);
|
|
198
202
|
throw error;
|
|
@@ -50,4 +50,4 @@ export type FinalizerAttemptResult = {
|
|
|
50
50
|
* @param attempt - 1-based attempt number, for the per-attempt timeout error message
|
|
51
51
|
* @param shutdownSignal - the engine's dispose/shutdown abort signal; aborting it stops a cooperating finalizer
|
|
52
52
|
*/
|
|
53
|
-
export declare function runFinalizerActivity(finalizer: RunnableFinalizer, input: unknown, attempt: number, shutdownSignal: AbortSignal): Promise<FinalizerAttemptResult>;
|
|
53
|
+
export declare function runFinalizerActivity(finalizer: RunnableFinalizer, input: unknown, attempt: number, shutdownSignal: AbortSignal, workflowExecutionToken?: string): Promise<FinalizerAttemptResult>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseDuration } from "../../scheduler.js";
|
|
2
2
|
import { withPerAttemptTimeout } from "../activity-per-attempt-timeout.js";
|
|
3
|
-
export async function runFinalizerActivity(finalizer, input, attempt, shutdownSignal) {
|
|
3
|
+
export async function runFinalizerActivity(finalizer, input, attempt, shutdownSignal, workflowExecutionToken) {
|
|
4
4
|
const perAttemptTimeoutMs = finalizer.timeout === void 0 ? void 0 : parseDuration(finalizer.timeout), attemptController = new AbortController, relayShutdownAbort = () => attemptController.abort(shutdownSignal.reason);
|
|
5
5
|
if (shutdownSignal.aborted)
|
|
6
6
|
attemptController.abort(shutdownSignal.reason);
|
|
@@ -8,6 +8,10 @@ export async function runFinalizerActivity(finalizer, input, attempt, shutdownSi
|
|
|
8
8
|
shutdownSignal.addEventListener("abort", relayShutdownAbort, { once: !0 });
|
|
9
9
|
const activityContext = {
|
|
10
10
|
signal: attemptController.signal,
|
|
11
|
+
...workflowExecutionToken !== void 0 && {
|
|
12
|
+
workflowExecutionToken,
|
|
13
|
+
activityAttemptToken: `${workflowExecutionToken}:finalizer:${String(attempt)}`
|
|
14
|
+
},
|
|
11
15
|
heartbeat: () => {},
|
|
12
16
|
completeAsync: () => {
|
|
13
17
|
throw Error(`Finalizer "${finalizer.name}" called ctx.completeAsync(), which is not supported in a workflow finalizer: the finalizer runs after the workflow is already terminal, so there is no durable step to complete out of band. Run the teardown synchronously instead.`);
|
|
@@ -103,7 +103,7 @@ async function driveResolvedTeardown(internals, workflowId, token, drive, callba
|
|
|
103
103
|
await rearmTeardownTimer(internals, workflowId, token, TEARDOWN_SELF_HEAL_DELAY_MS);
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
|
-
const result = await runFinalizerActivity(finalizer, finalizerInput, attempt, internals.abortController.signal);
|
|
106
|
+
const result = await runFinalizerActivity(finalizer, finalizerInput, attempt, internals.abortController.signal, state.workflowExecutionToken);
|
|
107
107
|
if (result.ok) {
|
|
108
108
|
await settleTeardownSuccess(internals, workflowId, state.type, token, attempt, runningBytes, callbacks);
|
|
109
109
|
return;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScheduleOptions, ScheduleOverlapPolicy } from '../../types.ts';
|
|
2
|
+
export declare const SCHEDULE_OVERLAP_POLICIES: Set<ScheduleOverlapPolicy>;
|
|
3
|
+
type NormalizedScheduleOptions = Required<Pick<ScheduleOptions, 'overlap' | 'backfill'>> & {
|
|
4
|
+
id?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
jitterMs?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function normalizeScheduleOptions(options: ScheduleOptions | undefined): NormalizedScheduleOptions;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { parseDuration } from "../../scheduler.js";
|
|
2
|
+
import { coerceStartWorkflowId } from "../../start-workflow-validation.js";
|
|
3
|
+
export const SCHEDULE_OVERLAP_POLICIES = new Set([
|
|
4
|
+
"skip",
|
|
5
|
+
"queue",
|
|
6
|
+
"cancel-running",
|
|
7
|
+
"allow"
|
|
8
|
+
]);
|
|
9
|
+
export function normalizeScheduleOptions(options) {
|
|
10
|
+
if (options === void 0)
|
|
11
|
+
return { overlap: "skip", backfill: !1 };
|
|
12
|
+
if (typeof options !== "object" || options === null)
|
|
13
|
+
throw Error("options must be an object when provided");
|
|
14
|
+
return {
|
|
15
|
+
overlap: normalizeScheduleOverlap(options.overlap),
|
|
16
|
+
backfill: normalizeScheduleBackfill(options.backfill),
|
|
17
|
+
...options.id !== void 0 && { id: coerceStartWorkflowId(options.id, "options.id") },
|
|
18
|
+
...options.description !== void 0 && {
|
|
19
|
+
description: normalizeScheduleDescription(options.description)
|
|
20
|
+
},
|
|
21
|
+
...options.jitter !== void 0 && {
|
|
22
|
+
jitterMs: normalizeScheduleJitter(options.jitter, "options.jitter")
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function normalizeScheduleDescription(description) {
|
|
27
|
+
if (typeof description !== "string")
|
|
28
|
+
throw Error("options.description must be a string when provided");
|
|
29
|
+
return description;
|
|
30
|
+
}
|
|
31
|
+
function normalizeScheduleOverlap(overlap) {
|
|
32
|
+
if (overlap === void 0)
|
|
33
|
+
return "skip";
|
|
34
|
+
if (!SCHEDULE_OVERLAP_POLICIES.has(overlap))
|
|
35
|
+
throw Error(`options.overlap must be one of ${[...SCHEDULE_OVERLAP_POLICIES].join(", ")}`);
|
|
36
|
+
return overlap;
|
|
37
|
+
}
|
|
38
|
+
function normalizeScheduleBackfill(backfill) {
|
|
39
|
+
if (backfill === void 0)
|
|
40
|
+
return !1;
|
|
41
|
+
if (typeof backfill !== "boolean")
|
|
42
|
+
throw Error("options.backfill must be a boolean when provided");
|
|
43
|
+
return backfill;
|
|
44
|
+
}
|
|
45
|
+
function normalizeScheduleJitter(value, fieldName) {
|
|
46
|
+
if (typeof value !== "string" && typeof value !== "number")
|
|
47
|
+
throw Error(`${fieldName} must be a duration string or a number of milliseconds`);
|
|
48
|
+
let milliseconds;
|
|
49
|
+
try {
|
|
50
|
+
milliseconds = parseDuration(value);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
throw Error(`Invalid ${fieldName}: ${message}`, { cause: error });
|
|
54
|
+
}
|
|
55
|
+
const jitterMs = Math.ceil(milliseconds);
|
|
56
|
+
if (!Number.isSafeInteger(jitterMs) || jitterMs <= 0)
|
|
57
|
+
throw Error(`${fieldName} must resolve to a positive number of milliseconds`);
|
|
58
|
+
return jitterMs;
|
|
59
|
+
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import type { ScheduleFilter,
|
|
1
|
+
import type { ScheduleFilter, ScheduleOverlapPolicy, ScheduleSpec, ScheduleState, ScheduleStatus } from '../../types.ts';
|
|
2
|
+
export { normalizeScheduleOptions, SCHEDULE_OVERLAP_POLICIES } from './schedule-options.ts';
|
|
2
3
|
export declare const SCHEDULE_STATUSES: Set<ScheduleStatus>;
|
|
3
|
-
export declare const SCHEDULE_OVERLAP_POLICIES: Set<ScheduleOverlapPolicy>;
|
|
4
4
|
export declare function isValidScheduleTimestamp(value: unknown): value is number;
|
|
5
5
|
export declare function isValidScheduleStatus(value: unknown): value is ScheduleStatus;
|
|
6
6
|
export declare function isValidScheduleOverlapPolicy(value: unknown): value is ScheduleOverlapPolicy;
|
|
7
7
|
export declare function isValidScheduleIdentifier(value: unknown): value is string;
|
|
8
8
|
export declare function coerceScheduleId(scheduleId: string, fieldName: string): string;
|
|
9
|
-
export declare function normalizeScheduleOptions(options: ScheduleOptions | undefined): Required<Pick<ScheduleOptions, 'overlap' | 'backfill'>> & {
|
|
10
|
-
id?: string;
|
|
11
|
-
jitterMs?: number;
|
|
12
|
-
};
|
|
13
9
|
/**
|
|
14
10
|
* A recurrence specification normalized into the discriminated cadence the
|
|
15
11
|
* engine persists. Interval periods are resolved to whole milliseconds.
|
|
@@ -33,7 +29,6 @@ export declare function decodeScheduleIdentityFields(decoded: Record<string, unk
|
|
|
33
29
|
cronExpression?: string;
|
|
34
30
|
intervalMs?: number;
|
|
35
31
|
}) | null;
|
|
36
|
-
type ScheduleRuntimeFields = Pick<ScheduleState, 'backfill' | 'jitterMs' | 'createdAt' | 'updatedAt' | 'lastFireAt' | 'lastMissedFireAt' | 'nextFireAt' | 'currentWorkflowId' | 'missedFireCount' | 'queuedRuns'>;
|
|
32
|
+
type ScheduleRuntimeFields = Pick<ScheduleState, 'description' | 'backfill' | 'jitterMs' | 'createdAt' | 'updatedAt' | 'lastFireAt' | 'lastMissedFireAt' | 'nextFireAt' | 'currentWorkflowId' | 'missedFireCount' | 'queuedRuns'>;
|
|
37
33
|
export declare function decodeScheduleRuntimeFields(decoded: Record<string, unknown>, scheduleId: string): ScheduleRuntimeFields | null;
|
|
38
34
|
export declare function decodeScheduleState(bytes: Uint8Array): ScheduleState | null;
|
|
39
|
-
export {};
|