@metaobjectsdev/codegen-ts 1.0.0-rc.4 → 1.0.0-rc.6

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 (168) hide show
  1. package/dist/column-mapper.d.ts +12 -0
  2. package/dist/column-mapper.d.ts.map +1 -1
  3. package/dist/column-mapper.js +16 -0
  4. package/dist/column-mapper.js.map +1 -1
  5. package/dist/constants.d.ts +10 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +10 -0
  8. package/dist/constants.js.map +1 -1
  9. package/dist/generator-registry.d.ts.map +1 -1
  10. package/dist/generator-registry.js +1 -0
  11. package/dist/generator-registry.js.map +1 -1
  12. package/dist/generator.d.ts +15 -0
  13. package/dist/generator.d.ts.map +1 -1
  14. package/dist/generator.js.map +1 -1
  15. package/dist/generators/agent-docs-file.d.ts.map +1 -1
  16. package/dist/generators/agent-docs-file.js +12 -3
  17. package/dist/generators/agent-docs-file.js.map +1 -1
  18. package/dist/generators/agent-schema-input.d.ts +11 -0
  19. package/dist/generators/agent-schema-input.d.ts.map +1 -1
  20. package/dist/generators/agent-schema-page.d.ts.map +1 -1
  21. package/dist/generators/agent-schema-page.js +13 -1
  22. package/dist/generators/agent-schema-page.js.map +1 -1
  23. package/dist/generators/agent-ui-page.d.ts.map +1 -1
  24. package/dist/generators/agent-ui-page.js +11 -9
  25. package/dist/generators/agent-ui-page.js.map +1 -1
  26. package/dist/generators/api-model.d.ts +2 -1
  27. package/dist/generators/api-model.d.ts.map +1 -1
  28. package/dist/generators/api-model.js +12 -6
  29. package/dist/generators/api-model.js.map +1 -1
  30. package/dist/generators/barrel.d.ts.map +1 -1
  31. package/dist/generators/barrel.js +2 -1
  32. package/dist/generators/barrel.js.map +1 -1
  33. package/dist/generators/callable-file.d.ts.map +1 -1
  34. package/dist/generators/callable-file.js +2 -1
  35. package/dist/generators/callable-file.js.map +1 -1
  36. package/dist/generators/docs-data-builder.d.ts.map +1 -1
  37. package/dist/generators/docs-data-builder.js +6 -5
  38. package/dist/generators/docs-data-builder.js.map +1 -1
  39. package/dist/generators/entity-file.d.ts.map +1 -1
  40. package/dist/generators/entity-file.js +2 -1
  41. package/dist/generators/entity-file.js.map +1 -1
  42. package/dist/generators/names-file.d.ts +22 -1
  43. package/dist/generators/names-file.d.ts.map +1 -1
  44. package/dist/generators/names-file.js +13 -18
  45. package/dist/generators/names-file.js.map +1 -1
  46. package/dist/generators/queries-file.d.ts.map +1 -1
  47. package/dist/generators/queries-file.js +2 -1
  48. package/dist/generators/queries-file.js.map +1 -1
  49. package/dist/generators/render-helper-file.js +1 -1
  50. package/dist/generators/render-helper-file.js.map +1 -1
  51. package/dist/generators/requirements-markdown.d.ts.map +1 -1
  52. package/dist/generators/requirements-markdown.js +26 -1
  53. package/dist/generators/requirements-markdown.js.map +1 -1
  54. package/dist/generators/routes-file-hono.d.ts.map +1 -1
  55. package/dist/generators/routes-file-hono.js +2 -1
  56. package/dist/generators/routes-file-hono.js.map +1 -1
  57. package/dist/generators/routes-file.d.ts.map +1 -1
  58. package/dist/generators/routes-file.js +2 -1
  59. package/dist/generators/routes-file.js.map +1 -1
  60. package/dist/index.d.ts +9 -7
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +16 -7
  63. package/dist/index.js.map +1 -1
  64. package/dist/metaobjects-config.d.ts +10 -0
  65. package/dist/metaobjects-config.d.ts.map +1 -1
  66. package/dist/metaobjects-config.js +14 -1
  67. package/dist/metaobjects-config.js.map +1 -1
  68. package/dist/names.d.ts.map +1 -1
  69. package/dist/names.js +5 -4
  70. package/dist/names.js.map +1 -1
  71. package/dist/overwrite-policy.d.ts +23 -9
  72. package/dist/overwrite-policy.d.ts.map +1 -1
  73. package/dist/overwrite-policy.js +48 -0
  74. package/dist/overwrite-policy.js.map +1 -1
  75. package/dist/runner.d.ts +24 -0
  76. package/dist/runner.d.ts.map +1 -1
  77. package/dist/runner.js +143 -13
  78. package/dist/runner.js.map +1 -1
  79. package/dist/templates/callable-file.d.ts.map +1 -1
  80. package/dist/templates/callable-file.js +2 -1
  81. package/dist/templates/callable-file.js.map +1 -1
  82. package/dist/templates/drizzle-schema.d.ts.map +1 -1
  83. package/dist/templates/drizzle-schema.js +39 -14
  84. package/dist/templates/drizzle-schema.js.map +1 -1
  85. package/dist/templates/entity-file.d.ts.map +1 -1
  86. package/dist/templates/entity-file.js +4 -2
  87. package/dist/templates/entity-file.js.map +1 -1
  88. package/dist/templates/filter-allowlist.d.ts.map +1 -1
  89. package/dist/templates/filter-allowlist.js +10 -6
  90. package/dist/templates/filter-allowlist.js.map +1 -1
  91. package/dist/templates/filter-shared.d.ts +42 -1
  92. package/dist/templates/filter-shared.d.ts.map +1 -1
  93. package/dist/templates/filter-shared.js +68 -5
  94. package/dist/templates/filter-shared.js.map +1 -1
  95. package/dist/templates/inferred-types.d.ts.map +1 -1
  96. package/dist/templates/inferred-types.js +9 -6
  97. package/dist/templates/inferred-types.js.map +1 -1
  98. package/dist/templates/projection-decl.d.ts.map +1 -1
  99. package/dist/templates/projection-decl.js +4 -2
  100. package/dist/templates/projection-decl.js.map +1 -1
  101. package/dist/templates/queries-file.d.ts.map +1 -1
  102. package/dist/templates/queries-file.js +6 -5
  103. package/dist/templates/queries-file.js.map +1 -1
  104. package/dist/templates/relations-block.d.ts.map +1 -1
  105. package/dist/templates/relations-block.js +2 -1
  106. package/dist/templates/relations-block.js.map +1 -1
  107. package/dist/templates/render-helper.d.ts +12 -1
  108. package/dist/templates/render-helper.d.ts.map +1 -1
  109. package/dist/templates/render-helper.js +30 -7
  110. package/dist/templates/render-helper.js.map +1 -1
  111. package/dist/templates/routes-file-hono.d.ts.map +1 -1
  112. package/dist/templates/routes-file-hono.js +2 -1
  113. package/dist/templates/routes-file-hono.js.map +1 -1
  114. package/dist/templates/routes-file.d.ts.map +1 -1
  115. package/dist/templates/routes-file.js +18 -10
  116. package/dist/templates/routes-file.js.map +1 -1
  117. package/dist/templates/zod-validators.d.ts.map +1 -1
  118. package/dist/templates/zod-validators.js +7 -4
  119. package/dist/templates/zod-validators.js.map +1 -1
  120. package/dist/ui-tier-gate.d.ts +44 -0
  121. package/dist/ui-tier-gate.d.ts.map +1 -0
  122. package/dist/ui-tier-gate.js +68 -0
  123. package/dist/ui-tier-gate.js.map +1 -0
  124. package/package.json +6 -6
  125. package/src/column-mapper.ts +17 -0
  126. package/src/constants.ts +11 -0
  127. package/src/generator-registry.ts +1 -0
  128. package/src/generator.ts +15 -0
  129. package/src/generators/agent-docs-file.ts +11 -2
  130. package/src/generators/agent-schema-input.ts +11 -0
  131. package/src/generators/agent-schema-page.ts +14 -1
  132. package/src/generators/agent-ui-page.ts +10 -10
  133. package/src/generators/api-model.ts +14 -7
  134. package/src/generators/barrel.ts +2 -1
  135. package/src/generators/callable-file.ts +2 -1
  136. package/src/generators/docs-data-builder.ts +6 -5
  137. package/src/generators/entity-file.ts +2 -1
  138. package/src/generators/names-file.ts +34 -4
  139. package/src/generators/queries-file.ts +2 -1
  140. package/src/generators/render-helper-file.ts +1 -1
  141. package/src/generators/requirements-markdown.ts +28 -1
  142. package/src/generators/routes-file-hono.ts +2 -1
  143. package/src/generators/routes-file.ts +2 -1
  144. package/src/index.ts +23 -7
  145. package/src/metaobjects-config.ts +20 -1
  146. package/src/names.ts +5 -4
  147. package/src/overwrite-policy.ts +62 -11
  148. package/src/reference/barrel.ts +2 -1
  149. package/src/reference/entity.ts +2 -2
  150. package/src/reference/names.ts +30 -4
  151. package/src/reference/queries.ts +3 -3
  152. package/src/reference/routes-hono.ts +2 -1
  153. package/src/reference/routes.ts +2 -1
  154. package/src/runner.ts +162 -12
  155. package/src/templates/callable-file.ts +2 -1
  156. package/src/templates/drizzle-schema.ts +43 -13
  157. package/src/templates/entity-file.ts +4 -2
  158. package/src/templates/filter-allowlist.ts +9 -6
  159. package/src/templates/filter-shared.ts +91 -6
  160. package/src/templates/inferred-types.ts +9 -6
  161. package/src/templates/projection-decl.ts +4 -2
  162. package/src/templates/queries-file.ts +6 -5
  163. package/src/templates/relations-block.ts +2 -1
  164. package/src/templates/render-helper.ts +31 -6
  165. package/src/templates/routes-file-hono.ts +2 -1
  166. package/src/templates/routes-file.ts +18 -10
  167. package/src/templates/zod-validators.ts +7 -4
  168. package/src/ui-tier-gate.ts +79 -0
