@dudousxd/nestjs-durable-core 0.44.1 → 0.46.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.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  /**
4
- * Flow control for remote steps. A queue caps how much work `ctx.call(step, input, { queue })`
4
+ * Flow control for dispatched steps. A queue caps how much work `ctx.step(step, input, { queue })`
5
5
  * admits at once — a concurrency limit and/or a fixed-window rate limit. When a call can't be
6
6
  * admitted it doesn't dispatch: the run re-suspends with a near-future wake time, and the timer
7
7
  * poller re-tries admission later — so flow control is durable (survives crashes) without holding
@@ -25,7 +25,7 @@ import { z } from 'zod';
25
25
  * blocked, and on a later retry only admits the one that is rightful-next under the policy.
26
26
  */
27
27
  interface QueueConfig {
28
- /** Queue name, referenced by `ctx.call(step, input, { queue: name })`. */
28
+ /** Queue name, referenced by `ctx.step(step, input, { queue: name })`. */
29
29
  name: string;
30
30
  /** Max steps in flight at once for this queue (this instance). Omit for unlimited. */
31
31
  concurrency?: number;
@@ -114,7 +114,7 @@ declare class QueueController {
114
114
  }
115
115
 
116
116
  /**
117
- * Pluggable backend for the remote-step flow-control gate (`ctx.call(step, input, { queue })`). The
117
+ * Pluggable backend for the step flow-control gate (`ctx.step(step, input, { queue })`). The
118
118
  * engine asks it whether a queued call may be admitted now (concurrency / rate / ordering) and tells
119
119
  * it when an admitted slot is released.
120
120
  *
@@ -167,6 +167,69 @@ declare class InMemoryAdmissionBackend implements AdmissionBackend {
167
167
  */
168
168
  declare function parseDuration(duration: string | number): number;
169
169
 
170
+ /**
171
+ * Cross-package step-name stamp. The `@Step` decorator (`@dudousxd/nestjs-durable`) writes a
172
+ * method's derived routing name under this key; core's `ctx.step` reads it (via {@link stepNameOf})
173
+ * to route a method-reference call to the same handler a worker registers by name.
174
+ *
175
+ * `Symbol.for` (the GLOBAL registry), NOT a plain `Symbol()` — so a duplicate copy of this module
176
+ * (pnpm peer-dependency multiplexing, or a dual ESM/CJS load) still reads the SAME key. A plain
177
+ * `Symbol()` would mint a distinct token per copy, so a decorator running against one core instance
178
+ * could stamp a name core's `ctx.step` (a different instance) can never read back. Mirrors the
179
+ * `STATE_STORE` token fix in `./tokens.ts`.
180
+ */
181
+ declare const DURABLE_STEP_NAME: unique symbol;
182
+ /**
183
+ * Cross-package step-DISPATCH-POLICY stamp. `@Step({ retries, backoff, backoffMs, backoffMaxMs,
184
+ * jitter, timeoutMs })` (`@dudousxd/nestjs-durable`) writes the def-level durable-retry/liveness
185
+ * policy under this key; core's `ctx.step` reads it (via {@link stepConfigOf}) and merges it with any
186
+ * per-call {@link StepDispatchOpts} override to build the dispatched {@link StepDef}. Same
187
+ * `Symbol.for` rationale as {@link DURABLE_STEP_NAME} — a duplicate copy of this module (pnpm
188
+ * peer-dependency multiplexing, dual ESM/CJS) must still read back the SAME key.
189
+ */
190
+ declare const DURABLE_STEP_CONFIG: unique symbol;
191
+ /**
192
+ * The def-level durable-dispatch policy a `@Step(...)` can stamp on a method: retry/backoff and the
193
+ * remote-liveness `timeoutMs` — the dispatch-relevant subset of the engine's `StepOptions`. Read off
194
+ * a handler reference via {@link stepConfigOf}; a per-call {@link StepDispatchOpts} passed to
195
+ * `ctx.step(ref, input, opts)` overrides these field-by-field.
196
+ */
197
+ interface StepConfig {
198
+ /** Max attempts before the step (and run) fails. */
199
+ retries?: number | undefined;
200
+ /** How the delay between retries grows: `fixed` (constant) or `exp` (doubles each attempt). */
201
+ backoff?: 'fixed' | 'exp' | undefined;
202
+ /** Base delay in ms between retries. Omit (or 0) to retry with no delay. */
203
+ backoffMs?: number | undefined;
204
+ /** Upper bound on the (exponential) backoff delay. */
205
+ backoffMaxMs?: number | undefined;
206
+ /** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. */
207
+ jitter?: boolean | undefined;
208
+ /** Liveness window for the dispatched step: no result/heartbeat within this many ms presumes the
209
+ * worker dead and fails the dispatch with a `RemoteStepTimeout` (retryable — re-dispatches per
210
+ * `retries`). Omit to wait indefinitely. */
211
+ timeoutMs?: number | undefined;
212
+ }
213
+ /**
214
+ * A method carrying its `@Step`-stamped routing name (and, optionally, its dispatch policy).
215
+ * `ctx.step(ref, input)` reads the name via {@link stepNameOf} — the reference itself is never
216
+ * invoked directly by the caller's process (the worker serving that name re-resolves the real
217
+ * handler from DI), so an unbound `this` on `ref` is irrelevant; `ref` is purely a typed,
218
+ * refactor-safe handle onto the stamped name (and policy).
219
+ */
220
+ type StepRef<TInput = unknown, TOutput = unknown> = ((input: TInput) => Promise<TOutput> | TOutput) & {
221
+ [DURABLE_STEP_NAME]?: string | undefined;
222
+ [DURABLE_STEP_CONFIG]?: StepConfig | undefined;
223
+ };
224
+ /** Read the `@Step`-stamped routing name off a function ref. `undefined` for anything unstamped
225
+ * (including a plain string — {@link stepNameOf} only reads function refs; a caller passing a
226
+ * string already has the routing name and dispatches it directly). */
227
+ declare function stepNameOf(ref: unknown): string | undefined;
228
+ /** Read the `@Step`-stamped dispatch policy off a function ref. `undefined` for anything unstamped
229
+ * (including a plain string — a cross-runtime string-name call has no def-level policy to read; pass
230
+ * it via {@link StepDispatchOpts} instead). */
231
+ declare function stepConfigOf(ref: unknown): StepConfig | undefined;
232
+
170
233
  /**
171
234
  * A **workflow reference** is how one workflow names another to call it: either a registered workflow
172
235
  * **name** (a string — the only option across runtimes, e.g. a Python workflow) or, for a same-runtime
@@ -297,7 +360,7 @@ interface StepCheckpoint {
297
360
  * simply re-runs on replay (only `completed` short-circuits).
298
361
  */
299
362
  status: 'pending' | 'running' | 'completed' | 'failed';
300
- /** What the step was called with — the `ctx.call` args for a remote step (a local step has none). */
363
+ /** What the step was called with — the `ctx.step` (dispatched) args for a remote step (a local step has none). */
301
364
  input?: unknown;
302
365
  output?: unknown;
303
366
  error?: StepError | undefined;
@@ -594,6 +657,85 @@ interface SignalWaiter {
594
657
  */
595
658
  parallelGroup?: string | undefined;
596
659
  }
660
+ /**
661
+ * A DB-less tenant worker → control plane request to start a run. Published by a worker that has
662
+ * no direct DB access (the hosted-control-plane tenant model) onto the
663
+ * `<effectivePrefix>-start-run` queue; the control plane consumes it and turns it into a durable
664
+ * run (P4 of the tenants plan).
665
+ *
666
+ * The `tenant` identifies the namespace that owns the run (maps to the engine's `namespace`); it
667
+ * is separate from the wire-level key prefix so a single transport can serve multiple tenants.
668
+ */
669
+ interface StartRunMessage {
670
+ tenant: string;
671
+ /** Registered workflow name. */
672
+ workflow: string;
673
+ input: unknown;
674
+ /** Optional caller-supplied run id (idempotency key). The engine generates one if absent. */
675
+ runId?: string | undefined;
676
+ /** Tags to stamp on the run (merged with the workflow's static @Workflow tags). */
677
+ tags?: string[] | undefined;
678
+ /** Typed, queryable run data to stamp on the run (same as {@link StartOptions.searchAttributes}). */
679
+ searchAttributes?: SearchAttributes | undefined;
680
+ }
681
+ /**
682
+ * A tenant worker → control plane read/control request over the shared transport. Enqueued on
683
+ * `<effectivePrefix>-run-request`; the control plane's `onRunRequest` consumer answers it, scoped to
684
+ * `tenant`, and publishes a {@link RunReply} on `<effectivePrefix>-run-reply` correlated by `requestId`.
685
+ */
686
+ interface RunRequest {
687
+ /** Correlation id minted by the tenant; the matching {@link RunReply} carries it back. */
688
+ requestId: string;
689
+ /** The requesting tenant — the operator scopes the run's namespace to this. */
690
+ tenant: string;
691
+ body: RunRequestKind;
692
+ }
693
+ /** The discriminated verb + args of a {@link RunRequest}. Mirrors the tenant-facing `RunGateway`. */
694
+ type RunRequestKind = {
695
+ kind: 'getRunDetail';
696
+ runId: string;
697
+ } | {
698
+ kind: 'listRuns';
699
+ query: RunQuery;
700
+ } | {
701
+ kind: 'cancel';
702
+ runId: string;
703
+ opts?: {
704
+ compensate?: boolean;
705
+ };
706
+ } | {
707
+ kind: 'retry';
708
+ runId: string;
709
+ } | {
710
+ kind: 'continue';
711
+ runId: string;
712
+ } | {
713
+ kind: 'retryWithInput';
714
+ runId: string;
715
+ input: unknown;
716
+ };
717
+ /** The control plane's answer to a {@link RunRequest}, correlated by `requestId`. */
718
+ interface RunReply {
719
+ requestId: string;
720
+ result: RunReplyResult;
721
+ }
722
+ /** Success carries the verb's payload (JSON-serialised); failure carries a re-throwable error. */
723
+ type RunReplyResult = {
724
+ ok: true;
725
+ data: unknown;
726
+ } | {
727
+ ok: false;
728
+ error: {
729
+ message: string;
730
+ code?: string;
731
+ };
732
+ };
733
+ /** A lifecycle event re-published to a single tenant's channel (`<effectivePrefix>-tenant-events-<tenant>`)
734
+ * so a store-less tenant can live-tail ITS OWN runs. Scoped by the run's namespace at publish time. */
735
+ interface TenantEvent {
736
+ tenant: string;
737
+ event: EngineEvent;
738
+ }
597
739
  /** A unit of work dispatched to a remote worker. This is the documented wire payload. */
598
740
  interface RemoteTask {
599
741
  runId: string;
@@ -622,7 +764,7 @@ interface RemoteTask {
622
764
  /**
623
765
  * Admission priority carried through to the broker (BullMQ job `priority`) so a transport that
624
766
  * supports priority ordering lets an urgent task jump ahead of already-enqueued lower-priority
625
- * ones at the worker. Mirrors the per-call `priority` from `ctx.call(..., { priority })`. Higher
767
+ * ones at the worker. Mirrors the per-call `priority` from `ctx.step(..., { priority })`. Higher
626
768
  * wins; absent means default/unprioritised. Transports without priority support ignore it.
627
769
  */
628
770
  priority?: number | undefined;
@@ -693,10 +835,10 @@ interface HistoryEvent {
693
835
  }
694
836
  /** A decision the workflow function produced at a `seq` that was not yet in history. */
695
837
  type WorkflowCommand =
696
- /** `ctx.call(remoteStep, input)` — dispatch a remote step and await it. A worker's
697
- * `ctx.gather_calls([...])` fan-out tags every dispatched call with the same `parallelGroup` so the
698
- * dashboard renders the remote steps as one parallel fan (parity with the gathered `recordStep` /
699
- * `startChild` tags). Undefined for a lone sequential `ctx.call`. */
838
+ /** `ctx.step(handlerOrName, input)` — dispatch a step (by typed handler reference or by name) and
839
+ * await it. A worker's `ctx.gather_calls([...])` fan-out tags every dispatched call with the same
840
+ * `parallelGroup` so the dashboard renders the dispatched steps as one parallel fan (parity with
841
+ * the gathered `recordStep` / `startChild` tags). Undefined for a lone sequential `ctx.step`. */
700
842
  {
701
843
  kind: 'call';
702
844
  seq: number;
@@ -816,7 +958,7 @@ interface WorkflowExecutor {
816
958
  }
817
959
  /**
818
960
  * A transport in an ordered pool, identified by `id`. The engine dispatches on the first by default
819
- * and fails over to the next on a dispatch error; a step can pin one via `ctx.call(…, { transport })`.
961
+ * and fails over to the next on a dispatch error; a step can pin one via `ctx.step(…, { transport })`.
820
962
  * The chosen `id` is stamped on the {@link RemoteTask} so a worker replies through the matching one.
821
963
  */
822
964
  interface NamedTransport {
@@ -840,6 +982,13 @@ interface Transport {
840
982
  onResult(handler: (result: StepResult) => Promise<void>): void;
841
983
  /** worker → engine: liveness signal for an in-flight long step. */
842
984
  onHeartbeat(handler: (beat: Heartbeat) => Promise<void>): void;
985
+ /**
986
+ * Partition this transport's queues/keys by `namespace` (matching the engine's store namespace), so
987
+ * multiple logical deployments can share one broker. Idempotent. An explicit namespace passed to the
988
+ * transport's constructor TAKES PRECEDENCE over this. No-op when `namespace === "default"`. Optional —
989
+ * transports that don't partition simply omit it.
990
+ */
991
+ useNamespace?(namespace: string): void;
843
992
  /** Release the transport's resources (broker workers, queues, connections) for a clean shutdown.
844
993
  * Optional — an in-process transport has nothing to close. Called on `onApplicationShutdown`
845
994
  * after the engine drains, so a deploy hands off instead of leaving the broker to time out. */
@@ -864,6 +1013,35 @@ interface Transport {
864
1013
  * keyspace, so a group with workers but no engine-side registration (e.g. a local-step group)
865
1014
  * still surfaces. Pairs with {@link groupHealth}. */
866
1015
  listWorkerGroups?(): Promise<string[]>;
1016
+ /**
1017
+ * DB-less tenant worker → control plane: publish a {@link StartRunMessage} requesting a new run.
1018
+ * Optional — only transports that carry the hosted-control-plane protocol (P4) implement this.
1019
+ * The message is enqueued on `<effectivePrefix>-start-run`; the control plane's
1020
+ * `onStartRun` consumer turns it into a durable run.
1021
+ */
1022
+ dispatchStartRun?(msg: StartRunMessage): Promise<void>;
1023
+ /**
1024
+ * control plane ← tenant worker: consume {@link StartRunMessage}s and start runs. Pair with
1025
+ * {@link dispatchStartRun}. Optional — only control-plane-side transports implement this.
1026
+ */
1027
+ onStartRun?(handler: (msg: StartRunMessage) => Promise<void>): void;
1028
+ /**
1029
+ * Tenant worker → control plane: publish a {@link RunRequest} (read/control) on
1030
+ * `<effectivePrefix>-run-request`. Optional — only broker transports carry it.
1031
+ */
1032
+ dispatchRunRequest?(msg: RunRequest): Promise<void>;
1033
+ /** control plane ← tenant worker: consume {@link RunRequest}s. Pair with {@link dispatchRunRequest}. */
1034
+ onRunRequest?(handler: (msg: RunRequest) => Promise<void>): void;
1035
+ /** control plane → tenant worker: publish a correlated {@link RunReply} on `<effectivePrefix>-run-reply`
1036
+ * (pub/sub; every tenant subscribes and filters by `requestId`). */
1037
+ publishRunReply?(reply: RunReply): Promise<void>;
1038
+ /** tenant worker ← control plane: consume {@link RunReply}s (filter by `requestId` client-side). */
1039
+ onRunReply?(handler: (reply: RunReply) => void): void;
1040
+ /** control plane → tenant worker: re-publish a lifecycle {@link TenantEvent} on the run's per-tenant
1041
+ * channel `<effectivePrefix>-tenant-events-<tenant>`. */
1042
+ publishTenantEvent?(evt: TenantEvent): Promise<void>;
1043
+ /** tenant worker ← control plane: subscribe to THIS tenant's event channel. Returns an unsubscribe fn. */
1044
+ onTenantEvent?(tenant: string, handler: (evt: TenantEvent) => void): () => void;
867
1045
  }
868
1046
  /** How a worker decides its concurrency, carried in {@link WorkerStatus} so a dashboard can tell a
869
1047
  * fixed knob from a self-regulating one (and, for the latter, where the controller currently sits). */
@@ -997,10 +1175,10 @@ interface StepOptions {
997
1175
  /** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. */
998
1176
  jitter?: boolean;
999
1177
  /**
1000
- * Liveness window for a **remote** step (`ctx.call`): if the worker produces no result and no
1178
+ * Liveness window for a dispatched step (`ctx.step`): if the worker produces no result and no
1001
1179
  * heartbeat within this many ms, the engine presumes it dead and fails the dispatch with a
1002
1180
  * `RemoteStepTimeout` (retryable — it re-dispatches per `retries`). Each heartbeat resets the
1003
- * window. Ignored for local steps. Omit to wait indefinitely.
1181
+ * window. Omit to wait indefinitely.
1004
1182
  */
1005
1183
  timeoutMs?: number;
1006
1184
  /**
@@ -1012,17 +1190,22 @@ interface StepOptions {
1012
1190
  compensate?: () => Promise<void>;
1013
1191
  }
1014
1192
  /**
1015
- * A typed handle to a step that runs on a remote worker. The `name` string is the contract:
1016
- * the worker registers a handler under the same name. `input`/`output` validate at the boundary.
1193
+ * The structural carrier a dispatched `ctx.step` call resolves to and hands the engine: `name` is
1194
+ * the routing contract (the worker registers a handler under the same name, and routing is BY that
1195
+ * name — a worker subscribes per registered handler name, not a hand-declared group). There is no
1196
+ * public factory for this anymore — a `ctx.step(ref, input)` call builds one internally from the
1197
+ * `@Step`-stamped name (see {@link StepRef}/`stepNameOf`); a `ctx.step(name, input)` call builds one
1198
+ * from the literal string. `input`/`output` are optional runtime zod schemas an authoring layer MAY
1199
+ * attach (e.g. `@Step({ input, output })`) for validation at the dispatch boundary — a bare `@Step()`
1200
+ * carries neither, and the engine skips validation when they're absent.
1017
1201
  */
1018
- interface RemoteStepDef<TInput = unknown, TOutput = unknown> extends StepOptions {
1202
+ interface StepDef<TInput = unknown, TOutput = unknown> extends StepOptions {
1019
1203
  name: string;
1020
- /** Worker group expected to handle this step. */
1021
- group: string;
1022
- input: z.ZodType<TInput>;
1023
- output: z.ZodType<TOutput>;
1024
- /** Branding so `ctx.call` can infer types. */
1025
- readonly __remote: true;
1204
+ /** Optional isolation partition; routing is by `name`. Suffixes the routing token as
1205
+ * `<name>@<partition>` (via {@link tenantGroup}) — omit to route by the bare (sanitized) `name`. */
1206
+ partition?: string | undefined;
1207
+ input?: z.ZodType<TInput> | undefined;
1208
+ output?: z.ZodType<TOutput> | undefined;
1026
1209
  }
1027
1210
  /**
1028
1211
  * A durable webhook handle minted by {@link WorkflowCtx.webhook}. Hand `url` to a third party,
@@ -1041,6 +1224,45 @@ interface DurableWebhook<TPayload = unknown> {
1041
1224
  /** Suspend until the callback arrives, then resume with its payload. */
1042
1225
  wait(): Promise<TPayload>;
1043
1226
  }
1227
+ /**
1228
+ * Options for a dispatched {@link WorkflowCtx.step} call. `retries`/`backoff`/`backoffMs`/
1229
+ * `backoffMaxMs`/`jitter`/`timeoutMs` are a PER-CALL override of the `@Step`-declared
1230
+ * {@link StepConfig} (see `stepConfigOf`) — the effective policy `ctx.step` builds into the dispatched
1231
+ * {@link StepDef} is `{ ...stepConfigOf(ref), ...opts }`, so a call-site value wins field-by-field. The
1232
+ * string (cross-runtime) form of `ctx.step` has no stamped `@Step` to read, so it uses these fields
1233
+ * as-is.
1234
+ */
1235
+ interface StepDispatchOpts {
1236
+ /** Subject the dispatch to a registered flow-control queue (concurrency / rate limit). */
1237
+ queue?: string;
1238
+ /** Admission priority within `queue`; higher is admitted first when a slot is contended
1239
+ * (default 0). No effect without a `queue`. */
1240
+ priority?: number;
1241
+ /** The fairness bucket for a queue with `fairness: 'key'` (e.g. a tenant id) — the queue
1242
+ * round-robins across distinct keys so one key can't monopolize the budget. Defaults to the run
1243
+ * id when omitted. No effect without a `queue`. */
1244
+ fairnessKey?: string;
1245
+ /** Pin the dispatch to a named transport in the pool (else the pool's first, with failover to the
1246
+ * rest). See `engine.registerQueue` / the engine's `transports` option. */
1247
+ transport?: string;
1248
+ /** Max attempts before the step (and run) fails. Overrides the `@Step`-declared value. */
1249
+ retries?: number;
1250
+ /** How the delay between retries grows: `fixed` (constant) or `exp` (doubles each attempt).
1251
+ * Overrides the `@Step`-declared value. */
1252
+ backoff?: BackoffStrategy;
1253
+ /** Base delay in ms between retries. Omit (or 0) to retry with no delay. Overrides the
1254
+ * `@Step`-declared value. */
1255
+ backoffMs?: number;
1256
+ /** Upper bound on the (exponential) backoff delay. Overrides the `@Step`-declared value. */
1257
+ backoffMaxMs?: number;
1258
+ /** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. Overrides
1259
+ * the `@Step`-declared value. */
1260
+ jitter?: boolean;
1261
+ /** Liveness window for this dispatched step: no result/heartbeat within this many ms presumes the
1262
+ * worker dead and fails the dispatch with a `RemoteStepTimeout` (retryable — re-dispatches per
1263
+ * `retries`). Omit to wait indefinitely. Overrides the `@Step`-declared value. */
1264
+ timeoutMs?: number;
1265
+ }
1044
1266
  /**
1045
1267
  * The context handed to a workflow function. Every interaction with the outside world goes
1046
1268
  * through it so the engine can checkpoint — the workflow body itself stays deterministic.
@@ -1048,11 +1270,25 @@ interface DurableWebhook<TPayload = unknown> {
1048
1270
  interface WorkflowCtx {
1049
1271
  readonly runId: string;
1050
1272
  /**
1051
- * Run a local durable step: executed once, then its result is checkpointed and replayed. The
1052
- * body receives a {@link StepLogger} to record debug/error lines and sub-process outcomes — these
1053
- * are checkpointed with the step and shown under it in the dashboard.
1273
+ * Run a durable step — always dispatched, always engine-scheduled: the ONE step primitive (no
1274
+ * local/remote placement choice). Pass the step's method **reference** (a `@Step`-decorated
1275
+ * method, typed by its own signature — refactor-safe, autocompleted):
1276
+ *
1277
+ * ```ts
1278
+ * const r = await ctx.step(this.extraction.runExtractionPage, { page, key });
1279
+ * ```
1280
+ *
1281
+ * or its **name** for a cross-runtime handler (no JS reference to import, e.g. a Python `@step`):
1282
+ *
1283
+ * ```ts
1284
+ * const out = await ctx.step<ProcResult>('processing:proc', input);
1285
+ * ```
1286
+ *
1287
+ * Both forms dispatch identically — a step runs on whatever worker serves that name and the run
1288
+ * suspends (zero compute) until the result lands, then resumes with it (durable, replay-safe).
1054
1289
  */
1055
- step<TOutput>(name: string, fn: (log: StepLogger) => Promise<TOutput>, options?: StepOptions): Promise<TOutput>;
1290
+ step<TInput, TOutput>(handler: StepRef<TInput, TOutput>, input: TInput, opts?: StepDispatchOpts): Promise<TOutput>;
1291
+ step<TOutput = unknown>(name: string, input: unknown, opts?: StepDispatchOpts): Promise<TOutput>;
1056
1292
  /**
1057
1293
  * **Exactly-once** durable step for DB work: runs `fn` and writes the step's checkpoint in ONE
1058
1294
  * store transaction, so the business write and the "done" marker commit atomically — a crash can
@@ -1069,23 +1305,6 @@ interface WorkflowCtx {
1069
1305
  callEntity<TResult = unknown>(name: string, key: string, op: string, arg?: unknown): Promise<TResult>;
1070
1306
  /** Send a durable entity op without awaiting a result (fire-and-forget, dispatched once). */
1071
1307
  signalEntity(name: string, key: string, op: string, arg?: unknown): Promise<void>;
1072
- /**
1073
- * Dispatch a typed remote step and await its checkpointed result. Options:
1074
- * - `queue` — subject the dispatch to a registered flow-control queue (concurrency / rate limit).
1075
- * - `priority` — admission priority within that queue; higher is admitted first when a slot is
1076
- * contended (default 0). No effect without a `queue`.
1077
- * - `fairnessKey` — the fairness bucket for a queue with `fairness: 'key'` (e.g. a tenant id);
1078
- * the queue round-robins across distinct keys so one key can't monopolize the budget. Defaults
1079
- * to the run id when omitted. No effect without a `queue`.
1080
- * - `transport` — pin the dispatch to a named transport in the pool (else the pool's first, with
1081
- * failover to the rest). See `engine.registerQueue` / the engine's `transports` option.
1082
- */
1083
- call<TInput, TOutput>(step: RemoteStepDef<TInput, TOutput>, input: TInput, opts?: {
1084
- queue?: string;
1085
- priority?: number;
1086
- fairnessKey?: string;
1087
- transport?: string;
1088
- }): Promise<TOutput>;
1089
1308
  /**
1090
1309
  * Durable sleep: suspends the run for `duration` (e.g. `'30s'`, `'2h'`, `'7 days'`, or ms as a
1091
1310
  * number) without consuming resources, resuming automatically once the timer is due — even
@@ -1215,18 +1434,25 @@ interface WorkflowCtx {
1215
1434
  */
1216
1435
  patched(id: string): Promise<boolean>;
1217
1436
  /**
1218
- * Deterministic wall-clock (epoch ms): records the time on the first run and replays the SAME
1219
- * value afterwards. Use this instead of `Date.now()` inside a workflow — a raw `Date.now()` returns
1220
- * a different value on every replay, which silently corrupts a durable run.
1437
+ * Deterministic wall-clock, epoch **milliseconds** (like `Date.now()`): records the time on the
1438
+ * first run and replays the SAME value afterwards. Use this instead of `Date.now()`/`new Date()`
1439
+ * inside a workflow — a raw clock read returns a different value on every replay, which silently
1440
+ * corrupts a durable run. Returns a number so it composes in arithmetic (deadlines, elapsed);
1441
+ * for an ISO string do `new Date(await ctx.now()).toISOString()`. Every step is dispatched now
1442
+ * (see {@link step}), so a trivial timestamp capture gets this lightweight deterministic helper
1443
+ * instead of a full `@Step` + worker round-trip.
1221
1444
  */
1222
1445
  now(): Promise<number>;
1223
1446
  /**
1224
- * Deterministic random in `[0, 1)`: recorded once, then replayed. Use instead of `Math.random()`
1225
- * (same replay-safety reason as {@link now}).
1447
+ * **Deterministic capture.** Run `fn` once, checkpoint its result, and on replay return the SAME
1448
+ * value WITHOUT re-running `fn` — the durable way to bring a non-deterministic value into a workflow
1449
+ * where you control the generator: `ctx.sideEffect(() => uuidv7())`, `() => ulid()`,
1450
+ * `() => Math.random()`, a config/env read. Prefer a {@link step} for real work with side effects
1451
+ * (a DB write, an API call): `fn` here runs only once and MUST be effectively pure (it produces a
1452
+ * value; it is not re-executed on replay), like Temporal's `sideEffect`. For a plain timestamp use
1453
+ * {@link now}.
1226
1454
  */
1227
- random(): Promise<number>;
1228
- /** Deterministic UUID v4: recorded once, then replayed. Use instead of `crypto.randomUUID()`. */
1229
- uuid(): Promise<string>;
1455
+ sideEffect<TValue>(fn: () => TValue | Promise<TValue>): Promise<TValue>;
1230
1456
  /**
1231
1457
  * Merge `attrs` into THIS run's {@link WorkflowRun.searchAttributes} — the indexed metadata the
1232
1458
  * dashboard and {@link RunQuery} filter on. Shallow merge: keys you don't pass are kept. Durable +
@@ -1267,6 +1493,10 @@ interface EngineEvent {
1267
1493
  type: EngineEventType;
1268
1494
  runId: string;
1269
1495
  workflow?: string | undefined;
1496
+ /** The run's worker-pool partition (see {@link WorkflowRun.namespace}), stamped on the `run.*`
1497
+ * lifecycle events (where the emitting call site already has the run in hand) so a tenant-event
1498
+ * re-publisher can scope without a per-event store read. Absent on `step.*` events. */
1499
+ namespace?: string | undefined;
1270
1500
  seq?: number | undefined;
1271
1501
  name?: string | undefined;
1272
1502
  kind?: StepKind | undefined;
@@ -1302,6 +1532,17 @@ interface StepInvocation {
1302
1532
  */
1303
1533
  type StepInterceptor = (invocation: StepInvocation, next: () => Promise<unknown>) => Promise<unknown>;
1304
1534
 
1535
+ /**
1536
+ * {@link WorkflowCtx} plus the internal in-process local-step primitive — used ONLY by the library's
1537
+ * own built-in workflows (the durable-entity runner) that need a checkpointed, in-process step the
1538
+ * public `ctx.step` (always-dispatched, no in-process placement) no longer provides. Every
1539
+ * `createWorkflowCtx` result actually carries `localStep` at runtime; this type just names the wider
1540
+ * shape for the internal consumers that need it, without widening the public {@link WorkflowCtx}.
1541
+ */
1542
+ interface InternalWorkflowCtx extends WorkflowCtx {
1543
+ localStep<TOutput>(name: string, fn: (log: StepLogger) => Promise<TOutput>, options?: StepOptions): Promise<TOutput>;
1544
+ }
1545
+
1305
1546
  /** One operation on a durable entity: mutate `state` in place and/or return a result. */
1306
1547
  type EntityHandler<S = unknown> = (state: S, arg: unknown) => unknown | Promise<unknown>;
1307
1548
  /** A durable keyed entity (a virtual object): per-key serialized handlers over durable state. */
@@ -1313,7 +1554,7 @@ interface EntityConfig<S = unknown> {
1313
1554
  }
1314
1555
  /** The slice of the engine the entity subsystem needs (so it stays a standalone module). */
1315
1556
  interface EntityHost {
1316
- register(name: string, version: string, fn: (ctx: WorkflowCtx, input: unknown) => Promise<unknown>): void;
1557
+ register(name: string, version: string, fn: (ctx: InternalWorkflowCtx, input: unknown) => Promise<unknown>): void;
1317
1558
  signalWithStart(workflow: string, input: unknown, runId: string, signal: {
1318
1559
  token: string;
1319
1560
  payload?: unknown;
@@ -1383,7 +1624,7 @@ declare class EventAccumulators {
1383
1624
  private batch;
1384
1625
  }
1385
1626
 
1386
- type WorkflowFn = (ctx: WorkflowCtx, input: unknown) => Promise<unknown>;
1627
+ type WorkflowFn = (ctx: InternalWorkflowCtx, input: unknown) => Promise<unknown>;
1387
1628
  /** Options for {@link WorkflowEngine.start}. */
1388
1629
  interface StartOptions {
1389
1630
  /** Run-scoped tags, merged with the workflow's static `@Workflow({ tags })` onto the run. */
@@ -1395,6 +1636,12 @@ interface StartOptions {
1395
1636
  * the engine dispatches to advance it. Higher wins; absent = unprioritised. See {@link WorkflowRun.priority}.
1396
1637
  */
1397
1638
  priority?: number | undefined;
1639
+ /**
1640
+ * Override the namespace stamped on the run. Defaults to the engine's own `namespace`. Set by the
1641
+ * control plane when it starts a run on behalf of a tenant (a {@link StartRunMessage}'s `tenant`
1642
+ * maps here), so one DB-less-tenant-facing engine can create runs across many namespaces.
1643
+ */
1644
+ namespace?: string | undefined;
1398
1645
  }
1399
1646
  /**
1400
1647
  * Serialize runs of a workflow that share a key — a durable, FIFO mutex (e.g. one pipeline per base).
@@ -1421,7 +1668,7 @@ interface WorkflowEngineDeps {
1421
1668
  transport?: Transport | undefined;
1422
1669
  /**
1423
1670
  * An ordered pool of named transports. The engine dispatches on the first and fails over to the
1424
- * next on a dispatch error; a step pins one via `ctx.call(step, input, { transport: id })`. Use
1671
+ * next on a dispatch error; a step pins one via `ctx.step(step, input, { transport: id })`. Use
1425
1672
  * this instead of `transport` for failover / multi-broker setups.
1426
1673
  */
1427
1674
  transports?: NamedTransport[] | undefined;
@@ -1434,7 +1681,7 @@ interface WorkflowEngineDeps {
1434
1681
  /** Epoch-ms clock; injectable for tests. Defaults to `Date.now`. */
1435
1682
  clock?: (() => number) | undefined;
1436
1683
  /**
1437
- * Flow-control admission backend for `ctx.call(step, input, { queue })`. Defaults to an in-process
1684
+ * Flow-control admission backend for `ctx.step(step, input, { queue })`. Defaults to an in-process
1438
1685
  * {@link InMemoryAdmissionBackend} (per-instance caps). Inject a store/Redis-backed backend to make
1439
1686
  * concurrency / rate-limit / ordering GLOBAL across engine replicas.
1440
1687
  */
@@ -1444,8 +1691,11 @@ interface WorkflowEngineDeps {
1444
1691
  /**
1445
1692
  * Worker-pool partition for this engine. Stamped on every run it creates; the poll paths
1446
1693
  * (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`) only act on runs in this
1447
- * namespace. Default `'default'` — byte-identical to a single-pool deployment. Set distinct values
1448
- * to safely share ONE state store across non-interchangeable pools (e.g. local dev vs a cluster).
1694
+ * namespace. Set distinct values to safely share ONE state store across non-interchangeable pools
1695
+ * (e.g. local dev vs a cluster). **Omit it to make this engine an OPERATOR** — an unset namespace
1696
+ * drives/recovers/resumes runs of EVERY namespace (the poll filters no-op, the resume/decision
1697
+ * guards fall through) and leaves the transport on its bare prefix. This is the hosted control
1698
+ * plane: "ver tudo = ausência de namespace".
1449
1699
  */
1450
1700
  namespace?: string | undefined;
1451
1701
  /** Recovery lease duration in ms — how long this instance owns a run it picked up. Default 30s. */
@@ -1688,7 +1938,7 @@ declare class WorkflowEngine {
1688
1938
  validateInput?: (input: unknown) => void | Promise<void>;
1689
1939
  }): void;
1690
1940
  /**
1691
- * Register a flow-control queue referenced by `ctx.call(step, input, { queue })`. Caps concurrent
1941
+ * Register a flow-control queue referenced by `ctx.step(step, input, { queue })`. Caps concurrent
1692
1942
  * in-flight steps and/or the admission rate; blocked calls re-suspend and retry, so the limit is
1693
1943
  * durable. Per engine instance (see {@link QueueConfig}). Registering the same name replaces it.
1694
1944
  */
@@ -1760,6 +2010,17 @@ declare class WorkflowEngine {
1760
2010
  * its identity stays its own.
1761
2011
  */
1762
2012
  private synthesizeRemoteChild;
2013
+ /**
2014
+ * Attempt to route an unregistered workflow to the live worker group of the same name: if the
2015
+ * transport reports a live heartbeat for group `run.workflow`, synthesize a throwaway
2016
+ * {@link RegisteredWorkflow} that dispatches to that group over the primary transport. Returns
2017
+ * `undefined` when the group is not live. Always attempted — there is no opt-in flag; an explicit
2018
+ * `register`/`registerRemote` still takes precedence (this only runs when nothing is registered).
2019
+ *
2020
+ * Recomputed per call (never memoized into `this.workflows`) so it stays correct if group
2021
+ * liveness changes between calls, exactly as {@link synthesizeRemoteChild} does.
2022
+ */
2023
+ private resolveRemoteByConvention;
1763
2024
  /**
1764
2025
  * Walk the parent chain of `childRunId` to the nearest ancestor registered as REMOTE, returning its
1765
2026
  * registration (or `undefined` if there is none). The parent of an awaited child is found via its
@@ -2229,25 +2490,17 @@ type StepHandler = (input: unknown, log: StepLogger) => Promise<unknown> | unkno
2229
2490
  */
2230
2491
  declare function runStepHandler(task: RemoteTask, handler: StepHandler | undefined): Promise<StepResult>;
2231
2492
 
2232
- interface RemoteStepConfig<TInput, TOutput> extends StepOptions {
2233
- name: string;
2234
- /** Worker group expected to handle this step. Defaults to the `name` prefix before the first dot. */
2235
- group?: string;
2236
- input: z.ZodType<TInput>;
2237
- output: z.ZodType<TOutput>;
2238
- }
2239
- /**
2240
- * Defines a typed handle to a step that runs on a remote worker. Call it from a workflow with
2241
- * `ctx.call(step, input)`; a worker registers a handler under the same `name`.
2242
- */
2243
- declare function remoteStep<TInput, TOutput>(config: RemoteStepConfig<TInput, TOutput>): RemoteStepDef<TInput, TOutput>;
2244
-
2245
2493
  /**
2246
2494
  * A {@link WorkflowExecutor} backed by a {@link Transport}: it advances a remote workflow by
2247
2495
  * DISPATCHING a {@link WorkflowTask} over the broker under the ENGINE-SUPPLIED `taskId`, WITHOUT
2248
2496
  * awaiting the decision. Pass one to `engine.registerRemote(name, version, { group, executor })` so a
2249
2497
  * workflow authored in another SDK (e.g. the Python `durable-worker`) is driven over Redis/BullMQ.
2250
2498
  *
2499
+ * Routes by NAME, not a hand-declared group: the dispatched task's routing token is
2500
+ * `tenantGroup(sanitizeQueueToken(name), partition)` — the same formula a {@link import('./interfaces').StepDef}
2501
+ * uses — so `partition` is an optional isolation suffix, not a separate identity. Omit it to route by
2502
+ * the bare (sanitized) `name`.
2503
+ *
2251
2504
  * Multi-instance safe by construction: the executor holds NO in-memory state correlating a dispatched
2252
2505
  * turn to its decision. The engine records the `taskId` on the run as the awaited marker AND releases
2253
2506
  * the run's lease BEFORE calling `dispatch`, so the worker's {@link import('./interfaces').WorkflowDecision},
@@ -2264,8 +2517,9 @@ declare function remoteStep<TInput, TOutput>(config: RemoteStepConfig<TInput, TO
2264
2517
  */
2265
2518
  declare class RemoteWorkflowExecutor implements WorkflowExecutor {
2266
2519
  private readonly transport;
2267
- private readonly group;
2268
- constructor(transport: Transport, group: string, _opts?: {
2520
+ private readonly name;
2521
+ private readonly partition?;
2522
+ constructor(transport: Transport, name: string, partition?: string | undefined, _opts?: {
2269
2523
  timeoutMs?: number;
2270
2524
  });
2271
2525
  /**
@@ -2275,6 +2529,35 @@ declare class RemoteWorkflowExecutor implements WorkflowExecutor {
2275
2529
  dispatch(run: WorkflowRun, history: HistoryEvent[], taskId: string, pendingSignals?: WorkflowTask['pendingSignals']): Promise<void>;
2276
2530
  }
2277
2531
 
2532
+ /** A run + its timeline + child ids — the detail view. Mirrors the dashboard's `RunDetail`. */
2533
+ interface RunDetail {
2534
+ run: WorkflowRun;
2535
+ /** Steps in execution order (local + remote). */
2536
+ timeline: StepCheckpoint[];
2537
+ /** Ids of runs this run spawned (parent→children tree). */
2538
+ children: string[];
2539
+ }
2540
+ /**
2541
+ * The bounded read/control/stream surface a consumer (e.g. a controller) needs, satisfied by BOTH
2542
+ * topologies: the control plane binds a store-backed impl (reuses `DashboardService`); a tenant binds
2543
+ * a `ProxyRunGateway` that round-trips over the transport. Deliberately smaller than the full dashboard
2544
+ * (no metrics/bulk/workerHealth/update/signal) — those stay control-plane-only.
2545
+ */
2546
+ interface RunGateway {
2547
+ getRunDetail(runId: string): Promise<RunDetail | null>;
2548
+ listRuns(query: RunQuery): Promise<WorkflowRun[]>;
2549
+ cancel(runId: string, opts?: {
2550
+ compensate?: boolean;
2551
+ }): Promise<RunResult | null>;
2552
+ retry(runId: string): Promise<RunResult | null>;
2553
+ continue(runId: string): Promise<RunResult | null>;
2554
+ retryWithInput(runId: string, input: unknown): Promise<{
2555
+ runId: string;
2556
+ } | null>;
2557
+ /** Live lifecycle events for one run; returns an unsubscribe fn. Framework-agnostic (no rxjs). */
2558
+ subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void;
2559
+ }
2560
+
2278
2561
  /**
2279
2562
  * Transforms a payload value as it crosses the store boundary — e.g. encrypt-at-rest, compress, or
2280
2563
  * redact PII. `decode` must be the exact inverse of `encode`. Both are synchronous (use a local
@@ -2505,6 +2788,23 @@ declare function attributeColumnFor(filter: AttributeFilter): 'numValue' | 'strV
2505
2788
  /** The literal a side-table predicate compares against (booleans → `"true"`/`"false"` strings). */
2506
2789
  declare function attributeOperand(filter: AttributeFilter): string | number;
2507
2790
 
2791
+ /**
2792
+ * Compute the worker group a tenant's run dispatches to. Mirrors the transport's
2793
+ * default-is-bare rule (see {@link TransportPool.useNamespace}'s `'default'` no-op): a
2794
+ * `tenant` of `undefined`, `''`, or `'default'` is the bare `baseGroup`, so a single-tenant
2795
+ * deployment (or the `'default'` tenant of a multi-tenant one) stays byte-identical to today.
2796
+ * Any other tenant suffixes the group as `<baseGroup>@<tenant>`, so an operator control plane
2797
+ * (namespace: undefined) can route each tenant's run to its own worker pool.
2798
+ */
2799
+ declare function tenantGroup(baseGroup: string, tenant: string | undefined): string;
2800
+ /**
2801
+ * Sanitize a handler/workflow name for use as a BullMQ queue token: BullMQ forbids `:` in queue
2802
+ * names (it uses the colon internally as a key separator), so every `:` is replaced with `-`. `.`
2803
+ * is legal in a BullMQ queue name and is left as-is — step/workflow names commonly use it as a
2804
+ * namespacing separator (e.g. `payments.charge-card`).
2805
+ */
2806
+ declare function sanitizeQueueToken(name: string): string;
2807
+
2508
2808
  /**
2509
2809
  * Dependency-injection tokens. Adapters bind their implementations to these so `core` and
2510
2810
  * `@dudousxd/nestjs-durable` depend only on the interfaces in `./interfaces`.
@@ -2635,4 +2935,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
2635
2935
  onControl(handler: (msg: ControlMessage) => void): void;
2636
2936
  }
2637
2937
 
2638
- export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, type DurableWebhook, 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 MetricsCollector, type MetricsSnapshot, type NamedTransport, NonDeterminismError, type PayloadCodec, type QueueConfig, QueueController, type RemoteStepConfig, type RemoteStepDef, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RunAttributeRow, type RunDispatcher, type RunQuery, type RunResult, type RunSchedulesOptions, type RunStatus, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, type StartOptions, type StateStore, type StepCheckpoint, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepResult, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type Transport, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerHeartbeat, type WorkerStatus, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, breakpointToken, collectMetrics, createStepLogger, eventMatchOf, eventMatches, eventPrefix, eventToken, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, remoteStep, runSchedules, runStepHandler, scheduledRunId, sqlComparator, stepId, workflowName };
2938
+ export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AttributeFilter, type AttributeOp, type BackoffStrategy, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type DurableWebhook, 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 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 RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, 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 StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UpdateResult, type UpdateValidator, WORKFLOW_NAME_KEY, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerHeartbeat, type WorkerStatus, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRun, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, applyAttributeQuery, attributeColumnFor, attributeOperand, breakpointToken, collectMetrics, createStepLogger, eventMatchOf, eventMatches, eventPrefix, eventToken, matchesAttributes, normalizeAttributeRows, parseDuration, prevCronFireMs, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, tenantGroup, workflowName };