@metaobjectsdev/metadata 1.0.13 → 1.1.1-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 (233) hide show
  1. package/README.md +2 -0
  2. package/dist/attr-schema-validate.d.ts.map +1 -1
  3. package/dist/attr-schema-validate.js +9 -12
  4. package/dist/attr-schema-validate.js.map +1 -1
  5. package/dist/constants.d.ts +1 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +1 -0
  8. package/dist/constants.js.map +1 -1
  9. package/dist/core/attr/meta-attr-filter.d.ts.map +1 -1
  10. package/dist/core/attr/meta-attr-filter.js +8 -2
  11. package/dist/core/attr/meta-attr-filter.js.map +1 -1
  12. package/dist/core/attr/meta-attr.d.ts +14 -0
  13. package/dist/core/attr/meta-attr.d.ts.map +1 -1
  14. package/dist/core/attr/meta-attr.js +23 -1
  15. package/dist/core/attr/meta-attr.js.map +1 -1
  16. package/dist/core/field/shared-enum.d.ts +21 -0
  17. package/dist/core/field/shared-enum.d.ts.map +1 -0
  18. package/dist/core/field/shared-enum.js +49 -0
  19. package/dist/core/field/shared-enum.js.map +1 -0
  20. package/dist/core/identity/meta-identity.d.ts +1 -1
  21. package/dist/core/identity/meta-identity.d.ts.map +1 -1
  22. package/dist/core/identity/meta-identity.js +8 -13
  23. package/dist/core/identity/meta-identity.js.map +1 -1
  24. package/dist/core/index.d.ts +2 -0
  25. package/dist/core/index.d.ts.map +1 -1
  26. package/dist/core/index.js +3 -0
  27. package/dist/core/index.js.map +1 -1
  28. package/dist/core/object/object-constants.d.ts +14 -1
  29. package/dist/core/object/object-constants.d.ts.map +1 -1
  30. package/dist/core/object/object-constants.js +18 -0
  31. package/dist/core/object/object-constants.js.map +1 -1
  32. package/dist/core/object/object-definition.embedded.d.ts.map +1 -1
  33. package/dist/core/object/object-definition.embedded.js +80 -0
  34. package/dist/core/object/object-definition.embedded.js.map +1 -1
  35. package/dist/core/relationship/derive-m2m-fields.d.ts +1 -1
  36. package/dist/core/relationship/derive-m2m-fields.d.ts.map +1 -1
  37. package/dist/core/relationship/derive-m2m-fields.js +15 -44
  38. package/dist/core/relationship/derive-m2m-fields.js.map +1 -1
  39. package/dist/core/relationship/find-reference.d.ts.map +1 -1
  40. package/dist/core/relationship/find-reference.js +4 -2
  41. package/dist/core/relationship/find-reference.js.map +1 -1
  42. package/dist/core/relationship/resolve-relationship-reference.d.ts +6 -5
  43. package/dist/core/relationship/resolve-relationship-reference.d.ts.map +1 -1
  44. package/dist/core/relationship/resolve-relationship-reference.js +9 -7
  45. package/dist/core/relationship/resolve-relationship-reference.js.map +1 -1
  46. package/dist/core/reporting/meta-dimension.d.ts +13 -0
  47. package/dist/core/reporting/meta-dimension.d.ts.map +1 -0
  48. package/dist/core/reporting/meta-dimension.js +30 -0
  49. package/dist/core/reporting/meta-dimension.js.map +1 -0
  50. package/dist/core/reporting/meta-measure.d.ts +24 -0
  51. package/dist/core/reporting/meta-measure.d.ts.map +1 -0
  52. package/dist/core/reporting/meta-measure.js +54 -0
  53. package/dist/core/reporting/meta-measure.js.map +1 -0
  54. package/dist/core/reporting/meta-segment.d.ts +6 -0
  55. package/dist/core/reporting/meta-segment.d.ts.map +1 -0
  56. package/dist/core/reporting/meta-segment.js +13 -0
  57. package/dist/core/reporting/meta-segment.js.map +1 -0
  58. package/dist/core/reporting/report-accessors.d.ts +27 -0
  59. package/dist/core/reporting/report-accessors.d.ts.map +1 -0
  60. package/dist/core/reporting/report-accessors.js +56 -0
  61. package/dist/core/reporting/report-accessors.js.map +1 -0
  62. package/dist/core/reporting/report-describe.d.ts +59 -0
  63. package/dist/core/reporting/report-describe.d.ts.map +1 -0
  64. package/dist/core/reporting/report-describe.js +182 -0
  65. package/dist/core/reporting/report-describe.js.map +1 -0
  66. package/dist/core/reporting/report-read-model.d.ts +34 -0
  67. package/dist/core/reporting/report-read-model.d.ts.map +1 -0
  68. package/dist/core/reporting/report-read-model.js +165 -0
  69. package/dist/core/reporting/report-read-model.js.map +1 -0
  70. package/dist/core/reporting/report-shape.d.ts +103 -0
  71. package/dist/core/reporting/report-shape.d.ts.map +1 -0
  72. package/dist/core/reporting/report-shape.js +398 -0
  73. package/dist/core/reporting/report-shape.js.map +1 -0
  74. package/dist/core/reporting/reporting-constants.d.ts +65 -0
  75. package/dist/core/reporting/reporting-constants.d.ts.map +1 -0
  76. package/dist/core/reporting/reporting-constants.js +84 -0
  77. package/dist/core/reporting/reporting-constants.js.map +1 -0
  78. package/dist/core/reporting/reporting-definition.embedded.d.ts +3 -0
  79. package/dist/core/reporting/reporting-definition.embedded.d.ts.map +1 -0
  80. package/dist/core/reporting/reporting-definition.embedded.js +185 -0
  81. package/dist/core/reporting/reporting-definition.embedded.js.map +1 -0
  82. package/dist/core/requirement/resolve-claim.d.ts +8 -0
  83. package/dist/core/requirement/resolve-claim.d.ts.map +1 -1
  84. package/dist/core/requirement/resolve-claim.js +21 -0
  85. package/dist/core/requirement/resolve-claim.js.map +1 -1
  86. package/dist/core/yaml-desugar.d.ts.map +1 -1
  87. package/dist/core/yaml-desugar.js +12 -82
  88. package/dist/core/yaml-desugar.js.map +1 -1
  89. package/dist/core-types.d.ts.map +1 -1
  90. package/dist/core-types.js +28 -1
  91. package/dist/core-types.js.map +1 -1
  92. package/dist/docs-ref.d.ts +19 -0
  93. package/dist/docs-ref.d.ts.map +1 -0
  94. package/dist/docs-ref.js +37 -0
  95. package/dist/docs-ref.js.map +1 -0
  96. package/dist/errors.d.ts +1 -1
  97. package/dist/errors.d.ts.map +1 -1
  98. package/dist/errors.js +17 -6
  99. package/dist/errors.js.map +1 -1
  100. package/dist/fmt.d.ts.map +1 -1
  101. package/dist/fmt.js +3 -1
  102. package/dist/fmt.js.map +1 -1
  103. package/dist/index.d.ts +15 -4
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +18 -4
  106. package/dist/index.js.map +1 -1
  107. package/dist/loader/declared-duplicate-fields.d.ts +19 -0
  108. package/dist/loader/declared-duplicate-fields.d.ts.map +1 -0
  109. package/dist/loader/declared-duplicate-fields.js +97 -0
  110. package/dist/loader/declared-duplicate-fields.js.map +1 -0
  111. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  112. package/dist/loader/meta-data-loader.js +18 -1
  113. package/dist/loader/meta-data-loader.js.map +1 -1
  114. package/dist/loader/reporting-validation.d.ts +11 -0
  115. package/dist/loader/reporting-validation.d.ts.map +1 -0
  116. package/dist/loader/reporting-validation.js +883 -0
  117. package/dist/loader/reporting-validation.js.map +1 -0
  118. package/dist/loader/resolve-references.d.ts +8 -0
  119. package/dist/loader/resolve-references.d.ts.map +1 -0
  120. package/dist/loader/resolve-references.js +132 -0
  121. package/dist/loader/resolve-references.js.map +1 -0
  122. package/dist/loader/validation-passes.d.ts.map +1 -1
  123. package/dist/loader/validation-passes.js +14 -9
  124. package/dist/loader/validation-passes.js.map +1 -1
  125. package/dist/loader/validation-registry.d.ts.map +1 -1
  126. package/dist/loader/validation-registry.js +33 -10
  127. package/dist/loader/validation-registry.js.map +1 -1
  128. package/dist/metamodel-docs/index.js +3 -3
  129. package/dist/metamodel-docs/index.js.map +1 -1
  130. package/dist/metamodel-docs/render.js +4 -4
  131. package/dist/metamodel-docs/render.js.map +1 -1
  132. package/dist/naming-refs.d.ts +102 -50
  133. package/dist/naming-refs.d.ts.map +1 -1
  134. package/dist/naming-refs.js +223 -97
  135. package/dist/naming-refs.js.map +1 -1
  136. package/dist/naming.d.ts +42 -7
  137. package/dist/naming.d.ts.map +1 -1
  138. package/dist/naming.js +43 -5
  139. package/dist/naming.js.map +1 -1
  140. package/dist/object-serializer.d.ts.map +1 -1
  141. package/dist/object-serializer.js +6 -9
  142. package/dist/object-serializer.js.map +1 -1
  143. package/dist/parser-core.d.ts.map +1 -1
  144. package/dist/parser-core.js +18 -48
  145. package/dist/parser-core.js.map +1 -1
  146. package/dist/persistence/db/db-definition.embedded.js +2 -2
  147. package/dist/persistence/db/db-definition.embedded.js.map +1 -1
  148. package/dist/persistence/source/meta-source.d.ts +5 -3
  149. package/dist/persistence/source/meta-source.d.ts.map +1 -1
  150. package/dist/persistence/source/meta-source.js +8 -6
  151. package/dist/persistence/source/meta-source.js.map +1 -1
  152. package/dist/registry-manifest-exclusions.js +2 -2
  153. package/dist/registry-manifest-exclusions.js.map +1 -1
  154. package/dist/registry-manifest.d.ts +1 -1
  155. package/dist/registry-manifest.js +1 -1
  156. package/dist/release-docs.d.ts +11 -0
  157. package/dist/release-docs.d.ts.map +1 -0
  158. package/dist/release-docs.js +48 -0
  159. package/dist/release-docs.js.map +1 -0
  160. package/dist/serializer-json.d.ts +15 -1
  161. package/dist/serializer-json.d.ts.map +1 -1
  162. package/dist/serializer-json.js +55 -17
  163. package/dist/serializer-json.js.map +1 -1
  164. package/dist/shared/base-types.d.ts +7 -1
  165. package/dist/shared/base-types.d.ts.map +1 -1
  166. package/dist/shared/base-types.js +9 -0
  167. package/dist/shared/base-types.js.map +1 -1
  168. package/dist/shared/meta-data.d.ts +10 -0
  169. package/dist/shared/meta-data.d.ts.map +1 -1
  170. package/dist/shared/meta-data.js +22 -1
  171. package/dist/shared/meta-data.js.map +1 -1
  172. package/dist/shared/meta-root.d.ts +13 -1
  173. package/dist/shared/meta-root.d.ts.map +1 -1
  174. package/dist/shared/meta-root.js +19 -1
  175. package/dist/shared/meta-root.js.map +1 -1
  176. package/dist/super-resolve.d.ts +4 -1
  177. package/dist/super-resolve.d.ts.map +1 -1
  178. package/dist/super-resolve.js +44 -65
  179. package/dist/super-resolve.js.map +1 -1
  180. package/dist/vocabulary-catalog.js +2 -2
  181. package/dist/vocabulary-catalog.js.map +1 -1
  182. package/package.json +6 -1
  183. package/src/attr-schema-validate.ts +9 -13
  184. package/src/constants.ts +1 -0
  185. package/src/core/attr/meta-attr-filter.ts +8 -1
  186. package/src/core/attr/meta-attr.ts +24 -1
  187. package/src/core/field/shared-enum.ts +53 -0
  188. package/src/core/identity/meta-identity.ts +9 -12
  189. package/src/core/index.ts +5 -0
  190. package/src/core/object/object-constants.ts +19 -0
  191. package/src/core/object/object-definition.embedded.ts +80 -0
  192. package/src/core/relationship/derive-m2m-fields.ts +15 -48
  193. package/src/core/relationship/find-reference.ts +4 -2
  194. package/src/core/relationship/resolve-relationship-reference.ts +11 -9
  195. package/src/core/reporting/meta-dimension.ts +41 -0
  196. package/src/core/reporting/meta-measure.ts +75 -0
  197. package/src/core/reporting/meta-segment.ts +14 -0
  198. package/src/core/reporting/report-accessors.ts +73 -0
  199. package/src/core/reporting/report-describe.ts +206 -0
  200. package/src/core/reporting/report-read-model.ts +177 -0
  201. package/src/core/reporting/report-shape.ts +493 -0
  202. package/src/core/reporting/reporting-constants.ts +104 -0
  203. package/src/core/reporting/reporting-definition.embedded.ts +192 -0
  204. package/src/core/requirement/resolve-claim.ts +20 -0
  205. package/src/core/yaml-desugar.ts +9 -89
  206. package/src/core-types.ts +41 -0
  207. package/src/docs-ref.ts +41 -0
  208. package/src/errors.ts +17 -6
  209. package/src/fmt.ts +3 -1
  210. package/src/index.ts +72 -4
  211. package/src/loader/declared-duplicate-fields.ts +114 -0
  212. package/src/loader/meta-data-loader.ts +25 -1
  213. package/src/loader/reporting-validation.ts +1137 -0
  214. package/src/loader/resolve-references.ts +144 -0
  215. package/src/loader/validation-passes.ts +13 -9
  216. package/src/loader/validation-registry.ts +37 -9
  217. package/src/metamodel-docs/index.ts +3 -3
  218. package/src/metamodel-docs/render.ts +4 -4
  219. package/src/naming-refs.ts +237 -94
  220. package/src/naming.ts +75 -11
  221. package/src/object-serializer.ts +7 -9
  222. package/src/parser-core.ts +19 -56
  223. package/src/persistence/db/db-definition.embedded.ts +2 -2
  224. package/src/persistence/source/meta-source.ts +8 -6
  225. package/src/registry-manifest-exclusions.ts +2 -2
  226. package/src/registry-manifest.ts +1 -1
  227. package/src/release-docs.ts +48 -0
  228. package/src/serializer-json.ts +79 -12
  229. package/src/shared/base-types.ts +9 -0
  230. package/src/shared/meta-data.ts +22 -1
  231. package/src/shared/meta-root.ts +20 -1
  232. package/src/super-resolve.ts +49 -63
  233. package/src/vocabulary-catalog.ts +2 -2
