@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
@@ -45,7 +45,7 @@ import type { MetaData } from "./shared/meta-data.js";
45
45
  import { ParseError } from "./errors.js";
46
46
  import type { AttrSchema, TypeRegistry } from "./registry.js";
47
47
  import { childRuleMatches } from "./registry.js";
48
- import { TYPE_FIELD, TYPE_METADATA } from "./shared/base-types.js";
48
+ import { TYPE_FIELD } from "./shared/base-types.js";
49
49
  import { ATTR_SUBTYPE_PROPERTIES } from "./core/attr/attr-constants.js";
50
50
  // #337: turns "unknown X" into "retired in V — here is the migration". Changes no load
51
51
  // outcome; retired vocabulary still fails with the same code.
@@ -58,6 +58,7 @@ import {
58
58
  FIELD_ATTR_INT_VALUE_MAP,
59
59
  ENUM_MEMBER_PATTERN,
60
60
  } from "./core/field/field-constants.js";
61
+ import { isSharedEnumDeclaration } from "./core/field/shared-enum.js";
61
62
  import {
62
63
  FIELD_ATTR_DB_COLUMN_TYPE,
63
64
  DB_COLUMN_TYPE_VALUES,
@@ -337,18 +338,13 @@ function validateNode(
337
338
  // only own @values need checking here (mirrors the own-attrs-only policy of
338
339
  // Checks 2+3 above — inherited attrs were validated on the declaring node).
339
340
  if (node.type === TYPE_FIELD && node.subType === FIELD_SUBTYPE_ENUM) {
340
- // #246: the shared-enum super, if any — a root-level abstract field.enum (one
341
- // whose parent is the metadata root, not an object). FR-019 materializes such a
342
- // declaration ONCE per port as a single named type, so anything a consuming
343
- // field re-declares that is part of the shared TYPE's contract is a conflict.
344
- // Immediate-super-only, matching codegen's resolveSharedEnumDecl (enum-shared.ts)
345
- // so the validator and the collapse agree on what "shared" means.
346
- const sharedSuper =
347
- node.superData !== undefined &&
348
- node.superData.isAbstract &&
349
- node.superData.parent?.type === TYPE_METADATA
350
- ? node.superData
351
- : undefined;
341
+ // #246: the shared-enum super, if any. FR-019 materializes a shared declaration
342
+ // ONCE per port as a single named type, so anything a field directly under it
343
+ // re-declares that is part of the shared TYPE's contract is a conflict. The
344
+ // predicate is the one codegen's resolver walks with (core/field/shared-enum.ts).
345
+ // Only the IMMEDIATE super is checked: a redeclaration further down the chain is
346
+ // legal, and the resolver answers it with a per-object inline type.
347
+ const sharedSuper = isSharedEnumDeclaration(node.superData) ? node.superData : undefined;
352
348
 
353
349
  // ADR-0039: own — validates the @values DECLARED on this node; a concrete enum
354
350
  // extending an abstract one inherits already-validated @values (own-attrs-only).
package/src/constants.ts CHANGED
@@ -45,6 +45,7 @@ export * from "./core/index/index-constants.js";
45
45
  export * from "./core/object/object-constants.js";
46
46
  export * from "./core/query/query-constants.js";
47
47
  export * from "./core/relationship/relationship-constants.js";
48
+ export * from "./core/reporting/reporting-constants.js";
48
49
  export * from "./core/validator/validator-constants.js";
49
50
  export * from "./persistence/db/db-constants.js";
50
51
  export * from "./persistence/origin/origin-constants.js";
@@ -14,6 +14,7 @@ import {
14
14
  FILTER_COMPOSE_OR,
15
15
  FILTER_COMPOSE_AND,
16
16
  } from "../query/query-constants.js";
17
+ import { FILTER_RELATIVE_NOW } from "../reporting/reporting-constants.js";
17
18
  import { registerAttrClass } from "../../attr-class-map.js";
18
19
 
19
20
  export class FilterAttr extends MetaAttr {
@@ -60,7 +61,13 @@ function desugarFilterObject(filter: AttrObject): AttrObject {
60
61
  function desugarClause(raw: AttrJson): AttrObject {
61
62
  if (raw === null) return { [FILTER_OP_IS_NULL]: true };
62
63
  if (Array.isArray(raw)) return { [FILTER_OP_IN]: raw };
63
- if (typeof raw === "object") return raw as AttrObject;
64
+ // FR-044 F2: an object carrying a `now` key is a relative-date VALUE, never an
65
+ // op map (`now` is not an operator). Shorthand `{ f: { now: "-P7D" } }` therefore
66
+ // means `eq` like every other shorthand value, and survives as an opaque operand;
67
+ // validation then refuses it (`eq` is not a range op) instead of reading `now` as an op.
68
+ if (typeof raw === "object") {
69
+ return FILTER_RELATIVE_NOW in raw ? { [FILTER_OP_EQ]: raw } : (raw as AttrObject);
70
+ }
64
71
  return { [FILTER_OP_EQ]: raw };
65
72
  }
66
73
 
@@ -49,6 +49,22 @@ const BASE_ATTR_DATA_TYPE: Readonly<Record<string, DataType>> = {
49
49
  };
50
50
 
51
51
  export class MetaAttr extends MetaData implements DataTypeAware {
52
+ /**
53
+ * ADR-0059 R5 — for a reference attribute, the top-level object each reference
54
+ * names, set once by the loader's `resolveReferences` pass. A scalar value holds
55
+ * one node; a list value holds one entry per element (`undefined` where that
56
+ * element did not resolve). Unset when the attr is not a reference or did not
57
+ * resolve.
58
+ */
59
+ resolvedTarget?: MetaData | readonly (MetaData | undefined)[];
60
+
61
+ /**
62
+ * ADR-0059 R5 — the value exactly as authored, kept when `resolveReferences`
63
+ * rewrites `value` to its canonical spelling. `meta fmt` writes this back
64
+ * (`refs: "authored"`). Unset when the value was never rewritten.
65
+ */
66
+ authoredValue?: AttrValue;
67
+
52
68
  /** This attribute's coerced value (stored on the instance under RESERVED_KEY_VALUE). */
53
69
  get value(): AttrValue | undefined {
54
70
  // ADR-0039: own — an attr node's own scalar payload (the RESERVED_KEY_VALUE
@@ -95,7 +111,14 @@ export class MetaAttr extends MetaData implements DataTypeAware {
95
111
  if (dt === DATA_TYPE_STRING) {
96
112
  return typeof value === "string" ? [] : [this._typeError("string", value)];
97
113
  }
98
- if (dt === DATA_TYPE_INT || dt === DATA_TYPE_LONG || dt === DATA_TYPE_DOUBLE) {
114
+ if (dt === DATA_TYPE_INT || dt === DATA_TYPE_LONG) {
115
+ if (typeof value !== "number") return [this._typeError("number", value)];
116
+ // Rule A: an integral number (4, 4.0, 1e2) is an int; a fraction is not.
117
+ return Number.isInteger(value)
118
+ ? []
119
+ : [{ message: `attribute '@${this.name}' must be of type '${this.subType}' but got the fraction ${value}; this attribute is integral, so write an integer (for a validator @min/@max, use an integer bound)` }];
120
+ }
121
+ if (dt === DATA_TYPE_DOUBLE) {
99
122
  return typeof value === "number" ? [] : [this._typeError("number", value)];
100
123
  }
101
124
  if (dt === DATA_TYPE_BOOLEAN) {
@@ -0,0 +1,53 @@
1
+ // Shared enum declarations — the single authority for "which type does this enum
2
+ // field have" (FR-019, ADR-0026 Amendment 1). The loader's #246 check and every
3
+ // codegen site that names, references, imports or materializes an enum type call
4
+ // these two functions; neither keeps a predicate of its own.
5
+
6
+ import { TYPE_FIELD, TYPE_METADATA } from "../../shared/base-types.js";
7
+ import type { MetaData } from "../../shared/meta-data.js";
8
+ import { FIELD_ATTR_INT_VALUE_MAP, FIELD_ATTR_VALUES, FIELD_SUBTYPE_ENUM } from "./field-constants.js";
9
+ import type { MetaField } from "./meta-field.js";
10
+
11
+ function isEnumField(node: MetaData | undefined): node is MetaField {
12
+ return node !== undefined && node.type === TYPE_FIELD && node.subType === FIELD_SUBTYPE_ENUM;
13
+ }
14
+
15
+ /**
16
+ * True when `node` is a shared enum declaration: an abstract `field.enum` that is a
17
+ * direct child of the metadata root (a package-level sibling of `object.entity`). An
18
+ * abstract enum nested inside an object is not one.
19
+ */
20
+ export function isSharedEnumDeclaration(node: MetaData | undefined): node is MetaField {
21
+ return isEnumField(node) && isRootAbstract(node);
22
+ }
23
+
24
+ function isRootAbstract(field: MetaField): boolean {
25
+ return field.isAbstract && field.parent?.type === TYPE_METADATA;
26
+ }
27
+
28
+ /**
29
+ * The shared enum declaration an enum field's type resolves to, or undefined when the
30
+ * field keeps a per-object inline type.
31
+ *
32
+ * Walks from the field itself up its `extends` chain. At each node, in order: a shared
33
+ * declaration answers (the nearest one wins, so `Money extends Currency` answers
34
+ * `Money`); a node that declares its own `@values` or `@intValueMap` starts a new
35
+ * vocabulary and answers undefined; otherwise the walk continues at the super. A
36
+ * non-enum node, the end of the chain or a cycle answers undefined. The hosting
37
+ * object's subtype and the number of hops play no part.
38
+ */
39
+ export function resolveSharedEnumDeclaration(field: MetaData | undefined): MetaField | undefined {
40
+ const seen = new Set<MetaData>();
41
+ let node = field;
42
+ while (isEnumField(node) && !seen.has(node)) {
43
+ if (isRootAbstract(node)) return node;
44
+ // ADR-0039 own (declared-here layer): the question is whether THIS node redeclares
45
+ // the vocabulary. A resolving read would see the inherited @values on every hop and
46
+ // stop the walk at the first one.
47
+ const own = node.ownAttrs();
48
+ if (own.has(FIELD_ATTR_VALUES) || own.has(FIELD_ATTR_INT_VALUE_MAP)) return undefined;
49
+ seen.add(node);
50
+ node = node.superData;
51
+ }
52
+ return undefined;
53
+ }
@@ -16,6 +16,9 @@ import {
16
16
  IDENTITY_REFERENCE_ATTR_ON_UPDATE,
17
17
  } from "./identity-constants.js";
18
18
  import type { MetaRoot } from "../../shared/meta-root.js";
19
+ import type { MetaObject } from "../object/meta-object.js";
20
+ import { TYPE_OBJECT } from "../../shared/base-types.js";
21
+ import { refTarget } from "../../naming-refs.js";
19
22
 
20
23
  /** Strongly-typed identity generation strategies. */
21
24
  export type IdentityGeneration = "increment" | "uuid" | "assigned";
@@ -144,7 +147,7 @@ export class MetaReferenceIdentity extends MetaIdentity {
144
147
  * points at. Priority: explicit dotted-form `@references: "Entity.field"`
145
148
  * override → target entity's primary identity field → "id" fallback.
146
149
  *
147
- * Returns undefined only if the target entity cannot be found in `root`.
150
+ * Returns undefined only if the target entity does not resolve.
148
151
  * Centralizes the FK-target resolution rule used by projection view DDL,
149
152
  * Drizzle schema emit, and migration schema comparison.
150
153
  */
@@ -152,17 +155,11 @@ export class MetaReferenceIdentity extends MetaIdentity {
152
155
  const explicit = this.targetFields[0];
153
156
  if (explicit !== undefined) return explicit;
154
157
 
155
- const targetName = this.targetEntity;
156
- if (targetName === undefined) return undefined;
157
- // targetEntity may be package-qualified (FQN); findObject is keyed by bare
158
- // name, so fall back to the bare suffix after the last "::". Mirrors the
159
- // resolveTargetTable fix; without it a cross-package reference resolves no
160
- // target and the PK column wrongly defaults to "id".
161
- const targetObj = root.findObject(targetName)
162
- ?? (targetName.includes("::")
163
- ? root.findObject(targetName.slice(targetName.lastIndexOf("::") + 2))
164
- : undefined);
165
- if (!targetObj) return undefined;
158
+ // ADR-0059 R7: the target the loader bound for @references, else (a tree built
159
+ // without the loader) the value resolved from this reference's package.
160
+ const bound = refTarget(this, IDENTITY_REFERENCE_ATTR_REFERENCES);
161
+ if (bound === undefined || bound.type !== TYPE_OBJECT) return undefined;
162
+ const targetObj = bound as MetaObject;
166
163
 
167
164
  const primary = targetObj.primaryIdentity();
168
165
  // ADR-0039: resolving — the target's @fields may be inherited via extends.
package/src/core/index.ts CHANGED
@@ -16,3 +16,8 @@ export { UriSource } from "../loader/sources/uri-source.js";
16
16
  export { parseYaml } from "./parser-yaml.js";
17
17
  export { loadAndExportJson } from "./export-json.js";
18
18
  export type { ExportResult } from "./export-json.js";
19
+
20
+ // The `meta verify` field lint's structural pre-parse walk. Lives here, not on the
21
+ // root entry, because it parses YAML.
22
+ export { declaredDuplicateFields } from "../loader/declared-duplicate-fields.js";
23
+ export type { DeclaredDuplicateField } from "../loader/declared-duplicate-fields.js";
@@ -14,12 +14,15 @@ import { SUBTYPE_BASE } from "../../shared/base-types.js";
14
14
  export const OBJECT_SUBTYPE_ENTITY = "entity";
15
15
  export const OBJECT_SUBTYPE_VALUE = "value";
16
16
  export const OBJECT_SUBTYPE_PROJECTION = "projection";
17
+ // - report : declared dimensions x measures of one entity, compiled to a read-only view (FR-044)
18
+ export const OBJECT_SUBTYPE_REPORT = "report";
17
19
 
18
20
  export const OBJECT_SUBTYPES = [
19
21
  SUBTYPE_BASE,
20
22
  OBJECT_SUBTYPE_ENTITY,
21
23
  OBJECT_SUBTYPE_VALUE,
22
24
  OBJECT_SUBTYPE_PROJECTION,
25
+ OBJECT_SUBTYPE_REPORT,
23
26
  ] as const;
24
27
  export type ObjectSubType = (typeof OBJECT_SUBTYPES)[number];
25
28
 
@@ -49,3 +52,19 @@ export const OBJECT_ATTR_DISCRIMINATOR_VALUE = "discriminatorValue";
49
52
  * but resolves against the projection's OWN declared fields rather than one
50
53
  * aggregated entity. An aggregate-derived field is not addressable (fail-closed). */
51
54
  export const OBJECT_PROJECTION_ATTR_FILTER = "filter";
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // FR-044 — object.report attrs
58
+ // ---------------------------------------------------------------------------
59
+ /** The object.entity whose rows the report aggregates. */
60
+ export const OBJECT_REPORT_ATTR_FROM = "from";
61
+ /** Dimension names of @from; a time dimension is written `name:grain`. */
62
+ export const OBJECT_REPORT_ATTR_DIMENSIONS = "dimensions";
63
+ /** Measure names of @from. */
64
+ export const OBJECT_REPORT_ATTR_MEASURES = "measures";
65
+ /** Optional segment of @from scoping the rows. */
66
+ export const OBJECT_REPORT_ATTR_SEGMENT = "segment";
67
+ /** Optional row scope over @from's fields (an attr.filter). */
68
+ export const OBJECT_REPORT_ATTR_FILTER = "filter";
69
+ /** Optional to-one path from @from to the entity whose rows supply the report's rows (R8). */
70
+ export const OBJECT_REPORT_ATTR_SPINE = "spine";
@@ -80,6 +80,27 @@ export const OBJECT_DEFINITION: ProviderDefinition = {
80
80
  "min": 0,
81
81
  "max": null
82
82
  },
83
+ {
84
+ "type": "dimension",
85
+ "subType": "*",
86
+ "name": "*",
87
+ "min": 0,
88
+ "max": null
89
+ },
90
+ {
91
+ "type": "measure",
92
+ "subType": "*",
93
+ "name": "*",
94
+ "min": 0,
95
+ "max": null
96
+ },
97
+ {
98
+ "type": "segment",
99
+ "subType": "*",
100
+ "name": "*",
101
+ "min": 0,
102
+ "max": null
103
+ },
83
104
  {
84
105
  "type": "attr",
85
106
  "subType": "string",
@@ -132,6 +153,65 @@ export const OBJECT_DEFINITION: ProviderDefinition = {
132
153
  "description": "Optional row-scope predicate (a portable attr.filter object: eq/ne/gt/gte/lt/lte/like/in/isNull with and/or, desugared to { field: { op: value } } at parse time) selecting which rows the view returns — lowered to an outer SQL WHERE. Resolves against the projection's own declared fields; an aggregate-derived field is not addressable (fail-closed)."
133
154
  }
134
155
  ]
156
+ },
157
+ {
158
+ "type": "object",
159
+ "subType": "report",
160
+ "extendsBase": true,
161
+ "description": "A declared report (FR-044): a fixed combination of dimensions and measures of ONE entity (@from), compiled to a read-only view. One row per distinct dimension tuple; no dimensions means exactly one row. Fields are DERIVED, not declared: one per dimension (a time dimension's field is <dimension><Grain>, e.g. purchasedAtDay) and one per measure. Every measure must belong to @from (two fact tables are two reports). @filter and @segment scope rows before grouping and combine by AND. Read-only: no writes, no get-by-id.",
162
+ "whenToUse": "Dashboard totals, per-day series and per-group summaries that would otherwise be hand-written GROUP BY queries.",
163
+ "children": [
164
+ {
165
+ "type": "attr",
166
+ "subType": "string",
167
+ "name": "from",
168
+ "min": 1,
169
+ "max": 1,
170
+ "description": "The object.entity whose rows the report aggregates."
171
+ },
172
+ {
173
+ "type": "attr",
174
+ "subType": "string",
175
+ "name": "dimensions",
176
+ "isArray": true,
177
+ "min": 0,
178
+ "max": 1,
179
+ "description": "Dimension names of @from; a time dimension is written 'name:grain'. Absent means one global row."
180
+ },
181
+ {
182
+ "type": "attr",
183
+ "subType": "string",
184
+ "name": "measures",
185
+ "isArray": true,
186
+ "min": 1,
187
+ "max": 1,
188
+ "description": "Measure names of @from."
189
+ },
190
+ {
191
+ "type": "attr",
192
+ "subType": "string",
193
+ "name": "segment",
194
+ "min": 0,
195
+ "max": 1,
196
+ "description": "Optional segment of @from scoping the rows."
197
+ },
198
+ {
199
+ "type": "attr",
200
+ "subType": "filter",
201
+ "name": "filter",
202
+ "min": 0,
203
+ "max": 1,
204
+ "description": "Optional row scope over @from's fields; may use relative-date values."
205
+ },
206
+ {
207
+ "type": "attr",
208
+ "subType": "string",
209
+ "name": "spine",
210
+ "min": 0,
211
+ "max": 1,
212
+ "description": "Optional to-one path from @from to the entity whose rows supply the report's rows (e.g. 'Purchase.program'), written like a dimension's @via. With it the report has one row per distinct dimension tuple among THAT entity's rows, including the ones no row of @from refers to: a count there is 0 and any other measure is null unless it declares @default. Every listed dimension must be reached through this path. @segment and @filter still scope the rows of @from and never remove a row. A row of @from whose reference is null or matches nothing is in no row of the report."
213
+ }
214
+ ]
135
215
  }
136
216
  ]
137
217
  };
@@ -44,7 +44,9 @@ import type { MetaRoot } from "../../shared/meta-root.js";
44
44
  import type { MetaRelationship } from "./meta-relationship.js";
45
45
  import type { MetaReferenceIdentity } from "../identity/meta-identity.js";
46
46
  import { stripPackage } from "../../naming.js";
47
- import { resolveObjectRef } from "../../naming-refs.js";
47
+ import { refTarget } from "../../naming-refs.js";
48
+ import { IDENTITY_REFERENCE_ATTR_REFERENCES } from "../identity/identity-constants.js";
49
+ import { RELATIONSHIP_ATTR_OBJECT_REF, RELATIONSHIP_ATTR_THROUGH } from "./relationship-constants.js";
48
50
  import { TYPE_OBJECT } from "../../shared/base-types.js";
49
51
  import { PACKAGE_SEPARATOR } from "../../shared/structural.js";
50
52
 
@@ -69,37 +71,6 @@ function refFkField(ref: MetaReferenceIdentity): string | undefined {
69
71
  return ref.fields[0];
70
72
  }
71
73
 
72
- /**
73
- * The root entity a reference name denotes, or undefined — resolved by the ONE
74
- * matcher the loader uses (`resolveObjectRef`, ADR-0041/0042): a fully-qualified
75
- * name binds exactly on its resolution key, a bare name binds in the REFERRER's
76
- * package.
77
- *
78
- * This exists so the SUBJECT comparison below can be made on object IDENTITY.
79
- * A bare-name compare cannot tell `a::NodeBase` from `b::NodeBase`, which made a
80
- * genuine cross-package hetero M:N read as a self-join the moment the subject
81
- * set held two names — and the first-match-wins bare arm this used to carry
82
- * (Java's deferred #174) bound a bare name to whichever same-short-named entity
83
- * happened to load first, which is load-ORDER dependence, not a naming rule.
84
- */
85
- function findEntity(
86
- root: MetaRoot,
87
- name: string | undefined,
88
- referrerPkg: string,
89
- ): MetaObject | undefined {
90
- if (name === undefined || name === "") return undefined;
91
- // ADR-0041/0042 through the single resolveObjectRef matcher — an FQN resolves
92
- // exactly on its resolution key, a BARE name resolves in the REFERRER's
93
- // package. The bare arm used to be `objects().find(o => o.name === bare)`,
94
- // which took whichever same-short-named entity loaded first: a bare
95
- // `@objectRef: "Tag"` on an abstract base in package `base` bound to
96
- // `acme::Tag` merely because the acme source was read first. Callers pass the
97
- // referrer that owns the name they are resolving — the DECLARING entity for
98
- // the relationship's own @objectRef, the JUNCTION for its references' —
99
- // because those are different packages whenever a base is inherited across one.
100
- return resolveObjectRef(root, name, referrerPkg).node as MetaObject | undefined;
101
- }
102
-
103
74
  /**
104
75
  * Derive the source/target junction FK fields for a M:N relationship.
105
76
  *
@@ -108,7 +79,7 @@ function findEntity(
108
79
  * @param source the entity the caller is navigating from. Accepted alongside
109
80
  * `rel.parent` as a name for the relationship's subject, and used
110
81
  * as the declaring entity when `rel` has no entity parent.
111
- * @param root the loaded model root (to find the junction entity)
82
+ * @param root the loaded model root
112
83
  * @throws M2MDerivationError when the junction is missing/malformed or the
113
84
  * self-join is ambiguous.
114
85
  */
@@ -134,9 +105,10 @@ export function deriveM2MFields(
134
105
  `relationship "${declaringEntity.name}.${rel.name}" is missing @through (required for M:N derivation)`,
135
106
  );
136
107
  }
137
- // ADR-0041/0042 — resolve @through through the SAME matcher the loader uses
138
- // (resolveObjectRef, via validation-passes' _findObject): an FQN resolves
139
- // exactly on its resolution key, a bare name resolves package-locally.
108
+ // ADR-0059 R7 — @through, @objectRef and the junction's @references are read
109
+ // as bound targets (refTarget), each resolved in the package of the node that
110
+ // declares it, so an inherited reference keeps the meaning it had where it was
111
+ // written.
140
112
  //
141
113
  // This used to be `root.findObject(fqn)` with a fall back to the bare suffix
142
114
  // after the last "::". findObject is keyed by BARE name, so the FQN lookup
@@ -147,12 +119,7 @@ export function deriveM2MFields(
147
119
  // nowhere near the navigating entity. The pairing then failed, so a valid
148
120
  // cross-package M:N emitted no traversal route in TS and C# and failed the
149
121
  // build in Java, Kotlin and Python.
150
- //
151
- // Note findEntity() below was ALREADY FQN-exact for the same reason (see its
152
- // doc comment). The two halves of one function disagreed about how a
153
- // package-qualified name resolves; only the junction half was wrong.
154
- const referrerPkg = declaringEntity.package ?? declaringEntity.fileDefaultPackage ?? "";
155
- const junction = resolveObjectRef(root, throughName, referrerPkg).node as MetaObject | undefined;
122
+ const junction = refTarget(rel, RELATIONSHIP_ATTR_THROUGH) as MetaObject | undefined;
156
123
  if (junction === undefined) {
157
124
  throw new M2MDerivationError(
158
125
  `relationship "${declaringEntity.name}.${rel.name}" @through "${throughName}" does not resolve to an entity`,
@@ -202,16 +169,16 @@ export function deriveM2MFields(
202
169
  name !== undefined && subjectNames.includes(stripPackage(name));
203
170
 
204
171
  // The junction owns its references' @references names, so those resolve in the
205
- // JUNCTION's package; the relationship's own @objectRef resolves in the
206
- // DECLARING entity's. Under `extends` across packages these differ, and using
207
- // one for both is what bound a bare @objectRef to the wrong same-named entity.
208
- const junctionPkg = junction.package ?? junction.fileDefaultPackage ?? "";
172
+ // package of the node that declares them; the relationship's own @objectRef in
173
+ // the package of the node that declares it. Under `extends` across packages these
174
+ // differ, and using one for both is what bound a bare @objectRef to the wrong
175
+ // same-named entity.
209
176
  const refEntity = (r: MetaReferenceIdentity): MetaObject | undefined =>
210
- findEntity(root, r.targetEntity, junctionPkg);
177
+ refTarget(r, IDENTITY_REFERENCE_ATTR_REFERENCES) as MetaObject | undefined;
211
178
 
212
179
  // Defensive bare fallback when @objectRef does not resolve — loader validation
213
180
  // normally guarantees it does. Same carve-out the Java reference makes.
214
- const targetEntityNode = findEntity(root, targetName, referrerPkg);
181
+ const targetEntityNode = refTarget(rel, RELATIONSHIP_ATTR_OBJECT_REF) as MetaObject | undefined;
215
182
  const isSelfJoin = targetEntityNode !== undefined
216
183
  ? isSubject(targetEntityNode)
217
184
  : isSubjectName(targetName);
@@ -8,7 +8,8 @@
8
8
 
9
9
  import type { MetaObject } from "../object/meta-object.js";
10
10
  import type { MetaReferenceIdentity } from "../identity/meta-identity.js";
11
- import { stripPackage } from "../../naming.js";
11
+ import { refTarget } from "../../naming-refs.js";
12
+ import { IDENTITY_REFERENCE_ATTR_REFERENCES } from "../identity/identity-constants.js";
12
13
 
13
14
  export interface ReferenceLookup {
14
15
  /** The entity whose identity.reference points at `other`. */
@@ -39,7 +40,8 @@ export function findReferencesBetween(
39
40
  const found: ReferenceLookup[] = [];
40
41
  for (const [holder, other] of [[a, b], [b, a]] as const) {
41
42
  for (const ref of holder.referenceIdentities()) {
42
- if (stripPackage(ref.targetEntity) === other.name) {
43
+ // ADR-0059 R7: compare the bound target, never a short name.
44
+ if (refTarget(ref, IDENTITY_REFERENCE_ATTR_REFERENCES) === other) {
43
45
  found.push({ holder, other, referenceIdentity: ref });
44
46
  }
45
47
  }
@@ -10,7 +10,9 @@
10
10
 
11
11
  import type { MetaObject } from "../object/meta-object.js";
12
12
  import type { MetaReferenceIdentity } from "../identity/meta-identity.js";
13
- import { stripPackage } from "../../naming.js";
13
+ import type { MetaData } from "../../shared/meta-data.js";
14
+ import { refTarget } from "../../naming-refs.js";
15
+ import { IDENTITY_REFERENCE_ATTR_REFERENCES } from "../identity/identity-constants.js";
14
16
 
15
17
  /**
16
18
  * Trailing suffixes stripped from a CANDIDATE's name/FK field when building its
@@ -51,20 +53,20 @@ export function referencePairingKeys(ref: MetaReferenceIdentity): Set<string> {
51
53
  }
52
54
 
53
55
  /**
54
- * Every identity.reference on `holder` whose @references targets `targetEntity`.
55
- * Package-insensitive on both sides: @references and @objectRef may each be bare
56
- * or fully qualified.
56
+ * Every identity.reference on `holder` whose @references targets `targetEntity`: a
57
+ * reference matches when its @references is bound to that node (ADR-0059 R7: identity,
58
+ * never a short name). An unresolved target (undefined) matches nothing.
57
59
  */
58
60
  export function referenceCandidatesFor(
59
61
  holder: MetaObject,
60
- targetEntity: string,
62
+ targetEntity: MetaData | undefined,
61
63
  ): MetaReferenceIdentity[] {
62
- const target = stripPackage(targetEntity);
64
+ if (targetEntity === undefined) return [];
63
65
  // ADR-0039: resolving — referenceIdentities() honors references inherited via extends.
64
66
  return holder
65
67
  .referenceIdentities()
66
- .filter((ref) => stripPackage(ref.targetEntity ?? "") === target)
67
- .filter((ref) => refFkField(ref) !== undefined);
68
+ .filter((ref) => refFkField(ref) !== undefined)
69
+ .filter((ref) => refTarget(ref, IDENTITY_REFERENCE_ATTR_REFERENCES) === targetEntity);
68
70
  }
69
71
 
70
72
  /**
@@ -82,7 +84,7 @@ export function referenceCandidatesFor(
82
84
  export function resolveRelationshipReference(
83
85
  holder: MetaObject,
84
86
  relationshipName: string,
85
- targetEntity: string,
87
+ targetEntity: MetaData | undefined,
86
88
  sourceRefField?: string,
87
89
  ): MetaReferenceIdentity | undefined {
88
90
  const candidates = referenceCandidatesFor(holder, targetEntity);
@@ -0,0 +1,41 @@
1
+ // MetaDimension — concrete node class for type=dimension nodes (FR-044).
2
+ //
3
+ // Extends MetaData directly. Accessors are RESOLVING (ADR-0039): a dimension
4
+ // declared on an abstract base entity is read through the same accessors as one
5
+ // declared on the concrete entity.
6
+
7
+ import { MetaData } from "../../shared/meta-data.js";
8
+ import {
9
+ DIMENSION_SUBTYPE_TIME,
10
+ REPORTING_ATTR_GRAINS,
11
+ REPORTING_ATTR_OF,
12
+ REPORTING_ATTR_VIA,
13
+ TIME_GRAINS,
14
+ type TimeGrain,
15
+ } from "./reporting-constants.js";
16
+
17
+ export class MetaDimension extends MetaData {
18
+ /** True for `dimension.time` (grain truncation); false for `dimension.attribute`. */
19
+ isTime(): boolean {
20
+ return this.subType === DIMENSION_SUBTYPE_TIME;
21
+ }
22
+
23
+ /** Dotted `Entity.field` reference naming the grouped column. */
24
+ of(): string | undefined {
25
+ const v = this.attr(REPORTING_ATTR_OF);
26
+ return typeof v === "string" ? v : undefined;
27
+ }
28
+
29
+ /** Optional dotted to-one relationship path from the owning entity to the `@of` entity. */
30
+ via(): string | undefined {
31
+ const v = this.attr(REPORTING_ATTR_VIA);
32
+ return typeof v === "string" ? v : undefined;
33
+ }
34
+
35
+ /** The grains a `dimension.time` supports (empty for `dimension.attribute`). */
36
+ grains(): TimeGrain[] {
37
+ const v = this.attr(REPORTING_ATTR_GRAINS);
38
+ const items = Array.isArray(v) ? v : typeof v === "string" ? [v] : [];
39
+ return items.filter((g): g is TimeGrain => (TIME_GRAINS as readonly unknown[]).includes(g));
40
+ }
41
+ }
@@ -0,0 +1,75 @@
1
+ // MetaMeasure — concrete node class for type=measure nodes (FR-044).
2
+ //
3
+ // Extends MetaData directly. Accessors are RESOLVING (ADR-0039).
4
+
5
+ import { MetaData } from "../../shared/meta-data.js";
6
+ import {
7
+ MEASURE_AGGS,
8
+ MEASURE_SUBTYPE_RATIO,
9
+ REPORTING_ATTR_AGG,
10
+ REPORTING_ATTR_DEFAULT,
11
+ REPORTING_ATTR_DENOMINATOR,
12
+ REPORTING_ATTR_DISTINCT,
13
+ REPORTING_ATTR_FILTER,
14
+ REPORTING_ATTR_NUMERATOR,
15
+ REPORTING_ATTR_OF,
16
+ REPORTING_ATTR_SEGMENT,
17
+ type MeasureAgg,
18
+ } from "./reporting-constants.js";
19
+
20
+ function optionalString(v: unknown): string | undefined {
21
+ return typeof v === "string" ? v : undefined;
22
+ }
23
+
24
+ export class MetaMeasure extends MetaData {
25
+ /** True for `measure.ratio`; false for `measure.aggregate`. */
26
+ isRatio(): boolean {
27
+ return this.subType === MEASURE_SUBTYPE_RATIO;
28
+ }
29
+
30
+ /** The aggregate function (`measure.aggregate` only). */
31
+ agg(): MeasureAgg | undefined {
32
+ const v = this.attr(REPORTING_ATTR_AGG);
33
+ return (MEASURE_AGGS as readonly unknown[]).includes(v) ? (v as MeasureAgg) : undefined;
34
+ }
35
+
36
+ /** True when `@distinct` is set (legal only with `@agg: count`). */
37
+ distinct(): boolean {
38
+ return this.attr(REPORTING_ATTR_DISTINCT) === true;
39
+ }
40
+
41
+ /** The `Entity.field` references in `@of`: a bare string is one column, a list is the tuple form. */
42
+ ofColumns(): string[] {
43
+ const v = this.attr(REPORTING_ATTR_OF);
44
+ if (Array.isArray(v)) return v.filter((x): x is string => typeof x === "string");
45
+ return typeof v === "string" ? [v] : [];
46
+ }
47
+
48
+ /** Name of a segment declared on the same entity; combines with `@filter` by AND. */
49
+ segmentName(): string | undefined {
50
+ return optionalString(this.attr(REPORTING_ATTR_SEGMENT));
51
+ }
52
+
53
+ /** The canonical row-scope filter, when one is declared. */
54
+ filter(): Record<string, unknown> | undefined {
55
+ const v = this.attr(REPORTING_ATTR_FILTER);
56
+ return typeof v === "object" && v !== null && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;
57
+ }
58
+
59
+ /** `@default`: the integer this measure reads when it would otherwise be null (rule M7/M8
60
+ * decide where it is legal). ADR-0039: resolving, so an inherited measure keeps it. */
61
+ defaultValue(): number | undefined {
62
+ const v = this.attr(REPORTING_ATTR_DEFAULT);
63
+ return typeof v === "number" && Number.isInteger(v) ? v : undefined;
64
+ }
65
+
66
+ /** Name of the `measure.aggregate` sibling used as the numerator (`measure.ratio` only). */
67
+ numerator(): string | undefined {
68
+ return optionalString(this.attr(REPORTING_ATTR_NUMERATOR));
69
+ }
70
+
71
+ /** Name of the `measure.aggregate` sibling used as the denominator (`measure.ratio` only). */
72
+ denominator(): string | undefined {
73
+ return optionalString(this.attr(REPORTING_ATTR_DENOMINATOR));
74
+ }
75
+ }