@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.
Files changed (52) hide show
  1. package/dist/cli.js +131 -5
  2. package/dist/runtime/adapters/node.js +61 -1
  3. package/dist/runtime/connector-worker-sdk.d.ts +39 -0
  4. package/dist/runtime/connector-worker-sdk.js +40 -0
  5. package/dist/runtime/core/host.d.ts +30 -0
  6. package/dist/runtime/core/host.js +26 -1
  7. package/dist/runtime/core/modules/connectors.d.ts +53 -0
  8. package/dist/runtime/core/modules/connectors.js +204 -0
  9. package/dist/runtime/core/modules/datasource.js +20 -5
  10. package/dist/runtime/core/modules/workers.js +5 -0
  11. package/dist/runtime/core/runtime.js +7 -0
  12. package/dist/runtime/core/validate.js +34 -8
  13. package/dist/runtime/engine/nanosdk.d.ts +7 -0
  14. package/dist/runtime/engine/nanosdk.js +27 -0
  15. package/dist/runtime/index.d.ts +3 -0
  16. package/dist/runtime/index.js +5 -0
  17. package/dist/toolkit/addConnector.d.ts +29 -0
  18. package/dist/toolkit/addConnector.js +170 -0
  19. package/dist/toolkit/fsio.js +31 -2
  20. package/dist/toolkit/gen.d.ts +28 -0
  21. package/dist/toolkit/gen.js +55 -12
  22. package/dist/toolkit/index.d.ts +5 -1
  23. package/dist/toolkit/index.js +4 -1
  24. package/dist/toolkit/models.d.ts +63 -0
  25. package/dist/toolkit/models.js +142 -0
  26. package/package.json +2 -2
  27. package/src/cli.ts +128 -5
  28. package/src/runtime/adapters/node.ts +64 -1
  29. package/src/runtime/connector-worker-sdk.test.ts +56 -0
  30. package/src/runtime/connector-worker-sdk.ts +78 -0
  31. package/src/runtime/core/host.ts +40 -0
  32. package/src/runtime/core/modules/connectors.test.ts +206 -0
  33. package/src/runtime/core/modules/connectors.ts +269 -0
  34. package/src/runtime/core/modules/datasource.migrations.test.ts +114 -0
  35. package/src/runtime/core/modules/datasource.ts +24 -5
  36. package/src/runtime/core/modules/workers.test.ts +13 -0
  37. package/src/runtime/core/modules/workers.ts +4 -0
  38. package/src/runtime/core/runtime.ts +6 -0
  39. package/src/runtime/core/validate.test.ts +24 -0
  40. package/src/runtime/core/validate.ts +32 -8
  41. package/src/runtime/engine/nanosdk.test.ts +76 -0
  42. package/src/runtime/engine/nanosdk.ts +30 -0
  43. package/src/runtime/index.ts +12 -0
  44. package/src/toolkit/addConnector.test.ts +161 -0
  45. package/src/toolkit/addConnector.ts +233 -0
  46. package/src/toolkit/fsio.test.ts +23 -0
  47. package/src/toolkit/fsio.ts +22 -2
  48. package/src/toolkit/gen.test.ts +76 -1
  49. package/src/toolkit/gen.ts +81 -13
  50. package/src/toolkit/index.ts +16 -1
  51. package/src/toolkit/models.test.ts +159 -0
  52. 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] derive artifacts (migrations, worker-io)
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
- export {};
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 {};