@metaobjectsdev/codegen-ts 1.0.3 → 1.0.4-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/catalog-gates.d.ts +52 -0
  2. package/dist/catalog-gates.d.ts.map +1 -0
  3. package/dist/catalog-gates.js +183 -0
  4. package/dist/catalog-gates.js.map +1 -0
  5. package/dist/constants.d.ts +24 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +26 -0
  8. package/dist/constants.js.map +1 -1
  9. package/dist/generator-registry.d.ts +73 -0
  10. package/dist/generator-registry.d.ts.map +1 -1
  11. package/dist/generator-registry.js +174 -40
  12. package/dist/generator-registry.js.map +1 -1
  13. package/dist/generator.d.ts +17 -0
  14. package/dist/generator.d.ts.map +1 -1
  15. package/dist/generator.js.map +1 -1
  16. package/dist/generators/trace-helper-file.d.ts.map +1 -1
  17. package/dist/generators/trace-helper-file.js +54 -11
  18. package/dist/generators/trace-helper-file.js.map +1 -1
  19. package/dist/index.d.ts +5 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +6 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/metaobjects-config.d.ts +0 -15
  24. package/dist/metaobjects-config.d.ts.map +1 -1
  25. package/dist/metaobjects-config.js.map +1 -1
  26. package/dist/projection/extract-view-spec.d.ts.map +1 -1
  27. package/dist/projection/extract-view-spec.js +111 -4
  28. package/dist/projection/extract-view-spec.js.map +1 -1
  29. package/dist/relation-resolver.d.ts.map +1 -1
  30. package/dist/relation-resolver.js +11 -9
  31. package/dist/relation-resolver.js.map +1 -1
  32. package/dist/routes-expose.d.ts +30 -0
  33. package/dist/routes-expose.d.ts.map +1 -1
  34. package/dist/routes-expose.js +46 -0
  35. package/dist/routes-expose.js.map +1 -1
  36. package/dist/runner.d.ts +20 -0
  37. package/dist/runner.d.ts.map +1 -1
  38. package/dist/runner.js +23 -0
  39. package/dist/runner.js.map +1 -1
  40. package/dist/templates/entity-file.js +2 -2
  41. package/dist/templates/entity-file.js.map +1 -1
  42. package/dist/templates/queries-file.js +5 -5
  43. package/dist/templates/queries-file.js.map +1 -1
  44. package/dist/templates/routes-file-hono.d.ts.map +1 -1
  45. package/dist/templates/routes-file-hono.js +10 -3
  46. package/dist/templates/routes-file-hono.js.map +1 -1
  47. package/dist/templates/routes-file.d.ts.map +1 -1
  48. package/dist/templates/routes-file.js +21 -4
  49. package/dist/templates/routes-file.js.map +1 -1
  50. package/dist/templates/value-object-file.js +2 -2
  51. package/dist/templates/value-object-file.js.map +1 -1
  52. package/package.json +6 -6
  53. package/src/catalog-gates.ts +222 -0
  54. package/src/constants.ts +27 -0
  55. package/src/generator-registry.ts +245 -44
  56. package/src/generator.ts +17 -0
  57. package/src/generators/trace-helper-file.ts +55 -12
  58. package/src/index.ts +17 -3
  59. package/src/metaobjects-config.ts +0 -15
  60. package/src/projection/extract-view-spec.ts +126 -5
  61. package/src/reference/entity.ts +2 -2
  62. package/src/reference/queries.ts +2 -2
  63. package/src/relation-resolver.ts +14 -8
  64. package/src/routes-expose.ts +57 -0
  65. package/src/runner.ts +49 -0
  66. package/src/templates/entity-file.ts +2 -2
  67. package/src/templates/queries-file.ts +5 -5
  68. package/src/templates/routes-file-hono.ts +10 -3
  69. package/src/templates/routes-file.ts +21 -4
  70. package/src/templates/value-object-file.ts +2 -2
@@ -10,6 +10,13 @@
10
10
  // factory-array config keeps working unchanged — the registry powers `--list`
11
11
  // and a stable identity, it does not replace the config path.
12
12
  //
13
+ // **This is a SLICE, not the whole catalog.** `codegen-ts` cannot import
14
+ // `codegen-ts-react` / `codegen-ts-tanstack` (dependency direction), so those two
15
+ // packages export their own slices and the CLI unions all three
16
+ // (`cli/src/lib/catalog.ts`). Set equality against the cross-port manifest is a
17
+ // property of the COMPOSED catalog and is asserted there; this package's own
18
+ // conformance test can only check one direction — no rogue names.
19
+ //
13
20
  // Tiering (ADR-0020 / ADR-0021 D1):
