@metaobjectsdev/metadata 1.0.12 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +24 -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 +65 -7
  137. package/dist/naming.d.ts.map +1 -1
  138. package/dist/naming.js +98 -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 -2
  149. package/dist/persistence/source/meta-source.d.ts.map +1 -1
  150. package/dist/persistence/source/meta-source.js +10 -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 +24 -6
  209. package/src/fmt.ts +3 -1
  210. package/src/index.ts +73 -5
  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 +131 -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 +10 -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
@@ -39,6 +39,7 @@ import {
39
39
  extendsTargetCompatible,
40
40
  EXTENDS_TARGET_MISMATCH_RULE,
41
41
  } from "./super-resolve.js";
42
+ import { RefOverDropError } from "./naming-refs.js";
42
43
  import { JsonPathBuilder, type Segment as JsonPathSegment } from "./json-path.js";
43
44
  import { getYamlPosition, type YamlPosition } from "./core/yaml-positions.js";
44
45
  import {
@@ -66,7 +67,6 @@ import {
66
67
  import { ATTR_SUBTYPE_PROPERTIES, ATTR_SUBTYPE_STRINGARRAY } from "./core/attr/attr-constants.js";
67
68
  import { attrClassFor } from "./attr-class-map.js";
68
69
  import type { AttrValue } from "./shared/meta-data.js";
69
- import { isRelativeRef, REF_BEARING_ATTR_NAMES } from "./naming-refs.js";
70
70
 
71
71
  // ---------------------------------------------------------------------------
72
72
  // Public API
@@ -234,44 +234,6 @@ function reportProblem(
234
234
  warnings.push(msg);
235
235
  }
236
236
 
237
- // ---------------------------------------------------------------------------
238
- // FR-032 (ADR-0032) — canonical-JSON ref guard.
239
- //
240
- // Canonical JSON is the self-contained interchange form: every ref-bearing attr
241
- // MUST be fully-qualified. A relative authoring form (leading `::` or `..::`)
242
- // surviving into canonical JSON is `ERR_RELATIVE_REF_IN_CANONICAL`. The guard
243
- // fires ONLY for JSON-format input — YAML-format input has already been
244
- // desugar-expanded via expandRef (so any `::`/`..::` there is correct authoring
245
- // that was lowered to FQN before buildTree sees it). Like ERR_RESERVED_ATTR,
246
- // this is a hard error routed through the loader's error sink even in lax mode.
247
- // ---------------------------------------------------------------------------
248
-
249
- function guardRelativeRefInCanonical(
250
- refLabel: string,
251
- rawValue: unknown,
252
- strict: boolean,
253
- warnings: string[],
254
- path: string,
255
- ): void {
256
- if (_currentFormat !== "json") return;
257
- if (typeof rawValue !== "string") return;
258
- if (!isRelativeRef(rawValue)) return;
259
- const msg =
260
- `Relative reference '${rawValue}' on ${refLabel} at ${path} is not allowed in ` +
261
- `canonical JSON — canonical JSON must be fully-qualified. Relative forms ` +
262
- `(leading '::' or '..::') are YAML-authoring sugar that the desugar expands.`;
263
- if (strict) {
264
- throw new ParseError(msg, { code: "ERR_RELATIVE_REF_IN_CANONICAL", source: errSource() });
265
- }
266
- if (_currentErrors !== undefined) {
267
- _currentErrors.push(
268
- new ParseError(msg, { code: "ERR_RELATIVE_REF_IN_CANONICAL", source: errSource() }),
269
- );
270
- } else {
271
- warnings.push(msg);
272
- }
273
- }
274
-
275
237
  // ---------------------------------------------------------------------------
276
238
  // The one message for an authored `<type>.base`, so both doors say the same thing.
277
239
  //
@@ -701,10 +663,11 @@ export function buildTree(parsed: unknown, opts: ParseOptions): ParseResult {
701
663
  if (opts.intoRoot !== undefined) {
702
664
  // --- Merge mode: parse root's attrs/children into the existing root ---
703
665
  // The JSON root's own package/super/reserved-keys are not re-applied to the
704
- // existing root. BUT: children from the NEW JSON should inherit from the
705
- // NEW root's package, not the existing root's.
666
+ // existing root. Children from the NEW document take the NEW document's
667
+ // package — and a document with no `package` declares root-level nodes,
668
+ // whatever was loaded before it (ADR-0059 R10: the default package is per file).
706
669
  const newRootPkg = rootDataObj[RESERVED_KEY_PACKAGE];
707
- const contextPkg = (typeof newRootPkg === "string" ? newRootPkg : opts.intoRoot.package) ?? "";
670
+ const contextPkg = typeof newRootPkg === "string" ? newRootPkg : "";
708
671
  parseNodeInto(
709
672
  rootDataObj,
710
673
  opts.intoRoot,
@@ -825,9 +788,9 @@ function parseNodeFresh(
825
788
  // this node by its effective qualified key regardless of post-merge tree
826
789
  // shape or load order. This does NOT touch the node's `package` or `fqn()`
827
790
  // (objects stay bare per FR5d). Mirrors Java's parse-time getDefaultPackageName().
828
- if (inheritedContextPkg !== "") {
829
- model.setFileDefaultPackage(inheritedContextPkg);
830
- }
791
+ // Recorded even when empty: "" says the file declares no package (ADR-0059
792
+ // R10), which is not the same as a node built in code with no file at all.
793
+ model.setFileDefaultPackage(inheritedContextPkg);
831
794
 
832
795
  // --- Apply reserved keys (package, extends, abstract, isArray) ---
833
796
  applyReservedKeys(model, nodeData, strict, source, path, warnings, inheritedContextPkg);
@@ -861,7 +824,17 @@ function parseNodeFresh(
861
824
  if (model.superRef !== undefined && accumRoot !== undefined && !_deferSuperResolution) {
862
825
  // FR-024: thread the referrer's type so dotted `Entity.child` refs resolve
863
826
  // type-scoped — kept consistent with the deferred path (super-resolve.ts).
864
- const superModel = resolveSuperRef(model.superRef, effectivePkg, accumRoot, { type: model.type });
827
+ let superModel: MetaData | undefined;
828
+ try {
829
+ superModel = resolveSuperRef(model.superRef, effectivePkg, accumRoot, { type: model.type });
830
+ } catch (e) {
831
+ if (!(e instanceof RefOverDropError)) throw e;
832
+ // ADR-0059 R2 — `..::` dropped more segments than the package has.
833
+ throw new ParseError(
834
+ `the extends ref '${model.superRef}' on ${model.fqn()} climbs above the root package — each '..::' drops one segment of the declaring package`,
835
+ { code: "ERR_BAD_ATTR_VALUE", source: resolvedSource(errSource(), model.fqn(), model.superRef) },
836
+ );
837
+ }
865
838
  if (superModel !== undefined) {
866
839
  // FR-024 — a dotted child-targeting ref must resolve to a node of the
867
840
  // SAME type and subtype as the extending node. Dotted-only check; the
@@ -1458,9 +1431,6 @@ function applyReservedKeys(
1458
1431
  if (typeof rawExtends !== "string") {
1459
1432
  reportProblem(`"${RESERVED_KEY_EXTENDS}" must be a string at ${path}`, strict, warnings, "ERR_UNRESOLVED_SUPER");
1460
1433
  } else {
1461
- // FR-032: canonical JSON `extends` must be FQN; reject a surviving
1462
- // relative form (no-op for YAML-format input, which was desugar-expanded).
1463
- guardRelativeRefInCanonical(`"${RESERVED_KEY_EXTENDS}"`, rawExtends, strict, warnings, path);
1464
1434
  model.setSuper(rawExtends);
1465
1435
  }
1466
1436
  }
@@ -1548,13 +1518,6 @@ function applyInlineAttrsAndUnknownKeys(
1548
1518
 
1549
1519
  const rawVal = nodeData[key];
1550
1520
 
1551
- // FR-032: a ref-bearing inline attr (@objectRef/@references/@from/@of/@via/
1552
- // @parameterRef/@payloadRef/@responseRef) in canonical JSON must be FQN —
1553
- // reject a surviving relative form. No-op for YAML-format input.
1554
- if (REF_BEARING_ATTR_NAMES.has(attrName)) {
1555
- guardRelativeRefInCanonical(`${ATTR_PREFIX}${attrName}`, rawVal, strict, warnings, path);
1556
- }
1557
-
1558
1521
  try {
1559
1522
  const attr = materializeAttr(model, attrName, rawVal, registry);
1560
1523
  model.setMetaAttr(attr);
@@ -19,7 +19,7 @@ export const DB_DEFINITION: ProviderDefinition = {
19
19
  "name": "column",
20
20
  "min": 0,
21
21
  "max": 1,
22
- "description": "Physical column name for this field on an rdb source. Defaults to the field name via columnNamingStrategy."
22
+ "description": "Physical column name for this field on an rdb source. Defaults to the field name under the project's naming.columns rule (the \"naming\" setting in .metaobjects/config.json)."
23
23
  },
24
24
  {
25
25
  "type": "attr",
@@ -108,7 +108,7 @@ export const DB_DEFINITION: ProviderDefinition = {
108
108
  "name": "table",
109
109
  "min": 0,
110
110
  "max": 1,
111
- "description": "Physical SQL table name for source.rdb @kind: \"table\" (default). FR-016: Defaults from the source's bare structural `name` via the project's columnNamingStrategy when omitted, then from the owning entity's name. Pre-1.0 legacy spelling for view/materializedView/storedProc/tableFunction kinds during the transition; canonical-serializer rewrites to the kind-matching alias."
111
+ "description": "Physical SQL table name for source.rdb @kind: \"table\" (default). FR-016: Defaults from the source's bare structural `name` under the project's naming.tables rule (the \"naming\" setting in .metaobjects/config.json) when omitted, then from the owning entity's name. Pre-1.0 legacy spelling for view/materializedView/storedProc/tableFunction kinds during the transition; canonical-serializer rewrites to the kind-matching alias."
112
112
  },
113
113
  {
114
114
  "type": "attr",
@@ -6,7 +6,7 @@
6
6
  // Extends MetaData directly: no model wrapper, no metaOf() indirection.
7
7
 
8
8
  import { MetaData } from "../../shared/meta-data.js";
9
- import { pluralize, toSnakeCase } from "../../naming.js";
9
+ import { applyTableNamingRule, physicalNamingOf, pluralizeTableNameLegacy } from "../../naming.js";
10
10
  import {
11
11
  SOURCE_ATTR_TABLE,
12
12
  SOURCE_ATTR_VIEW,
@@ -100,8 +100,11 @@ export class MetaSource extends MetaData {
100
100
  * four-step rule:
101
101
  * 1. Kind-matching alias (e.g. @proc when @kind: "storedProc").
102
102
  * 2. Legacy @table for non-table kind (pre-1.0 fallback).
103
- * 3. Source's bare structural `name` via snake_case.
104
- * 4. Owning entity's name via pluralize(snake_case).
103
+ * 3. Source's bare structural `name`, cased by the project's table rule.
104
+ * 4. Owning entity's name, cased by the project's table rule, through the frozen
105
+ * legacy pluralizer — see pluralizeTableNameLegacy's doc for why it is frozen.
106
+ * The table rule is `naming.tables` in `.metaobjects/config.json` (ADR-0058), carried by
107
+ * the loaded root; default `snake_case`.
105
108
  *
106
109
  * Callers needing the legacy raw @table slot only should use `tableName`;
107
110
  * codegen / migrate / runtime should use `physicalName`.
@@ -127,14 +130,15 @@ export class MetaSource extends MetaData {
127
130
  // Step 3: source's structural `name` via snake_case (no pluralization —
128
131
  // the source's name IS the logical name).
129
132
  if (this.name !== "") {
130
- return toSnakeCase(this.name);
133
+ return applyTableNamingRule(this.name, physicalNamingOf(this).tables);
131
134
  }
132
135
 
133
136
  // Step 4: owning entity's name via pluralize(snake_case). The MetaData
134
- // parent of a source is always the entity that declared it.
137
+ // parent of a source is always the entity that declared it. Frozen legacy
138
+ // rule — this is a DEFAULT PHYSICAL name, not an API-surface name.
135
139
  const owner = this.parent;
136
140
  if (owner !== undefined && owner.name !== "") {
137
- return pluralize(toSnakeCase(owner.name));
141
+ return pluralizeTableNameLegacy(applyTableNamingRule(owner.name, physicalNamingOf(this).tables));
138
142
  }
139
143
 
140
144
  return "";
@@ -38,8 +38,8 @@
38
38
  // — a TS-web presentation facet (TS web client + TS
39
39
  // form codegen consume them; zero backend/codegen/
40
40
  // render consumers in any port), like the TS-only `D1`
41
- // dialect. Stay REGISTERED in TS; deregistered in
42
- // C#/Python; EXCLUDED from the manifest everywhere.
41
+ // dialect. REGISTERED in every port, for LOADING
42
+ // only; EXCLUDED from the manifest everywhere.
43
43
  //
44
44
  // The boundary is now an EXPLICIT classification (not a silent default): the
45
45
  // port-private set is enumerated WITH a reason category, and a TRIPWIRE
@@ -112,7 +112,7 @@ export interface ManifestType {
112
112
  * constant read `"0.10"`). Bump with that script — never by hand — so the manifest and
113
113
  * all four port constants move together.
114
114
  */
115
- export const METAMODEL_VERSION = "1.0";
115
+ export const METAMODEL_VERSION = "1.1";
116
116
 
117
117
  /** The full canonical manifest. All collections are sorted for byte-stability. */
118
118
  interface RegistryManifest {
@@ -0,0 +1,48 @@
1
+ // release-docs.ts — guide URLs pinned to the release this toolchain belongs to.
2
+ //
3
+ // Node-only (reads package.json), so it sits behind its own subpath
4
+ // (`@metaobjectsdev/metadata/release-docs`) and is never reachable from the root entry,
5
+ // which must stay browser-safe. The pure rule (`docsRefForVersion`) is in `docs-ref.ts`.
6
+
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { docsBlobUrl, docsRefForVersion } from "./docs-ref.js";
11
+ import { PHYSICAL_NAMING_MIGRATION_GUIDE_PATH } from "./naming.js";
12
+
13
+ let cachedVersion: string | undefined;
14
+
15
+ /**
16
+ * The version of this package, read from its own package.json (walking up from this
17
+ * module). All `@metaobjectsdev/*` npm packages release in lockstep, so it is also the
18
+ * version of the toolchain a message is printed by. "0.0.0" when not found.
19
+ */
20
+ export function metadataPackageVersion(): string {
21
+ if (cachedVersion !== undefined) return cachedVersion;
22
+ let dir = dirname(fileURLToPath(import.meta.url));
23
+ for (let i = 0; i < 6; i++) {
24
+ const candidate = join(dir, "package.json");
25
+ if (existsSync(candidate)) {
26
+ try {
27
+ const pkg = JSON.parse(readFileSync(candidate, "utf8")) as { name?: string; version?: string };
28
+ if (pkg.name === "@metaobjectsdev/metadata" && pkg.version) return (cachedVersion = pkg.version);
29
+ } catch {
30
+ // not our manifest / unreadable — keep walking up
31
+ }
32
+ }
33
+ const parent = dirname(dir);
34
+ if (parent === dir) break;
35
+ dir = parent;
36
+ }
37
+ return (cachedVersion = "0.0.0");
38
+ }
39
+
40
+ /** A guide URL pinned to the release this package belongs to. */
41
+ export function guideUrl(repoRelativePath: string): string {
42
+ return docsBlobUrl(repoRelativePath, docsRefForVersion(metadataPackageVersion()));
43
+ }
44
+
45
+ /** The physical-naming migration guide, pinned: what `physicalNamingRemovedMessage` is given. */
46
+ export function physicalNamingGuideUrl(): string {
47
+ return guideUrl(PHYSICAL_NAMING_MIGRATION_GUIDE_PATH);
48
+ }
@@ -41,15 +41,28 @@ const SOURCE_RDB_FUSED_KEY = `${TYPE_SOURCE}${TYPE_SUBTYPE_SEPARATOR}${SOURCE_SU
41
41
  // Public API
42
42
  // ---------------------------------------------------------------------------
43
43
 
44
+ /**
45
+ * How references are written (ADR-0059 R8).
46
+ * - "canonical" (default): each reference as the loader resolved it — `pkg::Name`,
47
+ * `::Name` for a root-level target from a packaged referrer — and the document
48
+ * package rule below. The output loads back to the same model from any source.
49
+ * - "authored": each reference, `extends` included, exactly as written, and every
50
+ * `package` where it was written. Only `meta fmt` uses it, so formatting never
51
+ * rewrites what an author wrote (ADR-0059, C2).
52
+ */
53
+ export type RefSpelling = "canonical" | "authored";
54
+
44
55
  export interface SerializeOptions {
45
56
  /** Pretty-print indent. 0 = no whitespace. Default 2. */
46
57
  indent?: number;
58
+ /** Reference spelling; default "canonical". */
59
+ refs?: RefSpelling;
47
60
  }
48
61
 
49
62
  export function serializeJson(model: MetaData, opts?: SerializeOptions): string {
50
63
  const indent = opts?.indent ?? 2;
51
64
 
52
- const nodeObj = serializeNode(model, false);
65
+ const nodeObj = serializeNode(model, false, opts?.refs ?? "canonical", undefined);
53
66
  return JSON.stringify(nodeObj, null, indent === 0 ? undefined : indent);
54
67
  }
55
68
 
@@ -82,17 +95,57 @@ function fusedKey(type: string, subType: string): string {
82
95
  // Serialize a single node — returns { "<type>.<subType>": { ...body } }
83
96
  // ---------------------------------------------------------------------------
84
97
 
98
+ /**
99
+ * How a node's `package` key is written. `undefined`: the node's own `package`, as
100
+ * authored. Otherwise the package the document rule (ADR-0059 R8) assigns it; ""
101
+ * writes none.
102
+ */
103
+ type PackageSlot = string | undefined;
104
+
85
105
  function serializeNode(
86
106
  model: MetaData,
87
107
  effective: boolean,
108
+ refs: RefSpelling,
109
+ pkgSlot: PackageSlot,
88
110
  ): Record<string, unknown> {
89
- const inner = serializeNodeInner(model, effective);
111
+ const inner = serializeNodeInner(model, effective, refs, pkgSlot);
90
112
  return { [fusedKey(model.type, model.subType)]: inner };
91
113
  }
92
114
 
115
+ /** Effective package of a top-level node (ADR-0059 R10): own, else its file's. A
116
+ * node built in code has no file, so it takes the root's package, as re-loading the
117
+ * output would give it. */
118
+ function topLevelPackage(node: MetaData, root: MetaData): string {
119
+ return node.package ?? node.fileDefaultPackage ?? root.package ?? "";
120
+ }
121
+
122
+ /**
123
+ * ADR-0059 R8 document package rule, for a `metadata.root` in canonical mode.
124
+ * Every top-level node in one non-empty package P: the root carries P (a node keeps
125
+ * a package it carries itself, as before). Otherwise the root carries none and each packaged top-level node
126
+ * carries its own. A root with no named top-level node keeps its own package.
127
+ */
128
+ function documentPackages(root: MetaData): { rootPkg: string | undefined; perNode: boolean } {
129
+ // ADR-0039: own — the serializer writes the declared layer; the root has no super.
130
+ const named = root.ownChildren().filter((c) => c.name !== "");
131
+ if (named.length === 0) return { rootPkg: root.package, perNode: false };
132
+ const pkgs = new Set(named.map((c) => topLevelPackage(c, root)));
133
+ if (pkgs.size === 1) {
134
+ const only = [...pkgs][0]!;
135
+ // When every node already names P itself and the root names none, the
136
+ // document is already complete as written; adding a root package would only
137
+ // repeat it.
138
+ const selfNamed = root.package === undefined && named.every((c) => c.package === only);
139
+ if (only !== "") return { rootPkg: selfNamed ? undefined : only, perNode: false };
140
+ }
141
+ return { rootPkg: undefined, perNode: true };
142
+ }
143
+
93
144
  function serializeNodeInner(
94
145
  model: MetaData,
95
146
  effective: boolean,
147
+ refs: RefSpelling,
148
+ pkgSlot: PackageSlot,
96
149
  ): Record<string, unknown> {
97
150
  // Canonical body-key order:
98
151
  // 1. name 2. package 3. extends 4. abstract
@@ -104,12 +157,19 @@ function serializeNodeInner(
104
157
  obj[RESERVED_KEY_NAME] = model.name;
105
158
  }
106
159
 
107
- if (model.package !== undefined && model.package !== "") {
108
- obj[RESERVED_KEY_PACKAGE] = model.package;
160
+ const isDocRoot = model.type === TYPE_METADATA && model.subType === SUBTYPE_ROOT;
161
+ const doc = isDocRoot && refs === "canonical" ? documentPackages(model) : undefined;
162
+
163
+ const pkg = doc !== undefined ? doc.rootPkg : pkgSlot !== undefined ? pkgSlot : model.package;
164
+ if (pkg !== undefined && pkg !== "") {
165
+ obj[RESERVED_KEY_PACKAGE] = pkg;
109
166
  }
110
167
 
111
- if (model.superRef !== undefined) {
112
- obj[RESERVED_KEY_EXTENDS] = model.superRef;
168
+ // After the loader's resolveReferences pass `superRef` is canonical (ADR-0059 R5);
169
+ // the authored spelling is kept beside it for `meta fmt`.
170
+ const superRef = refs === "authored" ? (model.authoredSuperRef ?? model.superRef) : model.superRef;
171
+ if (superRef !== undefined) {
172
+ obj[RESERVED_KEY_EXTENDS] = superRef;
113
173
  }
114
174
 
115
175
  if (model.isAbstract === true) {
@@ -141,13 +201,20 @@ function serializeNodeInner(
141
201
 
142
202
  const serializedChildren: Record<string, unknown>[] = [];
143
203
  for (const child of childList) {
144
- // Attrs never appear in children(); only structural nodes recurse.
145
- serializedChildren.push(serializeNode(child, effective));
204
+ // Attrs never appear in children(); only structural nodes recurse. A top-level
205
+ // node's package follows the document rule; deeper nodes keep their own.
206
+ // In a one-package document a node keeps any package it carries itself (the parser
207
+ // gives a root-level field its file's package), so single-package output is unchanged.
208
+ const childSlot: PackageSlot =
209
+ doc === undefined || !doc.perNode ? undefined : child.name !== "" ? topLevelPackage(child, model) : "";
210
+ serializedChildren.push(serializeNode(child, effective, refs, childSlot));
146
211
  }
147
212
 
148
213
  // Attrs ALWAYS emit inline @name (D5 — attrs have no children, one canonical form).
149
214
  for (const [attrName, attrValue] of attrMap) {
150
- obj[`${ATTR_PREFIX}${attrName}`] = attrValue;
215
+ // ADR-0039: own — the authored value lives on the declaring attr node.
216
+ const authored = refs === "authored" && !effective ? model.ownMetaAttr(attrName)?.authoredValue : undefined;
217
+ obj[`${ATTR_PREFIX}${attrName}`] = authored ?? attrValue;
151
218
  }
152
219
 
153
220
  if (serializedChildren.length > 0) {
@@ -167,8 +234,8 @@ function serializeNodeInner(
167
234
  // byte-identical output from the same input metamodel.
168
235
  // ---------------------------------------------------------------------------
169
236
 
170
- export function canonicalSerialize(model: MetaData): string {
171
- const raw = serializeJson(model, { indent: 2 });
237
+ export function canonicalSerialize(model: MetaData, opts?: { refs?: RefSpelling }): string {
238
+ const raw = serializeJson(model, { indent: 2, refs: opts?.refs ?? "canonical" });
172
239
 
173
240
  const parsed = JSON.parse(raw) as unknown;
174
241
  // FR-016 / ADR-0018: rewrite legacy @table → kind-matching alias on source.rdb
@@ -188,7 +255,7 @@ export function canonicalSerialize(model: MetaData): string {
188
255
  * Used by the conformance harness's expected-effective fixtures.
189
256
  */
190
257
  export function canonicalSerializeEffective(model: MetaData): string {
191
- const nodeObj = serializeNode(model, true);
258
+ const nodeObj = serializeNode(model, true, "canonical", undefined);
192
259
  const raw = JSON.stringify(nodeObj, null, 2);
193
260
 
194
261
  const parsed = JSON.parse(raw) as unknown;
@@ -22,6 +22,12 @@ export const TYPE_INDEX = "index";
22
22
  /** Capability / requirement records — `requirement.functional` (existence check)
23
23
  * and `requirement.architectural` (universality check). Ruling amendment 3. */
24
24
  export const TYPE_REQUIREMENT = "requirement";
25
+ /** FR-044 reporting vocabulary — a named group-by attribute (`dimension.attribute`,
26
+ * `dimension.time`), a named aggregate (`measure.aggregate`, `measure.ratio`) and a
27
+ * named row filter (`segment.filter`), all declared on an `object.entity`. */
28
+ export const TYPE_DIMENSION = "dimension";
29
+ export const TYPE_MEASURE = "measure";
30
+ export const TYPE_SEGMENT = "segment";
25
31
 
26
32
  export const BASE_TYPES = [
27
33
  TYPE_METADATA,
@@ -38,6 +44,9 @@ export const BASE_TYPES = [
38
44
  TYPE_TEMPLATE,
39
45
  TYPE_INDEX,
40
46
  TYPE_REQUIREMENT,
47
+ TYPE_DIMENSION,
48
+ TYPE_MEASURE,
49
+ TYPE_SEGMENT,
41
50
  ] as const;
42
51
  export type BaseType = (typeof BASE_TYPES)[number];
43
52
 
@@ -34,7 +34,10 @@ export abstract class MetaData {
34
34
  // order and post-merge tree shape. Mirrors Java's BaseMetaDataParser, which
35
35
  // folds getDefaultPackageName() into the registered name at parse time.
36
36
  fileDefaultPackage?: string;
37
- superRef?: string; // raw super reference string, pre-resolution
37
+ superRef?: string; // super reference string; canonical after the loader's resolveReferences pass
38
+ /** ADR-0059 R5 — the `extends` value exactly as authored, kept when the loader's
39
+ * `resolveReferences` pass rewrites `superRef` to its canonical spelling. */
40
+ authoredSuperRef?: string;
38
41
  private _superData?: MetaData; // post-resolution pointer; set by setSuperResolved() after parsing
39
42
  isAbstract: boolean = false;
40
43
 
@@ -384,6 +387,24 @@ export abstract class MetaData {
384
387
  return this.attrs().get(name);
385
388
  }
386
389
 
390
+ /**
391
+ * ADR-0059 — effective attr value for `name` as the author WROTE it: for a
392
+ * reference the loader rewrote to its canonical spelling, the authored string;
393
+ * for every other attr, the value. For human-facing text (descriptions) only —
394
+ * code that binds a reference reads `attr()`.
395
+ */
396
+ authoredAttr(name: string): AttrValue | undefined {
397
+ assertNotStructuralKeyword(name, "authoredAttr");
398
+ const seen = new Set<MetaData>();
399
+ for (let n: MetaData | undefined = this; n !== undefined && !seen.has(n); n = n._superData) {
400
+ seen.add(n);
401
+ // ADR-0039: own — locating the layer that declares the attr, walking the super chain.
402
+ const node = n._attrNodes.get(name);
403
+ if (node !== undefined) return node.authoredValue ?? node.value;
404
+ }
405
+ return undefined;
406
+ }
407
+
387
408
  /** True if `name` resolves to an effective attr (own or inherited). */
388
409
  hasAttr(name: string): boolean {
389
410
  assertNotStructuralKeyword(name, "hasAttr");
@@ -7,8 +7,18 @@ import { MetaData } from "./meta-data.js";
7
7
  import { TYPE_OBJECT, TYPE_FIELD } from "./base-types.js";
8
8
  import type { MetaObject } from "../core/object/meta-object.js";
9
9
  import type { MetaField } from "../core/field/meta-field.js";
10
+ import { DEFAULT_PHYSICAL_NAMING, type PhysicalNaming } from "../naming.js";
11
+ import { PACKAGE_SEPARATOR } from "./structural.js";
12
+ import { lookupCanonical } from "../naming-refs.js";
10
13
 
11
14
  export class MetaRoot extends MetaData {
15
+ /**
16
+ * The project's physical-naming rules (`naming` in `.metaobjects/config.json`, ADR-0058).
17
+ * Set by the project-aware load (`loadMemory`, `resolveCollection`), read by every name
18
+ * resolver through `physicalNamingOf`. A tree loaded with no project has the default.
19
+ */
20
+ physicalNaming: PhysicalNaming = DEFAULT_PHYSICAL_NAMING;
21
+
12
22
  /** Object entities defined at this root level. */
13
23
  objects(): MetaObject[] {
14
24
  return this.cached("objects", () =>
@@ -26,9 +36,18 @@ export class MetaRoot extends MetaData {
26
36
  );
27
37
  }
28
38
 
29
- /** Find an object by name. */
39
+ /**
40
+ * Find an object by name, or by a canonical reference (ADR-0059): a value with a
41
+ * package separator (`acme::Program`, `::Program`) binds the object whose resolution
42
+ * key matches exactly, so a reference the loader rewrote to canonical form still finds
43
+ * its target. A bare name matches a root-level child by name, as before.
44
+ */
30
45
  findObject(name: string): MetaObject | undefined {
31
46
  return this.cached(`findObject:${name}`, () => {
47
+ if (name.includes(PACKAGE_SEPARATOR)) {
48
+ const node = lookupCanonical(this, name, { ownerType: TYPE_OBJECT });
49
+ return node !== undefined && node.type === TYPE_OBJECT ? (node as MetaObject) : undefined;
50
+ }
32
51
  // ADR-0039: own — the metadata root has no super chain (it never `extends`).
33
52
  const child = this.ownChildByTypeAndName(TYPE_OBJECT, name);
34
53
  return child !== undefined ? (child as MetaObject) : undefined;