@dudousxd/nestjs-durable-core 0.52.0 → 0.54.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.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
@@ -638,6 +648,39 @@ interface StateStore {
638
648
  takeBufferedSignal(token: string): Promise<{
639
649
  payload: unknown;
640
650
  } | null>;
651
+ /**
652
+ * Buffer a published event that matched NO live waiter, so a LATER `ctx.waitForEvent(name, { match })`
653
+ * still consumes it instead of it being silently dropped — the events analog of {@link bufferSignal}'s
654
+ * reliability contract for signals, but MATCH-based rather than token-based: an event's buffer is keyed
655
+ * by `name` alone (many waiters can share a name with different `match` criteria), so consumption is
656
+ * list ({@link listBufferedEvents}) + evaluate the WAITER's own match predicate + claim
657
+ * ({@link removeBufferedEvent}) — never a blind take, because the store has no way to know which
658
+ * candidate a given waiter wants. `input.id` is minted by the caller (engine.publishEvent) so
659
+ * {@link removeBufferedEvent} can later target this exact row.
660
+ */
661
+ bufferEvent(input: {
662
+ name: string;
663
+ payload: unknown;
664
+ id: string;
665
+ publishedAt: number;
666
+ }): Promise<void>;
667
+ /**
668
+ * Buffered events for `name`, OLDEST (`publishedAt`) first, capped at `limit`. A waiter scans these,
669
+ * evaluates its own `eventMatches(payload, match)` locally, and claims the one it wants via
670
+ * {@link removeBufferedEvent} — the match predicate belongs to the WAITER, never the store.
671
+ */
672
+ listBufferedEvents(name: string, limit: number): Promise<Array<{
673
+ id: string;
674
+ payload: unknown;
675
+ publishedAt: number;
676
+ }>>;
677
+ /**
678
+ * Atomically delete the buffered event `id`. Returns `true` iff a row was actually deleted, `false`
679
+ * if it was already gone (claimed by a concurrent waiter, reclaimed by `engine.publishEvent`'s own
680
+ * late re-check, or pruned as expired) — the arbiter under concurrency: whichever caller's delete
681
+ * returns `true` is the one that gets to deliver the payload; every other caller backs off.
682
+ */
683
+ removeBufferedEvent(id: string): Promise<boolean>;
641
684
  /**
642
685
  * Run `work` in a SINGLE store transaction — giving it the store-native transaction handle (`raw`)
643
686
  * for the caller's own DB writes plus a `saveCheckpoint` that commits IN THE SAME transaction, so a
@@ -1997,6 +2040,19 @@ interface WorkflowEngineDeps {
1997
2040
  * for a genuinely-orphaned run. Default 5min; set `0` to disable (the prior wake-forever behavior).
1998
2041
  */
1999
2042
  reconcileMs?: number | undefined;
2043
+ /**
2044
+ * Retention for a BUFFERED event that no `waitForEvent` ever claims (see
2045
+ * {@link WorkflowEngine.publishEvent}'s buffering — a publish matching no live waiter keeps ONE
2046
+ * copy around for a later matching wait). Default `undefined`: keep it until consumed, same as
2047
+ * {@link StateStore.bufferSignal}'s buffered signals — no silent expiry. Set a duration (ms) to
2048
+ * have the {@link resumeDueTimers} reconcile pass prune rows past `publishedAt + eventBufferTtlMs`
2049
+ * as it encounters them. Scoped to event NAMES the current reconcile tick's registered waiters
2050
+ * reference (the sweep has no reason to otherwise enumerate every buffered name) — a name with a
2051
+ * stale buffered event but zero waiters ever registered against it is not visited by this pass; that
2052
+ * is an accepted gap given `listBufferedEvents` is name-scoped by design (see interfaces.ts), not a
2053
+ * bug to route around here.
2054
+ */
2055
+ eventBufferTtlMs?: number | undefined;
2000
2056
  /**
2001
2057
  * Opt-in self-heal window (ms) for a remote step with NO `timeoutMs` whose dispatched job was LOST —
2002
2058
  * the worker crashed mid-step leaving no result, or the transport dropped the job (a Redis
@@ -2094,6 +2150,7 @@ declare class WorkflowEngine {
2094
2150
  private readonly maxRecoveryAttempts?;
2095
2151
  private readonly remoteAdvanceSilenceMs?;
2096
2152
  private readonly reconcileMs;
2153
+ private readonly eventBufferTtlMs;
2097
2154
  private readonly remoteRedispatchMs;
2098
2155
  private readonly remoteRedispatchMax;
2099
2156
  private readonly webhookUrl?;
@@ -2373,8 +2430,46 @@ declare class WorkflowEngine {
2373
2430
  /**
2374
2431
  * Resume every suspended run whose durable timer is due. Call periodically (a poller) and on
2375
2432
  * boot. A run still not due re-suspends cheaply without running new work.
2433
+ *
2434
+ * Piggy-backs the signal/event-waiter reconcile safety net (see {@link reconcileSignalWaiter}): a
2435
+ * crash between the waiter/buffer pairing ops on EITHER side (this run's own `putSignalWaiter` →
2436
+ * `takeBufferedSignal`/`listBufferedEvents`, or the other side's `takeSignalWaiter`/fan-out →
2437
+ * `bufferSignal`/`bufferEvent` → re-check — see `signal`'s interleaving proof, which `publishEvent`
2438
+ * mirrors for events) can leave both rows stranded with nothing left to pair them: the waiter's turn
2439
+ * already suspended, the other side that buffered it already returned. Event-wait suspends already
2440
+ * carry a `reconcileMs` fallback `wakeAt` (see {@link reconcileWakeAt}) precisely so they show up
2441
+ * HERE — that's what makes this due-timer pass the natural place for the sweep (and, for events, also
2442
+ * where an `eventBufferTtlMs` prune piggy-backs — see {@link reconcileBufferedEvent}). One bulk waiter
2443
+ * scan, filtered to just this tick's batch, keeps it cheap regardless of store size.
2376
2444
  */
2377
2445
  resumeDueTimers(nowMs?: number): Promise<RunResult[]>;
2446
+ /**
2447
+ * The reconcile safety net's per-run check (see {@link resumeDueTimers}): if this suspended run has
2448
+ * a registered signal OR event waiter and a matching payload is already buffered, pair and deliver
2449
+ * them directly instead of falling through to a full replay. Child/breakpoint waiter tokens are
2450
+ * never buffered (a child/breakpoint notifies via `signal` only once its counterpart is already
2451
+ * registered) — `takeBufferedSignal` on those is a guaranteed miss, so this only ever does real work
2452
+ * for a plain signal or an event waiter (classified via {@link isEventToken}).
2453
+ *
2454
+ * Also opportunistically prunes expired buffered EVENTS for this waiter's name when
2455
+ * {@link eventBufferTtlMs} is set (see its doc — scoped to names a registered waiter references in
2456
+ * this tick's batch, piggy-backing the SAME scan rather than a separate pass), even on a miss.
2457
+ *
2458
+ * Returns `undefined` (not settled — `resumeLeased` proceeds with its normal `resume`) when there's
2459
+ * no waiter or no buffered hit. Deliberately never returns `null`: a delivered signal always resumes
2460
+ * via {@link deliverSignal}, and even a `NamespaceMismatch` there is reported as a real (non-nullish)
2461
+ * result — returning `null` would read as "not settled" to `resumeLeased`'s `??` fallback and
2462
+ * trigger a second, redundant `resume` call on the same run.
2463
+ */
2464
+ private reconcileSignalWaiter;
2465
+ /**
2466
+ * The EVENT half of {@link reconcileSignalWaiter}: scan this waiter's name for a matching buffered
2467
+ * event (same list+evaluate+claim shape as `waitForEvent`'s own consumption — the match predicate
2468
+ * belongs to the waiter, never the store), pruning any expired candidates it encounters along the
2469
+ * way when {@link eventBufferTtlMs} is configured. A pruned-but-non-matching candidate does not stop
2470
+ * the scan; only a MATCHING hit (or exhausting the scan window) does.
2471
+ */
2472
+ private reconcileBufferedEvent;
2378
2473
  /**
2379
2474
  * Lease and execute one run by id — the worker side of dispatch. Acquires the recovery lease (so
2380
2475
  * exactly one instance runs it), then resumes the body. Returns the result, or null if another
@@ -2443,10 +2538,57 @@ declare class WorkflowEngine {
2443
2538
  * 2. **Starts** a fresh run of every workflow registered with `onEvent: [name]`, passing the
2444
2539
  * payload as input. Idempotent by `evt:<id>:<workflow>` — pass `opts.id` to dedupe redeliveries
2445
2540
  * of the same logical event (default: a fresh uuid, so each publish triggers once).
2541
+ *
2542
+ * Semantics (deliberate — mirrors `signalWithStart`'s reliability contract, so document it exactly):
2543
+ * a publish that resumes ≥1 live waiter, OR routes into an `eventBatch` accumulator / starts ≥1
2544
+ * subscriber, behaves exactly as above and is NOT buffered — fan-out stays live-only, same as
2545
+ * today. A publish that touches NEITHER — no live waiter matched, no subscriber exists/started —
2546
+ * buffers ONE copy (via {@link StateStore.bufferEvent}) unless `opts.buffer === false`, so it isn't
2547
+ * silently dropped just because nobody happened to be listening yet. That buffered copy is consumed
2548
+ * by the FIRST future `ctx.waitForEvent(name, { match })` whose match accepts its payload —
2549
+ * point-to-point on redelivery, by design (only one waiter ever gets it, even though the LIVE path
2550
+ * above is fan-out) — never by a second, later-registered onEvent subscriber (subscriber starts stay
2551
+ * live-only; `opts.id` dedupe applies to those only, unchanged). Right after buffering, this re-checks
2552
+ * `listSignalWaiters` ONCE — sandwich parity with `signal`'s own take → buffer → re-check — so a
2553
+ * waiter that registers in the sliver between the initial miss and the buffer write is still paired
2554
+ * instead of leaving both rows stranded (see `signal`'s interleaving proof; `waitForEvent`'s own
2555
+ * post-registration buffer scan is the mirror-image half of this same race).
2446
2556
  */
2447
2557
  publishEvent(name: string, payload: unknown, opts?: {
2448
2558
  id?: string;
2559
+ buffer?: boolean;
2449
2560
  }): Promise<number>;
2561
+ /**
2562
+ * Deliver a signal to an already-known waiter: write its resolving `signal:<token>` checkpoint and
2563
+ * resume the run. Shared by {@link signal}'s direct hit, its post-buffer re-check reclaim, and the
2564
+ * due-timer reconcile sweep ({@link reconcileSignalWaiter}) — all three resolve a waiter the exact
2565
+ * same way.
2566
+ */
2567
+ private deliverSignal;
2568
+ /**
2569
+ * Deliver an external signal to the run waiting on `token`, resume it with `payload`, and return
2570
+ * the run result — or null if no run is (or ends up) waiting for it.
2571
+ *
2572
+ * Race with the waiter side (`waitForSignal`'s check → `putSignalWaiter` → re-check, same shape in
2573
+ * the remote `waitSignal` command): a waiter can register in the sliver between this method's
2574
+ * initial `takeSignalWaiter` miss and its `bufferSignal` write, and the naive old flow (buffer and
2575
+ * return) would then leave BOTH a buffered payload and a registered waiter sitting in the store with
2576
+ * nothing left to pair them — a lost wake, forever suspended.
2577
+ *
2578
+ * The interleaving proof: the waiter side does check → put → re-check, this side does take → buffer
2579
+ * → re-check. Whichever side's SECOND look runs last is guaranteed to observe the other side's
2580
+ * write, because it always follows that write in real time:
2581
+ * - If the waiter's `put` lands before this method's first `take`, the take just finds it directly
2582
+ * (the pre-existing, non-racy path).
2583
+ * - If the waiter's `put` lands after this method's `bufferSignal`, our re-check below either sees
2584
+ * it (if `put` also lands before the re-check) or the waiter's OWN post-put re-check sees our
2585
+ * buffer (if `put` lands after our re-check but the waiter hasn't re-checked yet either — its
2586
+ * re-check strictly follows its own put).
2587
+ * - If the waiter's `put` lands between this method's `take` and its re-`take`, the re-check below
2588
+ * catches it.
2589
+ * Either way, the destructive `take*` ops (on the waiter row, on the buffer) arbitrate: whichever
2590
+ * side actually removes the row is the one that delivers, so a signal is never delivered twice.
2591
+ */
2450
2592
  signal(token: string, payload: unknown): Promise<RunResult | null>;
2451
2593
  /**
2452
2594
  * Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
@@ -3155,18 +3297,46 @@ declare class CodecStateStore implements StateStore {
3155
3297
  putSignalWaiter(waiter: SignalWaiter): Promise<void>;
3156
3298
  takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
3157
3299
  listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
3300
+ removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
3158
3301
  bufferSignal(token: string, payload: unknown): Promise<void>;
3159
3302
  takeBufferedSignal(token: string): Promise<{
3160
3303
  payload: unknown;
3161
3304
  } | null>;
3305
+ bufferEvent(input: {
3306
+ name: string;
3307
+ payload: unknown;
3308
+ id: string;
3309
+ publishedAt: number;
3310
+ }): Promise<void>;
3311
+ listBufferedEvents(name: string, limit: number): Promise<Array<{
3312
+ id: string;
3313
+ payload: unknown;
3314
+ publishedAt: number;
3315
+ }>>;
3316
+ removeBufferedEvent(id: string): Promise<boolean>;
3162
3317
  listRuns(query: RunQuery): Promise<WorkflowRun[]>;
3163
3318
  listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
3164
3319
  getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
3165
3320
  listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
3166
3321
  }
3167
3322
 
3323
+ /**
3324
+ * How many candidates a buffered-event scan considers (`listBufferedEvents(name, limit)`) before
3325
+ * giving up. An event's buffer is keyed by `name` only (see {@link eventMatches}'s match-based
3326
+ * consumption), so a hot name with many distinct payloads sitting unclaimed could in principle
3327
+ * accumulate an unbounded backlog; this caps the per-scan cost regardless. Shared by the workflow-ctx
3328
+ * consumer and the engine's reconcile sweep so both see the same window.
3329
+ */
3330
+ declare const EVENT_BUFFER_SCAN_LIMIT = 50;
3331
+ /** The literal prefix EVERY event-waiter token starts with, regardless of `name` — `eventPrefix(name)`
3332
+ * always begins with this; exported so a caller that only needs to CLASSIFY a token (not fan out a
3333
+ * specific name) doesn't have to re-derive or duplicate the literal (see run-waiting.ts's own copy,
3334
+ * kept in sync with this one, and engine.ts's reconcile sweep). */
3335
+ declare const EVENT_TOKEN_PREFIX = "event:";
3168
3336
  /** Token prefix for every waiter on `name` — used by `listSignalWaiters` to fan an event out. */
3169
3337
  declare function eventPrefix(name: string): string;
3338
+ /** Whether `token` is an event-waiter token (vs. a plain signal / child / webhook / breakpoint one). */
3339
+ declare function isEventToken(token: string): boolean;
3170
3340
  /** Build the unique waiter token for one `waitForEvent` call (name + match + run position). */
3171
3341
  declare function eventToken(name: string, match: Record<string, unknown> | undefined, runId: string, seq: number): string;
3172
3342
  /** The event name embedded in an event waiter token (the 2nd `:`-segment is the base64 name), so a
@@ -3522,6 +3692,7 @@ declare class InMemoryStateStore implements StateStore {
3522
3692
  putSignalWaiter(waiter: SignalWaiter): Promise<void>;
3523
3693
  takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
3524
3694
  listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
3695
+ removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
3525
3696
  transaction<T>(work: (tx: {
3526
3697
  raw: unknown;
3527
3698
  saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
@@ -3531,6 +3702,21 @@ declare class InMemoryStateStore implements StateStore {
3531
3702
  takeBufferedSignal(token: string): Promise<{
3532
3703
  payload: unknown;
3533
3704
  } | null>;
3705
+ /** `id -> row`, so {@link removeBufferedEvent} can delete an exact row regardless of iteration
3706
+ * order — mirrors the SQL stores' PK-by-id delete. */
3707
+ private readonly bufferedEvents;
3708
+ bufferEvent(input: {
3709
+ name: string;
3710
+ payload: unknown;
3711
+ id: string;
3712
+ publishedAt: number;
3713
+ }): Promise<void>;
3714
+ listBufferedEvents(name: string, limit: number): Promise<Array<{
3715
+ id: string;
3716
+ payload: unknown;
3717
+ publishedAt: number;
3718
+ }>>;
3719
+ removeBufferedEvent(id: string): Promise<boolean>;
3534
3720
  /**
3535
3721
  * Candidate run ids satisfying ONE attribute predicate, served from {@link attributeIndex} — the
3536
3722
  * pushdown step. Numeric predicates scan only `numValue` rows under the key; string/boolean ones
@@ -3569,4 +3755,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
3569
3755
  onControl(handler: (msg: ControlMessage) => void): void;
3570
3756
  }
3571
3757
 
3572
- 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 };
3758
+ 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, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, 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, isEventToken, 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
@@ -638,6 +648,39 @@ interface StateStore {
638
648
  takeBufferedSignal(token: string): Promise<{
639
649
  payload: unknown;
640
650
  } | null>;
651
+ /**
652
+ * Buffer a published event that matched NO live waiter, so a LATER `ctx.waitForEvent(name, { match })`
653
+ * still consumes it instead of it being silently dropped — the events analog of {@link bufferSignal}'s
654
+ * reliability contract for signals, but MATCH-based rather than token-based: an event's buffer is keyed
655
+ * by `name` alone (many waiters can share a name with different `match` criteria), so consumption is
656
+ * list ({@link listBufferedEvents}) + evaluate the WAITER's own match predicate + claim
657
+ * ({@link removeBufferedEvent}) — never a blind take, because the store has no way to know which
658
+ * candidate a given waiter wants. `input.id` is minted by the caller (engine.publishEvent) so
659
+ * {@link removeBufferedEvent} can later target this exact row.
660
+ */
661
+ bufferEvent(input: {
662
+ name: string;
663
+ payload: unknown;
664
+ id: string;
665
+ publishedAt: number;
666
+ }): Promise<void>;
667
+ /**
668
+ * Buffered events for `name`, OLDEST (`publishedAt`) first, capped at `limit`. A waiter scans these,
669
+ * evaluates its own `eventMatches(payload, match)` locally, and claims the one it wants via
670
+ * {@link removeBufferedEvent} — the match predicate belongs to the WAITER, never the store.
671
+ */
672
+ listBufferedEvents(name: string, limit: number): Promise<Array<{
673
+ id: string;
674
+ payload: unknown;
675
+ publishedAt: number;
676
+ }>>;
677
+ /**
678
+ * Atomically delete the buffered event `id`. Returns `true` iff a row was actually deleted, `false`
679
+ * if it was already gone (claimed by a concurrent waiter, reclaimed by `engine.publishEvent`'s own
680
+ * late re-check, or pruned as expired) — the arbiter under concurrency: whichever caller's delete
681
+ * returns `true` is the one that gets to deliver the payload; every other caller backs off.
682
+ */
683
+ removeBufferedEvent(id: string): Promise<boolean>;
641
684
  /**
642
685
  * Run `work` in a SINGLE store transaction — giving it the store-native transaction handle (`raw`)
643
686
  * for the caller's own DB writes plus a `saveCheckpoint` that commits IN THE SAME transaction, so a
@@ -1997,6 +2040,19 @@ interface WorkflowEngineDeps {
1997
2040
  * for a genuinely-orphaned run. Default 5min; set `0` to disable (the prior wake-forever behavior).
1998
2041
  */
1999
2042
  reconcileMs?: number | undefined;
2043
+ /**
2044
+ * Retention for a BUFFERED event that no `waitForEvent` ever claims (see
2045
+ * {@link WorkflowEngine.publishEvent}'s buffering — a publish matching no live waiter keeps ONE
2046
+ * copy around for a later matching wait). Default `undefined`: keep it until consumed, same as
2047
+ * {@link StateStore.bufferSignal}'s buffered signals — no silent expiry. Set a duration (ms) to
2048
+ * have the {@link resumeDueTimers} reconcile pass prune rows past `publishedAt + eventBufferTtlMs`
2049
+ * as it encounters them. Scoped to event NAMES the current reconcile tick's registered waiters
2050
+ * reference (the sweep has no reason to otherwise enumerate every buffered name) — a name with a
2051
+ * stale buffered event but zero waiters ever registered against it is not visited by this pass; that
2052
+ * is an accepted gap given `listBufferedEvents` is name-scoped by design (see interfaces.ts), not a
2053
+ * bug to route around here.
2054
+ */
2055
+ eventBufferTtlMs?: number | undefined;
2000
2056
  /**
2001
2057
  * Opt-in self-heal window (ms) for a remote step with NO `timeoutMs` whose dispatched job was LOST —
2002
2058
  * the worker crashed mid-step leaving no result, or the transport dropped the job (a Redis
@@ -2094,6 +2150,7 @@ declare class WorkflowEngine {
2094
2150
  private readonly maxRecoveryAttempts?;
2095
2151
  private readonly remoteAdvanceSilenceMs?;
2096
2152
  private readonly reconcileMs;
2153
+ private readonly eventBufferTtlMs;
2097
2154
  private readonly remoteRedispatchMs;
2098
2155
  private readonly remoteRedispatchMax;
2099
2156
  private readonly webhookUrl?;
@@ -2373,8 +2430,46 @@ declare class WorkflowEngine {
2373
2430
  /**
2374
2431
  * Resume every suspended run whose durable timer is due. Call periodically (a poller) and on
2375
2432
  * boot. A run still not due re-suspends cheaply without running new work.
2433
+ *
2434
+ * Piggy-backs the signal/event-waiter reconcile safety net (see {@link reconcileSignalWaiter}): a
2435
+ * crash between the waiter/buffer pairing ops on EITHER side (this run's own `putSignalWaiter` →
2436
+ * `takeBufferedSignal`/`listBufferedEvents`, or the other side's `takeSignalWaiter`/fan-out →
2437
+ * `bufferSignal`/`bufferEvent` → re-check — see `signal`'s interleaving proof, which `publishEvent`
2438
+ * mirrors for events) can leave both rows stranded with nothing left to pair them: the waiter's turn
2439
+ * already suspended, the other side that buffered it already returned. Event-wait suspends already
2440
+ * carry a `reconcileMs` fallback `wakeAt` (see {@link reconcileWakeAt}) precisely so they show up
2441
+ * HERE — that's what makes this due-timer pass the natural place for the sweep (and, for events, also
2442
+ * where an `eventBufferTtlMs` prune piggy-backs — see {@link reconcileBufferedEvent}). One bulk waiter
2443
+ * scan, filtered to just this tick's batch, keeps it cheap regardless of store size.
2376
2444
  */
2377
2445
  resumeDueTimers(nowMs?: number): Promise<RunResult[]>;
2446
+ /**
2447
+ * The reconcile safety net's per-run check (see {@link resumeDueTimers}): if this suspended run has
2448
+ * a registered signal OR event waiter and a matching payload is already buffered, pair and deliver
2449
+ * them directly instead of falling through to a full replay. Child/breakpoint waiter tokens are
2450
+ * never buffered (a child/breakpoint notifies via `signal` only once its counterpart is already
2451
+ * registered) — `takeBufferedSignal` on those is a guaranteed miss, so this only ever does real work
2452
+ * for a plain signal or an event waiter (classified via {@link isEventToken}).
2453
+ *
2454
+ * Also opportunistically prunes expired buffered EVENTS for this waiter's name when
2455
+ * {@link eventBufferTtlMs} is set (see its doc — scoped to names a registered waiter references in
2456
+ * this tick's batch, piggy-backing the SAME scan rather than a separate pass), even on a miss.
2457
+ *
2458
+ * Returns `undefined` (not settled — `resumeLeased` proceeds with its normal `resume`) when there's
2459
+ * no waiter or no buffered hit. Deliberately never returns `null`: a delivered signal always resumes
2460
+ * via {@link deliverSignal}, and even a `NamespaceMismatch` there is reported as a real (non-nullish)
2461
+ * result — returning `null` would read as "not settled" to `resumeLeased`'s `??` fallback and
2462
+ * trigger a second, redundant `resume` call on the same run.
2463
+ */
2464
+ private reconcileSignalWaiter;
2465
+ /**
2466
+ * The EVENT half of {@link reconcileSignalWaiter}: scan this waiter's name for a matching buffered
2467
+ * event (same list+evaluate+claim shape as `waitForEvent`'s own consumption — the match predicate
2468
+ * belongs to the waiter, never the store), pruning any expired candidates it encounters along the
2469
+ * way when {@link eventBufferTtlMs} is configured. A pruned-but-non-matching candidate does not stop
2470
+ * the scan; only a MATCHING hit (or exhausting the scan window) does.
2471
+ */
2472
+ private reconcileBufferedEvent;
2378
2473
  /**
2379
2474
  * Lease and execute one run by id — the worker side of dispatch. Acquires the recovery lease (so
2380
2475
  * exactly one instance runs it), then resumes the body. Returns the result, or null if another
@@ -2443,10 +2538,57 @@ declare class WorkflowEngine {
2443
2538
  * 2. **Starts** a fresh run of every workflow registered with `onEvent: [name]`, passing the
2444
2539
  * payload as input. Idempotent by `evt:<id>:<workflow>` — pass `opts.id` to dedupe redeliveries
2445
2540
  * of the same logical event (default: a fresh uuid, so each publish triggers once).
2541
+ *
2542
+ * Semantics (deliberate — mirrors `signalWithStart`'s reliability contract, so document it exactly):
2543
+ * a publish that resumes ≥1 live waiter, OR routes into an `eventBatch` accumulator / starts ≥1
2544
+ * subscriber, behaves exactly as above and is NOT buffered — fan-out stays live-only, same as
2545
+ * today. A publish that touches NEITHER — no live waiter matched, no subscriber exists/started —
2546
+ * buffers ONE copy (via {@link StateStore.bufferEvent}) unless `opts.buffer === false`, so it isn't
2547
+ * silently dropped just because nobody happened to be listening yet. That buffered copy is consumed
2548
+ * by the FIRST future `ctx.waitForEvent(name, { match })` whose match accepts its payload —
2549
+ * point-to-point on redelivery, by design (only one waiter ever gets it, even though the LIVE path
2550
+ * above is fan-out) — never by a second, later-registered onEvent subscriber (subscriber starts stay
2551
+ * live-only; `opts.id` dedupe applies to those only, unchanged). Right after buffering, this re-checks
2552
+ * `listSignalWaiters` ONCE — sandwich parity with `signal`'s own take → buffer → re-check — so a
2553
+ * waiter that registers in the sliver between the initial miss and the buffer write is still paired
2554
+ * instead of leaving both rows stranded (see `signal`'s interleaving proof; `waitForEvent`'s own
2555
+ * post-registration buffer scan is the mirror-image half of this same race).
2446
2556
  */
2447
2557
  publishEvent(name: string, payload: unknown, opts?: {
2448
2558
  id?: string;
2559
+ buffer?: boolean;
2449
2560
  }): Promise<number>;
2561
+ /**
2562
+ * Deliver a signal to an already-known waiter: write its resolving `signal:<token>` checkpoint and
2563
+ * resume the run. Shared by {@link signal}'s direct hit, its post-buffer re-check reclaim, and the
2564
+ * due-timer reconcile sweep ({@link reconcileSignalWaiter}) — all three resolve a waiter the exact
2565
+ * same way.
2566
+ */
2567
+ private deliverSignal;
2568
+ /**
2569
+ * Deliver an external signal to the run waiting on `token`, resume it with `payload`, and return
2570
+ * the run result — or null if no run is (or ends up) waiting for it.
2571
+ *
2572
+ * Race with the waiter side (`waitForSignal`'s check → `putSignalWaiter` → re-check, same shape in
2573
+ * the remote `waitSignal` command): a waiter can register in the sliver between this method's
2574
+ * initial `takeSignalWaiter` miss and its `bufferSignal` write, and the naive old flow (buffer and
2575
+ * return) would then leave BOTH a buffered payload and a registered waiter sitting in the store with
2576
+ * nothing left to pair them — a lost wake, forever suspended.
2577
+ *
2578
+ * The interleaving proof: the waiter side does check → put → re-check, this side does take → buffer
2579
+ * → re-check. Whichever side's SECOND look runs last is guaranteed to observe the other side's
2580
+ * write, because it always follows that write in real time:
2581
+ * - If the waiter's `put` lands before this method's first `take`, the take just finds it directly
2582
+ * (the pre-existing, non-racy path).
2583
+ * - If the waiter's `put` lands after this method's `bufferSignal`, our re-check below either sees
2584
+ * it (if `put` also lands before the re-check) or the waiter's OWN post-put re-check sees our
2585
+ * buffer (if `put` lands after our re-check but the waiter hasn't re-checked yet either — its
2586
+ * re-check strictly follows its own put).
2587
+ * - If the waiter's `put` lands between this method's `take` and its re-`take`, the re-check below
2588
+ * catches it.
2589
+ * Either way, the destructive `take*` ops (on the waiter row, on the buffer) arbitrate: whichever
2590
+ * side actually removes the row is the one that delivers, so a signal is never delivered twice.
2591
+ */
2450
2592
  signal(token: string, payload: unknown): Promise<RunResult | null>;
2451
2593
  /**
2452
2594
  * Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
@@ -3155,18 +3297,46 @@ declare class CodecStateStore implements StateStore {
3155
3297
  putSignalWaiter(waiter: SignalWaiter): Promise<void>;
3156
3298
  takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
3157
3299
  listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
3300
+ removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
3158
3301
  bufferSignal(token: string, payload: unknown): Promise<void>;
3159
3302
  takeBufferedSignal(token: string): Promise<{
3160
3303
  payload: unknown;
3161
3304
  } | null>;
3305
+ bufferEvent(input: {
3306
+ name: string;
3307
+ payload: unknown;
3308
+ id: string;
3309
+ publishedAt: number;
3310
+ }): Promise<void>;
3311
+ listBufferedEvents(name: string, limit: number): Promise<Array<{
3312
+ id: string;
3313
+ payload: unknown;
3314
+ publishedAt: number;
3315
+ }>>;
3316
+ removeBufferedEvent(id: string): Promise<boolean>;
3162
3317
  listRuns(query: RunQuery): Promise<WorkflowRun[]>;
3163
3318
  listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
3164
3319
  getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
3165
3320
  listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
3166
3321
  }
3167
3322
 
3323
+ /**
3324
+ * How many candidates a buffered-event scan considers (`listBufferedEvents(name, limit)`) before
3325
+ * giving up. An event's buffer is keyed by `name` only (see {@link eventMatches}'s match-based
3326
+ * consumption), so a hot name with many distinct payloads sitting unclaimed could in principle
3327
+ * accumulate an unbounded backlog; this caps the per-scan cost regardless. Shared by the workflow-ctx
3328
+ * consumer and the engine's reconcile sweep so both see the same window.
3329
+ */
3330
+ declare const EVENT_BUFFER_SCAN_LIMIT = 50;
3331
+ /** The literal prefix EVERY event-waiter token starts with, regardless of `name` — `eventPrefix(name)`
3332
+ * always begins with this; exported so a caller that only needs to CLASSIFY a token (not fan out a
3333
+ * specific name) doesn't have to re-derive or duplicate the literal (see run-waiting.ts's own copy,
3334
+ * kept in sync with this one, and engine.ts's reconcile sweep). */
3335
+ declare const EVENT_TOKEN_PREFIX = "event:";
3168
3336
  /** Token prefix for every waiter on `name` — used by `listSignalWaiters` to fan an event out. */
3169
3337
  declare function eventPrefix(name: string): string;
3338
+ /** Whether `token` is an event-waiter token (vs. a plain signal / child / webhook / breakpoint one). */
3339
+ declare function isEventToken(token: string): boolean;
3170
3340
  /** Build the unique waiter token for one `waitForEvent` call (name + match + run position). */
3171
3341
  declare function eventToken(name: string, match: Record<string, unknown> | undefined, runId: string, seq: number): string;
3172
3342
  /** The event name embedded in an event waiter token (the 2nd `:`-segment is the base64 name), so a
@@ -3522,6 +3692,7 @@ declare class InMemoryStateStore implements StateStore {
3522
3692
  putSignalWaiter(waiter: SignalWaiter): Promise<void>;
3523
3693
  takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
3524
3694
  listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
3695
+ removeSignalWaiter(waiter: SignalWaiter): Promise<void>;
3525
3696
  transaction<T>(work: (tx: {
3526
3697
  raw: unknown;
3527
3698
  saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
@@ -3531,6 +3702,21 @@ declare class InMemoryStateStore implements StateStore {
3531
3702
  takeBufferedSignal(token: string): Promise<{
3532
3703
  payload: unknown;
3533
3704
  } | null>;
3705
+ /** `id -> row`, so {@link removeBufferedEvent} can delete an exact row regardless of iteration
3706
+ * order — mirrors the SQL stores' PK-by-id delete. */
3707
+ private readonly bufferedEvents;
3708
+ bufferEvent(input: {
3709
+ name: string;
3710
+ payload: unknown;
3711
+ id: string;
3712
+ publishedAt: number;
3713
+ }): Promise<void>;
3714
+ listBufferedEvents(name: string, limit: number): Promise<Array<{
3715
+ id: string;
3716
+ payload: unknown;
3717
+ publishedAt: number;
3718
+ }>>;
3719
+ removeBufferedEvent(id: string): Promise<boolean>;
3534
3720
  /**
3535
3721
  * Candidate run ids satisfying ONE attribute predicate, served from {@link attributeIndex} — the
3536
3722
  * pushdown step. Numeric predicates scan only `numValue` rows under the key; string/boolean ones
@@ -3569,4 +3755,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
3569
3755
  onControl(handler: (msg: ControlMessage) => void): void;
3570
3756
  }
3571
3757
 
3572
- 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 };
3758
+ 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, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, 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, isEventToken, isWorkflowControlFlowSignal, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, readSearchAttributes, resolveRunWaiting, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, tenantGroup, workflowName };