@@ -1,30 +1,25 @@
1
- // FR-032 (ADR-0032) — canonical reference expansion.
1
+ // References: the one resolver every reference form goes through (ADR-0059).
2
2
  //
3
- // `expandRef(raw, packageContext)` is the SINGLE ref-expansion primitive. It
4
- // lowers an authored metadata reference to its fully-qualified canonical form
5
- // per ADR-0032 §2.1 — deterministically, with NO root fallback:
3
+ // Sources do not interpret references: YAML, canonical JSON and any future source
4
+ // build the tree with every reference string exactly as authored. This module
5
+ // gives each form its meaning, once, against the built tree:
6
6
  //
7
- // bare `Name` (no `::`, no leading `.`) → `<P>::Name` (current package only;
8
- // stays bare when P is empty/root).
9
- // qualified `pkg::Name` (contains `::`, → unchanged (absolute from root).
10
- // NOT leading `::`)
11
- // `::Rest` (leading `::`) → strip the leading `::`; the
12
- // remainder is absolute from root
13
- // (so `::a::b::C` ≡ `a::b::C`,
14
- // `::Apple` = root-level Apple).
15
- // `..::Rest` (one or more leading `..::`) → drop one package segment from P
16
- // per `..::`, then resolve Rest
17
- // (itself bare/qualified) against the
18
- // reduced package. Over-drop (more
19
- // `..::` than P has segments) throws.
7
+ // `::Rest` root-absolute — the node whose resolution key is exactly `Rest`
8
+ // (`::Apple` is the root-level Apple; `::a::b::C` is `a::b::C`).
9
+ // `..::Rest` parent-relative — drop one segment of the referrer's package per
10
+ // `..::`, then the exact key `<remaining>::Rest`. More `..::` than
11
+ // the package has segments is ERR_BAD_ATTR_VALUE.
12
+ // `pkg::Name` exact.
13
+ // `Name` the referrer's own package (`<referrerPkg>::Name`), else the
14
+ // root-level `Name` (ADR-0042 §2). Never another package.
20
15
  //
