@nanobpm/workflow 0.8.1 → 0.9.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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/dist/declarative.d.ts +172 -10
  3. package/dist/declarative.js +213 -355
  4. package/dist/nodes/branch.d.ts +11 -0
  5. package/dist/nodes/branch.js +40 -0
  6. package/dist/nodes/break.d.ts +8 -0
  7. package/dist/nodes/break.js +19 -0
  8. package/dist/nodes/continue.d.ts +8 -0
  9. package/dist/nodes/continue.js +18 -0
  10. package/dist/nodes/forEach.d.ts +24 -0
  11. package/dist/nodes/forEach.js +104 -0
  12. package/dist/nodes/index.d.ts +11 -0
  13. package/dist/nodes/index.js +18 -0
  14. package/dist/nodes/loop.d.ts +9 -0
  15. package/dist/nodes/loop.js +25 -0
  16. package/dist/nodes/parallel.d.ts +9 -0
  17. package/dist/nodes/parallel.js +40 -0
  18. package/dist/nodes/registry.d.ts +33 -0
  19. package/dist/nodes/registry.js +71 -0
  20. package/dist/nodes/run.d.ts +10 -0
  21. package/dist/nodes/run.js +21 -0
  22. package/dist/nodes/signal.d.ts +11 -0
  23. package/dist/nodes/signal.js +21 -0
  24. package/dist/nodes/switch.d.ts +11 -0
  25. package/dist/nodes/switch.js +51 -0
  26. package/dist/nodes/task.d.ts +11 -0
  27. package/dist/nodes/task.js +21 -0
  28. package/dist/nodes/timer.d.ts +9 -0
  29. package/dist/nodes/timer.js +34 -0
  30. package/dist/test-support/diff.d.ts +8 -0
  31. package/dist/test-support/diff.js +70 -0
  32. package/dist/test-support/index.d.ts +8 -0
  33. package/dist/test-support/index.js +49 -0
  34. package/dist/test-support/normalize.d.ts +19 -0
  35. package/dist/test-support/normalize.js +245 -0
  36. package/dist/test-support/parity.d.ts +28 -0
  37. package/dist/test-support/parity.js +61 -0
  38. package/dist/test-support/xml.d.ts +17 -0
  39. package/dist/test-support/xml.js +135 -0
  40. package/dist/types.d.ts +20 -56
  41. package/package.json +14 -6
  42. package/src/declarative.ts +327 -359
  43. package/src/nodes/README.md +123 -0
  44. package/src/nodes/branch.ts +48 -0
  45. package/src/nodes/break.ts +25 -0
  46. package/src/nodes/continue.ts +24 -0
  47. package/src/nodes/forEach.ts +137 -0
  48. package/src/nodes/index.ts +19 -0
  49. package/src/nodes/loop.ts +34 -0
  50. package/src/nodes/parallel.ts +48 -0
  51. package/src/nodes/registry.ts +105 -0
  52. package/src/nodes/run.ts +29 -0
  53. package/src/nodes/signal.ts +29 -0
  54. package/src/nodes/switch.ts +56 -0
  55. package/src/nodes/task.ts +29 -0
  56. package/src/nodes/timer.ts +42 -0
  57. package/src/test-support/diff.ts +69 -0
  58. package/src/test-support/index.ts +53 -0
  59. package/src/test-support/normalize.ts +274 -0
  60. package/src/test-support/parity.ts +85 -0
  61. package/src/test-support/xml.ts +140 -0
  62. package/src/types.ts +20 -30
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @nanobpm/workflow
2
2
 
