@metaobjectsdev/metadata 1.0.13 → 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.
- package/README.md +2 -0
- package/dist/attr-schema-validate.d.ts.map +1 -1
- package/dist/attr-schema-validate.js +9 -12
- package/dist/attr-schema-validate.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/attr/meta-attr-filter.d.ts.map +1 -1
- package/dist/core/attr/meta-attr-filter.js +8 -2
- package/dist/core/attr/meta-attr-filter.js.map +1 -1
- package/dist/core/attr/meta-attr.d.ts +14 -0
- package/dist/core/attr/meta-attr.d.ts.map +1 -1
- package/dist/core/attr/meta-attr.js +23 -1
- package/dist/core/attr/meta-attr.js.map +1 -1
- package/dist/core/field/shared-enum.d.ts +21 -0
- package/dist/core/field/shared-enum.d.ts.map +1 -0
- package/dist/core/field/shared-enum.js +49 -0
- package/dist/core/field/shared-enum.js.map +1 -0
- package/dist/core/identity/meta-identity.d.ts +1 -1
- package/dist/core/identity/meta-identity.d.ts.map +1 -1
- package/dist/core/identity/meta-identity.js +8 -13
- package/dist/core/identity/meta-identity.js.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/object/object-constants.d.ts +14 -1
- package/dist/core/object/object-constants.d.ts.map +1 -1
- package/dist/core/object/object-constants.js +18 -0
- package/dist/core/object/object-constants.js.map +1 -1
- package/dist/core/object/object-definition.embedded.d.ts.map +1 -1
- package/dist/core/object/object-definition.embedded.js +80 -0
- package/dist/core/object/object-definition.embedded.js.map +1 -1
- package/dist/core/relationship/derive-m2m-fields.d.ts +1 -1
- package/dist/core/relationship/derive-m2m-fields.d.ts.map +1 -1
- package/dist/core/relationship/derive-m2m-fields.js +15 -44
- package/dist/core/relationship/derive-m2m-fields.js.map +1 -1
- package/dist/core/relationship/find-reference.d.ts.map +1 -1
- package/dist/core/relationship/find-reference.js +4 -2
- package/dist/core/relationship/find-reference.js.map +1 -1
- package/dist/core/relationship/resolve-relationship-reference.d.ts +6 -5
- package/dist/core/relationship/resolve-relationship-reference.d.ts.map +1 -1
- package/dist/core/relationship/resolve-relationship-reference.js +9 -7
- package/dist/core/relationship/resolve-relationship-reference.js.map +1 -1
- package/dist/core/reporting/meta-dimension.d.ts +13 -0
- package/dist/core/reporting/meta-dimension.d.ts.map +1 -0
- package/dist/core/reporting/meta-dimension.js +30 -0
- package/dist/core/reporting/meta-dimension.js.map +1 -0
- package/dist/core/reporting/meta-measure.d.ts +24 -0
- package/dist/core/reporting/meta-measure.d.ts.map +1 -0
- package/dist/core/reporting/meta-measure.js +54 -0
- package/dist/core/reporting/meta-measure.js.map +1 -0
- package/dist/core/reporting/meta-segment.d.ts +6 -0
- package/dist/core/reporting/meta-segment.d.ts.map +1 -0
- package/dist/core/reporting/meta-segment.js +13 -0
- package/dist/core/reporting/meta-segment.js.map +1 -0
- package/dist/core/reporting/report-accessors.d.ts +27 -0
- package/dist/core/reporting/report-accessors.d.ts.map +1 -0
- package/dist/core/reporting/report-accessors.js +56 -0
- package/dist/core/reporting/report-accessors.js.map +1 -0
- package/dist/core/reporting/report-describe.d.ts +59 -0
- package/dist/core/reporting/report-describe.d.ts.map +1 -0
- package/dist/core/reporting/report-describe.js +182 -0
- package/dist/core/reporting/report-describe.js.map +1 -0
- package/dist/core/reporting/report-read-model.d.ts +34 -0
- package/dist/core/reporting/report-read-model.d.ts.map +1 -0
- package/dist/core/reporting/report-read-model.js +165 -0
- package/dist/core/reporting/report-read-model.js.map +1 -0
- package/dist/core/reporting/report-shape.d.ts +103 -0
- package/dist/core/reporting/report-shape.d.ts.map +1 -0
- package/dist/core/reporting/report-shape.js +398 -0
- package/dist/core/reporting/report-shape.js.map +1 -0
- package/dist/core/reporting/reporting-constants.d.ts +65 -0
- package/dist/core/reporting/reporting-constants.d.ts.map +1 -0
- package/dist/core/reporting/reporting-constants.js +84 -0
- package/dist/core/reporting/reporting-constants.js.map +1 -0
- package/dist/core/reporting/reporting-definition.embedded.d.ts +3 -0
- package/dist/core/reporting/reporting-definition.embedded.d.ts.map +1 -0
- package/dist/core/reporting/reporting-definition.embedded.js +185 -0
- package/dist/core/reporting/reporting-definition.embedded.js.map +1 -0
- package/dist/core/requirement/resolve-claim.d.ts +8 -0
- package/dist/core/requirement/resolve-claim.d.ts.map +1 -1
- package/dist/core/requirement/resolve-claim.js +21 -0
- package/dist/core/requirement/resolve-claim.js.map +1 -1
- package/dist/core/yaml-desugar.d.ts.map +1 -1
- package/dist/core/yaml-desugar.js +12 -82
- package/dist/core/yaml-desugar.js.map +1 -1
- package/dist/core-types.d.ts.map +1 -1
- package/dist/core-types.js +28 -1
- package/dist/core-types.js.map +1 -1
- package/dist/docs-ref.d.ts +19 -0
- package/dist/docs-ref.d.ts.map +1 -0
- package/dist/docs-ref.js +37 -0
- package/dist/docs-ref.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -6
- package/dist/errors.js.map +1 -1
- package/dist/fmt.d.ts.map +1 -1
- package/dist/fmt.js +3 -1
- package/dist/fmt.js.map +1 -1
- package/dist/index.d.ts +15 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/loader/declared-duplicate-fields.d.ts +19 -0
- package/dist/loader/declared-duplicate-fields.d.ts.map +1 -0
- package/dist/loader/declared-duplicate-fields.js +97 -0
- package/dist/loader/declared-duplicate-fields.js.map +1 -0
- package/dist/loader/meta-data-loader.d.ts.map +1 -1
- package/dist/loader/meta-data-loader.js +18 -1
- package/dist/loader/meta-data-loader.js.map +1 -1
- package/dist/loader/reporting-validation.d.ts +11 -0
- package/dist/loader/reporting-validation.d.ts.map +1 -0
- package/dist/loader/reporting-validation.js +883 -0
- package/dist/loader/reporting-validation.js.map +1 -0
- package/dist/loader/resolve-references.d.ts +8 -0
- package/dist/loader/resolve-references.d.ts.map +1 -0
- package/dist/loader/resolve-references.js +132 -0
- package/dist/loader/resolve-references.js.map +1 -0
- package/dist/loader/validation-passes.d.ts.map +1 -1
- package/dist/loader/validation-passes.js +14 -9
- package/dist/loader/validation-passes.js.map +1 -1
- package/dist/loader/validation-registry.d.ts.map +1 -1
- package/dist/loader/validation-registry.js +33 -10
- package/dist/loader/validation-registry.js.map +1 -1
- package/dist/metamodel-docs/index.js +3 -3
- package/dist/metamodel-docs/index.js.map +1 -1
- package/dist/metamodel-docs/render.js +4 -4
- package/dist/metamodel-docs/render.js.map +1 -1
- package/dist/naming-refs.d.ts +102 -50
- package/dist/naming-refs.d.ts.map +1 -1
- package/dist/naming-refs.js +223 -97
- package/dist/naming-refs.js.map +1 -1
- package/dist/naming.d.ts +42 -7
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +43 -5
- package/dist/naming.js.map +1 -1
- package/dist/object-serializer.d.ts.map +1 -1
- package/dist/object-serializer.js +6 -9
- package/dist/object-serializer.js.map +1 -1
- package/dist/parser-core.d.ts.map +1 -1
- package/dist/parser-core.js +18 -48
- package/dist/parser-core.js.map +1 -1
- package/dist/persistence/db/db-definition.embedded.js +2 -2
- package/dist/persistence/db/db-definition.embedded.js.map +1 -1
- package/dist/persistence/source/meta-source.d.ts +5 -3
- package/dist/persistence/source/meta-source.d.ts.map +1 -1
- package/dist/persistence/source/meta-source.js +8 -6
- package/dist/persistence/source/meta-source.js.map +1 -1
- package/dist/registry-manifest-exclusions.js +2 -2
- package/dist/registry-manifest-exclusions.js.map +1 -1
- package/dist/registry-manifest.d.ts +1 -1
- package/dist/registry-manifest.js +1 -1
- package/dist/release-docs.d.ts +11 -0
- package/dist/release-docs.d.ts.map +1 -0
- package/dist/release-docs.js +48 -0
- package/dist/release-docs.js.map +1 -0
- package/dist/serializer-json.d.ts +15 -1
- package/dist/serializer-json.d.ts.map +1 -1
- package/dist/serializer-json.js +55 -17
- package/dist/serializer-json.js.map +1 -1
- package/dist/shared/base-types.d.ts +7 -1
- package/dist/shared/base-types.d.ts.map +1 -1
- package/dist/shared/base-types.js +9 -0
- package/dist/shared/base-types.js.map +1 -1
- package/dist/shared/meta-data.d.ts +10 -0
- package/dist/shared/meta-data.d.ts.map +1 -1
- package/dist/shared/meta-data.js +22 -1
- package/dist/shared/meta-data.js.map +1 -1
- package/dist/shared/meta-root.d.ts +13 -1
- package/dist/shared/meta-root.d.ts.map +1 -1
- package/dist/shared/meta-root.js +19 -1
- package/dist/shared/meta-root.js.map +1 -1
- package/dist/super-resolve.d.ts +4 -1
- package/dist/super-resolve.d.ts.map +1 -1
- package/dist/super-resolve.js +44 -65
- package/dist/super-resolve.js.map +1 -1
- package/dist/vocabulary-catalog.js +2 -2
- package/dist/vocabulary-catalog.js.map +1 -1
- package/package.json +6 -1
- package/src/attr-schema-validate.ts +9 -13
- package/src/constants.ts +1 -0
- package/src/core/attr/meta-attr-filter.ts +8 -1
- package/src/core/attr/meta-attr.ts +24 -1
- package/src/core/field/shared-enum.ts +53 -0
- package/src/core/identity/meta-identity.ts +9 -12
- package/src/core/index.ts +5 -0
- package/src/core/object/object-constants.ts +19 -0
- package/src/core/object/object-definition.embedded.ts +80 -0
- package/src/core/relationship/derive-m2m-fields.ts +15 -48
- package/src/core/relationship/find-reference.ts +4 -2
- package/src/core/relationship/resolve-relationship-reference.ts +11 -9
- package/src/core/reporting/meta-dimension.ts +41 -0
- package/src/core/reporting/meta-measure.ts +75 -0
- package/src/core/reporting/meta-segment.ts +14 -0
- package/src/core/reporting/report-accessors.ts +73 -0
- package/src/core/reporting/report-describe.ts +206 -0
- package/src/core/reporting/report-read-model.ts +177 -0
- package/src/core/reporting/report-shape.ts +493 -0
- package/src/core/reporting/reporting-constants.ts +104 -0
- package/src/core/reporting/reporting-definition.embedded.ts +192 -0
- package/src/core/requirement/resolve-claim.ts +20 -0
- package/src/core/yaml-desugar.ts +9 -89
- package/src/core-types.ts +41 -0
- package/src/docs-ref.ts +41 -0
- package/src/errors.ts +17 -6
- package/src/fmt.ts +3 -1
- package/src/index.ts +72 -4
- package/src/loader/declared-duplicate-fields.ts +114 -0
- package/src/loader/meta-data-loader.ts +25 -1
- package/src/loader/reporting-validation.ts +1137 -0
- package/src/loader/resolve-references.ts +144 -0
- package/src/loader/validation-passes.ts +13 -9
- package/src/loader/validation-registry.ts +37 -9
- package/src/metamodel-docs/index.ts +3 -3
- package/src/metamodel-docs/render.ts +4 -4
- package/src/naming-refs.ts +237 -94
- package/src/naming.ts +75 -11
- package/src/object-serializer.ts +7 -9
- package/src/parser-core.ts +19 -56
- package/src/persistence/db/db-definition.embedded.ts +2 -2
- package/src/persistence/source/meta-source.ts +8 -6
- package/src/registry-manifest-exclusions.ts +2 -2
- package/src/registry-manifest.ts +1 -1
- package/src/release-docs.ts +48 -0
- package/src/serializer-json.ts +79 -12
- package/src/shared/base-types.ts +9 -0
- package/src/shared/meta-data.ts +22 -1
- package/src/shared/meta-root.ts +20 -1
- package/src/super-resolve.ts +49 -63
- package/src/vocabulary-catalog.ts +2 -2
|
@@ -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
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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 {
|
|
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
|
|
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-
|
|
138
|
-
// (
|
|
139
|
-
//
|
|
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
|
-
//
|
|
206
|
-
//
|
|
207
|
-
// one for both is what bound a bare @objectRef to the wrong
|
|
208
|
-
|
|
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
|
-
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
*
|
|
56
|
-
*
|
|
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:
|
|
62
|
+
targetEntity: MetaData | undefined,
|
|
61
63
|
): MetaReferenceIdentity[] {
|
|
62
|
-
|
|
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) =>
|
|
67
|
-
.filter((ref) =>
|
|
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:
|
|
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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// MetaSegment — concrete node class for type=segment nodes (FR-044).
|
|
2
|
+
//
|
|
3
|
+
// Extends MetaData directly. Accessors are RESOLVING (ADR-0039).
|
|
4
|
+
|
|
5
|
+
import { MetaData } from "../../shared/meta-data.js";
|
|
6
|
+
import { REPORTING_ATTR_FILTER } from "./reporting-constants.js";
|
|
7
|
+
|
|
8
|
+
export class MetaSegment extends MetaData {
|
|
9
|
+
/** The named row scope: a canonical attr.filter over the declaring entity's fields. */
|
|
10
|
+
filter(): Record<string, unknown> | undefined {
|
|
11
|
+
const v = this.attr(REPORTING_ATTR_FILTER);
|
|
12
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Free accessors over an `object.report` node (FR-044). Used by the Plan 2
|
|
2
|
+
// lowering and by the loader's report validation, so the `name:grain` parse and
|
|
3
|
+
// the derived-field-name rule have exactly one definition.
|
|
4
|
+
|
|
5
|
+
import type { MetaData } from "../../shared/meta-data.js";
|
|
6
|
+
import {
|
|
7
|
+
OBJECT_REPORT_ATTR_DIMENSIONS,
|
|
8
|
+
OBJECT_REPORT_ATTR_FROM,
|
|
9
|
+
OBJECT_REPORT_ATTR_MEASURES,
|
|
10
|
+
OBJECT_REPORT_ATTR_SPINE,
|
|
11
|
+
} from "../object/object-constants.js";
|
|
12
|
+
import { CHILD_REF_SEPARATOR } from "../../shared/structural.js";
|
|
13
|
+
import { REPORT_DIMENSION_GRAIN_SEPARATOR } from "./reporting-constants.js";
|
|
14
|
+
|
|
15
|
+
export interface ReportDimensionItem {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly grain?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stringList(v: unknown): string[] {
|
|
21
|
+
if (Array.isArray(v)) return v.filter((x): x is string => typeof x === "string");
|
|
22
|
+
return typeof v === "string" ? [v] : [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The `@from` entity name of a report. */
|
|
26
|
+
export function reportFrom(obj: MetaData): string | undefined {
|
|
27
|
+
const v = obj.attr(OBJECT_REPORT_ATTR_FROM);
|
|
28
|
+
return typeof v === "string" ? v : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `@spine`: the to-one path to the entity whose rows supply the report's rows. */
|
|
32
|
+
export function reportSpine(obj: MetaData): string | undefined {
|
|
33
|
+
const v = obj.attr(OBJECT_REPORT_ATTR_SPINE);
|
|
34
|
+
return typeof v === "string" && v !== "" ? v : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The `@dimensions` items, each `name` or `name:grain`. */
|
|
38
|
+
export function reportDimensionItems(obj: MetaData): ReportDimensionItem[] {
|
|
39
|
+
return stringList(obj.attr(OBJECT_REPORT_ATTR_DIMENSIONS)).map((raw) => {
|
|
40
|
+
const i = raw.indexOf(REPORT_DIMENSION_GRAIN_SEPARATOR);
|
|
41
|
+
return i === -1 ? { name: raw } : { name: raw.slice(0, i), grain: raw.slice(i + 1) };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The `@measures` items AS WRITTEN: each a bare measure `name`, or a dotted
|
|
46
|
+
* `Entity.name` (loader rule R3). Use {@link reportMeasureItemName} for the measure name. */
|
|
47
|
+
export function reportMeasureNames(obj: MetaData): string[] {
|
|
48
|
+
return stringList(obj.attr(OBJECT_REPORT_ATTR_MEASURES));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The measure a `@measures` item names: the segment after its LAST `.`
|
|
53
|
+
* (`total`, `Sale.total` and `acme::shop::Sale.total` all name `total`). It is also
|
|
54
|
+
* the derived report field's name. The part before that `.`, when present, is an
|
|
55
|
+
* entity qualifier, which {@link reportMeasureItemOwner} returns.
|
|
56
|
+
*/
|
|
57
|
+
export function reportMeasureItemName(item: string): string {
|
|
58
|
+
const dot = item.lastIndexOf(CHILD_REF_SEPARATOR);
|
|
59
|
+
return dot === -1 ? item : item.slice(dot + CHILD_REF_SEPARATOR.length);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The entity qualifier of a dotted `@measures` item (`Sale` in `Sale.total`), or
|
|
63
|
+
* undefined for a bare item. Loader rule R3: it names `@from` or an entity `@from` extends. */
|
|
64
|
+
export function reportMeasureItemOwner(item: string): string | undefined {
|
|
65
|
+
const dot = item.lastIndexOf(CHILD_REF_SEPARATOR);
|
|
66
|
+
return dot === -1 ? undefined : item.slice(0, dot);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The derived report field for a dimension item: `name` (attribute) or `name` + Capitalized(grain) (time). */
|
|
70
|
+
export function reportDerivedFieldName(item: ReportDimensionItem): string {
|
|
71
|
+
if (item.grain === undefined || item.grain === "") return item.name;
|
|
72
|
+
return item.name + item.grain.charAt(0).toUpperCase() + item.grain.slice(1);
|
|
73
|
+
}
|