@metaobjectsdev/codegen-ts 0.9.0 → 0.11.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/README.md +1 -1
  2. package/dist/column-mapper.d.ts +12 -6
  3. package/dist/column-mapper.d.ts.map +1 -1
  4. package/dist/column-mapper.js +68 -28
  5. package/dist/column-mapper.js.map +1 -1
  6. package/dist/constants.d.ts +8 -0
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +16 -0
  9. package/dist/constants.js.map +1 -1
  10. package/dist/docs-paths.d.ts +58 -0
  11. package/dist/docs-paths.d.ts.map +1 -0
  12. package/dist/docs-paths.js +89 -0
  13. package/dist/docs-paths.js.map +1 -0
  14. package/dist/enum-import.d.ts +14 -0
  15. package/dist/enum-import.d.ts.map +1 -0
  16. package/dist/enum-import.js +35 -0
  17. package/dist/enum-import.js.map +1 -0
  18. package/dist/enum-shared.d.ts +32 -0
  19. package/dist/enum-shared.d.ts.map +1 -0
  20. package/dist/enum-shared.js +83 -0
  21. package/dist/enum-shared.js.map +1 -0
  22. package/dist/generator-registry.d.ts +22 -0
  23. package/dist/generator-registry.d.ts.map +1 -0
  24. package/dist/generator-registry.js +161 -0
  25. package/dist/generator-registry.js.map +1 -0
  26. package/dist/generator.d.ts +6 -0
  27. package/dist/generator.d.ts.map +1 -1
  28. package/dist/generator.js.map +1 -1
  29. package/dist/generators/api-doc-render.d.ts +17 -0
  30. package/dist/generators/api-doc-render.d.ts.map +1 -0
  31. package/dist/generators/api-doc-render.js +431 -0
  32. package/dist/generators/api-doc-render.js.map +1 -0
  33. package/dist/generators/api-docs-file.d.ts +21 -0
  34. package/dist/generators/api-docs-file.d.ts.map +1 -0
  35. package/dist/generators/api-docs-file.js +112 -0
  36. package/dist/generators/api-docs-file.js.map +1 -0
  37. package/dist/generators/api-field-shape.d.ts +39 -0
  38. package/dist/generators/api-field-shape.d.ts.map +1 -0
  39. package/dist/generators/api-field-shape.js +92 -0
  40. package/dist/generators/api-field-shape.js.map +1 -0
  41. package/dist/generators/api-label.d.ts +3 -0
  42. package/dist/generators/api-label.d.ts.map +1 -0
  43. package/dist/generators/api-label.js +8 -0
  44. package/dist/generators/api-label.js.map +1 -0
  45. package/dist/generators/api-model.d.ts +122 -0
  46. package/dist/generators/api-model.d.ts.map +1 -0
  47. package/dist/generators/api-model.js +809 -0
  48. package/dist/generators/api-model.js.map +1 -0
  49. package/dist/generators/docs-data-builder.d.ts +26 -4
  50. package/dist/generators/docs-data-builder.d.ts.map +1 -1
  51. package/dist/generators/docs-data-builder.js +439 -167
  52. package/dist/generators/docs-data-builder.js.map +1 -1
  53. package/dist/generators/docs-data.d.ts +136 -27
  54. package/dist/generators/docs-data.d.ts.map +1 -1
  55. package/dist/generators/docs-data.js +1 -1
  56. package/dist/generators/docs-data.js.map +1 -1
  57. package/dist/generators/docs-file.d.ts +19 -0
  58. package/dist/generators/docs-file.d.ts.map +1 -1
  59. package/dist/generators/docs-file.js +154 -27
  60. package/dist/generators/docs-file.js.map +1 -1
  61. package/dist/generators/entity-file.d.ts.map +1 -1
  62. package/dist/generators/entity-file.js +29 -14
  63. package/dist/generators/entity-file.js.map +1 -1
  64. package/dist/generators/extractor-file.d.ts.map +1 -1
  65. package/dist/generators/extractor-file.js +2 -1
  66. package/dist/generators/extractor-file.js.map +1 -1
  67. package/dist/generators/field-anchor.d.ts +7 -0
  68. package/dist/generators/field-anchor.d.ts.map +1 -0
  69. package/dist/generators/field-anchor.js +23 -0
  70. package/dist/generators/field-anchor.js.map +1 -0
  71. package/dist/generators/index.d.ts +8 -1
  72. package/dist/generators/index.d.ts.map +1 -1
  73. package/dist/generators/index.js +6 -0
  74. package/dist/generators/index.js.map +1 -1
  75. package/dist/generators/mermaid-er.d.ts +14 -0
  76. package/dist/generators/mermaid-er.d.ts.map +1 -1
  77. package/dist/generators/mermaid-er.js +14 -0
  78. package/dist/generators/mermaid-er.js.map +1 -1
  79. package/dist/generators/output-parser-file.d.ts.map +1 -1
  80. package/dist/generators/output-parser-file.js +3 -4
  81. package/dist/generators/output-parser-file.js.map +1 -1
  82. package/dist/generators/output-prompt-file.d.ts.map +1 -1
  83. package/dist/generators/output-prompt-file.js +2 -2
  84. package/dist/generators/output-prompt-file.js.map +1 -1
  85. package/dist/generators/prompt-render-file.d.ts.map +1 -1
  86. package/dist/generators/prompt-render-file.js +3 -4
  87. package/dist/generators/prompt-render-file.js.map +1 -1
  88. package/dist/generators/queries-file.d.ts.map +1 -1
  89. package/dist/generators/queries-file.js +8 -3
  90. package/dist/generators/queries-file.js.map +1 -1
  91. package/dist/generators/render-helper-file.d.ts.map +1 -1
  92. package/dist/generators/render-helper-file.js +2 -2
  93. package/dist/generators/render-helper-file.js.map +1 -1
  94. package/dist/generators/routes-file-hono.d.ts.map +1 -1
  95. package/dist/generators/routes-file-hono.js +5 -1
  96. package/dist/generators/routes-file-hono.js.map +1 -1
  97. package/dist/generators/routes-file.d.ts +3 -0
  98. package/dist/generators/routes-file.d.ts.map +1 -1
  99. package/dist/generators/routes-file.js +6 -1
  100. package/dist/generators/routes-file.js.map +1 -1
  101. package/dist/generators/template-doc-builder.d.ts +19 -0
  102. package/dist/generators/template-doc-builder.d.ts.map +1 -0
  103. package/dist/generators/template-doc-builder.js +220 -0
  104. package/dist/generators/template-doc-builder.js.map +1 -0
  105. package/dist/generators/template-doc-data.d.ts +62 -0
  106. package/dist/generators/template-doc-data.d.ts.map +1 -0
  107. package/dist/generators/template-doc-data.js +16 -0
  108. package/dist/generators/template-doc-data.js.map +1 -0
  109. package/dist/generators/template-payload-tree.d.ts +15 -0
  110. package/dist/generators/template-payload-tree.d.ts.map +1 -0
  111. package/dist/generators/template-payload-tree.js +61 -0
  112. package/dist/generators/template-payload-tree.js.map +1 -0
  113. package/dist/generators/template-source-annotate.d.ts +74 -0
  114. package/dist/generators/template-source-annotate.d.ts.map +1 -0
  115. package/dist/generators/template-source-annotate.js +184 -0
  116. package/dist/generators/template-source-annotate.js.map +1 -0
  117. package/dist/generators/template-source-render.d.ts +24 -0
  118. package/dist/generators/template-source-render.d.ts.map +1 -0
  119. package/dist/generators/template-source-render.js +175 -0
  120. package/dist/generators/template-source-render.js.map +1 -0
  121. package/dist/generators/trace-helper-file.d.ts +9 -0
  122. package/dist/generators/trace-helper-file.d.ts.map +1 -0
  123. package/dist/generators/trace-helper-file.js +196 -0
  124. package/dist/generators/trace-helper-file.js.map +1 -0
  125. package/dist/import-path.d.ts +18 -0
  126. package/dist/import-path.d.ts.map +1 -1
  127. package/dist/import-path.js +21 -0
  128. package/dist/import-path.js.map +1 -1
  129. package/dist/index.d.ts +29 -4
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +28 -2
  132. package/dist/index.js.map +1 -1
  133. package/dist/metaobjects-config.d.ts +101 -2
  134. package/dist/metaobjects-config.d.ts.map +1 -1
  135. package/dist/metaobjects-config.js +46 -0
  136. package/dist/metaobjects-config.js.map +1 -1
  137. package/dist/naming.d.ts +39 -2
  138. package/dist/naming.d.ts.map +1 -1
  139. package/dist/naming.js +52 -3
  140. package/dist/naming.js.map +1 -1
  141. package/dist/payload-codegen.d.ts.map +1 -1
  142. package/dist/payload-codegen.js +14 -6
  143. package/dist/payload-codegen.js.map +1 -1
  144. package/dist/pk-resolver.d.ts.map +1 -1
  145. package/dist/pk-resolver.js +4 -2
  146. package/dist/pk-resolver.js.map +1 -1
  147. package/dist/projection/extract-view-spec.d.ts.map +1 -1
  148. package/dist/projection/extract-view-spec.js +52 -26
  149. package/dist/projection/extract-view-spec.js.map +1 -1
  150. package/dist/relation-resolver.d.ts +16 -0
  151. package/dist/relation-resolver.d.ts.map +1 -1
  152. package/dist/relation-resolver.js +82 -1
  153. package/dist/relation-resolver.js.map +1 -1
  154. package/dist/render-context.d.ts +25 -2
  155. package/dist/render-context.d.ts.map +1 -1
  156. package/dist/render-context.js +7 -0
  157. package/dist/render-context.js.map +1 -1
  158. package/dist/render-engine/embedded-templates.generated.d.ts +2 -0
  159. package/dist/render-engine/embedded-templates.generated.d.ts.map +1 -0
  160. package/dist/render-engine/embedded-templates.generated.js +15 -0
  161. package/dist/render-engine/embedded-templates.generated.js.map +1 -0
  162. package/dist/render-engine/framework-provider.d.ts.map +1 -1
  163. package/dist/render-engine/framework-provider.js +26 -13
  164. package/dist/render-engine/framework-provider.js.map +1 -1
  165. package/dist/runner.d.ts.map +1 -1
  166. package/dist/runner.js +20 -0
  167. package/dist/runner.js.map +1 -1
  168. package/dist/templates/docs-file.d.ts +2 -6
  169. package/dist/templates/docs-file.d.ts.map +1 -1
  170. package/dist/templates/docs-file.js +2 -5
  171. package/dist/templates/docs-file.js.map +1 -1
  172. package/dist/templates/drizzle-schema.d.ts.map +1 -1
  173. package/dist/templates/drizzle-schema.js +72 -23
  174. package/dist/templates/drizzle-schema.js.map +1 -1
  175. package/dist/templates/entity-constants.d.ts +7 -0
  176. package/dist/templates/entity-constants.d.ts.map +1 -1
  177. package/dist/templates/entity-constants.js +3 -3
  178. package/dist/templates/entity-constants.js.map +1 -1
  179. package/dist/templates/entity-file.d.ts.map +1 -1
  180. package/dist/templates/entity-file.js +16 -5
  181. package/dist/templates/entity-file.js.map +1 -1
  182. package/dist/templates/enums-file.d.ts +11 -0
  183. package/dist/templates/enums-file.d.ts.map +1 -0
  184. package/dist/templates/enums-file.js +44 -0
  185. package/dist/templates/enums-file.js.map +1 -0
  186. package/dist/templates/extract-delegate-emitter.d.ts.map +1 -1
  187. package/dist/templates/extract-delegate-emitter.js +6 -8
  188. package/dist/templates/extract-delegate-emitter.js.map +1 -1
  189. package/dist/templates/extractor.d.ts.map +1 -1
  190. package/dist/templates/extractor.js +58 -41
  191. package/dist/templates/extractor.js.map +1 -1
  192. package/dist/templates/field-meta.d.ts.map +1 -1
  193. package/dist/templates/field-meta.js +2 -6
  194. package/dist/templates/field-meta.js.map +1 -1
  195. package/dist/templates/filter-allowlist.d.ts +7 -2
  196. package/dist/templates/filter-allowlist.d.ts.map +1 -1
  197. package/dist/templates/filter-allowlist.js +18 -10
  198. package/dist/templates/filter-allowlist.js.map +1 -1
  199. package/dist/templates/filter-shared.js +2 -2
  200. package/dist/templates/filter-shared.js.map +1 -1
  201. package/dist/templates/filter-type.d.ts +7 -1
  202. package/dist/templates/filter-type.d.ts.map +1 -1
  203. package/dist/templates/filter-type.js +10 -6
  204. package/dist/templates/filter-type.js.map +1 -1
  205. package/dist/templates/find-templates.d.ts +4 -0
  206. package/dist/templates/find-templates.d.ts.map +1 -0
  207. package/dist/templates/find-templates.js +15 -0
  208. package/dist/templates/find-templates.js.map +1 -0
  209. package/dist/templates/fr010-field-mapping.d.ts +2 -0
  210. package/dist/templates/fr010-field-mapping.d.ts.map +1 -1
  211. package/dist/templates/fr010-field-mapping.js +15 -11
  212. package/dist/templates/fr010-field-mapping.js.map +1 -1
  213. package/dist/templates/inferred-types.d.ts +44 -7
  214. package/dist/templates/inferred-types.d.ts.map +1 -1
  215. package/dist/templates/inferred-types.js +121 -19
  216. package/dist/templates/inferred-types.js.map +1 -1
  217. package/dist/templates/mermaid-er.d.ts +35 -2
  218. package/dist/templates/mermaid-er.d.ts.map +1 -1
  219. package/dist/templates/mermaid-er.js +174 -7
  220. package/dist/templates/mermaid-er.js.map +1 -1
  221. package/dist/templates/output-format-spec-emitter.js +1 -1
  222. package/dist/templates/output-format-spec-emitter.js.map +1 -1
  223. package/dist/templates/output-parser.d.ts.map +1 -1
  224. package/dist/templates/output-parser.js +31 -80
  225. package/dist/templates/output-parser.js.map +1 -1
  226. package/dist/templates/output-prompt.d.ts.map +1 -1
  227. package/dist/templates/output-prompt.js +2 -2
  228. package/dist/templates/output-prompt.js.map +1 -1
  229. package/dist/templates/queries-file.d.ts.map +1 -1
  230. package/dist/templates/queries-file.js +113 -5
  231. package/dist/templates/queries-file.js.map +1 -1
  232. package/dist/templates/queries.d.ts +7 -2
  233. package/dist/templates/queries.d.ts.map +1 -1
  234. package/dist/templates/queries.js +15 -15
  235. package/dist/templates/queries.js.map +1 -1
  236. package/dist/templates/relations-block.d.ts.map +1 -1
  237. package/dist/templates/relations-block.js +12 -3
  238. package/dist/templates/relations-block.js.map +1 -1
  239. package/dist/templates/render-helper.d.ts.map +1 -1
  240. package/dist/templates/render-helper.js +5 -5
  241. package/dist/templates/render-helper.js.map +1 -1
  242. package/dist/templates/routes-file-hono.d.ts.map +1 -1
  243. package/dist/templates/routes-file-hono.js +1 -2
  244. package/dist/templates/routes-file-hono.js.map +1 -1
  245. package/dist/templates/routes-file.d.ts.map +1 -1
  246. package/dist/templates/routes-file.js +184 -7
  247. package/dist/templates/routes-file.js.map +1 -1
  248. package/dist/templates/tph-discriminator.d.ts +56 -0
  249. package/dist/templates/tph-discriminator.d.ts.map +1 -0
  250. package/dist/templates/tph-discriminator.js +180 -0
  251. package/dist/templates/tph-discriminator.js.map +1 -0
  252. package/dist/templates/value-object-file.d.ts +2 -1
  253. package/dist/templates/value-object-file.d.ts.map +1 -1
  254. package/dist/templates/value-object-file.js +33 -5
  255. package/dist/templates/value-object-file.js.map +1 -1
  256. package/dist/templates/zod-validators.d.ts +65 -2
  257. package/dist/templates/zod-validators.d.ts.map +1 -1
  258. package/dist/templates/zod-validators.js +202 -22
  259. package/dist/templates/zod-validators.js.map +1 -1
  260. package/package.json +103 -34
  261. package/src/column-mapper.ts +79 -32
  262. package/src/constants.ts +18 -0
  263. package/src/docs-paths.ts +128 -0
  264. package/src/enum-import.ts +43 -0
  265. package/src/enum-shared.ts +95 -0
  266. package/src/generator-registry.ts +204 -0
  267. package/src/generator.ts +6 -0
  268. package/src/generators/api-doc-render.ts +572 -0
  269. package/src/generators/api-docs-file.ts +146 -0
  270. package/src/generators/api-field-shape.ts +114 -0
  271. package/src/generators/api-label.ts +7 -0
  272. package/src/generators/api-model.ts +1067 -0
  273. package/src/generators/docs-data-builder.ts +483 -189
  274. package/src/generators/docs-data.ts +139 -28
  275. package/src/generators/docs-file.ts +205 -39
  276. package/src/generators/entity-file.ts +31 -15
  277. package/src/generators/extractor-file.ts +2 -1
  278. package/src/generators/field-anchor.ts +24 -0
  279. package/src/generators/index.ts +8 -1
  280. package/src/generators/mermaid-er.ts +14 -0
  281. package/src/generators/output-parser-file.ts +3 -4
  282. package/src/generators/output-prompt-file.ts +2 -1
  283. package/src/generators/prompt-render-file.ts +3 -4
  284. package/src/generators/queries-file.ts +9 -3
  285. package/src/generators/render-helper-file.ts +2 -1
  286. package/src/generators/routes-file-hono.ts +5 -1
  287. package/src/generators/routes-file.ts +7 -1
  288. package/src/generators/template-doc-builder.ts +306 -0
  289. package/src/generators/template-doc-data.ts +85 -0
  290. package/src/generators/template-payload-tree.ts +71 -0
  291. package/src/generators/template-source-annotate.ts +290 -0
  292. package/src/generators/template-source-render.ts +203 -0
  293. package/src/generators/trace-helper-file.ts +301 -0
  294. package/src/import-path.ts +28 -0
  295. package/src/index.ts +55 -4
  296. package/src/metaobjects-config.ts +146 -2
  297. package/src/naming.ts +73 -3
  298. package/src/payload-codegen.ts +16 -5
  299. package/src/pk-resolver.ts +4 -2
  300. package/src/projection/extract-view-spec.ts +50 -31
  301. package/src/relation-resolver.ts +103 -1
  302. package/src/render-context.ts +32 -2
  303. package/src/render-engine/embedded-templates.generated.ts +14 -0
  304. package/src/render-engine/framework-provider.ts +25 -11
  305. package/src/runner.ts +24 -0
  306. package/src/templates/docs-file.ts +2 -9
  307. package/src/templates/drizzle-schema.ts +80 -28
  308. package/src/templates/entity-constants.ts +3 -3
  309. package/src/templates/entity-file.ts +16 -5
  310. package/src/templates/enums-file.ts +50 -0
  311. package/src/templates/extract-delegate-emitter.ts +6 -7
  312. package/src/templates/extractor.ts +70 -40
  313. package/src/templates/field-meta.ts +1 -7
  314. package/src/templates/filter-allowlist.ts +18 -11
  315. package/src/templates/filter-shared.ts +2 -2
  316. package/src/templates/filter-type.ts +9 -7
  317. package/src/templates/find-templates.ts +15 -0
  318. package/src/templates/fr010-field-mapping.ts +15 -13
  319. package/src/templates/inferred-types.ts +122 -21
  320. package/src/templates/mermaid-er.ts +176 -8
  321. package/src/templates/output-format-spec-emitter.ts +1 -1
  322. package/src/templates/output-parser.ts +31 -80
  323. package/src/templates/output-prompt.ts +2 -1
  324. package/src/templates/queries-file.ts +133 -4
  325. package/src/templates/queries.ts +21 -15
  326. package/src/templates/relations-block.ts +19 -3
  327. package/src/templates/render-helper.ts +5 -4
  328. package/src/templates/routes-file-hono.ts +1 -2
  329. package/src/templates/routes-file.ts +234 -7
  330. package/src/templates/tph-discriminator.ts +232 -0
  331. package/src/templates/value-object-file.ts +39 -5
  332. package/src/templates/zod-validators.ts +225 -21
  333. package/templates/api/agent-api.md.mustache +30 -0
  334. package/templates/api/entity-api.md.mustache +69 -0
  335. package/templates/api/index.md.mustache +21 -0
  336. package/templates/docs/entity-page.md.mustache +33 -21
  337. package/templates/docs/template-page.md.mustache +56 -0
  338. package/dist/templates/extract-schema-emitter.d.ts +0 -8
  339. package/dist/templates/extract-schema-emitter.d.ts.map +0 -1
  340. package/dist/templates/extract-schema-emitter.js +0 -81
  341. package/dist/templates/extract-schema-emitter.js.map +0 -1
  342. package/src/templates/extract-schema-emitter.ts +0 -111
