@nanobpm/urban 0.21.0 → 0.23.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 +35 -0
- package/dist/cli.d.ts +26 -0
- package/dist/cli.js +80 -16
- package/dist/runtime/adapters/deno.js +19 -3
- package/dist/runtime/adapters/detect.js +2 -2
- package/dist/runtime/adapters/globals.d.ts +19 -0
- package/dist/runtime/adapters/globals.js +9 -0
- package/dist/runtime/adapters/node.js +25 -5
- package/dist/runtime/core/context.d.ts +4 -0
- package/dist/runtime/core/guards.d.ts +2 -0
- package/dist/runtime/core/guards.js +10 -0
- package/dist/runtime/core/host.js +5 -4
- package/dist/runtime/core/manifest.d.ts +8 -0
- package/dist/runtime/core/manifest.js +0 -1
- package/dist/runtime/core/modules/actions.js +9 -5
- package/dist/runtime/core/modules/connectors.js +6 -4
- package/dist/runtime/core/modules/dataops.d.ts +36 -0
- package/dist/runtime/core/modules/dataops.js +231 -0
- package/dist/runtime/core/modules/datasource.d.ts +51 -1
- package/dist/runtime/core/modules/datasource.js +98 -21
- package/dist/runtime/core/modules/deploy.d.ts +3 -1
- package/dist/runtime/core/modules/deploy.js +29 -7
- package/dist/runtime/core/modules/gateway.d.ts +1 -1
- package/dist/runtime/core/modules/gateway.js +16 -17
- package/dist/runtime/core/modules/llm.js +9 -7
- package/dist/runtime/core/modules/pages.js +118 -16
- package/dist/runtime/core/modules/security.js +5 -5
- package/dist/runtime/core/modules/templates.d.ts +18 -0
- package/dist/runtime/core/modules/templates.js +133 -0
- package/dist/runtime/core/modules/triggers.js +14 -5
- package/dist/runtime/core/modules/workers.js +15 -9
- package/dist/runtime/core/runtime.d.ts +5 -0
- package/dist/runtime/core/runtime.js +5 -5
- package/dist/runtime/core/validate.d.ts +2 -1
- package/dist/runtime/core/validate.js +38 -28
- package/dist/runtime/devserver.js +2 -1
- package/dist/runtime/engine/nanosdk.d.ts +1 -1
- package/dist/runtime/engine/nanosdk.js +15 -9
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/run.js +10 -7
- package/dist/toolkit/addConnector.js +4 -3
- package/dist/toolkit/fsio.js +2 -1
- package/dist/toolkit/models.js +3 -5
- package/package.json +1 -1
- package/src/cli.test.ts +44 -0
- package/src/cli.ts +82 -16
- package/src/runtime/adapters/deno.ts +23 -3
- package/src/runtime/adapters/detect.ts +2 -2
- package/src/runtime/adapters/globals.ts +27 -0
- package/src/runtime/adapters/node.ts +27 -4
- package/src/runtime/connector-worker-sdk.test.ts +2 -1
- package/src/runtime/core/context.ts +4 -0
- package/src/runtime/core/guards.ts +10 -0
- package/src/runtime/core/host.ts +4 -4
- package/src/runtime/core/manifest.ts +19 -5
- package/src/runtime/core/modules/actions.test.ts +82 -31
- package/src/runtime/core/modules/actions.ts +11 -7
- package/src/runtime/core/modules/connectors.test.ts +29 -8
- package/src/runtime/core/modules/connectors.ts +6 -4
- package/src/runtime/core/modules/dataops.test.ts +306 -0
- package/src/runtime/core/modules/dataops.ts +292 -0
- package/src/runtime/core/modules/datasource.migrations.test.ts +12 -3
- package/src/runtime/core/modules/datasource.test.ts +129 -0
- package/src/runtime/core/modules/datasource.ts +105 -23
- package/src/runtime/core/modules/deploy.test.ts +258 -0
- package/src/runtime/core/modules/deploy.ts +29 -7
- package/src/runtime/core/modules/gateway.test.ts +5 -4
- package/src/runtime/core/modules/gateway.ts +29 -30
- package/src/runtime/core/modules/llm.test.ts +10 -12
- package/src/runtime/core/modules/llm.ts +11 -9
- package/src/runtime/core/modules/pages.test.ts +53 -2
- package/src/runtime/core/modules/pages.ts +121 -23
- package/src/runtime/core/modules/security.ts +5 -13
- package/src/runtime/core/modules/surfaces.test.ts +53 -10
- package/src/runtime/core/modules/templates.ts +153 -0
- package/src/runtime/core/modules/triggers.test.ts +51 -19
- package/src/runtime/core/modules/triggers.ts +14 -6
- package/src/runtime/core/modules/workers.test.ts +43 -20
- package/src/runtime/core/modules/workers.ts +18 -10
- package/src/runtime/core/runtime.test.ts +23 -10
- package/src/runtime/core/runtime.ts +15 -2
- package/src/runtime/core/validate.ts +43 -32
- package/src/runtime/devserver.test.ts +18 -5
- package/src/runtime/devserver.ts +2 -1
- package/src/runtime/engine/nanosdk.test.ts +16 -11
- package/src/runtime/engine/nanosdk.ts +22 -15
- package/src/runtime/index.ts +7 -0
- package/src/runtime/run.ts +10 -10
- package/src/toolkit/addConnector.ts +6 -5
- package/src/toolkit/derivers/messages.test.ts +4 -2
- package/src/toolkit/derivers/worker-io.test.ts +4 -2
- package/src/toolkit/fsio.ts +2 -1
- package/src/toolkit/gen.ts +4 -4
- package/src/toolkit/models.test.ts +1 -1
- package/src/toolkit/models.ts +3 -4
- package/src/toolkit/scaffold.ts +1 -1
package/README.md
CHANGED
|
@@ -141,6 +141,41 @@ wiring; `WorkflowClient` deploys and starts, `Worker` hosts your `run` steps.
|
|
|
141
141
|
inspectable in a modeller/Operate — `@nanobpm/urban` bundles `bpmn-auto-layout`
|
|
142
142
|
so this works out of the box.
|
|
143
143
|
|
|
144
|
+
### Deploy-time model templates (`{{name}}`)
|
|
145
|
+
|
|
146
|
+
A model file can carry `{{name}}` placeholders that are substituted at deploy
|
|
147
|
+
time — handy for inlining a large asset (e.g. an agent prompt authored as its own
|
|
148
|
+
file) into a `zeebe:header` value instead of hand-pasting it into XML or shipping
|
|
149
|
+
it as a bulky per-instance process variable. Declare the template sources under
|
|
150
|
+
`models.templates` (globs, a bare directory that is scanned, or literal files); a
|
|
151
|
+
template's name is its file **stem**, so `prompts/review.md` fills `{{review}}`:
|
|
152
|
+
|
|
153
|
+
```jsonc
|
|
154
|
+
{
|
|
155
|
+
"models": {
|
|
156
|
+
"processes": ["processes/*.bpmn"],
|
|
157
|
+
"templates": ["prompts/*.md"]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```xml
|
|
163
|
+
<!-- processes/agent.bpmn -->
|
|
164
|
+
<zeebe:header key="io.nanobpm.agentTask.task.prompt" value="{{review}}" />
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Content is escaped for the resource's type: XML for `.bpmn`/`.dmn` (newlines/tabs
|
|
168
|
+
become character references so a multi-line prompt survives XML attribute-value
|
|
169
|
+
normalization), JSON for `.form`. Substitution is single-pass (a template's own
|
|
170
|
+
`{{…}}` is not re-expanded); an unknown placeholder is left verbatim and logged as
|
|
171
|
+
a warning. Templates can also be supplied programmatically via the `templates`
|
|
172
|
+
run option (globs or an explicit `name → content` map), which wins over the
|
|
173
|
+
manifest on a name collision:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
await runFromEnv({ templates: { review: await readPrompt("review.md") } });
|
|
177
|
+
```
|
|
178
|
+
|
|
144
179
|
### Triggers — the inbound I/O edge
|
|
145
180
|
|
|
146
181
|
Declare `triggers[]` in the app manifest to turn outside events into engine
|
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1,27 @@
|
|
|
1
|
+
interface Flags {
|
|
2
|
+
root: string;
|
|
3
|
+
manifest: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
check: boolean;
|
|
6
|
+
write: boolean;
|
|
7
|
+
deno: boolean;
|
|
8
|
+
models: boolean;
|
|
9
|
+
stdout: boolean;
|
|
10
|
+
install: boolean;
|
|
11
|
+
style?: "model" | "code";
|
|
12
|
+
help: boolean;
|
|
13
|
+
version: boolean;
|
|
14
|
+
_: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `urban data` — the DB-manager op gateway. Reads a single JSON request from stdin
|
|
18
|
+
* (`{ op, source?, sql?, params?, statements? }`) and writes a single JSON response to stdout
|
|
19
|
+
* (`{ ok: true, ...result }` or `{ ok: false, error }`). Every response — a handled op error *or*
|
|
20
|
+
* an unreadable request — is a parseable `{ ok: false, error }` envelope and still exits 0, so the
|
|
21
|
+
* caller can always parse the reply off stdout and never has to treat a non-zero exit as a lost
|
|
22
|
+
* error; the process only exits non-zero on an unexpected crash. This is the seam the Nano console
|
|
23
|
+
* re-points its Data panel at (host dry-out nano-bpm#576), replacing the vendored data-cli.ts.
|
|
24
|
+
*/
|
|
25
|
+
export declare function cmdData(f: Flags, readInput?: () => Promise<string>): Promise<number>;
|
|
1
26
|
export declare function main(argv: string[]): Promise<number>;
|
|
27
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
// urban run materialize + serve the app (deploy, data, workers, surfaces, triggers)
|
|
8
8
|
// urban dev like run, plus watch sources and hot-reload on change
|
|
9
9
|
// urban deploy deploy models only, then exit
|
|
10
|
+
// urban data DB-manager op gateway (stdin JSON → stdout JSON)
|
|
10
11
|
//
|
|
11
12
|
// Global flags: --root <dir> (default "."), --manifest <file> (default nano.app.json),
|
|
12
13
|
// --port <n>, -h/--help, -v/--version.
|
|
13
|
-
import { collectManifestIssues, installSignalHandlers, loadManifest, runDev, runFromEnv, selectHost, } from "./runtime/index.js";
|
|
14
|
+
import { collectManifestIssues, installSignalHandlers, loadManifest, runDataOp, runDev, runFromEnv, selectHost, } from "./runtime/index.js";
|
|
15
|
+
import { denoGlobal, processGlobal } from "./runtime/adapters/globals.js";
|
|
16
|
+
import { errorMessage, isRecord } from "./runtime/core/guards.js";
|
|
14
17
|
import { scaffold, slugify } from "create-urban-app";
|
|
15
18
|
import { addConnector, createNodeGenIO, previewModels, runGen, scaffoldWorkers } from "./toolkit/index.js";
|
|
16
19
|
import { pathToFileURL } from "node:url";
|
|
@@ -21,7 +24,8 @@ import { spawnSync } from "node:child_process";
|
|
|
21
24
|
function readVersion() {
|
|
22
25
|
try {
|
|
23
26
|
const url = new URL("../package.json", import.meta.url);
|
|
24
|
-
|
|
27
|
+
const pkg = JSON.parse(readFileSync(url, "utf8"));
|
|
28
|
+
return isRecord(pkg) && typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
25
29
|
}
|
|
26
30
|
catch {
|
|
27
31
|
return "0.0.0";
|
|
@@ -102,6 +106,9 @@ Usage:
|
|
|
102
106
|
urban run materialize + serve the app
|
|
103
107
|
urban dev run + watch sources, hot-reload on change
|
|
104
108
|
urban deploy deploy models only, then exit
|
|
109
|
+
urban data DB-manager op gateway: read a JSON request on stdin
|
|
110
|
+
({op:sources|schema|query|exec|script|migrations|migrate,…}),
|
|
111
|
+
write a JSON result on stdout
|
|
105
112
|
|
|
106
113
|
Global flags:
|
|
107
114
|
--root <dir> app root (default ".")
|
|
@@ -257,6 +264,60 @@ async function cmdDeploy(f) {
|
|
|
257
264
|
await app.stop();
|
|
258
265
|
return 0;
|
|
259
266
|
}
|
|
267
|
+
/** Read all of stdin as UTF-8 text (Node or Deno). Returns "" if stdin is empty/closed. */
|
|
268
|
+
async function readStdin() {
|
|
269
|
+
const deno = denoGlobal();
|
|
270
|
+
const proc = processGlobal();
|
|
271
|
+
const chunks = [];
|
|
272
|
+
const dec = new TextDecoder();
|
|
273
|
+
if (deno?.stdin?.readable) {
|
|
274
|
+
for await (const chunk of deno.stdin.readable)
|
|
275
|
+
chunks.push(dec.decode(chunk, { stream: true }));
|
|
276
|
+
chunks.push(dec.decode());
|
|
277
|
+
return chunks.join("");
|
|
278
|
+
}
|
|
279
|
+
if (proc?.stdin) {
|
|
280
|
+
for await (const chunk of proc.stdin) {
|
|
281
|
+
chunks.push(typeof chunk === "string" ? chunk : dec.decode(chunk, { stream: true }));
|
|
282
|
+
}
|
|
283
|
+
chunks.push(dec.decode());
|
|
284
|
+
return chunks.join("");
|
|
285
|
+
}
|
|
286
|
+
return "";
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* `urban data` — the DB-manager op gateway. Reads a single JSON request from stdin
|
|
290
|
+
* (`{ op, source?, sql?, params?, statements? }`) and writes a single JSON response to stdout
|
|
291
|
+
* (`{ ok: true, ...result }` or `{ ok: false, error }`). Every response — a handled op error *or*
|
|
292
|
+
* an unreadable request — is a parseable `{ ok: false, error }` envelope and still exits 0, so the
|
|
293
|
+
* caller can always parse the reply off stdout and never has to treat a non-zero exit as a lost
|
|
294
|
+
* error; the process only exits non-zero on an unexpected crash. This is the seam the Nano console
|
|
295
|
+
* re-points its Data panel at (host dry-out nano-bpm#576), replacing the vendored data-cli.ts.
|
|
296
|
+
*/
|
|
297
|
+
export async function cmdData(f, readInput = readStdin) {
|
|
298
|
+
let req;
|
|
299
|
+
try {
|
|
300
|
+
req = JSON.parse(await readInput());
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
console.log(JSON.stringify({
|
|
304
|
+
ok: false,
|
|
305
|
+
error: `bad request: ${err instanceof Error ? err.message : String(err)}`,
|
|
306
|
+
}));
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
const host = selectHost({ cwd: f.root });
|
|
310
|
+
try {
|
|
311
|
+
// Mirror runFromEnv: the host is anchored at f.root, so pass root "." to avoid
|
|
312
|
+
// double-prefixing every manifest/db path (host.abs already resolves against f.root).
|
|
313
|
+
const result = await runDataOp(host, ".", f.manifest, req);
|
|
314
|
+
console.log(JSON.stringify({ ok: true, ...result }));
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
console.log(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }));
|
|
318
|
+
}
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
260
321
|
async function cmdNew(f) {
|
|
261
322
|
const name = f._[1];
|
|
262
323
|
if (!name) {
|
|
@@ -324,7 +385,7 @@ async function cmdAdd(f) {
|
|
|
324
385
|
return 0;
|
|
325
386
|
}
|
|
326
387
|
catch (err) {
|
|
327
|
-
console.error(`✖ ${err
|
|
388
|
+
console.error(`✖ ${errorMessage(err)}`);
|
|
328
389
|
return 1;
|
|
329
390
|
}
|
|
330
391
|
}
|
|
@@ -334,7 +395,7 @@ export async function main(argv) {
|
|
|
334
395
|
f = parse(argv);
|
|
335
396
|
}
|
|
336
397
|
catch (err) {
|
|
337
|
-
console.error(
|
|
398
|
+
console.error(errorMessage(err));
|
|
338
399
|
return 1;
|
|
339
400
|
}
|
|
340
401
|
if (f.version) {
|
|
@@ -360,6 +421,8 @@ export async function main(argv) {
|
|
|
360
421
|
return cmdStubs(f);
|
|
361
422
|
case "add":
|
|
362
423
|
return cmdAdd(f);
|
|
424
|
+
case "data":
|
|
425
|
+
return cmdData(f);
|
|
363
426
|
case "run":
|
|
364
427
|
return cmdRun(f);
|
|
365
428
|
case "dev":
|
|
@@ -372,27 +435,28 @@ export async function main(argv) {
|
|
|
372
435
|
return 1;
|
|
373
436
|
}
|
|
374
437
|
}
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
|
|
438
|
+
const proc = processGlobal();
|
|
439
|
+
const deno = denoGlobal();
|
|
440
|
+
const importMetaMain = Reflect.get(import.meta, "main");
|
|
441
|
+
const argv1 = proc?.argv?.[1];
|
|
442
|
+
const nodeMain = argv1 ? import.meta.url === pathToFileURL(argv1).href : false;
|
|
443
|
+
if (importMetaMain === true || nodeMain) {
|
|
380
444
|
// Node passes args via process.argv (slice off exec+script); Deno (run directly)
|
|
381
445
|
// exposes them on Deno.args. Prefer whichever actually carries args.
|
|
382
|
-
const fromNode =
|
|
383
|
-
const fromDeno =
|
|
446
|
+
const fromNode = proc?.argv?.slice(2);
|
|
447
|
+
const fromDeno = deno?.args;
|
|
384
448
|
const argv = (fromNode && fromNode.length ? fromNode : fromDeno) ?? fromNode ?? [];
|
|
385
449
|
const exit = (code) => {
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
else if (
|
|
389
|
-
|
|
450
|
+
if (proc?.exit)
|
|
451
|
+
proc.exit(code);
|
|
452
|
+
else if (deno?.exit)
|
|
453
|
+
deno.exit(code);
|
|
390
454
|
};
|
|
391
455
|
main(argv).then((code) => {
|
|
392
456
|
if (code >= 0)
|
|
393
457
|
exit(code);
|
|
394
458
|
}, (err) => {
|
|
395
|
-
console.error(
|
|
459
|
+
console.error(errorMessage(err));
|
|
396
460
|
exit(1);
|
|
397
461
|
});
|
|
398
462
|
}
|
|
@@ -13,6 +13,20 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
13
13
|
import { isAbsolute, resolve } from "node:path";
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
15
|
import { DatabaseSync } from "node:sqlite";
|
|
16
|
+
function sqliteParams(params) {
|
|
17
|
+
return params.map((param) => {
|
|
18
|
+
if (param === null ||
|
|
19
|
+
typeof param === "string" ||
|
|
20
|
+
typeof param === "number" ||
|
|
21
|
+
typeof param === "bigint" ||
|
|
22
|
+
param instanceof Uint8Array) {
|
|
23
|
+
return param;
|
|
24
|
+
}
|
|
25
|
+
if (typeof param === "boolean")
|
|
26
|
+
return param ? 1 : 0;
|
|
27
|
+
throw new TypeError(`unsupported SQLite parameter type: ${typeof param}`);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
16
30
|
export function createDenoHost(opts = {}) {
|
|
17
31
|
const cwd = opts.cwd ?? Deno.cwd();
|
|
18
32
|
const abs = (p) => (isAbsolute(p) ? p : resolve(cwd, p));
|
|
@@ -53,7 +67,8 @@ export function createDenoHost(opts = {}) {
|
|
|
53
67
|
},
|
|
54
68
|
importModule: (p) => {
|
|
55
69
|
const href = pathToFileURL(abs(p)).href + (opts.importNonce ? `?v=${opts.importNonce}` : "");
|
|
56
|
-
|
|
70
|
+
const mod = import(__rewriteRelativeImportExtension(href));
|
|
71
|
+
return mod;
|
|
57
72
|
},
|
|
58
73
|
async serveHttp(port, handler) {
|
|
59
74
|
return startDenoServer(port, handler);
|
|
@@ -96,12 +111,13 @@ function wrapSqlite(db) {
|
|
|
96
111
|
exec: (sql) => db.exec(sql),
|
|
97
112
|
run: (sql, params = []) => {
|
|
98
113
|
const stmt = db.prepare(sql);
|
|
99
|
-
const r = stmt.run(...params);
|
|
114
|
+
const r = stmt.run(...sqliteParams(params));
|
|
100
115
|
return { changes: Number(r.changes), lastInsertRowid: r.lastInsertRowid };
|
|
101
116
|
},
|
|
102
117
|
all: (sql, params = []) => {
|
|
103
118
|
const stmt = db.prepare(sql);
|
|
104
|
-
|
|
119
|
+
// biome-ignore lint/plugin: Deno sqlite returns untyped row objects; SqliteDb.all<T> is the host adapter boundary.
|
|
120
|
+
return stmt.all(...sqliteParams(params));
|
|
105
121
|
},
|
|
106
122
|
close: () => db.close(),
|
|
107
123
|
};
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// modules is safe — neither touches its runtime's globals at module top level.
|
|
3
3
|
import { createNodeHost } from "./node.js";
|
|
4
4
|
import { createDenoHost } from "./deno.js";
|
|
5
|
+
import { denoGlobal } from "./globals.js";
|
|
5
6
|
/** True when running under Deno. */
|
|
6
7
|
export function isDeno() {
|
|
7
|
-
|
|
8
|
-
return typeof g.Deno !== "undefined" && typeof g.Deno.version !== "undefined";
|
|
8
|
+
return denoGlobal()?.version !== undefined;
|
|
9
9
|
}
|
|
10
10
|
/** Select and construct the host for the current runtime. */
|
|
11
11
|
export function selectHost(opts = {}) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DenoRuntimeGlobal {
|
|
2
|
+
version?: unknown;
|
|
3
|
+
stdin?: {
|
|
4
|
+
readable?: ReadableStream<Uint8Array>;
|
|
5
|
+
};
|
|
6
|
+
args?: string[];
|
|
7
|
+
exit?: (code: number) => void;
|
|
8
|
+
addSignalListener?: (sig: string, cb: () => void) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface ProcessRuntimeGlobal {
|
|
11
|
+
argv?: string[];
|
|
12
|
+
exit?: (code: number) => void;
|
|
13
|
+
stdin?: AsyncIterable<Uint8Array | string> & {
|
|
14
|
+
setEncoding?(encoding: string): void;
|
|
15
|
+
};
|
|
16
|
+
on?: (sig: string, cb: () => void) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function denoGlobal(): DenoRuntimeGlobal | undefined;
|
|
19
|
+
export declare function processGlobal(): ProcessRuntimeGlobal | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { isRecord } from "../core/guards.js";
|
|
2
|
+
export function denoGlobal() {
|
|
3
|
+
const value = Reflect.get(globalThis, "Deno");
|
|
4
|
+
return isRecord(value) ? value : undefined;
|
|
5
|
+
}
|
|
6
|
+
export function processGlobal() {
|
|
7
|
+
const value = Reflect.get(globalThis, "process");
|
|
8
|
+
return isRecord(value) ? value : undefined;
|
|
9
|
+
}
|
|
@@ -16,6 +16,20 @@ import { register } from "node:module";
|
|
|
16
16
|
import { isAbsolute, resolve } from "node:path";
|
|
17
17
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
18
18
|
import { DatabaseSync } from "node:sqlite";
|
|
19
|
+
function sqliteParams(params) {
|
|
20
|
+
return params.map((param) => {
|
|
21
|
+
if (param === null ||
|
|
22
|
+
typeof param === "string" ||
|
|
23
|
+
typeof param === "number" ||
|
|
24
|
+
typeof param === "bigint" ||
|
|
25
|
+
param instanceof Uint8Array) {
|
|
26
|
+
return param;
|
|
27
|
+
}
|
|
28
|
+
if (typeof param === "boolean")
|
|
29
|
+
return param ? 1 : 0;
|
|
30
|
+
throw new TypeError(`unsupported SQLite parameter type: ${typeof param}`);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
19
33
|
/** Resolve the in-process `@nanobpm/worker` shim URL relative to this adapter,
|
|
20
34
|
* tolerating both the compiled package (`.js`, the published/`urban run` path)
|
|
21
35
|
* and a from-source run (`.ts`). */
|
|
@@ -101,7 +115,8 @@ export function createNodeHost(opts = {}) {
|
|
|
101
115
|
},
|
|
102
116
|
importModule: (p) => {
|
|
103
117
|
const href = pathToFileURL(abs(p)).href + (opts.importNonce ? `?v=${opts.importNonce}` : "");
|
|
104
|
-
|
|
118
|
+
const mod = import(__rewriteRelativeImportExtension(href));
|
|
119
|
+
return mod;
|
|
105
120
|
},
|
|
106
121
|
async importConnectorModule(entry) {
|
|
107
122
|
ensureConnectorHooks();
|
|
@@ -147,12 +162,13 @@ function wrapNodeSqlite(db) {
|
|
|
147
162
|
exec: (sql) => db.exec(sql),
|
|
148
163
|
run: (sql, params = []) => {
|
|
149
164
|
const stmt = db.prepare(sql);
|
|
150
|
-
const r = stmt.run(...params);
|
|
165
|
+
const r = stmt.run(...sqliteParams(params));
|
|
151
166
|
return { changes: Number(r.changes), lastInsertRowid: r.lastInsertRowid };
|
|
152
167
|
},
|
|
153
168
|
all: (sql, params = []) => {
|
|
154
169
|
const stmt = db.prepare(sql);
|
|
155
|
-
|
|
170
|
+
// biome-ignore lint/plugin: Node sqlite returns untyped row objects; SqliteDb.all<T> is the host adapter boundary.
|
|
171
|
+
return stmt.all(...sqliteParams(params));
|
|
156
172
|
},
|
|
157
173
|
close: () => db.close(),
|
|
158
174
|
};
|
|
@@ -160,8 +176,12 @@ function wrapNodeSqlite(db) {
|
|
|
160
176
|
async function startNodeServer(port, handler) {
|
|
161
177
|
const server = createServer(async (nreq, nres) => {
|
|
162
178
|
const chunks = [];
|
|
163
|
-
for await (const c of nreq)
|
|
164
|
-
|
|
179
|
+
for await (const c of nreq) {
|
|
180
|
+
if (Buffer.isBuffer(c))
|
|
181
|
+
chunks.push(c);
|
|
182
|
+
else
|
|
183
|
+
chunks.push(Buffer.from(c));
|
|
184
|
+
}
|
|
165
185
|
const bodyText = Buffer.concat(chunks).toString("utf8");
|
|
166
186
|
const url = new URL(nreq.url ?? "/", "http://localhost");
|
|
167
187
|
const headers = new Headers();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AppManifest } from "./manifest.ts";
|
|
2
2
|
import type { EngineClient, HostContext } from "./host.ts";
|
|
3
3
|
import type { EngineSdkClient } from "../engine/sdk.ts";
|
|
4
|
+
import type { TemplateSource } from "./modules/templates.ts";
|
|
4
5
|
/** Everything a runtime module needs. Passed to each module's mount function. */
|
|
5
6
|
export interface RuntimeContext {
|
|
6
7
|
manifest: AppManifest;
|
|
@@ -8,6 +9,9 @@ export interface RuntimeContext {
|
|
|
8
9
|
engine: EngineClient;
|
|
9
10
|
/** App root directory (paths in the manifest are relative to this). */
|
|
10
11
|
root: string;
|
|
12
|
+
/** Programmatic `{{name}}` template source for deploy-time substitution (globs or a
|
|
13
|
+
* `name → content` map), merged over the manifest's `models.templates`. See `deployModels`. */
|
|
14
|
+
templates?: TemplateSource;
|
|
11
15
|
}
|
|
12
16
|
/** A mounted module returns a disposer so the runtime can tear it down cleanly. */
|
|
13
17
|
export interface Mounted {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function isRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function errorMessage(error) {
|
|
5
|
+
if (error instanceof Error)
|
|
6
|
+
return error.message;
|
|
7
|
+
if (isRecord(error) && typeof error.message === "string")
|
|
8
|
+
return error.message;
|
|
9
|
+
return String(error);
|
|
10
|
+
}
|
|
@@ -23,8 +23,9 @@ export class BpmnError extends Error {
|
|
|
23
23
|
* BpmnError constructed against a different copy of this module still routes as
|
|
24
24
|
* a BPMN error rather than a generic failure). */
|
|
25
25
|
export function isBpmnError(err) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
if (typeof err !== "object" || err === null)
|
|
27
|
+
return false;
|
|
28
|
+
const errorCode = "errorCode" in err ? err.errorCode : undefined;
|
|
29
|
+
return (typeof errorCode === "string" &&
|
|
30
|
+
errorCode.length > 0);
|
|
30
31
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { HostContext } from "./host.ts";
|
|
2
2
|
import type { AppManifest, Worker } from "@nanobpm/nano-app-schema";
|
|
3
3
|
export type { ActionDecl, AppManifest, ChatSurface, Connection, Data, DataSource, DomainField, DomainType, LlmBinding, Models, PagesSurface, Runtime, Security, Surfaces, TaskInboxSurface, Trigger, TriggerAction, Worker, } from "@nanobpm/nano-app-schema";
|
|
4
|
+
declare module "@nanobpm/nano-app-schema" {
|
|
5
|
+
interface Models {
|
|
6
|
+
/** `{{name}}` template sources substituted into model resources at deploy time: globs, a bare
|
|
7
|
+
* directory (scanned), or literal files. A template's name is its file stem
|
|
8
|
+
* (`prompts/review-round.md` → `{{review-round}}`). See `deployModels`. */
|
|
9
|
+
templates?: string[];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
4
12
|
/** The job type a worker subscribes to (schema key: `taskType`). */
|
|
5
13
|
export declare function workerJobType(w: Worker): string | undefined;
|
|
6
14
|
/**
|
|
@@ -24,7 +24,6 @@ export function expandEnvString(input, lookup) {
|
|
|
24
24
|
return dflt ?? "";
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
/** Recursively expand env placeholders across every string in a JSON-ish value. */
|
|
28
27
|
export function expandEnv(value, lookup) {
|
|
29
28
|
if (typeof value === "string")
|
|
30
29
|
return expandEnvString(value, lookup);
|
|
@@ -9,16 +9,20 @@
|
|
|
9
9
|
// Precedence: action routes are mounted BEFORE the generic pages action routes (the router
|
|
10
10
|
// is first-match-wins), so an override at `/app/actions/cancel` shadows the generic one,
|
|
11
11
|
// while an unmatched process still falls through to the generic `/app/actions/start/` route.
|
|
12
|
+
import { errorMessage } from "../guards.js";
|
|
12
13
|
import { json, normalizeRoutePath } from "../router.js";
|
|
14
|
+
function isActionHandler(value) {
|
|
15
|
+
return typeof value === "function";
|
|
16
|
+
}
|
|
13
17
|
/**
|
|
14
18
|
* Resolve an action handler from a loaded module, in priority order:
|
|
15
19
|
* 1. `default` (when it is a function — the common one-handler-per-file case)
|
|
16
20
|
* 2. a named export `handler`
|
|
17
21
|
*/
|
|
18
22
|
export function resolveActionHandler(mod) {
|
|
19
|
-
if (
|
|
23
|
+
if (isActionHandler(mod.default))
|
|
20
24
|
return mod.default;
|
|
21
|
-
if (
|
|
25
|
+
if (isActionHandler(mod.handler))
|
|
22
26
|
return mod.handler;
|
|
23
27
|
return undefined;
|
|
24
28
|
}
|
|
@@ -28,7 +32,7 @@ export function resolveActionHandler(mod) {
|
|
|
28
32
|
* surfaces as a clear `500` on that route rather than failing the whole boot.
|
|
29
33
|
*/
|
|
30
34
|
export function mountActions(ctx, app) {
|
|
31
|
-
const decls =
|
|
35
|
+
const decls = ctx.manifest.actions ?? [];
|
|
32
36
|
const routes = [];
|
|
33
37
|
const mounted = [];
|
|
34
38
|
const joinRoot = (p) => p.startsWith("/") ? p : `${ctx.root.replace(/\/+$/, "")}/${p.replace(/^\/+/, "")}`;
|
|
@@ -85,7 +89,7 @@ export function mountActions(ctx, app) {
|
|
|
85
89
|
ctx.host.log("error", "action handler module failed to load", {
|
|
86
90
|
path,
|
|
87
91
|
module: decl.module,
|
|
88
|
-
error:
|
|
92
|
+
error: errorMessage(e),
|
|
89
93
|
});
|
|
90
94
|
return json({ error: `action handler ${decl.module} failed to load` }, 500);
|
|
91
95
|
}
|
|
@@ -103,7 +107,7 @@ export function mountActions(ctx, app) {
|
|
|
103
107
|
};
|
|
104
108
|
}
|
|
105
109
|
catch (e) {
|
|
106
|
-
return json({ error:
|
|
110
|
+
return json({ error: errorMessage(e) }, 500);
|
|
107
111
|
}
|
|
108
112
|
},
|
|
109
113
|
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// This file is runtime-agnostic (core purity): the Node/Deno specifics of aliasing
|
|
16
16
|
// the pack's `@nanobpm/worker` import live behind `host.importConnectorModule`.
|
|
17
|
+
import { errorMessage, isRecord } from "../guards.js";
|
|
17
18
|
import { BpmnError } from "../host.js";
|
|
18
19
|
import { workerJobType } from "../manifest.js";
|
|
19
20
|
import { drainDefinedWorkers, } from "../../connector-worker-sdk.js";
|
|
@@ -40,11 +41,12 @@ async function readJson(ctx, relPath, opts) {
|
|
|
40
41
|
if (!(await ctx.host.exists(path)))
|
|
41
42
|
return undefined;
|
|
42
43
|
try {
|
|
43
|
-
|
|
44
|
+
const parsed = JSON.parse(await ctx.host.readTextFile(path));
|
|
45
|
+
return parsed;
|
|
44
46
|
}
|
|
45
47
|
catch (err) {
|
|
46
48
|
if (opts?.strict) {
|
|
47
|
-
throw new Error(`failed to parse ${relPath}: ${err
|
|
49
|
+
throw new Error(`failed to parse ${relPath}: ${errorMessage(err)}`);
|
|
48
50
|
}
|
|
49
51
|
return undefined;
|
|
50
52
|
}
|
|
@@ -107,7 +109,7 @@ export function adaptConnectorHandler(worker) {
|
|
|
107
109
|
if (outcome?.kind === "complete")
|
|
108
110
|
return outcome.vars ?? {};
|
|
109
111
|
// No explicit outcome: fall back to the handle's return value (SDK-style).
|
|
110
|
-
return ret
|
|
112
|
+
return isRecord(ret) ? ret : {};
|
|
111
113
|
};
|
|
112
114
|
}
|
|
113
115
|
/**
|
|
@@ -118,7 +120,7 @@ export function adaptConnectorHandler(worker) {
|
|
|
118
120
|
* the host cannot host connector workers (`importConnectorModule` absent).
|
|
119
121
|
*/
|
|
120
122
|
export async function mountConnectors(ctx, app) {
|
|
121
|
-
const decls = (ctx.manifest.workers ?? []).filter((w) => w.connector);
|
|
123
|
+
const decls = (ctx.manifest.workers ?? []).filter((w) => Boolean(w.connector));
|
|
122
124
|
const subs = [];
|
|
123
125
|
const jobTypes = [];
|
|
124
126
|
if (decls.length === 0)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HostContext } from "../host.ts";
|
|
2
|
+
import type { AppManifest } from "../manifest.ts";
|
|
3
|
+
/** The DB-manager op protocol's known op set. `domaintypes` is part of the protocol but is not
|
|
4
|
+
* yet handled here (it errors clearly — see the file header); every other op is dispatched by
|
|
5
|
+
* `runDataOp`. Typing `op` as this union (instead of `string`) gives SDK/console callers
|
|
6
|
+
* type-safety and catches typos at compile time. */
|
|
7
|
+
export type DataOp = "sources" | "schema" | "query" | "exec" | "script" | "migrations" | "migrate" | "domaintypes";
|
|
8
|
+
export interface DataRequest {
|
|
9
|
+
op: DataOp;
|
|
10
|
+
source?: string;
|
|
11
|
+
sql?: string;
|
|
12
|
+
params?: unknown[];
|
|
13
|
+
statements?: string[];
|
|
14
|
+
}
|
|
15
|
+
/** One datasource the manifest declares, resolved against the environment (env templates in
|
|
16
|
+
* `driver`/`url` are already expanded by `loadManifest`). Mirrors the console's `ResolvedSource`
|
|
17
|
+
* so the panel's datasource picker is unchanged. */
|
|
18
|
+
export interface ResolvedSource {
|
|
19
|
+
name: string;
|
|
20
|
+
driver: string;
|
|
21
|
+
url: string;
|
|
22
|
+
migrations?: string;
|
|
23
|
+
}
|
|
24
|
+
/** JSON-safe a value: BigInt → number (when lossless) or string, recursing objects/arrays. SQLite
|
|
25
|
+
* reports `lastInsertRowid` (and INTEGER columns) as BigInt, which `JSON.stringify` cannot encode. */
|
|
26
|
+
export declare function jsonSafe(v: unknown): unknown;
|
|
27
|
+
/** List every declared datasource plus the default's name (no connection opened). */
|
|
28
|
+
export declare function listSources(manifest: AppManifest): {
|
|
29
|
+
default?: string;
|
|
30
|
+
sources: ResolvedSource[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Dispatch one DB-manager op against the app at `root`. Returns the op's result object (the CLI
|
|
34
|
+
* wraps it as `{ ok: true, ...result }`); throws on a handled error (wrapped as `{ ok: false }`).
|
|
35
|
+
*/
|
|
36
|
+
export declare function runDataOp(host: HostContext, root: string, manifestPath: string, req: DataRequest): Promise<Record<string, unknown>>;
|