@dudousxd/nestjs-durable-core 0.81.0 → 0.83.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
@@ -1285,8 +1285,15 @@ interface StateStore {
1285
1285
  * acquired — so concurrent engine instances never recover the same run twice.
1286
1286
  */
1287
1287
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
1288
- /** Release a run's recovery lease so another instance can pick it up (e.g. once it suspends). */
1289
- releaseRunLock(runId: string): Promise<void>;
1288
+ /**
1289
+ * Release a run's recovery lease so another instance can pick it up (e.g. once it suspends).
1290
+ *
1291
+ * `owner`, when given, makes the release owner-scoped: clear the lease ONLY if `owner` still holds
1292
+ * it, so an instance releasing a lease it may have lost (a drain that timed out) never frees the
1293
+ * lease another instance took over. Without it the release is unconditional. A store that ignores
1294
+ * the argument stays correct, only less precise in that race.
1295
+ */
1296
+ releaseRunLock(runId: string, owner?: string): Promise<void>;
1290
1297
  /**
1291
1298
  * Extend a run's lease to `leaseUntilMs`, but ONLY if `owner` still holds it — so a live worker
1292
1299
  * heartbeating its long run keeps the lease, while a dead worker's lease still expires and gets
@@ -2870,8 +2877,8 @@ interface WorkflowCtx<A extends SearchAttributes = SearchAttributes> {
2870
2877
  *
2871
2878
  * When the workflow declares a `@Workflow({ searchAttributes })` schema, the MERGED result (this
2872
2879
  * run's existing `searchAttributes` shallow-merged with `attrs`) is validated against it before the
2873
- * write — an invalid merge throws, naming the workflow, the offending key(s), and the schema's
2874
- * issues. Validation runs once, on the same first-run-only position as the write itself (skipped
2880
+ * write — an invalid merge throws a `SchemaValidationError` (code `schema_validation`, never
2881
+ * retried) naming the workflow, with the schema's issues. Validation runs once, on the same first-run-only position as the write itself (skipped
2875
2882
  * entirely on replay, so it's never a source of nondeterminism); it must be synchronous — a schema
2876
2883
  * whose `validate` returns a `Promise` throws instead of being awaited. No schema declared ⇒
2877
2884
  * unchanged, unvalidated behavior.
@@ -3820,7 +3827,10 @@ declare function liveHandoff<C extends {
3820
3827
  seq: number;
3821
3828
  name: string;
3822
3829
  }>(runId: string, checkpoints: readonly C[]): C | undefined;
3823
- /** The route a failure takes: the one for its exact code, else the catch-all, else none. */
3830
+ /**
3831
+ * The route a failure takes: the one for its exact code, else (for an `invalid_input` /
3832
+ * `invalid_output` failure) a `schema_validation` route, else the catch-all, else none.
3833
+ */
3824
3834
  declare function routeFor(routes: OnErrorRoute[] | undefined, error: StepError): OnErrorRoute | undefined;
3825
3835
  /** Reject two routes for one code, or two catch-alls — ambiguous config, caught at registration. */
3826
3836
  declare function assertRoutes(workflow: string, routes: OnErrorRoute[]): void;
@@ -4003,6 +4013,60 @@ declare class ScheduleClient {
4003
4013
  private write;
4004
4014
  }
4005
4015
 
4016
+ /** Default idle threshold for a stranded run: 15 minutes. */
4017
+ declare const DEFAULT_STALLED_AFTER_MS: number;
4018
+ /** The oldest unresolved remote step of a run — the lost-dispatch signature when it is old. */
4019
+ interface StalePendingStep {
4020
+ seq: number;
4021
+ name: string;
4022
+ /** How many times this step has been (re-)dispatched. A lost dispatch typically sits at 1. */
4023
+ attempts: number;
4024
+ /** Milliseconds since the step was enqueued to the transport. */
4025
+ ageMs: number;
4026
+ /**
4027
+ * The step's lease (`wakeAt`, epoch ms) when it has one: a step with a `timeoutMs`, a heartbeat
4028
+ * renewing it, or `remoteRedispatchMs`. `null` when nothing is leasing the step.
4029
+ */
4030
+ leaseUntil: number | null;
4031
+ }
4032
+ /** What an `engine.onStalled` listener (and {@link findStalledRuns}) receives: a stranded run and why. */
4033
+ interface StalledRunInfo {
4034
+ run: WorkflowRun;
4035
+ /** Milliseconds since the run's last activity (`updatedAt`). */
4036
+ ageMs: number;
4037
+ /** Suspended with NO wake timer — nothing will re-drive it (the `reconcileMs: 0` hazard). */
4038
+ wakeForever: boolean;
4039
+ /** The oldest pending remote step with no live lease, when that is the stranding signature. */
4040
+ stalePending: StalePendingStep | null;
4041
+ }
4042
+ /** Options for {@link findStalledRuns}. */
4043
+ interface FindStalledRunsOptions {
4044
+ /** Idle threshold in ms. Default {@link DEFAULT_STALLED_AFTER_MS}. */
4045
+ thresholdMs?: number | undefined;
4046
+ /** Max candidate runs inspected (newest first). Default 1000. */
4047
+ scan?: number | undefined;
4048
+ /** Evaluate as of this time (epoch ms). Default `Date.now()`. */
4049
+ now?: number | undefined;
4050
+ /** Only runs in this namespace. Omit for every namespace. */
4051
+ namespace?: string | undefined;
4052
+ }
4053
+ /**
4054
+ * The stranded runs in a store right now — the query behind `engine.sweepStalled`/`onStalled`,
4055
+ * `engine.listStalledRuns` and the CLI's `durable inspect --stale`. Works on a bare store, so an
4056
+ * ops script or a CLI can run it without an engine.
4057
+ *
4058
+ * A `running`/`suspended` run qualifies when it has been untouched for `thresholdMs` AND either:
4059
+ *
4060
+ * - it is suspended with NO wake timer (`wakeForever`): nothing will ever re-drive it; or
4061
+ * - its oldest pending REMOTE step is older than the threshold and has no lease in the future
4062
+ * (`stalePending`): the dispatch was most likely lost.
4063
+ *
4064
+ * Healthy long work is excluded: a long `ctx.sleep` has a future `wakeAt` and no pending step, and a
4065
+ * remote step with a `timeoutMs`, a heartbeat or `remoteRedispatchMs` carries a future lease. Issues
4066
+ * one `listCheckpoints` per idle candidate, bounded by `scan`: an operator read, not a hot path.
4067
+ */
4068
+ declare function findStalledRuns(store: Pick<StateStore, 'listRuns' | 'listCheckpoints'>, options?: FindStalledRunsOptions): Promise<StalledRunInfo[]>;
4069
+
4006
4070
  type WorkflowFn = (ctx: InternalWorkflowCtx, input: unknown) => Promise<unknown>;
4007
4071
  /**
4008
4072
  * Options for {@link WorkflowEngine.start}.
@@ -4179,6 +4243,14 @@ interface WorkflowEngineDeps {
4179
4243
  * for a genuinely-orphaned run. Default 5min; set `0` to disable (the prior wake-forever behavior).
4180
4244
  */
4181
4245
  reconcileMs?: number | undefined;
4246
+ /**
4247
+ * How long an in-flight run must sit untouched, with the stranded signature (a wake-forever
4248
+ * suspension, or an old pending remote step with no live lease), before the
4249
+ * {@link WorkflowEngine.onStalled} listeners are notified. Default 900 000 (15 min); floored at
4250
+ * 60 000. The sweep ({@link WorkflowEngine.sweepStalled}) only does work when a listener is
4251
+ * registered.
4252
+ */
4253
+ stalledAfterMs?: number | undefined;
4182
4254
  /**
4183
4255
  * The most due timers one {@link WorkflowEngine.resumeDueTimers} pass loads and drives, most
4184
4256
  * overdue first. Every driving instance runs that pass each tick, so a wave of thousands of due
@@ -4286,6 +4358,15 @@ interface WorkflowEngineDeps {
4286
4358
  */
4287
4359
  runDispatcher?: RunDispatcher | undefined;
4288
4360
  }
