@nanobpm/urban 0.6.0 → 0.8.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/runtime/core/host.d.ts +4 -0
- package/dist/runtime/core/manifest.d.ts +25 -0
- package/dist/runtime/core/modules/actions.d.ts +45 -0
- package/dist/runtime/core/modules/actions.js +113 -0
- package/dist/runtime/core/modules/gateway.d.ts +2 -0
- package/dist/runtime/core/modules/gateway.js +2 -1
- package/dist/runtime/core/modules/pages.d.ts +42 -0
- package/dist/runtime/core/modules/pages.js +670 -0
- package/dist/runtime/core/modules/surfaces.js +16 -0
- package/dist/runtime/engine/nanosdk.d.ts +6 -0
- package/dist/runtime/engine/nanosdk.js +3 -0
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/schema/nano-app.schema.json +32 -0
- package/package.json +1 -1
- package/src/runtime/core/host.ts +2 -0
- package/src/runtime/core/manifest.ts +26 -0
- package/src/runtime/core/modules/actions.test.ts +239 -0
- package/src/runtime/core/modules/actions.ts +161 -0
- package/src/runtime/core/modules/gateway.ts +2 -1
- package/src/runtime/core/modules/pages.test.ts +224 -0
- package/src/runtime/core/modules/pages.ts +705 -0
- package/src/runtime/core/modules/surfaces.ts +18 -0
- package/src/runtime/core/runtime.test.ts +4 -0
- package/src/runtime/core/validate.test.ts +8 -0
- package/src/runtime/engine/nanosdk.test.ts +18 -0
- package/src/runtime/engine/nanosdk.ts +8 -0
- package/src/runtime/index.ts +5 -0
- package/src/runtime/schema/nano-app.schema.json +32 -0
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import type { AppApi, RuntimeContext } from "../context.ts";
|
|
7
7
|
import { html, json, normalizeRoutePath, type Route } from "../router.ts";
|
|
8
|
+
import { mountActions } from "./actions.ts";
|
|
9
|
+
import { mountPages } from "./pages.ts";
|
|
8
10
|
|
|
9
11
|
/** Escape HTML-significant characters before embedding a value in markup. */
|
|
10
12
|
function escapeHtml(s: string): string {
|
|
@@ -98,6 +100,22 @@ export function mountSurfaces(ctx: RuntimeContext, app: AppApi): SurfacesHandle
|
|
|
98
100
|
});
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
// App-authored action overrides mount BEFORE the generic pages routes so an exact
|
|
104
|
+
// override (e.g. /app/actions/cancel) shadows the generic action; the router is
|
|
105
|
+
// first-match-wins.
|
|
106
|
+
const actions = mountActions(ctx, app);
|
|
107
|
+
if (actions.routes.length > 0) {
|
|
108
|
+
routes.push(...actions.routes);
|
|
109
|
+
enabled.push(`actions(${actions.routes.length})`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The pages surface (the schema-driven page runtime) mounts its own routes.
|
|
113
|
+
const pages = mountPages(ctx, app);
|
|
114
|
+
if (pages.routes.length > 0) {
|
|
115
|
+
routes.push(...pages.routes);
|
|
116
|
+
enabled.push("pages@/");
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
ctx.host.log("info", "surfaces mounted", { enabled });
|
|
102
120
|
return { name: "surfaces", routes, describe: () => ({ enabled }) };
|
|
103
121
|
}
|
|
@@ -18,6 +18,7 @@ class FakeEngine implements EngineClient {
|
|
|
18
18
|
deployed = 0;
|
|
19
19
|
workers = new Map<string, JobHandler>();
|
|
20
20
|
messages: { name: string; correlationKey?: string; variables?: Record<string, unknown> }[] = [];
|
|
21
|
+
canceledInstances: string[] = [];
|
|
21
22
|
completedTasks: { key: string; variables?: Record<string, unknown> }[] = [];
|
|
22
23
|
userTasks = [{ userTaskKey: "ut-1", elementId: "approve", variables: {} }];
|
|
23
24
|
|
|
@@ -28,6 +29,9 @@ class FakeEngine implements EngineClient {
|
|
|
28
29
|
async createInstance() {
|
|
29
30
|
return { processInstanceKey: "pi-1" };
|
|
30
31
|
}
|
|
32
|
+
async cancelInstance(input: { processInstanceKey: string }) {
|
|
33
|
+
this.canceledInstances.push(input.processInstanceKey);
|
|
34
|
+
}
|
|
31
35
|
async publishMessage(input: { name: string; correlationKey?: string; variables?: Record<string, unknown> }) {
|
|
32
36
|
this.messages.push(input);
|
|
33
37
|
}
|
|
@@ -29,6 +29,14 @@ test("unknown top-level keys are rejected (additionalProperties:false)", () => {
|
|
|
29
29
|
assert.ok(issues.some((i) => i.path === "bogus"));
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
test("an `actions` array is an accepted top-level key (ADR 0055 §3)", () => {
|
|
33
|
+
const issues = collectManifestIssues({
|
|
34
|
+
...valid,
|
|
35
|
+
actions: [{ path: "/app/actions/cancel", module: "actions/cancel.ts" }],
|
|
36
|
+
});
|
|
37
|
+
assert.deepEqual(issues, []);
|
|
38
|
+
});
|
|
39
|
+
|
|
32
40
|
test("bad schemaVersion and bad slug id are reported", () => {
|
|
33
41
|
const issues = collectManifestIssues({ ...valid, schemaVersion: 2, id: "Not A Slug" });
|
|
34
42
|
assert.ok(issues.some((i) => i.path === "schemaVersion"));
|
|
@@ -53,6 +53,10 @@ function fakeSdkClient(overrides: Partial<NanoSdkClient> = {}): NanoSdkClient &
|
|
|
53
53
|
calls.push("createProcessInstance");
|
|
54
54
|
return { processInstanceKey: 99, variables: input.variables };
|
|
55
55
|
},
|
|
56
|
+
async cancelProcessInstance(input) {
|
|
57
|
+
calls.push("cancelProcessInstance");
|
|
58
|
+
return { ...input };
|
|
59
|
+
},
|
|
56
60
|
async publishMessage(input) {
|
|
57
61
|
calls.push("publishMessage");
|
|
58
62
|
return { key: 1, ...input };
|
|
@@ -131,6 +135,20 @@ test("createInstance throws when the SDK response omits the instance key", async
|
|
|
131
135
|
);
|
|
132
136
|
});
|
|
133
137
|
|
|
138
|
+
test("cancelInstance routes through the SDK", async () => {
|
|
139
|
+
let seen: { processInstanceKey: string | number } | undefined;
|
|
140
|
+
const client = fakeSdkClient({
|
|
141
|
+
cancelProcessInstance: async (input) => {
|
|
142
|
+
seen = input;
|
|
143
|
+
return {};
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const engine = new SdkEngineClient(client);
|
|
147
|
+
await engine.cancelInstance({ processInstanceKey: "pi-7" });
|
|
148
|
+
assert.deepEqual(seen, { processInstanceKey: "pi-7" });
|
|
149
|
+
assert.ok(client.calls.includes("cancelProcessInstance") || seen !== undefined);
|
|
150
|
+
});
|
|
151
|
+
|
|
134
152
|
test("publishMessage defaults correlationKey/variables", async () => {
|
|
135
153
|
let seen: Record<string, unknown> | undefined;
|
|
136
154
|
const client = fakeSdkClient({
|
|
@@ -79,6 +79,10 @@ export interface NanoSdkClient {
|
|
|
79
79
|
},
|
|
80
80
|
options?: unknown,
|
|
81
81
|
): Promise<Record<string, unknown>>;
|
|
82
|
+
cancelProcessInstance(
|
|
83
|
+
input: { processInstanceKey: string | number },
|
|
84
|
+
options?: unknown,
|
|
85
|
+
): Promise<unknown>;
|
|
82
86
|
publishMessage(
|
|
83
87
|
input: { name: string; correlationKey?: string; variables?: Record<string, unknown> },
|
|
84
88
|
options?: unknown,
|
|
@@ -150,6 +154,10 @@ export class SdkEngineClient implements EngineClient {
|
|
|
150
154
|
};
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
async cancelInstance(input: { processInstanceKey: string }): Promise<void> {
|
|
158
|
+
await this.client.cancelProcessInstance({ processInstanceKey: input.processInstanceKey });
|
|
159
|
+
}
|
|
160
|
+
|
|
153
161
|
async publishMessage(input: {
|
|
154
162
|
name: string;
|
|
155
163
|
correlationKey?: string;
|
package/src/runtime/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
workerJobType,
|
|
18
18
|
} from "./core/manifest.ts";
|
|
19
19
|
export type {
|
|
20
|
+
ActionDecl,
|
|
20
21
|
AppManifest,
|
|
21
22
|
DataSource,
|
|
22
23
|
DomainType,
|
|
@@ -61,6 +62,10 @@ export type {
|
|
|
61
62
|
Row,
|
|
62
63
|
TableMeta,
|
|
63
64
|
} from "./core/modules/gateway.ts";
|
|
65
|
+
export { createPagesRoutes, mountPages } from "./core/modules/pages.ts";
|
|
66
|
+
export type { PagesDataSource, PagesDeps, PagesHandle, PagesOptions } from "./core/modules/pages.ts";
|
|
67
|
+
export { mountActions, resolveActionHandler } from "./core/modules/actions.ts";
|
|
68
|
+
export type { ActionHandler, ActionRequest, ActionResult, ActionsHandle } from "./core/modules/actions.ts";
|
|
64
69
|
export type { AppJobHandler } from "./core/modules/workers.ts";
|
|
65
70
|
export { resolveHandler } from "./core/modules/workers.ts";
|
|
66
71
|
export { evalCorrelation } from "./core/modules/triggers.ts";
|
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
"additionalProperties": { "$ref": "#/$defs/connection" }
|
|
53
53
|
},
|
|
54
54
|
"surfaces": { "$ref": "#/$defs/surfaces" },
|
|
55
|
+
"actions": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": { "$ref": "#/$defs/action" },
|
|
58
|
+
"description": "App-authored action handler overrides (ADR 0055 §3): each binds an HTTP route to a handler module that wraps the generic pages start/cancel/message actions with business logic, injected with the app's runtime API."
|
|
59
|
+
},
|
|
55
60
|
"workers": {
|
|
56
61
|
"type": "array",
|
|
57
62
|
"items": { "$ref": "#/$defs/worker" },
|
|
@@ -367,6 +372,33 @@
|
|
|
367
372
|
}
|
|
368
373
|
}
|
|
369
374
|
},
|
|
375
|
+
"action": {
|
|
376
|
+
"type": "object",
|
|
377
|
+
"additionalProperties": false,
|
|
378
|
+
"required": ["path", "module"],
|
|
379
|
+
"description": "An app-authored action handler override (ADR 0055 §3): binds a route to a handler module (default-exports an ActionHandler). Mounted before the generic pages action routes, so an exact override shadows the generic one.",
|
|
380
|
+
"properties": {
|
|
381
|
+
"path": {
|
|
382
|
+
"type": "string",
|
|
383
|
+
"minLength": 1,
|
|
384
|
+
"description": "Route path to serve, e.g. '/app/actions/cancel' or '/app/actions/start/convergence-loop'."
|
|
385
|
+
},
|
|
386
|
+
"module": {
|
|
387
|
+
"type": "string",
|
|
388
|
+
"minLength": 1,
|
|
389
|
+
"description": "Handler module path relative to the app root; default-exports (or names `handler`) an ActionHandler."
|
|
390
|
+
},
|
|
391
|
+
"method": {
|
|
392
|
+
"type": "string",
|
|
393
|
+
"minLength": 1,
|
|
394
|
+
"description": "HTTP method to match. Default 'POST'."
|
|
395
|
+
},
|
|
396
|
+
"prefix": {
|
|
397
|
+
"type": "boolean",
|
|
398
|
+
"description": "Match `path` as a prefix rather than exactly. Default false."
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
},
|
|
370
402
|
"llmBinding": {
|
|
371
403
|
"type": "object",
|
|
372
404
|
"additionalProperties": false,
|