@metaobjectsdev/codegen-ts 0.15.19 → 0.15.21-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 (58) hide show
  1. package/dist/generators/api-model.js +5 -5
  2. package/dist/generators/api-model.js.map +1 -1
  3. package/dist/generators/output-prompt-file.d.ts.map +1 -1
  4. package/dist/generators/output-prompt-file.js +3 -2
  5. package/dist/generators/output-prompt-file.js.map +1 -1
  6. package/dist/generators/prompt-render-file.d.ts.map +1 -1
  7. package/dist/generators/prompt-render-file.js +3 -1
  8. package/dist/generators/prompt-render-file.js.map +1 -1
  9. package/dist/generators/render-helper-file.d.ts.map +1 -1
  10. package/dist/generators/render-helper-file.js +3 -2
  11. package/dist/generators/render-helper-file.js.map +1 -1
  12. package/dist/generators/template-doc-builder.d.ts.map +1 -1
  13. package/dist/generators/template-doc-builder.js +11 -5
  14. package/dist/generators/template-doc-builder.js.map +1 -1
  15. package/dist/generators/template-payload-tree.d.ts +1 -1
  16. package/dist/generators/template-payload-tree.d.ts.map +1 -1
  17. package/dist/generators/template-payload-tree.js +16 -9
  18. package/dist/generators/template-payload-tree.js.map +1 -1
  19. package/dist/generators/trace-helper-file.d.ts.map +1 -1
  20. package/dist/generators/trace-helper-file.js +3 -2
  21. package/dist/generators/trace-helper-file.js.map +1 -1
  22. package/dist/payload-codegen.d.ts +4 -3
  23. package/dist/payload-codegen.d.ts.map +1 -1
  24. package/dist/payload-codegen.js +21 -14
  25. package/dist/payload-codegen.js.map +1 -1
  26. package/dist/projection/build-projection-views.d.ts.map +1 -1
  27. package/dist/projection/build-projection-views.js +19 -1
  28. package/dist/projection/build-projection-views.js.map +1 -1
  29. package/dist/templates/extract-delegate-emitter.d.ts.map +1 -1
  30. package/dist/templates/extract-delegate-emitter.js +7 -10
  31. package/dist/templates/extract-delegate-emitter.js.map +1 -1
  32. package/dist/templates/extractor.d.ts.map +1 -1
  33. package/dist/templates/extractor.js +9 -11
  34. package/dist/templates/extractor.js.map +1 -1
  35. package/dist/templates/output-parser.d.ts.map +1 -1
  36. package/dist/templates/output-parser.js +8 -7
  37. package/dist/templates/output-parser.js.map +1 -1
  38. package/dist/templates/output-prompt.d.ts.map +1 -1
  39. package/dist/templates/output-prompt.js +6 -4
  40. package/dist/templates/output-prompt.js.map +1 -1
  41. package/dist/templates/render-helper.d.ts.map +1 -1
  42. package/dist/templates/render-helper.js +16 -8
  43. package/dist/templates/render-helper.js.map +1 -1
  44. package/package.json +6 -6
  45. package/src/generators/api-model.ts +5 -5
  46. package/src/generators/output-prompt-file.ts +3 -3
  47. package/src/generators/prompt-render-file.ts +3 -1
  48. package/src/generators/render-helper-file.ts +3 -3
  49. package/src/generators/template-doc-builder.ts +20 -6
  50. package/src/generators/template-payload-tree.ts +16 -11
  51. package/src/generators/trace-helper-file.ts +7 -2
  52. package/src/payload-codegen.ts +26 -15
  53. package/src/projection/build-projection-views.ts +21 -0
  54. package/src/templates/extract-delegate-emitter.ts +7 -10
  55. package/src/templates/extractor.ts +9 -11
  56. package/src/templates/output-parser.ts +8 -8
  57. package/src/templates/output-prompt.ts +6 -5
  58. package/src/templates/render-helper.ts +16 -8
@@ -16,11 +16,10 @@
16
16
  // generators: [..., renderHelper({ outDir: "src/generated/render" })]
17
17
 
18
18
  import {
19
- TYPE_OBJECT,
20
19
  TYPE_TEMPLATE,
21
20
  TEMPLATE_SUBTYPE_OUTPUT,
22
21
  TEMPLATE_ATTR_PAYLOAD_REF,
23
- refMatchesObject,
22
+ resolveObjectRef,
24
23
  } from "@metaobjectsdev/metadata";
