@nanobpm/workflow 0.3.0 → 0.4.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 +4 -2
- package/dist/declarative.d.ts +17 -7
- package/dist/declarative.js +20 -8
- package/dist/layout.js +3 -2
- package/dist/types.d.ts +1 -0
- package/dist/xml.d.ts +1 -0
- package/dist/xml.js +15 -1
- package/package.json +3 -3
- package/src/bpmn-auto-layout.d.ts +5 -5
- package/src/declarative.ts +28 -13
- package/src/layout.ts +3 -2
- package/src/types.ts +1 -1
- package/src/xml.ts +17 -1
package/README.md
CHANGED
|
@@ -34,7 +34,9 @@ Both compile to the same engine durability; pick per workflow.
|
|
|
34
34
|
### Declarative (with control flow, human-in-the-loop signals, and typed I/O)
|
|
35
35
|
|
|
36
36
|
**The recommended surface.** Describe the flow as a **tree of nodes**: `w.run` (a
|
|
37
|
-
locally-hosted service task), `w.task` (an external-worker service task
|
|
37
|
+
locally-hosted service task), `w.task` (an external-worker service task — job
|
|
38
|
+
type `${flowId}:${name}` by default, or pass `w.task(name, { jobType })` to
|
|
39
|
+
target an existing worker pool, e.g. a `senior:pr-review` agent token), and
|
|
38
40
|
`w.signal` (a durable message catch that resumes via a correlated message — the
|
|
39
41
|
human-in-the-loop path), composed with control-flow combinators:
|
|
40
42
|
|
|
@@ -178,7 +180,7 @@ once.
|
|
|
178
180
|
| `defineWorkflow(id, orchFn)` | Imperative (replayed) workflow — experimental/internal. |
|
|
179
181
|
| `defineFlow(id, [contracts,] build)` | Declarative flow: `run`/`task`/`signal` + `switch`/`branch`/`loop`/`break`/`continue`, with an optional typed contracts map. |
|
|
180
182
|
| `envelope(name, fields)` | A typed data envelope; lifted into the model as a `nano:shape` + `dataEnvelope` wiring. |
|
|
181
|
-
| `externalJobTypes(flow)` | The
|
|
183
|
+
| `externalJobTypes(flow)` | The job types of a flow's external `task` steps (each overridable per-step via `w.task(name, { jobType })`). |
|
|
182
184
|
| `WorkflowClient` | `deploy`, `start`, `signal`, `getInstance` over REST v2. |
|
|
183
185
|
| `Worker` | Generic job runtime; routes job types → handlers, hosts the replay loop. |
|
|
184
186
|
| `toBpmn(workflow)` | The derived BPMN XML (for inspection / deployment). |
|
package/dist/declarative.d.ts
CHANGED
|
@@ -33,12 +33,19 @@ export interface FlowBuilder<C extends FlowContracts = Record<string, never>> {
|
|
|
33
33
|
run<K extends string>(name: K, handler: TypedHandler<VarsOf<C, K>, ResultOf<C, K>>): FlowBuilder<C>;
|
|
34
34
|
/**
|
|
35
35
|
* A durable activity served by a worker OUTSIDE this program (a BPMN service
|
|
36
|
-
* task
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
36
|
+
* task, but no locally-hosted handler). Its job type defaults to the derived
|
|
37
|
+
* `${flowId}:${name}`; pass `{ jobType }` to override it with an explicit
|
|
38
|
+
* worker token (e.g. a `rank:capability` token like `senior:pr-review` that a
|
|
39
|
+
* `c8ctl nano work` matrix subscribes to) so an existing pool of agents can
|
|
40
|
+
* service it without renaming the flow. The step name stays the BPMN element
|
|
41
|
+
* id; only the emitted `zeebe:taskDefinition` type changes. Use
|
|
42
|
+
* `externalJobTypes(flow)` to list the (possibly overridden) types those
|
|
43
|
+
* workers must poll. Its contract envelopes (if any) type the model, not a
|
|
44
|
+
* local handler.
|
|
40
45
|
*/
|
|
41
|
-
task<K extends string>(name: K
|
|
46
|
+
task<K extends string>(name: K, opts?: {
|
|
47
|
+
jobType?: string;
|
|
48
|
+
}): FlowBuilder<C>;
|
|
42
49
|
/**
|
|
43
50
|
* A durable wait for an external/human event, correlated on a process
|
|
44
51
|
* variable (a BPMN message intermediate catch event). Resume it with
|
|
@@ -89,8 +96,11 @@ export declare function defineFlow(id: string, build: (w: FlowBuilder) => void):
|
|
|
89
96
|
/** Depth-first visit of every node in a flow tree (structural combinators
|
|
90
97
|
* recurse into their bodies). */
|
|
91
98
|
export declare function walkNodes(nodes: FlowNode[], visit: (n: FlowNode) => void): void;
|
|
92
|
-
/** The
|
|
93
|
-
*
|
|
99
|
+
/** The job types of a flow's external `task` steps (anywhere in the tree) — the
|
|
100
|
+
* contract workers outside this program must subscribe to. Each is the derived
|
|
101
|
+
* `<flowId>:<stepName>` unless the step overrode it via `w.task(name,
|
|
102
|
+
* { jobType })`. Deduplicated (preserving first-seen order) since several steps
|
|
103
|
+
* may intentionally share one override token. */
|
|
94
104
|
export declare function externalJobTypes(flow: DeclarativeFlow): string[];
|
|
95
105
|
/** Derive an executable BPMN model from a declarative flow. */
|
|
96
106
|
export declare function declarativeToBpmn(flow: DeclarativeFlow): string;
|
package/dist/declarative.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// Typed data envelopes are LIFTED into the model (nano:shape + dataEnvelope
|
|
35
35
|
// zeebe:property), so the generated .bpmn is ejectable to model-first with its
|
|
36
36
|
// typed contracts intact.
|
|
37
|
-
import { assertIdent, escapeXml, jobType, messageName } from "./xml.js";
|
|
37
|
+
import { assertIdent, assertJobType, escapeXml, jobType, messageName } from "./xml.js";
|
|
38
38
|
/** Ids the emitter generates for structural nodes / flows / messages. A step
|
|
39
39
|
* name that collides with one of these would produce a duplicate BPMN id and an
|
|
40
40
|
* invalid model, so reject them at authoring time. */
|
|
@@ -77,9 +77,12 @@ function makeBuilder(id, out, ctx) {
|
|
|
77
77
|
out.push({ kind: "run", name, envelopes: contractEnvelopes(ctx, name) });
|
|
78
78
|
return b;
|
|
79
79
|
},
|
|
80
|
-
task(name) {
|
|
80
|
+
task(name, opts) {
|
|
81
81
|
claimName(ctx, id, name);
|
|
82
|
-
|
|
82
|
+
const override = opts?.jobType;
|
|
83
|
+
if (override !== undefined)
|
|
84
|
+
assertJobType("task jobType", override);
|
|
85
|
+
out.push({ kind: "task", name, envelopes: contractEnvelopes(ctx, name), jobType: override });
|
|
83
86
|
return b;
|
|
84
87
|
},
|
|
85
88
|
signal(name, opts) {
|
|
@@ -181,13 +184,22 @@ export function walkNodes(nodes, visit) {
|
|
|
181
184
|
}
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
|
-
/** The
|
|
185
|
-
*
|
|
187
|
+
/** The job types of a flow's external `task` steps (anywhere in the tree) — the
|
|
188
|
+
* contract workers outside this program must subscribe to. Each is the derived
|
|
189
|
+
* `<flowId>:<stepName>` unless the step overrode it via `w.task(name,
|
|
190
|
+
* { jobType })`. Deduplicated (preserving first-seen order) since several steps
|
|
191
|
+
* may intentionally share one override token. */
|
|
186
192
|
export function externalJobTypes(flow) {
|
|
193
|
+
const seen = new Set();
|
|
187
194
|
const types = [];
|
|
188
195
|
walkNodes(flow.steps, (n) => {
|
|
189
|
-
if (n.kind
|
|
190
|
-
|
|
196
|
+
if (n.kind !== "task")
|
|
197
|
+
return;
|
|
198
|
+
const type = n.jobType ?? jobType(flow.id, n.name);
|
|
199
|
+
if (seen.has(type))
|
|
200
|
+
return;
|
|
201
|
+
seen.add(type);
|
|
202
|
+
types.push(type);
|
|
191
203
|
});
|
|
192
204
|
return types;
|
|
193
205
|
}
|
|
@@ -224,7 +236,7 @@ class Compiler {
|
|
|
224
236
|
this.envelopes.set(env.name, env.fields);
|
|
225
237
|
}
|
|
226
238
|
addServiceTask(node) {
|
|
227
|
-
const type = jobType(this.flow.id, node.name);
|
|
239
|
+
const type = node.jobType ?? jobType(this.flow.id, node.name);
|
|
228
240
|
this.recordEnvelope(node.envelopes?.in);
|
|
229
241
|
this.recordEnvelope(node.envelopes?.out);
|
|
230
242
|
const props = [];
|
package/dist/layout.js
CHANGED
|
@@ -43,8 +43,9 @@ export async function layoutBpmn(bpmnXml) {
|
|
|
43
43
|
"Install it to generate diagram layout: npm i bpmn-auto-layout", { cause });
|
|
44
44
|
}
|
|
45
45
|
const result = await layoutProcess(bpmnXml);
|
|
46
|
-
// Version-shape normalization: bpmn-auto-layout >= 1.4
|
|
47
|
-
// `{ xml, warnings }`; 1.3.x
|
|
46
|
+
// Version-shape normalization: bpmn-auto-layout 2.x (and >= 1.4) resolves to
|
|
47
|
+
// `{ xml, warnings }`; 1.3.x resolved to the laid-out XML string directly. We
|
|
48
|
+
// pin >= 2.0.0-alpha.2 but normalize both so the SDK tolerates the whole range.
|
|
48
49
|
return typeof result === "string" ? result : result.xml;
|
|
49
50
|
}
|
|
50
51
|
/**
|
package/dist/types.d.ts
CHANGED
package/dist/xml.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export declare const messageName: (workflowId: string, step: string) => string;
|
|
|
7
7
|
/** The single orchestrator job type of an imperative workflow. */
|
|
8
8
|
export declare const orchestrateType: (workflowId: string) => string;
|
|
9
9
|
export declare function assertIdent(kind: string, value: string): void;
|
|
10
|
+
export declare function assertJobType(kind: string, value: string): void;
|
|
10
11
|
export declare function assertWorkflowIds(wf: Workflow): void;
|
package/dist/xml.js
CHANGED
|
@@ -19,6 +19,16 @@ export function assertIdent(kind, value) {
|
|
|
19
19
|
throw new Error(`${kind} "${value}" is not a valid BPMN identifier (expected an NCName)`);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
/** An explicit job type override (e.g. a `rank:capability` worker token like
|
|
23
|
+
* `senior:pr-review`). Unlike a step name it is NOT a BPMN element id, so it may
|
|
24
|
+
* carry the `:`/`+` token delimiters a `c8ctl nano work` matrix uses; it only
|
|
25
|
+
* has to be a non-empty, whitespace-free token. */
|
|
26
|
+
const JOB_TYPE = /^[A-Za-z0-9_][A-Za-z0-9_.:+-]*$/;
|
|
27
|
+
export function assertJobType(kind, value) {
|
|
28
|
+
if (typeof value !== "string" || !JOB_TYPE.test(value)) {
|
|
29
|
+
throw new Error(`${kind} "${value}" is not a valid job type (expected a non-empty token of letters, digits, and _ . : + -)`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
22
32
|
export function assertWorkflowIds(wf) {
|
|
23
33
|
assertIdent("workflow id", wf.id);
|
|
24
34
|
if (wf.kind === "declarative") {
|
|
@@ -29,10 +39,14 @@ function assertNodeNames(nodes) {
|
|
|
29
39
|
for (const node of nodes) {
|
|
30
40
|
switch (node.kind) {
|
|
31
41
|
case "run":
|
|
32
|
-
case "task":
|
|
33
42
|
case "signal":
|
|
34
43
|
assertIdent("step name", node.name);
|
|
35
44
|
break;
|
|
45
|
+
case "task":
|
|
46
|
+
assertIdent("step name", node.name);
|
|
47
|
+
if (node.jobType !== undefined)
|
|
48
|
+
assertJobType("job type", node.jobType);
|
|
49
|
+
break;
|
|
36
50
|
case "switch":
|
|
37
51
|
for (const c of node.cases)
|
|
38
52
|
assertNodeNames(c.body);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.20.1",
|
|
40
|
-
"bpmn-auto-layout": "^
|
|
40
|
+
"bpmn-auto-layout": "^2.0.0-alpha.2",
|
|
41
41
|
"typescript": "^5.6.3"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"bpmn-auto-layout": "^
|
|
44
|
+
"bpmn-auto-layout": "^2.0.0-alpha.2"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
|
47
47
|
"bpmn-auto-layout": {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Minimal ambient types for `bpmn-auto-layout` (
|
|
2
|
-
// adds diagram interchange (DI) to a BPMN XML string. The
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// site (see `layout.ts`).
|
|
1
|
+
// Minimal ambient types for `bpmn-auto-layout` (ships none of its own).
|
|
2
|
+
// `layoutProcess` adds diagram interchange (DI) to a BPMN XML string. The
|
|
3
|
+
// resolved shape has settled on `{ xml, warnings }` in 2.x (and >= 1.4); 1.3.x
|
|
4
|
+
// resolved to the laid-out XML string directly. We type the union and normalize
|
|
5
|
+
// at the call site (see `layout.ts`) so the SDK still works across that range.
|
|
6
6
|
declare module "bpmn-auto-layout" {
|
|
7
7
|
export function layoutProcess(
|
|
8
8
|
xml: string,
|
package/src/declarative.ts
CHANGED
|
@@ -46,7 +46,7 @@ import type {
|
|
|
46
46
|
SwitchCase,
|
|
47
47
|
} from "./types.js";
|
|
48
48
|
import type { Envelope, EnvelopeField } from "./envelope.js";
|
|
49
|
-
import { assertIdent, escapeXml, jobType, messageName } from "./xml.js";
|
|
49
|
+
import { assertIdent, assertJobType, escapeXml, jobType, messageName } from "./xml.js";
|
|
50
50
|
|
|
51
51
|
// --- Authoring surface -------------------------------------------------------
|
|
52
52
|
|
|
@@ -82,12 +82,17 @@ export interface FlowBuilder<C extends FlowContracts = Record<string, never>> {
|
|
|
82
82
|
run<K extends string>(name: K, handler: TypedHandler<VarsOf<C, K>, ResultOf<C, K>>): FlowBuilder<C>;
|
|
83
83
|
/**
|
|
84
84
|
* A durable activity served by a worker OUTSIDE this program (a BPMN service
|
|
85
|
-
* task
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
85
|
+
* task, but no locally-hosted handler). Its job type defaults to the derived
|
|
86
|
+
* `${flowId}:${name}`; pass `{ jobType }` to override it with an explicit
|
|
87
|
+
* worker token (e.g. a `rank:capability` token like `senior:pr-review` that a
|
|
88
|
+
* `c8ctl nano work` matrix subscribes to) so an existing pool of agents can
|
|
89
|
+
* service it without renaming the flow. The step name stays the BPMN element
|
|
90
|
+
* id; only the emitted `zeebe:taskDefinition` type changes. Use
|
|
91
|
+
* `externalJobTypes(flow)` to list the (possibly overridden) types those
|
|
92
|
+
* workers must poll. Its contract envelopes (if any) type the model, not a
|
|
93
|
+
* local handler.
|
|
89
94
|
*/
|
|
90
|
-
task<K extends string>(name: K): FlowBuilder<C>;
|
|
95
|
+
task<K extends string>(name: K, opts?: { jobType?: string }): FlowBuilder<C>;
|
|
91
96
|
/**
|
|
92
97
|
* A durable wait for an external/human event, correlated on a process
|
|
93
98
|
* variable (a BPMN message intermediate catch event). Resume it with
|
|
@@ -173,9 +178,11 @@ function makeBuilder<C extends FlowContracts>(id: string, out: FlowNode[], ctx:
|
|
|
173
178
|
out.push({ kind: "run", name, envelopes: contractEnvelopes(ctx, name) });
|
|
174
179
|
return b as unknown as FlowBuilder<C>;
|
|
175
180
|
},
|
|
176
|
-
task(name: string): FlowBuilder<C> {
|
|
181
|
+
task(name: string, opts?: { jobType?: string }): FlowBuilder<C> {
|
|
177
182
|
claimName(ctx, id, name);
|
|
178
|
-
|
|
183
|
+
const override = opts?.jobType;
|
|
184
|
+
if (override !== undefined) assertJobType("task jobType", override);
|
|
185
|
+
out.push({ kind: "task", name, envelopes: contractEnvelopes(ctx, name), jobType: override });
|
|
179
186
|
return b as unknown as FlowBuilder<C>;
|
|
180
187
|
},
|
|
181
188
|
signal(name: string, opts: { correlationKey: string }): FlowBuilder<C> {
|
|
@@ -286,12 +293,20 @@ export function walkNodes(nodes: FlowNode[], visit: (n: FlowNode) => void): void
|
|
|
286
293
|
}
|
|
287
294
|
}
|
|
288
295
|
|
|
289
|
-
/** The
|
|
290
|
-
*
|
|
296
|
+
/** The job types of a flow's external `task` steps (anywhere in the tree) — the
|
|
297
|
+
* contract workers outside this program must subscribe to. Each is the derived
|
|
298
|
+
* `<flowId>:<stepName>` unless the step overrode it via `w.task(name,
|
|
299
|
+
* { jobType })`. Deduplicated (preserving first-seen order) since several steps
|
|
300
|
+
* may intentionally share one override token. */
|
|
291
301
|
export function externalJobTypes(flow: DeclarativeFlow): string[] {
|
|
302
|
+
const seen = new Set<string>();
|
|
292
303
|
const types: string[] = [];
|
|
293
304
|
walkNodes(flow.steps, (n) => {
|
|
294
|
-
if (n.kind
|
|
305
|
+
if (n.kind !== "task") return;
|
|
306
|
+
const type = n.jobType ?? jobType(flow.id, n.name);
|
|
307
|
+
if (seen.has(type)) return;
|
|
308
|
+
seen.add(type);
|
|
309
|
+
types.push(type);
|
|
295
310
|
});
|
|
296
311
|
return types;
|
|
297
312
|
}
|
|
@@ -352,8 +367,8 @@ class Compiler {
|
|
|
352
367
|
this.envelopes.set(env.name, env.fields);
|
|
353
368
|
}
|
|
354
369
|
|
|
355
|
-
private addServiceTask(node: { name: string; envelopes?: NodeEnvelopes }): void {
|
|
356
|
-
const type = jobType(this.flow.id, node.name);
|
|
370
|
+
private addServiceTask(node: { name: string; envelopes?: NodeEnvelopes; jobType?: string }): void {
|
|
371
|
+
const type = node.jobType ?? jobType(this.flow.id, node.name);
|
|
357
372
|
this.recordEnvelope(node.envelopes?.in);
|
|
358
373
|
this.recordEnvelope(node.envelopes?.out);
|
|
359
374
|
const props: string[] = [];
|
package/src/layout.ts
CHANGED
|
@@ -48,8 +48,9 @@ export async function layoutBpmn(bpmnXml: string): Promise<string> {
|
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
const result = await layoutProcess(bpmnXml);
|
|
51
|
-
// Version-shape normalization: bpmn-auto-layout >= 1.4
|
|
52
|
-
// `{ xml, warnings }`; 1.3.x
|
|
51
|
+
// Version-shape normalization: bpmn-auto-layout 2.x (and >= 1.4) resolves to
|
|
52
|
+
// `{ xml, warnings }`; 1.3.x resolved to the laid-out XML string directly. We
|
|
53
|
+
// pin >= 2.0.0-alpha.2 but normalize both so the SDK tolerates the whole range.
|
|
53
54
|
return typeof result === "string" ? result : result.xml;
|
|
54
55
|
}
|
|
55
56
|
|
package/src/types.ts
CHANGED
|
@@ -51,7 +51,7 @@ export type StepHandler = (job: Job) => Promise<JsonObject | void> | JsonObject
|
|
|
51
51
|
* combinators carry nested `FlowNode[]` bodies. */
|
|
52
52
|
export type FlowNode =
|
|
53
53
|
| { kind: "run"; name: string; envelopes?: NodeEnvelopes }
|
|
54
|
-
| { kind: "task"; name: string; envelopes?: NodeEnvelopes }
|
|
54
|
+
| { kind: "task"; name: string; envelopes?: NodeEnvelopes; jobType?: string }
|
|
55
55
|
| { kind: "signal"; name: string; correlationKey: string; payload?: Envelope }
|
|
56
56
|
| { kind: "switch"; subject: string; cases: SwitchCase[]; default?: FlowNode[] }
|
|
57
57
|
| { kind: "branch"; condition: string; then: FlowNode[]; else?: FlowNode[] }
|
package/src/xml.ts
CHANGED
|
@@ -27,6 +27,19 @@ export function assertIdent(kind: string, value: string): void {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** An explicit job type override (e.g. a `rank:capability` worker token like
|
|
31
|
+
* `senior:pr-review`). Unlike a step name it is NOT a BPMN element id, so it may
|
|
32
|
+
* carry the `:`/`+` token delimiters a `c8ctl nano work` matrix uses; it only
|
|
33
|
+
* has to be a non-empty, whitespace-free token. */
|
|
34
|
+
const JOB_TYPE = /^[A-Za-z0-9_][A-Za-z0-9_.:+-]*$/;
|
|
35
|
+
export function assertJobType(kind: string, value: string): void {
|
|
36
|
+
if (typeof value !== "string" || !JOB_TYPE.test(value)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`${kind} "${value}" is not a valid job type (expected a non-empty token of letters, digits, and _ . : + -)`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
export function assertWorkflowIds(wf: Workflow): void {
|
|
31
44
|
assertIdent("workflow id", wf.id);
|
|
32
45
|
if (wf.kind === "declarative") {
|
|
@@ -38,10 +51,13 @@ function assertNodeNames(nodes: FlowNode[]): void {
|
|
|
38
51
|
for (const node of nodes) {
|
|
39
52
|
switch (node.kind) {
|
|
40
53
|
case "run":
|
|
41
|
-
case "task":
|
|
42
54
|
case "signal":
|
|
43
55
|
assertIdent("step name", node.name);
|
|
44
56
|
break;
|
|
57
|
+
case "task":
|
|
58
|
+
assertIdent("step name", node.name);
|
|
59
|
+
if (node.jobType !== undefined) assertJobType("job type", node.jobType);
|
|
60
|
+
break;
|
|
45
61
|
case "switch":
|
|
46
62
|
for (const c of node.cases) assertNodeNames(c.body);
|
|
47
63
|
if (node.default) assertNodeNames(node.default);
|