@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
@@ -8,7 +8,7 @@
8
8
  // for `docs/entity-page.md` (or any of the partials) reference these keys.
9
9
  //
10
10
  // `EntityDocData` is the **Markdown-flavored** data shape — it intentionally
11
- // mixes raw structural fields (entity, validation, generated) with
11
+ // mixes raw structural fields (entity, constraints) with
12
12
  // **pre-rendered Markdown fragments** so cross-port walk functions (TS,
13
13
  // Python, C#, Java, Kotlin) don't have to re-derive the same escaping rules
14
14
  // (pipe-inside-cell escapes, backtick wrapping, identity bullets,
@@ -40,20 +40,30 @@
40
40
  // `{{#identities.0}}` for the same effect, at which point the flag fields
41
41
  // can be deprecated.
42
42
 
43
- /** One row in the Storage table — fully-rendered as a single Markdown table
44
- * row. The escaping rules for pipe-inside-cell are non-trivial and live in
45
- * the data builder, not the template, so templates stay trivial and the
46
- * cross-port walk functions don't have to re-derive the rules. */
43
+ /** One row in the NEUTRAL Storage table — the physical persistence MAPPING,
44
+ * fully-rendered as a single Markdown table row. ADR-0020: the Storage
45
+ * section documents declared physical facts only (column name neutral
46
+ * physical type nullable key) and makes NO language assumption — it does
47
+ * NOT carry a TypeScript type or any ORM DDL. Its value-add over the
48
+ * Constraints table is the field→column name mapping + any physical
49
+ * `@dbColumnType` override + the key role. The pre-rendered `rowLine` keeps
50
+ * templates trivial and means cross-port walk functions don't re-derive the
51
+ * Markdown escaping. */
47
52
  export interface StorageFieldDoc {
48
53
  name: string; // raw field name (without backticks)
49
- /** @markdown — already escaped TS type, with backticks. */
50
- tsTypeCell: string;
51
- /** @markdown — already escaped SQL expression, wrapped in backticks. */
52
- sqlExprCell: string;
53
- /** @markdown already-formatted constraints text. */
54
- constraintsCell: string;
54
+ /** @markdown — physical column name (field's `@column` if set, else the
55
+ * field name), wrapped in backticks. */
56
+ columnCell: string;
57
+ /** @markdown — neutral physical type (declared `@dbColumnType` override
58
+ * uppercased, else the field's logical type), wrapped in backticks. */
59
+ typeCell: string;
60
+ /** @markdown — "yes" if the field is nullable (not required, not a PK),
61
+ * else "no". */
62
+ nullableCell: string;
63
+ /** @markdown — key role: "primary key", "foreign key → `Target`", or "". */
64
+ keyCell: string;
55
65
  /** @markdown — pre-rendered full Markdown table row, e.g.
56
- * "| `id` | `number` | `integer(\"id\")` | primary key |"
66
+ * "| `id` | `long` | no | primary key |"
57
67
  * Templates emit this verbatim via `{{{rowLine}}}`. */
58
68
  rowLine: string;
59
69
  }
@@ -77,9 +87,74 @@ export interface UsedByDoc {
77
87
  bullet: string;
78
88
  }
79
89
 
