@metaobjectsdev/codegen-ts 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/README.md +1 -1
  2. package/dist/column-mapper.d.ts.map +1 -1
  3. package/dist/column-mapper.js +24 -8
  4. package/dist/column-mapper.js.map +1 -1
  5. package/dist/constants.d.ts +8 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +16 -0
  8. package/dist/constants.js.map +1 -1
  9. package/dist/docs-paths.d.ts +58 -0
  10. package/dist/docs-paths.d.ts.map +1 -0
  11. package/dist/docs-paths.js +89 -0
  12. package/dist/docs-paths.js.map +1 -0
  13. package/dist/enum-import.d.ts +14 -0
  14. package/dist/enum-import.d.ts.map +1 -0
  15. package/dist/enum-import.js +35 -0
  16. package/dist/enum-import.js.map +1 -0
  17. package/dist/enum-shared.d.ts +32 -0
  18. package/dist/enum-shared.d.ts.map +1 -0
  19. package/dist/enum-shared.js +83 -0
  20. package/dist/enum-shared.js.map +1 -0
  21. package/dist/generator-registry.d.ts +22 -0
  22. package/dist/generator-registry.d.ts.map +1 -0
  23. package/dist/generator-registry.js +161 -0
  24. package/dist/generator-registry.js.map +1 -0
  25. package/dist/generator.d.ts +6 -0
  26. package/dist/generator.d.ts.map +1 -1
  27. package/dist/generator.js.map +1 -1
  28. package/dist/generators/api-doc-render.d.ts +17 -0
  29. package/dist/generators/api-doc-render.d.ts.map +1 -0
  30. package/dist/generators/api-doc-render.js +431 -0
  31. package/dist/generators/api-doc-render.js.map +1 -0
  32. package/dist/generators/api-docs-file.d.ts +21 -0
  33. package/dist/generators/api-docs-file.d.ts.map +1 -0
  34. package/dist/generators/api-docs-file.js +112 -0
  35. package/dist/generators/api-docs-file.js.map +1 -0
  36. package/dist/generators/api-field-shape.d.ts +39 -0
  37. package/dist/generators/api-field-shape.d.ts.map +1 -0
  38. package/dist/generators/api-field-shape.js +92 -0
  39. package/dist/generators/api-field-shape.js.map +1 -0
  40. package/dist/generators/api-label.d.ts +3 -0
  41. package/dist/generators/api-label.d.ts.map +1 -0
  42. package/dist/generators/api-label.js +8 -0
  43. package/dist/generators/api-label.js.map +1 -0
  44. package/dist/generators/api-model.d.ts +122 -0
  45. package/dist/generators/api-model.d.ts.map +1 -0
  46. package/dist/generators/api-model.js +809 -0
  47. package/dist/generators/api-model.js.map +1 -0
  48. package/dist/generators/docs-data-builder.d.ts +26 -4
  49. package/dist/generators/docs-data-builder.d.ts.map +1 -1
  50. package/dist/generators/docs-data-builder.js +436 -164
  51. package/dist/generators/docs-data-builder.js.map +1 -1
  52. package/dist/generators/docs-data.d.ts +136 -27
  53. package/dist/generators/docs-data.d.ts.map +1 -1
  54. package/dist/generators/docs-data.js +1 -1
  55. package/dist/generators/docs-data.js.map +1 -1
  56. package/dist/generators/docs-file.d.ts +19 -0
  57. package/dist/generators/docs-file.d.ts.map +1 -1
  58. package/dist/generators/docs-file.js +154 -27
  59. package/dist/generators/docs-file.js.map +1 -1
  60. package/dist/generators/entity-file.d.ts.map +1 -1
  61. package/dist/generators/entity-file.js +29 -14
  62. package/dist/generators/entity-file.js.map +1 -1
  63. package/dist/generators/extractor-file.d.ts.map +1 -1
  64. package/dist/generators/extractor-file.js +2 -1
  65. package/dist/generators/extractor-file.js.map +1 -1
  66. package/dist/generators/field-anchor.d.ts +7 -0
  67. package/dist/generators/field-anchor.d.ts.map +1 -0
  68. package/dist/generators/field-anchor.js +23 -0
  69. package/dist/generators/field-anchor.js.map +1 -0
  70. package/dist/generators/index.d.ts +8 -1
  71. package/dist/generators/index.d.ts.map +1 -1
  72. package/dist/generators/index.js +6 -0
  73. package/dist/generators/index.js.map +1 -1
  74. package/dist/generators/mermaid-er.d.ts +14 -0
  75. package/dist/generators/mermaid-er.d.ts.map +1 -1
  76. package/dist/generators/mermaid-er.js +14 -0
  77. package/dist/generators/mermaid-er.js.map +1 -1
  78. package/dist/generators/output-parser-file.d.ts.map +1 -1
  79. package/dist/generators/output-parser-file.js +3 -4
  80. package/dist/generators/output-parser-file.js.map +1 -1
  81. package/dist/generators/output-prompt-file.d.ts.map +1 -1
  82. package/dist/generators/output-prompt-file.js +2 -2
  83. package/dist/generators/output-prompt-file.js.map +1 -1
  84. package/dist/generators/prompt-render-file.d.ts.map +1 -1
  85. package/dist/generators/prompt-render-file.js +3 -4
  86. package/dist/generators/prompt-render-file.js.map +1 -1
  87. package/dist/generators/queries-file.d.ts.map +1 -1
  88. package/dist/generators/queries-file.js +8 -3
  89. package/dist/generators/queries-file.js.map +1 -1
  90. package/dist/generators/render-helper-file.d.ts.map +1 -1
  91. package/dist/generators/render-helper-file.js +2 -2
  92. package/dist/generators/render-helper-file.js.map +1 -1
  93. package/dist/generators/routes-file-hono.d.ts.map +1 -1
  94. package/dist/generators/routes-file-hono.js +5 -1
  95. package/dist/generators/routes-file-hono.js.map +1 -1
  96. package/dist/generators/routes-file.d.ts +3 -0
  97. package/dist/generators/routes-file.d.ts.map +1 -1
  98. package/dist/generators/routes-file.js +6 -1
  99. package/dist/generators/routes-file.js.map +1 -1
  100. package/dist/generators/template-doc-builder.d.ts +19 -0
  101. package/dist/generators/template-doc-builder.d.ts.map +1 -0
  102. package/dist/generators/template-doc-builder.js +220 -0
  103. package/dist/generators/template-doc-builder.js.map +1 -0
  104. package/dist/generators/template-doc-data.d.ts +62 -0
  105. package/dist/generators/template-doc-data.d.ts.map +1 -0
  106. package/dist/generators/template-doc-data.js +16 -0
  107. package/dist/generators/template-doc-data.js.map +1 -0
  108. package/dist/generators/template-payload-tree.d.ts +15 -0
  109. package/dist/generators/template-payload-tree.d.ts.map +1 -0
  110. package/dist/generators/template-payload-tree.js +61 -0
  111. package/dist/generators/template-payload-tree.js.map +1 -0
  112. package/dist/generators/template-source-annotate.d.ts +74 -0
  113. package/dist/generators/template-source-annotate.d.ts.map +1 -0
  114. package/dist/generators/template-source-annotate.js +184 -0
  115. package/dist/generators/template-source-annotate.js.map +1 -0
  116. package/dist/generators/template-source-render.d.ts +24 -0
  117. package/dist/generators/template-source-render.d.ts.map +1 -0
  118. package/dist/generators/template-source-render.js +175 -0
  119. package/dist/generators/template-source-render.js.map +1 -0
  120. package/dist/generators/trace-helper-file.d.ts +9 -0
  121. package/dist/generators/trace-helper-file.d.ts.map +1 -0
  122. package/dist/generators/trace-helper-file.js +196 -0
  123. package/dist/generators/trace-helper-file.js.map +1 -0
  124. package/dist/index.d.ts +29 -4
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +28 -2
  127. package/dist/index.js.map +1 -1
  128. package/dist/metaobjects-config.d.ts +75 -2
  129. package/dist/metaobjects-config.d.ts.map +1 -1
  130. package/dist/metaobjects-config.js +43 -0
  131. package/dist/metaobjects-config.js.map +1 -1
  132. package/dist/naming.d.ts +19 -0
  133. package/dist/naming.d.ts.map +1 -1
  134. package/dist/naming.js +41 -0
  135. package/dist/naming.js.map +1 -1
  136. package/dist/payload-codegen.d.ts.map +1 -1
  137. package/dist/payload-codegen.js +12 -4
  138. package/dist/payload-codegen.js.map +1 -1
  139. package/dist/projection/extract-view-spec.d.ts.map +1 -1
  140. package/dist/projection/extract-view-spec.js +51 -25
  141. package/dist/projection/extract-view-spec.js.map +1 -1
  142. package/dist/relation-resolver.d.ts +16 -0
  143. package/dist/relation-resolver.d.ts.map +1 -1
  144. package/dist/relation-resolver.js +82 -1
  145. package/dist/relation-resolver.js.map +1 -1
  146. package/dist/render-context.d.ts +4 -0
  147. package/dist/render-context.d.ts.map +1 -1
  148. package/dist/render-context.js.map +1 -1
  149. package/dist/render-engine/embedded-templates.generated.d.ts +2 -0
  150. package/dist/render-engine/embedded-templates.generated.d.ts.map +1 -0
  151. package/dist/render-engine/embedded-templates.generated.js +15 -0
  152. package/dist/render-engine/embedded-templates.generated.js.map +1 -0
  153. package/dist/render-engine/framework-provider.d.ts.map +1 -1
  154. package/dist/render-engine/framework-provider.js +26 -13
  155. package/dist/render-engine/framework-provider.js.map +1 -1
  156. package/dist/runner.d.ts.map +1 -1
  157. package/dist/runner.js +17 -0
  158. package/dist/runner.js.map +1 -1
  159. package/dist/templates/docs-file.d.ts +2 -6
  160. package/dist/templates/docs-file.d.ts.map +1 -1
  161. package/dist/templates/docs-file.js +2 -5
  162. package/dist/templates/docs-file.js.map +1 -1
  163. package/dist/templates/drizzle-schema.d.ts.map +1 -1
  164. package/dist/templates/drizzle-schema.js +30 -2
  165. package/dist/templates/drizzle-schema.js.map +1 -1
  166. package/dist/templates/entity-constants.d.ts +7 -0
  167. package/dist/templates/entity-constants.d.ts.map +1 -1
  168. package/dist/templates/entity-constants.js +1 -1
  169. package/dist/templates/entity-constants.js.map +1 -1
  170. package/dist/templates/entity-file.d.ts.map +1 -1
  171. package/dist/templates/entity-file.js +16 -5
  172. package/dist/templates/entity-file.js.map +1 -1
  173. package/dist/templates/enums-file.d.ts +11 -0
  174. package/dist/templates/enums-file.d.ts.map +1 -0
  175. package/dist/templates/enums-file.js +44 -0
  176. package/dist/templates/enums-file.js.map +1 -0
  177. package/dist/templates/extract-delegate-emitter.d.ts.map +1 -1
  178. package/dist/templates/extract-delegate-emitter.js +5 -7
  179. package/dist/templates/extract-delegate-emitter.js.map +1 -1
  180. package/dist/templates/extract-schema-emitter.d.ts.map +1 -1
  181. package/dist/templates/extract-schema-emitter.js +5 -1
  182. package/dist/templates/extract-schema-emitter.js.map +1 -1
  183. package/dist/templates/extractor.d.ts.map +1 -1
  184. package/dist/templates/extractor.js +56 -39
  185. package/dist/templates/extractor.js.map +1 -1
  186. package/dist/templates/field-meta.d.ts.map +1 -1
  187. package/dist/templates/field-meta.js +1 -5
  188. package/dist/templates/field-meta.js.map +1 -1
  189. package/dist/templates/filter-allowlist.d.ts +7 -2
  190. package/dist/templates/filter-allowlist.d.ts.map +1 -1
  191. package/dist/templates/filter-allowlist.js +17 -9
  192. package/dist/templates/filter-allowlist.js.map +1 -1
  193. package/dist/templates/filter-type.d.ts +7 -1
  194. package/dist/templates/filter-type.d.ts.map +1 -1
  195. package/dist/templates/filter-type.js +9 -5
  196. package/dist/templates/filter-type.js.map +1 -1
  197. package/dist/templates/find-templates.d.ts +4 -0
  198. package/dist/templates/find-templates.d.ts.map +1 -0
  199. package/dist/templates/find-templates.js +15 -0
  200. package/dist/templates/find-templates.js.map +1 -0
  201. package/dist/templates/fr010-field-mapping.d.ts +2 -0
  202. package/dist/templates/fr010-field-mapping.d.ts.map +1 -1
  203. package/dist/templates/fr010-field-mapping.js +10 -6
  204. package/dist/templates/fr010-field-mapping.js.map +1 -1
  205. package/dist/templates/inferred-types.d.ts +44 -7
  206. package/dist/templates/inferred-types.d.ts.map +1 -1
  207. package/dist/templates/inferred-types.js +107 -16
  208. package/dist/templates/inferred-types.js.map +1 -1
  209. package/dist/templates/mermaid-er.d.ts +35 -2
  210. package/dist/templates/mermaid-er.d.ts.map +1 -1
  211. package/dist/templates/mermaid-er.js +174 -7
  212. package/dist/templates/mermaid-er.js.map +1 -1
  213. package/dist/templates/output-parser.d.ts.map +1 -1
  214. package/dist/templates/output-parser.js +30 -79
  215. package/dist/templates/output-parser.js.map +1 -1
  216. package/dist/templates/output-prompt.d.ts.map +1 -1
  217. package/dist/templates/output-prompt.js +2 -2
  218. package/dist/templates/output-prompt.js.map +1 -1
  219. package/dist/templates/queries-file.d.ts.map +1 -1
  220. package/dist/templates/queries-file.js +112 -4
  221. package/dist/templates/queries-file.js.map +1 -1
  222. package/dist/templates/queries.d.ts +5 -0
  223. package/dist/templates/queries.d.ts.map +1 -1
  224. package/dist/templates/queries.js +7 -7
  225. package/dist/templates/queries.js.map +1 -1
  226. package/dist/templates/recover-schema-emitter.d.ts +8 -0
  227. package/dist/templates/recover-schema-emitter.d.ts.map +1 -0
  228. package/dist/templates/recover-schema-emitter.js +64 -0
  229. package/dist/templates/recover-schema-emitter.js.map +1 -0
  230. package/dist/templates/relations-block.js +10 -0
  231. package/dist/templates/relations-block.js.map +1 -1
  232. package/dist/templates/render-helper.d.ts.map +1 -1
  233. package/dist/templates/render-helper.js +4 -4
  234. package/dist/templates/render-helper.js.map +1 -1
  235. package/dist/templates/routes-file.d.ts.map +1 -1
  236. package/dist/templates/routes-file.js +183 -6
  237. package/dist/templates/routes-file.js.map +1 -1
  238. package/dist/templates/tph-discriminator.d.ts +56 -0
  239. package/dist/templates/tph-discriminator.d.ts.map +1 -0
  240. package/dist/templates/tph-discriminator.js +180 -0
  241. package/dist/templates/tph-discriminator.js.map +1 -0
  242. package/dist/templates/value-object-file.d.ts +2 -1
  243. package/dist/templates/value-object-file.d.ts.map +1 -1
  244. package/dist/templates/value-object-file.js +32 -4
  245. package/dist/templates/value-object-file.js.map +1 -1
  246. package/dist/templates/zod-validators.d.ts +64 -1
  247. package/dist/templates/zod-validators.d.ts.map +1 -1
  248. package/dist/templates/zod-validators.js +181 -8
  249. package/dist/templates/zod-validators.js.map +1 -1
  250. package/package.json +103 -34
  251. package/src/column-mapper.ts +25 -8
  252. package/src/constants.ts +18 -0
  253. package/src/docs-paths.ts +128 -0
  254. package/src/enum-import.ts +43 -0
  255. package/src/enum-shared.ts +95 -0
  256. package/src/generator-registry.ts +204 -0
  257. package/src/generator.ts +6 -0
  258. package/src/generators/api-doc-render.ts +572 -0
  259. package/src/generators/api-docs-file.ts +146 -0
  260. package/src/generators/api-field-shape.ts +114 -0
  261. package/src/generators/api-label.ts +7 -0
  262. package/src/generators/api-model.ts +1067 -0
  263. package/src/generators/docs-data-builder.ts +479 -185
  264. package/src/generators/docs-data.ts +139 -28
  265. package/src/generators/docs-file.ts +205 -39
  266. package/src/generators/entity-file.ts +31 -15
  267. package/src/generators/extractor-file.ts +2 -1
  268. package/src/generators/field-anchor.ts +24 -0
  269. package/src/generators/index.ts +8 -1
  270. package/src/generators/mermaid-er.ts +14 -0
  271. package/src/generators/output-parser-file.ts +3 -4
  272. package/src/generators/output-prompt-file.ts +2 -1
  273. package/src/generators/prompt-render-file.ts +3 -4
  274. package/src/generators/queries-file.ts +9 -3
  275. package/src/generators/render-helper-file.ts +2 -1
  276. package/src/generators/routes-file-hono.ts +5 -1
  277. package/src/generators/routes-file.ts +7 -1
  278. package/src/generators/template-doc-builder.ts +306 -0
  279. package/src/generators/template-doc-data.ts +85 -0
  280. package/src/generators/template-payload-tree.ts +71 -0
  281. package/src/generators/template-source-annotate.ts +290 -0
  282. package/src/generators/template-source-render.ts +203 -0
  283. package/src/generators/trace-helper-file.ts +301 -0
  284. package/src/index.ts +55 -4
  285. package/src/metaobjects-config.ts +117 -2
  286. package/src/naming.ts +48 -0
  287. package/src/payload-codegen.ts +14 -3
  288. package/src/projection/extract-view-spec.ts +49 -30
  289. package/src/relation-resolver.ts +103 -1
  290. package/src/render-context.ts +4 -0
  291. package/src/render-engine/embedded-templates.generated.ts +14 -0
  292. package/src/render-engine/framework-provider.ts +25 -11
  293. package/src/runner.ts +21 -0
  294. package/src/templates/docs-file.ts +2 -9
  295. package/src/templates/drizzle-schema.ts +31 -1
  296. package/src/templates/entity-constants.ts +1 -1
  297. package/src/templates/entity-file.ts +16 -5
  298. package/src/templates/enums-file.ts +50 -0
  299. package/src/templates/extract-delegate-emitter.ts +5 -6
  300. package/src/templates/extractor.ts +68 -38
  301. package/src/templates/field-meta.ts +0 -6
  302. package/src/templates/filter-allowlist.ts +17 -10
  303. package/src/templates/filter-type.ts +8 -6
  304. package/src/templates/find-templates.ts +15 -0
  305. package/src/templates/fr010-field-mapping.ts +10 -8
  306. package/src/templates/inferred-types.ts +108 -18
  307. package/src/templates/mermaid-er.ts +176 -8
  308. package/src/templates/output-parser.ts +30 -79
  309. package/src/templates/output-prompt.ts +2 -1
  310. package/src/templates/queries-file.ts +132 -3
  311. package/src/templates/queries.ts +15 -7
  312. package/src/templates/relations-block.ts +17 -0
  313. package/src/templates/render-helper.ts +4 -3
  314. package/src/templates/routes-file.ts +233 -6
  315. package/src/templates/tph-discriminator.ts +232 -0
  316. package/src/templates/value-object-file.ts +38 -4
  317. package/src/templates/zod-validators.ts +204 -7
  318. package/templates/api/agent-api.md.mustache +30 -0
  319. package/templates/api/entity-api.md.mustache +69 -0
  320. package/templates/api/index.md.mustache +21 -0
  321. package/templates/docs/entity-page.md.mustache +33 -21
  322. package/templates/docs/template-page.md.mustache +56 -0
  323. 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>;
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,10 +42,30 @@ 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;
38
71
  /** Path prefix applied to generated route registrations + hook fetch URLs. Defaults to "". */
