@lostgradient/weft 0.7.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 +9 -3
- package/dist/cli/generated/operation-client.generated.d.ts +1 -0
- package/dist/cli-main.js +78 -78
- package/dist/core/context/activity-retry-state.d.ts +6 -5
- package/dist/core/context/activity-retry-state.js +31 -21
- package/dist/core/context/durable-activity.d.ts +117 -0
- package/dist/core/context/durable-activity.js +79 -0
- 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 +17 -0
- package/dist/core/context/parallel-operations.js +1 -0
- package/dist/core/context/run-operation-cached-request.d.ts +8 -0
- package/dist/core/context/run-operation-cached-request.js +59 -0
- package/dist/core/context/run-operation.d.ts +23 -5
- package/dist/core/context/run-operation.js +63 -47
- 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 +8 -17
- package/dist/core/engine/activity-heartbeat-tracking.js +12 -2
- package/dist/core/engine/activity-reconciliation.d.ts +1 -0
- package/dist/core/engine/activity-reconciliation.js +7 -2
- package/dist/core/engine/anonymous-signal-sequence.js +6 -6
- package/dist/core/engine/async-activity-completion.d.ts +5 -7
- package/dist/core/engine/async-activity-completion.js +13 -9
- package/dist/core/engine/bulk-operations-purge.js +2 -1
- package/dist/core/engine/bulk-operations.js +18 -36
- package/dist/core/engine/callback-checkpoint-persistence.d.ts +3 -0
- package/dist/core/engine/callback-checkpoint-persistence.js +25 -0
- package/dist/core/engine/callback-creators-bundles.js +5 -2
- package/dist/core/engine/checkpoint-io.d.ts +4 -1
- package/dist/core/engine/checkpoint-io.js +19 -10
- package/dist/core/engine/completed-review-storage.d.ts +2 -1
- package/dist/core/engine/completed-review-storage.js +4 -3
- package/dist/core/engine/disposal.js +1 -0
- package/dist/core/engine/engine-internal-types.d.ts +1 -0
- package/dist/core/engine/index.d.ts +31 -0
- package/dist/core/engine/index.js +19 -10
- package/dist/core/engine/inline-launch-queue.js +1 -1
- package/dist/core/engine/internals.d.ts +25 -2
- package/dist/core/engine/lease-manager.js +2 -2
- 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.d.ts +11 -0
- package/dist/core/engine/memo-durable-activity.js +282 -0
- package/dist/core/engine/operations-activity.d.ts +6 -2
- package/dist/core/engine/operations-activity.js +24 -10
- package/dist/core/engine/operations-data.d.ts +4 -1
- package/dist/core/engine/operations-data.js +3 -3
- package/dist/core/engine/operations-time.d.ts +2 -2
- package/dist/core/engine/operations-time.js +40 -6
- package/dist/core/engine/reviews.js +7 -4
- package/dist/core/engine/schedule-timer.js +2 -0
- package/dist/core/engine/schedules.js +3 -0
- package/dist/core/engine/storage-io.js +1 -1
- 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/json.js +1 -1
- 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/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +2 -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 -1
- package/dist/index.js +6 -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 +32 -32
- 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/runtime/websocket-worker.js +7 -2
- package/dist/server/serve-internals.d.ts +28 -0
- package/dist/server/serve-internals.js +4 -2
- 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/service-worker/setup.d.ts +18 -1
- package/dist/service-worker/setup.js +7 -4
- 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.d.ts +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 +32 -32
- 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
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
resolveActivityTimeout
|
|
17
17
|
} from "./activity-schedule-to-close.js";
|
|
18
18
|
import { getInternals, hasContextInternals } from "./internals.js";
|
|
19
|
+
import { getCachedRunActivityRequest } from "./run-operation-cached-request.js";
|
|
19
20
|
import { isActivityCallOptions } from "./session-state.js";
|
|
20
21
|
import { captureCallerStack } from "./validation.js";
|
|
21
22
|
export function asConcreteContext(context) {
|
|
@@ -42,13 +43,27 @@ function parseRunArguments(activity, rest) {
|
|
|
42
43
|
return { input: values[0], options };
|
|
43
44
|
}
|
|
44
45
|
export const readActivityRetryAttemptForTesting = readActivityRetryAttempt, readCompletedRetrySleepCountForTesting = readCompletedRetrySleepCount, completeActivityRetryAttemptForTesting = completeActivityRetryAttempt;
|
|
45
|
-
|
|
46
|
+
const ACTIVITY_RETRY_SLEEP_DEADLINE_LOCAL_PREFIX = "__weftActivityRetrySleepDeadline:";
|
|
47
|
+
export function readOrInitActivityRetrySleepFireAt(context, operationId, duration) {
|
|
48
|
+
const internals = getInternals(context), localKey = `${ACTIVITY_RETRY_SLEEP_DEADLINE_LOCAL_PREFIX}${operationId}`, existing = internals.checkpointLocals[localKey];
|
|
49
|
+
if (typeof existing === "number" && Number.isFinite(existing))
|
|
50
|
+
return existing;
|
|
51
|
+
if (existing !== void 0)
|
|
52
|
+
throw Error(`Invalid checkpointed activity retry sleep deadline ${JSON.stringify(existing)} for "${operationId}".`);
|
|
53
|
+
const scheduledFireAt = internals.getNow() + duration;
|
|
54
|
+
internals.checkpointLocals = {
|
|
55
|
+
...internals.checkpointLocals,
|
|
56
|
+
[localKey]: scheduledFireAt
|
|
57
|
+
};
|
|
58
|
+
return scheduledFireAt;
|
|
59
|
+
}
|
|
60
|
+
export function resolveScheduleToCloseBudget(internals, retryStateKey, scheduleToCloseTimeout) {
|
|
46
61
|
const budgetMs = parseScheduleToCloseBudgetMs(scheduleToCloseTimeout);
|
|
47
62
|
if (budgetMs === void 0)
|
|
48
63
|
return;
|
|
49
64
|
return {
|
|
50
65
|
budgetMs,
|
|
51
|
-
dispatchedAt: readOrInitActivityDispatchedAt(internals,
|
|
66
|
+
dispatchedAt: readOrInitActivityDispatchedAt(internals, retryStateKey, internals.getNow())
|
|
52
67
|
};
|
|
53
68
|
}
|
|
54
69
|
function getActivityName(activity, explicitName) {
|
|
@@ -59,39 +74,7 @@ function getActivityName(activity, explicitName) {
|
|
|
59
74
|
function getActivityFunction(activity) {
|
|
60
75
|
return typeof activity === "function" ? activity : void 0;
|
|
61
76
|
}
|
|
62
|
-
function
|
|
63
|
-
const hasCachedResult = internals.accumulatedResults?.has(step) ?? !1;
|
|
64
|
-
if (hasCachedResult) {
|
|
65
|
-
const cachedResult = internals.accumulatedResults?.get(step);
|
|
66
|
-
internals.stepIndex += readCompletedRetrySleepCount(internals, step);
|
|
67
|
-
if (internals.explainMode)
|
|
68
|
-
console.log(`[weft] ctx.run(${activityName}) \u2192 Returning cached result from step ${step}`);
|
|
69
|
-
return {
|
|
70
|
-
request: { type: "activity", operationId: "", activityName, input },
|
|
71
|
-
step,
|
|
72
|
-
hasCachedResult,
|
|
73
|
-
cachedResult,
|
|
74
|
-
retryAttempt: 1
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
const retryAttempt = readActivityRetryAttempt(internals, step);
|
|
78
|
-
if (retryAttempt !== void 0) {
|
|
79
|
-
internals.stepIndex += retryAttempt - 2;
|
|
80
|
-
return {
|
|
81
|
-
request: { type: "activity", operationId: "", activityName, input },
|
|
82
|
-
step,
|
|
83
|
-
hasCachedResult: !1,
|
|
84
|
-
retryAttempt
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
request: { type: "activity", operationId: "", activityName, input },
|
|
89
|
-
step,
|
|
90
|
-
hasCachedResult,
|
|
91
|
-
retryAttempt: 1
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
function createFreshRunActivityRequest(internals, step, activityName, activityFunction, input, options) {
|
|
77
|
+
function createFreshRunActivityRequest(internals, step, activityName, activityFunction, input, options, activityStateKey, workflowExecutionToken) {
|
|
95
78
|
const queue = options?.queue ?? "default";
|
|
96
79
|
if (internals.explainMode) {
|
|
97
80
|
console.log(`[weft] ctx.run(${activityName}, ${JSON.stringify(input)})`);
|
|
@@ -103,6 +86,8 @@ function createFreshRunActivityRequest(internals, step, activityName, activityFu
|
|
|
103
86
|
operationId: crypto.randomUUID(),
|
|
104
87
|
activityName,
|
|
105
88
|
step,
|
|
89
|
+
...activityStateKey === void 0 ? {} : { activityStateKey },
|
|
90
|
+
...workflowExecutionToken === void 0 ? {} : { workflowExecutionToken },
|
|
106
91
|
...activityFunction !== void 0 ? { fn: activityFunction } : {},
|
|
107
92
|
input,
|
|
108
93
|
callerStack: captureCallerStack(),
|
|
@@ -130,7 +115,7 @@ function isNonRetryableActivityError(error, policy) {
|
|
|
130
115
|
export function shouldRetryActivityError(error, policy, attempt) {
|
|
131
116
|
return policy !== void 0 && attempt < policy.maxAttempts && !isNonRetryableActivityError(error, policy);
|
|
132
117
|
}
|
|
133
|
-
function prepareActivityRetryRequest(request, attempt) {
|
|
118
|
+
export function prepareActivityRetryRequest(request, attempt) {
|
|
134
119
|
if (attempt === 1)
|
|
135
120
|
return request;
|
|
136
121
|
return {
|
|
@@ -140,45 +125,76 @@ function prepareActivityRetryRequest(request, attempt) {
|
|
|
140
125
|
};
|
|
141
126
|
}
|
|
142
127
|
export function createRunActivityRequest(context, activity, rest, explicitName) {
|
|
143
|
-
|
|
128
|
+
parseRunArguments(activity, rest);
|
|
129
|
+
const internals = getInternals(context), step = internals.stepIndex++;
|
|
130
|
+
return createRunActivityRequestAtStep(context, activity, rest, step, {
|
|
131
|
+
...explicitName === void 0 ? {} : { explicitName },
|
|
132
|
+
advanceStepIndexForCachedRetryState: !0,
|
|
133
|
+
retryStateKey: step,
|
|
134
|
+
cacheResultStep: step
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export function createRunActivityRequestAtStep(context, activity, rest, step, configuration = {}) {
|
|
138
|
+
const { input, options } = parseRunArguments(activity, rest), activityName = getActivityName(activity, configuration.explicitName), activityFunction = getActivityFunction(activity), internals = getInternals(context), retryStateKey = configuration.retryStateKey ?? step, cacheResultStep = configuration.cacheResultStep === !1 ? void 0 : configuration.cacheResultStep ?? step, cachedRequest = getCachedRunActivityRequest(internals, step, retryStateKey, cacheResultStep, activityName, input, {
|
|
139
|
+
advanceStepIndexForCachedRetryState: configuration.advanceStepIndexForCachedRetryState ?? !1
|
|
140
|
+
});
|
|
144
141
|
if (cachedRequest.hasCachedResult)
|
|
145
142
|
return cachedRequest;
|
|
146
|
-
const retryPolicy = resolveActivityRetryPolicy(activity, options), scheduleToCloseTimeout = resolveActivityScheduleToCloseTimeout(activity, options)
|
|
143
|
+
const retryPolicy = resolveActivityRetryPolicy(activity, options), scheduleToCloseTimeout = resolveActivityScheduleToCloseTimeout(activity, options);
|
|
147
144
|
return {
|
|
148
|
-
request: createFreshRunActivityRequest(internals, step, activityName, activityFunction, input,
|
|
145
|
+
request: createFreshRunActivityRequest(internals, step, activityName, activityFunction, input, resolveDispatchedActivityOptions(activity, options), configuration.activityStateKey, context.workflowExecutionToken),
|
|
149
146
|
step,
|
|
147
|
+
retryStateKey,
|
|
148
|
+
...cacheResultStep === void 0 ? {} : { cacheResultStep },
|
|
150
149
|
hasCachedResult: !1,
|
|
151
150
|
retryAttempt: cachedRequest.retryAttempt,
|
|
152
151
|
...retryPolicy === void 0 ? {} : { retryPolicy },
|
|
153
152
|
...scheduleToCloseTimeout === void 0 ? {} : { scheduleToCloseTimeout }
|
|
154
153
|
};
|
|
155
154
|
}
|
|
155
|
+
function resolveDispatchedActivityOptions(activity, options) {
|
|
156
|
+
const effectiveTimeout = resolveActivityTimeout(activity, options);
|
|
157
|
+
return effectiveTimeout === void 0 ? options : { ...options, timeout: effectiveTimeout };
|
|
158
|
+
}
|
|
156
159
|
export function* runActivityWithRetry(context, activity, rest, explicitName) {
|
|
160
|
+
return yield* runPreparedActivityWithRetry(context, createRunActivityRequest(context, activity, rest, explicitName), defaultActivityRetrySleep(context));
|
|
161
|
+
}
|
|
162
|
+
export function* runActivityWithRetryAtStep(context, activity, rest, step, configuration = {}) {
|
|
163
|
+
return yield* runPreparedActivityWithRetry(context, createRunActivityRequestAtStep(context, activity, rest, step, configuration), configuration.retrySleep ?? defaultActivityRetrySleep(context));
|
|
164
|
+
}
|
|
165
|
+
function defaultActivityRetrySleep(context) {
|
|
166
|
+
return function* sleepForRetry(duration) {
|
|
167
|
+
yield* context.sleep(duration);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function* runPreparedActivityWithRetry(context, prepared, retrySleep) {
|
|
157
171
|
const {
|
|
158
172
|
request,
|
|
159
|
-
|
|
173
|
+
retryStateKey,
|
|
174
|
+
cacheResultStep,
|
|
160
175
|
hasCachedResult,
|
|
161
176
|
cachedResult,
|
|
162
177
|
retryAttempt,
|
|
163
178
|
retryPolicy,
|
|
164
179
|
scheduleToCloseTimeout
|
|
165
|
-
} =
|
|
180
|
+
} = prepared;
|
|
166
181
|
if (hasCachedResult)
|
|
167
182
|
return cachedResult;
|
|
168
|
-
const internals = getInternals(context), budget = retryPolicy === void 0 ? void 0 : resolveScheduleToCloseBudget(internals,
|
|
183
|
+
const internals = getInternals(context), budget = retryPolicy === void 0 ? void 0 : resolveScheduleToCloseBudget(internals, retryStateKey, scheduleToCloseTimeout);
|
|
169
184
|
let attempt = retryAttempt;
|
|
170
185
|
if (attempt > 1) {
|
|
171
186
|
if (retryPolicy === void 0)
|
|
172
187
|
throw Error(`Missing activity retry policy for checkpointed retry attempt ${attempt}`);
|
|
173
|
-
yield*
|
|
188
|
+
yield* retrySleep(calculateBackoff(attempt - 1, retryPolicy), attempt);
|
|
174
189
|
}
|
|
175
190
|
while (!0) {
|
|
176
191
|
if (attempt > 1)
|
|
177
192
|
assertScheduleToCloseBudgetNotExhausted(budget, request.activityName, internals.getNow());
|
|
178
193
|
try {
|
|
179
194
|
const result = yield prepareActivityRetryRequest(request, attempt);
|
|
180
|
-
|
|
181
|
-
|
|
195
|
+
if (cacheResultStep !== void 0)
|
|
196
|
+
context.accumulatedResults.set(cacheResultStep, result);
|
|
197
|
+
completeActivityRetryAttempt(internals, retryStateKey, attempt - 1);
|
|
182
198
|
return result;
|
|
183
199
|
} catch (error) {
|
|
184
200
|
if (!shouldRetryActivityError(error, retryPolicy, attempt))
|
|
@@ -186,8 +202,8 @@ export function* runActivityWithRetry(context, activity, rest, explicitName) {
|
|
|
186
202
|
const backoff = calculateBackoff(attempt, retryPolicy), now = internals.getNow();
|
|
187
203
|
assertScheduleToCloseBudgetNotExhausted(budget, request.activityName, now, now + backoff);
|
|
188
204
|
const nextAttempt = attempt + 1;
|
|
189
|
-
writeActivityRetryAttempt(internals,
|
|
190
|
-
yield*
|
|
205
|
+
writeActivityRetryAttempt(internals, retryStateKey, nextAttempt);
|
|
206
|
+
yield* retrySleep(backoff, nextAttempt);
|
|
191
207
|
attempt = nextAttempt;
|
|
192
208
|
}
|
|
193
209
|
}
|
|
@@ -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,
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
import type { ActivityContext } from '../types.ts';
|
|
2
2
|
import type { EngineInternals } from './internals.ts';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* later step's first attempt never inherits an earlier step's heartbeat, and
|
|
9
|
-
* concurrent `ctx.all` activities never clobber one another. The step is stable
|
|
10
|
-
* across retry attempts (assigned once at `stepIndex++`). Held only in engine
|
|
11
|
-
* memory and cleared by workflowId (the outer key) on terminal cleanup and purge.
|
|
12
|
-
*/
|
|
13
|
-
/** Record the heartbeat the current attempt of a step sent. */
|
|
14
|
-
export declare function recordLastHeartbeatForStep(internals: EngineInternals, workflowId: string, step: number, details: unknown): void;
|
|
15
|
-
/** Read the heartbeat a prior attempt of this step recorded, or `undefined`. */
|
|
16
|
-
export declare function readLastHeartbeatForStep(internals: EngineInternals, workflowId: string, step: number): unknown;
|
|
3
|
+
export type ActivityHeartbeatKey = number | string;
|
|
4
|
+
/** Record the heartbeat the current attempt of an activity sent. */
|
|
5
|
+
export declare function recordLastHeartbeatForStep(internals: EngineInternals, workflowId: string, step: ActivityHeartbeatKey, details: unknown): void;
|
|
6
|
+
/** Read the heartbeat a prior attempt of this activity recorded, or `undefined`. */
|
|
7
|
+
export declare function readLastHeartbeatForStep(internals: EngineInternals, workflowId: string, step: ActivityHeartbeatKey): unknown;
|
|
17
8
|
/**
|
|
18
9
|
* Drop the heartbeat tracked for a single step once that step has completed
|
|
19
10
|
* successfully — after inline verify, and (on the idempotency path) after the
|
|
@@ -26,7 +17,7 @@ export declare function readLastHeartbeatForStep(internals: EngineInternals, wor
|
|
|
26
17
|
* resumable-batch heartbeat a retry depends on, so this is only ever called from
|
|
27
18
|
* the non-speculative success path.
|
|
28
19
|
*/
|
|
29
|
-
export declare function clearLastHeartbeatForStep(internals: EngineInternals, workflowId: string, step:
|
|
20
|
+
export declare function clearLastHeartbeatForStep(internals: EngineInternals, workflowId: string, step: ActivityHeartbeatKey): void;
|
|
30
21
|
/**
|
|
31
22
|
* #493: in development mode, emit a COARSE warning when an inline activity RETRY
|
|
32
23
|
* (`attempt > 1`) starts with no `lastHeartbeatDetails` — the closest runtime
|
|
@@ -53,7 +44,7 @@ export declare function clearLastHeartbeatForStep(internals: EngineInternals, wo
|
|
|
53
44
|
* routing model would need this gate to key on the resolved per-dispatch mode
|
|
54
45
|
* instead.
|
|
55
46
|
*/
|
|
56
|
-
export declare function warnIfRetryMissingHeartbeat(internals: EngineInternals, workflowId: string, step:
|
|
47
|
+
export declare function warnIfRetryMissingHeartbeat(internals: EngineInternals, workflowId: string, step: ActivityHeartbeatKey, attempt: number): void;
|
|
57
48
|
/**
|
|
58
49
|
* Build the {@link ActivityContext} handed to an inline activity function. The
|
|
59
50
|
* signal comes from the per-workflow AbortController (so workflow cancellation
|
|
@@ -61,4 +52,4 @@ export declare function warnIfRetryMissingHeartbeat(internals: EngineInternals,
|
|
|
61
52
|
* tracking above; `completeAsync` is supplied by the caller (it owns the
|
|
62
53
|
* async-completion token machinery).
|
|
63
54
|
*/
|
|
64
|
-
export declare function buildActivityContext(internals: EngineInternals, workflowId: string, step: number, 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;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { DevelopmentWarningEvent } from "../events.js";
|
|
2
|
+
function activityHeartbeatKeyLabel(key) {
|
|
3
|
+
return typeof key === "number" ? `step ${String(key)}` : `activity state key ${key}`;
|
|
4
|
+
}
|
|
5
|
+
function activityHeartbeatFieldPath(key) {
|
|
6
|
+
return typeof key === "number" ? `step.${String(key)}.lastHeartbeatDetails` : `activityStateKey.${key}.lastHeartbeatDetails`;
|
|
7
|
+
}
|
|
2
8
|
export function recordLastHeartbeatForStep(internals, workflowId, step, details) {
|
|
3
9
|
let byStep = internals.lastHeartbeatDetailsByStep.get(workflowId);
|
|
4
10
|
if (byStep === void 0) {
|
|
@@ -27,11 +33,15 @@ export function warnIfRetryMissingHeartbeat(internals, workflowId, step, attempt
|
|
|
27
33
|
return;
|
|
28
34
|
if (readLastHeartbeatForStep(internals, workflowId, step) !== void 0)
|
|
29
35
|
return;
|
|
30
|
-
internals.engine.dispatchEvent(new DevelopmentWarningEvent(workflowId, `Activity retry (attempt ${attempt}) at
|
|
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)]));
|
|
31
37
|
}
|
|
32
|
-
export function buildActivityContext(internals, workflowId, step, signal, completeAsync) {
|
|
38
|
+
export function buildActivityContext(internals, workflowId, workflowExecutionToken, step, attempt, signal, completeAsync) {
|
|
33
39
|
return {
|
|
34
40
|
signal,
|
|
41
|
+
...workflowExecutionToken !== void 0 && {
|
|
42
|
+
workflowExecutionToken,
|
|
43
|
+
activityAttemptToken: `${workflowExecutionToken}:activity:${String(step)}:${String(attempt)}`
|
|
44
|
+
},
|
|
35
45
|
lastHeartbeatDetails: readLastHeartbeatForStep(internals, workflowId, step),
|
|
36
46
|
heartbeat: (details) => {
|
|
37
47
|
internals.heartbeatDetails.set(workflowId, details);
|
|
@@ -102,6 +102,7 @@ export declare function resolveStartedActivityReconciliationRecord(internals: En
|
|
|
102
102
|
}>;
|
|
103
103
|
export declare function writeActivityReconciliationTransition(storage: Storage, reference: ActivityReconciliationReference, expectedRecord: ActivityReconciliationRecord, nextRecord: ActivityReconciliationRecord): Promise<void>;
|
|
104
104
|
export declare function stageActivityReconciliationTransitionWithAtomicWorkflowCommit(internals: EngineInternals, workflowId: string, reference: ActivityReconciliationReference, expectedRecord: ActivityReconciliationRecord, nextRecord: ActivityReconciliationRecord): void;
|
|
105
|
+
export declare function commitActivityReconciliationTransitionWithFencedWrite(internals: EngineInternals, reference: ActivityReconciliationReference, expectedRecord: ActivityReconciliationRecord, nextRecord: ActivityReconciliationRecord): Promise<void>;
|
|
105
106
|
export declare function normalizePreDispatchVerificationResult(result: ActivityVerificationResult): 'not-completed' | 'completed-result-unavailable' | 'indeterminate' | {
|
|
106
107
|
result: unknown;
|
|
107
108
|
};
|
|
@@ -7,6 +7,7 @@ import { decode, encode } from "../codec.js";
|
|
|
7
7
|
import { assertPayloadWithinLimit } from "../payload-size.js";
|
|
8
8
|
import { WeftError } from "../weft-error.js";
|
|
9
9
|
import { stageAtomicWorkflowCommitSideEffects } from "./checkpoint-side-effects.js";
|
|
10
|
+
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
10
11
|
|
|
11
12
|
export class ActivityReconciliationCapabilityError extends WeftError {
|
|
12
13
|
constructor() {
|
|
@@ -83,7 +84,7 @@ export async function resolveStartedActivityReconciliationRecord(internals, work
|
|
|
83
84
|
ownerId: crypto.randomUUID(),
|
|
84
85
|
updatedAt: internals.options.getNow()
|
|
85
86
|
};
|
|
86
|
-
await
|
|
87
|
+
await commitActivityReconciliationTransitionWithFencedWrite(internals, reference, record, nextRecord);
|
|
87
88
|
return nextRecord;
|
|
88
89
|
}
|
|
89
90
|
if (normalized === "completed-result-unavailable")
|
|
@@ -92,7 +93,7 @@ export async function resolveStartedActivityReconciliationRecord(internals, work
|
|
|
92
93
|
throw new ActivityReconciliationIndeterminateError(`Activity "${operation.activityName}" reconciliation is indeterminate.`);
|
|
93
94
|
validateActivityResultForReconciliation(normalized.result, internals.options.payloadSizePolicy.maxBytes);
|
|
94
95
|
const completedRecord = createCompletedActivityReconciliationRecord(record, normalized.result, internals.options.getNow());
|
|
95
|
-
await
|
|
96
|
+
await commitActivityReconciliationTransitionWithFencedWrite(internals, reference, record, completedRecord);
|
|
96
97
|
return { completedResult: normalized.result };
|
|
97
98
|
}
|
|
98
99
|
export async function writeActivityReconciliationTransition(storage, reference, expectedRecord, nextRecord) {
|
|
@@ -103,6 +104,10 @@ export async function writeActivityReconciliationTransition(storage, reference,
|
|
|
103
104
|
export function stageActivityReconciliationTransitionWithAtomicWorkflowCommit(internals, workflowId, reference, expectedRecord, nextRecord) {
|
|
104
105
|
stageAtomicWorkflowCommitSideEffects(internals, workflowId, buildActivityReconciliationTransitionSideEffects(reference, expectedRecord, nextRecord));
|
|
105
106
|
}
|
|
107
|
+
export async function commitActivityReconciliationTransitionWithFencedWrite(internals, reference, expectedRecord, nextRecord) {
|
|
108
|
+
const sideEffects = buildActivityReconciliationTransitionSideEffects(reference, expectedRecord, nextRecord);
|
|
109
|
+
await commitFencedEngineWrite(internals, sideEffects.operations, sideEffects.conditions, () => new ActivityReconciliationConflictError("Activity reconciliation completion lost compare-and-set ownership."));
|
|
110
|
+
}
|
|
106
111
|
function buildActivityReconciliationTransitionSideEffects(reference, expectedRecord, nextRecord) {
|
|
107
112
|
return {
|
|
108
113
|
conditions: [{ key: reference.key, expectedValue: encode(expectedRecord) }],
|
|
@@ -79,21 +79,21 @@ async function scanNextAnonymousSignalSequence(internals, workflowId) {
|
|
|
79
79
|
if (sequence !== null && sequence >= nextSequence)
|
|
80
80
|
nextSequence = sequence + 1;
|
|
81
81
|
}
|
|
82
|
+
if (!Number.isSafeInteger(nextSequence))
|
|
83
|
+
throw Error(`Anonymous signal sequence overflow for workflow "${workflowId}": computed ${nextSequence}`);
|
|
82
84
|
return nextSequence;
|
|
83
85
|
}
|
|
84
86
|
function extractAnonymousSignalSequence(key) {
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
+
const idComponent = key.slice(key.lastIndexOf(":") + 1), marker = "anonymous%3A";
|
|
88
|
+
if (!idComponent.startsWith(marker))
|
|
87
89
|
return null;
|
|
88
|
-
const sequenceStart =
|
|
90
|
+
const sequenceStart = marker.length, sequenceEnd = idComponent.indexOf("%3A", sequenceStart);
|
|
89
91
|
if (sequenceEnd === -1)
|
|
90
92
|
return null;
|
|
91
|
-
const sequence = Number(
|
|
93
|
+
const sequence = Number(idComponent.slice(sequenceStart, sequenceEnd));
|
|
92
94
|
return Number.isSafeInteger(sequence) && sequence >= 0 ? sequence : null;
|
|
93
95
|
}
|
|
94
96
|
function decodeSignalSequence(bytes) {
|
|
95
|
-
if (bytes === null)
|
|
96
|
-
return 0;
|
|
97
97
|
const value = decode(bytes);
|
|
98
98
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
99
99
|
throw Error("Stored anonymous signal sequence must be a non-negative safe integer");
|
|
@@ -26,9 +26,6 @@
|
|
|
26
26
|
* arrives after token recovery but before replay has adopted the workflow
|
|
27
27
|
* generator, the engine buffers the completion or failure outcome and drains it
|
|
28
28
|
* when replay reaches the same deterministic token.
|
|
29
|
-
*
|
|
30
|
-
* Internal-only. Imported from `src/core/engine/**` and the `ActivityContext`
|
|
31
|
-
* construction path.
|
|
32
29
|
*/
|
|
33
30
|
import type { OperationOutcome } from '../types.ts';
|
|
34
31
|
import { WeftError } from '../weft-error.ts';
|
|
@@ -101,13 +98,14 @@ export type PendingAsyncActivityResolution = {
|
|
|
101
98
|
/**
|
|
102
99
|
* Derive the durable, deterministic task token for an async activity.
|
|
103
100
|
*
|
|
104
|
-
* The token is anchored to the workflow id, the
|
|
101
|
+
* The token is anchored to the workflow id, the activity state key, and the
|
|
105
102
|
* dispatch attempt — all of which are stable across replay — so a workflow that
|
|
106
103
|
* crashes while parked on an async activity mints the identical token after
|
|
107
|
-
* recovery. `
|
|
108
|
-
*
|
|
104
|
+
* recovery. Plain `ctx.run()` uses the workflow step as the state key.
|
|
105
|
+
* `operationId` is deliberately excluded because it is regenerated on every
|
|
106
|
+
* yield and would change on replay.
|
|
109
107
|
*/
|
|
110
|
-
export declare function deriveAsyncActivityToken(workflowId: string, step: number, attempt: number): string;
|
|
108
|
+
export declare function deriveAsyncActivityToken(workflowId: string, step: number | string, attempt: number): string;
|
|
111
109
|
/**
|
|
112
110
|
* Register a deferred activity: record it in memory and durably, then announce
|
|
113
111
|
* the token via {@link ActivityAsyncPendingEvent}. Idempotent on `token`: if the
|
|
@@ -3,6 +3,8 @@ import { decode, encode } from "../codec.js";
|
|
|
3
3
|
import { ActivityAsyncPendingEvent } from "../events.js";
|
|
4
4
|
import { assertPayloadWithinLimit } from "../payload-size.js";
|
|
5
5
|
import { WeftError } from "../weft-error.js";
|
|
6
|
+
import { stageAtomicWorkflowCommitSideEffects } from "./checkpoint-side-effects.js";
|
|
7
|
+
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
6
8
|
const ASYNC_ACTIVITY_TOKEN_PREFIX = "async-act:v1", ASYNC_ACTIVITY_KEY_PREFIX = "async-act:v1:";
|
|
7
9
|
export function asyncActivityWorkflowPrefix(workflowId) {
|
|
8
10
|
return `${ASYNC_ACTIVITY_KEY_PREFIX}${encodeStorageKeyComponent(workflowId)}:`;
|
|
@@ -33,7 +35,7 @@ function isPersistedAsyncActivity(value) {
|
|
|
33
35
|
export function deriveAsyncActivityToken(workflowId, step, attempt) {
|
|
34
36
|
return `${ASYNC_ACTIVITY_TOKEN_PREFIX}:${workflowId}:${step}:${attempt}`;
|
|
35
37
|
}
|
|
36
|
-
function
|
|
38
|
+
function buildPersistPendingAsyncActivityOperation(pending) {
|
|
37
39
|
const record = {
|
|
38
40
|
version: 1,
|
|
39
41
|
token: pending.token,
|
|
@@ -44,12 +46,16 @@ function persistPendingAsyncActivity(storage, pending) {
|
|
|
44
46
|
attempt: pending.attempt,
|
|
45
47
|
createdAt: pending.createdAt
|
|
46
48
|
};
|
|
47
|
-
return
|
|
49
|
+
return {
|
|
50
|
+
type: "put",
|
|
51
|
+
key: KEYS.asyncActivity(pending.workflowId, pending.token),
|
|
52
|
+
value: encode(record)
|
|
53
|
+
};
|
|
48
54
|
}
|
|
49
55
|
export async function registerPendingAsyncActivity(internals, pending) {
|
|
50
56
|
const alreadyRegistered = internals.pendingAsyncActivities.has(pending.token);
|
|
51
57
|
internals.pendingAsyncActivities.set(pending.token, pending);
|
|
52
|
-
await
|
|
58
|
+
await commitFencedEngineWrite(internals, [buildPersistPendingAsyncActivityOperation(pending)], [], () => Error(`Async activity registration for token "${pending.token}" lost its precondition.`));
|
|
53
59
|
if (!alreadyRegistered)
|
|
54
60
|
internals.engine.dispatchEvent(new ActivityAsyncPendingEvent(pending.token, pending.operationId, pending.workflowId, pending.activityName, pending.attempt));
|
|
55
61
|
}
|
|
@@ -87,12 +93,10 @@ async function consumePendingAsyncActivity(internals, token) {
|
|
|
87
93
|
if (!pending)
|
|
88
94
|
throw new AsyncActivityTokenNotFoundError(token);
|
|
89
95
|
internals.pendingAsyncActivities.delete(token);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
throw error;
|
|
95
|
-
}
|
|
96
|
+
stageAtomicWorkflowCommitSideEffects(internals, pending.workflowId, {
|
|
97
|
+
conditions: [],
|
|
98
|
+
operations: [{ type: "delete", key: KEYS.asyncActivity(pending.workflowId, token) }]
|
|
99
|
+
});
|
|
96
100
|
return pending;
|
|
97
101
|
}
|
|
98
102
|
function shouldBufferPendingAsyncActivityResolution(internals, workflowId) {
|
|
@@ -10,6 +10,7 @@ import { buildIndexOperations } from "../search-attributes.js";
|
|
|
10
10
|
import { buildWorkflowTagIndexOperations, normalizeWorkflowTags } from "../workflow-tags.js";
|
|
11
11
|
import { asyncActivityWorkflowPrefix } from "./async-activity-completion.js";
|
|
12
12
|
import { forgetCommittedCheckpointBytes } from "./checkpoint-commit-snapshots.js";
|
|
13
|
+
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
13
14
|
import { streamWorkflowStates } from "./listing.js";
|
|
14
15
|
import { createTerminalCleanupTimerId } from "./state-utilities.js";
|
|
15
16
|
import {
|
|
@@ -131,7 +132,7 @@ function getWorkflowRetentionDeadline(internals, state) {
|
|
|
131
132
|
}
|
|
132
133
|
export async function purgeWorkflow(internals, state, cleanupWaiters) {
|
|
133
134
|
const deleteOperations = await collectWorkflowPurgeDeleteOperations(internals, state);
|
|
134
|
-
await internals
|
|
135
|
+
await commitFencedEngineWrite(internals, deleteOperations, [], () => Error(`Purge commit for workflow "${state.id}" lost its precondition.`));
|
|
135
136
|
clearPurgedWorkflowInMemoryState(internals, state.id, cleanupWaiters);
|
|
136
137
|
}
|
|
137
138
|
export async function collectWorkflowPurgeDeleteOperations(internals, state) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
KEYS,
|
|
3
3
|
requireStorageCapability,
|
|
4
|
-
storageConditionalBatch,
|
|
5
4
|
storageHas
|
|
6
5
|
} from "../../storage/interface.js";
|
|
7
6
|
import { assertScopedBulkWorkflowFilter } from "../bulk-workflow-filter.js";
|
|
@@ -24,6 +23,10 @@ import {
|
|
|
24
23
|
withBulkAuditEvent
|
|
25
24
|
} from "./bulk-operations-shared.js";
|
|
26
25
|
import { BulkDeleteRequiresTerminalWorkflowsError } from "./errors.js";
|
|
26
|
+
import {
|
|
27
|
+
assertLeaseHeldForEngineWork,
|
|
28
|
+
commitFencedEngineWriteAllowingPreconditionFailure
|
|
29
|
+
} from "./fenced-write.js";
|
|
27
30
|
import { BULK_OPERATION_BATCH_SIZE } from "./listing.js";
|
|
28
31
|
import { createTerminalCleanupTimerId } from "./state-utilities.js";
|
|
29
32
|
import { loadWorkflowState, runSerializedWorkflowStateWrite } from "./storage-io.js";
|
|
@@ -59,20 +62,13 @@ async function runBulkCancellation(internals, filter, options = {}) {
|
|
|
59
62
|
const workflowIdsToCancel = preparation.workflowIds, bulkConcurrency = resolveBulkOperationConcurrency(options);
|
|
60
63
|
let cancelled = 0;
|
|
61
64
|
const errors = [], cancellationResults = await runBulkWorkflowPool(workflowIdsToCancel, bulkConcurrency, async (workflowId) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
};
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return {
|
|
72
|
-
status: "failed",
|
|
73
|
-
error: toBulkOperationError(internals, workflowId, error)
|
|
74
|
-
};
|
|
75
|
-
}
|
|
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
|
+
};
|
|
76
72
|
});
|
|
77
73
|
for (const cancellationResult of cancellationResults) {
|
|
78
74
|
if (cancellationResult.status === "rejected") {
|
|
@@ -104,27 +100,15 @@ async function runBulkFailedWorkflowRetry(internals, filter, options = {}) {
|
|
|
104
100
|
const bulkConcurrency = resolveBulkOperationConcurrency(options);
|
|
105
101
|
let retried = 0;
|
|
106
102
|
const errors = [], retryResults = await runBulkWorkflowPool(preparation.workflowIds, bulkConcurrency, async (workflowId) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return { status: "retried" };
|
|
110
|
-
} catch (error) {
|
|
111
|
-
return {
|
|
112
|
-
status: "failed",
|
|
113
|
-
error: toBulkOperationError(internals, workflowId, error)
|
|
114
|
-
};
|
|
115
|
-
}
|
|
103
|
+
await retryFailedWorkflow(internals, workflowId);
|
|
104
|
+
return { status: "retried" };
|
|
116
105
|
});
|
|
117
106
|
for (const retryResult of retryResults) {
|
|
118
107
|
if (retryResult.status === "rejected") {
|
|
119
108
|
errors.push(toBulkOperationError(internals, retryResult.item, retryResult.reason));
|
|
120
109
|
continue;
|
|
121
110
|
}
|
|
122
|
-
|
|
123
|
-
if (value.status === "retried") {
|
|
124
|
-
retried += 1;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
errors.push(value.error);
|
|
111
|
+
retried += 1;
|
|
128
112
|
}
|
|
129
113
|
const result = { retried, failed: errors.length, errors };
|
|
130
114
|
if (!shouldPersistBulkAudit(options))
|
|
@@ -132,6 +116,7 @@ async function runBulkFailedWorkflowRetry(internals, filter, options = {}) {
|
|
|
132
116
|
return withBulkAuditEvent(internals, preparation, options, result, retried);
|
|
133
117
|
}
|
|
134
118
|
export async function retryFailedAll(internals, filter, options = {}) {
|
|
119
|
+
assertLeaseHeldForEngineWork(internals);
|
|
135
120
|
return runBulkFailedWorkflowRetry(internals, filter, options);
|
|
136
121
|
}
|
|
137
122
|
async function retryFailedWorkflow(internals, workflowId) {
|
|
@@ -194,12 +179,9 @@ async function reactivateFailedWorkflowFromCheckpointSerialized(internals, workf
|
|
|
194
179
|
throw Error(`Workflow concurrency admission for "${lastConcurrencyStateKey ?? workflowId}" changed too many times while retrying failed workflow "${workflowId}"`);
|
|
195
180
|
}
|
|
196
181
|
async function commitFailedWorkflowReactivation(internals, operations, conditions) {
|
|
197
|
-
if (conditions.length
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
requireStorageCapability(internals.storage, "conditionalBatch", "retry failed workflow");
|
|
202
|
-
return storageConditionalBatch(internals.storage, conditions, operations);
|
|
182
|
+
if (conditions.length > 0)
|
|
183
|
+
requireStorageCapability(internals.storage, "conditionalBatch", "retry failed workflow");
|
|
184
|
+
return commitFencedEngineWriteAllowingPreconditionFailure(internals, operations, conditions);
|
|
203
185
|
}
|
|
204
186
|
function buildReactivatedWorkflowState(internals, state) {
|
|
205
187
|
const reactivatedState = {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ContextOperationRequest } from '../context.ts';
|
|
2
|
+
import type { Engine } from './index.ts';
|
|
3
|
+
export declare function persistCheckpointForDataOperation<TWorkflows extends object, TActivities extends object>(engine: Engine<TWorkflows, TActivities>, workflowId: string, operation: ContextOperationRequest): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HISTORY_CIRCUIT_BREAKER_REASON } from "../types.js";
|
|
2
|
+
import { validateAttributeValueSizes } from "./attributes-tags.js";
|
|
3
|
+
import { createTerminationCallbacks } from "./callback-creators-core.js";
|
|
4
|
+
import {
|
|
5
|
+
appendTimelineBatchOperations,
|
|
6
|
+
persistCheckpoint,
|
|
7
|
+
pruneCheckpointHistory
|
|
8
|
+
} from "./checkpoint-io.js";
|
|
9
|
+
import { getInternals } from "./internals.js";
|
|
10
|
+
import { swallowPromiseRejection } from "./strategy-helpers.js";
|
|
11
|
+
import { terminateWorkflow } from "./termination.js";
|
|
12
|
+
export function persistCheckpointForDataOperation(engine, workflowId, operation) {
|
|
13
|
+
return persistCheckpoint(getInternals(engine), workflowId, operation, void 0, {
|
|
14
|
+
appendTimelineBatchOperations: (id, checkpointOperation, step, timestamp, operations) => appendTimelineBatchOperations(getInternals(engine), id, checkpointOperation, step, timestamp, operations),
|
|
15
|
+
swallowPromiseRejection: (promise) => {
|
|
16
|
+
swallowPromiseRejection(promise);
|
|
17
|
+
},
|
|
18
|
+
validateAttributeValueSizes,
|
|
19
|
+
pruneCheckpointHistory: (id, step) => pruneCheckpointHistory(getInternals(engine), id, step),
|
|
20
|
+
dispatchEvent: (event) => {
|
|
21
|
+
engine.dispatchEvent(event);
|
|
22
|
+
},
|
|
23
|
+
enforceHistoryCircuitBreaker: (id) => terminateWorkflow(getInternals(engine), id, "timed-out", createTerminationCallbacks(engine), HISTORY_CIRCUIT_BREAKER_REASON)
|
|
24
|
+
}, { timeline: "preserve-pending" });
|
|
25
|
+
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
broadcast as broadcastFromInternals,
|
|
4
4
|
dispatchPendingUpdateReceived as dispatchPendingUpdateReceivedFromBroadcast
|
|
5
5
|
} from "./broadcast.js";
|
|
6
|
+
import { persistCheckpointForDataOperation } from "./callback-checkpoint-persistence.js";
|
|
6
7
|
import {
|
|
7
8
|
createBroadcastCallbacks,
|
|
8
9
|
createGuardCallbacks,
|
|
@@ -97,7 +98,9 @@ export function createConditionOperationCallbacks(engine) {
|
|
|
97
98
|
}
|
|
98
99
|
export function createDataOperationCallbacks(engine) {
|
|
99
100
|
return {
|
|
100
|
-
runOperationWithResult: (workflowId, operation, execute) => runOperationWithResultForEngine(engine, workflowId, operation, execute)
|
|
101
|
+
runOperationWithResult: (workflowId, operation, execute) => runOperationWithResultForEngine(engine, workflowId, operation, execute),
|
|
102
|
+
persistCheckpoint: (workflowId, operation) => persistCheckpointForDataOperation(engine, workflowId, operation),
|
|
103
|
+
getActivityOperationCallbacks: () => createActivityOperationCallbacks(engine)
|
|
101
104
|
};
|
|
102
105
|
}
|
|
103
106
|
export function createStateOperationCallbacks(engine) {
|
|
@@ -119,7 +122,7 @@ export function createTimeOperationCallbacks(engine) {
|
|
|
119
122
|
loadWorkflowState: (workflowId) => loadWorkflowState(getInternals(engine), workflowId),
|
|
120
123
|
failWorkflow: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine)),
|
|
121
124
|
runSerializedWorkflowStateWrite: (workflowId, writeOperation) => runSerializedWorkflowStateWrite(getInternals(engine), workflowId, writeOperation),
|
|
122
|
-
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)),
|
|
123
126
|
workflowVersionTupleFromState: (state) => workflowVersionTupleFromState(getInternals(engine), state, createLifecycleCallbacks(engine)),
|
|
124
127
|
setWorkflowStartHeaders: (workflowId, headers) => setWorkflowStartHeaders(getInternals(engine), workflowId, headers, createLifecycleCallbacks(engine)),
|
|
125
128
|
loadWorkflowStartHeaders: (workflowId) => loadWorkflowStartHeaders(getInternals(engine), workflowId, createLifecycleCallbacks(engine)),
|