@metaobjectsdev/codegen-ts 0.24.5 → 1.0.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 (237) hide show
  1. package/README.md +37 -12
  2. package/dist/api-surface.d.ts +29 -0
  3. package/dist/api-surface.d.ts.map +1 -1
  4. package/dist/api-surface.js +47 -0
  5. package/dist/api-surface.js.map +1 -1
  6. package/dist/column-mapper.d.ts +12 -0
  7. package/dist/column-mapper.d.ts.map +1 -1
  8. package/dist/column-mapper.js +1 -0
  9. package/dist/column-mapper.js.map +1 -1
  10. package/dist/constants.d.ts +24 -11
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/constants.js +54 -19
  13. package/dist/constants.js.map +1 -1
  14. package/dist/db-emitting.d.ts +6 -0
  15. package/dist/db-emitting.d.ts.map +1 -0
  16. package/dist/db-emitting.js +26 -0
  17. package/dist/db-emitting.js.map +1 -0
  18. package/dist/generator-registry.d.ts.map +1 -1
  19. package/dist/generator-registry.js +14 -1
  20. package/dist/generator-registry.js.map +1 -1
  21. package/dist/generator.d.ts +5 -0
  22. package/dist/generator.d.ts.map +1 -1
  23. package/dist/generator.js.map +1 -1
  24. package/dist/generators/agent-docs-file.d.ts +19 -0
  25. package/dist/generators/agent-docs-file.d.ts.map +1 -0
  26. package/dist/generators/agent-docs-file.js +257 -0
  27. package/dist/generators/agent-docs-file.js.map +1 -0
  28. package/dist/generators/agent-requirements-page.d.ts +22 -0
  29. package/dist/generators/agent-requirements-page.d.ts.map +1 -0
  30. package/dist/generators/agent-requirements-page.js +175 -0
  31. package/dist/generators/agent-requirements-page.js.map +1 -0
  32. package/dist/generators/agent-schema-input.d.ts +69 -0
  33. package/dist/generators/agent-schema-input.d.ts.map +1 -0
  34. package/dist/generators/agent-schema-input.js +23 -0
  35. package/dist/generators/agent-schema-input.js.map +1 -0
  36. package/dist/generators/agent-schema-page.d.ts +21 -0
  37. package/dist/generators/agent-schema-page.d.ts.map +1 -0
  38. package/dist/generators/agent-schema-page.js +241 -0
  39. package/dist/generators/agent-schema-page.js.map +1 -0
  40. package/dist/generators/agent-ui-page.d.ts +22 -0
  41. package/dist/generators/agent-ui-page.d.ts.map +1 -0
  42. package/dist/generators/agent-ui-page.js +253 -0
  43. package/dist/generators/agent-ui-page.js.map +1 -0
  44. package/dist/generators/api-docs-file.d.ts.map +1 -1
  45. package/dist/generators/api-docs-file.js +13 -6
  46. package/dist/generators/api-docs-file.js.map +1 -1
  47. package/dist/generators/api-model.d.ts +6 -0
  48. package/dist/generators/api-model.d.ts.map +1 -1
  49. package/dist/generators/api-model.js +54 -35
  50. package/dist/generators/api-model.js.map +1 -1
  51. package/dist/generators/callable-file.js +1 -1
  52. package/dist/generators/callable-file.js.map +1 -1
  53. package/dist/generators/docs-data-builder.d.ts.map +1 -1
  54. package/dist/generators/docs-data-builder.js +47 -2
  55. package/dist/generators/docs-data-builder.js.map +1 -1
  56. package/dist/generators/docs-data.d.ts +6 -2
  57. package/dist/generators/docs-data.d.ts.map +1 -1
  58. package/dist/generators/index.d.ts +3 -8
  59. package/dist/generators/index.d.ts.map +1 -1
  60. package/dist/generators/index.js +19 -14
  61. package/dist/generators/index.js.map +1 -1
  62. package/dist/generators/names-file.d.ts +17 -0
  63. package/dist/generators/names-file.d.ts.map +1 -0
  64. package/dist/generators/names-file.js +95 -0
  65. package/dist/generators/names-file.js.map +1 -0
  66. package/dist/generators/requirements-markdown.d.ts +18 -1
  67. package/dist/generators/requirements-markdown.d.ts.map +1 -1
  68. package/dist/generators/requirements-markdown.js +11 -9
  69. package/dist/generators/requirements-markdown.js.map +1 -1
  70. package/dist/generators/routes-file-hono.d.ts +17 -2
  71. package/dist/generators/routes-file-hono.d.ts.map +1 -1
  72. package/dist/generators/routes-file-hono.js +9 -11
  73. package/dist/generators/routes-file-hono.js.map +1 -1
  74. package/dist/generators/routes-file.d.ts +17 -2
  75. package/dist/generators/routes-file.d.ts.map +1 -1
  76. package/dist/generators/routes-file.js +10 -8
  77. package/dist/generators/routes-file.js.map +1 -1
  78. package/dist/generators/template-generator.d.ts.map +1 -1
  79. package/dist/generators/template-generator.js +20 -6
  80. package/dist/generators/template-generator.js.map +1 -1
  81. package/dist/index.d.ts +20 -5
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +43 -4
  84. package/dist/index.js.map +1 -1
  85. package/dist/metaobjects-config.d.ts +38 -1
  86. package/dist/metaobjects-config.d.ts.map +1 -1
  87. package/dist/metaobjects-config.js +9 -1
  88. package/dist/metaobjects-config.js.map +1 -1
  89. package/dist/names.d.ts +275 -0
  90. package/dist/names.d.ts.map +1 -0
  91. package/dist/names.js +433 -0
  92. package/dist/names.js.map +1 -0
  93. package/dist/overwrite-policy.d.ts.map +1 -1
  94. package/dist/overwrite-policy.js +15 -3
  95. package/dist/overwrite-policy.js.map +1 -1
  96. package/dist/projection/extract-view-spec.d.ts +25 -1
  97. package/dist/projection/extract-view-spec.d.ts.map +1 -1
  98. package/dist/projection/extract-view-spec.js +79 -6
  99. package/dist/projection/extract-view-spec.js.map +1 -1
  100. package/dist/reference-templates.d.ts +1 -1
  101. package/dist/reference-templates.d.ts.map +1 -1
  102. package/dist/reference-templates.js +1 -1
  103. package/dist/reference-templates.js.map +1 -1
  104. package/dist/render-context.d.ts +12 -1
  105. package/dist/render-context.d.ts.map +1 -1
  106. package/dist/render-context.js +1 -0
  107. package/dist/render-context.js.map +1 -1
  108. package/dist/retired-codegen-attrs.d.ts +32 -0
  109. package/dist/retired-codegen-attrs.d.ts.map +1 -0
  110. package/dist/retired-codegen-attrs.js +47 -0
  111. package/dist/retired-codegen-attrs.js.map +1 -0
  112. package/dist/routes-expose.d.ts +63 -0
  113. package/dist/routes-expose.d.ts.map +1 -0
  114. package/dist/routes-expose.js +57 -0
  115. package/dist/routes-expose.js.map +1 -0
  116. package/dist/runner.d.ts +18 -0
  117. package/dist/runner.d.ts.map +1 -1
  118. package/dist/runner.js +109 -7
  119. package/dist/runner.js.map +1 -1
  120. package/dist/templates/callable-file.d.ts +7 -2
  121. package/dist/templates/callable-file.d.ts.map +1 -1
  122. package/dist/templates/callable-file.js +54 -9
  123. package/dist/templates/callable-file.js.map +1 -1
  124. package/dist/templates/drizzle-schema.d.ts.map +1 -1
  125. package/dist/templates/drizzle-schema.js +236 -65
  126. package/dist/templates/drizzle-schema.js.map +1 -1
  127. package/dist/templates/entity-constants.d.ts +15 -6
  128. package/dist/templates/entity-constants.d.ts.map +1 -1
  129. package/dist/templates/entity-constants.js +85 -161
  130. package/dist/templates/entity-constants.js.map +1 -1
  131. package/dist/templates/entity-file.d.ts.map +1 -1
  132. package/dist/templates/entity-file.js +38 -4
  133. package/dist/templates/entity-file.js.map +1 -1
  134. package/dist/templates/entity-meta-file.d.ts +15 -1
  135. package/dist/templates/entity-meta-file.d.ts.map +1 -1
  136. package/dist/templates/entity-meta-file.js +12 -2
  137. package/dist/templates/entity-meta-file.js.map +1 -1
  138. package/dist/templates/entity-ui-descriptor.d.ts +112 -0
  139. package/dist/templates/entity-ui-descriptor.d.ts.map +1 -0
  140. package/dist/templates/entity-ui-descriptor.js +238 -0
  141. package/dist/templates/entity-ui-descriptor.js.map +1 -0
  142. package/dist/templates/field-meta.d.ts +46 -9
  143. package/dist/templates/field-meta.d.ts.map +1 -1
  144. package/dist/templates/field-meta.js +95 -21
  145. package/dist/templates/field-meta.js.map +1 -1
  146. package/dist/templates/names-decl.d.ts +38 -0
  147. package/dist/templates/names-decl.d.ts.map +1 -0
  148. package/dist/templates/names-decl.js +104 -0
  149. package/dist/templates/names-decl.js.map +1 -0
  150. package/dist/templates/projection-decl.d.ts +21 -0
  151. package/dist/templates/projection-decl.d.ts.map +1 -1
  152. package/dist/templates/projection-decl.js +53 -29
  153. package/dist/templates/projection-decl.js.map +1 -1
  154. package/dist/templates/queries-file.d.ts.map +1 -1
  155. package/dist/templates/queries-file.js +17 -2
  156. package/dist/templates/queries-file.js.map +1 -1
  157. package/dist/templates/requirement-test.d.ts.map +1 -1
  158. package/dist/templates/requirement-test.js +13 -2
  159. package/dist/templates/requirement-test.js.map +1 -1
  160. package/dist/templates/routes-file-hono.d.ts +2 -1
  161. package/dist/templates/routes-file-hono.d.ts.map +1 -1
  162. package/dist/templates/routes-file-hono.js +11 -2
  163. package/dist/templates/routes-file-hono.js.map +1 -1
  164. package/dist/templates/routes-file.d.ts +2 -1
  165. package/dist/templates/routes-file.d.ts.map +1 -1
  166. package/dist/templates/routes-file.js +54 -21
  167. package/dist/templates/routes-file.js.map +1 -1
  168. package/dist/templates/value-object-file.d.ts.map +1 -1
  169. package/dist/templates/value-object-file.js +15 -1
  170. package/dist/templates/value-object-file.js.map +1 -1
  171. package/dist/templates/view-decl.d.ts +37 -1
  172. package/dist/templates/view-decl.d.ts.map +1 -1
  173. package/dist/templates/view-decl.js +27 -3
  174. package/dist/templates/view-decl.js.map +1 -1
  175. package/dist/templates/zod-validators.d.ts +21 -0
  176. package/dist/templates/zod-validators.d.ts.map +1 -1
  177. package/dist/templates/zod-validators.js +40 -5
  178. package/dist/templates/zod-validators.js.map +1 -1
  179. package/dist/view-context.d.ts +24 -0
  180. package/dist/view-context.d.ts.map +1 -0
  181. package/dist/view-context.js +81 -0
  182. package/dist/view-context.js.map +1 -0
  183. package/package.json +6 -6
  184. package/src/api-surface.ts +53 -0
  185. package/src/column-mapper.ts +13 -0
  186. package/src/constants.ts +91 -16
  187. package/src/db-emitting.ts +31 -0
  188. package/src/generator-registry.ts +14 -4
  189. package/src/generator.ts +5 -0
  190. package/src/generators/agent-docs-file.ts +297 -0
  191. package/src/generators/agent-requirements-page.ts +199 -0
  192. package/src/generators/agent-schema-input.ts +88 -0
  193. package/src/generators/agent-schema-page.ts +297 -0
  194. package/src/generators/agent-ui-page.ts +290 -0
  195. package/src/generators/api-docs-file.ts +13 -6
  196. package/src/generators/api-model.ts +63 -34
  197. package/src/generators/callable-file.ts +1 -1
  198. package/src/generators/docs-data-builder.ts +46 -2
  199. package/src/generators/docs-data.ts +6 -2
  200. package/src/generators/index.ts +25 -14
  201. package/src/generators/names-file.ts +98 -0
  202. package/src/generators/requirements-markdown.ts +36 -2
  203. package/src/generators/routes-file-hono.ts +19 -10
  204. package/src/generators/routes-file.ts +20 -7
  205. package/src/generators/template-generator.ts +20 -6
  206. package/src/index.ts +64 -4
  207. package/src/metaobjects-config.ts +48 -2
  208. package/src/names.ts +629 -0
  209. package/src/overwrite-policy.ts +15 -3
  210. package/src/projection/extract-view-spec.ts +85 -6
  211. package/src/reference/entity.ts +21 -2
  212. package/src/reference/names.ts +125 -0
  213. package/src/reference/queries.ts +19 -3
  214. package/src/reference/routes-hono.ts +19 -9
  215. package/src/reference/routes.ts +25 -9
  216. package/src/reference-templates.ts +1 -1
  217. package/src/render-context.ts +13 -1
  218. package/src/retired-codegen-attrs.ts +52 -0
  219. package/src/routes-expose.ts +86 -0
  220. package/src/runner.ts +122 -11
  221. package/src/templates/callable-file.ts +60 -8
  222. package/src/templates/drizzle-schema.ts +248 -60
  223. package/src/templates/entity-constants.ts +95 -184
  224. package/src/templates/entity-file.ts +46 -4
  225. package/src/templates/entity-meta-file.ts +16 -3
  226. package/src/templates/entity-ui-descriptor.ts +305 -0
  227. package/src/templates/field-meta.ts +100 -19
  228. package/src/templates/names-decl.ts +165 -0
  229. package/src/templates/projection-decl.ts +72 -31
  230. package/src/templates/queries-file.ts +17 -2
  231. package/src/templates/requirement-test.ts +13 -2
  232. package/src/templates/routes-file-hono.ts +14 -2
  233. package/src/templates/routes-file.ts +69 -24
  234. package/src/templates/value-object-file.ts +15 -1
  235. package/src/templates/view-decl.ts +53 -3
  236. package/src/templates/zod-validators.ts +41 -5
  237. package/src/view-context.ts +88 -0
