@metaobjectsdev/codegen-ts 0.24.5 → 1.0.0-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 (237) hide show
  1. package/README.md +37 -12
  2. package/dist/api-surface.d.ts +29 -0
  3. package/dist/api-surface.d.ts.map +1 -1
  4. package/dist/api-surface.js +47 -0
  5. package/dist/api-surface.js.map +1 -1
  6. package/dist/column-mapper.d.ts +12 -0
  7. package/dist/column-mapper.d.ts.map +1 -1
  8. package/dist/column-mapper.js +1 -0
  9. package/dist/column-mapper.js.map +1 -1
  10. package/dist/constants.d.ts +24 -11
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +54 -19
  13. package/dist/constants.js.map +1 -1
  14. package/dist/db-emitting.d.ts +6 -0
  15. package/dist/db-emitting.d.ts.map +1 -0
  16. package/dist/db-emitting.js +26 -0
  17. package/dist/db-emitting.js.map +1 -0
  18. package/dist/generator-registry.d.ts.map +1 -1
  19. package/dist/generator-registry.js +14 -1
  20. package/dist/generator-registry.js.map +1 -1
  21. package/dist/generator.d.ts +5 -0
  22. package/dist/generator.d.ts.map +1 -1
  23. package/dist/generator.js.map +1 -1
  24. package/dist/generators/agent-docs-file.d.ts +19 -0
  25. package/dist/generators/agent-docs-file.d.ts.map +1 -0
  26. package/dist/generators/agent-docs-file.js +257 -0
  27. package/dist/generators/agent-docs-file.js.map +1 -0
  28. package/dist/generators/agent-requirements-page.d.ts +22 -0
  29. package/dist/generators/agent-requirements-page.d.ts.map +1 -0
  30. package/dist/generators/agent-requirements-page.js +175 -0
  31. package/dist/generators/agent-requirements-page.js.map +1 -0
  32. package/dist/generators/agent-schema-input.d.ts +69 -0
  33. package/dist/generators/agent-schema-input.d.ts.map +1 -0
  34. package/dist/generators/agent-schema-input.js +23 -0
  35. package/dist/generators/agent-schema-input.js.map +1 -0
  36. package/dist/generators/agent-schema-page.d.ts +21 -0
  37. package/dist/generators/agent-schema-page.d.ts.map +1 -0
  38. package/dist/generators/agent-schema-page.js +241 -0
  39. package/dist/generators/agent-schema-page.js.map +1 -0
  40. package/dist/generators/agent-ui-page.d.ts +22 -0
  41. package/dist/generators/agent-ui-page.d.ts.map +1 -0
  42. package/dist/generators/agent-ui-page.js +253 -0
  43. package/dist/generators/agent-ui-page.js.map +1 -0
  44. package/dist/generators/api-docs-file.d.ts.map +1 -1
  45. package/dist/generators/api-docs-file.js +13 -6
  46. package/dist/generators/api-docs-file.js.map +1 -1
  47. package/dist/generators/api-model.d.ts +6 -0
  48. package/dist/generators/api-model.d.ts.map +1 -1
  49. package/dist/generators/api-model.js +54 -35
  50. package/dist/generators/api-model.js.map +1 -1
  51. package/dist/generators/callable-file.js +1 -1
  52. package/dist/generators/callable-file.js.map +1 -1
  53. package/dist/generators/docs-data-builder.d.ts.map +1 -1
  54. package/dist/generators/docs-data-builder.js +47 -2
  55. package/dist/generators/docs-data-builder.js.map +1 -1
  56. package/dist/generators/docs-data.d.ts +6 -2
  57. package/dist/generators/docs-data.d.ts.map +1 -1
  58. package/dist/generators/index.d.ts +3 -8
  59. package/dist/generators/index.d.ts.map +1 -1
  60. package/dist/generators/index.js +19 -14
  61. package/dist/generators/index.js.map +1 -1
  62. package/dist/generators/names-file.d.ts +17 -0
  63. package/dist/generators/names-file.d.ts.map +1 -0
  64. package/dist/generators/names-file.js +95 -0
  65. package/dist/generators/names-file.js.map +1 -0
  66. package/dist/generators/requirements-markdown.d.ts +18 -1
  67. package/dist/generators/requirements-markdown.d.ts.map +1 -1
  68. package/dist/generators/requirements-markdown.js +11 -9
  69. package/dist/generators/requirements-markdown.js.map +1 -1
  70. package/dist/generators/routes-file-hono.d.ts +17 -2
  71. package/dist/generators/routes-file-hono.d.ts.map +1 -1
  72. package/dist/generators/routes-file-hono.js +9 -11
  73. package/dist/generators/routes-file-hono.js.map +1 -1
  74. package/dist/generators/routes-file.d.ts +17 -2
  75. package/dist/generators/routes-file.d.ts.map +1 -1
  76. package/dist/generators/routes-file.js +10 -8
  77. package/dist/generators/routes-file.js.map +1 -1
  78. package/dist/generators/template-generator.d.ts.map +1 -1
  79. package/dist/generators/template-generator.js +20 -6
  80. package/dist/generators/template-generator.js.map +1 -1
  81. package/dist/index.d.ts +20 -5
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +43 -4
  84. package/dist/index.js.map +1 -1
  85. package/dist/metaobjects-config.d.ts +38 -1
  86. package/dist/metaobjects-config.d.ts.map +1 -1
  87. package/dist/metaobjects-config.js +9 -1
  88. package/dist/metaobjects-config.js.map +1 -1
  89. package/dist/names.d.ts +275 -0
  90. package/dist/names.d.ts.map +1 -0
  91. package/dist/names.js +433 -0
  92. package/dist/names.js.map +1 -0
  93. package/dist/overwrite-policy.d.ts.map +1 -1
  94. package/dist/overwrite-policy.js +15 -3
  95. package/dist/overwrite-policy.js.map +1 -1
  96. package/dist/projection/extract-view-spec.d.ts +25 -1
  97. package/dist/projection/extract-view-spec.d.ts.map +1 -1
  98. package/dist/projection/extract-view-spec.js +79 -6
  99. package/dist/projection/extract-view-spec.js.map +1 -1
  100. package/dist/reference-templates.d.ts +1 -1
  101. package/dist/reference-templates.d.ts.map +1 -1
  102. package/dist/reference-templates.js +1 -1
  103. package/dist/reference-templates.js.map +1 -1
  104. package/dist/render-context.d.ts +12 -1
  105. package/dist/render-context.d.ts.map +1 -1
  106. package/dist/render-context.js +1 -0
  107. package/dist/render-context.js.map +1 -1
  108. package/dist/retired-codegen-attrs.d.ts +32 -0
  109. package/dist/retired-codegen-attrs.d.ts.map +1 -0
  110. package/dist/retired-codegen-attrs.js +47 -0
  111. package/dist/retired-codegen-attrs.js.map +1 -0
  112. package/dist/routes-expose.d.ts +63 -0
  113. package/dist/routes-expose.d.ts.map +1 -0
  114. package/dist/routes-expose.js +57 -0
  115. package/dist/routes-expose.js.map +1 -0
  116. package/dist/runner.d.ts +18 -0
  117. package/dist/runner.d.ts.map +1 -1
  118. package/dist/runner.js +109 -7
  119. package/dist/runner.js.map +1 -1
  120. package/dist/templates/callable-file.d.ts +7 -2
  121. package/dist/templates/callable-file.d.ts.map +1 -1
  122. package/dist/templates/callable-file.js +54 -9
  123. package/dist/templates/callable-file.js.map +1 -1
  124. package/dist/templates/drizzle-schema.d.ts.map +1 -1
  125. package/dist/templates/drizzle-schema.js +236 -65
  126. package/dist/templates/drizzle-schema.js.map +1 -1
  127. package/dist/templates/entity-constants.d.ts +15 -6
  128. package/dist/templates/entity-constants.d.ts.map +1 -1
  129. package/dist/templates/entity-constants.js +85 -161
  130. package/dist/templates/entity-constants.js.map +1 -1
  131. package/dist/templates/entity-file.d.ts.map +1 -1
  132. package/dist/templates/entity-file.js +38 -4
  133. package/dist/templates/entity-file.js.map +1 -1
  134. package/dist/templates/entity-meta-file.d.ts +15 -1
  135. package/dist/templates/entity-meta-file.d.ts.map +1 -1
  136. package/dist/templates/entity-meta-file.js +12 -2
  137. package/dist/templates/entity-meta-file.js.map +1 -1
  138. package/dist/templates/entity-ui-descriptor.d.ts +112 -0
  139. package/dist/templates/entity-ui-descriptor.d.ts.map +1 -0
  140. package/dist/templates/entity-ui-descriptor.js +238 -0
  141. package/dist/templates/entity-ui-descriptor.js.map +1 -0
  142. package/dist/templates/field-meta.d.ts +46 -9
  143. package/dist/templates/field-meta.d.ts.map +1 -1
  144. package/dist/templates/field-meta.js +95 -21
  145. package/dist/templates/field-meta.js.map +1 -1
  146. package/dist/templates/names-decl.d.ts +38 -0
  147. package/dist/templates/names-decl.d.ts.map +1 -0
  148. package/dist/templates/names-decl.js +104 -0
  149. package/dist/templates/names-decl.js.map +1 -0
  150. package/dist/templates/projection-decl.d.ts +21 -0
  151. package/dist/templates/projection-decl.d.ts.map +1 -1
  152. package/dist/templates/projection-decl.js +53 -29
  153. package/dist/templates/projection-decl.js.map +1 -1
  154. package/dist/templates/queries-file.d.ts.map +1 -1
  155. package/dist/templates/queries-file.js +17 -2
  156. package/dist/templates/queries-file.js.map +1 -1
  157. package/dist/templates/requirement-test.d.ts.map +1 -1
  158. package/dist/templates/requirement-test.js +13 -2
  159. package/dist/templates/requirement-test.js.map +1 -1
  160. package/dist/templates/routes-file-hono.d.ts +2 -1
  161. package/dist/templates/routes-file-hono.d.ts.map +1 -1
  162. package/dist/templates/routes-file-hono.js +11 -2
  163. package/dist/templates/routes-file-hono.js.map +1 -1
  164. package/dist/templates/routes-file.d.ts +2 -1
  165. package/dist/templates/routes-file.d.ts.map +1 -1
  166. package/dist/templates/routes-file.js +54 -21
  167. package/dist/templates/routes-file.js.map +1 -1
  168. package/dist/templates/value-object-file.d.ts.map +1 -1
  169. package/dist/templates/value-object-file.js +15 -1
  170. package/dist/templates/value-object-file.js.map +1 -1
  171. package/dist/templates/view-decl.d.ts +37 -1
  172. package/dist/templates/view-decl.d.ts.map +1 -1
  173. package/dist/templates/view-decl.js +27 -3
  174. package/dist/templates/view-decl.js.map +1 -1
  175. package/dist/templates/zod-validators.d.ts +21 -0
  176. package/dist/templates/zod-validators.d.ts.map +1 -1
  177. package/dist/templates/zod-validators.js +40 -5
  178. package/dist/templates/zod-validators.js.map +1 -1
  179. package/dist/view-context.d.ts +24 -0
  180. package/dist/view-context.d.ts.map +1 -0
  181. package/dist/view-context.js +81 -0
  182. package/dist/view-context.js.map +1 -0
  183. package/package.json +6 -6
  184. package/src/api-surface.ts +53 -0
  185. package/src/column-mapper.ts +13 -0
  186. package/src/constants.ts +91 -16
  187. package/src/db-emitting.ts +31 -0
  188. package/src/generator-registry.ts +14 -4
  189. package/src/generator.ts +5 -0
  190. package/src/generators/agent-docs-file.ts +297 -0
  191. package/src/generators/agent-requirements-page.ts +199 -0
  192. package/src/generators/agent-schema-input.ts +88 -0
  193. package/src/generators/agent-schema-page.ts +297 -0
  194. package/src/generators/agent-ui-page.ts +290 -0
  195. package/src/generators/api-docs-file.ts +13 -6
  196. package/src/generators/api-model.ts +63 -34
  197. package/src/generators/callable-file.ts +1 -1
  198. package/src/generators/docs-data-builder.ts +46 -2
  199. package/src/generators/docs-data.ts +6 -2
  200. package/src/generators/index.ts +25 -14
  201. package/src/generators/names-file.ts +98 -0
  202. package/src/generators/requirements-markdown.ts +36 -2
  203. package/src/generators/routes-file-hono.ts +19 -10
  204. package/src/generators/routes-file.ts +20 -7
  205. package/src/generators/template-generator.ts +20 -6
  206. package/src/index.ts +64 -4
  207. package/src/metaobjects-config.ts +48 -2
  208. package/src/names.ts +629 -0
  209. package/src/overwrite-policy.ts +15 -3
  210. package/src/projection/extract-view-spec.ts +85 -6
  211. package/src/reference/entity.ts +21 -2
  212. package/src/reference/names.ts +125 -0
  213. package/src/reference/queries.ts +19 -3
  214. package/src/reference/routes-hono.ts +19 -9
  215. package/src/reference/routes.ts +25 -9
  216. package/src/reference-templates.ts +1 -1
  217. package/src/render-context.ts +13 -1
  218. package/src/retired-codegen-attrs.ts +52 -0
  219. package/src/routes-expose.ts +86 -0
  220. package/src/runner.ts +122 -11
  221. package/src/templates/callable-file.ts +60 -8
  222. package/src/templates/drizzle-schema.ts +248 -60
  223. package/src/templates/entity-constants.ts +95 -184
  224. package/src/templates/entity-file.ts +46 -4
  225. package/src/templates/entity-meta-file.ts +16 -3
  226. package/src/templates/entity-ui-descriptor.ts +305 -0
  227. package/src/templates/field-meta.ts +100 -19
  228. package/src/templates/names-decl.ts +165 -0
  229. package/src/templates/projection-decl.ts +72 -31
  230. package/src/templates/queries-file.ts +17 -2
  231. package/src/templates/requirement-test.ts +13 -2
  232. package/src/templates/routes-file-hono.ts +14 -2
  233. package/src/templates/routes-file.ts +69 -24
  234. package/src/templates/value-object-file.ts +15 -1
  235. package/src/templates/view-decl.ts +53 -3
  236. package/src/templates/zod-validators.ts +41 -5
  237. package/src/view-context.ts +88 -0
