@nanobpm/workflow 0.2.0 → 0.3.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 +96 -28
- package/dist/client.js +8 -2
- package/dist/declarative.d.ts +84 -17
- package/dist/declarative.js +423 -83
- package/dist/envelope.d.ts +57 -0
- package/dist/envelope.js +62 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -1
- package/dist/layout.d.ts +22 -0
- package/dist/layout.js +61 -0
- package/dist/types.d.ts +57 -5
- package/dist/worker.js +8 -5
- package/dist/xml.js +29 -2
- package/package.json +11 -2
- package/src/bpmn-auto-layout.d.ts +10 -0
- package/src/client.ts +7 -2
- package/src/declarative.ts +569 -101
- package/src/envelope.ts +130 -0
- package/src/index.ts +17 -1
- package/src/layout.ts +67 -0
- package/src/types.ts +56 -9
- package/src/worker.ts +8 -5
- package/src/xml.ts +28 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export { defineWorkflow, imperativeToBpmn, replayOnce } from "./imperative.js";
|
|
2
2
|
export type { Journal, ReplayStep } from "./imperative.js";
|
|
3
|
-
export { defineFlow, declarativeToBpmn, externalJobTypes } from "./declarative.js";
|
|
3
|
+
export { defineFlow, declarativeToBpmn, externalJobTypes, walkNodes } from "./declarative.js";
|
|
4
4
|
export type { FlowBuilder } from "./declarative.js";
|
|
5
|
+
export { layoutBpmn, declarativeToLayoutedBpmn } from "./layout.js";
|
|
6
|
+
export { envelope } from "./envelope.js";
|
|
7
|
+
export type { Envelope, EnvelopeType, EnvelopeField, FieldSpec, ScalarType, ScalarTs, FieldTs, } from "./envelope.js";
|
|
5
8
|
export { WorkflowClient, WorkflowError, toBpmn } from "./client.js";
|
|
6
9
|
export type { WorkflowClientOptions, ActivateOptions } from "./client.js";
|
|
7
10
|
export { Worker } from "./worker.js";
|
|
8
11
|
export type { WorkerOptions, ActivityEvent } from "./worker.js";
|
|
9
|
-
export type { Json, JsonObject, Job, StepHandler, DeclarativeFlow, DeclarativeStep, ImperativeWorkflow, Orchestration, WorkflowContext, Workflow, DeployResult, StartResult, } from "./types.js";
|
|
12
|
+
export type { Json, JsonObject, Job, StepHandler, DeclarativeFlow, DeclarativeStep, FlowNode, SwitchCase, NodeEnvelopes, StepContract, FlowContracts, ImperativeWorkflow, Orchestration, WorkflowContext, Workflow, DeployResult, StartResult, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
// code-first surface (steps are not engine-visible; requires determinism
|
|
40
40
|
// discipline). Retained for advanced durable-orchestration use only.
|
|
41
41
|
export { defineWorkflow, imperativeToBpmn, replayOnce } from "./imperative.js";
|
|
42
|
-
export { defineFlow, declarativeToBpmn, externalJobTypes } from "./declarative.js";
|
|
42
|
+
export { defineFlow, declarativeToBpmn, externalJobTypes, walkNodes } from "./declarative.js";
|
|
43
|
+
export { layoutBpmn, declarativeToLayoutedBpmn } from "./layout.js";
|
|
44
|
+
export { envelope } from "./envelope.js";
|
|
43
45
|
export { WorkflowClient, WorkflowError, toBpmn } from "./client.js";
|
|
44
46
|
export { Worker } from "./worker.js";
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DeclarativeFlow } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generate BPMN diagram interchange (DI) for a BPMN XML string using bpmn-io's
|
|
4
|
+
* `bpmn-auto-layout`, returning the same model with an auto-laid-out diagram
|
|
5
|
+
* (`bpmndi:BPMNDiagram`). Works on DI-less *or* already-laid-out input, and
|
|
6
|
+
* preserves `zeebe:` extension elements (task definitions, message
|
|
7
|
+
* subscriptions) through the round-trip.
|
|
8
|
+
*
|
|
9
|
+
* Requires the optional peer dependency `bpmn-auto-layout` to be installed; a
|
|
10
|
+
* clear error is thrown if it is missing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function layoutBpmn(bpmnXml: string): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Derive an executable BPMN model from a declarative flow AND auto-generate its
|
|
15
|
+
* diagram (DI), so the model opens rendered in a modeller/viewer. Convenience
|
|
16
|
+
* over `layoutBpmn(declarativeToBpmn(flow))`; async because layout is async. The
|
|
17
|
+
* semantic model stays authoritative — see `declarativeToBpmn` for the DI-less
|
|
18
|
+
* model the engine actually runs.
|
|
19
|
+
*
|
|
20
|
+
* Requires the optional peer dependency `bpmn-auto-layout` to be installed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function declarativeToLayoutedBpmn(flow: DeclarativeFlow): Promise<string>;
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Optional diagram-interchange (DI) generation for the code-first surfaces.
|
|
2
|
+
//
|
|
3
|
+
// The authoring surfaces emit *semantic* BPMN — process, flow nodes, sequence
|
|
4
|
+
// flows, messages — with no `bpmndi:` diagram: the engine runs the semantic model
|
|
5
|
+
// and DI is an authoring-time concern only (a derived flow has no diagram of its
|
|
6
|
+
// own). This module layers bpmn-io's `bpmn-auto-layout` on top so a derived model
|
|
7
|
+
// opens with a rendered, auto-laid-out diagram in a modeller/viewer, which makes
|
|
8
|
+
// code-first flows visually inspectable, reviewable, and round-trippable.
|
|
9
|
+
//
|
|
10
|
+
// `bpmn-auto-layout` is an *optional peer dependency*: the runtime client stays
|
|
11
|
+
// dependency-free and the layout engine is loaded lazily, only when you ask for a
|
|
12
|
+
// diagram. Install it alongside this package (`npm i bpmn-auto-layout`) to use
|
|
13
|
+
// these helpers.
|
|
14
|
+
//
|
|
15
|
+
// DI is best-effort and *regenerable* — the semantic model (`declarativeToBpmn`)
|
|
16
|
+
// stays authoritative. Re-run layout whenever the flow changes rather than
|
|
17
|
+
// hand-editing the generated diagram.
|
|
18
|
+
import { declarativeToBpmn } from "./declarative.js";
|
|
19
|
+
/**
|
|
20
|
+
* Generate BPMN diagram interchange (DI) for a BPMN XML string using bpmn-io's
|
|
21
|
+
* `bpmn-auto-layout`, returning the same model with an auto-laid-out diagram
|
|
22
|
+
* (`bpmndi:BPMNDiagram`). Works on DI-less *or* already-laid-out input, and
|
|
23
|
+
* preserves `zeebe:` extension elements (task definitions, message
|
|
24
|
+
* subscriptions) through the round-trip.
|
|
25
|
+
*
|
|
26
|
+
* Requires the optional peer dependency `bpmn-auto-layout` to be installed; a
|
|
27
|
+
* clear error is thrown if it is missing.
|
|
28
|
+
*/
|
|
29
|
+
export async function layoutBpmn(bpmnXml) {
|
|
30
|
+
let layoutProcess;
|
|
31
|
+
try {
|
|
32
|
+
({ layoutProcess } = await import("bpmn-auto-layout"));
|
|
33
|
+
}
|
|
34
|
+
catch (cause) {
|
|
35
|
+
// Only substitute the friendly "not installed" message when the module
|
|
36
|
+
// genuinely cannot be resolved. A different failure (the package is present
|
|
37
|
+
// but fails to load — syntax, a broken transitive dep, a runtime throw at
|
|
38
|
+
// import time) must surface as-is so it isn't masked as a missing dependency.
|
|
39
|
+
if (cause?.code !== "ERR_MODULE_NOT_FOUND") {
|
|
40
|
+
throw cause;
|
|
41
|
+
}
|
|
42
|
+
throw new Error('layoutBpmn requires the optional peer dependency "bpmn-auto-layout". ' +
|
|
43
|
+
"Install it to generate diagram layout: npm i bpmn-auto-layout", { cause });
|
|
44
|
+
}
|
|
45
|
+
const result = await layoutProcess(bpmnXml);
|
|
46
|
+
// Version-shape normalization: bpmn-auto-layout >= 1.4 (and `main`) resolves to
|
|
47
|
+
// `{ xml, warnings }`; 1.3.x resolves to the laid-out XML string directly.
|
|
48
|
+
return typeof result === "string" ? result : result.xml;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Derive an executable BPMN model from a declarative flow AND auto-generate its
|
|
52
|
+
* diagram (DI), so the model opens rendered in a modeller/viewer. Convenience
|
|
53
|
+
* over `layoutBpmn(declarativeToBpmn(flow))`; async because layout is async. The
|
|
54
|
+
* semantic model stays authoritative — see `declarativeToBpmn` for the DI-less
|
|
55
|
+
* model the engine actually runs.
|
|
56
|
+
*
|
|
57
|
+
* Requires the optional peer dependency `bpmn-auto-layout` to be installed.
|
|
58
|
+
*/
|
|
59
|
+
export function declarativeToLayoutedBpmn(flow) {
|
|
60
|
+
return layoutBpmn(declarativeToBpmn(flow));
|
|
61
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Envelope } from "./envelope.js";
|
|
1
2
|
/** A JSON-serialisable value, as carried by process variables. */
|
|
2
3
|
export type Json = null | boolean | number | string | Json[] | {
|
|
3
4
|
[k: string]: Json;
|
|
@@ -5,31 +6,82 @@ export type Json = null | boolean | number | string | Json[] | {
|
|
|
5
6
|
export type JsonObject = {
|
|
6
7
|
[k: string]: Json;
|
|
7
8
|
};
|
|
8
|
-
/** A job as delivered by the nanobpmn gateway's `POST /v2/jobs/activation`.
|
|
9
|
-
|
|
9
|
+
/** A job as delivered by the nanobpmn gateway's `POST /v2/jobs/activation`. The
|
|
10
|
+
* `variables` type parameter carries the input envelope's inferred payload when
|
|
11
|
+
* a step is declared with a typed envelope; it defaults to the untyped
|
|
12
|
+
* `JsonObject`. */
|
|
13
|
+
export interface Job<V extends JsonObject = JsonObject> {
|
|
10
14
|
jobKey: string;
|
|
11
15
|
processInstanceKey: string;
|
|
12
16
|
elementId: string;
|
|
13
17
|
type: string;
|
|
14
|
-
variables:
|
|
18
|
+
variables: V;
|
|
15
19
|
}
|
|
20
|
+
/** The input/output data envelopes lifted onto an activity node. */
|
|
21
|
+
export interface NodeEnvelopes {
|
|
22
|
+
in?: Envelope;
|
|
23
|
+
out?: Envelope;
|
|
24
|
+
}
|
|
25
|
+
/** A typed I/O contract for one step: its input and/or output data envelope.
|
|
26
|
+
* For a `signal`, `in` types the message payload. */
|
|
27
|
+
export interface StepContract {
|
|
28
|
+
in?: Envelope;
|
|
29
|
+
out?: Envelope;
|
|
30
|
+
}
|
|
31
|
+
/** A flow's typed I/O registry, keyed by step name — the single source of truth
|
|
32
|
+
* for each step's envelopes. Passed to `defineFlow`; a step whose name is a key
|
|
33
|
+
* is typed (and its envelopes lifted to the model), others fall back to the
|
|
34
|
+
* untyped `JsonObject`. */
|
|
35
|
+
export type FlowContracts = Record<string, StepContract>;
|
|
16
36
|
/** Handler for a declarative `run` step: does real work, returns variables. */
|
|
17
37
|
export type StepHandler = (job: Job) => Promise<JsonObject | void> | JsonObject | void;
|
|
18
|
-
|
|
38
|
+
/** A node in a declarative flow tree. Leaf activities carry optional data
|
|
39
|
+
* envelopes (lifted to `nano:shape` + `dataEnvelope` in the model); structural
|
|
40
|
+
* combinators carry nested `FlowNode[]` bodies. */
|
|
41
|
+
export type FlowNode = {
|
|
19
42
|
kind: "run";
|
|
20
43
|
name: string;
|
|
44
|
+
envelopes?: NodeEnvelopes;
|
|
21
45
|
} | {
|
|
22
46
|
kind: "task";
|
|
23
47
|
name: string;
|
|
48
|
+
envelopes?: NodeEnvelopes;
|
|
24
49
|
} | {
|
|
25
50
|
kind: "signal";
|
|
26
51
|
name: string;
|
|
27
52
|
correlationKey: string;
|
|
53
|
+
payload?: Envelope;
|
|
54
|
+
} | {
|
|
55
|
+
kind: "switch";
|
|
56
|
+
subject: string;
|
|
57
|
+
cases: SwitchCase[];
|
|
58
|
+
default?: FlowNode[];
|
|
59
|
+
} | {
|
|
60
|
+
kind: "branch";
|
|
61
|
+
condition: string;
|
|
62
|
+
then: FlowNode[];
|
|
63
|
+
else?: FlowNode[];
|
|
64
|
+
} | {
|
|
65
|
+
kind: "loop";
|
|
66
|
+
body: FlowNode[];
|
|
67
|
+
} | {
|
|
68
|
+
kind: "break";
|
|
69
|
+
} | {
|
|
70
|
+
kind: "continue";
|
|
28
71
|
};
|
|
72
|
+
/** One case of a `switch`: routed when `subject = value` (FEEL equality). */
|
|
73
|
+
export interface SwitchCase {
|
|
74
|
+
value: string;
|
|
75
|
+
body: FlowNode[];
|
|
76
|
+
}
|
|
77
|
+
/** @deprecated Renamed to {@link FlowNode} now that a flow is a tree of nodes,
|
|
78
|
+
* not a flat list of steps. Kept as an alias for source compatibility. */
|
|
79
|
+
export type DeclarativeStep = FlowNode;
|
|
29
80
|
export interface DeclarativeFlow {
|
|
30
81
|
kind: "declarative";
|
|
31
82
|
id: string;
|
|
32
|
-
|
|
83
|
+
/** The flow's node tree (top-level sequence). */
|
|
84
|
+
steps: FlowNode[];
|
|
33
85
|
handlers: Record<string, StepHandler>;
|
|
34
86
|
}
|
|
35
87
|
/** The context passed to an imperative orchestration function. */
|
package/dist/worker.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// the ADR 0044 spike proved.
|
|
14
14
|
import { WorkflowClient } from "./client.js";
|
|
15
15
|
import { replayOnce } from "./imperative.js";
|
|
16
|
+
import { walkNodes } from "./declarative.js";
|
|
16
17
|
import { jobType } from "./xml.js";
|
|
17
18
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
18
19
|
export class Worker {
|
|
@@ -54,11 +55,13 @@ export class Worker {
|
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
else {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// Walk the flow tree; only `run` steps are hosted locally. `signal`
|
|
59
|
+
// (message catch) and `task` (external worker) contribute no in-process
|
|
60
|
+
// route, and structural combinators (`switch`/`branch`/`loop`) are pure
|
|
61
|
+
// routing with no job of their own.
|
|
62
|
+
walkNodes(wf.steps, (s) => {
|
|
60
63
|
if (s.kind !== "run")
|
|
61
|
-
|
|
64
|
+
return;
|
|
62
65
|
const handler = wf.handlers[s.name];
|
|
63
66
|
if (typeof handler !== "function") {
|
|
64
67
|
// `DeclarativeFlow` is a public type; a consumer-constructed flow could
|
|
@@ -71,7 +74,7 @@ export class Worker {
|
|
|
71
74
|
workflowId: wf.id,
|
|
72
75
|
handle: async (job) => ({ variables: ((await handler(job)) ?? {}) }),
|
|
73
76
|
});
|
|
74
|
-
}
|
|
77
|
+
});
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
/** Register a derived job type, failing fast on a collision. Two workflows can
|
package/dist/xml.js
CHANGED
|
@@ -22,7 +22,34 @@ export function assertIdent(kind, value) {
|
|
|
22
22
|
export function assertWorkflowIds(wf) {
|
|
23
23
|
assertIdent("workflow id", wf.id);
|
|
24
24
|
if (wf.kind === "declarative") {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
assertNodeNames(wf.steps);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function assertNodeNames(nodes) {
|
|
29
|
+
for (const node of nodes) {
|
|
30
|
+
switch (node.kind) {
|
|
31
|
+
case "run":
|
|
32
|
+
case "task":
|
|
33
|
+
case "signal":
|
|
34
|
+
assertIdent("step name", node.name);
|
|
35
|
+
break;
|
|
36
|
+
case "switch":
|
|
37
|
+
for (const c of node.cases)
|
|
38
|
+
assertNodeNames(c.body);
|
|
39
|
+
if (node.default)
|
|
40
|
+
assertNodeNames(node.default);
|
|
41
|
+
break;
|
|
42
|
+
case "branch":
|
|
43
|
+
assertNodeNames(node.then);
|
|
44
|
+
if (node.else)
|
|
45
|
+
assertNodeNames(node.else);
|
|
46
|
+
break;
|
|
47
|
+
case "loop":
|
|
48
|
+
assertNodeNames(node.body);
|
|
49
|
+
break;
|
|
50
|
+
case "break":
|
|
51
|
+
case "continue":
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
27
54
|
}
|
|
28
55
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/workflow",
|
|
3
|
-
"version": "0.
|
|
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. Talks to a running nanobpmn gateway over the REST v2 API.",
|
|
3
|
+
"version": "0.3.0",
|
|
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 over the REST v2 API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -37,8 +37,17 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.20.1",
|
|
40
|
+
"bpmn-auto-layout": "^1.3.0",
|
|
40
41
|
"typescript": "^5.6.3"
|
|
41
42
|
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"bpmn-auto-layout": "^1.3.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"bpmn-auto-layout": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
42
51
|
"publishConfig": {
|
|
43
52
|
"access": "public"
|
|
44
53
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Minimal ambient types for `bpmn-auto-layout` (1.3.0 ships none). `layoutProcess`
|
|
2
|
+
// adds diagram interchange (DI) to a BPMN XML string. The resolved shape changed
|
|
3
|
+
// across versions: 1.3.x resolves to the laid-out XML string; >= 1.4 / `main`
|
|
4
|
+
// resolves to `{ xml, warnings }`. We type the union and normalize at the call
|
|
5
|
+
// site (see `layout.ts`).
|
|
6
|
+
declare module "bpmn-auto-layout" {
|
|
7
|
+
export function layoutProcess(
|
|
8
|
+
xml: string,
|
|
9
|
+
): Promise<string | { xml: string; warnings?: unknown[] }>;
|
|
10
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// deploy a workflow's derived model, start instances, correlate signals, and the
|
|
3
3
|
// low-level job activate/complete/fail used by the Worker runtime.
|
|
4
4
|
|
|
5
|
-
import { declarativeToBpmn } from "./declarative.js";
|
|
5
|
+
import { declarativeToBpmn, walkNodes } from "./declarative.js";
|
|
6
6
|
import { imperativeToBpmn } from "./imperative.js";
|
|
7
7
|
import type { DeclarativeFlow, DeployResult, Job, JsonObject, StartResult, Workflow } from "./types.js";
|
|
8
8
|
import { assertWorkflowIds, messageName } from "./xml.js";
|
|
@@ -111,7 +111,12 @@ export class WorkflowClient {
|
|
|
111
111
|
correlationKey: string,
|
|
112
112
|
variables: JsonObject = {},
|
|
113
113
|
): Promise<JsonObject> {
|
|
114
|
-
|
|
114
|
+
// Signal steps can live anywhere in the tree (inside switch/branch/loop),
|
|
115
|
+
// so walk the whole flow, not just the top-level sequence.
|
|
116
|
+
const signals: string[] = [];
|
|
117
|
+
walkNodes(flow.steps, (n) => {
|
|
118
|
+
if (n.kind === "signal") signals.push(n.name);
|
|
119
|
+
});
|
|
115
120
|
if (!signals.includes(signalName)) {
|
|
116
121
|
throw new WorkflowError(
|
|
117
122
|
`unknown signal "${signalName}" on flow "${flow.id}" — declared signals: ${
|