@nanobpm/workflow 0.15.1 → 0.16.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/README.md +13 -0
- package/dist/declarative.d.ts +6 -0
- package/dist/nodes/run.d.ts +1 -0
- package/dist/nodes/run.js +11 -1
- package/dist/worker.d.ts +3 -0
- package/dist/worker.js +9 -1
- package/package.json +1 -1
- package/src/declarative.ts +9 -1
- package/src/nodes/run.ts +13 -3
- package/src/worker.ts +8 -1
package/README.md
CHANGED
|
@@ -105,6 +105,19 @@ const { processInstanceKey } = await client.start(onboarding, {});
|
|
|
105
105
|
await client.signal(onboarding, "approved", userId, { by: "alice" });
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
A long-running step (transcription, an agent call, a multi-GB upload) can
|
|
109
|
+
outlive the engine's default job lock: the lease expires mid-handler, the
|
|
110
|
+
engine re-activates the job, and a second worker runs it again. Declare the
|
|
111
|
+
lock timeout per step with `w.run(name, handler, { timeoutMs })` (overrides the
|
|
112
|
+
`Worker`'s worker-wide `jobTimeoutMs`, default 30s, for that step's job type):
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const flow = defineFlow("uploads", (w) => {
|
|
116
|
+
w.run("transcode", transcode, { timeoutMs: 3_600_000 }); // 1h lock for this step
|
|
117
|
+
w.run("notify", notify);
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
108
121
|
A human-in-the-loop **approval gate** with a form, an assignment, and an output
|
|
109
122
|
mapping that stamps the decision back onto a process variable:
|
|
110
123
|
|
package/dist/declarative.d.ts
CHANGED
|
@@ -45,6 +45,12 @@ export interface FlowBuilder<C extends object = object> {
|
|
|
45
45
|
*/
|
|
46
46
|
run<K extends string>(name: K, handler: TypedHandler<VarsOf<C, K>, ResultOf<C, K>>, opts?: {
|
|
47
47
|
io?: HumanIoMapping;
|
|
48
|
+
/** Job lock timeout, ms, for this step's derived worker (issue #596): how long the
|
|
49
|
+
* engine leases an activated job before re-activating it elsewhere. Set it above the
|
|
50
|
+
* handler's worst-case runtime for long steps (transcription, an agent call, a
|
|
51
|
+
* multi-GB upload) so the lock cannot expire mid-handler and the job run twice.
|
|
52
|
+
* Overrides the `Worker`'s worker-wide `jobTimeoutMs` for this step's job type. */
|
|
53
|
+
timeoutMs?: number;
|
|
48
54
|
}): FlowBuilder<C>;
|
|
49
55
|
/**
|
|
50
56
|
* A durable activity served by a worker OUTSIDE this program (a BPMN service
|
package/dist/nodes/run.d.ts
CHANGED
package/dist/nodes/run.js
CHANGED
|
@@ -11,8 +11,18 @@ registerNodeKind("run", {
|
|
|
11
11
|
if (typeof handler !== "function")
|
|
12
12
|
throw new Error(`run("${name}") needs a handler function`);
|
|
13
13
|
assertIoMapping(`run("${name}")`, opts?.io);
|
|
14
|
+
const timeoutMs = opts?.timeoutMs;
|
|
15
|
+
if (timeoutMs !== undefined && (!Number.isInteger(timeoutMs) || timeoutMs <= 0)) {
|
|
16
|
+
throw new Error(`run("${name}"): timeoutMs must be a positive integer (ms)`);
|
|
17
|
+
}
|
|
14
18
|
api.handlers[name] = handler;
|
|
15
|
-
api.out.push({
|
|
19
|
+
api.out.push({
|
|
20
|
+
kind: "run",
|
|
21
|
+
name,
|
|
22
|
+
envelopes: api.contractEnvelopes(name),
|
|
23
|
+
io: opts?.io,
|
|
24
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
25
|
+
});
|
|
16
26
|
return api.self();
|
|
17
27
|
},
|
|
18
28
|
emit: (node, incoming, _loop, api) => {
|
package/dist/worker.d.ts
CHANGED
|
@@ -54,6 +54,9 @@ export declare class Worker {
|
|
|
54
54
|
private readonly onError?;
|
|
55
55
|
/** job type → { workflowId, handle } */
|
|
56
56
|
private readonly routes;
|
|
57
|
+
/** Job lock timeout (ms) per derived job type, from a `run` step's `{ timeoutMs }`
|
|
58
|
+
* (issue #596). Overrides the worker-wide `jobTimeoutMs` for that type. */
|
|
59
|
+
private readonly typeTimeoutMs;
|
|
57
60
|
private running;
|
|
58
61
|
private workers;
|
|
59
62
|
constructor(opts: WorkerOptions);
|
package/dist/worker.js
CHANGED
|
@@ -30,6 +30,9 @@ export class Worker {
|
|
|
30
30
|
onError;
|
|
31
31
|
/** job type → { workflowId, handle } */
|
|
32
32
|
routes = new Map();
|
|
33
|
+
/** Job lock timeout (ms) per derived job type, from a `run` step's `{ timeoutMs }`
|
|
34
|
+
* (issue #596). Overrides the worker-wide `jobTimeoutMs` for that type. */
|
|
35
|
+
typeTimeoutMs = new Map();
|
|
33
36
|
running = false;
|
|
34
37
|
workers = [];
|
|
35
38
|
constructor(opts) {
|
|
@@ -87,6 +90,10 @@ export class Worker {
|
|
|
87
90
|
return { variables };
|
|
88
91
|
},
|
|
89
92
|
});
|
|
93
|
+
// A step-declared job lock timeout (issue #596) applies to this step's derived
|
|
94
|
+
// job type, overriding the worker-wide `jobTimeoutMs` at `start()`.
|
|
95
|
+
if (s.timeoutMs !== undefined)
|
|
96
|
+
this.typeTimeoutMs.set(jobType(wf.id, s.name), s.timeoutMs);
|
|
90
97
|
});
|
|
91
98
|
}
|
|
92
99
|
}
|
|
@@ -141,7 +148,8 @@ export class Worker {
|
|
|
141
148
|
jobType: type,
|
|
142
149
|
workerName: this.name,
|
|
143
150
|
maxParallelJobs: this.maxParallelJobs,
|
|
144
|
-
|
|
151
|
+
// A step-declared `{ timeoutMs }` (issue #596) wins over the worker-wide default.
|
|
152
|
+
jobTimeoutMs: this.typeTimeoutMs.get(type) ?? this.jobTimeoutMs,
|
|
145
153
|
pollTimeoutMs: this.pollTimeoutMs,
|
|
146
154
|
autoStart: false,
|
|
147
155
|
jobHandler: (job) => this.handleJob(type, route, job),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Code-first durable orchestration for nanobpmn (ADR 0044/0045). Author a durable workflow as a declarative flow — `defineFlow(id, w => { w.run(...); w.signal(...); w.task(...) })` — and the SDK derives the executable BPMN model, the job types, and the message/correlation wiring, then hosts a generic worker. `w.run` is a local step, `w.signal` is a durable human-in-the-loop wait, `w.task` is a step serviced by an external worker. `defineFlow` is the single code-first surface; the imperative `defineWorkflow` replay function is experimental/internal, not a recommended API. No diagram, no task-type wiring, no correlation plumbing authored by hand — and you can optionally auto-generate a diagram (DI) for the derived model with `declarativeToLayoutedBpmn` via the optional `bpmn-auto-layout` peer dependency. Talks to a running nanobpmn gateway through @nanobpm/nano-sdk (the shared engine-transport spine — ADR 0055), which negotiates the Falcon protocol on a Nano server and REST elsewhere.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/src/declarative.ts
CHANGED
|
@@ -102,7 +102,15 @@ export interface FlowBuilder<C extends object = object> {
|
|
|
102
102
|
run<K extends string>(
|
|
103
103
|
name: K,
|
|
104
104
|
handler: TypedHandler<VarsOf<C, K>, ResultOf<C, K>>,
|
|
105
|
-
opts?: {
|
|
105
|
+
opts?: {
|
|
106
|
+
io?: HumanIoMapping;
|
|
107
|
+
/** Job lock timeout, ms, for this step's derived worker (issue #596): how long the
|
|
108
|
+
* engine leases an activated job before re-activating it elsewhere. Set it above the
|
|
109
|
+
* handler's worst-case runtime for long steps (transcription, an agent call, a
|
|
110
|
+
* multi-GB upload) so the lock cannot expire mid-handler and the job run twice.
|
|
111
|
+
* Overrides the `Worker`'s worker-wide `jobTimeoutMs` for this step's job type. */
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
},
|
|
106
114
|
): FlowBuilder<C>;
|
|
107
115
|
/**
|
|
108
116
|
* A durable activity served by a worker OUTSIDE this program (a BPMN service
|
package/src/nodes/run.ts
CHANGED
|
@@ -11,17 +11,27 @@ import { registerNodeKind } from "./registry.js";
|
|
|
11
11
|
|
|
12
12
|
declare module "../types.js" {
|
|
13
13
|
interface FlowNodeRegistry {
|
|
14
|
-
run: { kind: "run"; name: string; envelopes?: NodeEnvelopes; io?: HumanIoMapping };
|
|
14
|
+
run: { kind: "run"; name: string; envelopes?: NodeEnvelopes; io?: HumanIoMapping; timeoutMs?: number };
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
registerNodeKind("run", {
|
|
19
|
-
build: (api) => (name: string, handler: StepHandler, opts?: { io?: HumanIoMapping }) => {
|
|
19
|
+
build: (api) => (name: string, handler: StepHandler, opts?: { io?: HumanIoMapping; timeoutMs?: number }) => {
|
|
20
20
|
api.claim(name);
|
|
21
21
|
if (typeof handler !== "function") throw new Error(`run("${name}") needs a handler function`);
|
|
22
22
|
assertIoMapping(`run("${name}")`, opts?.io);
|
|
23
|
+
const timeoutMs = opts?.timeoutMs;
|
|
24
|
+
if (timeoutMs !== undefined && (!Number.isInteger(timeoutMs) || timeoutMs <= 0)) {
|
|
25
|
+
throw new Error(`run("${name}"): timeoutMs must be a positive integer (ms)`);
|
|
26
|
+
}
|
|
23
27
|
api.handlers[name] = handler;
|
|
24
|
-
api.out.push({
|
|
28
|
+
api.out.push({
|
|
29
|
+
kind: "run",
|
|
30
|
+
name,
|
|
31
|
+
envelopes: api.contractEnvelopes(name),
|
|
32
|
+
io: opts?.io,
|
|
33
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
34
|
+
});
|
|
25
35
|
return api.self();
|
|
26
36
|
},
|
|
27
37
|
emit: (node, incoming, _loop, api) => {
|
package/src/worker.ts
CHANGED
|
@@ -84,6 +84,9 @@ export class Worker {
|
|
|
84
84
|
private readonly onError?: WorkerOptions["onError"];
|
|
85
85
|
/** job type → { workflowId, handle } */
|
|
86
86
|
private readonly routes = new Map<string, { workflowId: string; handle: Handler }>();
|
|
87
|
+
/** Job lock timeout (ms) per derived job type, from a `run` step's `{ timeoutMs }`
|
|
88
|
+
* (issue #596). Overrides the worker-wide `jobTimeoutMs` for that type. */
|
|
89
|
+
private readonly typeTimeoutMs = new Map<string, number>();
|
|
87
90
|
private running = false;
|
|
88
91
|
private workers: NanoJobWorker[] = [];
|
|
89
92
|
|
|
@@ -142,6 +145,9 @@ export class Worker {
|
|
|
142
145
|
return { variables };
|
|
143
146
|
},
|
|
144
147
|
});
|
|
148
|
+
// A step-declared job lock timeout (issue #596) applies to this step's derived
|
|
149
|
+
// job type, overriding the worker-wide `jobTimeoutMs` at `start()`.
|
|
150
|
+
if (s.timeoutMs !== undefined) this.typeTimeoutMs.set(jobType(wf.id, s.name), s.timeoutMs);
|
|
145
151
|
});
|
|
146
152
|
}
|
|
147
153
|
}
|
|
@@ -200,7 +206,8 @@ export class Worker {
|
|
|
200
206
|
jobType: type,
|
|
201
207
|
workerName: this.name,
|
|
202
208
|
maxParallelJobs: this.maxParallelJobs,
|
|
203
|
-
|
|
209
|
+
// A step-declared `{ timeoutMs }` (issue #596) wins over the worker-wide default.
|
|
210
|
+
jobTimeoutMs: this.typeTimeoutMs.get(type) ?? this.jobTimeoutMs,
|
|
204
211
|
pollTimeoutMs: this.pollTimeoutMs,
|
|
205
212
|
autoStart: false,
|
|
206
213
|
jobHandler: (job) => this.handleJob(type, route, job),
|