@@ -12,9 +12,7 @@
12
12
  // name: "email",
13
13
  // label: "Email Address", // from @label on the view, falls back to humanized field name
14
14
  // view: "text", // MetaView subtype
15
- // htmlType: "email", // optional; only when it maps to a real HTML input type
16
- // placeholder: "you@example.com", // optional; only when @placeholder is set on the view
17
- // helpText: "We never share this.", // optional; only when @helpText is set
15
+ // htmlType: "email", // optional; only when the view maps to a real HTML input type
18
16
  // rules: { // optional; derived from validator children
19
17
  // required: "Email is required",
20
18
  // maxLength: { value: 255, message: "Must be 255 characters or fewer" },
@@ -28,205 +26,110 @@
28
26
  // per-attribute access (placeholder, rules, etc.) by hand — the helper
29
27
  // picks them up from this object automatically.
30
28
 
31
- import { code, type Code } from "ts-poet";
32
- import type { MetaData } from "@metaobjectsdev/metadata";
33
- import { MetaObject, MetaField } from "@metaobjectsdev/metadata";
29
+ import { code, joinCode, type Code } from "ts-poet";
30
+ import { MetaObject } from "@metaobjectsdev/metadata";
31
+ import { resolveTableName } from "@metaobjectsdev/metadata";
32
+ import { physicalNameExpr } from "../names.js";
34
33
  import {
35
- VIEW_SUBTYPE_TEXT,
36
- VIEW_SUBTYPE_TEXTAREA,
37
- VIEW_SUBTYPE_NUMBER,
38
- VIEW_SUBTYPE_CHECKBOX,
39
- VIEW_SUBTYPE_DATE,
40
- VIEW_SUBTYPE_PASSWORD,
41
- VIEW_SUBTYPE_HIDDEN,
42
- VIEW_SUBTYPE_DROPDOWN,
43
- VIEW_SUBTYPE_RADIO,
44
- VALIDATOR_SUBTYPE_REQUIRED,
45
- VALIDATOR_SUBTYPE_LENGTH,
46
- VALIDATOR_SUBTYPE_REGEX,
47
- VALIDATOR_ATTR_MIN,
48
- VALIDATOR_ATTR_MAX,
49
- VALIDATOR_ATTR_PATTERN,
50
- FIELD_ATTR_MAX_LENGTH,
51
- FIELD_ATTR_REQUIRED,
52
- } from "@metaobjectsdev/metadata";
53
- import { resolveTableName, pluralize, toSnakeCase } from "@metaobjectsdev/metadata";
54
- import { inferViewKind, currencyMetaFor, labelFor } from "./field-meta.js";
34
+ buildEntityUiDescriptor,
35
+ resourcePath,
36
+ type UiFieldDescriptor,
37
+ type UiRule,
38
+ } from "./entity-ui-descriptor.js";
55
39
 