@@ -46,6 +79,8 @@ export interface MetaobjectsGenConfig extends ResolvedGenConfig {
46
79
  * governs the shape, mirroring the cross-port `emitAbstractShapes` option.
47
80
  */
48
81
  emitAbstractShapes?: boolean;
82
+ /** Docs-output config consumed by the `meta docs` door. See {@link DocsConfig}. */
83
+ docs?: DocsConfig;
49
84
  /** Named output destinations. Generators reference one via `target`. */
50
85
  targets?: Record<string, TargetConfig>;
51
86
  /** importBase for the default target (top-level outDir). */
@@ -63,7 +98,9 @@ export interface MetaobjectsGenConfig extends ResolvedGenConfig {
63
98
  * `targets` is Omitted from the base so it can narrow from the user-facing
64
99
  * TargetConfig to the fully-resolved ResolvedTarget (incompatible under
65
100
  * exactOptionalPropertyTypes otherwise). */
66
- export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfig, "targets"> {
101
+ export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfig, "targets" | "generators"> {
102
+ /** Fully resolved — every string spec has been mapped to its factory result. */
103
+ generators: Generator[];
67
104
  columnNamingStrategy: ColumnNamingStrategy;
68
105
  apiPrefix: string;
69
106
  emitAbstractShapes: boolean;
@@ -71,6 +108,50 @@ export interface NormalizedMetaobjectsGenConfig extends Omit<MetaobjectsGenConfi
71
108
  targets: Record<string, ResolvedTarget>;
72
109
  }
73
110
 
111
+ export type DocsSurface = "model" | "api";
112
+
113
+ export interface ApiSurface {
114
+ lang: string;
115
+ subDir: string;
116
+ baseUrl?: string;
117
+ }
118
+
119
+ /** The single docs-output config: where ALL doc surfaces go, how pages are laid
120
+ * out, and which surfaces to emit. Read by the `meta docs` door (and, when the
121
+ * api surface fans out, by each port's docs command). */
122
+ export interface DocsConfig {
123
+ outDir?: string;
124
+ layout?: OutputLayout;
125
+ baseUrl?: string;
126
+ surfaces?: DocsSurface[];
127
+ apiSurfaces?: ApiSurface[];
128
+ }
129
+
130
+ export interface ResolvedDocsConfig {
131
+ outDir: string;
132
+ layout: OutputLayout;
133
+ baseUrl: string;
134
+ surfaces: DocsSurface[];
135
+ apiSurfaces: ApiSurface[];
136
+ }
137
+
138
+ /** Merge the config `docs:` block with CLI overrides over documented defaults.
139
+ * `fallbackLayout` is the project's `outputLayout` so docs default to the same
140
+ * page placement as codegen when `docs.layout` is unset. */
141
+ export function resolveDocsConfig(
142
+ block: DocsConfig | undefined,
143
+ cli: Partial<ResolvedDocsConfig>,
144
+ fallbackLayout: OutputLayout,
145
+ ): ResolvedDocsConfig {
146
+ return {
147
+ outDir: cli.outDir ?? block?.outDir ?? "./docs",
148
+ layout: cli.layout ?? block?.layout ?? fallbackLayout,
149
+ baseUrl: cli.baseUrl ?? block?.baseUrl ?? "",
150
+ surfaces: cli.surfaces ?? block?.surfaces ?? ["model", "api"],
151
+ apiSurfaces: cli.apiSurfaces ?? block?.apiSurfaces ?? [{ lang: "ts", subDir: "api" }],
152
+ };
153
+ }
154
+
74
155
  /** Identity passthrough; exists for IDE type-inference + autocomplete. */
75
156
  export function defineConfig(config: MetaobjectsGenConfig): MetaobjectsGenConfig {
76
157
  return config;
@@ -102,10 +183,44 @@ export function resolveTargets(config: MetaobjectsGenConfig): Record<string, Res
102
183
  return out;
103
184
  }
104
185
 
186
+ /**
187
+ * Materialize the config `generators` array: pass typed generators through
188
+ * untouched and resolve each stable-name string via the {@link generatorRegistry}
189
+ * to its factory result (default options). ADR-0021 #1.
190
+ *
191
+ * Errors:
192
+ * - a NEUTRAL name (`docs`, `mermaid-er`) is owned by `meta docs` (ADR-0021 D1)
193
+ * and is not selectable in the gen suite.
194
+ * - an UNKNOWN name throws listing the available NATIVE names.
195
+ */
196
+ export function resolveGenerators(specs: readonly GeneratorSpec[]): Generator[] {
197
+ return specs.map((spec) => {
198
+ if (typeof spec !== "string") return spec;
199
+ const entry = generatorRegistry[spec];
200
+ if (entry === undefined) {
201
+ const native = Object.values(generatorRegistry)
202
+ .filter((e) => e.tier === "native")
203
+ .map((e) => e.name)
204
+ .sort();
205
+ throw new Error(
206
+ `unknown generator "${spec}". Available native generators: ${native.join(", ")}.`,
207
+ );
208
+ }
209
+ if (entry.tier !== "native") {
210
+ throw new Error(
211
+ `generator "${spec}" is neutral (owned by 'meta docs'); ` +
212
+ `not selectable in the gen suite.`,
213
+ );
214
+ }
215
+ return entry.factory();
216
+ });
217
+ }
218
+
105
219
  /** Apply defaults to a MetaobjectsGenConfig, returning a NormalizedMetaobjectsGenConfig. */
106
220
  export function normalizeConfig(config: MetaobjectsGenConfig): NormalizedMetaobjectsGenConfig {
107
221
  return {
108
222
  ...config,
223
+ generators: resolveGenerators(config.generators),
109
224
  columnNamingStrategy: config.columnNamingStrategy ?? DEFAULT_COLUMN_NAMING_STRATEGY,
110
225
  apiPrefix: config.apiPrefix ?? "",
111
226
  emitAbstractShapes: config.emitAbstractShapes ?? true,
package/src/naming.ts CHANGED
@@ -63,5 +63,53 @@ export function variableNameFromEntity(entityName: string): string {
63
63
  return pluralize(toCamelCase(entityName.charAt(0).toLowerCase() + entityName.slice(1)));
64
64
  }
65
65
 
66
+ // ---------------------------------------------------------------------------
67
+ // Generated CRUD-helper symbol names (single source of truth).
68
+ //
69
+ // The queries generator (templates/queries.ts) emits one exported async function
70
+ // per CRUD verb whose NAME is derived purely from the entity name. These helpers
71
+ // are the canonical spelling of those names so anything that needs to REFER to a
72
+ // generated symbol (e.g. the api-docs ApiModel builder) derives the exact same
73
+ // string the generator emits — no drift, no invented names. The queries template
74
+ // itself uses these so the two can never disagree.
75
+ // ---------------------------------------------------------------------------
76
+
77
+ /** Generated read-by-PK helper name: `find<Entity>ById`. */
78
+ export function findByIdFnName(entityName: string): string {
79
+ return `find${entityName}ById`;
80
+ }
81
+
82
+ /** Generated list helper name: `list<Plural>` (PascalCase plural). */
83
+ export function listFnName(entityName: string): string {
84
+ return `list${pluralize(entityName)}`;
85
+ }
86
+
87
+ /** Generated create helper name: `create<Entity>`. */
88
+ export function createFnName(entityName: string): string {
89
+ return `create${entityName}`;
90
+ }
91
+
92
+ /** Generated update helper name: `update<Entity>`. */
93
+ export function updateFnName(entityName: string): string {
94
+ return `update${entityName}`;
95
+ }
96
+
97
+ /** Generated delete-by-PK helper name: `delete<Entity>ById`. */
98
+ export function deleteByIdFnName(entityName: string): string {
99
+ return `delete${entityName}ById`;
100
+ }
101
+
102
+ /**
103
+ * Generated Fastify route-registrar name: camelCase `<entity>Routes`. The routes
104
+ * generator (templates/routes-file.ts) emits a single exported
105
+ * `export async function <entity>Routes(fastify)` that mounts the entity's CRUD
106
+ * verb set — this is the symbol an adopter imports to wire the endpoints. Kept
107
+ * here as the single source of truth so the routes template and the api-docs
108
+ * ApiModel builder derive the exact same spelling (no drift).
109
+ */
110
+ export function routesHandlerName(entityName: string): string {
111
+ return `${entityName.charAt(0).toLowerCase()}${entityName.slice(1)}Routes`;
112
+ }
113
+
66
114
  // Re-exported here for callers that import from codegen-ts's naming module.
67
115
  export { toKebabCase };
@@ -23,6 +23,8 @@ import {
23
23
  TEMPLATE_ATTR_PAYLOAD_REF,
24
24
  TEMPLATE_ATTR_TEXT_REF,
25
25
  TEMPLATE_ATTR_FORMAT,
26
+ refMatchesObject,
27
+ stripPackage,
26
28
  } from "@metaobjectsdev/metadata";
27
29
  import { enumValues } from "./enum-meta.js";
28
30
  import { enumUnionAliasName, enumUnionString } from "./templates/inferred-types.js";
@@ -45,7 +47,9 @@ const SCALAR_TS: Record<string, string> = {
45
47
  };
46
48
 
47
49
  function findObject(root: MetaData, name: string): MetaData | undefined {
48
- return root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === name);
50
+ // FR-032 @payloadRef/@responseRef are FQN after the desugar/sweep; match on
51
+ // the effective FQN resolution key (with bare back-compat).
52
+ return root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
49
53
  }
50
54
 
51
55
  /**
@@ -168,14 +172,21 @@ export function generateRenderHandle(root: MetaData, templateName: string): stri
168
172
  const tmpl = root.ownChildren().find((c) => c.type === TYPE_TEMPLATE && c.name === templateName);
169
173
  if (!tmpl) throw new Error(`template "${templateName}" not found`);
170
174
  const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
175
+ // FR-032 — @payloadRef is an FQN after the desugar/sweep; the generated TS
176
+ // TYPE NAME is the resolved value-object's bare name (an FQN like
177
+ // `acme::ai::Payload` is not a valid TS identifier). Fall back to the last
178
+ // `::`-segment when the VO is not in this root (defensive).
179
+ const payloadType =
180
+ (typeof payloadRef === "string" ? findObject(root, payloadRef)?.name : undefined) ??
181
+ (typeof payloadRef === "string" ? stripPackage(payloadRef) : String(payloadRef));
171
182
  const textRef = tmpl.ownAttr(TEMPLATE_ATTR_TEXT_REF);
172
183
  const format = (tmpl.ownAttr(TEMPLATE_ATTR_FORMAT) as string | undefined) ?? "text";
173
184
  const fn = `render${pascal(templateName)}`;
174
185
  return [
175
186
  `import { render, type Provider } from "@metaobjectsdev/render";`,
176
- `import type { ${payloadRef} } from "./payloads.js";`,
187
+ `import type { ${payloadType} } from "./payloads.js";`,
177
188
  ``,
178
- `export function ${fn}(payload: ${payloadRef}, provider: Provider): string {`,
189
+ `export function ${fn}(payload: ${payloadType}, provider: Provider): string {`,
179
190
  ` return render({ ref: ${JSON.stringify(textRef)}, payload, format: ${JSON.stringify(format)}, provider });`,
180
191
  `}`,
181
192
  ``,