@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.
Files changed (71) hide show
  1. package/README.md +17 -9
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +212 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.js +25 -0
  6. package/dist/runtime/adapters/deno.d.ts +6 -0
  7. package/dist/runtime/adapters/deno.js +98 -0
  8. package/dist/runtime/adapters/detect.d.ts +9 -0
  9. package/dist/runtime/adapters/detect.js +13 -0
  10. package/dist/runtime/adapters/node.d.ts +7 -0
  11. package/dist/runtime/adapters/node.js +113 -0
  12. package/dist/runtime/core/context.d.ts +40 -0
  13. package/dist/runtime/core/context.js +1 -0
  14. package/dist/runtime/core/glob.d.ts +5 -0
  15. package/dist/runtime/core/glob.js +51 -0
  16. package/dist/runtime/core/host.d.ts +128 -0
  17. package/dist/runtime/core/host.js +5 -0
  18. package/dist/runtime/core/manifest.d.ts +83 -0
  19. package/dist/runtime/core/manifest.js +45 -0
  20. package/dist/runtime/core/modules/datasource.d.ts +38 -0
  21. package/dist/runtime/core/modules/datasource.js +172 -0
  22. package/dist/runtime/core/modules/deploy.d.ts +6 -0
  23. package/dist/runtime/core/modules/deploy.js +37 -0
  24. package/dist/runtime/core/modules/security.d.ts +11 -0
  25. package/dist/runtime/core/modules/security.js +32 -0
  26. package/dist/runtime/core/modules/surfaces.d.ts +9 -0
  27. package/dist/runtime/core/modules/surfaces.js +88 -0
  28. package/dist/runtime/core/modules/triggers.d.ts +15 -0
  29. package/dist/runtime/core/modules/triggers.js +119 -0
  30. package/dist/runtime/core/modules/workers.d.ts +16 -0
  31. package/dist/runtime/core/modules/workers.js +79 -0
  32. package/dist/runtime/core/router.d.ts +22 -0
  33. package/dist/runtime/core/router.js +39 -0
  34. package/dist/runtime/core/runtime.d.ts +46 -0
  35. package/dist/runtime/core/runtime.js +173 -0
  36. package/dist/runtime/core/validate.d.ts +13 -0
  37. package/dist/runtime/core/validate.js +108 -0
  38. package/dist/runtime/engine/nanosdk.d.ts +151 -0
  39. package/dist/runtime/engine/nanosdk.js +209 -0
  40. package/dist/runtime/engine/sdk.d.ts +3 -0
  41. package/dist/runtime/engine/sdk.js +13 -0
  42. package/dist/runtime/index.d.ts +21 -0
  43. package/dist/runtime/index.js +17 -0
  44. package/dist/runtime/run.d.ts +17 -0
  45. package/dist/runtime/run.js +69 -0
  46. package/dist/runtime/schema/nano-app.schema.json +471 -0
  47. package/dist/toolkit/artifact.d.ts +19 -0
  48. package/dist/toolkit/artifact.js +11 -0
  49. package/dist/toolkit/derivers/migrations.d.ts +26 -0
  50. package/dist/toolkit/derivers/migrations.js +73 -0
  51. package/dist/toolkit/derivers/worker-io.d.ts +38 -0
  52. package/dist/toolkit/derivers/worker-io.js +135 -0
  53. package/dist/toolkit/fsio.d.ts +2 -0
  54. package/dist/toolkit/fsio.js +35 -0
  55. package/dist/toolkit/gen.d.ts +28 -0
  56. package/dist/toolkit/gen.js +75 -0
  57. package/dist/toolkit/index.d.ts +13 -0
  58. package/dist/toolkit/index.js +15 -0
  59. package/package.json +20 -6
  60. package/src/bin.mjs +6 -11
  61. package/src/cli.ts +14 -1
  62. package/src/index.ts +7 -0
  63. package/src/runtime/core/context.ts +10 -0
  64. package/src/runtime/core/runtime.test.ts +46 -1
  65. package/src/runtime/core/runtime.ts +9 -0
  66. package/src/runtime/engine/nanosdk.test.ts +245 -85
  67. package/src/runtime/engine/nanosdk.ts +284 -78
  68. package/src/runtime/engine/sdk.ts +18 -0
  69. package/src/runtime/index.ts +8 -4
  70. package/src/runtime/run.ts +15 -14
  71. package/src/runtime/engine/rest.ts +0 -243
