@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
|
@@ -4,7 +4,7 @@ import type { StoredStreamChunk } from '../context.ts';
|
|
|
4
4
|
import type { Interceptor } from '../interceptor.ts';
|
|
5
5
|
import { type ReviewRequest } from '../review/index.ts';
|
|
6
6
|
import { Scheduler } from '../scheduler.ts';
|
|
7
|
-
import { type AnyActivityDefinition, type AnyWorkflowDefinition, type AttributeFilterKey, type BulkCancelResult, type BulkDeleteResult, type BulkOperationCommitOptions, type BulkOperationDryRunOptions, type BulkOperationDryRunResult, type BulkSignalAllCommitOptions, type BulkSignalAllDryRunOptions, type BulkSignalResult, type BulkTagResult, type CheckpointState, type CheckpointSummary, type CoordinatedUpdateResult, type DefaultActivityTypes, type DefaultWorkflowRegistry, type ForkOptions, type InferActivityEntries, type InferActivityEntry, type InferWorkflowEntries, type InferWorkflowEntry, type ListFilter, type ListOptions, type PaginatedResult, type PurgeResult, type QueryDefinition, type RegisteredWorkflowDefinition, type RetentionOverview, type ReviewListEntry, type ReviewListFilter, type ScheduleDefinition, type ScheduleFilter, type ScheduleOptions, type ScheduleSpec, type ScheduleSummary, type SearchAttributeValue, type SignalDefinition, type SignalDeliveryOptions, type StartOptions, type SubmitReviewOptions, type TypedListFilter, type UpdateDefinition, type WorkflowEvent, type WorkflowInput, type WorkflowOutput, type WorkflowReplay, type WorkflowState, type WorkflowSummary, type WorkflowTimelineEntry } from '../types.ts';
|
|
7
|
+
import { type AnyActivityDefinition, type AnyWorkflowDefinition, type AttributeFilterKey, type BulkCancelResult, type BulkDeleteResult, type BulkOperationCommitOptions, type BulkOperationDryRunOptions, type BulkOperationDryRunResult, type BulkSignalAllCommitOptions, type BulkSignalAllDryRunOptions, type BulkSignalResult, type BulkTagResult, type CheckpointState, type CheckpointSummary, type CoordinatedUpdateResult, type DefaultActivityTypes, type DefaultWorkflowRegistry, type ForkOptions, type InferActivityEntries, type InferActivityEntry, type InferWorkflowEntries, type InferWorkflowEntry, type ListFilter, type ListOptions, type PaginatedResult, type PurgeResult, type QueryDefinition, type RegisteredWorkflowDefinition, type RetentionOverview, type ReviewListEntry, type ReviewListFilter, type ScheduleDefinition, type ScheduleFilter, type ScheduleOptions, type ScheduleSpec, type ScheduleSummary, type SearchAttributeValue, type SignalDefinition, type SignalDeliveryOptions, type StartOptions, type StartOrSignalSignal, type SubmitReviewOptions, type TypedListFilter, type UpdateDefinition, type WorkflowEvent, type WorkflowInput, type WorkflowOutput, type WorkflowReplay, type WorkflowState, type WorkflowSummary, type WorkflowTimelineEntry } from '../types.ts';
|
|
8
8
|
import type { TimerEntry } from '../types/checkpoint.ts';
|
|
9
9
|
import type { WorkflowAlreadyRegistered } from '../types/workflow-builder.ts';
|
|
10
10
|
import { type AggregateOptions, type AggregateResult } from './aggregate.ts';
|
|
@@ -12,17 +12,19 @@ import { type EmptyActivityDefinitions, type EmptyWorkflowDefinitions, type Know
|
|
|
12
12
|
import { type ActivityDefinitionName, type EngineCreateOptions, type RegisteredActivityDefinitionExecute, type UnknownWorkflowNameWhenDefaultRegistryIsEmpty } from './engine-create-types.ts';
|
|
13
13
|
import type { EngineConstructorOptions } from './engine-internal-types.ts';
|
|
14
14
|
import type { EngineStateNamespace } from './engine-state-namespace.ts';
|
|
15
|
-
import { HANDLE_RESULT_PROMISE,
|
|
15
|
+
import { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts';
|
|
16
16
|
import { type RecoverAllOptions } from './lifecycle.ts';
|
|
17
17
|
import { assertCompatiblePersistedDataVersion } from './persisted-data-version.ts';
|
|
18
|
+
import { ScheduleHandle } from './schedule-handle.ts';
|
|
18
19
|
import { type WorkflowFeedListener, type WorkflowFeedRecord, type WorkflowFeedSelector } from './workflow-feed.ts';
|
|
19
20
|
export { ActivityReconciliationCapabilityError, ActivityReconciliationConflictError, ActivityReconciliationIndeterminateError, } from './activity-reconciliation.ts';
|
|
20
21
|
export { AsyncActivityTokenNotFoundError } from './async-activity-completion.ts';
|
|
21
22
|
export type { PendingAsyncActivity } from './async-activity-completion.ts';
|
|
22
23
|
export type { PendingTimelineEntry, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter, } from './engine-internal-types.ts';
|
|
23
|
-
export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposedError, PersistedDataIncompatibleError, WorkflowAlreadyExistsError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
|
|
24
|
-
export { HANDLE_RESULT_PROMISE,
|
|
24
|
+
export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposedError, IdempotencyKeyPurgedError, PersistedDataIncompatibleError, StartOrSignalConflictError, WorkflowAlreadyExistsError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowSuspendNotSupportedError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
|
|
25
|
+
export { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts';
|
|
25
26
|
export type { RecoverAllOptions } from './lifecycle.ts';
|
|
27
|
+
export { ScheduleHandle } from './schedule-handle.ts';
|
|
26
28
|
export type { WorkflowFeedListener, WorkflowFeedRecord, WorkflowFeedSelector, } from './workflow-feed.ts';
|
|
27
29
|
export type { EngineCreateOptions } from './engine-create-types.ts';
|
|
28
30
|
export { clearEngineLeakWarningTokenForTesting, getEngineLeakCollectionCountForTesting, hasEngineLeakWarningTokenForTesting, setEngineLeakWarningOverrideForTesting, setNextEngineLeakWarningTokenForTesting, shouldEmitEngineLeakWarningForTesting, } from './engine-leak-warnings.ts';
|
|
@@ -30,6 +32,7 @@ export type { EngineStateNamespace } from './engine-state-namespace.ts';
|
|
|
30
32
|
export { assertCompatiblePersistedDataVersion };
|
|
31
33
|
export declare const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING: unique symbol;
|
|
32
34
|
export declare const ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING: unique symbol;
|
|
35
|
+
export declare const ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING: unique symbol;
|
|
33
36
|
/**
|
|
34
37
|
* Durable execution engine.
|
|
35
38
|
*
|
|
@@ -185,6 +188,25 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
185
188
|
listActivityDefinitions(): ActivityMetadata[];
|
|
186
189
|
start<TName extends KnownWorkflowNames<TWorkflows>>(type: TName, input: WorkflowInput<TWorkflows, TName>, options?: StartOptions): Promise<WorkflowHandle<WorkflowOutput<TWorkflows, TName>>>;
|
|
187
190
|
start<TName extends string>(type: UnknownWorkflowNameWhenDefaultRegistryIsEmpty<TWorkflows, TName>, input: unknown, options?: StartOptions): Promise<WorkflowHandle>;
|
|
191
|
+
/**
|
|
192
|
+
* Atomically start a workflow or signal it if it already exists
|
|
193
|
+
* (signal-with-start). With an absent target, the workflow record and the
|
|
194
|
+
* first signal commit in one batch and the freshly-launched run consumes the
|
|
195
|
+
* signal on its first drive. A non-terminal target (running, pending, or
|
|
196
|
+
* suspended) is signalled through the normal signal path; a terminal target
|
|
197
|
+
* throws {@link StartOrSignalConflictError} rather than starting a new run or
|
|
198
|
+
* dropping the signal.
|
|
199
|
+
*
|
|
200
|
+
* Concurrent callers converge on one workflow and one delivered signal. Pass
|
|
201
|
+
* `options.idempotencyKey` to dedup independent callers (e.g. retried
|
|
202
|
+
* webhooks); the signal id derives from the key when `signal.signalId` is
|
|
203
|
+
* omitted, so callers that share only the key still converge. `signal.signalId`
|
|
204
|
+
* and `options.idempotencyKey` are mutually exclusive (provide exactly one), as
|
|
205
|
+
* are `options.id` and `options.idempotencyKey`. Requires a storage backend
|
|
206
|
+
* with `conditionalBatch`.
|
|
207
|
+
*/
|
|
208
|
+
startOrSignal<TName extends KnownWorkflowNames<TWorkflows>>(type: TName, input: WorkflowInput<TWorkflows, TName>, signal: StartOrSignalSignal, options?: StartOptions): Promise<WorkflowHandle<WorkflowOutput<TWorkflows, TName>>>;
|
|
209
|
+
startOrSignal<TName extends string>(type: UnknownWorkflowNameWhenDefaultRegistryIsEmpty<TWorkflows, TName>, input: unknown, signal: StartOrSignalSignal, options?: StartOptions): Promise<WorkflowHandle>;
|
|
188
210
|
getHandle(workflowId: string): WorkflowHandle;
|
|
189
211
|
list<const TAttributeKeys extends readonly AttributeFilterKey[] = readonly AttributeFilterKey[]>(filter?: TypedListFilter<TAttributeKeys>, options?: ListOptions): Promise<PaginatedResult<WorkflowSummary>>;
|
|
190
212
|
aggregate(filter: ListFilter | undefined, options: AggregateOptions): Promise<AggregateResult>;
|
|
@@ -201,6 +223,37 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
201
223
|
tagAll(filter: ListFilter, tags: string[], options?: BulkOperationCommitOptions): Promise<BulkTagResult>;
|
|
202
224
|
untagAll(filter: ListFilter, tags: string[], options: BulkOperationDryRunOptions): Promise<BulkOperationDryRunResult>;
|
|
203
225
|
untagAll(filter: ListFilter, tags: string[], options?: BulkOperationCommitOptions): Promise<BulkTagResult>;
|
|
226
|
+
/**
|
|
227
|
+
* Register a recurring schedule that starts a workflow on a cron expression or
|
|
228
|
+
* fixed interval. Returns a {@link ScheduleHandle} for pausing, resuming,
|
|
229
|
+
* updating, or cancelling the schedule.
|
|
230
|
+
*
|
|
231
|
+
* Two call forms:
|
|
232
|
+
* - A {@link ScheduleDefinition} object: `engine.schedule({ workflow, cron, input })`.
|
|
233
|
+
* Carries the workflow (definition or type name), the `cron`/`every` spec,
|
|
234
|
+
* optional `input`, `id`, `overlapPolicy`, and `backfill`.
|
|
235
|
+
* - Positional: `engine.schedule(type, input, spec, options?)` where `spec` is
|
|
236
|
+
* a cron string or a {@link ScheduleSpec} (`{ cron }` or `{ every }`).
|
|
237
|
+
*
|
|
238
|
+
* The {@link ScheduleOptions.overlap} policy governs what happens when a tick
|
|
239
|
+
* fires while the previous run is still in flight.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* import { workflow, Engine } from '@lostgradient/weft';
|
|
244
|
+
*
|
|
245
|
+
* const engine = new Engine();
|
|
246
|
+
* engine.register(workflow({ name: 'sweep' }).execute(async function* () { return 'ok'; }));
|
|
247
|
+
*
|
|
248
|
+
* // Definition form: every day at 09:00, skip a tick if the prior run is still running.
|
|
249
|
+
* const handle = await engine.schedule({
|
|
250
|
+
* workflow: 'sweep',
|
|
251
|
+
* cron: '0 9 * * *',
|
|
252
|
+
* overlapPolicy: 'skip',
|
|
253
|
+
* });
|
|
254
|
+
* await handle.pause();
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
204
257
|
schedule<TInput>(definition: ScheduleDefinition<TInput>): Promise<ScheduleHandle>;
|
|
205
258
|
schedule(type: string, input: unknown, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise<ScheduleHandle>;
|
|
206
259
|
getSchedule(scheduleId: string): Promise<ScheduleSummary | null>;
|
|
@@ -212,6 +265,7 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
212
265
|
[HANDLE_RESULT_PROMISE](workflowId: string): Promise<unknown>;
|
|
213
266
|
[ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING](): number;
|
|
214
267
|
[ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING](): number;
|
|
268
|
+
[ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING](): number;
|
|
215
269
|
signal(workflowId: string, name: SignalDefinition): Promise<void>;
|
|
216
270
|
signal<TInput>(workflowId: string, name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
|
|
217
271
|
signal(workflowId: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
|
|
@@ -257,6 +311,13 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
257
311
|
*/
|
|
258
312
|
getOffload(workflowId: string, key: string): Promise<unknown>;
|
|
259
313
|
fork(sourceWorkflowId: string, options?: ForkOptions): Promise<WorkflowHandle>;
|
|
314
|
+
/**
|
|
315
|
+
* Re-drive a workflow from its persisted checkpoint and return a live handle.
|
|
316
|
+
* Accepts a workflow left `'running'` (e.g. recovered after a process restart)
|
|
317
|
+
* or one explicitly `'suspended'` via {@link Engine.suspend} — a suspended
|
|
318
|
+
* workflow is durably flipped back to `'running'` as part of resuming. Throws
|
|
319
|
+
* if the workflow is in any other status (terminal, pending) or not found.
|
|
320
|
+
*/
|
|
260
321
|
resume(workflowId: string): Promise<WorkflowHandle>;
|
|
261
322
|
/**
|
|
262
323
|
* Recover every running workflow found in storage. By default, recovery
|
|
@@ -291,8 +352,23 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
291
352
|
*/
|
|
292
353
|
failAsyncActivity(token: string, error: unknown): Promise<void>;
|
|
293
354
|
cancel(workflowId: string): Promise<void>;
|
|
355
|
+
/**
|
|
356
|
+
* Suspend a running workflow without terminating it. The workflow transitions
|
|
357
|
+
* to the non-terminal `'suspended'` status, keeps its durable checkpoint, and
|
|
358
|
+
* is later resumable via {@link Engine.resume} (or `handle.resume()`). Unlike
|
|
359
|
+
* {@link Engine.cancel}, this does not run cancel handlers and does not settle
|
|
360
|
+
* the result promise — `handle.result()` stays pending until a later `resume()`
|
|
361
|
+
* drives the run to completion.
|
|
362
|
+
*
|
|
363
|
+
* Suspension is client-driven preemption, so a suspended workflow is NOT
|
|
364
|
+
* auto-recovered by {@link Engine.recoverAll}; resume it explicitly. Calling
|
|
365
|
+
* `suspend` on a workflow that is not running (already terminal, or never
|
|
366
|
+
* started) is a no-op.
|
|
367
|
+
*/
|
|
368
|
+
suspend(workflowId: string): Promise<void>;
|
|
294
369
|
timeout(workflowId: string): Promise<void>;
|
|
295
370
|
get(workflowId: string): Promise<WorkflowState | null>;
|
|
371
|
+
getCurrentCheckpointStep(workflowId: string): Promise<number | null>;
|
|
296
372
|
getAttributes(workflowId: string): Promise<Record<string, SearchAttributeValue> | null>;
|
|
297
373
|
setAttributes(workflowId: string, attributes: Record<string, SearchAttributeValue>): Promise<void>;
|
|
298
374
|
addTags(workflowId: string, ...tags: string[]): Promise<void>;
|
|
@@ -313,7 +389,23 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
|
|
|
313
389
|
timeout?: number;
|
|
314
390
|
idempotencyKey?: string;
|
|
315
391
|
}): Promise<CoordinatedUpdateResult>;
|
|
392
|
+
/**
|
|
393
|
+
* Synchronous teardown (`using engine = ...`). Pending inline launches that
|
|
394
|
+
* have not yet run are **discarded**, not executed. When you need queued
|
|
395
|
+
* starts to complete before teardown — or want a clean event loop with no
|
|
396
|
+
* dangling deferred-launch macrotask — prefer {@link Engine[Symbol.asyncDispose]}
|
|
397
|
+
* via `await using`.
|
|
398
|
+
*/
|
|
316
399
|
[Symbol.dispose](): void;
|
|
400
|
+
/**
|
|
401
|
+
* Async teardown (`await using engine = ...`). Drains pending inline launches
|
|
402
|
+
* so each queued workflow completes its first turn before disposal, leaving no
|
|
403
|
+
* deferred-launch macrotask to fire against torn-down state. The drain is
|
|
404
|
+
* bounded (a pass cap and the abort signal); in the pathological case where it
|
|
405
|
+
* cannot converge, anything still queued is discarded by the synchronous
|
|
406
|
+
* teardown that always follows. Prefer this over the synchronous
|
|
407
|
+
* {@link Engine[Symbol.dispose]} in async contexts and tests.
|
|
408
|
+
*/
|
|
317
409
|
[Symbol.asyncDispose](): Promise<void>;
|
|
318
410
|
get storage(): WeftStorage;
|
|
319
411
|
get scheduler(): Scheduler;
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ActivityRegistry
|
|
4
4
|
} from "../activity-registry.js";
|
|
5
5
|
import { AtomicState } from "../atomic-state.js";
|
|
6
|
+
import { deserializeCheckpoint } from "../checkpoint.js";
|
|
6
7
|
import { createHandleCacheFinalizer } from "../engine-helpers.js";
|
|
7
8
|
import { ReviewCoordinator } from "../review/index.js";
|
|
8
9
|
import { Scheduler } from "../scheduler.js";
|
|
@@ -63,6 +64,8 @@ import {
|
|
|
63
64
|
import {
|
|
64
65
|
createCleanupIntervalTick,
|
|
65
66
|
createQueuedInlineWorkflowStartHandler,
|
|
67
|
+
createSecondInstanceDetectorResolver,
|
|
68
|
+
drainQueuedInlineWorkflowStartsForEngine,
|
|
66
69
|
isActivityDefinition
|
|
67
70
|
} from "./engine-runtime-helpers.js";
|
|
68
71
|
import { EngineCreateNameMismatchError } from "./errors.js";
|
|
@@ -70,7 +73,7 @@ import {
|
|
|
70
73
|
createWorkflowHandleWithResultPromise as createWorkflowHandleWithResultPromiseFromInternals,
|
|
71
74
|
getWorkflowResultPromise as getWorkflowResultPromiseFromInternals
|
|
72
75
|
} from "./handle-result.js";
|
|
73
|
-
import { HANDLE_RESULT_PROMISE,
|
|
76
|
+
import { HANDLE_RESULT_PROMISE, WorkflowHandle } from "./handles.js";
|
|
74
77
|
import { hasQueuedInlineWorkflowStart } from "./inline-launch-queue.js";
|
|
75
78
|
import {
|
|
76
79
|
handleStrategyMessage as handleStrategyMessageFromInternals,
|
|
@@ -81,6 +84,8 @@ import {
|
|
|
81
84
|
fork as forkFromLifecycle,
|
|
82
85
|
recoverAll as recoverAllFromLifecycle,
|
|
83
86
|
resume as resumeFromLifecycle,
|
|
87
|
+
startOrSignal as startOrSignalFromLifecycle,
|
|
88
|
+
startWithIdempotency as startWithIdempotencyFromLifecycle,
|
|
84
89
|
startWorkflow as startWorkflowFromLifecycle
|
|
85
90
|
} from "./lifecycle.js";
|
|
86
91
|
import {
|
|
@@ -114,6 +119,7 @@ import {
|
|
|
114
119
|
listReviews as listReviewsFromInternals,
|
|
115
120
|
submitReview as submitReviewFromInternals
|
|
116
121
|
} from "./reviews.js";
|
|
122
|
+
import { ScheduleHandle } from "./schedule-handle.js";
|
|
117
123
|
import {
|
|
118
124
|
cancelSchedule as cancelScheduleFromInternals,
|
|
119
125
|
listSchedules as listSchedulesFromInternals,
|
|
@@ -123,6 +129,10 @@ import {
|
|
|
123
129
|
toScheduleSummary,
|
|
124
130
|
updateSchedule as updateScheduleFromInternals
|
|
125
131
|
} from "./schedules.js";
|
|
132
|
+
import {
|
|
133
|
+
createSecondInstanceDetectionTick,
|
|
134
|
+
createSecondInstanceDetector
|
|
135
|
+
} from "./second-instance-detector.js";
|
|
126
136
|
import { signal as signalWorkflow } from "./signals.js";
|
|
127
137
|
import { loadScheduleState, loadWorkflowState } from "./storage-io.js";
|
|
128
138
|
import {
|
|
@@ -134,6 +144,7 @@ import {
|
|
|
134
144
|
cancelWorkflow as cancelWorkflowFromTermination,
|
|
135
145
|
cleanupWaiters as cleanupWaitersFromTermination,
|
|
136
146
|
finalizePendingTimelineEntry,
|
|
147
|
+
suspendWorkflow as suspendWorkflowFromTermination,
|
|
137
148
|
timeoutWorkflow as timeoutWorkflowFromTermination
|
|
138
149
|
} from "./termination.js";
|
|
139
150
|
import {
|
|
@@ -159,13 +170,17 @@ export {
|
|
|
159
170
|
BulkOperationConfirmationError,
|
|
160
171
|
EngineCreateNameMismatchError,
|
|
161
172
|
EngineDisposedError,
|
|
173
|
+
IdempotencyKeyPurgedError,
|
|
162
174
|
PersistedDataIncompatibleError,
|
|
175
|
+
StartOrSignalConflictError,
|
|
163
176
|
WorkflowAlreadyExistsError,
|
|
164
177
|
WorkflowNotFoundError,
|
|
165
178
|
WorkflowNotRegisteredError,
|
|
179
|
+
WorkflowSuspendNotSupportedError,
|
|
166
180
|
WorkflowTypeNotRegisteredForRecoveryError
|
|
167
181
|
} from "./errors.js";
|
|
168
|
-
export { HANDLE_RESULT_PROMISE,
|
|
182
|
+
export { HANDLE_RESULT_PROMISE, WorkflowHandle } from "./handles.js";
|
|
183
|
+
export { ScheduleHandle } from "./schedule-handle.js";
|
|
169
184
|
export {
|
|
170
185
|
clearEngineLeakWarningTokenForTesting,
|
|
171
186
|
getEngineLeakCollectionCountForTesting,
|
|
@@ -175,13 +190,13 @@ export {
|
|
|
175
190
|
shouldEmitEngineLeakWarningForTesting
|
|
176
191
|
} from "./engine-leak-warnings.js";
|
|
177
192
|
export { assertCompatiblePersistedDataVersion };
|
|
178
|
-
export const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING = Symbol("engineParkedWorkflowCountForTesting"), ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING = Symbol("engineSignalWaiterCountForTesting");
|
|
193
|
+
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");
|
|
179
194
|
|
|
180
195
|
export class Engine extends EventTarget {
|
|
181
196
|
static async create(options) {
|
|
182
197
|
const engine = new Engine(options);
|
|
183
198
|
try {
|
|
184
|
-
await assertCompatiblePersistedDataVersion(getInternals(engine).storage
|
|
199
|
+
await assertCompatiblePersistedDataVersion(getInternals(engine).storage);
|
|
185
200
|
for (const [name, definition] of definitionEntries(options.activities)) {
|
|
186
201
|
if (name !== definition.name)
|
|
187
202
|
throw new EngineCreateNameMismatchError("activity", name, definition.name);
|
|
@@ -283,6 +298,7 @@ export class Engine extends EventTarget {
|
|
|
283
298
|
const cleanupIntervalDisposalTracker = {
|
|
284
299
|
disposed: !1,
|
|
285
300
|
cleanupInterval: null,
|
|
301
|
+
secondInstanceDetectionInterval: null,
|
|
286
302
|
testToken: consumeNextEngineLeakWarningTokenForTesting()
|
|
287
303
|
}, cleanupInterval = setInterval(createCleanupIntervalTick(weakEngine, cleanupIntervalDisposalTracker), 60000);
|
|
288
304
|
cleanupIntervalDisposalTracker.cleanupInterval = cleanupInterval;
|
|
@@ -292,6 +308,8 @@ export class Engine extends EventTarget {
|
|
|
292
308
|
getInternals(this).retentionSweepInterval = null;
|
|
293
309
|
getInternals(this).retentionSweepInFlight = null;
|
|
294
310
|
getInternals(this).nextRetentionSweepAt = null;
|
|
311
|
+
getInternals(this).secondInstanceDetectionInterval = null;
|
|
312
|
+
getInternals(this).secondInstanceDetector = null;
|
|
295
313
|
getInternals(this).eventLogHeads = new Map;
|
|
296
314
|
getInternals(this).workflowFeedListeners = new Map;
|
|
297
315
|
getInternals(this).workflowVersionTuples = new Map;
|
|
@@ -299,6 +317,25 @@ export class Engine extends EventTarget {
|
|
|
299
317
|
getInternals(this).strategy.onMessage(this.#handleStrategyMessage.bind(this));
|
|
300
318
|
getInternals(this).alertManager = createAlertManagerForEngine(this, options?.alerts, getNow);
|
|
301
319
|
this.#ensureRetentionSweepInterval();
|
|
320
|
+
this.#startSecondInstanceDetection();
|
|
321
|
+
}
|
|
322
|
+
#startSecondInstanceDetection() {
|
|
323
|
+
const internals = getInternals(this);
|
|
324
|
+
if (!internals.options.secondInstanceDetectionEnabled)
|
|
325
|
+
return;
|
|
326
|
+
const detector = createSecondInstanceDetector({
|
|
327
|
+
storage: internals.storage,
|
|
328
|
+
instanceId: crypto.randomUUID(),
|
|
329
|
+
getNow: internals.options.getNow,
|
|
330
|
+
intervalMs: internals.options.secondInstanceHeartbeatIntervalMs
|
|
331
|
+
});
|
|
332
|
+
internals.secondInstanceDetector = detector;
|
|
333
|
+
const tracker = internals.cleanupIntervalDisposalTracker;
|
|
334
|
+
if (tracker === null)
|
|
335
|
+
return;
|
|
336
|
+
const weakEngine = new WeakRef(this), detectionInterval = setInterval(createSecondInstanceDetectionTick(createSecondInstanceDetectorResolver(weakEngine), tracker), internals.options.secondInstanceHeartbeatIntervalMs);
|
|
337
|
+
internals.secondInstanceDetectionInterval = detectionInterval;
|
|
338
|
+
tracker.secondInstanceDetectionInterval = detectionInterval;
|
|
302
339
|
}
|
|
303
340
|
#hasConfiguredRetention() {
|
|
304
341
|
return hasConfiguredRetention(getInternals(this));
|
|
@@ -400,8 +437,19 @@ export class Engine extends EventTarget {
|
|
|
400
437
|
return getInternals(this).activityRegistry.listDefinitions();
|
|
401
438
|
}
|
|
402
439
|
async start(type, input, options) {
|
|
440
|
+
if (options?.idempotencyKey !== void 0)
|
|
441
|
+
return startWithIdempotencyFromLifecycle(getInternals(this), type, input, options, this.#createLifecycleCallbacks());
|
|
403
442
|
return startWorkflowFromLifecycle(getInternals(this), type, input, options, void 0, this.#createLifecycleCallbacks());
|
|
404
443
|
}
|
|
444
|
+
async startOrSignal(type, input, signal, options) {
|
|
445
|
+
return startOrSignalFromLifecycle(getInternals(this), type, input, signal, options, this.#createStartOrSignalCallbacks());
|
|
446
|
+
}
|
|
447
|
+
#createStartOrSignalCallbacks() {
|
|
448
|
+
return {
|
|
449
|
+
...this.#createLifecycleCallbacks(),
|
|
450
|
+
signalExistingWorkflow: (workflowId, signalName, payload, signalId) => this.signal(workflowId, signalName, payload, { signalId })
|
|
451
|
+
};
|
|
452
|
+
}
|
|
405
453
|
getHandle(workflowId) {
|
|
406
454
|
const entry = getInternals(this).handleCache.get(workflowId);
|
|
407
455
|
if (entry) {
|
|
@@ -481,6 +529,9 @@ export class Engine extends EventTarget {
|
|
|
481
529
|
[ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING]() {
|
|
482
530
|
return getInternals(this).signalWaiters.size;
|
|
483
531
|
}
|
|
532
|
+
[ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING]() {
|
|
533
|
+
return getInternals(this).sleepResolvers.size;
|
|
534
|
+
}
|
|
484
535
|
async signal(workflowId, nameOrDefinition, payload, options) {
|
|
485
536
|
return signalWorkflow(getInternals(this), workflowId, messageName(nameOrDefinition), payload, {
|
|
486
537
|
loadWorkflowState: (id) => loadWorkflowState(getInternals(this), id),
|
|
@@ -521,6 +572,9 @@ export class Engine extends EventTarget {
|
|
|
521
572
|
async cancel(workflowId) {
|
|
522
573
|
await cancelWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
|
|
523
574
|
}
|
|
575
|
+
async suspend(workflowId) {
|
|
576
|
+
await suspendWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
|
|
577
|
+
}
|
|
524
578
|
async timeout(workflowId) {
|
|
525
579
|
await timeoutWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
|
|
526
580
|
}
|
|
@@ -530,6 +584,15 @@ export class Engine extends EventTarget {
|
|
|
530
584
|
return { ...state, status: "pending" };
|
|
531
585
|
return state;
|
|
532
586
|
}
|
|
587
|
+
async getCurrentCheckpointStep(workflowId) {
|
|
588
|
+
const inMemory = getInternals(this).checkpoints.get(workflowId);
|
|
589
|
+
if (inMemory !== void 0)
|
|
590
|
+
return inMemory.step;
|
|
591
|
+
const bytes = await getInternals(this).storage.get(KEYS.checkpoint(workflowId));
|
|
592
|
+
if (bytes === null)
|
|
593
|
+
return null;
|
|
594
|
+
return deserializeCheckpoint(bytes).step;
|
|
595
|
+
}
|
|
533
596
|
async getAttributes(workflowId) {
|
|
534
597
|
return getWorkflowAttributes(getInternals(this), workflowId);
|
|
535
598
|
}
|
|
@@ -587,6 +650,14 @@ export class Engine extends EventTarget {
|
|
|
587
650
|
disposeEngine(getInternals(this));
|
|
588
651
|
}
|
|
589
652
|
async[Symbol.asyncDispose]() {
|
|
653
|
+
if (!getInternals(this).disposed) {
|
|
654
|
+
try {
|
|
655
|
+
await drainQueuedInlineWorkflowStartsForEngine(this);
|
|
656
|
+
} finally {
|
|
657
|
+
this[Symbol.dispose]();
|
|
658
|
+
}
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
590
661
|
this[Symbol.dispose]();
|
|
591
662
|
}
|
|
592
663
|
get storage() {
|
|
@@ -9,6 +9,20 @@ export declare function queueInlineWorkflowExecutionStart(internals: EngineInter
|
|
|
9
9
|
export declare function flushQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
|
|
10
10
|
/** Used by scheduler-driven direct backfill flushes. Clears the scheduled flag first. */
|
|
11
11
|
export declare function flushQueuedInlineWorkflowStartsDirectly(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Drain every pending inline launch before engine teardown. Called from
|
|
14
|
+
* `[Symbol.asyncDispose]` *before* `disposeEngine` aborts the signal, so the
|
|
15
|
+
* flush actually executes the queued starts (the abort check in
|
|
16
|
+
* {@link flushQueuedInlineWorkflowStarts} would otherwise discard them). This
|
|
17
|
+
* turns a deferred-launch macrotask into work that completes before
|
|
18
|
+
* `asyncDispose` returns, so a disposed engine leaves no dangling pending
|
|
19
|
+
* launch — the fix for the test-runner macrotask-starvation footgun.
|
|
20
|
+
*
|
|
21
|
+
* A queued start that was already aborted (signal set before this is reached)
|
|
22
|
+
* is left for the synchronous `disposeQueuedInlineWorkflowStarts` path, which
|
|
23
|
+
* discards it and settles its `defer: false` awaiter.
|
|
24
|
+
*/
|
|
25
|
+
export declare function drainQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
|
|
12
26
|
export declare function dropQueuedInlineWorkflowStart(internals: EngineInternals, workflowId: string): boolean;
|
|
13
27
|
export declare function disposeQueuedInlineWorkflowStarts(internals: EngineInternals): void;
|
|
14
28
|
export declare function hasQueuedInlineWorkflowStart(internals: EngineInternals, workflowId: string): boolean;
|
|
@@ -17,9 +17,18 @@ export function queueInlineWorkflowExecutionStart(internals, start, callbacks) {
|
|
|
17
17
|
callbacks.swallowPromiseRejection(flushQueuedInlineWorkflowStarts(internals, callbacks));
|
|
18
18
|
}, 0);
|
|
19
19
|
}
|
|
20
|
+
function settleDiscardedInlineStarts(internals, discarded) {
|
|
21
|
+
for (const start of discarded) {
|
|
22
|
+
internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
|
|
23
|
+
internals.queuedOrLaunchingInlineWorkflowStartIds.delete(start.workflowId);
|
|
24
|
+
start.onStarted?.();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
20
27
|
export async function flushQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
21
28
|
if (internals.abortController.signal.aborted) {
|
|
29
|
+
const discarded = internals.queuedInlineWorkflowStarts;
|
|
22
30
|
internals.queuedInlineWorkflowStarts = [];
|
|
31
|
+
settleDiscardedInlineStarts(internals, discarded);
|
|
23
32
|
return;
|
|
24
33
|
}
|
|
25
34
|
if (internals.queuedInlineWorkflowStarts.length === 0)
|
|
@@ -27,12 +36,21 @@ export async function flushQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
|
27
36
|
const pendingStarts = internals.queuedInlineWorkflowStarts;
|
|
28
37
|
internals.queuedInlineWorkflowStarts = [];
|
|
29
38
|
for (const start of pendingStarts)
|
|
30
|
-
await startQueuedInlineWorkflowExecution(internals, start, callbacks);
|
|
39
|
+
await callbacks.swallowPromiseRejection(startQueuedInlineWorkflowExecution(internals, start, callbacks));
|
|
31
40
|
}
|
|
32
41
|
export async function flushQueuedInlineWorkflowStartsDirectly(internals, callbacks) {
|
|
33
42
|
internals.queuedInlineWorkflowStartFlushScheduled = !1;
|
|
34
43
|
await flushQueuedInlineWorkflowStarts(internals, callbacks);
|
|
35
44
|
}
|
|
45
|
+
export async function drainQueuedInlineWorkflowStarts(internals, callbacks) {
|
|
46
|
+
internals.queuedInlineWorkflowStartFlushScheduled = !1;
|
|
47
|
+
let passes = 0;
|
|
48
|
+
const maxPasses = 1000;
|
|
49
|
+
while (internals.queuedInlineWorkflowStarts.length > 0 && !internals.abortController.signal.aborted && passes < maxPasses) {
|
|
50
|
+
passes += 1;
|
|
51
|
+
await callbacks.swallowPromiseRejection(flushQueuedInlineWorkflowStarts(internals, callbacks));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
36
54
|
async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
|
|
37
55
|
try {
|
|
38
56
|
const state = await loadWorkflowState(internals, start.workflowId);
|
|
@@ -45,22 +63,29 @@ async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
|
|
|
45
63
|
} finally {
|
|
46
64
|
internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
|
|
47
65
|
internals.queuedOrLaunchingInlineWorkflowStartIds.delete(start.workflowId);
|
|
66
|
+
start.onStarted?.();
|
|
48
67
|
}
|
|
49
68
|
}
|
|
50
69
|
export function dropQueuedInlineWorkflowStart(internals, workflowId) {
|
|
51
70
|
if (internals.queuedInlineWorkflowStarts.length === 0)
|
|
52
71
|
return !1;
|
|
53
|
-
const initialLength = internals.queuedInlineWorkflowStarts.length;
|
|
54
|
-
internals.queuedInlineWorkflowStarts = internals.queuedInlineWorkflowStarts.filter((start) =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
const initialLength = internals.queuedInlineWorkflowStarts.length, dropped = [];
|
|
73
|
+
internals.queuedInlineWorkflowStarts = internals.queuedInlineWorkflowStarts.filter((start) => {
|
|
74
|
+
if (start.workflowId === workflowId) {
|
|
75
|
+
dropped.push(start);
|
|
76
|
+
return !1;
|
|
77
|
+
}
|
|
78
|
+
return !0;
|
|
79
|
+
});
|
|
80
|
+
if (internals.queuedInlineWorkflowStarts.length !== initialLength)
|
|
81
|
+
settleDiscardedInlineStarts(internals, dropped);
|
|
59
82
|
return internals.queuedInlineWorkflowStarts.length !== initialLength;
|
|
60
83
|
}
|
|
61
84
|
export function disposeQueuedInlineWorkflowStarts(internals) {
|
|
62
85
|
internals.queuedInlineWorkflowStartFlushScheduled = !1;
|
|
86
|
+
const discarded = internals.queuedInlineWorkflowStarts;
|
|
63
87
|
internals.queuedInlineWorkflowStarts = [];
|
|
88
|
+
settleDiscardedInlineStarts(internals, discarded);
|
|
64
89
|
internals.queuedInlineWorkflowStartIds.clear();
|
|
65
90
|
internals.queuedOrLaunchingInlineWorkflowStartIds.clear();
|
|
66
91
|
const channel = internals.queuedInlineWorkflowStartChannel;
|
|
@@ -32,8 +32,11 @@ import type { Checkpoint } from '../types.ts';
|
|
|
32
32
|
import type { UpdateCoordinator } from '../updates.ts';
|
|
33
33
|
import type { WorkflowVersionTuple } from '../workflow-version-tuple.ts';
|
|
34
34
|
import type { PendingTimelineEntry, QueuedInlineWorkflowExecutionStart, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter } from './engine-internal-types.ts';
|
|
35
|
-
import type {
|
|
35
|
+
import type { EngineCleanupIntervalDisposalTracker } from './engine-leak-warnings.ts';
|
|
36
|
+
import type { WorkflowHandle, WorkflowHandleEngine } from './handles.ts';
|
|
36
37
|
import type { WorkflowFeedListener } from './index.ts';
|
|
38
|
+
import type { ScheduleHandleEngine } from './schedule-handle.ts';
|
|
39
|
+
import type { SecondInstanceDetector } from './second-instance-detector.ts';
|
|
37
40
|
type EngineRuntime = WorkflowHandleEngine & ScheduleHandleEngine;
|
|
38
41
|
export interface EngineInternals {
|
|
39
42
|
engine: EngineRuntime;
|
|
@@ -75,11 +78,15 @@ export interface EngineInternals {
|
|
|
75
78
|
activityRegistry: ActivityRegistry;
|
|
76
79
|
/**
|
|
77
80
|
* Per-workflow activity registries built from
|
|
78
|
-
* `workflow({ name }).activities({ ... }).execute(...)
|
|
79
|
-
* type.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
81
|
+
* `workflow({ name }).activities({ ... }).execute(...)`, indexed by workflow
|
|
82
|
+
* type. Activity lookup is per-activity: it consults the workflow's
|
|
83
|
+
* `activityRegistriesByWorkflow.get(type)` registry first and, for any activity
|
|
84
|
+
* that registry does not contain, falls back to the engine-wide
|
|
85
|
+
* {@link EngineInternals.activityRegistry}. Both sources are first-class — a
|
|
86
|
+
* workflow can resolve some activities from its own `activities(...)` map and
|
|
87
|
+
* others (shared/globally-registered) from the global registry, and a workflow
|
|
88
|
+
* with no per-workflow map resolves entirely from the global one. Each
|
|
89
|
+
* per-workflow registry is a defensive deep clone+freeze of the workflow's
|
|
83
90
|
* `activities` map so post-registration mutation cannot reach the engine.
|
|
84
91
|
*/
|
|
85
92
|
activityRegistriesByWorkflow: Map<string, ActivityRegistry>;
|
|
@@ -127,13 +134,14 @@ export interface EngineInternals {
|
|
|
127
134
|
pendingScheduleCreations: Set<string>;
|
|
128
135
|
workflowsNeedingTerminalCleanup: Set<string>;
|
|
129
136
|
cleanupInterval: ReturnType<typeof setInterval> | null;
|
|
130
|
-
cleanupIntervalDisposalTracker:
|
|
131
|
-
disposed: boolean;
|
|
132
|
-
cleanupInterval: ReturnType<typeof setInterval> | null;
|
|
133
|
-
} | null;
|
|
137
|
+
cleanupIntervalDisposalTracker: EngineCleanupIntervalDisposalTracker | null;
|
|
134
138
|
retentionSweepInterval: ReturnType<typeof setInterval> | null;
|
|
135
139
|
retentionSweepInFlight: Promise<void> | null;
|
|
136
140
|
nextRetentionSweepAt: number | null;
|
|
141
|
+
/** Interval driving the best-effort second-instance detector; `null` when off. */
|
|
142
|
+
secondInstanceDetectionInterval: ReturnType<typeof setInterval> | null;
|
|
143
|
+
/** The active second-instance detector; `null` when detection is disabled. */
|
|
144
|
+
secondInstanceDetector: SecondInstanceDetector | null;
|
|
137
145
|
reviewCoordinator: ReviewCoordinator;
|
|
138
146
|
reviewWaiters: Map<string, (decision: HumanReviewResult) => void>;
|
|
139
147
|
reviewWaitersByWorkflow: Map<string, TrackedWaiterKeys>;
|
|
@@ -22,17 +22,11 @@ export function createForkedWorkflowState(_internals, workflowId, sourceState, v
|
|
|
22
22
|
type: sourceState.type,
|
|
23
23
|
status: "running",
|
|
24
24
|
input: sourceState.input,
|
|
25
|
-
|
|
25
|
+
versionTuple,
|
|
26
26
|
executionStateOwnerId: workflowId,
|
|
27
27
|
createdAt: forkedAt,
|
|
28
28
|
startedAt: forkedAt,
|
|
29
29
|
updatedAt: forkedAt,
|
|
30
|
-
...versionTuple.agentVersion !== void 0 && {
|
|
31
|
-
agentVersion: versionTuple.agentVersion
|
|
32
|
-
},
|
|
33
|
-
...versionTuple.toolVersions !== void 0 && {
|
|
34
|
-
toolVersions: versionTuple.toolVersions
|
|
35
|
-
},
|
|
36
30
|
forkedFrom: lineage
|
|
37
31
|
};
|
|
38
32
|
}
|
|
@@ -16,28 +16,13 @@ export function createWorkflowVersionTuple(_internals, registration, _callbacks)
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
export function workflowVersionTupleFromState(_internals, state, _callbacks) {
|
|
19
|
-
return
|
|
20
|
-
workflowVersion: state.version,
|
|
21
|
-
...state.agentVersion !== void 0 && { agentVersion: state.agentVersion },
|
|
22
|
-
...state.toolVersions !== void 0 && { toolVersions: state.toolVersions }
|
|
23
|
-
};
|
|
19
|
+
return state.versionTuple;
|
|
24
20
|
}
|
|
25
21
|
export function workflowStateWithVersionTuple(internals, state, versionTuple, _callbacks) {
|
|
26
|
-
const {
|
|
27
|
-
agentVersion: _existingAgentVersion,
|
|
28
|
-
toolVersions: _existingToolVersions,
|
|
29
|
-
...rest
|
|
30
|
-
} = state;
|
|
31
22
|
return {
|
|
32
|
-
...
|
|
33
|
-
|
|
34
|
-
updatedAt: internals.options.getNow()
|
|
35
|
-
...versionTuple.agentVersion !== void 0 && {
|
|
36
|
-
agentVersion: versionTuple.agentVersion
|
|
37
|
-
},
|
|
38
|
-
...versionTuple.toolVersions !== void 0 && {
|
|
39
|
-
toolVersions: versionTuple.toolVersions
|
|
40
|
-
}
|
|
23
|
+
...state,
|
|
24
|
+
versionTuple,
|
|
25
|
+
updatedAt: internals.options.getNow()
|
|
41
26
|
};
|
|
42
27
|
}
|
|
43
28
|
export function derivePreparedExecutionState(internals, workflowId, state, checkpoint, registration, callbacks) {
|
|
@@ -76,5 +61,5 @@ export async function prepareResumeState(internals, workflowId, state, checkpoin
|
|
|
76
61
|
};
|
|
77
62
|
}
|
|
78
63
|
export function throwVersionMismatch(_internals, workflowId, state, registration, versionDiff, _callbacks) {
|
|
79
|
-
throw new VersionMismatchError(workflowId, state.type, state.
|
|
64
|
+
throw new VersionMismatchError(workflowId, state.type, state.versionTuple.workflowVersion, registration.version, void 0, versionDiff);
|
|
80
65
|
}
|