@lostgradient/weft 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/alerting/alert-manager.d.ts +3 -1
- package/dist/alerting/alert-manager.js +5 -2
- package/dist/cli-main.js +63 -63
- package/dist/core/context/index.d.ts +2 -1
- package/dist/core/context/index.js +8 -0
- package/dist/core/context/operation-request.d.ts +2 -0
- package/dist/core/context/parallel-cache-entry.d.ts +1 -1
- package/dist/core/context/parallel-cache-entry.js +12 -8
- package/dist/core/context/parallel-operations.d.ts +1 -1
- package/dist/core/context/parallel-operations.js +50 -8
- package/dist/core/engine/async-activity-completion.d.ts +24 -54
- package/dist/core/engine/async-activity-completion.js +25 -90
- package/dist/core/engine/async-activity-records.d.ts +106 -0
- package/dist/core/engine/async-activity-records.js +124 -0
- package/dist/core/engine/bulk-operations-purge.js +2 -1
- package/dist/core/engine/callback-creators-core.js +1 -0
- package/dist/core/engine/construction.d.ts +1 -3
- package/dist/core/engine/construction.js +2 -5
- package/dist/core/engine/deferred-consume-envelope.d.ts +10 -1
- package/dist/core/engine/deferred-consume-envelope.js +9 -1
- package/dist/core/engine/engine-internal-types.d.ts +1 -0
- package/dist/core/engine/engine-runtime-helpers.d.ts +8 -0
- package/dist/core/engine/engine-runtime-helpers.js +9 -0
- package/dist/core/engine/errors.js +1 -1
- package/dist/core/engine/index.d.ts +12 -2
- package/dist/core/engine/index.js +22 -7
- package/dist/core/engine/internals.d.ts +2 -2
- package/dist/core/engine/lifecycle/recovered-services.d.ts +4 -2
- package/dist/core/engine/lifecycle/recovered-services.js +37 -7
- package/dist/core/engine/lifecycle/resume.d.ts +2 -2
- package/dist/core/engine/lifecycle/resume.js +39 -7
- package/dist/core/engine/lifecycle/shared.d.ts +34 -0
- package/dist/core/engine/lifecycle/transition.d.ts +1 -1
- package/dist/core/engine/lifecycle/transition.js +3 -3
- package/dist/core/engine/lifecycle.d.ts +1 -1
- package/dist/core/engine/memo-durable-activity.js +7 -9
- package/dist/core/engine/operations-activity.js +1 -1
- package/dist/core/engine/operations-coordination.d.ts +15 -1
- package/dist/core/engine/operations-coordination.js +43 -10
- package/dist/core/engine/ownership-options.d.ts +1 -0
- package/dist/core/engine/ownership-options.js +14 -0
- package/dist/core/engine/retention.js +4 -0
- package/dist/core/engine/schedule-run-metadata.d.ts +3 -0
- package/dist/core/engine/schedule-run-metadata.js +29 -0
- package/dist/core/engine/schedule-run.js +19 -4
- package/dist/core/engine/schedules.js +4 -3
- package/dist/core/engine/sub-operation.js +6 -13
- package/dist/core/engine/termination/cleanup.js +3 -1
- package/dist/core/types/options.d.ts +12 -0
- package/dist/core/types/services-resolution.d.ts +8 -3
- package/dist/core/types/workflow-builder.d.ts +2 -2
- package/dist/core/types/workflow-context.d.ts +27 -0
- package/dist/index.d.ts +2 -2
- package/dist/mcp/cli.js +17 -17
- package/dist/server/authorization.d.ts +3 -3
- package/dist/server/fault-to-json-rpc.d.ts +2 -1
- package/dist/server/handler.js +22 -22
- package/dist/server/index.js +15 -15
- package/dist/server/json-rpc-dispatch.d.ts +2 -2
- package/dist/server/json-rpc-parse.d.ts +1 -1
- package/dist/server/json-rpc-protocol.d.ts +2 -2
- package/dist/server/json-rpc-websocket.d.ts +1 -1
- package/dist/server/operation-fault.d.ts +3 -1
- package/dist/server/operations/async-activity.js +2 -2
- package/dist/server/principal.d.ts +2 -2
- package/dist/server/stdio-session.d.ts +4 -4
- package/dist/service-worker/index.js +13 -13
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/http.js +2 -2
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +1 -0
- package/dist/storage/interface.js +1 -1
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +1 -1
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +1 -1
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/index.js +26 -26
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { KEYS, encodeStorageKeyComponent } from "../../storage/interface.js";
|
|
2
|
+
import { decode, encode } from "../codec.js";
|
|
3
|
+
import { ActivityAsyncPendingEvent } from "../events.js";
|
|
4
|
+
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
5
|
+
const ASYNC_ACTIVITY_TOKEN_PREFIX = "async-act:v1";
|
|
6
|
+
export const ASYNC_ACTIVITY_KEY_PREFIX = "async-act:v1:";
|
|
7
|
+
export function asyncActivityWorkflowPrefix(workflowId) {
|
|
8
|
+
return `${ASYNC_ACTIVITY_KEY_PREFIX}${encodeStorageKeyComponent(workflowId)}:`;
|
|
9
|
+
}
|
|
10
|
+
function isPersistedAsyncActivity(value) {
|
|
11
|
+
if (typeof value !== "object" || value === null)
|
|
12
|
+
return !1;
|
|
13
|
+
const record = value;
|
|
14
|
+
return record.version === 1 && typeof record.token === "string" && typeof record.workflowId === "string" && typeof record.activityName === "string" && typeof record.operationId === "string" && typeof record.step === "number" && typeof record.attempt === "number" && typeof record.createdAt === "number";
|
|
15
|
+
}
|
|
16
|
+
function isPersistedOperationOutcome(value) {
|
|
17
|
+
if (typeof value !== "object" || value === null)
|
|
18
|
+
return !1;
|
|
19
|
+
const record = value;
|
|
20
|
+
if (record.status === "completed")
|
|
21
|
+
return "value" in record;
|
|
22
|
+
if (record.status === "failed")
|
|
23
|
+
return typeof record.error === "string";
|
|
24
|
+
return !1;
|
|
25
|
+
}
|
|
26
|
+
function isPersistedAsyncActivityResolution(value) {
|
|
27
|
+
if (typeof value !== "object" || value === null)
|
|
28
|
+
return !1;
|
|
29
|
+
const record = value;
|
|
30
|
+
return record.version === 1 && record.kind === "resolution" && typeof record.token === "string" && typeof record.workflowId === "string" && isPersistedOperationOutcome(record.outcome);
|
|
31
|
+
}
|
|
32
|
+
export function deriveAsyncActivityToken(workflowId, step, attempt) {
|
|
33
|
+
return `${ASYNC_ACTIVITY_TOKEN_PREFIX}:${workflowId}:${step}:${attempt}`;
|
|
34
|
+
}
|
|
35
|
+
function buildPersistPendingAsyncActivityOperation(pending) {
|
|
36
|
+
const record = {
|
|
37
|
+
version: 1,
|
|
38
|
+
token: pending.token,
|
|
39
|
+
workflowId: pending.workflowId,
|
|
40
|
+
activityName: pending.activityName,
|
|
41
|
+
operationId: pending.operationId,
|
|
42
|
+
step: pending.step,
|
|
43
|
+
attempt: pending.attempt,
|
|
44
|
+
createdAt: pending.createdAt
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
type: "put",
|
|
48
|
+
key: KEYS.asyncActivity(pending.workflowId, pending.token),
|
|
49
|
+
value: encode(record)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function buildAsyncActivityAcknowledgementOperations(pending, outcome) {
|
|
53
|
+
const record = {
|
|
54
|
+
version: 1,
|
|
55
|
+
kind: "resolution",
|
|
56
|
+
token: pending.token,
|
|
57
|
+
workflowId: pending.workflowId,
|
|
58
|
+
outcome
|
|
59
|
+
};
|
|
60
|
+
return [
|
|
61
|
+
{ type: "delete", key: KEYS.asyncActivity(pending.workflowId, pending.token) },
|
|
62
|
+
{
|
|
63
|
+
type: "put",
|
|
64
|
+
key: KEYS.asyncActivityResolution(pending.workflowId, pending.token),
|
|
65
|
+
value: encode(record)
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
export async function registerPendingAsyncActivity(internals, pending) {
|
|
70
|
+
const alreadyRegistered = internals.pendingAsyncActivities.has(pending.token);
|
|
71
|
+
internals.pendingAsyncActivities.set(pending.token, pending);
|
|
72
|
+
await commitFencedEngineWrite(internals, [buildPersistPendingAsyncActivityOperation(pending)], [], () => Error(`Async activity registration for token "${pending.token}" lost its precondition.`));
|
|
73
|
+
if (!alreadyRegistered)
|
|
74
|
+
internals.engine.dispatchEvent(new ActivityAsyncPendingEvent(pending.token, pending.operationId, pending.workflowId, pending.activityName, pending.attempt));
|
|
75
|
+
}
|
|
76
|
+
export async function recoverPendingAsyncActivities(internals) {
|
|
77
|
+
for await (const [, bytes] of internals.storage.scan(ASYNC_ACTIVITY_KEY_PREFIX)) {
|
|
78
|
+
const decoded = decode(bytes);
|
|
79
|
+
if (isPersistedAsyncActivity(decoded)) {
|
|
80
|
+
internals.pendingAsyncActivities.set(decoded.token, {
|
|
81
|
+
token: decoded.token,
|
|
82
|
+
workflowId: decoded.workflowId,
|
|
83
|
+
activityName: decoded.activityName,
|
|
84
|
+
operationId: decoded.operationId,
|
|
85
|
+
step: decoded.step,
|
|
86
|
+
attempt: decoded.attempt,
|
|
87
|
+
createdAt: decoded.createdAt
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (isPersistedAsyncActivityResolution(decoded))
|
|
92
|
+
queuePendingAsyncActivityResolution(internals, decoded.workflowId, {
|
|
93
|
+
token: decoded.token,
|
|
94
|
+
outcome: decoded.outcome,
|
|
95
|
+
timelineStatus: decoded.outcome.status,
|
|
96
|
+
timelineOutput: decoded.outcome.status === "completed" ? decoded.outcome.value : decoded.outcome.error
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function shouldBufferPendingAsyncActivityResolution(internals, workflowId) {
|
|
101
|
+
return internals.inlineStrategy !== null && !internals.inlineStrategy.hasGenerator(workflowId);
|
|
102
|
+
}
|
|
103
|
+
export function queuePendingAsyncActivityResolution(internals, workflowId, resolution) {
|
|
104
|
+
internals.pendingAsyncActivityResolutions ??= new Map;
|
|
105
|
+
const queued = internals.pendingAsyncActivityResolutions.get(workflowId) ?? [];
|
|
106
|
+
queued.push(resolution);
|
|
107
|
+
internals.pendingAsyncActivityResolutions.set(workflowId, queued);
|
|
108
|
+
}
|
|
109
|
+
export function takePendingAsyncActivityResolution(internals, workflowId, token) {
|
|
110
|
+
internals.pendingAsyncActivityResolutions ??= new Map;
|
|
111
|
+
const queued = internals.pendingAsyncActivityResolutions.get(workflowId);
|
|
112
|
+
if (queued === void 0)
|
|
113
|
+
return;
|
|
114
|
+
const index = queued.findIndex((resolution) => resolution.token === token);
|
|
115
|
+
if (index === -1)
|
|
116
|
+
return;
|
|
117
|
+
const resolution = queued[index];
|
|
118
|
+
if (resolution === void 0)
|
|
119
|
+
return;
|
|
120
|
+
queued.splice(index, 1);
|
|
121
|
+
if (queued.length === 0)
|
|
122
|
+
internals.pendingAsyncActivityResolutions.delete(workflowId);
|
|
123
|
+
return resolution;
|
|
124
|
+
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { decode } from "../codec.js";
|
|
9
9
|
import { buildIndexOperations } from "../search-attributes.js";
|
|
10
10
|
import { buildWorkflowTagIndexOperations, normalizeWorkflowTags } from "../workflow-tags.js";
|
|
11
|
-
import { asyncActivityWorkflowPrefix } from "./async-activity-
|
|
11
|
+
import { asyncActivityWorkflowPrefix } from "./async-activity-records.js";
|
|
12
12
|
import { forgetCommittedCheckpointBytes } from "./checkpoint-commit-snapshots.js";
|
|
13
13
|
import { commitFencedEngineWrite } from "./fenced-write.js";
|
|
14
14
|
import { streamWorkflowStates } from "./listing.js";
|
|
@@ -192,6 +192,7 @@ function buildBaseWorkflowDeleteKeys(state) {
|
|
|
192
192
|
KEYS.workflowHeaders(state.id),
|
|
193
193
|
KEYS.terminalCleanupNeeded(state.id),
|
|
194
194
|
KEYS.workflowConcurrencyHolder(state.id),
|
|
195
|
+
KEYS.scheduleRun(state.id),
|
|
195
196
|
KEYS.workflowHasServices(state.id),
|
|
196
197
|
KEYS.finalizerState(state.id),
|
|
197
198
|
KEYS.teardownOwed(state.id),
|
|
@@ -76,6 +76,7 @@ export function createLifecycleCallbacks(engine) {
|
|
|
76
76
|
swallowPromiseRejection: (promise) => swallowPromiseRejection(promise),
|
|
77
77
|
enforceHistoryCircuitBreaker: (workflowId) => terminateWorkflow(getInternals(engine), workflowId, "timed-out", createTerminationCallbacks(engine), HISTORY_CIRCUIT_BREAKER_REASON),
|
|
78
78
|
failWorkflowForUnavailableServices: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system"),
|
|
79
|
+
failWorkflowForRecoveryHook: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system"),
|
|
79
80
|
failWorkflowForCheckpointDecodeError: (workflowId, error) => failWorkflow(getInternals(engine), workflowId, error, createTerminationCallbacks(engine), "system")
|
|
80
81
|
};
|
|
81
82
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { AlertManager } from '../../alerting/alert-manager.ts';
|
|
2
1
|
import type { Storage as WeftStorage } from '../../storage/interface.ts';
|
|
3
2
|
import { ActivityWorkerDispatcher } from '../../workers/activity-worker-dispatcher.ts';
|
|
4
3
|
import type { ComposedWorkflowInterceptor, Interceptor } from '../interceptor.ts';
|
|
5
|
-
import { type AnyActivityDefinition, type AnyWorkflowDefinition, type
|
|
4
|
+
import { type AnyActivityDefinition, type AnyWorkflowDefinition, type RegisteredWorkflowDefinition } from '../types.ts';
|
|
6
5
|
import type { WorkflowLogRecord } from '../types/workflow-log.ts';
|
|
7
6
|
import type { EngineConstructorOptions, ExecutionStrategyBundle, RegistrationEntry, ResolvedOptions } from './engine-internal-types.ts';
|
|
8
7
|
export type KnownWorkflowNames<TWorkflows extends object> = Extract<keyof TWorkflows, string>;
|
|
@@ -60,5 +59,4 @@ export declare function createExecutionStrategyBundle(parameters: {
|
|
|
60
59
|
getLogSink?: () => ((record: WorkflowLogRecord) => void) | undefined;
|
|
61
60
|
}): ExecutionStrategyBundle;
|
|
62
61
|
export declare function createActivityWorkerDispatcher(activityExecution: EngineConstructorOptions['activityExecution']): ActivityWorkerDispatcher | null;
|
|
63
|
-
export declare function createAlertManagerForEngine(engine: EventTarget, alerts: EngineOptions['alerts'] | undefined, getNow: () => number): AlertManager | null;
|
|
64
62
|
export {};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AlertManager } from "../../alerting/alert-manager.js";
|
|
2
1
|
import { CompressedStorage } from "../../storage/compressed-storage.js";
|
|
3
2
|
import { MemoryStorage } from "../../storage/memory.js";
|
|
4
3
|
import { ActivityWorkerDispatcher } from "../../workers/activity-worker-dispatcher.js";
|
|
@@ -16,7 +15,7 @@ import {
|
|
|
16
15
|
DEFAULT_WORKER_TURN_TIMEOUT_MS,
|
|
17
16
|
MIN_WORKER_PROTOCOL_MESSAGE_BYTES
|
|
18
17
|
} from "../worker-protocol.js";
|
|
19
|
-
import { resolveOwnershipFields } from "./ownership-options.js";
|
|
18
|
+
import { resolveBackgroundTaskMode, resolveOwnershipFields } from "./ownership-options.js";
|
|
20
19
|
import {
|
|
21
20
|
normalizeHistoryPolicy,
|
|
22
21
|
normalizePayloadSizePolicy,
|
|
@@ -122,6 +121,7 @@ export function resolveEngineOptions(storage, options, getNow) {
|
|
|
122
121
|
getNow,
|
|
123
122
|
resolveWorkflowServices: options?.resolveWorkflowServices ?? null,
|
|
124
123
|
onLog: options?.onLog ?? null,
|
|
124
|
+
backgroundTaskMode: resolveBackgroundTaskMode(options),
|
|
125
125
|
...resolveBooleanDefaults(options),
|
|
126
126
|
...resolveNumericDefaults(options),
|
|
127
127
|
...resolveRetentionFields(options),
|
|
@@ -235,6 +235,3 @@ export function createActivityWorkerDispatcher(activityExecution) {
|
|
|
235
235
|
smol: activityExecution.smol ?? !1
|
|
236
236
|
}));
|
|
237
237
|
}
|
|
238
|
-
export function createAlertManagerForEngine(engine, alerts, getNow) {
|
|
239
|
-
return alerts ? new AlertManager(engine, alerts, getNow) : null;
|
|
240
|
-
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* nested coordinator surfaces up to its parent.
|
|
26
26
|
*/
|
|
27
27
|
declare const DEFERRED_CONSUME_BRAND: unique symbol;
|
|
28
|
+
declare const KEYED_RACE_RESULT_BRAND: unique symbol;
|
|
28
29
|
/**
|
|
29
30
|
* A branch result whose real value is produced by a single deferred consume that
|
|
30
31
|
* only the winning coordinator performs.
|
|
@@ -34,6 +35,12 @@ export type DeferredConsumeEnvelope = {
|
|
|
34
35
|
/** Perform the single destructive consume and return the consumed payload. */
|
|
35
36
|
readonly finalize: () => Promise<unknown>;
|
|
36
37
|
};
|
|
38
|
+
type KeyedRaceResultEnvelope = {
|
|
39
|
+
readonly [KEYED_RACE_RESULT_BRAND]: true;
|
|
40
|
+
readonly key: string;
|
|
41
|
+
readonly value: unknown;
|
|
42
|
+
};
|
|
43
|
+
export declare function createKeyedRaceResultEnvelope(key: string, value: unknown): KeyedRaceResultEnvelope;
|
|
37
44
|
/** Wrap a deferred consume into a branded envelope. */
|
|
38
45
|
export declare function createDeferredConsumeEnvelope(finalize: () => Promise<unknown>): DeferredConsumeEnvelope;
|
|
39
46
|
/** Detect an envelope by its brand symbol only (never structurally). */
|
|
@@ -43,7 +50,9 @@ export declare function isDeferredConsumeEnvelope(value: unknown): value is Defe
|
|
|
43
50
|
* cache. A winning `wait-signal` branch surfaces a {@link DeferredConsumeEnvelope}
|
|
44
51
|
* whose `finalize()` performs the single consume; a nested `ctx.all` branch
|
|
45
52
|
* surfaces an ARRAY that may hold envelopes at arbitrary positions, so arrays are
|
|
46
|
-
* walked and each element finalized.
|
|
53
|
+
* walked and each element finalized. A nested `ctx.raceKeyed()` winner is walked
|
|
54
|
+
* through its value while retaining its key. Any other value passes through
|
|
55
|
+
* untouched.
|
|
47
56
|
*
|
|
48
57
|
* This runs only on the WINNING path (race winner, or every branch of a settled
|
|
49
58
|
* `ctx.all`), so finalizing here is exactly the linearization point of "this
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
const DEFERRED_CONSUME_BRAND = Symbol("weft.deferredConsume");
|
|
1
|
+
const DEFERRED_CONSUME_BRAND = Symbol("weft.deferredConsume"), KEYED_RACE_RESULT_BRAND = Symbol("weft.keyedRaceResult");
|
|
2
|
+
export function createKeyedRaceResultEnvelope(key, value) {
|
|
3
|
+
return { [KEYED_RACE_RESULT_BRAND]: !0, key, value };
|
|
4
|
+
}
|
|
5
|
+
function isKeyedRaceResultEnvelope(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && value[KEYED_RACE_RESULT_BRAND] === !0;
|
|
7
|
+
}
|
|
2
8
|
export function createDeferredConsumeEnvelope(finalize) {
|
|
3
9
|
return { [DEFERRED_CONSUME_BRAND]: !0, finalize };
|
|
4
10
|
}
|
|
@@ -10,5 +16,7 @@ export async function finalizeAndUnwrap(value) {
|
|
|
10
16
|
return value.finalize();
|
|
11
17
|
if (Array.isArray(value))
|
|
12
18
|
return Promise.all(value.map((element) => finalizeAndUnwrap(element)));
|
|
19
|
+
if (isKeyedRaceResultEnvelope(value))
|
|
20
|
+
return { key: value.key, value: await finalizeAndUnwrap(value.value) };
|
|
13
21
|
return value;
|
|
14
22
|
}
|
|
@@ -24,6 +24,7 @@ export interface RegistrationEntry {
|
|
|
24
24
|
export interface ResolvedOptions {
|
|
25
25
|
storage: WeftStorage;
|
|
26
26
|
development: boolean;
|
|
27
|
+
backgroundTaskMode: 'automatic' | 'manual';
|
|
27
28
|
checkpointHistory: number;
|
|
28
29
|
checkpointSizeWarningThreshold: number;
|
|
29
30
|
maxNestingDepth: number;
|
|
@@ -4,6 +4,13 @@ import type { Engine } from './index.ts';
|
|
|
4
4
|
import { type EngineInternals } from './internals.ts';
|
|
5
5
|
import type { SecondInstanceDetector } from './second-instance-detector.ts';
|
|
6
6
|
export declare function isActivityDefinition(value: unknown): value is AnyActivityDefinition;
|
|
7
|
+
type EngineCreateBackgroundTaskOptions = {
|
|
8
|
+
backgroundTasks?: 'automatic' | 'manual' | undefined;
|
|
9
|
+
recover?: boolean | undefined;
|
|
10
|
+
startScheduler?: boolean | undefined;
|
|
11
|
+
};
|
|
12
|
+
export declare function validateEngineCreateBackgroundTaskOptions(options: EngineCreateBackgroundTaskOptions): void;
|
|
13
|
+
export declare function shouldStartEngineScheduler(options: EngineCreateBackgroundTaskOptions, backgroundTaskMode: 'automatic' | 'manual'): boolean;
|
|
7
14
|
export declare function createQueuedInlineWorkflowStartHandler<TWorkflows extends object, TActivities extends object>(weakEngine: WeakRef<Engine<TWorkflows, TActivities>>, channel: MessageChannel): () => void;
|
|
8
15
|
/**
|
|
9
16
|
* Drain pending inline launches for `engine` before teardown. Built with the
|
|
@@ -23,3 +30,4 @@ export declare function createCleanupIntervalTick<TWorkflows extends object, TAc
|
|
|
23
30
|
*/
|
|
24
31
|
export declare function createSecondInstanceDetectorResolver<TWorkflows extends object, TActivities extends object>(weakEngine: WeakRef<Engine<TWorkflows, TActivities>>): () => SecondInstanceDetector | null;
|
|
25
32
|
export declare function disposeEngineCleanupInterval(internals: EngineInternals): void;
|
|
33
|
+
export {};
|
|
@@ -12,6 +12,15 @@ import { swallowPromiseRejection } from "./strategy-helpers.js";
|
|
|
12
12
|
export function isActivityDefinition(value) {
|
|
13
13
|
return typeof value === "function" && typeof value.name === "string" && "execute" in value && typeof value.execute === "function";
|
|
14
14
|
}
|
|
15
|
+
export function validateEngineCreateBackgroundTaskOptions(options) {
|
|
16
|
+
if (options.backgroundTasks === "manual" && options.startScheduler === !0)
|
|
17
|
+
throw Error('startScheduler cannot be true when backgroundTasks is "manual"');
|
|
18
|
+
}
|
|
19
|
+
export function shouldStartEngineScheduler(options, backgroundTaskMode) {
|
|
20
|
+
if (backgroundTaskMode === "manual")
|
|
21
|
+
return !1;
|
|
22
|
+
return options.startScheduler ?? options.recover !== !1;
|
|
23
|
+
}
|
|
15
24
|
function inlineLaunchQueueCallbacksForEngine(engine) {
|
|
16
25
|
const lifecycleCallbacks = createLifecycleCallbacks(engine);
|
|
17
26
|
return {
|
|
@@ -43,7 +43,7 @@ export class WorkflowTypeNotRegisteredForRecoveryError extends WeftError {
|
|
|
43
43
|
const missingWorkflowCount = parameters.missingWorkflows.length, missingTypes = [
|
|
44
44
|
...new Set(parameters.missingWorkflows.map((workflow) => workflow.type))
|
|
45
45
|
].toSorted(), registeredTypes = [...parameters.registeredTypes].toSorted(), summarizedTypes = summarizeMissingWorkflowTypes(missingTypes);
|
|
46
|
-
super("WorkflowTypeNotRegisteredForRecoveryError", `Cannot recover ${missingWorkflowCount} running workflow(s): workflow type(s) not registered: ${summarizedTypes}. Register the missing workflow types before calling \`recoverAll()\`, or pass ` + "`{ acknowledgeUnknownWorkflowTypes: true }` (dangerous \u2014 see
|
|
46
|
+
super("WorkflowTypeNotRegisteredForRecoveryError", `Cannot recover ${missingWorkflowCount} running workflow(s): workflow type(s) not registered: ${summarizedTypes}. Register the missing workflow types before calling \`recoverAll()\`, or pass ` + "`{ acknowledgeUnknownWorkflowTypes: true }` (dangerous \u2014 see " + "https://github.com/stevekinney/weft/blob/main/documentation/guides/recovery-and-deploys.md#acknowledging-drift-acknowledgeunknownworkflowtypes).");
|
|
47
47
|
this.registeredTypes = registeredTypes;
|
|
48
48
|
this.missingTypes = missingTypes;
|
|
49
49
|
this.missingWorkflowSamples = parameters.missingWorkflows.slice(0, MISSING_WORKFLOW_SAMPLE_LIMIT).map((workflow) => ({ ...workflow }));
|
|
@@ -21,12 +21,12 @@ import { ScheduleHandle } from './schedule-handle.ts';
|
|
|
21
21
|
import { type WorkflowFeedListener, type WorkflowFeedRecord, type WorkflowFeedSelector } from './workflow-feed.ts';
|
|
22
22
|
export { ActivityReconciliationCapabilityError, ActivityReconciliationConflictError, ActivityReconciliationIndeterminateError, } from './activity-reconciliation.ts';
|
|
23
23
|
export { AsyncActivityTokenNotFoundError } from './async-activity-completion.ts';
|
|
24
|
-
export type { PendingAsyncActivity } from './async-activity-
|
|
24
|
+
export type { PendingAsyncActivity } from './async-activity-records.ts';
|
|
25
25
|
export type { PendingTimelineEntry, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter, } from './engine-internal-types.ts';
|
|
26
26
|
export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposedError, IdempotencyKeyPurgedError, PersistedDataIncompatibleError, StartOrSignalConflictError, WorkflowAlreadyExistsError, WorkflowConcurrencyLimitExceededError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowSuspendNotSupportedError, WorkflowTeardownPendingError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
|
|
27
27
|
export { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts';
|
|
28
28
|
export { EngineLeaseAcquisitionTimeoutError, EngineLeaseCorruptedError, EngineLeaseNotHeldError, } from './lease-errors.ts';
|
|
29
|
-
export type { RecoverAllOptions } from './lifecycle.ts';
|
|
29
|
+
export type { RecoverAllOptions, RecoveredWorkflowInfo } from './lifecycle.ts';
|
|
30
30
|
export { ScheduleHandle } from './schedule-handle.ts';
|
|
31
31
|
export type { WorkflowFeedListener, WorkflowFeedRecord, WorkflowFeedSelector, } from './workflow-feed.ts';
|
|
32
32
|
export type { EngineCreateOptions } from './engine-create-types.ts';
|
|
@@ -239,6 +239,16 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
239
239
|
list<const TAttributeKeys extends readonly AttributeFilterKey[] = readonly AttributeFilterKey[]>(filter?: TypedListFilter<TAttributeKeys>, options?: ListOptions): Promise<PaginatedResult<WorkflowSummary>>;
|
|
240
240
|
aggregate(filter: ListFilter | undefined, options: AggregateOptions): Promise<AggregateResult>;
|
|
241
241
|
getRetentionOverview(): RetentionOverview;
|
|
242
|
+
/**
|
|
243
|
+
* Run one host-driven maintenance cycle. This fires due durable timers,
|
|
244
|
+
* deletes expired update responses, applies configured retention, and
|
|
245
|
+
* re-evaluates alert rules without relying on process-local intervals.
|
|
246
|
+
*
|
|
247
|
+
* Use with `backgroundTasks: 'manual'` from a serverless alarm, Cron trigger,
|
|
248
|
+
* or another externally scheduled wake-up. Concurrent calls are safe, but a
|
|
249
|
+
* host should await each cycle before scheduling another.
|
|
250
|
+
*/
|
|
251
|
+
runMaintenance(now?: number): Promise<void>;
|
|
242
252
|
purge(filter?: ListFilter): Promise<PurgeResult>;
|
|
243
253
|
cancelAll(filter: ListFilter, options: BulkOperationDryRunOptions): Promise<BulkOperationDryRunResult>;
|
|
244
254
|
cancelAll(filter: ListFilter, options?: BulkOperationCommitOptions): Promise<BulkCancelResult>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AlertManager } from "../../alerting/alert-manager.js";
|
|
1
2
|
import {
|
|
2
3
|
KEYS,
|
|
3
4
|
requireStorageCapability
|
|
@@ -19,9 +20,9 @@ import {
|
|
|
19
20
|
} from "./aggregate.js";
|
|
20
21
|
import {
|
|
21
22
|
completeAsyncActivity as completeAsyncActivityFromInternals,
|
|
22
|
-
failAsyncActivity as failAsyncActivityFromInternals
|
|
23
|
-
recoverPendingAsyncActivities
|
|
23
|
+
failAsyncActivity as failAsyncActivityFromInternals
|
|
24
24
|
} from "./async-activity-completion.js";
|
|
25
|
+
import { recoverPendingAsyncActivities } from "./async-activity-records.js";
|
|
25
26
|
import { broadcast as broadcastFromInternals } from "./broadcast.js";
|
|
26
27
|
import {
|
|
27
28
|
cancelAll as cancelAllWorkflows,
|
|
@@ -52,7 +53,6 @@ import {
|
|
|
52
53
|
import {
|
|
53
54
|
copyWorkflowDefinition,
|
|
54
55
|
createActivityWorkerDispatcher,
|
|
55
|
-
createAlertManagerForEngine,
|
|
56
56
|
createExecutionStrategyBundle,
|
|
57
57
|
definitionEntries,
|
|
58
58
|
resolveEngineInterceptors,
|
|
@@ -70,7 +70,9 @@ import {
|
|
|
70
70
|
createQueuedInlineWorkflowStartHandler,
|
|
71
71
|
createSecondInstanceDetectorResolver,
|
|
72
72
|
drainQueuedInlineWorkflowStartsForEngine,
|
|
73
|
-
isActivityDefinition
|
|
73
|
+
isActivityDefinition,
|
|
74
|
+
shouldStartEngineScheduler,
|
|
75
|
+
validateEngineCreateBackgroundTaskOptions
|
|
74
76
|
} from "./engine-runtime-helpers.js";
|
|
75
77
|
import { EngineCreateNameMismatchError, EngineDisposedError } from "./errors.js";
|
|
76
78
|
import { assertLeaseHeldForEngineWork, commitFencedEngineWrite } from "./fenced-write.js";
|
|
@@ -222,6 +224,7 @@ function scheduleDefinitionFromInternals(internals, definition) {
|
|
|
222
224
|
|
|
223
225
|
export class Engine extends EventTarget {
|
|
224
226
|
static async create(options) {
|
|
227
|
+
validateEngineCreateBackgroundTaskOptions(options);
|
|
225
228
|
const engine = new Engine(options);
|
|
226
229
|
try {
|
|
227
230
|
await assertCompatiblePersistedDataVersion(getInternals(engine).storage);
|
|
@@ -238,7 +241,7 @@ export class Engine extends EventTarget {
|
|
|
238
241
|
await engine.#acquireLeaseIfConfigured();
|
|
239
242
|
if (options.recover !== !1)
|
|
240
243
|
await engine.recoverAll(options.acknowledgeUnknownWorkflowTypes !== void 0 ? { acknowledgeUnknownWorkflowTypes: options.acknowledgeUnknownWorkflowTypes } : {});
|
|
241
|
-
if (options.
|
|
244
|
+
if (shouldStartEngineScheduler(options, getInternals(engine).options.backgroundTaskMode))
|
|
242
245
|
getInternals(engine).scheduler.start();
|
|
243
246
|
} catch (error) {
|
|
244
247
|
await engine[Symbol.asyncDispose]();
|
|
@@ -341,7 +344,7 @@ export class Engine extends EventTarget {
|
|
|
341
344
|
cleanupInterval: null,
|
|
342
345
|
secondInstanceDetectionInterval: null,
|
|
343
346
|
testToken: consumeNextEngineLeakWarningTokenForTesting()
|
|
344
|
-
}, cleanupInterval = setInterval(createCleanupIntervalTick(weakEngine, cleanupIntervalDisposalTracker), 60000);
|
|
347
|
+
}, cleanupInterval = resolvedOptions.backgroundTaskMode === "automatic" ? setInterval(createCleanupIntervalTick(weakEngine, cleanupIntervalDisposalTracker), 60000) : null;
|
|
345
348
|
cleanupIntervalDisposalTracker.cleanupInterval = cleanupInterval;
|
|
346
349
|
getInternals(this).cleanupInterval = cleanupInterval;
|
|
347
350
|
getInternals(this).cleanupIntervalDisposalTracker = cleanupIntervalDisposalTracker;
|
|
@@ -364,7 +367,7 @@ export class Engine extends EventTarget {
|
|
|
364
367
|
getInternals(this).workflowVisibilityWatermarkExpiresAt = void 0;
|
|
365
368
|
getInternals(this).activityWorkerDispatcher = createActivityWorkerDispatcher(options?.activityExecution);
|
|
366
369
|
getInternals(this).strategy.onMessage(this.#handleStrategyMessage.bind(this));
|
|
367
|
-
getInternals(this).alertManager =
|
|
370
|
+
getInternals(this).alertManager = options?.alerts ? new AlertManager(this, options.alerts, getNow, resolvedOptions.backgroundTaskMode === "automatic") : null;
|
|
368
371
|
this.#ensureRetentionSweepInterval();
|
|
369
372
|
this.#startSecondInstanceDetection();
|
|
370
373
|
}
|
|
@@ -586,6 +589,18 @@ export class Engine extends EventTarget {
|
|
|
586
589
|
getRetentionOverview() {
|
|
587
590
|
return getRetentionOverviewSnapshot(getInternals(this), (type) => resolveWorkflowTypeRetention(getInternals(this), type));
|
|
588
591
|
}
|
|
592
|
+
async runMaintenance(now = getInternals(this).options.getNow()) {
|
|
593
|
+
const internals = getInternals(this);
|
|
594
|
+
await internals.scheduler.tick(now);
|
|
595
|
+
try {
|
|
596
|
+
await internals.updateCoordinator.cleanupExpiredResponses();
|
|
597
|
+
} catch (error) {
|
|
598
|
+
this.#createTerminationCallbacks().handleCleanupError("cleanupExpiredResponses", error);
|
|
599
|
+
}
|
|
600
|
+
if (this.#hasConfiguredRetention())
|
|
601
|
+
await this.#runRetentionSweep();
|
|
602
|
+
internals.alertManager?.tick();
|
|
603
|
+
}
|
|
589
604
|
async purge(filter) {
|
|
590
605
|
return purgeWorkflows(getInternals(this), filter, (workflowId) => cleanupWaitersFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks()));
|
|
591
606
|
}
|
|
@@ -186,13 +186,13 @@ export interface EngineInternals {
|
|
|
186
186
|
* by their durable task token. Mirrored to storage (`KEYS.asyncActivity`) and
|
|
187
187
|
* reloaded by `recoverAll()`. See `async-activity-completion.ts`.
|
|
188
188
|
*/
|
|
189
|
-
pendingAsyncActivities: Map<string, import('./async-activity-
|
|
189
|
+
pendingAsyncActivities: Map<string, import('./async-activity-records.ts').PendingAsyncActivity>;
|
|
190
190
|
/**
|
|
191
191
|
* Completed or failed async-activity tokens that were consumed before inline
|
|
192
192
|
* recovery adopted the workflow generator. Replay drains these by workflow id
|
|
193
193
|
* when it reaches the same deterministic async-activity token again.
|
|
194
194
|
*/
|
|
195
|
-
pendingAsyncActivityResolutions: Map<string, import('./async-activity-
|
|
195
|
+
pendingAsyncActivityResolutions: Map<string, import('./async-activity-records.ts').PendingAsyncActivityResolution[]>;
|
|
196
196
|
pendingStarts: Set<string>;
|
|
197
197
|
pendingScheduleCreations: Set<string>;
|
|
198
198
|
workflowsNeedingTerminalCleanup: Set<string>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WorkflowState } from '../../types.ts';
|
|
1
|
+
import type { WorkflowServicesResolverInfo, WorkflowState } from '../../types.ts';
|
|
2
2
|
import type { EngineInternals } from '../internals.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Re-provide a recovered inline workflow's non-serialized `services` before its
|
|
@@ -36,7 +36,9 @@ import type { EngineInternals } from '../internals.ts';
|
|
|
36
36
|
* @param onCommitError - Records a fail-warn when `failRun` itself throws, so the
|
|
37
37
|
* swallowed terminal-commit fault is still observable.
|
|
38
38
|
*/
|
|
39
|
-
export declare function reprovideRecoveredServices(internals: EngineInternals, state: WorkflowState, failRun: (workflowId: string, error: Error) => Promise<void>, onCommitError: (source: string, error: unknown, workflowId: string) => void, dispatchDiagnostic?: (event: Event) => void): Promise<boolean>;
|
|
39
|
+
export declare function reprovideRecoveredServices(internals: EngineInternals, state: WorkflowState, failRun: (workflowId: string, error: Error) => Promise<void>, onCommitError: (source: string, error: unknown, workflowId: string) => void, dispatchDiagnostic?: (event: Event) => void, resolverInfo?: WorkflowServicesResolverInfo): Promise<boolean>;
|
|
40
|
+
/** Build the durable recovery context shared by the services resolver and recovery hook. */
|
|
41
|
+
export declare function workflowServicesResolverInfoFromState(internals: EngineInternals, state: WorkflowState): Promise<WorkflowServicesResolverInfo>;
|
|
40
42
|
/**
|
|
41
43
|
* The canonical terminal error for any run whose services could not be provided —
|
|
42
44
|
* both recovery re-provision paths and a scheduled-occurrence launch. Shared so
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { KEYS } from "../../../storage/interface.js";
|
|
2
2
|
import { DevelopmentWarningEvent } from "../../events.js";
|
|
3
|
+
import { decodeScheduleRunMetadata } from "../schedule-run-metadata.js";
|
|
4
|
+
import { loadScheduleState } from "../storage-io.js";
|
|
3
5
|
const RESOLVE_WORKFLOW_SERVICES_OPTION_PATH = "EngineOptions.resolveWorkflowServices";
|
|
4
|
-
export async function reprovideRecoveredServices(internals, state, failRun, onCommitError, dispatchDiagnostic = () => {}) {
|
|
6
|
+
export async function reprovideRecoveredServices(internals, state, failRun, onCommitError, dispatchDiagnostic = () => {}, resolverInfo) {
|
|
5
7
|
const resolver = internals.options.resolveWorkflowServices;
|
|
6
8
|
if (internals.inlineStrategy === null)
|
|
7
9
|
return !1;
|
|
@@ -20,13 +22,9 @@ export async function reprovideRecoveredServices(internals, state, failRun, onCo
|
|
|
20
22
|
return !0;
|
|
21
23
|
}
|
|
22
24
|
let reason;
|
|
25
|
+
const info = resolverInfo ?? await workflowServicesResolverInfoFromState(internals, state);
|
|
23
26
|
try {
|
|
24
|
-
const resolution = await resolver(
|
|
25
|
-
workflowId: state.id,
|
|
26
|
-
workflowType: state.type,
|
|
27
|
-
input: state.input,
|
|
28
|
-
launchOptions: launchOptionsFromWorkflowState(state)
|
|
29
|
-
});
|
|
27
|
+
const resolution = await resolver(info);
|
|
30
28
|
if (resolution.status === "available") {
|
|
31
29
|
internals.workflowServices.set(state.id, resolution.services);
|
|
32
30
|
return !1;
|
|
@@ -42,6 +40,16 @@ export async function reprovideRecoveredServices(internals, state, failRun, onCo
|
|
|
42
40
|
}
|
|
43
41
|
return !0;
|
|
44
42
|
}
|
|
43
|
+
export async function workflowServicesResolverInfoFromState(internals, state) {
|
|
44
|
+
const schedule = await scheduleFromWorkflowState(internals, state);
|
|
45
|
+
return {
|
|
46
|
+
workflowId: state.id,
|
|
47
|
+
workflowType: state.type,
|
|
48
|
+
input: state.input,
|
|
49
|
+
launchOptions: launchOptionsFromWorkflowState(state),
|
|
50
|
+
...schedule !== null ? { schedule } : {}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
45
53
|
export function unavailableServicesError(workflowId, reason) {
|
|
46
54
|
return Error(`Workflow "${workflowId}" services unavailable: ${reason}`);
|
|
47
55
|
}
|
|
@@ -54,3 +62,25 @@ function launchOptionsFromWorkflowState(state) {
|
|
|
54
62
|
...state.tags !== void 0 && state.tags.length > 0 ? { tags: [...state.tags] } : {}
|
|
55
63
|
};
|
|
56
64
|
}
|
|
65
|
+
async function scheduleFromWorkflowState(internals, state) {
|
|
66
|
+
const bytes = await internals.storage.get(KEYS.scheduleRun(state.id));
|
|
67
|
+
if (bytes === null)
|
|
68
|
+
return null;
|
|
69
|
+
const metadata = decodeScheduleRunMetadata(bytes);
|
|
70
|
+
if (metadata === null)
|
|
71
|
+
return null;
|
|
72
|
+
const scheduleState = await loadScheduleState(internals, metadata.id);
|
|
73
|
+
if (scheduleState === null)
|
|
74
|
+
return null;
|
|
75
|
+
if (scheduleState.workflowType !== state.type)
|
|
76
|
+
return null;
|
|
77
|
+
if (scheduleState.overlap === "allow")
|
|
78
|
+
return metadata.occurrence !== void 0 ? metadata : null;
|
|
79
|
+
if (scheduleState.currentWorkflowId === state.id)
|
|
80
|
+
return metadata;
|
|
81
|
+
if (metadata.occurrence !== void 0 && scheduleState.nextFireAt === metadata.occurrence)
|
|
82
|
+
return metadata;
|
|
83
|
+
if (metadata.occurrence === void 0 && scheduleState.overlap === "queue" && scheduleState.queuedRuns > 0)
|
|
84
|
+
return metadata;
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type WorkflowHandle } from '../handles.ts';
|
|
2
2
|
import type { EngineInternals } from '../internals.ts';
|
|
3
|
-
import { type LifecycleCallbacks } from './shared.ts';
|
|
4
|
-
export declare function resumeWorkflowFromStorage(internals: EngineInternals, workflowId: string, dispatchResumedEvent: boolean, callbacks: LifecycleCallbacks): Promise<WorkflowHandle>;
|
|
3
|
+
import { type LifecycleCallbacks, type RecoverAllOptions } from './shared.ts';
|
|
4
|
+
export declare function resumeWorkflowFromStorage(internals: EngineInternals, workflowId: string, dispatchResumedEvent: boolean, callbacks: LifecycleCallbacks, onRecoveredWorkflow?: RecoverAllOptions['onRecoveredWorkflow']): Promise<WorkflowHandle>;
|
|
@@ -15,15 +15,47 @@ import { getComposedWorkflowInterceptor } from "../strategy-helpers.js";
|
|
|
15
15
|
import { decodeWorkflowState } from "../validation.js";
|
|
16
16
|
import { buildWorkflowVisibilityIndexTransition } from "../workflow-indexes.js";
|
|
17
17
|
import { prepareResumeState } from "./persist.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
reprovideRecoveredServices,
|
|
20
|
+
workflowServicesResolverInfoFromState
|
|
21
|
+
} from "./recovered-services.js";
|
|
19
22
|
import {
|
|
20
23
|
enforceHistoryPolicyBeforeReplay,
|
|
21
24
|
loadTerminalCleanupTrackedState,
|
|
22
25
|
loadWorkflowStartHeaders,
|
|
23
26
|
setWorkflowStartHeaders
|
|
24
27
|
} from "./shared.js";
|
|
25
|
-
async function prepareRecoveredServicesOrFail(internals, state, callbacks) {
|
|
26
|
-
return reprovideRecoveredServices(internals, state, callbacks.failWorkflowForUnavailableServices, callbacks.handleCleanupError, callbacks.dispatchEvent);
|
|
28
|
+
async function prepareRecoveredServicesOrFail(internals, state, callbacks, resolverInfo) {
|
|
29
|
+
return reprovideRecoveredServices(internals, state, callbacks.failWorkflowForUnavailableServices, callbacks.handleCleanupError, callbacks.dispatchEvent, resolverInfo);
|
|
30
|
+
}
|
|
31
|
+
async function runRecoveredWorkflowHookOrFail(internals, state, handle, resolverInfo, onRecoveredWorkflow, callbacks) {
|
|
32
|
+
const info = {
|
|
33
|
+
...resolverInfo,
|
|
34
|
+
handle,
|
|
35
|
+
launchOptions: resolverInfo.launchOptions ?? { id: state.id },
|
|
36
|
+
services: internals.workflowServices.get(state.id)
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
await onRecoveredWorkflow(info);
|
|
40
|
+
return !1;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const reason = error instanceof Error ? error.message : String(error), hookError = Error(`Recovery hook failed for workflow "${state.id}": ${reason}`);
|
|
43
|
+
try {
|
|
44
|
+
await callbacks.failWorkflowForRecoveryHook(state.id, hookError);
|
|
45
|
+
} catch (commitError) {
|
|
46
|
+
callbacks.handleCleanupError("onRecoveredWorkflow", commitError, state.id);
|
|
47
|
+
}
|
|
48
|
+
return !0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function prepareRecoveredWorkflowOrFail(internals, state, callbacks, onRecoveredWorkflow) {
|
|
52
|
+
const resolverInfo = onRecoveredWorkflow === void 0 ? void 0 : await workflowServicesResolverInfoFromState(internals, state), handle = callbacks.getHandle(state.id);
|
|
53
|
+
if (await prepareRecoveredServicesOrFail(internals, state, callbacks, resolverInfo))
|
|
54
|
+
return { handle, shouldStop: !0 };
|
|
55
|
+
if (onRecoveredWorkflow === void 0 || resolverInfo === void 0)
|
|
56
|
+
return { handle, shouldStop: !1 };
|
|
57
|
+
const shouldStop = await runRecoveredWorkflowHookOrFail(internals, state, handle, resolverInfo, onRecoveredWorkflow, callbacks);
|
|
58
|
+
return { handle, shouldStop };
|
|
27
59
|
}
|
|
28
60
|
function assertResumeNotTerminating(internals, workflowId) {
|
|
29
61
|
if (internals.terminalizingWorkflows.has(workflowId))
|
|
@@ -138,7 +170,7 @@ async function reactivateSuspendedWorkflowState(internals, state) {
|
|
|
138
170
|
}) : []
|
|
139
171
|
], [], () => Error(`Resume of workflow "${state.id}" lost its CAS race.`));
|
|
140
172
|
}
|
|
141
|
-
export async function resumeWorkflowFromStorage(internals, workflowId, dispatchResumedEvent, callbacks) {
|
|
173
|
+
export async function resumeWorkflowFromStorage(internals, workflowId, dispatchResumedEvent, callbacks, onRecoveredWorkflow) {
|
|
142
174
|
const stateBytes = await internals.storage.get(KEYS.workflow(workflowId));
|
|
143
175
|
if (!stateBytes)
|
|
144
176
|
throw Error(`Workflow "${workflowId}" not found in storage`);
|
|
@@ -156,9 +188,9 @@ export async function resumeWorkflowFromStorage(internals, workflowId, dispatchR
|
|
|
156
188
|
return callbacks.getHandle(workflowId);
|
|
157
189
|
const workflowStartHeaders = await loadWorkflowStartHeaders(internals, workflowId, callbacks);
|
|
158
190
|
await loadTerminalCleanupTrackedState(internals, workflowId, callbacks);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
191
|
+
const { handle, shouldStop } = await prepareRecoveredWorkflowOrFail(internals, state, callbacks, onRecoveredWorkflow);
|
|
192
|
+
if (shouldStop)
|
|
193
|
+
return handle;
|
|
162
194
|
await callbacks.runSerializedWorkflowStateWrite(workflowId, () => performSerializedResume(internals, {
|
|
163
195
|
workflowId,
|
|
164
196
|
resumeCheckpoint,
|