@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
|
@@ -1,124 +1,284 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
createNanoSdkEngineClient,
|
|
5
|
+
requireProcessInstanceKey,
|
|
6
|
+
SdkEngineClient,
|
|
7
|
+
type NanoSdkActivatedJob,
|
|
8
|
+
type NanoSdkClient,
|
|
9
|
+
type NanoSdkJobWorker,
|
|
10
|
+
type NanoSdkJobWorkerConfig,
|
|
11
|
+
} from "./nanosdk.ts";
|
|
5
12
|
|
|
6
|
-
/** A
|
|
7
|
-
function
|
|
13
|
+
/** A fake nano-sdk client that records calls and lets a test drive its job worker. */
|
|
14
|
+
function fakeSdkClient(overrides: Partial<NanoSdkClient> = {}): NanoSdkClient & {
|
|
15
|
+
calls: string[];
|
|
16
|
+
deployments: File[][];
|
|
17
|
+
workers: {
|
|
18
|
+
cfg: NanoSdkJobWorkerConfig;
|
|
19
|
+
started: number;
|
|
20
|
+
stopped: number;
|
|
21
|
+
dispatch: (job: NanoSdkActivatedJob) => Promise<unknown> | unknown;
|
|
22
|
+
}[];
|
|
23
|
+
closed: number;
|
|
24
|
+
} {
|
|
8
25
|
const calls: string[] = [];
|
|
9
|
-
|
|
26
|
+
const deployments: File[][] = [];
|
|
27
|
+
const workers: {
|
|
28
|
+
cfg: NanoSdkJobWorkerConfig;
|
|
29
|
+
started: number;
|
|
30
|
+
stopped: number;
|
|
31
|
+
dispatch: (job: NanoSdkActivatedJob) => Promise<unknown> | unknown;
|
|
32
|
+
}[] = [];
|
|
33
|
+
let closed = 0;
|
|
34
|
+
|
|
35
|
+
const client: NanoSdkClient & {
|
|
36
|
+
calls: string[];
|
|
37
|
+
deployments: File[][];
|
|
38
|
+
workers: typeof workers;
|
|
39
|
+
closed: number;
|
|
40
|
+
} = {
|
|
10
41
|
calls,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
42
|
+
deployments,
|
|
43
|
+
workers,
|
|
44
|
+
get closed() {
|
|
45
|
+
return closed;
|
|
46
|
+
},
|
|
47
|
+
async createDeployment(input) {
|
|
48
|
+
calls.push("createDeployment");
|
|
49
|
+
deployments.push(input.resources);
|
|
50
|
+
return { deployments: input.resources.map((_, i) => ({ i })) };
|
|
14
51
|
},
|
|
15
|
-
async
|
|
16
|
-
calls.push("
|
|
17
|
-
return { processInstanceKey:
|
|
52
|
+
async createProcessInstance(input) {
|
|
53
|
+
calls.push("createProcessInstance");
|
|
54
|
+
return { processInstanceKey: 99, variables: input.variables };
|
|
18
55
|
},
|
|
19
|
-
async publishMessage() {
|
|
56
|
+
async publishMessage(input) {
|
|
20
57
|
calls.push("publishMessage");
|
|
58
|
+
return { key: 1, ...input };
|
|
21
59
|
},
|
|
22
60
|
async searchUserTasks() {
|
|
23
61
|
calls.push("searchUserTasks");
|
|
24
|
-
return [];
|
|
62
|
+
return { items: [] };
|
|
25
63
|
},
|
|
26
64
|
async completeUserTask() {
|
|
27
65
|
calls.push("completeUserTask");
|
|
66
|
+
return {};
|
|
28
67
|
},
|
|
29
|
-
|
|
30
|
-
calls.push("
|
|
31
|
-
|
|
68
|
+
createJobWorker(cfg) {
|
|
69
|
+
calls.push("createJobWorker");
|
|
70
|
+
const rec = { cfg, started: 0, stopped: 0, dispatch: cfg.jobHandler };
|
|
71
|
+
workers.push(rec);
|
|
72
|
+
const worker: NanoSdkJobWorker = {
|
|
73
|
+
start() {
|
|
74
|
+
rec.started++;
|
|
75
|
+
},
|
|
76
|
+
stop() {
|
|
77
|
+
rec.stopped++;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
return worker;
|
|
32
81
|
},
|
|
33
|
-
|
|
34
|
-
|
|
82
|
+
close() {
|
|
83
|
+
closed++;
|
|
35
84
|
},
|
|
85
|
+
...overrides,
|
|
36
86
|
};
|
|
87
|
+
return client;
|
|
37
88
|
}
|
|
38
89
|
|
|
39
|
-
test("
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
90
|
+
test("requireProcessInstanceKey coerces and rejects empties", () => {
|
|
91
|
+
assert.equal(requireProcessInstanceKey(42), "42");
|
|
92
|
+
assert.equal(requireProcessInstanceKey("k"), "k");
|
|
93
|
+
assert.throws(() => requireProcessInstanceKey(null), /missing processInstanceKey/);
|
|
94
|
+
assert.throws(() => requireProcessInstanceKey(""), /missing processInstanceKey/);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("deployResources builds web Files and calls createDeployment", async () => {
|
|
98
|
+
const client = fakeSdkClient();
|
|
99
|
+
const engine = new SdkEngineClient(client);
|
|
100
|
+
const res = await engine.deployResources([
|
|
101
|
+
{ name: "a.bpmn", content: "<x/>", contentType: "text/xml" },
|
|
102
|
+
{ name: "b.form", content: "{}", contentType: "application/json" },
|
|
103
|
+
]);
|
|
104
|
+
assert.deepEqual(res, { deployed: 2 });
|
|
105
|
+
assert.equal(client.deployments.length, 1);
|
|
106
|
+
const [f0, f1] = client.deployments[0];
|
|
107
|
+
assert.ok(f0 instanceof File);
|
|
108
|
+
assert.equal(f0.name, "a.bpmn");
|
|
109
|
+
assert.equal(f0.type, "text/xml");
|
|
110
|
+
assert.equal(await f0.text(), "<x/>");
|
|
111
|
+
assert.equal(f1.name, "b.form");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("createInstance routes through the SDK and coerces the key", async () => {
|
|
115
|
+
const client = fakeSdkClient();
|
|
116
|
+
const engine = new SdkEngineClient(client);
|
|
117
|
+
const res = await engine.createInstance({ processDefinitionId: "p", variables: { a: 1 } });
|
|
118
|
+
assert.equal(res.processInstanceKey, "99");
|
|
119
|
+
assert.deepEqual(res.variables, { a: 1 });
|
|
120
|
+
assert.ok(client.calls.includes("createProcessInstance"));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("createInstance throws when the SDK response omits the instance key", async () => {
|
|
124
|
+
const client = fakeSdkClient({
|
|
125
|
+
createProcessInstance: async () => ({ variables: { ok: true } }),
|
|
52
126
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
assert.ok(!fallback.calls.includes("createInstance"), "did not use REST for create");
|
|
127
|
+
const engine = new SdkEngineClient(client);
|
|
128
|
+
await assert.rejects(
|
|
129
|
+
() => engine.createInstance({ processDefinitionId: "p" }),
|
|
130
|
+
/missing processInstanceKey/,
|
|
131
|
+
);
|
|
59
132
|
});
|
|
60
133
|
|
|
61
|
-
test("
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
134
|
+
test("publishMessage defaults correlationKey/variables", async () => {
|
|
135
|
+
let seen: Record<string, unknown> | undefined;
|
|
136
|
+
const client = fakeSdkClient({
|
|
137
|
+
publishMessage: async (input) => {
|
|
138
|
+
seen = input;
|
|
139
|
+
return {};
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const engine = new SdkEngineClient(client);
|
|
143
|
+
await engine.publishMessage({ name: "m" });
|
|
144
|
+
assert.deepEqual(seen, { name: "m", correlationKey: "", variables: {} });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("searchUserTasks passes zero-wait consistency and maps items", async () => {
|
|
148
|
+
let consistency: unknown;
|
|
149
|
+
const client = fakeSdkClient({
|
|
150
|
+
searchUserTasks: async (_input, c) => {
|
|
151
|
+
consistency = c;
|
|
152
|
+
return {
|
|
153
|
+
items: [
|
|
154
|
+
{ userTaskKey: 7, elementId: "task_a", variables: { x: 1 } },
|
|
155
|
+
{ key: "" }, // keyless → skipped
|
|
156
|
+
{ userTaskKey: "8" },
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
},
|
|
67
160
|
});
|
|
68
|
-
|
|
69
|
-
await engine
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"deployResources",
|
|
75
|
-
"publishMessage",
|
|
76
|
-
"searchUserTasks",
|
|
77
|
-
"completeUserTask",
|
|
78
|
-
"registerWorker",
|
|
161
|
+
const engine = new SdkEngineClient(client);
|
|
162
|
+
const tasks = await engine.searchUserTasks({ processInstanceKey: "pi" });
|
|
163
|
+
assert.deepEqual(consistency, { consistency: { waitUpToMs: 0 } });
|
|
164
|
+
assert.deepEqual(tasks, [
|
|
165
|
+
{ userTaskKey: "7", elementId: "task_a", variables: { x: 1 } },
|
|
166
|
+
{ userTaskKey: "8", elementId: undefined, variables: undefined },
|
|
79
167
|
]);
|
|
80
168
|
});
|
|
81
169
|
|
|
82
|
-
test("
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
170
|
+
test("completeUserTask routes through the SDK", async () => {
|
|
171
|
+
let seen: unknown;
|
|
172
|
+
const client = fakeSdkClient({
|
|
173
|
+
completeUserTask: async (input) => {
|
|
174
|
+
seen = input;
|
|
175
|
+
return {};
|
|
87
176
|
},
|
|
88
177
|
});
|
|
89
|
-
|
|
178
|
+
const engine = new SdkEngineClient(client);
|
|
179
|
+
await engine.completeUserTask("utk", { done: true });
|
|
180
|
+
assert.deepEqual(seen, { userTaskKey: "utk", variables: { done: true } });
|
|
90
181
|
});
|
|
91
182
|
|
|
92
|
-
test("
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
183
|
+
test("registerWorker creates a worker (autoStart false), starts it, and dispatches + completes", async () => {
|
|
184
|
+
const client = fakeSdkClient();
|
|
185
|
+
const engine = new SdkEngineClient(client);
|
|
186
|
+
const handled: Record<string, unknown>[] = [];
|
|
187
|
+
const sub = await engine.registerWorker(
|
|
188
|
+
"svc",
|
|
189
|
+
(job) => {
|
|
190
|
+
handled.push(job.variables);
|
|
191
|
+
return { out: job.variables.n };
|
|
192
|
+
},
|
|
193
|
+
{ maxParallelJobs: 4 },
|
|
194
|
+
);
|
|
195
|
+
assert.equal(sub.jobType, "svc");
|
|
196
|
+
const rec = client.workers[0];
|
|
197
|
+
assert.equal(rec.cfg.autoStart, false);
|
|
198
|
+
assert.equal(rec.cfg.workerName, "urban:svc");
|
|
199
|
+
assert.equal(rec.cfg.maxParallelJobs, 4);
|
|
200
|
+
assert.equal(rec.started, 1);
|
|
201
|
+
|
|
202
|
+
let completedWith: Record<string, unknown> | undefined;
|
|
203
|
+
await rec.dispatch({
|
|
204
|
+
jobKey: 12,
|
|
205
|
+
processInstanceKey: 34,
|
|
206
|
+
elementId: "e1",
|
|
207
|
+
variables: { n: 5 },
|
|
208
|
+
async complete(v?: Record<string, unknown>) {
|
|
209
|
+
completedWith = v;
|
|
210
|
+
return "receipt";
|
|
211
|
+
},
|
|
212
|
+
async fail() {
|
|
213
|
+
throw new Error("should not fail");
|
|
214
|
+
},
|
|
215
|
+
} as unknown as NanoSdkActivatedJob);
|
|
216
|
+
assert.deepEqual(handled, [{ n: 5 }]);
|
|
217
|
+
assert.deepEqual(completedWith, { out: 5 });
|
|
218
|
+
|
|
219
|
+
await sub.unsubscribe();
|
|
220
|
+
assert.equal(rec.stopped, 1);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("registerWorker fails the job when the handler throws", async () => {
|
|
224
|
+
const client = fakeSdkClient();
|
|
225
|
+
const engine = new SdkEngineClient(client);
|
|
226
|
+
await engine.registerWorker("svc", () => {
|
|
227
|
+
throw new Error("boom");
|
|
104
228
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
229
|
+
const rec = client.workers[0];
|
|
230
|
+
let failBody: { errorMessage: string; retries?: number } | undefined;
|
|
231
|
+
await rec.dispatch({
|
|
232
|
+
jobKey: "j1",
|
|
233
|
+
variables: {},
|
|
234
|
+
async complete() {
|
|
235
|
+
throw new Error("should not complete");
|
|
236
|
+
},
|
|
237
|
+
async fail(body: { errorMessage: string; retries?: number }) {
|
|
238
|
+
failBody = body;
|
|
239
|
+
return "failed";
|
|
240
|
+
},
|
|
241
|
+
} as unknown as NanoSdkActivatedJob);
|
|
242
|
+
assert.equal(failBody?.errorMessage, "boom");
|
|
243
|
+
assert.equal(failBody?.retries, 0);
|
|
108
244
|
});
|
|
109
245
|
|
|
110
|
-
test("
|
|
111
|
-
const
|
|
246
|
+
test("close stops every worker and closes the SDK client", async () => {
|
|
247
|
+
const client = fakeSdkClient();
|
|
248
|
+
const engine = new SdkEngineClient(client);
|
|
249
|
+
await engine.registerWorker("a", async () => ({}));
|
|
250
|
+
await engine.registerWorker("b", async () => ({}));
|
|
251
|
+
await engine.close();
|
|
252
|
+
assert.equal(client.workers[0].stopped, 1);
|
|
253
|
+
assert.equal(client.workers[1].stopped, 1);
|
|
254
|
+
assert.equal(client.closed, 1);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("exposes the underlying nano-sdk client as .sdk", () => {
|
|
258
|
+
const client = fakeSdkClient();
|
|
259
|
+
const engine = new SdkEngineClient(client);
|
|
260
|
+
assert.equal(engine.sdk, client, "sdk returns the exact client the adapter was built from");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("createNanoSdkEngineClient uses an injected client", async () => {
|
|
264
|
+
const client = fakeSdkClient();
|
|
265
|
+
const engine = await createNanoSdkEngineClient({ restAddress: "http://x/v2", client });
|
|
266
|
+
await engine.createInstance({ processDefinitionId: "p" });
|
|
267
|
+
assert.ok(client.calls.includes("createProcessInstance"));
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("createNanoSdkEngineClient uses an injected client factory with the resolved transport", async () => {
|
|
271
|
+
let seen: { restAddress: string; token?: string; transport?: string } | undefined;
|
|
272
|
+
const client = fakeSdkClient();
|
|
112
273
|
const engine = await createNanoSdkEngineClient({
|
|
113
274
|
restAddress: "http://x/v2",
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
275
|
+
token: "t",
|
|
276
|
+
transport: "falcon",
|
|
277
|
+
createClient: (o) => {
|
|
278
|
+
seen = o;
|
|
279
|
+
return client;
|
|
280
|
+
},
|
|
118
281
|
});
|
|
282
|
+
assert.deepEqual(seen, { restAddress: "http://x/v2", token: "t", transport: "falcon" });
|
|
119
283
|
assert.ok(engine);
|
|
120
|
-
await assert.rejects(
|
|
121
|
-
() => engine!.createInstance({ processDefinitionId: "p" }),
|
|
122
|
-
/missing processInstanceKey/,
|
|
123
|
-
);
|
|
124
284
|
});
|