@nanobpm/urban 0.96.1 → 0.96.3
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/dist/cli.js +24 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/core/modules/dataops.d.ts +4 -4
- package/dist/runtime/core/modules/dataops.js +11 -10
- package/dist/runtime/core/modules/datasource.d.ts +6 -5
- package/dist/runtime/core/modules/datasource.js +8 -5
- package/dist/toolkit/artifact.d.ts +6 -4
- package/dist/toolkit/artifact.js +6 -6
- package/dist/toolkit/gen.d.ts +10 -0
- package/dist/toolkit/gen.js +3 -2
- package/dist/toolkit/generate.js +9 -1
- package/dist/toolkit/models.d.ts +7 -0
- package/dist/toolkit/models.js +4 -1
- package/dist/toolkit/scaffold/workers.d.ts +9 -6
- package/dist/toolkit/scaffold/workers.js +13 -5
- package/dist/toolkit/scaffold.d.ts +17 -0
- package/dist/toolkit/scaffold.js +9 -2
- package/package.json +3 -3
- package/src/cli-lazy.test.ts +172 -0
- package/src/cli.ts +25 -12
- package/src/index.ts +1 -0
- package/src/runtime/core/modules/dataops.ts +41 -37
- package/src/runtime/core/modules/datasource.ts +8 -5
- package/src/toolkit/artifact.ts +6 -6
- package/src/toolkit/gen.ts +13 -2
- package/src/toolkit/generate.test.ts +86 -1
- package/src/toolkit/generate.ts +9 -1
- package/src/toolkit/models.test.ts +21 -0
- package/src/toolkit/models.ts +10 -1
- package/src/toolkit/scaffold/workers.test.ts +65 -0
- package/src/toolkit/scaffold/workers.ts +14 -5
- package/src/toolkit/scaffold.ts +27 -1
|
@@ -146,6 +146,19 @@ test("planWorkerScaffold: skips a task type declared external (out-of-process wo
|
|
|
146
146
|
assert.equal(reasons["senior:review"], "external");
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
+
test("planWorkerScaffold: skips an inline-handled (w.run) task type — never stubs a hosted handler (nano-ide#595)", () => {
|
|
150
|
+
// A code-first `w.run` step's handler lives inline in the flow and is hosted by the in-process
|
|
151
|
+
// Worker; scaffolding a throwing stub for its job type would put a second, always-failing worker
|
|
152
|
+
// on the same job type. The derivation reports those types, and the planner must skip them.
|
|
153
|
+
const models = [
|
|
154
|
+
model("a.bpmn", "greet", serviceTask("hello", "greet:hello"), serviceTask("B", "greet:ship")),
|
|
155
|
+
];
|
|
156
|
+
const { plans, skipped } = planWorkerScaffold(models, [], [], [], ["greet:hello"]);
|
|
157
|
+
assert.deepEqual(plans.map((p) => p.taskType), ["greet:ship"]);
|
|
158
|
+
const reasons = Object.fromEntries(skipped.map((s) => [s.taskType, s.reason]));
|
|
159
|
+
assert.equal(reasons["greet:hello"], "inline");
|
|
160
|
+
});
|
|
161
|
+
|
|
149
162
|
test("planWorkerScaffold: colliding slugs get a numeric suffix", () => {
|
|
150
163
|
const m = model("a.bpmn", "p", serviceTask("A", "orders.charge"), serviceTask("B", "orders-charge"));
|
|
151
164
|
const { plans } = planWorkerScaffold([m], [], []);
|
|
@@ -258,6 +271,58 @@ test("scaffoldWorkers is idempotent: a second --write run is a no-op", async ()
|
|
|
258
271
|
assert.equal(JSON.stringify(io.files), snapshot, "files unchanged on re-run");
|
|
259
272
|
});
|
|
260
273
|
|
|
274
|
+
test("scaffoldWorkers skips inline-handled (w.run) task types — no stub, no wiring (nano-ide#595)", async () => {
|
|
275
|
+
const files = {
|
|
276
|
+
"/app/nano.app.json": JSON.stringify({
|
|
277
|
+
id: "demo",
|
|
278
|
+
models: { processes: ["processes/*.bpmn"] },
|
|
279
|
+
}),
|
|
280
|
+
"/app/processes/p.bpmn": model("p.bpmn", "greet",
|
|
281
|
+
serviceTask("hello", "greet:hello"),
|
|
282
|
+
serviceTask("B", "greet:notify"),
|
|
283
|
+
).xml,
|
|
284
|
+
};
|
|
285
|
+
const io = memIO(files);
|
|
286
|
+
const run = await scaffoldWorkers({ root: "/app", io, write: true, inlineTaskTypes: ["greet:hello"] });
|
|
287
|
+
// Only the external (un-hosted) task is scaffolded; the inline one is skipped entirely.
|
|
288
|
+
assert.deepEqual(run.outcomes.map((o) => o.taskType), ["greet:notify"]);
|
|
289
|
+
assert.ok(run.skipped.some((s) => s.taskType === "greet:hello" && s.reason === "inline"));
|
|
290
|
+
assert.equal(io.files["/app/workers/greet-hello/worker.ts"], undefined, "inline task gets no stub");
|
|
291
|
+
const manifest = JSON.parse(io.files["/app/nano.app.json"]);
|
|
292
|
+
assert.ok(
|
|
293
|
+
!manifest.workers?.some((w: { taskType: string }) => w.taskType === "greet:hello"),
|
|
294
|
+
"inline task is never wired into workers[]",
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("scaffoldWorkers prefers an in-memory derived model over a stale disk copy at the same path (nano-ide#595)", async () => {
|
|
299
|
+
// `--check`/`--no-models` runs never write the derived `.bpmn`, so a derived model's path can
|
|
300
|
+
// hold a STALE disk copy. Planning from disk would miss newly added external tasks and keep
|
|
301
|
+
// removed ones. The in-memory model must win — the same precedence `collectAll` (gen.ts) gives
|
|
302
|
+
// the type-contract derivers.
|
|
303
|
+
const files = {
|
|
304
|
+
"/app/nano.app.json": JSON.stringify({
|
|
305
|
+
id: "demo",
|
|
306
|
+
models: { processes: ["processes/*.bpmn"] },
|
|
307
|
+
}),
|
|
308
|
+
// Stale disk copy: still has the REMOVED task, lacks the ADDED one.
|
|
309
|
+
"/app/processes/p.bpmn": model("p.bpmn", "p", serviceTask("Old", "t.removed")).xml,
|
|
310
|
+
};
|
|
311
|
+
const io = memIO(files);
|
|
312
|
+
const derived = model("processes/p.bpmn", "p", serviceTask("New", "t.added"));
|
|
313
|
+
const run = await scaffoldWorkers({ root: "/app", io, write: true, extraModels: [derived] });
|
|
314
|
+
// The fresh model is planned from: the added task is scaffolded, the removed one is not.
|
|
315
|
+
assert.deepEqual(run.outcomes.map((o) => o.taskType), ["t.added"]);
|
|
316
|
+
assert.ok(io.files["/app/workers/t-added/worker.ts"], "added task gets a stub");
|
|
317
|
+
assert.equal(io.files["/app/workers/t-removed/worker.ts"], undefined, "removed task gets no stub");
|
|
318
|
+
const manifest = JSON.parse(io.files["/app/nano.app.json"]);
|
|
319
|
+
assert.deepEqual(
|
|
320
|
+
manifest.workers?.map((w: { taskType: string }) => w.taskType),
|
|
321
|
+
["t.added"],
|
|
322
|
+
"only the fresh model's task is wired",
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
261
326
|
test("scaffoldWorkers reads manifest.externalTaskTypes and skips those (never scaffolds/wires)", async () => {
|
|
262
327
|
const files = {
|
|
263
328
|
"/app/nano.app.json": JSON.stringify({
|
|
@@ -39,7 +39,7 @@ export interface WorkerStubPlan {
|
|
|
39
39
|
manifestEntry: StubManifestEntry;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export type SkipReason = "already-wired" | "orchestrator" | "duplicate" | "external";
|
|
42
|
+
export type SkipReason = "already-wired" | "orchestrator" | "duplicate" | "external" | "inline";
|
|
43
43
|
|
|
44
44
|
export interface SkippedWorker {
|
|
45
45
|
taskType: string;
|
|
@@ -118,16 +118,20 @@ export function renderWorkerStub(
|
|
|
118
118
|
/**
|
|
119
119
|
* Plan write-once handler stubs from the models. A stub is planned for each service-task
|
|
120
120
|
* `taskType` that is NOT already wired in the manifest, NOT the imperative orchestrator, NOT
|
|
121
|
-
* declared external (serviced by an out-of-process worker),
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
121
|
+
* declared external (serviced by an out-of-process worker), NOT inline-handled (a code-first
|
|
122
|
+
* `w.run` step whose handler the app's in-process Worker already serves — nano-ide#595), and
|
|
123
|
+
* not a duplicate of one already planned. `declaredTypeIds` are the manifest `types` ids — an
|
|
124
|
+
* envelope `in`/`out` is typed only when it names one (matching the worker-io deriver).
|
|
125
|
+
* `externalTaskTypes` are the manifest's declared external task types, which the app owns the
|
|
126
|
+
* model for but deliberately does not host. `inlineTaskTypes` are the flow-derived inline
|
|
127
|
+
* (`w.run`) job types (`deriveModels().inlineTaskTypes`).
|
|
125
128
|
*/
|
|
126
129
|
export function planWorkerScaffold(
|
|
127
130
|
models: ModelSource[],
|
|
128
131
|
workers: ScaffoldWorker[] = [],
|
|
129
132
|
declaredTypeIds: Iterable<string> = [],
|
|
130
133
|
externalTaskTypes: Iterable<string> = [],
|
|
134
|
+
inlineTaskTypes: Iterable<string> = [],
|
|
131
135
|
): WorkerScaffoldPlan {
|
|
132
136
|
const wired = new Set(
|
|
133
137
|
workers
|
|
@@ -136,6 +140,7 @@ export function planWorkerScaffold(
|
|
|
136
140
|
);
|
|
137
141
|
const declared = new Set(declaredTypeIds);
|
|
138
142
|
const external = new Set(externalTaskTypes);
|
|
143
|
+
const inline = new Set(inlineTaskTypes);
|
|
139
144
|
|
|
140
145
|
const plans: WorkerStubPlan[] = [];
|
|
141
146
|
const skipped: SkippedWorker[] = [];
|
|
@@ -153,6 +158,10 @@ export function planWorkerScaffold(
|
|
|
153
158
|
skipped.push({ taskType, reason: "external" });
|
|
154
159
|
continue;
|
|
155
160
|
}
|
|
161
|
+
if (inline.has(taskType)) {
|
|
162
|
+
skipped.push({ taskType, reason: "inline" });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
156
165
|
if (wired.has(taskType)) {
|
|
157
166
|
skipped.push({ taskType, reason: "already-wired" });
|
|
158
167
|
continue;
|
package/src/toolkit/scaffold.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { GenIO } from "./gen.ts";
|
|
7
7
|
import { joinPath, readModels } from "./gen.ts";
|
|
8
|
+
import type { ModelSource } from "./derivers/worker-io.ts";
|
|
8
9
|
import {
|
|
9
10
|
planWorkerScaffold,
|
|
10
11
|
type ScaffoldWorker,
|
|
@@ -50,6 +51,22 @@ export interface ScaffoldOptions {
|
|
|
50
51
|
manifestFile?: string;
|
|
51
52
|
/** Apply changes (create files + patch manifest). Default false = dry-run. */
|
|
52
53
|
write?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Job types of inline-handled (`w.run`) code-first steps — the set the app's in-process
|
|
56
|
+
* Worker already serves (nano-ide#595). The planner skips these with reason `"inline"` so
|
|
57
|
+
* `urban gen` never scaffolds a throwing stub that competes with the real handler. Pass
|
|
58
|
+
* `deriveModels().inlineTaskTypes` (the `generate` composition does).
|
|
59
|
+
*/
|
|
60
|
+
inlineTaskTypes?: Iterable<string>;
|
|
61
|
+
/**
|
|
62
|
+
* Extra in-memory models to plan against in addition to the on-disk ones `readModels`
|
|
63
|
+
* resolves (nano-ide#595). A code-first app's derived `.bpmn` lives under
|
|
64
|
+
* `resources/processes/`, which `readModels` does NOT resolve when the manifest declares a
|
|
65
|
+
* `models` block (e.g. `models.workflows`) without `models.processes` — a declared block
|
|
66
|
+
* suppresses the `resources/` convention. Pass `deriveModels().models` so the scaffolder
|
|
67
|
+
* plans from the same fused model set the type-contract derivers saw.
|
|
68
|
+
*/
|
|
69
|
+
extraModels?: ModelSource[];
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
export type StubStatus = "created" | "would-create" | "kept";
|
|
@@ -87,14 +104,23 @@ export async function scaffoldWorkers(opts: ScaffoldOptions): Promise<ScaffoldRu
|
|
|
87
104
|
// Preserve the manifest's own indentation when we patch it back (default tab, matching the
|
|
88
105
|
// scaffold's Biome config), so wiring a worker never reformats the file out from under lint.
|
|
89
106
|
const manifestIndent = detectJsonIndent(manifestRaw);
|
|
90
|
-
const
|
|
107
|
+
const diskModels = await readModels(root, io, manifest);
|
|
91
108
|
const declaredTypeIds = Object.keys(manifest.types ?? {});
|
|
92
109
|
|
|
110
|
+
// Fuse on-disk models with any in-memory extras (code-first derived models — see
|
|
111
|
+
// `ScaffoldOptions.extraModels`), deduped by path with the IN-MEMORY model winning: a derived
|
|
112
|
+
// model whose path also exists on disk may be stale there (`--check`/`--no-models` never write
|
|
113
|
+
// it), so planning from the disk copy would miss newly added tasks and keep removed ones. This
|
|
114
|
+
// mirrors the precedence `collectAll` (gen.ts) gives the type-contract derivers.
|
|
115
|
+
const extraPaths = new Set((opts.extraModels ?? []).map((m) => m.path));
|
|
116
|
+
const models = [...diskModels.filter((m) => !extraPaths.has(m.path)), ...(opts.extraModels ?? [])];
|
|
117
|
+
|
|
93
118
|
const { plans, skipped } = planWorkerScaffold(
|
|
94
119
|
models,
|
|
95
120
|
manifest.workers ?? [],
|
|
96
121
|
declaredTypeIds,
|
|
97
122
|
manifest.externalTaskTypes ?? [],
|
|
123
|
+
opts.inlineTaskTypes ?? [],
|
|
98
124
|
);
|
|
99
125
|
|
|
100
126
|
const outcomes: StubOutcome[] = [];
|