@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,151 @@
|
|
|
1
|
+
import type { EngineClient, JobHandler, WorkerSubscription } from "../core/host.ts";
|
|
2
|
+
import type { EngineSdkClient } from "./sdk.ts";
|
|
3
|
+
/** Coerce an engine response's process-instance key to a non-empty string, or
|
|
4
|
+
* throw — a missing key means a malformed/partial response, not a real instance. */
|
|
5
|
+
export declare function requireProcessInstanceKey(key: string | number | null | undefined): string;
|
|
6
|
+
/** A job as delivered to a nano-sdk job handler: the frame fields plus the
|
|
7
|
+
* acknowledgement actions the handler must call. */
|
|
8
|
+
export interface NanoSdkActivatedJob {
|
|
9
|
+
jobKey: string;
|
|
10
|
+
type?: string;
|
|
11
|
+
processInstanceKey?: string | number;
|
|
12
|
+
elementId?: string;
|
|
13
|
+
variables?: Record<string, unknown>;
|
|
14
|
+
complete(variables?: Record<string, unknown>): Promise<unknown>;
|
|
15
|
+
fail(body: {
|
|
16
|
+
errorMessage: string;
|
|
17
|
+
retries?: number;
|
|
18
|
+
}): Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
/** Config for a nano-sdk job worker (the subset this adapter sets). */
|
|
21
|
+
export interface NanoSdkJobWorkerConfig {
|
|
22
|
+
jobType: string;
|
|
23
|
+
jobHandler: (job: NanoSdkActivatedJob) => Promise<unknown> | unknown;
|
|
24
|
+
workerName?: string;
|
|
25
|
+
maxParallelJobs?: number;
|
|
26
|
+
jobTimeoutMs?: number;
|
|
27
|
+
pollTimeoutMs?: number;
|
|
28
|
+
fetchVariables?: string[];
|
|
29
|
+
/** Start polling immediately. This adapter sets it false and starts explicitly. */
|
|
30
|
+
autoStart?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** The handle returned by `createJobWorker`. */
|
|
33
|
+
export interface NanoSdkJobWorker {
|
|
34
|
+
start(): void | Promise<void>;
|
|
35
|
+
stop(): void | Promise<void>;
|
|
36
|
+
stopGracefully?(opts?: {
|
|
37
|
+
waitUpToMs?: number;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The subset of the `@nanobpm/nano-sdk` (Camunda orchestration-cluster) client the
|
|
42
|
+
* engine adapter uses. `createCamundaClient` returns a superset of this, so a test —
|
|
43
|
+
* or an author bringing their own transport (e.g. the embedded engine) — can inject
|
|
44
|
+
* any object satisfying it.
|
|
45
|
+
*/
|
|
46
|
+
export interface NanoSdkClient {
|
|
47
|
+
createDeployment(input: {
|
|
48
|
+
resources: File[];
|
|
49
|
+
[k: string]: unknown;
|
|
50
|
+
}, options?: unknown): Promise<Record<string, unknown>>;
|
|
51
|
+
createProcessInstance(input: {
|
|
52
|
+
processDefinitionId: string;
|
|
53
|
+
variables?: Record<string, unknown>;
|
|
54
|
+
awaitCompletion?: boolean;
|
|
55
|
+
}, options?: unknown): Promise<Record<string, unknown>>;
|
|
56
|
+
publishMessage(input: {
|
|
57
|
+
name: string;
|
|
58
|
+
correlationKey?: string;
|
|
59
|
+
variables?: Record<string, unknown>;
|
|
60
|
+
}, options?: unknown): Promise<Record<string, unknown>>;
|
|
61
|
+
searchUserTasks(input: {
|
|
62
|
+
filter?: Record<string, unknown>;
|
|
63
|
+
}, consistency?: unknown, options?: unknown): Promise<Record<string, unknown>>;
|
|
64
|
+
completeUserTask(input: {
|
|
65
|
+
userTaskKey: string;
|
|
66
|
+
variables?: Record<string, unknown>;
|
|
67
|
+
}, options?: unknown): Promise<unknown>;
|
|
68
|
+
createJobWorker(cfg: NanoSdkJobWorkerConfig): NanoSdkJobWorker;
|
|
69
|
+
close?(): void | Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
type Log = (level: "info" | "warn" | "error", msg: string, fields?: Record<string, unknown>) => void;
|
|
72
|
+
/**
|
|
73
|
+
* An `EngineClient` backed entirely by a `@nanobpm/nano-sdk` client. Deploy, create,
|
|
74
|
+
* message, and user-task calls map one-to-one onto the SDK's client methods; workers
|
|
75
|
+
* are the SDK's own job worker, which handles activation, completion, failure, and
|
|
76
|
+
* long-poll/backoff reconnect behind the same transport.
|
|
77
|
+
*/
|
|
78
|
+
export declare class SdkEngineClient implements EngineClient {
|
|
79
|
+
private readonly workers;
|
|
80
|
+
private readonly client;
|
|
81
|
+
private readonly log;
|
|
82
|
+
constructor(client: NanoSdkClient, log?: Log);
|
|
83
|
+
/**
|
|
84
|
+
* The underlying `@nanobpm/nano-sdk` client — the full Camunda orchestration-cluster
|
|
85
|
+
* surface (decisions, cluster variables, incidents, agents, batch operations, …) over
|
|
86
|
+
* the same connection. Surfaced to handlers as `AppApi.sdk`.
|
|
87
|
+
*/
|
|
88
|
+
get sdk(): EngineSdkClient;
|
|
89
|
+
deployResources(resources: {
|
|
90
|
+
name: string;
|
|
91
|
+
content: string;
|
|
92
|
+
contentType: string;
|
|
93
|
+
}[]): Promise<{
|
|
94
|
+
deployed: number;
|
|
95
|
+
}>;
|
|
96
|
+
createInstance(input: {
|
|
97
|
+
processDefinitionId: string;
|
|
98
|
+
variables?: Record<string, unknown>;
|
|
99
|
+
awaitCompletion?: boolean;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
processInstanceKey: string;
|
|
102
|
+
variables?: Record<string, unknown>;
|
|
103
|
+
}>;
|
|
104
|
+
publishMessage(input: {
|
|
105
|
+
name: string;
|
|
106
|
+
correlationKey?: string;
|
|
107
|
+
variables?: Record<string, unknown>;
|
|
108
|
+
}): Promise<void>;
|
|
109
|
+
searchUserTasks(filter?: {
|
|
110
|
+
processInstanceKey?: string;
|
|
111
|
+
assignee?: string;
|
|
112
|
+
candidateGroup?: string;
|
|
113
|
+
}): Promise<{
|
|
114
|
+
userTaskKey: string;
|
|
115
|
+
elementId?: string;
|
|
116
|
+
variables?: Record<string, unknown>;
|
|
117
|
+
}[]>;
|
|
118
|
+
completeUserTask(userTaskKey: string, variables?: Record<string, unknown>): Promise<void>;
|
|
119
|
+
registerWorker(jobType: string, handler: JobHandler, options?: {
|
|
120
|
+
workerName?: string;
|
|
121
|
+
maxParallelJobs?: number;
|
|
122
|
+
fetchVariables?: string[];
|
|
123
|
+
}): Promise<WorkerSubscription>;
|
|
124
|
+
/** Stop one worker, tolerating a Falcon worker whose transport never bound
|
|
125
|
+
* (detection still pending) — its `stop` throws, which must not fail teardown. */
|
|
126
|
+
private stopWorker;
|
|
127
|
+
close(): Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
export interface NanoSdkEngineOptions {
|
|
130
|
+
/** REST base, e.g. http://localhost:8080/v2. */
|
|
131
|
+
restAddress: string;
|
|
132
|
+
token?: string;
|
|
133
|
+
/** CAMUNDA_TRANSPORT: "auto" | "falcon" | "rest" | "embedded". Passed to createCamundaClient. */
|
|
134
|
+
transport?: string;
|
|
135
|
+
log?: Log;
|
|
136
|
+
/** Test seam: inject a ready-made SDK client (or a compatible fake). */
|
|
137
|
+
client?: NanoSdkClient;
|
|
138
|
+
/** Test seam: provide the SDK client factory instead of importing @nanobpm/nano-sdk. */
|
|
139
|
+
createClient?: (opts: {
|
|
140
|
+
restAddress: string;
|
|
141
|
+
token?: string;
|
|
142
|
+
transport?: string;
|
|
143
|
+
}) => NanoSdkClient;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Build the Urban runtime's `EngineClient`, backed by a single `@nanobpm/nano-sdk`
|
|
147
|
+
* client. Provide a `client`/`createClient` seam to inject a fake (tests, embedded
|
|
148
|
+
* transport); otherwise a client is constructed from `restAddress`/`token`/`transport`.
|
|
149
|
+
*/
|
|
150
|
+
export declare function createNanoSdkEngineClient(opts: NanoSdkEngineOptions): Promise<EngineClient>;
|
|
151
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// The Urban runtime's single engine client. Every path to a Nano engine — deploy,
|
|
2
|
+
// instance creation, message publication, user tasks, and job workers — routes
|
|
3
|
+
// through one `@nanobpm/nano-sdk` client (ADR 0055). The SDK's `createCamundaClient`
|
|
4
|
+
// transparently upgrades the throughput-critical paths (process-instance creation and
|
|
5
|
+
// job serving) to the Falcon protocol on a Nano server and falls back to REST
|
|
6
|
+
// everywhere else, so the runtime and the IDE talk to the engine the same way with a
|
|
7
|
+
// single transport instead of a hand-rolled REST client plus a Falcon shim.
|
|
8
|
+
//
|
|
9
|
+
// `@nanobpm/nano-sdk` is a direct dependency, but it is imported lazily (via an
|
|
10
|
+
// indirected specifier) so a caller that injects its own client — unit tests, the
|
|
11
|
+
// Deno smoke, or an author bringing the embedded transport — never loads it, keeping
|
|
12
|
+
// a dependency-free import graph for those paths.
|
|
13
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
14
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
15
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
16
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return path;
|
|
20
|
+
};
|
|
21
|
+
/** Coerce an engine response's process-instance key to a non-empty string, or
|
|
22
|
+
* throw — a missing key means a malformed/partial response, not a real instance. */
|
|
23
|
+
export function requireProcessInstanceKey(key) {
|
|
24
|
+
if (key == null || key === "") {
|
|
25
|
+
throw new Error("engine response missing processInstanceKey/key");
|
|
26
|
+
}
|
|
27
|
+
return String(key);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* An `EngineClient` backed entirely by a `@nanobpm/nano-sdk` client. Deploy, create,
|
|
31
|
+
* message, and user-task calls map one-to-one onto the SDK's client methods; workers
|
|
32
|
+
* are the SDK's own job worker, which handles activation, completion, failure, and
|
|
33
|
+
* long-poll/backoff reconnect behind the same transport.
|
|
34
|
+
*/
|
|
35
|
+
export class SdkEngineClient {
|
|
36
|
+
workers = new Set();
|
|
37
|
+
client;
|
|
38
|
+
log;
|
|
39
|
+
constructor(client, log = () => { }) {
|
|
40
|
+
this.client = client;
|
|
41
|
+
this.log = log;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The underlying `@nanobpm/nano-sdk` client — the full Camunda orchestration-cluster
|
|
45
|
+
* surface (decisions, cluster variables, incidents, agents, batch operations, …) over
|
|
46
|
+
* the same connection. Surfaced to handlers as `AppApi.sdk`.
|
|
47
|
+
*/
|
|
48
|
+
get sdk() {
|
|
49
|
+
return this.client;
|
|
50
|
+
}
|
|
51
|
+
async deployResources(resources) {
|
|
52
|
+
const files = resources.map((r) => new File([r.content], r.name, { type: r.contentType }));
|
|
53
|
+
await this.client.createDeployment({ resources: files });
|
|
54
|
+
return { deployed: resources.length };
|
|
55
|
+
}
|
|
56
|
+
async createInstance(input) {
|
|
57
|
+
const body = await this.client.createProcessInstance({
|
|
58
|
+
processDefinitionId: input.processDefinitionId,
|
|
59
|
+
variables: input.variables ?? {},
|
|
60
|
+
awaitCompletion: input.awaitCompletion ?? false,
|
|
61
|
+
});
|
|
62
|
+
const key = body.processInstanceKey ??
|
|
63
|
+
body.key;
|
|
64
|
+
return {
|
|
65
|
+
processInstanceKey: requireProcessInstanceKey(key),
|
|
66
|
+
variables: body.variables ?? undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async publishMessage(input) {
|
|
70
|
+
await this.client.publishMessage({
|
|
71
|
+
name: input.name,
|
|
72
|
+
correlationKey: input.correlationKey ?? "",
|
|
73
|
+
variables: input.variables ?? {},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async searchUserTasks(filter) {
|
|
77
|
+
// User tasks are an eventually consistent read; ask for zero-wait consistency so
|
|
78
|
+
// the search reflects what is currently visible without blocking.
|
|
79
|
+
const body = await this.client.searchUserTasks({ filter: (filter ?? {}) }, { consistency: { waitUpToMs: 0 } });
|
|
80
|
+
const items = body.items ?? [];
|
|
81
|
+
return items.flatMap((it) => {
|
|
82
|
+
const userTaskKey = it.userTaskKey ?? it.key;
|
|
83
|
+
if (userTaskKey == null || userTaskKey === "") {
|
|
84
|
+
this.log("warn", "skipping user task with no key in engine response");
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
return [{
|
|
88
|
+
userTaskKey: String(userTaskKey),
|
|
89
|
+
elementId: it.elementId,
|
|
90
|
+
variables: it.variables,
|
|
91
|
+
}];
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async completeUserTask(userTaskKey, variables) {
|
|
95
|
+
await this.client.completeUserTask({ userTaskKey, variables: variables ?? {} });
|
|
96
|
+
}
|
|
97
|
+
async registerWorker(jobType, handler, options) {
|
|
98
|
+
const worker = this.client.createJobWorker({
|
|
99
|
+
jobType,
|
|
100
|
+
workerName: options?.workerName ?? `urban:${jobType}`,
|
|
101
|
+
maxParallelJobs: options?.maxParallelJobs ?? 8,
|
|
102
|
+
fetchVariables: options?.fetchVariables,
|
|
103
|
+
// Own the poll lifecycle here rather than leaving it to the SDK's auto-start
|
|
104
|
+
// default, so nothing polls until `start()` is called below.
|
|
105
|
+
autoStart: false,
|
|
106
|
+
jobHandler: async (job) => {
|
|
107
|
+
const rawKey = job.jobKey;
|
|
108
|
+
if (rawKey == null || rawKey === "") {
|
|
109
|
+
this.log("warn", `activation ${jobType}: skipping job with no jobKey in engine response`);
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const engineJob = {
|
|
113
|
+
jobKey: String(rawKey),
|
|
114
|
+
jobType,
|
|
115
|
+
processInstanceKey: job.processInstanceKey != null ? String(job.processInstanceKey) : undefined,
|
|
116
|
+
elementId: job.elementId,
|
|
117
|
+
variables: job.variables ?? {},
|
|
118
|
+
};
|
|
119
|
+
try {
|
|
120
|
+
const out = await handler(engineJob);
|
|
121
|
+
return await job.complete(out ?? {});
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
125
|
+
this.log("error", `handler ${jobType} threw`, { err: message });
|
|
126
|
+
try {
|
|
127
|
+
// Best-effort failure report; the engine redelivers after the lock
|
|
128
|
+
// times out (at-least-once → handlers must be idempotent). Await +
|
|
129
|
+
// swallow so a rejected `fail` cannot escape as an unhandled rejection.
|
|
130
|
+
return await job.fail({ errorMessage: message.slice(0, 500), retries: 0 });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
await worker.start();
|
|
139
|
+
this.workers.add(worker);
|
|
140
|
+
return {
|
|
141
|
+
jobType,
|
|
142
|
+
unsubscribe: async () => {
|
|
143
|
+
await this.stopWorker(worker);
|
|
144
|
+
this.workers.delete(worker);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** Stop one worker, tolerating a Falcon worker whose transport never bound
|
|
149
|
+
* (detection still pending) — its `stop` throws, which must not fail teardown. */
|
|
150
|
+
async stopWorker(worker) {
|
|
151
|
+
try {
|
|
152
|
+
await (worker.stopGracefully ? worker.stopGracefully() : worker.stop());
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
/* worker never fully started (transport unbound); nothing to drain */
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async close() {
|
|
159
|
+
for (const w of this.workers)
|
|
160
|
+
await this.stopWorker(w);
|
|
161
|
+
this.workers.clear();
|
|
162
|
+
try {
|
|
163
|
+
await this.client.close?.();
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
/* transport already closed / never opened */
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function importNanoSdk() {
|
|
171
|
+
// Indirect the specifier so the (lazily loaded) module is not resolved at
|
|
172
|
+
// typecheck time and stays out of the import graph for injected-client paths.
|
|
173
|
+
const spec = "@nanobpm/nano-sdk";
|
|
174
|
+
const mod = (await import(__rewriteRelativeImportExtension(spec)));
|
|
175
|
+
if (typeof mod.createCamundaClient !== "function") {
|
|
176
|
+
throw new Error("@nanobpm/nano-sdk does not export createCamundaClient");
|
|
177
|
+
}
|
|
178
|
+
return mod.createCamundaClient;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Build the Urban runtime's `EngineClient`, backed by a single `@nanobpm/nano-sdk`
|
|
182
|
+
* client. Provide a `client`/`createClient` seam to inject a fake (tests, embedded
|
|
183
|
+
* transport); otherwise a client is constructed from `restAddress`/`token`/`transport`.
|
|
184
|
+
*/
|
|
185
|
+
export async function createNanoSdkEngineClient(opts) {
|
|
186
|
+
const log = opts.log ?? (() => { });
|
|
187
|
+
let client;
|
|
188
|
+
if (opts.client) {
|
|
189
|
+
client = opts.client;
|
|
190
|
+
}
|
|
191
|
+
else if (opts.createClient) {
|
|
192
|
+
client = opts.createClient({
|
|
193
|
+
restAddress: opts.restAddress,
|
|
194
|
+
token: opts.token,
|
|
195
|
+
transport: opts.transport,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
const createCamundaClient = await importNanoSdk();
|
|
200
|
+
client = createCamundaClient({
|
|
201
|
+
config: {
|
|
202
|
+
CAMUNDA_REST_ADDRESS: opts.restAddress,
|
|
203
|
+
...(opts.token ? { CAMUNDA_TOKEN: opts.token } : {}),
|
|
204
|
+
CAMUNDA_TRANSPORT: opts.transport ?? "auto",
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return new SdkEngineClient(client, log);
|
|
209
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The full nano-sdk engine client, surfaced to code-first Urban authors.
|
|
2
|
+
//
|
|
3
|
+
// `AppApi.engine` is the transport-agnostic seam (deploy, create, message, user
|
|
4
|
+
// tasks, workers). When the app runs on the nano-sdk transport, `AppApi.sdk` also
|
|
5
|
+
// carries the underlying `@nanobpm/nano-sdk` client, giving handlers the whole
|
|
6
|
+
// Camunda orchestration-cluster surface — decisions, cluster variables, incidents,
|
|
7
|
+
// agents, batch operations, and more — over the same connection, without a separate
|
|
8
|
+
// `@nanobpm/nano-sdk` dependency.
|
|
9
|
+
//
|
|
10
|
+
// This module is type-only: the `import(...)` type query resolves nano-sdk's types at
|
|
11
|
+
// typecheck and is fully erased at runtime, so re-exporting it from the runtime barrel
|
|
12
|
+
// adds no load-time dependency on nano-sdk.
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { createUrbanApp } from "./core/runtime.ts";
|
|
2
|
+
export type { CreateUrbanAppOptions, MountFlags, UrbanApp, } from "./core/runtime.ts";
|
|
3
|
+
export { expandEnv, expandEnvString, loadManifest, parseManifest, workerJobType, } from "./core/manifest.ts";
|
|
4
|
+
export type { AppManifest, DataSource, DomainType, SurfaceDecl, TriggerDecl, TypeField, WorkerDecl, } from "./core/manifest.ts";
|
|
5
|
+
export { collectManifestIssues, ManifestValidationError, validateManifest, } from "./core/validate.ts";
|
|
6
|
+
export type { ValidationIssue } from "./core/validate.ts";
|
|
7
|
+
export type { EngineClient, EngineJob, HostContext, HttpHandler, HttpRequest, HttpResponse, HttpServer, JobHandler, SqliteDb, WorkerSubscription, } from "./core/host.ts";
|
|
8
|
+
export type { AppApi, Mounted, RuntimeContext } from "./core/context.ts";
|
|
9
|
+
export { DataLayer, TypeRepo } from "./core/modules/datasource.ts";
|
|
10
|
+
export type { ProvisionedSource } from "./core/modules/datasource.ts";
|
|
11
|
+
export type { AppJobHandler } from "./core/modules/workers.ts";
|
|
12
|
+
export { resolveHandler } from "./core/modules/workers.ts";
|
|
13
|
+
export { evalCorrelation } from "./core/modules/triggers.ts";
|
|
14
|
+
export { createNodeHost } from "./adapters/node.ts";
|
|
15
|
+
export { createDenoHost } from "./adapters/deno.ts";
|
|
16
|
+
export { isDeno, selectHost } from "./adapters/detect.ts";
|
|
17
|
+
export { createNanoSdkEngineClient, SdkEngineClient } from "./engine/nanosdk.ts";
|
|
18
|
+
export type { NanoSdkClient, NanoSdkEngineOptions, NanoSdkJobWorker, NanoSdkJobWorkerConfig, } from "./engine/nanosdk.ts";
|
|
19
|
+
export type { EngineSdkClient } from "./engine/sdk.ts";
|
|
20
|
+
export { runFromEnv } from "./run.ts";
|
|
21
|
+
export type { RunOptions } from "./run.ts";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Public API for the Urban runtime (@nanobpm/urban, subpath ./runtime).
|
|
2
|
+
// Core runtime
|
|
3
|
+
export { createUrbanApp } from "./core/runtime.js";
|
|
4
|
+
// Manifest
|
|
5
|
+
export { expandEnv, expandEnvString, loadManifest, parseManifest, workerJobType, } from "./core/manifest.js";
|
|
6
|
+
// Validation
|
|
7
|
+
export { collectManifestIssues, ManifestValidationError, validateManifest, } from "./core/validate.js";
|
|
8
|
+
// Data layer
|
|
9
|
+
export { DataLayer, TypeRepo } from "./core/modules/datasource.js";
|
|
10
|
+
export { resolveHandler } from "./core/modules/workers.js";
|
|
11
|
+
export { evalCorrelation } from "./core/modules/triggers.js";
|
|
12
|
+
// Adapters + engine + run entrypoint
|
|
13
|
+
export { createNodeHost } from "./adapters/node.js";
|
|
14
|
+
export { createDenoHost } from "./adapters/deno.js";
|
|
15
|
+
export { isDeno, selectHost } from "./adapters/detect.js";
|
|
16
|
+
export { createNanoSdkEngineClient, SdkEngineClient } from "./engine/nanosdk.js";
|
|
17
|
+
export { runFromEnv } from "./run.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CreateUrbanAppOptions, type UrbanApp } from "./core/runtime.ts";
|
|
2
|
+
export interface RunOptions extends Partial<CreateUrbanAppOptions> {
|
|
3
|
+
/** App root. Default ".". */
|
|
4
|
+
root?: string;
|
|
5
|
+
/** Engine REST base. Default $CAMUNDA_REST_ADDRESS or http://localhost:8080/v2. */
|
|
6
|
+
restAddress?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Engine transport passed to `@nanobpm/nano-sdk`: "auto" (default) upgrades
|
|
9
|
+
* process-instance creation and job serving to the Falcon protocol on a Nano
|
|
10
|
+
* server and falls back to REST elsewhere; "falcon" forces it; "rest" never
|
|
11
|
+
* upgrades; "embedded" runs an in-process engine. Overridable via $CAMUNDA_TRANSPORT.
|
|
12
|
+
*/
|
|
13
|
+
transport?: string;
|
|
14
|
+
/** Install SIGINT/SIGTERM handlers to stop the app. Default true. */
|
|
15
|
+
handleSignals?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function runFromEnv(opts?: RunOptions): Promise<UrbanApp>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// runFromEnv — the one-call entrypoint a scaffolded app's main and the `urban run` CLI both
|
|
2
|
+
// use: select the host for the current runtime, build the nano-sdk engine client from env,
|
|
3
|
+
// create the Urban app, start it, and install a graceful-shutdown handler.
|
|
4
|
+
import { createUrbanApp } from "./core/runtime.js";
|
|
5
|
+
import { selectHost } from "./adapters/detect.js";
|
|
6
|
+
import { createNanoSdkEngineClient } from "./engine/nanosdk.js";
|
|
7
|
+
export async function runFromEnv(opts = {}) {
|
|
8
|
+
const host = opts.host ?? selectHost({ cwd: opts.root });
|
|
9
|
+
const restAddress = opts.restAddress ?? host.env("CAMUNDA_REST_ADDRESS") ?? "http://localhost:8080/v2";
|
|
10
|
+
const transport = opts.transport ?? host.env("CAMUNDA_TRANSPORT") ?? "auto";
|
|
11
|
+
const engine = opts.engine ??
|
|
12
|
+
(await createNanoSdkEngineClient({
|
|
13
|
+
restAddress,
|
|
14
|
+
token: host.env("CAMUNDA_TOKEN"),
|
|
15
|
+
transport,
|
|
16
|
+
log: host.log,
|
|
17
|
+
}));
|
|
18
|
+
const app = await createUrbanApp({
|
|
19
|
+
host,
|
|
20
|
+
engine,
|
|
21
|
+
// The host is anchored at opts.root (cwd), so its APIs are already
|
|
22
|
+
// root-relative; passing opts.root here as well would double-prefix every
|
|
23
|
+
// path (e.g. "<root>/<root>/nano.app.json"). Keep root "." for the app.
|
|
24
|
+
root: ".",
|
|
25
|
+
manifest: opts.manifest,
|
|
26
|
+
manifestPath: opts.manifestPath,
|
|
27
|
+
port: opts.port,
|
|
28
|
+
mount: opts.mount,
|
|
29
|
+
});
|
|
30
|
+
await app.start();
|
|
31
|
+
if (opts.handleSignals !== false)
|
|
32
|
+
installSignalHandlers(() => app.stop());
|
|
33
|
+
return app;
|
|
34
|
+
}
|
|
35
|
+
function installSignalHandlers(stop) {
|
|
36
|
+
let stopping = false;
|
|
37
|
+
const onSignal = () => {
|
|
38
|
+
if (stopping)
|
|
39
|
+
return;
|
|
40
|
+
stopping = true;
|
|
41
|
+
// Neither Deno.addSignalListener nor process.on awaits the callback, so a
|
|
42
|
+
// rejection from stop() would surface as an unhandled rejection during
|
|
43
|
+
// shutdown. Register a non-async listener and swallow/log any rejection.
|
|
44
|
+
Promise.resolve()
|
|
45
|
+
.then(stop)
|
|
46
|
+
.catch((err) => {
|
|
47
|
+
try {
|
|
48
|
+
console.error("error during shutdown:", err);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* ignore logging failures during teardown */
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const g = globalThis;
|
|
56
|
+
if (g.Deno) {
|
|
57
|
+
try {
|
|
58
|
+
g.Deno.addSignalListener("SIGINT", onSignal);
|
|
59
|
+
g.Deno.addSignalListener("SIGTERM", onSignal);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* signal listeners may be unavailable (e.g. Windows) */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (g.process) {
|
|
66
|
+
g.process.on("SIGINT", onSignal);
|
|
67
|
+
g.process.on("SIGTERM", onSignal);
|
|
68
|
+
}
|
|
69
|
+
}
|