@lostgradient/weft 0.15.0 → 0.17.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 +1 -1
- package/dist/alerting/alert-manager.d.ts +5 -3
- package/dist/alerting/alert-manager.js +26 -5
- package/dist/alerting/index.d.ts +1 -1
- package/dist/alerting/types.d.ts +17 -13
- package/dist/alerting/types.js +5 -0
- package/dist/cli/api.js +5 -19
- package/dist/cli/console.d.ts +12 -0
- package/dist/cli/console.js +62 -0
- package/dist/cli/generated/operation-catalog.snapshot.json +71 -0
- package/dist/cli/generated/operation-client.generated.d.ts +15 -2
- package/dist/cli/generated/operation-client.generated.js +2 -0
- package/dist/cli/help-text.d.ts +1 -1
- package/dist/cli/help-text.js +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-input.d.ts +20 -0
- package/dist/cli/json-input.js +33 -0
- package/dist/cli/parse-arguments.js +2 -0
- package/dist/cli/schedule.js +7 -9
- package/dist/cli/types.d.ts +1 -0
- package/dist/cli/workflow-commands.js +5 -25
- package/dist/cli-main.js +4 -2
- package/dist/core/engine/disposal.js +7 -0
- package/dist/core/engine/engine-runtime-helpers.d.ts +3 -1
- package/dist/core/engine/engine-runtime-helpers.js +2 -2
- package/dist/core/engine/errors.d.ts +20 -0
- package/dist/core/engine/errors.js +8 -0
- package/dist/core/engine/index.d.ts +39 -14
- package/dist/core/engine/index.js +84 -9
- package/dist/core/engine/inline-launch-queue.d.ts +6 -2
- package/dist/core/engine/inline-launch-queue.js +41 -6
- package/dist/core/engine/internals.d.ts +2 -0
- package/dist/core/engine/lease-manager.d.ts +3 -2
- package/dist/core/engine/lease-manager.js +7 -3
- package/dist/core/engine/operations-time.js +6 -0
- package/dist/core/execution-strategy.d.ts +8 -0
- package/dist/core/inline-execution-strategy.d.ts +1 -0
- package/dist/core/inline-execution-strategy.js +33 -20
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +1 -0
- package/dist/core/worker-execution-strategy.d.ts +2 -0
- package/dist/core/worker-execution-strategy.js +7 -2
- package/dist/core/worker-turn-watchdog.d.ts +4 -1
- package/dist/core/worker-turn-watchdog.js +9 -4
- package/dist/diagnostics/recommendations.js +1 -9
- package/dist/http.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -0
- package/dist/indexeddb.js +1 -1
- package/dist/json-schema.js +1 -1
- package/dist/mcp/tools.js +1 -3
- package/dist/server/authorization-scope.d.ts +1 -1
- package/dist/server/authorization-scope.js +0 -2
- package/dist/server/dashboard-assets.d.ts +37 -0
- package/dist/server/dashboard-assets.js +209 -0
- package/dist/server/index.d.ts +10 -1
- package/dist/server/index.js +11 -2
- package/dist/server/interactive-operations.js +1 -0
- package/dist/server/operations/aggregate-workflows.js +0 -7
- package/dist/server/operations/async-activity.js +5 -24
- package/dist/server/operations/bulk-cancel-workflows.js +3 -8
- package/dist/server/operations/bulk-delete-workflows.js +3 -8
- package/dist/server/operations/bulk-mutate-workflow-tags.js +3 -8
- package/dist/server/operations/bulk-operation-helpers.d.ts +2 -10
- package/dist/server/operations/bulk-operation-helpers.js +0 -6
- package/dist/server/operations/bulk-retry-failed-workflows.js +3 -8
- package/dist/server/operations/bulk-signal-workflows.js +3 -8
- package/dist/server/operations/cancel-schedule.js +1 -3
- package/dist/server/operations/cancel-workflow.js +1 -3
- package/dist/server/operations/create-schedule.js +21 -49
- package/dist/server/operations/fork-workflow.js +2 -3
- package/dist/server/operations/get-checkpoint-at.js +1 -6
- package/dist/server/operations/get-registry.js +1 -7
- package/dist/server/operations/get-retention-overview.js +2 -15
- package/dist/server/operations/get-review.js +2 -15
- package/dist/server/operations/get-schedule.js +2 -8
- package/dist/server/operations/get-stream-chunks.js +2 -6
- package/dist/server/operations/get-system-lease.js +1 -7
- package/dist/server/operations/get-system-metrics.js +0 -4
- package/dist/server/operations/get-task-diagnostics.js +2 -13
- package/dist/server/operations/get-update-result.js +1 -6
- package/dist/server/operations/get-workflow-attributes.js +2 -15
- package/dist/server/operations/get-workflow-events.js +2 -15
- package/dist/server/operations/get-workflow-observability.js +2 -5
- package/dist/server/operations/get-workflow-result.js +0 -7
- package/dist/server/operations/get-workflow-timeline.js +1 -6
- package/dist/server/operations/get-workflow.d.ts +3 -6
- package/dist/server/operations/get-workflow.js +2 -12
- package/dist/server/operations/list-alerts.d.ts +29 -0
- package/dist/server/operations/list-alerts.js +47 -0
- package/dist/server/operations/list-checkpoints.js +1 -6
- package/dist/server/operations/list-reviews.js +0 -7
- package/dist/server/operations/list-schedules.js +3 -8
- package/dist/server/operations/list-task-queues.js +0 -4
- package/dist/server/operations/list-workers.js +0 -4
- package/dist/server/operations/list-workflows.js +0 -7
- package/dist/server/operations/pause-schedule.js +1 -3
- package/dist/server/operations/purge-workflows.js +2 -5
- package/dist/server/operations/query-workflow.js +4 -15
- package/dist/server/operations/replay-workflow.js +2 -6
- package/dist/server/operations/resume-schedule.js +1 -3
- package/dist/server/operations/resume-workflow.js +1 -3
- package/dist/server/operations/schedule-faults.d.ts +9 -1
- package/dist/server/operations/schedule-faults.js +51 -0
- package/dist/server/operations/set-workflow-attributes.js +3 -15
- package/dist/server/operations/signal-workflow.js +3 -12
- package/dist/server/operations/single-workflow-tag-mutation.js +0 -7
- package/dist/server/operations/start-or-signal-workflow.js +2 -3
- package/dist/server/operations/start-workflow.js +2 -3
- package/dist/server/operations/storage-capabilities.js +1 -3
- package/dist/server/operations/storage.js +7 -13
- package/dist/server/operations/submit-review-decision.js +3 -12
- package/dist/server/operations/suspend-workflow.js +1 -3
- package/dist/server/operations/timeout-workflow.js +1 -3
- package/dist/server/operations/update-schedule.js +7 -56
- package/dist/server/operations/update-workflow.js +2 -3
- package/dist/server/operations/worker-drain.js +0 -8
- package/dist/server/rest-binding.d.ts +3 -2
- package/dist/server/rest-bindings.js +3 -0
- package/dist/server/route-model.d.ts +2 -0
- package/dist/server/route-model.js +1 -1
- package/dist/server/runtime/task-reconciliation.d.ts +10 -1
- package/dist/server/runtime/task-reconciliation.js +26 -2
- package/dist/server/serve-internals.js +12 -6
- package/dist/service-worker/index.d.ts +4 -1
- package/dist/service-worker/index.js +2 -2
- package/dist/service-worker/setup.d.ts +20 -0
- package/dist/service-worker/setup.js +5 -5
- package/dist/storage/bun-sql.js +13 -31
- package/dist/storage/indexeddb.js +28 -48
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon.js +2 -2
- package/dist/storage/postgres.js +2 -2
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.d.ts +6 -3
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web-extension.js +1 -1
- package/dist/worker/protocol-schemas.d.ts +2 -2
- package/dist/worker/protocol-schemas.js +2 -2
- package/dist/worker/protocol.js +1 -1
- package/package.json +5 -1
package/dist/cli-main.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
executeWorkflow,
|
|
22
22
|
findCliSubcommandName,
|
|
23
23
|
HELP_TEXT,
|
|
24
|
+
loadConsoleMount,
|
|
24
25
|
parseCliArguments,
|
|
25
26
|
removeRunLockfile,
|
|
26
27
|
SCHEDULE_HELP_TEXT,
|
|
@@ -53,7 +54,7 @@ if (parsedArguments.command === "version") {
|
|
|
53
54
|
console.log(HELP_TEXT);
|
|
54
55
|
process.exit(0);
|
|
55
56
|
}
|
|
56
|
-
const storage = await createStorage(parsedArguments.storage, parsedArguments.database), engine = new Engine({ storage });
|
|
57
|
+
const consoleMount = parsedArguments.console ? await loadConsoleMount() : void 0, storage = await createStorage(parsedArguments.storage, parsedArguments.database), engine = new Engine({ storage });
|
|
57
58
|
if (parsedArguments.workflows) {
|
|
58
59
|
const { loadRegistrationsFromModule } = await import("./diagnostics/validate.js"), { registerModuleExports } = await import("./cli/serve-registrations.js"), loaded = await loadRegistrationsFromModule(parsedArguments.workflows);
|
|
59
60
|
registerModuleExports(engine, loaded.registrations, loaded.activities);
|
|
@@ -61,7 +62,8 @@ if (parsedArguments.command === "version") {
|
|
|
61
62
|
console.log("No --workflows module provided; starting in inspect-only mode.");
|
|
62
63
|
const server = serve({
|
|
63
64
|
engine,
|
|
64
|
-
port: Number(parsedArguments.port)
|
|
65
|
+
port: Number(parsedArguments.port),
|
|
66
|
+
...consoleMount
|
|
65
67
|
});
|
|
66
68
|
await writeRunLockfile(server.url);
|
|
67
69
|
console.log(`Weft API running at ${new URL("/api/v1", server.url).href}`);
|
|
@@ -2,6 +2,12 @@ import { disposeEngineCleanupInterval } from "./engine-runtime-helpers.js";
|
|
|
2
2
|
import { EngineDisposedError } from "./errors.js";
|
|
3
3
|
import { disposeQueuedInlineWorkflowStarts } from "./inline-launch-queue.js";
|
|
4
4
|
import { rejectAllSleepTimerAcknowledgements } from "./sleep-timer-acknowledgements.js";
|
|
5
|
+
function settleSleepResolverReadyWaitersForTesting(internals) {
|
|
6
|
+
for (const waiters of internals.sleepResolverReadyWaitersForTesting?.values() ?? [])
|
|
7
|
+
for (const notifyReady of waiters)
|
|
8
|
+
notifyReady();
|
|
9
|
+
internals.sleepResolverReadyWaitersForTesting?.clear();
|
|
10
|
+
}
|
|
5
11
|
export function disposeEngine(internals) {
|
|
6
12
|
internals.disposed = !0;
|
|
7
13
|
internals.alertManager?.[Symbol.dispose]();
|
|
@@ -48,6 +54,7 @@ export function disposeEngine(internals) {
|
|
|
48
54
|
internals.pendingWebhooks.clear();
|
|
49
55
|
internals.sleepResolvers.clear();
|
|
50
56
|
internals.sleepResolversByWorkflow.clear();
|
|
57
|
+
settleSleepResolverReadyWaitersForTesting(internals);
|
|
51
58
|
internals.sleepTimerAcknowledgementWaiters.clear();
|
|
52
59
|
internals.durableInlineOperations.clear();
|
|
53
60
|
internals.sleepTimersFiredWithoutResolver.clear();
|
|
@@ -19,7 +19,9 @@ export declare function createQueuedInlineWorkflowStartHandler<TWorkflows extend
|
|
|
19
19
|
* `[Symbol.asyncDispose]` ahead of synchronous disposal (which aborts the signal
|
|
20
20
|
* and would otherwise discard the queue).
|
|
21
21
|
*/
|
|
22
|
-
export declare function drainQueuedInlineWorkflowStartsForEngine<TWorkflows extends object, TActivities extends object>(engine: Engine<TWorkflows, TActivities
|
|
22
|
+
export declare function drainQueuedInlineWorkflowStartsForEngine<TWorkflows extends object, TActivities extends object>(engine: Engine<TWorkflows, TActivities>, options?: {
|
|
23
|
+
abortStartedWorkflows?: boolean;
|
|
24
|
+
}): Promise<void>;
|
|
23
25
|
export declare function createCleanupIntervalTick<TWorkflows extends object, TActivities extends object>(weakEngine: WeakRef<Engine<TWorkflows, TActivities>>, tracker: EngineCleanupIntervalDisposalTracker): () => void;
|
|
24
26
|
/**
|
|
25
27
|
* Build the resolver the second-instance detection interval uses to find its live
|
|
@@ -40,8 +40,8 @@ export function createQueuedInlineWorkflowStartHandler(weakEngine, channel) {
|
|
|
40
40
|
swallowPromiseRejection(flushQueuedInlineWorkflowStarts(getInternals(engine), inlineLaunchQueueCallbacksForEngine(engine)));
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
export async function drainQueuedInlineWorkflowStartsForEngine(engine) {
|
|
44
|
-
await drainQueuedInlineWorkflowStarts(getInternals(engine), inlineLaunchQueueCallbacksForEngine(engine));
|
|
43
|
+
export async function drainQueuedInlineWorkflowStartsForEngine(engine, options) {
|
|
44
|
+
await drainQueuedInlineWorkflowStarts(getInternals(engine), inlineLaunchQueueCallbacksForEngine(engine), options);
|
|
45
45
|
}
|
|
46
46
|
export function createCleanupIntervalTick(weakEngine, tracker) {
|
|
47
47
|
return function cleanupExpiredResponsesForLiveEngine() {
|
|
@@ -163,6 +163,26 @@ export declare class EngineCreateNameMismatchError extends WeftError<'EngineCrea
|
|
|
163
163
|
export declare class EngineDisposedError extends WeftError<'EngineDisposedError'> {
|
|
164
164
|
constructor();
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Thrown when queued inline workflow draining fails during asynchronous engine
|
|
168
|
+
* disposal. The engine is still fully torn down, and `leaseReleased` reports
|
|
169
|
+
* whether the final fenced lease delete committed. The original drain failure
|
|
170
|
+
* is available as `cause`.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* import { EngineDisposalError } from '@lostgradient/weft';
|
|
175
|
+
*
|
|
176
|
+
* function releaseSucceeded(error: unknown): boolean {
|
|
177
|
+
* return error instanceof EngineDisposalError && error.leaseReleased;
|
|
178
|
+
* }
|
|
179
|
+
* void releaseSucceeded;
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export declare class EngineDisposalError extends WeftError<'EngineDisposalError'> {
|
|
183
|
+
readonly leaseReleased: boolean;
|
|
184
|
+
constructor(cause: unknown, leaseReleased: boolean);
|
|
185
|
+
}
|
|
166
186
|
/**
|
|
167
187
|
* Thrown by engine APIs that need a workflow to be present in storage but
|
|
168
188
|
* cannot find one with the given ID. Inspect `workflowId` to identify the
|
|
@@ -70,6 +70,14 @@ export class EngineDisposedError extends WeftError {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export class EngineDisposalError extends WeftError {
|
|
74
|
+
leaseReleased;
|
|
75
|
+
constructor(cause, leaseReleased) {
|
|
76
|
+
super("EngineDisposalError", "Engine disposal failed while draining queued work", { cause });
|
|
77
|
+
this.leaseReleased = leaseReleased;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
export class WorkflowNotFoundError extends WeftError {
|
|
74
82
|
workflowId;
|
|
75
83
|
constructor(workflowId) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AlertStateSnapshot } from '../../alerting/types.ts';
|
|
1
2
|
import { type Storage as WeftStorage } from '../../storage/interface.ts';
|
|
2
3
|
import { type ActivityMetadata, type ActivityRegistrationOptions, type RegisteredActivityFunction } from '../activity-registry.ts';
|
|
3
4
|
import type { StoredStreamChunk } from '../context.ts';
|
|
@@ -24,7 +25,7 @@ export { ActivityReconciliationCapabilityError, ActivityReconciliationConflictEr
|
|
|
24
25
|
export { AsyncActivityTokenNotFoundError } from './async-activity-completion.ts';
|
|
25
26
|
export type { PendingAsyncActivity } from './async-activity-records.ts';
|
|
26
27
|
export type { PendingTimelineEntry, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter, } from './engine-internal-types.ts';
|
|
27
|
-
export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposedError, IdempotencyKeyPurgedError, PersistedDataIncompatibleError, StartOrSignalConflictError, WorkflowAlreadyExistsError, WorkflowConcurrencyLimitExceededError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowSuspendNotSupportedError, WorkflowTeardownPendingError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
|
|
28
|
+
export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposalError, EngineDisposedError, IdempotencyKeyPurgedError, PersistedDataIncompatibleError, StartOrSignalConflictError, WorkflowAlreadyExistsError, WorkflowConcurrencyLimitExceededError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowSuspendNotSupportedError, WorkflowTeardownPendingError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
|
|
28
29
|
export { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts';
|
|
29
30
|
export { EngineLeaseAcquisitionTimeoutError, EngineLeaseCorruptedError, EngineLeaseNotHeldError, } from './lease-errors.ts';
|
|
30
31
|
export type { EngineLeaseHealth, LeaseLostReason } from './lease-health.ts';
|
|
@@ -38,6 +39,30 @@ export { assertCompatiblePersistedDataVersion };
|
|
|
38
39
|
export declare const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING: unique symbol;
|
|
39
40
|
export declare const ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING: unique symbol;
|
|
40
41
|
export declare const ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING: unique symbol;
|
|
42
|
+
export declare const ENGINE_WAIT_FOR_SLEEP_RESOLVER_FOR_TESTING: unique symbol;
|
|
43
|
+
export declare const ENGINE_SET_WORKER_TURN_TIMEOUT_RESOLVER_FOR_TESTING: unique symbol;
|
|
44
|
+
/**
|
|
45
|
+
* Bound for `ENGINE_WAIT_FOR_SLEEP_RESOLVER_FOR_TESTING`.
|
|
46
|
+
*
|
|
47
|
+
* The only production-adjacent consumer awaits that hook inside the
|
|
48
|
+
* `weft:test:periodic-sync` Service Worker message handler in
|
|
49
|
+
* service-worker-browser.test.ts, and that file bounds each message round trip
|
|
50
|
+
* at 5s (`sendWorkerMessage`), each phase at 15s or whatever remains of the
|
|
51
|
+
* test budget, and each test at 30s. This bound has to be strictly tighter than
|
|
52
|
+
* the innermost of those — at 5s it would tie with the message bound and the
|
|
53
|
+
* generic "Service Worker message timed out" could win the race, hiding the
|
|
54
|
+
* diagnostic this bound exists to produce. 3s expires first, and the handler
|
|
55
|
+
* posts the rejection back over the port so the workflow-naming error is what
|
|
56
|
+
* reaches CI.
|
|
57
|
+
*
|
|
58
|
+
* The ordering holds whenever the phase bound is the 15s ceiling. It cannot
|
|
59
|
+
* hold once a test has burned all but a few seconds of its 30s budget, because
|
|
60
|
+
* no inner window survives an outer bound shorter than itself. That case is
|
|
61
|
+
* reported rather than papered over: the phase error says the bound was
|
|
62
|
+
* shortened by the remaining budget and that a nested timeout may have been
|
|
63
|
+
* preempted, which points at the earlier phases that actually consumed it.
|
|
64
|
+
*/
|
|
65
|
+
export declare const SLEEP_RESOLVER_READY_WAIT_TIMEOUT_MS_FOR_TESTING = 3000;
|
|
41
66
|
/**
|
|
42
67
|
* The `name` of the `process` warning emitted when a lease-owning engine is
|
|
43
68
|
* disposed through the synchronous `[Symbol.dispose]()` path. Sync disposal can
|
|
@@ -145,6 +170,8 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
145
170
|
removeEventListener<K extends Extract<keyof WeftEventMap, string>>(type: K, listener: (event: WeftEventMap[K]) => void, options?: boolean | EventListenerOptions): void;
|
|
146
171
|
removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
|
|
147
172
|
get state(): EngineStateNamespace;
|
|
173
|
+
/** Return detached snapshots of the alert rules that are currently firing. */
|
|
174
|
+
getActiveAlerts(): readonly AlertStateSnapshot[];
|
|
148
175
|
/**
|
|
149
176
|
* Register a workflow by name or definition, or register an activity
|
|
150
177
|
* definition. Definition overloads return this same engine with the
|
|
@@ -311,6 +338,11 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
311
338
|
[ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING](): number;
|
|
312
339
|
[ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING](): number;
|
|
313
340
|
[ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING](): number;
|
|
341
|
+
[ENGINE_WAIT_FOR_SLEEP_RESOLVER_FOR_TESTING](workflowId: string): Promise<void>;
|
|
342
|
+
[ENGINE_SET_WORKER_TURN_TIMEOUT_RESOLVER_FOR_TESTING](resolver: (turn: {
|
|
343
|
+
workflowId: string;
|
|
344
|
+
kind: 'run' | 'resume';
|
|
345
|
+
}) => number): void;
|
|
314
346
|
signal(workflowId: string, name: SignalDefinition): Promise<void>;
|
|
315
347
|
signal<TInput>(workflowId: string, name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
|
|
316
348
|
signal(workflowId: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
|
|
@@ -477,11 +509,13 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
477
509
|
*
|
|
478
510
|
* Under `ownership: 'lease'`, this is the explicit prompt-handoff primitive:
|
|
479
511
|
* it drains queued inline starts, tears down in-memory write paths, and awaits
|
|
480
|
-
* lease release before resolving.
|
|
481
|
-
*
|
|
482
|
-
*
|
|
512
|
+
* lease release before resolving. The returned boolean is `true` when no lease
|
|
513
|
+
* needed release or the holder delete committed, and `false` when the delete
|
|
514
|
+
* did not commit. Synchronous disposal remains immediate and can make the next
|
|
515
|
+
* engine wait for `leaseTtl` if the process exits before its background release
|
|
516
|
+
* completes.
|
|
483
517
|
*/
|
|
484
|
-
shutdown(): Promise<
|
|
518
|
+
shutdown(): Promise<boolean>;
|
|
485
519
|
/**
|
|
486
520
|
* Synchronous teardown (`using engine = ...`). Pending inline launches that
|
|
487
521
|
* have not yet run are **discarded**, not executed. When you need queued
|
|
@@ -490,15 +524,6 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
490
524
|
* via `await using`.
|
|
491
525
|
*/
|
|
492
526
|
[Symbol.dispose](): void;
|
|
493
|
-
/**
|
|
494
|
-
* Async teardown (`await using engine = ...`). Drains pending inline launches
|
|
495
|
-
* so each queued workflow completes its first turn before disposal, leaving no
|
|
496
|
-
* deferred-launch macrotask to fire against torn-down state. The drain is
|
|
497
|
-
* bounded (a pass cap and the abort signal); in the pathological case where it
|
|
498
|
-
* cannot converge, anything still queued is discarded by the synchronous
|
|
499
|
-
* teardown that always follows. Prefer this over the synchronous
|
|
500
|
-
* {@link Engine[Symbol.dispose]} in async contexts and tests.
|
|
501
|
-
*/
|
|
502
527
|
[Symbol.asyncDispose](): Promise<void>;
|
|
503
528
|
get storage(): WeftStorage;
|
|
504
529
|
get scheduler(): Scheduler;
|
|
@@ -79,6 +79,7 @@ import {
|
|
|
79
79
|
} from "./engine-runtime-helpers.js";
|
|
80
80
|
import {
|
|
81
81
|
EngineCreateNameMismatchError,
|
|
82
|
+
EngineDisposalError,
|
|
82
83
|
EngineDisposedError,
|
|
83
84
|
StartOrSignalConflictError
|
|
84
85
|
} from "./errors.js";
|
|
@@ -194,6 +195,7 @@ export {
|
|
|
194
195
|
BulkDeleteRequiresTerminalWorkflowsError,
|
|
195
196
|
BulkOperationConfirmationError,
|
|
196
197
|
EngineCreateNameMismatchError,
|
|
198
|
+
EngineDisposalError,
|
|
197
199
|
EngineDisposedError,
|
|
198
200
|
IdempotencyKeyPurgedError,
|
|
199
201
|
PersistedDataIncompatibleError,
|
|
@@ -222,7 +224,7 @@ export {
|
|
|
222
224
|
shouldEmitEngineLeakWarningForTesting
|
|
223
225
|
} from "./engine-leak-warnings.js";
|
|
224
226
|
export { assertCompatiblePersistedDataVersion };
|
|
225
|
-
export const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING = Symbol("engineParkedWorkflowCountForTesting"), ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING = Symbol("engineSignalWaiterCountForTesting"), ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING = Symbol("engineSleepResolverCountForTesting"), ENGINE_LEASE_SYNCHRONOUS_DISPOSE_WARNING_NAME = "WeftEngineLeaseSynchronousDisposeWarning";
|
|
227
|
+
export const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING = Symbol("engineParkedWorkflowCountForTesting"), ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING = Symbol("engineSignalWaiterCountForTesting"), ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING = Symbol("engineSleepResolverCountForTesting"), ENGINE_WAIT_FOR_SLEEP_RESOLVER_FOR_TESTING = Symbol("engineWaitForSleepResolverForTesting"), ENGINE_SET_WORKER_TURN_TIMEOUT_RESOLVER_FOR_TESTING = Symbol("engineSetWorkerTurnTimeoutResolverForTesting"), SLEEP_RESOLVER_READY_WAIT_TIMEOUT_MS_FOR_TESTING = 3000, ENGINE_LEASE_SYNCHRONOUS_DISPOSE_WARNING_NAME = "WeftEngineLeaseSynchronousDisposeWarning";
|
|
226
228
|
|
|
227
229
|
export { ENGINE_LEASE_LOST_WARNING_NAME };
|
|
228
230
|
function scheduleDefinitionFromInternals(internals, definition) {
|
|
@@ -237,6 +239,9 @@ function scheduleDefinitionFromInternals(internals, definition) {
|
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
export class Engine extends EventTarget {
|
|
242
|
+
#asyncDisposeResult = null;
|
|
243
|
+
#shutdownResult = null;
|
|
244
|
+
#synchronousDisposeResult = null;
|
|
240
245
|
static async create(options) {
|
|
241
246
|
validateEngineCreateBackgroundTaskOptions(options);
|
|
242
247
|
const engine = new Engine(options);
|
|
@@ -294,6 +299,7 @@ export class Engine extends EventTarget {
|
|
|
294
299
|
getInternals(this).updateWaitersByWorkflow = new Map;
|
|
295
300
|
getInternals(this).sleepResolvers = new Map;
|
|
296
301
|
getInternals(this).sleepResolversByWorkflow = new Map;
|
|
302
|
+
getInternals(this).sleepResolverReadyWaitersForTesting = new Map;
|
|
297
303
|
getInternals(this).sleepTimerAcknowledgementWaiters = new Map;
|
|
298
304
|
getInternals(this).durableInlineOperations = new Map;
|
|
299
305
|
getInternals(this).sleepTimersFiredWithoutResolver = new Map;
|
|
@@ -434,7 +440,9 @@ export class Engine extends EventTarget {
|
|
|
434
440
|
throw error;
|
|
435
441
|
}
|
|
436
442
|
if (internals.disposed) {
|
|
437
|
-
|
|
443
|
+
const releaseResult = manager.release();
|
|
444
|
+
this.#synchronousDisposeResult = releaseResult;
|
|
445
|
+
await releaseResult;
|
|
438
446
|
if (internals.leaseManager === manager)
|
|
439
447
|
internals.leaseManager = null;
|
|
440
448
|
throw new EngineDisposedError;
|
|
@@ -455,7 +463,9 @@ export class Engine extends EventTarget {
|
|
|
455
463
|
return;
|
|
456
464
|
const leaseManager = internals.leaseManager;
|
|
457
465
|
disposeEngine(internals);
|
|
458
|
-
|
|
466
|
+
if (this.#synchronousDisposeResult === null)
|
|
467
|
+
this.#synchronousDisposeResult = (leaseManager?.release() ?? Promise.resolve(!0)).catch(() => !1);
|
|
468
|
+
this.#synchronousDisposeResult;
|
|
459
469
|
}
|
|
460
470
|
#startSecondInstanceDetection() {
|
|
461
471
|
const internals = getInternals(this);
|
|
@@ -522,6 +532,9 @@ export class Engine extends EventTarget {
|
|
|
522
532
|
workflow: (workflowType, key, options) => new AtomicState(storage, KEYS.stateWorkflow(workflowType, key), options)
|
|
523
533
|
};
|
|
524
534
|
}
|
|
535
|
+
getActiveAlerts() {
|
|
536
|
+
return getInternals(this).alertManager?.activeStates ?? [];
|
|
537
|
+
}
|
|
525
538
|
async#handleStrategyMessage(message) {
|
|
526
539
|
return handleStrategyMessageFromInternals(getInternals(this), message, this.#createInlineParkingCallbacks());
|
|
527
540
|
}
|
|
@@ -696,6 +709,34 @@ export class Engine extends EventTarget {
|
|
|
696
709
|
[ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING]() {
|
|
697
710
|
return getInternals(this).sleepResolvers.size;
|
|
698
711
|
}
|
|
712
|
+
async[ENGINE_WAIT_FOR_SLEEP_RESOLVER_FOR_TESTING](workflowId) {
|
|
713
|
+
const internals = getInternals(this);
|
|
714
|
+
if (internals.sleepResolversByWorkflow.has(workflowId))
|
|
715
|
+
return;
|
|
716
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
717
|
+
let waiters = internals.sleepResolverReadyWaitersForTesting?.get(workflowId);
|
|
718
|
+
if (waiters === void 0) {
|
|
719
|
+
waiters = new Set;
|
|
720
|
+
internals.sleepResolverReadyWaitersForTesting?.set(workflowId, waiters);
|
|
721
|
+
}
|
|
722
|
+
waiters.add(resolve);
|
|
723
|
+
const expiry = setTimeout(() => {
|
|
724
|
+
const pendingWaiters = internals.sleepResolverReadyWaitersForTesting?.get(workflowId);
|
|
725
|
+
if (pendingWaiters !== void 0) {
|
|
726
|
+
pendingWaiters.delete(resolve);
|
|
727
|
+
if (pendingWaiters.size === 0)
|
|
728
|
+
internals.sleepResolverReadyWaitersForTesting?.delete(workflowId);
|
|
729
|
+
}
|
|
730
|
+
reject(Error(`Timed out after ${SLEEP_RESOLVER_READY_WAIT_TIMEOUT_MS_FOR_TESTING}ms waiting for workflow "${workflowId}" to register a sleep resolver`));
|
|
731
|
+
}, SLEEP_RESOLVER_READY_WAIT_TIMEOUT_MS_FOR_TESTING);
|
|
732
|
+
return await promise.finally(() => clearTimeout(expiry));
|
|
733
|
+
}
|
|
734
|
+
[ENGINE_SET_WORKER_TURN_TIMEOUT_RESOLVER_FOR_TESTING](resolver) {
|
|
735
|
+
const strategy = getInternals(this).strategy;
|
|
736
|
+
if (strategy.setWorkflowTurnTimeoutResolverForTesting === void 0)
|
|
737
|
+
throw Error("Worker turn timeout resolver is only available in Worker execution mode");
|
|
738
|
+
strategy.setWorkflowTurnTimeoutResolverForTesting(resolver);
|
|
739
|
+
}
|
|
699
740
|
async signal(workflowId, nameOrDefinition, payload, options) {
|
|
700
741
|
return signalWorkflow(getInternals(this), workflowId, messageName(nameOrDefinition), payload, {
|
|
701
742
|
loadWorkflowState: (id) => loadWorkflowState(getInternals(this), id),
|
|
@@ -868,28 +909,62 @@ export class Engine extends EventTarget {
|
|
|
868
909
|
return submitCoordinatedUpdateFromInternals(getInternals(this), workflowId, name, payload, options, this.#createUpdateCallbacks());
|
|
869
910
|
}
|
|
870
911
|
async shutdown() {
|
|
871
|
-
|
|
912
|
+
if (this.#shutdownResult === null)
|
|
913
|
+
this.#shutdownResult = (async () => {
|
|
914
|
+
await this[Symbol.asyncDispose]();
|
|
915
|
+
const baseResult = this.#asyncDisposeResult;
|
|
916
|
+
if (baseResult === null) {
|
|
917
|
+
await getInternals(this).inFlightLeaseAcquire?.catch(() => {});
|
|
918
|
+
return await this.#synchronousDisposeResult ?? !0;
|
|
919
|
+
}
|
|
920
|
+
try {
|
|
921
|
+
return await baseResult;
|
|
922
|
+
} catch (error) {
|
|
923
|
+
return error instanceof EngineDisposalError ? error.leaseReleased : !0;
|
|
924
|
+
}
|
|
925
|
+
})();
|
|
926
|
+
return this.#shutdownResult;
|
|
872
927
|
}
|
|
873
928
|
[Symbol.dispose]() {
|
|
929
|
+
if (this.#asyncDisposeResult !== null) {
|
|
930
|
+
this.#asyncDisposeResult;
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
874
933
|
const leaseManager = getInternals(this).leaseManager;
|
|
875
934
|
if (leaseManager !== null && leaseManager.currentEpochBytes() !== null)
|
|
876
935
|
process.emitWarning("engine ownership lease disposed synchronously; lease release is fire-and-forget, so exiting before the release completes can make the next instance wait for leaseTtl. Use await engine.shutdown(), await using, or await engine[Symbol.asyncDispose]() for prompt rolling-deploy handoff.", ENGINE_LEASE_SYNCHRONOUS_DISPOSE_WARNING_NAME);
|
|
877
936
|
disposeEngine(getInternals(this));
|
|
878
|
-
|
|
937
|
+
if (this.#synchronousDisposeResult === null)
|
|
938
|
+
this.#synchronousDisposeResult = leaseManager?.release() ?? Promise.resolve(!0);
|
|
939
|
+
this.#synchronousDisposeResult;
|
|
879
940
|
}
|
|
880
|
-
async
|
|
941
|
+
async#disposeAsyncWithLeaseResult() {
|
|
881
942
|
if (!getInternals(this).disposed) {
|
|
882
943
|
const leaseManager = getInternals(this).leaseManager;
|
|
944
|
+
let leaseReleased = !0, drainFailure = null;
|
|
883
945
|
try {
|
|
884
|
-
await drainQueuedInlineWorkflowStartsForEngine(this
|
|
946
|
+
await drainQueuedInlineWorkflowStartsForEngine(this, {
|
|
947
|
+
abortStartedWorkflows: !0
|
|
948
|
+
});
|
|
949
|
+
} catch (error) {
|
|
950
|
+
drainFailure = { error };
|
|
885
951
|
} finally {
|
|
886
952
|
disposeEngine(getInternals(this));
|
|
887
953
|
await getInternals(this).inFlightLeaseAcquire?.catch(() => {});
|
|
888
|
-
await leaseManager?.release();
|
|
954
|
+
leaseReleased = await (this.#synchronousDisposeResult ?? leaseManager?.release() ?? Promise.resolve(!0));
|
|
889
955
|
}
|
|
890
|
-
|
|
956
|
+
if (drainFailure !== null)
|
|
957
|
+
throw new EngineDisposalError(drainFailure.error, leaseReleased);
|
|
958
|
+
return leaseReleased;
|
|
891
959
|
}
|
|
892
960
|
this[Symbol.dispose]();
|
|
961
|
+
await getInternals(this).inFlightLeaseAcquire?.catch(() => {});
|
|
962
|
+
return await this.#synchronousDisposeResult ?? !0;
|
|
963
|
+
}
|
|
964
|
+
async[Symbol.asyncDispose]() {
|
|
965
|
+
if (this.#asyncDisposeResult === null)
|
|
966
|
+
this.#asyncDisposeResult = this.#disposeAsyncWithLeaseResult();
|
|
967
|
+
await this.#asyncDisposeResult;
|
|
893
968
|
}
|
|
894
969
|
get storage() {
|
|
895
970
|
return getInternals(this).storage;
|
|
@@ -6,7 +6,9 @@ export type InlineLaunchQueueCallbacks = {
|
|
|
6
6
|
};
|
|
7
7
|
/** Queue a new inline workflow start and schedule a flush if one is not already scheduled. */
|
|
8
8
|
export declare function queueInlineWorkflowExecutionStart(internals: EngineInternals, start: QueuedInlineWorkflowExecutionStart, callbacks: InlineLaunchQueueCallbacks): void;
|
|
9
|
-
export declare function flushQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks
|
|
9
|
+
export declare function flushQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks, options?: {
|
|
10
|
+
abortStartedWorkflows?: boolean;
|
|
11
|
+
}): Promise<void>;
|
|
10
12
|
/** Used by scheduler-driven direct backfill flushes. Clears the scheduled flag first. */
|
|
11
13
|
export declare function flushQueuedInlineWorkflowStartsDirectly(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
|
|
12
14
|
/**
|
|
@@ -22,7 +24,9 @@ export declare function flushQueuedInlineWorkflowStartsDirectly(internals: Engin
|
|
|
22
24
|
* is left for the synchronous `disposeQueuedInlineWorkflowStarts` path, which
|
|
23
25
|
* discards it and settles its `defer: false` awaiter.
|
|
24
26
|
*/
|
|
25
|
-
export declare function drainQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks
|
|
27
|
+
export declare function drainQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks, options?: {
|
|
28
|
+
abortStartedWorkflows?: boolean;
|
|
29
|
+
}): Promise<void>;
|
|
26
30
|
export declare function dropQueuedInlineWorkflowStart(internals: EngineInternals, workflowId: string): boolean;
|
|
27
31
|
export declare function disposeQueuedInlineWorkflowStarts(internals: EngineInternals): void;
|
|
28
32
|
export declare function hasQueuedInlineWorkflowStart(internals: EngineInternals, workflowId: string): boolean;
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { WorkflowStartedEvent } from "../events.js";
|
|
2
2
|
import { startWorkflowExecution } from "./lifecycle.js";
|
|
3
3
|
import { loadWorkflowState } from "./storage-io.js";
|
|
4
|
+
async function yieldQueuedShutdownAdvanceOpportunity() {
|
|
5
|
+
await new Promise((resolve) => {
|
|
6
|
+
setTimeout(resolve, 0);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
async function settleQueuedShutdownWork(pendingWork) {
|
|
10
|
+
if (pendingWork.length === 0)
|
|
11
|
+
return [];
|
|
12
|
+
const opportunityElapsed = yieldQueuedShutdownAdvanceOpportunity().then(() => !1);
|
|
13
|
+
return (await Promise.all(pendingWork.map(async ({ workflowId, promise }) => {
|
|
14
|
+
if (promise === void 0)
|
|
15
|
+
return workflowId;
|
|
16
|
+
return await Promise.race([promise.then(() => !0), opportunityElapsed]) ? workflowId : null;
|
|
17
|
+
}))).filter((workflowId) => workflowId !== null);
|
|
18
|
+
}
|
|
4
19
|
export function queueInlineWorkflowExecutionStart(internals, start, callbacks) {
|
|
5
20
|
internals.queuedInlineWorkflowStartIds.add(start.workflowId);
|
|
6
21
|
internals.queuedOrLaunchingInlineWorkflowStartIds.add(start.workflowId);
|
|
@@ -24,7 +39,7 @@ function settleDiscardedInlineStarts(internals, discarded) {
|
|
|
24
39
|
start.onStarted?.();
|
|
25
40
|
}
|
|
26
41
|
}
|
|
27
|
-
export async function flushQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
42
|
+
export async function flushQueuedInlineWorkflowStarts(internals, callbacks, options) {
|
|
28
43
|
if (internals.abortController.signal.aborted) {
|
|
29
44
|
const discarded = internals.queuedInlineWorkflowStarts;
|
|
30
45
|
internals.queuedInlineWorkflowStarts = [];
|
|
@@ -36,22 +51,39 @@ export async function flushQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
|
36
51
|
const pendingStarts = internals.queuedInlineWorkflowStarts;
|
|
37
52
|
internals.queuedInlineWorkflowStarts = [];
|
|
38
53
|
for (const start of pendingStarts)
|
|
39
|
-
await callbacks.swallowPromiseRejection(startQueuedInlineWorkflowExecution(internals, start, callbacks));
|
|
54
|
+
await callbacks.swallowPromiseRejection(startQueuedInlineWorkflowExecution(internals, start, callbacks, options));
|
|
40
55
|
}
|
|
41
56
|
export async function flushQueuedInlineWorkflowStartsDirectly(internals, callbacks) {
|
|
42
57
|
internals.queuedInlineWorkflowStartFlushScheduled = !1;
|
|
43
58
|
await flushQueuedInlineWorkflowStarts(internals, callbacks);
|
|
44
59
|
}
|
|
45
|
-
export async function drainQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
60
|
+
export async function drainQueuedInlineWorkflowStarts(internals, callbacks, options) {
|
|
46
61
|
internals.queuedInlineWorkflowStartFlushScheduled = !1;
|
|
47
62
|
let passes = 0;
|
|
48
63
|
const maxPasses = 1000;
|
|
49
64
|
while (internals.queuedInlineWorkflowStarts.length > 0 && !internals.abortController.signal.aborted && passes < maxPasses) {
|
|
50
65
|
passes += 1;
|
|
51
|
-
|
|
66
|
+
const workflowIds = internals.queuedInlineWorkflowStarts.map((start) => start.workflowId);
|
|
67
|
+
await callbacks.swallowPromiseRejection(flushQueuedInlineWorkflowStarts(internals, callbacks, options));
|
|
68
|
+
const pendingAdvances = workflowIds.map((workflowId) => {
|
|
69
|
+
const pendingAdvance = internals.inlineStrategy?.waitForWorkflowAdvance(workflowId);
|
|
70
|
+
return {
|
|
71
|
+
workflowId,
|
|
72
|
+
promise: pendingAdvance === void 0 ? void 0 : callbacks.swallowPromiseRejection(pendingAdvance)
|
|
73
|
+
};
|
|
74
|
+
}), settledWorkflowIds = await settleQueuedShutdownWork(pendingAdvances);
|
|
75
|
+
if (settledWorkflowIds.length > 0) {
|
|
76
|
+
const terminalTurns = settledWorkflowIds.flatMap((workflowId) => {
|
|
77
|
+
if (internals.inlineStrategy?.hasGenerator(workflowId))
|
|
78
|
+
return [];
|
|
79
|
+
const pendingTurn = internals.inlineStrategy?.waitForWorkflowTurn(workflowId);
|
|
80
|
+
return pendingTurn === void 0 ? [] : [callbacks.swallowPromiseRejection(pendingTurn)];
|
|
81
|
+
});
|
|
82
|
+
await Promise.all(terminalTurns);
|
|
83
|
+
}
|
|
52
84
|
}
|
|
53
85
|
}
|
|
54
|
-
async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
|
|
86
|
+
async function startQueuedInlineWorkflowExecution(internals, start, callbacks, options) {
|
|
55
87
|
try {
|
|
56
88
|
const state = await loadWorkflowState(internals, start.workflowId);
|
|
57
89
|
if (!state || state.status !== "running")
|
|
@@ -59,7 +91,10 @@ async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
|
|
|
59
91
|
internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
|
|
60
92
|
internals.engine.dispatchEvent(new WorkflowStartedEvent(start.workflowId, start.workflowType, start.input));
|
|
61
93
|
startWorkflowExecution(internals, start.workflowId, state.workflowExecutionToken, start.workflowType, start.input, start.checkpoint, start.nestingDepth, start.executionDeadline, start.executionStateOwnerId);
|
|
62
|
-
|
|
94
|
+
if (options?.abortStartedWorkflows === !0)
|
|
95
|
+
internals.inlineStrategy?.abortWorkflowAdvanceForShutdown(start.workflowId);
|
|
96
|
+
else
|
|
97
|
+
await callbacks.processPendingUpdatesAfterInlineAdvance(start.workflowId);
|
|
63
98
|
} finally {
|
|
64
99
|
internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
|
|
65
100
|
internals.queuedOrLaunchingInlineWorkflowStartIds.delete(start.workflowId);
|
|
@@ -114,6 +114,8 @@ export interface EngineInternals {
|
|
|
114
114
|
fireAt: number;
|
|
115
115
|
}>;
|
|
116
116
|
sleepResolversByWorkflow: Map<string, Set<string>>;
|
|
117
|
+
/** Test-only event waiters notified when a workflow registers a sleep resolver. */
|
|
118
|
+
sleepResolverReadyWaitersForTesting?: Map<string, Set<() => void>>;
|
|
117
119
|
/**
|
|
118
120
|
* Fired sleep timers awaiting proof that the awakened inline workflow reached
|
|
119
121
|
* its next durable checkpoint or terminal state. External schedulers must not
|
|
@@ -95,7 +95,8 @@ export type LeaseManagerOptions = {
|
|
|
95
95
|
* - `currentEpochBytes()` returns the held epoch as bytes for Step-2 fencing
|
|
96
96
|
* conditions; `null` before a successful acquire. Synchronous and stable across
|
|
97
97
|
* renewals.
|
|
98
|
-
* - `release()` best-effort relinquishes the lease (holder key only) on dispose
|
|
98
|
+
* - `release()` best-effort relinquishes the lease (holder key only) on dispose
|
|
99
|
+
* and resolves to whether the holder delete committed.
|
|
99
100
|
*/
|
|
100
101
|
export type LeaseManager = {
|
|
101
102
|
acquire(): Promise<void>;
|
|
@@ -104,7 +105,7 @@ export type LeaseManager = {
|
|
|
104
105
|
currentEpochBytes(): Uint8Array | null;
|
|
105
106
|
/** Return a defensive, synchronous view of this process's last-known lease state. */
|
|
106
107
|
health(): LeaseManagerHealth;
|
|
107
|
-
release(): Promise<
|
|
108
|
+
release(): Promise<boolean>;
|
|
108
109
|
stop(): void;
|
|
109
110
|
};
|
|
110
111
|
/**
|
|
@@ -149,11 +149,15 @@ export function createLeaseManager(options) {
|
|
|
149
149
|
if (inFlightRenewal !== null)
|
|
150
150
|
await inFlightRenewal;
|
|
151
151
|
if (lastHolderBytes === null)
|
|
152
|
-
return;
|
|
152
|
+
return !0;
|
|
153
153
|
try {
|
|
154
|
-
|
|
154
|
+
const deleted = await storageConditionalBatch(storage, [{ key: KEYS.leaseHolder(), expectedValue: lastHolderBytes }], [{ type: "delete", key: KEYS.leaseHolder() }]);
|
|
155
|
+
if (deleted)
|
|
155
156
|
lastHolderBytes = null;
|
|
156
|
-
|
|
157
|
+
return deleted;
|
|
158
|
+
} catch {
|
|
159
|
+
return !1;
|
|
160
|
+
}
|
|
157
161
|
}
|
|
158
162
|
function stop() {
|
|
159
163
|
stopped = !0;
|
|
@@ -71,6 +71,12 @@ export function registerSleepResolver(internals, workflowId, operationId, resolv
|
|
|
71
71
|
internals.sleepResolversByWorkflow.set(workflowId, workflowOperations);
|
|
72
72
|
}
|
|
73
73
|
workflowOperations.add(operationId);
|
|
74
|
+
const readinessWaiters = internals.sleepResolverReadyWaitersForTesting?.get(workflowId);
|
|
75
|
+
if (readinessWaiters !== void 0) {
|
|
76
|
+
internals.sleepResolverReadyWaitersForTesting?.delete(workflowId);
|
|
77
|
+
for (const notifyReady of readinessWaiters)
|
|
78
|
+
notifyReady();
|
|
79
|
+
}
|
|
74
80
|
}
|
|
75
81
|
export async function startDelayedWorkflow(internals, entry, callbacks) {
|
|
76
82
|
const state = await callbacks.loadWorkflowState(entry.workflowId);
|
|
@@ -71,6 +71,14 @@
|
|
|
71
71
|
*/
|
|
72
72
|
import type { OperationOutcome, WorkerOutboundMessage } from './types.ts';
|
|
73
73
|
export interface ExecutionStrategy extends Disposable, AsyncDisposable {
|
|
74
|
+
/**
|
|
75
|
+
* Internal test seam for selecting a load-tolerant timeout around one
|
|
76
|
+
* intentionally wedged Worker turn. Production construction never sets it.
|
|
77
|
+
*/
|
|
78
|
+
setWorkflowTurnTimeoutResolverForTesting?: (resolver: (turn: {
|
|
79
|
+
workflowId: string;
|
|
80
|
+
kind: 'run' | 'resume';
|
|
81
|
+
}) => number) => void;
|
|
74
82
|
/**
|
|
75
83
|
* Start a new workflow execution from the beginning.
|
|
76
84
|
*/
|
|
@@ -53,6 +53,7 @@ export declare class InlineExecutionStrategy implements ExecutionStrategy {
|
|
|
53
53
|
clearParkedContext(workflowId: string): void;
|
|
54
54
|
getAbortController(workflowId: string): AbortController | undefined;
|
|
55
55
|
waitForWorkflowTurn(workflowId: string): Promise<void> | undefined;
|
|
56
|
+
abortWorkflowAdvanceForShutdown(workflowId: string): void;
|
|
56
57
|
waitForWorkflowAdvance(workflowId: string): Promise<void> | undefined;
|
|
57
58
|
hasGenerator(workflowId: string): boolean;
|
|
58
59
|
/**
|