@metaobjectsdev/codegen-ts 0.10.0 → 0.11.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 (132) hide show
  1. package/dist/column-mapper.d.ts +12 -6
  2. package/dist/column-mapper.d.ts.map +1 -1
  3. package/dist/column-mapper.js +48 -24
  4. package/dist/column-mapper.js.map +1 -1
  5. package/dist/enum-shared.js +1 -1
  6. package/dist/enum-shared.js.map +1 -1
  7. package/dist/generators/docs-data-builder.js +14 -14
  8. package/dist/generators/docs-data-builder.js.map +1 -1
  9. package/dist/generators/template-payload-tree.js +1 -1
  10. package/dist/generators/template-payload-tree.js.map +1 -1
  11. package/dist/import-path.d.ts +18 -0
  12. package/dist/import-path.d.ts.map +1 -1
  13. package/dist/import-path.js +21 -0
  14. package/dist/import-path.js.map +1 -1
  15. package/dist/metaobjects-config.d.ts +26 -0
  16. package/dist/metaobjects-config.d.ts.map +1 -1
  17. package/dist/metaobjects-config.js +3 -0
  18. package/dist/metaobjects-config.js.map +1 -1
  19. package/dist/naming.d.ts +20 -2
  20. package/dist/naming.d.ts.map +1 -1
  21. package/dist/naming.js +11 -3
  22. package/dist/naming.js.map +1 -1
  23. package/dist/payload-codegen.js +2 -2
  24. package/dist/payload-codegen.js.map +1 -1
  25. package/dist/pk-resolver.d.ts.map +1 -1
  26. package/dist/pk-resolver.js +4 -2
  27. package/dist/pk-resolver.js.map +1 -1
  28. package/dist/projection/extract-view-spec.js +1 -1
  29. package/dist/projection/extract-view-spec.js.map +1 -1
  30. package/dist/render-context.d.ts +21 -2
  31. package/dist/render-context.d.ts.map +1 -1
  32. package/dist/render-context.js +7 -0
  33. package/dist/render-context.js.map +1 -1
  34. package/dist/runner.d.ts.map +1 -1
  35. package/dist/runner.js +3 -0
  36. package/dist/runner.js.map +1 -1
  37. package/dist/templates/drizzle-schema.d.ts.map +1 -1
  38. package/dist/templates/drizzle-schema.js +44 -23
  39. package/dist/templates/drizzle-schema.js.map +1 -1
  40. package/dist/templates/entity-constants.js +2 -2
  41. package/dist/templates/entity-constants.js.map +1 -1
  42. package/dist/templates/entity-file.js +1 -1
  43. package/dist/templates/entity-file.js.map +1 -1
  44. package/dist/templates/extract-delegate-emitter.js +1 -1
  45. package/dist/templates/extract-delegate-emitter.js.map +1 -1
  46. package/dist/templates/extractor.js +2 -2
  47. package/dist/templates/extractor.js.map +1 -1
  48. package/dist/templates/field-meta.js +1 -1
  49. package/dist/templates/field-meta.js.map +1 -1
  50. package/dist/templates/filter-allowlist.js +2 -2
  51. package/dist/templates/filter-allowlist.js.map +1 -1
  52. package/dist/templates/filter-shared.js +2 -2
  53. package/dist/templates/filter-shared.js.map +1 -1
  54. package/dist/templates/filter-type.js +1 -1
  55. package/dist/templates/filter-type.js.map +1 -1
  56. package/dist/templates/fr010-field-mapping.js +6 -6
  57. package/dist/templates/fr010-field-mapping.js.map +1 -1
  58. package/dist/templates/inferred-types.d.ts +1 -1
  59. package/dist/templates/inferred-types.d.ts.map +1 -1
  60. package/dist/templates/inferred-types.js +18 -7
  61. package/dist/templates/inferred-types.js.map +1 -1
  62. package/dist/templates/mermaid-er.js +1 -1
  63. package/dist/templates/mermaid-er.js.map +1 -1
  64. package/dist/templates/output-format-spec-emitter.js +1 -1
  65. package/dist/templates/output-format-spec-emitter.js.map +1 -1
  66. package/dist/templates/output-parser.js +1 -1
  67. package/dist/templates/output-parser.js.map +1 -1
  68. package/dist/templates/queries-file.js +3 -3
  69. package/dist/templates/queries-file.js.map +1 -1
  70. package/dist/templates/queries.d.ts +2 -2
  71. package/dist/templates/queries.d.ts.map +1 -1
  72. package/dist/templates/queries.js +9 -9
  73. package/dist/templates/queries.js.map +1 -1
  74. package/dist/templates/relations-block.d.ts.map +1 -1
  75. package/dist/templates/relations-block.js +3 -4
  76. package/dist/templates/relations-block.js.map +1 -1
  77. package/dist/templates/render-helper.js +1 -1
  78. package/dist/templates/render-helper.js.map +1 -1
  79. package/dist/templates/routes-file-hono.d.ts.map +1 -1
  80. package/dist/templates/routes-file-hono.js +1 -2
  81. package/dist/templates/routes-file-hono.js.map +1 -1
  82. package/dist/templates/routes-file.js +5 -5
  83. package/dist/templates/routes-file.js.map +1 -1
  84. package/dist/templates/value-object-file.js +2 -2
  85. package/dist/templates/value-object-file.js.map +1 -1
  86. package/dist/templates/zod-validators.d.ts +2 -2
  87. package/dist/templates/zod-validators.d.ts.map +1 -1
  88. package/dist/templates/zod-validators.js +29 -22
  89. package/dist/templates/zod-validators.js.map +1 -1
  90. package/package.json +6 -6
  91. package/src/column-mapper.ts +58 -28
  92. package/src/enum-shared.ts +1 -1
  93. package/src/generators/docs-data-builder.ts +14 -14
  94. package/src/generators/template-payload-tree.ts +1 -1
  95. package/src/import-path.ts +28 -0
  96. package/src/metaobjects-config.ts +29 -0
  97. package/src/naming.ts +25 -3
  98. package/src/payload-codegen.ts +2 -2
  99. package/src/pk-resolver.ts +4 -2
  100. package/src/projection/extract-view-spec.ts +1 -1
  101. package/src/render-context.ts +28 -2
  102. package/src/runner.ts +3 -0
  103. package/src/templates/drizzle-schema.ts +51 -29
  104. package/src/templates/entity-constants.ts +2 -2
  105. package/src/templates/entity-file.ts +1 -1
  106. package/src/templates/extract-delegate-emitter.ts +1 -1
  107. package/src/templates/extractor.ts +2 -2
  108. package/src/templates/field-meta.ts +1 -1
  109. package/src/templates/filter-allowlist.ts +2 -2
  110. package/src/templates/filter-shared.ts +2 -2
  111. package/src/templates/filter-type.ts +1 -1
  112. package/src/templates/fr010-field-mapping.ts +6 -6
  113. package/src/templates/inferred-types.ts +18 -7
  114. package/src/templates/mermaid-er.ts +1 -1
  115. package/src/templates/output-format-spec-emitter.ts +1 -1
  116. package/src/templates/output-parser.ts +1 -1
  117. package/src/templates/queries-file.ts +3 -3
  118. package/src/templates/queries.ts +8 -10
  119. package/src/templates/relations-block.ts +3 -4
  120. package/src/templates/render-helper.ts +1 -1
  121. package/src/templates/routes-file-hono.ts +1 -2
  122. package/src/templates/routes-file.ts +5 -5
  123. package/src/templates/value-object-file.ts +2 -2
  124. package/src/templates/zod-validators.ts +29 -22
  125. package/dist/templates/extract-schema-emitter.d.ts +0 -8
  126. package/dist/templates/extract-schema-emitter.d.ts.map +0 -1
  127. package/dist/templates/extract-schema-emitter.js +0 -85
  128. package/dist/templates/extract-schema-emitter.js.map +0 -1
  129. package/dist/templates/recover-schema-emitter.d.ts +0 -8
  130. package/dist/templates/recover-schema-emitter.d.ts.map +0 -1
  131. package/dist/templates/recover-schema-emitter.js +0 -64
  132. package/dist/templates/recover-schema-emitter.js.map +0 -1