3
- Code-first durable orchestration for [nanobpmn](https://github.com/Magikcraft/nano-bpm)
3
+ Code-first durable orchestration for [nanobpmn](https://nanobpm.io)
4
4
  (**ADR 0044**). Author durable workflows as ordinary async code; the SDK derives
5
5
  the executable BPMN model, the job types, and the message/correlation wiring, and
6
6
  hosts a generic worker. No diagram, no task-type wiring, no correlation plumbing
@@ -1,20 +1,21 @@
1
- import type { DeclarativeFlow, FlowContracts, FlowNode, JsonObject } from "./types.js";
1
+ import type { DeclarativeFlow, FlowContracts, FlowNode, JsonObject, NodeEnvelopes, StepHandler, TimerStart } from "./types.js";
2
2
  import type { Envelope } from "./envelope.js";
3
+ import "./nodes/index.js";
3
4
  /** The TS payload type of a contract's input envelope (untyped fallback). */
4
- type InPayload<Ct> = Ct extends {
5
+ export type InPayload<Ct> = Ct extends {
5
6
  in: Envelope;
6
7
  } ? Ct["in"]["type"] & JsonObject : JsonObject;
7
8
  /** The TS payload type of a contract's output envelope (untyped fallback). */
8
- type OutPayload<Ct> = Ct extends {
9
+ export type OutPayload<Ct> = Ct extends {
9
10
  out: Envelope;
10
11
  } ? Ct["out"]["type"] & JsonObject : JsonObject;
11
12
  /** The input payload type of step `K` under contracts `C`. */
12
- type VarsOf<C, K extends string> = K extends keyof C ? InPayload<C[K]> : JsonObject;
13
+ export type VarsOf<C, K extends string> = K extends keyof C ? InPayload<C[K]> : JsonObject;
13
14
  /** The output payload type of step `K` under contracts `C`. */
14
- type ResultOf<C, K extends string> = K extends keyof C ? OutPayload<C[K]> : JsonObject;
15
+ export type ResultOf<C, K extends string> = K extends keyof C ? OutPayload<C[K]> : JsonObject;
15
16
  /** A typed handler for a `run` step: its job variables and result are resolved
16
17
  * from the flow contracts by the step name. */
17
- type TypedHandler<V extends JsonObject, R extends JsonObject> = (job: {
18
+ export type TypedHandler<V extends JsonObject, R extends JsonObject> = (job: {
18
19
  jobKey: string;
19
20
  processInstanceKey: string;
20
21
  elementId: string;
@@ -22,7 +23,7 @@ type TypedHandler<V extends JsonObject, R extends JsonObject> = (job: {
22
23
  variables: V;
23
24
  }) => Promise<R | void> | R | void;
24
25
  /** A block: the callback that populates a nested body (a case, arm, or loop). */
25
- type Block<C extends object> = (b: FlowBuilder<C>) => void;
26
+ export type Block<C extends object = object> = (b: FlowBuilder<C>) => void;
26
27
  /** The typed builder for a flow body.
27
28
  *
28
29
  * `C` is the flow's contracts map (step name → `{ in, out }` envelopes). The
@@ -157,6 +158,62 @@ export interface FlowBuilder<C extends object = object> {
157
158
  * the body. Only valid inside a `loop`. */
158
159
  continue(): FlowBuilder<C>;
159
160
  }
161
+ /** A builder method as stored on the dynamically-assembled builder. Its precise,
162
+ * generic user-facing signature comes from the `FlowBuilder<C>` interface (a
163
+ * built-in method declared centrally, a slice method declaration-merged from
164
+ * its module); this loose shape is only what the registry stores/installs. */
165
+ export type BuilderMethod = (...args: never[]) => unknown;
166
+ /** The per-body authoring context a kind module's `build` factory closes over to
167
+ * implement its builder method. Everything a method needs to append its node —
168
+ * claim its step name, resolve contract envelopes, recurse into nested bodies,
169
+ * and return the builder for chaining — without reaching into `declarative.ts`
170
+ * internals or the shared `FlowNode` union. */
171
+ export interface BuildApi {
172
+ /** The flow id (for error messages). */
173
+ readonly id: string;
174
+ /** Whether this is the root (top-level) builder — e.g. `startOn` is root-only. */
175
+ readonly isRoot: boolean;
176
+ /** The node list this builder appends to (`api.out.push(node)`). */
177
+ readonly out: FlowNode[];
178
+ /** The flow's typed I/O contracts, keyed by step name. */
179
+ readonly contracts: FlowContracts;
180
+ /** The flow's handler registry — a `run`-like kind registers its handler here. */
181
+ readonly handlers: Record<string, StepHandler>;
182
+ /** The current loop-nesting depth (0 outside any loop) — `break`/`continue`
183
+ * guard on it. */
184
+ readonly loopDepth: number;
185
+ /** The builder itself, for chaining (`return api.self()`). */
186
+ self(): FlowBuilder;
187
+ /** Populate a nested body block, returning its node list. `inLoop` deepens the
188
+ * loop nesting (a `loop` body); use it for bodies that can `break`/`continue`. */
189
+ child(fn: (b: FlowBuilder) => void, inLoop: boolean): FlowNode[];
190
+ /** Populate a nested body that runs in its OWN token scope (an embedded
191
+ * sub-process): `break`/`continue` cannot cross the boundary (loop depth 0). */
192
+ childScoped(fn: (b: FlowBuilder) => void): FlowNode[];
193
+ /** Claim a step name (rejects duplicates / reserved ids), as every leaf does. */
194
+ claim(name: string): void;
195
+ /** Resolve the declared envelopes for a step name from the contracts. */
196
+ contractEnvelopes(name: string): {
197
+ in?: Envelope;
198
+ out?: Envelope;
199
+ } | undefined;
200
+ /** Lift a durable timer start onto the flow (used only by `startOn`). */
201
+ setStartTimer(t: TimerStart): void;
202
+ }
203
+ /** A dynamically-assembled builder satisfies `FlowBuilder<C>` once every core
204
+ * built-in method is installed. The check is a runtime type guard (not an `as`
205
+ * cast): each own-property must be a function, AND every built-in method in
206
+ * {@link BUILTIN_BUILDER_METHODS} must be present — so a missing or
207
+ * tree-shaken node-kind registration fails fast here at builder assembly (a
208
+ * clear "missing registered methods" error) instead of surfacing later as an
209
+ * opaque `w.run is not a function` at authoring time. */
210
+ export declare function isFlowBuilder<C extends object = object>(x: object): x is FlowBuilder<C>;
211
+ /** Diagnostic for a failed builder assembly. Names the missing built-in
212
+ * method(s) when a registration was tree-shaken/omitted so the failure is
213
+ * actionable; otherwise, when every built-in is present but some own-property
214
+ * isn't a function, names that offending property instead. Exported (like
215
+ * {@link isFlowBuilder}) so the assembly invariant is unit-testable. */
216
+ export declare function assemblyFailureMessage(x: object): string;
160
217
  /**
161
218
  * Define a declarative flow. Pass a typed `contracts` map (keyed by step name)
162
219
  * to type each step's I/O and lift its data envelopes into the model; or omit it
@@ -164,8 +221,13 @@ export interface FlowBuilder<C extends object = object> {
164
221
  */
165
222
  export declare function defineFlow<C extends FlowContracts>(id: string, contracts: C, build: (w: FlowBuilder<C>) => void): DeclarativeFlow;
166
223
  export declare function defineFlow(id: string, build: (w: FlowBuilder) => void): DeclarativeFlow;
167
- /** Depth-first visit of every node in a flow tree (structural combinators
168
- * recurse into their bodies). */
224
+ /** Depth-first visit of every node in a flow tree. Recursion into a kind's
225
+ * nested bodies is DISPATCHED through the registry (each kind's `walk` handler),
226
+ * so a slice's structural combinator recurses without editing a central switch.
227
+ * Fails fast (via `requireNodeKind`) on an unregistered kind — matching the
228
+ * emitter — so a tree-shaken/missing registration surfaces as a clear error
229
+ * rather than silently skipping recursion into that node's nested bodies. A
230
+ * registered leaf kind with no `walk` handler is fine (nothing to recurse). */
169
231
  export declare function walkNodes(nodes: FlowNode[], visit: (n: FlowNode) => void): void;
170
232
  /** The job types of a flow's external `task` steps (anywhere in the tree) — the
171
233
  * contract workers outside this program must subscribe to. Each is the derived
@@ -173,6 +235,106 @@ export declare function walkNodes(nodes: FlowNode[], visit: (n: FlowNode) => voi
173
235
  * { jobType })`. Deduplicated (preserving first-seen order) since several steps
174
236
  * may intentionally share one override token. */
175
237
  export declare function externalJobTypes(flow: DeclarativeFlow): string[];
238
+ /** A renderable BPMN element the emitter has placed. A kind's `emit` handler
239
+ * adds one (or more) via `api.addNode(...)` (or a typed helper like
240
+ * `api.addServiceTask`). */
241
+ export interface RenderNode {
242
+ id: string;
243
+ render(incoming: string[], outgoing: string[]): string;
244
+ /** For exclusive gateways: the flow id of the unconditional default edge. */
245
+ defaultFlow?: string;
246
+ /** Id of the embedded sub-process this node lives in, or undefined at the
247
+ * top level (the root process). */
248
+ scope?: string;
249
+ }
250
+ /** A BPMN sequence flow being wired. A kind's `emit` handler creates danglers
251
+ * with `api.newEdge(from)` and resolves them with `api.connect(edges, toId)`. */
252
+ export interface Edge {
253
+ id: string;
254
+ from: string;
255
+ to?: string;
256
+ condition?: string;
257
+ name?: string;
258
+ /** The scope (sub-process id, or undefined = root process) this flow is
259
+ * nested in — so it renders inside the right container. */
260
+ scope?: string;
261
+ }
262
+ /** The enclosing-loop context threaded through `emit`: `break`/`continue` route
263
+ * to `headId`, and `break` danglers collect in `breaks`. `null` outside a loop. */
264
+ export interface LoopCtx {
265
+ headId: string;
266
+ breaks: Edge[];
267
+ }
268
+ /** The emitter primitives a kind's `emit` handler uses to place its BPMN nodes
269
+ * and wire its sequence flows — WITHOUT reaching into `Compiler` internals or a
270
+ * central emit switch. Built-in kinds and slice-added kinds emit through this
271
+ * same surface. Node-render string helpers (`incomingOutgoing`, `escapeXml`, …)
272
+ * are exported alongside for building custom `RenderNode.render` closures. */
273
+ export interface EmitApi {
274
+ /** The flow id (for error messages). */
275
+ readonly flowId: string;
276
+ /** Create a new (dangling) sequence flow from `from`; resolve its target with
277
+ * `connect`. */
278
+ newEdge(from: string, opts?: {
279
+ condition?: string;
280
+ name?: string;
281
+ }): Edge;
282
+ /** Point every one of `incoming` at `toId`. */
283
+ connect(incoming: Edge[], toId: string): void;
284
+ /** Record a referenced data envelope so it is lifted to a `nano:shape`. */
285
+ recordEnvelope(env?: Envelope): void;
286
+ /** Emit a sequence of nodes, threading danglers; used to emit nested bodies. */
287
+ emitList(list: FlowNode[], incoming: Edge[], loop: LoopCtx | null): Edge[];
288
+ /** Place a fully custom BPMN element (a slice's own render closure). */
289
+ addNode(node: RenderNode): void;
290
+ /** Place a `<bpmn:serviceTask>` with the standard taskDefinition/envelope
291
+ * extension elements (optionally with multi-instance characteristics). */
292
+ addServiceTask(node: {
293
+ name: string;
294
+ envelopes?: NodeEnvelopes;
295
+ jobType?: string;
296
+ }, mi?: string): void;
297
+ /** Place a `<bpmn:intermediateCatchEvent>` with a message event definition. */
298
+ addCatchEvent(node: {
299
+ name: string;
300
+ }): void;
301
+ /** Place a `<bpmn:intermediateCatchEvent>` with a timer event definition. */
302
+ addTimerCatchEvent(node: {
303
+ name: string;
304
+ after?: string;
305
+ at?: string;
306
+ }): void;
307
+ /** Place a `<bpmn:exclusiveGateway>`, returning it so `defaultFlow` can be set. */
308
+ addGateway(id: string, name?: string): RenderNode;
309
+ /** Place a `<bpmn:parallelGateway>`. */
310
+ addParallelGateway(id: string): RenderNode;
311
+ /** Place an embedded `<bpmn:subProcess>` whose body renders in its own scope. */
312
+ addSubProcess(id: string, mi: string): RenderNode;
313
+ /** Place a plain none `<bpmn:startEvent>` (for an embedded sub-process). */
314
+ addPlainStart(id: string): void;
315
+ /** Place a plain `<bpmn:endEvent>` (for an embedded sub-process). */
316
+ addPlainEnd(id: string): void;
317
+ /** A fresh monotonic counter value for generated gateway/loop/sub ids. */
318
+ nextGw(): number;
319
+ /** The current scope (enclosing sub-process id, or undefined at the top level). */
320
+ currentScope(): string | undefined;
321
+ /** Open a sub-process scope (subsequent nodes/edges nest inside it). */
322
+ pushScope(id: string): void;
323
+ /** Close the current sub-process scope. */
324
+ popScope(): void;
325
+ }
176
326
  /** Derive an executable BPMN model from a declarative flow. */
177
327
  export declare function declarativeToBpmn(flow: DeclarativeFlow): string;
178
- export {};
328
+ /** Wrap a raw FEEL expression as a Zeebe condition body (leading `=`). Exported
329
+ * for slice-added combinators that emit conditional sequence flows. */
330
+ export declare const feel: (expr: string) => string;
331
+ /** A FEEL equality test `subject = "value"`, with the value as a FEEL string.
332
+ * Exported for slice-added combinators that emit an XOR gateway. */
333
+ export declare const feelEquals: (subject: string, value: string) => string;
334
+ /** Render the `<bpmn:incoming>`/`<bpmn:outgoing>` refs of a flow node. Exported
335
+ * for slice `RenderNode.render` closures. */
336
+ export declare function incomingOutgoing(inc: string[], outg: string[]): string;
337
+ /** Render only the `<bpmn:incoming>` refs (for end-like events). Exported. */
338
+ export declare const incomingOnly: (inc: string[]) => string;
339
+ /** Render only the `<bpmn:outgoing>` refs (for start-like events). Exported. */
340
+ export declare const outgoingOnly: (outg: string[]) => string;