@dudousxd/nestjs-durable 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +992 -308
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +476 -217
- package/dist/index.d.ts +476 -217
- package/dist/index.js +954 -278
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
- package/skills/durable-determinism/SKILL.md +45 -31
- package/skills/durable-setup/SKILL.md +23 -15
- package/skills/durable-signals-and-timers/SKILL.md +25 -9
- package/skills/durable-testing/SKILL.md +85 -28
- package/skills/durable-workflows/SKILL.md +138 -83
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { StepError, WorkflowRef, SingletonConfig,
|
|
2
|
-
import {
|
|
1
|
+
import { StepError, WorkflowRef, SingletonConfig, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowClass, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, RunDetail, RunQuery, WorkflowRun, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
|
|
3
4
|
import { DynamicModule, InjectionToken, OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown, Provider } from '@nestjs/common';
|
|
4
5
|
import { DiscoveryService, MetadataScanner } from '@nestjs/core';
|
|
5
6
|
|
|
@@ -131,14 +132,76 @@ declare function Workflow(options: WorkflowOptions): ClassDecorator;
|
|
|
131
132
|
declare function getWorkflowMeta(target: Function): WorkflowMeta | undefined;
|
|
132
133
|
declare const DURABLE_STEP_METADATA: unique symbol;
|
|
133
134
|
interface DurableStepMeta {
|
|
135
|
+
/** The resolved routing name — derived (`Class.method`) or explicit, always present. */
|
|
134
136
|
name: string;
|
|
137
|
+
/** Opt-in runtime input schema, validated when the handler is served (see `scanSteps`). Absent on
|
|
138
|
+
* a bare `@Step()` — compile-time types from the method signature only, no runtime check. */
|
|
139
|
+
input?: z.ZodType | undefined;
|
|
140
|
+
/** Opt-in runtime output schema, validated before the handler's result is handed back. */
|
|
141
|
+
output?: z.ZodType | undefined;
|
|
135
142
|
}
|
|
136
143
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* argument is the step input; its return value is the step output.
|
|
144
|
+
* `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,
|
|
145
|
+
* timeoutMs? })` — the object call form. See {@link Step}.
|
|
140
146
|
*/
|
|
141
|
-
|
|
147
|
+
interface StepOptions {
|
|
148
|
+
/** Explicit routing name, overriding the derived `Class.method`. */
|
|
149
|
+
name?: string;
|
|
150
|
+
/** Runtime input schema, validated at the serve boundary (opt-in — a bare `@Step()` skips it). */
|
|
151
|
+
input?: z.ZodType;
|
|
152
|
+
/** Runtime output schema, validated before the result is returned (opt-in). */
|
|
153
|
+
output?: z.ZodType;
|
|
154
|
+
/**
|
|
155
|
+
* Def-level durable-dispatch policy stamped under `DURABLE_STEP_CONFIG` and read by `ctx.step` at
|
|
156
|
+
* the dispatch boundary (see `stepConfigOf`) — a per-call `ctx.step(ref, input, opts)` overrides
|
|
157
|
+
* these field-by-field. Max attempts before the step (and run) fails.
|
|
158
|
+
*/
|
|
159
|
+
retries?: number;
|
|
160
|
+
/** How the delay between retries grows: `fixed` (constant) or `exp` (doubles each attempt). */
|
|
161
|
+
backoff?: 'fixed' | 'exp';
|
|
162
|
+
/** Base delay in ms between retries. Omit (or 0) to retry with no delay. */
|
|
163
|
+
backoffMs?: number;
|
|
164
|
+
/** Upper bound on the (exponential) backoff delay. */
|
|
165
|
+
backoffMaxMs?: number;
|
|
166
|
+
/** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. */
|
|
167
|
+
jitter?: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Liveness window for a dispatched step: no result/heartbeat within this many ms presumes the
|
|
170
|
+
* worker dead and fails the dispatch with a `RemoteStepTimeout` (retryable — re-dispatches per
|
|
171
|
+
* `retries`). Omit to wait indefinitely.
|
|
172
|
+
*/
|
|
173
|
+
timeoutMs?: number;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Marks a provider method as a durable step handler. An in-process transport (e.g. the
|
|
177
|
+
* event-emitter transport) or a co-located/thin worker runtime routes a dispatched task to it BY
|
|
178
|
+
* NAME — `ctx.step(this.svc.method, input)` reads that same name off the method reference (stamped
|
|
179
|
+
* via the shared, cross-package `DURABLE_STEP_NAME` symbol from `@dudousxd/nestjs-durable-core`), so
|
|
180
|
+
* there's no separately-declared def linking a call site to this handler. The method's single
|
|
181
|
+
* argument is the step input (plus an optional `StepLogger` second arg); its return value is the
|
|
182
|
+
* step output.
|
|
183
|
+
*
|
|
184
|
+
* Three call forms:
|
|
185
|
+
* - `@Step()` — bare: the routing name is DERIVED from the method as `` `${ClassName}.${method}` ``
|
|
186
|
+
* (e.g. `ExtractionService.runExtractionPage`) — refactor-safe, no magic string.
|
|
187
|
+
* - `@Step('custom:name')` — explicit name override (stable across refactors, or a cross-runtime
|
|
188
|
+
* contract with a non-JS worker that has no `@Step` of its own).
|
|
189
|
+
* - `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,
|
|
190
|
+
* timeoutMs? })` — optional name override, opt-in RUNTIME zod schemas, and a def-level
|
|
191
|
+
* durable-retry/liveness-timeout policy. `input` validates before the method runs; `output`
|
|
192
|
+
* validates its return value before it's handed back (see `scanSteps`). `retries`/`backoff`/
|
|
193
|
+
* `backoffMs`/`backoffMaxMs`/`jitter`/`timeoutMs` are the policy `ctx.step` reads off this method
|
|
194
|
+
* reference (via `stepConfigOf`) to build the dispatched `StepDef`'s durable retry/backoff and
|
|
195
|
+
* remote-liveness timeout — a per-call `ctx.step(ref, input, opts)` overrides them field-by-field.
|
|
196
|
+
* A bare `@Step()` carries none of this and skips validation/retry/timeout — compile-time types
|
|
197
|
+
* from the method signature are the only check, and the step dispatches with no retry/timeout.
|
|
198
|
+
*/
|
|
199
|
+
declare function Step(nameOrOptions?: string | StepOptions): MethodDecorator;
|
|
200
|
+
/**
|
|
201
|
+
* @deprecated Use `@Step` instead. `@DurableStep` is a back-compat alias of {@link Step} and
|
|
202
|
+
* writes the same `DURABLE_STEP_METADATA`, so discovery/registrars treat them identically.
|
|
203
|
+
*/
|
|
204
|
+
declare const DurableStep: typeof Step;
|
|
142
205
|
declare function getDurableStepMeta(method: Function): DurableStepMeta | undefined;
|
|
143
206
|
declare const DEAD_LETTER_METADATA: unique symbol;
|
|
144
207
|
/**
|
|
@@ -178,184 +241,6 @@ declare function OnEvent(...events: string[]): ClassDecorator;
|
|
|
178
241
|
/** All events a workflow class subscribes to — the union of `@Workflow({ onEvent })` and `@OnEvent`. */
|
|
179
242
|
declare function getOnEvents(meta: WorkflowMeta, target: object): string[];
|
|
180
243
|
|
|
181
|
-
/**
|
|
182
|
-
* Options for a **store-less thin worker** process: where to consume (`connection`/`prefix`) and
|
|
183
|
-
* which worker `groups` this process serves. There is deliberately NO `store`, `transport`, engine,
|
|
184
|
-
* dashboard, recovery, or timer config — a thin worker only registers `@Workflow`/`@DurableStep`
|
|
185
|
-
* handlers and runs the BullMQ consumer (see {@link DurableWorkerModule}).
|
|
186
|
-
*/
|
|
187
|
-
interface DurableWorkerModuleOptions {
|
|
188
|
-
/** ioredis connection (string or options), as `runRedisWorker` / `BullMQTransport` accept. */
|
|
189
|
-
connection: string | Record<string, unknown>;
|
|
190
|
-
/** The worker groups this process serves — one BullMQ consumer is started per group. */
|
|
191
|
-
groups: string[];
|
|
192
|
-
/** Key prefix namespacing the durable queues. Defaults to `durable` (matches the transport). */
|
|
193
|
-
prefix?: string;
|
|
194
|
-
/** Stable id for this worker process in heartbeats/control. Defaults to a per-host/pid id. */
|
|
195
|
-
instanceId?: string;
|
|
196
|
-
/**
|
|
197
|
-
* How many tasks each group's consumer runs concurrently (BullMQ Worker concurrency). Defaults to 1.
|
|
198
|
-
* Applies to every group unless overridden per-group by {@link concurrencyByGroup}. Raise it so a
|
|
199
|
-
* fanned-out batch (e.g. the N remote steps of a `gather`) runs in parallel instead of serially.
|
|
200
|
-
*
|
|
201
|
-
* Pass `'adaptive'` (or `{ mode:'adaptive', ... }`) to let each consumer self-tune its concurrency
|
|
202
|
-
* (latency gradient + RAM brake + backpressure) and publish a live status on its heartbeat.
|
|
203
|
-
*/
|
|
204
|
-
concurrency?: ConcurrencyOption;
|
|
205
|
-
/** Per-group concurrency override, keyed by group name. Falls back to {@link concurrency} (then 1).
|
|
206
|
-
* Each value may itself be `'adaptive'` / `{ mode:'adaptive', ... }`. */
|
|
207
|
-
concurrencyByGroup?: Record<string, ConcurrencyOption>;
|
|
208
|
-
}
|
|
209
|
-
interface DurableWorkerModuleAsyncOptions {
|
|
210
|
-
useFactory: (...args: never[]) => DurableWorkerModuleOptions | Promise<DurableWorkerModuleOptions>;
|
|
211
|
-
inject?: InjectionToken[];
|
|
212
|
-
}
|
|
213
|
-
/** The resolved {@link DurableWorkerModuleOptions} (canonical token). */
|
|
214
|
-
declare const DURABLE_WORKER_OPTIONS: unique symbol;
|
|
215
|
-
/**
|
|
216
|
-
* The `runRedisWorker` function the module uses to start each group's BullMQ consumer. Defaults to
|
|
217
|
-
* the real one from `@dudousxd/durable-worker`; tests `overrideProvider` it with a fake so no real
|
|
218
|
-
* Redis is needed.
|
|
219
|
-
*/
|
|
220
|
-
declare const RUN_REDIS_WORKER: unique symbol;
|
|
221
|
-
/** The list of started {@link RunningWorker} handles (one per group), closed on shutdown. */
|
|
222
|
-
declare const DURABLE_WORKER_RUNNERS: unique symbol;
|
|
223
|
-
/** The signature of `runRedisWorker` — injected behind {@link RUN_REDIS_WORKER}. */
|
|
224
|
-
type RunRedisWorkerFn = (opts: RunRedisWorkerOptions) => Promise<RunningWorker>;
|
|
225
|
-
/**
|
|
226
|
-
* Discovers every provider carrying `@Workflow` metadata and registers its `run(ctx, input)` on the
|
|
227
|
-
* thin {@link DurableWorkerRuntime}. Mirrors the engine-side `WorkflowRegistrar`, but registers on
|
|
228
|
-
* the runner-core runtime instead of a `WorkflowEngine` — the runtime drives `run` with the thin
|
|
229
|
-
* `WorkflowContext` (which `implements WorkflowCtx`), so the body runs unchanged. NO store/engine.
|
|
230
|
-
*/
|
|
231
|
-
declare class ThinWorkflowRegistrar implements OnModuleInit {
|
|
232
|
-
private readonly discovery;
|
|
233
|
-
private readonly runtime;
|
|
234
|
-
constructor(discovery: DiscoveryService, runtime: DurableWorkerRuntime);
|
|
235
|
-
onModuleInit(): void;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Discovers every `@DurableStep` method and registers it as a step handler on the thin
|
|
239
|
-
* {@link DurableWorkerRuntime}. Mirrors the engine-side `DurableStepRegistrar`, but always registers
|
|
240
|
-
* on the runtime (a thin worker IS the consumer — there is no in-process-vs-queue branch).
|
|
241
|
-
*/
|
|
242
|
-
declare class ThinStepRegistrar implements OnModuleInit {
|
|
243
|
-
private readonly discovery;
|
|
244
|
-
private readonly metadataScanner;
|
|
245
|
-
private readonly runtime;
|
|
246
|
-
constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, runtime: DurableWorkerRuntime);
|
|
247
|
-
onModuleInit(): void;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Starts one BullMQ consumer per configured group on bootstrap (after both registrars have run, so
|
|
251
|
-
* every handler is registered before any task is consumed) and closes them on shutdown. The only
|
|
252
|
-
* runtime side effect of a thin worker: register handlers, run the consumer. No engine, no store,
|
|
253
|
-
* no recovery, no timers, no dispatch.
|
|
254
|
-
*
|
|
255
|
-
* For the shutdown close to fire, enable Nest's shutdown hooks: `app.enableShutdownHooks()`.
|
|
256
|
-
*/
|
|
257
|
-
declare class ThinWorkerBootstrap implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
258
|
-
private readonly runtime;
|
|
259
|
-
private readonly options;
|
|
260
|
-
private readonly runRedisWorker;
|
|
261
|
-
private readonly runnersSink;
|
|
262
|
-
private readonly runners;
|
|
263
|
-
constructor(runtime: DurableWorkerRuntime, options: DurableWorkerModuleOptions, runRedisWorker: RunRedisWorkerFn, runnersSink: RunningWorker[]);
|
|
264
|
-
onApplicationBootstrap(): Promise<void>;
|
|
265
|
-
onApplicationShutdown(): Promise<void>;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* A NestJS dynamic module that turns an app into a **PURE durable worker**: it discovers
|
|
269
|
-
* `@Workflow`/`@DurableStep` providers, registers them on the thin {@link DurableWorkerRuntime}, and
|
|
270
|
-
* runs one BullMQ consumer per group via `runRedisWorker`. It is **control-plane-less** — it binds
|
|
271
|
-
* NO `WorkflowEngine`, NO store/ORM, NO dashboard, NO timer poller, NO recovery, and NO run dispatch.
|
|
272
|
-
* Use it for a worker pod that only executes work an engine elsewhere coordinates.
|
|
273
|
-
*
|
|
274
|
-
* Contrast with `DurableModule` (`worker: true`), which is a full control plane WITH a store: this
|
|
275
|
-
* module is the store-less half. A workflow body runs identically on either (conformance-tested).
|
|
276
|
-
*/
|
|
277
|
-
declare class DurableWorkerModule {
|
|
278
|
-
static forRoot(options: DurableWorkerModuleOptions): DynamicModule;
|
|
279
|
-
static forRootAsync(options: DurableWorkerModuleAsyncOptions): DynamicModule;
|
|
280
|
-
private static build;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Opt-in config for an **in-app worker**: the same NestJS process runs the engine AND serves its own
|
|
285
|
-
* discovered `@Workflow`/`@DurableStep` on one default `group`. The engine registers each `@Workflow`
|
|
286
|
-
* GROUP-SERVED (its turns are dispatched to `group` over the transport via a {@link RemoteWorkflowExecutor}
|
|
287
|
-
* instead of run inline), and a co-located {@link DurableWorkerRuntime} consumes `group` and replays the
|
|
288
|
-
* very same TS bodies. This is the uniform-dispatch "one app, both roles" shape — every turn pays a
|
|
289
|
-
* transport round-trip even though the worker is the same process. Requires a transport that carries
|
|
290
|
-
* workflow tasks (BullMQ); an in-process-only transport cannot dispatch a {@link WorkflowExecutor}.
|
|
291
|
-
*/
|
|
292
|
-
interface DurableInAppWorkerOptions {
|
|
293
|
-
/** The default group this app dispatches its `@Workflow` turns to AND consumes as a worker. */
|
|
294
|
-
group: string;
|
|
295
|
-
/** ioredis connection (string or options) for the co-located worker consumer (`runRedisWorker`). */
|
|
296
|
-
connection: string | Record<string, unknown>;
|
|
297
|
-
/** Key prefix namespacing the durable queues. Defaults to `durable` (matches the transport). */
|
|
298
|
-
prefix?: string;
|
|
299
|
-
/** Stable id for this worker process in heartbeats/control. Defaults to a per-host/pid id. */
|
|
300
|
-
instanceId?: string;
|
|
301
|
-
/**
|
|
302
|
-
* How many tasks the co-located worker runs concurrently from its group's queue (BullMQ Worker
|
|
303
|
-
* concurrency). Defaults to 1. Raise it so a fanned-out batch (e.g. the N remote steps of a
|
|
304
|
-
* `gather`) runs in parallel. Per process; total parallelism is `concurrency × replicas`.
|
|
305
|
-
*
|
|
306
|
-
* Pass `'adaptive'` (or `{ mode:'adaptive', ... }`) to let the worker self-tune its concurrency
|
|
307
|
-
* (latency gradient + RAM brake + backpressure) and publish a live status on its heartbeat.
|
|
308
|
-
*/
|
|
309
|
-
concurrency?: ConcurrencyOption;
|
|
310
|
-
}
|
|
311
|
-
/** The resolved {@link DurableInAppWorkerOptions} or `null` when the app didn't opt in. */
|
|
312
|
-
declare const IN_APP_WORKER_OPTIONS: unique symbol;
|
|
313
|
-
/**
|
|
314
|
-
* The group-served binding the {@link import('./workflow.registrar').WorkflowRegistrar} applies to every
|
|
315
|
-
* discovered `@Workflow`: `{ group, executor }` when the app opted into an in-app worker, else `null`
|
|
316
|
-
* (workflows register inline — the unchanged default). The executor is a {@link RemoteWorkflowExecutor}
|
|
317
|
-
* over the engine's own transport, so a group-served turn is dispatched to `group` and the co-located
|
|
318
|
-
* worker (below) advances it.
|
|
319
|
-
*/
|
|
320
|
-
declare const IN_APP_WORKER_BINDING: unique symbol;
|
|
321
|
-
/** The co-located worker's {@link DurableWorkerRuntime} (the consumer half). */
|
|
322
|
-
declare const IN_APP_WORKER_RUNTIME: unique symbol;
|
|
323
|
-
/** `runRedisWorker`, injected so tests can substitute a fake (no real Redis). Defaults to the real one. */
|
|
324
|
-
declare const IN_APP_RUN_REDIS_WORKER: unique symbol;
|
|
325
|
-
/** Started {@link RunningWorker} handles for the in-app group(s), closed on shutdown. */
|
|
326
|
-
declare const IN_APP_WORKER_RUNNERS: unique symbol;
|
|
327
|
-
/** The group-served binding shape resolved behind {@link IN_APP_WORKER_BINDING}. */
|
|
328
|
-
interface InAppWorkerBinding {
|
|
329
|
-
group: string;
|
|
330
|
-
executor: WorkflowExecutor;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* The consumer half of the in-app worker: on init it registers every discovered `@Workflow`/`@DurableStep`
|
|
334
|
-
* on a {@link DurableWorkerRuntime} (the SAME bodies the engine registered group-served), and on bootstrap
|
|
335
|
-
* it starts one `runRedisWorker` consumer on the configured group, closing it on shutdown. A no-op when
|
|
336
|
-
* the app didn't opt in. Mirrors the thin {@link import('./durable-worker.module').DurableWorkerModule},
|
|
337
|
-
* but co-located with a full engine.
|
|
338
|
-
*/
|
|
339
|
-
declare class InAppWorkerBootstrap implements OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown {
|
|
340
|
-
private readonly discovery;
|
|
341
|
-
private readonly metadataScanner;
|
|
342
|
-
private readonly options;
|
|
343
|
-
private readonly runtime;
|
|
344
|
-
private readonly runRedisWorker;
|
|
345
|
-
private readonly runnersSink;
|
|
346
|
-
private readonly runners;
|
|
347
|
-
constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, options: DurableInAppWorkerOptions | null, runtime: DurableWorkerRuntime, runRedisWorker: RunRedisWorkerFn, runnersSink: RunningWorker[]);
|
|
348
|
-
onModuleInit(): void;
|
|
349
|
-
onApplicationBootstrap(): Promise<void>;
|
|
350
|
-
onApplicationShutdown(): Promise<void>;
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* The providers that stand up an in-app worker, added to {@link import('./durable.module').DurableModule}.
|
|
354
|
-
* All are inert when `inAppWorker` is unset (the binding resolves to `null`, the bootstrap no-ops), so a
|
|
355
|
-
* plain `DurableModule` is byte-for-byte unchanged.
|
|
356
|
-
*/
|
|
357
|
-
declare function inAppWorkerProviders(): Provider[];
|
|
358
|
-
|
|
359
244
|
/**
|
|
360
245
|
* Retention config for the {@link RetentionPoller}. One or more {@link RetentionPolicy policies}
|
|
361
246
|
* (status sets must be disjoint — validated at boot), swept together on a shared interval.
|
|
@@ -382,12 +267,32 @@ interface DurableRetentionOptions {
|
|
|
382
267
|
/** Max runs hard-deleted per batch (per policy, looped until drained). Defaults to 1000. */
|
|
383
268
|
batchSize?: number;
|
|
384
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Options for `DurableModule.forRoot`/`forRootAsync`. The **role is inferred** from which of `store`/
|
|
272
|
+
* `connection` are set:
|
|
273
|
+
*
|
|
274
|
+
* - `{ store, transport }` — **operator**: a real `WorkflowEngine` + `StoreRunGateway` + drivers/
|
|
275
|
+
* timer/retention/registrars, executing registered bodies INLINE. Driven by {@link drive} (default
|
|
276
|
+
* `true`).
|
|
277
|
+
* - `{ connection }` (no `store`) — **thin worker**: `WorkflowEngine` resolves to a store-less
|
|
278
|
+
* `DurableStartClient`; `RUN_GATEWAY` is a `ProxyRunGateway` when `transport` is also given, else
|
|
279
|
+
* every method rejects with a clear error. No store/timer/retention/entity — just discovered
|
|
280
|
+
* `@Workflow`/`@Step` handlers served over ONE `runRedisWorker` consumer.
|
|
281
|
+
* - `{ store, transport, connection }` — an **operator that also runs a co-located worker**: every
|
|
282
|
+
* `@Workflow` is registered GROUP-SERVED (dispatched over the transport, PER WORKFLOW NAME, instead
|
|
283
|
+
* of inline) and a co-located consumer (one `runRedisWorker` call) replays the same bodies.
|
|
284
|
+
*
|
|
285
|
+
* `forRoot` throws when neither `store` nor `connection` is set, and when `store` is set without a
|
|
286
|
+
* `transport`.
|
|
287
|
+
*/
|
|
385
288
|
interface DurableModuleOptions {
|
|
386
|
-
store
|
|
289
|
+
/** State store — set this to play the **operator** role (see the interface doc for the full role
|
|
290
|
+
* matrix). Omit for a store-less **thin worker** (`connection` only). */
|
|
291
|
+
store?: StateStore;
|
|
387
292
|
transport?: Transport;
|
|
388
293
|
/**
|
|
389
294
|
* An ordered pool of named transports for failover / multi-broker setups. The engine dispatches on
|
|
390
|
-
* the first and fails over to the next; a step pins one via `ctx.
|
|
295
|
+
* the first and fails over to the next; a step pins one via `ctx.step(handler, input, { transport })`.
|
|
391
296
|
* Use instead of `transport`.
|
|
392
297
|
*/
|
|
393
298
|
transports?: NamedTransport[];
|
|
@@ -396,29 +301,34 @@ interface DurableModuleOptions {
|
|
|
396
301
|
* transport when it can broadcast (event-emitter, BullMQ); set explicitly to use a dedicated one.
|
|
397
302
|
*/
|
|
398
303
|
controlPlane?: ControlPlane;
|
|
399
|
-
/** Interval (ms) for the durable-timer poller. `0` disables it. Defaults to 1000. */
|
|
304
|
+
/** Interval (ms) for the durable-timer poller. `0` disables it. Defaults to 1000. Operator only. */
|
|
400
305
|
timerPollMs?: number;
|
|
401
306
|
/**
|
|
402
307
|
* Auto-create the durable tables on boot via `store.ensureSchema()`. Defaults to true. Turn
|
|
403
308
|
* off in production and call the store adapter's `ensure*DurableSchema()` from a migration.
|
|
309
|
+
* Operator only.
|
|
404
310
|
*/
|
|
405
311
|
autoSchema?: boolean;
|
|
406
312
|
/**
|
|
407
|
-
* Worker-pool
|
|
408
|
-
*
|
|
409
|
-
*
|
|
313
|
+
* Worker-pool namespace for this instance (forwarded to the engine). The poll paths
|
|
314
|
+
* (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`) only act on runs in this
|
|
315
|
+
* namespace. Set distinct values to safely share ONE state store across non-interchangeable
|
|
316
|
+
* pools — e.g. a developer's local instance vs the deployed cluster. **Omit it to make this
|
|
317
|
+
* instance an OPERATOR** — an unset namespace drives/recovers/resumes runs of EVERY namespace and
|
|
318
|
+
* leaves the transport on its bare prefix. See `WorkflowEngineDeps.namespace`. Not to be confused
|
|
319
|
+
* with {@link partition} (the co-located/thin-worker QUEUE routing suffix).
|
|
410
320
|
*/
|
|
411
321
|
namespace?: string;
|
|
412
322
|
/**
|
|
413
323
|
* Multi-instance recovery lease, in ms — how long an instance owns a run it picked up before
|
|
414
|
-
* another may take over. Defaults to 30000. Set above your longest synchronous run.
|
|
324
|
+
* another may take over. Defaults to 30000. Set above your longest synchronous run. Operator only.
|
|
415
325
|
*/
|
|
416
326
|
leaseMs?: number;
|
|
417
|
-
/** Unique id for this instance (for leases
|
|
327
|
+
/** Unique id for this instance (for leases, and the co-located/thin-worker consumer's heartbeats). */
|
|
418
328
|
instanceId?: string;
|
|
419
329
|
/**
|
|
420
330
|
* Cap recovery attempts before a still-`running` run is moved to the `dead` dead-letter state
|
|
421
|
-
* (a poison pill that crashes the process every boot). Omit for unlimited.
|
|
331
|
+
* (a poison pill that crashes the process every boot). Omit for unlimited. Operator only.
|
|
422
332
|
*/
|
|
423
333
|
maxRecoveryAttempts?: number;
|
|
424
334
|
/**
|
|
@@ -427,6 +337,7 @@ interface DurableModuleOptions {
|
|
|
427
337
|
* and lets recovery re-drive; each heartbeat rearms the window so a slow-but-alive worker is never
|
|
428
338
|
* re-driven. Pair with a worker SDK that emits run-scoped heartbeats (`@dudousxd/durable-worker` ≥ the
|
|
429
339
|
* release that ships them, and the Python `durable-worker`). Omit for the prior unbounded await.
|
|
340
|
+
* Operator only.
|
|
430
341
|
*/
|
|
431
342
|
remoteAdvanceSilenceMs?: number;
|
|
432
343
|
/**
|
|
@@ -436,54 +347,59 @@ interface DurableModuleOptions {
|
|
|
436
347
|
* it can alert, compensate, or queue for review. Resolution per dead run: the workflow's inline
|
|
437
348
|
* `@DeadLetter()` method → its `@Workflow({ deadLetterWorkflow })` reference → this default. Omit
|
|
438
349
|
* everything to just leave dead runs parked (inspectable + retriable from the dashboard). Accepts a
|
|
439
|
-
* workflow class (refactor-safe) or a name (cross-runtime).
|
|
350
|
+
* workflow class (refactor-safe) or a name (cross-runtime). Operator only.
|
|
440
351
|
*/
|
|
441
352
|
deadLetterWorkflow?: WorkflowRef;
|
|
442
353
|
/**
|
|
443
|
-
* Whether
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
354
|
+
* Whether an **operator** instance actively DRIVES runs — polls pending, recovers crashed
|
|
355
|
+
* (`recoverIncomplete`), resumes due timers, sweeps timeouts, prunes retention, and consumes local
|
|
356
|
+
* steps — as opposed to a read-only/dashboard replica. Defaults to `true`. Set `false` for a
|
|
357
|
+
* **dashboard/dispatch-only** instance (e.g. an API pod) that mounts the store/dashboard but must
|
|
358
|
+
* not process or recover workflows — leave that to another driving instance. `drive: false` also
|
|
359
|
+
* installs the engine's no-op run dispatcher, so a freshly `start()`ed run stays enqueue-only until
|
|
360
|
+
* a driving instance's poll picks it up. Ignored (irrelevant) for a thin worker (no `store`).
|
|
447
361
|
*/
|
|
448
|
-
|
|
449
|
-
/** Max ms to wait for in-flight runs on shutdown before exiting. Defaults to 10000. */
|
|
362
|
+
drive?: boolean;
|
|
363
|
+
/** Max ms to wait for in-flight runs on shutdown before exiting. Defaults to 10000. Operator only. */
|
|
450
364
|
shutdownTimeoutMs?: number;
|
|
451
365
|
/**
|
|
452
366
|
* Recurring workflows to start on a schedule (fixed interval or cron). The timer poller fires
|
|
453
|
-
* them each tick on **
|
|
367
|
+
* them each tick on **driving** instances only; `engine.start` is idempotent by the schedule's
|
|
454
368
|
* time-bucket run id, so racing instances start each window exactly once. Cron schedules need the
|
|
455
|
-
* optional `cron-parser` peer dependency.
|
|
369
|
+
* optional `cron-parser` peer dependency. Operator only.
|
|
456
370
|
*/
|
|
457
371
|
schedules?: ScheduledWorkflow[];
|
|
458
372
|
/**
|
|
459
373
|
* Hard-prune terminal run history on an interval so `durable_workflow_runs` (and its child tables)
|
|
460
374
|
* stays bounded — without it, completed/failed/cancelled runs accumulate forever and the timer
|
|
461
|
-
* poller's per-tick status scans get linearly slower.
|
|
375
|
+
* poller's per-tick status scans get linearly slower. Driving instances only. Omit to keep all
|
|
462
376
|
* history (the default). Requires a store adapter that implements `pruneTerminalRuns` (the
|
|
463
377
|
* MikroORM adapter does); other adapters no-op with a warning. See {@link DurableRetentionOptions}.
|
|
378
|
+
* Operator only.
|
|
464
379
|
*/
|
|
465
380
|
retention?: DurableRetentionOptions;
|
|
466
381
|
/**
|
|
467
382
|
* Build the public callback URL for a `ctx.webhook()` token, e.g.
|
|
468
383
|
* ``(t) => `https://api.example.com/durable/api/webhooks/${t}` ``. Populates `DurableWebhook.url`
|
|
469
384
|
* so a step can hand the URL to a third party. The dashboard's `POST webhooks/:token` receives the
|
|
470
|
-
* callback. Omit to build URLs yourself from the token.
|
|
385
|
+
* callback. Omit to build URLs yourself from the token. Operator only.
|
|
471
386
|
*/
|
|
472
387
|
webhookUrl?: (token: string) => string;
|
|
473
388
|
/**
|
|
474
389
|
* Flow-control queues for remote steps, registered on the engine at startup. Reference one from a
|
|
475
|
-
* workflow with `ctx.
|
|
390
|
+
* workflow with `ctx.step(handler, input, { queue: name })` to cap its concurrency / admission rate.
|
|
391
|
+
* Operator only.
|
|
476
392
|
*/
|
|
477
393
|
queues?: QueueConfig[];
|
|
478
394
|
/**
|
|
479
395
|
* Admission backend for the flow-control `queues`. Defaults to in-process (per-instance) caps. Pass
|
|
480
396
|
* a `RedisAdmissionBackend` (from `@dudousxd/nestjs-durable-admission-redis`) to make concurrency /
|
|
481
|
-
* rate-limit / priority ordering GLOBAL across every engine replica.
|
|
397
|
+
* rate-limit / priority ordering GLOBAL across every engine replica. Operator only.
|
|
482
398
|
*/
|
|
483
399
|
admission?: AdmissionBackend;
|
|
484
400
|
/**
|
|
485
401
|
* Provide the current W3C `traceparent` to stamp on dispatched remote tasks, so workers continue
|
|
486
|
-
* the distributed trace. Pass `otelTraceparent` from `@dudousxd/nestjs-durable-otel`.
|
|
402
|
+
* the distributed trace. Pass `otelTraceparent` from `@dudousxd/nestjs-durable-otel`. Operator only.
|
|
487
403
|
*/
|
|
488
404
|
traceparent?: () => string | undefined;
|
|
489
405
|
/**
|
|
@@ -500,20 +416,63 @@ interface DurableModuleOptions {
|
|
|
500
416
|
* Re-evaluated at each (re)dispatch — including a retry or a crash/scale-down resume that the engine
|
|
501
417
|
* drives OUTSIDE the originating request scope, where this reader may return empty or stale values.
|
|
502
418
|
* Treat the carrier as best-effort correlation/propagation metadata only — do NOT treat it as an
|
|
503
|
-
* authorization boundary.
|
|
419
|
+
* authorization boundary. Operator only.
|
|
504
420
|
*/
|
|
505
421
|
context?: () => Record<string, unknown> | undefined;
|
|
506
|
-
/** Attempts for each saga compensation when a run fails. Default 1 (no retry). Idempotent undos.
|
|
422
|
+
/** Attempts for each saga compensation when a run fails. Default 1 (no retry). Idempotent undos.
|
|
423
|
+
* Operator only. */
|
|
507
424
|
compensationRetries?: number;
|
|
508
425
|
/**
|
|
509
|
-
* Opt into
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
|
|
426
|
+
* Opt into tenant read scoping: when `true` AND {@link namespace} is set, the module confines the
|
|
427
|
+
* store's reads to that namespace (a tenant-boundary view) instead of the operator view that sees
|
|
428
|
+
* all namespaces. Default `false` — the control plane (e.g. flip's `/ctrl` operator screens) stays
|
|
429
|
+
* unscoped. Requires a store that exposes the `withScope` capability (the MikroORM adapter does);
|
|
430
|
+
* a pre-built store without it is used as-is (construct it already-scoped instead). Operator only.
|
|
431
|
+
*/
|
|
432
|
+
scopeReads?: boolean;
|
|
433
|
+
/**
|
|
434
|
+
* ioredis connection (string or options) for a **thin worker** (`connection` only) or the
|
|
435
|
+
* **co-located worker** consumer (`store` + `connection`). Set this to play a worker role (see the
|
|
436
|
+
* interface doc for the full role matrix). Omit to keep every `@Workflow` on the operator's inline
|
|
437
|
+
* fast path with zero dispatch round-trips.
|
|
438
|
+
*/
|
|
439
|
+
connection?: string | Record<string, unknown>;
|
|
440
|
+
/**
|
|
441
|
+
* The isolation partition a worker role serves — a thin worker's or co-located worker's queue
|
|
442
|
+
* subscription, AND (for a co-located worker) the suffix each `@Workflow`'s dispatch token carries.
|
|
443
|
+
* Each handler's queue token is `tenantGroup(sanitizeQueueToken(name), partition)`
|
|
444
|
+
* (`@dudousxd/nestjs-durable-core`), so `undefined`, `''`, or `'default'` stays byte-identical to
|
|
445
|
+
* the bare (sanitized) name (single-tenant deployment unchanged), and any other partition serves
|
|
446
|
+
* `<name>@<partition>` — matching the queue name an operator's convention dispatch routes that
|
|
447
|
+
* tenant's runs to (`tenantGroup(run.workflow, run.namespace)` on the engine side). Not to be
|
|
448
|
+
* confused with {@link namespace} (the operator's own poll-scoping axis). Ignored for a plain
|
|
449
|
+
* operator (no `connection`).
|
|
450
|
+
*/
|
|
451
|
+
partition?: string;
|
|
452
|
+
/** Key prefix namespacing the durable queues for a worker role's consumer. Defaults to `durable`
|
|
453
|
+
* (matches the transport). Ignored for a plain operator (no `connection`). */
|
|
454
|
+
prefix?: string;
|
|
455
|
+
/**
|
|
456
|
+
* How many tasks a worker role's co-located/thin consumer runs concurrently PER SUBSCRIBED QUEUE
|
|
457
|
+
* (BullMQ Worker concurrency — the same limit is applied to every per-name queue the single
|
|
458
|
+
* `runRedisWorker` call starts). Defaults to 1. Raise it so a fanned-out batch (e.g. the N remote
|
|
459
|
+
* steps of a `gather`) runs in parallel instead of serially.
|
|
460
|
+
*
|
|
461
|
+
* Pass `'adaptive'` (or `{ mode:'adaptive', ... }`) to let the consumer self-tune its concurrency
|
|
462
|
+
* (latency gradient + RAM brake + backpressure) and publish a live status on its heartbeat. Ignored
|
|
463
|
+
* for a plain operator (no `connection`).
|
|
464
|
+
*/
|
|
465
|
+
concurrency?: ConcurrencyOption;
|
|
466
|
+
/**
|
|
467
|
+
* Per-handler concurrency override, keyed by workflow/step NAME. Falls back to {@link concurrency}
|
|
468
|
+
* (then 1). NOT YET WIRED THROUGH: `runRedisWorker` (`@dudousxd/durable-worker`) currently applies
|
|
469
|
+
* one {@link concurrency} limit uniformly across every per-name queue it subscribes to from a
|
|
470
|
+
* single call — reserved here for when per-name concurrency lands there.
|
|
515
471
|
*/
|
|
516
|
-
|
|
472
|
+
concurrencyByHandler?: Record<string, ConcurrencyOption>;
|
|
473
|
+
/** Timeout for a thin worker's `RunGateway` round-trip over `transport` before it rejects. Defaults
|
|
474
|
+
* to 10_000ms inside `ProxyRunGateway`. Ignored for an operator (bound to `StoreRunGateway`). */
|
|
475
|
+
runGatewayTimeoutMs?: number;
|
|
517
476
|
}
|
|
518
477
|
interface DurableModuleAsyncOptions {
|
|
519
478
|
useFactory: (...args: never[]) => DurableModuleOptions | Promise<DurableModuleOptions>;
|
|
@@ -525,6 +484,118 @@ declare class DurableModule {
|
|
|
525
484
|
private static build;
|
|
526
485
|
}
|
|
527
486
|
|
|
487
|
+
/**
|
|
488
|
+
* The **store-less `engine.start` facade** for a tenant worker. Provided under the `WorkflowEngine`
|
|
489
|
+
* DI token by {@link import('./durable.module').DurableModule} (`forRoot({ connection })`, no
|
|
490
|
+
* `store`), so tenant code calls `engine.start(...)` UNCHANGED — it has no idea it is a tenant.
|
|
491
|
+
* Instead of touching a DB, `start` publishes a `StartRunMessage` on the SHARED `durable-start-run`
|
|
492
|
+
* queue (Option B: tenant rides as message DATA, never as wire segmentation). The operator (control
|
|
493
|
+
* plane, `namespace: undefined`) consumes it, stamps the run's namespace from `tenant`, and routes
|
|
494
|
+
* the run's task to `<workflow>@<tenant>` — the partition THIS tenant's worker serves.
|
|
495
|
+
*
|
|
496
|
+
* `cancel`/`deleteRun` need the store/driver a tenant does not have; they throw. No wire message
|
|
497
|
+
* exists for them (the operator owns cancellation/retention).
|
|
498
|
+
*/
|
|
499
|
+
declare class DurableStartClient {
|
|
500
|
+
private readonly options;
|
|
501
|
+
private readonly deps?;
|
|
502
|
+
private readonly tenant;
|
|
503
|
+
constructor(options: DurableModuleOptions, deps?: StartRunDeps | undefined);
|
|
504
|
+
start<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, runId?: string, opts?: StartOptions): Promise<RunResult>;
|
|
505
|
+
start(workflow: string, input: unknown, runId?: string, opts?: StartOptions): Promise<RunResult>;
|
|
506
|
+
cancel(_runId: string): Promise<void>;
|
|
507
|
+
deleteRun(_runId: string): Promise<void>;
|
|
508
|
+
resume(_runId: string): Promise<void>;
|
|
509
|
+
waitForRun(_runId: string, _opts?: {
|
|
510
|
+
timeoutMs?: number;
|
|
511
|
+
}): Promise<void>;
|
|
512
|
+
signal(_token: string, _payload: unknown): Promise<void>;
|
|
513
|
+
signalWithStart(_workflow: string, _input: unknown, _runId: string, _signal: {
|
|
514
|
+
token: string;
|
|
515
|
+
payload?: unknown;
|
|
516
|
+
}, _opts?: StartOptions): Promise<void>;
|
|
517
|
+
publishEvent(_name: string, _payload: unknown, _opts?: {
|
|
518
|
+
id?: string;
|
|
519
|
+
}): Promise<void>;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* The `runRedisWorker` function the module uses to start each partition's BullMQ consumer.
|
|
524
|
+
* Defaults to the real one from `@dudousxd/durable-worker`; tests `overrideProvider` it with a
|
|
525
|
+
* fake so no real Redis is needed.
|
|
526
|
+
*/
|
|
527
|
+
declare const RUN_REDIS_WORKER: unique symbol;
|
|
528
|
+
/** The list of started {@link RunningWorker} handles (the single handle {@link ThinWorkerBootstrap}
|
|
529
|
+
* starts), closed on shutdown. */
|
|
530
|
+
declare const DURABLE_WORKER_RUNNERS: unique symbol;
|
|
531
|
+
/** The signature of `runRedisWorker` — injected behind {@link RUN_REDIS_WORKER}. */
|
|
532
|
+
type RunRedisWorkerFn = (opts: RunRedisWorkerOptions) => Promise<RunningWorker>;
|
|
533
|
+
/**
|
|
534
|
+
* Discovers every provider carrying `@Workflow` metadata and registers its `run(ctx, input)` on the
|
|
535
|
+
* thin {@link DurableWorkerRuntime}. Mirrors the engine-side `WorkflowRegistrar`, but registers on
|
|
536
|
+
* the runner-core runtime instead of a `WorkflowEngine` — the runtime drives `run` with the thin
|
|
537
|
+
* `WorkflowContext` (which `implements WorkflowCtx`), so the body runs unchanged. NO store/engine.
|
|
538
|
+
* Inert (skips registration) outside the pure thin-worker role — see {@link isPureThinWorker}.
|
|
539
|
+
*/
|
|
540
|
+
declare class ThinWorkflowRegistrar implements OnModuleInit {
|
|
541
|
+
private readonly discovery;
|
|
542
|
+
private readonly runtime;
|
|
543
|
+
private readonly options;
|
|
544
|
+
constructor(discovery: DiscoveryService, runtime: DurableWorkerRuntime, options: DurableModuleOptions);
|
|
545
|
+
onModuleInit(): void;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Discovers every `@Step` method and registers it as a step handler on the thin
|
|
549
|
+
* {@link DurableWorkerRuntime}. Mirrors the engine-side `DurableStepRegistrar`, but always registers
|
|
550
|
+
* on the runtime (a thin worker IS the consumer — there is no in-process-vs-queue branch). Inert
|
|
551
|
+
* outside the pure thin-worker role — see {@link isPureThinWorker}.
|
|
552
|
+
*/
|
|
553
|
+
declare class ThinStepRegistrar implements OnModuleInit {
|
|
554
|
+
private readonly discovery;
|
|
555
|
+
private readonly metadataScanner;
|
|
556
|
+
private readonly runtime;
|
|
557
|
+
private readonly options;
|
|
558
|
+
constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, runtime: DurableWorkerRuntime, options: DurableModuleOptions);
|
|
559
|
+
onModuleInit(): void;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Starts ONE `runRedisWorker` call on bootstrap — after both registrars have run, so every handler
|
|
563
|
+
* is registered before any task is consumed — and closes it on shutdown. The runner (`@dudousxd/
|
|
564
|
+
* durable-worker`) derives its subscription from `runtime.registeredNames()` (one queue per
|
|
565
|
+
* registered `@Workflow`/`@Step` name), not a hand-declared group list. Inert outside the pure
|
|
566
|
+
* thin-worker role — see {@link isPureThinWorker}: an operator (`store` set) never starts this
|
|
567
|
+
* consumer, and `store + connection` starts its OWN co-located consumer instead (`in-app-worker.ts`).
|
|
568
|
+
*
|
|
569
|
+
* For the shutdown close to fire, enable Nest's shutdown hooks: `app.enableShutdownHooks()`.
|
|
570
|
+
*/
|
|
571
|
+
declare class ThinWorkerBootstrap implements OnApplicationBootstrap, OnApplicationShutdown {
|
|
572
|
+
private readonly runtime;
|
|
573
|
+
private readonly options;
|
|
574
|
+
private readonly runRedisWorker;
|
|
575
|
+
private readonly runnersSink;
|
|
576
|
+
private readonly runners;
|
|
577
|
+
constructor(runtime: DurableWorkerRuntime, options: DurableModuleOptions, runRedisWorker: RunRedisWorkerFn, runnersSink: RunningWorker[]);
|
|
578
|
+
onApplicationBootstrap(): Promise<void>;
|
|
579
|
+
onApplicationShutdown(): Promise<void>;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* The thin-worker slice of {@link import('./durable.module').DurableModule}'s unified provider set —
|
|
583
|
+
* active for the pure thin-worker role (`connection` set, `store` unset): registers discovered
|
|
584
|
+
* `@Workflow`/`@Step` on a store-less {@link DurableWorkerRuntime} and starts ONE `runRedisWorker`
|
|
585
|
+
* consumer, one queue PER REGISTERED NAME. Always present in the provider list (so a single
|
|
586
|
+
* `DurableModule.forRootAsync` can serve any role once its options resolve); every class/factory here
|
|
587
|
+
* is inert (see {@link isPureThinWorker}) outside that role.
|
|
588
|
+
*/
|
|
589
|
+
declare function thinWorkerProviders(): Provider[];
|
|
590
|
+
/**
|
|
591
|
+
* The no-transport fallback bound to `RUN_GATEWAY` for a thin worker (`connection` set, `store`
|
|
592
|
+
* unset) that didn't also pass a `transport` — mirrors `DurableStartClient`'s tenant-error idiom
|
|
593
|
+
* (`tenantUnsupported`): every method rejects with a clear, named error instead of a cryptic
|
|
594
|
+
* `this.gateway.X is not a function`. `subscribe` is synchronous on the `RunGateway` port, so it
|
|
595
|
+
* throws synchronously (same message) rather than returning a rejected promise.
|
|
596
|
+
*/
|
|
597
|
+
declare function unavailableRunGateway(): RunGateway;
|
|
598
|
+
|
|
528
599
|
declare const ENTITY_METADATA: unique symbol;
|
|
529
600
|
declare const ENTITY_ON_METADATA: unique symbol;
|
|
530
601
|
/**
|
|
@@ -566,6 +637,135 @@ declare class EntityService {
|
|
|
566
637
|
getState<S = unknown>(name: string, key: string): Promise<S | undefined>;
|
|
567
638
|
}
|
|
568
639
|
|
|
640
|
+
/**
|
|
641
|
+
* The **co-located in-app worker** (uniform dispatch): active whenever an app supplies BOTH `store`
|
|
642
|
+
* AND `connection` (`DurableModule.forRoot({ store, transport, connection, partition? })`) — the same
|
|
643
|
+
* process runs the engine AND serves its own discovered `@Workflow`/`@Step`. The engine registers each
|
|
644
|
+
* `@Workflow` GROUP-SERVED — its turns are dispatched, PER WORKFLOW NAME, to
|
|
645
|
+
* `tenantGroup(sanitizeQueueToken(name), partition)` over the transport via a per-workflow
|
|
646
|
+
* `RemoteWorkflowExecutor` instead of run inline — and a co-located {@link DurableWorkerRuntime}
|
|
647
|
+
* subscribes one queue per discovered name (via `runRedisWorker`) and replays the very same TS bodies.
|
|
648
|
+
* This is the uniform-dispatch "one app, both roles" shape — every turn pays a transport round-trip
|
|
649
|
+
* even though the worker is the same process. Requires a transport that carries workflow tasks
|
|
650
|
+
* (BullMQ); an in-process-only transport cannot dispatch a `WorkflowExecutor`.
|
|
651
|
+
*
|
|
652
|
+
* Distinct from the PURE thin-worker role (`connection` set, `store` unset — see
|
|
653
|
+
* `durable-worker.module.ts`'s `ThinWorkflowRegistrar`/`ThinStepRegistrar`/`ThinWorkerBootstrap`),
|
|
654
|
+
* which has no engine/store of its own at all.
|
|
655
|
+
*/
|
|
656
|
+
declare const IN_APP_WORKER_BINDING: unique symbol;
|
|
657
|
+
/** The co-located worker's {@link DurableWorkerRuntime} (the consumer half). */
|
|
658
|
+
declare const IN_APP_WORKER_RUNTIME: unique symbol;
|
|
659
|
+
/** `runRedisWorker`, injected so tests can substitute a fake (no real Redis). Defaults to the real one. */
|
|
660
|
+
declare const IN_APP_RUN_REDIS_WORKER: unique symbol;
|
|
661
|
+
/** Started {@link RunningWorker} handles for the in-app worker, closed on shutdown. */
|
|
662
|
+
declare const IN_APP_WORKER_RUNNERS: unique symbol;
|
|
663
|
+
/** The group-served binding shape resolved behind {@link IN_APP_WORKER_BINDING}. */
|
|
664
|
+
interface InAppWorkerBinding {
|
|
665
|
+
transport: Transport;
|
|
666
|
+
partition?: string;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* The consumer half of the in-app worker: on init it registers every discovered `@Workflow`/`@Step`
|
|
670
|
+
* on a {@link DurableWorkerRuntime} (the SAME bodies the engine registered group-served), and on
|
|
671
|
+
* bootstrap it starts one `runRedisWorker` call that subscribes one queue per discovered name,
|
|
672
|
+
* suffixed by the configured partition, closing it on shutdown. A no-op outside the co-located role
|
|
673
|
+
* (see {@link isCoLocatedWorker}). Mirrors the thin {@link
|
|
674
|
+
* import('./durable-worker.module').ThinWorkerBootstrap}, but co-located with a full engine.
|
|
675
|
+
*/
|
|
676
|
+
declare class InAppWorkerBootstrap implements OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown {
|
|
677
|
+
private readonly discovery;
|
|
678
|
+
private readonly metadataScanner;
|
|
679
|
+
private readonly options;
|
|
680
|
+
private readonly runtime;
|
|
681
|
+
private readonly runRedisWorker;
|
|
682
|
+
private readonly runnersSink;
|
|
683
|
+
private readonly runners;
|
|
684
|
+
constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, options: DurableModuleOptions, runtime: DurableWorkerRuntime, runRedisWorker: RunRedisWorkerFn, runnersSink: RunningWorker[]);
|
|
685
|
+
onModuleInit(): void;
|
|
686
|
+
onApplicationBootstrap(): Promise<void>;
|
|
687
|
+
onApplicationShutdown(): Promise<void>;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* The providers that stand up the co-located in-app worker, added to {@link
|
|
691
|
+
* import('./durable.module').DurableModule}'s unified provider set. All are inert outside the
|
|
692
|
+
* co-located role (`store` + `connection` both set — see {@link isCoLocatedWorker}), so a plain
|
|
693
|
+
* operator (`store` only) or pure thin worker (`connection` only) is unaffected.
|
|
694
|
+
*/
|
|
695
|
+
declare function inAppWorkerProviders(): Provider[];
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Tenant-side `RunGateway` — round-trips every verb as a `RunRequest`/`RunReply` pair over the
|
|
699
|
+
* transport, correlated by a minted `requestId`, and bridges `subscribe` onto the transport's
|
|
700
|
+
* per-tenant event stream. Bound under `RUN_GATEWAY` by `DurableModule`'s thin-worker role
|
|
701
|
+
* (`connection` set, no `store`) when the app supplies a `transport` (see `unavailableRunGateway`
|
|
702
|
+
* for the no-transport fallback). The counterpart to the operator-side `StoreRunGateway`: a tenant
|
|
703
|
+
* worker never touches a store/driver directly, only this proxy.
|
|
704
|
+
*/
|
|
705
|
+
declare class ProxyRunGateway implements RunGateway {
|
|
706
|
+
private readonly transport;
|
|
707
|
+
private readonly tenant;
|
|
708
|
+
private readonly timeoutMs;
|
|
709
|
+
private readonly pending;
|
|
710
|
+
constructor(transport: Transport, tenant: string, timeoutMs?: number);
|
|
711
|
+
private handleReply;
|
|
712
|
+
private request;
|
|
713
|
+
getRunDetail(runId: string): Promise<RunDetail | null>;
|
|
714
|
+
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
715
|
+
cancel(runId: string, opts?: {
|
|
716
|
+
compensate?: boolean;
|
|
717
|
+
}): Promise<RunResult | null>;
|
|
718
|
+
retry(runId: string): Promise<RunResult | null>;
|
|
719
|
+
continue(runId: string): Promise<RunResult | null>;
|
|
720
|
+
retryWithInput(runId: string, input: unknown): Promise<{
|
|
721
|
+
runId: string;
|
|
722
|
+
} | null>;
|
|
723
|
+
subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* True for the **operator** role — `store` is set (with or without a co-located `connection`). See
|
|
728
|
+
* {@link DurableModuleOptions} for the full role matrix. Kept in its own module (rather than
|
|
729
|
+
* `durable.module.ts`) so the poller/registrar classes can import it as a plain VALUE without a
|
|
730
|
+
* circular value dependency on the module that also imports them as providers.
|
|
731
|
+
*/
|
|
732
|
+
declare function isOperatorRole(options: DurableModuleOptions): boolean;
|
|
733
|
+
/**
|
|
734
|
+
* True when an operator instance actively DRIVES runs — polls pending, recovers crashed, resumes due
|
|
735
|
+
* timers, sweeps timeouts, prunes retention, consumes local steps — as opposed to a read-only/
|
|
736
|
+
* dashboard replica (`drive: false`) that mounts the store/dashboard but leaves driving to another
|
|
737
|
+
* instance. Meaningless (`false`) outside the operator role. Defaults to `true` for an operator.
|
|
738
|
+
*/
|
|
739
|
+
declare function isDrivingOperator(options: DurableModuleOptions): boolean;
|
|
740
|
+
|
|
741
|
+
/** The narrow slice of `Transport` the responder needs — a tenant's read/control request in, a
|
|
742
|
+
* correlated reply out. Both are OPTIONAL on the full `Transport` interface (only broker
|
|
743
|
+
* transports carry the run-request/reply protocol); the caller capability-checks before wiring
|
|
744
|
+
* this up (see `durable.module.ts`). */
|
|
745
|
+
interface RunRequestTransport {
|
|
746
|
+
onRunRequest(handler: (msg: RunRequest) => Promise<void>): void;
|
|
747
|
+
publishRunReply(reply: RunReply): Promise<void>;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Operator-side consumer of a tenant's {@link RunRequest}s: answers each one against a
|
|
751
|
+
* `RunGateway`, enforcing the tenant boundary before touching the run. For every runId-bearing
|
|
752
|
+
* verb it loads the run via `getRunDetail` FIRST and compares `run.namespace` to the requesting
|
|
753
|
+
* `msg.tenant` — a mismatch short-circuits into a `cross-tenant` error reply WITHOUT calling the
|
|
754
|
+
* verb, so a tenant can never read or act on another tenant's run. `listRuns` is scoped by
|
|
755
|
+
* overwriting the query's `namespace` with the requester's tenant, ignoring whatever the client
|
|
756
|
+
* sent. This is the security boundary of the tenant run gateway — do not weaken it.
|
|
757
|
+
*/
|
|
758
|
+
declare class RunRequestResponder {
|
|
759
|
+
private readonly transport;
|
|
760
|
+
private readonly gateway;
|
|
761
|
+
constructor(transport: RunRequestTransport, gateway: RunGateway);
|
|
762
|
+
/** Register the consumer on the transport. Each request is answered independently; a handler
|
|
763
|
+
* failure never throws back into the transport (errors are captured into an error reply). */
|
|
764
|
+
start(): void;
|
|
765
|
+
private handle;
|
|
766
|
+
private callVerb;
|
|
767
|
+
}
|
|
768
|
+
|
|
569
769
|
declare const STEP_INTERCEPTOR_METADATA: unique symbol;
|
|
570
770
|
/**
|
|
571
771
|
* The shape a `@StepInterceptor()` provider must implement: `intercept(invocation, next)` wraps the
|
|
@@ -584,6 +784,57 @@ interface DurableStepInterceptor {
|
|
|
584
784
|
declare function StepInterceptor(): ClassDecorator;
|
|
585
785
|
declare function isStepInterceptor(target: Function): boolean;
|
|
586
786
|
|
|
787
|
+
/**
|
|
788
|
+
* Store-backed `RunGateway` — the operator-side implementation, bound to {@link RUN_GATEWAY} on
|
|
789
|
+
* worker/drive instances. Reuses `DashboardService`'s six read/control method bodies verbatim
|
|
790
|
+
* (`dashboard.service.ts:76-172`), so a consumer that only needs the bounded `RunGateway` surface
|
|
791
|
+
* (the `RunRequestResponder`, a thin controller) doesn't have to depend on the dashboard package.
|
|
792
|
+
*/
|
|
793
|
+
declare class StoreRunGateway implements RunGateway {
|
|
794
|
+
private readonly store;
|
|
795
|
+
private readonly engine;
|
|
796
|
+
constructor(store: StateStore, engine: WorkflowEngine);
|
|
797
|
+
getRunDetail(runId: string): Promise<RunDetail | null>;
|
|
798
|
+
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
799
|
+
cancel(runId: string, opts?: {
|
|
800
|
+
compensate?: boolean;
|
|
801
|
+
}): Promise<RunResult | null>;
|
|
802
|
+
/** Re-enqueue (dispatch model) instead of resuming inline — a worker picks the run up and replays it. */
|
|
803
|
+
retry(runId: string): Promise<RunResult | null>;
|
|
804
|
+
continue(runId: string): Promise<RunResult | null>;
|
|
805
|
+
retryWithInput(runId: string, input: unknown): Promise<{
|
|
806
|
+
runId: string;
|
|
807
|
+
} | null>;
|
|
808
|
+
subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/** The narrow slice of `StateStore` the republisher needs — the `step.*`-event namespace fallback
|
|
812
|
+
* (see {@link TenantEventRepublisher.namespaceFor}). Mirrors the `RunRequestTransport` narrowing
|
|
813
|
+
* convention (`run-request-responder.ts`): depend on the smallest surface, not the whole store. */
|
|
814
|
+
type RunLookupStore = Pick<StateStore, 'getRun'>;
|
|
815
|
+
/**
|
|
816
|
+
* Re-publishes engine lifecycle events onto a run's per-tenant channel via `publish` (bound from
|
|
817
|
+
* `Transport.publishTenantEvent`), so a store-less tenant worker can live-tail its OWN runs.
|
|
818
|
+
*
|
|
819
|
+
* The run's namespace is read straight off `event.namespace` (stamped by `engine.ts`'s `emit()` on
|
|
820
|
+
* every `run.*` lifecycle event, where the run is already in hand); a `step.*` event doesn't carry
|
|
821
|
+
* it, so those fall back to `store.getRun`. EVERY run's resolved namespace is memoized in
|
|
822
|
+
* `runNamespaces` — including a bare/`default` run — so the store is read at most ONCE per run id
|
|
823
|
+
* regardless of how many `step.*` events that run emits; a `null` cache entry means "resolved, not
|
|
824
|
+
* a tenant" (distinct from "not yet resolved", i.e. absent from the map). A run's entry is deleted
|
|
825
|
+
* the moment its terminal event is handled, so the cache stays bounded to in-flight runs (tenant and
|
|
826
|
+
* default alike) — the same order of magnitude it was already paying for tenant runs alone, in
|
|
827
|
+
* exchange for turning a per-event store read into a per-run one.
|
|
828
|
+
*/
|
|
829
|
+
declare class TenantEventRepublisher {
|
|
830
|
+
private readonly store;
|
|
831
|
+
private readonly publish;
|
|
832
|
+
private readonly runNamespaces;
|
|
833
|
+
constructor(store: RunLookupStore, publish: (event: TenantEvent) => Promise<void>);
|
|
834
|
+
handle(event: EngineEvent): Promise<void>;
|
|
835
|
+
private namespaceFor;
|
|
836
|
+
}
|
|
837
|
+
|
|
587
838
|
/**
|
|
588
839
|
* Cross-lib injection token for the current-request context accessor, owned by
|
|
589
840
|
* `@dudousxd/nestjs-context`. We do NOT import nestjs-context (it is an OPTIONAL
|
|
@@ -596,6 +847,14 @@ declare function isStepInterceptor(target: Function): boolean;
|
|
|
596
847
|
* byte-identical with nestjs-context's export.
|
|
597
848
|
*/
|
|
598
849
|
declare const CONTEXT_ACCESSOR: unique symbol;
|
|
850
|
+
/**
|
|
851
|
+
* Injection token for the bound `RunGateway` — the read/control surface a consumer (a controller,
|
|
852
|
+
* or the `RunRequestResponder`) uses instead of reaching for `WorkflowEngine`/`StateStore`
|
|
853
|
+
* directly. `DurableModule` binds it to `StoreRunGateway` on worker/drive (operator) instances.
|
|
854
|
+
* `Symbol.for` so re-resolving this module (e.g. across a dynamic-module boundary) still hits the
|
|
855
|
+
* same registration.
|
|
856
|
+
*/
|
|
857
|
+
declare const RUN_GATEWAY: unique symbol;
|
|
599
858
|
|
|
600
859
|
/** Public entry point for starting and resuming workflow runs. */
|
|
601
860
|
declare class WorkflowService {
|
|
@@ -651,4 +910,4 @@ declare class WorkflowService {
|
|
|
651
910
|
}): Promise<number>;
|
|
652
911
|
}
|
|
653
912
|
|
|
654
|
-
export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA,
|
|
913
|
+
export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DeadLetter, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, ON_EVENT_METADATA, On, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowMeta, type WorkflowOptions, WorkflowService, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, thinWorkerProviders, unavailableRunGateway };
|