@dudousxd/nestjs-durable-core 0.51.0 → 0.53.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/dist/index.cjs +198 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +125 -1
- package/dist/index.d.ts +125 -1
- package/dist/index.js +196 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -628,6 +628,16 @@ interface StateStore {
|
|
|
628
628
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
629
629
|
/** List waiters whose `token` starts with `prefix` — used to fan out an event to its subscribers. */
|
|
630
630
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
631
|
+
/**
|
|
632
|
+
* Delete the EXACT waiter row — `token` AND `runId` AND `seq` must all match — no-op if absent.
|
|
633
|
+
* Unlike {@link takeSignalWaiter}, which deletes ANY row for `token` (fine when the caller just won
|
|
634
|
+
* the race to consume it), this is for a caller removing its OWN registration after resolving the
|
|
635
|
+
* wait some other way (a buffered hit, a timeout): blind `takeSignalWaiter(token)` there could steal
|
|
636
|
+
* a DIFFERENT run's waiter that has since claimed the same token (`token` is the store's primary key,
|
|
637
|
+
* so a later `putSignalWaiter` for the same token replaces the row). The exact-match variant only
|
|
638
|
+
* ever removes the row this caller itself put there.
|
|
639
|
+
*/
|
|
640
|
+
removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
631
641
|
/**
|
|
632
642
|
* Buffer a signal whose waiter hasn't arrived yet, so the next `waitForSignal(token)` consumes it
|
|
633
643
|
* instead of it being lost (FIFO per token). Makes signals reliable regardless of timing and
|
|
@@ -1749,6 +1759,59 @@ declare function runInWorkflowCtx<T>(ctx: WorkflowCtx, fn: () => T): T;
|
|
|
1749
1759
|
*/
|
|
1750
1760
|
declare function currentWorkflowCtx(): WorkflowCtx | undefined;
|
|
1751
1761
|
|
|
1762
|
+
/**
|
|
1763
|
+
* Cross-package, cross-runtime marker for engine CONTROL-FLOW signals — {@link WorkflowSuspended}
|
|
1764
|
+
* and {@link ContinueAsNew} (this package), plus the thin worker's {@link
|
|
1765
|
+
* import('@dudousxd/durable-worker').Suspend} (`@dudousxd/durable-worker`, the BullMQ
|
|
1766
|
+
* route-by-handler runtime). These are thrown by `WorkflowCtx`/`WorkflowContext` ops to unwind the
|
|
1767
|
+
* current turn — never real failures — so a workflow's `catch` block MUST rethrow them untouched.
|
|
1768
|
+
* Running a failure/cleanup path on a control-flow signal corrupts the run: it emits extra
|
|
1769
|
+
* `recordStep` commands into history that a subsequent replay didn't produce the first time,
|
|
1770
|
+
* surfacing as a `NonDeterminismError`/`NondeterminismError` on resume.
|
|
1771
|
+
*
|
|
1772
|
+
* `Symbol.for` (the GLOBAL registry), NOT a plain `Symbol()` — so a duplicate copy of this module
|
|
1773
|
+
* (pnpm peer-dependency multiplexing, a dual ESM/CJS load, or the SEPARATE `@dudousxd/durable-worker`
|
|
1774
|
+
* package declaring the same `Symbol.for` key locally) still collapses onto the SAME symbol. Same
|
|
1775
|
+
* rationale as `DURABLE_STEP_NAME` in `./step-name-symbol.ts`.
|
|
1776
|
+
*
|
|
1777
|
+
* Deliberately checked as a stamped property, NOT `instanceof` — `instanceof` only recognizes a
|
|
1778
|
+
* signal thrown by the SAME class the checking code imported, which fails across the in-process
|
|
1779
|
+
* engine (`@dudousxd/nestjs-durable-core`'s `WorkflowSuspended`/`ContinueAsNew`) and the thin worker
|
|
1780
|
+
* (`@dudousxd/durable-worker`'s `Suspend`) — two different classes for what is, from a workflow
|
|
1781
|
+
* author's catch block, the same "this isn't a real error, let it through" event.
|
|
1782
|
+
*
|
|
1783
|
+
* NOT designed to survive a serialization boundary (e.g. JSON over the wire) — these signals are
|
|
1784
|
+
* always thrown and caught in-process within a single replay turn, never (de)serialized, so a
|
|
1785
|
+
* stamped-but-not-cloneable property is sufficient.
|
|
1786
|
+
*/
|
|
1787
|
+
declare const CONTROL_FLOW_SIGNAL: unique symbol;
|
|
1788
|
+
/**
|
|
1789
|
+
* True for any engine control-flow signal (currently: {@link WorkflowSuspended}, {@link
|
|
1790
|
+
* ContinueAsNew}, and the thin worker's `Suspend`) — regardless of which runtime or module instance
|
|
1791
|
+
* threw it. Workflow `catch` blocks MUST rethrow these untouched; running a failure/cleanup path on
|
|
1792
|
+
* one corrupts the run's history (extra commands recorded during what replay will later see as a
|
|
1793
|
+
* suspend, not a failure) and the resumed replay dies with a non-determinism error.
|
|
1794
|
+
*
|
|
1795
|
+
* Deliberately NOT control-flow (do not add to this predicate):
|
|
1796
|
+
* - `Cancelled` (worker) / a cancelled run — a TERMINAL outcome the consumer may legitimately want
|
|
1797
|
+
* to observe and react to (e.g. release a lock), not a mid-turn unwind to rethrow untouched.
|
|
1798
|
+
* - `StepFailed` (worker) / an awaited step rejection reaching the engine — a REAL failure a
|
|
1799
|
+
* workflow's `catch` is meant to compensate for; misclassifying it as control-flow would swallow
|
|
1800
|
+
* genuine errors instead of protecting them.
|
|
1801
|
+
*
|
|
1802
|
+
* @example
|
|
1803
|
+
* ```ts
|
|
1804
|
+
* try {
|
|
1805
|
+
* await ctx.step('chargeCard', input);
|
|
1806
|
+
* } catch (error) {
|
|
1807
|
+
* if (isWorkflowControlFlowSignal(error)) throw error; // suspend/continue-as-new: rethrow as-is
|
|
1808
|
+
* await ctx.step('refund', input); // a REAL failure — safe to run cleanup here
|
|
1809
|
+
* throw error;
|
|
1810
|
+
* }
|
|
1811
|
+
* ```
|
|
1812
|
+
*/
|
|
1813
|
+
declare function isWorkflowControlFlowSignal(error: unknown): boolean;
|
|
1814
|
+
|
|
1752
1815
|
/**
|
|
1753
1816
|
* Back-compat alias of {@link WorkflowCtx}. `localStep` — the checkpointed in-process step primitive
|
|
1754
1817
|
* used for saga compensation — is now part of the public {@link WorkflowCtx}, so this type adds
|
|
@@ -2320,8 +2383,32 @@ declare class WorkflowEngine {
|
|
|
2320
2383
|
/**
|
|
2321
2384
|
* Resume every suspended run whose durable timer is due. Call periodically (a poller) and on
|
|
2322
2385
|
* boot. A run still not due re-suspends cheaply without running new work.
|
|
2386
|
+
*
|
|
2387
|
+
* Piggy-backs the signal-waiter reconcile safety net (see {@link reconcileSignalWaiter}): a crash
|
|
2388
|
+
* between the waiter/buffer pairing ops on EITHER side (this run's own `putSignalWaiter` →
|
|
2389
|
+
* `takeBufferedSignal`, or a signaler's `takeSignalWaiter` → `bufferSignal` → re-check — see
|
|
2390
|
+
* `signal`'s interleaving proof) can leave both rows stranded with nothing left to pair them: the
|
|
2391
|
+
* waiter's turn already suspended, the signaler that buffered it already returned. Event-wait
|
|
2392
|
+
* suspends already carry a `reconcileMs` fallback `wakeAt` (see {@link reconcileWakeAt}) precisely so
|
|
2393
|
+
* they show up HERE — that's what makes this due-timer pass the natural place for the sweep. One
|
|
2394
|
+
* bulk waiter scan, filtered to just this tick's batch, keeps it cheap regardless of store size.
|
|
2323
2395
|
*/
|
|
2324
2396
|
resumeDueTimers(nowMs?: number): Promise<RunResult[]>;
|
|
2397
|
+
/**
|
|
2398
|
+
* The reconcile safety net's per-run check (see {@link resumeDueTimers}): if this suspended run has
|
|
2399
|
+
* a registered signal waiter AND its token already has a buffered payload, pair and deliver them
|
|
2400
|
+
* directly instead of falling through to a full replay. Event/child/breakpoint waiter tokens are
|
|
2401
|
+
* never buffered (`publishEvent` doesn't buffer, and a child/breakpoint notifies via `signal` only
|
|
2402
|
+
* once its counterpart is already registered) — so `takeBufferedSignal` on those is a guaranteed
|
|
2403
|
+
* miss, and this is naturally signal-only without needing to classify the token.
|
|
2404
|
+
*
|
|
2405
|
+
* Returns `undefined` (not settled — `resumeLeased` proceeds with its normal `resume`) when there's
|
|
2406
|
+
* no waiter or no buffered hit. Deliberately never returns `null`: a delivered signal always resumes
|
|
2407
|
+
* via {@link deliverSignal}, and even a `NamespaceMismatch` there is reported as a real (non-nullish)
|
|
2408
|
+
* result — returning `null` would read as "not settled" to `resumeLeased`'s `??` fallback and
|
|
2409
|
+
* trigger a second, redundant `resume` call on the same run.
|
|
2410
|
+
*/
|
|
2411
|
+
private reconcileSignalWaiter;
|
|
2325
2412
|
/**
|
|
2326
2413
|
* Lease and execute one run by id — the worker side of dispatch. Acquires the recovery lease (so
|
|
2327
2414
|
* exactly one instance runs it), then resumes the body. Returns the result, or null if another
|
|
@@ -2394,6 +2481,37 @@ declare class WorkflowEngine {
|
|
|
2394
2481
|
publishEvent(name: string, payload: unknown, opts?: {
|
|
2395
2482
|
id?: string;
|
|
2396
2483
|
}): Promise<number>;
|
|
2484
|
+
/**
|
|
2485
|
+
* Deliver a signal to an already-known waiter: write its resolving `signal:<token>` checkpoint and
|
|
2486
|
+
* resume the run. Shared by {@link signal}'s direct hit, its post-buffer re-check reclaim, and the
|
|
2487
|
+
* due-timer reconcile sweep ({@link reconcileSignalWaiter}) — all three resolve a waiter the exact
|
|
2488
|
+
* same way.
|
|
2489
|
+
*/
|
|
2490
|
+
private deliverSignal;
|
|
2491
|
+
/**
|
|
2492
|
+
* Deliver an external signal to the run waiting on `token`, resume it with `payload`, and return
|
|
2493
|
+
* the run result — or null if no run is (or ends up) waiting for it.
|
|
2494
|
+
*
|
|
2495
|
+
* Race with the waiter side (`waitForSignal`'s check → `putSignalWaiter` → re-check, same shape in
|
|
2496
|
+
* the remote `waitSignal` command): a waiter can register in the sliver between this method's
|
|
2497
|
+
* initial `takeSignalWaiter` miss and its `bufferSignal` write, and the naive old flow (buffer and
|
|
2498
|
+
* return) would then leave BOTH a buffered payload and a registered waiter sitting in the store with
|
|
2499
|
+
* nothing left to pair them — a lost wake, forever suspended.
|
|
2500
|
+
*
|
|
2501
|
+
* The interleaving proof: the waiter side does check → put → re-check, this side does take → buffer
|
|
2502
|
+
* → re-check. Whichever side's SECOND look runs last is guaranteed to observe the other side's
|
|
2503
|
+
* write, because it always follows that write in real time:
|
|
2504
|
+
* - If the waiter's `put` lands before this method's first `take`, the take just finds it directly
|
|
2505
|
+
* (the pre-existing, non-racy path).
|
|
2506
|
+
* - If the waiter's `put` lands after this method's `bufferSignal`, our re-check below either sees
|
|
2507
|
+
* it (if `put` also lands before the re-check) or the waiter's OWN post-put re-check sees our
|
|
2508
|
+
* buffer (if `put` lands after our re-check but the waiter hasn't re-checked yet either — its
|
|
2509
|
+
* re-check strictly follows its own put).
|
|
2510
|
+
* - If the waiter's `put` lands between this method's `take` and its re-`take`, the re-check below
|
|
2511
|
+
* catches it.
|
|
2512
|
+
* Either way, the destructive `take*` ops (on the waiter row, on the buffer) arbitrate: whichever
|
|
2513
|
+
* side actually removes the row is the one that delivers, so a signal is never delivered twice.
|
|
2514
|
+
*/
|
|
2397
2515
|
signal(token: string, payload: unknown): Promise<RunResult | null>;
|
|
2398
2516
|
/**
|
|
2399
2517
|
* Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
|
|
@@ -2875,6 +2993,8 @@ declare class GatherError extends Error {
|
|
|
2875
2993
|
declare class WorkflowSuspended extends Error {
|
|
2876
2994
|
/** Epoch ms to auto-resume (durable sleep), or undefined when waiting on an external signal. */
|
|
2877
2995
|
readonly wakeAt?: number | undefined;
|
|
2996
|
+
/** Marks this as a control-flow signal — see {@link isWorkflowControlFlowSignal}. */
|
|
2997
|
+
readonly [CONTROL_FLOW_SIGNAL] = true;
|
|
2878
2998
|
constructor(wakeAt?: number);
|
|
2879
2999
|
}
|
|
2880
3000
|
/**
|
|
@@ -2884,6 +3004,8 @@ declare class WorkflowSuspended extends Error {
|
|
|
2884
3004
|
*/
|
|
2885
3005
|
declare class ContinueAsNew extends Error {
|
|
2886
3006
|
readonly input: unknown;
|
|
3007
|
+
/** Marks this as a control-flow signal — see {@link isWorkflowControlFlowSignal}. */
|
|
3008
|
+
readonly [CONTROL_FLOW_SIGNAL] = true;
|
|
2887
3009
|
constructor(input: unknown);
|
|
2888
3010
|
}
|
|
2889
3011
|
|
|
@@ -3098,6 +3220,7 @@ declare class CodecStateStore implements StateStore {
|
|
|
3098
3220
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
3099
3221
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
3100
3222
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
3223
|
+
removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
3101
3224
|
bufferSignal(token: string, payload: unknown): Promise<void>;
|
|
3102
3225
|
takeBufferedSignal(token: string): Promise<{
|
|
3103
3226
|
payload: unknown;
|
|
@@ -3465,6 +3588,7 @@ declare class InMemoryStateStore implements StateStore {
|
|
|
3465
3588
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
3466
3589
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
3467
3590
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
3591
|
+
removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
3468
3592
|
transaction<T>(work: (tx: {
|
|
3469
3593
|
raw: unknown;
|
|
3470
3594
|
saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
|
|
@@ -3512,4 +3636,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
|
|
|
3512
3636
|
onControl(handler: (msg: ControlMessage) => void): void;
|
|
3513
3637
|
}
|
|
3514
3638
|
|
|
3515
|
-
export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DurableTopology, type DurableWebhook, DurableWorkflow, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, type MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RunAttributeRow, type RunDetail, type RunDispatcher, type RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerHeartbeat, type WorkerStatus, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, classifyWaiterToken, collectMetrics, createStepLogger, currentWorkflowCtx, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, indexWaitersByRun, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, readSearchAttributes, resolveRunWaiting, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, tenantGroup, workflowName };
|
|
3639
|
+
export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, CONTROL_FLOW_SIGNAL, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DurableTopology, type DurableWebhook, DurableWorkflow, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, type MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RunAttributeRow, type RunDetail, type RunDispatcher, type RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerHeartbeat, type WorkerStatus, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, classifyWaiterToken, collectMetrics, createStepLogger, currentWorkflowCtx, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, indexWaitersByRun, isWorkflowControlFlowSignal, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, readSearchAttributes, resolveRunWaiting, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, tenantGroup, workflowName };
|
package/dist/index.d.ts
CHANGED
|
@@ -628,6 +628,16 @@ interface StateStore {
|
|
|
628
628
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
629
629
|
/** List waiters whose `token` starts with `prefix` — used to fan out an event to its subscribers. */
|
|
630
630
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
631
|
+
/**
|
|
632
|
+
* Delete the EXACT waiter row — `token` AND `runId` AND `seq` must all match — no-op if absent.
|
|
633
|
+
* Unlike {@link takeSignalWaiter}, which deletes ANY row for `token` (fine when the caller just won
|
|
634
|
+
* the race to consume it), this is for a caller removing its OWN registration after resolving the
|
|
635
|
+
* wait some other way (a buffered hit, a timeout): blind `takeSignalWaiter(token)` there could steal
|
|
636
|
+
* a DIFFERENT run's waiter that has since claimed the same token (`token` is the store's primary key,
|
|
637
|
+
* so a later `putSignalWaiter` for the same token replaces the row). The exact-match variant only
|
|
638
|
+
* ever removes the row this caller itself put there.
|
|
639
|
+
*/
|
|
640
|
+
removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
631
641
|
/**
|
|
632
642
|
* Buffer a signal whose waiter hasn't arrived yet, so the next `waitForSignal(token)` consumes it
|
|
633
643
|
* instead of it being lost (FIFO per token). Makes signals reliable regardless of timing and
|
|
@@ -1749,6 +1759,59 @@ declare function runInWorkflowCtx<T>(ctx: WorkflowCtx, fn: () => T): T;
|
|
|
1749
1759
|
*/
|
|
1750
1760
|
declare function currentWorkflowCtx(): WorkflowCtx | undefined;
|
|
1751
1761
|
|
|
1762
|
+
/**
|
|
1763
|
+
* Cross-package, cross-runtime marker for engine CONTROL-FLOW signals — {@link WorkflowSuspended}
|
|
1764
|
+
* and {@link ContinueAsNew} (this package), plus the thin worker's {@link
|
|
1765
|
+
* import('@dudousxd/durable-worker').Suspend} (`@dudousxd/durable-worker`, the BullMQ
|
|
1766
|
+
* route-by-handler runtime). These are thrown by `WorkflowCtx`/`WorkflowContext` ops to unwind the
|
|
1767
|
+
* current turn — never real failures — so a workflow's `catch` block MUST rethrow them untouched.
|
|
1768
|
+
* Running a failure/cleanup path on a control-flow signal corrupts the run: it emits extra
|
|
1769
|
+
* `recordStep` commands into history that a subsequent replay didn't produce the first time,
|
|
1770
|
+
* surfacing as a `NonDeterminismError`/`NondeterminismError` on resume.
|
|
1771
|
+
*
|
|
1772
|
+
* `Symbol.for` (the GLOBAL registry), NOT a plain `Symbol()` — so a duplicate copy of this module
|
|
1773
|
+
* (pnpm peer-dependency multiplexing, a dual ESM/CJS load, or the SEPARATE `@dudousxd/durable-worker`
|
|
1774
|
+
* package declaring the same `Symbol.for` key locally) still collapses onto the SAME symbol. Same
|
|
1775
|
+
* rationale as `DURABLE_STEP_NAME` in `./step-name-symbol.ts`.
|
|
1776
|
+
*
|
|
1777
|
+
* Deliberately checked as a stamped property, NOT `instanceof` — `instanceof` only recognizes a
|
|
1778
|
+
* signal thrown by the SAME class the checking code imported, which fails across the in-process
|
|
1779
|
+
* engine (`@dudousxd/nestjs-durable-core`'s `WorkflowSuspended`/`ContinueAsNew`) and the thin worker
|
|
1780
|
+
* (`@dudousxd/durable-worker`'s `Suspend`) — two different classes for what is, from a workflow
|
|
1781
|
+
* author's catch block, the same "this isn't a real error, let it through" event.
|
|
1782
|
+
*
|
|
1783
|
+
* NOT designed to survive a serialization boundary (e.g. JSON over the wire) — these signals are
|
|
1784
|
+
* always thrown and caught in-process within a single replay turn, never (de)serialized, so a
|
|
1785
|
+
* stamped-but-not-cloneable property is sufficient.
|
|
1786
|
+
*/
|
|
1787
|
+
declare const CONTROL_FLOW_SIGNAL: unique symbol;
|
|
1788
|
+
/**
|
|
1789
|
+
* True for any engine control-flow signal (currently: {@link WorkflowSuspended}, {@link
|
|
1790
|
+
* ContinueAsNew}, and the thin worker's `Suspend`) — regardless of which runtime or module instance
|
|
1791
|
+
* threw it. Workflow `catch` blocks MUST rethrow these untouched; running a failure/cleanup path on
|
|
1792
|
+
* one corrupts the run's history (extra commands recorded during what replay will later see as a
|
|
1793
|
+
* suspend, not a failure) and the resumed replay dies with a non-determinism error.
|
|
1794
|
+
*
|
|
1795
|
+
* Deliberately NOT control-flow (do not add to this predicate):
|
|
1796
|
+
* - `Cancelled` (worker) / a cancelled run — a TERMINAL outcome the consumer may legitimately want
|
|
1797
|
+
* to observe and react to (e.g. release a lock), not a mid-turn unwind to rethrow untouched.
|
|
1798
|
+
* - `StepFailed` (worker) / an awaited step rejection reaching the engine — a REAL failure a
|
|
1799
|
+
* workflow's `catch` is meant to compensate for; misclassifying it as control-flow would swallow
|
|
1800
|
+
* genuine errors instead of protecting them.
|
|
1801
|
+
*
|
|
1802
|
+
* @example
|
|
1803
|
+
* ```ts
|
|
1804
|
+
* try {
|
|
1805
|
+
* await ctx.step('chargeCard', input);
|
|
1806
|
+
* } catch (error) {
|
|
1807
|
+
* if (isWorkflowControlFlowSignal(error)) throw error; // suspend/continue-as-new: rethrow as-is
|
|
1808
|
+
* await ctx.step('refund', input); // a REAL failure — safe to run cleanup here
|
|
1809
|
+
* throw error;
|
|
1810
|
+
* }
|
|
1811
|
+
* ```
|
|
1812
|
+
*/
|
|
1813
|
+
declare function isWorkflowControlFlowSignal(error: unknown): boolean;
|
|
1814
|
+
|
|
1752
1815
|
/**
|
|
1753
1816
|
* Back-compat alias of {@link WorkflowCtx}. `localStep` — the checkpointed in-process step primitive
|
|
1754
1817
|
* used for saga compensation — is now part of the public {@link WorkflowCtx}, so this type adds
|
|
@@ -2320,8 +2383,32 @@ declare class WorkflowEngine {
|
|
|
2320
2383
|
/**
|
|
2321
2384
|
* Resume every suspended run whose durable timer is due. Call periodically (a poller) and on
|
|
2322
2385
|
* boot. A run still not due re-suspends cheaply without running new work.
|
|
2386
|
+
*
|
|
2387
|
+
* Piggy-backs the signal-waiter reconcile safety net (see {@link reconcileSignalWaiter}): a crash
|
|
2388
|
+
* between the waiter/buffer pairing ops on EITHER side (this run's own `putSignalWaiter` →
|
|
2389
|
+
* `takeBufferedSignal`, or a signaler's `takeSignalWaiter` → `bufferSignal` → re-check — see
|
|
2390
|
+
* `signal`'s interleaving proof) can leave both rows stranded with nothing left to pair them: the
|
|
2391
|
+
* waiter's turn already suspended, the signaler that buffered it already returned. Event-wait
|
|
2392
|
+
* suspends already carry a `reconcileMs` fallback `wakeAt` (see {@link reconcileWakeAt}) precisely so
|
|
2393
|
+
* they show up HERE — that's what makes this due-timer pass the natural place for the sweep. One
|
|
2394
|
+
* bulk waiter scan, filtered to just this tick's batch, keeps it cheap regardless of store size.
|
|
2323
2395
|
*/
|
|
2324
2396
|
resumeDueTimers(nowMs?: number): Promise<RunResult[]>;
|
|
2397
|
+
/**
|
|
2398
|
+
* The reconcile safety net's per-run check (see {@link resumeDueTimers}): if this suspended run has
|
|
2399
|
+
* a registered signal waiter AND its token already has a buffered payload, pair and deliver them
|
|
2400
|
+
* directly instead of falling through to a full replay. Event/child/breakpoint waiter tokens are
|
|
2401
|
+
* never buffered (`publishEvent` doesn't buffer, and a child/breakpoint notifies via `signal` only
|
|
2402
|
+
* once its counterpart is already registered) — so `takeBufferedSignal` on those is a guaranteed
|
|
2403
|
+
* miss, and this is naturally signal-only without needing to classify the token.
|
|
2404
|
+
*
|
|
2405
|
+
* Returns `undefined` (not settled — `resumeLeased` proceeds with its normal `resume`) when there's
|
|
2406
|
+
* no waiter or no buffered hit. Deliberately never returns `null`: a delivered signal always resumes
|
|
2407
|
+
* via {@link deliverSignal}, and even a `NamespaceMismatch` there is reported as a real (non-nullish)
|
|
2408
|
+
* result — returning `null` would read as "not settled" to `resumeLeased`'s `??` fallback and
|
|
2409
|
+
* trigger a second, redundant `resume` call on the same run.
|
|
2410
|
+
*/
|
|
2411
|
+
private reconcileSignalWaiter;
|
|
2325
2412
|
/**
|
|
2326
2413
|
* Lease and execute one run by id — the worker side of dispatch. Acquires the recovery lease (so
|
|
2327
2414
|
* exactly one instance runs it), then resumes the body. Returns the result, or null if another
|
|
@@ -2394,6 +2481,37 @@ declare class WorkflowEngine {
|
|
|
2394
2481
|
publishEvent(name: string, payload: unknown, opts?: {
|
|
2395
2482
|
id?: string;
|
|
2396
2483
|
}): Promise<number>;
|
|
2484
|
+
/**
|
|
2485
|
+
* Deliver a signal to an already-known waiter: write its resolving `signal:<token>` checkpoint and
|
|
2486
|
+
* resume the run. Shared by {@link signal}'s direct hit, its post-buffer re-check reclaim, and the
|
|
2487
|
+
* due-timer reconcile sweep ({@link reconcileSignalWaiter}) — all three resolve a waiter the exact
|
|
2488
|
+
* same way.
|
|
2489
|
+
*/
|
|
2490
|
+
private deliverSignal;
|
|
2491
|
+
/**
|
|
2492
|
+
* Deliver an external signal to the run waiting on `token`, resume it with `payload`, and return
|
|
2493
|
+
* the run result — or null if no run is (or ends up) waiting for it.
|
|
2494
|
+
*
|
|
2495
|
+
* Race with the waiter side (`waitForSignal`'s check → `putSignalWaiter` → re-check, same shape in
|
|
2496
|
+
* the remote `waitSignal` command): a waiter can register in the sliver between this method's
|
|
2497
|
+
* initial `takeSignalWaiter` miss and its `bufferSignal` write, and the naive old flow (buffer and
|
|
2498
|
+
* return) would then leave BOTH a buffered payload and a registered waiter sitting in the store with
|
|
2499
|
+
* nothing left to pair them — a lost wake, forever suspended.
|
|
2500
|
+
*
|
|
2501
|
+
* The interleaving proof: the waiter side does check → put → re-check, this side does take → buffer
|
|
2502
|
+
* → re-check. Whichever side's SECOND look runs last is guaranteed to observe the other side's
|
|
2503
|
+
* write, because it always follows that write in real time:
|
|
2504
|
+
* - If the waiter's `put` lands before this method's first `take`, the take just finds it directly
|
|
2505
|
+
* (the pre-existing, non-racy path).
|
|
2506
|
+
* - If the waiter's `put` lands after this method's `bufferSignal`, our re-check below either sees
|
|
2507
|
+
* it (if `put` also lands before the re-check) or the waiter's OWN post-put re-check sees our
|
|
2508
|
+
* buffer (if `put` lands after our re-check but the waiter hasn't re-checked yet either — its
|
|
2509
|
+
* re-check strictly follows its own put).
|
|
2510
|
+
* - If the waiter's `put` lands between this method's `take` and its re-`take`, the re-check below
|
|
2511
|
+
* catches it.
|
|
2512
|
+
* Either way, the destructive `take*` ops (on the waiter row, on the buffer) arbitrate: whichever
|
|
2513
|
+
* side actually removes the row is the one that delivers, so a signal is never delivered twice.
|
|
2514
|
+
*/
|
|
2397
2515
|
signal(token: string, payload: unknown): Promise<RunResult | null>;
|
|
2398
2516
|
/**
|
|
2399
2517
|
* Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
|
|
@@ -2875,6 +2993,8 @@ declare class GatherError extends Error {
|
|
|
2875
2993
|
declare class WorkflowSuspended extends Error {
|
|
2876
2994
|
/** Epoch ms to auto-resume (durable sleep), or undefined when waiting on an external signal. */
|
|
2877
2995
|
readonly wakeAt?: number | undefined;
|
|
2996
|
+
/** Marks this as a control-flow signal — see {@link isWorkflowControlFlowSignal}. */
|
|
2997
|
+
readonly [CONTROL_FLOW_SIGNAL] = true;
|
|
2878
2998
|
constructor(wakeAt?: number);
|
|
2879
2999
|
}
|
|
2880
3000
|
/**
|
|
@@ -2884,6 +3004,8 @@ declare class WorkflowSuspended extends Error {
|
|
|
2884
3004
|
*/
|
|
2885
3005
|
declare class ContinueAsNew extends Error {
|
|
2886
3006
|
readonly input: unknown;
|
|
3007
|
+
/** Marks this as a control-flow signal — see {@link isWorkflowControlFlowSignal}. */
|
|
3008
|
+
readonly [CONTROL_FLOW_SIGNAL] = true;
|
|
2887
3009
|
constructor(input: unknown);
|
|
2888
3010
|
}
|
|
2889
3011
|
|
|
@@ -3098,6 +3220,7 @@ declare class CodecStateStore implements StateStore {
|
|
|
3098
3220
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
3099
3221
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
3100
3222
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
3223
|
+
removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
3101
3224
|
bufferSignal(token: string, payload: unknown): Promise<void>;
|
|
3102
3225
|
takeBufferedSignal(token: string): Promise<{
|
|
3103
3226
|
payload: unknown;
|
|
@@ -3465,6 +3588,7 @@ declare class InMemoryStateStore implements StateStore {
|
|
|
3465
3588
|
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
3466
3589
|
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
3467
3590
|
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
3591
|
+
removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
3468
3592
|
transaction<T>(work: (tx: {
|
|
3469
3593
|
raw: unknown;
|
|
3470
3594
|
saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
|
|
@@ -3512,4 +3636,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
|
|
|
3512
3636
|
onControl(handler: (msg: ControlMessage) => void): void;
|
|
3513
3637
|
}
|
|
3514
3638
|
|
|
3515
|
-
export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DurableTopology, type DurableWebhook, DurableWorkflow, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, type MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RunAttributeRow, type RunDetail, type RunDispatcher, type RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerHeartbeat, type WorkerStatus, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, classifyWaiterToken, collectMetrics, createStepLogger, currentWorkflowCtx, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, indexWaitersByRun, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, readSearchAttributes, resolveRunWaiting, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, tenantGroup, workflowName };
|
|
3639
|
+
export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, CONTROL_FLOW_SIGNAL, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DurableTopology, type DurableWebhook, DurableWorkflow, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, type MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RunAttributeRow, type RunDetail, type RunDispatcher, type RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerHeartbeat, type WorkerStatus, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, classifyWaiterToken, collectMetrics, createStepLogger, currentWorkflowCtx, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, indexWaitersByRun, isWorkflowControlFlowSignal, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, readSearchAttributes, resolveRunWaiting, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, tenantGroup, workflowName };
|