4361
+ /** Options for {@link WorkflowEngine.listStalledRuns}. */
4362
+ interface ListStalledRunsOptions {
4363
+ /** Idle threshold in ms. Defaults to the engine's `stalledAfterMs` (15 min). */
4364
+ thresholdMs?: number | undefined;
4365
+ /** Max candidate runs inspected (newest first). Default 1000. */
4366
+ scan?: number | undefined;
4367
+ /** Evaluate as of this clock time. Defaults to the engine clock. */
4368
+ now?: number | undefined;
4369
+ }
4289
4370
  /**
4290
4371
  * The orchestrator. Owns workflow state and replays runs deterministically: each step's
4291
4372
  * result is checkpointed, so on resume a completed step returns its saved output instead of
@@ -4410,6 +4491,18 @@ declare class WorkflowEngine {
4410
4491
  private readonly stepQueue;
4411
4492
  /** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
4412
4493
  private readonly inflight;
4494
+ /**
4495
+ * The run lease of each execution in flight on THIS instance, by run id — renewed while it runs
4496
+ * and checked by every settle (the zombie fence, see {@link ExecutionLease}). Also what `drain()`
4497
+ * releases when its budget runs out.
4498
+ */
4499
+ private readonly executionLeases;
4500
+ /** Pager hooks for stranded runs (see {@link onStalled}). */
4501
+ private readonly stalledListeners;
4502
+ /** Runs already paged about, by id → the `updatedAt` of the paged episode (see {@link sweepStalled}). */
4503
+ private readonly notifiedStalled;
4504
+ private lastStalledSweepAt;
4505
+ private readonly stalledAfterMs;
4413
4506
  private draining;
4414
4507
  /** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
4415
4508
  private drainSpentMs;
@@ -4575,6 +4668,33 @@ declare class WorkflowEngine {
4575
4668
  * Returns an unsubscribe function.
4576
4669
  */
4577
4670
  onDead(listener: (run: WorkflowRun) => void): () => void;