25
24
  import {
26
25
  type EmittedFile,
@@ -58,7 +57,8 @@ export const renderHelper = function renderHelper(opts?: RenderHelperOpts): Gene
58
57
  // ADR-0039: resolving — a template may inherit its @* refs/format/kind via extends.
59
58
  const payloadRef = t.attr(TEMPLATE_ATTR_PAYLOAD_REF);
60
59
  if (typeof payloadRef !== "string") continue;
61
- const vo = root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, payloadRef));
60
+ // ADR-0042: a bare @payloadRef resolves in the template's package.
61
+ const vo = resolveObjectRef(root, payloadRef, t.package ?? t.fileDefaultPackage ?? "").node;
62
62
  if (!vo) continue;
63
63
  files.push({
64
64
  // renderRenderHelper THROWS (fails codegen) on a mustache↔VO drift —
@@ -114,10 +114,12 @@ export function buildTemplateDocData(
114
114
  const kind: "document" | "email" = isEmail ? TEMPLATE_KIND_EMAIL : TEMPLATE_KIND_DOCUMENT;
115
115
 
116
116
  const payloadRefRaw = template.attr(TEMPLATE_ATTR_PAYLOAD_REF);
117
- const payloadName =
118
- typeof payloadRefRaw === "string" && payloadRefRaw.length > 0
119
- ? stripPackage(payloadRefRaw)
120
- : "unknown";
117
+ // ADR-0042: keep the FULL ref for RESOLUTION (an FQN @payloadRef to another
118
+ // package must resolve exactly the old bare-tail fallback that used to find
119
+ // it is gone); strip to the bare name only for DISPLAY labels.
120
+ const payloadRefFull =
121
+ typeof payloadRefRaw === "string" && payloadRefRaw.length > 0 ? payloadRefRaw : "unknown";
122
+ const payloadName = payloadRefFull !== "unknown" ? stripPackage(payloadRefFull) : "unknown";
121
123
 
122
124
  const requiredTags = attrStringList(template.attr(TEMPLATE_ATTR_REQUIRED_TAGS));
123
125
  const maxChars = readMaxChars(template.attr(TEMPLATE_ATTR_MAX_CHARS));
@@ -188,6 +190,7 @@ export function buildTemplateDocData(
188
190
  layout,
189
191
  template,
190
192
  payloadName,
193
+ payloadRefFull,
191
194
  // Document: the single @textRef (unlabeled). Email: one labeled part each.
192
195
  refs:
193
196
  parts !== undefined
@@ -221,7 +224,11 @@ interface BuildSectionArgs {
221
224
  /** The `template.output` node whose page is being built — the FROM page for
222
225
  * every relative href on this section. */
223
226
  template: MetaData;
227
+ /** Bare payload name — DISPLAY only (labels, owner names). */
224
228
  payloadName: string;
229
+ /** Full @payloadRef (FQN for a cross-package payload, bare for same-package) —
230
+ * used for RESOLUTION so a cross-package payload tree still resolves (ADR-0042). */
231
+ payloadRefFull: string;
225
232
  refs: SourceRefSpec[];
226
233
  }
227
234
 
@@ -236,8 +243,15 @@ interface BuildSectionArgs {
236
243
  * resolved, so the caller omits the section entirely.
237
244
  */
238
245
  function buildTemplateSourceSection(args: BuildSectionArgs): string | undefined {
239
- const { provider, root, layout, template, payloadName, refs } = args;
240
- const tree: AnnotatePayloadField[] = buildEnrichedPayloadTree(root, payloadName);
246
+ const { provider, root, layout, template, payloadName, payloadRefFull, refs } = args;
247
+ // ADR-0042: resolve the payload tree from the FULL ref — an FQN cross-package
248
+ // @payloadRef resolves exactly; a bare same-package ref resolves in the
249
+ // template's package.
250
+ const tree: AnnotatePayloadField[] = buildEnrichedPayloadTree(
251
+ root,
252
+ payloadRefFull,
253
+ template.package ?? template.fileDefaultPackage ?? "",
254
+ );
241
255
  const fromNode = docPageNode(template);
242
256
 
243
257
  // Layout-aware field href: route through the SAME docPageHref the Payload
@@ -21,23 +21,21 @@
21
21
  import {
22
22
  type MetaObject,
23
23
  type MetaRoot,
24
- TYPE_OBJECT,
25
24
  FIELD_SUBTYPE_OBJECT,
26
25
  FIELD_ATTR_OBJECT_REF,
27
26
  stripPackage,
28
- refMatchesObject,
27
+ resolveObjectRef,
29
28
  } from "@metaobjectsdev/metadata";
30
29
  import { isFieldRequired, neutralTypeStr } from "./docs-data-builder.js";
31
30
  import type { AnnotatePayloadField } from "./template-source-annotate.js";
32
31
 
33
- // ADR-0041: FQN-exact object resolution — a "::"-qualified @objectRef binds the exact
32
+ // ADR-0041/0042: FQN-exact object resolution — a "::"-qualified @objectRef binds the exact
34
33
  // package (never a bare-tail fallback), so a same-named object.value in another package
35
- // can't be wrongly bound (which would attach the wrong owner/type/link to a doc node).
34
+ // can't be wrongly bound (which would attach the wrong owner/type/link to a doc node); a
35
+ // bare ref binds the referrer's package FIRST, else a root-level object.
36
36
  // Mirrors render-helper.ts's findObject + the CLI payload-field-tree resolver.
37
- function findObject(root: MetaRoot, ref: string): MetaObject | undefined {
38
- return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, ref)) as
39
- | MetaObject
40
- | undefined;
37
+ function findObject(root: MetaRoot, ref: string, referrerPkg = ""): MetaObject | undefined {
38
+ return resolveObjectRef(root, ref, referrerPkg).node as MetaObject | undefined;
41
39
  }
42
40
 
43
41
  /**
@@ -54,12 +52,17 @@ function findObject(root: MetaRoot, ref: string): MetaObject | undefined {
54
52
  export function buildEnrichedPayloadTree(
55
53
  root: MetaRoot,
56
54
  voName: string,
55
+ referrerPkg = "",
57
56
  seen: ReadonlySet<string> = new Set(),
58
57
  ): AnnotatePayloadField[] {
59
58
  if (seen.has(voName)) return [];
60
- const vo: MetaObject | undefined = findObject(root, voName);
59
+ const vo: MetaObject | undefined = findObject(root, voName, referrerPkg);
61
60
  if (vo === undefined) return [];
62
61
  const owner = stripPackage(vo.name);
62
+ // ADR-0042: a nested @objectRef resolves in the FIELD's own declaring package
63
+ // (fallback below), which differs from this VO's when the field is inherited via
64
+ // extends from an abstract VO in another package (the bare ref was authored there).
65
+ const voPkg = vo.package ?? vo.fileDefaultPackage ?? "";
63
66
  const nextSeen = new Set(seen).add(voName);
64
67
  const out: AnnotatePayloadField[] = [];
65
68
  for (const f of vo.fields()) {
@@ -74,8 +77,10 @@ export function buildEnrichedPayloadTree(
74
77
  if (typeof ref === "string" && ref.length > 0) {
75
78
  // Owner switches to the nested VO for its fields (the recursion below
76
79
  // re-derives `owner` from the resolved VO's own name). Pass the FULL ref
77
- // (not stripPackage) so findObject resolves it FQN-exact (ADR-0041).
78
- node.fields = buildEnrichedPayloadTree(root, ref, nextSeen);
80
+ // (not stripPackage), resolved package-locally against the FIELD's declaring
81
+ // package (ADR-0042 — the bare ref was authored there, not on the concrete VO).
82
+ const fieldPkg = f.parent?.package ?? f.parent?.fileDefaultPackage ?? voPkg;
83
+ node.fields = buildEnrichedPayloadTree(root, ref, fieldPkg, nextSeen);
79
84
  }
80
85
  }
81
86
  out.push(node);
@@ -122,8 +122,13 @@ export const traceHelperFile = function traceHelperFile(opts?: TraceHelperOpts):
122
122
  : "Format.JSON";
123
123
 
124
124
  // Collect VO names for interface emission (dedupe via batch emitter).
125
- // Both refs are guaranteed strings by the guards above.
126
- const interfaces = generatePayloadInterfacesBatch(ctx.loadedRoot, [payloadRef, responseRef]);
125
+ // Both refs are guaranteed strings by the guards above. ADR-0042: a bare
126
+ // ref resolves in the prompt's package.
127
+ const interfaces = generatePayloadInterfacesBatch(
128
+ ctx.loadedRoot,
129
+ [payloadRef, responseRef],
130
+ prompt.package ?? prompt.fileDefaultPackage ?? "",
131
+ );
127
132
 
128
133
  const requestType = payloadRef;
129
134
 
@@ -13,7 +13,6 @@
13
13
  import {
14
14
  type MetaData,
15
15
  type MetaField,
16
- TYPE_OBJECT,
17
16
  TYPE_FIELD,
18
17
  TYPE_TEMPLATE,
19
18
  FIELD_SUBTYPE_OBJECT,
@@ -23,7 +22,7 @@ import {
23
22
  TEMPLATE_ATTR_PAYLOAD_REF,
24
23
  TEMPLATE_ATTR_TEXT_REF,
25
24
  TEMPLATE_ATTR_FORMAT,
26
- refMatchesObject,
25
+ resolveObjectRef,
27
26
  stripPackage,
28
27
  } from "@metaobjectsdev/metadata";
29
28
  import { enumValues } from "./enum-meta.js";
@@ -47,10 +46,9 @@ const SCALAR_TS: Record<string, string> = {
47
46
  };
48
47
 
49
48
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
50
- function findObject(root: MetaData, name: string): MetaData | undefined {
51
- // FR-032 @payloadRef/@responseRef are FQN after the desugar/sweep; match on
52
- // the effective FQN resolution key (with bare back-compat).
53
- return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
49
+ // ADR-0042: resolveObjectRef gives package-local-before-root-level precedence for a bare ref, FQN-exact otherwise.
50
+ function findObject(root: MetaData, name: string, referrerPkg = ""): MetaData | undefined {
51
+ return resolveObjectRef(root, name, referrerPkg).node;
54
52
  }
55
53
 
56
54
  /**
@@ -106,12 +104,18 @@ function isFieldRequired(field: MetaData): boolean {
106
104
  function emitInterface(
107
105
  root: MetaData,
108
106
  voRef: string,
107
+ referrerPkg: string,
109
108
  emitted: Set<string>,
110
109
  out: string[],
111
110
  enumAliases: Set<string>,
112
111
  ): void {
113
- const vo = findObject(root, voRef);
112
+ const vo = findObject(root, voRef, referrerPkg);
114
113
  if (!vo) return;
114
+ // ADR-0042: a nested @objectRef resolves in the FIELD's OWN declaring package —
115
+ // which may differ from this resolved VO's package when the field is inherited
116
+ // via extends from an abstract VO in another package (the bare ref was authored
117
+ // there). Fall back to this VO's package when a field has no parent package.
118
+ const voPkg = vo.package ?? vo.fileDefaultPackage ?? "";
115
119
  // FR-032/ADR-0041 — voRef may arrive as an FQN (a nested @objectRef); the emitted
116
120
  // interface NAME must be the bare, TS-valid name. Dedupe on the bare name too, so a
117
121
  // VO reached once by bare name and once by FQN still emits exactly once.
@@ -120,7 +124,7 @@ function emitInterface(
120
124
  emitted.add(voName);
121
125
  const aliasLines: string[] = [];
122
126
  const lines: string[] = [`export interface ${voName} {`];
123
- const refs: string[] = [];
127
+ const refs: { ref: string; pkg: string }[] = [];
124
128
  for (const f of vo.children().filter((c) => c.type === TYPE_FIELD)) {
125
129
  const { type, refVo, enumAlias } = fieldTsType(f, voName);
126
130
  // Hoist the enum union alias above the interface, deduped across the whole
@@ -138,18 +142,21 @@ function emitInterface(
138
142
  const tsType = isRequired ? type : `${type} | null`;
139
143
  const optional = isRequired ? "" : "?";
140
144
  lines.push(` ${f.name}${optional}: ${tsType};`);
141
- if (refVo) refs.push(refVo);
145
+ // The nested ref resolves in the FIELD's declaring package (ADR-0042), not the
146
+ // resolved VO's — they differ when `f` is inherited from an abstract VO elsewhere.
147
+ if (refVo) refs.push({ ref: refVo, pkg: f.parent?.package ?? f.parent?.fileDefaultPackage ?? voPkg });
142
148
  }
143
149
  lines.push("}");
144
150
  const block = aliasLines.length > 0 ? `${aliasLines.join("\n")}\n${lines.join("\n")}` : lines.join("\n");
145
151
  out.push(block);
146
- for (const r of refs) emitInterface(root, r, emitted, out, enumAliases);
152
+ for (const r of refs) emitInterface(root, r.ref, r.pkg, emitted, out, enumAliases);
147
153
  }
148
154
 
149
- /** Emit the payload `interface` (+ nested element interfaces) for an object.value view-object. */
150
- export function generatePayloadInterfaces(root: MetaData, voName: string): string {
155
+ /** Emit the payload `interface` (+ nested element interfaces) for an object.value view-object.
156
+ * `referrerPkg` (ADR-0042) is the package a bare `voName` resolves in; pass an FQN and it is ignored. */
157
+ export function generatePayloadInterfaces(root: MetaData, voName: string, referrerPkg = ""): string {
151
158
  const out: string[] = [];
152
- emitInterface(root, voName, new Set<string>(), out, new Set<string>());
159
+ emitInterface(root, voName, referrerPkg, new Set<string>(), out, new Set<string>());
153
160
  return out.join("\n\n") + "\n";
154
161
  }
155
162
 
@@ -160,13 +167,17 @@ export function generatePayloadInterfaces(root: MetaData, voName: string): strin
160
167
  *
161
168
  * Returns the empty string when `voNames` is empty.
162
169
  */
163
- export function generatePayloadInterfacesBatch(root: MetaData, voNames: readonly string[]): string {
170
+ export function generatePayloadInterfacesBatch(
171
+ root: MetaData,
172
+ voNames: readonly string[],
173
+ referrerPkg = "",
174
+ ): string {
164
175
  if (voNames.length === 0) return "";
165
176
  const out: string[] = [];
166
177
  const emitted = new Set<string>();
167
178
  const enumAliases = new Set<string>();
168
179
  for (const name of voNames) {
169
- emitInterface(root, name, emitted, out, enumAliases);
180
+ emitInterface(root, name, referrerPkg, emitted, out, enumAliases);
170
181
  }
171
182
  return out.length === 0 ? "" : out.join("\n\n") + "\n";
172
183
  }
@@ -11,6 +11,8 @@
11
11
  import {
12
12
  type MetaData,
13
13
  MetaRoot,
14
+ MetaSource,
15
+ SOURCE_KIND_VIEW,
14
16
  resolveTableName,
15
17
  resolveTableSchema,
16
18
  } from "@metaobjectsdev/metadata";
@@ -55,6 +57,25 @@ export function buildProjectionViews(
55
57
 
56
58
  const out: ExpectedView[] = [];
57
59
  for (const projection of root.objects().filter(isProjection)) {
60
+ // Only PLAIN-VIEW projections produce managed CREATE VIEW DDL. The other
61
+ // read-only kinds must be skipped, not fed to extractViewSpec:
62
+ // - storedProc / tableFunction (FR-015) are CALLABLES, not views. They are
63
+ // base-less (no extends-bound identity), so extractViewSpec THROWS for
64
+ // them — and the CLI calls this function unconditionally, so one proc
65
+ // projection used to crash `meta migrate` outright.
66
+ // - materializedView cannot be managed by the migrate pipeline today:
67
+ // there is no CREATE MATERIALIZED VIEW emit, and PG introspection cannot
68
+ // even see matviews (information_schema.views excludes them), so a
69
+ // "managed" matview would re-propose create-view on every run and the
70
+ // apply would collide with the existing object. Worse, feeding it
71
+ // through here silently created a PLAIN view under the matview's name.
72
+ // Matviews are hand-managed, like the documented custom-SQL-view
73
+ // exception: migrate neither creates nor drops them.
74
+ // ADR-0039: own — mirrors isProjection/viewName's own-source classification.
75
+ const readOnlySource = projection.ownChildren().find(
76
+ (c): c is MetaSource => c instanceof MetaSource && c.isReadOnly(),
77
+ );
78
+ if (readOnlySource?.effectiveKind !== SOURCE_KIND_VIEW) continue;
58
79
  const spec = extractViewSpec(projection, root, { columnNamingStrategy });
59
80
  const baseTableName = joinTables[spec.joinTree.baseEntity];
60
81
  if (!baseTableName) continue; // unresolved base — skip (loader/codegen surface the error elsewhere)
@@ -21,30 +21,27 @@
21
21
 
22
22
  import {
23
23
  type MetaData,
24
- TYPE_OBJECT,
25
24
  TYPE_FIELD,
26
25
  FIELD_SUBTYPE_OBJECT,
27
26
  FIELD_SUBTYPE_ENUM,
28
27
  FIELD_ATTR_OBJECT_REF,
29
- PACKAGE_SEPARATOR,
30
- refMatchesObject,
28
+ resolveObjectRef,
31
29
  } from "@metaobjectsdev/metadata";
32
30
  import { fields, isArray, scalarKind, jsonStringLiteral } from "./fr010-field-mapping.js";
33
31
 
34
32
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
35
- function findObject(root: MetaData, name: string): MetaData | undefined {
36
- return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
33
+ // ADR-0042: resolveObjectRef gives package-local-before-root-level precedence for a bare ref, FQN-exact otherwise.
34
+ function findObject(root: MetaData, name: string, referrerPkg = ""): MetaData | undefined {
35
+ return resolveObjectRef(root, name, referrerPkg).node;
37
36
  }
38
37
 
39
38
  /** The @objectRef target VO for a nested-object field, or undefined when unresolvable. */
40
39
  function refVo(field: MetaData, root: MetaData): MetaData | undefined {
41
40
  const ref = field.attr(FIELD_ATTR_OBJECT_REF);
42
41
  if (typeof ref !== "string") return undefined;
43
- const direct = findObject(root, ref);
44
- if (direct !== undefined) return direct;
45
- const sep = ref.lastIndexOf(PACKAGE_SEPARATOR);
46
- if (sep >= 0) return findObject(root, ref.slice(sep + PACKAGE_SEPARATOR.length));
47
- return undefined;
42
+ // ADR-0042: resolve as authored — a bare ref binds the declaring VO's package,
43
+ // an FQN exactly; NO bare-tail fallback (never bind a same-named VO elsewhere).
44
+ return findObject(root, ref, field.parent?.package ?? field.parent?.fileDefaultPackage ?? "");
48
45
  }
49
46
 
50
47
  /** True iff the field is a nested object reference (field.object — distinct from the
@@ -23,7 +23,6 @@
23
23
  import {
24
24
  type MetaData,
25
25
  type MetaField,
26
- TYPE_OBJECT,
27
26
  TYPE_TEMPLATE,
28
27
  TEMPLATE_SUBTYPE_OUTPUT,
29
28
  FIELD_SUBTYPE_OBJECT,
@@ -32,8 +31,7 @@ import {
32
31
  FIELD_ATTR_REQUIRED,
33
32
  TEMPLATE_ATTR_PAYLOAD_REF,
34
33
  TEMPLATE_ATTR_FORMAT,
35
- PACKAGE_SEPARATOR,
36
- refMatchesObject,
34
+ resolveObjectRef,
37
35
  } from "@metaobjectsdev/metadata";
38
36
  import { fields, isArray } from "./fr010-field-mapping.js";
39
37
  import { mirrorName } from "./extract-delegate-emitter.js";
@@ -41,8 +39,9 @@ import { enumUnionAliasName } from "./inferred-types.js";
41
39
  import { enumValues } from "../enum-meta.js";
42
40
 
43
41
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
44
- function findObject(root: MetaData, name: string): MetaData | undefined {
45
- return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
42
+ // ADR-0042: resolveObjectRef gives package-local-before-root-level precedence for a bare ref, FQN-exact otherwise.
43
+ function findObject(root: MetaData, name: string, referrerPkg = ""): MetaData | undefined {
44
+ return resolveObjectRef(root, name, referrerPkg).node;
46
45
  }
47
46
 
48
47
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
@@ -54,11 +53,9 @@ function findTemplate(root: MetaData, name: string): MetaData | undefined {
54
53
  function refVo(field: MetaData, root: MetaData): MetaData | undefined {
55
54
  const ref = field.attr(FIELD_ATTR_OBJECT_REF);
56
55
  if (typeof ref !== "string") return undefined;
57
- const direct = findObject(root, ref);
58
- if (direct !== undefined) return direct;
59
- const sep = ref.lastIndexOf(PACKAGE_SEPARATOR);
60
- if (sep >= 0) return findObject(root, ref.slice(sep + PACKAGE_SEPARATOR.length));
61
- return undefined;
56
+ // ADR-0042: resolve as authored — a bare ref binds the declaring VO's package,
57
+ // an FQN exactly; NO bare-tail fallback (never bind a same-named VO elsewhere).
58
+ return findObject(root, ref, field.parent?.package ?? field.parent?.fileDefaultPackage ?? "");
62
59
  }
63
60
 
64
61
  function isObjectField(field: MetaData): boolean {
@@ -295,7 +292,8 @@ export function renderExtractor(root: MetaData, templateName: string): string {
295
292
  if (typeof payloadRef !== "string") {
296
293
  throw new Error(`template "${templateName}" missing @payloadRef`);
297
294
  }
298
- const vo = findObject(root, payloadRef);
295
+ // ADR-0042: a bare @payloadRef resolves in the template's package.
296
+ const vo = findObject(root, payloadRef, tmpl.package ?? tmpl.fileDefaultPackage ?? "");
299
297
  if (!vo) {
300
298
  throw new Error(`template "${templateName}" @payloadRef "${payloadRef}" not found in metadata root`);
301
299
  }
@@ -11,7 +11,6 @@
11
11
 
12
12
  import {
13
13
  type MetaData,
14
- TYPE_OBJECT,
15
14
  TYPE_FIELD,
16
15
  TYPE_TEMPLATE,
17
16
  TEMPLATE_SUBTYPE_OUTPUT,
@@ -19,7 +18,7 @@ import {
19
18
  FIELD_ATTR_OBJECT_REF,
20
19
  TEMPLATE_ATTR_PAYLOAD_REF,
21
20
  TEMPLATE_ATTR_FORMAT,
22
- refMatchesObject,
21
+ resolveObjectRef,
23
22
  } from "@metaobjectsdev/metadata";
24
23
  import {
25
24
  nestedMirrorInterfaces,
@@ -43,10 +42,9 @@ const SCALAR_ZOD: Record<string, string> = {
43
42
  };
44
43
 
45
44
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
46
- function findObject(root: MetaData, name: string): MetaData | undefined {
47
- // FR-032 @payloadRef is FQN after the desugar/sweep; match on the effective
48
- // FQN resolution key (with bare back-compat).
49
- return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
45
+ // ADR-0042: resolveObjectRef gives package-local-before-root-level precedence for a bare ref, FQN-exact otherwise.
46
+ function findObject(root: MetaData, name: string, referrerPkg = ""): MetaData | undefined {
47
+ return resolveObjectRef(root, name, referrerPkg).node;
50
48
  }
51
49
 
52
50
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
@@ -67,7 +65,8 @@ function fieldZod(field: MetaData, root: MetaData, seen: ReadonlySet<string>, de
67
65
  // Cycle guard — emit unknown for self-references (rare; lazy schemas not in scope for v1).
68
66
  base = "z.unknown()";
69
67
  } else {
70
- const inner = findObject(root, refName);
68
+ // ADR-0042: a bare nested @objectRef resolves in the declaring VO's package.
69
+ const inner = findObject(root, refName, field.parent?.package ?? field.parent?.fileDefaultPackage ?? "");
71
70
  base = inner ? renderObjectSchema(inner, root, new Set(seen).add(refName), depth + 1) : "z.unknown()";
72
71
  }
73
72
  } else {
@@ -107,7 +106,8 @@ export function renderOutputParser(root: MetaData, templateName: string): string
107
106
  if (typeof payloadRef !== "string") {
108
107
  throw new Error(`template "${templateName}" missing @payloadRef`);
109
108
  }
110
- const vo = findObject(root, payloadRef);
109
+ // ADR-0042: a bare @payloadRef resolves in the template's package.
110
+ const vo = findObject(root, payloadRef, tmpl.package ?? tmpl.fileDefaultPackage ?? "");
111
111
  if (!vo) {
112
112
  throw new Error(`template "${templateName}" @payloadRef "${payloadRef}" not found in metadata root`);
113
113
  }
@@ -12,18 +12,18 @@
12
12
 
13
13
  import {
14
14
  type MetaData,
15
- TYPE_OBJECT,
16
15
  TYPE_TEMPLATE,
17
16
  TEMPLATE_SUBTYPE_OUTPUT,
18
17
  TEMPLATE_ATTR_PAYLOAD_REF,
19
18
  TEMPLATE_ATTR_FORMAT,
20
- refMatchesObject,
19
+ resolveObjectRef,
21
20
  } from "@metaobjectsdev/metadata";
22
21
  import { specLiteral } from "./output-format-spec-emitter.js";
23
22
 
24
23
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
25
- function findObject(root: MetaData, name: string): MetaData | undefined {
26
- return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
24
+ // ADR-0042: resolveObjectRef gives package-local-before-root-level precedence for a bare ref, FQN-exact otherwise.
25
+ function findObject(root: MetaData, name: string, referrerPkg = ""): MetaData | undefined {
26
+ return resolveObjectRef(root, name, referrerPkg).node;
27
27
  }
28
28
 
29
29
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
@@ -59,7 +59,8 @@ export function renderOutputPrompt(root: MetaData, templateName: string): string
59
59
  if (typeof payloadRef !== "string") {
60
60
  throw new Error(`template "${templateName}" missing @payloadRef`);
61
61
  }
62
- const vo = findObject(root, payloadRef);
62
+ // ADR-0042: a bare @payloadRef resolves in the template's package.
63
+ const vo = findObject(root, payloadRef, tmpl.package ?? tmpl.fileDefaultPackage ?? "");
63
64
  if (!vo) {
64
65
  throw new Error(`template "${templateName}" @payloadRef "${payloadRef}" not found in metadata root`);
65
66
  }
@@ -27,7 +27,6 @@
27
27
 
28
28
  import {
29
29
  type MetaData,
30
- TYPE_OBJECT,
31
30
  TYPE_FIELD,
32
31
  TYPE_TEMPLATE,
33
32
  FIELD_SUBTYPE_OBJECT,
@@ -43,7 +42,7 @@ import {
43
42
  TEMPLATE_ATTR_SUBJECT_REF,
44
43
  TEMPLATE_ATTR_HTML_BODY_REF,
45
44
  TEMPLATE_ATTR_TEXT_BODY_REF,
46
- refMatchesObject,
45
+ resolveObjectRef,
47
46
  } from "@metaobjectsdev/metadata";
48
47
  import {
49
48
  verify,
@@ -54,8 +53,9 @@ import {
54
53
  } from "@metaobjectsdev/render";
55
54
 
56
55
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
57
- function findObject(root: MetaData, name: string): MetaData | undefined {
58
- return root.children().find((c) => c.type === TYPE_OBJECT && refMatchesObject(c, name));
56
+ // ADR-0042: package-local resolveObjectRef binds a bare @objectRef in `referrerPkg` first (else root-level), an FQN exactly.
57
+ function findObject(root: MetaData, name: string, referrerPkg: string): MetaData | undefined {
58
+ return resolveObjectRef(root, name, referrerPkg).node;
59
59
  }
60
60
 
61
61
  // ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
@@ -74,18 +74,24 @@ function findTemplate(root: MetaData, name: string): MetaData | undefined {
74
74
  function derivePayloadFieldTree(
75
75
  root: MetaData,
76
76
  voName: string,
77
+ referrerPkg: string,
77
78
  seen: ReadonlySet<string> = new Set(),
78
79
  ): PayloadField[] {
79
80
  if (seen.has(voName)) return [];
80
- const vo = findObject(root, voName);
81
+ const vo = findObject(root, voName, referrerPkg);
81
82
  if (!vo) return [];
83
+ // ADR-0042: a nested @objectRef resolves in the FIELD's own declaring package
84
+ // (fallback below), which differs from this VO's when the field is inherited via
85
+ // extends from an abstract VO elsewhere.
86
+ const voPkg = vo.package ?? vo.fileDefaultPackage ?? "";
82
87
  const nextSeen = new Set(seen).add(voName);
83
88
  const fields: PayloadField[] = [];
84
89
  for (const f of vo.children().filter((c) => c.type === TYPE_FIELD)) {
85
90
  if (f.subType === FIELD_SUBTYPE_OBJECT) {
86
91
  const ref = f.attr(FIELD_ATTR_OBJECT_REF);
87
92
  if (typeof ref === "string") {
88
- fields.push({ name: f.name, fields: derivePayloadFieldTree(root, ref, nextSeen) });
93
+ const fieldPkg = f.parent?.package ?? f.parent?.fileDefaultPackage ?? voPkg;
94
+ fields.push({ name: f.name, fields: derivePayloadFieldTree(root, ref, fieldPkg, nextSeen) });
89
95
  continue;
90
96
  }
91
97
  }
@@ -157,14 +163,16 @@ export function renderRenderHelper(
157
163
  if (typeof payloadRef !== "string") {
158
164
  throw new Error(`template "${templateName}" missing @payloadRef`);
159
165
  }
160
- const vo = findObject(root, payloadRef);
166
+ // ADR-0042: a bare @payloadRef resolves in the template's package.
167
+ const tmplPkg = tmpl.package ?? tmpl.fileDefaultPackage ?? "";
168
+ const vo = findObject(root, payloadRef, tmplPkg);
161
169
  if (!vo) {
162
170
  throw new Error(
163
171
  `template "${templateName}" @payloadRef "${payloadRef}" not found in metadata root`,
164
172
  );
165
173
  }
166
174
 
167
- const fields = derivePayloadFieldTree(root, payloadRef);
175
+ const fields = derivePayloadFieldTree(root, payloadRef, tmplPkg);
168
176
  const ft = fieldTreeLiteral(fields);
169
177
  const fnName = `render${templateName}`;
170
178