@@ -1,243 +0,0 @@
1
- // A REST-backed EngineClient using only global `fetch`/`FormData`/`AbortController` — all
2
- // present in both Node 18+ and Deno, so this stays runtime-agnostic. Workers drain via the
3
- // v2 job-activation long-poll (no Falcon push dependency); the SDK-backed client can add
4
- // the push path later behind the same EngineClient seam.
5
-
6
- import type {
7
- EngineClient,
8
- EngineJob,
9
- JobHandler,
10
- WorkerSubscription,
11
- } from "../core/host.ts";
12
-
13
- export interface RestEngineOptions {
14
- /** Base URL including the /v2 suffix, e.g. http://localhost:8080/v2. */
15
- baseUrl: string;
16
- /** Optional bearer token. */
17
- token?: string;
18
- /** Long-poll request timeout (ms) for job activation. Default 15000. */
19
- longPollMs?: number;
20
- log?: (level: "info" | "warn" | "error", msg: string, fields?: Record<string, unknown>) => void;
21
- }
22
-
23
- function normBase(u: string): string {
24
- const t = u.replace(/\/+$/, "");
25
- return /\/v\d+$/.test(t) ? t : `${t}/v2`;
26
- }
27
-
28
- /** Coerce an engine response's process-instance key to a non-empty string, or
29
- * throw — a missing key means a malformed/partial response, not a real instance. */
30
- export function requireProcessInstanceKey(key: string | number | null | undefined): string {
31
- if (key == null || key === "") {
32
- throw new Error("engine response missing processInstanceKey/key");
33
- }
34
- return String(key);
35
- }
36
-
37
- export class RestEngineClient implements EngineClient {
38
- private readonly base: string;
39
- private readonly token?: string;
40
- private readonly longPollMs: number;
41
- private readonly log: NonNullable<RestEngineOptions["log"]>;
42
- private readonly subs = new Set<{ stop: () => void }>();
43
-
44
- constructor(opts: RestEngineOptions) {
45
- this.base = normBase(opts.baseUrl);
46
- this.token = opts.token;
47
- this.longPollMs = opts.longPollMs ?? 15000;
48
- this.log = opts.log ?? (() => {});
49
- }
50
-
51
- private headers(json = true): Record<string, string> {
52
- const h: Record<string, string> = {};
53
- if (json) h["content-type"] = "application/json";
54
- if (this.token) h["authorization"] = `Bearer ${this.token}`;
55
- return h;
56
- }
57
-
58
- private async req(path: string, body: unknown, init?: RequestInit): Promise<Response> {
59
- const res = await fetch(`${this.base}${path}`, {
60
- method: "POST",
61
- headers: this.headers(),
62
- body: body === undefined ? undefined : JSON.stringify(body),
63
- ...init,
64
- });
65
- return res;
66
- }
67
-
68
- private async okJson<T>(path: string, body: unknown): Promise<T> {
69
- const res = await this.req(path, body);
70
- if (!res.ok) throw new Error(`${path} → ${res.status}: ${(await res.text()).slice(0, 300)}`);
71
- const text = await res.text();
72
- return (text ? JSON.parse(text) : {}) as T;
73
- }
74
-
75
- async deployResources(
76
- resources: { name: string; content: string; contentType: string }[],
77
- ): Promise<{ deployed: number }> {
78
- const form = new FormData();
79
- for (const r of resources) {
80
- form.append("resources", new Blob([r.content], { type: r.contentType }), r.name);
81
- }
82
- const headers: Record<string, string> = {};
83
- if (this.token) headers["authorization"] = `Bearer ${this.token}`;
84
- const res = await fetch(`${this.base}/deployments`, { method: "POST", headers, body: form });
85
- if (!res.ok) {
86
- throw new Error(`deploy → ${res.status}: ${(await res.text()).slice(0, 300)}`);
87
- }
88
- return { deployed: resources.length };
89
- }
90
-
91
- async createInstance(input: {
92
- processDefinitionId: string;
93
- variables?: Record<string, unknown>;
94
- awaitCompletion?: boolean;
95
- }): Promise<{ processInstanceKey: string; variables?: Record<string, unknown> }> {
96
- const r = await this.okJson<{ processInstanceKey?: string; key?: string; variables?: Record<string, unknown> }>(
97
- "/process-instances",
98
- {
99
- processDefinitionId: input.processDefinitionId,
100
- variables: input.variables ?? {},
101
- awaitCompletion: input.awaitCompletion ?? false,
102
- },
103
- );
104
- return { processInstanceKey: requireProcessInstanceKey(r.processInstanceKey ?? r.key), variables: r.variables };
105
- }
106
-
107
- async publishMessage(input: {
108
- name: string;
109
- correlationKey?: string;
110
- variables?: Record<string, unknown>;
111
- }): Promise<void> {
112
- await this.okJson("/messages/publication", {
113
- name: input.name,
114
- correlationKey: input.correlationKey ?? "",
115
- variables: input.variables ?? {},
116
- });
117
- }
118
-
119
- async searchUserTasks(filter?: {
120
- processInstanceKey?: string;
121
- assignee?: string;
122
- candidateGroup?: string;
123
- }): Promise<{ userTaskKey: string; elementId?: string; variables?: Record<string, unknown> }[]> {
124
- const r = await this.okJson<{ items?: Record<string, unknown>[] }>("/user-tasks/search", {
125
- filter: filter ?? {},
126
- });
127
- return (r.items ?? []).flatMap((it) => {
128
- const userTaskKey = it.userTaskKey ?? it.key;
129
- if (userTaskKey == null || userTaskKey === "") {
130
- this.log("warn", "skipping user task with no key in engine response");
131
- return [];
132
- }
133
- return [{
134
- userTaskKey: String(userTaskKey),
135
- elementId: it.elementId as string | undefined,
136
- variables: it.variables as Record<string, unknown> | undefined,
137
- }];
138
- });
139
- }
140
-
141
- async completeUserTask(userTaskKey: string, variables?: Record<string, unknown>): Promise<void> {
142
- const res = await this.req(`/user-tasks/${userTaskKey}/completion`, { variables: variables ?? {} });
143
- if (!res.ok && res.status !== 204) {
144
- throw new Error(`complete user-task → ${res.status}: ${(await res.text()).slice(0, 300)}`);
145
- }
146
- }
147
-
148
- async registerWorker(
149
- jobType: string,
150
- handler: JobHandler,
151
- options?: { workerName?: string; maxParallelJobs?: number; fetchVariables?: string[] },
152
- ): Promise<WorkerSubscription> {
153
- const workerName = options?.workerName ?? `urban:${jobType}`;
154
- const maxJobs = options?.maxParallelJobs ?? 8;
155
- let running = true;
156
- const control = { stop: () => (running = false) };
157
- this.subs.add(control);
158
-
159
- const activateOnce = async (): Promise<Record<string, unknown>[]> => {
160
- const ctrl = new AbortController();
161
- const timer = setTimeout(() => ctrl.abort(), this.longPollMs + 5000);
162
- try {
163
- const res = await this.req(
164
- "/jobs/activation",
165
- {
166
- type: jobType,
167
- worker: workerName,
168
- timeout: 5 * 60 * 1000,
169
- maxJobsToActivate: maxJobs,
170
- requestTimeout: this.longPollMs,
171
- fetchVariable: options?.fetchVariables ?? [],
172
- },
173
- { signal: ctrl.signal },
174
- );
175
- if (!res.ok) {
176
- if (res.status !== 408) {
177
- this.log("warn", `activation ${jobType} → ${res.status}`);
178
- }
179
- return [];
180
- }
181
- const body = (await res.json()) as { jobs?: Record<string, unknown>[] };
182
- return body.jobs ?? [];
183
- } catch (err) {
184
- if (running) this.log("warn", `activation ${jobType} error`, { err: String(err) });
185
- return [];
186
- } finally {
187
- clearTimeout(timer);
188
- }
189
- };
190
-
191
- const runOne = async (raw: Record<string, unknown>): Promise<void> => {
192
- const rawKey = raw.jobKey ?? raw.key;
193
- if (rawKey == null || rawKey === "") {
194
- this.log("warn", `activation ${jobType}: skipping job with no jobKey in engine response`);
195
- return;
196
- }
197
- const key = String(rawKey);
198
- const job: EngineJob = {
199
- jobKey: key,
200
- jobType,
201
- processInstanceKey: raw.processInstanceKey ? String(raw.processInstanceKey) : undefined,
202
- elementId: raw.elementId as string | undefined,
203
- variables: (raw.variables as Record<string, unknown>) ?? {},
204
- };
205
- try {
206
- const out = await handler(job);
207
- const res = await this.req(`/jobs/${key}/completion`, { variables: out ?? {} });
208
- if (!res.ok && res.status !== 204) {
209
- this.log("warn", `complete ${jobType} → ${res.status}`);
210
- }
211
- } catch (err) {
212
- await this.req(`/jobs/${key}/failure`, {
213
- retries: 0,
214
- errorMessage: String(err).slice(0, 500),
215
- }).catch(() => {});
216
- this.log("error", `handler ${jobType} threw`, { err: String(err) });
217
- }
218
- };
219
-
220
- const loop = async (): Promise<void> => {
221
- while (running) {
222
- const jobs = await activateOnce();
223
- if (!running) break;
224
- if (jobs.length === 0) continue;
225
- await Promise.all(jobs.map(runOne));
226
- }
227
- };
228
- void loop();
229
-
230
- return {
231
- jobType,
232
- unsubscribe: async () => {
233
- control.stop();
234
- this.subs.delete(control);
235
- },
236
- };
237
- }
238
-
239
- async close(): Promise<void> {
240
- for (const s of this.subs) s.stop();
241
- this.subs.clear();
242
- }
243
- }