@nanobpm/urban 0.28.0 → 0.30.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/openapi/spec.d.ts +127 -0
- package/dist/openapi/spec.js +444 -0
- package/dist/runtime/adapters/deno.js +5 -0
- package/dist/runtime/adapters/node.js +5 -0
- package/dist/runtime/core/execContext.d.ts +28 -0
- package/dist/runtime/core/execContext.js +39 -0
- package/dist/runtime/core/host.d.ts +41 -0
- package/dist/runtime/core/manifest.d.ts +1 -1
- package/dist/runtime/core/modules/api.d.ts +61 -0
- package/dist/runtime/core/modules/api.js +359 -0
- package/dist/runtime/core/modules/cancel.d.ts +31 -0
- package/dist/runtime/core/modules/cancel.js +90 -0
- package/dist/runtime/core/modules/connectors.js +3 -1
- package/dist/runtime/core/modules/datasource.d.ts +23 -3
- package/dist/runtime/core/modules/datasource.js +61 -6
- package/dist/runtime/core/modules/gateway.d.ts +10 -3
- package/dist/runtime/core/modules/gateway.js +23 -6
- package/dist/runtime/core/modules/instance-tracking.d.ts +22 -0
- package/dist/runtime/core/modules/instance-tracking.js +158 -0
- package/dist/runtime/core/modules/pages.d.ts +8 -0
- package/dist/runtime/core/modules/pages.js +17 -1
- package/dist/runtime/core/modules/scheduler.d.ts +11 -0
- package/dist/runtime/core/modules/scheduler.js +21 -0
- package/dist/runtime/core/modules/surfaces.js +9 -0
- package/dist/runtime/core/modules/triggers.d.ts +2 -8
- package/dist/runtime/core/modules/triggers.js +1 -16
- package/dist/runtime/core/modules/workers.js +3 -2
- package/dist/runtime/core/runtime.d.ts +3 -0
- package/dist/runtime/core/runtime.js +12 -0
- package/dist/runtime/core/validate.js +38 -0
- package/dist/runtime/engine/nanosdk.d.ts +14 -1
- package/dist/runtime/engine/nanosdk.js +90 -7
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +2 -0
- package/dist/toolkit/artifact.d.ts +5 -0
- package/dist/toolkit/artifact.js +7 -0
- package/dist/toolkit/derivers/api.d.ts +19 -0
- package/dist/toolkit/derivers/api.js +224 -0
- package/dist/toolkit/gen.js +23 -4
- package/dist/toolkit/index.d.ts +5 -0
- package/dist/toolkit/index.js +12 -1
- package/package.json +2 -2
- package/src/openapi/spec.test.ts +292 -0
- package/src/openapi/spec.ts +540 -0
- package/src/runtime/adapters/deno.ts +6 -0
- package/src/runtime/adapters/node.ts +6 -0
- package/src/runtime/core/execContext.ts +56 -0
- package/src/runtime/core/host.ts +44 -0
- package/src/runtime/core/manifest.ts +1 -0
- package/src/runtime/core/modules/actions.test.ts +1 -0
- package/src/runtime/core/modules/api.test.ts +445 -0
- package/src/runtime/core/modules/api.ts +453 -0
- package/src/runtime/core/modules/cancel.test.ts +310 -0
- package/src/runtime/core/modules/cancel.ts +119 -0
- package/src/runtime/core/modules/connectors.test.ts +1 -0
- package/src/runtime/core/modules/connectors.ts +7 -1
- package/src/runtime/core/modules/datasource.migrations.test.ts +1 -0
- package/src/runtime/core/modules/datasource.ts +77 -6
- package/src/runtime/core/modules/gateway.ts +28 -6
- package/src/runtime/core/modules/instance-tracking.test.ts +345 -0
- package/src/runtime/core/modules/instance-tracking.ts +180 -0
- package/src/runtime/core/modules/pages.test.ts +47 -2
- package/src/runtime/core/modules/pages.ts +23 -1
- package/src/runtime/core/modules/provenance.test.ts +181 -0
- package/src/runtime/core/modules/scheduler.ts +31 -0
- package/src/runtime/core/modules/surfaces.test.ts +2 -0
- package/src/runtime/core/modules/surfaces.ts +10 -0
- package/src/runtime/core/modules/triggers.test.ts +1 -0
- package/src/runtime/core/modules/triggers.ts +3 -25
- package/src/runtime/core/modules/workers.test.ts +1 -0
- package/src/runtime/core/modules/workers.ts +11 -2
- package/src/runtime/core/runtime.test.ts +3 -0
- package/src/runtime/core/runtime.ts +16 -0
- package/src/runtime/core/validate.test.ts +96 -0
- package/src/runtime/core/validate.ts +39 -0
- package/src/runtime/engine/nanosdk.test.ts +118 -5
- package/src/runtime/engine/nanosdk.ts +102 -7
- package/src/runtime/index.ts +12 -0
- package/src/toolkit/artifact.ts +8 -0
- package/src/toolkit/derivers/api.test.ts +219 -0
- package/src/toolkit/derivers/api.ts +255 -0
- package/src/toolkit/gen.test.ts +39 -0
- package/src/toolkit/gen.ts +24 -4
- package/src/toolkit/index.ts +41 -1
|
@@ -13,6 +13,9 @@ export interface MountFlags {
|
|
|
13
13
|
surfaces?: boolean;
|
|
14
14
|
triggers?: boolean;
|
|
15
15
|
security?: boolean;
|
|
16
|
+
/** The `instanceTracking` reconciler poll loop. Shares no HTTP surface; safe to disable
|
|
17
|
+
* in hosts that only render surfaces (e.g. the console preview). */
|
|
18
|
+
instanceTracking?: boolean;
|
|
16
19
|
}
|
|
17
20
|
export interface CreateUrbanAppOptions {
|
|
18
21
|
host: HostContext;
|
|
@@ -7,10 +7,12 @@ import { validateManifest } from "./validate.js";
|
|
|
7
7
|
import { makeRouter } from "./router.js";
|
|
8
8
|
import { deployModels } from "./modules/deploy.js";
|
|
9
9
|
import { provisionData, DataLayer } from "./modules/datasource.js";
|
|
10
|
+
import { installExecStore } from "./execContext.js";
|
|
10
11
|
import { mountWorkers } from "./modules/workers.js";
|
|
11
12
|
import { mountConnectors } from "./modules/connectors.js";
|
|
12
13
|
import { mountSurfaces } from "./modules/surfaces.js";
|
|
13
14
|
import { mountTriggers } from "./modules/triggers.js";
|
|
15
|
+
import { mountInstanceTracking } from "./modules/instance-tracking.js";
|
|
14
16
|
import { mountSecurity } from "./modules/security.js";
|
|
15
17
|
/** Resolve the HTTP port: explicit option, else $PORT, else 8090. Throws a clear
|
|
16
18
|
* error when $PORT is set but not a valid integer in 0..65535. */
|
|
@@ -43,8 +45,13 @@ export async function createUrbanApp(opts) {
|
|
|
43
45
|
surfaces: opts.mount?.surfaces ?? true,
|
|
44
46
|
triggers: opts.mount?.triggers ?? true,
|
|
45
47
|
security: opts.mount?.security ?? true,
|
|
48
|
+
instanceTracking: opts.mount?.instanceTracking ?? true,
|
|
46
49
|
};
|
|
47
50
|
const ctx = { manifest, host, engine, root, templates: opts.templates };
|
|
51
|
+
// Install the ambient job-execution store once per process (idempotent) so worker dispatch can
|
|
52
|
+
// stamp write-provenance onto DataLayer inserts. Absent-safe: a host without `createAsyncStore`
|
|
53
|
+
// leaves provenance capture disabled.
|
|
54
|
+
installExecStore(() => host.createAsyncStore?.());
|
|
48
55
|
let data;
|
|
49
56
|
let security;
|
|
50
57
|
let server;
|
|
@@ -144,6 +151,11 @@ export async function createUrbanApp(opts) {
|
|
|
144
151
|
mounted.push(t);
|
|
145
152
|
describe.triggers = t.describe?.();
|
|
146
153
|
}
|
|
154
|
+
if (flags.instanceTracking && (manifest.instanceTracking?.length ?? 0) > 0) {
|
|
155
|
+
const it = mountInstanceTracking(ctx, api);
|
|
156
|
+
mounted.push(it);
|
|
157
|
+
describe.instanceTracking = it.describe?.();
|
|
158
|
+
}
|
|
147
159
|
if (security)
|
|
148
160
|
describe.security = security.describe();
|
|
149
161
|
if (data)
|
|
@@ -131,6 +131,44 @@ export function collectManifestIssues(m) {
|
|
|
131
131
|
issues.push({ path: `triggers[${i}].type`, message: "missing type" });
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
+
// instanceTracking: each binding must name the table + key column it tracks and a
|
|
135
|
+
// non-empty `onTerminated.set` patch — a reconciler with nothing to write is inert
|
|
136
|
+
// and almost certainly a mistake. `activeStatuses` without `statusField` is incoherent
|
|
137
|
+
// (no column to read the statuses from), so the runtime would silently poll every row.
|
|
138
|
+
const tracking = Array.isArray(obj.instanceTracking) ? obj.instanceTracking : undefined;
|
|
139
|
+
if (tracking) {
|
|
140
|
+
tracking.forEach((t, i) => {
|
|
141
|
+
const b = isRecord(t) ? t : undefined;
|
|
142
|
+
if (typeof b?.table !== "string" || b.table.length === 0) {
|
|
143
|
+
issues.push({ path: `instanceTracking[${i}].table`, message: "missing table" });
|
|
144
|
+
}
|
|
145
|
+
if (typeof b?.keyField !== "string" || b.keyField.length === 0) {
|
|
146
|
+
issues.push({ path: `instanceTracking[${i}].keyField`, message: "missing keyField" });
|
|
147
|
+
}
|
|
148
|
+
const onTerminated = isRecord(b?.onTerminated) ? b.onTerminated : undefined;
|
|
149
|
+
const set = isRecord(onTerminated?.set) ? onTerminated.set : undefined;
|
|
150
|
+
if (!set || Object.keys(set).length === 0) {
|
|
151
|
+
issues.push({
|
|
152
|
+
path: `instanceTracking[${i}].onTerminated.set`,
|
|
153
|
+
message: "missing onTerminated.set patch (a non-empty column → value map)",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(b?.activeStatuses) && (typeof b?.statusField !== "string" || b.statusField.length === 0)) {
|
|
157
|
+
issues.push({
|
|
158
|
+
path: `instanceTracking[${i}].activeStatuses`,
|
|
159
|
+
message: "activeStatuses requires statusField",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// `pollMs`, when set, schedules a self-rescheduling timer; a non-number/NaN/non-positive
|
|
163
|
+
// value would become a 0-delay hot loop at runtime, so reject it at author time.
|
|
164
|
+
if (b?.pollMs !== undefined && (typeof b.pollMs !== "number" || !Number.isFinite(b.pollMs) || b.pollMs <= 0)) {
|
|
165
|
+
issues.push({
|
|
166
|
+
path: `instanceTracking[${i}].pollMs`,
|
|
167
|
+
message: "pollMs must be a finite positive number of milliseconds",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
134
172
|
return issues;
|
|
135
173
|
}
|
|
136
174
|
export function assertValidManifest(m) {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import type { EngineClient, JobHandler, WorkerSubscription } from "../core/host.ts";
|
|
1
|
+
import type { EngineClient, JobHandler, ProcessInstanceSnapshot, ProcessInstanceState, WorkerSubscription } from "../core/host.ts";
|
|
2
2
|
import type { EngineSdkClient } from "./sdk.ts";
|
|
3
3
|
/** Coerce an engine response's process-instance key to a non-empty string, or
|
|
4
4
|
* throw — a missing key means a malformed/partial response, not a real instance. */
|
|
5
5
|
export declare function requireProcessInstanceKey(key: string | number | null | undefined): string;
|
|
6
|
+
/** Map the engine's process-instance `state` onto the transport-agnostic
|
|
7
|
+
* {@link ProcessInstanceState} union, or `undefined` for an unrecognized value
|
|
8
|
+
* (which the caller skips rather than mis-reconciling). Case-insensitive so a
|
|
9
|
+
* future casing tweak on the engine side doesn't silently drop instances. */
|
|
10
|
+
export declare function normalizeProcessInstanceState(raw: unknown): ProcessInstanceState | undefined;
|
|
6
11
|
/** A job as delivered to a nano-sdk job handler: the frame fields plus the
|
|
7
12
|
* acknowledgement actions the handler must call. */
|
|
8
13
|
export interface NanoSdkActivatedJob {
|
|
@@ -76,6 +81,10 @@ export interface NanoSdkClient {
|
|
|
76
81
|
userTaskKey: string;
|
|
77
82
|
variables?: Record<string, unknown>;
|
|
78
83
|
}, options?: unknown): Promise<unknown>;
|
|
84
|
+
searchProcessInstances(input: {
|
|
85
|
+
filter?: Record<string, unknown>;
|
|
86
|
+
page?: Record<string, unknown>;
|
|
87
|
+
}, consistency?: unknown, options?: unknown): Promise<Record<string, unknown>>;
|
|
79
88
|
createJobWorker(cfg: NanoSdkJobWorkerConfig): NanoSdkJobWorker;
|
|
80
89
|
/** Stop every worker created on this client. Used on teardown to also drain a
|
|
81
90
|
* REST-fallback worker, whose handle the `createJobWorker` proxy starts internally
|
|
@@ -134,6 +143,10 @@ export declare class SdkEngineClient implements EngineClient {
|
|
|
134
143
|
variables?: Record<string, unknown>;
|
|
135
144
|
}[]>;
|
|
136
145
|
completeUserTask(userTaskKey: string, variables?: Record<string, unknown>): Promise<void>;
|
|
146
|
+
searchProcessInstances(filter?: {
|
|
147
|
+
processInstanceKeys?: string[];
|
|
148
|
+
state?: ProcessInstanceState;
|
|
149
|
+
}): Promise<ProcessInstanceSnapshot[]>;
|
|
137
150
|
registerWorker(jobType: string, handler: JobHandler, options?: {
|
|
138
151
|
workerName?: string;
|
|
139
152
|
maxParallelJobs?: number;
|
|
@@ -28,6 +28,24 @@ export function requireProcessInstanceKey(key) {
|
|
|
28
28
|
}
|
|
29
29
|
return String(key);
|
|
30
30
|
}
|
|
31
|
+
/** Map the engine's process-instance `state` onto the transport-agnostic
|
|
32
|
+
* {@link ProcessInstanceState} union, or `undefined` for an unrecognized value
|
|
33
|
+
* (which the caller skips rather than mis-reconciling). Case-insensitive so a
|
|
34
|
+
* future casing tweak on the engine side doesn't silently drop instances. */
|
|
35
|
+
export function normalizeProcessInstanceState(raw) {
|
|
36
|
+
if (typeof raw !== "string")
|
|
37
|
+
return undefined;
|
|
38
|
+
switch (raw.toUpperCase()) {
|
|
39
|
+
case "ACTIVE":
|
|
40
|
+
return "ACTIVE";
|
|
41
|
+
case "COMPLETED":
|
|
42
|
+
return "COMPLETED";
|
|
43
|
+
case "TERMINATED":
|
|
44
|
+
return "TERMINATED";
|
|
45
|
+
default:
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
31
49
|
/**
|
|
32
50
|
* An `EngineClient` backed entirely by a `@nanobpm/nano-sdk` client. Deploy, create,
|
|
33
51
|
* message, and user-task calls map one-to-one onto the SDK's client methods; workers
|
|
@@ -101,6 +119,37 @@ export class SdkEngineClient {
|
|
|
101
119
|
async completeUserTask(userTaskKey, variables) {
|
|
102
120
|
await this.client.completeUserTask({ userTaskKey, variables: variables ?? {} });
|
|
103
121
|
}
|
|
122
|
+
async searchProcessInstances(filter) {
|
|
123
|
+
const f = {};
|
|
124
|
+
if (filter?.state)
|
|
125
|
+
f.state = filter.state;
|
|
126
|
+
const keys = filter?.processInstanceKeys?.filter((k) => k != null && k !== "");
|
|
127
|
+
if (keys && keys.length > 0)
|
|
128
|
+
f.processInstanceKey = { $in: keys };
|
|
129
|
+
// A process-instance search is an eventually consistent read; ask for zero-wait
|
|
130
|
+
// consistency so it reflects what is currently visible without blocking. Cap the page
|
|
131
|
+
// to the number of keys asked for (each key matches at most one instance), so a bounded
|
|
132
|
+
// reconcile query never silently truncates on the API's default page size.
|
|
133
|
+
const page = keys && keys.length > 0 ? { limit: keys.length } : undefined;
|
|
134
|
+
const body = await this.client.searchProcessInstances({ filter: f, ...(page ? { page } : {}) }, { consistency: { waitUpToMs: 0 } });
|
|
135
|
+
const items = Array.isArray(body.items) ? body.items.filter(isRecord) : [];
|
|
136
|
+
return items.flatMap((it) => {
|
|
137
|
+
const key = it.processInstanceKey;
|
|
138
|
+
if (key == null || key === "") {
|
|
139
|
+
this.log("warn", "skipping process instance with no key in engine response");
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const state = normalizeProcessInstanceState(it.state);
|
|
143
|
+
if (!state) {
|
|
144
|
+
this.log("warn", "skipping process instance with unrecognized state", {
|
|
145
|
+
processInstanceKey: String(key),
|
|
146
|
+
state: String(it.state),
|
|
147
|
+
});
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
return [{ processInstanceKey: String(key), state }];
|
|
151
|
+
});
|
|
152
|
+
}
|
|
104
153
|
async registerWorker(jobType, handler, options) {
|
|
105
154
|
const worker = this.client.createJobWorker({
|
|
106
155
|
jobType,
|
|
@@ -120,9 +169,9 @@ export class SdkEngineClient {
|
|
|
120
169
|
elementId: job.elementId,
|
|
121
170
|
variables: job.variables ?? {},
|
|
122
171
|
};
|
|
172
|
+
let out;
|
|
123
173
|
try {
|
|
124
|
-
|
|
125
|
-
return await job.complete(out ?? {});
|
|
174
|
+
out = await handler(engineJob);
|
|
126
175
|
}
|
|
127
176
|
catch (err) {
|
|
128
177
|
// A handler-raised BPMN error is a modelled, non-retryable outcome:
|
|
@@ -152,17 +201,51 @@ export class SdkEngineClient {
|
|
|
152
201
|
}
|
|
153
202
|
}
|
|
154
203
|
const message = err instanceof Error ? err.message : String(err);
|
|
155
|
-
this.log("error", `handler ${jobType} threw`, {
|
|
204
|
+
this.log("error", `handler ${jobType} threw`, {
|
|
205
|
+
err: message,
|
|
206
|
+
jobKey: engineJob.jobKey,
|
|
207
|
+
processInstanceKey: engineJob.processInstanceKey,
|
|
208
|
+
elementId: engineJob.elementId,
|
|
209
|
+
});
|
|
156
210
|
try {
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
|
|
211
|
+
// A generic handler failure omits the retry count so the SDK decrements the job's
|
|
212
|
+
// remaining retries (`job.retries - 1`): a transient failure (e.g. a fleeting store
|
|
213
|
+
// error) self-heals on redelivery and only parks as an incident once the budget is
|
|
214
|
+
// exhausted. A `BpmnError` reaching here (no `job.error()` transport, or the error
|
|
215
|
+
// report itself threw) is the exception: it is a modelled, DETERMINISTIC outcome, so
|
|
216
|
+
// retrying would re-throw the same error and burn the budget for nothing — pin
|
|
217
|
+
// `retries: 0` to keep it non-retryable. Await + swallow so a rejected `fail` cannot
|
|
218
|
+
// escape as an unhandled rejection.
|
|
219
|
+
const body = {
|
|
220
|
+
errorMessage: message.slice(0, 500),
|
|
221
|
+
};
|
|
222
|
+
if (isBpmnError(err))
|
|
223
|
+
body.retries = 0;
|
|
224
|
+
return await job.fail(body);
|
|
161
225
|
}
|
|
162
226
|
catch {
|
|
163
227
|
return undefined;
|
|
164
228
|
}
|
|
165
229
|
}
|
|
230
|
+
// The handler resolved: its work is done. Reporting that result to the engine
|
|
231
|
+
// (`complete`) is a SEPARATE concern — a failure here is a transport/engine problem
|
|
232
|
+
// (e.g. a transient store error), NOT a handler bug. Failing it (even with a decrementing
|
|
233
|
+
// retry) would burn the job's retry budget for a completion we can't confirm didn't land.
|
|
234
|
+
// Instead, log and leave the job locked so the engine redelivers it on lock timeout;
|
|
235
|
+
// handlers are idempotent (at-least-once), so a redelivery re-completes cleanly.
|
|
236
|
+
try {
|
|
237
|
+
return await job.complete(out ?? {});
|
|
238
|
+
}
|
|
239
|
+
catch (completeErr) {
|
|
240
|
+
const message = completeErr instanceof Error ? completeErr.message : String(completeErr);
|
|
241
|
+
this.log("error", `complete ${jobType} failed; leaving job for engine redelivery`, {
|
|
242
|
+
err: message,
|
|
243
|
+
jobKey: engineJob.jobKey,
|
|
244
|
+
processInstanceKey: engineJob.processInstanceKey,
|
|
245
|
+
elementId: engineJob.elementId,
|
|
246
|
+
});
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
166
249
|
},
|
|
167
250
|
});
|
|
168
251
|
// The nano-sdk job worker owns its own start lifecycle: `createJobWorker` runs
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -17,8 +17,12 @@ export { makeGateway, Table } from "./core/modules/gateway.ts";
|
|
|
17
17
|
export type { ColumnMeta, DataSource as GatewayDataSource, ExecResult, ForeignKeyMeta, Row, TableMeta, } from "./core/modules/gateway.ts";
|
|
18
18
|
export { createPagesRoutes, mountPages } from "./core/modules/pages.ts";
|
|
19
19
|
export type { PagesDataSource, PagesDeps, PagesHandle, PagesOptions } from "./core/modules/pages.ts";
|
|
20
|
+
export { cancelInstanceReconciling } from "./core/modules/cancel.ts";
|
|
21
|
+
export type { CancelInstanceResult, CancelInstanceState } from "./core/modules/cancel.ts";
|
|
20
22
|
export { mountActions, resolveActionHandler } from "./core/modules/actions.ts";
|
|
21
23
|
export type { ActionHandler, ActionRequest, ActionResult, ActionsHandle } from "./core/modules/actions.ts";
|
|
24
|
+
export { defineOperation, mountApi, resolveOperationHandler } from "./core/modules/api.ts";
|
|
25
|
+
export type { ApiBinding, ApiHandle, DefaultContract, OperationContract, OperationHandler, OperationInput, OperationResult, } from "./core/modules/api.ts";
|
|
22
26
|
export type { AppJobHandler } from "./core/modules/workers.ts";
|
|
23
27
|
export { resolveHandler, sdkDecisionEvaluator } from "./core/modules/workers.ts";
|
|
24
28
|
export { buildMessages, callLlm, resolveProvider, runLlmJob, } from "./core/modules/llm.ts";
|
package/dist/runtime/index.js
CHANGED
|
@@ -15,7 +15,9 @@ export { DataLayer, TypeRepo } from "./core/modules/datasource.js";
|
|
|
15
15
|
export { runDataOp } from "./core/modules/dataops.js";
|
|
16
16
|
export { makeGateway, Table } from "./core/modules/gateway.js";
|
|
17
17
|
export { createPagesRoutes, mountPages } from "./core/modules/pages.js";
|
|
18
|
+
export { cancelInstanceReconciling } from "./core/modules/cancel.js";
|
|
18
19
|
export { mountActions, resolveActionHandler } from "./core/modules/actions.js";
|
|
20
|
+
export { defineOperation, mountApi, resolveOperationHandler } from "./core/modules/api.js";
|
|
19
21
|
export { resolveHandler, sdkDecisionEvaluator } from "./core/modules/workers.js";
|
|
20
22
|
export { buildMessages, callLlm, resolveProvider, runLlmJob, } from "./core/modules/llm.js";
|
|
21
23
|
export { evalCorrelation } from "./core/modules/triggers.js";
|
|
@@ -17,3 +17,8 @@ export interface Deriver<I> {
|
|
|
17
17
|
export declare const GENERATED_DIR = "nano-generated";
|
|
18
18
|
/** Stable ordering so artifact lists compare deterministically. */
|
|
19
19
|
export declare function sortArtifacts(a: DerivedArtifact[]): DerivedArtifact[];
|
|
20
|
+
/** True when `p` is an absolute path — POSIX root (`/`), a drive-letter root (`C:\` or `C:/`), or a
|
|
21
|
+
* Windows UNC/drive-root backslash (`\`). The single source of truth for the absolute-path rule,
|
|
22
|
+
* shared by the toolkit's path join (gen.ts) and the runtime's resolveAppPath so both agree on
|
|
23
|
+
* whether a manifest path is app-root-relative or absolute (no gen/runtime drift). */
|
|
24
|
+
export declare function isAbsolutePath(p: string): boolean;
|
package/dist/toolkit/artifact.js
CHANGED
|
@@ -9,3 +9,10 @@ export const GENERATED_DIR = "nano-generated";
|
|
|
9
9
|
export function sortArtifacts(a) {
|
|
10
10
|
return [...a].sort((x, y) => (x.path < y.path ? -1 : x.path > y.path ? 1 : 0));
|
|
11
11
|
}
|
|
12
|
+
/** True when `p` is an absolute path — POSIX root (`/`), a drive-letter root (`C:\` or `C:/`), or a
|
|
13
|
+
* Windows UNC/drive-root backslash (`\`). The single source of truth for the absolute-path rule,
|
|
14
|
+
* shared by the toolkit's path join (gen.ts) and the runtime's resolveAppPath so both agree on
|
|
15
|
+
* whether a manifest path is app-root-relative or absolute (no gen/runtime drift). */
|
|
16
|
+
export function isAbsolutePath(p) {
|
|
17
|
+
return /^(\/|\\|[A-Za-z]:[/\\])/.test(p);
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DerivedArtifact, Deriver } from "../artifact.ts";
|
|
2
|
+
import { type OpenApiDoc, type OpenApiSchema } from "../../openapi/spec.ts";
|
|
3
|
+
/** Filenames of the emitted artifacts (siblings of worker-io.d.ts in nano-generated/). */
|
|
4
|
+
export declare const API_BINDINGS_DTS = "api-io.d.ts";
|
|
5
|
+
export declare const API_BINDINGS_TS = "operations.ts";
|
|
6
|
+
/** Render a JSON-Schema (OpenAPI subset) as a TS type expression. Local component `$ref`s render as
|
|
7
|
+
* their component name (emitted as named interfaces); unknown shapes fall back to `unknown`. */
|
|
8
|
+
export declare function schemaToTs(schema: OpenApiSchema | undefined, depth?: number): string;
|
|
9
|
+
/** Emit `api-io.d.ts`: named component types + per-operation Request/Response + the id union. */
|
|
10
|
+
export declare function emitApiBindings(doc: OpenApiDoc): string;
|
|
11
|
+
/** Emit `operations.ts`: the typed `defineOperation` wrapper, keyed by operationId (mirrors the
|
|
12
|
+
* generated typed `defineWorker`). Written verbatim (it never changes with the spec) — it only
|
|
13
|
+
* re-types the runtime's `defineOperation`. */
|
|
14
|
+
export declare function emitApiBindingsRuntime(): string;
|
|
15
|
+
/** Derive the api artifacts from a parsed OpenAPI document. */
|
|
16
|
+
export declare function deriveApi(doc: OpenApiDoc): DerivedArtifact[];
|
|
17
|
+
export declare const apiDeriver: Deriver<{
|
|
18
|
+
doc: OpenApiDoc;
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Deriver: an OpenAPI document → `api-io.d.ts` (the endpoint type map) + `operations.ts` (the typed
|
|
2
|
+
// `defineOperation` wrapper). ADR 0058: the contract-first endpoint surface. This is the authoring
|
|
3
|
+
// half — per `operationId` it emits a `Request`/`Response` type from the operation's parameters,
|
|
4
|
+
// requestBody, and first success response, plus an `ApiOperationId` union — mirroring how
|
|
5
|
+
// `worker-io.d.ts` types `defineWorker` from the model (ADR 0033 §3). The runtime
|
|
6
|
+
// (`runtime/core/modules/api.ts`) is the matching half: it validates + routes from the SAME spec via
|
|
7
|
+
// the shared `openapi/spec.ts`, so types and runtime never drift.
|
|
8
|
+
import { GENERATED_DIR } from "../artifact.js";
|
|
9
|
+
import { collectOperations, isObjectSchema, refName, } from "../../openapi/spec.js";
|
|
10
|
+
/** Filenames of the emitted artifacts (siblings of worker-io.d.ts in nano-generated/). */
|
|
11
|
+
export const API_BINDINGS_DTS = "api-io.d.ts";
|
|
12
|
+
export const API_BINDINGS_TS = "operations.ts";
|
|
13
|
+
/** A TS identifier form of an operationId (used for the per-op type names). */
|
|
14
|
+
function typeStem(operationId) {
|
|
15
|
+
const cleaned = operationId.replace(/[^A-Za-z0-9]+/g, " ").trim();
|
|
16
|
+
const pascal = cleaned
|
|
17
|
+
.split(/\s+/)
|
|
18
|
+
.map((w) => (w.length > 0 ? w[0].toUpperCase() + w.slice(1) : ""))
|
|
19
|
+
.join("");
|
|
20
|
+
return /^[A-Za-z_]/.test(pascal) ? pascal : `Op${pascal}`;
|
|
21
|
+
}
|
|
22
|
+
/** Render a JSON-Schema (OpenAPI subset) as a TS type expression. Local component `$ref`s render as
|
|
23
|
+
* their component name (emitted as named interfaces); unknown shapes fall back to `unknown`. */
|
|
24
|
+
export function schemaToTs(schema, depth = 0) {
|
|
25
|
+
if (!schema || depth > 20)
|
|
26
|
+
return "unknown";
|
|
27
|
+
if (schema.$ref) {
|
|
28
|
+
const name = refName(schema.$ref);
|
|
29
|
+
return name ? typeStem(name) : "unknown";
|
|
30
|
+
}
|
|
31
|
+
const nullable = schema.nullable === true;
|
|
32
|
+
const wrap = (t) => (nullable ? `${t} | null` : t);
|
|
33
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
34
|
+
return wrap(schema.enum.map((v) => JSON.stringify(v)).join(" | "));
|
|
35
|
+
}
|
|
36
|
+
if (schema.const !== undefined)
|
|
37
|
+
return wrap(JSON.stringify(schema.const));
|
|
38
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
|
|
39
|
+
return wrap(schema.oneOf.map((s) => schemaToTs(s, depth + 1)).join(" | "));
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
|
|
42
|
+
return wrap(schema.anyOf.map((s) => schemaToTs(s, depth + 1)).join(" | "));
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
|
|
45
|
+
return wrap(schema.allOf.map((s) => schemaToTs(s, depth + 1)).join(" & "));
|
|
46
|
+
}
|
|
47
|
+
switch (schema.type) {
|
|
48
|
+
case "string":
|
|
49
|
+
return wrap("string");
|
|
50
|
+
case "integer":
|
|
51
|
+
case "number":
|
|
52
|
+
return wrap("number");
|
|
53
|
+
case "boolean":
|
|
54
|
+
return wrap("boolean");
|
|
55
|
+
case "null":
|
|
56
|
+
return "null";
|
|
57
|
+
case "array":
|
|
58
|
+
return wrap(`Array<${schemaToTs(schema.items, depth + 1)}>`);
|
|
59
|
+
case "object":
|
|
60
|
+
return wrap(objectToTs(schema, depth));
|
|
61
|
+
default:
|
|
62
|
+
// No `type`: infer object-ness from object-only keywords (shared with the runtime
|
|
63
|
+
// validator via isObjectSchema so types and validation agree); otherwise unknown.
|
|
64
|
+
if (isObjectSchema(schema))
|
|
65
|
+
return wrap(objectToTs(schema, depth));
|
|
66
|
+
return "unknown";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function objectToTs(schema, depth) {
|
|
70
|
+
const props = schema.properties ?? {};
|
|
71
|
+
const required = new Set(schema.required ?? []);
|
|
72
|
+
const entries = Object.entries(props).map(([key, ps]) => {
|
|
73
|
+
const opt = required.has(key) ? "" : "?";
|
|
74
|
+
const doc = typeof ps.description === "string" ? ps.description.replace(/\*\//g, "*\\/") : undefined;
|
|
75
|
+
const jsdoc = doc ? `/** ${doc} */ ` : "";
|
|
76
|
+
return `${jsdoc}${JSON.stringify(key)}${opt}: ${schemaToTs(ps, depth + 1)}`;
|
|
77
|
+
});
|
|
78
|
+
const extra = schema.additionalProperties;
|
|
79
|
+
if (extra && typeof extra === "object") {
|
|
80
|
+
// TS requires every named property type to be assignable to the index signature. When the
|
|
81
|
+
// schema also declares properties, a narrow index type (e.g. `number`) would make the emitted
|
|
82
|
+
// type invalid (TS2411), so fall back to `unknown`; with no declared properties we can use the
|
|
83
|
+
// precise additionalProperties type (the common "map" case).
|
|
84
|
+
const hasProps = Object.keys(props).length > 0;
|
|
85
|
+
entries.push(`[key: string]: ${hasProps ? "unknown" : schemaToTs(extra, depth + 1)}`);
|
|
86
|
+
}
|
|
87
|
+
else if (extra !== false) {
|
|
88
|
+
// OpenAPI/JSON-Schema default: additional properties are allowed. Emit an index
|
|
89
|
+
// signature so the emitted type isn't stricter than the runtime validator, which only
|
|
90
|
+
// rejects extras when additionalProperties === false (otherwise excess-property errors
|
|
91
|
+
// on object literals would reject bodies the runtime accepts).
|
|
92
|
+
entries.push("[key: string]: unknown");
|
|
93
|
+
}
|
|
94
|
+
if (entries.length > 0)
|
|
95
|
+
return `{ ${entries.join("; ")} }`;
|
|
96
|
+
// No declared properties and additionalProperties: false → a closed, empty object.
|
|
97
|
+
return "Record<string, never>";
|
|
98
|
+
}
|
|
99
|
+
/** The `params`/`query` object type for an operation, from its parameters (strings on the wire). */
|
|
100
|
+
function paramsTs(op, where) {
|
|
101
|
+
const ps = op.parameters.filter((p) => p.in === where);
|
|
102
|
+
if (ps.length === 0)
|
|
103
|
+
return "{}";
|
|
104
|
+
const fields = ps.map((p) => {
|
|
105
|
+
const opt = p.required ? "" : "?";
|
|
106
|
+
// Query values arrive as strings (or string[] for repeated keys); path params are single
|
|
107
|
+
// strings. Optionality is carried by the `?` modifier — don't fold `undefined` into the
|
|
108
|
+
// value type, or required query params would still accept undefined.
|
|
109
|
+
const t = where === "query" ? "string | string[]" : "string";
|
|
110
|
+
return `${JSON.stringify(p.name)}${opt}: ${t}`;
|
|
111
|
+
});
|
|
112
|
+
return `{ ${fields.join("; ")} }`;
|
|
113
|
+
}
|
|
114
|
+
/** Emit `api-io.d.ts`: named component types + per-operation Request/Response + the id union. */
|
|
115
|
+
export function emitApiBindings(doc) {
|
|
116
|
+
const ops = collectOperations(doc);
|
|
117
|
+
const components = doc.components?.schemas ?? {};
|
|
118
|
+
// Fail-closed on TypeScript name collisions: typeStem is lossy (non-alphanumerics collapse), so
|
|
119
|
+
// distinct component/operation names ("get-invoice" vs "get_invoice") can map to the same TS
|
|
120
|
+
// identifier and emit duplicate declarations that break typechecking. Surface it here (where
|
|
121
|
+
// `urban gen`/`urban check` reports it) rather than shipping broken generated types.
|
|
122
|
+
const emittedTypeNames = new Map();
|
|
123
|
+
const claimTypeName = (typeName, source) => {
|
|
124
|
+
const prior = emittedTypeNames.get(typeName);
|
|
125
|
+
if (prior !== undefined) {
|
|
126
|
+
throw new Error(`OpenAPI spec produces a duplicate TypeScript type "${typeName}" from ${prior} and ${source} ` +
|
|
127
|
+
`— their names collapse to the same identifier; rename one to disambiguate (ADR 0058).`);
|
|
128
|
+
}
|
|
129
|
+
emittedTypeNames.set(typeName, source);
|
|
130
|
+
};
|
|
131
|
+
for (const name of Object.keys(components)) {
|
|
132
|
+
claimTypeName(typeStem(name), `component schema "${name}"`);
|
|
133
|
+
}
|
|
134
|
+
for (const op of ops) {
|
|
135
|
+
claimTypeName(`${typeStem(op.operationId)}Request`, `operationId "${op.operationId}"`);
|
|
136
|
+
claimTypeName(`${typeStem(op.operationId)}Response`, `operationId "${op.operationId}"`);
|
|
137
|
+
}
|
|
138
|
+
const header = "// AUTO-GENERATED by @nanobpm/urban from the app's OpenAPI spec (ADR 0058).\n" +
|
|
139
|
+
"// The endpoint type map: each operationId maps to its typed request (path/query params +\n" +
|
|
140
|
+
"// JSON body) and success response, derived from the spec. Do not edit — regenerated from\n" +
|
|
141
|
+
"// the spec; erased to plain JS at compile.\n" +
|
|
142
|
+
"// eslint-disable\n\n";
|
|
143
|
+
const componentDecls = Object.entries(components)
|
|
144
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
145
|
+
.map(([name, schema]) => `export type ${typeStem(name)} = ${schemaToTs(schema)};`)
|
|
146
|
+
.join("\n");
|
|
147
|
+
const opDecls = ops
|
|
148
|
+
.map((op) => {
|
|
149
|
+
const stem = typeStem(op.operationId);
|
|
150
|
+
// With a schema → the typed body. Required but no JSON schema → `unknown` (a body IS
|
|
151
|
+
// required at runtime, just of an unknown shape). Otherwise (optional, absent) → `undefined`.
|
|
152
|
+
const bodyType = op.requestBodySchema
|
|
153
|
+
? schemaToTs(op.requestBodySchema)
|
|
154
|
+
: op.requestBodyRequired
|
|
155
|
+
? "unknown"
|
|
156
|
+
: "undefined";
|
|
157
|
+
// An optional (or absent) request body is passed to the delegate as `undefined` at runtime,
|
|
158
|
+
// so mark it optional in the type rather than forcing handlers to treat it as always-present.
|
|
159
|
+
const bodyOpt = op.requestBodyRequired ? "" : "?";
|
|
160
|
+
const resp = op.responseSchema ? schemaToTs(op.responseSchema) : "unknown";
|
|
161
|
+
const summary = op.summary ? `/** ${op.summary.replace(/\*\//g, "*\\/")} */\n` : "";
|
|
162
|
+
return (`${summary}export interface ${stem}Request {\n` +
|
|
163
|
+
` params: ${paramsTs(op, "path")};\n` +
|
|
164
|
+
` query: ${paramsTs(op, "query")};\n` +
|
|
165
|
+
` body${bodyOpt}: ${bodyType};\n` +
|
|
166
|
+
`}\n` +
|
|
167
|
+
`export type ${stem}Response = ${resp};`);
|
|
168
|
+
})
|
|
169
|
+
.join("\n\n");
|
|
170
|
+
const idUnion = ops.length > 0
|
|
171
|
+
? ops.map((o) => JSON.stringify(o.operationId)).join(" | ")
|
|
172
|
+
: "never";
|
|
173
|
+
const mapEntries = ops
|
|
174
|
+
.map((op) => {
|
|
175
|
+
const stem = typeStem(op.operationId);
|
|
176
|
+
return ` ${JSON.stringify(op.operationId)}: { request: ${stem}Request; response: ${stem}Response };`;
|
|
177
|
+
})
|
|
178
|
+
.join("\n");
|
|
179
|
+
return (header +
|
|
180
|
+
(componentDecls ? `${componentDecls}\n\n` : "") +
|
|
181
|
+
(opDecls ? `${opDecls}\n\n` : "") +
|
|
182
|
+
`/** Every declared operationId (ADR 0058): the delegate key set. */\n` +
|
|
183
|
+
`export type ApiOperationId = ${idUnion};\n\n` +
|
|
184
|
+
`/** Request/response contract per operationId. */\n` +
|
|
185
|
+
`export interface ApiOperations {\n${mapEntries}\n}\n`);
|
|
186
|
+
}
|
|
187
|
+
/** Emit `operations.ts`: the typed `defineOperation` wrapper, keyed by operationId (mirrors the
|
|
188
|
+
* generated typed `defineWorker`). Written verbatim (it never changes with the spec) — it only
|
|
189
|
+
* re-types the runtime's `defineOperation`. */
|
|
190
|
+
export function emitApiBindingsRuntime() {
|
|
191
|
+
return ("// AUTO-GENERATED by @nanobpm/urban (ADR 0058): the typed `defineOperation`.\n" +
|
|
192
|
+
"// Re-exports the operation SDK and overrides `defineOperation` with an operationId-keyed\n" +
|
|
193
|
+
"// signature (the delegate's validated request + result typed from the spec). Erased to a\n" +
|
|
194
|
+
"// pass-through at runtime. Do not edit.\n" +
|
|
195
|
+
"// eslint-disable\n\n" +
|
|
196
|
+
`import { defineOperation as defineOperationRaw } from "@nanobpm/urban";\n` +
|
|
197
|
+
`import type { OperationHandler } from "@nanobpm/urban";\n` +
|
|
198
|
+
`import type { ApiOperationId, ApiOperations } from "./${API_BINDINGS_DTS}";\n\n` +
|
|
199
|
+
`type ReqFor<K extends ApiOperationId> = ApiOperations[K]["request"];\n` +
|
|
200
|
+
`type ResFor<K extends ApiOperationId> = ApiOperations[K]["response"];\n\n` +
|
|
201
|
+
`/**\n` +
|
|
202
|
+
` * Typed \`defineOperation\`: \`id\` is constrained to the spec's declared operationIds\n` +
|
|
203
|
+
` * (ADR 0058) so it autocompletes and rejects unknown ops, and the handler's request\n` +
|
|
204
|
+
` * (validated params/query/body) + result are typed from the operation's schemas.\n` +
|
|
205
|
+
` */\n` +
|
|
206
|
+
`export function defineOperation<K extends ApiOperationId>(\n` +
|
|
207
|
+
` id: K,\n` +
|
|
208
|
+
` handler: OperationHandler<ReqFor<K>, ResFor<K>>,\n` +
|
|
209
|
+
`): OperationHandler<ReqFor<K>, ResFor<K>> {\n` +
|
|
210
|
+
` return defineOperationRaw(id, handler);\n` +
|
|
211
|
+
`}\n`);
|
|
212
|
+
}
|
|
213
|
+
/** Derive the api artifacts from a parsed OpenAPI document. */
|
|
214
|
+
export function deriveApi(doc) {
|
|
215
|
+
return [
|
|
216
|
+
{ path: `${GENERATED_DIR}/${API_BINDINGS_DTS}`, content: emitApiBindings(doc) },
|
|
217
|
+
{ path: `${GENERATED_DIR}/${API_BINDINGS_TS}`, content: emitApiBindingsRuntime() },
|
|
218
|
+
];
|
|
219
|
+
}
|
|
220
|
+
export const apiDeriver = {
|
|
221
|
+
id: "openapi->api-io",
|
|
222
|
+
describe: "Derive api-io.d.ts (typed request/response per operationId) + the typed defineOperation.",
|
|
223
|
+
derive: ({ doc }) => deriveApi(doc),
|
|
224
|
+
};
|
package/dist/toolkit/gen.js
CHANGED
|
@@ -2,17 +2,28 @@
|
|
|
2
2
|
// models, runs the pure derivers, and either writes the artifacts to disk (`urban gen`) or
|
|
3
3
|
// compares them against what's on disk and reports drift (`urban gen --check`). The derivers stay
|
|
4
4
|
// pure; all IO is confined here behind a tiny FS port so the same code runs on Node and Deno.
|
|
5
|
-
import { sortArtifacts } from "./artifact.js";
|
|
5
|
+
import { isAbsolutePath, sortArtifacts } from "./artifact.js";
|
|
6
6
|
import { deriveMigrations } from "./derivers/migrations.js";
|
|
7
7
|
import { deriveDomain } from "./derivers/domain.js";
|
|
8
8
|
import { deriveWorkerBindings } from "./derivers/worker-io.js";
|
|
9
9
|
import { deriveMeta } from "./derivers/meta.js";
|
|
10
10
|
import { deriveMessageBindings } from "./derivers/messages.js";
|
|
11
|
+
import { deriveApi } from "./derivers/api.js";
|
|
12
|
+
import { parseSpec } from "../openapi/spec.js";
|
|
11
13
|
import { deriveModels, MODEL_PROVENANCE } from "./models.js";
|
|
12
14
|
function join(root, rel) {
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
15
|
+
// Normalize Windows-style separators to forward slashes (GenIO uses forward slashes on all
|
|
16
|
+
// platforms — matching the runtime's resolveAppPath) and trim edge separators, so callers may
|
|
17
|
+
// pass either style without gen/runtime drift over where a file resolves.
|
|
18
|
+
const norm = (s) => s.replace(/\\/g, "/");
|
|
19
|
+
// An absolute `rel` resolves to itself — never prefixed with `root` — mirroring the runtime's
|
|
20
|
+
// resolveAppPath (isAbsolutePath is the shared SoT in artifact.ts). Without this an absolute
|
|
21
|
+
// manifest path (e.g. "/abs/openapi.json") would be stripped to root-relative and gen would
|
|
22
|
+
// read/derive a different file than the runtime resolves. Trailing edge separators are still
|
|
23
|
+
// trimmed for stable, comparable artifact keys.
|
|
24
|
+
if (isAbsolutePath(rel))
|
|
25
|
+
return norm(rel).replace(/\/+$/, "");
|
|
26
|
+
return `${norm(root).replace(/\/+$/, "")}/${norm(rel).replace(/^\/+/, "")}`;
|
|
16
27
|
}
|
|
17
28
|
function dirOf(p) {
|
|
18
29
|
const i = p.lastIndexOf("/");
|
|
@@ -81,6 +92,14 @@ async function collectAll(opts) {
|
|
|
81
92
|
artifacts.push(...deriveMessageBindings(models, declaredTypeIds));
|
|
82
93
|
}
|
|
83
94
|
}
|
|
95
|
+
// 4. OpenAPI `api` binding → typed endpoint contracts (ADR 0058). Fail-closed: a declared spec
|
|
96
|
+
// that is missing or malformed throws here so `urban gen`/`urban check` surfaces it. Trim the
|
|
97
|
+
// spec path so benign whitespace matches the runtime's readApiBinding (no gen/runtime drift).
|
|
98
|
+
const specRef = typeof manifest.api?.spec === "string" ? manifest.api.spec.trim() : "";
|
|
99
|
+
if (!opts.modelsOnly && specRef.length > 0) {
|
|
100
|
+
const specText = await io.readText(join(root, specRef));
|
|
101
|
+
artifacts.push(...deriveApi(parseSpec(specText)));
|
|
102
|
+
}
|
|
84
103
|
return { artifacts: sortArtifacts(artifacts), derived };
|
|
85
104
|
}
|
|
86
105
|
/** Run the derivers and write artifacts (or, with `check`, report drift without writing). */
|
package/dist/toolkit/index.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ export { deriveMeta, emitMeta, foldMeta, metaDeriver, scanModelMeta, META_TS, }
|
|
|
13
13
|
export type { MetaDecl } from "./derivers/meta.ts";
|
|
14
14
|
export { deriveMessageBindings, emitMessageBindings, emitMessageBindingsRuntime, messagesDeriver, scanModelMessages, MESSAGE_BINDINGS_DTS, MESSAGE_BINDINGS_TS, } from "./derivers/messages.ts";
|
|
15
15
|
export type { MessageIo, MessageBindingDecl } from "./derivers/messages.ts";
|
|
16
|
+
export { apiDeriver, deriveApi, emitApiBindings, emitApiBindingsRuntime, schemaToTs, API_BINDINGS_DTS, API_BINDINGS_TS, } from "./derivers/api.ts";
|
|
17
|
+
export { collectOperations, HTTP_METHODS, isSafeOperationId, operationsWithoutId, operationsWithUnsafeId, parseSpec, refName, resolveSchema, toRouteMatcher, undeclaredPathParams, validateValue, } from "../openapi/spec.ts";
|
|
18
|
+
export type { HttpMethodLower, OpenApiDoc, OpenApiParameter, OpenApiSchema, OperationInfo, } from "../openapi/spec.ts";
|
|
16
19
|
export declare const DERIVERS: readonly [import("./artifact.ts").Deriver<import("./derivers/migrations.ts").ToolkitManifest>, import("./artifact.ts").Deriver<import("./derivers/migrations.ts").ToolkitManifest>, import("./artifact.ts").Deriver<{
|
|
17
20
|
models: import("./derivers/worker-io.ts").ModelSource[];
|
|
18
21
|
declaredTypeIds: Iterable<string>;
|
|
@@ -21,6 +24,8 @@ export declare const DERIVERS: readonly [import("./artifact.ts").Deriver<import(
|
|
|
21
24
|
}>, import("./artifact.ts").Deriver<{
|
|
22
25
|
models: import("./derivers/worker-io.ts").ModelSource[];
|
|
23
26
|
declaredTypeIds: Iterable<string>;
|
|
27
|
+
}>, import("./artifact.ts").Deriver<{
|
|
28
|
+
doc: import("../openapi/spec.ts").OpenApiDoc;
|
|
24
29
|
}>];
|
|
25
30
|
export { collectArtifacts, runGen, joinPath, readModels, expandPattern, previewModels } from "./gen.ts";
|
|
26
31
|
export type { GenIO, GenOptions, GenResult } from "./gen.ts";
|