21
- // The trailing FR-024 DOTTED CHILD suffix (`.child` / `.child.grandchild`) is
22
- // PRESERVED verbatim: only the OWNER part (everything before the first `.` in
23
- // the final `::`-segment) is expanded; the `.child...` tail is reattached.
16
+ // No form falls back to another: `::Apple` never binds a local `acme::Apple`.
17
+ // A FR-024 dotted child tail (`Owner.child.grand`) starts at the first `.` after the
18
+ // last `::`; only the owner part is resolved by form, and the tail is kept as written.
24
19
  //
25
- // The desugar runs this on every ref-bearing attr so canonical JSON is FQN-only
26
- // (ADR-0032 §2.2/§2.3); the resolution layer then does pure FQN matching. The
27
- // `package` attribute is NEVER expanded — it is the node's identity.
20
+ // `spellRef` is the inverse: the one canonical spelling of a resolved target that
21
+ // resolves to the same node with or without a referrer. The `resolveReferences`
22
+ // loader pass rewrites every reference to it, so consumers read canonical values.
28
23
 
29
24
  import type { MetaData } from "./shared/meta-data.js";
30
25
  import { PACKAGE_SEPARATOR, PACKAGE_PARENT, CHILD_REF_SEPARATOR } from "./shared/structural.js";
@@ -39,22 +34,20 @@ import {
39
34
  ORIGIN_AGGREGATE_ATTR_VIA,
40
35
  } from "./persistence/origin/origin-constants.js";