56
- /** Convert a camelCase or PascalCase field name to a human-friendly label. */
57
- function humanize(s: string): string {
58
- return s
59
- .replace(/([a-z])([A-Z])/g, "$1 $2")
60
- .replace(/_/g, " ")
61
- .replace(/\b\w/g, (c) => c.toUpperCase())
62
- .trim();
63
- }
64
-
65
- /**
66
- * REST resource path for an entity. Pluralized + snake_cased + lowercased.
67
- * "Subscriber" → "/subscribers"
68
- * "WorkoutEvent" → "/workout_events"
69
- */
70
- export function resourcePath(entity: MetaData): string {
71
- // ADR-0039: resolving — a concrete entity may inherit @routePath via extends.
72
- const overrideAttr = entity.attr("routePath");
73
- if (typeof overrideAttr === "string" && overrideAttr.length > 0) {
74
- return overrideAttr.startsWith("/") ? overrideAttr : `/${overrideAttr}`;
75
- }
76
- return `/${pluralize(toSnakeCase(entity.name))}`;
77
- }
78
-
79
- /** Resolve the view subtype: explicit `view` child (own or inherited) wins, else inferred from field subType. */
80
- function resolveView(field: MetaField): { view: string; viewNode?: MetaData } {
81
- const viewChild = field.views()[0];
82
- if (viewChild) {
83
- return { view: viewChild.subType, viewNode: viewChild };
84
- }
85
- return { view: inferViewKind(field) };
86
- }
87
-
88
- /**
89
- * Map a MetaView subtype to a real HTML <input type=...> value. Returns
90
- * undefined for views that don't map to <input> at all (textarea, dropdown,
91
- * radio) — consumers render the right element type themselves.
92
- */
93
- function htmlTypeFromView(view: string, override?: string): string | undefined {
94
- if (typeof override === "string" && override.length > 0) return override;
95
- switch (view) {
96
- case VIEW_SUBTYPE_TEXT:
97
- return "text";
98
- case VIEW_SUBTYPE_NUMBER:
99
- return "number";
100
- case VIEW_SUBTYPE_DATE:
101
- return "date";
102
- case VIEW_SUBTYPE_PASSWORD:
103
- return "password";
104
- case VIEW_SUBTYPE_CHECKBOX:
105
- return "checkbox";
106
- case VIEW_SUBTYPE_HIDDEN:
107
- return "hidden";
108
- case VIEW_SUBTYPE_RADIO:
109
- return "radio";
110
- case "month":
111
- return "month";
112
- case "email":
113
- return "email";
114
- case VIEW_SUBTYPE_TEXTAREA:
115
- case VIEW_SUBTYPE_DROPDOWN:
116
- return undefined;
117
- default:
118
- return undefined;
119
- }
120
- }
40
+ // `resourcePath` moved to entity-ui-descriptor.ts with the rest of the form derivation.
41
+ // Re-exported here because it is part of this module's published surface (the package
42
+ // barrel re-exports it from this path, and api-model.ts imports it from here).
43
+ export { resourcePath };
121
44
 