4671
+ /**
4672
+ * Register a pager hook for STRANDED runs: an in-flight run untouched past `stalledAfterMs` that is
4673
+ * either suspended with NO wake timer (wake-forever: nothing will ever re-drive it) or parked on an
4674
+ * old pending remote step with no live lease (dispatched and, most likely, lost). The timer poller
4675
+ * sweeps for these ({@link sweepStalled}, throttled to one pass a minute) and notifies each episode
4676
+ * ONCE per instance: a run is paged again only after it makes progress and strands again. Wire your
4677
+ * alerting here like {@link onDead}. A listener that throws never breaks the sweep. Returns an
4678
+ * unsubscribe function.
4679
+ */
4680
+ onStalled(listener: (info: StalledRunInfo) => void): () => void;
4681
+ /**
4682
+ * Detect stranded in-flight runs and notify the {@link onStalled} listeners. Self-throttled to one
4683
+ * pass a minute, skipped while draining, and a no-op without listeners — call it every tick (the
4684
+ * NestJS timer poller does). Scoped to this engine's `namespace` (an operator engine with no
4685
+ * namespace sweeps every namespace). Returns the runs notified THIS pass.
4686
+ */
4687
+ sweepStalled(now?: number): Promise<StalledRunInfo[]>;
4688
+ /**
4689
+ * The stranded runs right now, without notifying anyone: the query behind {@link sweepStalled} and
4690
+ * the CLI's `durable inspect --stale`. A `running`/`suspended` run qualifies when it has been
4691
+ * untouched for `thresholdMs` (default `stalledAfterMs`) AND either it is suspended with no wake
4692
+ * timer, or its oldest pending remote step is older than the threshold with no lease in the future
4693
+ * (a step with a `timeoutMs`, a heartbeat or `remoteRedispatchMs` keeps a lease; a long
4694
+ * `ctx.sleep` has a future `wakeAt`; neither is stranded). Scans up to `scan` candidates (default
4695
+ * 1000), newest first, in this engine's namespace.
4696
+ */
4697
+ listStalledRuns(options?: ListStalledRunsOptions): Promise<StalledRunInfo[]>;
4578
4698
  private notifyDead;
4579
4699
  /** Emit a locally-produced lifecycle event: deliver to subscribers AND broadcast it on the
4580
4700
  * control plane so other instances (e.g. a dashboard pod) can live-tail this run. */