41
36
  import { SOURCE_ATTR_PARAMETER_REF } from "./persistence/source/source-constants.js";
37
+ import { OBJECT_REPORT_ATTR_FROM, OBJECT_REPORT_ATTR_SPINE } from "./core/object/object-constants.js";
42
38
  import { TEMPLATE_ATTR_PAYLOAD_REF, TEMPLATE_ATTR_RESPONSE_REF } from "./template/template-constants.js";
43
39
 
44
40
  const PARENT_PREFIX = PACKAGE_PARENT + PACKAGE_SEPARATOR; // "..::"
45
41
 
46
42
  /**
47
- * The inline (`@`-prefixed) attribute names whose VALUE is a metadata reference
48
- * subject to FR-032 expansion/guarding (ADR-0032 §3). The structural `extends`
49
- * key is handled separately (it is not `@`-prefixed). `@objectRef`/`@references`
50
- * are pure object refs; `@from`/`@of`/`@via` carry an entity HEAD (possibly with
51
- * a dotted relationship/field tail — expandRef preserves the tail);
52
- * `@parameterRef`/`@payloadRef`/`@responseRef` reference value-objects. These are
53
- * expanded by the YAML desugar and rejected (when still relative) by the
54
- * canonical-JSON guard. `@through` (the M:N junction ref) is in the set per
55
- * ADR-0042 §4 — it desugars to FQN and resolves package-local like every other
56
- * object ref. `@sourceRefField` (a FK FIELD name, not an object ref) is NOT in
57
- * the set.
43
+ * The inline (`@`-prefixed) attribute names whose VALUE is a metadata object
44
+ * reference (ADR-0059 R4). The structural `extends` key is handled separately (it is
45
+ * not `@`-prefixed). `@objectRef`/`@references`/`@through` are pure object refs;
46
+ * `@from`/`@of`/`@via`/`@spine` carry an entity HEAD with a dotted
47
+ * relationship/field tail; `@parameterRef`/`@payloadRef`/`@responseRef` name
48
+ * value-objects. The same set is used in every port. `@sourceRefField` (a FK FIELD
49
+ * name) and `@implementedBy` (which may name a template as well as an object) are
50
+ * NOT in the set.
58
51
  */
