@dudousxd/nestjs-durable 0.47.0 → 0.48.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-durable",
3
- "version": "0.47.0",
3
+ "version": "0.48.0",
4
4
  "description": "Durable workflows for NestJS — module, decorators, discovery and boot recovery",
5
5
  "license": "MIT",
6
6
  "author": "Davide Carvalho",
@@ -67,11 +67,12 @@
67
67
  "tsup": "8.3.5",
68
68
  "@dudousxd/nestjs-diagnostics": "0.5.0",
69
69
  "@tanstack/intent": "0.4.0",
70
- "@dudousxd/nestjs-durable-core": "^0.78.0",
71
- "@dudousxd/nestjs-durable-dashboard": "0.50.0",
72
- "@dudousxd/nestjs-durable-transport-bullmq": "0.22.0",
73
- "@dudousxd/nestjs-durable-transport-event-emitter": "^0.5.1",
74
- "@dudousxd/durable-worker": "0.10.3"
70
+ "@dudousxd/durable-worker": "0.11.0",
71
+ "@dudousxd/nestjs-durable-core": "^0.79.0",
72
+ "@dudousxd/nestjs-durable-dashboard": "0.51.0",
73
+ "@dudousxd/nestjs-durable-testing": "^0.18.0",
74
+ "@dudousxd/nestjs-durable-transport-bullmq": "0.22.1",
75
+ "@dudousxd/nestjs-durable-transport-event-emitter": "^0.5.1"
75
76
  },
76
77
  "module": "./dist/index.js",
