@nanobpm/urban 0.24.1 → 0.26.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/modules/dataops.d.ts +28 -4
- package/dist/runtime/core/modules/dataops.js +123 -5
- package/dist/runtime/core/modules/pages.js +17 -2
- package/dist/runtime/core/runtime.js +19 -0
- package/dist/toolkit/derivers/domain.d.ts +18 -0
- package/dist/toolkit/derivers/domain.js +134 -0
- package/dist/toolkit/derivers/messages.d.ts +14 -0
- package/dist/toolkit/derivers/messages.js +37 -0
- package/dist/toolkit/derivers/shapes.d.ts +96 -0
- package/dist/toolkit/derivers/shapes.js +498 -0
- package/dist/toolkit/derivers/worker-io.d.ts +26 -0
- package/dist/toolkit/derivers/worker-io.js +101 -1
- package/dist/toolkit/index.d.ts +6 -4
- package/dist/toolkit/index.js +4 -3
- package/package.json +1 -1
- package/src/runtime/core/modules/dataops.test.ts +132 -9
- package/src/runtime/core/modules/dataops.ts +180 -11
- package/src/runtime/core/modules/pages.test.ts +10 -0
- package/src/runtime/core/modules/pages.ts +17 -2
- package/src/runtime/core/runtime.test.ts +78 -0
- package/src/runtime/core/runtime.ts +18 -0
- package/src/toolkit/derivers/bindings-parity.test.ts +66 -0
- package/src/toolkit/derivers/domain.ts +171 -0
- package/src/toolkit/derivers/messages.ts +38 -0
- package/src/toolkit/derivers/shapes.test.ts +171 -0
- package/src/toolkit/derivers/shapes.ts +602 -0
- package/src/toolkit/derivers/worker-io.ts +103 -1
- package/src/toolkit/index.ts +24 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { HostContext } from "../host.ts";
|
|
2
2
|
import type { AppManifest } from "../manifest.ts";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { type ShapeDecl } from "../../../toolkit/derivers/shapes.ts";
|
|
4
|
+
import { type WorkerBindingDecl } from "../../../toolkit/derivers/worker-io.ts";
|
|
5
|
+
import { type MessageBindingDecl } from "../../../toolkit/derivers/messages.ts";
|
|
6
|
+
import { type MetaDecl } from "../../../toolkit/derivers/meta.ts";
|
|
7
|
+
/** The DB-manager op protocol's known op set — every op (including `domaintypes`) is dispatched by
|
|
8
|
+
* `runDataOp`. Typing `op` as this union (instead of `string`) gives SDK/console callers type-safety
|
|
9
|
+
* and catches typos at compile time. */
|
|
7
10
|
export type DataOp = "sources" | "schema" | "query" | "exec" | "script" | "migrations" | "migrate" | "domaintypes";
|
|
8
11
|
export interface DataRequest {
|
|
9
12
|
op: DataOp;
|
|
@@ -11,6 +14,27 @@ export interface DataRequest {
|
|
|
11
14
|
sql?: string;
|
|
12
15
|
params?: unknown[];
|
|
13
16
|
statements?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* `domaintypes`: persist the derived artifacts (default true). A `write:false` request is the
|
|
19
|
+
* latency-sensitive composer preview — it introspects WITHOUT migrating the DB first and returns
|
|
20
|
+
* only the artifact *content* (never a DB side-effect per keystroke). `urban data` never writes
|
|
21
|
+
* files itself (the host is read-only); the caller persists the returned content when `write` is
|
|
22
|
+
* not false. This flag only gates the pre-introspection migration side-effect.
|
|
23
|
+
*/
|
|
24
|
+
write?: boolean;
|
|
25
|
+
/** `domaintypes`: the model-derived worker-IO map (`taskType → {in,out,headerKeys}`), scanned
|
|
26
|
+
* from the process models by the caller. Overlaid on the manifest `workers[]` (authoritative for
|
|
27
|
+
* the envelope). Absent → the manifest projection alone. */
|
|
28
|
+
derivedWorkers?: WorkerBindingDecl[];
|
|
29
|
+
/** `domaintypes`: the model-derived message-payload map (`messageName → {in,out}`), scanned from
|
|
30
|
+
* the models' `bpmn:message` envelopes. Authoritative (no manifest projection). */
|
|
31
|
+
derivedMessages?: MessageBindingDecl[];
|
|
32
|
+
/** `domaintypes`: the model-derived composed shapes (`nano:shape`). Each is resolved through the
|
|
33
|
+
* fuse into a flat domain type folded into the `types` registry. Absent → no shapes. */
|
|
34
|
+
derivedShapes?: ShapeDecl[];
|
|
35
|
+
/** `domaintypes`: the model-derived model-level metadata (`nano:meta`). Folded into `meta.ts` and
|
|
36
|
+
* the structured `domain.json`. Absent → no metadata. */
|
|
37
|
+
derivedMeta?: MetaDecl[];
|
|
14
38
|
}
|
|
15
39
|
/** One datasource the manifest declares, resolved against the environment (env templates in
|
|
16
40
|
* `driver`/`url` are already expanded by `loadManifest`). Mirrors the console's `ResolvedSource`
|
|
@@ -14,14 +14,25 @@
|
|
|
14
14
|
// op = "sources" | "schema" | "query" | "exec" | "script" | "migrations" | "migrate"
|
|
15
15
|
// | "domaintypes"
|
|
16
16
|
//
|
|
17
|
-
// `domaintypes` (live-schema reification + shape resolution)
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// `domaintypes` (live-schema reification + shape resolution) composes the domain reifier from the
|
|
18
|
+
// pure toolkit derivers (ported from the console's `domain_types.ts`, host dry-out #576); it never
|
|
19
|
+
// writes files (the host is read-only) — it returns every artifact's content and the caller
|
|
20
|
+
// persists it.
|
|
20
21
|
import { isRecord } from "../guards.js";
|
|
21
22
|
import { loadManifest } from "../manifest.js";
|
|
22
23
|
import { validateManifest } from "../validate.js";
|
|
23
24
|
import { makeGateway } from "./gateway.js";
|
|
24
25
|
import { applyMigrations, MIGRATIONS_TABLE, openSqliteSource, resolveAppPath } from "./datasource.js";
|
|
26
|
+
// The `domaintypes` op composes the domain reifier from the pure toolkit derivers (single source of
|
|
27
|
+
// truth for the IDE's codegen, host dry-out nano-bpm#576). Imported directly from the deriver files
|
|
28
|
+
// (not the `toolkit/` barrel, which pulls the Node fs IO) so core stays free of `node:*`/Deno; the
|
|
29
|
+
// derivers' only core dependency is the gateway's `TableMeta`/`ColumnMeta` *types*, which erase.
|
|
30
|
+
import { GENERATED_DIR } from "../../../toolkit/artifact.js";
|
|
31
|
+
import { DOMAIN_BINDINGS, emitDomainBindings, emitDomainModel, registryFromManifest, } from "../../../toolkit/derivers/domain.js";
|
|
32
|
+
import { DOMAIN_MODEL_JSON, emitDomainModelJson, resolveShapes, } from "../../../toolkit/derivers/shapes.js";
|
|
33
|
+
import { DOMAIN_DTS, emitWorkerBindings, emitWorkerBindingsRuntime, overlayDerivedWorkerIo, WORKER_BINDINGS_DTS, WORKER_BINDINGS_TS, } from "../../../toolkit/derivers/worker-io.js";
|
|
34
|
+
import { emitMessageBindings, emitMessageBindingsRuntime, MESSAGE_BINDINGS_DTS, MESSAGE_BINDINGS_TS, } from "../../../toolkit/derivers/messages.js";
|
|
35
|
+
import { emitMeta, META_TS } from "../../../toolkit/derivers/meta.js";
|
|
25
36
|
const DEFAULT_MIGRATIONS_DIR = "db/migrations";
|
|
26
37
|
/** JSON-safe a value: BigInt → number (when lossless) or string, recursing objects/arrays. SQLite
|
|
27
38
|
* reports `lastInsertRowid` (and INTEGER columns) as BigInt, which `JSON.stringify` cannot encode. */
|
|
@@ -222,8 +233,115 @@ export async function runDataOp(host, root, manifestPath, req) {
|
|
|
222
233
|
}
|
|
223
234
|
}
|
|
224
235
|
case "domaintypes": {
|
|
225
|
-
|
|
226
|
-
|
|
236
|
+
// ADR 0029 §4.1/§4.2/§6 + ADR 0040 §9/§10: reify the domain model from the *live* schema.
|
|
237
|
+
// Union every declared datasource (the table spine), fold in the manifest `types` registry and
|
|
238
|
+
// the model's composed `nano:shape`s (resolved through the fuse), and derive the worker/message
|
|
239
|
+
// typed accessors. Unlike the vendored data_cli, this op NEVER writes files (the host is
|
|
240
|
+
// read-only): it returns every artifact's content and the caller persists it. `write:false` is
|
|
241
|
+
// the latency-sensitive composer preview — it skips the pre-introspection migration so a
|
|
242
|
+
// debounced keystroke has no DB side-effect and pays only for `text` + `shapeDiagnostics`.
|
|
243
|
+
const { default: def, sources } = listSources(manifest);
|
|
244
|
+
const persist = req.write !== false;
|
|
245
|
+
// Introspect each sqlite source's live schema. When persisting, migrate first (the domain
|
|
246
|
+
// model is derived from `gw.schema()`, so a regen that raced ahead of `migrate` on a fresh DB
|
|
247
|
+
// would emit an empty `Domain`); the migration runs on the shared `_urban_migrations` ledger.
|
|
248
|
+
const migrated = {};
|
|
249
|
+
const schemas = [];
|
|
250
|
+
for (const s of sources) {
|
|
251
|
+
if (String(s.driver) !== "sqlite")
|
|
252
|
+
continue; // only sqlite is introspectable (see openGateway)
|
|
253
|
+
const { gw, db } = await openGateway(host, root, manifest, s.name);
|
|
254
|
+
try {
|
|
255
|
+
if (persist) {
|
|
256
|
+
const applied = await applyMigrations(host, db, root, s.migrations ?? DEFAULT_MIGRATIONS_DIR);
|
|
257
|
+
if (applied.length)
|
|
258
|
+
migrated[s.name] = applied;
|
|
259
|
+
}
|
|
260
|
+
schemas.push({ source: s.name, tables: await gw.schema() });
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
db.close();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const types = registryFromManifest(manifest);
|
|
267
|
+
// Snapshot the manifest `types` *before* folding the resolved shapes so `domain.json` can tag
|
|
268
|
+
// each entity with its true provenance (manifest vs model) — once folded they are
|
|
269
|
+
// indistinguishable in `types`.
|
|
270
|
+
const manifestTypesSnapshot = { ...types };
|
|
271
|
+
// Composed motion shapes (ADR 0040 §9/§10): resolve each `nano:shape` against the leaf fuse
|
|
272
|
+
// into a flat domain type and fold it into the registry *before* it feeds the domain model and
|
|
273
|
+
// the worker/message bindings, so a composed shape becomes a first-class `DomainTypes` entry.
|
|
274
|
+
// Broken shapes are omitted; their diagnostics are returned so the panel can surface them.
|
|
275
|
+
const shapeResolution = resolveShapes(req.derivedShapes ?? [], types, schemas);
|
|
276
|
+
const shapeDeclById = new Map((req.derivedShapes ?? []).map((s) => [s.id, s]));
|
|
277
|
+
const shapeEntities = [];
|
|
278
|
+
for (const [id, shapeDef] of Object.entries(shapeResolution.types)) {
|
|
279
|
+
types[id] = shapeDef;
|
|
280
|
+
shapeEntities.push({ decl: shapeDeclById.get(id) ?? { id, ops: [] }, def: shapeDef });
|
|
281
|
+
}
|
|
282
|
+
const declaredIds = Object.keys(types);
|
|
283
|
+
const text = emitDomainModel(schemas, def, types);
|
|
284
|
+
const bindings = emitDomainBindings(schemas, def);
|
|
285
|
+
const derivedMeta = req.derivedMeta ?? [];
|
|
286
|
+
const metaAccessor = emitMeta(derivedMeta);
|
|
287
|
+
// Worker-IO map (ADR 0033 §3): the model is authoritative for the envelope, so an injected
|
|
288
|
+
// `derivedWorkers` is overlaid on the manifest `workers[]` (which still carries non-IO
|
|
289
|
+
// bindings such as `llm`); absent, the manifest projection stands alone.
|
|
290
|
+
const manifestWorkers = (manifest.workers ?? []).map((w) => ({
|
|
291
|
+
taskType: w.taskType,
|
|
292
|
+
inputType: w.inputType,
|
|
293
|
+
outputType: w.outputType,
|
|
294
|
+
}));
|
|
295
|
+
const workers = req.derivedWorkers
|
|
296
|
+
? overlayDerivedWorkerIo(manifestWorkers, req.derivedWorkers)
|
|
297
|
+
: manifestWorkers;
|
|
298
|
+
const workerBindings = emitWorkerBindings(workers, declaredIds);
|
|
299
|
+
const workerRuntime = emitWorkerBindingsRuntime();
|
|
300
|
+
// Message-payload registry (ADR 0040 slice 2): no manifest projection, so the model-derived
|
|
301
|
+
// map is authoritative and emitted directly.
|
|
302
|
+
const messages = req.derivedMessages ?? [];
|
|
303
|
+
const messageBindings = emitMessageBindings(messages, declaredIds);
|
|
304
|
+
const messageRuntime = emitMessageBindingsRuntime();
|
|
305
|
+
// The structured Fused Domain Model (`domain.json`, ADR 0040 §1): the largest artifact, so it
|
|
306
|
+
// is built only when persisting (the preview consumes only `text` + `shapeDiagnostics`).
|
|
307
|
+
const meta = derivedMeta;
|
|
308
|
+
const domainModel = persist
|
|
309
|
+
? emitDomainModelJson({
|
|
310
|
+
sources: schemas,
|
|
311
|
+
default: def,
|
|
312
|
+
manifestTypes: manifestTypesSnapshot,
|
|
313
|
+
shapes: shapeEntities,
|
|
314
|
+
meta,
|
|
315
|
+
diagnostics: shapeResolution.diagnostics,
|
|
316
|
+
})
|
|
317
|
+
: null;
|
|
318
|
+
const rel = (name) => `${GENERATED_DIR}/${name}`;
|
|
319
|
+
const tables = schemas.reduce((n, s) => n + s.tables.length, 0);
|
|
320
|
+
// The returned `*Path` fields are the app-relative targets the caller writes to when
|
|
321
|
+
// persisting (null on a preview). `urban data` returns content only — persisting is the
|
|
322
|
+
// caller's job (host dry-out nano-bpm#576). The static runtime wrappers (`workers.ts`,
|
|
323
|
+
// `messages.ts`) are returned too so the caller can write them verbatim.
|
|
324
|
+
return {
|
|
325
|
+
path: persist ? rel(DOMAIN_DTS) : null,
|
|
326
|
+
text,
|
|
327
|
+
tables,
|
|
328
|
+
bindingsPath: persist ? rel(DOMAIN_BINDINGS) : null,
|
|
329
|
+
bindings,
|
|
330
|
+
workerBindingsPath: persist ? rel(WORKER_BINDINGS_DTS) : null,
|
|
331
|
+
workerBindings,
|
|
332
|
+
workerRuntimePath: persist ? rel(WORKER_BINDINGS_TS) : null,
|
|
333
|
+
workerRuntime,
|
|
334
|
+
messageBindingsPath: persist ? rel(MESSAGE_BINDINGS_DTS) : null,
|
|
335
|
+
messageBindings,
|
|
336
|
+
messageRuntimePath: persist ? rel(MESSAGE_BINDINGS_TS) : null,
|
|
337
|
+
messageRuntime,
|
|
338
|
+
metaPath: persist ? rel(META_TS) : null,
|
|
339
|
+
meta: metaAccessor,
|
|
340
|
+
domainModelPath: persist ? rel(DOMAIN_MODEL_JSON) : null,
|
|
341
|
+
domainModel,
|
|
342
|
+
migrated,
|
|
343
|
+
shapeDiagnostics: shapeResolution.diagnostics,
|
|
344
|
+
};
|
|
227
345
|
}
|
|
228
346
|
default:
|
|
229
347
|
throw new Error(`unknown op "${req.op}"`);
|
|
@@ -319,7 +319,7 @@ function rendererShell(homePage) {
|
|
|
319
319
|
</head>
|
|
320
320
|
<body>
|
|
321
321
|
<main id="page" data-home="${escapeAttr(homePage)}"><p class="pc-empty">Loading…</p></main>
|
|
322
|
-
<script type="module" src="
|
|
322
|
+
<script type="module" src="./app/runtime.js"></script>
|
|
323
323
|
</body>
|
|
324
324
|
</html>`;
|
|
325
325
|
}
|
|
@@ -373,8 +373,23 @@ const RENDERER_JS = String.raw `
|
|
|
373
373
|
const root = document.getElementById("page");
|
|
374
374
|
const HOME = root.dataset.home || "home";
|
|
375
375
|
|
|
376
|
+
// Resolve every app endpoint against where THIS module was actually served from,
|
|
377
|
+
// so absolute-looking "/app/…" paths work both at the origin root (direct run,
|
|
378
|
+
// e.g. the CLI on :3000) and under a path-prefixed reverse proxy (the Nano
|
|
379
|
+
// console embeds the app at /console/app-view/<name>/). "../" drops the trailing
|
|
380
|
+
// "app/runtime.js" to land on the app's mount root (always ends in "/").
|
|
381
|
+
const APP_BASE = new URL("../", import.meta.url);
|
|
382
|
+
function apiUrl(u) {
|
|
383
|
+
// Rebase root-absolute app paths ("/app/…") onto the mount root. Leave a
|
|
384
|
+
// protocol-relative ("//host/…") or otherwise non-root-relative string alone —
|
|
385
|
+
// only a single leading slash denotes an app path we own.
|
|
386
|
+
return typeof u === "string" && u.startsWith("/") && !u.startsWith("//")
|
|
387
|
+
? new URL(u.slice(1), APP_BASE).toString()
|
|
388
|
+
: u;
|
|
389
|
+
}
|
|
390
|
+
|
|
376
391
|
async function getJSON(url, opts) {
|
|
377
|
-
const r = await fetch(url, opts);
|
|
392
|
+
const r = await fetch(apiUrl(url), opts);
|
|
378
393
|
const body = await r.json().catch(() => ({}));
|
|
379
394
|
if (!r.ok) throw new Error(body.error || ("HTTP " + r.status));
|
|
380
395
|
return body;
|
|
@@ -131,9 +131,11 @@ export async function createUrbanApp(opts) {
|
|
|
131
131
|
describe.connectors = c.describe?.();
|
|
132
132
|
}
|
|
133
133
|
const routes = [];
|
|
134
|
+
let hasUiSurfaces = false;
|
|
134
135
|
if (flags.surfaces) {
|
|
135
136
|
const s = mountSurfaces(ctx, api);
|
|
136
137
|
routes.push(...s.routes);
|
|
138
|
+
hasUiSurfaces = s.routes.length > 0;
|
|
137
139
|
describe.surfaces = s.describe();
|
|
138
140
|
}
|
|
139
141
|
if (flags.triggers) {
|
|
@@ -160,6 +162,23 @@ export async function createUrbanApp(opts) {
|
|
|
160
162
|
server = await host.serveHttp(port, makeRouter(routes));
|
|
161
163
|
httpPort = server.port;
|
|
162
164
|
host.log("info", "urban app serving surfaces/triggers", { port: httpPort, routes: routes.length });
|
|
165
|
+
// ADR 0057 boot handshake: under a supervising host (the nano-bpm
|
|
166
|
+
// Studio sets NANOBPMN_APP_HANDSHAKE), announce the port we actually
|
|
167
|
+
// bound on a machine-readable stdout control line so the host can
|
|
168
|
+
// locate the app's UI even when the app picks its port at runtime.
|
|
169
|
+
// Only emitted when the app actually serves UI surfaces (a trigger- or
|
|
170
|
+
// worker-only app binds a port too, but it has no webview for the host
|
|
171
|
+
// to frame), and gated on the env var so direct terminal runs aren't
|
|
172
|
+
// cluttered. `=== "1"` so an app that sets the var to "0" opts out.
|
|
173
|
+
if (httpPort !== undefined && hasUiSurfaces && host.env("NANOBPMN_APP_HANDSHAKE") === "1") {
|
|
174
|
+
try {
|
|
175
|
+
console.log(`@@NBPM_LISTENING@@${JSON.stringify({ port: httpPort })}`);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// stdout may be closed / non-writable — the handshake is
|
|
179
|
+
// best-effort telemetry, never fatal to the app.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
163
182
|
}
|
|
164
183
|
host.log("info", `urban app "${manifest.id}" started`, {});
|
|
165
184
|
}
|
|
@@ -44,6 +44,9 @@ export interface DomainTypeDef {
|
|
|
44
44
|
}
|
|
45
45
|
/** The manifest `types` registry: type id → declared record type. */
|
|
46
46
|
export type DomainTypeRegistry = Record<string, DomainTypeDef>;
|
|
47
|
+
/** Whether a manifest field keyword resolves to a primitive (vs a nominal ref). Shared with the
|
|
48
|
+
* shape fuse (`shapes.ts`) so the primitive-keyword set has one definition (no drift). */
|
|
49
|
+
export declare function isPrimitiveKeyword(type: string): boolean;
|
|
47
50
|
/**
|
|
48
51
|
* Emit the `DomainTypes` block for the manifest `types` registry: an inline map keyed by type id.
|
|
49
52
|
* Returns `""` when the registry is empty, so the table spine stays byte-identical when no types
|
|
@@ -55,6 +58,21 @@ export declare function emitDomainTypeRegistry(types: DomainTypeRegistry): strin
|
|
|
55
58
|
* followed by the manifest `types` registry (§4.2).
|
|
56
59
|
*/
|
|
57
60
|
export declare function emitDomainModel(sources: SourceSchema[], defaultSource: string | undefined, types: DomainTypeRegistry): string;
|
|
61
|
+
/** The generated bindings file's basename — the typed `openDomain()` accessor materialised next to
|
|
62
|
+
* `domain-rows.d.ts` under a project's `nano-generated/`. */
|
|
63
|
+
export declare const DOMAIN_BINDINGS = "domain.ts";
|
|
64
|
+
/**
|
|
65
|
+
* Emit `domain.ts`: a typed data-object accessor over the App's default datasource, so a worker
|
|
66
|
+
* writes `db.orders.insert({...})` / `db.orders.get(id)` instead of hand-writing SQL (ADR 0029 §6).
|
|
67
|
+
* The generic `Table<T>` runtime lives in `data-sdk.ts`; this generated module only binds each
|
|
68
|
+
* table name to its row type (from `domain-rows.d.ts`) and primary key, so it imports nothing but
|
|
69
|
+
* the sibling SDK (relative) and a type-only `domain-rows.d.ts` — staying dual-runtime and erased at
|
|
70
|
+
* `deno compile`. When the App declares multiple datasources this emits a *keyed*
|
|
71
|
+
* `openDomain<K>(source?)` whose returned accessor is typed against that source's tables; `db.raw`
|
|
72
|
+
* is the per-source escape hatch. Single-source apps keep the zero-arg `openDomain()` concrete form
|
|
73
|
+
* (byte-stable).
|
|
74
|
+
*/
|
|
75
|
+
export declare function emitDomainBindings(sources: SourceSchema[], defaultSource?: string): string;
|
|
58
76
|
/** The datasource table spine derived from the manifest types, grouped by source (same grouping
|
|
59
77
|
* and order as the SQL migration). */
|
|
60
78
|
export declare function sourcesFromManifest(manifest: ToolkitManifest): SourceSchema[];
|
|
@@ -143,6 +143,11 @@ const PRIMITIVE_TS = {
|
|
|
143
143
|
datetime: "string",
|
|
144
144
|
json: "unknown",
|
|
145
145
|
};
|
|
146
|
+
/** Whether a manifest field keyword resolves to a primitive (vs a nominal ref). Shared with the
|
|
147
|
+
* shape fuse (`shapes.ts`) so the primitive-keyword set has one definition (no drift). */
|
|
148
|
+
export function isPrimitiveKeyword(type) {
|
|
149
|
+
return Object.prototype.hasOwnProperty.call(PRIMITIVE_TS, type);
|
|
150
|
+
}
|
|
146
151
|
/**
|
|
147
152
|
* The TS type for a registry field. A primitive keyword maps directly (and takes precedence over
|
|
148
153
|
* an identically named type, per the schema); anything else is a nominal reference to another
|
|
@@ -187,6 +192,135 @@ export function emitDomainModel(sources, defaultSource, types) {
|
|
|
187
192
|
const registry = emitDomainTypeRegistry(types);
|
|
188
193
|
return registry ? `${spine}\n${registry}` : spine;
|
|
189
194
|
}
|
|
195
|
+
// --- domain bindings: the typed data-object accessor (ADR 0029 §6) ---------------------------
|
|
196
|
+
/** The generated bindings file's basename — the typed `openDomain()` accessor materialised next to
|
|
197
|
+
* `domain-rows.d.ts` under a project's `nano-generated/`. */
|
|
198
|
+
export const DOMAIN_BINDINGS = "domain.ts";
|
|
199
|
+
/** Members `openDomain()` reserves on the returned handle — the `DomainBase` escape hatch
|
|
200
|
+
* (`raw`/`close`). A table whose wire name is one of these would clobber the escape hatch (or the
|
|
201
|
+
* type intersection), so it is dropped from the typed table surface; it stays reachable via
|
|
202
|
+
* `db.raw.table("<name>", pk)`. */
|
|
203
|
+
const RESERVED_DOMAIN_MEMBERS = new Set(["raw", "close"]);
|
|
204
|
+
/** The primary-key column for a table's `Table` gateway: its first declared PK column, or `id`
|
|
205
|
+
* when the table declares none. */
|
|
206
|
+
function primaryKeyOf(t) {
|
|
207
|
+
return t.columns.find((c) => c.primaryKey)?.name ?? "id";
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Emit `domain.ts`: a typed data-object accessor over the App's default datasource, so a worker
|
|
211
|
+
* writes `db.orders.insert({...})` / `db.orders.get(id)` instead of hand-writing SQL (ADR 0029 §6).
|
|
212
|
+
* The generic `Table<T>` runtime lives in `data-sdk.ts`; this generated module only binds each
|
|
213
|
+
* table name to its row type (from `domain-rows.d.ts`) and primary key, so it imports nothing but
|
|
214
|
+
* the sibling SDK (relative) and a type-only `domain-rows.d.ts` — staying dual-runtime and erased at
|
|
215
|
+
* `deno compile`. When the App declares multiple datasources this emits a *keyed*
|
|
216
|
+
* `openDomain<K>(source?)` whose returned accessor is typed against that source's tables; `db.raw`
|
|
217
|
+
* is the per-source escape hatch. Single-source apps keep the zero-arg `openDomain()` concrete form
|
|
218
|
+
* (byte-stable).
|
|
219
|
+
*/
|
|
220
|
+
export function emitDomainBindings(sources, defaultSource) {
|
|
221
|
+
const header = "// AUTO-GENERATED by nanobpmn from the datasource schema (ADR 0029 §4.1/§6).\n" +
|
|
222
|
+
"// The typed data-object layer: `openDomain()` → one `Table<T>` per table, so\n" +
|
|
223
|
+
"// workers manipulate typed rows instead of hand-writing SQL. Do not edit —\n" +
|
|
224
|
+
"// regenerated from the live tables. Erased to plain JS at `deno compile`.\n" +
|
|
225
|
+
"// eslint-disable\n";
|
|
226
|
+
const multi = sources.length > 1;
|
|
227
|
+
const def = defaultSource && sources.some((s) => s.source === defaultSource)
|
|
228
|
+
? defaultSource
|
|
229
|
+
: sources[0]?.source;
|
|
230
|
+
const primary = sources.find((s) => s.source === def);
|
|
231
|
+
const tables = (primary?.tables ?? []).filter((t) => !RESERVED_DOMAIN_MEMBERS.has(t.name));
|
|
232
|
+
// Multi-source apps: emit a *keyed* accessor so `openDomain("analytics")` is typed against that
|
|
233
|
+
// source's tables, not the default's. The row-type spine (`DomainSources`, keyed by alias then
|
|
234
|
+
// wire table name) is already emitted in `domain-rows.d.ts`; here we bind a runtime
|
|
235
|
+
// table-descriptor map per source and a generic `openDomain<K>` that indexes it. Single-source
|
|
236
|
+
// apps keep the zero-arg concrete form below (byte-stable).
|
|
237
|
+
if (multi) {
|
|
238
|
+
return emitKeyedDomainBindings(header, sources, def);
|
|
239
|
+
}
|
|
240
|
+
// Interface names must match `domain-rows.d.ts` exactly (prefixed when multi-source).
|
|
241
|
+
const typeName = (table) => multi ? prefixedInterfaceName(primary.source, table) : interfaceName(table);
|
|
242
|
+
const propKey = (table) => (isIdent(table) ? table : JSON.stringify(table));
|
|
243
|
+
const typeImports = [...new Set(tables.map((t) => typeName(t.name)))].sort();
|
|
244
|
+
const importTypes = typeImports.length > 0
|
|
245
|
+
? `import type { ${typeImports.join(", ")} } from "./${DOMAIN_DTS}";\n`
|
|
246
|
+
: "";
|
|
247
|
+
const fields = tables
|
|
248
|
+
.map((t) => ` readonly ${propKey(t.name)}: Table<${typeName(t.name)}>;`)
|
|
249
|
+
.join("\n");
|
|
250
|
+
const binds = tables
|
|
251
|
+
.map((t) => ` ${propKey(t.name)}: raw.table<${typeName(t.name)}>(` +
|
|
252
|
+
`${JSON.stringify(t.name)}, ${JSON.stringify(primaryKeyOf(t))}),`)
|
|
253
|
+
.join("\n");
|
|
254
|
+
const domainIface = `export interface Domain {\n` +
|
|
255
|
+
` /** The underlying datasource handle — the raw-SQL escape hatch. */\n` +
|
|
256
|
+
` readonly raw: DataSource;\n` +
|
|
257
|
+
(fields ? `${fields}\n` : "") +
|
|
258
|
+
` /** Close the underlying connection. */\n close(): void;\n}`;
|
|
259
|
+
const openFn = `/**\n` +
|
|
260
|
+
` * Open the App's ${multi ? `default (\`${def}\`) ` : ""}datasource as a typed domain: each table is\n` +
|
|
261
|
+
` * a \`Table<T>\` gateway (\`db.orders.insert(...)\`, \`db.orders.get(id)\`), with the\n` +
|
|
262
|
+
` * raw \`DataSource\` at \`db.raw\` for anything the gateway doesn't cover.\n` +
|
|
263
|
+
` */\n` +
|
|
264
|
+
`export async function openDomain(source?: string): Promise<Domain> {\n` +
|
|
265
|
+
` const raw = await openDataSource(source);\n` +
|
|
266
|
+
` return {\n raw,\n` +
|
|
267
|
+
(binds ? `${binds}\n` : "") +
|
|
268
|
+
` close: () => raw.close(),\n };\n}`;
|
|
269
|
+
return `${header}\n` +
|
|
270
|
+
`import { openDataSource, type DataSource, type Table } from "./data-sdk.ts";\n` +
|
|
271
|
+
importTypes +
|
|
272
|
+
`\n${domainIface}\n\n${openFn}\n\nexport type { Table };\n`;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Emit the *keyed* `domain.ts` for a multi-datasource App. `openDomain(source)` is
|
|
276
|
+
* runtime-polymorphic (it opens whichever source you name); here
|
|
277
|
+
* `openDomain<K extends DomainSource>(source?: K)` selects the row-type map for the requested
|
|
278
|
+
* source from `DomainSources` (emitted in `domain-rows.d.ts`), and a runtime per-source
|
|
279
|
+
* table-descriptor map binds the matching `Table<T>` gateways. Zero-arg / default-source calls keep
|
|
280
|
+
* resolving to the default source.
|
|
281
|
+
*/
|
|
282
|
+
function emitKeyedDomainBindings(header, sources, def) {
|
|
283
|
+
const tableDescriptors = sources
|
|
284
|
+
.map((s) => {
|
|
285
|
+
const rows = s.tables
|
|
286
|
+
.filter((t) => !RESERVED_DOMAIN_MEMBERS.has(t.name))
|
|
287
|
+
.map((t) => `{ name: ${JSON.stringify(t.name)}, pk: ${JSON.stringify(primaryKeyOf(t))} }`)
|
|
288
|
+
.join(", ");
|
|
289
|
+
return ` ${JSON.stringify(s.source)}: [${rows}],`;
|
|
290
|
+
})
|
|
291
|
+
.join("\n");
|
|
292
|
+
const runtimeMap = `const DOMAIN_TABLES: Record<string, { name: string; pk: string }[]> = {\n` +
|
|
293
|
+
`${tableDescriptors}\n};\n` +
|
|
294
|
+
`const DEFAULT_SOURCE = ${JSON.stringify(def)};`;
|
|
295
|
+
const types = `/** The App's datasource aliases (ADR 0024). */\n` +
|
|
296
|
+
`export type DomainSource = keyof DomainSources;\n\n` +
|
|
297
|
+
`interface DomainBase {\n` +
|
|
298
|
+
` /** The underlying datasource handle — the raw-SQL escape hatch. */\n` +
|
|
299
|
+
` readonly raw: DataSource;\n` +
|
|
300
|
+
` /** Close the underlying connection. */\n close(): void;\n}\n\n` +
|
|
301
|
+
`type DomainTablesOf<M extends Record<string, object>> = { readonly [K in keyof M as K extends "raw" | "close" ? never : K]: Table<M[K]> };\n\n` +
|
|
302
|
+
`/** A datasource opened as a typed domain: one \`Table<T>\` per table, plus\n` +
|
|
303
|
+
` * \`raw\`/\`close\`. Defaults to the default source (\`${def}\`) when unparameterised. */\n` +
|
|
304
|
+
`export type Domain<M extends Record<string, object> = DomainSources[${JSON.stringify(def)}]> =\n` +
|
|
305
|
+
` DomainBase & DomainTablesOf<M>;`;
|
|
306
|
+
const openFn = `/**\n` +
|
|
307
|
+
` * Open one of the App's datasources as a typed domain. The \`source\` key\n` +
|
|
308
|
+
` * selects that source's tables (\`openDomain("analytics").customers\`); omit it\n` +
|
|
309
|
+
` * for the default source (\`${def}\`). \`db.raw\` is the raw-SQL escape hatch.\n` +
|
|
310
|
+
` */\n` +
|
|
311
|
+
`export async function openDomain<K extends DomainSource = ${JSON.stringify(def)}>(\n` +
|
|
312
|
+
` source?: K,\n` +
|
|
313
|
+
`): Promise<Domain<DomainSources[K]>> {\n` +
|
|
314
|
+
` const key = (source ?? DEFAULT_SOURCE) as string;\n` +
|
|
315
|
+
` const raw = await openDataSource(key);\n` +
|
|
316
|
+
` const db: Record<string, unknown> = { raw, close: () => raw.close() };\n` +
|
|
317
|
+
` for (const t of DOMAIN_TABLES[key] ?? []) db[t.name] = raw.table(t.name, t.pk);\n` +
|
|
318
|
+
` return db as Domain<DomainSources[K]>;\n}`;
|
|
319
|
+
return `${header}\n` +
|
|
320
|
+
`import { openDataSource, type DataSource, type Table } from "./data-sdk.ts";\n` +
|
|
321
|
+
`import type { DomainSources } from "./${DOMAIN_DTS}";\n\n` +
|
|
322
|
+
`${runtimeMap}\n\n${types}\n\n${openFn}\n\nexport type { Table };\n`;
|
|
323
|
+
}
|
|
190
324
|
// --- manifest bridge (code-first, ADR 0054) --------------------------------------------------
|
|
191
325
|
/** Project one manifest type's canonical schema into a `TableMeta` for the row-type spine. Reuses
|
|
192
326
|
* the *same* `tableSchemaForType` the SQL migration uses, so the row types can't drift from the DB. */
|
|
@@ -2,6 +2,9 @@ import type { DerivedArtifact, Deriver } from "../artifact.ts";
|
|
|
2
2
|
import { type ModelSource } from "./worker-io.ts";
|
|
3
3
|
/** Filename the console uses for the derived message-payload map; kept identical (drop-in). */
|
|
4
4
|
export declare const MESSAGE_BINDINGS_DTS = "message-io.d.ts";
|
|
5
|
+
/** The generated typed-`publishMessage` wrapper's basename — re-exports the worker SDK's
|
|
6
|
+
* `publishMessage` with a messageName-keyed typed signature. */
|
|
7
|
+
export declare const MESSAGE_BINDINGS_TS = "messages.ts";
|
|
5
8
|
/** One message-IO entry scanned from a model: a `bpmn:message` name plus the envelope
|
|
6
9
|
* type refs it carries. `inputType` is the received payload (what a `publishMessage`
|
|
7
10
|
* caller sends); `outputType` is reserved. Mirrors the Rust `MessageIo`. */
|
|
@@ -34,3 +37,14 @@ export declare const messagesDeriver: Deriver<{
|
|
|
34
37
|
models: ModelSource[];
|
|
35
38
|
declaredTypeIds: Iterable<string>;
|
|
36
39
|
}>;
|
|
40
|
+
/**
|
|
41
|
+
* The static typed-`publishMessage` wrapper (`messages.ts`). It re-exports the worker SDK's
|
|
42
|
+
* `publishMessage` (and its option/result types) and overrides `publishMessage` with a signature
|
|
43
|
+
* that keys off the `name` string literal: when a message is present in the generated
|
|
44
|
+
* `MessagePayloads` (ADR 0040 slice 2), the call's `variables` are typed from the declared domain
|
|
45
|
+
* type; otherwise they fall back to `MessageVars`. The body is a pass-through — the wire stays
|
|
46
|
+
* untyped JSON. This file never changes with the model, so it is written verbatim (unlike the
|
|
47
|
+
* regenerated `message-io.d.ts`). A byte-faithful port of the console's
|
|
48
|
+
* `emitMessageBindingsRuntime`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function emitMessageBindingsRuntime(): string;
|
|
@@ -9,6 +9,9 @@ import { GENERATED_DIR } from "../artifact.js";
|
|
|
9
9
|
import { byModelPath, DOMAIN_DTS, typeRefFor } from "./worker-io.js";
|
|
10
10
|
/** Filename the console uses for the derived message-payload map; kept identical (drop-in). */
|
|
11
11
|
export const MESSAGE_BINDINGS_DTS = "message-io.d.ts";
|
|
12
|
+
/** The generated typed-`publishMessage` wrapper's basename — re-exports the worker SDK's
|
|
13
|
+
* `publishMessage` with a messageName-keyed typed signature. */
|
|
14
|
+
export const MESSAGE_BINDINGS_TS = "messages.ts";
|
|
12
15
|
const ENVELOPE_IN = "io.nanobpm.dataEnvelope.in";
|
|
13
16
|
const ENVELOPE_OUT = "io.nanobpm.dataEnvelope.out";
|
|
14
17
|
function attr(tag, name) {
|
|
@@ -132,3 +135,37 @@ export const messagesDeriver = {
|
|
|
132
135
|
describe: "Derive message-io.d.ts (bpmn:message name + envelope payload) from BPMN models.",
|
|
133
136
|
derive: ({ models, declaredTypeIds }) => deriveMessageBindings(models, declaredTypeIds),
|
|
134
137
|
};
|
|
138
|
+
/**
|
|
139
|
+
* The static typed-`publishMessage` wrapper (`messages.ts`). It re-exports the worker SDK's
|
|
140
|
+
* `publishMessage` (and its option/result types) and overrides `publishMessage` with a signature
|
|
141
|
+
* that keys off the `name` string literal: when a message is present in the generated
|
|
142
|
+
* `MessagePayloads` (ADR 0040 slice 2), the call's `variables` are typed from the declared domain
|
|
143
|
+
* type; otherwise they fall back to `MessageVars`. The body is a pass-through — the wire stays
|
|
144
|
+
* untyped JSON. This file never changes with the model, so it is written verbatim (unlike the
|
|
145
|
+
* regenerated `message-io.d.ts`). A byte-faithful port of the console's
|
|
146
|
+
* `emitMessageBindingsRuntime`.
|
|
147
|
+
*/
|
|
148
|
+
export function emitMessageBindingsRuntime() {
|
|
149
|
+
return "// AUTO-GENERATED by nanobpmn (ADR 0040 slice 2): the typed `publishMessage`.\n" +
|
|
150
|
+
"// Re-exports the worker SDK's `publishMessage` and overrides it with a\n" +
|
|
151
|
+
"// messageName-keyed typed signature (variables typed from the message's declared\n" +
|
|
152
|
+
"// envelope). Erased to a pass-through at runtime. Do not edit.\n" +
|
|
153
|
+
"// eslint-disable\n\n" +
|
|
154
|
+
`import { publishMessage as publishMessageRaw } from "./worker-sdk.ts";\n` +
|
|
155
|
+
`import type { PublishMessageOptions, PublishMessageResult } from "./worker-sdk.ts";\n` +
|
|
156
|
+
`import type { MessageName, MessagePayloads, MessageVars } from "./${MESSAGE_BINDINGS_DTS}";\n\n` +
|
|
157
|
+
`export type { PublishMessageOptions, PublishMessageResult } from "./worker-sdk.ts";\n\n` +
|
|
158
|
+
`type PayloadFor<K extends MessageName> = K extends keyof MessagePayloads ? MessagePayloads[K] : MessageVars;\n\n` +
|
|
159
|
+
`/**\n` +
|
|
160
|
+
` * Typed \`publishMessage\`: \`name\` is constrained to the model's declared message\n` +
|
|
161
|
+
` * names (\`MessageName\`, ADR 0040 slice 2) so it autocompletes and rejects unknown\n` +
|
|
162
|
+
` * messages, and \`variables\` are typed from the message's declared envelope. A\n` +
|
|
163
|
+
` * declared message with no domain type falls back to MessageVars.\n` +
|
|
164
|
+
` */\n` +
|
|
165
|
+
`export function publishMessage<K extends MessageName>(\n` +
|
|
166
|
+
` name: K,\n` +
|
|
167
|
+
` opts?: PublishMessageOptions<PayloadFor<K> & object>,\n` +
|
|
168
|
+
`): Promise<PublishMessageResult> {\n` +
|
|
169
|
+
` return publishMessageRaw(name, opts as unknown as PublishMessageOptions);\n` +
|
|
170
|
+
`}\n`;
|
|
171
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ColumnMeta } from "../../runtime/core/modules/gateway.ts";
|
|
2
|
+
import { type DomainFieldDef, type DomainTypeDef, type DomainTypeRegistry, type SourceSchema } from "./domain.ts";
|
|
3
|
+
/** One composition operation of a `nano:shape`, in author (XML) order. */
|
|
4
|
+
export type ShapeOp = {
|
|
5
|
+
op: "carry";
|
|
6
|
+
ref: string;
|
|
7
|
+
} | {
|
|
8
|
+
op: "project";
|
|
9
|
+
ref: string;
|
|
10
|
+
fields: string[];
|
|
11
|
+
via?: string;
|
|
12
|
+
} | {
|
|
13
|
+
op: "extend";
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
optional?: boolean;
|
|
17
|
+
list?: boolean;
|
|
18
|
+
} | {
|
|
19
|
+
op: "reference";
|
|
20
|
+
name: string;
|
|
21
|
+
ref: string;
|
|
22
|
+
spread?: boolean;
|
|
23
|
+
list?: boolean;
|
|
24
|
+
};
|
|
25
|
+
/** A composed motion-shape declaration lifted from the model (ADR 0040 §9). */
|
|
26
|
+
export interface ShapeDecl {
|
|
27
|
+
/** The fuse identity; also the `DomainTypes` key the resolved shape lands under. */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Optional human label (`nano:shape name`). */
|
|
30
|
+
name?: string;
|
|
31
|
+
/** The defining process id, for the `model:<processId>` provenance tag. */
|
|
32
|
+
process?: string;
|
|
33
|
+
/** The ordered composition operations. */
|
|
34
|
+
ops: ShapeOp[];
|
|
35
|
+
/** Model-level metadata (`nano:meta`), free-form key/value (ADR 0040 §5). */
|
|
36
|
+
meta?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
/** A scan/resolve-time problem with a shape, surfaced like the `workers[]` drift warning (never a
|
|
39
|
+
* silent merge). A shape with any `error` diagnostic is omitted from the fuse; a `warning` (e.g. a
|
|
40
|
+
* deliberate field shadow) still resolves. */
|
|
41
|
+
export interface ShapeDiagnostic {
|
|
42
|
+
/** The offending shape id. */
|
|
43
|
+
shape: string;
|
|
44
|
+
kind: "unresolved-reference" | "reference-cycle" | "field-conflict" | "unknown-field" | "duplicate-id" | "ambiguous-reference" | "nominal-table-ref" | "same-id-collision";
|
|
45
|
+
severity: "error" | "warning";
|
|
46
|
+
message: string;
|
|
47
|
+
}
|
|
48
|
+
/** The result of resolving the project's shapes against the leaf fuse. */
|
|
49
|
+
export interface ShapeResolution {
|
|
50
|
+
/** The resolved shapes as registry entries, keyed by shape id, ready to fold into the manifest
|
|
51
|
+
* `types` registry before `emitDomainModel`. */
|
|
52
|
+
types: DomainTypeRegistry;
|
|
53
|
+
diagnostics: ShapeDiagnostic[];
|
|
54
|
+
}
|
|
55
|
+
/** Map one datasource column to a domain field. A nullable column widens to `optional` (the
|
|
56
|
+
* composed shape is a motion snapshot: an absent value reads as undefined, not SQL NULL). The
|
|
57
|
+
* manifest keyword is inferred from the column's SQLite affinity so it flows through `fieldTsType`
|
|
58
|
+
* like any declared field; affinities with no keyword (BLOB/opaque) fall back to `json`. */
|
|
59
|
+
export declare function columnToField(col: ColumnMeta): DomainFieldDef;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the project's composed shapes into `DomainTypeDef`s and diagnostics (ADR 0040 §10).
|
|
62
|
+
* Leaves (DB tables, manifest types) fuse first; shapes resolve in dependency order so a shape can
|
|
63
|
+
* carry another shape regardless of declaration order. A shape in a reference cycle, or one that
|
|
64
|
+
* names an unresolvable id, is omitted (degrades to untyped) with an `error` diagnostic; a
|
|
65
|
+
* deliberate field shadow with a differing type resolves with a `field-conflict` warning.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveShapes(shapes: ShapeDecl[], types: DomainTypeRegistry, sources?: SourceSchema[]): ShapeResolution;
|
|
68
|
+
/** The basename of the generated structured fuse cache (ADR 0040 §1). */
|
|
69
|
+
export declare const DOMAIN_MODEL_JSON = "domain.json";
|
|
70
|
+
/** One model-level metadata entry (`nano:meta`) as `emitDomainModelJson` consumes it. Mirrors
|
|
71
|
+
* `meta.ts`'s `MetaDecl` (kept local so the fuse cache does not depend on the meta deriver). */
|
|
72
|
+
export interface FusedMetaDecl {
|
|
73
|
+
process?: string;
|
|
74
|
+
key: string;
|
|
75
|
+
value: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Emit `domain.json`: the structured Fused Domain Model (ADR 0040 §1). Assembles every fused
|
|
79
|
+
* entity — DB tables (`db:` provenance), manifest `types` (`manifest:`), and composed motion shapes
|
|
80
|
+
* (`model:<processId>`, or bare `model` for an unsaved editor shape with no process) — with its
|
|
81
|
+
* resolved fields, plus the model-level metadata and the shape diagnostics. An `inputsHash` —
|
|
82
|
+
* FNV-1a (hex) over the compact `JSON.stringify` of the model object with `inputsHash` omitted
|
|
83
|
+
* (property-insertion order, not the pretty-printed bytes) — tags the cache for staleness.
|
|
84
|
+
*/
|
|
85
|
+
export declare function emitDomainModelJson(input: {
|
|
86
|
+
sources: SourceSchema[];
|
|
87
|
+
default?: string;
|
|
88
|
+
manifestTypes: DomainTypeRegistry;
|
|
89
|
+
/** Resolved shapes paired with their declaration (for provenance/label). */
|
|
90
|
+
shapes: {
|
|
91
|
+
decl: ShapeDecl;
|
|
92
|
+
def: DomainTypeDef;
|
|
93
|
+
}[];
|
|
94
|
+
meta: FusedMetaDecl[];
|
|
95
|
+
diagnostics: ShapeDiagnostic[];
|
|
96
|
+
}): string;
|