@adonis-agora/durable 0.1.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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/assets/dashboard.html +446 -0
- package/dist/commands/main.d.ts +15 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +15 -0
- package/dist/commands/main.js.map +1 -0
- package/dist/commands/retry.d.ts +16 -0
- package/dist/commands/retry.d.ts.map +1 -0
- package/dist/commands/retry.js +34 -0
- package/dist/commands/retry.js.map +1 -0
- package/dist/commands/runs.d.ts +17 -0
- package/dist/commands/runs.d.ts.map +1 -0
- package/dist/commands/runs.js +40 -0
- package/dist/commands/runs.js.map +1 -0
- package/dist/commands/work.d.ts +18 -0
- package/dist/commands/work.d.ts.map +1 -0
- package/dist/commands/work.js +58 -0
- package/dist/commands/work.js.map +1 -0
- package/dist/configure.d.ts +15 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +26 -0
- package/dist/configure.js.map +1 -0
- package/dist/providers/dashboard_provider.d.ts +27 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -0
- package/dist/providers/dashboard_provider.js +94 -0
- package/dist/providers/dashboard_provider.js.map +1 -0
- package/dist/providers/durable_provider.d.ts +36 -0
- package/dist/providers/durable_provider.d.ts.map +1 -0
- package/dist/providers/durable_provider.js +113 -0
- package/dist/providers/durable_provider.js.map +1 -0
- package/dist/src/admission-redis/index.d.ts +2 -0
- package/dist/src/admission-redis/index.d.ts.map +1 -0
- package/dist/src/admission-redis/index.js +2 -0
- package/dist/src/admission-redis/index.js.map +1 -0
- package/dist/src/admission-redis/redis-admission-backend.d.ts +83 -0
- package/dist/src/admission-redis/redis-admission-backend.d.ts.map +1 -0
- package/dist/src/admission-redis/redis-admission-backend.js +200 -0
- package/dist/src/admission-redis/redis-admission-backend.js.map +1 -0
- package/dist/src/admission.d.ts +49 -0
- package/dist/src/admission.d.ts.map +1 -0
- package/dist/src/admission.js +30 -0
- package/dist/src/admission.js.map +1 -0
- package/dist/src/backoff.d.ts +5 -0
- package/dist/src/backoff.d.ts.map +1 -0
- package/dist/src/backoff.js +11 -0
- package/dist/src/backoff.js.map +1 -0
- package/dist/src/checkpoints.d.ts +42 -0
- package/dist/src/checkpoints.d.ts.map +1 -0
- package/dist/src/checkpoints.js +57 -0
- package/dist/src/checkpoints.js.map +1 -0
- package/dist/src/codec-state-store.d.ts +60 -0
- package/dist/src/codec-state-store.d.ts.map +1 -0
- package/dist/src/codec-state-store.js +129 -0
- package/dist/src/codec-state-store.js.map +1 -0
- package/dist/src/commands/index.d.ts +3 -0
- package/dist/src/commands/index.d.ts.map +1 -0
- package/dist/src/commands/index.js +3 -0
- package/dist/src/commands/index.js.map +1 -0
- package/dist/src/commands/runs.d.ts +24 -0
- package/dist/src/commands/runs.d.ts.map +1 -0
- package/dist/src/commands/runs.js +39 -0
- package/dist/src/commands/runs.js.map +1 -0
- package/dist/src/commands/worker.d.ts +61 -0
- package/dist/src/commands/worker.d.ts.map +1 -0
- package/dist/src/commands/worker.js +74 -0
- package/dist/src/commands/worker.js.map +1 -0
- package/dist/src/completion.d.ts +15 -0
- package/dist/src/completion.d.ts.map +1 -0
- package/dist/src/completion.js +10 -0
- package/dist/src/completion.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +48 -0
- package/dist/src/dashboard/define_config.d.ts.map +1 -0
- package/dist/src/dashboard/define_config.js +57 -0
- package/dist/src/dashboard/define_config.js.map +1 -0
- package/dist/src/dashboard/handlers.d.ts +47 -0
- package/dist/src/dashboard/handlers.d.ts.map +1 -0
- package/dist/src/dashboard/handlers.js +163 -0
- package/dist/src/dashboard/handlers.js.map +1 -0
- package/dist/src/dashboard/html.d.ts +7 -0
- package/dist/src/dashboard/html.d.ts.map +1 -0
- package/dist/src/dashboard/html.js +24 -0
- package/dist/src/dashboard/html.js.map +1 -0
- package/dist/src/dashboard/index.d.ts +8 -0
- package/dist/src/dashboard/index.d.ts.map +1 -0
- package/dist/src/dashboard/index.js +6 -0
- package/dist/src/dashboard/index.js.map +1 -0
- package/dist/src/define_config.d.ts +72 -0
- package/dist/src/define_config.d.ts.map +1 -0
- package/dist/src/define_config.js +8 -0
- package/dist/src/define_config.js.map +1 -0
- package/dist/src/diagnostics-bridge.d.ts +22 -0
- package/dist/src/diagnostics-bridge.d.ts.map +1 -0
- package/dist/src/diagnostics-bridge.js +25 -0
- package/dist/src/diagnostics-bridge.js.map +1 -0
- package/dist/src/duration.d.ts +6 -0
- package/dist/src/duration.d.ts.map +1 -0
- package/dist/src/duration.js +39 -0
- package/dist/src/duration.js.map +1 -0
- package/dist/src/engine.d.ts +583 -0
- package/dist/src/engine.d.ts.map +1 -0
- package/dist/src/engine.js +1894 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/entities.d.ts +44 -0
- package/dist/src/entities.d.ts.map +1 -0
- package/dist/src/entities.js +64 -0
- package/dist/src/entities.js.map +1 -0
- package/dist/src/errors.d.ts +68 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +102 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/event-accumulators.d.ts +39 -0
- package/dist/src/event-accumulators.d.ts.map +1 -0
- package/dist/src/event-accumulators.js +70 -0
- package/dist/src/event-accumulators.js.map +1 -0
- package/dist/src/events.d.ts +9 -0
- package/dist/src/events.d.ts.map +1 -0
- package/dist/src/events.js +35 -0
- package/dist/src/events.js.map +1 -0
- package/dist/src/index.d.ts +36 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +37 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interfaces.d.ts +909 -0
- package/dist/src/interfaces.d.ts.map +1 -0
- package/dist/src/interfaces.js +2 -0
- package/dist/src/interfaces.js.map +1 -0
- package/dist/src/metrics.d.ts +44 -0
- package/dist/src/metrics.d.ts.map +1 -0
- package/dist/src/metrics.js +78 -0
- package/dist/src/metrics.js.map +1 -0
- package/dist/src/otel/durable-metrics.d.ts +42 -0
- package/dist/src/otel/durable-metrics.d.ts.map +1 -0
- package/dist/src/otel/durable-metrics.js +75 -0
- package/dist/src/otel/durable-metrics.js.map +1 -0
- package/dist/src/otel/durable-otel.d.ts +20 -0
- package/dist/src/otel/durable-otel.d.ts.map +1 -0
- package/dist/src/otel/durable-otel.js +74 -0
- package/dist/src/otel/durable-otel.js.map +1 -0
- package/dist/src/otel/index.d.ts +4 -0
- package/dist/src/otel/index.d.ts.map +1 -0
- package/dist/src/otel/index.js +4 -0
- package/dist/src/otel/index.js.map +1 -0
- package/dist/src/otel/traceparent.d.ts +17 -0
- package/dist/src/otel/traceparent.d.ts.map +1 -0
- package/dist/src/otel/traceparent.js +21 -0
- package/dist/src/otel/traceparent.js.map +1 -0
- package/dist/src/pollers.d.ts +33 -0
- package/dist/src/pollers.d.ts.map +1 -0
- package/dist/src/pollers.js +76 -0
- package/dist/src/pollers.js.map +1 -0
- package/dist/src/protocol.d.ts +17 -0
- package/dist/src/protocol.d.ts.map +1 -0
- package/dist/src/protocol.js +47 -0
- package/dist/src/protocol.js.map +1 -0
- package/dist/src/queue.d.ts +113 -0
- package/dist/src/queue.d.ts.map +1 -0
- package/dist/src/queue.js +128 -0
- package/dist/src/queue.js.map +1 -0
- package/dist/src/remote-step-factory.d.ts +15 -0
- package/dist/src/remote-step-factory.d.ts.map +1 -0
- package/dist/src/remote-step-factory.js +12 -0
- package/dist/src/remote-step-factory.js.map +1 -0
- package/dist/src/remote-workflow-executor.d.ts +24 -0
- package/dist/src/remote-workflow-executor.d.ts.map +1 -0
- package/dist/src/remote-workflow-executor.js +70 -0
- package/dist/src/remote-workflow-executor.js.map +1 -0
- package/dist/src/scheduler.d.ts +67 -0
- package/dist/src/scheduler.d.ts.map +1 -0
- package/dist/src/scheduler.js +111 -0
- package/dist/src/scheduler.js.map +1 -0
- package/dist/src/search-attributes.d.ts +48 -0
- package/dist/src/search-attributes.d.ts.map +1 -0
- package/dist/src/search-attributes.js +98 -0
- package/dist/src/search-attributes.js.map +1 -0
- package/dist/src/singleton-gate.d.ts +44 -0
- package/dist/src/singleton-gate.d.ts.map +1 -0
- package/dist/src/singleton-gate.js +90 -0
- package/dist/src/singleton-gate.js.map +1 -0
- package/dist/src/step-logger.d.ts +9 -0
- package/dist/src/step-logger.d.ts.map +1 -0
- package/dist/src/step-logger.js +89 -0
- package/dist/src/step-logger.js.map +1 -0
- package/dist/src/stores/factory.d.ts +42 -0
- package/dist/src/stores/factory.d.ts.map +1 -0
- package/dist/src/stores/factory.js +29 -0
- package/dist/src/stores/factory.js.map +1 -0
- package/dist/src/stores/lucid-helpers.d.ts +11 -0
- package/dist/src/stores/lucid-helpers.d.ts.map +1 -0
- package/dist/src/stores/lucid-helpers.js +38 -0
- package/dist/src/stores/lucid-helpers.js.map +1 -0
- package/dist/src/stores/lucid-mappers.d.ts +58 -0
- package/dist/src/stores/lucid-mappers.d.ts.map +1 -0
- package/dist/src/stores/lucid-mappers.js +174 -0
- package/dist/src/stores/lucid-mappers.js.map +1 -0
- package/dist/src/stores/lucid-schema.d.ts +28 -0
- package/dist/src/stores/lucid-schema.d.ts.map +1 -0
- package/dist/src/stores/lucid-schema.js +115 -0
- package/dist/src/stores/lucid-schema.js.map +1 -0
- package/dist/src/stores/lucid.d.ts +69 -0
- package/dist/src/stores/lucid.d.ts.map +1 -0
- package/dist/src/stores/lucid.js +342 -0
- package/dist/src/stores/lucid.js.map +1 -0
- package/dist/src/telescope/dashboard.d.ts +15 -0
- package/dist/src/telescope/dashboard.d.ts.map +1 -0
- package/dist/src/telescope/dashboard.js +113 -0
- package/dist/src/telescope/dashboard.js.map +1 -0
- package/dist/src/telescope/data-providers.d.ts +20 -0
- package/dist/src/telescope/data-providers.d.ts.map +1 -0
- package/dist/src/telescope/data-providers.js +322 -0
- package/dist/src/telescope/data-providers.js.map +1 -0
- package/dist/src/telescope/extension.d.ts +22 -0
- package/dist/src/telescope/extension.d.ts.map +1 -0
- package/dist/src/telescope/extension.js +38 -0
- package/dist/src/telescope/extension.js.map +1 -0
- package/dist/src/telescope/index.d.ts +5 -0
- package/dist/src/telescope/index.d.ts.map +1 -0
- package/dist/src/telescope/index.js +4 -0
- package/dist/src/telescope/index.js.map +1 -0
- package/dist/src/telescope/telescope-sdk.d.ts +123 -0
- package/dist/src/telescope/telescope-sdk.d.ts.map +1 -0
- package/dist/src/telescope/telescope-sdk.js +12 -0
- package/dist/src/telescope/telescope-sdk.js.map +1 -0
- package/dist/src/test-helpers.d.ts +11 -0
- package/dist/src/test-helpers.d.ts.map +1 -0
- package/dist/src/test-helpers.js +12 -0
- package/dist/src/test-helpers.js.map +1 -0
- package/dist/src/testing/in-memory-state-store.d.ts +64 -0
- package/dist/src/testing/in-memory-state-store.d.ts.map +1 -0
- package/dist/src/testing/in-memory-state-store.js +259 -0
- package/dist/src/testing/in-memory-state-store.js.map +1 -0
- package/dist/src/testing/in-memory-transport.d.ts +25 -0
- package/dist/src/testing/in-memory-transport.d.ts.map +1 -0
- package/dist/src/testing/in-memory-transport.js +49 -0
- package/dist/src/testing/in-memory-transport.js.map +1 -0
- package/dist/src/testing-kit/admission-backend-conformance.d.ts +18 -0
- package/dist/src/testing-kit/admission-backend-conformance.d.ts.map +1 -0
- package/dist/src/testing-kit/admission-backend-conformance.js +81 -0
- package/dist/src/testing-kit/admission-backend-conformance.js.map +1 -0
- package/dist/src/testing-kit/assertions.d.ts +10 -0
- package/dist/src/testing-kit/assertions.d.ts.map +1 -0
- package/dist/src/testing-kit/assertions.js +41 -0
- package/dist/src/testing-kit/assertions.js.map +1 -0
- package/dist/src/testing-kit/harness.d.ts +28 -0
- package/dist/src/testing-kit/harness.d.ts.map +1 -0
- package/dist/src/testing-kit/harness.js +42 -0
- package/dist/src/testing-kit/harness.js.map +1 -0
- package/dist/src/testing-kit/index.d.ts +8 -0
- package/dist/src/testing-kit/index.d.ts.map +1 -0
- package/dist/src/testing-kit/index.js +8 -0
- package/dist/src/testing-kit/index.js.map +1 -0
- package/dist/src/testing-kit/replay.d.ts +22 -0
- package/dist/src/testing-kit/replay.d.ts.map +1 -0
- package/dist/src/testing-kit/replay.js +38 -0
- package/dist/src/testing-kit/replay.js.map +1 -0
- package/dist/src/testing-kit/state-store-conformance.d.ts +51 -0
- package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -0
- package/dist/src/testing-kit/state-store-conformance.js +430 -0
- package/dist/src/testing-kit/state-store-conformance.js.map +1 -0
- package/dist/src/testing-kit/steps.d.ts +5 -0
- package/dist/src/testing-kit/steps.d.ts.map +1 -0
- package/dist/src/testing-kit/steps.js +15 -0
- package/dist/src/testing-kit/steps.js.map +1 -0
- package/dist/src/testing-kit/transport-conformance.d.ts +16 -0
- package/dist/src/testing-kit/transport-conformance.d.ts.map +1 -0
- package/dist/src/testing-kit/transport-conformance.js +64 -0
- package/dist/src/testing-kit/transport-conformance.js.map +1 -0
- package/dist/src/tokens.d.ts +18 -0
- package/dist/src/tokens.d.ts.map +1 -0
- package/dist/src/tokens.js +18 -0
- package/dist/src/tokens.js.map +1 -0
- package/dist/src/transport-pool.d.ts +30 -0
- package/dist/src/transport-pool.d.ts.map +1 -0
- package/dist/src/transport-pool.js +82 -0
- package/dist/src/transport-pool.js.map +1 -0
- package/dist/src/transports/db-helpers.d.ts +10 -0
- package/dist/src/transports/db-helpers.d.ts.map +1 -0
- package/dist/src/transports/db-helpers.js +33 -0
- package/dist/src/transports/db-helpers.js.map +1 -0
- package/dist/src/transports/db-schema.d.ts +28 -0
- package/dist/src/transports/db-schema.d.ts.map +1 -0
- package/dist/src/transports/db-schema.js +98 -0
- package/dist/src/transports/db-schema.js.map +1 -0
- package/dist/src/transports/db.d.ts +81 -0
- package/dist/src/transports/db.d.ts.map +1 -0
- package/dist/src/transports/db.js +340 -0
- package/dist/src/transports/db.js.map +1 -0
- package/dist/src/transports/factory.d.ts +79 -0
- package/dist/src/transports/factory.d.ts.map +1 -0
- package/dist/src/transports/factory.js +57 -0
- package/dist/src/transports/factory.js.map +1 -0
- package/dist/src/transports/queue-mock-adapter.d.ts +39 -0
- package/dist/src/transports/queue-mock-adapter.d.ts.map +1 -0
- package/dist/src/transports/queue-mock-adapter.js +86 -0
- package/dist/src/transports/queue-mock-adapter.js.map +1 -0
- package/dist/src/transports/queue.d.ts +84 -0
- package/dist/src/transports/queue.d.ts.map +1 -0
- package/dist/src/transports/queue.js +210 -0
- package/dist/src/transports/queue.js.map +1 -0
- package/dist/src/workflow-ctx.d.ts +64 -0
- package/dist/src/workflow-ctx.d.ts.map +1 -0
- package/dist/src/workflow-ctx.js +457 -0
- package/dist/src/workflow-ctx.js.map +1 -0
- package/dist/src/workflow-ref.d.ts +34 -0
- package/dist/src/workflow-ref.d.ts.map +1 -0
- package/dist/src/workflow-ref.js +28 -0
- package/dist/src/workflow-ref.js.map +1 -0
- package/dist/stubs/config/durable.stub +49 -0
- package/dist/stubs/config/durable_dashboard.stub +17 -0
- package/dist/stubs/database/migrations/create_durable_tables.stub +87 -0
- package/dist/stubs/database/migrations/create_durable_transport_tables.stub +75 -0
- package/dist/stubs/main.d.ts +3 -0
- package/dist/stubs/main.d.ts.map +1 -0
- package/dist/stubs/main.js +4 -0
- package/dist/stubs/main.js.map +1 -0
- package/package.json +154 -0
|
@@ -0,0 +1,909 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { WorkflowClass, WorkflowInputOf, WorkflowOutputOf } from './workflow-ref.js';
|
|
3
|
+
/**
|
|
4
|
+
* Core type contracts for nestjs-durable.
|
|
5
|
+
*
|
|
6
|
+
* These are intentionally framework-agnostic: `@dudousxd/nestjs-durable-core` knows only
|
|
7
|
+
* these interfaces, never a concrete transport, store or ORM. Adapters implement them.
|
|
8
|
+
*/
|
|
9
|
+
export type RunStatus =
|
|
10
|
+
/** Created + enqueued by `start`, not yet picked up — a worker will lease and execute it. */
|
|
11
|
+
'pending' | 'running' | 'suspended' | 'completed' | 'failed' | 'cancelled'
|
|
12
|
+
/** Dead-letter: recovery gave up after `maxRecoveryAttempts` (a poison pill). Terminal; inspect it. */
|
|
13
|
+
| 'dead';
|
|
14
|
+
/** One execution of a workflow. The unit of durability and the unit shown in the dashboard. */
|
|
15
|
+
export interface WorkflowRun {
|
|
16
|
+
id: string;
|
|
17
|
+
/** Registered workflow name, e.g. `checkout`. */
|
|
18
|
+
workflow: string;
|
|
19
|
+
/** Code version at start; old runs must resume on the version they began on. */
|
|
20
|
+
workflowVersion: string;
|
|
21
|
+
status: RunStatus;
|
|
22
|
+
/** Serialized workflow input (the args the run was started with). */
|
|
23
|
+
input: unknown;
|
|
24
|
+
/** Serialized workflow output, once `completed`. */
|
|
25
|
+
output?: unknown;
|
|
26
|
+
/** Structured error, once `failed`. */
|
|
27
|
+
error?: StepError | undefined;
|
|
28
|
+
/** When `suspended` on a durable sleep: epoch ms at which the run becomes due to resume. */
|
|
29
|
+
wakeAt?: number | undefined;
|
|
30
|
+
/** Recovery lease owner (engine instance id) while a run is being resumed. */
|
|
31
|
+
lockedBy?: string | undefined;
|
|
32
|
+
/** Recovery lease expiry (epoch ms); another instance may take over once it passes. */
|
|
33
|
+
lockedUntil?: number | undefined;
|
|
34
|
+
/** How many times crash-recovery has picked this run up — caps poison pills (see maxRecoveryAttempts). */
|
|
35
|
+
recoveryAttempts?: number | undefined;
|
|
36
|
+
/** Searchable labels: the workflow's static `@Workflow({ tags })` merged with the run's start-time tags. */
|
|
37
|
+
tags?: string[] | undefined;
|
|
38
|
+
/** Typed, queryable run data (e.g. `{ amount: 200, tier: 'pro' }`) — see {@link RunQuery.attributes}. */
|
|
39
|
+
searchAttributes?: SearchAttributes | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Dispatch priority for a REMOTE run (one advanced by a {@link WorkflowExecutor} over a broker):
|
|
42
|
+
* carried onto each {@link WorkflowTask} so an urgent child workflow can jump ahead of enqueued
|
|
43
|
+
* lower-priority ones at the worker. Higher wins; absent = unprioritised. Best-effort ordering, not
|
|
44
|
+
* durable state required for correctness — a transport without priority support ignores it.
|
|
45
|
+
*/
|
|
46
|
+
priority?: number | undefined;
|
|
47
|
+
createdAt: Date;
|
|
48
|
+
updatedAt: Date;
|
|
49
|
+
}
|
|
50
|
+
export type StepKind = 'local' | 'remote' | 'sleep' | 'signal';
|
|
51
|
+
/**
|
|
52
|
+
* The recorded result of a single step at a deterministic logical position (`seq`).
|
|
53
|
+
* On replay, a `completed` checkpoint means the step is NOT re-executed — its `output` is
|
|
54
|
+
* returned. A non-terminal checkpoint (`pending`/`running`) does not short-circuit: the step is
|
|
55
|
+
* re-awaited (remote) or re-run (local).
|
|
56
|
+
*/
|
|
57
|
+
export interface StepCheckpoint {
|
|
58
|
+
runId: string;
|
|
59
|
+
/** Deterministic logical position of the step within the run. */
|
|
60
|
+
seq: number;
|
|
61
|
+
/** Registered step name (matches the remote handler name for remote steps). */
|
|
62
|
+
name: string;
|
|
63
|
+
kind: StepKind;
|
|
64
|
+
/** Stable id passed to remote workers so they can dedupe a re-delivered task. */
|
|
65
|
+
stepId: string;
|
|
66
|
+
/**
|
|
67
|
+
* `pending` = a remote step dispatched and awaiting its worker result (the run is durably
|
|
68
|
+
* suspended, not held in memory); it becomes `completed`/`failed` when the result arrives.
|
|
69
|
+
* `running` = a local step whose body is executing in-process right now (see `trackStepStart`);
|
|
70
|
+
* it's overwritten by `completed`/`failed` when the body settles, and on a crash mid-body it
|
|
71
|
+
* simply re-runs on replay (only `completed` short-circuits).
|
|
72
|
+
*/
|
|
73
|
+
status: 'pending' | 'running' | 'completed' | 'failed';
|
|
74
|
+
/** What the step was called with — the `ctx.call` args for a remote step (a local step has none). */
|
|
75
|
+
input?: unknown;
|
|
76
|
+
output?: unknown;
|
|
77
|
+
error?: StepError | undefined;
|
|
78
|
+
attempts: number;
|
|
79
|
+
/** For remote steps: which worker group ran it. */
|
|
80
|
+
workerGroup?: string | undefined;
|
|
81
|
+
/** Structured events/logs the step emitted (sub-step outcomes, debug/error lines). */
|
|
82
|
+
events?: StepEvent[] | undefined;
|
|
83
|
+
/** For sleep steps: epoch ms the sleep elapses at. */
|
|
84
|
+
wakeAt?: number | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* When the step entered the system: for a remote step, when the engine dispatched it to the
|
|
87
|
+
* transport; for a local step, when it began. Queue-wait time = `startedAt − enqueuedAt`.
|
|
88
|
+
*/
|
|
89
|
+
enqueuedAt: Date;
|
|
90
|
+
/** When processing actually began: worker pickup for a remote step, execution start for a local one. */
|
|
91
|
+
startedAt: Date;
|
|
92
|
+
finishedAt: Date;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A structured event a step (or its worker) emits while running — a log line and/or a sub-step
|
|
96
|
+
* outcome. The dashboard renders these under the step, so you can see what happened inside a step
|
|
97
|
+
* that the workflow treats as one unit (e.g. which of N parallel sub-processes ok/failed/skipped).
|
|
98
|
+
*/
|
|
99
|
+
export interface StepEvent {
|
|
100
|
+
/** Epoch ms. */
|
|
101
|
+
at: number;
|
|
102
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
103
|
+
message: string;
|
|
104
|
+
/** Stable run identity for a sub-process. Distinct invocations of the same `name` carry distinct
|
|
105
|
+
* ids, so their phases and log trails never collapse into one. Absent on events emitted by the
|
|
106
|
+
* legacy `sub()` path, which keys by `name` instead. */
|
|
107
|
+
subId?: string;
|
|
108
|
+
/** For a sub-step/sub-process within the step: its name. */
|
|
109
|
+
name?: string;
|
|
110
|
+
/** Open, consumer-defined grouping label for a sub-process (e.g. a handler/lane). The dashboard
|
|
111
|
+
* groups rows by it. The library never interprets it. */
|
|
112
|
+
group?: string;
|
|
113
|
+
/** For a sub-step: its terminal outcome (closed enum — drives colour + aggregation). */
|
|
114
|
+
status?: 'ok' | 'failed' | 'skipped';
|
|
115
|
+
/** Open, consumer-defined intermediate phase label for a sub-process transition. Carries no
|
|
116
|
+
* terminal `status`; the library timestamps and orders it but never interprets it. */
|
|
117
|
+
phase?: string;
|
|
118
|
+
/** For a log line emitted *inside* a sub-process: that owning sub-process's name, so the dashboard
|
|
119
|
+
* can group a step's log trail under each sub-process instead of one flat list. Set on logs (no
|
|
120
|
+
* `status`); a worker stamps it from the sub-process it's running.
|
|
121
|
+
* @deprecated Superseded by `subId` for run-distinct grouping; kept so existing workers/runs render. */
|
|
122
|
+
process?: string;
|
|
123
|
+
/** Optional structured payload. `data.durationMs` (number) overrides the derived duration. */
|
|
124
|
+
data?: unknown;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Handed to a local step's body (`ctx.step(name, (log) => …)`) so it can record what happened
|
|
128
|
+
* inside the step — debug/info/warn/error lines and per-sub-process outcomes. The events are
|
|
129
|
+
* checkpointed with the step and rendered under it in the dashboard. The remote/cross-language
|
|
130
|
+
* counterpart is the worker attaching the same `StepEvent[]` to its `StepResult` (see the Python
|
|
131
|
+
* SDK's `StepContext`), so observability is symmetric regardless of where the step ran.
|
|
132
|
+
*/
|
|
133
|
+
export interface StepLogger {
|
|
134
|
+
debug(message: string, data?: unknown): void;
|
|
135
|
+
info(message: string, data?: unknown): void;
|
|
136
|
+
warn(message: string, data?: unknown): void;
|
|
137
|
+
error(message: string, data?: unknown): void;
|
|
138
|
+
/** Record a sub-step / sub-process outcome (e.g. one of N parallel p-processes). */
|
|
139
|
+
sub(name: string, status: 'ok' | 'failed' | 'skipped', message?: string, data?: unknown): void;
|
|
140
|
+
/** Record a sub-process event. Typically pass `phase` for an intermediate transition (carrying no
|
|
141
|
+
* terminal status), or `status` for the terminal outcome — the type permits either; the engine
|
|
142
|
+
* interprets which is set. `id` is the run identity (distinct per invocation); `group` is an
|
|
143
|
+
* open grouping label. */
|
|
144
|
+
subEvent(e: {
|
|
145
|
+
id: string;
|
|
146
|
+
name: string;
|
|
147
|
+
group?: string | undefined;
|
|
148
|
+
phase?: string | undefined;
|
|
149
|
+
status?: 'ok' | 'failed' | 'skipped' | undefined;
|
|
150
|
+
message?: string | undefined;
|
|
151
|
+
data?: unknown;
|
|
152
|
+
}): void;
|
|
153
|
+
/**
|
|
154
|
+
* Ergonomic sub-process lifecycle: run `body`, timing it, and record a terminal `ok` with the
|
|
155
|
+
* measured `durationMs` on success — or `failed` (with the error message) if it throws, then
|
|
156
|
+
* re-throw. `sp.phase(label)` records an intermediate transition; `sp.skip(reason)` a terminal
|
|
157
|
+
* `skipped`. Logs emitted inside `body` are tagged to this sub-process so the dashboard groups
|
|
158
|
+
* them under it. Returns whatever `body` returns. The TS twin of the Python SDK's `sub_process`.
|
|
159
|
+
*
|
|
160
|
+
* ```ts
|
|
161
|
+
* const rows = await log.subProcess('fetch-data', async () => readEverything());
|
|
162
|
+
* await log.subProcess('export-file', () => upload(rows));
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
subProcess<T>(name: string, body: (sp: SubProcessHandle) => Promise<T> | T, opts?: {
|
|
166
|
+
group?: string;
|
|
167
|
+
id?: string;
|
|
168
|
+
}): Promise<T>;
|
|
169
|
+
}
|
|
170
|
+
/** The handle a {@link StepLogger.subProcess} body receives to mark phases / a non-`ok` outcome. */
|
|
171
|
+
export interface SubProcessHandle {
|
|
172
|
+
/** Record an intermediate phase transition (a consumer-defined label, no terminal status). */
|
|
173
|
+
phase(phase: string, data?: unknown): SubProcessHandle;
|
|
174
|
+
/** Record a terminal `skipped` outcome (e.g. nothing to do / validation failed). */
|
|
175
|
+
skip(reason?: string, data?: unknown): void;
|
|
176
|
+
/** Record a terminal `failed` outcome explicitly (the wrapper also does this if the body throws). */
|
|
177
|
+
fail(reason?: string, data?: unknown): void;
|
|
178
|
+
}
|
|
179
|
+
export interface StepError {
|
|
180
|
+
message: string;
|
|
181
|
+
/** Optional machine-readable code, e.g. `declined`, `timeout`. */
|
|
182
|
+
code?: string | undefined;
|
|
183
|
+
/** Whether the engine should treat this as retryable. */
|
|
184
|
+
retryable?: boolean | undefined;
|
|
185
|
+
stack?: string | undefined;
|
|
186
|
+
}
|
|
187
|
+
export interface StateStore {
|
|
188
|
+
/**
|
|
189
|
+
* Provision the tables/collections this store needs, idempotently. Called on boot when the
|
|
190
|
+
* module's `autoSchema` is on. Optional: stores that need no setup (in-memory) omit it.
|
|
191
|
+
*/
|
|
192
|
+
ensureSchema?(): Promise<void>;
|
|
193
|
+
createRun(run: WorkflowRun): Promise<void>;
|
|
194
|
+
updateRun(runId: string, patch: Partial<WorkflowRun>): Promise<void>;
|
|
195
|
+
getRun(runId: string): Promise<WorkflowRun | null>;
|
|
196
|
+
/**
|
|
197
|
+
* Hard-delete a run and everything tied to it (its checkpoints, signal waiters, and search-attribute
|
|
198
|
+
* index rows). Idempotent: deleting an unknown run is a no-op. This removes exactly the one run — the
|
|
199
|
+
* engine's {@link WorkflowEngine.deleteRun} handles the child-subtree cascade.
|
|
200
|
+
*/
|
|
201
|
+
deleteRun(runId: string): Promise<void>;
|
|
202
|
+
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
203
|
+
/**
|
|
204
|
+
* Persist a checkpoint and advance the run atomically. Durable semantics depend on this
|
|
205
|
+
* being a single transaction; stores without transactions cannot give the strong guarantee.
|
|
206
|
+
*/
|
|
207
|
+
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
208
|
+
/** Used by recovery on boot to find runs to resume (crashed, left `running`). */
|
|
209
|
+
listIncompleteRuns(): Promise<WorkflowRun[]>;
|
|
210
|
+
/** The oldest `pending` runs awaiting dispatch (FIFO, by `createdAt`), capped at `limit`. */
|
|
211
|
+
listPendingRuns(limit: number): Promise<WorkflowRun[]>;
|
|
212
|
+
/** Suspended runs whose durable timer is due (`wakeAt <= nowMs`), ready to resume. */
|
|
213
|
+
listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
|
|
214
|
+
/**
|
|
215
|
+
* Atomically acquire the recovery lease on a run for `owner` until `leaseUntilMs`, but only if
|
|
216
|
+
* it is currently unlocked or its lease has expired (`<= nowMs`). Returns whether it was
|
|
217
|
+
* acquired — so concurrent engine instances never recover the same run twice.
|
|
218
|
+
*/
|
|
219
|
+
tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
|
|
220
|
+
/** Release a run's recovery lease so another instance can pick it up (e.g. once it suspends). */
|
|
221
|
+
releaseRunLock(runId: string): Promise<void>;
|
|
222
|
+
/**
|
|
223
|
+
* Extend a run's lease to `leaseUntilMs`, but ONLY if `owner` still holds it — so a live worker
|
|
224
|
+
* heartbeating its long run keeps the lease, while a dead worker's lease still expires and gets
|
|
225
|
+
* reclaimed. Returns false if the lease was lost (taken over or released).
|
|
226
|
+
*/
|
|
227
|
+
renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
|
|
228
|
+
/** Record that a run is suspended waiting for an external signal `token`. */
|
|
229
|
+
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
230
|
+
/** Atomically take (and remove) the run waiting on `token`, if any. */
|
|
231
|
+
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
232
|
+
/** List waiters whose `token` starts with `prefix` — used to fan out an event to its subscribers. */
|
|
233
|
+
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
234
|
+
/**
|
|
235
|
+
* Buffer a signal whose waiter hasn't arrived yet, so the next `waitForSignal(token)` consumes it
|
|
236
|
+
* instead of it being lost (FIFO per token). Makes signals reliable regardless of timing and
|
|
237
|
+
* powers `signalWithStart`.
|
|
238
|
+
*/
|
|
239
|
+
bufferSignal(token: string, payload: unknown): Promise<void>;
|
|
240
|
+
/** Take the oldest buffered signal for `token` (FIFO), or null if none is buffered. */
|
|
241
|
+
takeBufferedSignal(token: string): Promise<{
|
|
242
|
+
payload: unknown;
|
|
243
|
+
} | null>;
|
|
244
|
+
/**
|
|
245
|
+
* Run `work` in a SINGLE store transaction — giving it the store-native transaction handle (`raw`)
|
|
246
|
+
* for the caller's own DB writes plus a `saveCheckpoint` that commits IN THE SAME transaction, so a
|
|
247
|
+
* business write and the step's "done" checkpoint are atomic (exactly-once). Optional — only the SQL
|
|
248
|
+
* adapters implement it; `ctx.transaction` errors on a store without it.
|
|
249
|
+
*/
|
|
250
|
+
transaction?<T>(work: (tx: StoreTransaction) => Promise<T>): Promise<T>;
|
|
251
|
+
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
252
|
+
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
253
|
+
/**
|
|
254
|
+
* The LATEST checkpoint for `runId` whose `name` equals `name` exactly (highest `seq` wins), or
|
|
255
|
+
* `undefined` if none. A targeted read that avoids fetching + deserializing every checkpoint just to
|
|
256
|
+
* keep one match — the store does the filter (`WHERE name = … ORDER BY seq DESC LIMIT 1`). Preserves
|
|
257
|
+
* the "last in seq order wins" semantics the engine relies on for `getEvent` (a re-published key
|
|
258
|
+
* overwrites the prior value at a higher seq, so the highest-seq match is the current value).
|
|
259
|
+
*
|
|
260
|
+
* Optional: a store that omits it still works — the engine falls back to {@link listCheckpoints}
|
|
261
|
+
* plus an in-JS filter that produces the identical result.
|
|
262
|
+
*/
|
|
263
|
+
getLatestCheckpointByName?(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
264
|
+
/**
|
|
265
|
+
* All checkpoints for `runId` whose `name` starts with ANY of `prefixes`, ordered by `seq` ascending
|
|
266
|
+
* (same order as {@link listCheckpoints}). A targeted read that avoids scanning every checkpoint just
|
|
267
|
+
* to keep the prefix matches — the store does the filter (`WHERE name LIKE 'prefix%' …`). Used by the
|
|
268
|
+
* run-tree to find a parent's child edges (`signal:child:` / `spawn:` checkpoints) without loading the
|
|
269
|
+
* whole history. An empty `prefixes` array matches nothing.
|
|
270
|
+
*
|
|
271
|
+
* Optional: a store that omits it still works — the engine falls back to {@link listCheckpoints}
|
|
272
|
+
* plus an in-JS prefix scan that produces the identical result.
|
|
273
|
+
*/
|
|
274
|
+
listCheckpointsByNamePrefix?(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
275
|
+
}
|
|
276
|
+
/** Typed, queryable per-run data — exact values for `eq`/`ne`, numbers/strings for range ops. */
|
|
277
|
+
export type SearchAttributes = Record<string, string | number | boolean>;
|
|
278
|
+
export type AttributeOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte';
|
|
279
|
+
/** One predicate over a run's {@link SearchAttributes}; a {@link RunQuery} ANDs them all. */
|
|
280
|
+
export interface AttributeFilter {
|
|
281
|
+
key: string;
|
|
282
|
+
op: AttributeOp;
|
|
283
|
+
value: string | number | boolean;
|
|
284
|
+
}
|
|
285
|
+
export interface RunQuery {
|
|
286
|
+
workflow?: string | undefined;
|
|
287
|
+
status?: RunStatus | undefined;
|
|
288
|
+
/**
|
|
289
|
+
* Match any of these statuses (a `status IN (...)` filter). ORed together, and ANDed with the other
|
|
290
|
+
* predicates. Use this instead of issuing one {@link listRuns} call per status — e.g. the singleton
|
|
291
|
+
* admission gate counts `running` + `suspended` in-flight runs in a single scan. If both `status` and
|
|
292
|
+
* `statuses` are set, both must hold (the single `status` further narrows the set). Empty array =
|
|
293
|
+
* matches nothing.
|
|
294
|
+
*/
|
|
295
|
+
statuses?: RunStatus[] | undefined;
|
|
296
|
+
/** Only runs carrying this tag (exact match against {@link WorkflowRun.tags}). */
|
|
297
|
+
tag?: string | undefined;
|
|
298
|
+
/**
|
|
299
|
+
* Typed/range predicates over {@link WorkflowRun.searchAttributes}, ANDed together (e.g. `amount`
|
|
300
|
+
* >= 200 and `tier` = 'pro'). Applied in-process after the coarse filters, so pair with
|
|
301
|
+
* `workflow`/`status`/`tag` to bound the scan on large stores.
|
|
302
|
+
*/
|
|
303
|
+
attributes?: AttributeFilter[] | undefined;
|
|
304
|
+
limit?: number | undefined;
|
|
305
|
+
offset?: number | undefined;
|
|
306
|
+
}
|
|
307
|
+
/** The transaction handle `StateStore.transaction` hands to its work callback. */
|
|
308
|
+
export interface StoreTransaction {
|
|
309
|
+
/** The store-native transaction handle (TypeORM `EntityManager`, Prisma tx client, MikroORM `EntityManager`,
|
|
310
|
+
* Drizzle tx) — do your business DB writes on THIS so they commit atomically with the checkpoint. */
|
|
311
|
+
readonly raw: unknown;
|
|
312
|
+
/** Persist the step checkpoint inside this transaction. */
|
|
313
|
+
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
314
|
+
}
|
|
315
|
+
/** Binds an external signal `token` to the suspended run/step position waiting for it. */
|
|
316
|
+
export interface SignalWaiter {
|
|
317
|
+
token: string;
|
|
318
|
+
runId: string;
|
|
319
|
+
seq: number;
|
|
320
|
+
}
|
|
321
|
+
/** A unit of work dispatched to a remote worker. This is the documented wire payload. */
|
|
322
|
+
export interface RemoteTask {
|
|
323
|
+
runId: string;
|
|
324
|
+
seq: number;
|
|
325
|
+
/** Handler name the worker registered, e.g. `payments.charge-card`. */
|
|
326
|
+
name: string;
|
|
327
|
+
stepId: string;
|
|
328
|
+
/** Worker group expected to handle this task. */
|
|
329
|
+
group: string;
|
|
330
|
+
input: unknown;
|
|
331
|
+
/** W3C traceparent so the worker can continue the distributed trace. */
|
|
332
|
+
traceparent?: string | undefined;
|
|
333
|
+
/**
|
|
334
|
+
* Opaque context carrier (tenant / user / correlation ids) the worker re-exposes to the step
|
|
335
|
+
* handler, for cross-process propagation alongside the {@link traceparent}. The engine treats it
|
|
336
|
+
* as a pass-through object and never inspects its shape — the producer (e.g. `@dudousxd/nestjs-context`)
|
|
337
|
+
* owns the keys. Absent when no `context` provider is configured.
|
|
338
|
+
*/
|
|
339
|
+
context?: Record<string, unknown> | undefined;
|
|
340
|
+
/**
|
|
341
|
+
* Id of the transport this task was dispatched on (when the engine runs a pool — see
|
|
342
|
+
* {@link NamedTransport}). A worker that consumes several transports replies via the matching one,
|
|
343
|
+
* so failover is symmetric without the worker choosing a transport. Absent for a single transport.
|
|
344
|
+
*/
|
|
345
|
+
transport?: string | undefined;
|
|
346
|
+
/**
|
|
347
|
+
* Admission priority carried through to the broker (the queue job `priority`) so a transport that
|
|
348
|
+
* supports priority ordering lets an urgent task jump ahead of already-enqueued lower-priority
|
|
349
|
+
* ones at the worker. Mirrors the per-call `priority` from `ctx.call(..., { priority })`. Higher
|
|
350
|
+
* wins; absent means default/unprioritised. Transports without priority support ignore it.
|
|
351
|
+
*/
|
|
352
|
+
priority?: number | undefined;
|
|
353
|
+
attempt: number;
|
|
354
|
+
}
|
|
355
|
+
export interface StepResult {
|
|
356
|
+
runId: string;
|
|
357
|
+
seq: number;
|
|
358
|
+
stepId: string;
|
|
359
|
+
status: 'completed' | 'failed';
|
|
360
|
+
output?: unknown;
|
|
361
|
+
error?: StepError | undefined;
|
|
362
|
+
/** Epoch ms when the worker began processing — lets the engine report queue-wait time. */
|
|
363
|
+
startedAt?: number | undefined;
|
|
364
|
+
/** Structured events the worker emitted while running the step (sub-step outcomes, logs). */
|
|
365
|
+
events?: StepEvent[] | undefined;
|
|
366
|
+
}
|
|
367
|
+
export interface Heartbeat {
|
|
368
|
+
runId: string;
|
|
369
|
+
seq: number;
|
|
370
|
+
stepId: string;
|
|
371
|
+
group: string;
|
|
372
|
+
}
|
|
373
|
+
/** engine → workflow worker: advance this run one turn by replaying the function against `history`. */
|
|
374
|
+
export interface WorkflowTask {
|
|
375
|
+
/** Dedupe id for this turn (a re-delivered task must be idempotent). */
|
|
376
|
+
taskId: string;
|
|
377
|
+
runId: string;
|
|
378
|
+
/** Registered workflow name + the version the run started on — replay must use that version. */
|
|
379
|
+
workflow: string;
|
|
380
|
+
workflowVersion: string;
|
|
381
|
+
input: unknown;
|
|
382
|
+
/** Completed durable ops so far, ordered by seq — what the worker replays its results from. */
|
|
383
|
+
history: HistoryEvent[];
|
|
384
|
+
/** Signals delivered to the run but not yet consumed, so `wait_signal` resolves on replay. */
|
|
385
|
+
pendingSignals?: Array<{
|
|
386
|
+
seq: number;
|
|
387
|
+
signal: string;
|
|
388
|
+
payload: unknown;
|
|
389
|
+
}>;
|
|
390
|
+
group: string;
|
|
391
|
+
/** Id of the transport this task was dispatched on (pool failover) — see {@link NamedTransport}. */
|
|
392
|
+
transport?: string;
|
|
393
|
+
traceparent?: string;
|
|
394
|
+
/**
|
|
395
|
+
* Admission priority carried to the broker (the queue job `priority`) so an urgent child workflow
|
|
396
|
+
* can jump ahead of already-enqueued lower-priority ones at the worker. Higher wins; absent means
|
|
397
|
+
* default/unprioritised. Transports without priority support ignore it.
|
|
398
|
+
*/
|
|
399
|
+
priority?: number | undefined;
|
|
400
|
+
attempt: number;
|
|
401
|
+
}
|
|
402
|
+
/** One resolved durable op in a run's history — a superset of a completed {@link StepCheckpoint}. */
|
|
403
|
+
export interface HistoryEvent {
|
|
404
|
+
seq: number;
|
|
405
|
+
kind: 'step' | 'call' | 'timer' | 'signal' | 'child';
|
|
406
|
+
name?: string | undefined;
|
|
407
|
+
/** Resolved value: a step/call output, a child run's output, a signal payload. */
|
|
408
|
+
output?: unknown;
|
|
409
|
+
/** Set when the op resolved to a failure (e.g. a failed remote step the workflow may catch). */
|
|
410
|
+
error?: StepError | undefined;
|
|
411
|
+
}
|
|
412
|
+
/** A decision the workflow function produced at a `seq` that was not yet in history. */
|
|
413
|
+
export type WorkflowCommand =
|
|
414
|
+
/** `ctx.call(remoteStep, input)` — dispatch a remote step and await it. */
|
|
415
|
+
{
|
|
416
|
+
kind: 'call';
|
|
417
|
+
seq: number;
|
|
418
|
+
name: string;
|
|
419
|
+
group: string;
|
|
420
|
+
input: unknown;
|
|
421
|
+
}
|
|
422
|
+
/** `ctx.step(name, body)` — a LOCAL step the worker already ran this turn; the engine persists its
|
|
423
|
+
* result so replay returns it instead of re-running (durability for side-effectful work).
|
|
424
|
+
* `startedAt`/`finishedAt` (epoch ms) carry the step's real wall-clock window so the dashboard
|
|
425
|
+
* shows a true duration instead of 0ms, and `events` carry the sub-process/log trail the step
|
|
426
|
+
* emitted (so each handler's p-processes show under it). All optional for back-compat. */
|
|
427
|
+
| {
|
|
428
|
+
kind: 'recordStep';
|
|
429
|
+
seq: number;
|
|
430
|
+
name: string;
|
|
431
|
+
output?: unknown;
|
|
432
|
+
error?: StepError;
|
|
433
|
+
startedAt?: number;
|
|
434
|
+
finishedAt?: number;
|
|
435
|
+
events?: StepEvent[];
|
|
436
|
+
}
|
|
437
|
+
/** `ctx.sleep(ms)` — a durable timer of `ms` duration. The engine computes the absolute deadline
|
|
438
|
+
* (now + ms) when it applies the command, so the worker never reads the clock (determinism). */
|
|
439
|
+
| {
|
|
440
|
+
kind: 'sleep';
|
|
441
|
+
seq: number;
|
|
442
|
+
ms: number;
|
|
443
|
+
}
|
|
444
|
+
/** `ctx.wait_signal(name)` — block until a signal `name` is delivered to the run. */
|
|
445
|
+
| {
|
|
446
|
+
kind: 'waitSignal';
|
|
447
|
+
seq: number;
|
|
448
|
+
signal: string;
|
|
449
|
+
}
|
|
450
|
+
/** `ctx.start_child(workflow, input)` — start a child run with its own lifecycle. */
|
|
451
|
+
| {
|
|
452
|
+
kind: 'startChild';
|
|
453
|
+
seq: number;
|
|
454
|
+
workflow: string;
|
|
455
|
+
input: unknown;
|
|
456
|
+
};
|
|
457
|
+
/** workflow worker → engine: the result of replaying one turn of a remote workflow. */
|
|
458
|
+
export interface WorkflowDecision {
|
|
459
|
+
taskId: string;
|
|
460
|
+
runId: string;
|
|
461
|
+
/** `continue` = produced `commands` and is blocked on an await; otherwise the run settles.
|
|
462
|
+
* `cancelled` = the worker bailed at an op boundary because the run was cancelled mid-turn. */
|
|
463
|
+
status: 'continue' | 'completed' | 'failed' | 'cancelled';
|
|
464
|
+
/** New durable ops the replay produced this turn (status === 'continue'), ordered by seq. */
|
|
465
|
+
commands: WorkflowCommand[];
|
|
466
|
+
/** Final workflow output (status === 'completed'). */
|
|
467
|
+
output?: unknown;
|
|
468
|
+
/** Terminal error (status === 'failed'). */
|
|
469
|
+
error?: StepError;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* workflow worker → engine: a LOCAL step's lifecycle, streamed AS IT HAPPENS (not batched into the
|
|
473
|
+
* turn's final {@link WorkflowDecision}). A Python `@workflow` runs its `ctx.step`s inline over one
|
|
474
|
+
* turn that can last minutes; without this the engine learns of the steps only when the turn ends,
|
|
475
|
+
* so the dashboard shows nothing mid-run. The worker emits `running` when a step's body starts and
|
|
476
|
+
* `completed`/`failed` when it settles; the engine checkpoints each immediately, so a step appears
|
|
477
|
+
* in-flight and then resolves live. The turn's final `recordStep` command re-persists the same
|
|
478
|
+
* checkpoint idempotently (replay history), so this is purely additive observability.
|
|
479
|
+
*/
|
|
480
|
+
export interface WorkflowStepEvent {
|
|
481
|
+
runId: string;
|
|
482
|
+
seq: number;
|
|
483
|
+
name: string;
|
|
484
|
+
phase: 'running' | 'completed' | 'failed';
|
|
485
|
+
/** Epoch ms the step body began (all phases) and settled (`completed`/`failed`). */
|
|
486
|
+
startedAt: number;
|
|
487
|
+
finishedAt?: number;
|
|
488
|
+
/** The replayed result / failure for the settled phases. */
|
|
489
|
+
output?: unknown;
|
|
490
|
+
error?: StepError;
|
|
491
|
+
/** Sub-process + log trail the step emitted so far (the handler's p-processes). */
|
|
492
|
+
events?: StepEvent[];
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Advances a workflow run one turn. The engine has one per workflow: the default {@link InProcess}
|
|
496
|
+
* one runs a registered TS function with the in-process replay machinery; a remote one dispatches a
|
|
497
|
+
* {@link WorkflowTask} to a worker (Python) and awaits its {@link WorkflowDecision}. Either way the
|
|
498
|
+
* engine applies the returned decision — so recovery, timers, singleton and dead-letter stay engine
|
|
499
|
+
* concerns, identical for in-process and remote workflows.
|
|
500
|
+
*/
|
|
501
|
+
export interface WorkflowExecutor {
|
|
502
|
+
advance(run: WorkflowRun, history: HistoryEvent[], pendingSignals?: WorkflowTask['pendingSignals']): Promise<WorkflowDecision>;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* A transport in an ordered pool, identified by `id`. The engine dispatches on the first by default
|
|
506
|
+
* and fails over to the next on a dispatch error; a step can pin one via `ctx.call(…, { transport })`.
|
|
507
|
+
* The chosen `id` is stamped on the {@link RemoteTask} so a worker replies through the matching one.
|
|
508
|
+
*/
|
|
509
|
+
export interface NamedTransport {
|
|
510
|
+
id: string;
|
|
511
|
+
transport: Transport;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Decides where a freshly-`start`ed run executes. `start` creates the run as `pending` and hands its
|
|
515
|
+
* id here instead of running the body inline — so the API/caller never blocks on workflow execution.
|
|
516
|
+
* The default in-process dispatcher runs it on this instance (a microtask); a broker-backed one
|
|
517
|
+
* enqueues the id for a worker pool to consume (`engine.runOne(runId)`); a no-op one leaves it
|
|
518
|
+
* `pending` in the store for a worker's `runPending` poll to pick up (DB-only, caller-doesn't-execute).
|
|
519
|
+
*/
|
|
520
|
+
export interface RunDispatcher {
|
|
521
|
+
dispatch(runId: string): void | Promise<void>;
|
|
522
|
+
}
|
|
523
|
+
export interface Transport {
|
|
524
|
+
/** engine → worker */
|
|
525
|
+
dispatch(task: RemoteTask): Promise<void>;
|
|
526
|
+
/** worker → engine: a step finished (ok or error). */
|
|
527
|
+
onResult(handler: (result: StepResult) => Promise<void>): void;
|
|
528
|
+
/** worker → engine: liveness signal for an in-flight long step. */
|
|
529
|
+
onHeartbeat(handler: (beat: Heartbeat) => Promise<void>): void;
|
|
530
|
+
/** Release the transport's resources (broker workers, queues, connections) for a clean shutdown.
|
|
531
|
+
* Optional — an in-process transport has nothing to close. Called on `onApplicationShutdown`
|
|
532
|
+
* after the engine drains, so a deploy hands off instead of leaving the broker to time out. */
|
|
533
|
+
close?(): Promise<void>;
|
|
534
|
+
/** engine → workflow worker: dispatch a {@link WorkflowTask} (the polyglot-workflow path). Optional
|
|
535
|
+
* — only transports that carry workflow tasks (BullMQ) implement it; the {@link RemoteWorkflowExecutor}
|
|
536
|
+
* uses it + {@link onDecision} to advance a remote workflow over the broker. */
|
|
537
|
+
dispatchWorkflowTask?(task: WorkflowTask): Promise<void>;
|
|
538
|
+
/** workflow worker → engine: a replayed turn's {@link WorkflowDecision}. Pair with dispatchWorkflowTask. */
|
|
539
|
+
onDecision?(handler: (decision: WorkflowDecision) => Promise<void>): void;
|
|
540
|
+
/** workflow worker → engine: a LOCAL step's {@link WorkflowStepEvent}, streamed mid-turn so the
|
|
541
|
+
* engine can checkpoint it live. Point-to-point (a single engine instance consumes each event and
|
|
542
|
+
* persists it once — no cross-pod duplicate writes). Optional; only broker transports carry it. */
|
|
543
|
+
dispatchStepEvent?(event: WorkflowStepEvent): Promise<void>;
|
|
544
|
+
/** engine ← workflow worker: consume streamed {@link WorkflowStepEvent}s. Pair with dispatchStepEvent. */
|
|
545
|
+
onStepEvent?(handler: (event: WorkflowStepEvent) => Promise<void>): void;
|
|
546
|
+
/** Worker-health for a group: queue backlog + live worker heartbeats. Optional — only broker
|
|
547
|
+
* transports (BullMQ) that can introspect the task queue and the worker-heartbeat keys implement
|
|
548
|
+
* it. The engine aggregates this across its groups in {@link WorkflowEngine.workerHealth}. */
|
|
549
|
+
groupHealth?(group: string): Promise<GroupHealth>;
|
|
550
|
+
/** Distinct groups that currently have a live worker heartbeat — discovered from the heartbeat
|
|
551
|
+
* keyspace, so a group with workers but no engine-side registration (e.g. a local-step group)
|
|
552
|
+
* still surfaces. Pairs with {@link groupHealth}. */
|
|
553
|
+
listWorkerGroups?(): Promise<string[]>;
|
|
554
|
+
}
|
|
555
|
+
/** One worker's liveness record — a TTL'd heartbeat a worker refreshes while it's consuming. Its
|
|
556
|
+
* ABSENCE (the key expired) is the signal: a worker that died or stalled stops refreshing. */
|
|
557
|
+
export interface WorkerHeartbeat {
|
|
558
|
+
/** The worker group this instance serves (e.g. `pipeline`, `processing-workflows`). */
|
|
559
|
+
group: string;
|
|
560
|
+
/** Stable per-process id (host + pid), so N replicas of a group each show as a distinct worker. */
|
|
561
|
+
instanceId: string;
|
|
562
|
+
/** Epoch ms of the worker's most recent heartbeat. */
|
|
563
|
+
lastBeatAt: number;
|
|
564
|
+
}
|
|
565
|
+
/** Per-group worker-health snapshot: how much work is queued vs. how many workers are alive to do it.
|
|
566
|
+
* The actionable alert state is `depth > 0 && liveWorkers.length === 0` — work piling up with no
|
|
567
|
+
* consumer (exactly the failure where a worker is "alive but not consuming"). */
|
|
568
|
+
export interface GroupHealth {
|
|
569
|
+
group: string;
|
|
570
|
+
/** Outstanding jobs in the group's task queue (waiting + active + delayed + prioritized). */
|
|
571
|
+
depth: number;
|
|
572
|
+
/** Workers with a non-expired heartbeat for this group. */
|
|
573
|
+
liveWorkers: WorkerHeartbeat[];
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* The **control plane** — a broadcast pub/sub across ALL engine instances (every pod), separate
|
|
577
|
+
* from the {@link Transport}'s point-to-point work queues (`dispatch`/`onResult`). It carries what
|
|
578
|
+
* every instance may need regardless of who runs a given run: lifecycle events (so a dashboard-only
|
|
579
|
+
* pod can live-tail a run executing on a worker pod) and cancellation (so the pod actually running a
|
|
580
|
+
* run learns it was cancelled elsewhere). In-process implementations broadcast locally; a
|
|
581
|
+
* cross-process one (BullMQ) fans out over its broker (Redis pub/sub). Give the engine a
|
|
582
|
+
* `controlPlane` to enable cross-instance events/cancellation; omit it and the engine is local-only.
|
|
583
|
+
* A transport that can broadcast may implement this too and be passed as both.
|
|
584
|
+
*/
|
|
585
|
+
export interface ControlPlane {
|
|
586
|
+
publishControl(msg: ControlMessage): Promise<void>;
|
|
587
|
+
onControl(handler: (msg: ControlMessage) => void): void;
|
|
588
|
+
}
|
|
589
|
+
/** A message on the {@link ControlPlane}. `from` is the originating engine's `instanceId`, so a
|
|
590
|
+
* broker that echoes a publish back to its own subscriber (e.g. Redis pub/sub) can be deduped by
|
|
591
|
+
* the originator. */
|
|
592
|
+
export type ControlMessage = {
|
|
593
|
+
from?: string;
|
|
594
|
+
} & ({
|
|
595
|
+
kind: 'event';
|
|
596
|
+
event: EngineEvent;
|
|
597
|
+
} | {
|
|
598
|
+
kind: 'cancel';
|
|
599
|
+
runId: string;
|
|
600
|
+
} | {
|
|
601
|
+
kind: 'enqueued';
|
|
602
|
+
runId: string;
|
|
603
|
+
});
|
|
604
|
+
export type BackoffStrategy = 'fixed' | 'exp';
|
|
605
|
+
/**
|
|
606
|
+
* Options for `ctx.child` / `ctx.startChild`. A bare string passed instead is shorthand for
|
|
607
|
+
* `{ childId }`, so the existing `ctx.child(ref, input, 'my-id')` form keeps working.
|
|
608
|
+
*/
|
|
609
|
+
export interface ChildCallOptions {
|
|
610
|
+
/** Deterministic child run id; defaults to one derived from the parent run id + call position. */
|
|
611
|
+
childId?: string | undefined;
|
|
612
|
+
/**
|
|
613
|
+
* Dispatch priority for a REMOTE child workflow — stamped on the child run and carried onto every
|
|
614
|
+
* {@link WorkflowTask} dispatched to advance it, so an urgent child can jump ahead of enqueued
|
|
615
|
+
* lower-priority ones at the worker. Higher wins; absent = unprioritised. Ignored for an in-process
|
|
616
|
+
* (TS class) child, which runs in the engine and never hits a broker queue.
|
|
617
|
+
*/
|
|
618
|
+
priority?: number | undefined;
|
|
619
|
+
}
|
|
620
|
+
export interface StepOptions {
|
|
621
|
+
/** Max attempts before the step (and run) fails. */
|
|
622
|
+
retries?: number;
|
|
623
|
+
/** How the delay between retries grows: `fixed` (constant) or `exp` (doubles each attempt). */
|
|
624
|
+
backoff?: BackoffStrategy;
|
|
625
|
+
/** Base delay in ms between retries. Omit (or 0) to retry with no delay. */
|
|
626
|
+
backoffMs?: number;
|
|
627
|
+
/** Upper bound on the (exponential) backoff delay. */
|
|
628
|
+
backoffMaxMs?: number;
|
|
629
|
+
/** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. */
|
|
630
|
+
jitter?: boolean;
|
|
631
|
+
/**
|
|
632
|
+
* Liveness window for a **remote** step (`ctx.call`): if the worker produces no result and no
|
|
633
|
+
* heartbeat within this many ms, the engine presumes it dead and fails the dispatch with a
|
|
634
|
+
* `RemoteStepTimeout` (retryable — it re-dispatches per `retries`). Each heartbeat resets the
|
|
635
|
+
* window. Ignored for local steps. Omit to wait indefinitely.
|
|
636
|
+
*/
|
|
637
|
+
timeoutMs?: number;
|
|
638
|
+
/**
|
|
639
|
+
* Saga compensation: if this step completes but the run later **fails**, the engine runs the
|
|
640
|
+
* registered `compensate` callbacks in reverse order (undo what was done). Local steps only.
|
|
641
|
+
* Idempotency note: a step is already deduplicated by its deterministic `stepId` (runId:seq) —
|
|
642
|
+
* remote workers can use it as the idempotency key, so there's no separate key option.
|
|
643
|
+
*/
|
|
644
|
+
compensate?: () => Promise<void>;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* A typed handle to a step that runs on a remote worker. The `name` string is the contract:
|
|
648
|
+
* the worker registers a handler under the same name. `input`/`output` validate at the boundary.
|
|
649
|
+
*/
|
|
650
|
+
export interface RemoteStepDef<TInput = unknown, TOutput = unknown> extends StepOptions {
|
|
651
|
+
name: string;
|
|
652
|
+
/** Worker group expected to handle this step. */
|
|
653
|
+
group: string;
|
|
654
|
+
input: z.ZodType<TInput>;
|
|
655
|
+
output: z.ZodType<TOutput>;
|
|
656
|
+
/** Branding so `ctx.call` can infer types. */
|
|
657
|
+
readonly __remote: true;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* A durable webhook handle minted by {@link WorkflowCtx.webhook}. Hand `url` to a third party,
|
|
661
|
+
* then `await wait()` — the run suspends with zero compute until the external system POSTs the
|
|
662
|
+
* callback (delivered as `engine.signal(token, body)`), and resumes with the body.
|
|
663
|
+
*/
|
|
664
|
+
export interface DurableWebhook<TPayload = unknown> {
|
|
665
|
+
/** Deterministic signal token (`wh:<runId>:<seq>`) the callback delivers on — stable across replay. */
|
|
666
|
+
readonly token: string;
|
|
667
|
+
/**
|
|
668
|
+
* Public callback URL for `token`, built by the engine's `webhookUrl` option. Hand this to the
|
|
669
|
+
* third party. `undefined` when no builder is configured (use {@link DurableWebhook.token} to
|
|
670
|
+
* build your own).
|
|
671
|
+
*/
|
|
672
|
+
readonly url?: string | undefined;
|
|
673
|
+
/** Suspend until the callback arrives, then resume with its payload. */
|
|
674
|
+
wait(): Promise<TPayload>;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* The context handed to a workflow function. Every interaction with the outside world goes
|
|
678
|
+
* through it so the engine can checkpoint — the workflow body itself stays deterministic.
|
|
679
|
+
*/
|
|
680
|
+
export interface WorkflowCtx {
|
|
681
|
+
readonly runId: string;
|
|
682
|
+
/**
|
|
683
|
+
* Run a local durable step: executed once, then its result is checkpointed and replayed. The
|
|
684
|
+
* body receives a {@link StepLogger} to record debug/error lines and sub-process outcomes — these
|
|
685
|
+
* are checkpointed with the step and shown under it in the dashboard.
|
|
686
|
+
*/
|
|
687
|
+
step<TOutput>(name: string, fn: (log: StepLogger) => Promise<TOutput>, options?: StepOptions): Promise<TOutput>;
|
|
688
|
+
/**
|
|
689
|
+
* **Exactly-once** durable step for DB work: runs `fn` and writes the step's checkpoint in ONE
|
|
690
|
+
* store transaction, so the business write and the "done" marker commit atomically — a crash can
|
|
691
|
+
* never leave the write done-but-not-checkpointed (which a plain `ctx.step` would re-run). `fn`
|
|
692
|
+
* receives the store-native transaction handle (`tx` — a TypeORM/MikroORM `EntityManager`, a Prisma
|
|
693
|
+
* tx client, or a Drizzle tx); do your writes on it. Needs a SQL store that supports transactions
|
|
694
|
+
* (the bundled SQL adapters do); throws otherwise.
|
|
695
|
+
*/
|
|
696
|
+
transaction<TOutput>(name: string, fn: (tx: unknown) => Promise<TOutput>): Promise<TOutput>;
|
|
697
|
+
/**
|
|
698
|
+
* Call a durable **entity** op and await its result — the entity (`engine.registerEntity`) runs the
|
|
699
|
+
* op serialized per `key` over durable state. e.g. `await ctx.callEntity('cart', userId, 'add', item)`.
|
|
700
|
+
*/
|
|
701
|
+
callEntity<TResult = unknown>(name: string, key: string, op: string, arg?: unknown): Promise<TResult>;
|
|
702
|
+
/** Send a durable entity op without awaiting a result (fire-and-forget, dispatched once). */
|
|
703
|
+
signalEntity(name: string, key: string, op: string, arg?: unknown): Promise<void>;
|
|
704
|
+
/**
|
|
705
|
+
* Dispatch a typed remote step and await its checkpointed result. Options:
|
|
706
|
+
* - `queue` — subject the dispatch to a registered flow-control queue (concurrency / rate limit).
|
|
707
|
+
* - `priority` — admission priority within that queue; higher is admitted first when a slot is
|
|
708
|
+
* contended (default 0). No effect without a `queue`.
|
|
709
|
+
* - `fairnessKey` — the fairness bucket for a queue with `fairness: 'key'` (e.g. a tenant id);
|
|
710
|
+
* the queue round-robins across distinct keys so one key can't monopolize the budget. Defaults
|
|
711
|
+
* to the run id when omitted. No effect without a `queue`.
|
|
712
|
+
* - `transport` — pin the dispatch to a named transport in the pool (else the pool's first, with
|
|
713
|
+
* failover to the rest). See `engine.registerQueue` / the engine's `transports` option.
|
|
714
|
+
*/
|
|
715
|
+
call<TInput, TOutput>(step: RemoteStepDef<TInput, TOutput>, input: TInput, opts?: {
|
|
716
|
+
queue?: string;
|
|
717
|
+
priority?: number;
|
|
718
|
+
fairnessKey?: string;
|
|
719
|
+
transport?: string;
|
|
720
|
+
}): Promise<TOutput>;
|
|
721
|
+
/**
|
|
722
|
+
* Durable sleep: suspends the run for `duration` (e.g. `'30s'`, `'2h'`, `'7 days'`, or ms as a
|
|
723
|
+
* number) without consuming resources, resuming automatically once the timer is due — even
|
|
724
|
+
* across restarts.
|
|
725
|
+
*/
|
|
726
|
+
sleep(duration: string | number): Promise<void>;
|
|
727
|
+
/**
|
|
728
|
+
* Durable sleep until an **absolute** time (a `Date` or epoch ms) — like {@link sleep} but for a
|
|
729
|
+
* fixed deadline (e.g. "resume at midnight"). Resumes automatically once the time passes, across
|
|
730
|
+
* restarts. The recorded wake time is fixed on the first run, so it's replay-stable.
|
|
731
|
+
*/
|
|
732
|
+
sleepUntil(when: Date | number): Promise<void>;
|
|
733
|
+
/**
|
|
734
|
+
* End this run and **continue as a fresh execution** of the same workflow with `input` and a clean
|
|
735
|
+
* history — for long-running / looping workflows that would otherwise accumulate unbounded
|
|
736
|
+
* checkpoints (and slow replays). The next run gets id `<runId>~N`. Terminal: it always throws, so
|
|
737
|
+
* code after it never runs. Carry forward whatever state the next iteration needs in `input`.
|
|
738
|
+
*/
|
|
739
|
+
continueAsNew(input?: unknown): Promise<never>;
|
|
740
|
+
/**
|
|
741
|
+
* Suspend the run until an external `engine.signal(token, payload)` arrives (e.g. a webhook or
|
|
742
|
+
* human approval), then resume with the payload. Waits indefinitely by default — no compute
|
|
743
|
+
* consumed. Pass `{ timeoutMs }` to bound the wait: if the deadline passes first the call throws
|
|
744
|
+
* a `SignalTimeoutError` (catch it in the workflow to branch).
|
|
745
|
+
*/
|
|
746
|
+
waitForSignal<TPayload>(token: string, opts?: {
|
|
747
|
+
timeoutMs?: number;
|
|
748
|
+
}): Promise<TPayload>;
|
|
749
|
+
/**
|
|
750
|
+
* Wait for a named **event** published via `engine.publishEvent(name, payload)`, then resume with
|
|
751
|
+
* the payload. Unlike a signal (point-to-point by token), events are name-based pub/sub: pass an
|
|
752
|
+
* optional `match` (a subset of the payload that must deep-equal) so a publish fans out only to the
|
|
753
|
+
* runs it concerns — e.g. `ctx.waitForEvent('payment.settled', { match: { orderId } })`. `timeoutMs`
|
|
754
|
+
* bounds the wait (throws `SignalTimeoutError`). No compute consumed while waiting.
|
|
755
|
+
*/
|
|
756
|
+
waitForEvent<TPayload>(name: string, opts?: {
|
|
757
|
+
match?: Record<string, unknown>;
|
|
758
|
+
timeoutMs?: number;
|
|
759
|
+
}): Promise<TPayload>;
|
|
760
|
+
/**
|
|
761
|
+
* An external task with **async completion**: run `dispatch` once (checkpointed — e.g. send to a
|
|
762
|
+
* queue, kick off a non-durable worker or a foreign service like a Python process), then suspend
|
|
763
|
+
* with zero compute until `engine.completeTask(runId, name, result)` (or `failTask`) reports back,
|
|
764
|
+
* and resume with the result. The durable, first-class counterpart of the hand-rolled
|
|
765
|
+
* "dispatch over SQS → wait for COMPLETE_PHASE → signal" pattern. `name` must be unique per run.
|
|
766
|
+
*/
|
|
767
|
+
task<TResult>(name: string, dispatch: () => Promise<void>, options?: StepOptions): Promise<TResult>;
|
|
768
|
+
/**
|
|
769
|
+
* Run another registered workflow as a **tracked child** and await its result: starts it once and
|
|
770
|
+
* suspends — zero compute — until the child reaches a terminal state, then resumes with the child's
|
|
771
|
+
* output (or throws a FatalError if the child failed). `childId` defaults to a deterministic id
|
|
772
|
+
* derived from this run and the call position, so it's stable across replay.
|
|
773
|
+
*
|
|
774
|
+
* Pass the child's **class** (`ctx.child(ShippingWorkflow, input)`) for a typed input + result; pass
|
|
775
|
+
* a **string** name for a cross-runtime child (e.g. a Python workflow) where there's no class.
|
|
776
|
+
*/
|
|
777
|
+
child<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, options?: string | ChildCallOptions): Promise<WorkflowOutputOf<C>>;
|
|
778
|
+
child<TOutput>(workflow: string, input: unknown, options?: string | ChildCallOptions): Promise<TOutput>;
|
|
779
|
+
/**
|
|
780
|
+
* Start a child workflow **fire-and-forget**: dispatches it once (checkpointed, replay-safe) and
|
|
781
|
+
* returns its run id immediately — the parent keeps running instead of suspending. Use it to kick
|
|
782
|
+
* off side work (an audit log, a notification) you don't need to wait on, or to fan out: collect
|
|
783
|
+
* the ids, then later `await ctx.child(...)` each with the same id to join (the start is idempotent
|
|
784
|
+
* by id, so the child runs exactly once). Class or string ref, like {@link child}.
|
|
785
|
+
*/
|
|
786
|
+
startChild<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, options?: string | ChildCallOptions): Promise<string>;
|
|
787
|
+
startChild(workflow: string, input: unknown, options?: string | ChildCallOptions): Promise<string>;
|
|
788
|
+
/**
|
|
789
|
+
* Pause the run at this point until a human resumes it from the dashboard (or
|
|
790
|
+
* `engine.continue(runId)`). Records a visible `pending` checkpoint so the breakpoint shows up
|
|
791
|
+
* in the timeline, then suspends with zero compute — the durable equivalent of a debugger
|
|
792
|
+
* breakpoint. Gate it on your own config to make breakpoints opt-in per run:
|
|
793
|
+
* `if (cfg.breakAfterExtraction) await ctx.breakpoint('after-extraction')`.
|
|
794
|
+
*/
|
|
795
|
+
breakpoint(label?: string): Promise<void>;
|
|
796
|
+
/**
|
|
797
|
+
* Mint a durable webhook: returns a handle with a deterministic `token` and (if the engine has a
|
|
798
|
+
* `webhookUrl` builder) a public `url`. Hand the url to a third party — inside a `ctx.step` — then
|
|
799
|
+
* `await handle.wait()` to suspend with zero compute until they POST the callback (the dashboard
|
|
800
|
+
* turns that POST into `engine.signal(token, body)`). The first-class, replay-safe version of
|
|
801
|
+
* "expose a callback URL and wait for it".
|
|
802
|
+
*/
|
|
803
|
+
webhook<TPayload>(): DurableWebhook<TPayload>;
|
|
804
|
+
/**
|
|
805
|
+
* Publish a named, queryable value from inside the run — the latest value for `key` is readable
|
|
806
|
+
* externally via `engine.getEvent(runId, key)` while the run is still in flight (progress, a
|
|
807
|
+
* partial result, a status). Checkpointed and replay-safe (overwrites the previous value for the
|
|
808
|
+
* same key). The read side has no effect on the run — the durable, suspend-model counterpart of a
|
|
809
|
+
* Temporal query.
|
|
810
|
+
*/
|
|
811
|
+
setEvent<TValue>(key: string, value: TValue): Promise<void>;
|
|
812
|
+
/**
|
|
813
|
+
* Expose a named **update point**: suspend until an external `engine.update(runId, name, arg)`
|
|
814
|
+
* delivers `arg`, then resume with it. The update is run-scoped (`name` need only be unique within
|
|
815
|
+
* the run) and gated by any validator registered via `engine.registerUpdateValidator` — a rejected
|
|
816
|
+
* update never reaches here. Pass `{ timeoutMs }` to bound the wait (throws `SignalTimeoutError`).
|
|
817
|
+
* The durable counterpart of a Temporal update handler.
|
|
818
|
+
*/
|
|
819
|
+
onUpdate<TArg>(name: string, opts?: {
|
|
820
|
+
timeoutMs?: number;
|
|
821
|
+
}): Promise<TArg>;
|
|
822
|
+
/**
|
|
823
|
+
* Guard an in-place workflow change without a new version. Wrap the changed code in
|
|
824
|
+
* `if (await ctx.patched('my-change')) { …new… } else { …old… }`: a fresh run records a marker and
|
|
825
|
+
* takes the new branch (`true`); a run already recorded under the old code keeps the old branch
|
|
826
|
+
* (`false`), because its history has a real step where the marker would sit. The marker is
|
|
827
|
+
* position-transparent for old runs (it doesn't shift their recorded steps), so guarding code is
|
|
828
|
+
* replay-safe. Once every old run has drained, remove the guard (keep the new branch).
|
|
829
|
+
*/
|
|
830
|
+
patched(id: string): Promise<boolean>;
|
|
831
|
+
/**
|
|
832
|
+
* Deterministic wall-clock (epoch ms): records the time on the first run and replays the SAME
|
|
833
|
+
* value afterwards. Use this instead of `Date.now()` inside a workflow — a raw `Date.now()` returns
|
|
834
|
+
* a different value on every replay, which silently corrupts a durable run.
|
|
835
|
+
*/
|
|
836
|
+
now(): Promise<number>;
|
|
837
|
+
/**
|
|
838
|
+
* Deterministic random in `[0, 1)`: recorded once, then replayed. Use instead of `Math.random()`
|
|
839
|
+
* (same replay-safety reason as {@link now}).
|
|
840
|
+
*/
|
|
841
|
+
random(): Promise<number>;
|
|
842
|
+
/** Deterministic UUID v4: recorded once, then replayed. Use instead of `crypto.randomUUID()`. */
|
|
843
|
+
uuid(): Promise<string>;
|
|
844
|
+
}
|
|
845
|
+
/** Result of executing or resuming a workflow run. */
|
|
846
|
+
export interface RunResult {
|
|
847
|
+
runId: string;
|
|
848
|
+
status: RunStatus;
|
|
849
|
+
output?: unknown;
|
|
850
|
+
error?: StepError | undefined;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Validates an incoming `engine.update` before it is delivered to the run. Throw (or return a
|
|
854
|
+
* non-empty string) to reject — the run is left untouched. Return nothing/void to accept. May be
|
|
855
|
+
* async (e.g. a business-rule check against a DB).
|
|
856
|
+
*/
|
|
857
|
+
export type UpdateValidator<TArg = unknown> = (arg: TArg) => void | string | Promise<void | string>;
|
|
858
|
+
/** Outcome of `engine.update`: rejected by the validator, or accepted and delivered. */
|
|
859
|
+
export type UpdateResult = {
|
|
860
|
+
accepted: false;
|
|
861
|
+
reason: string;
|
|
862
|
+
} | {
|
|
863
|
+
accepted: true;
|
|
864
|
+
run: RunResult | null;
|
|
865
|
+
};
|
|
866
|
+
export type EngineEventType = 'run.started' | 'run.completed' | 'run.failed' | 'run.suspended' | 'step.started' | 'step.completed' | 'step.failed' | 'step.progress';
|
|
867
|
+
/**
|
|
868
|
+
* A lifecycle event emitted by the engine. The observability surfaces (dashboard, OTel, the
|
|
869
|
+
* Telescope integration) all subscribe to these rather than reaching into the store.
|
|
870
|
+
*/
|
|
871
|
+
export interface EngineEvent {
|
|
872
|
+
type: EngineEventType;
|
|
873
|
+
runId: string;
|
|
874
|
+
workflow?: string | undefined;
|
|
875
|
+
seq?: number | undefined;
|
|
876
|
+
name?: string | undefined;
|
|
877
|
+
kind?: StepKind | undefined;
|
|
878
|
+
output?: unknown;
|
|
879
|
+
error?: StepError | undefined;
|
|
880
|
+
/** Wall-clock duration of the unit that just finished (step or run), when known. */
|
|
881
|
+
durationMs?: number | undefined;
|
|
882
|
+
/** For a remote step: how long it waited in the queue before a worker picked it up. */
|
|
883
|
+
queueMs?: number | undefined;
|
|
884
|
+
/** The live step event carried by a `step.progress` (the single log line / sub-process outcome a
|
|
885
|
+
* running step just emitted). Absent on lifecycle events. */
|
|
886
|
+
event?: StepEvent | undefined;
|
|
887
|
+
at: Date;
|
|
888
|
+
}
|
|
889
|
+
export type EngineListener = (event: EngineEvent) => void;
|
|
890
|
+
/** What a {@link StepInterceptor} is told about the local step it is wrapping. */
|
|
891
|
+
export interface StepInvocation {
|
|
892
|
+
readonly runId: string;
|
|
893
|
+
readonly workflow: string;
|
|
894
|
+
/** The step name passed to `ctx.step(name, ...)` (also `'now'`/`'random'`/`'uuid'` internals). */
|
|
895
|
+
readonly stepName: string;
|
|
896
|
+
/** The step's logical position within the run. */
|
|
897
|
+
readonly seq: number;
|
|
898
|
+
/** 1-based attempt number — increments across `ctx.step` retries. */
|
|
899
|
+
readonly attempt: number;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Wraps the **real execution** of a local `ctx.step` (Template/Nest-style onion middleware). Call
|
|
903
|
+
* `next()` to run the step body (or the next interceptor) and return — or transform — its result;
|
|
904
|
+
* throw to fail the step. First-registered runs outermost. Interceptors fire only when a step
|
|
905
|
+
* actually executes, NOT on replay (a replayed step returns its recorded output without running),
|
|
906
|
+
* so they see true execution timing. Register with `engine.use`.
|
|
907
|
+
*/
|
|
908
|
+
export type StepInterceptor = (invocation: StepInvocation, next: () => Promise<unknown>) => Promise<unknown>;
|
|
909
|
+
//# sourceMappingURL=interfaces.d.ts.map
|