@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
package/src/constants.ts CHANGED
@@ -8,3 +8,21 @@ export const EXTRA_SUFFIX = ".extra";
8
8
 
9
9
  /** Default outDir used by tests + as a sane default for generate(). */
10
10
  export const DEFAULT_OUT_DIR = "./src/db/entities";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Codegen-control attributes.
14
+ //
15
+ // These are per-entity opt-in/opt-out flags read by generators (NOT metamodel
16
+ // vocabulary — they tune codegen, not the model). Named here so the literals
17
+ // aren't scattered as magic strings across the generator packages (compile-time
18
+ // typo safety), matching the metadata package's constants discipline.
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /** `@emitTanstack: false` — skip the TanStack hooks + grid generators for an entity. */
22
+ export const CODEGEN_ATTR_EMIT_TANSTACK = "emitTanstack";
23
+ /** `@emitGrid: true` — opt a TPH subtype IN to its own per-subtype grid (default: the polymorphic base grid is the single source). */
24
+ export const CODEGEN_ATTR_EMIT_GRID = "emitGrid";
25
+ /** `@emitForm: false` — skip the React form generator for an entity. */
26
+ export const CODEGEN_ATTR_EMIT_FORM = "emitForm";
27
+ /** `@emitRoutes: false` — skip the Fastify routes generator for an entity. */
28
+ export const CODEGEN_ATTR_EMIT_ROUTES = "emitRoutes";
@@ -0,0 +1,128 @@
1
+ // docs-paths.ts — the SINGLE source of truth for where a docs page is written
2
+ // AND how one page links to another. The file location and every inbound link
3
+ // href are derived from the SAME functions here, so they can never diverge.
4
+ //
5
+ // Why this matters: pages are placed by short name. In "flat" layout two nodes
6
+ // that share a short name across different packages (e.g. `acme::sales::Order`
7
+ // and `acme::billing::Order`) would both want `Order.md` and one would silently
8
+ // overwrite the other. `assertNoDuplicateDocPaths()` is the hard backstop that
9
+ // turns that data-loss into a clear error; "package" layout folds pages under
10
+ // package-path subdirs (`acme/sales/Order.md`) so multi-package models work.
11
+
12
+ import { PACKAGE_SEPARATOR } from "@metaobjectsdev/metadata";
13
+ import { relative as posixRelative } from "node:path/posix";
14
+ import { packageToPath, type OutputLayout } from "./import-path.js";
15
+
16
+ /** The minimal shape needed to place a docs page / compute a link to it: a
17
+ * short name and its EFFECTIVE package. Build one from a metadata node via
18
+ * `docPageNode()`. */
19
+ export interface DocPageNode {
20
+ readonly name: string;
21
+ readonly package?: string | undefined;
22
+ }
23
+
24
+ /** A metadata node enough to derive page placement. `resolutionKey()` carries
25
+ * the EFFECTIVE package (own package OR the file-default captured at parse
26
+ * time) folded as `<pkg>::<name>` — `.package` alone is often undefined for
27
+ * objects (FR5d keeps object fqn() bare), so we read placement off the
28
+ * resolution key instead. */
29
+ interface PlaceableNode {
30
+ readonly name: string;
31
+ resolutionKey(): string;
32
+ }
33
+
34
+ /** Effective package of a placeable node: the prefix of `resolutionKey()`
35
+ * before the trailing `::<name>`, or undefined when the node is package-less. */
36
+ export function effectivePackage(node: PlaceableNode): string | undefined {
37
+ const key = node.resolutionKey();
38
+ const suffix = `${PACKAGE_SEPARATOR}${node.name}`;
39
+ if (key === node.name) return undefined;
40
+ if (key.endsWith(suffix)) {
41
+ const pkg = key.slice(0, key.length - suffix.length);
42
+ return pkg === "" ? undefined : pkg;
43
+ }
44
+ return undefined;
45
+ }
46
+
47
+ /** Build a placement node ({name, effective package}) from a metadata node. The
48
+ * single bridge from a loaded node to the path/href helpers — so file location
49
+ * and link href derive from the SAME effective package. */
50
+ export function docPageNode(node: PlaceableNode): DocPageNode {
51
+ return { name: node.name, package: effectivePackage(node) };
52
+ }
53
+
54
+ /** Output path (relative to the docs out dir) for a node's `.md` page.
55
+ * Flat → `<name>.md` (today's value, byte-identical). Package → folded under
56
+ * the package path (`acme/sales/Order.md`); a package-less node stays at root. */
57
+ export function docPageOutputPath(layout: OutputLayout, node: DocPageNode): string {
58
+ const filename = `${node.name}.md`;
59
+ if (layout === "flat") return filename;
60
+ const dir = packageToPath(node.package);
61
+ return dir === "" ? filename : `${dir}/${filename}`;
62
+ }
63
+
64
+ /** Relative href FROM `fromNode`'s page TO `toNode`'s page. Derived from the
65
+ * same `docPageOutputPath()` placement, so a link always points at the file's
66
+ * real location in BOTH layouts. Flat → `./<to>.md`; package → a correct
67
+ * relative path (e.g. `../comms/OrderEmail.md`). */
68
+ export function docPageHref(
69
+ layout: OutputLayout,
70
+ fromNode: DocPageNode,
71
+ toNode: DocPageNode,
72
+ ): string {
73
+ const toPath = docPageOutputPath(layout, toNode);
74
+ if (layout === "flat") return `./${toPath}`;
75
+ // Relative path from the FROM page's directory to the TO page — the same
76
+ // raw-path rule surfaceCrossHref uses, so the two can never diverge.
77
+ return surfaceCrossHref(docPageOutputPath(layout, fromNode), toPath);
78
+ }
79
+
80
+ /** Relative href between two doc pages whose output paths (relative to the shared
81
+ * docs outDir) may sit under different surface sub-roots — e.g. model `Order.md`
82
+ * and api `api/Order.md`. The shared relative-path rule, over raw paths;
83
+ * docPageHref delegates its package-layout branch here. */
84
+ export function surfaceCrossHref(fromOutputPath: string, toOutputPath: string): string {
85
+ const fromDir = fromOutputPath.includes("/") ? fromOutputPath.slice(0, fromOutputPath.lastIndexOf("/")) : "";
86
+ const rel = posixRelative(fromDir, toOutputPath);
87
+ return rel.startsWith(".") ? rel : `./${rel}`;
88
+ }
89
+
90
+ /** Href FROM a page (at `fromOutputPath`, relative to the docs root) TO a page
91
+ * (`page`, relative to the surface's own root) in an api surface. Relative via
92
+ * `surfaceCrossHref` when the surface is in the same tree; absolute `baseUrl/page`
93
+ * when the surface declares a baseUrl (federated / separate repo). */
94
+ export function apiSurfaceHref(
95
+ fromOutputPath: string,
96
+ surface: { subDir: string; baseUrl?: string },
97
+ page: string,
98
+ ): string {
99
+ if (surface.baseUrl !== undefined && surface.baseUrl !== "") {
100
+ return `${surface.baseUrl.replace(/\/$/, "")}/${page}`;
101
+ }
102
+ return surfaceCrossHref(fromOutputPath, `${surface.subDir}/${page}`);
103
+ }
104
+
105
+ /** A page about to be emitted, paired with the FQN of the node that produced it
106
+ * (for a precise collision diagnostic). */
107
+ export interface DocPagePlacement {
108
+ path: string;
109
+ fqn: string;
110
+ }
111
+
112
+ /** Hard backstop against silent overwrite (ALL layouts): if two placements
113
+ * resolve to the SAME output path, THROW naming both colliding node FQNs and
114
+ * the path. Guarantees a docs run never silently drops a page. */
115
+ export function assertNoDuplicateDocPaths(placements: DocPagePlacement[]): void {
116
+ const seen = new Map<string, string>();
117
+ for (const { path, fqn } of placements) {
118
+ const prior = seen.get(path);
119
+ if (prior !== undefined) {
120
+ throw new Error(
121
+ `docs: duplicate output path "${path}" from nodes ${prior} and ${fqn} — ` +
122
+ `use package layout (outputLayout: "package" / meta docs --layout package) ` +
123
+ `to disambiguate.`,
124
+ );
125
+ }
126
+ seen.set(path, fqn);
127
+ }
128
+ }
@@ -0,0 +1,43 @@
1
+ // FR-019 — import-specifier resolution for shared + provided enums.
2
+ //
3
+ // A consuming entity file references a shared enum `E` by importing it:
4
+ // • materialized (non-@provided) → from the generated shared enums module
5
+ // (`./enums`, at the entity-module target root, package-layout-aware).
6
+ // • @provided → from the per-port-configured module
7
+ // (`ctx.providedEnumModule`). Missing config ⇒ a codegen-time error naming
8
+ // the enum + the config key (ADR-0026: namespace is config, not metadata).
9
+
10
+ import { withExt } from "./render-context.js";
11
+ import type { RenderContext } from "./render-context.js";
12
+ import { relativeModuleSpecifier } from "./import-path.js";
13
+ import { SHARED_ENUMS_BASENAME } from "./templates/enums-file.js";
14
+
15
+ /**
16
+ * Specifier to import a MATERIALIZED shared enum into an entity file in
17
+ * `entityPkg`. The shared module sits at the entity-module target root; in
18
+ * package layout the entity may be nested, so the `./enums` base is adjusted by
19
+ * package depth.
20
+ */
21
+ export function sharedEnumImportSpecifier(
22
+ ctx: RenderContext,
23
+ entityPkg: string | undefined,
24
+ ): string {
25
+ const base = withExt(`./${SHARED_ENUMS_BASENAME}`, ctx.extStyle);
26
+ return relativeModuleSpecifier(ctx.selfTarget.outputLayout, entityPkg, base);
27
+ }
28
+
29
+ /**
30
+ * Specifier to import an externally-PROVIDED enum. Resolved from codegen config
31
+ * (`providedEnumModule`); throws a clear codegen-time error when unset.
32
+ */
33
+ export function providedEnumImportSpecifier(ctx: RenderContext, enumName: string): string {
34
+ const mod = ctx.providedEnumModule;
35
+ if (mod === undefined || mod === "") {
36
+ throw new Error(
37
+ `provided enum "${enumName}" is marked @provided but no module is configured ` +
38
+ `to import it from. Set "providedEnumModule" in your codegen config (e.g. ` +
39
+ `providedEnumModule: "@your-app/enums") so the generated code can reference "${enumName}".`,
40
+ );
41
+ }
42
+ return mod;
43
+ }
@@ -0,0 +1,95 @@
1
+ // FR-019 — shared + externally-provided enum resolution.
2
+ //
3
+ // A reusable enum is a ROOT/package-level abstract `field.enum` (a sibling of
4
+ // object.entity) that concrete entity fields `extends`. Per ADR-0026 such a
5
+ // declaration is materialized ONCE per port and referenced, instead of being
6
+ // redeclared inline in every consuming entity file.
7
+ //
8
+ // Two cases on the abstract declaration:
9
+ // • non-@provided → metaobjects MATERIALIZES the type once (a shared module),
10
+ // and consuming fields reference it.
11
+ // • @provided: true → metaobjects emits NOTHING for the type; consuming fields
12
+ // reference an existing hand-written/third-party type, resolved via per-port
13
+ // codegen config (the namespace/module never lives in metadata — ADR-0001).
14
+ //
15
+ // The common inline case (a concrete `field.enum` with @values declared directly,
16
+ // no root-extends) is UNCHANGED — it stays a per-entity `<Entity><Field>` union.
17
+
18
+ import type { MetaField, MetaRoot } from "@metaobjectsdev/metadata";
19
+ import {
20
+ FIELD_SUBTYPE_ENUM,
21
+ FIELD_ATTR_PROVIDED,
22
+ TYPE_METADATA,
23
+ } from "@metaobjectsdev/metadata";
24
+ import { toPascalCase } from "./naming.js";
25
+ import { enumValues } from "./enum-meta.js";
26
+
27
+ /** A shared (root-level abstract) enum declaration the codegen must reason about. */
28
+ export interface SharedEnum {
29
+ /** The materialized type name — the abstract declaration's PascalCase name (cross-port identity). */
30
+ readonly name: string;
31
+ /** Member symbols (the @values SSOT). */
32
+ readonly values: string[];
33
+ /** True when the declaration carries `@provided: true` (reference, don't materialize). */
34
+ readonly provided: boolean;
35
+ }
36
+
37
+ /**
38
+ * The root-level abstract `field.enum` a concrete enum field resolves to via
39
+ * `extends`, or undefined when the field is an inline enum (no root-abstract
40
+ * super). A super qualifies only when it is (a) an abstract field, AND (b) a
41
+ * direct child of the metadata root (a package-level declaration) — NOT an
42
+ * abstract field nested inside another object.
43
+ */
44
+ export function resolveSharedEnumDecl(field: MetaField): MetaField | undefined {
45
+ if (field.subType !== FIELD_SUBTYPE_ENUM) return undefined;
46
+ const sup = field.resolveSuper();
47
+ if (sup === undefined) return undefined;
48
+ if (!sup.isAbstract) return undefined;
49
+ // The declaration must sit at the package/root level (sibling of object.entity),
50
+ // i.e. its parent node is the metadata root.
51
+ const parent = sup.parent;
52
+ if (parent === undefined || parent.type !== TYPE_METADATA) return undefined;
53
+ return sup;
54
+ }
55
+
56
+ /** The shared-enum descriptor a field resolves to, or undefined for inline enums. */
57
+ export function sharedEnumForField(field: MetaField): SharedEnum | undefined {
58
+ const decl = resolveSharedEnumDecl(field);
59
+ if (decl === undefined) return undefined;
60
+ const values = enumValues(field);
61
+ if (values === undefined) return undefined;
62
+ return {
63
+ name: toPascalCase(decl.name),
64
+ values,
65
+ provided: decl.ownAttr(FIELD_ATTR_PROVIDED) === true,
66
+ };
67
+ }
68
+
69
+ /**
70
+ * All shared (root-level abstract) enum declarations in the loaded root that are
71
+ * actually CONSUMED by at least one concrete entity field — keyed by the
72
+ * materialized type name. A declaration nobody extends is not materialized (no
73
+ * dangling type). Deterministic order: first-consumption order across entities.
74
+ */
75
+ export function collectSharedEnums(root: MetaRoot): Map<string, SharedEnum> {
76
+ const out = new Map<string, SharedEnum>();
77
+ for (const entity of root.objects()) {
78
+ for (const field of entity.fields()) {
79
+ const shared = sharedEnumForField(field);
80
+ if (shared === undefined) continue;
81
+ if (!out.has(shared.name)) out.set(shared.name, shared);
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+
87
+ /** The shared enums that metaobjects MATERIALIZES (non-@provided, consumed). */
88
+ export function materializedSharedEnums(root: MetaRoot): SharedEnum[] {
89
+ return [...collectSharedEnums(root).values()].filter((e) => !e.provided);
90
+ }
91
+
92
+ /** Whether any shared enum (materialized or provided) is consumed in the model. */
93
+ export function hasSharedEnums(root: MetaRoot): boolean {
94
+ return collectSharedEnums(root).size > 0;
95
+ }
@@ -0,0 +1,204 @@
1
+ // ADR-0021 D3 — stable-name generator registry.
2
+ //
3
+ // Generators are identified by a STABLE string id (e.g. `entity`, `routes`,
4
+ // `render-helper`) rather than by a language-specific factory import. The id is
5
+ // the cross-port contract: the same logical generator carries the same stable
6
+ // name in every port. This module is the discoverability + identity surface
7
+ // behind `meta gen --list`.
8
+ //
9
+ // It is ADDITIVE. The existing `defineConfig({ generators: [entityFile(), ...] })`
10
+ // factory-array config keeps working unchanged — the registry powers `--list`
11
+ // and a stable identity, it does not replace the config path.
12
+ //
13
+ // Tiering (ADR-0020 / ADR-0021 D1):
14
+ // - "native" — the recommended Tier-1 `meta gen` suite (idiomatic emission).
15
+ // - "neutral" — Tier-2 artifacts owned by the neutral docs engine. `docs` and
16
+ // `mermaid-er` are present here for identity/discoverability but
17
+ // are NOT part of the recommended native surface — the canonical
18
+ // door for documentation is `meta docs` (D1).
19
+
20
+ import type { Generator } from "./generator.js";
21
+ import {
22
+ entityFile,
23
+ queriesFile,
24
+ callableFile,
25
+ routesFile,
26
+ routesFileHono,
27
+ barrel,
28
+ mermaidErDiagram,
29
+ promptRender,
30
+ outputParser,
31
+ extractor,
32
+ outputPrompt,
33
+ renderHelper,
34
+ apiDocsFile,
35
+ docsFile,
36
+ templateGenerator,
37
+ traceHelperFile,
38
+ } from "./generators/index.js";
39
+
40
+ export type GeneratorTier = "native" | "neutral";
41
+
42
+ export interface GeneratorRegistryEntry {
43
+ /** Stable, cross-port-consistent id. Equals the registry map key. */
44
+ name: string;
45
+ /** One-line (no newline) human description for `--list`. */
46
+ description: string;
47
+ /** "native" = recommended `meta gen` suite; "neutral" = `meta docs`-owned. */
48
+ tier: GeneratorTier;
49
+ /** Constructs the generator with sensible defaults. Calling it must not throw. */
50
+ factory: () => Generator;
51
+ /** Optional one-line options summary for `--list`. */
52
+ options?: string;
53
+ /** Optional note — used to point neutral entries at their canonical door. */
54
+ note?: string;
55
+ }
56
+
57
+ // The `template` generator is a PRIMITIVE: callers supply { name, walk,
58
+ // template }. For registry identity + `--list` we expose a no-op default so the
59
+ // factory constructs a valid Generator without throwing; real use passes opts
60
+ // via the config factory-array path. (docsFile() is the first instance of this
61
+ // primitive — see template-generator.ts.)
62
+ function templatePrimitive(): Generator {
63
+ return templateGenerator({
64
+ name: "template",
65
+ template: "",
66
+ walk: () => [],
67
+ });
68
+ }
69
+
70
+ export const generatorRegistry: Record<string, GeneratorRegistryEntry> = {
71
+ // ----- Tier-1 native suite (idiomatic per-port emission) -----------------
72
+ entity: {
73
+ name: "entity",
74
+ description: "Per-entity Drizzle table + typed model module (the entity module).",
75
+ tier: "native",
76
+ factory: () => entityFile(),
77
+ options: "filter?, target?",
78
+ },
79
+ queries: {
80
+ name: "queries",
81
+ description: "Per-entity typed query helpers (findById/create/...).",
82
+ tier: "native",
83
+ factory: () => queriesFile(),
84
+ options: "filter?, target?",
85
+ },
86
+ callable: {
87
+ name: "callable",
88
+ description: "Per-entity callable/service surface wrapping the query helpers.",
89
+ tier: "native",
90
+ factory: () => callableFile(),
91
+ options: "filter?, target?",
92
+ },
93
+ routes: {
94
+ name: "routes",
95
+ description: "Per-entity Fastify CRUD routes (drizzle-fastify mountCrudRoutes).",
96
+ tier: "native",
97
+ factory: () => routesFile(),
98
+ options: "filter?, target?",
99
+ },
100
+ "routes-hono": {
101
+ name: "routes-hono",
102
+ description: "Per-entity Hono CRUD routes (runtime-ts/hono mountCrudRoutes).",
103
+ tier: "native",
104
+ factory: () => routesFileHono(),
105
+ options: "filter?, target?",
106
+ },
107
+ barrel: {
108
+ name: "barrel",
109
+ description: "Single index.ts re-exporting every generated entity module.",
110
+ tier: "native",
111
+ factory: () => barrel(),
112
+ options: "target?",
113
+ },
114
+ "prompt-render": {
115
+ name: "prompt-render",
116
+ description: "Per-template prompt-render helper over the render engine.",
117
+ tier: "native",
118
+ factory: () => promptRender(),
119
+ options: "filter?, target?",
120
+ },
121
+ "output-parser": {
122
+ name: "output-parser",
123
+ description: "Per-template tolerant output parser (recover-on-receipt).",
124
+ tier: "native",
125
+ factory: () => outputParser(),
126
+ options: "filter?, target?",
127
+ },
128
+ extractor: {
129
+ name: "extractor",
130
+ description: "Per-template typed extract<Name> helper (strict payload extraction).",
131
+ tier: "native",
132
+ factory: () => extractor(),
133
+ options: "filter?, target?",
134
+ },
135
+ "output-prompt": {
136
+ name: "output-prompt",
137
+ description: "Per-template output-format prompt fragment generator.",
138
+ tier: "native",
139
+ factory: () => outputPrompt(),
140
+ options: "filter?, target?",
141
+ },
142
+ "render-helper": {
143
+ name: "render-helper",
144
+ description: "Per-template.output render helper (document/email typed wrappers).",
145
+ tier: "native",
146
+ factory: () => renderHelper(),
147
+ options: "filter?, target?",
148
+ },
149
+ template: {
150
+ name: "template",
151
+ description: "Generic Mustache template primitive (walk + template → files).",
152
+ tier: "native",
153
+ factory: () => templatePrimitive(),
154
+ options: "name, walk, template, format?, filter?, provider?, target?",
155
+ },
156
+ "api-docs": {
157
+ name: "api-docs",
158
+ description:
159
+ "Per-entity/template SDK API reference (the generated code's API, human + agent forms).",
160
+ tier: "native",
161
+ factory: () => apiDocsFile(),
162
+ options: "filter?, target?",
163
+ },
164
+
165
+ "trace-helper": {
166
+ name: "trace-helper",
167
+ description: "Per-entity typed record<Entity>/call<Entity> trace helpers (extract + buildLlmCallRow + persist; LlmCallBase-derived entities only).",
168
+ tier: "native",
169
+ factory: () => traceHelperFile(),
170
+ options: "outDir?, target?",
171
+ },
172
+
173
+ // ----- Tier-2 neutral (owned by the `meta docs` engine — D1 / ADR-0020) ---
174
+ docs: {
175
+ name: "docs",
176
+ description: "Neutral per-entity / per-template Markdown documentation pages.",
177
+ tier: "neutral",
178
+ factory: () => docsFile(),
179
+ note: "neutral artifact — use `meta docs` (the single docs door, ADR-0021 D1); not part of the recommended `meta gen` native suite.",
180
+ },
181
+ "mermaid-er": {
182
+ name: "mermaid-er",
183
+ description: "Mermaid ER diagram of the entity/relationship model.",
184
+ tier: "neutral",
185
+ factory: () => mermaidErDiagram(),
186
+ note: "neutral artifact owned by the docs engine (ADR-0020); surfaced via `meta docs`, not the recommended `meta gen` native suite.",
187
+ },
188
+ };
189
+
190
+ /** All registry entries, native first then neutral, alphabetical within tier. */
191
+ export function listGenerators(): GeneratorRegistryEntry[] {
192
+ const entries = Object.values(generatorRegistry);
193
+ const byName = (a: GeneratorRegistryEntry, b: GeneratorRegistryEntry) =>
194
+ a.name.localeCompare(b.name);
195
+ return [
196
+ ...entries.filter((e) => e.tier === "native").sort(byName),
197
+ ...entries.filter((e) => e.tier === "neutral").sort(byName),
198
+ ];
199
+ }
200
+
201
+ /** Resolve a generator entry by its stable id, or undefined if unknown. */
202
+ export function getGenerator(id: string): GeneratorRegistryEntry | undefined {
203
+ return generatorRegistry[id];
204
+ }
package/src/generator.ts CHANGED
@@ -47,6 +47,12 @@ export interface Generator {
47
47
  /** Marks the generator that produces entity modules — the runner uses its
48
48
  * target as the entity-module target for cross-target import resolution. */
49
49
  emitsEntityModule?: boolean;
50
+ /** Marks the OPT-IN Hono routes generator (routesFileHono). The runner
51
+ * aggregates this across the active suite into `ctx.config.includeHonoRoutes`,
52
+ * so a generator that documents the API surface (api-docs) can AUTO-DETECT
53
+ * that Hono routes are actually being emitted and document them — rather than
54
+ * silently omitting the Hono CRUD registrars whenever the variant is wired. */
55
+ emitsHonoRoutes?: boolean;
50
56
  }
51
57
 
52
58
  export type GeneratorFactory<TOpts = void> = TOpts extends void