@dudousxd/nestjs-durable 0.47.1 → 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.1",
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,9 +67,10 @@
67
67
  "tsup": "8.3.5",
68
68
  "@dudousxd/nestjs-diagnostics": "0.5.0",
69
69
  "@tanstack/intent": "0.4.0",
70
- "@dudousxd/durable-worker": "0.10.4",
71
- "@dudousxd/nestjs-durable-core": "^0.78.0",
72
- "@dudousxd/nestjs-durable-dashboard": "0.50.0",
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",
73
74
  "@dudousxd/nestjs-durable-transport-bullmq": "0.22.1",
74
75
  "@dudousxd/nestjs-durable-transport-event-emitter": "^0.5.1"
75
76
  },
@@ -146,6 +146,17 @@ outputs in input order (a `GatherError` carries the failed indexes, as for a chi
146
146
  const results = await ctx.all(this.lines.processLine, lines, { concurrency: 500 });
147
147
  ```
148
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
+
149
160
  Prefer it to `Promise.all` past a handful of steps: every result of a `Promise.all` replays the
150
161
  workflow, which makes a fan of N steps cost O(N²) checkpoint reads. Prefer it to a child-workflow
151
162
  `ctx.all` when each item is just a step: a child costs a whole run (row, turn, lease, signal).
@@ -213,7 +224,7 @@ const rows = await ctx.child<{ rows: number }>('processing', { baseId });
213
224
  `ctx.child` starts the child once (checkpointed) and suspends — zero compute — until it reaches a
214
225
  terminal state, then resumes with its output (or throws if the child failed). Use `ctx.all(Class,
215
226
  inputs)` / `ctx.all(name, inputs)` to fan out N children of the same workflow and wait for all of
216
- them.
227
+ them, or `ctx.allSettled(...)` to get every outcome back instead of a `GatherError`.
217
228
 
218
229
  ## Common mistakes
219
230
 
@@ -276,4 +287,29 @@ async charge(order: Order) {
276
287
  ```
277
288
 
278
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.
279
315
  Source: packages/core/src/workflow-ctx.ts (`step` retry loop), packages/core/src/errors.ts.