14
21
  // - "native" — the recommended Tier-1 `meta gen` suite (idiomatic emission).
15
22
  // - "neutral" — Tier-2 artifacts owned by the neutral docs engine. `docs` and
@@ -18,6 +25,8 @@
18
25
  // door for documentation is `meta docs` (D1).
19
26
 
20
27
  import type { Generator } from "./generator.js";
28
+ import type { MetaobjectsGenConfig } from "./metaobjects-config.js";
29
+ import { REFERENCE_GENERATOR_NAMES } from "./reference-templates.js";
21
30
  // The four ADR-0034 ownable generators are no longer exported from ./generators/index.js
22
31
  // (1.0 removed them — see that file's header). They remain the engine's internal composers
23
32
  // and the stable-name registry still constructs them, so import them from their own modules.
@@ -40,13 +49,55 @@ import {
40
49
  templateGenerator,
41
50
  traceHelperFile,
42
51
  sharedModelFile,
52
+ requirementTests,
43
53
  } from "./generators/index.js";
44
54
 
45
55
  export type GeneratorTier = "native" | "neutral";
46
56
 
57
+ /**
58
+ * The six layers a generator can belong to — the axis an adopter SELECTS BY.
59
+ *
60
+ * Cross-port: `layer` is gated by every port's registry-conformance test against
61
+ * `fixtures/generator-registry-conformance/registry.json`, exactly as `tier` is, so a
62
+ * polyglot agent groups the catalog by the same words everywhere.
63
+ *
64
+ * Six, not ten. The first four are app-shape decisions a builder makes; `docs` is on by
65
+ * default (`meta docs`); `capability` holds the ones the MODEL has already made — nobody
66
+ * picks `prompt-render` by browsing a taxonomy, they pick it because they declared a
67
+ * `template.prompt`, which `meta gen --list --probe` reports with a real file count. A
68
+ * layer with one member does no grouping work, so do not split `capability` to tidy it.
69
+ */
70
+ export const GENERATOR_LAYERS = [
71
+ "model",
72
+ "persistence",
73
+ "api",
74
+ "client",
75
+ "docs",
76
+ "capability",
77
+ ] as const;
78
+ export type Layer = (typeof GENERATOR_LAYERS)[number];
79
+
80
+ /**
81
+ * The framework a generator's OUTPUT targets. Absent = framework-neutral.
82
+ *
83
+ * Exclusivity is an ADVISORY and only on the `api` layer (spec §8a): `routes` and
84
+ * `routes-hono` emit to different paths, so wiring both is legal and silently produces
85
+ * two HTTP surfaces — worth a warning, never an error, because migrating between them is
86
+ * legitimate. There is NO general per-layer rule: `@metaobjectsdev/tanstack` peers on
87
+ * `react`, so `form` (react) + `hooks`/`grid` (tanstack) is the intended composition.
88
+ */
89
+ export type GeneratorFramework = "fastify" | "hono" | "react" | "tanstack";
90
+
47
91
  export interface GeneratorRegistryEntry {
48
92
  /** Stable, cross-port-consistent id. Equals the registry map key. */
49
93
  name: string;
94
+ /**
95
+ * Catalog discriminator. Today every entry is a generator; FR-043 adds
96
+ * `kind: "library"` rows to the same table rather than building a parallel one.
97
+ */
98
+ kind: "generator";
99
+ /** The selection axis — see {@link GENERATOR_LAYERS}. Gated cross-port. */
100
+ layer: Layer;
50
101
  /** One-line (no newline) human description for `--list`. */
51
102
  description: string;
52
103
  /** "native" = recommended `meta gen` suite; "neutral" = `meta docs`-owned. */
@@ -55,10 +106,55 @@ export interface GeneratorRegistryEntry {
55
106
  factory: () => Generator;
56
107
  /** Optional one-line options summary for `--list`. */
57
108
  options?: string;
109
+ /** The framework this generator's OUTPUT targets. Absent = framework-neutral. */
110
+ framework?: GeneratorFramework;
111
+ /**
112
+ * Stable names whose emitted output THIS generator's output imports.
113
+ *
114
+ * Resolved, not trusted: a gate dry-runs every generator, resolves each emitted
115
+ * RELATIVE import back to whichever generator emits that path, and asserts the result
116
+ * is a subset of this list — so a generator that quietly starts depending on `entity`
117
+ * cannot ship claiming it depends on nothing.
118
+ */
119
+ requires?: readonly string[];
120
+ /**
121
+ * The `@metaobjectsdev` runtime packages the EMITTED code imports.
122
+ *
123
+ * PLURAL, against the design's first sketch, because the emitted code is: an
124
+ * `output-parser` module imports `@metaobjectsdev/metadata`, `/render` AND
125
+ * `/runtime-ts`, and a `hooks` module imports `/runtime-web` and `/tanstack`. A
126
+ * singular field would have forced two of the three to go undeclared, which is the
127
+ * exact under-declaration the gate exists to catch. Resolved, not trusted — the
128
+ * emitted files are read back and every `@metaobjectsdev` import must appear here.
129
+ */
130
+ runtimePackages?: readonly string[];
131
+ /**
132
+ * Third-party packages the EMITTED code imports.
133
+ *
134
+ * Per generator rather than derived from {@link runtimePackages}, because
135
+ * `@metaobjectsdev/runtime-ts`'s peers are a union (drizzle-orm, fastify, hono, kysely,
136
+ * zod) — deriving per package would tell someone ejecting `entity` to install both
137
+ * Fastify and Hono. Version RANGES are read from the runtime package's own
138
+ * `peerDependencies`, never written here. Gated the same way `requires` is.
139
+ */
140
+ runtimePeers?: readonly string[];
141
+ /**
142
+ * Config keys this generator reads. Typed against the real config so a renamed key
143
+ * breaks the build instead of going quietly stale — the same "resolved, not trusted"
144
+ * doctrine as `requires`, obtained here for free from the type system.
145
+ */
146
+ configKeys?: readonly (keyof MetaobjectsGenConfig)[];
147
+ /** True iff this package ships a `src/reference/<name>.ts` for `meta eject`. */
148
+ ejectable: boolean;
58
149
  /** Optional note — used to point neutral entries at their canonical door. */
59
150
  note?: string;
60
151
  }
61
152
 
153
+ /** True iff this package ships a copyable reference template for `name`. */
154
+ function ejectable(name: string): boolean {
155
+ return (REFERENCE_GENERATOR_NAMES as readonly string[]).includes(name);
156
+ }
157
+
62
158
  // The `template` generator is a PRIMITIVE: callers supply { name, walk,
63
159
  // template }. For registry identity + `--list` we expose a no-op default so the
64
160
  // factory constructs a valid Generator without throwing; real use passes opts
@@ -73,139 +169,244 @@ function templatePrimitive(): Generator {
73
169
  }
74
170
 
75
171
  export const generatorRegistry: Record<string, GeneratorRegistryEntry> = {
76
- // ----- Tier-1 native suite (idiomatic per-port emission) -----------------
172
+ // ----- model ------------------------------------------------------------
77
173
  entity: {
78
174
  name: "entity",
175
+ kind: "generator",
176
+ layer: "model",
79
177
  description: "Per-entity Drizzle table + typed model module (the entity module).",
80
178
  tier: "native",
81
179
  factory: () => entityFile(),
82
180
  options: "filter?, target?",
181
+ requires: [],
182
+ runtimePackages: ["@metaobjectsdev/runtime-ts"],
183
+ runtimePeers: ["drizzle-orm", "zod"],
184
+ configKeys: ["dialect", "extStyle", "outputLayout", "columnNamingStrategy",
185
+ "pluralizeCollections", "collectionNameOverrides", "providedEnumModule", "apiPrefix"],
186
+ ejectable: ejectable("entity"),
187
+ },
188
+ names: {
189
+ name: "names",
190
+ kind: "generator",
191
+ layer: "model",
192
+ description: "Per-entity physical database name constants (table/view, schema, columns).",
193
+ tier: "native",
194
+ factory: () => namesFile(),
195
+ options: "filter?, target?",
196
+ configKeys: ["columnNamingStrategy"],
197
+ ejectable: ejectable("names"),
83
198
  },
199
+ barrel: {
200
+ name: "barrel",
201
+ kind: "generator",
202
+ layer: "model",
203
+ description: "Single index.ts re-exporting every generated entity module.",
204
+ tier: "native",
205
+ factory: () => barrel(),
206
+ options: "target?",
207
+ requires: ["entity"],
208
+ configKeys: ["extStyle"],
209
+ ejectable: ejectable("barrel"),
210
+ },
211
+
212
+ // ----- persistence ------------------------------------------------------
84
213
  queries: {
85
214
  name: "queries",
215
+ kind: "generator",
216
+ layer: "persistence",
86
217
  description: "Per-entity typed query helpers (findById/create/...).",
87
218
  tier: "native",
88
219
  factory: () => queriesFile(),
89
220
  options: "filter?, target?",
221
+ requires: ["entity"],
222
+ runtimePeers: ["drizzle-orm"],
223
+ configKeys: ["dialect", "extStyle", "pluralizeCollections", "collectionNameOverrides"],
224
+ ejectable: ejectable("queries"),
90
225
  },
91
- callable: {
92
- name: "callable",
93
- description: "Per-entity callable/service surface wrapping the query helpers.",
94
- tier: "native",
95
- factory: () => callableFile(),
96
- options: "filter?, target?",
97
- },
226
+
227
+ // ----- api --------------------------------------------------------------
98
228
  routes: {
99
229
  name: "routes",
230
+ kind: "generator",
231
+ layer: "api",
100
232
  description: "Per-entity Fastify CRUD routes (drizzle-fastify mountCrudRoutes).",
101
233
  tier: "native",
102
234
  factory: () => routesFile(),
103
235
  options: "filter?, target?",
236
+ framework: "fastify",
237
+ requires: ["entity"],
238
+ runtimePackages: ["@metaobjectsdev/runtime-ts"],
239
+ runtimePeers: ["fastify"],
240
+ configKeys: ["dbImport", "apiPrefix", "dialect", "extStyle", "outputLayout", "columnNamingStrategy"],
241
+ ejectable: ejectable("routes"),
104
242
  },
105
243
  "routes-hono": {
106
244
  name: "routes-hono",
245
+ kind: "generator",
246
+ layer: "api",
107
247
  description: "Per-entity Hono CRUD routes (runtime-ts/hono mountCrudRoutes).",
108
248
  tier: "native",
109
249
  factory: () => routesFileHono(),
110
250
  options: "filter?, target?",
251
+ framework: "hono",
252
+ requires: ["entity"],
253
+ runtimePackages: ["@metaobjectsdev/runtime-ts"],
254
+ runtimePeers: ["hono"],
255
+ configKeys: ["dbImport", "apiPrefix", "dialect", "extStyle"],
256
+ ejectable: ejectable("routes-hono"),
111
257
  },
112
- barrel: {
113
- name: "barrel",
114
- description: "Single index.ts re-exporting every generated entity module.",
258
+
259
+ // ----- docs -------------------------------------------------------------
260
+ "api-docs": {
261
+ name: "api-docs",
262
+ kind: "generator",
263
+ layer: "docs",
264
+ description:
265
+ "Per-entity/template SDK API reference (the generated code's API, human + agent forms).",
115
266
  tier: "native",
116
- factory: () => barrel(),
117
- options: "target?",
267
+ factory: () => apiDocsFile(),
268
+ options: "filter?, target?",
269
+ ejectable: ejectable("api-docs"),
118
270
  },
119
- names: {
120
- name: "names",
121
- description: "Per-entity physical database name constants (table/view, schema, columns).",
271
+ docs: {
272
+ name: "docs",
273
+ kind: "generator",
274
+ layer: "docs",
275
+ description: "Neutral per-entity / per-template Markdown documentation pages.",
276
+ tier: "neutral",
277
+ factory: () => docsFile(),
278
+ ejectable: ejectable("docs"),
279
+ note: "neutral artifact — use `meta docs` (the single docs door, ADR-0021 D1); not part of the recommended `meta gen` native suite.",
280
+ },
281
+ "mermaid-er": {
282
+ name: "mermaid-er",
283
+ kind: "generator",
284
+ layer: "docs",
285
+ description: "Mermaid ER diagram of the entity/relationship model.",
286
+ tier: "neutral",
287
+ factory: () => mermaidErDiagram(),
288
+ ejectable: ejectable("mermaid-er"),
289
+ note: "neutral artifact owned by the docs engine (ADR-0020); surfaced via `meta docs`, not the recommended `meta gen` native suite.",
290
+ },
291
+
292
+ // ----- capability -------------------------------------------------------
293
+ // Not chosen by browsing this list — chosen because the MODEL already asks for
294
+ // them. `meta gen --list --probe` reports a real file count per entry.
295
+ callable: {
296
+ name: "callable",
297
+ kind: "generator",
298
+ layer: "capability",
299
+ description: "Per-entity callable/service surface wrapping the query helpers.",
122
300
  tier: "native",
123
- factory: () => namesFile(),
301
+ factory: () => callableFile(),
124
302
  options: "filter?, target?",
303
+ requires: ["entity"],
304
+ runtimePeers: ["drizzle-orm"],
305
+ configKeys: ["columnNamingStrategy"],
306
+ ejectable: ejectable("callable"),
125
307
  },
126
308
  "prompt-render": {
127
309
  name: "prompt-render",
310
+ kind: "generator",
311
+ layer: "capability",
128
312
  description: "Per-template prompt-render helper over the render engine.",
129
313
  tier: "native",
130
314
  factory: () => promptRender(),
131
315
  options: "filter?, target?",
316
+ runtimePackages: ["@metaobjectsdev/render"],
317
+ ejectable: ejectable("prompt-render"),
132
318
  },
133
319
  "output-parser": {
134
320
  name: "output-parser",
321
+ kind: "generator",
322
+ layer: "capability",
135
323
  description: "Per-template tolerant output parser (recover-on-receipt).",
136
324
  tier: "native",
137
325
  factory: () => outputParser(),
138
326
  options: "filter?, target?",
327
+ runtimePackages: ["@metaobjectsdev/metadata", "@metaobjectsdev/render", "@metaobjectsdev/runtime-ts"],
328
+ runtimePeers: ["zod"],
329
+ ejectable: ejectable("output-parser"),
139
330
  },
140
331
  extractor: {
141
332
  name: "extractor",
333
+ kind: "generator",
334
+ layer: "capability",
142
335
  description: "Per-template typed extract<Name> helper (strict payload extraction).",
143
336
  tier: "native",
144
337
  factory: () => extractor(),
145
338
  options: "filter?, target?",
339
+ requires: ["entity", "output-parser"],
340
+ runtimePackages: ["@metaobjectsdev/metadata", "@metaobjectsdev/render"],
341
+ ejectable: ejectable("extractor"),
146
342
  },
147
343
  "output-prompt": {
148
344
  name: "output-prompt",
345
+ kind: "generator",
346
+ layer: "capability",
149
347
  description: "Per-template output-format prompt fragment generator.",
150
348
  tier: "native",
151
349
  factory: () => outputPrompt(),
152
350
  options: "filter?, target?",
351
+ runtimePackages: ["@metaobjectsdev/render"],
352
+ ejectable: ejectable("output-prompt"),
153
353
  },
154
354
  "render-helper": {
155
355
  name: "render-helper",
356
+ kind: "generator",
357
+ layer: "capability",
156
358
  description: "Per-template.output render helper (document/email typed wrappers).",
157
359
  tier: "native",
158
360
  factory: () => renderHelper(),
159
361
  options: "filter?, target?",
362
+ runtimePackages: ["@metaobjectsdev/render"],
363
+ configKeys: ["extStyle"],
364
+ ejectable: ejectable("render-helper"),
160
365
  },
161
366
  template: {
162
367
  name: "template",
368
+ kind: "generator",
369
+ layer: "capability",
163
370
  description: "Generic Mustache template primitive (walk + template → files).",
164
371
  tier: "native",
165
372
  factory: () => templatePrimitive(),
166
373
  options: "name, walk, template, format?, filter?, provider?, target?",
374
+ ejectable: ejectable("template"),
167
375
  },
168
- "api-docs": {
169
- name: "api-docs",
170
- description:
171
- "Per-entity/template SDK API reference (the generated code's API, human + agent forms).",
172
- tier: "native",
173
- factory: () => apiDocsFile(),
174
- options: "filter?, target?",
175
- },
176
-
177
376
  "trace-helper": {
178
377
  name: "trace-helper",
179
- description: "Per-entity typed record<Entity>/call<Entity> trace helpers (extract + buildLlmCallRow + persist; LlmCallBase-derived entities only).",
378
+ kind: "generator",
379
+ layer: "capability",
380
+ description:
381
+ "Per-entity typed record<Entity>/call<Entity> trace helpers (extract + buildLlmCallRow + persist; LlmCallBase-derived entities only).",
180
382
  tier: "native",
181
383
  factory: () => traceHelperFile(),
182
384
  options: "outDir?, target?",
385
+ ejectable: ejectable("trace-helper"),
386
+ },
387
+ "requirement-tests": {
388
+ name: "requirement-tests",
389
+ kind: "generator",
390
+ layer: "capability",
391
+ description: "Per-requirement test stub, one per requirement.functional claim in the ledger.",
392
+ tier: "native",
393
+ factory: () => requirementTests(),
394
+ options: "filter?, target?",
395
+ ejectable: ejectable("requirement-tests"),
183
396
  },
184
397
  "shared-model": {
185
398
  name: "shared-model",
186
- description: "FR-023: a publisher's flattened shared-model artifact + manifest for a consumer's `meta deps sync`.",
399
+ kind: "generator",
400
+ layer: "capability",
401
+ description:
402
+ "FR-023: a publisher's flattened shared-model artifact + manifest for a consumer's `meta deps sync`.",
187
403
  tier: "native",
188
404
  // `name`/`include` are required at run time (an empty include matches
189
405
  // everything, so a placeholder here constructs without throwing — real use
190
406
  // always supplies both, same as `template`'s templatePrimitive() above).
191
407
  factory: () => sharedModelFile({ name: "shared-model", include: [] }),
192
408
  options: "name, include, exclude?, files?, version?, target?",
193
- },
194
-
195
- // ----- Tier-2 neutral (owned by the `meta docs` engine — D1 / ADR-0020) ---
196
- docs: {
197
- name: "docs",
198
- description: "Neutral per-entity / per-template Markdown documentation pages.",
199
- tier: "neutral",
200
- factory: () => docsFile(),
201
- note: "neutral artifact — use `meta docs` (the single docs door, ADR-0021 D1); not part of the recommended `meta gen` native suite.",
202
- },
203
- "mermaid-er": {
204
- name: "mermaid-er",
205
- description: "Mermaid ER diagram of the entity/relationship model.",
206
- tier: "neutral",
207
- factory: () => mermaidErDiagram(),
208
- note: "neutral artifact owned by the docs engine (ADR-0020); surfaced via `meta docs`, not the recommended `meta gen` native suite.",
409
+ ejectable: ejectable("shared-model"),
209
410
  },
210
411
  };
211
412
 
package/src/generator.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { MetaObject, MetaRoot, TypeRegistry } from "@metaobjectsdev/metadata";
2
+ import type { LibraryManifest } from "@metaobjectsdev/metadata/library";
2
3
  import type { RenderContext } from "./render-context.js";
3
4
  import type { ResolvedGenConfig } from "./metaobjects-config.js";
4
5
  import type { OrphanPolicy } from "./reconcile-orphans.js";
@@ -65,6 +66,22 @@ export interface GenContext {
65
66
  * own `files` option does).
66
67
  */
67
68
  sourceFiles?: readonly string[];
69
+ /**
70
+ * FR-043 §6 — the manifests of the libraries this project opted into, filled by the
71
+ * runner from `RunGenOpts.libraries`.
72
+ *
73
+ * What a generator reads it FOR is the `anchor`: the library node it keys on. That
74
+ * retires the hard-coded entity name a library-aware generator would otherwise carry
75
+ * (`trace-helper` compared `.name === "LlmCallBase"`, so ANY adopter entity of that
76
+ * name in ANY package triggered it) in favour of resolving the manifest's anchor to a
77
+ * node and comparing by node identity.
78
+ *
79
+ * Undefined means the caller never said — a programmatic `runGen()` rather than
80
+ * `meta gen`. A generator reading it should fall back to every SHIPPED manifest's
81
+ * anchors, which is still FQN-anchored and still not a bare name. An empty array is
82
+ * the opposite and is meaningful: the caller looked, and this project opted into none.
83
+ */
84
+ libraries?: readonly LibraryManifest[];
68
85
  warn: (msg: string) => void;
69
86
  }
70
87
 
@@ -28,9 +28,11 @@ import {
28
28
  TEMPLATE_ATTR_TEXT_REF,
29
29
  } from "@metaobjectsdev/metadata";
30
30
  import { responseFormatOf } from "../templates/find-inbound.js";
31
- import type { MetaObject } from "@metaobjectsdev/metadata";
31
+ import type { MetaData, MetaObject } from "@metaobjectsdev/metadata";
32
+ import { libraryManifests } from "@metaobjectsdev/metadata/library";
32
33
  import {
33
34
  type EmittedFile,
35
+ type GenContext,
34
36
  type Generator,
35
37
  type GeneratorFactory,
36
38
  perEntity,
@@ -39,8 +41,40 @@ import { generatePayloadInterfacesBatch } from "../payload-codegen.js";
39
41
  import { GENERATED_HEADER } from "../constants.js";
40
42
  import { tphDiscriminatorPin } from "../templates/zod-validators.js";
41
43
 
42
- /** Short name of the shipped abstract base every trace entity extends. */
43
- const LLM_CALL_BASE = "LlmCallBase";
44
+ /** This generator's stable name the key a library manifest declares its anchor under. */
45
+ const STABLE_NAME = "trace-helper";
46
+
47
+ /**
48
+ * The FQNs this generator keys on, from the LIBRARY MANIFESTS rather than a constant
49
+ * here (FR-043 §6).
50
+ *
51
+ * What it replaces: `const LLM_CALL_BASE = "LlmCallBase"`, compared against `.name`
52
+ * anywhere in the super chain — so any adopter entity called `LlmCallBase`, in any
53
+ * package, triggered the generator. An anchor is a fully-qualified node the library
54
+ * declares, and it is resolved to a node and compared by identity below.
55
+ *
56
+ * `ctx.libraries` undefined means the caller never said which libraries are selected
57
+ * (a programmatic `runGen()`); every shipped manifest's anchor is then a candidate,
58
+ * which is still FQN-anchored. An EMPTY array is the opposite and is honoured: the
59
+ * caller looked, this project opted into none, and the generator matches nothing.
60
+ */
61
+ function anchorFqns(ctx: GenContext): string[] {
62
+ const manifests = ctx.libraries ?? Object.values(libraryManifests());
63
+ const out: string[] = [];
64
+ for (const manifest of manifests) {
65
+ for (const g of manifest.generators ?? []) {
66
+ if (g.name === STABLE_NAME && g.anchor !== undefined) out.push(g.anchor);
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+
72
+ /** Resolve each anchor FQN to the node it names, skipping any the model does not hold. */
73
+ function anchorNodes(ctx: GenContext): MetaData[] {
74
+ const wanted = new Set(anchorFqns(ctx));
75
+ if (wanted.size === 0) return [];
76
+ return ctx.loadedRoot.children().filter((n) => wanted.has(n.resolutionKey()));
77
+ }
44
78
 
45
79
  export interface TraceHelperOpts {
46
80
  /** Output directory prefix relative to the target's outDir. Default: "" (root). */
@@ -49,12 +83,17 @@ export interface TraceHelperOpts {
49
83
  target?: string;
50
84
  }
51
85
 
52
- /** Walk the super chain looking for a node named LLM_CALL_BASE. */
53
- function extendsBase(obj: MetaObject): boolean {
54
- let cur = obj.superResolved;
55
- while (cur !== undefined) {
56
- if (cur.name === LLM_CALL_BASE) return true;
57
- cur = cur.superResolved;
86
+ /**
87
+ * Walk the super chain looking for one of the anchor NODES.
88
+ *
89
+ * Node identity, not `.name` and not even the FQN string: the anchors were resolved
90
+ * against this run's own loaded root, so an entity whose chain reaches one reaches
91
+ * exactly the node the library declared.
92
+ */
93
+ function extendsAnchor(obj: MetaObject, anchors: readonly MetaData[]): boolean {
94
+ if (anchors.length === 0) return false;
95
+ for (let cur = obj.superResolved; cur !== undefined; cur = cur.superResolved) {
96
+ if (anchors.includes(cur)) return true;
58
97
  }
59
98
  return false;
60
99
  }
@@ -66,12 +105,16 @@ function pascal(s: string): string {
66
105
 
67
106
  export const traceHelperFile = function traceHelperFile(opts?: TraceHelperOpts): Generator {
68
107
  const dirPrefix = opts?.outDir ? `${opts.outDir.replace(/\/$/, "")}/` : "";
108
+ // Resolved once per run, on first use: `perEntity` calls back per entity and the
109
+ // anchor set is a property of the run, not of the entity.
110
+ let anchors: MetaData[] | undefined;
69
111
  const generator: Generator = {
70
- name: "trace-helper",
112
+ name: STABLE_NAME,
71
113
  generate: perEntity((entity, ctx) => {
72
- // Only concrete entities derived from LlmCallBase.
114
+ anchors ??= anchorNodes(ctx);
115
+ // Only concrete entities derived from a library's declared anchor.
73
116
  if (entity.isAbstract) return [];
74
- if (!extendsBase(entity)) return [];
117
+ if (!extendsAnchor(entity, anchors)) return [];
75
118
 
76
119
  // Find the nested template.prompt.
77
120
  // ADR-0039: resolving — a concrete trace entity may inherit its
package/src/index.ts CHANGED
@@ -38,8 +38,22 @@ export {
38
38
  generatorRegistry,
39
39
  listGenerators,
40
40
  getGenerator,
41
+ GENERATOR_LAYERS,
41
42
  } from "./generator-registry.js";
42
- export type { GeneratorRegistryEntry, GeneratorTier } from "./generator-registry.js";
43
+ export type {
44
+ GeneratorRegistryEntry,
45
+ GeneratorTier,
46
+ GeneratorFramework,
47
+ Layer,
48
+ } from "./generator-registry.js";
49
+
50
+ // The post-selection audits `meta gen` runs over a wired suite, and the impl-name →
51
+ // stable-name resolver both of them go through. Public so the CLI can gate it.
52
+ export {
53
+ warnUnsatisfiedRequires,
54
+ warnMixedApiFrameworks,
55
+ stableNameIndex,
56
+ } from "./catalog-gates.js";
43
57
 
44
58
  export type { MetaobjectsGenConfig, NormalizedMetaobjectsGenConfig, ResolvedGenConfig, Dialect, ExtStyle, ColumnNamingStrategy, MetaDataTypeProvider, GeneratorSpec, DocsConfig, ResolvedDocsConfig, DocsSurface, ApiSurface, VerifyConfig } from "./metaobjects-config.js";
45
59
  export { defineConfig, normalizeConfig, resolveGenerators, resolveDocsConfig } from "./metaobjects-config.js";
@@ -82,7 +96,7 @@ export { decideAndWrite, GitMissingError, WRITE_STATUSES } from "./overwrite-pol
82
96
  export { contentHash, readGeneratedHash, listGeneratedPaths } from "./overwrite-policy.js";
83
97
 
84
98
  export { CodegenError } from "./errors.js";
85
- export { GENERATED_HEADER, NAMES_FILE_SUFFIX, DEFAULT_OUT_DIR, RETIRED_CODEGEN_ATTRS, type RetiredCodegenAttr } from "./constants.js";
99
+ export { GENERATED_HEADER, NAMES_FILE_SUFFIX, DEFAULT_OUT_DIR, RETIRED_CODEGEN_ATTRS, sidecarLine, type RetiredCodegenAttr } from "./constants.js";
86
100
  export { warnRetiredCodegenAttrs } from "./retired-codegen-attrs.js";
87
101
 
88
102
  export { formatTs } from "./format.js";
@@ -210,7 +224,7 @@ export { renderEntityConstants, resourcePath } from "./templates/entity-constant
210
224
  export { renderQueriesFile } from "./templates/queries-file.js";
211
225
  // #348 — which CRUD verbs a generated routes file mounts. Public because an OWNED
212
226
  // routes generator (ADR-0034) composes the same render call and needs the same option.
213
- export { CRUD_VERBS, TPH_POLYMORPHIC_VERBS, resolveExpose, intersectExpose, exposeLine } from "./routes-expose.js";
227
+ export { CRUD_VERBS, TPH_POLYMORPHIC_VERBS, resolveExpose, intersectExpose, exposeLine, authSeamJsDoc } from "./routes-expose.js";
214
228
  export type { CrudVerb, ExposeOption } from "./routes-expose.js";
215
229
  export { renderRoutesFile } from "./templates/routes-file.js";
216
230
  export { renderRoutesFileHono } from "./templates/routes-file-hono.js";
@@ -198,21 +198,6 @@ export interface MetaobjectsGenConfig extends Omit<ResolvedGenConfig, "dbImport"
198
198
  * loader. Composed AFTER the default core+forge bundle.
199
199
  */
200
200
  providers?: readonly MetaDataTypeProvider[];
201
- /**
202
- * MetaObjects-shipped library packages this project loads alongside its own metadata —
203
- * `["ai"]` makes `extends: "metaobjects::ai::LlmCallBase"` resolve.
204
- *
205
- * Sits beside `providers` because it answers the same shape of question: what does this
206
- * project's model need in scope beyond the files it declares. Opt-in, because a library
207
- * registers real top-level nodes and a project that never references one should not find
208
- * them in its model, its generated output or its docs.
209
- *
210
- * Threaded to `loadMemory` by every CLI command that loads metadata. Before it existed,
211
- * `librarySources` was reachable only from `MetaDataLoader.fromDirectory` — which the
212
- * CLI does not use — so a generator that consumes a library was registered FOR the CLI
213
- * while its input was unreachable THROUGH it (#333).
214
- */
215
- libraries?: readonly string[];
216
201
  }
217
202
 
218
203
  /** MetaobjectsGenConfig after applying defaults. All fields required.