@nanobpm/urban 0.3.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 +17 -9
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +212 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +25 -0
- package/dist/runtime/adapters/deno.d.ts +6 -0
- package/dist/runtime/adapters/deno.js +98 -0
- package/dist/runtime/adapters/detect.d.ts +9 -0
- package/dist/runtime/adapters/detect.js +13 -0
- package/dist/runtime/adapters/node.d.ts +7 -0
- package/dist/runtime/adapters/node.js +113 -0
- package/dist/runtime/core/context.d.ts +40 -0
- package/dist/runtime/core/context.js +1 -0
- package/dist/runtime/core/glob.d.ts +5 -0
- package/dist/runtime/core/glob.js +51 -0
- package/dist/runtime/core/host.d.ts +128 -0
- package/dist/runtime/core/host.js +5 -0
- package/dist/runtime/core/manifest.d.ts +83 -0
- package/dist/runtime/core/manifest.js +45 -0
- package/dist/runtime/core/modules/datasource.d.ts +38 -0
- package/dist/runtime/core/modules/datasource.js +172 -0
- package/dist/runtime/core/modules/deploy.d.ts +6 -0
- package/dist/runtime/core/modules/deploy.js +37 -0
- package/dist/runtime/core/modules/security.d.ts +11 -0
- package/dist/runtime/core/modules/security.js +32 -0
- package/dist/runtime/core/modules/surfaces.d.ts +9 -0
- package/dist/runtime/core/modules/surfaces.js +88 -0
- package/dist/runtime/core/modules/triggers.d.ts +15 -0
- package/dist/runtime/core/modules/triggers.js +119 -0
- package/dist/runtime/core/modules/workers.d.ts +16 -0
- package/dist/runtime/core/modules/workers.js +79 -0
- package/dist/runtime/core/router.d.ts +22 -0
- package/dist/runtime/core/router.js +39 -0
- package/dist/runtime/core/runtime.d.ts +46 -0
- package/dist/runtime/core/runtime.js +173 -0
- package/dist/runtime/core/validate.d.ts +13 -0
- package/dist/runtime/core/validate.js +108 -0
- package/dist/runtime/engine/nanosdk.d.ts +151 -0
- package/dist/runtime/engine/nanosdk.js +209 -0
- package/dist/runtime/engine/sdk.d.ts +3 -0
- package/dist/runtime/engine/sdk.js +13 -0
- package/dist/runtime/index.d.ts +21 -0
- package/dist/runtime/index.js +17 -0
- package/dist/runtime/run.d.ts +17 -0
- package/dist/runtime/run.js +69 -0
- package/dist/runtime/schema/nano-app.schema.json +471 -0
- package/dist/toolkit/artifact.d.ts +19 -0
- package/dist/toolkit/artifact.js +11 -0
- package/dist/toolkit/derivers/migrations.d.ts +26 -0
- package/dist/toolkit/derivers/migrations.js +73 -0
- package/dist/toolkit/derivers/worker-io.d.ts +38 -0
- package/dist/toolkit/derivers/worker-io.js +135 -0
- package/dist/toolkit/fsio.d.ts +2 -0
- package/dist/toolkit/fsio.js +35 -0
- package/dist/toolkit/gen.d.ts +28 -0
- package/dist/toolkit/gen.js +75 -0
- package/dist/toolkit/index.d.ts +13 -0
- package/dist/toolkit/index.js +15 -0
- package/package.json +20 -6
- package/src/bin.mjs +6 -11
- package/src/cli.ts +14 -1
- package/src/index.ts +7 -0
- package/src/runtime/core/context.ts +10 -0
- package/src/runtime/core/runtime.test.ts +46 -1
- package/src/runtime/core/runtime.ts +9 -0
- package/src/runtime/engine/nanosdk.test.ts +245 -85
- package/src/runtime/engine/nanosdk.ts +284 -78
- package/src/runtime/engine/sdk.ts +18 -0
- package/src/runtime/index.ts +8 -4
- package/src/runtime/run.ts +15 -14
- package/src/runtime/engine/rest.ts +0 -243
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Deriver: BPMN models → `worker-io.d.ts`, the worker type map (ADR 0033 §3). Scans each
|
|
2
|
+
// process for service tasks and extracts the Zeebe task type plus the data-envelope in/out
|
|
3
|
+
// contract (io.nanobpm.dataEnvelope.in / .out, carried as zeebe:property). It emits the SAME
|
|
4
|
+
// `worker-io.d.ts` the console generates (a faithful port of `emitWorkerBindings` in the
|
|
5
|
+
// server's domain_types.ts), so the toolkit is a drop-in for the IDE's codegen (ADR 0053).
|
|
6
|
+
import { GENERATED_DIR } from "../artifact.js";
|
|
7
|
+
/** Filename the console uses; kept identical so the artifact is a drop-in. */
|
|
8
|
+
export const WORKER_BINDINGS_DTS = "worker-io.d.ts";
|
|
9
|
+
/** The domain type declarations `worker-io.d.ts` imports from (console: domain-rows.d.ts). */
|
|
10
|
+
export const DOMAIN_DTS = "domain-rows.d.ts";
|
|
11
|
+
const ENVELOPE_IN = "io.nanobpm.dataEnvelope.in";
|
|
12
|
+
const ENVELOPE_OUT = "io.nanobpm.dataEnvelope.out";
|
|
13
|
+
function attr(tag, name) {
|
|
14
|
+
const m = tag.match(new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`));
|
|
15
|
+
return m ? m[1] : undefined;
|
|
16
|
+
}
|
|
17
|
+
function processId(xml) {
|
|
18
|
+
const m = xml.match(/<bpmn:process\b[^>]*>/);
|
|
19
|
+
return m ? attr(m[0], "id") : undefined;
|
|
20
|
+
}
|
|
21
|
+
/** Scan one BPMN document for service-task worker I/O. */
|
|
22
|
+
export function scanModelWorkers(xml) {
|
|
23
|
+
const proc = processId(xml);
|
|
24
|
+
const out = [];
|
|
25
|
+
const blockRe = /<bpmn:serviceTask\b([^>]*)>([\s\S]*?)<\/bpmn:serviceTask>/g;
|
|
26
|
+
let m;
|
|
27
|
+
while ((m = blockRe.exec(xml)) !== null) {
|
|
28
|
+
const openAttrs = m[1];
|
|
29
|
+
const body = m[2];
|
|
30
|
+
const elementId = attr(`<x ${openAttrs}>`, "id");
|
|
31
|
+
const tdMatch = body.match(/<zeebe:taskDefinition\b[^>]*>/);
|
|
32
|
+
if (!tdMatch)
|
|
33
|
+
continue;
|
|
34
|
+
const taskType = attr(tdMatch[0], "type");
|
|
35
|
+
if (!taskType)
|
|
36
|
+
continue;
|
|
37
|
+
const io = { taskType, elementId, process: proc };
|
|
38
|
+
const propRe = /<zeebe:property\b[^>]*>/g;
|
|
39
|
+
let p;
|
|
40
|
+
while ((p = propRe.exec(body)) !== null) {
|
|
41
|
+
const pname = attr(p[0], "name");
|
|
42
|
+
const pvalue = attr(p[0], "value");
|
|
43
|
+
if (pname === ENVELOPE_IN && pvalue)
|
|
44
|
+
io.in = pvalue;
|
|
45
|
+
else if (pname === ENVELOPE_OUT && pvalue)
|
|
46
|
+
io.out = pvalue;
|
|
47
|
+
}
|
|
48
|
+
out.push(io);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
// --- Faithful port of the console's emitWorkerBindings (domain_types.ts) so the emitted
|
|
53
|
+
// worker-io.d.ts is byte-compatible with the IDE's own codegen. ---
|
|
54
|
+
function typeRefFor(id, declared) {
|
|
55
|
+
return id != null && declared.has(id) ? `DomainTypes[${JSON.stringify(id)}]` : undefined;
|
|
56
|
+
}
|
|
57
|
+
/** Emit `worker-io.d.ts` from worker bindings + the set of declared domain type ids. */
|
|
58
|
+
export function emitWorkerBindings(workers, declaredTypeIds) {
|
|
59
|
+
const declared = new Set(declaredTypeIds);
|
|
60
|
+
const propKey = (t) => JSON.stringify(t);
|
|
61
|
+
const taskTypes = [
|
|
62
|
+
...new Set(workers
|
|
63
|
+
.map((w) => w?.taskType)
|
|
64
|
+
.filter((t) => typeof t === "string" && t.length > 0)),
|
|
65
|
+
];
|
|
66
|
+
const taskTypeUnion = taskTypes.length > 0
|
|
67
|
+
? taskTypes.map((t) => JSON.stringify(t)).join(" | ")
|
|
68
|
+
: "string";
|
|
69
|
+
const inputs = [];
|
|
70
|
+
const outputs = [];
|
|
71
|
+
for (const w of workers) {
|
|
72
|
+
if (typeof w?.taskType !== "string" || w.taskType.length === 0)
|
|
73
|
+
continue;
|
|
74
|
+
const inRef = typeRefFor(w.inputType, declared);
|
|
75
|
+
if (inRef)
|
|
76
|
+
inputs.push(` ${propKey(w.taskType)}: ${inRef};`);
|
|
77
|
+
const outRef = typeRefFor(w.outputType, declared);
|
|
78
|
+
if (outRef)
|
|
79
|
+
outputs.push(` ${propKey(w.taskType)}: ${outRef};`);
|
|
80
|
+
}
|
|
81
|
+
const header = "// AUTO-GENERATED by nanobpmn from the App manifest (ADR 0033 §3).\n" +
|
|
82
|
+
"// The bridge from the process model to the worker type system: each declared\n" +
|
|
83
|
+
"// worker's `taskType` maps to the TS type of its input payload (`job.variables`)\n" +
|
|
84
|
+
"// and result, so the typed `defineWorker` types a handler by its job type. Do\n" +
|
|
85
|
+
"// not edit — regenerated from the manifest. Erased to plain JS at compile.\n" +
|
|
86
|
+
"// eslint-disable\n";
|
|
87
|
+
const needsRegistry = inputs.length > 0 || outputs.length > 0;
|
|
88
|
+
const importTypes = needsRegistry
|
|
89
|
+
? `import type { DomainTypes } from "./${DOMAIN_DTS}";\n`
|
|
90
|
+
: "";
|
|
91
|
+
const inputsIface = inputs.length > 0
|
|
92
|
+
? `export interface WorkerInputs {\n${inputs.join("\n")}\n}\n`
|
|
93
|
+
: `export interface WorkerInputs {}\n`;
|
|
94
|
+
const outputsIface = outputs.length > 0
|
|
95
|
+
? `export interface WorkerOutputs {\n${outputs.join("\n")}\n}\n`
|
|
96
|
+
: `export interface WorkerOutputs {}\n`;
|
|
97
|
+
const headersIface = `export interface WorkerHeaders {}\n`;
|
|
98
|
+
return `${header}\n` +
|
|
99
|
+
importTypes +
|
|
100
|
+
`\n/** Untyped fallback for a job whose worker declares no input/output type. */\n` +
|
|
101
|
+
`export type WorkerVars = Record<string, unknown>;\n\n` +
|
|
102
|
+
`/** Untyped fallback for a job whose worker declares no custom headers. */\n` +
|
|
103
|
+
`export type WorkerHdrs = Record<string, unknown>;\n\n` +
|
|
104
|
+
`/** Every declared worker \`taskType\` (ADR 0033 §3): the model-derived set the\n` +
|
|
105
|
+
` * typed \`defineWorker\` accepts, so \`type\` autocompletes and rejects unknown jobs. */\n` +
|
|
106
|
+
`export type WorkerTaskType = ${taskTypeUnion};\n\n` +
|
|
107
|
+
`/** Input payload (\`job.variables\`) per declared worker, keyed by \`taskType\`. */\n` +
|
|
108
|
+
inputsIface +
|
|
109
|
+
`\n/** Output payload (worker result) per declared worker, keyed by \`taskType\`. */\n` +
|
|
110
|
+
outputsIface +
|
|
111
|
+
`\n/** Custom headers (\`job.customHeaders\`) per declared worker, keyed by \`taskType\`.\n` +
|
|
112
|
+
` * Header values are strings on the wire, so each declared key maps to \`string\`;\n` +
|
|
113
|
+
` * the extra index signature keeps undeclared headers accessible (non-breaking). */\n` +
|
|
114
|
+
headersIface;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Derive `nano-generated/worker-io.d.ts` from BPMN models. `declaredTypeIds` is the set of
|
|
118
|
+
* domain type ids declared in the manifest `types` — an envelope in/out only becomes a typed
|
|
119
|
+
* ref when it names a declared type (matches the console's typeRefFor).
|
|
120
|
+
*/
|
|
121
|
+
export function deriveWorkerBindings(models, declaredTypeIds = []) {
|
|
122
|
+
const workers = [];
|
|
123
|
+
for (const model of [...models].sort((a, b) => (a.path < b.path ? -1 : 1))) {
|
|
124
|
+
for (const w of scanModelWorkers(model.xml)) {
|
|
125
|
+
workers.push({ taskType: w.taskType, inputType: w.in, outputType: w.out });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const content = emitWorkerBindings(workers, declaredTypeIds);
|
|
129
|
+
return [{ path: `${GENERATED_DIR}/${WORKER_BINDINGS_DTS}`, content }];
|
|
130
|
+
}
|
|
131
|
+
export const workerIoDeriver = {
|
|
132
|
+
id: "model->worker-io",
|
|
133
|
+
describe: "Derive worker-io.d.ts (task type + data-envelope in/out) from BPMN models.",
|
|
134
|
+
derive: ({ models, declaredTypeIds }) => deriveWorkerBindings(models, declaredTypeIds),
|
|
135
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Node/Deno filesystem implementation of the GenIO port. Uses node:fs/promises, which both
|
|
2
|
+
// Node and Deno provide, so a single implementation serves both runtimes.
|
|
3
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
export function createNodeGenIO() {
|
|
6
|
+
return {
|
|
7
|
+
async readText(path) {
|
|
8
|
+
return readFile(path, "utf8");
|
|
9
|
+
},
|
|
10
|
+
async writeText(path, content) {
|
|
11
|
+
await mkdir(dirname(path), { recursive: true });
|
|
12
|
+
await writeFile(path, content);
|
|
13
|
+
},
|
|
14
|
+
async listDir(path) {
|
|
15
|
+
try {
|
|
16
|
+
// The GenIO contract is file names only — exclude subdirectories so
|
|
17
|
+
// pattern expansion never hands a directory to readText()/JSON.parse.
|
|
18
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
19
|
+
return entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
async exists(path) {
|
|
26
|
+
try {
|
|
27
|
+
await stat(path);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { DerivedArtifact } from "./artifact.ts";
|
|
2
|
+
/** Minimal filesystem port. Node/Deno impls live in `fsio.ts`. */
|
|
3
|
+
export interface GenIO {
|
|
4
|
+
readText(path: string): Promise<string>;
|
|
5
|
+
writeText(path: string, content: string): Promise<void>;
|
|
6
|
+
/** File names (not paths) in a directory; empty if it does not exist. */
|
|
7
|
+
listDir(path: string): Promise<string[]>;
|
|
8
|
+
exists(path: string): Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
export interface GenOptions {
|
|
11
|
+
root: string;
|
|
12
|
+
io: GenIO;
|
|
13
|
+
manifestFile?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface GenResult {
|
|
16
|
+
artifacts: DerivedArtifact[];
|
|
17
|
+
/** Paths that differ from disk (only populated by `check`). */
|
|
18
|
+
drift: string[];
|
|
19
|
+
}
|
|
20
|
+
declare function join(root: string, rel: string): string;
|
|
21
|
+
declare function dirOf(p: string): string;
|
|
22
|
+
/** Collect all artifacts a run would produce, without touching disk beyond reads. */
|
|
23
|
+
export declare function collectArtifacts(opts: GenOptions): Promise<DerivedArtifact[]>;
|
|
24
|
+
/** Run the derivers and write artifacts (or, with `check`, report drift without writing). */
|
|
25
|
+
export declare function runGen(opts: GenOptions & {
|
|
26
|
+
check?: boolean;
|
|
27
|
+
}): Promise<GenResult>;
|
|
28
|
+
export { dirOf, join as joinPath };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// The gen orchestrator: the one impure edge of the toolkit. It reads an app's manifest and
|
|
2
|
+
// models, runs the pure derivers, and either writes the artifacts to disk (`urban gen`) or
|
|
3
|
+
// compares them against what's on disk and reports drift (`urban gen --check`). The derivers stay
|
|
4
|
+
// pure; all IO is confined here behind a tiny FS port so the same code runs on Node and Deno.
|
|
5
|
+
import { sortArtifacts } from "./artifact.js";
|
|
6
|
+
import { deriveMigrations } from "./derivers/migrations.js";
|
|
7
|
+
import { deriveWorkerBindings } from "./derivers/worker-io.js";
|
|
8
|
+
function join(root, rel) {
|
|
9
|
+
// Trim either separator so callers may pass Windows-style paths; GenIO
|
|
10
|
+
// implementations accept forward slashes on all platforms.
|
|
11
|
+
return `${root.replace(/[/\\]+$/, "")}/${rel.replace(/^[/\\]+/, "")}`;
|
|
12
|
+
}
|
|
13
|
+
function dirOf(p) {
|
|
14
|
+
const i = p.lastIndexOf("/");
|
|
15
|
+
return i <= 0 ? "." : p.slice(0, i);
|
|
16
|
+
}
|
|
17
|
+
/** Resolve a `dir/*.ext` (or literal) manifest pattern to file paths relative to root. */
|
|
18
|
+
async function expandPattern(root, io, pattern) {
|
|
19
|
+
const star = pattern.indexOf("*");
|
|
20
|
+
if (star === -1) {
|
|
21
|
+
return (await io.exists(join(root, pattern))) ? [pattern] : [];
|
|
22
|
+
}
|
|
23
|
+
const slash = pattern.lastIndexOf("/", star);
|
|
24
|
+
const dir = slash === -1 ? "." : pattern.slice(0, slash);
|
|
25
|
+
const tail = pattern.slice(slash + 1); // e.g. "*.bpmn"
|
|
26
|
+
const ext = tail.startsWith("*") ? tail.slice(1) : tail;
|
|
27
|
+
const names = await io.listDir(join(root, dir));
|
|
28
|
+
return names
|
|
29
|
+
.filter((n) => n.endsWith(ext))
|
|
30
|
+
.map((n) => (dir === "." ? n : `${dir}/${n}`))
|
|
31
|
+
.sort();
|
|
32
|
+
}
|
|
33
|
+
/** Collect all artifacts a run would produce, without touching disk beyond reads. */
|
|
34
|
+
export async function collectArtifacts(opts) {
|
|
35
|
+
const { root, io } = opts;
|
|
36
|
+
const manifestPath = join(root, opts.manifestFile ?? "nano.app.json");
|
|
37
|
+
const manifest = JSON.parse(await io.readText(manifestPath));
|
|
38
|
+
const artifacts = [];
|
|
39
|
+
// 1. types → migrations
|
|
40
|
+
if (manifest.types && Object.keys(manifest.types).length > 0) {
|
|
41
|
+
artifacts.push(...deriveMigrations(manifest));
|
|
42
|
+
}
|
|
43
|
+
// 2. models → worker I/O index
|
|
44
|
+
const procPatterns = manifest.models?.processes ?? [];
|
|
45
|
+
const models = [];
|
|
46
|
+
for (const pat of procPatterns) {
|
|
47
|
+
for (const rel of await expandPattern(root, io, pat)) {
|
|
48
|
+
models.push({ path: rel, xml: await io.readText(join(root, rel)) });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (models.length > 0) {
|
|
52
|
+
const declaredTypeIds = Object.keys(manifest.types ?? {});
|
|
53
|
+
artifacts.push(...deriveWorkerBindings(models, declaredTypeIds));
|
|
54
|
+
}
|
|
55
|
+
return sortArtifacts(artifacts);
|
|
56
|
+
}
|
|
57
|
+
/** Run the derivers and write artifacts (or, with `check`, report drift without writing). */
|
|
58
|
+
export async function runGen(opts) {
|
|
59
|
+
const artifacts = await collectArtifacts(opts);
|
|
60
|
+
const { root, io } = opts;
|
|
61
|
+
const drift = [];
|
|
62
|
+
for (const a of artifacts) {
|
|
63
|
+
const abs = join(root, a.path);
|
|
64
|
+
if (opts.check) {
|
|
65
|
+
const current = (await io.exists(abs)) ? await io.readText(abs) : null;
|
|
66
|
+
if (current !== a.content)
|
|
67
|
+
drift.push(a.path);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
await io.writeText(abs, a.content);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { artifacts, drift };
|
|
74
|
+
}
|
|
75
|
+
export { dirOf, join as joinPath };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type { DerivedArtifact, Deriver } from "./artifact.ts";
|
|
2
|
+
export { GENERATED_DIR, sortArtifacts } from "./artifact.ts";
|
|
3
|
+
export { deriveMigrations, migrationsDeriver, sqlType, createTableSql, } from "./derivers/migrations.ts";
|
|
4
|
+
export type { ToolkitManifest, ToolkitType, ToolkitField } from "./derivers/migrations.ts";
|
|
5
|
+
export { deriveWorkerBindings, emitWorkerBindings, workerIoDeriver, scanModelWorkers, WORKER_BINDINGS_DTS, DOMAIN_DTS, } from "./derivers/worker-io.ts";
|
|
6
|
+
export type { ModelSource, WorkerIo, WorkerBindingDecl } from "./derivers/worker-io.ts";
|
|
7
|
+
export declare const DERIVERS: readonly [import("./artifact.ts").Deriver<import("./derivers/migrations.ts").ToolkitManifest>, import("./artifact.ts").Deriver<{
|
|
8
|
+
models: import("./derivers/worker-io.ts").ModelSource[];
|
|
9
|
+
declaredTypeIds: Iterable<string>;
|
|
10
|
+
}>];
|
|
11
|
+
export { collectArtifacts, runGen, joinPath } from "./gen.ts";
|
|
12
|
+
export type { GenIO, GenOptions, GenResult } from "./gen.ts";
|
|
13
|
+
export { createNodeGenIO } from "./fsio.ts";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The Urban derivation toolkit — the shared derivation library. The IDE and the
|
|
2
|
+
// `urban gen` CLI are peer callers of these pure derivers (ADR 0053). Code-first
|
|
3
|
+
// process authoring is provided by `@nanobpm/workflow` (`defineFlow`), not here
|
|
4
|
+
// (ADR 0054) — the toolkit derives migrations and worker I/O from the model.
|
|
5
|
+
export { GENERATED_DIR, sortArtifacts } from "./artifact.js";
|
|
6
|
+
// Derivers
|
|
7
|
+
export { deriveMigrations, migrationsDeriver, sqlType, createTableSql, } from "./derivers/migrations.js";
|
|
8
|
+
export { deriveWorkerBindings, emitWorkerBindings, workerIoDeriver, scanModelWorkers, WORKER_BINDINGS_DTS, DOMAIN_DTS, } from "./derivers/worker-io.js";
|
|
9
|
+
// The registry of all derivers (for discovery / IDE migration).
|
|
10
|
+
import { migrationsDeriver } from "./derivers/migrations.js";
|
|
11
|
+
import { workerIoDeriver } from "./derivers/worker-io.js";
|
|
12
|
+
export const DERIVERS = [migrationsDeriver, workerIoDeriver];
|
|
13
|
+
// Gen orchestrator + IO
|
|
14
|
+
export { collectArtifacts, runGen, joinPath } from "./gen.js";
|
|
15
|
+
export { createNodeGenIO } from "./fsio.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/urban",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -28,23 +28,37 @@
|
|
|
28
28
|
"bin": {
|
|
29
29
|
"urban": "./src/bin.mjs"
|
|
30
30
|
},
|
|
31
|
-
"main": "./
|
|
32
|
-
"types": "./
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
33
|
"exports": {
|
|
34
|
-
".":
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./runtime": {
|
|
39
|
+
"types": "./dist/runtime/index.d.ts",
|
|
40
|
+
"default": "./dist/runtime/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./toolkit": {
|
|
43
|
+
"types": "./dist/toolkit/index.d.ts",
|
|
44
|
+
"default": "./dist/toolkit/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./source": "./src/index.ts"
|
|
37
47
|
},
|
|
38
48
|
"files": [
|
|
49
|
+
"dist",
|
|
39
50
|
"src",
|
|
40
51
|
"README.md"
|
|
41
52
|
],
|
|
42
53
|
"scripts": {
|
|
54
|
+
"build": "tsc -p tsconfig.build.json && node -e \"const fs=require('fs');fs.mkdirSync('dist/runtime/schema',{recursive:true});fs.copyFileSync('src/runtime/schema/nano-app.schema.json','dist/runtime/schema/nano-app.schema.json')\"",
|
|
55
|
+
"prepack": "npm run build",
|
|
43
56
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
44
57
|
"test": "node --test --experimental-strip-types \"src/**/*.test.ts\"",
|
|
45
58
|
"test:deno": "deno test -A --no-check src/"
|
|
46
59
|
},
|
|
47
60
|
"dependencies": {
|
|
61
|
+
"@nanobpm/nano-sdk": "^1.2.0",
|
|
48
62
|
"@nanobpm/workflow": "^0.6.0",
|
|
49
63
|
"bpmn-auto-layout": "^2.0.0-alpha.2",
|
|
50
64
|
"create-urban-app": "^0.2.0"
|
package/src/bin.mjs
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Bootstrap for the `urban` CLI.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// Bootstrap for the `urban` CLI. Runs the compiled CLI (dist/cli.js). The package is
|
|
3
|
+
// published as compiled JS + .d.ts because Node cannot strip types for files under
|
|
4
|
+
// node_modules (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so no type-stripping flag
|
|
5
|
+
// is needed here.
|
|
5
6
|
|
|
6
7
|
import { spawnSync } from "node:child_process";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { dirname, join } from "node:path";
|
|
9
10
|
|
|
10
|
-
const cli = join(dirname(fileURLToPath(import.meta.url)), "cli.
|
|
11
|
-
const [maj, min] = process.versions.node.split(".").map(Number);
|
|
12
|
-
const needsFlag = maj < 23 || (maj === 23 && min < 6);
|
|
11
|
+
const cli = join(dirname(fileURLToPath(import.meta.url)), "..", "dist", "cli.js");
|
|
13
12
|
|
|
14
|
-
const
|
|
15
|
-
? ["--disable-warning=ExperimentalWarning", "--experimental-strip-types", cli, ...process.argv.slice(2)]
|
|
16
|
-
: [cli, ...process.argv.slice(2)];
|
|
17
|
-
|
|
18
|
-
const r = spawnSync(process.execPath, args, { stdio: "inherit" });
|
|
13
|
+
const r = spawnSync(process.execPath, [cli, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
19
14
|
if (r.error) {
|
|
20
15
|
console.error(String(r.error.message ?? r.error));
|
|
21
16
|
process.exit(1);
|
package/src/cli.ts
CHANGED
|
@@ -19,8 +19,21 @@ import {
|
|
|
19
19
|
import { scaffold, slugify } from "create-urban-app";
|
|
20
20
|
import { createNodeGenIO, runGen } from "./toolkit/index.ts";
|
|
21
21
|
import { pathToFileURL } from "node:url";
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
23
|
+
|
|
24
|
+
// Read the package version from package.json (one hop up from both src/cli.ts and the
|
|
25
|
+
// compiled dist/cli.js) so `urban --version` never drifts from the published version.
|
|
26
|
+
function readVersion(): string {
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL("../package.json", import.meta.url);
|
|
29
|
+
return (JSON.parse(readFileSync(url, "utf8")) as { version?: string }).version ?? "0.0.0";
|
|
30
|
+
} catch {
|
|
31
|
+
return "0.0.0";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const VERSION = readVersion();
|
|
22
36
|
|
|
23
|
-
const VERSION = "0.2.0";
|
|
24
37
|
|
|
25
38
|
interface Flags {
|
|
26
39
|
root: string;
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
// Runtime — app materialization + hosting
|
|
16
16
|
export * from "./runtime/index.ts";
|
|
17
17
|
|
|
18
|
+
// The nano-sdk engine client factory: build a client to talk to a Nano/Camunda
|
|
19
|
+
// engine directly, or type an `AppApi.sdk`. The runtime uses this same factory, so a
|
|
20
|
+
// code-first author reaches the full engine surface without a separate
|
|
21
|
+
// `@nanobpm/nano-sdk` dependency. (`EngineSdkClient`, its return type, is re-exported
|
|
22
|
+
// from ./runtime/index.ts above.)
|
|
23
|
+
export { createCamundaClient } from "@nanobpm/nano-sdk";
|
|
24
|
+
|
|
18
25
|
// Toolkit — derivation library (`urban gen`)
|
|
19
26
|
export * from "./toolkit/index.ts";
|
|
20
27
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AppManifest } from "./manifest.ts";
|
|
2
2
|
import type { EngineClient, HostContext } from "./host.ts";
|
|
3
|
+
import type { EngineSdkClient } from "../engine/sdk.ts";
|
|
3
4
|
|
|
4
5
|
/** Everything a runtime module needs. Passed to each module's mount function. */
|
|
5
6
|
export interface RuntimeContext {
|
|
@@ -30,6 +31,15 @@ export interface AppApi {
|
|
|
30
31
|
manifest: AppManifest;
|
|
31
32
|
data: DataLayer;
|
|
32
33
|
engine: EngineClient;
|
|
34
|
+
/**
|
|
35
|
+
* The underlying `@nanobpm/nano-sdk` engine client, present when the app runs on
|
|
36
|
+
* the nano-sdk transport (the default). It exposes the full Camunda
|
|
37
|
+
* orchestration-cluster surface — decisions, cluster variables, incidents, user
|
|
38
|
+
* tasks, agents, batch operations — beyond the transport-agnostic `engine` seam,
|
|
39
|
+
* over the same connection. Undefined when a non-SDK engine is injected (e.g. an
|
|
40
|
+
* in-memory test double).
|
|
41
|
+
*/
|
|
42
|
+
sdk?: EngineSdkClient;
|
|
33
43
|
env(name: string): string | undefined;
|
|
34
44
|
log(level: "info" | "warn" | "error", msg: string, fields?: Record<string, unknown>): void;
|
|
35
45
|
}
|
|
@@ -69,7 +69,7 @@ async function makeFixture(): Promise<string> {
|
|
|
69
69
|
`export const handlers = {
|
|
70
70
|
"wf.claim": async (job, app) => {
|
|
71
71
|
app.data.repo("task").insert({ title: job.variables.title, status: "claimed" });
|
|
72
|
-
return { claimed: true };
|
|
72
|
+
return { claimed: true, hasSdk: !!app.sdk, sdkMarker: app.sdk?.__marker };
|
|
73
73
|
},
|
|
74
74
|
};`,
|
|
75
75
|
);
|
|
@@ -154,6 +154,51 @@ test("runtime materializes the manifest end-to-end against a fake engine", async
|
|
|
154
154
|
}
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
+
test("threads the engine SDK client onto app.sdk for handlers (SDK-backed engine)", async () => {
|
|
158
|
+
const dir = await makeFixture();
|
|
159
|
+
const host = createNodeHost({ cwd: dir, log: () => {} });
|
|
160
|
+
class SdkEngine extends FakeEngine {
|
|
161
|
+
sdk = { __marker: "engine-sdk" };
|
|
162
|
+
}
|
|
163
|
+
const engine = new SdkEngine();
|
|
164
|
+
const app = await createUrbanApp({ host, engine, root: ".", port: 0 });
|
|
165
|
+
await app.start();
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const out = (await engine.deliver("wf.claim", {
|
|
169
|
+
jobKey: "j1",
|
|
170
|
+
jobType: "wf.claim",
|
|
171
|
+
variables: { title: "Fix bug" },
|
|
172
|
+
})) as Record<string, unknown>;
|
|
173
|
+
assert.equal(out.hasSdk, true, "handler saw app.sdk");
|
|
174
|
+
assert.equal(out.sdkMarker, "engine-sdk", "app.sdk is the engine's own client");
|
|
175
|
+
} finally {
|
|
176
|
+
await app.stop();
|
|
177
|
+
await rm(dir, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("app.sdk is undefined when the engine exposes no SDK client", async () => {
|
|
182
|
+
const dir = await makeFixture();
|
|
183
|
+
const host = createNodeHost({ cwd: dir, log: () => {} });
|
|
184
|
+
const engine = new FakeEngine();
|
|
185
|
+
const app = await createUrbanApp({ host, engine, root: ".", port: 0 });
|
|
186
|
+
await app.start();
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const out = (await engine.deliver("wf.claim", {
|
|
190
|
+
jobKey: "j1",
|
|
191
|
+
jobType: "wf.claim",
|
|
192
|
+
variables: { title: "Fix bug" },
|
|
193
|
+
})) as Record<string, unknown>;
|
|
194
|
+
assert.equal(out.hasSdk, false, "no app.sdk for a non-SDK engine");
|
|
195
|
+
assert.equal(out.sdkMarker, undefined);
|
|
196
|
+
} finally {
|
|
197
|
+
await app.stop();
|
|
198
|
+
await rm(dir, { recursive: true, force: true });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
157
202
|
test("stop() resets state so the app can be cleanly restarted", async () => {
|
|
158
203
|
const dir = await makeFixture();
|
|
159
204
|
const host = createNodeHost({ cwd: dir, log: () => {} });
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { AppApi, Mounted } from "./context.ts";
|
|
7
7
|
import type { EngineClient, HostContext, HttpServer } from "./host.ts";
|
|
8
|
+
import type { EngineSdkClient } from "../engine/sdk.ts";
|
|
8
9
|
import { loadManifest, type AppManifest } from "./manifest.ts";
|
|
9
10
|
import { validateManifest } from "./validate.ts";
|
|
10
11
|
import { makeRouter, type Route } from "./router.ts";
|
|
@@ -27,6 +28,13 @@ export function resolvePort(explicit: number | undefined, envPort: string | unde
|
|
|
27
28
|
return n;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/** Read the underlying nano-sdk client off an engine when it exposes one (the
|
|
32
|
+
* `SdkEngineClient` does). Non-SDK engines (e.g. an in-memory test double) have no
|
|
33
|
+
* `sdk`, so handlers get `undefined` and fall back to the transport-agnostic seam. */
|
|
34
|
+
function engineSdk(engine: EngineClient): EngineSdkClient | undefined {
|
|
35
|
+
return (engine as { sdk?: EngineSdkClient }).sdk;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
export interface MountFlags {
|
|
31
39
|
deploy?: boolean;
|
|
32
40
|
data?: boolean;
|
|
@@ -152,6 +160,7 @@ export async function createUrbanApp(opts: CreateUrbanAppOptions): Promise<Urban
|
|
|
152
160
|
manifest,
|
|
153
161
|
data,
|
|
154
162
|
engine,
|
|
163
|
+
sdk: engineSdk(engine),
|
|
155
164
|
env: (n) => host.env(n),
|
|
156
165
|
log: (l, m, f) => host.log(l, m, f),
|
|
157
166
|
};
|