@nanobpm/urban 0.17.1 → 0.19.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/dist/cli.js +131 -5
- package/dist/runtime/adapters/node.js +61 -1
- package/dist/runtime/connector-worker-sdk.d.ts +39 -0
- package/dist/runtime/connector-worker-sdk.js +40 -0
- package/dist/runtime/core/host.d.ts +30 -0
- package/dist/runtime/core/host.js +26 -1
- package/dist/runtime/core/modules/connectors.d.ts +53 -0
- package/dist/runtime/core/modules/connectors.js +204 -0
- package/dist/runtime/core/modules/datasource.js +20 -5
- package/dist/runtime/core/modules/workers.js +5 -0
- package/dist/runtime/core/runtime.js +7 -0
- package/dist/runtime/core/validate.js +34 -8
- package/dist/runtime/engine/nanosdk.d.ts +7 -0
- package/dist/runtime/engine/nanosdk.js +27 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +5 -0
- package/dist/toolkit/addConnector.d.ts +29 -0
- package/dist/toolkit/addConnector.js +170 -0
- package/dist/toolkit/fsio.js +31 -2
- package/dist/toolkit/gen.d.ts +28 -0
- package/dist/toolkit/gen.js +55 -12
- package/dist/toolkit/index.d.ts +5 -1
- package/dist/toolkit/index.js +4 -1
- package/dist/toolkit/models.d.ts +63 -0
- package/dist/toolkit/models.js +142 -0
- package/package.json +2 -2
- package/src/cli.ts +128 -5
- package/src/runtime/adapters/node.ts +64 -1
- package/src/runtime/connector-worker-sdk.test.ts +56 -0
- package/src/runtime/connector-worker-sdk.ts +78 -0
- package/src/runtime/core/host.ts +40 -0
- package/src/runtime/core/modules/connectors.test.ts +206 -0
- package/src/runtime/core/modules/connectors.ts +269 -0
- package/src/runtime/core/modules/datasource.migrations.test.ts +114 -0
- package/src/runtime/core/modules/datasource.ts +24 -5
- package/src/runtime/core/modules/workers.test.ts +13 -0
- package/src/runtime/core/modules/workers.ts +4 -0
- package/src/runtime/core/runtime.ts +6 -0
- package/src/runtime/core/validate.test.ts +24 -0
- package/src/runtime/core/validate.ts +32 -8
- package/src/runtime/engine/nanosdk.test.ts +76 -0
- package/src/runtime/engine/nanosdk.ts +30 -0
- package/src/runtime/index.ts +12 -0
- package/src/toolkit/addConnector.test.ts +161 -0
- package/src/toolkit/addConnector.ts +233 -0
- package/src/toolkit/fsio.test.ts +23 -0
- package/src/toolkit/fsio.ts +22 -2
- package/src/toolkit/gen.test.ts +76 -1
- package/src/toolkit/gen.ts +81 -13
- package/src/toolkit/index.ts +16 -1
- package/src/toolkit/models.test.ts +159 -0
- package/src/toolkit/models.ts +193 -0
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
//
|
|
3
3
|
// urban new <name> scaffold a new app (delegates to create-urban-app)
|
|
4
4
|
// urban check load + validate the manifest, report issues
|
|
5
|
-
// urban gen derive generated artifacts (migrations, worker I/O)
|
|
5
|
+
// urban gen derive generated artifacts (migrations, worker I/O, code-first models)
|
|
6
|
+
// urban derive derive code-first models only (workflows/*.ts → processes/*.bpmn)
|
|
6
7
|
// urban run materialize + serve the app (deploy, data, workers, surfaces, triggers)
|
|
7
8
|
// urban dev like run, plus watch sources and hot-reload on change
|
|
8
9
|
// urban deploy deploy models only, then exit
|
|
@@ -11,9 +12,10 @@
|
|
|
11
12
|
// --port <n>, -h/--help, -v/--version.
|
|
12
13
|
import { collectManifestIssues, installSignalHandlers, loadManifest, runDev, runFromEnv, selectHost, } from "./runtime/index.js";
|
|
13
14
|
import { scaffold, slugify } from "create-urban-app";
|
|
14
|
-
import { createNodeGenIO, runGen, scaffoldWorkers } from "./toolkit/index.js";
|
|
15
|
+
import { addConnector, createNodeGenIO, previewModels, runGen, scaffoldWorkers } from "./toolkit/index.js";
|
|
15
16
|
import { pathToFileURL } from "node:url";
|
|
16
17
|
import { readFileSync } from "node:fs";
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
17
19
|
// Read the package version from package.json (one hop up from both src/cli.ts and the
|
|
18
20
|
// compiled dist/cli.js) so `urban --version` never drifts from the published version.
|
|
19
21
|
function readVersion() {
|
|
@@ -27,7 +29,7 @@ function readVersion() {
|
|
|
27
29
|
}
|
|
28
30
|
const VERSION = readVersion();
|
|
29
31
|
function parse(argv) {
|
|
30
|
-
const f = { root: ".", manifest: "nano.app.json", check: false, write: false, deno: false, help: false, version: false, _: [] };
|
|
32
|
+
const f = { root: ".", manifest: "nano.app.json", check: false, write: false, deno: false, models: true, stdout: false, install: true, help: false, version: false, _: [] };
|
|
31
33
|
const need = (i, flag) => {
|
|
32
34
|
const v = argv[i];
|
|
33
35
|
if (v === undefined || v.startsWith("-"))
|
|
@@ -46,6 +48,16 @@ function parse(argv) {
|
|
|
46
48
|
f.write = true;
|
|
47
49
|
else if (a === "--deno")
|
|
48
50
|
f.deno = true;
|
|
51
|
+
else if (a === "--no-models")
|
|
52
|
+
f.models = false;
|
|
53
|
+
else if (a === "--models")
|
|
54
|
+
f.models = true;
|
|
55
|
+
else if (a === "--stdout")
|
|
56
|
+
f.stdout = true;
|
|
57
|
+
else if (a === "--no-install")
|
|
58
|
+
f.install = false;
|
|
59
|
+
else if (a === "--install")
|
|
60
|
+
f.install = true;
|
|
49
61
|
else if (a === "--code-first")
|
|
50
62
|
f.style = "code";
|
|
51
63
|
else if (a === "--style") {
|
|
@@ -81,8 +93,12 @@ Usage:
|
|
|
81
93
|
urban new <name> [--root <path>] [--deno] [--style model|code] scaffold a new Urban app
|
|
82
94
|
(--code-first is shorthand for --style code)
|
|
83
95
|
urban check validate the manifest
|
|
84
|
-
urban gen [--check]
|
|
96
|
+
urban gen [--check] [--no-models] derive artifacts (migrations, worker-io, + code-first models)
|
|
97
|
+
(--no-models: skip writing derived .bpmn; type-contracts only)
|
|
98
|
+
urban derive [--check|--stdout] derive code-first models only (workflows/*.ts → processes/*.bpmn)
|
|
99
|
+
(--stdout: print {models,incomplete} JSON without writing)
|
|
85
100
|
urban stubs [--write] scaffold write-once handler stubs from the model
|
|
101
|
+
urban add <pkg> [--no-install] install a connector pack + wire it into nano.app.json
|
|
86
102
|
urban run materialize + serve the app
|
|
87
103
|
urban dev run + watch sources, hot-reload on change
|
|
88
104
|
urban deploy deploy models only, then exit
|
|
@@ -113,7 +129,13 @@ async function cmdCheck(f) {
|
|
|
113
129
|
}
|
|
114
130
|
async function cmdGen(f) {
|
|
115
131
|
const io = createNodeGenIO();
|
|
116
|
-
const res = await runGen({ root: f.root, io, manifestFile: f.manifest, check: f.check });
|
|
132
|
+
const res = await runGen({ root: f.root, io, manifestFile: f.manifest, check: f.check, emitModels: f.models });
|
|
133
|
+
if (res.incomplete) {
|
|
134
|
+
console.error(`✖ ${res.modelErrors.length} workflow(s) failed to derive:`);
|
|
135
|
+
for (const e of res.modelErrors)
|
|
136
|
+
console.error(` • ${e.path}: ${e.message}`);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
117
139
|
if (f.check) {
|
|
118
140
|
if (res.drift.length === 0) {
|
|
119
141
|
console.log(`✔ generated artifacts are up to date (${res.artifacts.length} checked)`);
|
|
@@ -129,6 +151,52 @@ async function cmdGen(f) {
|
|
|
129
151
|
console.log(` • ${a.path}`);
|
|
130
152
|
return 0;
|
|
131
153
|
}
|
|
154
|
+
async function cmdDerive(f) {
|
|
155
|
+
// `urban derive` IS model derivation, so `--no-models` is contradictory. Reject it explicitly
|
|
156
|
+
// rather than silently ignoring it (flags are global, so it's easy to pass by accident).
|
|
157
|
+
if (!f.models) {
|
|
158
|
+
console.error("✖ `urban derive` derives models — `--no-models` is contradictory. Use `urban gen --no-models` for type-contracts only.");
|
|
159
|
+
return 2;
|
|
160
|
+
}
|
|
161
|
+
const io = createNodeGenIO();
|
|
162
|
+
// Non-writing preview: emit `{ models: [{ id, kind, xml }], incomplete }` to stdout for the
|
|
163
|
+
// console's read-only model viewer (never touches `processes/`).
|
|
164
|
+
if (f.stdout) {
|
|
165
|
+
const d = await previewModels({ root: f.root, io, manifestFile: f.manifest });
|
|
166
|
+
process.stdout.write(JSON.stringify({
|
|
167
|
+
models: d.list.map((m) => ({ id: m.id, kind: m.kind, xml: m.xml })),
|
|
168
|
+
incomplete: d.incomplete,
|
|
169
|
+
}) + "\n");
|
|
170
|
+
if (d.incomplete) {
|
|
171
|
+
for (const e of d.errors)
|
|
172
|
+
console.error(` • ${e.path}: ${e.message}`);
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
// Derive + write models only (skip the type-contract derivers); `generate_models` delegates here.
|
|
178
|
+
const res = await runGen({ root: f.root, io, manifestFile: f.manifest, check: f.check, modelsOnly: true });
|
|
179
|
+
if (res.incomplete) {
|
|
180
|
+
console.error(`✖ ${res.modelErrors.length} workflow(s) failed to derive:`);
|
|
181
|
+
for (const e of res.modelErrors)
|
|
182
|
+
console.error(` • ${e.path}: ${e.message}`);
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
if (f.check) {
|
|
186
|
+
if (res.drift.length === 0) {
|
|
187
|
+
console.log(`✔ derived models are up to date (${res.artifacts.length} checked)`);
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
console.error(`✖ ${res.drift.length} derived model(s) are out of date — run \`urban derive\`:`);
|
|
191
|
+
for (const p of res.drift)
|
|
192
|
+
console.error(` • ${p}`);
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
console.log(`✔ derived ${res.artifacts.length} model(s):`);
|
|
196
|
+
for (const a of res.artifacts)
|
|
197
|
+
console.log(` • ${a.path}`);
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
132
200
|
async function cmdStubs(f) {
|
|
133
201
|
const io = createNodeGenIO();
|
|
134
202
|
const run = await scaffoldWorkers({ root: f.root, io, manifestFile: f.manifest, write: f.write });
|
|
@@ -206,6 +274,60 @@ async function cmdNew(f) {
|
|
|
206
274
|
console.log(` # or on Deno: deno task start`);
|
|
207
275
|
return 0;
|
|
208
276
|
}
|
|
277
|
+
async function cmdAdd(f) {
|
|
278
|
+
const pkg = f._[1];
|
|
279
|
+
if (!pkg) {
|
|
280
|
+
console.error("usage: urban add <pkg> [--no-install]");
|
|
281
|
+
return 1;
|
|
282
|
+
}
|
|
283
|
+
// Install the connector pack (a subprocess — the toolkit stays runtime-agnostic).
|
|
284
|
+
if (f.install) {
|
|
285
|
+
console.log(`Installing ${pkg} …`);
|
|
286
|
+
const res = spawnSync("npm", ["install", pkg], {
|
|
287
|
+
cwd: f.root,
|
|
288
|
+
stdio: "inherit",
|
|
289
|
+
});
|
|
290
|
+
if (res.error) {
|
|
291
|
+
console.error(`npm install failed: ${res.error.message}`);
|
|
292
|
+
return 1;
|
|
293
|
+
}
|
|
294
|
+
if (res.signal) {
|
|
295
|
+
console.error(`npm install was terminated by signal ${res.signal}`);
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
if (res.status !== 0) {
|
|
299
|
+
console.error(`npm install exited with code ${res.status}`);
|
|
300
|
+
return res.status ?? 1;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const result = await addConnector({
|
|
305
|
+
root: f.root,
|
|
306
|
+
pkg,
|
|
307
|
+
manifestFile: f.manifest,
|
|
308
|
+
io: createNodeGenIO(),
|
|
309
|
+
});
|
|
310
|
+
const added = result.wired.filter((w) => !w.alreadyPresent);
|
|
311
|
+
const kept = result.wired.filter((w) => w.alreadyPresent);
|
|
312
|
+
console.log(`✔ enabled connector "${result.packId}" from ${pkg}`);
|
|
313
|
+
for (const w of added)
|
|
314
|
+
console.log(` + worker ${w.taskType}`);
|
|
315
|
+
for (const w of kept)
|
|
316
|
+
console.log(` = worker ${w.taskType} (already wired)`);
|
|
317
|
+
if (result.connection)
|
|
318
|
+
console.log(` connection: ${result.connection}`);
|
|
319
|
+
if (result.requiredEnv.length > 0) {
|
|
320
|
+
console.log(`\nSet these environment variables before \`urban run\`:`);
|
|
321
|
+
for (const name of result.requiredEnv)
|
|
322
|
+
console.log(` ${name}`);
|
|
323
|
+
}
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
catch (err) {
|
|
327
|
+
console.error(`✖ ${err.message}`);
|
|
328
|
+
return 1;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
209
331
|
export async function main(argv) {
|
|
210
332
|
let f;
|
|
211
333
|
try {
|
|
@@ -232,8 +354,12 @@ export async function main(argv) {
|
|
|
232
354
|
return cmdCheck(f);
|
|
233
355
|
case "gen":
|
|
234
356
|
return cmdGen(f);
|
|
357
|
+
case "derive":
|
|
358
|
+
return cmdDerive(f);
|
|
235
359
|
case "stubs":
|
|
236
360
|
return cmdStubs(f);
|
|
361
|
+
case "add":
|
|
362
|
+
return cmdAdd(f);
|
|
237
363
|
case "run":
|
|
238
364
|
return cmdRun(f);
|
|
239
365
|
case "dev":
|
|
@@ -9,11 +9,62 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
return path;
|
|
10
10
|
};
|
|
11
11
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
12
13
|
import { watch as fsWatch } from "node:fs";
|
|
13
14
|
import { createServer } from "node:http";
|
|
15
|
+
import { register } from "node:module";
|
|
14
16
|
import { isAbsolute, resolve } from "node:path";
|
|
15
|
-
import { pathToFileURL } from "node:url";
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
18
|
import { DatabaseSync } from "node:sqlite";
|
|
19
|
+
/** Resolve the in-process `@nanobpm/worker` shim URL relative to this adapter,
|
|
20
|
+
* tolerating both the compiled package (`.js`, the published/`urban run` path)
|
|
21
|
+
* and a from-source run (`.ts`). */
|
|
22
|
+
function connectorShimUrl() {
|
|
23
|
+
const js = new URL("../connector-worker-sdk.js", import.meta.url);
|
|
24
|
+
if (existsSync(fileURLToPath(js)))
|
|
25
|
+
return js.href;
|
|
26
|
+
return new URL("../connector-worker-sdk.ts", import.meta.url).href;
|
|
27
|
+
}
|
|
28
|
+
let connectorHooksRegistered = false;
|
|
29
|
+
/**
|
|
30
|
+
* Register (once per process) the ESM customization hooks that let a connector
|
|
31
|
+
* pack's worker `entry` be imported in-process (ADR 0050, in-process port):
|
|
32
|
+
* - a **resolve** hook aliasing the bare `@nanobpm/worker` specifier the pack
|
|
33
|
+
* imports to the runtime's shim, so its `defineWorker(...)` registers into the
|
|
34
|
+
* registry the runtime drains;
|
|
35
|
+
* - a **load** hook that strips TypeScript from ANY `.ts` module resolved under
|
|
36
|
+
* `node_modules` (Node refuses to type-strip there by default). In practice
|
|
37
|
+
* only a connector pack's `.ts` entry and its `.ts` imports travel this path,
|
|
38
|
+
* but the hook is process-global once registered, so it applies to every
|
|
39
|
+
* subsequent `node_modules` `.ts` import — a safe superset (Node would
|
|
40
|
+
* otherwise throw on such a file). The module's real URL is preserved so its
|
|
41
|
+
* own bare imports (npm deps) still resolve.
|
|
42
|
+
* The hooks run on a separate thread, so the shim URL is baked into the hook
|
|
43
|
+
* source; the shim itself loads on the main thread (shared registry instance).
|
|
44
|
+
*/
|
|
45
|
+
function ensureConnectorHooks() {
|
|
46
|
+
if (connectorHooksRegistered)
|
|
47
|
+
return;
|
|
48
|
+
const shim = JSON.stringify(connectorShimUrl());
|
|
49
|
+
const src = `import { readFileSync } from "node:fs";\n` +
|
|
50
|
+
`import { fileURLToPath } from "node:url";\n` +
|
|
51
|
+
`import { stripTypeScriptTypes } from "node:module";\n` +
|
|
52
|
+
`const SHIM = ${shim};\n` +
|
|
53
|
+
`export async function resolve(spec, ctx, next) {\n` +
|
|
54
|
+
` if (spec === "@nanobpm/worker") return { url: SHIM, shortCircuit: true };\n` +
|
|
55
|
+
` return next(spec, ctx);\n` +
|
|
56
|
+
`}\n` +
|
|
57
|
+
`export async function load(url, ctx, next) {\n` +
|
|
58
|
+
` const path = url.split("?")[0];\n` +
|
|
59
|
+
` if (path.endsWith(".ts") && path.includes("/node_modules/")) {\n` +
|
|
60
|
+
` const source = stripTypeScriptTypes(readFileSync(fileURLToPath(path), "utf8"), { mode: "strip" });\n` +
|
|
61
|
+
` return { format: "module", source, shortCircuit: true };\n` +
|
|
62
|
+
` }\n` +
|
|
63
|
+
` return next(url, ctx);\n` +
|
|
64
|
+
`}\n`;
|
|
65
|
+
register("data:text/javascript," + encodeURIComponent(src));
|
|
66
|
+
connectorHooksRegistered = true;
|
|
67
|
+
}
|
|
17
68
|
export function createNodeHost(opts = {}) {
|
|
18
69
|
const cwd = opts.cwd ?? process.cwd();
|
|
19
70
|
const abs = (p) => (isAbsolute(p) ? p : resolve(cwd, p));
|
|
@@ -52,6 +103,15 @@ export function createNodeHost(opts = {}) {
|
|
|
52
103
|
const href = pathToFileURL(abs(p)).href + (opts.importNonce ? `?v=${opts.importNonce}` : "");
|
|
53
104
|
return import(__rewriteRelativeImportExtension(href));
|
|
54
105
|
},
|
|
106
|
+
async importConnectorModule(entry) {
|
|
107
|
+
ensureConnectorHooks();
|
|
108
|
+
// Import for side effects only: the pack's top-level `defineWorker(...)` runs
|
|
109
|
+
// and registers into the shim registry, which the caller drains. Apply the
|
|
110
|
+
// dev `importNonce` cache-buster (as importModule does) so a hot-reloaded
|
|
111
|
+
// pack entry is re-evaluated instead of served stale from the ESM cache.
|
|
112
|
+
const href = pathToFileURL(abs(entry)).href + (opts.importNonce ? `?v=${opts.importNonce}` : "");
|
|
113
|
+
await import(__rewriteRelativeImportExtension(href));
|
|
114
|
+
},
|
|
55
115
|
async serveHttp(port, handler) {
|
|
56
116
|
return await startNodeServer(port, handler);
|
|
57
117
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** One activated job handed to a connector worker's `handle` (the subset the
|
|
2
|
+
* `@nanobpm/worker` contract exposes; mirrors packages/connector-slack/types). */
|
|
3
|
+
export interface ConnectorWorkerJob {
|
|
4
|
+
readonly jobKey: string;
|
|
5
|
+
readonly type: string;
|
|
6
|
+
readonly processInstanceKey: string;
|
|
7
|
+
readonly elementId?: string;
|
|
8
|
+
readonly variables: Record<string, unknown>;
|
|
9
|
+
/** Complete the job, optionally with output variables. */
|
|
10
|
+
complete(variables?: Record<string, unknown>): Promise<void>;
|
|
11
|
+
/** Fail the job (retryable). */
|
|
12
|
+
fail(message: string, retries?: number): Promise<void>;
|
|
13
|
+
/** Raise a BPMN error (routed to an error boundary event). */
|
|
14
|
+
error(code: string, message?: string): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
/** The options a connector worker passes to `defineWorker`. */
|
|
17
|
+
export interface ConnectorWorkerOptions {
|
|
18
|
+
type: string;
|
|
19
|
+
maxParallelJobs?: number;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
/** Ignored in-process: the runtime owns the engine connection. Accepted so a
|
|
22
|
+
* pack authored against the standalone SDK still type-checks and imports. */
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
worker?: string;
|
|
25
|
+
handle(job: ConnectorWorkerJob): Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
|
|
26
|
+
}
|
|
27
|
+
/** A worker the runtime will register on the engine, drained from the registry. */
|
|
28
|
+
export interface DefinedConnectorWorker {
|
|
29
|
+
type: string;
|
|
30
|
+
maxParallelJobs?: number;
|
|
31
|
+
handle: ConnectorWorkerOptions["handle"];
|
|
32
|
+
}
|
|
33
|
+
/** The `@nanobpm/worker` entrypoint the pack calls. Records the handler; does not
|
|
34
|
+
* connect. */
|
|
35
|
+
export declare function defineWorker(opts: ConnectorWorkerOptions): void;
|
|
36
|
+
/** Return every worker registered since the last drain, and clear the registry.
|
|
37
|
+
* The runtime calls this immediately after importing one pack entry, so the
|
|
38
|
+
* returned set belongs to that pack. */
|
|
39
|
+
export declare function drainDefinedWorkers(): DefinedConnectorWorker[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// The in-process `@nanobpm/worker` shim (ADR 0050, in-process port).
|
|
2
|
+
//
|
|
3
|
+
// A connector pack's `worker.ts` is a self-contained program that imports the
|
|
4
|
+
// host-provided `@nanobpm/worker` SDK and calls `defineWorker({ type, handle })`.
|
|
5
|
+
// The old Rust host ran each such worker as its own supervised child process. The
|
|
6
|
+
// @nanobpm/urban runtime hosts app workers IN-PROCESS, so it hosts connector
|
|
7
|
+
// workers the same way: it aliases the pack's `@nanobpm/worker` import to THIS
|
|
8
|
+
// module, imports the pack entry, and reads back the registered handler(s) —
|
|
9
|
+
// which it then registers on the engine like any other worker.
|
|
10
|
+
//
|
|
11
|
+
// `defineWorker` here does NOT connect to anything. It only records the handler in
|
|
12
|
+
// a module-level registry that the runtime drains (`drainDefinedWorkers`). Because
|
|
13
|
+
// the pack and the runtime resolve `@nanobpm/worker` to the SAME module URL, they
|
|
14
|
+
// share this registry instance.
|
|
15
|
+
//
|
|
16
|
+
// This module is deliberately runtime-agnostic (no `node:*`, no `Deno`): it is the
|
|
17
|
+
// contract surface, and the Node/Deno specifics of aliasing the import live in the
|
|
18
|
+
// adapters.
|
|
19
|
+
// Module-level registry. Shared across every importer of this module URL, which is
|
|
20
|
+
// exactly how the pack's `defineWorker` call reaches the runtime's drain: both
|
|
21
|
+
// resolve `@nanobpm/worker` to this file.
|
|
22
|
+
const registered = [];
|
|
23
|
+
/** The `@nanobpm/worker` entrypoint the pack calls. Records the handler; does not
|
|
24
|
+
* connect. */
|
|
25
|
+
export function defineWorker(opts) {
|
|
26
|
+
if (!opts || typeof opts.type !== "string" || opts.type.length === 0) {
|
|
27
|
+
throw new Error("defineWorker: `type` is required");
|
|
28
|
+
}
|
|
29
|
+
if (typeof opts.handle !== "function") {
|
|
30
|
+
throw new Error(`defineWorker("${opts.type}"): handle must be a function`);
|
|
31
|
+
}
|
|
32
|
+
registered.push({ type: opts.type, maxParallelJobs: opts.maxParallelJobs, handle: opts.handle });
|
|
33
|
+
}
|
|
34
|
+
/** Return every worker registered since the last drain, and clear the registry.
|
|
35
|
+
* The runtime calls this immediately after importing one pack entry, so the
|
|
36
|
+
* returned set belongs to that pack. */
|
|
37
|
+
export function drainDefinedWorkers() {
|
|
38
|
+
const out = registered.splice(0, registered.length);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
@@ -60,6 +60,16 @@ export interface HostContext {
|
|
|
60
60
|
* between runtimes, so it lives behind the host seam.
|
|
61
61
|
*/
|
|
62
62
|
importModule(path: string): Promise<Record<string, unknown>>;
|
|
63
|
+
/**
|
|
64
|
+
* Import a connector pack's worker `entry` (ADR 0050) with the bare
|
|
65
|
+
* `@nanobpm/worker` specifier the pack imports aliased to the runtime's
|
|
66
|
+
* in-process shim (`connector-worker-sdk.ts`), so its top-level
|
|
67
|
+
* `defineWorker(...)` registers into the shared registry. `entry` is an
|
|
68
|
+
* absolute path (already resolved from the installed pack). Hosts that cannot
|
|
69
|
+
* alias a bare specifier (or run no connector workers) may omit this; the
|
|
70
|
+
* connector mount then reports the workers as unsupported rather than crashing.
|
|
71
|
+
*/
|
|
72
|
+
importConnectorModule?(entry: string): Promise<void>;
|
|
63
73
|
/** Start an HTTP server. Routing is done by the caller inside `handler`. */
|
|
64
74
|
serveHttp(port: number, handler: HttpHandler): Promise<HttpServer>;
|
|
65
75
|
/**
|
|
@@ -87,6 +97,26 @@ export interface EngineJob<In extends object = Record<string, unknown>> {
|
|
|
87
97
|
variables: In;
|
|
88
98
|
}
|
|
89
99
|
export type JobHandler = (job: EngineJob) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
|
|
100
|
+
/**
|
|
101
|
+
* A BPMN error raised by a job handler. When a handler throws this, the engine
|
|
102
|
+
* adapter reports it to the engine as a BPMN error (`throwError`) — routed to a
|
|
103
|
+
* matching error boundary/event subprocess — instead of a plain job failure
|
|
104
|
+
* (which retries then raises an incident). This is the runtime face of a
|
|
105
|
+
* connector worker's `job.error(code, message)` (ADR 0050): a non-retryable,
|
|
106
|
+
* modelled outcome. Handlers that throw any other error are failed (retried).
|
|
107
|
+
*/
|
|
108
|
+
export declare class BpmnError extends Error {
|
|
109
|
+
readonly errorCode: string;
|
|
110
|
+
constructor(errorCode: string, message?: string);
|
|
111
|
+
}
|
|
112
|
+
/** Structural guard: true for a {@link BpmnError} or any error carrying a
|
|
113
|
+
* non-empty string `errorCode`, so the check survives module-duplication (a
|
|
114
|
+
* BpmnError constructed against a different copy of this module still routes as
|
|
115
|
+
* a BPMN error rather than a generic failure). */
|
|
116
|
+
export declare function isBpmnError(err: unknown): err is {
|
|
117
|
+
errorCode: string;
|
|
118
|
+
message?: string;
|
|
119
|
+
};
|
|
90
120
|
/** A registered worker subscription. */
|
|
91
121
|
export interface WorkerSubscription {
|
|
92
122
|
readonly jobType: string;
|
|
@@ -2,4 +2,29 @@
|
|
|
2
2
|
// JavaScript runtime (Node or Deno) or the Nano engine. Everything under core/ is
|
|
3
3
|
// written against these interfaces and MUST NOT import `node:*` or reference `Deno`.
|
|
4
4
|
// The adapters/ directory supplies concrete implementations.
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* A BPMN error raised by a job handler. When a handler throws this, the engine
|
|
7
|
+
* adapter reports it to the engine as a BPMN error (`throwError`) — routed to a
|
|
8
|
+
* matching error boundary/event subprocess — instead of a plain job failure
|
|
9
|
+
* (which retries then raises an incident). This is the runtime face of a
|
|
10
|
+
* connector worker's `job.error(code, message)` (ADR 0050): a non-retryable,
|
|
11
|
+
* modelled outcome. Handlers that throw any other error are failed (retried).
|
|
12
|
+
*/
|
|
13
|
+
export class BpmnError extends Error {
|
|
14
|
+
errorCode;
|
|
15
|
+
constructor(errorCode, message) {
|
|
16
|
+
super(message ?? errorCode);
|
|
17
|
+
this.name = "BpmnError";
|
|
18
|
+
this.errorCode = errorCode;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Structural guard: true for a {@link BpmnError} or any error carrying a
|
|
22
|
+
* non-empty string `errorCode`, so the check survives module-duplication (a
|
|
23
|
+
* BpmnError constructed against a different copy of this module still routes as
|
|
24
|
+
* a BPMN error rather than a generic failure). */
|
|
25
|
+
export function isBpmnError(err) {
|
|
26
|
+
return (typeof err === "object" &&
|
|
27
|
+
err !== null &&
|
|
28
|
+
typeof err.errorCode === "string" &&
|
|
29
|
+
err.errorCode.length > 0);
|
|
30
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AppApi, Mounted, RuntimeContext } from "../context.ts";
|
|
2
|
+
import { type JobHandler } from "../host.ts";
|
|
3
|
+
import { type DefinedConnectorWorker } from "../../connector-worker-sdk.ts";
|
|
4
|
+
/** A config field a pack declares (subset of ext-types `ConfigField`): an env
|
|
5
|
+
* pointer with an optional default. */
|
|
6
|
+
interface PackConfigField {
|
|
7
|
+
key: string;
|
|
8
|
+
env?: string;
|
|
9
|
+
default?: string;
|
|
10
|
+
}
|
|
11
|
+
/** A worker a pack declares (subset of ext-types `WorkerSpec`). */
|
|
12
|
+
interface PackWorkerSpec {
|
|
13
|
+
type: string;
|
|
14
|
+
entry: string;
|
|
15
|
+
maxParallelJobs?: number;
|
|
16
|
+
configFields?: PackConfigField[];
|
|
17
|
+
}
|
|
18
|
+
/** The pack manifest (`nano-ide.ext.json`) fields the runtime reads. */
|
|
19
|
+
interface PackManifest {
|
|
20
|
+
id: string;
|
|
21
|
+
workers?: PackWorkerSpec[];
|
|
22
|
+
}
|
|
23
|
+
/** An installed connector pack, indexed by its manifest `id`. */
|
|
24
|
+
interface InstalledPack {
|
|
25
|
+
id: string;
|
|
26
|
+
/** Pack dir relative to the app root, e.g. `node_modules/@nanobpm/nano-ide-connector-slack`. */
|
|
27
|
+
dir: string;
|
|
28
|
+
manifest: PackManifest;
|
|
29
|
+
}
|
|
30
|
+
export interface ConnectorsHandle extends Mounted {
|
|
31
|
+
readonly jobTypes: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Discover installed connector packs: read the app `package.json` dependencies,
|
|
35
|
+
* and for each that ships a `nano-ide.ext.json` with a worker, index it by its
|
|
36
|
+
* declared pack `id` (the value a manifest `worker.connector` field carries — NOT
|
|
37
|
+
* the npm package name).
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveInstalledConnectors(ctx: RuntimeContext): Promise<Map<string, InstalledPack>>;
|
|
40
|
+
/** Wrap a pack's `DefinedConnectorWorker.handle` as an engine `JobHandler`,
|
|
41
|
+
* bridging the `@nanobpm/worker` job facade (complete/fail/error) to the engine
|
|
42
|
+
* seam. `job.error(code)` becomes a thrown {@link BpmnError} (routed to a BPMN
|
|
43
|
+
* error boundary by the engine adapter); `job.fail(msg)` a plain throw (retry). */
|
|
44
|
+
export declare function adaptConnectorHandler(worker: DefinedConnectorWorker): JobHandler;
|
|
45
|
+
/**
|
|
46
|
+
* Mount every enabled connector worker (`workers[]` entries carrying a
|
|
47
|
+
* `connector` field). Fails closed on a seam mismatch (an enabled connector whose
|
|
48
|
+
* pack or worker type is not installed); skips-with-warning a worker whose
|
|
49
|
+
* required env is unset (so a missing credential does not crash the app), or when
|
|
50
|
+
* the host cannot host connector workers (`importConnectorModule` absent).
|
|
51
|
+
*/
|
|
52
|
+
export declare function mountConnectors(ctx: RuntimeContext, app: AppApi): Promise<ConnectorsHandle>;
|
|
53
|
+
export {};
|