@@ -0,0 +1,86 @@
1
+ import type { MetaObject } from "@metaobjectsdev/metadata";
2
+
3
+ /**
4
+ * The five CRUD verbs a generated routes file can mount.
5
+ *
6
+ * Mirrors `CrudVerb` in `@metaobjectsdev/runtime-ts` — both the fastify and hono mount
7
+ * modules declare it identically. It is RESTATED rather than imported because codegen-ts
8
+ * does not depend on runtime-ts: codegen emits a call to that helper, it never links
9
+ * against it. Pinned against both runtime declarations by test/routes-expose.test.ts, so
10
+ * the two cannot drift the way the cell-renderer keys drifted from the view registry.
11
+ */
12
+ export const CRUD_VERBS = ["list", "get", "create", "update", "delete"] as const;
13
+
14
+ /**
15
+ * The verbs a TPH discriminator BASE serves at its own path.
16
+ *
17
+ * Read-only BY CONSTRUCTION: the discriminated union has no single writable shape, so the
18
+ * base mount can never carry `create`/`update`/`delete` — an author-supplied `expose`
19
+ * INTERSECTS with this set rather than replacing it, and may narrow to just `list`.
20
+ * Writes live on the per-subtype mounts at `<base path>/<segment>`.
21
+ *
22
+ * Named here because two places need it and a second literal would be a second answer:
23
+ * `routes-file.ts` emits the mount, and `api-model.ts` documents it. They disagreed —
24
+ * the api surface documented POST/PATCH/DELETE on a base path that serves none, under a
25
+ * comment claiming the documented paths "match the generated routes exactly".
26
+ */
27
+ export const TPH_POLYMORPHIC_VERBS = ["list", "get"] as const;
28
+ export type CrudVerb = (typeof CRUD_VERBS)[number];
29
+
30
+ /**
31
+ * Which CRUD verbs a generated routes file mounts (#348).
32
+ *
33
+ * A `filter` cannot express this. `filter` decides whether the file emits AT ALL, per
34
+ * entity, so it can only remove the whole surface; restricting to a SUBSET of verbs is a
35
+ * different axis. That is why this is a generator option rather than the "narrow it with
36
+ * `filter`" remedy that answered the retired `@emit*` attributes — the same reasoning that
37
+ * made a TPH subtype's opt-IN grid `tphSubtypeGrids` rather than a filter.
38
+ *
39
+ * It is deliberately NOT metadata. Which verbs a deployment exposes is a property of the
40
+ * app, not of the model: the same entity is read-only in one service and writable in
41
+ * another, and an attribute would force one answer into the shared spine.
42
+ *
43
+ * Absent — or a function returning `undefined` for an entity — means all five, and emits
44
+ * output byte-identical to before this option existed.
45
+ */
46
+ export type ExposeOption =
47
+ | readonly CrudVerb[]
48
+ | ((entity: MetaObject) => readonly CrudVerb[] | undefined);
49
+
50
+ /** Resolve the option for one entity. `undefined` means "mount all five". */
51
+ export function resolveExpose(
52
+ entity: MetaObject,
53
+ expose: ExposeOption | undefined,
54
+ ): readonly CrudVerb[] | undefined {
55
+ if (expose === undefined) return undefined;
56
+ return typeof expose === "function" ? expose(entity) : expose;
57
+ }
58
+
59
+ /**
60
+ * Narrow a mount whose verb set is already fixed by construction.
61
+ *
62
+ * A TPH polymorphic mount is read-only (`["list", "get"]`) because the discriminated union
63
+ * has no single writable shape. An author-supplied `expose` may narrow that further but
64
+ * must never widen it, so this INTERSECTS rather than replaces: mounting `create` on a
65
+ * surface that cannot serve it would emit a route that fails at runtime, and a wrong
66
+ * endpoint is worse than a missing one.
67
+ */
68
+ export function intersectExpose(
69
+ fixed: readonly CrudVerb[],
70
+ requested: readonly CrudVerb[] | undefined,
71
+ ): readonly CrudVerb[] {
72
+ if (requested === undefined) return fixed;
73
+ const want = new Set<string>(requested);
74
+ return fixed.filter((v) => want.has(v));
75
+ }
76
+
77
+ /**
78
+ * The `expose: [...]` line for a mount call, or "" when every verb mounts.
79
+ *
80
+ * Emitting nothing rather than the full list keeps output byte-identical for every
81
+ * project that does not use the option.
82
+ */
83
+ export function exposeLine(verbs: readonly CrudVerb[] | undefined, indent: string): string {
84
+ if (verbs === undefined) return "";
85
+ return `\n${indent}expose: [${verbs.map((v) => JSON.stringify(v)).join(", ")}],`;
86
+ }
package/src/runner.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { join, relative, resolve, isAbsolute, dirname } from "node:path";
2
2
  import { warnMissingPromptGenerators } from "./prompt-generator-gate.js";
