@lostgradient/weft 0.2.1 → 0.3.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 +47 -22
- package/dist/cli/generated/operation-client.generated.d.ts +28 -1
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/cli-main.js +79 -79
- package/dist/client/handle-delegation.d.ts +4 -0
- package/dist/client/handle-delegation.js +6 -0
- package/dist/client/http-client-requests.d.ts +2 -0
- package/dist/client/http-client-requests.js +3 -0
- package/dist/client/http-client.d.ts +4 -1
- package/dist/client/http-client.js +9 -1
- package/dist/client/interface.d.ts +57 -2
- package/dist/client/local.d.ts +4 -1
- package/dist/client/local.js +7 -0
- package/dist/client/start-body.d.ts +7 -1
- package/dist/client/start-body.js +13 -4
- package/dist/core/codec/extension-codec.js +4 -2
- package/dist/core/codec/index.d.ts +1 -0
- package/dist/core/codec/index.js +1 -0
- package/dist/core/codec/serializer-registry.d.ts +122 -0
- package/dist/core/codec/serializer-registry.js +51 -0
- package/dist/core/context/index.d.ts +9 -0
- package/dist/core/context/internals.d.ts +9 -0
- package/dist/core/context/internals.js +3 -0
- package/dist/core/context/run-operation.d.ts +16 -3
- package/dist/core/context/run-operation.js +16 -7
- package/dist/core/engine/bulk-operations.js +1 -1
- package/dist/core/engine/construction.d.ts +0 -1
- package/dist/core/engine/construction.js +10 -1
- package/dist/core/engine/disposal.js +12 -0
- package/dist/core/engine/engine-create-types.d.ts +0 -14
- package/dist/core/engine/engine-internal-types.d.ts +12 -0
- package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
- package/dist/core/engine/engine-leak-warnings.js +4 -0
- package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
- package/dist/core/engine/engine-runtime-helpers.js +26 -5
- package/dist/core/engine/errors.d.ts +74 -0
- package/dist/core/engine/errors.js +25 -1
- package/dist/core/engine/handle-result.js +1 -1
- package/dist/core/engine/handles.d.ts +89 -40
- package/dist/core/engine/handles.js +25 -27
- package/dist/core/engine/index.d.ts +96 -4
- package/dist/core/engine/index.js +75 -4
- package/dist/core/engine/inline-launch-queue.d.ts +14 -0
- package/dist/core/engine/inline-launch-queue.js +32 -7
- package/dist/core/engine/internals.d.ts +18 -10
- package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
- package/dist/core/engine/lifecycle/persist.js +5 -20
- package/dist/core/engine/lifecycle/resume.js +25 -4
- package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
- package/dist/core/engine/lifecycle/start-commit.js +27 -0
- package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
- package/dist/core/engine/lifecycle/start-exec.js +38 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
- package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
- package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
- package/dist/core/engine/lifecycle/start.d.ts +3 -3
- package/dist/core/engine/lifecycle/start.js +31 -37
- package/dist/core/engine/lifecycle.d.ts +3 -2
- package/dist/core/engine/lifecycle.js +9 -2
- package/dist/core/engine/listing.js +1 -1
- package/dist/core/engine/persisted-data-version.d.ts +5 -9
- package/dist/core/engine/persisted-data-version.js +4 -5
- package/dist/core/engine/schedule-handle.d.ts +45 -0
- package/dist/core/engine/schedule-handle.js +26 -0
- package/dist/core/engine/schedules.d.ts +1 -1
- package/dist/core/engine/schedules.js +7 -3
- package/dist/core/engine/second-instance-detector.d.ts +96 -0
- package/dist/core/engine/second-instance-detector.js +108 -0
- package/dist/core/engine/signals.d.ts +22 -0
- package/dist/core/engine/signals.js +15 -0
- package/dist/core/engine/termination/cleanup.d.ts +25 -0
- package/dist/core/engine/termination/cleanup.js +19 -1
- package/dist/core/engine/termination/complete.js +4 -3
- package/dist/core/engine/termination/suspend.d.ts +68 -0
- package/dist/core/engine/termination/suspend.js +41 -0
- package/dist/core/engine/termination.d.ts +4 -2
- package/dist/core/engine/termination.js +2 -0
- package/dist/core/engine/validation.js +25 -1
- package/dist/core/engine/workflow-feed.d.ts +5 -3
- package/dist/core/events/event-map.d.ts +2 -1
- package/dist/core/events/workflow-events.d.ts +23 -0
- package/dist/core/events/workflow-events.js +9 -0
- package/dist/core/list-filter-validation.js +2 -1
- package/dist/core/start-workflow-validation.d.ts +22 -0
- package/dist/core/start-workflow-validation.js +11 -1
- package/dist/core/step-context.d.ts +10 -6
- package/dist/core/step-context.js +7 -15
- package/dist/core/types/activity.d.ts +6 -3
- package/dist/core/types/identity.d.ts +8 -1
- package/dist/core/types/launch-metadata.d.ts +33 -0
- package/dist/core/types/launch-metadata.js +0 -0
- package/dist/core/types/message-handles.d.ts +25 -0
- package/dist/core/types/options.d.ts +48 -54
- package/dist/core/types/reviews.d.ts +2 -1
- package/dist/core/types/services-resolution.d.ts +47 -0
- package/dist/core/types/services-resolution.js +0 -0
- package/dist/core/types/state.d.ts +11 -11
- package/dist/core/types/workflow-builder.d.ts +5 -4
- package/dist/core/types/workflow-function.d.ts +17 -0
- package/dist/core/types/workflow-snapshot.d.ts +29 -0
- package/dist/core/types/workflow-snapshot.js +0 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.js +3 -0
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +3 -1
- package/dist/diagnostics/doctor.js +6 -3
- package/dist/diagnostics/format.js +2 -2
- package/dist/diagnostics/types.d.ts +1 -0
- package/dist/diagnostics/version-check.js +6 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -1
- package/dist/json-schema.js +1 -1
- package/dist/mcp/cli.js +35 -35
- package/dist/mcp/list-filter.js +2 -1
- package/dist/mcp/session.js +1 -0
- package/dist/observability/index.js +2 -2
- package/dist/server/handler.js +30 -30
- package/dist/server/index.js +33 -33
- package/dist/server/interactive-operations.js +1 -0
- package/dist/server/operations/resume-workflow.js +2 -2
- package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
- package/dist/server/operations/start-or-signal-workflow.js +140 -0
- package/dist/server/operations/start-workflow-options.d.ts +32 -0
- package/dist/server/operations/start-workflow-options.js +63 -0
- package/dist/server/operations/start-workflow.js +7 -69
- package/dist/server/operations/suspend-workflow.d.ts +13 -0
- package/dist/server/operations/suspend-workflow.js +36 -0
- package/dist/server/rest-binding.d.ts +18 -7
- package/dist/server/rest-bindings.js +12 -0
- package/dist/server/runtime/task-dispatch.js +5 -3
- package/dist/server/runtime/task-polling.d.ts +16 -2
- package/dist/server/runtime/task-polling.js +20 -5
- package/dist/server/runtime/websocket-worker.js +8 -0
- package/dist/server/serve-internals.d.ts +8 -0
- package/dist/server/serve-internals.js +4 -2
- package/dist/server/task-state.d.ts +8 -0
- package/dist/service-worker/index.js +28 -28
- package/dist/storage/capabilities.d.ts +10 -2
- package/dist/storage/capabilities.js +2 -2
- package/dist/storage/http.js +2 -2
- package/dist/storage/index.d.ts +6 -1
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +26 -0
- package/dist/storage/interface.js +1 -1
- package/dist/storage/key-prefixes.d.ts +1 -1
- package/dist/storage/key-prefixes.js +2 -0
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon-value-mapping.d.ts +47 -0
- package/dist/storage/neon-value-mapping.js +11 -0
- package/dist/storage/neon.d.ts +108 -0
- package/dist/storage/neon.js +10 -0
- package/dist/storage/node-sqlite-loader.d.ts +71 -0
- package/dist/storage/node-sqlite-loader.js +41 -0
- package/dist/storage/node-sqlite.d.ts +1 -19
- package/dist/storage/node-sqlite.js +38 -32
- package/dist/storage/postgres-key-value-queries.d.ts +79 -0
- package/dist/storage/postgres-key-value-queries.js +63 -0
- package/dist/storage/resolve.d.ts +2 -165
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/storage-configuration.d.ts +209 -0
- package/dist/storage/storage-configuration.js +0 -0
- package/dist/storage/text-value-store.d.ts +9 -9
- package/dist/storage/turso.js +2 -2
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/index.js +33 -33
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/index.js +9 -5
- package/dist/worker/long-poll.js +4 -0
- package/dist/worker/protocol-messages.d.ts +20 -0
- package/dist/worker/protocol-schemas.d.ts +32 -0
- package/dist/worker/protocol-schemas.js +8 -4
- package/dist/worker/protocol-task-result.d.ts +28 -0
- package/dist/worker/protocol-task-result.js +76 -0
- package/dist/worker/protocol.d.ts +4 -15
- package/dist/worker/protocol.js +1 -1
- package/dist/worker/registry/fair-share.d.ts +29 -0
- package/dist/worker/registry/fair-share.js +30 -0
- package/dist/worker/registry/routing.d.ts +18 -0
- package/dist/worker/registry/routing.js +14 -0
- package/dist/worker/registry/types.d.ts +7 -0
- package/dist/worker/registry.d.ts +16 -1
- package/dist/worker/registry.js +24 -36
- package/package.json +17 -4
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { calculateBackoff } from "../scheduler.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_RETRY_POLICY
|
|
4
|
+
} from "../types.js";
|
|
5
|
+
import { getInternals, hasContextInternals } from "./internals.js";
|
|
4
6
|
import { isActivityCallOptions } from "./session-state.js";
|
|
5
7
|
import { captureCallerStack } from "./validation.js";
|
|
8
|
+
export function asConcreteContext(context) {
|
|
9
|
+
if (!hasContextInternals(context))
|
|
10
|
+
throw Error("Step-based workflows (compileStepWorkflow / ctx.step) require workflowExecutionMode: 'inline'. The worker execution strategy runs workflows with a different context that has no durable step machinery. Use the generator workflow API for worker execution mode.");
|
|
11
|
+
return context;
|
|
12
|
+
}
|
|
6
13
|
const ACTIVITY_RETRY_STATE_LOCAL_KEY = "__weftActivityRetryState", ACTIVITY_RETRY_STATE_VERSION = 1, MAX_CHECKPOINTED_RETRY_ATTEMPT = 1e4;
|
|
7
14
|
function acceptsNoActivityInput(fn) {
|
|
8
15
|
if (typeof fn !== "function")
|
|
@@ -104,7 +111,9 @@ function completeActivityRetryAttempt(internals, step, completedRetrySleepCount)
|
|
|
104
111
|
}
|
|
105
112
|
};
|
|
106
113
|
}
|
|
107
|
-
function getActivityName(activity) {
|
|
114
|
+
function getActivityName(activity, explicitName) {
|
|
115
|
+
if (explicitName !== void 0)
|
|
116
|
+
return explicitName;
|
|
108
117
|
return typeof activity === "string" ? activity : activity.name || "anonymous";
|
|
109
118
|
}
|
|
110
119
|
function getActivityFunction(activity) {
|
|
@@ -190,8 +199,8 @@ function prepareActivityRetryRequest(request, attempt) {
|
|
|
190
199
|
attempt
|
|
191
200
|
};
|
|
192
201
|
}
|
|
193
|
-
export function createRunActivityRequest(context, activity, rest) {
|
|
194
|
-
const { input, options } = parseRunArguments(activity, rest), activityName = getActivityName(activity), activityFunction = getActivityFunction(activity), internals = getInternals(context), step = internals.stepIndex++, cachedRequest = getCachedRunActivityRequest(internals, step, activityName, input);
|
|
202
|
+
export function createRunActivityRequest(context, activity, rest, explicitName) {
|
|
203
|
+
const { input, options } = parseRunArguments(activity, rest), activityName = getActivityName(activity, explicitName), activityFunction = getActivityFunction(activity), internals = getInternals(context), step = internals.stepIndex++, cachedRequest = getCachedRunActivityRequest(internals, step, activityName, input);
|
|
195
204
|
if (cachedRequest.hasCachedResult)
|
|
196
205
|
return cachedRequest;
|
|
197
206
|
const retryPolicy = resolveActivityRetryPolicy(activity, options);
|
|
@@ -203,8 +212,8 @@ export function createRunActivityRequest(context, activity, rest) {
|
|
|
203
212
|
...retryPolicy === void 0 ? {} : { retryPolicy }
|
|
204
213
|
};
|
|
205
214
|
}
|
|
206
|
-
export function* runActivityWithRetry(context, activity, rest) {
|
|
207
|
-
const { request, step, hasCachedResult, cachedResult, retryAttempt, retryPolicy } = createRunActivityRequest(context, activity, rest);
|
|
215
|
+
export function* runActivityWithRetry(context, activity, rest, explicitName) {
|
|
216
|
+
const { request, step, hasCachedResult, cachedResult, retryAttempt, retryPolicy } = createRunActivityRequest(context, activity, rest, explicitName);
|
|
208
217
|
if (hasCachedResult)
|
|
209
218
|
return cachedResult;
|
|
210
219
|
const internals = getInternals(context);
|
|
@@ -17,7 +17,7 @@ import { BULK_OPERATION_BATCH_SIZE } from "./listing.js";
|
|
|
17
17
|
import { loadWorkflowState } from "./storage-io.js";
|
|
18
18
|
import { isTerminalWorkflowStatus } from "./validation.js";
|
|
19
19
|
export { purgeInternal, TERMINAL_CLEANUP_DELAY_MS } from "./bulk-operations-purge.js";
|
|
20
|
-
const ACTIVE_WORKFLOW_STATUSES = ["pending", "running"];
|
|
20
|
+
const ACTIVE_WORKFLOW_STATUSES = ["pending", "running", "suspended"];
|
|
21
21
|
export async function purge(internals, filter, cleanupWaiters) {
|
|
22
22
|
return purgeInternal(internals, filter, { expiredOnly: !1, now: internals.options.getNow() }, cleanupWaiters);
|
|
23
23
|
}
|
|
@@ -18,7 +18,6 @@ export type EngineCreateRuntimeOptions = EngineConstructorOptions & {
|
|
|
18
18
|
workflows?: Record<string, AnyWorkflowDefinition> | undefined;
|
|
19
19
|
recover?: boolean | undefined;
|
|
20
20
|
acknowledgeUnknownWorkflowTypes?: boolean | undefined;
|
|
21
|
-
allowLegacyData?: boolean | undefined;
|
|
22
21
|
};
|
|
23
22
|
export type NormalizedWorkerExecutionConfiguration = {
|
|
24
23
|
mode: 'inline';
|
|
@@ -103,6 +103,14 @@ function resolveHistoryFields(options) {
|
|
|
103
103
|
payloadSizePolicy: normalizePayloadSizePolicy(options?.payloadSize, "options.payloadSize")
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
+
const DEFAULT_SECOND_INSTANCE_HEARTBEAT_INTERVAL_MS = 15000;
|
|
107
|
+
function resolveSecondInstanceFields(options) {
|
|
108
|
+
const enabled = defaultTo(options?.detectSecondInstance, !1), intervalMs = enabled ? normalizeRetentionDuration(options?.secondInstanceHeartbeatInterval, "options.secondInstanceHeartbeatInterval") ?? DEFAULT_SECOND_INSTANCE_HEARTBEAT_INTERVAL_MS : DEFAULT_SECOND_INSTANCE_HEARTBEAT_INTERVAL_MS;
|
|
109
|
+
return {
|
|
110
|
+
secondInstanceDetectionEnabled: enabled,
|
|
111
|
+
secondInstanceHeartbeatIntervalMs: intervalMs
|
|
112
|
+
};
|
|
113
|
+
}
|
|
106
114
|
export function resolveEngineOptions(storage, options, getNow) {
|
|
107
115
|
return {
|
|
108
116
|
storage,
|
|
@@ -111,7 +119,8 @@ export function resolveEngineOptions(storage, options, getNow) {
|
|
|
111
119
|
...resolveBooleanDefaults(options),
|
|
112
120
|
...resolveNumericDefaults(options),
|
|
113
121
|
...resolveRetentionFields(options),
|
|
114
|
-
...resolveHistoryFields(options)
|
|
122
|
+
...resolveHistoryFields(options),
|
|
123
|
+
...resolveSecondInstanceFields(options)
|
|
115
124
|
};
|
|
116
125
|
}
|
|
117
126
|
export function normalizeWorkerExecutionConfiguration(options) {
|
|
@@ -22,6 +22,7 @@ export function disposeEngine(internals) {
|
|
|
22
22
|
internals.retentionSweepInterval = null;
|
|
23
23
|
}
|
|
24
24
|
internals.nextRetentionSweepAt = null;
|
|
25
|
+
disposeSecondInstanceDetection(internals);
|
|
25
26
|
internals.handleCache.clear();
|
|
26
27
|
for (const waiter of internals.resultResolvers.values())
|
|
27
28
|
waiter.reject(new EngineDisposedError);
|
|
@@ -43,6 +44,7 @@ export function disposeEngine(internals) {
|
|
|
43
44
|
internals.checkpoints.clear();
|
|
44
45
|
internals.pendingExecutionStateOwnerId = void 0;
|
|
45
46
|
internals.workflowNestingDepths.clear();
|
|
47
|
+
internals.workflowServices.clear();
|
|
46
48
|
internals.workflowHeaders.clear();
|
|
47
49
|
internals.pendingStarts.clear();
|
|
48
50
|
internals.pendingScheduleCreations.clear();
|
|
@@ -56,3 +58,13 @@ export function disposeEngine(internals) {
|
|
|
56
58
|
internals.broadcastChannel?.close();
|
|
57
59
|
internals.broadcastChannel = null;
|
|
58
60
|
}
|
|
61
|
+
function disposeSecondInstanceDetection(internals) {
|
|
62
|
+
if (internals.secondInstanceDetectionInterval !== null) {
|
|
63
|
+
clearInterval(internals.secondInstanceDetectionInterval ?? void 0);
|
|
64
|
+
internals.secondInstanceDetectionInterval = null;
|
|
65
|
+
}
|
|
66
|
+
if (internals.secondInstanceDetector !== null) {
|
|
67
|
+
internals.secondInstanceDetector.stop();
|
|
68
|
+
internals.secondInstanceDetector = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -29,20 +29,6 @@ export type EngineCreateOptions<TWorkflowDefinitions extends Record<string, AnyW
|
|
|
29
29
|
workflows?: TWorkflowDefinitions;
|
|
30
30
|
/** Activity definitions to register before workflows. */
|
|
31
31
|
activities?: TActivityDefinitions;
|
|
32
|
-
/**
|
|
33
|
-
* Opt out of the persisted-data schema-version gate when opening a storage
|
|
34
|
-
* that contains workflow data written by `new Engine({ storage })` before
|
|
35
|
-
* the schema-version sentinel existed.
|
|
36
|
-
*
|
|
37
|
-
* The gate normally rejects any storage that already holds workflow records
|
|
38
|
-
* but lacks the sentinel — a safety check against silently classifying
|
|
39
|
-
* pre-sentinel data as schema-current. Set `allowLegacyData: true` only when
|
|
40
|
-
* you knowingly opened the same storage with the legacy constructor path and
|
|
41
|
-
* are intentionally recovering it via `Engine.create`. On success, the
|
|
42
|
-
* current schema-version sentinel is written. Remove the opt-in once the
|
|
43
|
-
* data has been migrated.
|
|
44
|
-
*/
|
|
45
|
-
allowLegacyData?: boolean;
|
|
46
32
|
} & ({
|
|
47
33
|
/**
|
|
48
34
|
* Recover stored running workflows after registration. Defaults to
|
|
@@ -29,6 +29,10 @@ export interface ResolvedOptions {
|
|
|
29
29
|
/** Operator-supplied sink for compacted event-log ranges; `null` when none. */
|
|
30
30
|
archiveAdapter: ArchiveAdapter | null;
|
|
31
31
|
payloadSizePolicy: NormalizedPayloadSizePolicy;
|
|
32
|
+
/** Whether the best-effort second-instance liveness detector is enabled. */
|
|
33
|
+
secondInstanceDetectionEnabled: boolean;
|
|
34
|
+
/** Heartbeat interval (ms) for the second-instance detector when enabled. */
|
|
35
|
+
secondInstanceHeartbeatIntervalMs: number;
|
|
32
36
|
getNow: () => number;
|
|
33
37
|
/**
|
|
34
38
|
* Re-provides the non-serialized per-run `services` value on recovery; `null`
|
|
@@ -61,4 +65,12 @@ export type QueuedInlineWorkflowExecutionStart = {
|
|
|
61
65
|
nestingDepth: number;
|
|
62
66
|
executionDeadline: number | undefined;
|
|
63
67
|
executionStateOwnerId: string;
|
|
68
|
+
/**
|
|
69
|
+
* Liveness callback invoked once this queued start has actually begun
|
|
70
|
+
* executing (its generator has been driven). Set only for `defer: false`
|
|
71
|
+
* launches, which await it before `engine.start()` resolves. Fired exactly
|
|
72
|
+
* once; a discarded start (engine disposed without draining) settles it via
|
|
73
|
+
* the dispose path so the `defer: false` awaiter never hangs.
|
|
74
|
+
*/
|
|
75
|
+
onStarted?: () => void;
|
|
64
76
|
};
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
export type EngineCleanupIntervalDisposalTracker = {
|
|
7
7
|
disposed: boolean;
|
|
8
8
|
cleanupInterval: ReturnType<typeof setInterval> | null;
|
|
9
|
+
/**
|
|
10
|
+
* The optional second-instance detector interval, tracked alongside the
|
|
11
|
+
* cleanup interval so the same finalizer clears it when an engine is
|
|
12
|
+
* garbage-collected without `[Symbol.dispose]()`. Null when detection is off.
|
|
13
|
+
*/
|
|
14
|
+
secondInstanceDetectionInterval: ReturnType<typeof setInterval> | null;
|
|
9
15
|
testToken: symbol | undefined;
|
|
10
16
|
};
|
|
11
17
|
export declare const engineCleanupIntervalFinalizer: FinalizationRegistry<EngineCleanupIntervalDisposalTracker>;
|
|
@@ -6,6 +6,10 @@ export const engineCleanupIntervalFinalizer = new FinalizationRegistry((tracker)
|
|
|
6
6
|
clearInterval(tracker.cleanupInterval);
|
|
7
7
|
tracker.cleanupInterval = null;
|
|
8
8
|
}
|
|
9
|
+
if (tracker.secondInstanceDetectionInterval !== null) {
|
|
10
|
+
clearInterval(tracker.secondInstanceDetectionInterval);
|
|
11
|
+
tracker.secondInstanceDetectionInterval = null;
|
|
12
|
+
}
|
|
9
13
|
if (!tracker.disposed && shouldEmitEngineLeakWarning()) {
|
|
10
14
|
if (tracker.testToken !== void 0)
|
|
11
15
|
engineLeakWarningTokensForTesting.add(tracker.testToken);
|
|
@@ -2,7 +2,24 @@ import type { AnyActivityDefinition } from '../types.ts';
|
|
|
2
2
|
import { type EngineCleanupIntervalDisposalTracker } from './engine-leak-warnings.ts';
|
|
3
3
|
import type { Engine } from './index.ts';
|
|
4
4
|
import { type EngineInternals } from './internals.ts';
|
|
5
|
+
import type { SecondInstanceDetector } from './second-instance-detector.ts';
|
|
5
6
|
export declare function isActivityDefinition(value: unknown): value is AnyActivityDefinition;
|
|
6
7
|
export declare function createQueuedInlineWorkflowStartHandler<TWorkflows extends object, TActivities extends object>(weakEngine: WeakRef<Engine<TWorkflows, TActivities>>, channel: MessageChannel): () => void;
|
|
8
|
+
/**
|
|
9
|
+
* Drain pending inline launches for `engine` before teardown. Built with the
|
|
10
|
+
* same inline-launch-queue callbacks as the scheduled flush handler so a drained
|
|
11
|
+
* start advances identically to a normally-flushed one. Called from
|
|
12
|
+
* `[Symbol.asyncDispose]` ahead of synchronous disposal (which aborts the signal
|
|
13
|
+
* and would otherwise discard the queue).
|
|
14
|
+
*/
|
|
15
|
+
export declare function drainQueuedInlineWorkflowStartsForEngine<TWorkflows extends object, TActivities extends object>(engine: Engine<TWorkflows, TActivities>): Promise<void>;
|
|
7
16
|
export declare function createCleanupIntervalTick<TWorkflows extends object, TActivities extends object>(weakEngine: WeakRef<Engine<TWorkflows, TActivities>>, tracker: EngineCleanupIntervalDisposalTracker): () => void;
|
|
17
|
+
/**
|
|
18
|
+
* Build the resolver the second-instance detection interval uses to find its live
|
|
19
|
+
* detector. Returns `null` when the engine has been garbage-collected or disposed
|
|
20
|
+
* (so the tick is skipped), otherwise the engine's current detector. Extracted
|
|
21
|
+
* here — like {@link createCleanupIntervalTick} — so the deref/disposed guard is
|
|
22
|
+
* directly testable without driving a real interval.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createSecondInstanceDetectorResolver<TWorkflows extends object, TActivities extends object>(weakEngine: WeakRef<Engine<TWorkflows, TActivities>>): () => SecondInstanceDetector | null;
|
|
8
25
|
export declare function disposeEngineCleanupInterval(internals: EngineInternals): void;
|
|
@@ -3,12 +3,22 @@ import { createLifecycleCallbacks, createTerminationCallbacks } from "./callback
|
|
|
3
3
|
import {
|
|
4
4
|
engineCleanupIntervalFinalizer
|
|
5
5
|
} from "./engine-leak-warnings.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
drainQueuedInlineWorkflowStarts,
|
|
8
|
+
flushQueuedInlineWorkflowStarts
|
|
9
|
+
} from "./inline-launch-queue.js";
|
|
7
10
|
import { getInternals } from "./internals.js";
|
|
8
11
|
import { swallowPromiseRejection } from "./strategy-helpers.js";
|
|
9
12
|
export function isActivityDefinition(value) {
|
|
10
13
|
return typeof value === "function" && typeof value.name === "string" && "execute" in value && typeof value.execute === "function";
|
|
11
14
|
}
|
|
15
|
+
function inlineLaunchQueueCallbacksForEngine(engine) {
|
|
16
|
+
const lifecycleCallbacks = createLifecycleCallbacks(engine);
|
|
17
|
+
return {
|
|
18
|
+
processPendingUpdatesAfterInlineAdvance: (workflowId) => lifecycleCallbacks.processPendingUpdatesAfterInlineAdvance(workflowId),
|
|
19
|
+
swallowPromiseRejection: (promise) => swallowPromiseRejection(promise)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
12
22
|
export function createQueuedInlineWorkflowStartHandler(weakEngine, channel) {
|
|
13
23
|
return function handleQueuedInlineWorkflowStart() {
|
|
14
24
|
const engine = weakEngine.deref();
|
|
@@ -18,12 +28,12 @@ export function createQueuedInlineWorkflowStartHandler(weakEngine, channel) {
|
|
|
18
28
|
return;
|
|
19
29
|
}
|
|
20
30
|
getInternals(engine).queuedInlineWorkflowStartFlushScheduled = !1;
|
|
21
|
-
swallowPromiseRejection(flushQueuedInlineWorkflowStarts(getInternals(engine),
|
|
22
|
-
processPendingUpdatesAfterInlineAdvance: (workflowId) => createLifecycleCallbacks(engine).processPendingUpdatesAfterInlineAdvance(workflowId),
|
|
23
|
-
swallowPromiseRejection: (promise) => swallowPromiseRejection(promise)
|
|
24
|
-
}));
|
|
31
|
+
swallowPromiseRejection(flushQueuedInlineWorkflowStarts(getInternals(engine), inlineLaunchQueueCallbacksForEngine(engine)));
|
|
25
32
|
};
|
|
26
33
|
}
|
|
34
|
+
export async function drainQueuedInlineWorkflowStartsForEngine(engine) {
|
|
35
|
+
await drainQueuedInlineWorkflowStarts(getInternals(engine), inlineLaunchQueueCallbacksForEngine(engine));
|
|
36
|
+
}
|
|
27
37
|
export function createCleanupIntervalTick(weakEngine, tracker) {
|
|
28
38
|
return function cleanupExpiredResponsesForLiveEngine() {
|
|
29
39
|
const engine = weakEngine.deref();
|
|
@@ -38,6 +48,17 @@ export function createCleanupIntervalTick(weakEngine, tracker) {
|
|
|
38
48
|
createExpiredResponseCleanupTick(internals.updateCoordinator, (source, error) => createTerminationCallbacks(engine).handleCleanupError(source, error))();
|
|
39
49
|
};
|
|
40
50
|
}
|
|
51
|
+
export function createSecondInstanceDetectorResolver(weakEngine) {
|
|
52
|
+
return function resolveLiveSecondInstanceDetector() {
|
|
53
|
+
const engine = weakEngine.deref();
|
|
54
|
+
if (engine === void 0)
|
|
55
|
+
return null;
|
|
56
|
+
const internals = getInternals(engine);
|
|
57
|
+
if (internals.disposed)
|
|
58
|
+
return null;
|
|
59
|
+
return internals.secondInstanceDetector;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
41
62
|
export function disposeEngineCleanupInterval(internals) {
|
|
42
63
|
if (internals.cleanupInterval !== null) {
|
|
43
64
|
clearInterval(internals.cleanupInterval ?? void 0);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkflowStatus } from '../types/identity.ts';
|
|
1
2
|
import { WeftError } from '../weft-error.ts';
|
|
2
3
|
/**
|
|
3
4
|
* Thrown by {@link Engine.start} when a workflow with the requested ID already
|
|
@@ -186,6 +187,32 @@ export declare class WorkflowNotRegisteredError extends WeftError<'WorkflowNotRe
|
|
|
186
187
|
readonly workflowType: string;
|
|
187
188
|
constructor(workflowType: string);
|
|
188
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Thrown by {@link Engine.suspend} when invoked on an engine running in worker
|
|
192
|
+
* execution mode. Suspension parks the live run without aborting it, which the
|
|
193
|
+
* inline strategy supports directly; a worker run cannot be paused without
|
|
194
|
+
* sending it a cancellation, so the engine refuses rather than silently
|
|
195
|
+
* aborting. Suspend/resume in worker mode is a scoped follow-up.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* import { Engine, WorkflowSuspendNotSupportedError } from '@lostgradient/weft';
|
|
200
|
+
*
|
|
201
|
+
* async function trySuspend(engine: Engine, id: string) {
|
|
202
|
+
* try {
|
|
203
|
+
* await engine.suspend(id);
|
|
204
|
+
* } catch (err) {
|
|
205
|
+
* if (err instanceof WorkflowSuspendNotSupportedError) {
|
|
206
|
+
* // worker-mode engine: cancel instead, or run inline
|
|
207
|
+
* }
|
|
208
|
+
* }
|
|
209
|
+
* }
|
|
210
|
+
* void trySuspend;
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export declare class WorkflowSuspendNotSupportedError extends WeftError<'WorkflowSuspendNotSupportedError'> {
|
|
214
|
+
constructor(message: string);
|
|
215
|
+
}
|
|
189
216
|
/**
|
|
190
217
|
* Thrown when the engine cannot resolve an activity name to a registered
|
|
191
218
|
* function during dispatch. The per-workflow {@link ActivityRegistry} (built
|
|
@@ -212,4 +239,51 @@ export declare class ActivityResolutionError extends WeftError<'ActivityResoluti
|
|
|
212
239
|
readonly activityName: string;
|
|
213
240
|
constructor(workflowType: string, activityName: string);
|
|
214
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Thrown by {@link Engine.startOrSignal} when the target workflow already exists
|
|
244
|
+
* and is in a terminal state (completed, failed, cancelled, or timed out). A
|
|
245
|
+
* terminal run cannot accept a signal and must not be silently replaced, so
|
|
246
|
+
* `startOrSignal` surfaces this conflict instead of starting a fresh run under
|
|
247
|
+
* the same id or dropping the signal.
|
|
248
|
+
*
|
|
249
|
+
* Inspect `workflowId` to identify the conflicting run and `status` to see why
|
|
250
|
+
* it was rejected. To deliberately start a new run, choose a different id (or
|
|
251
|
+
* idempotency key); to deliver to a fresh run, terminal-state reuse is not a
|
|
252
|
+
* supported policy.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* import { StartOrSignalConflictError } from '@lostgradient/weft';
|
|
257
|
+
*
|
|
258
|
+
* function isTerminalStartOrSignalConflict(error: unknown): boolean {
|
|
259
|
+
* return error instanceof StartOrSignalConflictError;
|
|
260
|
+
* }
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
export declare class StartOrSignalConflictError extends WeftError<'StartOrSignalConflictError'> {
|
|
264
|
+
readonly workflowId: string;
|
|
265
|
+
readonly status: WorkflowStatus;
|
|
266
|
+
constructor(workflowId: string, status: WorkflowStatus);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Thrown by {@link Engine.start} / {@link Engine.startOrSignal} when an
|
|
270
|
+
* `idempotencyKey` resolves to a workflow that no longer exists — its run was
|
|
271
|
+
* purged or bulk-deleted while the durable `start-idem:` mapping (intentionally
|
|
272
|
+
* not swept on cleanup) lived on. The key is "spent": it can neither return the
|
|
273
|
+
* gone run nor safely start a fresh one under the same key (a concurrent caller
|
|
274
|
+
* may still hold the mapping). Use a different idempotency key to start anew.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* import { IdempotencyKeyPurgedError } from '@lostgradient/weft';
|
|
279
|
+
*
|
|
280
|
+
* function isPurgedIdempotencyKey(error: unknown): boolean {
|
|
281
|
+
* return error instanceof IdempotencyKeyPurgedError;
|
|
282
|
+
* }
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
export declare class IdempotencyKeyPurgedError extends WeftError<'IdempotencyKeyPurgedError'> {
|
|
286
|
+
readonly workflowId: string;
|
|
287
|
+
constructor(workflowId: string);
|
|
288
|
+
}
|
|
215
289
|
export { PersistedDataIncompatibleError } from '../persisted-data-incompatible-error.ts';
|
|
@@ -78,13 +78,37 @@ export class WorkflowNotRegisteredError extends WeftError {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
export class WorkflowSuspendNotSupportedError extends WeftError {
|
|
82
|
+
constructor(message) {
|
|
83
|
+
super("WorkflowSuspendNotSupportedError", message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
export class ActivityResolutionError extends WeftError {
|
|
82
88
|
workflowType;
|
|
83
89
|
activityName;
|
|
84
90
|
constructor(workflowType, activityName) {
|
|
85
|
-
super("ActivityResolutionError", `No activity registered with name "${activityName}" for workflow type "${workflowType}". Register the activity via \`workflow({ name }).activities({ ... })\` on the workflow that runs it, or via \`engine.register(activityDefinition)\`
|
|
91
|
+
super("ActivityResolutionError", `No activity registered with name "${activityName}" for workflow type "${workflowType}". Register the activity via \`workflow({ name }).activities({ ... })\` on the workflow that runs it, or via \`engine.register(activityDefinition)\` to make it available to every workflow on that engine instance.`);
|
|
86
92
|
this.workflowType = workflowType;
|
|
87
93
|
this.activityName = activityName;
|
|
88
94
|
}
|
|
89
95
|
}
|
|
96
|
+
|
|
97
|
+
export class StartOrSignalConflictError extends WeftError {
|
|
98
|
+
workflowId;
|
|
99
|
+
status;
|
|
100
|
+
constructor(workflowId, status) {
|
|
101
|
+
super("StartOrSignalConflictError", `Workflow "${workflowId}" is already in terminal state "${status}" and cannot accept a startOrSignal: a terminal run cannot be signalled and is not replaced. Use a different id or idempotency key to start a new run.`);
|
|
102
|
+
this.workflowId = workflowId;
|
|
103
|
+
this.status = status;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class IdempotencyKeyPurgedError extends WeftError {
|
|
108
|
+
workflowId;
|
|
109
|
+
constructor(workflowId) {
|
|
110
|
+
super("IdempotencyKeyPurgedError", `The idempotency key maps to workflow "${workflowId}", which no longer exists (it was purged or deleted). Use a different idempotency key to start a new run.`);
|
|
111
|
+
this.workflowId = workflowId;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
90
114
|
export { PersistedDataIncompatibleError } from "../persisted-data-incompatible-error.js";
|
|
@@ -35,7 +35,7 @@ export async function bootstrapWorkflowResultResolver(internals, workflowId, wai
|
|
|
35
35
|
waiter.reject(Error(`Workflow "${workflowId}" not found in storage`));
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
if (state.status === "running" || state.status === "pending")
|
|
38
|
+
if (state.status === "running" || state.status === "pending" || state.status === "suspended")
|
|
39
39
|
return;
|
|
40
40
|
try {
|
|
41
41
|
const result = await loadWorkflowResult(internals, workflowId);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LaunchMetadata, QueryDefinition, SearchAttributeValue, SignalDefinition, SignalDeliveryOptions, UpdateDefinition, WorkflowState } from '../types.ts';
|
|
2
|
+
import type { WorkflowSnapshot } from '../types/workflow-snapshot.ts';
|
|
2
3
|
export declare function getWorkflowExecutionStartedAt(state: Pick<WorkflowState, 'createdAt' | 'startedAt'>): number;
|
|
3
4
|
export declare const HANDLE_RESULT_PROMISE: unique symbol;
|
|
4
5
|
export interface WorkflowHandleEngine extends EventTarget {
|
|
5
6
|
[HANDLE_RESULT_PROMISE](workflowId: string): Promise<unknown>;
|
|
6
7
|
cancel(workflowId: string): Promise<void>;
|
|
8
|
+
suspend(workflowId: string): Promise<void>;
|
|
9
|
+
resume(workflowId: string): Promise<WorkflowHandle>;
|
|
7
10
|
signal(workflowId: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
|
|
8
11
|
update(workflowId: string, name: string, payload?: unknown, options?: {
|
|
9
12
|
timeout?: number;
|
|
@@ -14,13 +17,13 @@ export interface WorkflowHandleEngine extends EventTarget {
|
|
|
14
17
|
addTags(workflowId: string, ...tags: string[]): Promise<void>;
|
|
15
18
|
removeTags(workflowId: string, ...tags: string[]): Promise<void>;
|
|
16
19
|
get(workflowId: string): Promise<WorkflowState | null>;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Current checkpoint step (the run's cursor) for a workflow, or `null` when no
|
|
22
|
+
* checkpoint exists. Reads the in-memory checkpoint when the run is live in
|
|
23
|
+
* this engine, otherwise the durably persisted checkpoint — so it is correct
|
|
24
|
+
* for both an in-flight run and one recovered or inspected in a fresh process.
|
|
25
|
+
*/
|
|
26
|
+
getCurrentCheckpointStep(workflowId: string): Promise<number | null>;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Handle to a running or completed workflow. Returned by {@link Engine.start}
|
|
@@ -72,6 +75,84 @@ export declare class WorkflowHandle<TResult = unknown> extends EventTarget imple
|
|
|
72
75
|
constructor(id: string, engine: WorkflowHandleEngine);
|
|
73
76
|
result(): Promise<TResult>;
|
|
74
77
|
cancel(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Suspend this workflow without terminating it: it transitions to the
|
|
80
|
+
* non-terminal `'suspended'` status, keeps its durable checkpoint, and is
|
|
81
|
+
* later resumable via {@link WorkflowHandle.resume}. Unlike `cancel()`, this
|
|
82
|
+
* does not run cancel handlers and does not settle `result()` — the result
|
|
83
|
+
* promise stays pending until a later `resume()` completes the run. A
|
|
84
|
+
* suspended workflow is NOT auto-recovered by `engine.recoverAll()`; resume it
|
|
85
|
+
* explicitly. Suspending a workflow that is not running is a no-op.
|
|
86
|
+
*/
|
|
87
|
+
suspend(): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Resume this workflow from its persisted checkpoint after it was suspended
|
|
90
|
+
* (or left `'running'` by a prior process). The run is re-driven on this
|
|
91
|
+
* engine; `result()` on this handle resolves when the resumed run completes.
|
|
92
|
+
* Throws if the workflow is in a status that cannot be resumed (terminal,
|
|
93
|
+
* pending, or not found).
|
|
94
|
+
*/
|
|
95
|
+
resume(): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Reconstruct this workflow's launch context — its original `input` and the
|
|
98
|
+
* launch options recoverable from durable state — from the persisted
|
|
99
|
+
* {@link WorkflowState}. Resolves `null` if the workflow no longer exists
|
|
100
|
+
* (never started, or purged).
|
|
101
|
+
*
|
|
102
|
+
* Designed for the post-`recoverAll()` case: a recovered handle can recover
|
|
103
|
+
* the input a run was started with (and its `id`/`tags`) without the caller
|
|
104
|
+
* keeping a side table correlating recovered workflows back to their launch
|
|
105
|
+
* context. This is an async read (it loads state) so it behaves identically
|
|
106
|
+
* on handles from `start()`, `recoverAll()`, and `getHandle()` — none of which
|
|
107
|
+
* is special-cased — rather than a sync property that would be `undefined` on
|
|
108
|
+
* a handle created without a state load.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* import { Engine } from '@lostgradient/weft';
|
|
113
|
+
*
|
|
114
|
+
* const engine = new Engine();
|
|
115
|
+
* const handles = await engine.recoverAll();
|
|
116
|
+
* for (const handle of handles) {
|
|
117
|
+
* const metadata = await handle.getLaunchMetadata();
|
|
118
|
+
* if (metadata) {
|
|
119
|
+
* // rebuild this run's dependencies from metadata.input
|
|
120
|
+
* void metadata.input;
|
|
121
|
+
* }
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
getLaunchMetadata(): Promise<LaunchMetadata | null>;
|
|
126
|
+
/**
|
|
127
|
+
* A point-in-time view of this workflow's progress: its status and current
|
|
128
|
+
* checkpoint step (cursor). Resolves `null` if the workflow no longer exists.
|
|
129
|
+
* The `status` matches `engine.get(id)` — notably it reports `'pending'` for a
|
|
130
|
+
* run whose inline start is still queued, even though its persisted status is
|
|
131
|
+
* `'running'`.
|
|
132
|
+
*
|
|
133
|
+
* Designed for observing a recovered run: after `engine.recoverAll()`, a
|
|
134
|
+
* caller can read where a resumed run currently is — and rebuild its own
|
|
135
|
+
* progress adapter to re-register the run on a live surface — without waiting
|
|
136
|
+
* for the run's final `result()`. It is an async read (loads state +
|
|
137
|
+
* checkpoint), so it behaves identically on handles from `start()`,
|
|
138
|
+
* `recoverAll()`, and `getHandle()`.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* import { Engine } from '@lostgradient/weft';
|
|
143
|
+
*
|
|
144
|
+
* const engine = new Engine();
|
|
145
|
+
* const handles = await engine.recoverAll();
|
|
146
|
+
* for (const handle of handles) {
|
|
147
|
+
* const snapshot = await handle.snapshot();
|
|
148
|
+
* if (snapshot) {
|
|
149
|
+
* // re-register a progress adapter at snapshot.step
|
|
150
|
+
* void snapshot.step;
|
|
151
|
+
* }
|
|
152
|
+
* }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
snapshot(): Promise<WorkflowSnapshot | null>;
|
|
75
156
|
signal(name: SignalDefinition): Promise<void>;
|
|
76
157
|
signal<TInput>(name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
|
|
77
158
|
signal(name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
|
|
@@ -103,35 +184,3 @@ export declare class WorkflowHandle<TResult = unknown> extends EventTarget imple
|
|
|
103
184
|
};
|
|
104
185
|
[Symbol.asyncDispose](): Promise<void>;
|
|
105
186
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Handle to a recurring schedule created by {@link Engine.schedule}. Use
|
|
108
|
-
* `handle.pause()`, `handle.resume()`, `handle.cancel()`, or
|
|
109
|
-
* `handle.update(cronExpression)` to manage the schedule lifecycle.
|
|
110
|
-
* `handle.describe()` returns the current {@link ScheduleSummary}.
|
|
111
|
-
*
|
|
112
|
-
* @example
|
|
113
|
-
* ```ts
|
|
114
|
-
* import { workflow, Engine, ScheduleHandle } from '@lostgradient/weft';
|
|
115
|
-
*
|
|
116
|
-
* const engine = new Engine();
|
|
117
|
-
* engine.register(workflow({ name: 'daily-report' }).execute(async function* () { return 'ok'; }));
|
|
118
|
-
*
|
|
119
|
-
* const handle = await engine.schedule('daily-report', null, '0 9 * * *');
|
|
120
|
-
* const typedHandle: ScheduleHandle = handle;
|
|
121
|
-
* await handle.pause();
|
|
122
|
-
* const summary = await handle.describe();
|
|
123
|
-
* void typedHandle;
|
|
124
|
-
* console.log(summary.status); // 'paused'
|
|
125
|
-
* await handle.cancel();
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
128
|
-
export declare class ScheduleHandle {
|
|
129
|
-
#private;
|
|
130
|
-
readonly id: string;
|
|
131
|
-
constructor(id: string, engine: ScheduleHandleEngine);
|
|
132
|
-
pause(): Promise<void>;
|
|
133
|
-
resume(): Promise<void>;
|
|
134
|
-
cancel(): Promise<void>;
|
|
135
|
-
update(newSpec: string | ScheduleSpec): Promise<void>;
|
|
136
|
-
describe(): Promise<ScheduleSummary>;
|
|
137
|
-
}
|
|
@@ -51,6 +51,31 @@ export class WorkflowHandle extends EventTarget {
|
|
|
51
51
|
async cancel() {
|
|
52
52
|
return this.#engine.cancel(this.id);
|
|
53
53
|
}
|
|
54
|
+
async suspend() {
|
|
55
|
+
return this.#engine.suspend(this.id);
|
|
56
|
+
}
|
|
57
|
+
async resume() {
|
|
58
|
+
await this.#engine.resume(this.id);
|
|
59
|
+
}
|
|
60
|
+
async getLaunchMetadata() {
|
|
61
|
+
const state = await this.#engine.get(this.id);
|
|
62
|
+
if (state === null)
|
|
63
|
+
return null;
|
|
64
|
+
return {
|
|
65
|
+
input: state.input,
|
|
66
|
+
launchOptions: {
|
|
67
|
+
id: state.id,
|
|
68
|
+
...state.tags !== void 0 && state.tags.length > 0 && { tags: state.tags }
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async snapshot() {
|
|
73
|
+
const state = await this.#engine.get(this.id);
|
|
74
|
+
if (state === null)
|
|
75
|
+
return null;
|
|
76
|
+
const step = await this.#engine.getCurrentCheckpointStep(this.id);
|
|
77
|
+
return { status: state.status, step: step ?? 0 };
|
|
78
|
+
}
|
|
54
79
|
async signal(nameOrDefinition, payload, options) {
|
|
55
80
|
return this.#engine.signal(this.id, messageName(nameOrDefinition), payload, options);
|
|
56
81
|
}
|
|
@@ -144,30 +169,3 @@ export class WorkflowHandle extends EventTarget {
|
|
|
144
169
|
}
|
|
145
170
|
async[Symbol.asyncDispose]() {}
|
|
146
171
|
}
|
|
147
|
-
|
|
148
|
-
export class ScheduleHandle {
|
|
149
|
-
id;
|
|
150
|
-
#engine;
|
|
151
|
-
constructor(id, engine) {
|
|
152
|
-
this.id = id;
|
|
153
|
-
this.#engine = engine;
|
|
154
|
-
}
|
|
155
|
-
async pause() {
|
|
156
|
-
await this.#engine.pauseSchedule(this.id);
|
|
157
|
-
}
|
|
158
|
-
async resume() {
|
|
159
|
-
await this.#engine.resumeSchedule(this.id);
|
|
160
|
-
}
|
|
161
|
-
async cancel() {
|
|
162
|
-
await this.#engine.cancelSchedule(this.id);
|
|
163
|
-
}
|
|
164
|
-
async update(newSpec) {
|
|
165
|
-
await this.#engine.updateSchedule(this.id, newSpec);
|
|
166
|
-
}
|
|
167
|
-
async describe() {
|
|
168
|
-
const schedule = await this.#engine.getSchedule(this.id);
|
|
169
|
-
if (!schedule)
|
|
170
|
-
throw Error(`Schedule "${this.id}" not found`);
|
|
171
|
-
return schedule;
|
|
172
|
-
}
|
|
173
|
-
}
|