@metaobjectsdev/codegen-ts 0.9.0 → 0.11.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 (342) hide show
  1. package/README.md +1 -1
  2. package/dist/column-mapper.d.ts +12 -6
  3. package/dist/column-mapper.d.ts.map +1 -1
  4. package/dist/column-mapper.js +68 -28
  5. package/dist/column-mapper.js.map +1 -1
  6. package/dist/constants.d.ts +8 -0
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +16 -0
  9. package/dist/constants.js.map +1 -1
  10. package/dist/docs-paths.d.ts +58 -0
  11. package/dist/docs-paths.d.ts.map +1 -0
  12. package/dist/docs-paths.js +89 -0
  13. package/dist/docs-paths.js.map +1 -0
  14. package/dist/enum-import.d.ts +14 -0
  15. package/dist/enum-import.d.ts.map +1 -0
  16. package/dist/enum-import.js +35 -0
  17. package/dist/enum-import.js.map +1 -0
  18. package/dist/enum-shared.d.ts +32 -0
  19. package/dist/enum-shared.d.ts.map +1 -0
  20. package/dist/enum-shared.js +83 -0
  21. package/dist/enum-shared.js.map +1 -0
  22. package/dist/generator-registry.d.ts +22 -0
  23. package/dist/generator-registry.d.ts.map +1 -0
  24. package/dist/generator-registry.js +161 -0
  25. package/dist/generator-registry.js.map +1 -0
  26. package/dist/generator.d.ts +6 -0
  27. package/dist/generator.d.ts.map +1 -1
  28. package/dist/generator.js.map +1 -1
  29. package/dist/generators/api-doc-render.d.ts +17 -0
  30. package/dist/generators/api-doc-render.d.ts.map +1 -0
  31. package/dist/generators/api-doc-render.js +431 -0
  32. package/dist/generators/api-doc-render.js.map +1 -0
  33. package/dist/generators/api-docs-file.d.ts +21 -0
  34. package/dist/generators/api-docs-file.d.ts.map +1 -0
  35. package/dist/generators/api-docs-file.js +112 -0
  36. package/dist/generators/api-docs-file.js.map +1 -0
  37. package/dist/generators/api-field-shape.d.ts +39 -0
  38. package/dist/generators/api-field-shape.d.ts.map +1 -0
  39. package/dist/generators/api-field-shape.js +92 -0
  40. package/dist/generators/api-field-shape.js.map +1 -0
  41. package/dist/generators/api-label.d.ts +3 -0
  42. package/dist/generators/api-label.d.ts.map +1 -0
  43. package/dist/generators/api-label.js +8 -0
  44. package/dist/generators/api-label.js.map +1 -0
  45. package/dist/generators/api-model.d.ts +122 -0
  46. package/dist/generators/api-model.d.ts.map +1 -0
  47. package/dist/generators/api-model.js +809 -0
  48. package/dist/generators/api-model.js.map +1 -0
  49. package/dist/generators/docs-data-builder.d.ts +26 -4
  50. package/dist/generators/docs-data-builder.d.ts.map +1 -1
  51. package/dist/generators/docs-data-builder.js +439 -167
  52. package/dist/generators/docs-data-builder.js.map +1 -1
  53. package/dist/generators/docs-data.d.ts +136 -27
  54. package/dist/generators/docs-data.d.ts.map +1 -1
  55. package/dist/generators/docs-data.js +1 -1
  56. package/dist/generators/docs-data.js.map +1 -1
  57. package/dist/generators/docs-file.d.ts +19 -0
  58. package/dist/generators/docs-file.d.ts.map +1 -1
  59. package/dist/generators/docs-file.js +154 -27
  60. package/dist/generators/docs-file.js.map +1 -1
  61. package/dist/generators/entity-file.d.ts.map +1 -1
  62. package/dist/generators/entity-file.js +29 -14
  63. package/dist/generators/entity-file.js.map +1 -1
  64. package/dist/generators/extractor-file.d.ts.map +1 -1
  65. package/dist/generators/extractor-file.js +2 -1
  66. package/dist/generators/extractor-file.js.map +1 -1
  67. package/dist/generators/field-anchor.d.ts +7 -0
  68. package/dist/generators/field-anchor.d.ts.map +1 -0
  69. package/dist/generators/field-anchor.js +23 -0
  70. package/dist/generators/field-anchor.js.map +1 -0
  71. package/dist/generators/index.d.ts +8 -1
  72. package/dist/generators/index.d.ts.map +1 -1
  73. package/dist/generators/index.js +6 -0
  74. package/dist/generators/index.js.map +1 -1
  75. package/dist/generators/mermaid-er.d.ts +14 -0
  76. package/dist/generators/mermaid-er.d.ts.map +1 -1
  77. package/dist/generators/mermaid-er.js +14 -0
  78. package/dist/generators/mermaid-er.js.map +1 -1
  79. package/dist/generators/output-parser-file.d.ts.map +1 -1
  80. package/dist/generators/output-parser-file.js +3 -4
  81. package/dist/generators/output-parser-file.js.map +1 -1
  82. package/dist/generators/output-prompt-file.d.ts.map +1 -1
  83. package/dist/generators/output-prompt-file.js +2 -2
  84. package/dist/generators/output-prompt-file.js.map +1 -1
  85. package/dist/generators/prompt-render-file.d.ts.map +1 -1
  86. package/dist/generators/prompt-render-file.js +3 -4
  87. package/dist/generators/prompt-render-file.js.map +1 -1
  88. package/dist/generators/queries-file.d.ts.map +1 -1
  89. package/dist/generators/queries-file.js +8 -3
  90. package/dist/generators/queries-file.js.map +1 -1
  91. package/dist/generators/render-helper-file.d.ts.map +1 -1
  92. package/dist/generators/render-helper-file.js +2 -2
  93. package/dist/generators/render-helper-file.js.map +1 -1
  94. package/dist/generators/routes-file-hono.d.ts.map +1 -1
  95. package/dist/generators/routes-file-hono.js +5 -1
  96. package/dist/generators/routes-file-hono.js.map +1 -1
  97. package/dist/generators/routes-file.d.ts +3 -0
  98. package/dist/generators/routes-file.d.ts.map +1 -1
  99. package/dist/generators/routes-file.js +6 -1
  100. package/dist/generators/routes-file.js.map +1 -1
  101. package/dist/generators/template-doc-builder.d.ts +19 -0
  102. package/dist/generators/template-doc-builder.d.ts.map +1 -0
  103. package/dist/generators/template-doc-builder.js +220 -0
  104. package/dist/generators/template-doc-builder.js.map +1 -0
  105. package/dist/generators/template-doc-data.d.ts +62 -0
  106. package/dist/generators/template-doc-data.d.ts.map +1 -0
  107. package/dist/generators/template-doc-data.js +16 -0
  108. package/dist/generators/template-doc-data.js.map +1 -0
  109. package/dist/generators/template-payload-tree.d.ts +15 -0
  110. package/dist/generators/template-payload-tree.d.ts.map +1 -0
  111. package/dist/generators/template-payload-tree.js +61 -0
  112. package/dist/generators/template-payload-tree.js.map +1 -0
  113. package/dist/generators/template-source-annotate.d.ts +74 -0
  114. package/dist/generators/template-source-annotate.d.ts.map +1 -0
  115. package/dist/generators/template-source-annotate.js +184 -0
  116. package/dist/generators/template-source-annotate.js.map +1 -0
  117. package/dist/generators/template-source-render.d.ts +24 -0
  118. package/dist/generators/template-source-render.d.ts.map +1 -0
  119. package/dist/generators/template-source-render.js +175 -0
  120. package/dist/generators/template-source-render.js.map +1 -0
  121. package/dist/generators/trace-helper-file.d.ts +9 -0
  122. package/dist/generators/trace-helper-file.d.ts.map +1 -0
  123. package/dist/generators/trace-helper-file.js +196 -0
  124. package/dist/generators/trace-helper-file.js.map +1 -0
  125. package/dist/import-path.d.ts +18 -0
  126. package/dist/import-path.d.ts.map +1 -1
  127. package/dist/import-path.js +21 -0
  128. package/dist/import-path.js.map +1 -1
  129. package/dist/index.d.ts +29 -4
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +28 -2
  132. package/dist/index.js.map +1 -1
  133. package/dist/metaobjects-config.d.ts +101 -2
  134. package/dist/metaobjects-config.d.ts.map +1 -1
  135. package/dist/metaobjects-config.js +46 -0
  136. package/dist/metaobjects-config.js.map +1 -1
  137. package/dist/naming.d.ts +39 -2
  138. package/dist/naming.d.ts.map +1 -1
  139. package/dist/naming.js +52 -3
  140. package/dist/naming.js.map +1 -1
  141. package/dist/payload-codegen.d.ts.map +1 -1
  142. package/dist/payload-codegen.js +14 -6
  143. package/dist/payload-codegen.js.map +1 -1
  144. package/dist/pk-resolver.d.ts.map +1 -1
  145. package/dist/pk-resolver.js +4 -2
  146. package/dist/pk-resolver.js.map +1 -1
  147. package/dist/projection/extract-view-spec.d.ts.map +1 -1
  148. package/dist/projection/extract-view-spec.js +52 -26
  149. package/dist/projection/extract-view-spec.js.map +1 -1
  150. package/dist/relation-resolver.d.ts +16 -0
  151. package/dist/relation-resolver.d.ts.map +1 -1
  152. package/dist/relation-resolver.js +82 -1
  153. package/dist/relation-resolver.js.map +1 -1
  154. package/dist/render-context.d.ts +25 -2
  155. package/dist/render-context.d.ts.map +1 -1
  156. package/dist/render-context.js +7 -0
  157. package/dist/render-context.js.map +1 -1
  158. package/dist/render-engine/embedded-templates.generated.d.ts +2 -0
  159. package/dist/render-engine/embedded-templates.generated.d.ts.map +1 -0
  160. package/dist/render-engine/embedded-templates.generated.js +15 -0
  161. package/dist/render-engine/embedded-templates.generated.js.map +1 -0
  162. package/dist/render-engine/framework-provider.d.ts.map +1 -1
  163. package/dist/render-engine/framework-provider.js +26 -13
  164. package/dist/render-engine/framework-provider.js.map +1 -1
  165. package/dist/runner.d.ts.map +1 -1
  166. package/dist/runner.js +20 -0
  167. package/dist/runner.js.map +1 -1
  168. package/dist/templates/docs-file.d.ts +2 -6
  169. package/dist/templates/docs-file.d.ts.map +1 -1
  170. package/dist/templates/docs-file.js +2 -5
  171. package/dist/templates/docs-file.js.map +1 -1
  172. package/dist/templates/drizzle-schema.d.ts.map +1 -1
  173. package/dist/templates/drizzle-schema.js +72 -23
  174. package/dist/templates/drizzle-schema.js.map +1 -1
  175. package/dist/templates/entity-constants.d.ts +7 -0
  176. package/dist/templates/entity-constants.d.ts.map +1 -1
  177. package/dist/templates/entity-constants.js +3 -3
  178. package/dist/templates/entity-constants.js.map +1 -1
  179. package/dist/templates/entity-file.d.ts.map +1 -1
  180. package/dist/templates/entity-file.js +16 -5
  181. package/dist/templates/entity-file.js.map +1 -1
  182. package/dist/templates/enums-file.d.ts +11 -0
  183. package/dist/templates/enums-file.d.ts.map +1 -0
  184. package/dist/templates/enums-file.js +44 -0
  185. package/dist/templates/enums-file.js.map +1 -0
  186. package/dist/templates/extract-delegate-emitter.d.ts.map +1 -1
  187. package/dist/templates/extract-delegate-emitter.js +6 -8
  188. package/dist/templates/extract-delegate-emitter.js.map +1 -1
  189. package/dist/templates/extractor.d.ts.map +1 -1
  190. package/dist/templates/extractor.js +58 -41
  191. package/dist/templates/extractor.js.map +1 -1
  192. package/dist/templates/field-meta.d.ts.map +1 -1
  193. package/dist/templates/field-meta.js +2 -6
  194. package/dist/templates/field-meta.js.map +1 -1
  195. package/dist/templates/filter-allowlist.d.ts +7 -2
  196. package/dist/templates/filter-allowlist.d.ts.map +1 -1
  197. package/dist/templates/filter-allowlist.js +18 -10
  198. package/dist/templates/filter-allowlist.js.map +1 -1
  199. package/dist/templates/filter-shared.js +2 -2
  200. package/dist/templates/filter-shared.js.map +1 -1
  201. package/dist/templates/filter-type.d.ts +7 -1
  202. package/dist/templates/filter-type.d.ts.map +1 -1
  203. package/dist/templates/filter-type.js +10 -6
  204. package/dist/templates/filter-type.js.map +1 -1
  205. package/dist/templates/find-templates.d.ts +4 -0
  206. package/dist/templates/find-templates.d.ts.map +1 -0
  207. package/dist/templates/find-templates.js +15 -0
  208. package/dist/templates/find-templates.js.map +1 -0
  209. package/dist/templates/fr010-field-mapping.d.ts +2 -0
  210. package/dist/templates/fr010-field-mapping.d.ts.map +1 -1
  211. package/dist/templates/fr010-field-mapping.js +15 -11
  212. package/dist/templates/fr010-field-mapping.js.map +1 -1
  213. package/dist/templates/inferred-types.d.ts +44 -7
  214. package/dist/templates/inferred-types.d.ts.map +1 -1
  215. package/dist/templates/inferred-types.js +121 -19
  216. package/dist/templates/inferred-types.js.map +1 -1
  217. package/dist/templates/mermaid-er.d.ts +35 -2
  218. package/dist/templates/mermaid-er.d.ts.map +1 -1
  219. package/dist/templates/mermaid-er.js +174 -7
  220. package/dist/templates/mermaid-er.js.map +1 -1
  221. package/dist/templates/output-format-spec-emitter.js +1 -1
  222. package/dist/templates/output-format-spec-emitter.js.map +1 -1
  223. package/dist/templates/output-parser.d.ts.map +1 -1
  224. package/dist/templates/output-parser.js +31 -80
  225. package/dist/templates/output-parser.js.map +1 -1
  226. package/dist/templates/output-prompt.d.ts.map +1 -1
  227. package/dist/templates/output-prompt.js +2 -2
  228. package/dist/templates/output-prompt.js.map +1 -1
  229. package/dist/templates/queries-file.d.ts.map +1 -1
  230. package/dist/templates/queries-file.js +113 -5
  231. package/dist/templates/queries-file.js.map +1 -1
  232. package/dist/templates/queries.d.ts +7 -2
  233. package/dist/templates/queries.d.ts.map +1 -1
  234. package/dist/templates/queries.js +15 -15
  235. package/dist/templates/queries.js.map +1 -1
  236. package/dist/templates/relations-block.d.ts.map +1 -1
  237. package/dist/templates/relations-block.js +12 -3
  238. package/dist/templates/relations-block.js.map +1 -1
  239. package/dist/templates/render-helper.d.ts.map +1 -1
  240. package/dist/templates/render-helper.js +5 -5
  241. package/dist/templates/render-helper.js.map +1 -1
  242. package/dist/templates/routes-file-hono.d.ts.map +1 -1
  243. package/dist/templates/routes-file-hono.js +1 -2
  244. package/dist/templates/routes-file-hono.js.map +1 -1
  245. package/dist/templates/routes-file.d.ts.map +1 -1
  246. package/dist/templates/routes-file.js +184 -7
  247. package/dist/templates/routes-file.js.map +1 -1
  248. package/dist/templates/tph-discriminator.d.ts +56 -0
  249. package/dist/templates/tph-discriminator.d.ts.map +1 -0
  250. package/dist/templates/tph-discriminator.js +180 -0
  251. package/dist/templates/tph-discriminator.js.map +1 -0
  252. package/dist/templates/value-object-file.d.ts +2 -1
  253. package/dist/templates/value-object-file.d.ts.map +1 -1
  254. package/dist/templates/value-object-file.js +33 -5
  255. package/dist/templates/value-object-file.js.map +1 -1
  256. package/dist/templates/zod-validators.d.ts +65 -2
  257. package/dist/templates/zod-validators.d.ts.map +1 -1
  258. package/dist/templates/zod-validators.js +202 -22
  259. package/dist/templates/zod-validators.js.map +1 -1
  260. package/package.json +103 -34
  261. package/src/column-mapper.ts +79 -32
  262. package/src/constants.ts +18 -0
  263. package/src/docs-paths.ts +128 -0
  264. package/src/enum-import.ts +43 -0
  265. package/src/enum-shared.ts +95 -0
  266. package/src/generator-registry.ts +204 -0
  267. package/src/generator.ts +6 -0
  268. package/src/generators/api-doc-render.ts +572 -0
  269. package/src/generators/api-docs-file.ts +146 -0
  270. package/src/generators/api-field-shape.ts +114 -0
  271. package/src/generators/api-label.ts +7 -0
  272. package/src/generators/api-model.ts +1067 -0
  273. package/src/generators/docs-data-builder.ts +483 -189
  274. package/src/generators/docs-data.ts +139 -28
  275. package/src/generators/docs-file.ts +205 -39
  276. package/src/generators/entity-file.ts +31 -15
  277. package/src/generators/extractor-file.ts +2 -1
  278. package/src/generators/field-anchor.ts +24 -0
  279. package/src/generators/index.ts +8 -1
  280. package/src/generators/mermaid-er.ts +14 -0
  281. package/src/generators/output-parser-file.ts +3 -4
  282. package/src/generators/output-prompt-file.ts +2 -1
  283. package/src/generators/prompt-render-file.ts +3 -4
  284. package/src/generators/queries-file.ts +9 -3
  285. package/src/generators/render-helper-file.ts +2 -1
  286. package/src/generators/routes-file-hono.ts +5 -1
  287. package/src/generators/routes-file.ts +7 -1
  288. package/src/generators/template-doc-builder.ts +306 -0
  289. package/src/generators/template-doc-data.ts +85 -0
  290. package/src/generators/template-payload-tree.ts +71 -0
  291. package/src/generators/template-source-annotate.ts +290 -0
  292. package/src/generators/template-source-render.ts +203 -0
  293. package/src/generators/trace-helper-file.ts +301 -0
  294. package/src/import-path.ts +28 -0
  295. package/src/index.ts +55 -4
  296. package/src/metaobjects-config.ts +146 -2
  297. package/src/naming.ts +73 -3
  298. package/src/payload-codegen.ts +16 -5
  299. package/src/pk-resolver.ts +4 -2
  300. package/src/projection/extract-view-spec.ts +50 -31
  301. package/src/relation-resolver.ts +103 -1
  302. package/src/render-context.ts +32 -2
  303. package/src/render-engine/embedded-templates.generated.ts +14 -0
  304. package/src/render-engine/framework-provider.ts +25 -11
  305. package/src/runner.ts +24 -0
  306. package/src/templates/docs-file.ts +2 -9
  307. package/src/templates/drizzle-schema.ts +80 -28
  308. package/src/templates/entity-constants.ts +3 -3
  309. package/src/templates/entity-file.ts +16 -5
  310. package/src/templates/enums-file.ts +50 -0
  311. package/src/templates/extract-delegate-emitter.ts +6 -7
  312. package/src/templates/extractor.ts +70 -40
  313. package/src/templates/field-meta.ts +1 -7
  314. package/src/templates/filter-allowlist.ts +18 -11
  315. package/src/templates/filter-shared.ts +2 -2
  316. package/src/templates/filter-type.ts +9 -7
  317. package/src/templates/find-templates.ts +15 -0
  318. package/src/templates/fr010-field-mapping.ts +15 -13
  319. package/src/templates/inferred-types.ts +122 -21
  320. package/src/templates/mermaid-er.ts +176 -8
  321. package/src/templates/output-format-spec-emitter.ts +1 -1
  322. package/src/templates/output-parser.ts +31 -80
  323. package/src/templates/output-prompt.ts +2 -1
  324. package/src/templates/queries-file.ts +133 -4
  325. package/src/templates/queries.ts +21 -15
  326. package/src/templates/relations-block.ts +19 -3
  327. package/src/templates/render-helper.ts +5 -4
  328. package/src/templates/routes-file-hono.ts +1 -2
  329. package/src/templates/routes-file.ts +234 -7
  330. package/src/templates/tph-discriminator.ts +232 -0
  331. package/src/templates/value-object-file.ts +39 -5
  332. package/src/templates/zod-validators.ts +225 -21
  333. package/templates/api/agent-api.md.mustache +30 -0
  334. package/templates/api/entity-api.md.mustache +69 -0
  335. package/templates/api/index.md.mustache +21 -0
  336. package/templates/docs/entity-page.md.mustache +33 -21
  337. package/templates/docs/template-page.md.mustache +56 -0
  338. package/dist/templates/extract-schema-emitter.d.ts +0 -8
  339. package/dist/templates/extract-schema-emitter.d.ts.map +0 -1
  340. package/dist/templates/extract-schema-emitter.js +0 -81
  341. package/dist/templates/extract-schema-emitter.js.map +0 -1
  342. package/src/templates/extract-schema-emitter.ts +0 -111