59
52
  export const REF_BEARING_ATTR_NAMES: ReadonlySet<string> = new Set<string>([
60
53
  RELATIONSHIP_ATTR_OBJECT_REF, // = FIELD_ATTR_OBJECT_REF (same spelling "objectRef")
@@ -68,13 +61,11 @@ export const REF_BEARING_ATTR_NAMES: ReadonlySet<string> = new Set<string>([
68
61
  SOURCE_ATTR_PARAMETER_REF,
69
62
  TEMPLATE_ATTR_PAYLOAD_REF,
70
63
  TEMPLATE_ATTR_RESPONSE_REF,
64
+ OBJECT_REPORT_ATTR_FROM, // = ORIGIN_PASSTHROUGH_ATTR_FROM ("from")
65
+ OBJECT_REPORT_ATTR_SPINE,
71
66
  ]);
72
67
 
73
- /**
74
- * True when `raw` is a relative reference form (`::Rest` or `..::Rest`) that the
75
- * YAML desugar must expand before canonical JSON. Canonical JSON must be FQN; a
76
- * relative ref surviving into it is `ERR_RELATIVE_REF_IN_CANONICAL`.
77
- */
68
+ /** True when `raw` is a relative reference form (`::Rest` or `..::Rest`). */
78
69
  export function isRelativeRef(raw: string): boolean {
79
70
  return raw.startsWith(PACKAGE_SEPARATOR) || raw.startsWith(PARENT_PREFIX);
80
71
  }
@@ -86,7 +77,7 @@ export function isRelativeRef(raw: string): boolean {
86
77
  * at the first `.` AFTER the last `::`. Returns `{ owner, tail }` where `tail`
87
78
  * includes the leading `.` (or is "" when there is no child suffix).
88
79
  */
89
- function splitChildTail(raw: string): { owner: string; tail: string } {
80
+ export function splitChildTail(raw: string): { owner: string; tail: string } {
90
81
  const lastSep = raw.lastIndexOf(PACKAGE_SEPARATOR);
91
82
  const segStart = lastSep === -1 ? 0 : lastSep + PACKAGE_SEPARATOR.length;
92
83
  const dotInSeg = raw.indexOf(CHILD_REF_SEPARATOR, segStart);
@@ -95,8 +86,11 @@ function splitChildTail(raw: string): { owner: string; tail: string } {
95
86
  }
96
87
 
97
88
  /**
98
- * Expand an authored reference's OWNER part (no child tail) to its FQN per
99
- * ADR-0032 §2.1. Throws on parent-relative over-drop.
89
+ * Expand an authored reference's OWNER part (no child tail) against a package
90
+ * context: `::Rest` → `Rest`, `..::Rest` → the reduced package `::Rest`,
91
+ * `pkg::Name` unchanged, bare `Name` → `<P>::Name`. Throws on parent-relative
92
+ * over-drop. The resolver uses it for the two relative forms only; a bare name is
93
+ * resolved own-package-then-root (see `ownerCandidateKeys`).
100
94
  */
101
95
  function expandOwner(owner: string, packageContext: string): string {
102
96
  // root-absolute: leading "::" → strip; the remainder is absolute from root.
@@ -136,73 +130,176 @@ function expandOwner(owner: string, packageContext: string): string {
136
130
  }
137
131
 
138
132
  /**
139
- * Expand an authored reference to its fully-qualified canonical form per
140
- * ADR-0032 §2.1, preserving any FR-024 dotted child suffix. The package context
141
- * `P` is the declaring node's effective package (its own `package` or the
142
- * file-default). Throws on parent-relative over-drop — the caller emits an error.
133
+ * Expand an authored reference against a package context, preserving any FR-024
134
+ * dotted child suffix. Throws on parent-relative over-drop — the caller emits an
135
+ * error.
143
136
  */
144
137
  export function expandRef(raw: string, packageContext: string): string {
145
138
  const { owner, tail } = splitChildTail(raw);
146
139
  return expandOwner(owner, packageContext) + tail;
147
140
  }
148
141
 
142
+ /** A parent-relative reference with more `..::` than its referrer's package has segments. */
143
+ export class RefOverDropError extends Error {
144
+ readonly code = "ERR_BAD_ATTR_VALUE" as const;
145
+ }
146
+
149
147
  /**
150
- * ADR-0042 — does root-level object `node` satisfy object reference `ref`,
151
- * declared by a node whose effective package is `referrerPkg`? A single
152
- * package-local matcher:
153
- * - **FQN** `ref` (contains `::`) → EXACT match on `resolutionKey()`. No
154
- * bare-tail fallback, so an FQN pointing at one package never binds a
155
- * same-named object in another.
156
- * - **bare** `ref` (no `::`) → the referrer's OWN package
157
- * (`<referrerPkg>::<ref>`), else a **root-level** (empty-package) object
158
- * whose resolution key IS `ref`. No cross-package bare resolution, no
159
- * globally-unique scan.
160
- *
161
- * Objects keep a BARE `fqn()` per the FR5d cross-port contract, so the canonical
162
- * FQN accessor is `resolutionKey()` (`<package | fileDefaultPackage>::<name>`) —
163
- * this mirrors `super-resolve`'s `findInTree`. This is the single matcher the
164
- * non-super resolvers (origin `@from`/`@of`/`@via` heads, template
165
- * `@payloadRef`/`@responseRef`, source `@parameterRef`, relationship `@through`)
166
- * share, so resolution behaves identically everywhere a ref resolves.
167
- *
168
- * `referrerPkg` defaults to `""` — the fail-closed FQN-exact-plus-root-level
169
- * behavior. The LOADER always passes the real referrer package (bare refs there
170
- * must resolve package-locally); downstream CODEGEN resolvers, which run on
171
- * already-validated metadata whose refs are FQN, may omit it. An empty
172
- * `referrerPkg` never binds a bare ref across a package boundary (fail-closed),
173
- * so omission can only fail to resolve — never mis-resolve.
148
+ * The resolution keys an OWNER reference (no child tail) may bind, in the order to
149
+ * try them (ADR-0059 R2). Every form but bare is one exact key; bare is the
150
+ * referrer's own package, then root-level. Throws RefOverDropError on over-drop.
174
151
  */
175
- export function refMatchesObject(node: MetaData, ref: string, referrerPkg = ""): boolean {
176
- const key = node.resolutionKey();
177
- if (ref.includes(PACKAGE_SEPARATOR)) return key === ref;
178
- if (referrerPkg !== "" && key === `${referrerPkg}${PACKAGE_SEPARATOR}${ref}`) return true;
179
- return key === ref; // root-level (empty-package) object whose key is the bare name
152
+ export function ownerCandidateKeys(owner: string, referrerPkg: string): readonly string[] {
153
+ if (owner.startsWith(PACKAGE_SEPARATOR)) return [owner.slice(PACKAGE_SEPARATOR.length)];
154
+ if (owner.startsWith(PARENT_PREFIX)) {
155
+ try {
156
+ return [expandOwner(owner, referrerPkg)];
157
+ } catch (err) {
158
+ throw new RefOverDropError((err as Error).message);
159
+ }
160
+ }
161
+ if (owner.includes(PACKAGE_SEPARATOR)) return [owner];
162
+ return referrerPkg !== "" ? [`${referrerPkg}${PACKAGE_SEPARATOR}${owner}`, owner] : [owner];
180
163
  }
181
164
 
182
165
  /**
183
- * Resolve a metadata OBJECT reference under the ADR-0042 package-local contract
184
- * (see `refMatchesObject` for the matcher). `referrerPkg` is the effective
185
- * package of the node carrying the ref. Returns `{ node }` on resolution, or
186
- * `{}` (node undefined) when nothing matches — there is NO ambiguous outcome
187
- * (bare = package-local, so ambiguity is unreachable). The SINGLE resolver every
188
- * object-ref site shares so the contract is uniform.
166
+ * Does root-level object `node` satisfy object reference `ref` (no child tail),
167
+ * declared by a node whose effective package is `referrerPkg`? True when the node's
168
+ * resolution key is the FIRST candidate key that any object could bind — so it
169
+ * agrees with `resolveObjectRef`. Never matches on a short name.
170
+ */
171
+ export function refMatchesObject(node: MetaData, ref: string, referrerPkg: string): boolean {
172
+ const resolved = resolveObjectRef(node.parent ?? node, ref, referrerPkg).node;
173
+ if (resolved !== undefined) return resolved === node;
174
+ return false;
175
+ }
176
+
177
+ /** The root's direct children of `type` (ADR-0039: root has no super). */
178
+ function topLevelOfType(root: MetaData, type: string | undefined): readonly MetaData[] {
179
+ const all = root.children();
180
+ return type === undefined ? all : all.filter((c) => c.type === type);
181
+ }
182
+
183
+ /** Bind an owner reference among `candidates` by exact resolution key (R2). */
184
+ function bindOwner(
185
+ candidates: readonly MetaData[],
186
+ owner: string,
187
+ referrerPkg: string,
188
+ ): { node?: MetaData | undefined; error?: string | undefined } {
189
+ let keys: readonly string[];
190
+ try {
191
+ keys = ownerCandidateKeys(owner, referrerPkg);
192
+ } catch (err) {
193
+ return { error: (err as Error).message };
194
+ }
195
+ for (const key of keys) {
196
+ const node = candidates.find((c) => c.resolutionKey() === key);
197
+ if (node !== undefined) return { node };
198
+ }
199
+ return {};
200
+ }
201
+
202
+ /**
203
+ * Resolve a metadata OBJECT reference (no child tail) under ADR-0059 R2.
204
+ * `referrerPkg` is the referrer's package (R3). Returns `{ node }` on resolution,
205
+ * `{ error }` for a parent-relative over-drop, or `{}` when nothing matches. The
206
+ * single object resolver every object-ref site shares.
189
207
  */
190
208
  export function resolveObjectRef(
191
209
  root: MetaData,
192
210
  ref: string,
193
211
  referrerPkg: string,
194
- ): { node?: MetaData | undefined } {
195
- const objects = root.children().filter((c) => c.type === TYPE_OBJECT);
196
- if (ref.includes(PACKAGE_SEPARATOR)) {
197
- return { node: objects.find((c) => c.resolutionKey() === ref) };
212
+ ): { node?: MetaData | undefined; error?: string | undefined } {
213
+ return bindOwner(topLevelOfType(root, TYPE_OBJECT), ref, referrerPkg);
214
+ }
215
+
216
+ /**
217
+ * Resolve a reference that may carry a dotted child tail (ADR-0059 R2): bind the
218
+ * owner among the root's top-level nodes (of `ownerType` when given), then walk the
219
+ * tail by child name. Intermediate segments must be unique among the current node's
220
+ * effective children; the last segment is scoped to `lastType` when given. Returns
221
+ * the owner as well as the target, so a caller can spell the owner and keep the tail.
222
+ */
223
+ export function resolveRef(
224
+ root: MetaData,
225
+ ref: string,
226
+ referrerPkg: string,
227
+ opts?: { ownerType?: string; lastType?: string },
228
+ ): { node?: MetaData | undefined; owner?: MetaData | undefined; error?: string | undefined } {
229
+ const { owner, tail } = splitChildTail(ref);
230
+ const bound = bindOwner(topLevelOfType(root, opts?.ownerType), owner, referrerPkg);
231
+ if (bound.node === undefined) return bound;
232
+ if (tail === "") return { node: bound.node, owner: bound.node };
233
+ const path = tail.slice(CHILD_REF_SEPARATOR.length).split(CHILD_REF_SEPARATOR);
234
+ if (path.some((p) => p === "")) return {};
235
+ let current: MetaData = bound.node;
236
+ for (let i = 0; i < path.length - 1; i++) {
237
+ const matches = current.children().filter((c) => c.name === path[i]);
238
+ if (matches.length !== 1) return { owner: bound.node };
239
+ current = matches[0]!;
198
240
  }
199
- // Bare: PREFER the referrer's own package, THEN a root-level (empty-package)
200
- // object — mirroring the loader symbol table's `byKey.get(localKey) ?? get(ref)`
201
- // so both resolvers agree when a root-level object shares the bare name.
202
- const localKey = referrerPkg !== "" ? `${referrerPkg}${PACKAGE_SEPARATOR}${ref}` : ref;
203
- const own = objects.find((c) => c.resolutionKey() === localKey);
204
- if (own !== undefined) return { node: own };
205
- return { node: localKey !== ref ? objects.find((c) => c.resolutionKey() === ref) : undefined };
241
+ const last = path[path.length - 1]!;
242
+ const target = current
243
+ .children()
244
+ .find((c) => c.name === last && (opts?.lastType === undefined || c.type === opts.lastType));
245
+ return { node: target, owner: bound.node };
246
+ }
247
+
248
+ /**
249
+ * ADR-0059 R7 — resolve a CANONICAL reference with no referrer: strip one leading
250
+ * `::`, match a top-level node's resolution key exactly, then walk any dotted tail.
251
+ * For consumers that hold a resolved model and no referrer context. Never matches
252
+ * on a short name.
253
+ */
254
+ export function lookupCanonical(
255
+ root: MetaData,
256
+ value: string,
257
+ opts?: { ownerType?: string; lastType?: string },
258
+ ): MetaData | undefined {
259
+ const { owner, tail } = splitChildTail(value);
260
+ const exact = owner.startsWith(PACKAGE_SEPARATOR) ? owner : `${PACKAGE_SEPARATOR}${owner}`;
261
+ return resolveRef(root, exact + tail, "", opts).node;
262
+ }
263
+
264
+ /**
265
+ * ADR-0059 R6 — the canonical spelling of a resolved owner, as written from a
266
+ * referrer in package `referrerPkg`: `pkg::Name` for a packaged target, `::Name` for
267
+ * a root-level target from a packaged referrer, `Name` from a root-level referrer.
268
+ * Every spelling resolves to the same node with or without a referrer.
269
+ */
270
+ export function spellRef(target: MetaData, referrerPkg: string, tail = ""): string {
271
+ const key = target.resolutionKey();
272
+ const rootLevel = !key.includes(PACKAGE_SEPARATOR);
273
+ return (rootLevel && referrerPkg !== "" ? `${PACKAGE_SEPARATOR}${key}` : key) + tail;
274
+ }
275
+
276
+ /** Effective package of a node: its own `package`, else its file's (ADR-0059 R10). */
277
+ export function effectivePackageOf(node: MetaData): string {
278
+ return node.package ?? node.fileDefaultPackage ?? "";
279
+ }
280
+
281
+ /** The top-level node (a direct child of the root) that physically contains `node`. */
282
+ export function topLevelNodeOf(node: MetaData): MetaData {
283
+ let current = node;
284
+ while (current.parent !== undefined && current.parent.parent !== undefined) current = current.parent;
285
+ return current;
286
+ }
287
+
288
+ /**
289
+ * ADR-0059 R3 default — the package a reference attribute resolves in: the effective
290
+ * package of the top-level node that PHYSICALLY declares the attribute. For an
291
+ * attribute `node` inherits through `extends`, that is the ancestor that wrote it,
292
+ * so an inherited reference means what it meant where it was written.
293
+ */
294
+ export function referrerPackageFor(node: MetaData, attrName: string): string {
295
+ let declarer: MetaData | undefined = node;
296
+ const seen = new Set<MetaData>();
297
+ // ADR-0039: own — locating the layer that physically declares the attr.
298
+ while (declarer !== undefined && !declarer.ownHasAttr(attrName) && !seen.has(declarer)) {
299
+ seen.add(declarer);
300
+ declarer = declarer.superResolved;
301
+ }
302
+ return effectivePackageOf(topLevelNodeOf(declarer ?? node));
206
303
  }
207
304
 
208
305
  /**
@@ -213,6 +310,27 @@ export function resolveObjectRef(
213
310
  * suggest). Appended to the per-attr unresolved-ref error message.
214
311
  */
215
312
  export function didYouMeanHint(root: MetaData, ref: string): string {
313
+ const found = sameNamedObjects(root, ref);
314
+ if (found === undefined) return "";
315
+ return ` An object named "${found.shortName}" exists in: ${found.candidates.join(", ")}. Qualify it with its package (FQN).`;
316
+ }
317
+
318
+ /**
319
+ * The did-you-mean suffix for an UNRESOLVED `extends` ref: the same candidate list
320
+ * as {@link didYouMeanHint}, plus the exact spelling to write. A bare `extends`
321
+ * resolves in the referrer's own package (ADR-0042/0059), so a base declared in
322
+ * another package must be named by its FQN. Returns "" when nothing is named alike.
323
+ */
324
+ export function didYouMeanSuperHint(root: MetaData, ref: string): string {
325
+ const found = sameNamedObjects(root, ref);
326
+ if (found === undefined) return "";
327
+ return ` An object named "${found.shortName}" exists in: ${found.candidates.join(", ")}. Qualify it with its package (FQN): write extends: "${found.candidates[0]}".`;
328
+ }
329
+
330
+ function sameNamedObjects(
331
+ root: MetaData,
332
+ ref: string,
333
+ ): { shortName: string; candidates: string[] } | undefined {
216
334
  const { owner } = splitChildTail(ref);
217
335
  const sep = owner.lastIndexOf(PACKAGE_SEPARATOR);
218
336
  const shortName = sep === -1 ? owner : owner.slice(sep + PACKAGE_SEPARATOR.length);
@@ -220,6 +338,31 @@ export function didYouMeanHint(root: MetaData, ref: string): string {
220
338
  .children()
221
339
  .filter((c) => c.type === TYPE_OBJECT && c.name === shortName)
222
340
  .map((c) => c.resolutionKey());
223
- if (candidates.length === 0) return "";
224
- return ` An object named "${shortName}" exists in: ${candidates.join(", ")}. Qualify it with its package (FQN).`;
341
+ return candidates.length === 0 ? undefined : { shortName, candidates };
342
+ }
343
+
344
+ /**
345
+ * ADR-0059 R7 — the object a reference attribute on `node` names: the owner the loader
346
+ * bound once (`MetaAttr.resolvedTarget`), read RESOLVING so an inherited attribute
347
+ * works (ADR-0039). For a dotted ref (`Week.id`, `Program.weeks`) it is the owner
348
+ * object, not the tail. A tree built without the loader has no recorded target, so the
349
+ * value is resolved here from the package of the node that declared it. Undefined when
350
+ * the attribute is absent, is a list, or names nothing. Consumers use this instead of
351
+ * comparing a reference value to a short name.
352
+ */
353
+ export function refTarget(node: MetaData, attrName: string): MetaData | undefined {
354
+ const seen = new Set<MetaData>();
355
+ for (let n: MetaData | undefined = node; n !== undefined && !seen.has(n); n = n.superResolved) {
356
+ seen.add(n);
357
+ // ADR-0039: own — locating the layer that declares the attr, walking the super chain.
358
+ const attr = n.ownMetaAttr(attrName);
359
+ if (attr === undefined) continue;
360
+ const recorded = attr.resolvedTarget;
361
+ if (recorded !== undefined) return Array.isArray(recorded) ? undefined : (recorded as MetaData);
362
+ const value = attr.value;
363
+ if (typeof value !== "string" || value === "") return undefined;
364
+ const { owner } = splitChildTail(value);
365
+ return resolveObjectRef(node.root(), owner, effectivePackageOf(topLevelNodeOf(n))).node;
366
+ }
367
+ return undefined;
225
368
  }
package/src/naming.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { docsBlobUrl } from "./docs-ref.js";
1
2
  import type { MetaData } from "./shared/meta-data.js";
2
3
  import { TYPE_FIELD } from "./shared/base-types.js";
3
4
  import { PACKAGE_SEPARATOR } from "./shared/structural.js";
@@ -53,18 +54,81 @@ export function toKebabCase(s: string): string {
53
54
  }
54
55
 
55
56
  /**
56
- * Column-naming strategy applied by the persistence layer to fields with no
57
- * explicit `@column` override. Persistence-layer config (set on
58
- * ObjectManager / buildExpectedSchema / codegen config), not a metadata attr —
59
- * the same metadata can drive snake_case (PG convention) or literal (EF
60
- * convention) consumers. TS port defaults to snake_case; C# port defaults to
61
- * literal.
57
+ * How a field with no `@column` becomes a column name. One project setting,
58
+ * `naming.columns` in `.metaobjects/config.json`, read by every port (ADR-0058): the
59
+ * loaded root carries it ({@link physicalNamingOf}), so codegen, migrate, the runtime
60
+ * and verify resolve the same name. Not a metadata attr: `@column` is the per-field
61
+ * override, this is the rule for the rest.
62
62
  */
63
63
  export type ColumnNamingStrategy = "snake_case" | "literal" | "kebab-case";
64
64
 
65
- /** Single source of truth for the TS-port default. */
65
+ /** Every column-naming rule, in the order the conformance corpus lists them. */
66
+ export const COLUMN_NAMING_STRATEGIES: readonly ColumnNamingStrategy[] = ["snake_case", "literal", "kebab-case"];
67
+
68
+ /** The default in EVERY port: what `meta migrate` has always created. */
66
69
  export const DEFAULT_COLUMN_NAMING_STRATEGY: ColumnNamingStrategy = "snake_case";
67
70
 
71
+ /**
72
+ * How an object with no physical-name attribute (`@table`, `@view`, ...) and no source name
73
+ * gets its table name: the entity (or source) name cased by this rule. The entity-name
74
+ * fallback keeps the frozen legacy pluralizer under every rule ({@link pluralizeTableNameLegacy}).
75
+ */
76
+ export type TableNamingRule = "snake_case" | "literal";
77
+
78
+ /** Every table-naming rule. */
79
+ export const TABLE_NAMING_RULES: readonly TableNamingRule[] = ["snake_case", "literal"];
80
+
81
+ /** The project's physical-naming rules: `naming` in `.metaobjects/config.json` (ADR-0058). */
82
+ export interface PhysicalNaming {
83
+ readonly columns: ColumnNamingStrategy;
84
+ readonly tables: TableNamingRule;
85
+ }
86
+
87
+ /** The default in every port, equal to what `meta migrate` has always created. */
88
+ export const DEFAULT_PHYSICAL_NAMING: PhysicalNaming = Object.freeze({
89
+ columns: DEFAULT_COLUMN_NAMING_STRATEGY,
90
+ tables: "snake_case",
91
+ });
92
+
93
+ /**
94
+ * The physical-naming rules for the tree `node` belongs to: what its root carries, set by the
95
+ * project-aware load from `.metaobjects/config.json`, else {@link DEFAULT_PHYSICAL_NAMING}.
96
+ * Read through the root, not passed around, so every consumer of one loaded model resolves
97
+ * the same physical names.
98
+ */
99
+ export function physicalNamingOf(node: MetaData): PhysicalNaming {
100
+ const naming = (node.root() as { physicalNaming?: PhysicalNaming }).physicalNaming;
101
+ return naming ?? DEFAULT_PHYSICAL_NAMING;
102
+ }
103
+
104
+ /** The migration guide every refusal of a former naming source names (ADR-0058). */
105
+ export const PHYSICAL_NAMING_MIGRATION_GUIDE_PATH = "docs/features/migrations/physical-naming.md";
106
+
107
+ /** The guide on `main`: the default when a caller has no release to pin to. */
108
+ export const PHYSICAL_NAMING_MIGRATION_GUIDE = docsBlobUrl(PHYSICAL_NAMING_MIGRATION_GUIDE_PATH);
109
+
110
+ /**
111
+ * What a former column-naming source prints when it is still used (ADR-0058): the one
112
+ * setting, where it goes, and the guide. Same words in every port. `what` names the source
113
+ * exactly; `value` is the rule it gave, carried into the example so the move is mechanical.
114
+ */
115
+ export function physicalNamingRemovedMessage(
116
+ what: string,
117
+ value: string = "literal",
118
+ guide: string = PHYSICAL_NAMING_MIGRATION_GUIDE,
119
+ ): string {
120
+ return (
121
+ `${what} was removed in 1.1.0. Column and table naming is one project setting now, read by ` +
122
+ `every port: "naming" in .metaobjects/config.json, for example "naming": { "columns": "${value}" }. ` +
123
+ `Move it there and delete ${what}. See ${guide}`
124
+ );
125
+ }
126
+
127
+ /** `name` cased by the table rule. */
128
+ export function applyTableNamingRule(name: string, rule: TableNamingRule): string {
129
+ return rule === "literal" ? name : toSnakeCase(name);
130
+ }
131
+
68
132
  export function applyColumnNamingStrategy(name: string, strategy: ColumnNamingStrategy): string {
69
133
  switch (strategy) {
70
134
  case "literal": return name;
@@ -238,13 +302,13 @@ export function resolveTableName(entity: MetaData): string {
238
302
  const source = primaryRdbSource(entity);
239
303
  if (source !== undefined) return source.physicalName;
240
304
  // Frozen: this is a DEFAULT PHYSICAL name, not an API-surface name. See
241
- // pluralizeTableNameLegacy's doc.
242
- return pluralizeTableNameLegacy(toSnakeCase(entity.name));
305
+ // pluralizeTableNameLegacy's doc. Cased by the project's table rule (ADR-0058).
306
+ return pluralizeTableNameLegacy(applyTableNamingRule(entity.name, physicalNamingOf(entity).tables));
243
307
  }
244
308
 
245
309
  export function resolveColumnName(
246
310
  field: MetaData,
247
- strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
311
+ strategy: ColumnNamingStrategy = physicalNamingOf(field).columns,
248
312
  ): string {
249
313
  // ADR-0039: resolving — a concrete field may inherit @column via extends.
250
314
  const col = field.attr(FIELD_ATTR_COLUMN);
@@ -335,7 +399,7 @@ export interface EntityNameMap {
335
399
 
336
400
  export function buildNameMap(
337
401
  entity: MetaData,
338
- strategy: ColumnNamingStrategy = DEFAULT_COLUMN_NAMING_STRATEGY,
402
+ strategy: ColumnNamingStrategy = physicalNamingOf(entity).columns,
339
403
  ): EntityNameMap {
340
404
  const jsToDb = new Map<string, string>();
341
405
  const dbToJs = new Map<string, string>();
@@ -20,6 +20,7 @@ import {
20
20
  DATA_TYPE_OBJECT,
21
21
  } from "./data-type.js";
22
22
  import { TYPE_OBJECT } from "./shared/base-types.js";
23
+ import { lookupCanonical } from "./naming-refs.js";
23
24
 
24
25
  const TYPE_DISCRIMINATOR = "@type";
25
26
 
@@ -142,16 +143,13 @@ function coerceNumeric(raw: unknown): unknown {
142
143
  return raw;
143
144
  }
144
145
 
145
- /** Resolve an object-typed field's `@objectRef` to its target MetaObject by
146
- * walking to the tree root and looking up the named object child. Returns
147
- * undefined when there is no ref or it does not resolve (caller passes the
148
- * raw value through — best-effort). */
146
+ /** Resolve an object-typed field's `@objectRef` to its target MetaObject. The loader
147
+ * has already rewritten the ref to its canonical spelling (ADR-0059 R5), so it is
148
+ * looked up exactly, with no referrer (R7). Returns undefined when there is no ref
149
+ * or it does not resolve (caller passes the raw value through — best-effort). */
149
150
  function resolveObjectRef(field: MetaField, mo: MetaObject): MetaObject | undefined {
150
151
  const ref = field.objectRef;
151
152
  if (ref === undefined) return undefined;
152
- // ADR-0039: own — lookup is on the tree ROOT, which has no super chain
153
- // (it never `extends`), so its own children ARE its effective children.
154
- const found: MetaData | undefined = mo.root().ownChildByTypeAndName(TYPE_OBJECT, ref);
155
- if (found === undefined) return undefined;
156
- return found as MetaObject;
153
+ const found: MetaData | undefined = lookupCanonical(mo.root(), ref, { ownerType: TYPE_OBJECT });
154
+ return found as MetaObject | undefined;
157
155
  }