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