@@ -5,13 +5,19 @@ import { code, imp, type Code } from "ts-poet";
5
5
  import type { MetaObject } from "@metaobjectsdev/metadata";
6
6
  import { IDENTITY_ATTR_FIELDS } from "@metaobjectsdev/metadata";
7
7
  import type { RenderContext } from "../render-context.js";
8
- import { variableNameFromEntity, pluralize } from "../naming.js";
8
+ import {
9
+ findByIdFnName,
10
+ listFnName,
11
+ createFnName,
12
+ updateFnName,
13
+ deleteByIdFnName,
14
+ } from "../naming.js";
9
15
 
10
16
  /** Get the PK field name and its TS type for a given entity. */
11
- function getPkInfo(entity: MetaObject, ctx: RenderContext): { fieldName: string; tsType: string } {
17
+ export function getPkInfo(entity: MetaObject, ctx: RenderContext): { fieldName: string; tsType: string } {
12
18
  // Use primaryIdentity() to find the primary identity (may be inherited from extends:/super:).
13
19
  const primary = entity.primaryIdentity();
14
- const rawFields = primary?.ownAttr(IDENTITY_ATTR_FIELDS);
20
+ const rawFields = primary?.attr(IDENTITY_ATTR_FIELDS);
15
21
  const fields = Array.isArray(rawFields) ? rawFields : (typeof rawFields === "string" ? [rawFields] : undefined);
16
22
  const pkFieldName = fields?.[0] ?? "id";
17
23
  const pkInfo = ctx.pkMap.get(entity.name);
@@ -26,11 +32,11 @@ function getPkInfo(entity: MetaObject, ctx: RenderContext): { fieldName: string;
26
32
  }
27
33
 
28
34
  export function renderFindByIdFn(entity: MetaObject, ctx: RenderContext): Code {
29
- const varName = variableNameFromEntity(entity.name);
35
+ const varName = ctx.collectionName(entity.name);
30
36
  const entityName = entity.name;
31
37
  const singularVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
32
38
  const { fieldName: pkField, tsType: pkType } = getPkInfo(entity, ctx);
33
- const fnName = `find${entityName}ById`;
39
+ const fnName = findByIdFnName(entityName);
34
40
  const eqSym = imp("eq@drizzle-orm");
35
41
 
36
42
  return code`
@@ -41,12 +47,12 @@ export async function ${fnName}(db: Db, ${pkField}: ${pkType}): Promise<${entity
41
47
  `;
42
48
  }
43
49
 
44
- export function renderListFn(entity: MetaObject, _ctx: RenderContext): Code {
45
- const varName = variableNameFromEntity(entity.name);
50
+ export function renderListFn(entity: MetaObject, ctx: RenderContext): Code {
51
+ const varName = ctx.collectionName(entity.name);
46
52
  const entityName = entity.name;
47
53
  // Pluralize the PascalCase entity name, preserving capitalization
48
54
  // (e.g., "Category" -> "Categories", not "Categorys").
49
- const fnName = `list${pluralize(entityName)}`;
55
+ const fnName = listFnName(entityName);
50
56
 
51
57
  return code`
52
58
  export async function ${fnName}(db: Db, opts?: { limit?: number; offset?: number }): Promise<${entityName}[]> {
@@ -58,11 +64,11 @@ export async function ${fnName}(db: Db, opts?: { limit?: number; offset?: number
58
64
  `;
59
65
  }
60
66
 
61
- export function renderCreateFn(entity: MetaObject, _ctx: RenderContext): Code {
62
- const varName = variableNameFromEntity(entity.name);
67
+ export function renderCreateFn(entity: MetaObject, ctx: RenderContext): Code {
68
+ const varName = ctx.collectionName(entity.name);
63
69
  const entityName = entity.name;
64
70
  const singularVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
65
- const fnName = `create${entityName}`;
71
+ const fnName = createFnName(entityName);
66
72
  const schemaName = `${entityName}InsertSchema`;
67
73
 
68
74
  return code`
@@ -75,11 +81,11 @@ export async function ${fnName}(db: Db, data: unknown): Promise<${entityName}> {
75
81
  }
76
82
 
77
83
  export function renderUpdateFn(entity: MetaObject, ctx: RenderContext): Code {
78
- const varName = variableNameFromEntity(entity.name);
84
+ const varName = ctx.collectionName(entity.name);
79
85
  const entityName = entity.name;
80
86
  const singularVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
81
87
  const { fieldName: pkField, tsType: pkType } = getPkInfo(entity, ctx);
82
- const fnName = `update${entityName}`;
88
+ const fnName = updateFnName(entityName);
83
89
  const schemaName = `${entityName}InsertSchema`;
84
90
  const eqSym = imp("eq@drizzle-orm");
85
91
 
@@ -93,10 +99,10 @@ export async function ${fnName}(db: Db, ${pkField}: ${pkType}, data: unknown): P
93
99
  }
94
100
 
95
101
  export function renderDeleteByIdFn(entity: MetaObject, ctx: RenderContext): Code {
96
- const varName = variableNameFromEntity(entity.name);
102
+ const varName = ctx.collectionName(entity.name);
97
103
  const entityName = entity.name;
98
104
  const { fieldName: pkField, tsType: pkType } = getPkInfo(entity, ctx);
99
- const fnName = `delete${entityName}ById`;
105
+ const fnName = deleteByIdFnName(entityName);
100
106
  const eqSym = imp("eq@drizzle-orm");
101
107
 
102
108
  return code`
@@ -6,7 +6,6 @@ import type { MetaObject } from "@metaobjectsdev/metadata";
6
6
  import { CARDINALITY_ONE, CARDINALITY_MANY } from "@metaobjectsdev/metadata";
7
7
  import { type RenderContext } from "../render-context.js";
8
8
  import { crossEntitySpecifier } from "../import-path.js";
9
- import { variableNameFromEntity } from "../naming.js";
10
9
  import type { RelationEntry } from "../relation-resolver.js";
11
10
 
12
11
  /**
@@ -17,7 +16,7 @@ export function renderRelationsBlock(entity: MetaObject, ctx: RenderContext): Co
17
16
  const entries = ctx.relationMap.get(entity.name);
18
17
  if (!entries || entries.length === 0) return null;
19
18
 
20
- const varName = variableNameFromEntity(entity.name);
19
+ const varName = ctx.collectionName(entity.name);
21
20
  const relationsFn = imp("relations@drizzle-orm");
22
21
  const relationsVarName = `${varName}Relations`;
23
22
 
@@ -46,6 +45,23 @@ function renderRelationEntry(
46
45
  thisVarName: string,
47
46
  thisEntityPackage: string | undefined,
48
47
  ): Code {
48
+ // FR-018 M:N: the source navigates through the junction table, so the Drizzle
49
+ // many() targets the JUNCTION, not the target entity (the relational query API
50
+ // then hops junction → target via the junction's one() sides). The
51
+ // mountM2mRoute the routes generator emits performs the flattened two-stage
52
+ // traversal for the REST contract.
53
+ if (entry.cardinality === CARDINALITY_MANY && entry.junctionEntity !== undefined) {
54
+ const junctionSpec = crossEntitySpecifier(
55
+ ctx.outputLayout,
56
+ thisEntityPackage,
57
+ ctx.packageOf.get(entry.junctionEntity),
58
+ entry.junctionEntity,
59
+ ctx.extStyle,
60
+ );
61
+ const junctionVarSym = imp(`${ctx.collectionName(entry.junctionEntity)}@${junctionSpec}`);
62
+ return code` ${entry.name}: many(${junctionVarSym})`;
63
+ }
64
+
49
65
  // Use imp() for cross-entity references so ts-poet tracks and emits the import.
50
66
  const targetSpec = crossEntitySpecifier(
51
67
  ctx.outputLayout,
@@ -54,7 +70,7 @@ function renderRelationEntry(
54
70
  entry.targetEntity,
55
71
  ctx.extStyle,
56
72
  );
57
- const targetVarSym = imp(`${variableNameFromEntity(entry.targetEntity)}@${targetSpec}`);
73
+ const targetVarSym = imp(`${ctx.collectionName(entry.targetEntity)}@${targetSpec}`);
58
74
 
59
75
  if (entry.cardinality === CARDINALITY_ONE) {
60
76
  const pkInfo = ctx.pkMap.get(entry.targetEntity);
@@ -43,6 +43,7 @@ import {
43
43
  TEMPLATE_ATTR_SUBJECT_REF,
44
44
  TEMPLATE_ATTR_HTML_BODY_REF,
45
45
  TEMPLATE_ATTR_TEXT_BODY_REF,
46
+ refMatchesObject,
46
47
  } from "@metaobjectsdev/metadata";
47
48
  import {
48
49
  verify,
@@ -53,7 +54,7 @@ import {
53
54
  } from "@metaobjectsdev/render";
54
55
 
55
56
  function findObject(root: MetaData, name: string): MetaData | undefined {
56
- return root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === name);
57
+ return root.ownChildren().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
57
58
  }
58
59
 
59
60
  function findTemplate(root: MetaData, name: string): MetaData | undefined {
@@ -80,7 +81,7 @@ function derivePayloadFieldTree(
80
81
  const fields: PayloadField[] = [];
81
82
  for (const f of vo.children().filter((c) => c.type === TYPE_FIELD)) {
82
83
  if (f.subType === FIELD_SUBTYPE_OBJECT) {
83
- const ref = f.ownAttr(FIELD_ATTR_OBJECT_REF);
84
+ const ref = f.attr(FIELD_ATTR_OBJECT_REF);
84
85
  if (typeof ref === "string") {
85
86
  fields.push({ name: f.name, fields: derivePayloadFieldTree(root, ref, nextSeen) });
86
87
  continue;
@@ -192,7 +193,7 @@ export function renderRenderHelper(
192
193
 
193
194
  return `import { render } from "@metaobjectsdev/render";
194
195
  import type { Provider, EmailDocument } from "@metaobjectsdev/render";
195
- import type { ${payloadRef} } from "./payloads.js";
196
+ import type { ${payloadRef} } from "./${payloadRef}.js";
196
197
 
197
198
  /**
198
199
  * Render the ${templateName} email (subject + html body${typeof textBodyRef === "string" ? " + text body" : ""}) from a
@@ -230,7 +231,7 @@ export function ${fnName}(payload: ${payloadRef}, provider: Provider): EmailDocu
230
231
 
231
232
  return `import { render } from "@metaobjectsdev/render";
232
233
  import type { Provider } from "@metaobjectsdev/render";
233
- import type { ${payloadRef} } from "./payloads.js";
234
+ import type { ${payloadRef} } from "./${payloadRef}.js";
234
235
 
235
236
  /**
236
237
  * Render the ${templateName} document from a typed ${payloadRef} payload. Wraps the
@@ -29,7 +29,6 @@ import type { MetaObject } from "@metaobjectsdev/metadata";
29
29
  import { type RenderContext } from "../render-context.js";
30
30
  import { entityModuleSpecifier } from "../import-path.js";
31
31
  import { GENERATED_HEADER } from "../constants.js";
32
- import { variableNameFromEntity } from "../naming.js";
33
32
  import { isProjection } from "../projection/projection-detector.js";
34
33
 
35
34
  export function renderRoutesFileHono(entity: MetaObject, ctx: RenderContext): string {
@@ -97,7 +96,7 @@ export function ${handlerName}(app: ${HonoSym}<any, any, any>, deps: { db: unkno
97
96
  }
98
97
 
99
98
  // --- Vanilla / write-through entity path: full CRUD routes ---
100
- const tableVar = variableNameFromEntity(entityName);
99
+ const tableVar = ctx.collectionName(entityName);
101
100
 
102
101
  const HonoSym = imp("t:Hono@hono");
103
102
  const mountCrudRoutesSym = imp("mountCrudRoutes@@metaobjectsdev/runtime-ts/hono");
@@ -15,17 +15,31 @@
15
15
  // the existing queries-file template). The entity's Drizzle table const is
16
16
  // imported alongside the Zod schemas + constants from the sibling Entity.ts.
17
17
 
18
- import { code, imp } from "ts-poet";
18
+ import { code, imp, joinCode, type Code } from "ts-poet";
19
19
  import type { MetaObject } from "@metaobjectsdev/metadata";
20
+ import {
21
+ TYPE_FIELD,
22
+ resolveColumnName,
23
+ } from "@metaobjectsdev/metadata";
20
24
  import { type RenderContext } from "../render-context.js";
21
- import { entityModuleSpecifier, relativeModuleSpecifier } from "../import-path.js";
25
+ import { crossEntitySpecifier, entityModuleSpecifier, relativeModuleSpecifier } from "../import-path.js";
22
26
  import { GENERATED_HEADER } from "../constants.js";
23
- import { variableNameFromEntity } from "../naming.js";
27
+ import { routesHandlerName } from "../naming.js";
24
28
  import { isProjection } from "../projection/projection-detector.js";
29
+ import type { RelationEntry } from "../relation-resolver.js";
30
+ import { isTphDiscriminatorBase, tphPlan } from "./tph-discriminator.js";
25
31
 
26
32
  export function renderRoutesFile(entity: MetaObject, ctx: RenderContext): string {
33
+ // FR-017 Tier 2 — a TPH discriminator base mounts polymorphic list/get at the
34
+ // base path plus a full per-subtype CRUD route set scoped to each
35
+ // discriminator value. (Subtype entities are filtered out of the routes
36
+ // generator entirely — their routes live here.)
37
+ if (isTphDiscriminatorBase(entity, ctx.loadedRoot)) {
38
+ return renderTphRoutesFile(entity, ctx);
39
+ }
40
+
27
41
  const entityName = entity.name;
28
- const handlerName = `${entityName.charAt(0).toLowerCase()}${entityName.slice(1)}Routes`;
42
+ const handlerName = routesHandlerName(entityName);
29
43
  // Import the entity's own file. Same target → relative "./Entity"; cross
30
44
  // target → importBase-qualified package path.
31
45
  const entityFileSpec = entityModuleSpecifier(
@@ -108,11 +122,24 @@ export async function ${handlerName}(fastify: ${FastifyInstanceSym}) {
108
122
  }
109
123
 
110
124
  // --- Vanilla / write-through entity path: full CRUD routes ---
111
- const tableVar = variableNameFromEntity(entityName);
125
+ const tableVar = ctx.collectionName(entityName);
112
126
 
113
127
  const FastifyInstanceSym = imp("t:FastifyInstance@fastify");
114
128
  const mountCrudRoutesSym = imp("mountCrudRoutes@@metaobjectsdev/runtime-ts/drizzle-fastify");
115
129
 
130
+ // FR-018 M:N traversal: for each many-to-many navigation declared on this
131
+ // entity, emit a mountM2mRoute(...) that traverses the junction. The junction
132
+ // FK columns were derived from the junction's identity.reference children (the
133
+ // SSOT) by the relation-resolver pre-pass; here we resolve them to physical
134
+ // column names for the Drizzle two-stage join.
135
+ const m2mEntries = (ctx.relationMap.get(entityName) ?? []).filter(
136
+ (e): e is RelationEntry & { junctionEntity: string } => e.junctionEntity !== undefined,
137
+ );
138
+ // Two fastify-scope variants: under an apiPrefix the mounts live inside the
139
+ // register-block (`instance`); otherwise they bind directly to `fastify`.
140
+ const m2mMountsPrefixed = renderM2mMounts(m2mEntries, entity, ctx, "instance");
141
+ const m2mMountsFlat = renderM2mMounts(m2mEntries, entity, ctx, "fastify");
142
+
116
143
  const literalImports = code`
117
144
  import { db } from ${JSON.stringify(dbImportSpec)};
118
145
  import {
@@ -148,7 +175,7 @@ export async function ${handlerName}(fastify: ${FastifyInstanceSym}) {
148
175
  sortAllowlist: ${entityName}SortAllowlist,
149
176
  dialect: ${JSON.stringify(ctx.dialect)},
150
177
  });
151
- }, { prefix: ${JSON.stringify(ctx.apiPrefix)} });
178
+ ${m2mMountsPrefixed} }, { prefix: ${JSON.stringify(ctx.apiPrefix)} });
152
179
  }
153
180
  `
154
181
  : code`
@@ -172,8 +199,208 @@ export async function ${handlerName}(fastify: ${FastifyInstanceSym}) {
172
199
  sortAllowlist: ${entityName}SortAllowlist,
173
200
  dialect: ${JSON.stringify(ctx.dialect)},
174
201
  });
175
- }
202
+ ${m2mMountsFlat}}
176
203
  `;
177
204
 
178
205
  return header + literalImports.toString() + body.toString();
179
206
  }
207
+
208
+ /**
209
+ * Render the M:N traversal mounts for an entity as a single Code fragment to
210
+ * interpolate INTO the handler-body code template (so the junction/target table
211
+ * + mountM2mRoute imports hoist with the rest of the body's imports, not inline
212
+ * mid-function). `fastifyVar` is the in-scope Fastify reference (`instance`
213
+ * under an apiPrefix register-block, else `fastify`). Returns "" when the entity
214
+ * has no M:N relationships — CRUD-only output stays byte-identical to before.
215
+ */
216
+ function renderM2mMounts(
217
+ entries: ReadonlyArray<RelationEntry & { junctionEntity: string }>,
218
+ source: MetaObject,
219
+ ctx: RenderContext,
220
+ fastifyVar: string,
221
+ ): Code | string {
222
+ if (entries.length === 0) return "";
223
+ const mounts = entries.map((e) => renderM2mMount(e, source, ctx, fastifyVar));
224
+ return code`${joinCode(mounts, { on: "\n", trim: false })}
225
+ `;
226
+ }
227
+
228
+ /**
229
+ * Render one M:N traversal mount. The junction + target Drizzle table consts are
230
+ * imported from their sibling entity files (imp() lets ts-poet track + emit the
231
+ * import). The source/target FK columns + the target PK are resolved to PHYSICAL
232
+ * column names via resolveColumnName (the runtime two-stage join queries by
233
+ * column). mountM2mRoute appends `/:id/<relationName>` to the source $path.
234
+ */
235
+ function renderM2mMount(
236
+ entry: RelationEntry & { junctionEntity: string },
237
+ source: MetaObject,
238
+ ctx: RenderContext,
239
+ fastifyVar: string,
240
+ ): Code {
241
+ const junctionVarSym = imp(
242
+ `${ctx.collectionName(entry.junctionEntity)}@${crossEntitySpecifier(
243
+ ctx.outputLayout,
244
+ source.package,
245
+ ctx.packageOf.get(entry.junctionEntity),
246
+ entry.junctionEntity,
247
+ ctx.extStyle,
248
+ )}`,
249
+ );
250
+ const targetVarSym = imp(
251
+ `${ctx.collectionName(entry.targetEntity)}@${crossEntitySpecifier(
252
+ ctx.outputLayout,
253
+ source.package,
254
+ ctx.packageOf.get(entry.targetEntity),
255
+ entry.targetEntity,
256
+ ctx.extStyle,
257
+ )}`,
258
+ );
259
+ const mountM2mRouteSym = imp("mountM2mRoute@@metaobjectsdev/runtime-ts/drizzle-fastify");
260
+ const junction = ctx.loadedRoot.findObject(entry.junctionEntity);
261
+ const target = ctx.loadedRoot.findObject(entry.targetEntity);
262
+ const sourceColumn = junction
263
+ ? resolveJunctionColumn(junction, entry.sourceJoinField!, ctx)
264
+ : entry.sourceJoinField!;
265
+ const targetColumn = junction
266
+ ? resolveJunctionColumn(junction, entry.targetJoinField!, ctx)
267
+ : entry.targetJoinField!;
268
+ const targetPkColumn = target
269
+ ? resolveJunctionColumn(target, ctx.pkMap.get(entry.targetEntity)?.fieldName ?? "id", ctx)
270
+ : "id";
271
+
272
+ return code` ${mountM2mRouteSym}({
273
+ fastify: ${fastifyVar},
274
+ path: ${source.name}.$path,
275
+ relationName: ${JSON.stringify(entry.name)},
276
+ db,
277
+ junctionTable: ${junctionVarSym},
278
+ targetTable: ${targetVarSym},
279
+ sourceColumn: ${JSON.stringify(sourceColumn)},
280
+ targetColumn: ${JSON.stringify(targetColumn)},
281
+ targetPkColumn: ${JSON.stringify(targetPkColumn)},
282
+ symmetric: ${entry.symmetric ? "true" : "false"},
283
+ });`;
284
+ }
285
+
286
+ /** Resolve a field's physical column name on an entity (defaults if missing). */
287
+ function resolveJunctionColumn(entity: MetaObject, fieldName: string, ctx: RenderContext): string {
288
+ const field = entity.ownChildren().find((c) => c.type === TYPE_FIELD && c.name === fieldName);
289
+ if (!field) return fieldName;
290
+ return resolveColumnName(field, ctx.columnNamingStrategy);
291
+ }
292
+
293
+ /**
294
+ * FR-017 Tier 2 — the routes file for a TPH discriminator base.
295
+ *
296
+ * Mounts a polymorphic list/get route set at the base path (`GET /auths`,
297
+ * `GET /auths/:id` — rows carry the discriminator by value), then a full
298
+ * per-subtype CRUD route set at `<basePath>/<discriminatorValue lowercased>`
299
+ * (`/auths/bridge`, ...). The per-subtype create body OMITS the discriminator
300
+ * (the URL names the subtype); the runtime helper injects it. The per-subtype
301
+ * route set is scoped to its discriminator value via the `discriminator` option
302
+ * (cross-subtype get/update/delete 404; update strips the discriminator).
303
+ *
304
+ * Subtype route segment defaults to the lowercased `@discriminatorValue`
305
+ * (`"Bridge"` → `bridge`) — a robust, value-derived path that matches the
306
+ * FR-017 design's `/auths/bridge` examples. Fastify resolves the static
307
+ * `/auths/bridge` ahead of the parametric `/auths/:id`, so the two coexist.
308
+ */
309
+ function renderTphRoutesFile(base: MetaObject, ctx: RenderContext): string {
310
+ const baseName = base.name;
311
+ const handlerName = routesHandlerName(baseName);
312
+ // Single source of truth for the discriminator field + subtypes + route segments.
313
+ const plan = tphPlan(base, ctx.loadedRoot)!;
314
+ const discField = plan.discriminatorField;
315
+ const tableVar = ctx.collectionName(baseName);
316
+
317
+ const baseFileSpec = entityModuleSpecifier(
318
+ ctx.selfTarget, ctx.entityModuleTarget, base.package, baseName, ctx.extStyle,
319
+ );
320
+ const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout, base.package, ctx.dbImport);
321
+
322
+ const FastifyInstanceSym = imp("t:FastifyInstance@fastify");
323
+ const mountCrudRoutesSym = imp("mountCrudRoutes@@metaobjectsdev/runtime-ts/drizzle-fastify");
324
+ const dbSym = imp(`db@${dbImportSpec}`);
325
+ const tableSym = imp(`${tableVar}@${baseFileSpec}`);
326
+ const baseConstSym = imp(`${baseName}@${baseFileSpec}`);
327
+ const baseInsertSym = imp(`${baseName}InsertSchema@${baseFileSpec}`);
328
+ const baseUpdateSym = imp(`${baseName}UpdateSchema@${baseFileSpec}`);
329
+ const baseFilterSym = imp(`${baseName}FilterAllowlist@${baseFileSpec}`);
330
+ const baseSortSym = imp(`${baseName}SortAllowlist@${baseFileSpec}`);
331
+
332
+ const fastifyRef = ctx.apiPrefix ? "instance" : "fastify";
333
+ const dialectLit = JSON.stringify(ctx.dialect);
334
+
335
+ const polymorphic = code`
336
+ ${mountCrudRoutesSym}({
337
+ fastify: ${fastifyRef},
338
+ path: ${baseConstSym}.$path,
339
+ db: ${dbSym},
340
+ table: ${tableSym},
341
+ insertSchema: ${baseInsertSym},
342
+ updateSchema: ${baseUpdateSym},
343
+ filterAllowlist: ${baseFilterSym},
344
+ sortAllowlist: ${baseSortSym},
345
+ dialect: ${dialectLit},
346
+ expose: ["list", "get"],
347
+ });`;
348
+
349
+ const subtypeMounts: Code[] = plan.subtypes.map(({ entity: sub, value, routeSegment: segment }) => {
350
+ const subFileSpec = entityModuleSpecifier(
351
+ ctx.selfTarget, ctx.entityModuleTarget, sub.package, sub.name, ctx.extStyle,
352
+ );
353
+ const subInsertSym = imp(`${sub.name}InsertSchema@${subFileSpec}`);
354
+ // FR-017 Tier 3: each subtype carries its OWN filter/sort allowlist
355
+ // (discriminator excluded — it's pinned by this path).
356
+ const subFilterSym = imp(`${sub.name}FilterAllowlist@${subFileSpec}`);
357
+ const subSortSym = imp(`${sub.name}SortAllowlist@${subFileSpec}`);
358
+ return code`
359
+ ${mountCrudRoutesSym}({
360
+ fastify: ${fastifyRef},
361
+ path: ${baseConstSym}.$path + ${JSON.stringify("/" + segment)},
362
+ db: ${dbSym},
363
+ table: ${tableSym},
364
+ insertSchema: ${subInsertSym}.omit({ ${discField}: true }),
365
+ updateSchema: ${subInsertSym}.omit({ ${discField}: true }).partial(),
366
+ filterAllowlist: ${subFilterSym},
367
+ sortAllowlist: ${subSortSym},
368
+ dialect: ${dialectLit},
369
+ discriminator: { column: ${JSON.stringify(discField)}, value: ${JSON.stringify(value)} },
370
+ });`;
371
+ });
372
+
373
+ const mounts = joinCode([polymorphic, ...subtypeMounts], { on: "\n" });
374
+
375
+ const fn = ctx.apiPrefix
376
+ ? code`
377
+ /**
378
+ * Mount polymorphic + per-subtype REST endpoints for the ${baseName} TPH hierarchy.
379
+ *
380
+ * GET ${baseName}.$path (+ /:id) lists/gets the discriminated union; each
381
+ * /${baseName}.$path/<subtype> path is a full per-subtype CRUD set.
382
+ */
383
+ export async function ${handlerName}(fastify: ${FastifyInstanceSym}) {
384
+ await fastify.register(async (instance) => {
385
+ ${mounts}
386
+ }, { prefix: ${JSON.stringify(ctx.apiPrefix)} });
387
+ }
388
+ `
389
+ : code`
390
+ /**
391
+ * Mount polymorphic + per-subtype REST endpoints for the ${baseName} TPH hierarchy.
392
+ *
393
+ * GET ${baseName}.$path (+ /:id) lists/gets the discriminated union; each
394
+ * /${baseName}.$path/<subtype> path is a full per-subtype CRUD set.
395
+ */
396
+ export async function ${handlerName}(fastify: ${FastifyInstanceSym}) {
397
+ ${mounts}
398
+ }
399
+ `;
400
+
401
+ const header =
402
+ `// ${GENERATED_HEADER} — DO NOT EDIT.\n` +
403
+ `// Source metadata: ${baseName} (${base.fqn()}) — TPH discriminator base\n` +
404
+ `// Customize via ${baseName}.extra.ts in this directory (e.g., auth, additional handlers).\n`;
405
+ return header + fn.toString();
406
+ }