@nanobpm/workflow 0.4.0 → 0.5.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 CHANGED
@@ -183,7 +183,25 @@ once.
183
183
  | `externalJobTypes(flow)` | The job types of a flow's external `task` steps (each overridable per-step via `w.task(name, { jobType })`). |
184
184
  | `WorkflowClient` | `deploy`, `start`, `signal`, `getInstance` over REST v2. |
185
185
  | `Worker` | Generic job runtime; routes job types → handlers, hosts the replay loop. |
186
- | `toBpmn(workflow)` | The derived BPMN XML (for inspection / deployment). |
186
+ | `toBpmn(workflow)` | The DI-less semantic BPMN the engine runs (for inspection / deployment). |
187
+ | `toDeployableBpmn(workflow, { layout? })` | The deployable BPMN **with an auto-generated diagram (DI)** so the deployed model is inspectable; what `deploy` sends by default. |
188
+ | `layoutBpmn(xml)` / `declarativeToLayoutedBpmn(flow)` | Add DI to a model with `bpmn-auto-layout`. |
189
+
190
+ ### Inspectable deployments (diagram layout)
191
+
192
+ `client.deploy(flow)` deploys the model **with an auto-generated diagram
193
+ interchange (DI)** so it opens rendered and inspectable in a modeller/Operate
194
+ rather than as a blank canvas. DI is generated with the optional peer dependency
195
+ [`bpmn-auto-layout`](https://www.npmjs.com/package/bpmn-auto-layout):
196
+
197
+ ```sh
198
+ npm i bpmn-auto-layout
199
+ ```
200
+
201
+ If it is not installed, `deploy` degrades gracefully — it warns once and deploys
202
+ the DI-less model. Pass `deploy(flow, { layout: false })` to skip layout
203
+ deliberately. The semantic model stays authoritative; DI is derived and
204
+ regenerable.
187
205
 
188
206
  See [ADR 0044](../docs/adr/0044-code-first-durable-orchestration.md) for the design
189
207
  rationale and [ADR 0047](../docs/adr/0047-declarative-flow-control-and-typed-envelopes.md)
package/dist/client.d.ts CHANGED
@@ -1,6 +1,21 @@
1
1
  import type { DeclarativeFlow, DeployResult, Job, JsonObject, StartResult, Workflow } from "./types.js";
2
2
  /** Render a workflow (either surface) to its executable BPMN model. */
3
3
  export declare function toBpmn(wf: Workflow): string;
4
+ /**
5
+ * Render a workflow to *deployable* BPMN — the executable model plus diagram
6
+ * interchange (DI), auto-generated with `bpmn-auto-layout` — so the deployed
7
+ * process opens rendered and inspectable in a modeller/Operate rather than as a
8
+ * blank canvas. The semantic model stays authoritative; DI is derived.
9
+ *
10
+ * DI generation needs the optional peer dependency `bpmn-auto-layout`. When it is
11
+ * absent, this degrades gracefully: it warns once and returns the DI-less model
12
+ * so `deploy` still works everywhere. Pass `{ layout: false }` to skip layout
13
+ * deliberately. A genuine layout failure (the dep is installed but errors) is
14
+ * surfaced, not swallowed.
15
+ */
16
+ export declare function toDeployableBpmn(wf: Workflow, opts?: {
17
+ layout?: boolean;
18
+ }): Promise<string>;
4
19
  type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
5
20
  export interface WorkflowClientOptions {
6
21
  /** Base URL of the nanobpmn gateway, e.g. `http://localhost:8080`. */
@@ -29,8 +44,16 @@ export declare class WorkflowClient {
29
44
  /** Perform a request, throwing WorkflowError on transport error or !ok, and
30
45
  * return the raw Response (for endpoints with an empty/no-content body). */
31
46
  private send;
32
- /** Deploy a workflow's derived BPMN model. */
33
- deploy(wf: Workflow): Promise<DeployResult>;
47
+ /**
48
+ * Deploy a workflow's derived BPMN model. The deployed model includes
49
+ * auto-generated diagram interchange (DI) so it is inspectable in a
50
+ * modeller/Operate; pass `{ layout: false }` to deploy the DI-less semantic
51
+ * model. DI needs the optional `bpmn-auto-layout` dependency — see
52
+ * `toDeployableBpmn` for the graceful-degradation behaviour when it is absent.
53
+ */
54
+ deploy(wf: Workflow, opts?: {
55
+ layout?: boolean;
56
+ }): Promise<DeployResult>;
34
57
  /**
35
58
  * Start a workflow instance. For imperative workflows the engine variables are
36
59
  * seeded with `{ input, journal: {}, wfDone: false }` (the replay state); for
package/dist/client.js CHANGED
@@ -3,12 +3,50 @@
3
3
  // low-level job activate/complete/fail used by the Worker runtime.
4
4
  import { declarativeToBpmn, walkNodes } from "./declarative.js";
5
5
  import { imperativeToBpmn } from "./imperative.js";
6
+ import { layoutBpmn } from "./layout.js";
6
7
  import { assertWorkflowIds, messageName } from "./xml.js";
7
8
  /** Render a workflow (either surface) to its executable BPMN model. */
8
9
  export function toBpmn(wf) {
9
10
  assertWorkflowIds(wf);
10
11
  return wf.kind === "imperative" ? imperativeToBpmn(wf) : declarativeToBpmn(wf);
11
12
  }
13
+ let warnedNoLayout = false;
14
+ /**
15
+ * Render a workflow to *deployable* BPMN — the executable model plus diagram
16
+ * interchange (DI), auto-generated with `bpmn-auto-layout` — so the deployed
17
+ * process opens rendered and inspectable in a modeller/Operate rather than as a
18
+ * blank canvas. The semantic model stays authoritative; DI is derived.
19
+ *
20
+ * DI generation needs the optional peer dependency `bpmn-auto-layout`. When it is
21
+ * absent, this degrades gracefully: it warns once and returns the DI-less model
22
+ * so `deploy` still works everywhere. Pass `{ layout: false }` to skip layout
23
+ * deliberately. A genuine layout failure (the dep is installed but errors) is
24
+ * surfaced, not swallowed.
25
+ */
26
+ export async function toDeployableBpmn(wf, opts = {}) {
27
+ const semantic = toBpmn(wf);
28
+ if (opts.layout === false)
29
+ return semantic;
30
+ try {
31
+ return await layoutBpmn(semantic);
32
+ }
33
+ catch (e) {
34
+ // Only fall back for the "optional dep not installed" case (layoutBpmn wraps
35
+ // it with an ERR_MODULE_NOT_FOUND cause). Any other failure is a real layout
36
+ // problem and must surface rather than silently deploy an uninspectable model.
37
+ const cause = e?.cause;
38
+ if (cause?.code !== "ERR_MODULE_NOT_FOUND")
39
+ throw e;
40
+ if (!warnedNoLayout) {
41
+ warnedNoLayout = true;
42
+ console.warn(`[@nanobpm/workflow] Deploying "${wf.id}" without a diagram: the optional ` +
43
+ '"bpmn-auto-layout" dependency is not installed, so the model will be ' +
44
+ "uninspectable in a modeller/Operate. Install it for diagram layout: " +
45
+ "npm i bpmn-auto-layout");
46
+ }
47
+ return semantic;
48
+ }
49
+ }
12
50
  export class WorkflowError extends Error {
13
51
  status;
14
52
  body;
@@ -51,9 +89,15 @@ export class WorkflowClient {
51
89
  }
52
90
  return res;
53
91
  }
54
- /** Deploy a workflow's derived BPMN model. */
55
- async deploy(wf) {
56
- const xml = toBpmn(wf);
92
+ /**
93
+ * Deploy a workflow's derived BPMN model. The deployed model includes
94
+ * auto-generated diagram interchange (DI) so it is inspectable in a
95
+ * modeller/Operate; pass `{ layout: false }` to deploy the DI-less semantic
96
+ * model. DI needs the optional `bpmn-auto-layout` dependency — see
97
+ * `toDeployableBpmn` for the graceful-degradation behaviour when it is absent.
98
+ */
99
+ async deploy(wf, opts = {}) {
100
+ const xml = await toDeployableBpmn(wf, opts);
57
101
  const form = new FormData();
58
102
  form.append("resources", new Blob([xml], { type: "text/xml" }), `${wf.id}.bpmn`);
59
103
  return this.json(`/v2/deployments`, { method: "POST", body: form }, "deploy");
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export type { FlowBuilder } from "./declarative.js";
5
5
  export { layoutBpmn, declarativeToLayoutedBpmn } from "./layout.js";
6
6
  export { envelope } from "./envelope.js";
7
7
  export type { Envelope, EnvelopeType, EnvelopeField, FieldSpec, ScalarType, ScalarTs, FieldTs, } from "./envelope.js";
8
- export { WorkflowClient, WorkflowError, toBpmn } from "./client.js";
8
+ export { WorkflowClient, WorkflowError, toBpmn, toDeployableBpmn } from "./client.js";
9
9
  export type { WorkflowClientOptions, ActivateOptions } from "./client.js";
10
10
  export { Worker } from "./worker.js";
11
11
  export type { WorkerOptions, ActivityEvent } from "./worker.js";
package/dist/index.js CHANGED
@@ -42,5 +42,5 @@ export { defineWorkflow, imperativeToBpmn, replayOnce } from "./imperative.js";
42
42
  export { defineFlow, declarativeToBpmn, externalJobTypes, walkNodes } from "./declarative.js";
43
43
  export { layoutBpmn, declarativeToLayoutedBpmn } from "./layout.js";
44
44
  export { envelope } from "./envelope.js";
45
- export { WorkflowClient, WorkflowError, toBpmn } from "./client.js";
45
+ export { WorkflowClient, WorkflowError, toBpmn, toDeployableBpmn } from "./client.js";
46
46
  export { Worker } from "./worker.js";
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@nanobpm/workflow",
3
- "version": "0.4.0",
3
+ "version": "0.5.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 over the REST v2 API.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/Magikcraft/nano-bpm",
10
- "directory": "workflow"
9
+ "url": "https://github.com/jwulf/nano-ide.git",
10
+ "directory": "packages/workflow"
11
11
  },
12
12
  "main": "./dist/index.js",
13
13
  "module": "./dist/index.js",
package/src/client.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { declarativeToBpmn, walkNodes } from "./declarative.js";
6
6
  import { imperativeToBpmn } from "./imperative.js";
7
+ import { layoutBpmn } from "./layout.js";
7
8
  import type { DeclarativeFlow, DeployResult, Job, JsonObject, StartResult, Workflow } from "./types.js";
8
9
  import { assertWorkflowIds, messageName } from "./xml.js";
9
10
 
@@ -13,6 +14,47 @@ export function toBpmn(wf: Workflow): string {
13
14
  return wf.kind === "imperative" ? imperativeToBpmn(wf) : declarativeToBpmn(wf);
14
15
  }
15
16
 
17
+ let warnedNoLayout = false;
18
+
19
+ /**
20
+ * Render a workflow to *deployable* BPMN — the executable model plus diagram
21
+ * interchange (DI), auto-generated with `bpmn-auto-layout` — so the deployed
22
+ * process opens rendered and inspectable in a modeller/Operate rather than as a
23
+ * blank canvas. The semantic model stays authoritative; DI is derived.
24
+ *
25
+ * DI generation needs the optional peer dependency `bpmn-auto-layout`. When it is
26
+ * absent, this degrades gracefully: it warns once and returns the DI-less model
27
+ * so `deploy` still works everywhere. Pass `{ layout: false }` to skip layout
28
+ * deliberately. A genuine layout failure (the dep is installed but errors) is
29
+ * surfaced, not swallowed.
30
+ */
31
+ export async function toDeployableBpmn(
32
+ wf: Workflow,
33
+ opts: { layout?: boolean } = {},
34
+ ): Promise<string> {
35
+ const semantic = toBpmn(wf);
36
+ if (opts.layout === false) return semantic;
37
+ try {
38
+ return await layoutBpmn(semantic);
39
+ } catch (e) {
40
+ // Only fall back for the "optional dep not installed" case (layoutBpmn wraps
41
+ // it with an ERR_MODULE_NOT_FOUND cause). Any other failure is a real layout
42
+ // problem and must surface rather than silently deploy an uninspectable model.
43
+ const cause = (e as { cause?: NodeJS.ErrnoException })?.cause;
44
+ if (cause?.code !== "ERR_MODULE_NOT_FOUND") throw e;
45
+ if (!warnedNoLayout) {
46
+ warnedNoLayout = true;
47
+ console.warn(
48
+ `[@nanobpm/workflow] Deploying "${wf.id}" without a diagram: the optional ` +
49
+ '"bpmn-auto-layout" dependency is not installed, so the model will be ' +
50
+ "uninspectable in a modeller/Operate. Install it for diagram layout: " +
51
+ "npm i bpmn-auto-layout",
52
+ );
53
+ }
54
+ return semantic;
55
+ }
56
+ }
57
+
16
58
  type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
17
59
 
18
60
  export interface WorkflowClientOptions {
@@ -75,9 +117,15 @@ export class WorkflowClient {
75
117
  return res;
76
118
  }
77
119
 
78
- /** Deploy a workflow's derived BPMN model. */
79
- async deploy(wf: Workflow): Promise<DeployResult> {
80
- const xml = toBpmn(wf);
120
+ /**
121
+ * Deploy a workflow's derived BPMN model. The deployed model includes
122
+ * auto-generated diagram interchange (DI) so it is inspectable in a
123
+ * modeller/Operate; pass `{ layout: false }` to deploy the DI-less semantic
124
+ * model. DI needs the optional `bpmn-auto-layout` dependency — see
125
+ * `toDeployableBpmn` for the graceful-degradation behaviour when it is absent.
126
+ */
127
+ async deploy(wf: Workflow, opts: { layout?: boolean } = {}): Promise<DeployResult> {
128
+ const xml = await toDeployableBpmn(wf, opts);
81
129
  const form = new FormData();
82
130
  form.append("resources", new Blob([xml], { type: "text/xml" }), `${wf.id}.bpmn`);
83
131
  return this.json<DeployResult>(`/v2/deployments`, { method: "POST", body: form }, "deploy");
package/src/index.ts CHANGED
@@ -54,7 +54,7 @@ export type {
54
54
  ScalarTs,
55
55
  FieldTs,
56
56
  } from "./envelope.js";
57
- export { WorkflowClient, WorkflowError, toBpmn } from "./client.js";
57
+ export { WorkflowClient, WorkflowError, toBpmn, toDeployableBpmn } from "./client.js";
58
58
  export type { WorkflowClientOptions, ActivateOptions } from "./client.js";
59
59
  export { Worker } from "./worker.js";
60
60
  export type { WorkerOptions, ActivityEvent } from "./worker.js";