@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/dist/index.cjs +538 -283
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -5
- package/dist/index.d.ts +87 -5
- package/dist/index.js +451 -200
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/skills/durable-setup/SKILL.md +7 -2
- package/skills/durable-workflows/SKILL.md +37 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-durable",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
71
|
-
"@dudousxd/nestjs-durable-core": "^0.
|
|
72
|
-
"@dudousxd/nestjs-durable-dashboard": "0.
|
|
73
|
-
"@dudousxd/nestjs-durable-
|
|
74
|
-
"@dudousxd/nestjs-durable-transport-
|
|
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'`
|
|
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.
|