@lostgradient/weft 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/dist/cli/generated/operation-client.generated.d.ts +1 -0
- package/dist/cli-main.js +63 -63
- package/dist/client/event-stream-options.d.ts +58 -0
- package/dist/client/event-stream-options.js +0 -0
- package/dist/client/event-stream.d.ts +27 -44
- package/dist/client/event-stream.js +21 -56
- package/dist/client/event-tail-lifecycle.d.ts +21 -0
- package/dist/client/event-tail-lifecycle.js +71 -0
- package/dist/client/event-tail.d.ts +3 -1
- package/dist/client/http-client.d.ts +4 -5
- package/dist/client/http-client.js +4 -1
- package/dist/client/http-handle.d.ts +4 -5
- package/dist/client/http-request.d.ts +7 -0
- package/dist/client/index.d.ts +2 -1
- package/dist/client/interface.d.ts +23 -7
- package/dist/client/local-handles.d.ts +2 -0
- package/dist/client/local-handles.js +10 -1
- package/dist/client/local.d.ts +3 -3
- package/dist/client/open-event-subscription.d.ts +3 -2
- package/dist/client/open-event-subscription.js +28 -1
- package/dist/client/sse-event-stream.d.ts +27 -0
- package/dist/client/sse-event-stream.js +276 -0
- package/dist/client/start-body.d.ts +2 -2
- package/dist/client/start-body.js +1 -0
- 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/operation-request.d.ts +15 -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 +62 -47
- package/dist/core/engine/activity-heartbeat-tracking.d.ts +8 -17
- package/dist/core/engine/activity-heartbeat-tracking.js +7 -1
- 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 -4
- 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 +8 -7
- 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 +4 -1
- 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/construction.js +3 -1
- package/dist/core/engine/engine-internal-types.d.ts +2 -0
- package/dist/core/engine/errors.d.ts +6 -6
- package/dist/core/engine/errors.js +1 -1
- package/dist/core/engine/handles.js +1 -1
- package/dist/core/engine/index.d.ts +37 -5
- package/dist/core/engine/index.js +7 -1
- package/dist/core/engine/internals.d.ts +2 -1
- package/dist/core/engine/lease-manager.js +2 -2
- package/dist/core/engine/lifecycle/recovered-services.js +8 -1
- package/dist/core/engine/lifecycle/start-or-signal-create.d.ts +2 -2
- package/dist/core/engine/lifecycle/start-or-signal-create.js +17 -15
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +1 -1
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +23 -5
- package/dist/core/engine/lifecycle/start-or-signal.d.ts +6 -4
- package/dist/core/engine/lifecycle/start-or-signal.js +21 -4
- 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 +5 -1
- package/dist/core/engine/operations-activity.js +19 -7
- package/dist/core/engine/operations-data.d.ts +4 -1
- package/dist/core/engine/operations-data.js +3 -3
- package/dist/core/engine/reviews.js +7 -4
- package/dist/core/engine/schedule-run.js +12 -3
- package/dist/core/engine/schedule-timer.js +2 -0
- package/dist/core/engine/storage-io.js +2 -2
- package/dist/core/engine/termination/complete.js +3 -3
- package/dist/core/json.js +1 -1
- package/dist/core/scheduler/scheduler-class.js +2 -1
- package/dist/core/start-workflow-validation.d.ts +3 -4
- package/dist/core/start-workflow-validation.js +1 -1
- package/dist/core/timeouts.d.ts +13 -1
- package/dist/core/timeouts.js +4 -1
- package/dist/core/types/constants.d.ts +16 -0
- package/dist/core/types/options.d.ts +60 -12
- package/dist/core/types/services-resolution.d.ts +49 -3
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +3 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +7 -0
- package/dist/json-schema.js +1 -1
- package/dist/mcp/cli.js +26 -26
- package/dist/server/asyncapi-channels.js +82 -16
- package/dist/server/handler/auth-context-principal.d.ts +19 -0
- package/dist/server/handler/auth-context-principal.js +25 -0
- package/dist/server/handler/index.d.ts +2 -1
- package/dist/server/handler/index.js +35 -18
- package/dist/server/handler/route-dispatch.d.ts +12 -22
- package/dist/server/handler/route-dispatch.js +30 -36
- package/dist/server/handler/sse-route-dispatch.d.ts +25 -0
- package/dist/server/handler/sse-route-dispatch.js +45 -0
- package/dist/server/handler.js +28 -28
- package/dist/server/index.d.ts +8 -7
- package/dist/server/index.js +17 -17
- package/dist/server/operations/fleet-events-sse.d.ts +11 -0
- package/dist/server/operations/fleet-events-sse.js +164 -0
- package/dist/server/operations/sse-stream.d.ts +43 -0
- package/dist/server/operations/sse-stream.js +152 -7
- package/dist/server/operations/start-or-signal-workflow.d.ts +2 -0
- package/dist/server/operations/start-or-signal-workflow.js +25 -14
- package/dist/server/operations/start-workflow-options.d.ts +9 -6
- package/dist/server/operations/start-workflow-options.js +13 -0
- package/dist/server/operations/workflow-events-sse.d.ts +17 -0
- package/dist/server/operations/workflow-events-sse.js +200 -0
- package/dist/server/rest-bindings.js +12 -0
- package/dist/server/runtime/authentication-bridge.js +4 -0
- package/dist/server/runtime/cors.d.ts +1 -1
- package/dist/server/runtime/cors.js +6 -1
- package/dist/server/runtime/websocket-stream.d.ts +4 -0
- package/dist/server/runtime/websocket-stream.js +20 -3
- 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/workflow-event-feed.d.ts +1 -0
- package/dist/server/workflow-event-feed.js +17 -6
- package/dist/service-worker/index.js +28 -28
- package/dist/service-worker/setup.d.ts +18 -1
- package/dist/service-worker/setup.js +7 -4
- package/dist/storage/typed-storage.d.ts +1 -1
- package/dist/storage/typed-storage.js +1 -1
- package/dist/testing/index.js +27 -27
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { WeftError } from "../weft-error.js";
|
|
2
|
+
import { captureCallerStack } from "./validation.js";
|
|
3
|
+
|
|
4
|
+
export class DurableActivityScopeError extends WeftError {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super("DurableActivityScopeError", message);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class DurableActivityUnsupportedError extends WeftError {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super("DurableActivityUnsupportedError", message);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
let durableActivityScopeStorage;
|
|
16
|
+
const fallbackDurableActivityScopes = [], AMBIGUOUS_FALLBACK_DURABLE_ACTIVITY_SCOPE = Symbol("AMBIGUOUS_FALLBACK_DURABLE_ACTIVITY_SCOPE");
|
|
17
|
+
function loadAsyncLocalStorageConstructor() {
|
|
18
|
+
const getBuiltinModule = ("process" in globalThis ? globalThis.process : void 0)?.getBuiltinModule;
|
|
19
|
+
if (typeof getBuiltinModule !== "function")
|
|
20
|
+
return;
|
|
21
|
+
return getBuiltinModule("node:async_hooks")?.AsyncLocalStorage;
|
|
22
|
+
}
|
|
23
|
+
function getDurableActivityScopeStorage() {
|
|
24
|
+
if (durableActivityScopeStorage !== void 0)
|
|
25
|
+
return durableActivityScopeStorage;
|
|
26
|
+
const AsyncLocalStorage = loadAsyncLocalStorageConstructor();
|
|
27
|
+
durableActivityScopeStorage = AsyncLocalStorage === void 0 ? null : new AsyncLocalStorage;
|
|
28
|
+
return durableActivityScopeStorage;
|
|
29
|
+
}
|
|
30
|
+
function currentDurableActivityScope() {
|
|
31
|
+
const storage = getDurableActivityScopeStorage();
|
|
32
|
+
if (storage !== null)
|
|
33
|
+
return storage.getStore();
|
|
34
|
+
if (fallbackDurableActivityScopes.length > 1)
|
|
35
|
+
return AMBIGUOUS_FALLBACK_DURABLE_ACTIVITY_SCOPE;
|
|
36
|
+
return fallbackDurableActivityScopes.at(-1);
|
|
37
|
+
}
|
|
38
|
+
export function runWithDurableActivityScope(scope, execute) {
|
|
39
|
+
const storage = getDurableActivityScopeStorage();
|
|
40
|
+
if (storage === null)
|
|
41
|
+
return runWithFallbackDurableActivityScope(scope, execute);
|
|
42
|
+
return storage.run(scope, execute);
|
|
43
|
+
}
|
|
44
|
+
function runWithFallbackDurableActivityScope(scope, execute) {
|
|
45
|
+
fallbackDurableActivityScopes.push(scope);
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = execute();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
removeFallbackDurableActivityScope(scope);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
if (hasFinally(result))
|
|
54
|
+
return result.finally(() => removeFallbackDurableActivityScope(scope));
|
|
55
|
+
removeFallbackDurableActivityScope(scope);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
function removeFallbackDurableActivityScope(scope) {
|
|
59
|
+
const index = fallbackDurableActivityScopes.lastIndexOf(scope);
|
|
60
|
+
if (index >= 0)
|
|
61
|
+
fallbackDurableActivityScopes.splice(index, 1);
|
|
62
|
+
}
|
|
63
|
+
function hasFinally(value) {
|
|
64
|
+
if (typeof value !== "object" && typeof value !== "function" || value === null)
|
|
65
|
+
return !1;
|
|
66
|
+
return "finally" in value && typeof value.finally === "function";
|
|
67
|
+
}
|
|
68
|
+
export function durableActivity(activity, ...activityArguments) {
|
|
69
|
+
const scope = currentDurableActivityScope();
|
|
70
|
+
if (scope === AMBIGUOUS_FALLBACK_DURABLE_ACTIVITY_SCOPE)
|
|
71
|
+
return Promise.reject(new DurableActivityScopeError("durableActivity() cannot resolve a unique ctx.memo() scope because AsyncLocalStorage is unavailable and multiple memo callbacks are active. Run this workflow in a runtime with AsyncLocalStorage support."));
|
|
72
|
+
if (scope === void 0)
|
|
73
|
+
return Promise.reject(new DurableActivityScopeError("durableActivity() can only be called from a ctx.memo() callback during inline workflow activation. Use yield* ctx.run() in generator workflow code outside that memo helper boundary."));
|
|
74
|
+
return scope.dispatch({
|
|
75
|
+
activity,
|
|
76
|
+
arguments: activityArguments,
|
|
77
|
+
callerStack: captureCallerStack()
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -40,6 +40,14 @@ export type ContextOperationRequest = {
|
|
|
40
40
|
* anchors the durable async-completion task token to a fixed activity.
|
|
41
41
|
*/
|
|
42
42
|
step?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Optional deterministic state key for activity sub-operations that are
|
|
45
|
+
* owned by another workflow step. Plain `ctx.run()` leaves this unset and
|
|
46
|
+
* uses `step`; memo-scoped helper activities set it to a key derived from
|
|
47
|
+
* the owning memo step, memo key, and helper-call ordinal so retry and
|
|
48
|
+
* heartbeat state cannot collide with the parent memo step.
|
|
49
|
+
*/
|
|
50
|
+
activityStateKey?: string;
|
|
43
51
|
/** Serialized interceptor headers (Map entries) for remote worker propagation. */
|
|
44
52
|
headers?: [string, string][];
|
|
45
53
|
} | {
|
|
@@ -122,6 +130,13 @@ export type ContextOperationRequest = {
|
|
|
122
130
|
type: 'memo';
|
|
123
131
|
operationId: string;
|
|
124
132
|
key: string;
|
|
133
|
+
/**
|
|
134
|
+
* Workflow step index for the owning `ctx.memo()` call. Plain async
|
|
135
|
+
* helper activity calls derive sub-operation identity from this parent
|
|
136
|
+
* step, the memo key, and their call ordinal without consuming the
|
|
137
|
+
* workflow's outer step index.
|
|
138
|
+
*/
|
|
139
|
+
step?: number;
|
|
125
140
|
fn: () => unknown;
|
|
126
141
|
callerStack?: string;
|
|
127
142
|
} | {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ActivityRetryStateKey } from './activity-retry-state.ts';
|
|
2
|
+
import type { ContextInternals } from './internals.ts';
|
|
3
|
+
import type { RunActivityRequest } from './run-operation.ts';
|
|
4
|
+
type CachedRunActivityRequestConfiguration = {
|
|
5
|
+
advanceStepIndexForCachedRetryState: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function getCachedRunActivityRequest<TResult>(internals: ContextInternals, step: number, retryStateKey: ActivityRetryStateKey, cacheResultStep: number | undefined, activityName: string, input: unknown, configuration: CachedRunActivityRequestConfiguration): RunActivityRequest<TResult>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readActivityRetryAttempt,
|
|
3
|
+
readCompletedRetrySleepCount
|
|
4
|
+
} from "./activity-retry-state.js";
|
|
5
|
+
export function getCachedRunActivityRequest(internals, step, retryStateKey, cacheResultStep, activityName, input, configuration) {
|
|
6
|
+
const cachedResultState = readCachedResultState(internals, cacheResultStep);
|
|
7
|
+
if (cachedResultState.hasCachedResult)
|
|
8
|
+
return createCachedResultRequest(internals, step, retryStateKey, cacheResultStep, activityName, input, cachedResultState.cachedResult, configuration);
|
|
9
|
+
const retryAttempt = readActivityRetryAttempt(internals, retryStateKey);
|
|
10
|
+
if (retryAttempt !== void 0)
|
|
11
|
+
return createRetryStateRequest(internals, step, retryStateKey, cacheResultStep, activityName, input, retryAttempt, configuration);
|
|
12
|
+
return {
|
|
13
|
+
...createBaseRequest(step, retryStateKey, cacheResultStep, activityName, input),
|
|
14
|
+
hasCachedResult: !1,
|
|
15
|
+
retryAttempt: 1
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function readCachedResultState(internals, cacheResultStep) {
|
|
19
|
+
if (cacheResultStep === void 0)
|
|
20
|
+
return { hasCachedResult: !1 };
|
|
21
|
+
if (internals.accumulatedResults?.has(cacheResultStep) !== !0)
|
|
22
|
+
return { hasCachedResult: !1 };
|
|
23
|
+
return {
|
|
24
|
+
cachedResult: internals.accumulatedResults.get(cacheResultStep),
|
|
25
|
+
hasCachedResult: !0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function createCachedResultRequest(internals, step, retryStateKey, cacheResultStep, activityName, input, cachedResult, configuration) {
|
|
29
|
+
if (configuration.advanceStepIndexForCachedRetryState)
|
|
30
|
+
internals.stepIndex += readCompletedRetrySleepCount(internals, retryStateKey);
|
|
31
|
+
if (internals.explainMode)
|
|
32
|
+
console.log(`[weft] ctx.run(${activityName}) \u2192 Returning cached result from step ${step}`);
|
|
33
|
+
return {
|
|
34
|
+
...createBaseRequest(step, retryStateKey, cacheResultStep, activityName, input),
|
|
35
|
+
cachedResult,
|
|
36
|
+
hasCachedResult: !0,
|
|
37
|
+
retryAttempt: 1
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function createRetryStateRequest(internals, step, retryStateKey, cacheResultStep, activityName, input, retryAttempt, configuration) {
|
|
41
|
+
if (configuration.advanceStepIndexForCachedRetryState)
|
|
42
|
+
internals.stepIndex += retryAttempt - 2;
|
|
43
|
+
return {
|
|
44
|
+
...createBaseRequest(step, retryStateKey, cacheResultStep, activityName, input),
|
|
45
|
+
hasCachedResult: !1,
|
|
46
|
+
retryAttempt
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function createBaseRequest(step, retryStateKey, cacheResultStep, activityName, input) {
|
|
50
|
+
return {
|
|
51
|
+
request: createReplayActivityOperation(activityName, input),
|
|
52
|
+
step,
|
|
53
|
+
retryStateKey,
|
|
54
|
+
...cacheResultStep === void 0 ? {} : { cacheResultStep }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function createReplayActivityOperation(activityName, input) {
|
|
58
|
+
return { type: "activity", operationId: "", activityName, input };
|
|
59
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Duration, type RetryPolicy, type WorkflowContext } from '../types.ts';
|
|
2
|
-
import { completeActivityRetryAttempt, readActivityRetryAttempt, readCompletedRetrySleepCount } from './activity-retry-state.ts';
|
|
2
|
+
import { completeActivityRetryAttempt, readActivityRetryAttempt, readCompletedRetrySleepCount, type ActivityRetryStateKey } from './activity-retry-state.ts';
|
|
3
|
+
import { type ScheduleToCloseBudget } from './activity-schedule-to-close.ts';
|
|
3
4
|
import type { Context } from './index.ts';
|
|
5
|
+
import { type ContextInternals } from './internals.ts';
|
|
4
6
|
import type { ContextOperationRequest } from './operation-request.ts';
|
|
5
7
|
/**
|
|
6
8
|
* Recover the concrete {@link Context} from the public {@link WorkflowContext}
|
|
@@ -15,25 +17,41 @@ import type { ContextOperationRequest } from './operation-request.ts';
|
|
|
15
17
|
* "Context internals not initialized" from a downstream `getInternals` call.
|
|
16
18
|
*/
|
|
17
19
|
export declare function asConcreteContext(context: WorkflowContext): Context;
|
|
18
|
-
type ActivityInput = string | (Function & {
|
|
20
|
+
export type ActivityInput = string | (Function & {
|
|
19
21
|
retry?: RetryPolicy;
|
|
20
22
|
});
|
|
21
|
-
type ActivityOperationRequest = Extract<ContextOperationRequest, {
|
|
23
|
+
export type ActivityOperationRequest = Extract<ContextOperationRequest, {
|
|
22
24
|
type: 'activity';
|
|
23
25
|
}>;
|
|
24
|
-
interface RunActivityRequest<TResult> {
|
|
26
|
+
export interface RunActivityRequest<TResult> {
|
|
25
27
|
request: ActivityOperationRequest;
|
|
26
28
|
step: number;
|
|
29
|
+
retryStateKey: ActivityRetryStateKey;
|
|
30
|
+
cacheResultStep?: number;
|
|
27
31
|
hasCachedResult: boolean;
|
|
28
32
|
cachedResult?: TResult;
|
|
29
33
|
retryAttempt: number;
|
|
30
34
|
retryPolicy?: RetryPolicy;
|
|
31
35
|
scheduleToCloseTimeout?: Duration;
|
|
32
36
|
}
|
|
37
|
+
export type ActivityRetrySleepGenerator = (duration: number, nextAttempt: number) => Generator<ContextOperationRequest, void, unknown>;
|
|
38
|
+
export interface RunActivityAtStepConfiguration {
|
|
39
|
+
explicitName?: string;
|
|
40
|
+
advanceStepIndexForCachedRetryState?: boolean;
|
|
41
|
+
retryStateKey?: ActivityRetryStateKey;
|
|
42
|
+
cacheResultStep?: number | false;
|
|
43
|
+
activityStateKey?: string;
|
|
44
|
+
retrySleep?: ActivityRetrySleepGenerator;
|
|
45
|
+
}
|
|
33
46
|
export declare const readActivityRetryAttemptForTesting: typeof readActivityRetryAttempt;
|
|
34
47
|
export declare const readCompletedRetrySleepCountForTesting: typeof readCompletedRetrySleepCount;
|
|
35
48
|
export declare const completeActivityRetryAttemptForTesting: typeof completeActivityRetryAttempt;
|
|
49
|
+
export declare function readOrInitActivityRetrySleepFireAt(context: Context, operationId: string, duration: number): number;
|
|
50
|
+
/** Parse the schedule-to-close budget and anchor it to the step's first dispatch. */
|
|
51
|
+
export declare function resolveScheduleToCloseBudget(internals: ContextInternals, retryStateKey: ActivityRetryStateKey, scheduleToCloseTimeout: Duration | undefined): ScheduleToCloseBudget | undefined;
|
|
36
52
|
export declare function shouldRetryActivityError(error: unknown, policy: RetryPolicy | undefined, attempt: number): policy is RetryPolicy;
|
|
53
|
+
export declare function prepareActivityRetryRequest(request: ActivityOperationRequest, attempt: number): ActivityOperationRequest;
|
|
37
54
|
export declare function createRunActivityRequest<TResult>(context: Context, activity: ActivityInput, rest: readonly unknown[], explicitName?: string): RunActivityRequest<TResult>;
|
|
55
|
+
export declare function createRunActivityRequestAtStep<TResult>(context: Context, activity: ActivityInput, rest: readonly unknown[], step: number, configuration?: RunActivityAtStepConfiguration): RunActivityRequest<TResult>;
|
|
38
56
|
export declare function runActivityWithRetry<TResult>(context: Context, activity: ActivityInput, rest: readonly unknown[], explicitName?: string): Generator<ContextOperationRequest, TResult, unknown>;
|
|
39
|
-
export
|
|
57
|
+
export declare function runActivityWithRetryAtStep<TResult>(context: Context, activity: ActivityInput, rest: readonly unknown[], step: number, configuration?: RunActivityAtStepConfiguration): Generator<ContextOperationRequest, TResult, unknown>;
|
|
@@ -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) {
|
|
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,7 @@ function createFreshRunActivityRequest(internals, step, activityName, activityFu
|
|
|
103
86
|
operationId: crypto.randomUUID(),
|
|
104
87
|
activityName,
|
|
105
88
|
step,
|
|
89
|
+
...activityStateKey === void 0 ? {} : { activityStateKey },
|
|
106
90
|
...activityFunction !== void 0 ? { fn: activityFunction } : {},
|
|
107
91
|
input,
|
|
108
92
|
callerStack: captureCallerStack(),
|
|
@@ -130,7 +114,7 @@ function isNonRetryableActivityError(error, policy) {
|
|
|
130
114
|
export function shouldRetryActivityError(error, policy, attempt) {
|
|
131
115
|
return policy !== void 0 && attempt < policy.maxAttempts && !isNonRetryableActivityError(error, policy);
|
|
132
116
|
}
|
|
133
|
-
function prepareActivityRetryRequest(request, attempt) {
|
|
117
|
+
export function prepareActivityRetryRequest(request, attempt) {
|
|
134
118
|
if (attempt === 1)
|
|
135
119
|
return request;
|
|
136
120
|
return {
|
|
@@ -140,45 +124,76 @@ function prepareActivityRetryRequest(request, attempt) {
|
|
|
140
124
|
};
|
|
141
125
|
}
|
|
142
126
|
export function createRunActivityRequest(context, activity, rest, explicitName) {
|
|
143
|
-
|
|
127
|
+
parseRunArguments(activity, rest);
|
|
128
|
+
const internals = getInternals(context), step = internals.stepIndex++;
|
|
129
|
+
return createRunActivityRequestAtStep(context, activity, rest, step, {
|
|
130
|
+
...explicitName === void 0 ? {} : { explicitName },
|
|
131
|
+
advanceStepIndexForCachedRetryState: !0,
|
|
132
|
+
retryStateKey: step,
|
|
133
|
+
cacheResultStep: step
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
export function createRunActivityRequestAtStep(context, activity, rest, step, configuration = {}) {
|
|
137
|
+
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, {
|
|
138
|
+
advanceStepIndexForCachedRetryState: configuration.advanceStepIndexForCachedRetryState ?? !1
|
|
139
|
+
});
|
|
144
140
|
if (cachedRequest.hasCachedResult)
|
|
145
141
|
return cachedRequest;
|
|
146
|
-
const retryPolicy = resolveActivityRetryPolicy(activity, options), scheduleToCloseTimeout = resolveActivityScheduleToCloseTimeout(activity, options)
|
|
142
|
+
const retryPolicy = resolveActivityRetryPolicy(activity, options), scheduleToCloseTimeout = resolveActivityScheduleToCloseTimeout(activity, options);
|
|
147
143
|
return {
|
|
148
|
-
request: createFreshRunActivityRequest(internals, step, activityName, activityFunction, input,
|
|
144
|
+
request: createFreshRunActivityRequest(internals, step, activityName, activityFunction, input, resolveDispatchedActivityOptions(activity, options), configuration.activityStateKey),
|
|
149
145
|
step,
|
|
146
|
+
retryStateKey,
|
|
147
|
+
...cacheResultStep === void 0 ? {} : { cacheResultStep },
|
|
150
148
|
hasCachedResult: !1,
|
|
151
149
|
retryAttempt: cachedRequest.retryAttempt,
|
|
152
150
|
...retryPolicy === void 0 ? {} : { retryPolicy },
|
|
153
151
|
...scheduleToCloseTimeout === void 0 ? {} : { scheduleToCloseTimeout }
|
|
154
152
|
};
|
|
155
153
|
}
|
|
154
|
+
function resolveDispatchedActivityOptions(activity, options) {
|
|
155
|
+
const effectiveTimeout = resolveActivityTimeout(activity, options);
|
|
156
|
+
return effectiveTimeout === void 0 ? options : { ...options, timeout: effectiveTimeout };
|
|
157
|
+
}
|
|
156
158
|
export function* runActivityWithRetry(context, activity, rest, explicitName) {
|
|
159
|
+
return yield* runPreparedActivityWithRetry(context, createRunActivityRequest(context, activity, rest, explicitName), defaultActivityRetrySleep(context));
|
|
160
|
+
}
|
|
161
|
+
export function* runActivityWithRetryAtStep(context, activity, rest, step, configuration = {}) {
|
|
162
|
+
return yield* runPreparedActivityWithRetry(context, createRunActivityRequestAtStep(context, activity, rest, step, configuration), configuration.retrySleep ?? defaultActivityRetrySleep(context));
|
|
163
|
+
}
|
|
164
|
+
function defaultActivityRetrySleep(context) {
|
|
165
|
+
return function* sleepForRetry(duration) {
|
|
166
|
+
yield* context.sleep(duration);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function* runPreparedActivityWithRetry(context, prepared, retrySleep) {
|
|
157
170
|
const {
|
|
158
171
|
request,
|
|
159
|
-
|
|
172
|
+
retryStateKey,
|
|
173
|
+
cacheResultStep,
|
|
160
174
|
hasCachedResult,
|
|
161
175
|
cachedResult,
|
|
162
176
|
retryAttempt,
|
|
163
177
|
retryPolicy,
|
|
164
178
|
scheduleToCloseTimeout
|
|
165
|
-
} =
|
|
179
|
+
} = prepared;
|
|
166
180
|
if (hasCachedResult)
|
|
167
181
|
return cachedResult;
|
|
168
|
-
const internals = getInternals(context), budget = retryPolicy === void 0 ? void 0 : resolveScheduleToCloseBudget(internals,
|
|
182
|
+
const internals = getInternals(context), budget = retryPolicy === void 0 ? void 0 : resolveScheduleToCloseBudget(internals, retryStateKey, scheduleToCloseTimeout);
|
|
169
183
|
let attempt = retryAttempt;
|
|
170
184
|
if (attempt > 1) {
|
|
171
185
|
if (retryPolicy === void 0)
|
|
172
186
|
throw Error(`Missing activity retry policy for checkpointed retry attempt ${attempt}`);
|
|
173
|
-
yield*
|
|
187
|
+
yield* retrySleep(calculateBackoff(attempt - 1, retryPolicy), attempt);
|
|
174
188
|
}
|
|
175
189
|
while (!0) {
|
|
176
190
|
if (attempt > 1)
|
|
177
191
|
assertScheduleToCloseBudgetNotExhausted(budget, request.activityName, internals.getNow());
|
|
178
192
|
try {
|
|
179
193
|
const result = yield prepareActivityRetryRequest(request, attempt);
|
|
180
|
-
|
|
181
|
-
|
|
194
|
+
if (cacheResultStep !== void 0)
|
|
195
|
+
context.accumulatedResults.set(cacheResultStep, result);
|
|
196
|
+
completeActivityRetryAttempt(internals, retryStateKey, attempt - 1);
|
|
182
197
|
return result;
|
|
183
198
|
} catch (error) {
|
|
184
199
|
if (!shouldRetryActivityError(error, retryPolicy, attempt))
|
|
@@ -186,8 +201,8 @@ export function* runActivityWithRetry(context, activity, rest, explicitName) {
|
|
|
186
201
|
const backoff = calculateBackoff(attempt, retryPolicy), now = internals.getNow();
|
|
187
202
|
assertScheduleToCloseBudgetNotExhausted(budget, request.activityName, now, now + backoff);
|
|
188
203
|
const nextAttempt = attempt + 1;
|
|
189
|
-
writeActivityRetryAttempt(internals,
|
|
190
|
-
yield*
|
|
204
|
+
writeActivityRetryAttempt(internals, retryStateKey, nextAttempt);
|
|
205
|
+
yield* retrySleep(backoff, nextAttempt);
|
|
191
206
|
attempt = nextAttempt;
|
|
192
207
|
}
|
|
193
208
|
}
|
|
@@ -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:
|
|
55
|
+
export declare function buildActivityContext(internals: EngineInternals, workflowId: string, step: ActivityHeartbeatKey, 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,7 +33,7 @@ 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
38
|
export function buildActivityContext(internals, workflowId, step, signal, completeAsync) {
|
|
33
39
|
return {
|
|
@@ -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,16 +79,18 @@ 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) {
|
|
@@ -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
|