@lostgradient/weft 0.7.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 +4 -2
- package/dist/cli-main.js +63 -63
- 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/index.d.ts +31 -0
- package/dist/core/engine/index.js +6 -1
- package/dist/core/engine/internals.d.ts +2 -1
- package/dist/core/engine/lease-manager.js +2 -2
- 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-timer.js +2 -0
- package/dist/core/engine/storage-io.js +1 -1
- package/dist/core/json.js +1 -1
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -0
- package/dist/json-schema.js +1 -1
- package/dist/mcp/cli.js +26 -26
- package/dist/server/handler.js +21 -21
- package/dist/server/index.js +17 -17
- 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/service-worker/index.js +22 -22
- 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
|
@@ -10,6 +10,7 @@ import type { ContextInternals } from './internals.ts';
|
|
|
10
10
|
export declare const ACTIVITY_RETRY_STATE_LOCAL_KEY = "__weftActivityRetryState";
|
|
11
11
|
declare const ACTIVITY_RETRY_STATE_VERSION = 1;
|
|
12
12
|
export declare const MAX_CHECKPOINTED_RETRY_ATTEMPT = 10000;
|
|
13
|
+
export type ActivityRetryStateKey = number | string;
|
|
13
14
|
export interface ActivityRetryState {
|
|
14
15
|
version: typeof ACTIVITY_RETRY_STATE_VERSION;
|
|
15
16
|
attempts: Record<string, number>;
|
|
@@ -24,15 +25,15 @@ export interface ActivityRetryState {
|
|
|
24
25
|
dispatchedAt?: Record<string, number>;
|
|
25
26
|
}
|
|
26
27
|
export declare function isActivityRetryState(value: unknown): value is ActivityRetryState;
|
|
27
|
-
export declare function readActivityRetryAttempt(internals: ContextInternals,
|
|
28
|
-
export declare function readCompletedRetrySleepCount(internals: ContextInternals,
|
|
29
|
-
export declare function writeActivityRetryAttempt(internals: ContextInternals,
|
|
30
|
-
export declare function completeActivityRetryAttempt(internals: ContextInternals,
|
|
28
|
+
export declare function readActivityRetryAttempt(internals: ContextInternals, key: ActivityRetryStateKey): number | undefined;
|
|
29
|
+
export declare function readCompletedRetrySleepCount(internals: ContextInternals, key: ActivityRetryStateKey): number;
|
|
30
|
+
export declare function writeActivityRetryAttempt(internals: ContextInternals, key: ActivityRetryStateKey, attempt: number): void;
|
|
31
|
+
export declare function completeActivityRetryAttempt(internals: ContextInternals, key: ActivityRetryStateKey, completedRetrySleepCount: number): void;
|
|
31
32
|
/**
|
|
32
33
|
* Read the first-dispatch anchor for a step's `scheduleToCloseTimeout`, writing
|
|
33
34
|
* `now` on the first dispatch. Read-first so a crash/replay never resets the
|
|
34
35
|
* window: the budget is measured from the original dispatch, and the anchor
|
|
35
36
|
* lands in the checkpoint committed at the dispatch `yield` that follows.
|
|
36
37
|
*/
|
|
37
|
-
export declare function readOrInitActivityDispatchedAt(internals: ContextInternals,
|
|
38
|
+
export declare function readOrInitActivityDispatchedAt(internals: ContextInternals, key: ActivityRetryStateKey, now: number): number;
|
|
38
39
|
export {};
|
|
@@ -7,29 +7,39 @@ function isRecord(value) {
|
|
|
7
7
|
export function isActivityRetryState(value) {
|
|
8
8
|
return isRecord(value) && value.version === ACTIVITY_RETRY_STATE_VERSION && isRecord(value.attempts) && (value.completedRetrySleeps === void 0 || isRecord(value.completedRetrySleeps)) && (value.dispatchedAt === void 0 || isRecord(value.dispatchedAt));
|
|
9
9
|
}
|
|
10
|
-
function
|
|
10
|
+
function retryStateSlotKey(key) {
|
|
11
|
+
if (typeof key === "number")
|
|
12
|
+
return String(key);
|
|
13
|
+
if (key.length === 0)
|
|
14
|
+
throw Error("Invalid empty checkpointed activity retry state key");
|
|
15
|
+
return key;
|
|
16
|
+
}
|
|
17
|
+
function retryStateKeyLabel(key) {
|
|
18
|
+
return typeof key === "number" ? `step ${String(key)}` : `key ${key}`;
|
|
19
|
+
}
|
|
20
|
+
function assertValidRetryAttempt(attempt, key) {
|
|
11
21
|
if (!Number.isInteger(attempt) || attempt <= 1 || attempt > MAX_CHECKPOINTED_RETRY_ATTEMPT)
|
|
12
|
-
throw Error(`Invalid checkpointed activity retry attempt ${String(attempt)} for
|
|
22
|
+
throw Error(`Invalid checkpointed activity retry attempt ${String(attempt)} for ${retryStateKeyLabel(key)}`);
|
|
13
23
|
}
|
|
14
|
-
export function readActivityRetryAttempt(internals,
|
|
24
|
+
export function readActivityRetryAttempt(internals, key) {
|
|
15
25
|
const state = internals.checkpointLocals[ACTIVITY_RETRY_STATE_LOCAL_KEY];
|
|
16
26
|
if (!isActivityRetryState(state))
|
|
17
27
|
return;
|
|
18
|
-
const attempt = state.attempts[
|
|
28
|
+
const attempt = state.attempts[retryStateSlotKey(key)];
|
|
19
29
|
if (attempt === void 0)
|
|
20
30
|
return;
|
|
21
|
-
assertValidRetryAttempt(attempt,
|
|
31
|
+
assertValidRetryAttempt(attempt, key);
|
|
22
32
|
return attempt;
|
|
23
33
|
}
|
|
24
|
-
export function readCompletedRetrySleepCount(internals,
|
|
34
|
+
export function readCompletedRetrySleepCount(internals, key) {
|
|
25
35
|
const state = internals.checkpointLocals[ACTIVITY_RETRY_STATE_LOCAL_KEY];
|
|
26
36
|
if (!isActivityRetryState(state))
|
|
27
37
|
return 0;
|
|
28
|
-
const count = state.completedRetrySleeps?.[
|
|
38
|
+
const count = state.completedRetrySleeps?.[retryStateSlotKey(key)];
|
|
29
39
|
if (count === void 0)
|
|
30
40
|
return 0;
|
|
31
41
|
if (!Number.isInteger(count) || count < 0 || count > MAX_CHECKPOINTED_RETRY_ATTEMPT)
|
|
32
|
-
throw Error(`Invalid checkpointed activity retry sleep count ${String(count)} for
|
|
42
|
+
throw Error(`Invalid checkpointed activity retry sleep count ${String(count)} for ${retryStateKeyLabel(key)}`);
|
|
33
43
|
return count;
|
|
34
44
|
}
|
|
35
45
|
function currentRetryState(internals) {
|
|
@@ -52,38 +62,38 @@ function writeRetryStateSlot(internals, attempts, completedRetrySleeps, dispatch
|
|
|
52
62
|
}
|
|
53
63
|
};
|
|
54
64
|
}
|
|
55
|
-
export function writeActivityRetryAttempt(internals,
|
|
56
|
-
assertValidRetryAttempt(attempt,
|
|
65
|
+
export function writeActivityRetryAttempt(internals, key, attempt) {
|
|
66
|
+
assertValidRetryAttempt(attempt, key);
|
|
57
67
|
const current = currentRetryState(internals), attempts = current ? { ...current.attempts } : {};
|
|
58
|
-
attempts[
|
|
68
|
+
attempts[retryStateSlotKey(key)] = attempt;
|
|
59
69
|
writeRetryStateSlot(internals, attempts, current?.completedRetrySleeps, current?.dispatchedAt);
|
|
60
70
|
}
|
|
61
|
-
function clearActivityRetryAttempt(internals,
|
|
71
|
+
function clearActivityRetryAttempt(internals, key) {
|
|
62
72
|
const current = currentRetryState(internals);
|
|
63
73
|
if (!current)
|
|
64
74
|
return;
|
|
65
75
|
const attempts = { ...current.attempts };
|
|
66
|
-
delete attempts[
|
|
76
|
+
delete attempts[retryStateSlotKey(key)];
|
|
67
77
|
writeRetryStateSlot(internals, attempts, current.completedRetrySleeps, current.dispatchedAt);
|
|
68
78
|
}
|
|
69
|
-
export function completeActivityRetryAttempt(internals,
|
|
79
|
+
export function completeActivityRetryAttempt(internals, key, completedRetrySleepCount) {
|
|
70
80
|
if (!Number.isInteger(completedRetrySleepCount) || completedRetrySleepCount < 0 || completedRetrySleepCount > MAX_CHECKPOINTED_RETRY_ATTEMPT)
|
|
71
|
-
throw Error(`Invalid completed activity retry sleep count ${String(completedRetrySleepCount)} for
|
|
72
|
-
clearActivityRetryAttempt(internals,
|
|
81
|
+
throw Error(`Invalid completed activity retry sleep count ${String(completedRetrySleepCount)} for ${retryStateKeyLabel(key)}`);
|
|
82
|
+
clearActivityRetryAttempt(internals, key);
|
|
73
83
|
if (completedRetrySleepCount === 0)
|
|
74
84
|
return;
|
|
75
85
|
const current = currentRetryState(internals), attempts = current ? { ...current.attempts } : {}, completedRetrySleeps = current ? { ...current.completedRetrySleeps } : {};
|
|
76
|
-
completedRetrySleeps[
|
|
86
|
+
completedRetrySleeps[retryStateSlotKey(key)] = completedRetrySleepCount;
|
|
77
87
|
writeRetryStateSlot(internals, attempts, completedRetrySleeps, current?.dispatchedAt);
|
|
78
88
|
}
|
|
79
|
-
export function readOrInitActivityDispatchedAt(internals,
|
|
80
|
-
const current = currentRetryState(internals), existing = current?.dispatchedAt?.[
|
|
89
|
+
export function readOrInitActivityDispatchedAt(internals, key, now) {
|
|
90
|
+
const current = currentRetryState(internals), slotKey = retryStateSlotKey(key), existing = current?.dispatchedAt?.[slotKey];
|
|
81
91
|
if (typeof existing === "number" && Number.isFinite(existing))
|
|
82
92
|
return existing;
|
|
83
93
|
if (existing !== void 0)
|
|
84
|
-
throw Error(`Invalid checkpointed activity dispatch anchor ${String(existing)} for
|
|
94
|
+
throw Error(`Invalid checkpointed activity dispatch anchor ${String(existing)} for ${retryStateKeyLabel(key)}`);
|
|
85
95
|
const attempts = current ? { ...current.attempts } : {}, dispatchedAt = current?.dispatchedAt ? { ...current.dispatchedAt } : {};
|
|
86
|
-
dispatchedAt[
|
|
96
|
+
dispatchedAt[slotKey] = now;
|
|
87
97
|
writeRetryStateSlot(internals, attempts, current?.completedRetrySleeps, dispatchedAt);
|
|
88
98
|
return now;
|
|
89
99
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ActivityCallable, ActivityCallOptions } from '../types.ts';
|
|
2
|
+
import { WeftError } from '../weft-error.ts';
|
|
3
|
+
type BareActivityFunction<TInput, TResult> = ((input: TInput) => Promise<TResult> | TResult) & {
|
|
4
|
+
execute?: never;
|
|
5
|
+
};
|
|
6
|
+
type BareNoInputActivityFunction<TResult> = (() => Promise<TResult> | TResult) & {
|
|
7
|
+
execute?: never;
|
|
8
|
+
};
|
|
9
|
+
export type DurableActivityInput = string | ActivityCallable<void, unknown> | BareNoInputActivityFunction<unknown> | ActivityCallable<unknown, unknown> | BareActivityFunction<unknown, unknown>;
|
|
10
|
+
export interface DurableActivityInvocation {
|
|
11
|
+
activity: DurableActivityInput;
|
|
12
|
+
arguments: readonly unknown[];
|
|
13
|
+
callerStack: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DurableActivityScope {
|
|
16
|
+
dispatch<TResult>(invocation: DurableActivityInvocation): Promise<TResult>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when `durableActivity()` is called without an active inline `ctx.memo`
|
|
20
|
+
* durable-activity scope, or after that scope has already closed.
|
|
21
|
+
*
|
|
22
|
+
* The error usually means helper code escaped the memo callback boundary, ran
|
|
23
|
+
* under Worker execution mode, or started a helper activity without awaiting it
|
|
24
|
+
* before the memo callback returned.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { durableActivity, DurableActivityScopeError } from '@lostgradient/weft';
|
|
29
|
+
*
|
|
30
|
+
* async function sendOutsideWorkflow(): Promise<void> {
|
|
31
|
+
* try {
|
|
32
|
+
* await durableActivity('sendEmail', { userId: 'user-1' });
|
|
33
|
+
* } catch (error) {
|
|
34
|
+
* if (error instanceof DurableActivityScopeError) {
|
|
35
|
+
* console.error(error.message);
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* void sendOutsideWorkflow;
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare class DurableActivityScopeError extends WeftError<'DurableActivityScopeError'> {
|
|
44
|
+
constructor(message: string);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Thrown when helper-launched activity code reaches an activity feature that
|
|
48
|
+
* cannot safely be represented inside a plain async memo callback.
|
|
49
|
+
*
|
|
50
|
+
* The first unsupported feature is `ActivityContext.completeAsync()`: async
|
|
51
|
+
* activity completion must use `yield* ctx.run()` directly so the parked
|
|
52
|
+
* completion token stays attached to a normal workflow operation.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { durableActivity, DurableActivityUnsupportedError, workflow } from '@lostgradient/weft';
|
|
57
|
+
*
|
|
58
|
+
* export const completionWorkflow = workflow({ name: 'manual-completion' }).execute(async function* (
|
|
59
|
+
* ctx,
|
|
60
|
+
* ) {
|
|
61
|
+
* try {
|
|
62
|
+
* yield* ctx.memo('manual-completion-helper', async () =>
|
|
63
|
+
* durableActivity('manualCompletionActivity', undefined, {
|
|
64
|
+
* idempotencyKey: 'manual-completion:1',
|
|
65
|
+
* }),
|
|
66
|
+
* );
|
|
67
|
+
* } catch (error) {
|
|
68
|
+
* if (error instanceof DurableActivityUnsupportedError) {
|
|
69
|
+
* console.error(error.message);
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare class DurableActivityUnsupportedError extends WeftError<'DurableActivityUnsupportedError'> {
|
|
76
|
+
constructor(message: string);
|
|
77
|
+
}
|
|
78
|
+
export declare function runWithDurableActivityScope<TResult>(scope: DurableActivityScope, execute: () => TResult): TResult;
|
|
79
|
+
/**
|
|
80
|
+
* Run a durable activity from plain async helper code while an inline workflow
|
|
81
|
+
* is executing a `ctx.memo()` callback.
|
|
82
|
+
*
|
|
83
|
+
* `durableActivity()` gives shared async helpers the same activity dispatch
|
|
84
|
+
* machinery as `yield* ctx.run()` without converting the helper stack to
|
|
85
|
+
* generators. It is intentionally scoped: call it only from code awaited by one
|
|
86
|
+
* inline `ctx.memo()` callback, and await each helper activity sequentially
|
|
87
|
+
* before the callback returns. Activities with `idempotencyKey` get immediate
|
|
88
|
+
* lease-fenced result durability for the crash window between the activity
|
|
89
|
+
* returning and the memo checkpoint commit; unkeyed helper activities keep the
|
|
90
|
+
* normal at-least-once crash behavior.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { durableActivity, workflow } from '@lostgradient/weft';
|
|
95
|
+
*
|
|
96
|
+
* async function reserveInventory(input: { orderId: string }): Promise<{ reservationId: string }> {
|
|
97
|
+
* return { reservationId: input.orderId };
|
|
98
|
+
* }
|
|
99
|
+
*
|
|
100
|
+
* export const checkout = workflow({ name: 'checkout' }).execute(async function* (
|
|
101
|
+
* ctx,
|
|
102
|
+
* input: { orderId: string },
|
|
103
|
+
* ) {
|
|
104
|
+
* return yield* ctx.memo('reserve-inventory', async () =>
|
|
105
|
+
* durableActivity(reserveInventory, { orderId: input.orderId }, {
|
|
106
|
+
* idempotencyKey: `reserve:${input.orderId}`,
|
|
107
|
+
* }),
|
|
108
|
+
* );
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare function durableActivity<TResult = unknown>(name: string, input?: unknown, options?: ActivityCallOptions): Promise<TResult>;
|
|
113
|
+
export declare function durableActivity<TResult>(fn: ActivityCallable<void, TResult>, options?: ActivityCallOptions): Promise<TResult>;
|
|
114
|
+
export declare function durableActivity<TResult>(fn: BareNoInputActivityFunction<TResult>, options?: ActivityCallOptions): Promise<TResult>;
|
|
115
|
+
export declare function durableActivity<TInput, TResult>(fn: ActivityCallable<TInput, TResult>, input: TInput, options?: ActivityCallOptions): Promise<TResult>;
|
|
116
|
+
export declare function durableActivity<TInput, TResult>(fn: BareActivityFunction<TInput, TResult>, input: TInput, options?: ActivityCallOptions): Promise<TResult>;
|
|
117
|
+
export {};
|
|
@@ -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>;
|