@nanobpm/urban 0.13.0 → 0.14.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.
|
@@ -74,13 +74,17 @@ export interface HostContext {
|
|
|
74
74
|
/** Structured log sink. */
|
|
75
75
|
log(level: "info" | "warn" | "error", msg: string, fields?: Record<string, unknown>): void;
|
|
76
76
|
}
|
|
77
|
-
/** A job handed to a worker handler, plus the ack/fail callbacks.
|
|
78
|
-
|
|
77
|
+
/** A job handed to a worker handler, plus the ack/fail callbacks. `In` types the process
|
|
78
|
+
* variables carried on the job; it defaults to an open record when a handler doesn't
|
|
79
|
+
* declare a concrete input shape. The `object` bound (rather than `Record<string, unknown>`)
|
|
80
|
+
* lets plain `interface` shapes be used as `In` — interfaces lack the implicit index
|
|
81
|
+
* signature that a `Record` bound would demand. */
|
|
82
|
+
export interface EngineJob<In extends object = Record<string, unknown>> {
|
|
79
83
|
jobKey: string;
|
|
80
84
|
jobType: string;
|
|
81
85
|
processInstanceKey?: string;
|
|
82
86
|
elementId?: string;
|
|
83
|
-
variables:
|
|
87
|
+
variables: In;
|
|
84
88
|
}
|
|
85
89
|
export type JobHandler = (job: EngineJob) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
|
|
86
90
|
/** A registered worker subscription. */
|
|
@@ -6,8 +6,23 @@ import { type DecisionEvaluator } from "./llm.ts";
|
|
|
6
6
|
* SDK shape and adds decision context to a parse failure rather than surfacing a raw
|
|
7
7
|
* "is not a function" / `JSON.parse` error. */
|
|
8
8
|
export declare function sdkDecisionEvaluator(sdk: unknown): DecisionEvaluator;
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* A handler as authored by an app: the job plus the injected app API.
|
|
11
|
+
*
|
|
12
|
+
* Both type parameters are optional:
|
|
13
|
+
* - `In` types `job.variables` (the process variables the job carries).
|
|
14
|
+
* - `Out` types the completion **variables map** the handler returns to the engine.
|
|
15
|
+
* Each defaults to an open `Record<string, unknown>`, so `AppJobHandler`,
|
|
16
|
+
* `AppJobHandler<In>`, and `AppJobHandler<In, Out>` are all valid.
|
|
17
|
+
*
|
|
18
|
+
* The bound is `extends object` (not `Record<string, unknown>`) so a plain `interface In {…}`
|
|
19
|
+
* / `interface Out {…}` can be supplied — interfaces have no implicit index signature and so
|
|
20
|
+
* don't satisfy a `Record` bound. The looser bound means `Out` is not *forced* to be a plain
|
|
21
|
+
* variables map at the type level; authors are expected to return a JSON-object of completion
|
|
22
|
+
* variables (not a `Date`, `Map`, array, etc.), which is what the engine sends on job
|
|
23
|
+
* completion. Both parameters are erased at runtime — they only shape authoring-time types.
|
|
24
|
+
*/
|
|
25
|
+
export type AppJobHandler<In extends object = Record<string, unknown>, Out extends object = Record<string, unknown>> = (job: EngineJob<In>, app: AppApi) => Promise<Out | void> | Out | void;
|
|
11
26
|
/**
|
|
12
27
|
* Resolve a handler for `jobType` from a loaded module, in priority order:
|
|
13
28
|
* 1. `handlers[jobType]` (a map keyed by job type — the multi-type module case)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/urban",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
package/src/runtime/core/host.ts
CHANGED
|
@@ -83,13 +83,17 @@ export interface HostContext {
|
|
|
83
83
|
log(level: "info" | "warn" | "error", msg: string, fields?: Record<string, unknown>): void;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/** A job handed to a worker handler, plus the ack/fail callbacks.
|
|
87
|
-
|
|
86
|
+
/** A job handed to a worker handler, plus the ack/fail callbacks. `In` types the process
|
|
87
|
+
* variables carried on the job; it defaults to an open record when a handler doesn't
|
|
88
|
+
* declare a concrete input shape. The `object` bound (rather than `Record<string, unknown>`)
|
|
89
|
+
* lets plain `interface` shapes be used as `In` — interfaces lack the implicit index
|
|
90
|
+
* signature that a `Record` bound would demand. */
|
|
91
|
+
export interface EngineJob<In extends object = Record<string, unknown>> {
|
|
88
92
|
jobKey: string;
|
|
89
93
|
jobType: string;
|
|
90
94
|
processInstanceKey?: string;
|
|
91
95
|
elementId?: string;
|
|
92
|
-
variables:
|
|
96
|
+
variables: In;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
export type JobHandler = (
|
|
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import type { AppApi, RuntimeContext } from "../context.ts";
|
|
4
4
|
import type { EngineJob, JobHandler, WorkerSubscription } from "../host.ts";
|
|
5
5
|
import type { AppManifest } from "../manifest.ts";
|
|
6
|
-
import { mountWorkers, sdkDecisionEvaluator } from "./workers.ts";
|
|
6
|
+
import { mountWorkers, sdkDecisionEvaluator, type AppJobHandler } from "./workers.ts";
|
|
7
7
|
|
|
8
8
|
/** A tiny engine that records registrations and can deliver a job to a handler. */
|
|
9
9
|
class MiniEngine {
|
|
@@ -156,3 +156,55 @@ test("sdkDecisionEvaluator throws a clear error when the SDK lacks evaluateDecis
|
|
|
156
156
|
const evaluate = sdkDecisionEvaluator({});
|
|
157
157
|
await assert.rejects(evaluate("risk", {}), /does not support evaluateDecision.*"risk"/);
|
|
158
158
|
});
|
|
159
|
+
|
|
160
|
+
// ── AppJobHandler generics: optional In/Out type parameters ──────────────────────────────
|
|
161
|
+
// These are primarily compile-time assertions (the suite is typechecked by `tsc --noEmit`),
|
|
162
|
+
// with a runtime smoke that a typed handler still returns its output when delivered a job.
|
|
163
|
+
test("AppJobHandler carries optional In/Out variable types", async () => {
|
|
164
|
+
// `In` is a plain interface — the `object` bound (not `Record<string, unknown>`) makes this
|
|
165
|
+
// work, since interfaces have no implicit index signature.
|
|
166
|
+
interface In {
|
|
167
|
+
prKey: string;
|
|
168
|
+
round: number;
|
|
169
|
+
summary?: string;
|
|
170
|
+
}
|
|
171
|
+
interface Out {
|
|
172
|
+
ok: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// In + Out: job.variables is typed as In, the return must be Out (or void).
|
|
176
|
+
const typed: AppJobHandler<In, Out> = async (job) => {
|
|
177
|
+
const key: string = job.variables.prKey; // typed access compiles
|
|
178
|
+
const n: number = job.variables.round;
|
|
179
|
+
return { ok: key.length > 0 && n >= 0 };
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// In only: Out defaults to an open variables map, so any plain object of completion
|
|
183
|
+
// variables may be returned.
|
|
184
|
+
const inOnly: AppJobHandler<In> = (job) => ({ echoed: job.variables.prKey });
|
|
185
|
+
|
|
186
|
+
// No parameters: fully open — the pre-generics behaviour.
|
|
187
|
+
const open: AppJobHandler = (job) => ({ count: Object.keys(job.variables).length });
|
|
188
|
+
|
|
189
|
+
// Returning nothing (void) is allowed under any parameterisation.
|
|
190
|
+
const voidReturn: AppJobHandler<In> = () => {};
|
|
191
|
+
|
|
192
|
+
const job: EngineJob<In> = {
|
|
193
|
+
jobKey: "1",
|
|
194
|
+
jobType: "pr.finalize",
|
|
195
|
+
variables: { prKey: "p1", round: 2 },
|
|
196
|
+
};
|
|
197
|
+
// The open handler defaults `In` to a record; interface-typed jobs aren't assignable to it,
|
|
198
|
+
// so hand it a plainly-typed job (the pre-generics shape).
|
|
199
|
+
const openJob: EngineJob = {
|
|
200
|
+
jobKey: "2",
|
|
201
|
+
jobType: "pr.finalize",
|
|
202
|
+
variables: { prKey: "p1", round: 2 },
|
|
203
|
+
};
|
|
204
|
+
const app = {} as AppApi;
|
|
205
|
+
|
|
206
|
+
assert.deepEqual(await typed(job, app), { ok: true });
|
|
207
|
+
assert.deepEqual(await inOnly(job, app), { echoed: "p1" });
|
|
208
|
+
assert.deepEqual(await open(openJob, app), { count: 2 });
|
|
209
|
+
assert.equal(await voidReturn(job, app), undefined);
|
|
210
|
+
});
|
|
@@ -42,11 +42,26 @@ export function sdkDecisionEvaluator(sdk: unknown): DecisionEvaluator {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
/**
|
|
46
|
+
* A handler as authored by an app: the job plus the injected app API.
|
|
47
|
+
*
|
|
48
|
+
* Both type parameters are optional:
|
|
49
|
+
* - `In` types `job.variables` (the process variables the job carries).
|
|
50
|
+
* - `Out` types the completion **variables map** the handler returns to the engine.
|
|
51
|
+
* Each defaults to an open `Record<string, unknown>`, so `AppJobHandler`,
|
|
52
|
+
* `AppJobHandler<In>`, and `AppJobHandler<In, Out>` are all valid.
|
|
53
|
+
*
|
|
54
|
+
* The bound is `extends object` (not `Record<string, unknown>`) so a plain `interface In {…}`
|
|
55
|
+
* / `interface Out {…}` can be supplied — interfaces have no implicit index signature and so
|
|
56
|
+
* don't satisfy a `Record` bound. The looser bound means `Out` is not *forced* to be a plain
|
|
57
|
+
* variables map at the type level; authors are expected to return a JSON-object of completion
|
|
58
|
+
* variables (not a `Date`, `Map`, array, etc.), which is what the engine sends on job
|
|
59
|
+
* completion. Both parameters are erased at runtime — they only shape authoring-time types.
|
|
60
|
+
*/
|
|
61
|
+
export type AppJobHandler<In extends object = Record<string, unknown>, Out extends object = Record<string, unknown>> = (
|
|
62
|
+
job: EngineJob<In>,
|
|
48
63
|
app: AppApi,
|
|
49
|
-
) => Promise<
|
|
64
|
+
) => Promise<Out | void> | Out | void;
|
|
50
65
|
|
|
51
66
|
/**
|
|
52
67
|
* Resolve a handler for `jobType` from a loaded module, in priority order:
|