@@ -45,7 +45,7 @@ function filterableFields(entity: MetaObject, exclude?: string): MetaField[] {
45
45
  // fields() returns effective fields, so inherited fields (from extends:/super:) are included in allowlists.
46
46
  return entity
47
47
  .fields()
48
- .filter((c) => c.ownAttr(FIELD_ATTR_FILTERABLE) === true && c.name !== exclude);
48
+ .filter((c) => c.attr(FIELD_ATTR_FILTERABLE) === true && c.name !== exclude);
49
49
  }
50
50
 
51
51
  /**
@@ -93,7 +93,7 @@ export const ${entity.name}SortAllowlist = {} as const satisfies SortAllowlist;
93
93
  }
94
94
  const rows = sortable
95
95
  .map((f) => {
96
- const defaultOrder = f.ownAttr(FIELD_ATTR_SORTABLE_DEFAULT_ORDER) as string | undefined;
96
+ const defaultOrder = f.attr(FIELD_ATTR_SORTABLE_DEFAULT_ORDER) as string | undefined;
97
97
  const rule =
98
98
  defaultOrder === "asc" || defaultOrder === "desc"
99
99
  ? `{ defaultOrder: ${JSON.stringify(defaultOrder)} as const }`
@@ -15,10 +15,10 @@ import { FIELD_ATTR_FILTERABLE, FIELD_ATTR_SORTABLE } from "@metaobjectsdev/meta
15
15
  * 3. no @sortable → sortable iff @filterable === true
16
16
  */
17
17
  export function isSortableField(field: MetaField): boolean {
18
- const sortableAttr = field.ownAttr(FIELD_ATTR_SORTABLE);
18
+ const sortableAttr = field.attr(FIELD_ATTR_SORTABLE);
19
19
  if (sortableAttr === true) return true;
20
20
  if (sortableAttr === false) return false;
21
- return field.ownAttr(FIELD_ATTR_FILTERABLE) === true;
21
+ return field.attr(FIELD_ATTR_FILTERABLE) === true;
22
22
  }
23
23
 
24
24
  /**
@@ -54,7 +54,7 @@ function renderFieldUnion(field: MetaField): string {
54
54
  export function renderFilterType(entity: MetaObject, exclude?: string): Code {
55
55
  // fields() returns effective fields, so inherited fields (from extends:/super:) are included in filter types.
56
56
  const allFields = entity.fields().filter((c) => c.name !== exclude);
57
- const filterableFieldsList = allFields.filter((c) => c.ownAttr(FIELD_ATTR_FILTERABLE) === true);
57
+ const filterableFieldsList = allFields.filter((c) => c.attr(FIELD_ATTR_FILTERABLE) === true);
58
58
  // Sort union uses isSortableField — same predicate as renderSortAllowlist to prevent
59
59
  // client/server mismatches (@filterable: true + @sortable: false must be excluded from both).
60
60
  const sortFieldNames = allFields.filter(isSortableField).map((f) => `"${f.name}"`);
@@ -76,21 +76,21 @@ export function isArray(field: MetaData): boolean {
76
76
 
77
77
  /** True iff the field's @required is explicitly true (or the string "true"). */