@@ -0,0 +1,301 @@
1
+ // server/typescript/packages/codegen-ts/src/generators/trace-helper-file.ts
2
+ //
3
+ // Stock generator that emits one <Entity>.trace.ts helper file for each concrete
4
+ // entity that (a) extends LlmCallBase (directly or transitively) and (b) nests a
5
+ // template.prompt with @payloadRef and/or @responseRef.
6
+ //
7
+ // The emitted helper exports an async function `record<Entity>(om, responseMo, input)`
8
+ // that EXTRACTS the typed response VO itself and persists ONE row = base envelope +
9
+ // raw I/O (via buildLlmCallRow) PLUS the typed voRequest/voResponse columns. The
10
+ // helper is typed against the generated payload interfaces (request + response VOs)
11
+ // so call-sites get compile-time checks.
12
+ //
13
+ // NOTE: ObjectManager does not expose its loaded metadata root, so the caller must
14
+ // pass the resolved `responseMo: MetaObject` explicitly. The generated helper
15
+ // documents this in its JSDoc.
16
+ //
17
+ // Consumer wiring (metaobjects.config.ts):
18
+ // generators: [..., entityFile(), queriesFile(), traceHelperFile(), barrel()]
19
+
20
+ import {
21
+ TYPE_OBJECT,
22
+ TYPE_TEMPLATE,
23
+ TEMPLATE_SUBTYPE_PROMPT,
24
+ TEMPLATE_ATTR_PAYLOAD_REF,
25
+ TEMPLATE_ATTR_RESPONSE_REF,
26
+ TEMPLATE_ATTR_FORMAT,
27
+ TEMPLATE_ATTR_TEXT_REF,
28
+ } from "@metaobjectsdev/metadata";
29
+ import type { MetaObject } from "@metaobjectsdev/metadata";
30
+ import {
31
+ type EmittedFile,
32
+ type Generator,
33
+ type GeneratorFactory,
34
+ perEntity,
35
+ } from "../generator.js";
36
+ import { generatePayloadInterfacesBatch } from "../payload-codegen.js";
37
+ import { GENERATED_HEADER } from "../constants.js";
38
+ import { tphDiscriminatorPin } from "../templates/zod-validators.js";
39
+
40
+ /** Short name of the shipped abstract base every trace entity extends. */
41
+ const LLM_CALL_BASE = "LlmCallBase";
42
+
43
+ export interface TraceHelperOpts {
44
+ /** Output directory prefix relative to the target's outDir. Default: "" (root). */
45
+ outDir?: string;
46
+ /** Optional named output target (registry key). Defaults to "default". */
47
+ target?: string;
48
+ }
49
+
50
+ /** Walk the super chain looking for a node named LLM_CALL_BASE. */
51
+ function extendsBase(obj: MetaObject): boolean {
52
+ let cur = obj.superResolved;
53
+ while (cur !== undefined) {
54
+ if (cur.name === LLM_CALL_BASE) return true;
55
+ cur = cur.superResolved;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ /** Capitalise the first character. */
61
+ function pascal(s: string): string {
62
+ return s.length > 0 ? s[0]!.toUpperCase() + s.slice(1) : s;
63
+ }
64
+
65
+ export const traceHelperFile = function traceHelperFile(opts?: TraceHelperOpts): Generator {
66
+ const dirPrefix = opts?.outDir ? `${opts.outDir.replace(/\/$/, "")}/` : "";
67
+ const generator: Generator = {
68
+ name: "trace-helper",
69
+ generate: perEntity((entity, ctx) => {
70
+ // Only concrete entities derived from LlmCallBase.
71
+ if (entity.isAbstract) return [];
72
+ if (!extendsBase(entity)) return [];
73
+
74
+ // Find the nested template.prompt.
75
+ const prompt = entity.ownChildren().find(
76
+ (c) => c.type === TYPE_TEMPLATE && c.subType === TEMPLATE_SUBTYPE_PROMPT,
77
+ );
78
+ if (prompt === undefined) return [];
79
+
80
+ const payloadRef = prompt.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
81
+ const responseRef = prompt.ownAttr(TEMPLATE_ATTR_RESPONSE_REF);
82
+
83
+ // @responseRef types the result; @payloadRef types the request. Both gate
84
+ // the helper: the entity must declare voRequest/voResponse field.object
85
+ // columns (authored) for these writes to land, and the prompt's refs name
86
+ // the VOs to render/extract into.
87
+ if (typeof responseRef !== "string") return [];
88
+ if (typeof payloadRef !== "string") return [];
89
+
90
+ const entityName = entity.name;
91
+ const fnName = `record${pascal(entityName)}`;
92
+
93
+ // STI: a trace entity that is a TPH subtype stamps its declared
94
+ // discriminator value as callType and drops callType from the caller input
95
+ // (industry-standard STI — the discriminator is framework-managed).
96
+ const tphPin = tphDiscriminatorPin(entity);
97
+ const sti = tphPin !== undefined;
98
+ const callTypeValue = sti ? tphPin.value : entityName;
99
+
100
+ // Emitted record<Entity> fragments. The caller never supplies the derived
101
+ // status/errorDetail (the helper computes them from extraction), and an STI
102
+ // subtype additionally drops the framework-managed callType discriminator.
103
+ const recordInputOmit = sti
104
+ ? `"llmRequest" | "status" | "errorDetail" | "callType"`
105
+ : `"llmRequest" | "status" | "errorDetail"`;
106
+ // The object spread passed to buildLlmCallRow: STI subtypes stamp their
107
+ // discriminator value, all helpers fold in the derived status/errorDetail.
108
+ const recordBuildArg = sti
109
+ ? `{ ...input, callType: ${JSON.stringify(callTypeValue)}, status, errorDetail }`
110
+ : `{ ...input, status, errorDetail }`;
111
+
112
+ // Derive the parse format from the prompt's @format attr.
113
+ // "xml" → Format.XML; absent or any other value → Format.JSON.
114
+ const promptFormat = prompt.ownAttr(TEMPLATE_ATTR_FORMAT);
115
+ const formatLiteral = typeof promptFormat === "string" && promptFormat.toLowerCase() === "xml"
116
+ ? "Format.XML"
117
+ : "Format.JSON";
118
+
119
+ // Collect VO names for interface emission (dedupe via batch emitter).
120
+ // Both refs are guaranteed strings by the guards above.
121
+ const interfaces = generatePayloadInterfacesBatch(ctx.loadedRoot, [payloadRef, responseRef]);
122
+
123
+ const requestType = payloadRef;
124
+
125
+ // A renderable prompt (carries @textRef) gets an additional call<Entity> helper
126
+ // that renders the prompt text, calls the LLM, then parses + persists a trace row.
127
+ const textRef = prompt.ownAttr(TEMPLATE_ATTR_TEXT_REF);
128
+ const renderable = typeof textRef === "string";
129
+ // Same @format attr, two intentionally different shapes: extract() takes the
130
+ // Format enum (formatLiteral, above → Format.XML/Format.JSON), render() takes the
131
+ // raw format string (renderFormat, here → e.g. "json"/"xml", default "text").
132
+ const renderFormat = typeof promptFormat === "string" ? promptFormat : "text";
133
+
134
+ // Build the import block — all imports MUST stay at the top of the emitted file.
135
+ // `extract` + `render` both live in @metaobjectsdev/render: import them together
136
+ // (one de-duplicated statement) when the prompt is renderable, else import only
137
+ // `extract`.
138
+ const importLines: string[] = [
139
+ `import type { ObjectManager } from "@metaobjectsdev/runtime-ts";`,
140
+ `import {`,
141
+ ` LlmCallDbRecorder,`,
142
+ ` buildLlmCallRow,`,
143
+ ` persistLlmCallRow,`,
144
+ ` extractSchemaFor,`,
145
+ ` Format,`,
146
+ ` type LlmCallInput,`,
147
+ ` type LlmCallRow,`,
148
+ `} from "@metaobjectsdev/runtime-ts";`,
149
+ renderable
150
+ ? `import { extract, render, type Provider } from "@metaobjectsdev/render";`
151
+ : `import { extract } from "@metaobjectsdev/render";`,
152
+ `import type { MetaObject } from "@metaobjectsdev/metadata";`,
153
+ ];
154
+ if (renderable) {
155
+ importLines.push(
156
+ `import {`,
157
+ ` runLlmCall,`,
158
+ ` type RunLlmCallInput,`,
159
+ ` type RunLlmCallDeps,`,
160
+ ` type LlmClient,`,
161
+ ` type LlmRequest,`,
162
+ ` type CostFn,`,
163
+ ` type Clock,`,
164
+ ` type IdGen,`,
165
+ `} from "@metaobjectsdev/ai-runtime";`,
166
+ );
167
+ }
168
+
169
+ const lines: string[] = [
170
+ `// ${GENERATED_HEADER} — DO NOT EDIT.`,
171
+ ``,
172
+ ...importLines,
173
+ ``,
174
+ `// ---- Payload interfaces (inlined) ------------------------------------------`,
175
+ ``,
176
+ interfaces.trimEnd(),
177
+ ``,
178
+ `// ---- Typed result -----------------------------------------------------------`,
179
+ ``,
180
+ `export interface ${entityName}TraceResult {`,
181
+ ` status: "ok" | "error";`,
182
+ ` errorDetail: string | null;`,
183
+ ` /** Parsed response VO, or null when extraction reported a lost-required field. */`,
184
+ ` /** Note: voResponse is the plain extracted record typed as the response shape (structural, not an instance). */`,
185
+ ` voResponse: ${responseRef} | null;`,
186
+ `}`,
187
+ ``,
188
+ `// ---- Record helper ----------------------------------------------------------`,
189
+ ``,
190
+ `/**`,
191
+ ` * Record a single ${entityName} LLM call: extract the response VO and persist a`,
192
+ ` * trace row via ObjectManager regardless of whether extraction succeeded.`,
193
+ ` *`,
194
+ ` * @param om - ObjectManager wired to the application database.`,
195
+ ` * @param responseMo - MetaObject for \`${responseRef}\` (resolve via the loaded`,
196
+ ` * metadata root: \`root.findObject("${responseRef}")\`).`,
197
+ ` * Passed explicitly because ObjectManager does not expose`,
198
+ ` * its loaded metadata root.`,
199
+ ` * @param input - LLM call inputs; type \`llmRequest\` as \`${requestType}\`.`,
200
+ ` * @param opts - Optional \`redact\` hook applied to the row before persist`,
201
+ ` * (scrub PII/secrets on the typed path, same as the generic`,
202
+ ` * recordLlmCall/callLlm helpers).`,
203
+ ` */`,
204
+ `export async function ${fnName}(`,
205
+ ` om: ObjectManager,`,
206
+ ` responseMo: MetaObject,`,
207
+ ` input: Omit<LlmCallInput, ${recordInputOmit}> & { llmRequest: ${requestType} },`,
208
+ ` opts?: { redact?: (row: LlmCallRow) => LlmCallRow },`,
209
+ `): Promise<${entityName}TraceResult> {`,
210
+ ` const schema = extractSchemaFor(responseMo, ${formatLiteral});`,
211
+ ` const outcome = extract(input.llmResponseText, schema);`,
212
+ ` const failed = outcome.report.hasLostRequired();`,
213
+ ` const status = failed ? ("error" as const) : ("ok" as const);`,
214
+ ' const errorDetail = failed ? `lost required: ${outcome.report.lostRequired().join(", ")}` : null;',
215
+ ` const base = buildLlmCallRow(${recordBuildArg});`,
216
+ ` const row = { ...base, voRequest: input.llmRequest, voResponse: failed ? null : outcome.data };`,
217
+ ` await persistLlmCallRow(new LlmCallDbRecorder(om, "${entityName}"), row, opts?.redact ? { redact: opts.redact } : undefined);`,
218
+ ` return { status, errorDetail, voResponse: failed ? null : (outcome.data as ${responseRef}) };`,
219
+ `}`,
220
+ ``,
221
+ ];
222
+
223
+ if (renderable) {
224
+ const callFn = `call${pascal(entityName)}`;
225
+ lines.push(
226
+ ``,
227
+ `// ---- Call helper (GENERATE -> CALL -> record) -------------------------------`,
228
+ ``,
229
+ `export interface ${entityName}CallDeps {`,
230
+ ` om: ObjectManager;`,
231
+ ` responseMo: MetaObject;`,
232
+ ` client: LlmClient;`,
233
+ ` /** Prompt-TEXT resolver for render() (NOT the LLM client). */`,
234
+ ` provider: Provider;`,
235
+ ` model: string;`,
236
+ ` system?: string;`,
237
+ ` params?: Record<string, unknown>;`,
238
+ ` cost?: CostFn;`,
239
+ ` clock?: Clock;`,
240
+ ` ids?: IdGen;`,
241
+ ` traceId?: string;`,
242
+ ` parentSpanId?: string;`,
243
+ ` sessionId?: string;`,
244
+ ` /** Optional row-redaction hook applied before persist (scrub PII/secrets). */`,
245
+ ` redact?: (row: LlmCallRow) => LlmCallRow;`,
246
+ `}`,
247
+ ``,
248
+ `/**`,
249
+ ` * Render the ${entityName} prompt, call the LLM, then parse + persist a trace`,
250
+ ` * row (finally-style: a call/parse failure still writes a row).`,
251
+ ` */`,
252
+ `export async function ${callFn}(`,
253
+ ` payload: ${requestType},`,
254
+ ` deps: ${entityName}CallDeps,`,
255
+ `): Promise<${entityName}TraceResult> {`,
256
+ ` const prompt = render({ ref: ${JSON.stringify(textRef)}, payload, format: ${JSON.stringify(renderFormat)}, provider: deps.provider });`,
257
+ // Build request/input/deps conditionally so that an absent optional (T |
258
+ // undefined) is never assigned to an optional T? property — required for
259
+ // exactOptionalPropertyTypes-strict consumer projects.
260
+ ` const request: LlmRequest = { prompt, model: deps.model };`,
261
+ ` if (deps.system !== undefined) request.system = deps.system;`,
262
+ ` if (deps.params !== undefined) request.params = deps.params;`,
263
+ ` const runInput: RunLlmCallInput = { callType: ${JSON.stringify(callTypeValue)}, request };`,
264
+ ` if (deps.traceId !== undefined) runInput.traceId = deps.traceId;`,
265
+ ` if (deps.parentSpanId !== undefined) runInput.parentSpanId = deps.parentSpanId;`,
266
+ ` if (deps.sessionId !== undefined) runInput.sessionId = deps.sessionId;`,
267
+ ` const runDeps: RunLlmCallDeps = { client: deps.client };`,
268
+ ` if (deps.cost !== undefined) runDeps.cost = deps.cost;`,
269
+ ` if (deps.clock !== undefined) runDeps.clock = deps.clock;`,
270
+ ` if (deps.ids !== undefined) runDeps.ids = deps.ids;`,
271
+ ` const { input: recInput, completion } = await runLlmCall(runInput, runDeps);`,
272
+ ` let voResponse: ${responseRef} | null = null;`,
273
+ ` let status = recInput.status;`,
274
+ ` let errorDetail = recInput.errorDetail;`,
275
+ ` if (completion !== undefined) {`,
276
+ ` const outcome = extract(completion.body, extractSchemaFor(deps.responseMo, ${formatLiteral}));`,
277
+ ` if (outcome.report.hasLostRequired()) {`,
278
+ ` status = "error";`,
279
+ ' errorDetail = `lost required: ${outcome.report.lostRequired().join(", ")}`;',
280
+ ` } else {`,
281
+ ` voResponse = outcome.data as ${responseRef};`,
282
+ ` }`,
283
+ ` }`,
284
+ ` const row = { ...buildLlmCallRow({ ...recInput, status, errorDetail }), voRequest: payload, voResponse };`,
285
+ ` await persistLlmCallRow(new LlmCallDbRecorder(deps.om, ${JSON.stringify(entityName)}), row, deps.redact ? { redact: deps.redact } : undefined);`,
286
+ ` return { status, errorDetail, voResponse };`,
287
+ `}`,
288
+ );
289
+ }
290
+
291
+ return [{
292
+ path: `${dirPrefix}${entityName}.trace.ts`,
293
+ content: lines.join("\n"),
294
+ }];
295
+ }),
296
+ };
297
+ if (opts?.target) {
298
+ generator.target = opts.target;
299
+ }
300
+ return generator;
301
+ } as GeneratorFactory<TraceHelperOpts>;
@@ -49,6 +49,34 @@ export function crossEntitySpecifier(
49
49
  return withExt(`${prefix}${toEntity}`, extStyle);
50
50
  }
51
51
 
52
+ /**
53
+ * Module specifier to import a value-object's emitted module (`<VO>.ts`) from a
54
+ * file that lives in `fromPkg`. Value objects are emitted by the entity-file
55
+ * generator into the SAME target as entities, so this is the same same-target,
56
+ * package- + extStyle-aware resolution that FK references use.
57
+ *
58
+ * This is the SINGLE source of truth shared by the three places that reference a
59
+ * VO — the field's TS type (inferred-types), its runtime Zod schema
60
+ * (zod-validators), and its Drizzle `.$type<>()` (drizzle-schema). They MUST
61
+ * resolve a given VO to the identical module or they would import two distinct
62
+ * symbols; routing all three through this function makes divergence impossible.
63
+ *
64
+ * `voPkg` is looked up from `packageOf` (which includes `object.value` nodes —
65
+ * `root.objects()` returns every `TYPE_OBJECT` child). An unknown VO falls back
66
+ * to `fromPkg`, yielding a same-dir `./<VO>` specifier (correct for flat layout
67
+ * and for a same-package reference).
68
+ */
69
+ export function valueObjectModuleSpecifier(
70
+ voName: string,
71
+ packageOf: ReadonlyMap<string, string | undefined>,
72
+ fromPkg: string | undefined,
73
+ layout: OutputLayout,
74
+ extStyle: ExtStyle,
75
+ ): string {
76
+ const voPkg = packageOf.has(voName) ? packageOf.get(voName) : fromPkg;
77
+ return crossEntitySpecifier(layout, fromPkg, voPkg, voName, extStyle);
78
+ }
79
+
52
80
  /** Barrel (at outDir root) re-export specifier for an entity.
53
81
  * Equivalent to crossEntitySpecifier with fromPkg=undefined (barrel is always at root). */
54
82
  export function barrelEntrySpecifier(
package/src/index.ts CHANGED
@@ -9,8 +9,17 @@ export type { RunGenOpts, RunGenResult } from "./runner.js";
9
9
  export type { Generator, GenContext, EmittedFile, GeneratorFactory } from "./generator.js";
10
10
  export { perEntity, oncePerRun } from "./generator.js";
11
11
 
12
- export type { MetaobjectsGenConfig, NormalizedMetaobjectsGenConfig, ResolvedGenConfig, Dialect, ExtStyle, ColumnNamingStrategy, MetaDataTypeProvider } from "./metaobjects-config.js";
13
- export { defineConfig, normalizeConfig } from "./metaobjects-config.js";
12
+ // ADR-0021 D3 stable-name generator registry + discoverability surface.
13
+ export {
14
+ generatorRegistry,
15
+ listGenerators,
16
+ getGenerator,
17
+ } from "./generator-registry.js";
18
+ export type { GeneratorRegistryEntry, GeneratorTier } from "./generator-registry.js";
19
+
20
+ export type { MetaobjectsGenConfig, NormalizedMetaobjectsGenConfig, ResolvedGenConfig, Dialect, ExtStyle, ColumnNamingStrategy, MetaDataTypeProvider, GeneratorSpec, DocsConfig, ResolvedDocsConfig, DocsSurface, ApiSurface } from "./metaobjects-config.js";
21
+ export { defineConfig, normalizeConfig, resolveGenerators, resolveDocsConfig } from "./metaobjects-config.js";
22
+ export { apiLabel } from "./generators/api-label.js";
14
23
 
15
24
  export type { ColumnSpec, DefaultExpr } from "./column-mapper.js";
16
25
  export { mapColumnType } from "./column-mapper.js";
@@ -34,7 +43,7 @@ export type {
34
43
  export { decideAndWrite, GitMissingError } from "./overwrite-policy.js";
35
44
 
36
45
  export { CodegenError } from "./errors.js";
37
- export { GENERATED_HEADER, EXTRA_SUFFIX, DEFAULT_OUT_DIR } from "./constants.js";
46
+ export { GENERATED_HEADER, EXTRA_SUFFIX, DEFAULT_OUT_DIR, CODEGEN_ATTR_EMIT_TANSTACK, CODEGEN_ATTR_EMIT_GRID, CODEGEN_ATTR_EMIT_FORM, CODEGEN_ATTR_EMIT_ROUTES } from "./constants.js";
38
47
 
39
48
  export { formatTs } from "./format.js";
40
49
 
@@ -42,9 +51,49 @@ export { pluralize, columnNameFromField, tableNameFromEntity, viewNameFromProjec
42
51
 
43
52
  export { packageToPath, entityOutputPath, crossEntitySpecifier, barrelEntrySpecifier, relativeModuleSpecifier, entityModuleSpecifier, siblingSpecifier, barrelModuleSpecifier } from "./import-path.js";
44
53
  export type { OutputLayout, ResolvedTarget } from "./import-path.js";
54
+ export {
55
+ docPageOutputPath,
56
+ docPageHref,
57
+ docPageNode,
58
+ effectivePackage,
59
+ assertNoDuplicateDocPaths,
60
+ } from "./docs-paths.js";
61
+ export type { DocPageNode, DocPagePlacement } from "./docs-paths.js";
45
62
 
46
63
  export { isProjection, isWriteThrough } from "./projection/projection-detector.js";
47
64
  export { isAbstract, emitsInstanceArtifacts, emitsWriteArtifacts } from "./instance-artifacts.js";
65
+ // FR-017 TPH helpers — used by the per-framework codegen packages (tanstack,
66
+ // react) to dispatch polymorphic/per-subtype emission and skip subtype files.
67
+ export { isTphDiscriminatorBase, tphConcreteSubtypes, collectTphSubtypeFields, tphPlan, tphRouteSegment } from "./templates/tph-discriminator.js";
68
+ export type { TphPlan, TphSubtypePlan } from "./templates/tph-discriminator.js";
69
+ export { isTphSubtype, tphDiscriminatorPin } from "./templates/zod-validators.js";
70
+
71
+ // Built-in template render functions — the composition seam for adopters who
72
+ // want to call a built-in template, then post-process / append to its output
73
+ // from their own Generator (added to `generators: [...]`) WITHOUT forking the
74
+ // template. Mirrors the `renderZodValidators` export. Each is also reachable via
75
+ // a dedicated subpath (e.g. `@metaobjectsdev/codegen-ts/templates/entity-file`).
76
+ export { renderEntityFile } from "./templates/entity-file.js";
77
+ export type { RenderEntityFileOpts } from "./templates/entity-file.js";
78
+ export { renderZodValidators } from "./templates/zod-validators.js";
79
+ export { renderDrizzleSchema } from "./templates/drizzle-schema.js";
80
+ export {
81
+ renderInferredTypes,
82
+ renderEnumTypeAliases,
83
+ renderValueObjectInterface,
84
+ enumUnionAliasName,
85
+ enumUnionString,
86
+ } from "./templates/inferred-types.js";
87
+ export { renderBarrel } from "./templates/barrel.js";
88
+ export type { BarrelEntry } from "./templates/barrel.js";
89
+ export { renderFilterType } from "./templates/filter-type.js";
90
+ export { renderFilterAllowlist, renderSortAllowlist } from "./templates/filter-allowlist.js";
91
+ export { renderEntityConstants, resourcePath } from "./templates/entity-constants.js";
92
+ export { renderQueriesFile } from "./templates/queries-file.js";
93
+ export { renderRoutesFile } from "./templates/routes-file.js";
94
+ export { renderValueObjectFile } from "./templates/value-object-file.js";
95
+ export { renderProjectionDecl } from "./templates/projection-decl.js";
96
+ export type { ProjectionDeclOpts } from "./templates/projection-decl.js";
48
97
  export { extractViewSpec } from "./projection/extract-view-spec.js";
49
98
  export type { ExtractContext } from "./projection/extract-view-spec.js";
50
99
  export { emitViewDdl } from "./projection/view-ddl-emit.js";
@@ -74,6 +123,8 @@ export type {
74
123
  IdentityDoc,
75
124
  RelationshipDoc,
76
125
  UsedByDoc,
77
- GeneratedFileDoc,
126
+ ConstraintRow,
78
127
  } from "./generators/docs-data.js";
79
128
  export { buildEntityDocData } from "./generators/docs-data-builder.js";
129
+ export type { TemplateDocData, TemplateOutputPart } from "./generators/template-doc-data.js";
130
+ export { buildTemplateDocData } from "./generators/template-doc-builder.js";
@@ -2,6 +2,19 @@ import { DEFAULT_COLUMN_NAMING_STRATEGY, type ColumnNamingStrategy, type MetaDat
2
2
  import type { Generator } from "./generator.js";
3
3
  import type { ExtStyle } from "./render-context.js";
4
4
  import type { OutputLayout, ResolvedTarget } from "./import-path.js";
5
+ import { generatorRegistry } from "./generator-registry.js";
6
+
7
+ /**
8
+ * A config `generators` entry. Either a typed generator (the primary, fully
9
+ * typed form — `entityFile()`) OR a STABLE-NAME STRING resolved via the
10
+ * {@link generatorRegistry} (e.g. `"entity"`). The string form is the
11
+ * cross-port-consistent selection mechanism (matches C#/Python
12
+ * `--generators entity,routes`); it always uses the generator's DEFAULT
13
+ * options. Adopters needing options use the factory form.
14
+ *
15
+ * ADR-0021 #1 (TS parity).
16
+ */
17
+ export type GeneratorSpec = Generator | string;
5
18
 
6
19
  export type Dialect = "sqlite" | "postgres";
7
20
  /** Re-exported from metadata so codegen-ts consumers see one canonical type. */
@@ -29,12 +42,55 @@ export interface ResolvedGenConfig {
29
42
  dialect: Dialect;
30
43
  /** "flat" (default) — all files in outDir; "package" — files placed in a sub-path derived from each entity's metadata package. */
31
44
  outputLayout?: OutputLayout;
45
+ /** Whether the OPT-IN Hono routes generator (routesFileHono) is active in the
46
+ * run — aggregated by the runner from the suite's `emitsHonoRoutes` markers.
47
+ * api-docs reads this to AUTO-DETECT whether to document the Hono CRUD surface
48
+ * (it otherwise mirrors the default Fastify-only suite). Undefined ⇒ false. */
49
+ includeHonoRoutes?: boolean;
50
+ /**
51
+ * FR-019 / ADR-0026: the module specifier from which an externally-PROVIDED
52
+ * shared enum (`@provided: true` on an abstract package-level `field.enum`) is
53
+ * imported. metaobjects emits NO type for a provided enum — consuming entity
54
+ * files `import { <EnumName> } from "<providedEnumModule>"`. The per-port
55
+ * namespace/module is codegen config, never a metadata attr (ADR-0001). A model
56
+ * that references a provided enum without this set is a codegen-time error.
57
+ */
58
+ providedEnumModule?: string;
32
59
  }
33
60
 
34
61
  export interface MetaobjectsGenConfig extends ResolvedGenConfig {
35
- generators: Generator[];
62
+ /**
63
+ * Generators to run. Each entry is either a typed generator factory result
64
+ * (`entityFile()`) or a stable-name string (`"entity"`) resolved via the
65
+ * registry. Mixed arrays are allowed (`["entity", routesFile()]`). String
66
+ * entries use the generator's default options. ADR-0021 #1.
67
+ */
68
+ generators: GeneratorSpec[];
36
69
  /** How field names map to DB column names when @dbColumn is omitted. Defaults to "snake_case". */
37
70
  columnNamingStrategy?: ColumnNamingStrategy;
71
+ /**
72
+ * Auto-pluralize the Drizzle collection (table) variable name derived from
73
+ * each entity (`AgentConfig` → `agentConfigs`). Defaults to `true`. Set
74
+ * `false` to keep collection vars singular. Per-entity exceptions go in
75
+ * {@link collectionNameOverrides}. Naming is a per-port codegen concern
76
+ * (ADR-0001), so this is config — not a metadata attribute — and carries no
77
+ * cross-port conformance cost.
78
+ */
79
+ pluralizeCollections?: boolean;
80
+ /**
81
+ * Per-entity exact collection-var-name overrides, keyed by the bare entity
82
+ * name. Wins over {@link pluralizeCollections} — the escape hatch for the
83
+ * handful of tables a global rule gets wrong
84
+ * (e.g. `{ AuditLog: "auditLog", LlmTierConfig: "llmTierConfig" }`).
85
+ */
86
+ collectionNameOverrides?: Record<string, string>;
87
+ /**
88
+ * Drizzle timestamp column mode. "string" (default) types timestamp columns as
89
+ * ISO-8601 strings (matches the generated Zod + cross-port wire contract); "date"
90
+ * uses drizzle's native JS-Date mode (for consumers whose hand-written code works
91
+ * with `Date`).
92
+ */
93
+ timestampMode?: "date" | "string";
38
94
  /** Path prefix applied to generated route registrations + hook fetch URLs. Defaults to "". */
39
95
  apiPrefix?: string;
40
96
  /**
@@ -46,6 +102,8 @@ export interface MetaobjectsGenConfig extends ResolvedGenConfig {
46
102
  * governs the shape, mirroring the cross-port `emitAbstractShapes` option.
47
103
  */
48
104
  emitAbstractShapes?: boolean;
105
+ /** Docs-output config consumed by the `meta docs` door. See {@link DocsConfig}. */
106
+ docs?: DocsConfig;
49
107
  /** Named output destinations. Generators reference one via `target`. */
50
108
  targets?: Record<string, TargetConfig>;
51
109
  /** importBase for the default target (top-level outDir). */
@@ -63,14 +121,63 @@ export interface MetaobjectsGenConfig extends ResolvedGenConfig {
63
121
  * `targets` is Omitted from the base so it can narrow from the user-facing
64
122
  * TargetConfig to the fully-resolved ResolvedTarget (incompatible under
65
123
  * exactOptionalPropertyTypes otherwise). */
66
- export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfig, "targets"> {
124
+ export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfig, "targets" | "generators"> {
125
+ /** Fully resolved — every string spec has been mapped to its factory result. */
126
+ generators: Generator[];
67
127
  columnNamingStrategy: ColumnNamingStrategy;
128
+ pluralizeCollections: boolean;
129
+ collectionNameOverrides: Record<string, string>;
130
+ timestampMode: "date" | "string";
68
131
  apiPrefix: string;
69
132
  emitAbstractShapes: boolean;
70
133
  outputLayout: OutputLayout;
71
134
  targets: Record<string, ResolvedTarget>;
72
135
  }
73
136
 
137
+ export type DocsSurface = "model" | "api";
138
+
139
+ export interface ApiSurface {
140
+ lang: string;
141
+ subDir: string;
142
+ baseUrl?: string;
143
+ }
144
+
145
+ /** The single docs-output config: where ALL doc surfaces go, how pages are laid
146
+ * out, and which surfaces to emit. Read by the `meta docs` door (and, when the
147
+ * api surface fans out, by each port's docs command). */
148
+ export interface DocsConfig {
149
+ outDir?: string;
150
+ layout?: OutputLayout;
151
+ baseUrl?: string;
152
+ surfaces?: DocsSurface[];
153
+ apiSurfaces?: ApiSurface[];
154
+ }
155
+
156
+ export interface ResolvedDocsConfig {
157
+ outDir: string;
158
+ layout: OutputLayout;
159
+ baseUrl: string;
160
+ surfaces: DocsSurface[];
161
+ apiSurfaces: ApiSurface[];
162
+ }
163
+
164
+ /** Merge the config `docs:` block with CLI overrides over documented defaults.
165
+ * `fallbackLayout` is the project's `outputLayout` so docs default to the same
166
+ * page placement as codegen when `docs.layout` is unset. */
167
+ export function resolveDocsConfig(
168
+ block: DocsConfig | undefined,
169
+ cli: Partial<ResolvedDocsConfig>,
170
+ fallbackLayout: OutputLayout,
171
+ ): ResolvedDocsConfig {
172
+ return {
173
+ outDir: cli.outDir ?? block?.outDir ?? "./docs",
174
+ layout: cli.layout ?? block?.layout ?? fallbackLayout,
175
+ baseUrl: cli.baseUrl ?? block?.baseUrl ?? "",
176
+ surfaces: cli.surfaces ?? block?.surfaces ?? ["model", "api"],
177
+ apiSurfaces: cli.apiSurfaces ?? block?.apiSurfaces ?? [{ lang: "ts", subDir: "api" }],
178
+ };
179
+ }
180
+
74
181
  /** Identity passthrough; exists for IDE type-inference + autocomplete. */
75
182
  export function defineConfig(config: MetaobjectsGenConfig): MetaobjectsGenConfig {
76
183
  return config;
@@ -102,11 +209,48 @@ export function resolveTargets(config: MetaobjectsGenConfig): Record<string, Res
102
209
  return out;
103
210
  }
104
211
 
212
+ /**
213
+ * Materialize the config `generators` array: pass typed generators through
214
+ * untouched and resolve each stable-name string via the {@link generatorRegistry}
215
+ * to its factory result (default options). ADR-0021 #1.
216
+ *
217
+ * Errors:
218
+ * - a NEUTRAL name (`docs`, `mermaid-er`) is owned by `meta docs` (ADR-0021 D1)
219
+ * and is not selectable in the gen suite.
220
+ * - an UNKNOWN name throws listing the available NATIVE names.
221
+ */
222
+ export function resolveGenerators(specs: readonly GeneratorSpec[]): Generator[] {
223
+ return specs.map((spec) => {
224
+ if (typeof spec !== "string") return spec;
225
+ const entry = generatorRegistry[spec];
226
+ if (entry === undefined) {
227
+ const native = Object.values(generatorRegistry)
228
+ .filter((e) => e.tier === "native")
229
+ .map((e) => e.name)
230
+ .sort();
231
+ throw new Error(
232
+ `unknown generator "${spec}". Available native generators: ${native.join(", ")}.`,
233
+ );
234
+ }
235
+ if (entry.tier !== "native") {
236
+ throw new Error(
237
+ `generator "${spec}" is neutral (owned by 'meta docs'); ` +
238
+ `not selectable in the gen suite.`,
239
+ );
240
+ }
241
+ return entry.factory();
242
+ });
243
+ }
244
+
105
245
  /** Apply defaults to a MetaobjectsGenConfig, returning a NormalizedMetaobjectsGenConfig. */
106
246
  export function normalizeConfig(config: MetaobjectsGenConfig): NormalizedMetaobjectsGenConfig {
107
247
  return {
108
248
  ...config,
249
+ generators: resolveGenerators(config.generators),
109
250
  columnNamingStrategy: config.columnNamingStrategy ?? DEFAULT_COLUMN_NAMING_STRATEGY,
251
+ pluralizeCollections: config.pluralizeCollections ?? true,
252
+ collectionNameOverrides: config.collectionNameOverrides ?? {},
253
+ timestampMode: config.timestampMode ?? "string",
110
254
  apiPrefix: config.apiPrefix ?? "",
111
255
  emitAbstractShapes: config.emitAbstractShapes ?? true,
112
256
  outputLayout: config.outputLayout ?? "flat",