77
78
  "exports": {
@@ -116,6 +116,12 @@ DurableModule.forRootAsync({
116
116
  });
117
117
  ```
118
118
 
119
+ `BullMQTransport` has two concurrency knobs. `concurrency` (default `1`; a number, `'adaptive'`, or
120
+ `{ mode: 'adaptive', min, max, ... }`) is how many TASKS a worker runs at once from each step queue.
121
+ `resultConcurrency` (default `1`) is how many step RESULTS the engine side handles at once. Raise it
122
+ for wide fans; one run's results still resume it in order on an instance. A non-finite or sub-1
123
+ `resultConcurrency` falls back to `1`.
124
+
119
125
  ### Split API pods from worker pods
120
126
 
121
127
  `DurableModule.forRoot` is the ONE module; the role is inferred from which of `store`/`connection`
@@ -6,7 +6,8 @@ description: >
6
6
  always dispatched, always engine-scheduled. @Step()-decorated provider methods carry the routing
7
7
  identity (derived Class.method name, or an explicit override); a method reference dispatches a
8
8
  same-process/typed step, a string name dispatches a cross-runtime one (e.g. a Python worker). Covers
9
- retries/backoff and FatalError, Promise.all fan-out, sub-process log.sub/subProcess annotations
9
+ retries/backoff and FatalError, Promise.all fan-out, ctx.all over a @Step (step fans with
10
+ concurrency waves and waitAll/failFast), sub-process log.sub/subProcess annotations
10
11
  inside a @Step handler, ctx.child for cross-runtime sub-workflows, and constructor dependency
11
12
  injection inside a workflow class.
12
13
  license: MIT
@@ -145,10 +146,49 @@ outputs in input order (a `GatherError` carries the failed indexes, as for a chi
145
146
  const results = await ctx.all(this.lines.processLine, lines, { concurrency: 500 });
146
147
  ```
147
148
 
149
+ When a failed item is an expected outcome rather than a reason to fail the run, use
150
+ `ctx.allSettled` instead: the same fan, resolving with `{ status: 'fulfilled', value }` or
151
+ `{ status: 'rejected', id, error, code?, details? }` per input, in input order, and never throwing for an item. The rejected `code`/`details` are the `FatalError`'s (absent for a plain `Error`). Throw
152
+ `FatalError` in the step for a failure a retry can't fix (a schema mismatch); it settles as rejected
153
+ without spending the retries:
154
+
155
+ ```ts
156
+ const results = await ctx.allSettled(this.lines.validateLine, lines, { concurrency: 500 });
157
+ const rejected = results.filter((r) => r.status === 'rejected');
158
+ ```
159
+
148
160
  Prefer it to `Promise.all` past a handful of steps: every result of a `Promise.all` replays the
149
161
  workflow, which makes a fan of N steps cost O(N²) checkpoint reads. Prefer it to a child-workflow
150
162
  `ctx.all` when each item is just a step: a child costs a whole run (row, turn, lease, signal).
151
163
 
164
+ How the step fan behaves (`AllStepsOptions`):
165
+
166
+ - `concurrency` sends strict waves of that many steps; the next wave goes out once the whole
167
+ previous wave has settled. It is part of the fan's identity, like `inputs` — don't change it under
168
+ an in-flight run. It must be a positive integer.
169
+ - `mode: 'waitAll'` (default) lets every step settle, then throws `GatherError` with every failed
170
+ index. `mode: 'failFast'` throws `GatherError` with the first failure, sends no further waves, and
171
+ does NOT cancel steps already in flight.
172
+ - A step only counts as failed once its retries (the `@Step` policy, or per-call `retries` /
173
+ `backoff`) are spent.
174
+ - Rejected: a `queue` option, and a `@Step` that declares `timeoutMs`.
175
+ - The steps share `parallelGroup` `steps:<firstSeq>:<size>[:failFast]`.
176
+
177
+ ```ts
178
+ import { GatherError } from '@dudousxd/nestjs-durable-core';
179
+
180
+ let failedAt: number | undefined;
181
+ try {
182
+ await ctx.all(this.lines.processLine, lines, { concurrency: 500, mode: 'failFast' });
183
+ } catch (err) {
184
+ if (!(err instanceof GatherError)) throw err;
185
+ failedAt = err.failures[0]?.index; // the first failed input's position
186
+ }
187
+ ```
188
+
189
+ For a wide fan over `BullMQTransport`, raise `resultConcurrency` (default 1) on the instances that
190
+ consume results; one run's results still resume it in order on an instance.
191
+
152
192
  ### Sub-process events inside a step
153
193
 
154
194
  For pure visibility *inside* one step's execution, the `@Step` handler's optional second argument
@@ -184,7 +224,7 @@ const rows = await ctx.child<{ rows: number }>('processing', { baseId });
184
224
  `ctx.child` starts the child once (checkpointed) and suspends — zero compute — until it reaches a
185
225
  terminal state, then resumes with its output (or throws if the child failed). Use `ctx.all(Class,
186
226
  inputs)` / `ctx.all(name, inputs)` to fan out N children of the same workflow and wait for all of
187
- them.
227
+ them, or `ctx.allSettled(...)` to get every outcome back instead of a `GatherError`.
188
228
 
189
229
  ## Common mistakes
190
230
 
@@ -247,4 +287,29 @@ async charge(order: Order) {
247
287
  ```
248
288
 
249
289
  Any thrown error is retried to the step's limit; only `FatalError` short-circuits retries.
290
+
291
+ When a failure is an expected outcome rather than a reason to fail the run (a line that fails
292
+ validation), route its code to an `@OnError` handler. The engine starts the handler workflow
293
+ (`<workflow>.onError.<method>`) as run `<runId>.onError.<attempt>` with `{ runId, workflow, input, error }`
294
+ when the run fails with that code, and the run settles with the handler's outcome (its output, or
295
+ its error when the handler fails); the body never runs again, so a `ctx.all` over the workflow
296
+ keeps going. It works for remote (Python) bodies too:
297
+
298
+ ```ts
299
+ class InvalidLine extends FatalError {
300
+ static readonly code = 'invalid_line';
301
+ }
302
+
303
+ @OnError(InvalidLine) // or @OnError('invalid_line'); @OnError() catches anything else
304
+ async invalid(ctx: WorkflowCtx, failure: OnErrorInput<Line>): Promise<LineResult> {
305
+ await ctx.step(this.steps.recordDeadLetter, { line: failure.input, error: failure.error.message });
306
+ return { ok: 0, failed: 1 };
307
+ }
308
+ ```
309
+
310
+ `error.details` is the third argument of `FatalError(message, code, details)` (e.g. validation
311
+ issues). Retrying the run runs its body again; a new failure goes to a new handler run.
312
+ It matches on `code`, never `instanceof`, must return `run`'s output type, and never sees
313
+ cancellation (a `cancelled` run never routes), `nested_workflow_call` ctx misuse, or `non_determinism`
314
+ (`NonDeterminismError` carries `code: 'non_determinism'`). `@Workflow({ onError: [{ code, workflow }] })` routes to another workflow.
250
315
  Source: packages/core/src/workflow-ctx.ts (`step` retry loop), packages/core/src/errors.ts.