78
78
  export function isRequired(field: MetaData): boolean {
79
- const v = field.ownAttr(FIELD_ATTR_REQUIRED);
79
+ const v = field.attr(FIELD_ATTR_REQUIRED);
80
80
  if (v === true) return true;
81
81
  return typeof v === "string" && v.toLowerCase() === "true";
82
82
  }
83
83
 
84
84
  /** True iff the field's @xmlText is explicitly true (the XML text-content extract marker). */
85
85
  export function xmlText(field: MetaData): boolean {
86
- const v = field.ownAttr(FIELD_ATTR_XML_TEXT);
86
+ const v = field.attr(FIELD_ATTR_XML_TEXT);
87
87
  if (v === true) return true;
88
88
  return typeof v === "string" && v.toLowerCase() === "true";
89
89
  }
90
90
 
91
91
  /** The string members of an enum field's @values attr (empty when absent). */
92
92
  export function enumValues(field: MetaData): string[] {
93
- const v = field.ownAttr(FIELD_ATTR_VALUES);
93
+ const v = field.attr(FIELD_ATTR_VALUES);
94
94
  if (Array.isArray(v)) return v.map((e) => String(e));
95
95
  return [];
96
96
  }
@@ -100,7 +100,7 @@ export function enumValues(field: MetaData): string[] {
100
100
  * or null when absent. Read own-attr only — `@coerceDefault` is concrete, never inherited.
101
101
  */
102
102
  export function coerceDefault(field: MetaData): string | null {
103
- const v = field.ownAttr(FIELD_ATTR_COERCE_DEFAULT);
103
+ const v = field.attr(FIELD_ATTR_COERCE_DEFAULT);
104
104
  return typeof v === "string" && v.length > 0 ? v : null;
105
105
  }
106
106
 
@@ -108,7 +108,7 @@ export function coerceDefault(field: MetaData): string | null {
108
108
  * FR-011: the field's `@default` member symbol (absent-fill enum value), or null when absent.
109
109
  */
110
110
  export function defaultValue(field: MetaData): string | null {
111
- const v = field.ownAttr(FIELD_ATTR_DEFAULT);
111
+ const v = field.attr(FIELD_ATTR_DEFAULT);
112
112
  return typeof v === "string" && v.length > 0 ? v : null;
113
113
  }
114
114
 
@@ -128,7 +128,7 @@ export function resolveNormalize(field: MetaData, ownerObject: MetaData | null):
128
128
 
129
129
  /** The `@normalize` attr of a node as a NormalizeMode, or null when absent. */
130
130
  function normalizeAttrOf(node: MetaData): NormalizeMode | null {
131
- const v = node.ownAttr(FIELD_ATTR_NORMALIZE);
131
+ const v = node.attr(FIELD_ATTR_NORMALIZE);
132
132
  return typeof v === "string" && v.length > 0 ? (v as NormalizeMode) : null;
133
133
  }
134
134
 
@@ -28,6 +28,7 @@ import {
28
28
  FIELD_ATTR_OBJECT_REF,
29
29
  } from "@metaobjectsdev/metadata";
30
30
  import { variableNameFromEntity, toPascalCase } from "../naming.js";
31
+ import { valueObjectModuleSpecifier } from "../import-path.js";
31
32
  import { stripPackage } from "@metaobjectsdev/metadata";
32
33
  import { enumValues } from "../enum-meta.js";
33
34
  import { renderDocsFor } from "./jsdoc.js";
@@ -44,8 +45,12 @@ import type { RenderContext } from "../render-context.js";
44
45
  * to avoid a duplicate `export type <Base>`. Insert/Update keep their names
45
46
  * (no collision); they describe the physical TPH table row shape.
46
47
  */
47
- export function renderInferredTypes(entity: MetaObject, tphBase = false): Code {
48
- const varName = variableNameFromEntity(entity.name);
48
+ export function renderInferredTypes(entity: MetaObject, tphBase = false, ctx?: RenderContext): Code {
49
+ // The inferred Row/Insert types reference the Drizzle table var, so they must
50
+ // resolve to the SAME (possibly overridden) collection name the schema emits.
51
+ // ctx is optional for bare unit-test calls — those fall back to the default
52
+ // always-pluralize spelling.
53
+ const varName = ctx ? ctx.collectionName(entity.name) : variableNameFromEntity(entity.name);
49
54
  const selectSym = imp("InferSelectModel@drizzle-orm");
50
55
  const insertSym = imp("InferInsertModel@drizzle-orm");
51
56
  const docs = renderDocsFor(entity);
@@ -172,7 +177,7 @@ const SCALAR_TS_BY_SUBTYPE: Record<string, string> = {
172
177
  */
173
178
  export function fieldTsTypeString(ownerName: string, field: MetaField): string {
174
179
  if (field.subType === FIELD_SUBTYPE_OBJECT) {
175
- const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
180
+ const ref = field.attr(FIELD_ATTR_OBJECT_REF);
176
181
  if (typeof ref === "string" && ref.length > 0) {
177
182
  const base = stripPackage(ref);
178
183
  return field.isArray ? `${base}[]` : base;
@@ -205,12 +210,18 @@ function valueObjectFieldType(entity: MetaObject, field: MetaField, ctx?: Render
205
210
  // so ts-poet hoists the import. Mirrors zod-validators.ts's `<Ref>InsertSchema`
206
211
  // import strategy, just for the type alias instead of the schema constant.
207
212
  if (field.subType === FIELD_SUBTYPE_OBJECT) {
208
- const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
213
+ const ref = field.attr(FIELD_ATTR_OBJECT_REF);
209
214
  if (typeof ref === "string" && ref.length > 0) {
210
215
  // @objectRef may be authored fully-qualified (acme::sales::Brief) or bare; the
211
- // referenced interface + its sibling module are named by the BARE short name.
216
+ // referenced interface is named by the BARE short name. The import MODULE is
217
+ // resolved through the shared layout/package/extStyle-aware helper (the SAME
218
+ // one the Zod schema + Drizzle .$type<> use) so all three agree. Without a
219
+ // ctx (bare unit-test calls) fall back to the flat same-dir specifier.
212
220
  const base = stripPackage(ref);
213
- const refImp = imp(`${base}@./${base}.js`);
221
+ const moduleSpec = ctx
222
+ ? valueObjectModuleSpecifier(base, ctx.packageOf, entity.package, ctx.outputLayout, ctx.extStyle)
223
+ : `./${base}.js`;
224
+ const refImp = imp(`${base}@${moduleSpec}`);
214
225
  return field.isArray ? code`${refImp}[]` : code`${refImp}`;
215
226
  }
216
227
  return field.isArray ? code`unknown[]` : code`unknown`;
@@ -258,7 +269,7 @@ export function renderValueObjectInterface(entity: MetaObject, ctx?: RenderConte
258
269
 
259
270
  const lines: Code[] = [];
260
271
  for (const field of entity.fields()) {
261
- const required = field.ownAttr(FIELD_ATTR_REQUIRED) === true;
272
+ const required = field.attr(FIELD_ATTR_REQUIRED) === true;
262
273
  const optional = required ? "" : "?";
263
274
  const tsType = valueObjectFieldType(entity, field, ctx);
264
275
  lines.push(code` ${field.name}${optional}: ${tsType};`);
@@ -157,7 +157,7 @@ export function renderEntityNeighborhoodErBlock(
157
157
  // column?" Click-through to the VO docs answers that.
158
158
  for (const field of focal.fields()) {
159
159
  if (field.subType !== FIELD_SUBTYPE_OBJECT) continue;
160
- const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
160
+ const ref = field.attr(FIELD_ATTR_OBJECT_REF);
161
161
  if (typeof ref !== "string" || ref.length === 0) continue;
162
162
  const targetName = ref.split("::").pop()!;
163
163
  if (classify(targetName) === undefined) continue;
@@ -76,7 +76,7 @@ function promptFieldLiteral(field: MetaData): string {
76
76
 
77
77
  if (field.subType === FIELD_SUBTYPE_ENUM) {
78
78
  const valuesLit = stringArrayLiteral(enumValues(field));
79
- const enumDocLit = propertiesMapLiteral(field.ownAttr(FIELD_ATTR_ENUM_DOC));
79
+ const enumDocLit = propertiesMapLiteral(field.attr(FIELD_ATTR_ENUM_DOC));
80
80
  return (
81
81
  `{ name: ${name}, kind: FieldKind.ENUM, required: ${required}, array: ${array}, ` +
82
82
  `enumValues: ${valuesLit}, enumDoc: ${enumDocLit}, example: ${example}, ` +
@@ -58,7 +58,7 @@ function fieldZod(field: MetaData, root: MetaData, seen: ReadonlySet<string>, de
58
58
  const isArray = field.isArray === true;
59
59
  let base: string;
60
60
  if (field.subType === FIELD_SUBTYPE_OBJECT) {
61
- const refName = field.ownAttr(FIELD_ATTR_OBJECT_REF);
61
+ const refName = field.attr(FIELD_ATTR_OBJECT_REF);
62
62
  if (typeof refName !== "string") {
63
63
  base = "z.unknown()";
64
64
  } else if (seen.has(refName)) {
@@ -17,7 +17,7 @@ import {
17
17
  renderDeleteByIdFn,
18
18
  getPkInfo,
19
19
  } from "./queries.js";
20
- import { variableNameFromEntity, pluralize } from "../naming.js";
20
+ import { pluralize } from "../naming.js";
21
21
  import { GENERATED_HEADER } from "../constants.js";
22
22
  import { isTphDiscriminatorBase, tphConcreteSubtypes } from "./tph-discriminator.js";
23
23
 
@@ -40,7 +40,7 @@ export function renderQueriesFile(obj: MetaObject, ctx: RenderContext): string {
40
40
  entityName,
41
41
  ctx.extStyle,
42
42
  );
43
- const varName = variableNameFromEntity(entityName);
43
+ const varName = ctx.collectionName(entityName);
44
44
 
45
45
  // The persistence-context `db` is parameter-passed into every generated CRUD
46
46
  // helper (ADR-0008). Emit the dialect-correct Drizzle type alias so the
@@ -97,7 +97,7 @@ import { ${varName}, type ${entityName}, ${entityName}InsertSchema } from ${JSON
97
97
  */
98
98
  function renderTphQueriesFile(base: MetaObject, ctx: RenderContext): string {
99
99
  const baseName = base.name;
100
- const tableVar = variableNameFromEntity(baseName);
100
+ const tableVar = ctx.collectionName(baseName);
101
101
  const discField = base.ownAttr(OBJECT_ATTR_DISCRIMINATOR) as string;
102
102
  const { fieldName: pkField, tsType: pkType } = getPkInfo(base, ctx);
103
103
 
@@ -6,8 +6,6 @@ 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
8
  import {
9
- variableNameFromEntity,
10
- pluralize,
11
9
  findByIdFnName,
12
10
  listFnName,
13
11
  createFnName,
@@ -19,7 +17,7 @@ import {
19
17
  export function getPkInfo(entity: MetaObject, ctx: RenderContext): { fieldName: string; tsType: string } {
20
18
  // Use primaryIdentity() to find the primary identity (may be inherited from extends:/super:).
21
19
  const primary = entity.primaryIdentity();
22
- const rawFields = primary?.ownAttr(IDENTITY_ATTR_FIELDS);
20
+ const rawFields = primary?.attr(IDENTITY_ATTR_FIELDS);
23
21
  const fields = Array.isArray(rawFields) ? rawFields : (typeof rawFields === "string" ? [rawFields] : undefined);
24
22
  const pkFieldName = fields?.[0] ?? "id";
25
23
  const pkInfo = ctx.pkMap.get(entity.name);
@@ -34,7 +32,7 @@ export function getPkInfo(entity: MetaObject, ctx: RenderContext): { fieldName:
34
32
  }
35
33
 
36
34
  export function renderFindByIdFn(entity: MetaObject, ctx: RenderContext): Code {
37
- const varName = variableNameFromEntity(entity.name);
35
+ const varName = ctx.collectionName(entity.name);
38
36
  const entityName = entity.name;
39
37
  const singularVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
40
38
  const { fieldName: pkField, tsType: pkType } = getPkInfo(entity, ctx);
@@ -49,8 +47,8 @@ export async function ${fnName}(db: Db, ${pkField}: ${pkType}): Promise<${entity
49
47
  `;
50
48
  }
51
49
 
52
- export function renderListFn(entity: MetaObject, _ctx: RenderContext): Code {
53
- const varName = variableNameFromEntity(entity.name);
50
+ export function renderListFn(entity: MetaObject, ctx: RenderContext): Code {
51
+ const varName = ctx.collectionName(entity.name);
54
52
  const entityName = entity.name;
55
53
  // Pluralize the PascalCase entity name, preserving capitalization
56
54
  // (e.g., "Category" -> "Categories", not "Categorys").
@@ -66,8 +64,8 @@ export async function ${fnName}(db: Db, opts?: { limit?: number; offset?: number
66
64
  `;
67
65
  }
68
66
 
69
- export function renderCreateFn(entity: MetaObject, _ctx: RenderContext): Code {
70
- const varName = variableNameFromEntity(entity.name);
67
+ export function renderCreateFn(entity: MetaObject, ctx: RenderContext): Code {
68
+ const varName = ctx.collectionName(entity.name);
71
69
  const entityName = entity.name;
72
70
  const singularVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
73
71
  const fnName = createFnName(entityName);
@@ -83,7 +81,7 @@ export async function ${fnName}(db: Db, data: unknown): Promise<${entityName}> {
83
81
  }
84
82
 
85
83
  export function renderUpdateFn(entity: MetaObject, ctx: RenderContext): Code {
86
- const varName = variableNameFromEntity(entity.name);
84
+ const varName = ctx.collectionName(entity.name);
87
85
  const entityName = entity.name;
88
86
  const singularVar = entityName.charAt(0).toLowerCase() + entityName.slice(1);
89
87
  const { fieldName: pkField, tsType: pkType } = getPkInfo(entity, ctx);
@@ -101,7 +99,7 @@ export async function ${fnName}(db: Db, ${pkField}: ${pkType}, data: unknown): P
101
99
  }
102
100
 
103
101
  export function renderDeleteByIdFn(entity: MetaObject, ctx: RenderContext): Code {
104
- const varName = variableNameFromEntity(entity.name);
102
+ const varName = ctx.collectionName(entity.name);
105
103
  const entityName = entity.name;
106
104
  const { fieldName: pkField, tsType: pkType } = getPkInfo(entity, ctx);
107
105
  const fnName = deleteByIdFnName(entityName);
@@ -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
 
@@ -59,7 +58,7 @@ function renderRelationEntry(
59
58
  entry.junctionEntity,
60
59
  ctx.extStyle,
61
60
  );
62
- const junctionVarSym = imp(`${variableNameFromEntity(entry.junctionEntity)}@${junctionSpec}`);
61
+ const junctionVarSym = imp(`${ctx.collectionName(entry.junctionEntity)}@${junctionSpec}`);
63
62
  return code` ${entry.name}: many(${junctionVarSym})`;
64
63
  }
65
64
 
@@ -71,7 +70,7 @@ function renderRelationEntry(
71
70
  entry.targetEntity,
72
71
  ctx.extStyle,
73
72
  );
74
- const targetVarSym = imp(`${variableNameFromEntity(entry.targetEntity)}@${targetSpec}`);
73
+ const targetVarSym = imp(`${ctx.collectionName(entry.targetEntity)}@${targetSpec}`);
75
74
 
76
75
  if (entry.cardinality === CARDINALITY_ONE) {
77
76
  const pkInfo = ctx.pkMap.get(entry.targetEntity);
@@ -81,7 +81,7 @@ function derivePayloadFieldTree(
81
81
  const fields: PayloadField[] = [];
82
82
  for (const f of vo.children().filter((c) => c.type === TYPE_FIELD)) {
83
83
  if (f.subType === FIELD_SUBTYPE_OBJECT) {
84
- const ref = f.ownAttr(FIELD_ATTR_OBJECT_REF);
84
+ const ref = f.attr(FIELD_ATTR_OBJECT_REF);
85
85
  if (typeof ref === "string") {
86
86
  fields.push({ name: f.name, fields: derivePayloadFieldTree(root, ref, nextSeen) });
87
87
  continue;
@@ -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");
@@ -24,7 +24,7 @@ import {
24
24
  import { type RenderContext } from "../render-context.js";
25
25
  import { crossEntitySpecifier, entityModuleSpecifier, relativeModuleSpecifier } from "../import-path.js";
26
26
  import { GENERATED_HEADER } from "../constants.js";
27
- import { variableNameFromEntity, routesHandlerName } from "../naming.js";
27
+ import { routesHandlerName } from "../naming.js";
28
28
  import { isProjection } from "../projection/projection-detector.js";
29
29
  import type { RelationEntry } from "../relation-resolver.js";
30
30
  import { isTphDiscriminatorBase, tphPlan } from "./tph-discriminator.js";
@@ -122,7 +122,7 @@ export async function ${handlerName}(fastify: ${FastifyInstanceSym}) {
122
122
  }
123
123
 
124
124
  // --- Vanilla / write-through entity path: full CRUD routes ---
125
- const tableVar = variableNameFromEntity(entityName);
125
+ const tableVar = ctx.collectionName(entityName);
126
126
 
127
127
  const FastifyInstanceSym = imp("t:FastifyInstance@fastify");
128
128
  const mountCrudRoutesSym = imp("mountCrudRoutes@@metaobjectsdev/runtime-ts/drizzle-fastify");
@@ -239,7 +239,7 @@ function renderM2mMount(
239
239
  fastifyVar: string,
240
240
  ): Code {
241
241
  const junctionVarSym = imp(
242
- `${variableNameFromEntity(entry.junctionEntity)}@${crossEntitySpecifier(
242
+ `${ctx.collectionName(entry.junctionEntity)}@${crossEntitySpecifier(
243
243
  ctx.outputLayout,
244
244
  source.package,
245
245
  ctx.packageOf.get(entry.junctionEntity),
@@ -248,7 +248,7 @@ function renderM2mMount(
248
248
  )}`,
249
249
  );
250
250
  const targetVarSym = imp(
251
- `${variableNameFromEntity(entry.targetEntity)}@${crossEntitySpecifier(
251
+ `${ctx.collectionName(entry.targetEntity)}@${crossEntitySpecifier(
252
252
  ctx.outputLayout,
253
253
  source.package,
254
254
  ctx.packageOf.get(entry.targetEntity),
@@ -312,7 +312,7 @@ function renderTphRoutesFile(base: MetaObject, ctx: RenderContext): string {
312
312
  // Single source of truth for the discriminator field + subtypes + route segments.
313
313
  const plan = tphPlan(base, ctx.loadedRoot)!;
314
314
  const discField = plan.discriminatorField;
315
- const tableVar = variableNameFromEntity(baseName);
315
+ const tableVar = ctx.collectionName(baseName);
316
316
 
317
317
  const baseFileSpec = entityModuleSpecifier(
318
318
  ctx.selfTarget, ctx.entityModuleTarget, base.package, baseName, ctx.extStyle,
@@ -29,7 +29,7 @@ export function renderValueObjectFile(obj: MetaObject, apiPrefix = "", ctx?: Ren
29
29
  // but declares none of its own). In addition to the insert schema it emits a
30
30
  // full read schema `<Sub>Schema` so parse<Base>(row) can dispatch to it.
31
31
  const tphSubtype = isTphSubtype(obj);
32
- const tphReadSchema = tphSubtype ? renderTphSubtypeReadSchema(obj) : null;
32
+ const tphReadSchema = tphSubtype ? renderTphSubtypeReadSchema(obj, ctx) : null;
33
33
  // FR-017 Tier 3: a TPH subtype also emits its field-metadata constants object
34
34
  // (the `<Sub>` const), so the React form generator can render per-field
35
35
  // labels / rules / inputs the same way it does for ordinary entities.
@@ -49,7 +49,7 @@ export function renderValueObjectFile(obj: MetaObject, apiPrefix = "", ctx?: Ren
49
49
  renderValueObjectInterface(obj, ctx),
50
50
  ...(enumAliases !== null ? [enumAliases] : []),
51
51
  ...(tphReadSchema !== null ? [tphReadSchema] : []),
52
- renderInsertSchemaOnly(obj),
52
+ renderInsertSchemaOnly(obj, ctx),
53
53
  ...(tphConstants !== null ? [tphConstants] : []),
54
54
  ...(tphFilterAllowlist !== null ? [tphFilterAllowlist] : []),
55
55
  ...(tphSortAllowlist !== null ? [tphSortAllowlist] : []),
@@ -32,6 +32,7 @@ import { sharedEnumForField } from "../enum-shared.js";
32
32
  import { sharedEnumImportSpecifier } from "../enum-import.js";
33
33
  import { sharedEnumZodConstName } from "./enums-file.js";
34
34
  import type { RenderContext } from "../render-context.js";
35
+ import { valueObjectModuleSpecifier } from "../import-path.js";
35
36
 
36
37
  /**
37
38
  * FR-017 Tier 1 — when this object is a TPH subtype (@discriminatorValue set
@@ -73,7 +74,7 @@ export function isTphSubtype(obj: MetaObject): boolean {
73
74
  * back from the DB arrives as `null`, not `undefined`, so the read schema must
74
75
  * accept null (the insert schema, by contrast, makes them `.optional()`).
75
76
  */
76
- export function renderTphSubtypeReadSchema(obj: MetaObject): Code {
77
+ export function renderTphSubtypeReadSchema(obj: MetaObject, ctx?: RenderContext): Code {
77
78
  const z = imp("z@zod");
78
79
  const tphPin = tphDiscriminatorPin(obj);
79
80
 
@@ -83,7 +84,7 @@ export function renderTphSubtypeReadSchema(obj: MetaObject): Code {
83
84
  fieldLines.push(code` ${child.name}: z.literal(${JSON.stringify(tphPin.value)})`);
84
85
  continue;
85
86
  }
86
- const expr = zodFieldExpr(child);
87
+ const expr = zodFieldExpr(child, obj, ctx);
87
88
  // zodFieldExpr already appends `.optional()` for non-required fields; add
88
89
  // `.nullable()` on top so a NULL column value (the TPH default for any
89
90
  // subtype-only column) parses cleanly.
@@ -106,9 +107,9 @@ function autoGenPkFieldNames(obj: MetaObject): Set<string> {
106
107
  const out = new Set<string>();
107
108
  const primary = obj.primaryIdentity();
108
109
  if (primary) {
109
- const generation = primary.ownAttr(IDENTITY_ATTR_GENERATION);
110
+ const generation = primary.attr(IDENTITY_ATTR_GENERATION);
110
111
  if (generation === GENERATION_INCREMENT || generation === GENERATION_UUID) {
111
- const fields = primary.ownAttr(IDENTITY_ATTR_FIELDS);
112
+ const fields = primary.attr(IDENTITY_ATTR_FIELDS);
112
113
  const fieldsList = Array.isArray(fields) ? fields : (typeof fields === "string" ? [fields] : []);
113
114
  for (const f of fieldsList) out.add(String(f));
114
115
  }
@@ -125,7 +126,7 @@ function autoGenPkFieldNames(obj: MetaObject): Set<string> {
125
126
  * so consumer imports don't churn. A future polish PR could add a `<Name>Schema`
126
127
  * alias for clarity.
127
128
  */
128
- export function renderInsertSchemaOnly(obj: MetaObject): Code {
129
+ export function renderInsertSchemaOnly(obj: MetaObject, ctx?: RenderContext): Code {
129
130
  const z = imp("z@zod");
130
131
  const autoGenPkFields = autoGenPkFieldNames(obj);
131
132
  const tphPin = tphDiscriminatorPin(obj);
@@ -136,7 +137,7 @@ export function renderInsertSchemaOnly(obj: MetaObject): Code {
136
137
  // FR-013: @readOnly fields are populated by DB / replication / external
137
138
  // owner; the application has no path to write them. Exclude from the
138
139
  // create-shape schema entirely.
139
- if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
140
+ if (child.attr(FIELD_ATTR_READ_ONLY) === true) continue;
140
141
 
141
142
  // FR-017 Tier 1: TPH subtype pins its discriminator field to z.literal(...).
142
143
  if (tphPin !== undefined && child.name === tphPin.fieldName) {
@@ -146,14 +147,14 @@ export function renderInsertSchemaOnly(obj: MetaObject): Code {
146
147
  continue;
147
148
  }
148
149
 
149
- const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
150
+ const autoSet = child.attr(FIELD_ATTR_AUTO_SET);
150
151
 
151
152
  if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
152
153
  insertFieldLines.push(
153
154
  code` ${child.name}: z.string().optional().transform(() => new Date().toISOString())`,
154
155
  );
155
156
  } else {
156
- insertFieldLines.push(code` ${child.name}: ${zodFieldExpr(child)}`);
157
+ insertFieldLines.push(code` ${child.name}: ${zodFieldExpr(child, obj, ctx)}`);
157
158
  }
158
159
  }
159
160
 
@@ -200,12 +201,12 @@ export function insertSchemaFields(obj: MetaObject): SchemaFieldShape[] {
200
201
  const out: SchemaFieldShape[] = [];
201
202
  for (const child of obj.fields()) {
202
203
  if (autoGenPkFields.has(child.name)) continue;
203
- if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
204
+ if (child.attr(FIELD_ATTR_READ_ONLY) === true) continue;
204
205
  if (tphPin !== undefined && child.name === tphPin.fieldName) {
205
206
  out.push({ name: child.name, optional: false, pinnedLiteral: tphPin.value });
206
207
  continue;
207
208
  }
208
- const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
209
+ const autoSet = child.attr(FIELD_ATTR_AUTO_SET);
209
210
  if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
210
211
  out.push({ name: child.name, optional: true, autoSet: true });
211
212
  } else {
@@ -230,10 +231,10 @@ export function updateSchemaFields(obj: MetaObject): SchemaFieldShape[] {
230
231
  const out: SchemaFieldShape[] = [];
231
232
  for (const child of obj.fields()) {
232
233
  if (autoGenPkFields.has(child.name)) continue;
233
- if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
234
+ if (child.attr(FIELD_ATTR_READ_ONLY) === true) continue;
234
235
  // TPH subtype discriminator: omitted from the update schema entirely.
235
236
  if (tphPin !== undefined && child.name === tphPin.fieldName) continue;
236
- const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
237
+ const autoSet = child.attr(FIELD_ATTR_AUTO_SET);
237
238
  if (autoSet === AUTO_SET_ON_CREATE) {
238
239
  // Omitted: creation timestamps cannot change after creation.
239
240
  continue;
@@ -261,7 +262,7 @@ export function renderZodValidators(obj: MetaObject, ctx?: RenderContext): Code
261
262
  // The DB / trigger / replication owns the write path; the app must not
262
263
  // pass these values in POST/PATCH bodies (routesFile enforces the same
263
264
  // contract at the boundary with a 400 response).
264
- if (child.ownAttr(FIELD_ATTR_READ_ONLY) === true) continue;
265
+ if (child.attr(FIELD_ATTR_READ_ONLY) === true) continue;
265
266
 
266
267
  // FR-017 Tier 1: TPH subtype pins its discriminator field to z.literal(...).
267
268
  // The discriminator is implicit on subtype rows (controlled by URL / insert
@@ -274,7 +275,7 @@ export function renderZodValidators(obj: MetaObject, ctx?: RenderContext): Code
274
275
  continue;
275
276
  }
276
277
 
277
- const autoSet = child.ownAttr(FIELD_ATTR_AUTO_SET);
278
+ const autoSet = child.attr(FIELD_ATTR_AUTO_SET);
278
279
 
279
280
  // Insert schema: @autoSet fields use transform (always override client input).
280
281
  if (autoSet === AUTO_SET_ON_CREATE || autoSet === AUTO_SET_ON_UPDATE) {
@@ -326,12 +327,18 @@ function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext)
326
327
  // field used to collapse to z.string() / z.array(z.string()) and downstream
327
328
  // JSON Schema (e.g. LLM tool_use input_schema) lost the nested object shape.
328
329
  if (field.subType === FIELD_SUBTYPE_OBJECT) {
329
- const ref = field.ownAttr(FIELD_ATTR_OBJECT_REF);
330
+ const ref = field.attr(FIELD_ATTR_OBJECT_REF);
330
331
  if (typeof ref === "string" && ref.length > 0) {
331
332
  // @objectRef may be authored fully-qualified or bare — the referenced
332
- // <Ref>InsertSchema + its sibling module use the BARE short name.
333
+ // <Ref>InsertSchema is named by the BARE short name. The import MODULE is
334
+ // resolved via the shared layout/package/extStyle-aware helper (the SAME
335
+ // one the field's TS type + Drizzle .$type<> use) so all three agree.
336
+ // Without owner/ctx (bare unit-test calls) fall back to the flat same-dir.
333
337
  const refBase = stripPackage(ref);
334
- const refImp = imp(`${refBase}InsertSchema@./${refBase}.js`);
338
+ const moduleSpec = (ctx && owner)
339
+ ? valueObjectModuleSpecifier(refBase, ctx.packageOf, owner.package, ctx.outputLayout, ctx.extStyle)
340
+ : `./${refBase}.js`;
341
+ const refImp = imp(`${refBase}InsertSchema@${moduleSpec}`);
335
342
  let base: Code = code`${refImp}`;
336
343
  if (field.isArray) base = code`z.array(${base})`;
337
344
  return appendValidatorChain(base, field);
@@ -401,11 +408,11 @@ function zodFieldExpr(field: MetaField, owner?: MetaObject, ctx?: RenderContext)
401
408
  /** Mirrors the optional-or-not decision inside appendValidatorChain so the update-schema
402
409
  * caller can avoid stacking a second `.optional()` onto an already-optional expression. */
403
410
  function fieldWillBeOptional(field: MetaField): boolean {
404
- let isRequired = field.ownAttr(FIELD_ATTR_REQUIRED) === true;
411
+ let isRequired = field.attr(FIELD_ATTR_REQUIRED) === true;
405
412
  for (const child of field.validators()) {
406
413
  if (child.subType === VALIDATOR_SUBTYPE_REQUIRED) isRequired = true;
407
414
  }
408
- const hasDefault = field.ownAttr(FIELD_ATTR_DEFAULT) !== undefined;
415
+ const hasDefault = field.attr(FIELD_ATTR_DEFAULT) !== undefined;
409
416
  return !isRequired || hasDefault;
410
417
  }
411
418
 
@@ -423,8 +430,8 @@ const NUMERIC_FIELD_SUBTYPES = new Set<string>([
423
430
  * - array (any element) → .min/.max = element count (validator.array)
424
431
  */
425
432
  function appendValidatorChain(base: Code, field: MetaField): Code {
426
- let isRequired = field.ownAttr(FIELD_ATTR_REQUIRED) === true;
427
- let maxLen: number | undefined = field.ownAttr(FIELD_ATTR_MAX_LENGTH) as number | undefined;
433
+ let isRequired = field.attr(FIELD_ATTR_REQUIRED) === true;
434
+ let maxLen: number | undefined = field.attr(FIELD_ATTR_MAX_LENGTH) as number | undefined;
428
435
  let minLen: number | undefined;
429
436
  let pattern: string | undefined;
430
437
  let numMin: number | undefined;
@@ -475,7 +482,7 @@ function appendValidatorChain(base: Code, field: MetaField): Code {
475
482
  // Fields with DB-level defaults are optional in the InsertSchema: the caller
476
483
  // can omit them and the DB will fill in. Otherwise required-with-default
477
484
  // would force callers to repeat the default at every call site.
478
- const hasDefault = field.ownAttr(FIELD_ATTR_DEFAULT) !== undefined;
485
+ const hasDefault = field.attr(FIELD_ATTR_DEFAULT) !== undefined;
479
486
  if (!isRequired || hasDefault) chain = code`${chain}.optional()`;
480
487
  return chain;
481
488
  }
@@ -1,8 +0,0 @@
1
- import { type MetaData } from "@metaobjectsdev/metadata";
2
- /** Emit `extractSchema(Format.X, "rootName", [ … ])`. */
3
- export declare function schemaLiteral(vo: MetaData, format: string, rootName: string): string;
4
- /** Emit the all-nullable mirror interface declaration. */
5
- export declare function mirrorInterface(vo: MetaData, interfaceName: string): string;
6
- /** Emit `{ prop: asString(d, "prop"), … }` building the mirror from the forgiving map `d`. */
7
- export declare function mirrorInitializer(vo: MetaData): string;
8
- //# sourceMappingURL=extract-schema-emitter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extract-schema-emitter.d.ts","sourceRoot":"","sources":["../../src/templates/extract-schema-emitter.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,KAAK,QAAQ,EAId,MAAM,0BAA0B,CAAC;AAmBlC,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIpF;AAmDD,0DAA0D;AAC1D,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAc3E;AAED,8FAA8F;AAC9F,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAGtD"}