3
+ import { warnRetiredCodegenAttrs } from "./retired-codegen-attrs.js";
3
4
  import { tmpdir } from "node:os";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import { existsSync, readFileSync } from "node:fs";
@@ -7,6 +8,7 @@ import type { MetaData, MetaObject } from "@metaobjectsdev/metadata";
7
8
  import { isMetaRoot, OBJECT_SUBTYPE_VALUE, FIELD_SUBTYPE_TIMESTAMP, FIELD_ATTR_FILTERABLE } from "@metaobjectsdev/metadata";
8
9
  import { assignEmittedNames } from "./naming/collision-names.js";
9
10
  import { isAbstract } from "./instance-artifacts.js";
11
+ import { dbEmittingObjects, missingDialectMessage } from "./db-emitting.js";
10
12
  import { hasAnyRdbSource } from "./source-detect.js";
11
13
  import type { Generator, GenContext, EmittedFile } from "./generator.js";
12
14
  import type { MetaobjectsGenConfig } from "./metaobjects-config.js";
@@ -128,6 +130,50 @@ function engineVersion(): string | undefined {
128
130
  }
129
131
  }
130
132
 
133
+ /** The release in which `$apiPrefix` left the generated entity descriptor. */
134
+ const BASE_URL_MOVE_VERSION = "0.25.0";
135
+
136
+ /** `N.N.N` only. Anything else — a prerelease tag, a sentinel — is deliberately unordered. */
137
+ function orderable(v: string | undefined): [number, number, number] | undefined {
138
+ if (v === undefined) return undefined;
139
+ const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
140
+ return m === null ? undefined : [Number(m[1]), Number(m[2]), Number(m[3])];
141
+ }
142
+
143
+ /**
144
+ * True exactly on the FIRST gen after upgrading past the release that moved the API base
145
+ * URL out of the entity descriptor, and only for a project that actually had a prefix to
146
+ * move. Exported for test.
147
+ *
148
+ * Keyed on the #232 engine stamp rather than on `apiPrefix` alone so the note is
149
+ * self-extinguishing: whether the app passed `baseUrl` to its provider is runtime code
150
+ * `meta gen` cannot see, so a prefix-only trigger would nag for ever with no way to
151
+ * satisfy it — the cry-wolf failure that got the `timestampMode` warning deleted from
152
+ * this file (see `codegen-ts-tanstack/src/data-grid-gate.ts`).
153
+ *
154
+ * Silence is the default for an absent or unorderable recorded version. That is the
155
+ * OPPOSITE call to the 0.24.5 agent-context staleness nudge, and deliberately: there an
156
+ * unknown version meant "cannot prove it is fresh, so nudge"; here it means "no gen
157
+ * history, so nothing to migrate", and a false nag costs more than a missed one when the
158
+ * compile break already routes the adopter to the same line.
159
+ */
160
+ export function shouldNoteBaseUrlMove(
161
+ apiPrefix: string,
162
+ recordedEngine: string | undefined,
163
+ moveVersion: string = BASE_URL_MOVE_VERSION,
164
+ ): boolean {
165
+ if (apiPrefix === "") return false;
166
+ const was = orderable(recordedEngine);
167
+ const move = orderable(moveVersion);
168
+ if (was === undefined || move === undefined) return false;
169
+ for (let i = 0; i < 3; i++) {
170
+ const a = was[i] as number;
171
+ const b = move[i] as number;
172
+ if (a !== b) return a < b;
173
+ }
174
+ return false;
175
+ }
176
+
131
177
  export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