122
45
  /**
123
- * Build RHF rules JSON-ish code from a field's validator children plus
124
- * field-level attrs (`@required`, `@maxLength`). Returns the code string
125
- * (e.g. `{ required: "X", maxLength: { value: 255, message: "..." } }`)
126
- * or undefined when there are no rules to emit.
46
+ * Render one rule entry as the RHF-shaped code fragment the descriptor emits.
47
+ *
48
+ * Order is the descriptor's, which is declaration order see the ordering note in
49
+ * entity-ui-descriptor.ts for why that is not incidental.
127
50
  */
128
- function renderFieldRules(field: MetaField): string | undefined {
129
- const ruleParts: string[] = [];
130
-
131
- let hasRequired = false;
132
- let hasMaxLength = false;
133
-
134
- // ADR-0039: resolving — a validator may inherit its config attrs (@min/@max/
135
- // @pattern/@message/…) via extends.
136
- for (const child of field.validators()) {
137
- if (child.subType === VALIDATOR_SUBTYPE_REQUIRED) {
138
- const msg = (child.attr("message") as string | undefined) ?? `${humanize(field.name)} is required`;
139
- ruleParts.push(`required: ${JSON.stringify(msg)}`);
140
- hasRequired = true;
141
- } else if (child.subType === VALIDATOR_SUBTYPE_LENGTH) {
142
- const min = child.attr(VALIDATOR_ATTR_MIN);
143
- const max = child.attr(VALIDATOR_ATTR_MAX);
144
- if (typeof min === "number") {
145
- const msg = (child.attr("minMessage") as string | undefined) ?? `Must be at least ${min} characters`;
146
- ruleParts.push(`minLength: { value: ${min}, message: ${JSON.stringify(msg)} }`);
147
- }
148
- if (typeof max === "number") {
149
- const msg = (child.attr("maxMessage") as string | undefined) ?? `Must be ${max} characters or fewer`;
150
- ruleParts.push(`maxLength: { value: ${max}, message: ${JSON.stringify(msg)} }`);
151
- hasMaxLength = true;
152
- }
153
- } else if (child.subType === VALIDATOR_SUBTYPE_REGEX) {
154
- const pattern = child.attr(VALIDATOR_ATTR_PATTERN);
155
- if (typeof pattern === "string") {
156
- const msg = (child.attr("message") as string | undefined) ?? "Invalid format";
157
- // Emit as RegExp literal /.../ — `as const` preserves the value-ref.
158
- // Forward-slash inside the pattern is escaped so the literal closes correctly.
159
- const safe = pattern.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
160
- ruleParts.push(`pattern: { value: /${safe}/, message: ${JSON.stringify(msg)} }`);
161
- }
51
+ function renderRule(r: UiRule): string {
52
+ switch (r.kind) {
53
+ case "required":
54
+ return `required: ${JSON.stringify(r.message)}`;
55
+ case "minLength":
56
+ return `minLength: { value: ${r.value}, message: ${JSON.stringify(r.message)} }`;
57
+ case "maxLength":
58
+ return `maxLength: { value: ${r.value}, message: ${JSON.stringify(r.message)} }`;
59
+ case "pattern": {
60
+ // Emit as a RegExp literal /.../ — `as const` preserves the value-ref. The
61
+ // descriptor carries the RAW pattern; forward-slash and backslash are escaped
62
+ // HERE, because closing a TypeScript regex literal is an emission concern.
63
+ const safe = r.pattern.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
64
+ return `pattern: { value: /${safe}/, message: ${JSON.stringify(r.message)} }`;
162
65
  }
163
66
  }
164
-
165
- // Field-level @required attr (if not already covered by validator).
166
- if (!hasRequired && field.attr(FIELD_ATTR_REQUIRED) === true) {
167
- ruleParts.push(`required: ${JSON.stringify(`${humanize(field.name)} is required`)}`);
168
- }
169
-
170
- // Field-level @maxLength attr (if not already covered).
171
- const maxLenAttr = field.attr(FIELD_ATTR_MAX_LENGTH);
172
- if (!hasMaxLength && typeof maxLenAttr === "number") {
173
- ruleParts.push(
174
- `maxLength: { value: ${maxLenAttr}, message: ${JSON.stringify(`Must be ${maxLenAttr} characters or fewer`)} }`,
175
- );
176
- }
177
-
178
- if (ruleParts.length === 0) return undefined;
179
- return `{ ${ruleParts.join(", ")} }`;
180
67
  }
181
68
 
182
69
  /** Build one nested field-object entry like `email: { name, label, ... },`. */
183
- function renderFieldEntry(field: MetaField): string {
184
- const { view, viewNode } = resolveView(field);
185
- const label = labelFor(field);
186
- // ADR-0039: resolving — a view node may inherit @placeholder/@helpText/@htmlType via extends.
187
- const placeholder = viewNode?.attr("placeholder") as string | undefined;
188
- const helpText = viewNode?.attr("helpText") as string | undefined;
189
- const htmlType = htmlTypeFromView(view, viewNode?.attr("htmlType") as string | undefined);
190
- const rules = renderFieldRules(field);
191
-
70
+ function renderFieldEntry(f: UiFieldDescriptor): string {
192
71
  const entries: string[] = [
193
- `name: ${JSON.stringify(field.name)}`,
194
- `label: ${JSON.stringify(label)}`,
195
- `view: ${JSON.stringify(view)}`,
72
+ `name: ${JSON.stringify(f.name)}`,
73
+ `label: ${JSON.stringify(f.label)}`,
74
+ `view: ${JSON.stringify(f.view)}`,
196
75
  ];
197
- if (htmlType !== undefined) entries.push(`htmlType: ${JSON.stringify(htmlType)}`);
198
- if (placeholder !== undefined) entries.push(`placeholder: ${JSON.stringify(placeholder)}`);
199
- if (helpText !== undefined) entries.push(`helpText: ${JSON.stringify(helpText)}`);
200
- if (rules !== undefined) entries.push(`rules: ${rules}`);
76
+ if (f.htmlType !== undefined) entries.push(`htmlType: ${JSON.stringify(f.htmlType)}`);
77
+ if (f.rules.length > 0) entries.push(`rules: { ${f.rules.map(renderRule).join(", ")} }`);
201
78
 
202
79
  // Currency-specific keys: only emitted for currency-subtype fields.
203
- const currencyMeta = currencyMetaFor(field);
204
- if (currencyMeta !== null) {
205
- entries.push(`currency: ${JSON.stringify(currencyMeta.currency)}`);
206
- entries.push(`locale: ${JSON.stringify(currencyMeta.locale)}`);
80
+ if (f.currency !== undefined) {
81
+ entries.push(`currency: ${JSON.stringify(f.currency.currency)}`);
82
+ entries.push(`locale: ${JSON.stringify(f.currency.locale)}`);
207
83
  }
208
84
 
209
- return ` ${field.name}: {\n ${entries.join(",\n ")},\n }`;
85
+ return ` ${f.name}: {\n ${entries.join(",\n ")},\n }`;
210
86
  }
211
87
 
212
- export function renderEntityConstants(obj: MetaObject, apiPrefix = ""): Code {
213
- const entityName = obj.name;
88
+ export function renderEntityConstants(
89
+ obj: MetaObject,
90
+ /**
91
+ * @deprecated Accepted and IGNORED. The API base URL left the descriptor: generated
92
+ * client artifacts emit entity-relative paths and the base is supplied once at the
93
+ * client provider (`baseUrl`), because where the browser sends a request is a
94
+ * deployment fact rather than something to freeze at `meta gen` time.
95
+ *
96
+ * It must STAY in the signature: `src/reference/entity.ts` is copied verbatim into
97
+ * adopter repos (ADR-0034) and calls this positionally, so removing the parameter
98
+ * would fail to compile in every ejected copy. Removal is a separate later break.
99
+ */
100
+ _apiPrefix = "",
101
+ // §A6. OPTIONAL, and it must stay optional: `src/reference/entity.ts` is copied
102
+ // verbatim into adopter repos by ADR-0034 scaffold-and-own and calls this with two
103
+ // arguments. A required parameter would fail to compile in every ejected copy.
104
+ names?: { readonly name: string; readonly symbol: Code } | undefined,
105
+ ): Code {
106
+ // ONE derivation of the form surface, shared with the `agent/ui.md` docs page — see
107
+ // entity-ui-descriptor.ts. The descriptor already walks the RESOLVING fields(), so
108
+ // inherited fields (from extends:/super:) appear here as they always have.
109
+ const descriptor = buildEntityUiDescriptor(obj);
110
+ const entityName = descriptor.entity;
214
111
  const tableName = resolveTableName(obj);
215
- const path = resourcePath(obj);
216
-
217
- const fieldEntries: string[] = [];
218
- // Use fields() so inherited fields (from extends:/super:) appear in constants.
219
- for (const child of obj.fields()) {
220
- fieldEntries.push(renderFieldEntry(child));
221
- }
222
-
223
- const body = [
224
- ` $entity: ${JSON.stringify(entityName)}`,
225
- ` $table: ${JSON.stringify(tableName)}`,
226
- ` $path: ${JSON.stringify(path)}`,
227
- ` $apiPrefix: ${JSON.stringify(apiPrefix)}`,
228
- ...fieldEntries,
229
- ].join(",\n");
112
+ const path = descriptor.path;
113
+
114
+ const fieldEntries: string[] = descriptor.fields.map(renderFieldEntry);
115
+
116
+ // A6/B2 reference the constant whenever the artifact is in the run. No equality
117
+ // guard: primaryRdbSource (@metaobjectsdev/metadata) refuses any object whose
118
+ // @role: primary sources disagree on a physical name, and both the constant and the
119
+ // literal below resolve through it, so a reference here is the single spelling. `physicalNameExpr` accepts
120
+ // any `{ symbol }`-shaped value, so it works with this function's `{ name, symbol }`
121
+ // parameter as well as `namesRef`'s own `{ resolved, symbol }`.
122
+ const tableLine: Code = code` $table: ${physicalNameExpr(names, tableName, obj)}`;
123
+
124
+ const body = joinCode(
125
+ [
126
+ code` $entity: ${JSON.stringify(entityName)}`,
127
+ tableLine,
128
+ code` $path: ${JSON.stringify(path)}`,
129
+ ...fieldEntries.map((e) => code`${e}`),
130
+ ],
131
+ { on: ",\n" },
132
+ );
230
133
 
231
134
  return code`
232
135
  /**
@@ -234,9 +137,17 @@ export function renderEntityConstants(obj: MetaObject, apiPrefix = ""): Code {
234
137
  *
235
138
  * Use these instead of magic strings so TS catches typos and refactors stay
236
139
  * coherent. Each non-dollar-prefixed key is a per-field object carrying
237
- * name, label, view, optional htmlType/placeholder/helpText, and the
140
+ * name, label, view, an htmlType where the field's view maps to one, and the
238
141
  * RHF-shaped validation rules derived from the field's validator children.
239
142
  *
143
+ * placeholder and helpText are NOT emitted — no provider registers an attribute
144
+ * for them, so nothing here could derive one. (htmlType is emitted, but derived
145
+ * from the view subtype; the @htmlType override was removed for the same reason.)
146
+ * useEntityForm still honours placeholder if you add it: this file is generated,
147
+ * hand edits inside it survive regeneration through the three-way merge, and the
148
+ * consumers read this const rather than the metadata. That is the intended way
149
+ * to set it.
150
+ *
240
151
  * Typical usage with the metaobjects React form helper:
241
152
  *
242
153
  * import { useEntityForm } from '@metaobjectsdev/react';
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { code, imp, joinCode, type Code } from "ts-poet";
10
10
  import type { MetaObject, MetaField } from "@metaobjectsdev/metadata";
11
- import { FIELD_ATTR_OBJECT_REF } from "@metaobjectsdev/metadata";
11
+ import { FIELD_ATTR_OBJECT_REF, SOURCE_ROLE_REPLICA } from "@metaobjectsdev/metadata";
12
12
  import { fieldDeclaringPackage, type RenderContext } from "../render-context.js";
13
13
  import { renderDrizzleSchema } from "./drizzle-schema.js";
14
14
  import { renderInferredTypes, renderEnumTypeAliases } from "./inferred-types.js";
@@ -20,10 +20,13 @@ import { renderTphDiscriminatorUnion, isTphDiscriminatorBase } from "./tph-discr
20
20
  import { GENERATED_HEADER } from "../constants.js";
21
21
  import { isProjection, isWriteThrough } from "../projection/projection-detector.js";
22
22
  import { renderProjectionDecl } from "./projection-decl.js";
23
- import { projectionViewName } from "../projection/extract-view-spec.js";
23
+ import {
24
+ projectionViewName, projectionViewSchema, projectionViewRole,
25
+ } from "../projection/extract-view-spec.js";
24
26
  import { renderExistingViewDecl, renderViewReadZodObject } from "./view-decl.js";
25
27
  import { renderDocsFor } from "./jsdoc.js";
26
28
  import { valueObjectModuleSpecifier } from "../import-path.js";
29
+ import { namesRef, namesConstArg, physicalNameExpr, sourceSchemaExpr } from "../names.js";
27
30
  import { hasWritableRdbSource } from "../source-detect.js";
28
31
  import { renderValueObjectFile } from "./value-object-file.js";
29
32
  import { isAbstract } from "../instance-artifacts.js";
@@ -76,6 +79,10 @@ export function renderEntityFile(
76
79
  // Projections intentionally get the z.enum() validator but NOT a named enum
77
80
  // type alias — emitting aliases here is a deliberate v1 scope decision.
78
81
  if (isProjection(entity)) {
82
+ // §A6/§B2 — same `namesRef` pair drizzle-schema.ts builds, so the projection's view
83
+ // name + per-field dbCol reference the exact constant the names artifact exports
84
+ // (undefined when the artifact is not in this run — see names.ts). `namesRef`'s
85
+ // `{ resolved, symbol }` return is exactly the shape `ProjectionDeclOpts.names` wants.
79
86
  return renderProjectionDecl(entity, ctx.loadedRoot, {
80
87
  columnNamingStrategy: ctx.columnNamingStrategy,
81
88
  dialect: ctx.dialect,
@@ -85,6 +92,7 @@ export function renderEntityFile(
85
92
  ctx,
86
93
  // Contract target drops the Drizzle .existing() view decl + drizzle-orm import.
87
94
  includeViewDecl: runtime,
95
+ names: namesRef(entity, ctx),
88
96
  });
89
97
  }
90
98
 
@@ -141,17 +149,46 @@ export function renderEntityFile(
141
149
  const module = valueObjectModuleSpecifier(name, ctx.packageOf, entity.package, ctx.outputLayout, ctx.extStyle);
142
150
  return { name, module };
143
151
  };
152
+ // ONE selection of the replica source: `projectionViewName` names it, and this is the
153
+ // role its entry has in <Entity>Names.sources. Hardcoding "replica" here would be a
154
+ // second derivation of the very thing extract-view-spec.ts exists to decide once.
155
+ const viewRole = projectionViewRole(entity) ?? SOURCE_ROLE_REPLICA;
156
+ const entityNames = namesRef(entity, ctx);
144
157
  const viewOpts = {
145
158
  dialect: ctx.dialect, columnNamingStrategy: ctx.columnNamingStrategy, timestampMode: ctx.timestampMode, voRef,
146
159
  // PK fields type non-null in the replica-view decl + read schema even
147
160
  // without @required (a PK is never NULL; see ViewDeclOpts.pkFieldNames).
148
161
  pkFieldNames: new Set(primaryIdentityFieldNames(entity)) as ReadonlySet<string>,
162
+ // §A6 — the entity's names artifact supplies this view's COLUMN constants. A
163
+ // replica view exposes the entity's own fields, so `<Entity>Names.fields.<f>.column`
164
+ // is the same physical name the table side binds; a derived field the artifact
165
+ // does not carry falls back to the literal via `columnExpr`. The view's own NAME
166
+ // is passed separately below and stays a literal — the artifact holds the primary
167
+ // (table) source's name, not this one.
168
+ names: entityNames,
169
+ // The replica view's OWN @schema, from the same source node projectionViewName picks
170
+ // — never the entity's, which is the WRITE TABLE's and would qualify this view with a
171
+ // schema that belongs to something else. A view and the table it replicates need not
172
+ // live in the same schema, and a name paired with someone else's schema is worse than
173
+ // no schema at all.
174
+ //
175
+ // Both this and the view's NAME are now CONSTANTS. They were literals for a real
176
+ // reason that has stopped being true: <Entity>Names carried the primary source only,
177
+ // so a write-through entity's two physical names had one slot between them and the
178
+ // second was emitted in full — the same escape C#'s AppDbContext had, in a different
179
+ // language, which is what showed it was the artifact's shape and not the binding.
180
+ schema: sourceSchemaExpr(entityNames, viewRole) ?? projectionViewSchema(entity),
149
181
  };
150
182
  const z = imp("z@zod");
151
183
  const docs = renderDocsFor(entity);
152
184
  const docsPrefix = docs ? `${docs}\n` : "";
153
185
  viewSections.push(
154
- renderExistingViewDecl(fields, projectionViewName(entity, ctx.columnNamingStrategy), `${camel}View`, viewOpts),
186
+ renderExistingViewDecl(
187
+ fields,
188
+ physicalNameExpr(entityNames, projectionViewName(entity, ctx.columnNamingStrategy), entity, viewRole),
189
+ `${camel}View`,
190
+ viewOpts,
191
+ ),
155
192
  code`
156
193
  export const ${entity.name}Schema = ${renderViewReadZodObject(fields, viewOpts)};
157
194
  `,
@@ -161,13 +198,18 @@ ${docsPrefix}export type ${entity.name} = ${z}.infer<typeof ${entity.name}Schema
161
198
  );
162
199
  }
163
200
 
201
+ // §A6/§B2 — same `namesRef` pair drizzle-schema.ts (and this file's own projection
202
+ // branch above) build, so the descriptor's $table references the exact constant the
203
+ // names artifact exports (undefined when the artifact is not in this run).
204
+ const constantsNames = namesRef(entity, ctx);
205
+
164
206
  const sections: Code[] = [
165
207
  renderDrizzleSchema(entity, ctx),
166
208
  ...viewSections,
167
209
  renderInferredTypes(entity, tphBase, ctx, writeThrough /* skipRow — read type is the view schema */),
168
210
  ...(enumAliases !== null ? [enumAliases] : []),
169
211
  renderZodValidators(entity, ctx),
170
- renderEntityConstants(entity, ctx.apiPrefix),
212
+ renderEntityConstants(entity, ctx.apiPrefix, namesConstArg(constantsNames)),
171
213
  ...(allowlists ? [renderFilterAllowlist(entity, undefined, ctx), renderSortAllowlist(entity)] : []),
172
214
  renderFilterType(entity),
173
215
  ...(tphBlock !== null ? [tphBlock] : []),
@@ -20,7 +20,7 @@
20
20
  // consumer having wired an extra generator, since the entity generator is
21
21
  // scaffold-and-own (ADR-0034) and cannot be changed from the package.
22
22
 
23
- import { code } from "ts-poet";
23
+ import { code, type Code } from "ts-poet";
24
24
  import type { MetaObject } from "@metaobjectsdev/metadata";
25
25
  import { GENERATED_HEADER } from "../constants.js";
26
26
  import { renderEntityConstants } from "./entity-constants.js";
@@ -35,7 +35,20 @@ export function entityMetaSpecifier(entityName: string, extStyle: string | undef
35
35
  return `./${entityName}.meta${extStyle === "js" ? ".js" : ""}`;
36
36
  }
37
37
 
38
- export function renderEntityMetaFile(entity: MetaObject, apiPrefix = ""): string {
38
+ export function renderEntityMetaFile(
39
+ entity: MetaObject,
40
+ /**
41
+ * @deprecated Accepted and IGNORED — see `renderEntityConstants`. The API base URL
42
+ * left the descriptor; the client provider's `baseUrl` supplies it at runtime.
43
+ *
44
+ * §A6 fix round 3. OPTIONAL, and it must stay optional: this function is called
45
+ * from codegen-ts-tanstack's reference/hooks.ts and reference/grid-hook.ts, both
46
+ * copied verbatim into adopter repos (ADR-0034). A required parameter would fail
47
+ * to compile in every ejected copy.
48
+ */
49
+ _apiPrefix = "",
50
+ names?: { readonly name: string; readonly symbol: Code } | undefined,
51
+ ): string {
39
52
  const body = code`
40
53
  // ${GENERATED_HEADER} — DO NOT EDIT.
41
54
  // Source metadata: ${entity.name}
@@ -45,7 +58,7 @@ export function renderEntityMetaFile(entity: MetaObject, apiPrefix = ""): string
45
58
  // from here so a client bundle never pulls the ORM in. \`${entity.name}.ts\` also
46
59
  // exports this descriptor, for server-side and pre-existing consumers.
47
60
 
48
- ${renderEntityConstants(entity, apiPrefix)}
61
+ ${renderEntityConstants(entity, "", names)}
49
62
  `;
50
63
  return body.toString();
51
64
  }