80
- export interface GeneratedFileDoc {
81
- filename: string; // "Author.ts"
82
- description: string; // "Drizzle table, Zod schemas, ..."
90
+ /** One row in the unified Fields table — merges the old Storage + Constraints
91
+ * cells into a single per-field row. Cells are pre-rendered Markdown so
92
+ * templates stay trivial. An empty cell is "".
93
+ *
94
+ * Replaces the previous Storage + Constraints split which duplicated facts
95
+ * (field name, logical type, key role, required-vs-nullable). Following the
96
+ * research synthesis: domain-model docs (FHIR, GitHub GraphQL Objects,
97
+ * Schema.org) all surface one Fields/Properties table per resource. */
98
+ export interface FieldDoc {
99
+ field: string; // raw field name (without backticks/anchor)
100
+ /** @markdown — anchored, badge-prefixed field cell:
101
+ * `<a id="field-id"></a>🔑 \`id\`` (PK)
102
+ * `<a id="field-userId"></a>🔗 \`userId\`` (FK)
103
+ * `<a id="field-name"></a>\`name\`` (plain) */
104
+ fieldCell: string;
105
+ /** @markdown — neutral logical type; for FK fields, suffixed with the
106
+ * cross-linked target — e.g. "`int` → [`User`](User.md)". */
107
+ typeCell: string;
108
+ /** @markdown — "yes" / "" — whether the field is required (or a PK). */
109
+ requiredCell: string;
110
+ /** @markdown — physical persistence info, ONLY when interesting:
111
+ * `@column` override that differs from the field name → "`UserId`"
112
+ * `@dbColumnType` set → "`UserId` `UUID`" (or "`Data` `JSONB`" when
113
+ * the field name happens to match the column)
114
+ * Empty when field name == column AND no @dbColumnType override. */
115
+ storageCell: string;
116
+ /** @markdown — all the rules: validators (regex/length/numeric), default,
117
+ * enum value set, extends EnumName, references, unique. Joined by " · ". */
118
+ rulesCell: string;
119
+ }
120
+
121
+ /** One expanded per-field detail entry — rendered as a sub-section below
122
+ * the at-a-glance Fields table. ONLY emitted for fields with non-trivial
123
+ * content (@description / @summary / validators / extends-enum / FK ref /
124
+ * default / column-override). Skipped for plain typed fields with nothing
125
+ * extra to surface — keeps the entity page from ballooning with empty
126
+ * stubs.
127
+ *
128
+ * The `block` is fully pre-rendered Markdown so the template is trivial
129
+ * (`{{{block}}}` per row) and cross-port walks emit consistent output
130
+ * without re-implementing the layout.
131
+ *
132
+ * Authoring path: any field that wants surface in this section just sets
133
+ * `@description` and/or `@summary` in the metadata YAML. Mirrors the
134
+ * per-entity pattern. */
135
+ export interface FieldDetailDoc {
136
+ field: string; // raw field name (without backticks)
137
+ /** @markdown — the full per-field block, headed by `### \`fieldName\``
138
+ * and followed by italic summary, description paragraph, validator
139
+ * bullets, type/FK/extends/default lines. */
140
+ block: string;
141
+ }
142
+
143
+ /** Deprecated alias for {@link FieldDoc} — kept for back-compat in case any
144
+ * external template author destructured the old ConstraintRow shape.
145
+ * @deprecated use FieldDoc */
146
+ export interface ConstraintRow {
147
+ field: string; // raw field name (without backticks)
148
+ /** @markdown — "yes" / "" — whether the field is required (or a PK). */
149
+ required: string;
150
+ /** @markdown — neutral logical type cell, e.g. "`string`", "`enum`",
151
+ * "`Address[]`". */
152
+ type: string;
153
+ /** @markdown — size/range limits, e.g. "maxLength: 200" — "" if none. */
154
+ limits: string;
155
+ /** @markdown — declared rules: enum value sets, patterns, validators,
156
+ * uniqueness, default — "" if none. */
157
+ rules: string;
83
158
  }
84
159
 
85
160
  export interface EntityDocData {
@@ -96,6 +171,7 @@ export interface EntityDocData {
96
171
  source?: string; // "meta.blog.json"
97
172
  package?: string; // "acme::blog"
98
173
  description?: string; // raw description text (may be multi-line)
174
+ summary?: string; // raw summary text (single line)
99
175
  };
100
176
 
101
177
  /** @markdown — description as a blockquote (one `> ` per line). Present
@@ -104,16 +180,49 @@ export interface EntityDocData {
104
180
  * constructs. */
105
181
  descriptionQuote?: string;
106
182
 
183
+ /** @markdown — `@summary` rendered as a one-line italic lead-in (e.g.
184
+ * `*Tracks ...*`). Present iff `entity.summary` is set. Distinct from
185
+ * `descriptionQuote` (a blockquote) so an entity that carries BOTH
186
+ * surfaces both — short headline above, expanded paragraph below. */
187
+ summaryLead?: string;
188
+
189
+ /** @markdown — fenced ```mermaid erDiagram block``` showing the focal
190
+ * entity plus its direct in/out FK neighbors (1-hop). Replaces the
191
+ * cognitive load of the whole-model graph with an in-context view.
192
+ * Mirrors the dbdocs pattern. Skipped when the entity has no neighbors. */
193
+ neighborhoodErBlock?: string;
194
+ /** Gate flag for Mustache — true iff `neighborhoodErBlock` is present.
195
+ * See the "Mustache idiom note" at the top of this file. */
196
+ hasNeighborhoodEr?: boolean;
197
+
107
198
  /** @markdown — multi-line preamble block: Type / Source? / Package?, one
108
199
  * per line, in the exact order matching the legacy emitter. Always
109
200
  * present. */
110
201
  preambleHeader: string;
111
202
 
112
- /** Storage section. Present iff the entity has a writable rdb source and
113
- * is NOT object.value. */
203
+ /** Unified Fields section one row per field, merging the per-field facts
204
+ * the old Storage + Constraints tables split between. Always emitted when
205
+ * the entity has any fields. */
206
+ fields: {
207
+ hasFields: boolean;
208
+ rows: FieldDoc[];
209
+ };
210
+
211
+ /** Expanded per-field details — emitted as a "## Field details" section
212
+ * AFTER the at-a-glance Fields table. Skips fields with nothing extra to
213
+ * say (no description, no summary, no validators, no extends, no default,
214
+ * no FK, no column override) so the section doesn't balloon the page. */
215
+ fieldDetails: {
216
+ hasDetails: boolean;
217
+ rows: FieldDetailDoc[];
218
+ };
219
+
220
+ /** @deprecated Storage section. The merged Fields table covers this now;
221
+ * the old shape is still populated for adopters with custom templates that
222
+ * reference it, but new templates should use `fields` instead. */
114
223
  storage?: {
115
- /** @markdown — pre-rendered "| Field | ... |\n|---|---|---|---|" header
116
- * pair. */
224
+ /** @markdown — pre-rendered "| Column | Type | Nullable | Key |\n|---|...|"
225
+ * header pair. */
117
226
  tableHeader: string;
118
227
  rows: StorageFieldDoc[];
119
228
  };
@@ -131,13 +240,14 @@ export interface EntityDocData {
131
240
  /** Present-and-non-empty flag for the relationships section. */
132
241
  hasRelationships?: boolean;
133
242
 
134
- /** Validation section RAW (not Markdown-flavored). Always emitted.
135
- * Custom non-Markdown templates can rely on these fields. */
136
- validation: {
137
- insertSchema: string; // "AuthorInsertSchema"
138
- updateSchema: string; // "AuthorUpdateSchema"
139
- entityFile: string; // "Author.ts"
140
- lower: string; // "author" (lowercased first letter)
243
+ /** @deprecated Constraints section. The merged Fields table covers this
244
+ * now; the old shape is still populated for adopters with custom templates
245
+ * that reference it, but new templates should use `fields` instead. */
246
+ constraints: {
247
+ /** True iff there is at least one row to render (objects always have
248
+ * fields, so this is generally true; gates the section header). */
249
+ hasConstraints: boolean;
250
+ rows: ConstraintRow[];
141
251
  };
142
252
 
143
253
  /** "Used by" — present iff any templates declare `@payloadRef` → this
@@ -149,6 +259,7 @@ export interface EntityDocData {
149
259
  /** Present flag for the storage section. */
150
260
  hasStorage?: boolean;
151
261
 
152
- /** Generated-code section always emitted (at minimum the entity file). */
153
- generated: GeneratedFileDoc[];
262
+ /** Cross-links to this entity's generated-SDK api page, one per api surface
263
+ * (per language). Present only when api surfaces are emitted with the model. */
264
+ apiRefs?: Array<{ label: string; href: string; last?: boolean }>;
154
265
  }
@@ -18,30 +18,72 @@
18
18
  // byte-for-byte. If you're hacking on this and the conformance test
19
19
  // breaks, the refactor is the bug, not the fixture.
20
20
 
21
- import type { MetaObject } from "@metaobjectsdev/metadata";
21
+ import type { MetaObject, MetaRoot } from "@metaobjectsdev/metadata";
22
+ import { TYPE_TEMPLATE, TEMPLATE_SUBTYPE_OUTPUT } from "@metaobjectsdev/metadata";
22
23
  import { render } from "@metaobjectsdev/render";
23
- import type { Generator, GeneratorFactory } from "../generator.js";
24
- import { entityOutputPath } from "../import-path.js";
24
+ import type { Provider } from "@metaobjectsdev/render";
25
+ import type { Generator, GeneratorFactory, EmittedFile } from "../generator.js";
26
+ import {
27
+ docPageOutputPath,
28
+ docPageHref,
29
+ docPageNode,
30
+ apiSurfaceHref,
31
+ assertNoDuplicateDocPaths,
32
+ type DocPageNode,
33
+ type DocPagePlacement,
34
+ } from "../docs-paths.js";
25
35
  import { projectProvider } from "../render-engine/framework-provider.js";
36
+ import { renderMermaidErBlock } from "../templates/mermaid-er.js";
26
37
  import { buildEntityDocData } from "./docs-data-builder.js";
38
+ import { buildTemplateDocData } from "./template-doc-builder.js";
39
+ import type { OutputLayout } from "../import-path.js";
40
+
41
+ // The neutral OVERVIEW/index page. GitHub (and most doc-site renderers) treat a
42
+ // folder's README.md as its landing page, so the model overview lands here.
43
+ const INDEX_FILENAME = "README.md";
44
+ // The index lives at the docs ROOT (package-less) in BOTH layouts — links are
45
+ // computed relative to root via docPageHref.
46
+ const INDEX_NODE: DocPageNode = { name: "README" };
27
47
 
28
48
  export interface DocsFileOpts {
29
49
  filter?: (entity: MetaObject) => boolean;
30
50
  target?: string;
51
+ /** When set, one or more api surfaces are emitted alongside the model surface,
52
+ * each under its own sub-directory (e.g. `"api/ts"`) with a per-language label.
53
+ * docsFile then cross-links each model entity page to ALL its api pages and
54
+ * adds an "API reference" section to the index, every href computed via the
55
+ * shared `apiSurfaceHref` so it resolves relative in BOTH layouts (or absolute
56
+ * when a `baseUrl` is given). ABSENT/empty ⇒ model-only output (byte-identical
57
+ * to historical behaviour). */
58
+ apiSurfaces?: Array<{ label: string; subDir: string; baseUrl?: string }>;
31
59
  }
32
60
 
33
- /** The names of the generators that may emit sibling files for an entity.
34
- * We always list them in the "Generated code" section — adopters cross-
35
- * reference their own metaobjects.config.ts to confirm which are wired in.
36
- * Matches the rc.11 behavior. */
37
- const KNOWN_SIBLING_GENERATORS = new Set([
38
- "queries-file",
39
- "routes-file",
40
- "routes-file-hono",
41
- ]);
42
-
43
61
  const TEMPLATE_REF = "docs/entity-page.md";
62
+ // The NEUTRAL render-contract page emitted per `template.output` node — a
63
+ // sibling artifact, distinct from the entity page (Task 3).
64
+ const TEMPLATE_PAGE_REF = "docs/template-page.md";
44
65
 
66
+ /** Render one docs page, wrapping any engine error with the page ref + output
67
+ * path so a template failure points at the exact page (shared by the entity
68
+ * and template.output emission paths — identical error contract). */
69
+ function renderDocPage(ref: string, payload: unknown, provider: Provider, path: string): string {
70
+ try {
71
+ return render({ ref, payload, provider, format: "markdown" });
72
+ } catch (err) {
73
+ const msg = err instanceof Error ? err.message : String(err);
74
+ throw new Error(`docs-file: failed rendering '${ref}' for '${path}': ${msg}`, {
75
+ cause: err instanceof Error ? err : undefined,
76
+ });
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @deprecated ADR-0021 D1: `meta docs` is the single door for documentation.
82
+ * `docsFile()` stays as the INTERNAL engine that `meta docs` calls — do NOT add
83
+ * it to a `meta gen` config / the public generator surface. It is flagged
84
+ * neutral in the generator registry (`--list`) and is not part of the
85
+ * recommended native `meta gen` suite. Use `meta docs` instead.
86
+ */
45
87
  export const docsFile = function docsFile(opts?: DocsFileOpts): Generator {
46
88
  const generator: Generator = {
47
89
  name: "docs-file",
@@ -52,36 +94,160 @@ export const docsFile = function docsFile(opts?: DocsFileOpts): Generator {
52
94
  const rc = ctx.renderContext;
53
95
  const provider = projectProvider(ctx.projectRoot ?? process.cwd());
54
96
  const layout = ctx.config.outputLayout ?? "flat";
55
- return ctx.loadedRoot.objects().filter(ctx.matches).map((entity: MetaObject) => {
56
- const path = entityOutputPath(layout, entity.package, `${entity.name}.md`);
57
- const payload = buildEntityDocData(entity, {
58
- dialect: rc.dialect,
59
- ...(rc.columnNamingStrategy !== undefined && {
60
- columnNamingStrategy: rc.columnNamingStrategy,
61
- }),
62
- loadedRoot: rc.loadedRoot,
63
- generatorNames: KNOWN_SIBLING_GENERATORS,
64
- });
65
- let content: string;
66
- try {
67
- content = render({
68
- ref: TEMPLATE_REF,
69
- payload,
70
- provider,
71
- format: "markdown",
97
+ // One {label, href} per api surface, every href computed via the shared
98
+ // `apiSurfaceHref` from the FROM page's own output path (so it resolves
99
+ // relative in BOTH layouts, or absolute when the surface declares a
100
+ // baseUrl). The api page for a node lives at `<subDir>/<same placement>`,
101
+ // so the from-path doubles as the page placement. ABSENT/empty surfaces
102
+ // undefined → output byte-identical to historical model-only runs.
103
+ const apiRefsFor = (fromPath: string): Array<{ label: string; href: string }> | undefined =>
104
+ opts?.apiSurfaces?.map((s) => ({ label: s.label, href: apiSurfaceHref(fromPath, s, fromPath) }));
105
+ // Track every (path, fqn) so we can hard-error on a collision (defense
106
+ // against silent doc-page overwrite) AFTER all pages are placed.
107
+ const placements: DocPagePlacement[] = [];
108
+ // Collect the placement nodes of each linkable page so the OVERVIEW/index
109
+ // (README.md) can link them via the SAME docPageHref used everywhere else
110
+ // (links resolve in flat AND package layout). Grouped entity vs template.
111
+ const entityNodes: DocPageNode[] = [];
112
+ const templateNodes: DocPageNode[] = [];
113
+ const files: EmittedFile[] = ctx.loadedRoot
114
+ .objects()
115
+ .filter(ctx.matches)
116
+ .map((entity: MetaObject) => {
117
+ const node = docPageNode(entity);
118
+ entityNodes.push(node);
119
+ const path = docPageOutputPath(layout, node);
120
+ placements.push({ path, fqn: entity.resolutionKey() });
121
+ // Cross-link to the sibling api surfaces, when emitted (shared builder).
122
+ const apiRefs = apiRefsFor(path);
123
+ const payload = buildEntityDocData(entity, {
124
+ dialect: rc.dialect,
125
+ layout,
126
+ ...(rc.columnNamingStrategy !== undefined && {
127
+ columnNamingStrategy: rc.columnNamingStrategy,
128
+ }),
129
+ loadedRoot: rc.loadedRoot,
130
+ ...(apiRefs !== undefined && { apiRefs }),
72
131
  });
73
- } catch (err) {
74
- const msg = err instanceof Error ? err.message : String(err);
75
- throw new Error(
76
- `docs-file: failed rendering '${TEMPLATE_REF}' for '${path}': ${msg}`,
77
- { cause: err instanceof Error ? err : undefined },
78
- );
79
- }
80
- return { path, content };
81
- });
132
+ return { path, content: renderDocPage(TEMPLATE_REF, payload, provider, path) };
133
+ });
134
+
135
+ // ALSO emit one NEUTRAL render-contract page per `template.output` node
136
+ // a sibling artifact, distinct from the entity page. Raw node name → file
137
+ // (`<name>.md`), agreeing with the entity Used-by back-link target.
138
+ for (const child of ctx.loadedRoot.ownChildren()) {
139
+ if (child.type !== TYPE_TEMPLATE || child.subType !== TEMPLATE_SUBTYPE_OUTPUT) continue;
140
+ const node = docPageNode(child);
141
+ templateNodes.push(node);
142
+ const path = docPageOutputPath(layout, node);
143
+ placements.push({ path, fqn: child.resolutionKey() });
144
+ const payload = buildTemplateDocData(child, {
145
+ layout,
146
+ loadedRoot: ctx.loadedRoot,
147
+ provider,
148
+ });
149
+ files.push({ path, content: renderDocPage(TEMPLATE_PAGE_REF, payload, provider, path) });
150
+ }
151
+
152
+ // Emit the neutral OVERVIEW/index page (README.md) at the docs root: the
153
+ // whole-model Mermaid ER diagram (reusing the single shared
154
+ // renderMermaidErBlock builder — no duplicated ER logic, ADR-0020) plus a
155
+ // navigable index linking every entity + template page. Prepended so it is
156
+ // the first file (and so README is included in the collision backstop).
157
+ // Only emitted when at least one page exists — an all-filtered/empty run
158
+ // produces nothing (no orphan landing page with an empty diagram).
159
+ if (files.length > 0) {
160
+ // The api index lives at `<subDir>/README.md` per surface; the model index
161
+ // lives at the docs root, so the from-path is the root-level index path
162
+ // (shared builder — same relative/absolute rule as the entity refs).
163
+ const apiIndexRefs = apiRefsFor(INDEX_FILENAME);
164
+ const indexContent = renderIndexPage(
165
+ ctx.loadedRoot,
166
+ layout,
167
+ entityNodes,
168
+ templateNodes,
169
+ apiIndexRefs,
170
+ );
171
+ placements.push({ path: INDEX_FILENAME, fqn: "<the auto-generated overview/index page>" });
172
+ files.unshift({ path: INDEX_FILENAME, content: indexContent });
173
+ }
174
+
175
+ // Hard backstop against silent overwrite (ALL layouts): two nodes that
176
+ // resolve to the same output path → throw naming both FQNs + the path.
177
+ assertNoDuplicateDocPaths(placements);
178
+
179
+ return files;
82
180
  },
83
181
  };
84
182
  if (opts?.filter) generator.filter = opts.filter;
85
183
  if (opts?.target) generator.target = opts.target;
86
184
  return generator;
87
185
  } as GeneratorFactory<DocsFileOpts>;
186
+
187
+ /** Build the neutral OVERVIEW/index page (README.md) body: a title + one-line
188
+ * description, the whole-model Mermaid ER diagram (the shared
189
+ * renderMermaidErBlock — ONE builder, no duplication), and a navigable index
190
+ * grouping entity pages vs template pages. Every link is computed with
191
+ * docPageHref(layout, INDEX_NODE, target) so it resolves in BOTH flat and
192
+ * package layout (the index lives at the docs root). Fully neutral — Mermaid +
193
+ * entity/template names + relationships only. */
194
+ function renderIndexPage(
195
+ root: MetaRoot,
196
+ layout: OutputLayout,
197
+ entityNodes: DocPageNode[],
198
+ templateNodes: DocPageNode[],
199
+ apiIndexRefs?: Array<{ label: string; href: string }>,
200
+ ): string {
201
+ const pkg = root.package;
202
+ const out: string[] = [];
203
+ out.push("# Data Model");
204
+ out.push("");
205
+ out.push(
206
+ pkg !== undefined && pkg.length > 0
207
+ ? `Overview of the \`${pkg}\` metadata model — entities, their relationships, and output templates.`
208
+ : "Overview of the metadata model — entities, their relationships, and output templates.",
209
+ );
210
+ out.push("");
211
+
212
+ // The whole-model ER diagram (shared neutral builder). Per-entity prose stays
213
+ // on each entity page; only the fenced erDiagram block lives on the overview.
214
+ out.push("## Diagram");
215
+ out.push("");
216
+ out.push(renderMermaidErBlock(root));
217
+ out.push("");
218
+
219
+ // Navigable index — grouped, sorted by name for stable output. Links resolve
220
+ // in both layouts because docPageHref derives them from the same placement.
221
+ const byName = (a: DocPageNode, b: DocPageNode) => a.name.localeCompare(b.name);
222
+ if (entityNodes.length > 0) {
223
+ out.push("## Entities");
224
+ out.push("");
225
+ for (const node of [...entityNodes].sort(byName)) {
226
+ out.push(`- [${node.name}](${docPageHref(layout, INDEX_NODE, node)})`);
227
+ }
228
+ out.push("");
229
+ }
230
+ if (templateNodes.length > 0) {
231
+ out.push("## Templates");
232
+ out.push("");
233
+ for (const node of [...templateNodes].sort(byName)) {
234
+ out.push(`- [${node.name}](${docPageHref(layout, INDEX_NODE, node)})`);
235
+ }
236
+ out.push("");
237
+ }
238
+
239
+ // Cross-link to the GENERATED-SDK api reference indexes, when api surfaces are
240
+ // emitted alongside the model surface — one bullet per language surface. ABSENT
241
+ // or empty → no section (index byte-identical to model-only runs).
242
+ if (apiIndexRefs !== undefined && apiIndexRefs.length > 0) {
243
+ out.push("## API reference");
244
+ out.push("");
245
+ for (const ref of apiIndexRefs) {
246
+ out.push(`- [${ref.label}](${ref.href})`);
247
+ }
248
+ out.push("");
249
+ }
250
+
251
+ // Trailing newline (one), matching the per-page convention.
252
+ return out.join("\n").replace(/\n+$/, "\n");
253
+ }
@@ -1,6 +1,7 @@
1
1
  import type { MetaObject } from "@metaobjectsdev/metadata";
2
- import { perEntity, type Generator, type GeneratorFactory } from "../generator.js";
2
+ import { perEntity, type EmittedFile, type GenContext, type Generator, type GeneratorFactory } from "../generator.js";
3
3
  import { renderEntityFile } from "../templates/entity-file.js";
4
+ import { renderSharedEnumsFile, SHARED_ENUMS_BASENAME } from "../templates/enums-file.js";
4
5
  import { formatTs } from "../format.js";
5
6
  import { entityOutputPath } from "../import-path.js";
6
7
  import { isAbstract } from "../instance-artifacts.js";
@@ -27,24 +28,39 @@ export interface EntityFileOpts {
27
28
 
28
29
  export const entityFile = function entityFile(opts?: EntityFileOpts): Generator {
29
30
  const allowlists = opts?.allowlists ?? true;
31
+ const perEntityEmit = perEntity(async (entity, ctx) => {
32
+ if (!ctx.renderContext) {
33
+ throw new Error("entity-file: renderContext is required (provided by runGen)");
34
+ }
35
+ // Abstract entities contribute shape only. When emitAbstractShapes is off
36
+ // (cross-port knob; default on) the entity-file generator emits nothing for
37
+ // them. Instance/write generators skip abstract unconditionally elsewhere.
38
+ if (isAbstract(entity) && !ctx.renderContext.emitAbstractShapes) {
39
+ return [];
40
+ }
41
+ return {
42
+ path: entityOutputPath(ctx.config.outputLayout ?? "flat", entity.package, `${entity.name}.ts`),
43
+ content: await formatTs(renderEntityFile(entity, ctx.renderContext, { allowlists })),
44
+ };
45
+ });
46
+
30
47
  const generator: Generator = {
31
48
  name: "entity-file",
32
49
  emitsEntityModule: true,
33
- generate: perEntity(async (entity, ctx) => {
34
- if (!ctx.renderContext) {
35
- throw new Error("entity-file: renderContext is required (provided by runGen)");
36
- }
37
- // Abstract entities contribute shape only. When emitAbstractShapes is off
38
- // (cross-port knob; default on) the entity-file generator emits nothing for
39
- // them. Instance/write generators skip abstract unconditionally elsewhere.
40
- if (isAbstract(entity) && !ctx.renderContext.emitAbstractShapes) {
41
- return [];
50
+ generate: async (ctx: GenContext): Promise<EmittedFile[]> => {
51
+ const files = await perEntityEmit(ctx);
52
+ // FR-019: emit the shared-enums module ONCE per run, into the entity-module
53
+ // target root. Returns null (no file) when the model uses no materialized
54
+ // shared enums keeping the inline-enum default byte-identical (no new file).
55
+ const sharedEnums = renderSharedEnumsFile(ctx.loadedRoot);
56
+ if (sharedEnums !== null) {
57
+ files.push({
58
+ path: `${SHARED_ENUMS_BASENAME}.ts`,
59
+ content: await formatTs(sharedEnums),
60
+ });
42
61
  }
43
- return {
44
- path: entityOutputPath(ctx.config.outputLayout ?? "flat", entity.package, `${entity.name}.ts`),
45
- content: await formatTs(renderEntityFile(entity, ctx.renderContext, { allowlists })),
46
- };
47
- }),
62
+ return files;
63
+ },
48
64
  };
49
65
  if (opts?.filter) {
50
66
  generator.filter = opts.filter;
@@ -5,7 +5,8 @@
5
5
  //
6
6
  // The emitted extractor sits over the output-parser's nested-capable extract and turns dirty LLM
7
7
  // text into the strict typed payload graph. It imports from the sibling <Name>.output.ts (the
8
- // output-parser) and ./payloads.ts, so run it alongside outputParser() + a payload generator.
8
+ // output-parser) and from each payload value-object's own entity module (<VO>.ts, emitted by
9
+ // entityFile), so run it alongside outputParser() + entityFile().
9
10
  //
10
11
  // Consumer wiring (metaobjects.config.ts):
11
12
  // generators: [..., outputParser(), extractor()]
@@ -0,0 +1,24 @@
1
+ // Per-field doc anchor convention (linked-template-source-docs).
2
+ //
3
+ // A field's stable doc anchor slug is `field-<name>` (the field name verbatim).
4
+ // This is the SINGLE source of truth for the slug, shared by BOTH:
5
+ // • the entity-page builder, which emits a literal `<a id="field-<name>">` in
6
+ // each Constraints-table Field cell, and
7
+ // • the template-source annotator, whose links target `#field-<name>`.
8
+ // Sharing one helper means the anchor and its links can never drift.
9
+ //
10
+ // The name is used verbatim (not URL-slugified): MetaObjects field names are
11
+ // already identifier-safe (no spaces / punctuation), so `field-<name>` is a
12
+ // valid HTML id and a valid Markdown fragment as-is.
13
+
14
+ /** The stable doc-anchor slug for a field: `field-<name>`. */
15
+ export function fieldAnchorSlug(name: string): string {
16
+ return `field-${name}`;
17
+ }
18
+
19
+ /** The literal HTML anchor a Markdown link can target: `<a id="field-<name>"></a>`.
20
+ * Renders on GitHub-flavored Markdown and static-site generators, including
21
+ * inside a table cell. */
22
+ export function fieldAnchorHtml(name: string): string {
23
+ return `<a id="${fieldAnchorSlug(name)}"></a>`;
24
+ }
@@ -4,12 +4,16 @@ export { callableFile, type CallableFileOpts } from "./callable-file.js";
4
4
  export { routesFile, type RoutesFileOpts } from "./routes-file.js";
5
5
  export { routesFileHono, type RoutesFileHonoOpts } from "./routes-file-hono.js";
6
6
  export { barrel, type BarrelOpts } from "./barrel.js";
7
+ /** @deprecated ADR-0021 D1 — neutral artifact owned by `meta docs` (ADR-0020); not part of the recommended `meta gen` suite. */
7
8
  export { mermaidErDiagram, type MermaidErOptions } from "./mermaid-er.js";
8
9
  export { promptRender, type PromptRenderOpts } from "./prompt-render-file.js";
9
10
  export { outputParser, type OutputParserOpts } from "./output-parser-file.js";
10
11
  export { extractor, type ExtractorOpts } from "./extractor-file.js";
11
12
  export { outputPrompt, type OutputPromptOpts } from "./output-prompt-file.js";
12
13
  export { renderHelper, type RenderHelperOpts } from "./render-helper-file.js";
14
+ /** @deprecated ADR-0025 — `meta docs` is the single docs door; `apiDocsFile` stays as the internal engine of its api surface, not a `meta gen` config generator. */
15
+ export { apiDocsFile, type ApiDocsFileOpts } from "./api-docs-file.js";
16
+ /** @deprecated ADR-0021 D1 — `meta docs` is the single docs door; `docsFile` stays as its internal engine, not a `meta gen` config generator. */
13
17
  export { docsFile, type DocsFileOpts } from "./docs-file.js";
14
18
  export {
15
19
  templateGenerator,
@@ -17,12 +21,15 @@ export {
17
21
  type TemplateWalkResult,
18
22
  type TemplateFormat,
19
23
  } from "./template-generator.js";
24
+ export { traceHelperFile, type TraceHelperOpts } from "./trace-helper-file.js";
20
25
  export type {
21
26
  EntityDocData,
22
27
  StorageFieldDoc,
23
28
  IdentityDoc,
24
29
  RelationshipDoc,
25
30
  UsedByDoc,
26
- GeneratedFileDoc,
31
+ ConstraintRow,
27
32
  } from "./docs-data.js";
28
33
  export { buildEntityDocData } from "./docs-data-builder.js";
34
+ export type { TemplateDocData, TemplateOutputPart } from "./template-doc-data.js";
35
+ export { buildTemplateDocData } from "./template-doc-builder.js";
@@ -12,6 +12,20 @@ export interface MermaidErOptions {
12
12
  * Emit a single Markdown file containing a Mermaid `erDiagram` plus per-entity
13
13
  * prose sections. The renderer walks the loaded root for all entities; the
14
14
  * default outFile is "docs/model.md".
15
+ *
16
+ * TIER NOTE (ADR-0020): the CANONICAL home of the neutral ER diagram is now the
17
+ * neutral docs engine — the OVERVIEW/index page (`README.md`) emitted by
18
+ * `docsFile()` / `meta docs` embeds the SAME `renderMermaidErBlock()` this
19
+ * generator's `renderMermaidModel()` reuses. This standalone Tier-1 generator is
20
+ * retained ONLY as a thin back-compat wrapper for adopters that already have
21
+ * `mermaidErDiagram` in their `meta gen` config; it adds NO ER logic of its own
22
+ * (no duplication) — it delegates to the shared `renderMermaidModel()` builder.
23
+ *
24
+ * @deprecated ADR-0021 D1: the ER diagram is a Tier-2 neutral artifact owned by
25
+ * the docs engine (ADR-0020). Use `meta docs` — it embeds the same ER block.
26
+ * This standalone `meta gen` generator is flagged neutral in the registry
27
+ * (`--list`) and is no longer part of the recommended native suite. Kept for
28
+ * back-compat; do not add to new gen configs.
15
29
  */
16
30
  export const mermaidErDiagram = function mermaidErDiagram(
17
31
  opts?: MermaidErOptions,