@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.
Files changed (62) hide show
  1. package/README.md +4 -2
  2. package/dist/cli-main.js +63 -63
  3. package/dist/core/context/activity-retry-state.d.ts +6 -5
  4. package/dist/core/context/activity-retry-state.js +31 -21
  5. package/dist/core/context/durable-activity.d.ts +117 -0
  6. package/dist/core/context/durable-activity.js +79 -0
  7. package/dist/core/context/operation-request.d.ts +15 -0
  8. package/dist/core/context/parallel-operations.js +1 -0
  9. package/dist/core/context/run-operation-cached-request.d.ts +8 -0
  10. package/dist/core/context/run-operation-cached-request.js +59 -0
  11. package/dist/core/context/run-operation.d.ts +23 -5
  12. package/dist/core/context/run-operation.js +62 -47
  13. package/dist/core/engine/activity-heartbeat-tracking.d.ts +8 -17
  14. package/dist/core/engine/activity-heartbeat-tracking.js +7 -1
  15. package/dist/core/engine/activity-reconciliation.d.ts +1 -0
  16. package/dist/core/engine/activity-reconciliation.js +7 -2
  17. package/dist/core/engine/anonymous-signal-sequence.js +6 -4
  18. package/dist/core/engine/async-activity-completion.d.ts +5 -7
  19. package/dist/core/engine/async-activity-completion.js +13 -9
  20. package/dist/core/engine/bulk-operations-purge.js +2 -1
  21. package/dist/core/engine/bulk-operations.js +8 -7
  22. package/dist/core/engine/callback-checkpoint-persistence.d.ts +3 -0
  23. package/dist/core/engine/callback-checkpoint-persistence.js +25 -0
  24. package/dist/core/engine/callback-creators-bundles.js +4 -1
  25. package/dist/core/engine/checkpoint-io.d.ts +4 -1
  26. package/dist/core/engine/checkpoint-io.js +19 -10
  27. package/dist/core/engine/completed-review-storage.d.ts +2 -1
  28. package/dist/core/engine/completed-review-storage.js +4 -3
  29. package/dist/core/engine/index.d.ts +31 -0
  30. package/dist/core/engine/index.js +6 -1
  31. package/dist/core/engine/internals.d.ts +2 -1
  32. package/dist/core/engine/lease-manager.js +2 -2
  33. package/dist/core/engine/memo-durable-activity.d.ts +11 -0
  34. package/dist/core/engine/memo-durable-activity.js +282 -0
  35. package/dist/core/engine/operations-activity.d.ts +5 -1
  36. package/dist/core/engine/operations-activity.js +19 -7
  37. package/dist/core/engine/operations-data.d.ts +4 -1
  38. package/dist/core/engine/operations-data.js +3 -3
  39. package/dist/core/engine/reviews.js +7 -4
  40. package/dist/core/engine/schedule-timer.js +2 -0
  41. package/dist/core/engine/storage-io.js +1 -1
  42. package/dist/core/json.js +1 -1
  43. package/dist/core/weft-error.d.ts +1 -1
  44. package/dist/core/weft-error.js +2 -0
  45. package/dist/index.d.ts +2 -1
  46. package/dist/index.js +6 -0
  47. package/dist/json-schema.js +1 -1
  48. package/dist/mcp/cli.js +26 -26
  49. package/dist/server/handler.js +21 -21
  50. package/dist/server/index.js +17 -17
  51. package/dist/server/runtime/websocket-worker.js +7 -2
  52. package/dist/server/serve-internals.d.ts +28 -0
  53. package/dist/server/serve-internals.js +4 -2
  54. package/dist/service-worker/index.js +22 -22
  55. package/dist/service-worker/setup.d.ts +18 -1
  56. package/dist/service-worker/setup.js +7 -4
  57. package/dist/storage/typed-storage.d.ts +1 -1
  58. package/dist/storage/typed-storage.js +1 -1
  59. package/dist/testing/index.js +27 -27
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. 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, step: number): number | undefined;
28
- export declare function readCompletedRetrySleepCount(internals: ContextInternals, step: number): number;
29
- export declare function writeActivityRetryAttempt(internals: ContextInternals, step: number, attempt: number): void;
30
- export declare function completeActivityRetryAttempt(internals: ContextInternals, step: number, completedRetrySleepCount: number): void;
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, step: number, now: number): number;
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 assertValidRetryAttempt(attempt, step) {
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 step ${String(step)}`);
22
+ throw Error(`Invalid checkpointed activity retry attempt ${String(attempt)} for ${retryStateKeyLabel(key)}`);
13
23
  }
14
- export function readActivityRetryAttempt(internals, step) {
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[String(step)];
28
+ const attempt = state.attempts[retryStateSlotKey(key)];
19
29
  if (attempt === void 0)
20
30
  return;
21
- assertValidRetryAttempt(attempt, step);
31
+ assertValidRetryAttempt(attempt, key);
22
32
  return attempt;
23
33
  }
24
- export function readCompletedRetrySleepCount(internals, step) {
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?.[String(step)];
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 step ${String(step)}`);
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, step, attempt) {
56
- assertValidRetryAttempt(attempt, step);
65
+ export function writeActivityRetryAttempt(internals, key, attempt) {
66
+ assertValidRetryAttempt(attempt, key);
57
67
  const current = currentRetryState(internals), attempts = current ? { ...current.attempts } : {};
58
- attempts[String(step)] = attempt;
68
+ attempts[retryStateSlotKey(key)] = attempt;
59
69
  writeRetryStateSlot(internals, attempts, current?.completedRetrySleeps, current?.dispatchedAt);
60
70
  }
61
- function clearActivityRetryAttempt(internals, step) {
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[String(step)];
76
+ delete attempts[retryStateSlotKey(key)];
67
77
  writeRetryStateSlot(internals, attempts, current.completedRetrySleeps, current.dispatchedAt);
68
78
  }
69
- export function completeActivityRetryAttempt(internals, step, completedRetrySleepCount) {
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 step ${String(step)}`);
72
- clearActivityRetryAttempt(internals, step);
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[String(step)] = completedRetrySleepCount;
86
+ completedRetrySleeps[retryStateSlotKey(key)] = completedRetrySleepCount;
77
87
  writeRetryStateSlot(internals, attempts, completedRetrySleeps, current?.dispatchedAt);
78
88
  }
79
- export function readOrInitActivityDispatchedAt(internals, step, now) {
80
- const current = currentRetryState(internals), existing = current?.dispatchedAt?.[String(step)];
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 step ${String(step)}`);
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[String(step)] = now;
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
  } | {
@@ -139,6 +139,7 @@ export function* memo(context, internals, key, fn) {
139
139
  type: "memo",
140
140
  operationId,
141
141
  key,
142
+ step,
142
143
  fn,
143
144
  callerStack
144
145
  };
@@ -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>;