@dudousxd/nestjs-durable 0.47.1 → 0.48.1

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.1",
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/durable-worker": "0.10.4",
71
- "@dudousxd/nestjs-durable-core": "^0.78.0",
72
- "@dudousxd/nestjs-durable-dashboard": "0.50.0",
73
- "@dudousxd/nestjs-durable-transport-bullmq": "0.22.1",
74
- "@dudousxd/nestjs-durable-transport-event-emitter": "^0.5.1"
70
+ "@dudousxd/durable-worker": "0.11.2",
71
+ "@dudousxd/nestjs-durable-core": "^0.80.0",
72
+ "@dudousxd/nestjs-durable-dashboard": "0.51.0",
73
+ "@dudousxd/nestjs-durable-testing": "^0.19.0",
74
+ "@dudousxd/nestjs-durable-transport-bullmq": "0.22.3",
75
+ "@dudousxd/nestjs-durable-transport-event-emitter": "^0.5.2"
75
76
  },
76
77
  "module": "./dist/index.js",
77
78
  "exports": {
@@ -76,6 +76,10 @@ await app.listen(3000);
76
76
 
77
77
  `start` **enqueues** the run and returns immediately with `{ runId, status: 'pending' }` — a worker
78
78
  runs the body, so the HTTP handler never blocks. Pass the workflow **class** for a typed input.
79
+ Pass `opts.startAt` (epoch ms or a `Date`) to delay the start: the run is created `suspended` with
80
+ its `wakeAt` set (the same durable timer `ctx.sleep` parks on, resumed by the timer poller once
81
+ due), and `start` returns `{ runId, status: 'suspended' }` instead. A time that is not in the
82
+ future starts the run at once.
79
83
 
80
84
  ```ts
81
85
  import { WorkflowService } from '@dudousxd/nestjs-durable';
@@ -158,7 +162,7 @@ name automatically; there's no `groups`/`remoteByConvention` flag to opt into.
158
162
  ### 1. Expecting `start` to return the workflow's result
159
163
 
160
164
  ```ts
161
- // ✗ Wrong — `start` enqueues; `result` is { runId, status: 'pending' }, never the run output
165
+ // ✗ Wrong — `start` enqueues; `result` is { runId, status: 'pending' } (or 'suspended' with startAt), never the run output
162
166
  const result = await this.workflows.start(CheckoutWorkflow, order);
163
167
  return result.output; // undefined
164
168
 
@@ -167,7 +171,8 @@ const { runId } = await this.workflows.start(CheckoutWorkflow, order);
167
171
  return this.workflows.waitForRun(runId); // resolves once the run settles
168
172
  ```
169
173
 
170
- `start` creates the run as `'pending'` and a worker runs the body asynchronously; only `waitForRun`
174
+ `start` creates the run as `'pending'` (or `'suspended'` when `opts.startAt` is in the future)
175
+ and a worker runs the body asynchronously; only `waitForRun`
171
176
  resolves with the settled outcome. Source: packages/nestjs/src/workflow.service.ts.
172
177
 
173
178
  ### 2. Leaving `autoSchema` on in production
@@ -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.recordRejectedLine, { 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.