@@ -26,6 +26,7 @@ import { GENERATED_HEADER } from "../constants.js";
26
26
  import { isTphDiscriminatorBase, tphConcreteSubtypes } from "./tph-discriminator.js";
27
27
  import { isProjection, isWriteThrough } from "../projection/projection-detector.js";
28
28
  import { hasAutoSetFields } from "./zod-validators.js";
29
+ import { effectivePackage } from "../docs-paths.js";
29
30
 
30
31
  /**
31
32
  * The dialect-correct Drizzle `Db` type alias + its import — parameter-passed into every
@@ -94,7 +95,7 @@ export function renderQueriesFile(obj: MetaObject, ctx: RenderContext): string {
94
95
  const entityFileName = entityModuleSpecifier(
95
96
  ctx.selfTarget,
96
97
  ctx.entityModuleTarget,
97
- obj.package,
98
+ effectivePackage(obj),
98
99
  entityName,
99
100
  ctx.extStyle,
100
101
  );
@@ -163,7 +164,7 @@ function renderProjectionQueriesFile(obj: MetaObject, ctx: RenderContext): strin
163
164
  const camelName = entityName.charAt(0).toLowerCase() + entityName.slice(1);
164
165
  const viewVar = `${camelName}View`;
165
166
  const entityFileName = entityModuleSpecifier(
166
- ctx.selfTarget, ctx.entityModuleTarget, obj.package, entityName, ctx.extStyle,
167
+ ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(obj), entityName, ctx.extStyle,
167
168
  );
168
169
  const { fieldName: pkField, tsType: pkType } = getPkInfo(obj, ctx);
169
170
  const eqSym = imp("eq@drizzle-orm");
@@ -215,7 +216,7 @@ function renderWriteThroughQueriesFile(obj: MetaObject, ctx: RenderContext): str
215
216
  const tableVar = ctx.collectionName(entityName);
216
217
  const singularVar = camelName;
217
218
  const entityFileName = entityModuleSpecifier(
218
- ctx.selfTarget, ctx.entityModuleTarget, obj.package, entityName, ctx.extStyle,
219
+ ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(obj), entityName, ctx.extStyle,
219
220
  );
220
221
  const { fieldName: pkField, tsType: pkType } = getPkInfo(obj, ctx);
221
222
  const pkFields = getPkFields(obj);
@@ -309,7 +310,7 @@ function renderTphQueriesFile(base: MetaObject, ctx: RenderContext): string {
309
310
  const { fieldName: pkField, tsType: pkType } = getPkInfo(base, ctx);
310
311
 
311
312
  const baseFileSpec = entityModuleSpecifier(
312
- ctx.selfTarget, ctx.entityModuleTarget, base.package, baseName, ctx.extStyle,
313
+ ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(base), baseName, ctx.extStyle,
313
314
  );
314
315
  const tableSym = imp(`${tableVar}@${baseFileSpec}`);
315
316
  const baseTypeSym = imp(`t:${baseName}@${baseFileSpec}`);
@@ -342,7 +343,7 @@ export async function list${pluralize(baseName)}(db: Db, opts?: { limit?: number
342
343
  const value = sub.ownAttr(OBJECT_ATTR_DISCRIMINATOR_VALUE) as string;
343
344
  const valueLit = JSON.stringify(value);
344
345
  const subFileSpec = entityModuleSpecifier(
345
- ctx.selfTarget, ctx.entityModuleTarget, sub.package, sub.name, ctx.extStyle,
346
+ ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(sub), sub.name, ctx.extStyle,
346
347
  );
347
348
  const subTypeSym = imp(`t:${sub.name}@${subFileSpec}`);
348
349
  const subSchemaSym = imp(`${sub.name}Schema@${subFileSpec}`);
@@ -7,6 +7,7 @@ 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
9
  import type { RelationEntry } from "../relation-resolver.js";
10
+ import { effectivePackage } from "../docs-paths.js";
10
11
 
11
12
  /**
12
13
  * Render the relations() block for one entity.
@@ -28,7 +29,7 @@ export function renderRelationsBlock(entity: MetaObject, ctx: RenderContext): Co
28
29
  if (hasMany) paramParts.push("many");
29
30
  const params = `{ ${paramParts.join(", ")} }`;
30
31
 
31
- const thisEntityPackage = entity.package;
32
+ const thisEntityPackage = effectivePackage(entity);
32
33
  const lines: Code[] = entries.map((entry) =>
33
34
  renderRelationEntry(entry, ctx, varName, thisEntityPackage),
34
35
  );
@@ -53,6 +53,8 @@ import {
53
53
  type VerifyError,
54
54
  } from "@metaobjectsdev/render";
55
55
  import { templateSymbolBase } from "../naming.js";
56
+ import { GENERATED_HEADER } from "../constants.js";
57
+ import type { ExtStyle } from "../metaobjects-config.js";
56
58
 
57
59
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
58
60
  // ADR-0042: package-local — resolveObjectRef binds a bare @objectRef in `referrerPkg` first (else root-level), an FQN exactly.
@@ -65,6 +67,20 @@ function findTemplate(root: MetaData, name: string): MetaData | undefined {
65
67
  return root.children().find((c) => c.type === TYPE_TEMPLATE && c.name === name);
66
68
  }
67
69
 
70
+ /**
71
+ * The three lines every emitted render-helper module opens with: the generated-file
72
+ * marker, the `render()` import, and the typed payload import. Identical between the
73
+ * email and document kinds below — only the `@metaobjectsdev/render` type import
74
+ * differs (email additionally needs `EmailDocument`), so that piece is a parameter
75
+ * rather than a second near-copy of this block.
76
+ */
77
+ function renderHelperHeader(typeImports: string, payloadTypeName: string, ext: string): string {
78
+ return `// ${GENERATED_HEADER} — DO NOT EDIT.
79
+ import { render } from "@metaobjectsdev/render";
80
+ import type { ${typeImports} } from "@metaobjectsdev/render";
81
+ import type { ${payloadTypeName} } from "./${payloadTypeName}${ext}";`;
82
+ }
83
+
68
84
  /**
69
85
  * Walk an `object.value` view-object into a render `PayloadField[]`. Object-ref
70
86
  * fields recurse into their referenced view-object; a `seen` set guards a
@@ -150,6 +166,16 @@ export function renderRenderHelper(
150
166
  root: MetaData,
151
167
  templateName: string,
152
168
  provider: Provider,
169
+ /**
170
+ * The project's import-extension style, as every other emitter honours it.
171
+ * Defaults to "js" — the engine default — so an existing caller is unchanged.
172
+ *
173
+ * This file used to hardcode `.js` regardless. Both spellings happen to resolve
174
+ * under nodenext AND bundler, so nothing broke; it was simply the one emitter
175
+ * that did not read the setting, which is how a project with `extStyle: "none"`
176
+ * ended up with one file spelling its imports differently from every other.
177
+ */
178
+ extStyle: ExtStyle = "js",
153
179
  ): string {
154
180
  const tmpl = findTemplate(root, templateName);
155
181
  if (!tmpl) {
@@ -186,6 +212,9 @@ export function renderRenderHelper(
186
212
  // resolution above (findObject/derivePayloadFieldTree) intentionally keeps
187
213
  // using the raw `payloadRef`.
188
214
  const payloadTypeName = stripPackage(payloadRef);
215
+ // The emitted relative import's extension, from the project's setting rather than
216
+ // hardcoded — see the `extStyle` parameter doc.
217
+ const ext = extStyle === "none" ? "" : ".js";
189
218
 
190
219
  // ADR-0039: resolving — a template may inherit its @* refs/format/kind via extends.
191
220
  const kind = ((tmpl.attr(TEMPLATE_ATTR_KIND) as string | undefined) ?? TEMPLATE_KIND_DEFAULT)
@@ -215,9 +244,7 @@ export function renderRenderHelper(
215
244
  ? `\n textBody: render({ ref: ${JSON.stringify(textBodyRef)}, payload, format: "text", provider, verify: ${ft} }),`
216
245
  : "";
217
246
 
218
- return `import { render } from "@metaobjectsdev/render";
219
- import type { Provider, EmailDocument } from "@metaobjectsdev/render";
220
- import type { ${payloadTypeName} } from "./${payloadTypeName}.js";
247
+ return `${renderHelperHeader("Provider, EmailDocument", payloadTypeName, ext)}
221
248
 
222
249
  /**
223
250
  * Render the ${templateName} email (subject + html body${typeof textBodyRef === "string" ? " + text body" : ""}) from a
@@ -255,9 +282,7 @@ export function ${fnName}(payload: ${payloadTypeName}, provider: Provider): Emai
255
282
  const maxCharsArg =
256
283
  maxChars !== undefined && Number.isFinite(maxChars) ? `, maxChars: ${maxChars}` : "";
257
284
 
258
- return `import { render } from "@metaobjectsdev/render";
259
- import type { Provider } from "@metaobjectsdev/render";
260
- import type { ${payloadTypeName} } from "./${payloadTypeName}.js";
285
+ return `${renderHelperHeader("Provider", payloadTypeName, ext)}
261
286
 
262
287
  /**
263
288
  * Render the ${templateName} document from a typed ${payloadTypeName} payload. Wraps the
@@ -31,6 +31,7 @@ import { entityModuleSpecifier } from "../import-path.js";
31
31
  import { GENERATED_HEADER } from "../constants.js";
32
32
  import { isProjection, isWriteThrough } from "../projection/projection-detector.js";
33
33
  import { type CrudVerb, exposeLine } from "../routes-expose.js";
34
+ import { effectivePackage } from "../docs-paths.js";
34
35
 
35
36
  export function renderRoutesFileHono(
36
37
  entity: MetaObject,
@@ -49,7 +50,7 @@ export function renderRoutesFileHono(
49
50
  const entityFileSpec = entityModuleSpecifier(
50
51
  ctx.selfTarget,
51
52
  ctx.entityModuleTarget,
52
- entity.package,
53
+ effectivePackage(entity),
53
54
  entityName,
54
55
  ctx.extStyle,
55
56
  );
@@ -30,6 +30,7 @@ import { isProjection, isWriteThrough } from "../projection/projection-detector.
30
30
  import type { RelationEntry } from "../relation-resolver.js";
31
31
  import { isTphDiscriminatorBase, tphPlan } from "./tph-discriminator.js";
32
32
  import { type CrudVerb, exposeLine, intersectExpose, TPH_POLYMORPHIC_VERBS } from "../routes-expose.js";
33
+ import { effectivePackage } from "../docs-paths.js";
33
34
 
34
35
  export function renderRoutesFile(
35
36
  entity: MetaObject,
@@ -49,16 +50,17 @@ export function renderRoutesFile(
49
50
 
50
51
  const entityName = entity.name;
51
52
  const handlerName = routesHandlerName(entityName);
53
+ const entityPkg = effectivePackage(entity);
52
54
  // Import the entity's own file. Same target → relative "./Entity"; cross
53
55
  // target → importBase-qualified package path.
54
56
  const entityFileSpec = entityModuleSpecifier(
55
57
  ctx.selfTarget,
56
58
  ctx.entityModuleTarget,
57
- entity.package,
59
+ entityPkg,
58
60
  entityName,
59
61
  ctx.extStyle,
60
62
  );
61
- const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout, entity.package, ctx.dbImport, ctx.extStyle);
63
+ const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout, entityPkg, ctx.dbImport, ctx.extStyle);
62
64
 
63
65
  const header =
64
66
  `// ${GENERATED_HEADER} — DO NOT EDIT.\n` +
@@ -260,10 +262,15 @@ function renderM2mMount(
260
262
  ctx: RenderContext,
261
263
  fastifyVar: string,
262
264
  ): Code {
265
+ // `source` never changes across this function, so its effective package is computed
266
+ // once and reused below (both crossEntitySpecifier calls, and the three
267
+ // resolveJunctionColumn calls' `fromPackage` — see that comment for why it must be
268
+ // SOURCE's package rather than the junction/target entity's own).
269
+ const sourcePkg = effectivePackage(source);
263
270
  const junctionVarSym = imp(
264
271
  `${ctx.collectionName(entry.junctionEntity)}@${crossEntitySpecifier(
265
272
  ctx.outputLayout,
266
- source.package,
273
+ sourcePkg,
267
274
  ctx.packageOf.get(entry.junctionEntity),
268
275
  entry.junctionEntity,
269
276
  ctx.extStyle,
@@ -272,7 +279,7 @@ function renderM2mMount(
272
279
  const targetVarSym = imp(
273
280
  `${ctx.collectionName(entry.targetEntity)}@${crossEntitySpecifier(
274
281
  ctx.outputLayout,
275
- source.package,
282
+ sourcePkg,
276
283
  ctx.packageOf.get(entry.targetEntity),
277
284
  entry.targetEntity,
278
285
  ctx.extStyle,
@@ -284,13 +291,13 @@ function renderM2mMount(
284
291
  // fromPackage = source.package: this routes file is SOURCE's own module, never the
285
292
  // junction's or the target's — see resolveJunctionColumn's doc comment (B1).
286
293
  const sourceColumn: Code = junction
287
- ? resolveJunctionColumn(junction, entry.sourceJoinField!, ctx, source.package)
294
+ ? resolveJunctionColumn(junction, entry.sourceJoinField!, ctx, sourcePkg)
288
295
  : code`${JSON.stringify(entry.sourceJoinField!)}`;
289
296
  const targetColumn: Code = junction
290
- ? resolveJunctionColumn(junction, entry.targetJoinField!, ctx, source.package)
297
+ ? resolveJunctionColumn(junction, entry.targetJoinField!, ctx, sourcePkg)
291
298
  : code`${JSON.stringify(entry.targetJoinField!)}`;
292
299
  const targetPkColumn: Code = target
293
- ? resolveJunctionColumn(target, ctx.pkMap.get(entry.targetEntity)?.fieldName ?? "id", ctx, source.package)
300
+ ? resolveJunctionColumn(target, ctx.pkMap.get(entry.targetEntity)?.fieldName ?? "id", ctx, sourcePkg)
294
301
  : code`${JSON.stringify("id")}`;
295
302
 
296
303
  return code` ${mountM2mRouteSym}({
@@ -368,10 +375,11 @@ function renderTphRoutesFile(
368
375
  const discField = plan.discriminatorField;
369
376
  const tableVar = ctx.collectionName(baseName);
370
377
 
378
+ const basePkg = effectivePackage(base);
371
379
  const baseFileSpec = entityModuleSpecifier(
372
- ctx.selfTarget, ctx.entityModuleTarget, base.package, baseName, ctx.extStyle,
380
+ ctx.selfTarget, ctx.entityModuleTarget, basePkg, baseName, ctx.extStyle,
373
381
  );
374
- const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout, base.package, ctx.dbImport, ctx.extStyle);
382
+ const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout, basePkg, ctx.dbImport, ctx.extStyle);
375
383
 
376
384
  const FastifyInstanceSym = imp("t:FastifyInstance@fastify");
377
385
  const mountCrudRoutesSym = imp("mountCrudRoutes@@metaobjectsdev/runtime-ts/drizzle-fastify");
@@ -405,7 +413,7 @@ function renderTphRoutesFile(
405
413
 
406
414
  const subtypeMounts: Code[] = plan.subtypes.map(({ entity: sub, value, routeSegment: segment }) => {
407
415
  const subFileSpec = entityModuleSpecifier(
408
- ctx.selfTarget, ctx.entityModuleTarget, sub.package, sub.name, ctx.extStyle,
416
+ ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(sub), sub.name, ctx.extStyle,
409
417
  );
410
418
  const subInsertSym = imp(`${sub.name}InsertSchema@${subFileSpec}`);
411
419
  // FR-036 Program B: the per-subtype UPDATE must carry the FR-035 present-key
@@ -42,6 +42,7 @@ import { valueObjectModuleSpecifier } from "../import-path.js";
42
42
  // FR-035: the SAME required-predicate that drives the Drizzle column's .notNull()
43
43
  // drives the UpdateSchema's .nullable() exclusion — shared so they cannot drift.
44
44
  import { isRequired } from "../column-mapper.js";
45
+ import { effectivePackage } from "../docs-paths.js";
45
46
 
46
47
  /**
47
48
  * FR-017 Tier 1 — when this object is a TPH subtype (@discriminatorValue set
@@ -593,9 +594,10 @@ function zodFieldExpr(
593
594
  // layout/package/extStyle-aware helper (the SAME one the field's TS type +
594
595
  // Drizzle .$type<> use) so all three agree. Without owner/ctx (bare
595
596
  // unit-test calls) fall back to the bare name + flat same-dir.
596
- const refName = (ctx && owner) ? ctx.resolveValueObjectName(ref, fieldDeclaringPackage(field, owner.package)) : stripPackage(ref);
597
+ const ownerPkg = (ctx && owner) ? effectivePackage(owner) : undefined;
598
+ const refName = (ctx && owner) ? ctx.resolveValueObjectName(ref, fieldDeclaringPackage(field, ownerPkg)) : stripPackage(ref);
597
599
  const moduleSpec = (ctx && owner)
598
- ? valueObjectModuleSpecifier(refName, ctx.packageOf, owner.package, ctx.outputLayout, ctx.extStyle)
600
+ ? valueObjectModuleSpecifier(refName, ctx.packageOf, ownerPkg, ctx.outputLayout, ctx.extStyle)
599
601
  : `./${refName}.js`;
600
602
  const refImp = imp(`${refName}InsertSchema@${moduleSpec}`);
601
603
  let base: Code = code`${refImp}`;
@@ -614,9 +616,10 @@ function zodFieldExpr(
614
616
  if (field.subType === FIELD_SUBTYPE_MAP) {
615
617
  const ref = field.attr(FIELD_ATTR_OBJECT_REF);
616
618
  if (typeof ref === "string" && ref.length > 0) {
617
- const refName = (ctx && owner) ? ctx.resolveValueObjectName(ref, fieldDeclaringPackage(field, owner.package)) : stripPackage(ref);
619
+ const ownerPkg = (ctx && owner) ? effectivePackage(owner) : undefined;
620
+ const refName = (ctx && owner) ? ctx.resolveValueObjectName(ref, fieldDeclaringPackage(field, ownerPkg)) : stripPackage(ref);
618
621
  const moduleSpec = (ctx && owner)
619
- ? valueObjectModuleSpecifier(refName, ctx.packageOf, owner.package, ctx.outputLayout, ctx.extStyle)
622
+ ? valueObjectModuleSpecifier(refName, ctx.packageOf, ownerPkg, ctx.outputLayout, ctx.extStyle)
620
623
  : `./${refName}.js`;
621
624
  const refImp = imp(`${refName}InsertSchema@${moduleSpec}`);
622
625
  return appendValidatorChain(code`z.record(z.string(), ${refImp})`, field, forceRequired);
@@ -0,0 +1,79 @@
1
+ import type { Generator } from "./generator.js";
2
+
3
+ /**
4
+ * Generators that emit a CLIENT UI artifact — a form, a hook, a grid or its columns.
5
+ *
6
+ * Names, not identities, for the same reason `PROMPT_GENERATOR_NAMES` uses names: under
7
+ * ADR-0034 scaffold-and-own an adopter runs their OWN copy, and `meta eject` copies the
8
+ * template verbatim, so the name survives the copy while the object identity does not.
9
+ *
10
+ * This set exists ONLY as a compatibility path for a copy ejected before `emitsUiTier`
11
+ * existed. The marker is the mechanism; a name match makes the run behave as it did
12
+ * before AND says the marker is missing (see {@link warnUnmarkedUiGenerators}), because
13
+ * a page silently disappearing is exactly the kind of degradation that must announce
14
+ * itself.
15
+ */
16
+ export const UI_TIER_GENERATOR_NAMES: ReadonlySet<string> = new Set([
17
+ "form-file",
18
+ "tanstack-query",
19
+ "tanstack-grid",
20
+ "tanstack-grid-hook",
21
+ "angular-form",
22
+ "angular-grid",
23
+ "angular-service",
24
+ ]);
25
+
26
+ /** Generators in the suite that emit a UI artifact but carry no `emitsUiTier` marker. */
27
+ function unmarked(generators: readonly Generator[]): Generator[] {
28
+ return generators.filter(
29
+ (g) => g.emitsUiTier !== true && UI_TIER_GENERATOR_NAMES.has(g.name),
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Does this run emit a client UI tier?
35
+ *
36
+ * The declared marker OR the legacy name. `agent/ui.md` gates on the answer: its own
37
+ * predicate was metadata-only (`servesReadApi`), which says a UI *could* be generated
38
+ * for an object and never that this run generates one — so a project with
39
+ * `generators: []` was handed a confident page naming endpoints nothing serves.
40
+ */
41
+ export function runEmitsUiTier(generators: readonly Generator[]): boolean {
42
+ return generators.some((g) => g.emitsUiTier === true) || unmarked(generators).length > 0;
43
+ }
44
+
45
+ /**
46
+ * A generator ejected before `emitsUiTier` existed still emits its tier, so the run is
47
+ * NOT degraded — the name match above keeps `agent/ui.md` emitting. It is warned about
48
+ * anyway: an adopter who renames their copy loses the page with no signal at all, and
49
+ * the one-line fix belongs where the fact is known rather than in a migration note
50
+ * nobody re-reads. Self-extinguishing — adding the marker silences it forever.
51
+ */
52
+ export function warnUnmarkedUiGenerators(
53
+ generators: readonly Generator[],
54
+ warn: (msg: string) => void,
55
+ ): void {
56
+ const stale = unmarked(generators);
57
+ if (stale.length === 0) return;
58
+ const names = stale.map((g) => `'${g.name}'`).join(", ");
59
+ warn(
60
+ `${names} emit${stale.length === 1 ? "s" : ""} a UI artifact but carr${stale.length === 1 ? "ies" : "y"} no ` +
61
+ `'emitsUiTier: true' marker — an owned copy ejected before the marker existed. ` +
62
+ `agent/ui.md still emits (matched by name), but a renamed copy would not: add ` +
63
+ `'emitsUiTier: true' beside 'name:' in your copy, or re-eject it.`,
64
+ );
65
+ }
66
+
67
+ /**
68
+ * Does this run emit the OPT-IN Hono routes surface?
69
+ *
70
+ * Lives beside `runEmitsUiTier` for one reason: both are "aggregate a marker across the
71
+ * suite", and both were being re-derived at the `meta docs` door — which read the RAW
72
+ * config, where a generator wired by stable name (`generators: ["routes-hono"]`, legal
73
+ * under ADR-0021 #1) is a string carrying no marker at all. So `api-docs` omitted the
74
+ * Hono surface for a project that had wired it. One helper, called after the strings are
75
+ * resolved, is what stops the two doors disagreeing again.
76
+ */
77
+ export function runEmitsHonoRoutes(generators: readonly Generator[]): boolean {
78
+ return generators.some((g) => g.emitsHonoRoutes === true);
79
+ }