132
178
  const warnings: string[] = [];
133
179
  const strategy = opts.mergeStrategy ?? "overwrite";
@@ -260,16 +306,9 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
260
306
  // projects — the error even told them their model "generates database code"
261
307
  // that in fact imported nothing. It is demanded below at the point of USE, by
262
308
  // the generator that reads it, which the runner names in the thrown message.
263
- const dbEmittingObjects = safeEntities.filter(
264
- (e) => !e.isAbstract && hasAnyRdbSource(e),
265
- );
266
- if (dbEmittingObjects.length > 0 && opts.config.dialect === undefined) {
267
- const names = dbEmittingObjects.map((e) => e.name).join(", ");
268
- throw new Error(
269
- `codegen config is missing dialect — required because this model ` +
270
- `generates database code for: ${names}. Set dialect in ` +
271
- `metaobjects.config.ts. (A model of only value objects and/or sourceless projections may omit it.)`,
272
- );
309
+ const dbEmitting = dbEmittingObjects(safeEntities);
310
+ if (dbEmitting.length > 0 && opts.config.dialect === undefined) {
311
+ throw new Error(missingDialectMessage(dbEmitting));
273
312
  }
274
313
 
275
314
  /** Did the AUTHOR declare a dbImport reachable by this target? Tracked, never
@@ -282,6 +321,19 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
282
321
  // 2. Resolve targets + entity-module target.
283
322
  const config = normalizeConfig(opts.config);
284
323
 
324
+ // The compile break (`value` → `fetcher` on the provider) sends an adopter to the right
325
+ // line; this names the value to put there, once, for the projects that had one.
326
+ if (shouldNoteBaseUrlMove(config.apiPrefix, recordedEngine)) {
327
+ warnings.push(
328
+ `apiPrefix ${JSON.stringify(config.apiPrefix)} no longer reaches the client — ` +
329
+ `generated hooks and services emit entity-relative paths. Supply it once at the ` +
330
+ `provider: <EntityFetcherProvider fetcher={...} baseUrl=${JSON.stringify(config.apiPrefix)}> ` +
331
+ `(Angular: provideEntityFetcher({ fetcher, baseUrl: ${JSON.stringify(config.apiPrefix)} })). ` +
332
+ `Server routes still mount under apiPrefix and are unaffected. ` +
333
+ `See docs/features/migrations/api-base-url-leaves-the-entity-descriptor.md`,
334
+ );
335
+ }
336
+
285
337
  // (Historical: a warning stood here for a @filterable timestamp under
286
338
  // timestampMode:"date", which used to throw at REQUEST time in runtime-ts's
287
339
  // filter parser. That limitation is fixed — the generated allowlist now carries
@@ -367,11 +419,36 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
367
419
  // CRUD surface it actually emits (rather than silently omitting it).
368
420
  const includeHonoRoutes = config.generators.some((g) => g.emitsHonoRoutes === true);
369
421
 
422
+ // §A6 — same auto-detection for the OPT-IN names generator. The entity tier may only
423
+ // REFERENCE `<Entity>Names` when something in this run actually emits it; the names
424
+ // generator is opt-in under ADR-0034 (meta gen runs the adopter's own copies), so an
425
+ // unconditional import would break every project that has not added it. Surfaced BOTH
426
+ // on ctx.config (the field reference/names.ts documents, and the shape a third-party
427
+ // generator reads) and on every RenderContext below (what the templates can actually
428
+ // see) — one aggregation, two consumers.
429
+ //
430
+ // Scoped BY TARGET, unlike includeHonoRoutes, because the two flags are read for
431
+ // different reasons: api-docs asks "is this surface in the run?", while a template
432
+ // referencing these constants emits a RELATIVE import at its own target's path. The
433
+ // names artifact is not a registered cross-target module (there is no importBase route
434
+ // to it), so `entityFile({ target: "db" })` beside a default-target `namesFile()` would
435
+ // otherwise emit `./<Entity>.names` from a directory that does not hold one.
436
+ const namesTargets = new Set(
437
+ config.generators.filter((g) => g.emitsNames === true).map((g) => targetOf(g).name),
438
+ );
439
+
370
440
  // A declared template.prompt with no prompt generator wired emits nothing and, before
371
441
  // this, said nothing — while `meta verify` reported the template "clean". See
372
442
  // prompt-generator-gate.ts. Self-extinguishing; warning only.
373
443
  warnMissingPromptGenerators(root, config.generators, (m) => warnings.push(m));
374
444
 
445
+ // A retired `@emit*` codegen flag still sitting in the metadata suppresses nothing now.
446
+ // Named here rather than left to be discovered as a file that reappeared. Scoped to
447
+ // the run's own entity set (the same set every generator sees), so `meta gen <entity>`
448
+ // reports on what it just generated rather than on objects it was told to skip.
449
+ // See retired-codegen-attrs.ts. Self-extinguishing; warning only.
450
+ warnRetiredCodegenAttrs(safeEntities, (m) => warnings.push(m));
451
+
375
452
  // 4. Run each generator with a per-target render context; collect with full path.
376
453
  const emitted: { fullPath: string; content: string; generatedBy: string }[] = [];
377
454
  // FR-038 §8 — generators that opted into orphan reconciliation, paired with the
@@ -421,6 +498,7 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
421
498
  apiPrefix: config.apiPrefix,
422
499
  emitAbstractShapes: config.emitAbstractShapes,
423
500
  outputLayout: selfTarget.outputLayout,
501
+ includeNames: namesTargets.has(selfTarget.name),
424
502
  pkMap,
425
503
  relationMap,
426
504
  packageOf,
@@ -440,6 +518,7 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
440
518
  dialect: config.dialect,
441
519
  outputLayout: selfTarget.outputLayout,
442
520
  includeHonoRoutes,
521
+ includeNames: namesTargets.has(selfTarget.name),
443
522
  },
444
523
  renderContext,
445
524
  ...(projectRoot !== undefined && { projectRoot }),
@@ -470,7 +549,11 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
470
549
  files = await generator.generate(ctx);
471
550
  } catch (err) {
472
551
  const msg = err instanceof Error ? err.message : String(err);
473
- throw new Error(`[${generator.name}] ${msg}`);
552
+ // `cause` preserves the original throw. Without it a `runGen` caller sees a
553
+ // plain Error carrying only the prefixed message, so it cannot tell a
554
+ // CodegenError (a metadata/config problem it can report) from a genuine bug
555
+ // in a generator, and every stack trace stops at this line.
556
+ throw new Error(`[${generator.name}] ${msg}`, { cause: err });
474
557
  }
475
558
 
476
559
  for (const file of files) {
@@ -544,6 +627,34 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
544
627
  `Refused to overwrite ${w.path}: ${w.conflictHint ?? "content differs and could not be verified as generated."}`,
545
628
  );
546
629
  }
630
+
631
+ // The recovery, ONCE — for the same reason the no-manifest branch above aggregates:
632
+ // it is the same three commands whether one file refused or sixteen, and repeating
633
+ // it per file buries it.
634
+ //
635
+ // It is stated as a SEQUENCE rather than a menu because there is exactly one way to
636
+ // keep an edit here and it is not obvious: a three-way merge needs a base, the
637
+ // `.gen-state` bodies are gitignored, and this machine has none — so the base has to
638
+ // be manufactured first. `--baseline=fresh` writes fresh output AND seeds the missing
639
+ // snapshot; restoring the file from git then puts the edit back with a base now
640
+ // present, and the next run merges it. Verified end to end (refused → overwrite →
641
+ // merged, edit intact).
642
+ //
643
+ // Two remedies were removed from the per-file hint and are not restored here as
644
+ // universal advice. "Move the edit into a non-generated file" holds only where the
645
+ // edit CAN live elsewhere — a `requirementTests()` stub's body cannot, because the
646
+ // test name is the link to the requirement and the stub's own header forbids renaming
647
+ // it. "--baseline=fresh" alone is a discard, and is named as one.
648
+ warnings.push(
649
+ `To KEEP your version of the file(s) above, the edit must be committed first — ` +
650
+ `then: 'meta gen --baseline=fresh' (writes fresh output over them and seeds the ` +
651
+ `missing .gen-state snapshot), 'git checkout -- <paths>' to bring your version back, ` +
652
+ `then 'meta gen' again — the snapshot now exists, so the edit merges. ` +
653
+ `To DISCARD your version instead, '--baseline=fresh' on its own is the whole answer. ` +
654
+ `Moving the edit into a non-generated file works only where the edit can live outside ` +
655
+ `the generated one; a requirementTests() stub's body cannot, since the test name is ` +
656
+ `its link to the requirement.`,
657
+ );
547
658
  };
548
659
 
549
660
  const sweep = (dryRun: boolean): void => {
@@ -7,7 +7,7 @@
7
7
  // import { PhaseSummarySchema, type PhaseSummary } from "./PhaseSummary.js";
8
8
  //
9
9
  // export async function callPhaseSummary(
10
- // db: PgDatabase<PgQueryResultHKT, Record<string, never>>,
10
+ // db: PgDatabase<PgQueryResultHKT, Record<string, unknown>>,
11
11
  // args: PhaseSummaryArgs,
12
12
  // ): Promise<PhaseSummary[]> {
13
13
  // const r = await db.execute(
@@ -20,6 +20,7 @@
20
20
  // `args` parameter and calls `fn_x()`.
21
21
 
22
22
  import {
23
+ primaryRdbSource,
23
24
  type MetaObject,
24
25
  type MetaSource,
25
26
  isMetaSource,
@@ -31,6 +32,9 @@ import {
31
32
  OBJECT_SUBTYPE_VALUE,
32
33
  } from "@metaobjectsdev/metadata";
33
34
  import { GENERATED_HEADER } from "../constants.js";
35
+ import { crossEntitySpecifier } from "../import-path.js";
36
+ import { resolveObjectNames } from "../names.js";
37
+ import type { RenderContext } from "../render-context.js";
34
38
 
35
39
  const CALLABLE_KINDS: ReadonlySet<string> = new Set([
36
40
  SOURCE_KIND_STORED_PROC,
@@ -57,8 +61,24 @@ function callableSource(entity: MetaObject): MetaSource | undefined {
57
61
  }
58
62
 
59
63
  /** Render the full file content for an entity's callable wrapper. Caller
60
- * is responsible for formatting (prettier / biome) and writing to disk. */
61
- export function renderCallableFile(entity: MetaObject): string {
64
+ * is responsible for formatting (prettier / biome) and writing to disk.
65
+ *
66
+ * `ctx` is optional only so the pre-existing callers that render a wrapper in isolation
67
+ * keep compiling; with it absent the physical name is spelled literally, which is the
68
+ * same documented fallback as running with `namesFile()` out of the suite. */
69
+ export function renderCallableFile(entity: MetaObject, ctx?: RenderContext): string {
70
+ // Run the primary-source DIVERGENCE refusal before resolving a physical name.
71
+ // `callableSource` selects by @kind with NO role filter, so it is a THIRD door into
72
+ // "what relation does this object name" — and it is reached by `callableFile()` alone,
73
+ // with no table-name resolver anywhere on the path. Without this an object whose
74
+ // @role: primary sources disagree emitted a wrapper bound to the inherited parent's
75
+ // procedure, on a model every other tier refuses. A refusal that depends on which
76
+ // generators ran is not a refusal.
77
+ //
78
+ // The SELECTION below is deliberately unchanged: divergence is about the NAME, while
79
+ // this function additionally asks "which source is callable?". Same shape as
80
+ // MetaObject.dbTable and the JVM/C# findPrimaryWritableSource.
81
+ primaryRdbSource(entity);
62
82
  const source = callableSource(entity);
63
83
  if (source === undefined) {
64
84
  throw new Error(
@@ -95,6 +115,30 @@ export function renderCallableFile(entity: MetaObject): string {
95
115
  ? ""
96
116
  : paramFieldNames.map((n) => `\${args.${n}}`).join(", ");
97
117
 
118
+ // §A6 — reference `<Entity>Names.sources.primary.proc` rather than spelling the
119
+ // procedure a SECOND time.
120
+ // This file is a raw string template rather than ts-poet, so the import is composed here
121
+ // instead of through `imp`; `crossEntitySpecifier` is the same helper `namesRef` uses, so
122
+ // the specifier (and its extension style) cannot drift from every other generator's.
123
+ const namesConst =
124
+ ctx !== undefined && ctx.includeNames &&
125
+ resolveObjectNames(entity, ctx.columnNamingStrategy) !== undefined
126
+ ? `${entity.name}Names`
127
+ : undefined;
128
+ const namesImport = namesConst === undefined
129
+ ? ""
130
+ : `import { ${namesConst} } from "${crossEntitySpecifier(
131
+ ctx!.selfTarget.outputLayout, entity.package, entity.package,
132
+ `${entity.name}.names`, ctx!.extStyle,
133
+ )}";\n`;
134
+ // The identifier must stay an IDENTIFIER. A bare interpolation into drizzle's `sql` tag
135
+ // binds a PARAMETER, so `sql`SELECT * FROM ${Names.sources.primary.proc}(…)`` would send the procedure
136
+ // name as a query argument and produce SQL that cannot execute. `sql.raw` splices text.
137
+ // `sql.identifier` is deliberately not used: it quotes, which changes the statement.
138
+ const procNameExpr = namesConst === undefined
139
+ ? procName
140
+ : `\${${"sql.raw"}(${namesConst}.name)}`;
141
+
98
142
  const fnName = `call${entity.name}`;
99
143
  const projectionType = entity.name;
100
144
  const projectionSchemaName = `${entity.name}Schema`;
@@ -105,23 +149,31 @@ export function renderCallableFile(entity: MetaObject): string {
105
149
  ? `import type { ${argsObjectName} } from "./${argsObjectName}.js";\n`
106
150
  : "";
107
151
 
152
+ // The `db` parameter type carries the same open-type-argument rule as the queries
153
+ // file's `Db` alias (see templates/queries-file.ts `dbTypeBlock`): `PgDatabase` is the
154
+ // base every PG driver extends, and the schema parameter stays at Drizzle's own bound
155
+ // `Record<string, unknown>` — NOT its `Record<string, never>` default, which rejects the
156
+ // idiomatic `drizzle(client, { schema })` db with TS2345 and makes this callable
157
+ // uninvokable. Uncompilable generated code is indistinguishable from unused generated
158
+ // code, so this parameter must never be narrowed back.
108
159
  const signature = argsObject
109
- ? `db: PgDatabase<PgQueryResultHKT, Record<string, never>>, args: ${argsObjectName}`
110
- : `db: PgDatabase<PgQueryResultHKT, Record<string, never>>`;
160
+ ? `db: PgDatabase<PgQueryResultHKT, Record<string, unknown>>, args: ${argsObjectName}`
161
+ : `db: PgDatabase<PgQueryResultHKT, Record<string, unknown>>`;
111
162
 
112
163
  return `// ${GENERATED_HEADER}
113
164
  import { sql } from "drizzle-orm";
114
165
  import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
115
- ${argsImport}import { ${projectionSchemaName}, type ${projectionType} } from "./${entity.name}.js";
166
+ ${namesImport}${argsImport}import { ${projectionSchemaName}, type ${projectionType} } from "./${entity.name}.js";
116
167
 
117
168
  /**
118
- * FR-015: typed wrapper around the \`${procName}\` ${source.effectiveKind === SOURCE_KIND_STORED_PROC ? "stored procedure" : "table function"}.
169
+ * FR-015: typed wrapper around the ${source.effectiveKind === SOURCE_KIND_STORED_PROC ? "stored procedure" : "table function"} named by
170
+ * \`${namesConst === undefined ? procName : `${namesConst}.name`}\`.
119
171
  * Drizzle passes a parameterised SELECT — args bind in declaration order from
120
172
  * the @parameterRef value-object${argsObject ? `, here ${argsObjectName}` : ""}.
121
173
  */
122
174
  export async function ${fnName}(${signature}): Promise<${projectionType}[]> {
123
175
  const r = await db.execute(
124
- sql\`SELECT * FROM ${procName}(${sqlArgList})\`,
176
+ sql\`SELECT * FROM ${procNameExpr}(${sqlArgList})\`,
125
177
  );
126
178
  return r.rows.map((row) => ${projectionSchemaName}.parse(row as unknown));
127
179
  }