@@ -4586,6 +4706,9 @@ declare class WorkflowEngine {
4586
4706
  start<TInput>(workflow: string, input: TInput, runId: string, opts?: StartOptions): Promise<RunResult>;
4587
4707
  /** Read a run's current persisted state (or null if unknown). A thin pass-through to the store. */
4588
4708
  getRun(runId: string): Promise<WorkflowRun | null>;
4709
+ /** A run's full checkpoint timeline, ordered by seq. A thin pass-through to the store (it is what
4710
+ * the testing kit's `captureHistory` reads, next to {@link getRun}). */
4711
+ listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
4589
4712
  /**
4590
4713
  * Re-execute a run's body (replaying its checkpoints). This is the EXPLICIT entry point: besides
4591
4714
  * driving a live run, it re-drives a `failed` run — the manual retry (prefer {@link requeue}, which
@@ -4727,6 +4850,8 @@ declare class WorkflowEngine {
4727
4850
  * between this call and `drain()` (other hooks, the HTTP server closing) never is. Calls add up.
4728
4851
  */
4729
4852
  beginDrain(spentMs?: number): void;
4853
+ /** True once {@link beginDrain} (or {@link drain}) has been called. */
4854
+ isDraining(): boolean;
4730
4855
  /**
4731
4856
  * Graceful shutdown: stop picking up new runs (recovery/timer become no-ops) and wait for
4732
4857
  * in-flight executions to settle, up to `timeoutMs`. Call from your app's shutdown hook so a
@@ -4737,6 +4862,16 @@ declare class WorkflowEngine {
4737
4862
  * for part of it waits only for the remainder. With nothing reported, the full `timeoutMs` applies.
4738
4863
  */
4739
4864
  drain(timeoutMs?: number): Promise<void>;
4865
+ /**
4866
+ * The drain budget ran out with executions still in flight: release their recovery leases so
4867
+ * another instance re-drives those runs on its next recovery pass, instead of waiting out
4868
+ * `leaseMs` after this process exits. Each still-running turn is FENCED first, so whatever it
4869
+ * computes after this point is never written over the new owner's work (see settleRun). Its
4870
+ * completed steps are checkpointed, so the new owner replays them; a step still executing here
4871
+ * may run again there, exactly as after a crash. The release is owner-scoped where the store
4872
+ * supports it (only a lease this instance still holds is cleared). Best effort: never throws.
4873
+ */
4874
+ private releaseInflightLeases;
4740
4875
  /**
4741
4876
  * Cancel in-flight runs that have outlived their workflow's `executionTimeout`. Call it from the
4742
4877
  * timer poller alongside {@link resumeDueTimers}. A timed-out run is moved to `cancelled` with an
@@ -5281,6 +5416,26 @@ declare class WorkflowEngine {
5281
5416
  private markStarted;
5282
5417
  private singletonWaitResult;
5283
5418
  private execute;
5419
+ /**
5420
+ * Renew `runId`'s lease for as long as this execution holds it, and register it in
5421
+ * {@link executionLeases} so settles can check the zombie fence and `drain()` can release it.
5422
+ *
5423
+ * A renewal that returns `false` means the lease is gone: this turn stalled past `leaseMs` and
5424
+ * another instance reclaimed the run, so this executor is now a zombie. It is FENCED (`lost`): its
5425
+ * settles degrade to an echo of the store's state instead of clobbering the new owner's writes, and
5426
+ * its own lease release is skipped (it would free the new owner's lease). A renewal that THROWS (a
5427
+ * transient store error) is not evidence of a takeover, so it stays swallowed and the next
5428
+ * interval retries. A lease this execution released on purpose (`handedOff`) is not renewed, and a
5429
+ * renewal resolving after the execution ended (`done`) fences nothing.
5430
+ */
5431
+ private holdExecutionLease;
5432
+ /** Whether `runId`'s execution on this instance lost its lease (see {@link holdExecutionLease}). */
5433
+ private isFenced;
5434
+ /**
5435
+ * Mark `runId`'s lease as given away on purpose by its own execution (released mid-turn), so a
5436
+ * renewal that now fails is not read as a takeover.
5437
+ */
5438
+ private handOffLease;
5284
5439
  /**
5285
5440
  * Advance a remote (cross-SDK) workflow one turn: hand its history to the executor (which dispatches
5286
5441
  * a workflow task to the worker and awaits its replay) and apply the decision. Mirrors
@@ -5794,6 +5949,14 @@ declare class DurableWorkflow {
5794
5949
  */
5795
5950
  declare function parseDuration(duration: string | number): number;
5796
5951
 
5952
+ /**
5953
+ * Format marker of a run-history replay fixture (`{ version, run, checkpoints }`). Written by the
5954
+ * testing kit's `serializeRunHistory` and the CLI's `durable export`, checked by `parseRunHistory`.
5955
+ * Lives in core so both writers (which already depend on core, not on each other) share one value.
5956
+ * Bump it only when the fixture shape changes incompatibly.
5957
+ */
5958
+ declare const RUN_HISTORY_FIXTURE_VERSION = 1;
5959
+
5797
5960
  /**
5798
5961
  * Thrown inside a step to signal an unrecoverable failure: the engine will NOT retry it,
5799
5962
  * regardless of the step's `retries` setting, and fails the run immediately. Use it for
@@ -5808,6 +5971,44 @@ declare class FatalError extends Error {
5808
5971
  readonly retryable = false;
5809
5972
  constructor(message: string, code?: string, details?: unknown);
5810
5973
  }
5974
+ /** One schema issue in JSON-safe form: its message and the path segments, as strings. */
5975
+ interface SchemaIssue {
5976
+ message: string;
5977
+ path: string[];
5978
+ }
5979
+ /**
5980
+ * The code a {@link SchemaValidationError} carries: `invalid_input` for an input schema (a
5981
+ * workflow's or a step's), `invalid_output` for an output schema, `schema_validation` for anything
5982
+ * else (a typed `searchAttributes` write). The same codes `@adonis-agora/durable` uses.
5983
+ */
5984
+ type SchemaValidationCode = 'invalid_input' | 'invalid_output' | 'schema_validation';
5985
+ /**
5986
+ * Thrown when a value fails a schema the app declared: `@Workflow({ input, output })`,
5987
+ * `@Step({ input, output })`, or the workflow's typed `searchAttributes`. A {@link FatalError}, so it
5988
+ * is NOT retried — retrying cannot fix a value that doesn't match its schema. Its `code` says which
5989
+ * schema rejected the value ({@link SchemaValidationCode}). `issues` (also `details.issues`, so it
5990
+ * survives the transport to the run's error and an onError handler's input) lists every issue with
5991
+ * a JSON-safe `path`. Route one code with `onError: [{ code: 'invalid_input', workflow }]`, or all
5992
+ * three with `onError: [{ code: SchemaValidationError, workflow }]`: its `static code`,
5993
+ * `schema_validation`, also catches an `invalid_input` or `invalid_output` no route of its own
5994
+ * matches. Same class, codes, message and `details` as `@adonis-agora/durable`.
5995
+ */
5996
+ declare class SchemaValidationError extends FatalError {
5997
+ static readonly code = "schema_validation";
5998
+ readonly issues: SchemaIssue[];
5999
+ /**
6000
+ * @param label What was validated, e.g. `workflow "checkout" input`; the message is
6001
+ * `invalid <label>: <path>: <message>; …`.
6002
+ * @param summary Overrides the `<path>: <message>; …` part, for issues whose messages already
6003
+ * name their field (class-validator's `orderId must be a string`).
6004
+ */
6005
+ constructor(label: string, issues: ReadonlyArray<{
6006
+ readonly message: string;
6007
+ readonly path?: ReadonlyArray<PropertyKey | {
6008
+ readonly key: PropertyKey;
6009
+ }> | undefined;
6010
+ }>, code?: SchemaValidationCode, summary?: string);
6011
+ }
5811
6012
  /**
5812
6013
  * Thrown by `start` when a run would exceed its concurrency quota: `active` runs sharing the quota
5813
6014
  * `key` are already in flight (in the quota's counted statuses) and the `limit` is `limit`. Nothing
@@ -6256,26 +6457,45 @@ interface PayloadCodec {
6256
6457
  encode(value: unknown): unknown;
6257
6458
  decode(value: unknown): unknown;
6258
6459
  }
6460
+ /** What {@link CodecStateStore} runs through the codec. */
6461
+ interface CodecStateStoreOptions {
6462
+ /**
6463
+ * `'payloads'` (default, the historical behaviour): run/step `input` + `output` only (plus a
6464
+ * schedule's `input` and buffered signal/event payloads, which are always encoded). **Know what
6465
+ * that leaves in the clear**: step `events` (everything step code passes to `ctx.log` and every
6466
+ * sub-step outcome) and the structured step/run `error` (message, stack, details). A deployment
6467
+ * sold as "encrypted payloads" still leaks whatever its steps log or throw.
6468
+ *
6469
+ * `'extended'`: also encode step `events`, step `error` and run `error`. Searchable metadata (ids,
6470
+ * status, workflow, tags, search attributes, namespace, origin, timestamps) stays clear — it must
6471
+ * remain queryable. ONLY enable on a FRESH table, or with a codec whose `decode` recognizes its own
6472
+ * ciphertext and passes other values through: rows written before the switch hold those fields as
6473
+ * plaintext, and a decode that can't tell would garble them.
6474
+ */
6475
+ coverage?: 'payloads' | 'extended';
6476
+ }
6259
6477
  /**
6260
- * A `StateStore` decorator that runs run/step **payloads** (input + output) through a
6261
- * {@link PayloadCodec} — encoded on write, decoded on read — so they're never stored in the clear.
6262
- * Adapter-agnostic: wrap any store. Searchable metadata (id, status, workflow, tags, timestamps) and
6263
- * the structured `error` are left untouched so the dashboard, queries, and recovery still work.
6478
+ * A `StateStore` decorator that runs run/step **payloads** through a {@link PayloadCodec} — encoded
6479
+ * on write, decoded on read — so they're never stored in the clear. Adapter-agnostic: wrap any
6480
+ * store. Searchable metadata (id, status, workflow, tags, timestamps) is left untouched so the
6481
+ * dashboard, queries, and recovery still work. See {@link CodecStateStoreOptions.coverage} for
6482
+ * exactly which fields are covered — the default covers `input`/`output` only.
6264
6483
  *
6265
6484
  * ```ts
6266
- * const store = new CodecStateStore(new TypeOrmStateStore(ds), aesCodec);
6485
+ * const store = new CodecStateStore(new TypeOrmStateStore(ds), aesCodec, { coverage: 'extended' });
6267
6486
  * ```
6268
6487
  */
6269
6488
  declare class CodecStateStore implements StateStore {
6270
6489
  private readonly inner;
6271
6490
  private readonly codec;
6491
+ private readonly extended;
6272
6492
  /** Atomic claims touch only searchable metadata and bypass payload encoding. */
6273
6493
  readonly tryAdmitSingleton?: (runId: string, tag: string, workflow: string, limit: number, retryWakeAt?: number) => Promise<boolean>;
6274
6494
  /**
6275
6495
  * Forwarded verbatim — a facet cell counts `status`/`origin`, neither of which the codec touches
6276
- * (it encodes payloads: input/output/error). Bound as a PROPERTY, and only when the inner store
6277
- * actually aggregates, so `store.runFacets?.()` still reports absence through the wrapper instead
6278
- * of resolving to a silently wrong empty result.
6496
+ * (it encodes payloads: input/output, plus events/error under `extended` coverage). Bound as a
6497
+ * PROPERTY, and only when the inner store actually aggregates, so `store.runFacets?.()` still
6498
+ * reports absence through the wrapper instead of resolving to a silently wrong empty result.
6279
6499
  */
6280
6500
  readonly runFacets?: (query: RunFacetQuery) => Promise<RunFacetRow[]>;
6281
6501
  /**
@@ -6329,7 +6549,7 @@ declare class CodecStateStore implements StateStore {
6329
6549
  readonly updateSchedule?: (id: string, patch: Partial<Omit<ScheduleRecord, 'id' | 'createdAt'>>, expectedNextFireAt?: number | null) => Promise<boolean>;
6330
6550
  readonly deleteSchedule?: (id: string) => Promise<boolean>;
6331
6551
  readonly listSchedules?: (query: ScheduleQuery) => Promise<ScheduleRecord[]>;
6332
- constructor(inner: StateStore, codec: PayloadCodec);
6552
+ constructor(inner: StateStore, codec: PayloadCodec, options?: CodecStateStoreOptions);
6333
6553
  private enc;
6334
6554
  private dec;
6335
6555
  private encRun;
@@ -6355,7 +6575,7 @@ declare class CodecStateStore implements StateStore {
6355
6575
  listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
6356
6576
  listDueTimers(nowMs: number, namespace?: string, limit?: number): Promise<WorkflowRun[]>;
6357
6577
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
6358
- releaseRunLock(runId: string): Promise<void>;
6578
+ releaseRunLock(runId: string, owner?: string): Promise<void>;
6359
6579
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
6360
6580
  putSignalWaiter(waiter: SignalWaiter): Promise<void>;
6361
6581
  takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
@@ -6457,11 +6677,20 @@ type Subscribable = {
6457
6677
  */
6458
6678
  declare function collectMetrics(engine: Subscribable): MetricsCollector;
6459
6679
 
6460
- interface ScheduledWorkflow {
6680
+ /**
6681
+ * A recurring start of `workflow`, fired on a fixed interval or a cron.
6682
+ *
6683
+ * `workflow` is a registered name or a `@Workflow` class. A class types `input` against its `run`;
6684
+ * annotate the schedule to get that check, as in
6685
+ * `const nightly: ScheduledWorkflow<typeof ReportWorkflow> = { workflow: ReportWorkflow, … }`.
6686
+ */
6687
+ interface ScheduledWorkflow<W extends WorkflowRef = WorkflowRef> {
6461
6688
  /** Stable key identifying this schedule — part of the deterministic run id. */
6462
6689
  key: string;
6463
- workflow: string;
6464
- input?: unknown;
6690
+ /** The workflow to start: a registered name (any runtime) or a `@Workflow` class. */
6691
+ workflow: W;
6692
+ /** The input every run starts with — typed against `run` when `workflow` is a class. */
6693
+ input?: WorkflowInputOf<W>;
6465
6694
  /** Start one run every `everyMs`. Mutually exclusive with `cron`. */
6466
6695
  everyMs?: number;
6467
6696
  /**
@@ -6499,6 +6728,16 @@ interface ScheduledWorkflow {
6499
6728
  backfill?: {
6500
6729
  maxCatchup: number;
6501
6730
  };
6731
+ /**
6732
+ * The namespace this schedule's runs are stamped with, overriding the ticking engine's own. Opt-in;
6733
+ * absent = the run gets the namespace of whichever engine fired it.
6734
+ *
6735
+ * The run id is a time bucket, so the instance that wins the race to fire a window decides which
6736
+ * namespace (worker pool) the run lands in. When the workflow can only run in one pool — its steps
6737
+ * need a capability only that pool's workers have — pin it here, so whoever fires the window, the
6738
+ * run is stamped for (and only picked up by) that pool.
6739
+ */
6740
+ namespace?: string;
6502
6741
  }
6503
6742
  /** Injectable dispatch-path effects (kept out of the deterministic run-id logic). Defaults to real
6504
6743
  * `Math.random` + `setTimeout`; overridden in tests so jitter is deterministic and instant. */
@@ -6520,14 +6759,23 @@ declare function prevCronFireMs(expr: string, nowMs: number, timezone?: string):
6520
6759
  * schedule is next due.
6521
6760
  */
6522
6761
  declare function nextCronFireMs(expr: string, afterMs: number, timezone?: string): number;
6762
+ /**
6763
+ * The deterministic, idempotent run id for a schedule at `nowMs` — its current fire window
6764
+ * (`sched:<key>:<bucket>`, the bucket being a fixed-interval index or the cron fire time). Pass the
6765
+ * same `ScheduledWorkflow` you registered to look a window's run up, e.g.
6766
+ * `engine.getRun(scheduleRunIdAt(dailyReport, Date.now()))`, rather than rebuilding the id by hand.
6767
+ */
6768
+ declare function scheduleRunIdAt(s: ScheduledWorkflow, nowMs: number): string;
6523
6769
  /**
6524
6770
  * Start each schedule's current-window run. The run id is the time bucket (a fixed-interval index,
6525
6771
  * or the cron fire time) and `engine.start` is idempotent, so firing this on an interval — or
6526
6772
  * racing two instances on the same tick — starts **each window exactly once**. Wire it to a
6527
- * `setInterval`, the durable timer poller, or `@nestjs/schedule`. Returns the run ids for the
6528
- * current windows.
6773
+ * `setInterval`, the durable timer poller, or `@nestjs/schedule`. Returns the run ids this call
6774
+ * started: a window that already has a run is not started or returned again, and once this process
6775
+ * has seen a window's run exist, later calls in that window skip it without a store read or a
6776
+ * jitter sleep.
6529
6777
  */
6530
- declare function runSchedules(engine: Pick<WorkflowEngine, 'start' | 'getRun'>, schedules: readonly ScheduledWorkflow[], nowMs: number, opts?: RunSchedulesOptions): Promise<string[]>;
6778
+ declare function runSchedules(engine: Pick<WorkflowEngine, 'start' | 'getRun'> & Partial<Pick<WorkflowEngine, 'isDraining'>>, schedules: readonly ScheduledWorkflow[], nowMs: number, opts?: RunSchedulesOptions): Promise<string[]>;
6531
6779
 
6532
6780
  /**
6533
6781
  * A {@link StepLogger} that appends to `events`, stamping each line with `now()`. Shared by the
@@ -6797,7 +7045,7 @@ declare class InMemoryStateStore implements StateStore {
6797
7045
  listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
6798
7046
  listDueTimers(nowMs: number, namespace?: string, limit?: number): Promise<WorkflowRun[]>;
6799
7047
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
6800
- releaseRunLock(runId: string): Promise<void>;
7048
+ releaseRunLock(runId: string, owner?: string): Promise<void>;
6801
7049
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
6802
7050
  putSignalWaiter(waiter: SignalWaiter): Promise<void>;
6803
7051
  takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
@@ -6940,4 +7188,4 @@ declare function singletonAdmissionDenialPatch(runs: WorkflowRun[], runId: strin
6940
7188
  updatedAt: Date;
6941
7189
  } | undefined;
6942
7190
 
6943
- export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AllOptions, type AllSettledOptions, type AllStepsOptions, type AllStepsSettledOptions, type AnnouncedWorkflow, type AnnouncementEvidence, type AttributeFilter, type AttributeOp, type AttributePredicateOperand, type AttributeValue, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CHILD_EDGE_PREFIXES, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type CheckpointAggregatingStore, type CheckpointNameKind, type CheckpointQuery, type CheckpointStat, type ChildCallOptions, type ChildWait, CodecStateStore, type ConcurrencyConfig, ConcurrencyLimitError, type ConcurrencyQuota, ContinueAsNew, type ControlMessage, type ControlPlane, DEFAULT_CONCURRENCY_STATUSES, DEFAULT_PARTITION, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type Disagreement, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, type DynamicWorkflowCtx, ENGINE_MINTED_TAG_PREFIXES, ENTITY_WORKFLOW, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, type EntityWorkflowHost, EventAccumulators, type EventBatchConfig, FAN_SUMMARY_FAILURES, type FanJoin, type FanPage, type FanPageQuery, type FanSummary, type FanWait, FatalError, GatherError, type GatherItemFailure, type GroupHealth, HANDOFF_SEQ_BASE, type Heartbeat, type HeartbeatSighting, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NestedWorkflowCallError, NonDeterminismError, type OnErrorInput, type OnErrorRoute, type OwnedLeaseReclaim, type PartitionSighting, type PayloadCodec, type QueueConfig, QueueController, RUN_VALUE_FACET_LIMIT, RUN_VALUE_FACET_SCAN, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDetailOptions, type RunDispatcher, type RunFacetQuery, type RunFacetRow, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunScope, type RunStatus, type RunValueAxis, type RunValueFacetOptions, type RunValueFacetOrder, type RunValueFacetRow, type RunWaiting, SINGLETON_ACTIVE_STATUSES, SINGLETON_ADMITTED_TAG, SINGLETON_RESERVED_TAGS, SINGLETON_TRACKED_TAG, STATE_STORE, STATE_STORE_CANONICAL, SUMMARY_CHILD_LIMIT, type ScalarAttributeFilter, ScheduleAlreadyExistsError, ScheduleClient, type ScheduleClientOptions, type ScheduleDescription, type ScheduleHost, type ScheduleListQuery, ScheduleNotFoundError, type ScheduleOptions, type ScheduleQuery, type ScheduleRecord, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, type Settled, 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 StepScope, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, VERSION_UNDECLARED_TAG, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, type WorkflowAnnouncement, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, type WorkflowDirectory, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRegistration, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, aggregateAnnouncements, aggregatesCheckpoints, announcementsOf, applyAttributeQuery, assertConcurrency, assertRoutes, attributeColumnFor, attributeOperand, attributePredicateOperands, axisIsRunColumn, bindWorkflowClass, breakpointToken, buildWorkflowDirectory, canRoute, capableWorkers, checkpointNameKind, checkpointStatsOf, childIdsOf, classifyWaiterToken, clearDurableExecutionWrappers, clearTerminalSingletonAdmission, codeAndDetails, codeOf, collectMetrics, concurrencyTag, controlPlaneDescriptor, countRuns, coveredTokens, createStepLogger, currentStep, currentStepScope, currentWorkflowCtx, debug, describeWorker, descriptorHash, detailsOf, error, etagMatches, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, facetOrigin, fanPageBounds, fanPageEtag, foldedCount, handoffHandlerId, handoffName, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEngineMintedTag, isEventToken, isFailedMember, isLegacyDescriptor, isNonEmptyRunScope, isWorkflowControlFlowSignal, legacySingletonHolders, listSignalWaitersOfRuns, liveHandoff, log, matchesAttributes, mergeRunFacetRows, mergeRunValueFacetRows, missingCapabilities, negotiate, nextCronFireMs, normalizeAttributeRows, normalizeDescriptor, observedAnnouncements, onErrorRunId, pageFan, pageFanFromStore, parentRunIdOf, parseDuration, parseWebhookToken, partitionOfToken, planDispatch, poolNamespace, prevCronFireMs, queryCheckpointsOf, readSearchAttributes, requiredCapabilities, resolveConcurrency, resolveRouting, resolveRunWaiting, retiredHandoffName, routeFor, runDetailEtag, runDurableExecution, runInStepLogger, runInStepScope, runInWorkflowCtx, runOutsideWorkflowCtx, runSchedules, runStepHandler, runValueFacetsFromRuns, sanitizeQueueToken, scheduledRunId, singletonAdmissionDenialPatch, singletonAdmissionTags, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, summarizeFan, summarizeRunFromStore, summarizeTimeline, tenantGroup, useDurableExecution, warn, withSingletonTracked, withoutSingletonMarkers, workflowName };
7191
+ export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AllOptions, type AllSettledOptions, type AllStepsOptions, type AllStepsSettledOptions, type AnnouncedWorkflow, type AnnouncementEvidence, type AttributeFilter, type AttributeOp, type AttributePredicateOperand, type AttributeValue, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CHILD_EDGE_PREFIXES, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type CheckpointAggregatingStore, type CheckpointNameKind, type CheckpointQuery, type CheckpointStat, type ChildCallOptions, type ChildWait, CodecStateStore, type CodecStateStoreOptions, type ConcurrencyConfig, ConcurrencyLimitError, type ConcurrencyQuota, ContinueAsNew, type ControlMessage, type ControlPlane, DEFAULT_CONCURRENCY_STATUSES, DEFAULT_PARTITION, DEFAULT_STALLED_AFTER_MS, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type Disagreement, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, type DynamicWorkflowCtx, ENGINE_MINTED_TAG_PREFIXES, ENTITY_WORKFLOW, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, type EntityWorkflowHost, EventAccumulators, type EventBatchConfig, FAN_SUMMARY_FAILURES, type FanJoin, type FanPage, type FanPageQuery, type FanSummary, type FanWait, FatalError, type FindStalledRunsOptions, GatherError, type GatherItemFailure, type GroupHealth, HANDOFF_SEQ_BASE, type Heartbeat, type HeartbeatSighting, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type ListStalledRunsOptions, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NestedWorkflowCallError, NonDeterminismError, type OnErrorInput, type OnErrorRoute, type OwnedLeaseReclaim, type PartitionSighting, type PayloadCodec, type QueueConfig, QueueController, RUN_HISTORY_FIXTURE_VERSION, RUN_VALUE_FACET_LIMIT, RUN_VALUE_FACET_SCAN, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDetailOptions, type RunDispatcher, type RunFacetQuery, type RunFacetRow, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunScope, type RunStatus, type RunValueAxis, type RunValueFacetOptions, type RunValueFacetOrder, type RunValueFacetRow, type RunWaiting, SINGLETON_ACTIVE_STATUSES, SINGLETON_ADMITTED_TAG, SINGLETON_RESERVED_TAGS, SINGLETON_TRACKED_TAG, STATE_STORE, STATE_STORE_CANONICAL, SUMMARY_CHILD_LIMIT, type ScalarAttributeFilter, ScheduleAlreadyExistsError, ScheduleClient, type ScheduleClientOptions, type ScheduleDescription, type ScheduleHost, type ScheduleListQuery, ScheduleNotFoundError, type ScheduleOptions, type ScheduleQuery, type ScheduleRecord, type ScheduledWorkflow, type SchemaIssue, type SchemaValidationCode, SchemaValidationError, type SearchAttributes, type SearchAttributesSchema, type Settled, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, type StalePendingStep, type StalledRunInfo, 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 StepScope, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, VERSION_UNDECLARED_TAG, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, type WorkflowAnnouncement, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, type WorkflowDirectory, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRegistration, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, aggregateAnnouncements, aggregatesCheckpoints, announcementsOf, applyAttributeQuery, assertConcurrency, assertRoutes, attributeColumnFor, attributeOperand, attributePredicateOperands, axisIsRunColumn, bindWorkflowClass, breakpointToken, buildWorkflowDirectory, canRoute, capableWorkers, checkpointNameKind, checkpointStatsOf, childIdsOf, classifyWaiterToken, clearDurableExecutionWrappers, clearTerminalSingletonAdmission, codeAndDetails, codeOf, collectMetrics, concurrencyTag, controlPlaneDescriptor, countRuns, coveredTokens, createStepLogger, currentStep, currentStepScope, currentWorkflowCtx, debug, describeWorker, descriptorHash, detailsOf, error, etagMatches, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, facetOrigin, fanPageBounds, fanPageEtag, findStalledRuns, foldedCount, handoffHandlerId, handoffName, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEngineMintedTag, isEventToken, isFailedMember, isLegacyDescriptor, isNonEmptyRunScope, isWorkflowControlFlowSignal, legacySingletonHolders, listSignalWaitersOfRuns, liveHandoff, log, matchesAttributes, mergeRunFacetRows, mergeRunValueFacetRows, missingCapabilities, negotiate, nextCronFireMs, normalizeAttributeRows, normalizeDescriptor, observedAnnouncements, onErrorRunId, pageFan, pageFanFromStore, parentRunIdOf, parseDuration, parseWebhookToken, partitionOfToken, planDispatch, poolNamespace, prevCronFireMs, queryCheckpointsOf, readSearchAttributes, requiredCapabilities, resolveConcurrency, resolveRouting, resolveRunWaiting, retiredHandoffName, routeFor, runDetailEtag, runDurableExecution, runInStepLogger, runInStepScope, runInWorkflowCtx, runOutsideWorkflowCtx, runSchedules, runStepHandler, runValueFacetsFromRuns, sanitizeQueueToken, scheduleRunIdAt, scheduledRunId, singletonAdmissionDenialPatch, singletonAdmissionTags, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, summarizeFan, summarizeRunFromStore, summarizeTimeline, tenantGroup, useDurableExecution, warn, withSingletonTracked, withoutSingletonMarkers, workflowName };