@metaobjectsdev/metadata 1.0.13 → 1.1.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
// Table B of docs/superpowers/plans/2026-10-03-fr-044-plan-2-report-view-lowering.md:
|
|
2
|
+
// a report's derived fields. The single definition; every port has a rule-for-rule copy,
|
|
3
|
+
// gated by fixtures/persistence-conformance/report-shapes.json. `required` follows Table C
|
|
4
|
+
// of docs/superpowers/plans/2026-10-09-fr-044-zero-rows-and-measure-defaults.md, which
|
|
5
|
+
// amends Table B for a report with `@spine` and a measure with `@default`.
|
|
6
|
+
|
|
7
|
+
import type { MetaData } from "../../shared/meta-data.js";
|
|
8
|
+
import type { MetaRoot } from "../../shared/meta-root.js";
|
|
9
|
+
import { isMetaObject } from "../../shared/node-guards.js";
|
|
10
|
+
import { refTarget, resolveObjectRef } from "../../naming-refs.js";
|
|
11
|
+
import { CHILD_REF_SEPARATOR, PACKAGE_SEPARATOR } from "../../shared/structural.js";
|
|
12
|
+
import type { MetaObject } from "../object/meta-object.js";
|
|
13
|
+
import type { MetaField } from "../field/meta-field.js";
|
|
14
|
+
import {
|
|
15
|
+
FIELD_SUBTYPE_BOOLEAN,
|
|
16
|
+
FIELD_SUBTYPE_CURRENCY,
|
|
17
|
+
FIELD_SUBTYPE_DATE,
|
|
18
|
+
FIELD_SUBTYPE_DECIMAL,
|
|
19
|
+
FIELD_SUBTYPE_DOUBLE,
|
|
20
|
+
FIELD_SUBTYPE_FLOAT,
|
|
21
|
+
FIELD_SUBTYPE_INT,
|
|
22
|
+
FIELD_SUBTYPE_LONG,
|
|
23
|
+
FIELD_SUBTYPE_TIMESTAMP,
|
|
24
|
+
} from "../field/field-constants.js";
|
|
25
|
+
import { TYPE_IDENTITY, TYPE_RELATIONSHIP } from "../../shared/base-types.js";
|
|
26
|
+
import type { MetaReferenceIdentity } from "../identity/meta-identity.js";
|
|
27
|
+
import { IDENTITY_REFERENCE_ATTR_REFERENCES, IDENTITY_SUBTYPE_REFERENCE } from "../identity/identity-constants.js";
|
|
28
|
+
import { RELATIONSHIP_ATTR_OBJECT_REF, RELATIONSHIP_ATTR_SOURCE_REF_FIELD } from "../relationship/relationship-constants.js";
|
|
29
|
+
import { resolveRelationshipReference } from "../relationship/resolve-relationship-reference.js";
|
|
30
|
+
import { MetaDimension } from "./meta-dimension.js";
|
|
31
|
+
import { MetaMeasure } from "./meta-measure.js";
|
|
32
|
+
import { identityEffectiveFields } from "../identity/validate-identity-passthrough.js";
|
|
33
|
+
import {
|
|
34
|
+
reportDerivedFieldName,
|
|
35
|
+
reportDimensionItems,
|
|
36
|
+
reportFrom,
|
|
37
|
+
reportMeasureItemName,
|
|
38
|
+
reportMeasureItemOwner,
|
|
39
|
+
reportMeasureNames,
|
|
40
|
+
reportSpine,
|
|
41
|
+
} from "./report-accessors.js";
|
|
42
|
+
import { AGG_ALL, AGG_ANY } from "../../persistence/origin/origin-constants.js";
|
|
43
|
+
import {
|
|
44
|
+
AGG_AVG,
|
|
45
|
+
AGG_COUNT,
|
|
46
|
+
AGG_SUM,
|
|
47
|
+
GRAIN_HOUR,
|
|
48
|
+
TIME_GRAINS,
|
|
49
|
+
TYPE_DIMENSION,
|
|
50
|
+
TYPE_MEASURE,
|
|
51
|
+
type TimeGrain,
|
|
52
|
+
} from "./reporting-constants.js";
|
|
53
|
+
|
|
54
|
+
export type ReportFieldRole = "dimension" | "measure";
|
|
55
|
+
|
|
56
|
+
export interface ReportField {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly role: ReportFieldRole;
|
|
59
|
+
/** A field subtype name (FIELD_SUBTYPE_*). */
|
|
60
|
+
readonly subType: string;
|
|
61
|
+
readonly required: boolean;
|
|
62
|
+
/** The `@of` field whose type-shaping attrs this field carries (Table B). */
|
|
63
|
+
readonly typeSource?: MetaField;
|
|
64
|
+
readonly dimension?: MetaDimension;
|
|
65
|
+
readonly grain?: TimeGrain;
|
|
66
|
+
readonly measure?: MetaMeasure;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ReportShape {
|
|
70
|
+
readonly report: MetaObject;
|
|
71
|
+
readonly from: MetaObject;
|
|
72
|
+
readonly fields: readonly ReportField[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const SUM_LONG: ReadonlySet<string> = new Set([FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG]);
|
|
76
|
+
const FLOATING: ReadonlySet<string> = new Set([FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT]);
|
|
77
|
+
|
|
78
|
+
/** Effective package of a node, taken from its resolution key ("<pkg>::<Name>"). */
|
|
79
|
+
function packageOfKey(key: string): string {
|
|
80
|
+
const i = key.lastIndexOf(PACKAGE_SEPARATOR);
|
|
81
|
+
return i >= 0 ? key.slice(0, i) : "";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** True when `candidate` is `entity` or an entity it extends (the super chain). */
|
|
85
|
+
function isSelfOrAncestor(candidate: MetaData, entity: MetaData): boolean {
|
|
86
|
+
const visited = new Set<MetaData>();
|
|
87
|
+
for (let n: MetaData | undefined = entity; n !== undefined && !visited.has(n); n = n.superData) {
|
|
88
|
+
if (n === candidate) return true;
|
|
89
|
+
visited.add(n);
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The entity that DECLARES a dimension, measure or segment reached through `from`: the
|
|
96
|
+
* member's parent, which is `from` itself or an entity `from` extends. A bare entity name
|
|
97
|
+
* inside the member (`@of`, `@via`) resolves in THIS entity's package, exactly as the
|
|
98
|
+
* loader's `validateReporting` resolves it (`pkgOf(ctx.declaring)`), never in `from`'s
|
|
99
|
+
* package or the report's.
|
|
100
|
+
*/
|
|
101
|
+
export function reportingMemberOwner(member: MetaData, from: MetaObject): MetaData {
|
|
102
|
+
return member.parent ?? from;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a dimension's or measure's `Entity.field` reference to the field node. The ONE
|
|
107
|
+
* rule, the same as the loader's (`validateReporting` D1 / M1):
|
|
108
|
+
*
|
|
109
|
+
* 1. The entity half resolves relative to the package of `declaring`, the entity that
|
|
110
|
+
* declares the member ({@link reportingMemberOwner}).
|
|
111
|
+
* 2. With `host` (a measure, or a dimension without `@via`: the reference is about the
|
|
112
|
+
* `@from` entity's own rows) the named entity must be `host` or an entity it extends,
|
|
113
|
+
* and the field is read from `host`, so a field `host` redeclares wins.
|
|
114
|
+
* 3. Without `host` the field is read from the named entity. A dimension with `@via` passes
|
|
115
|
+
* the entity its `@via` reaches as both `declaring` and `host`
|
|
116
|
+
* ({@link resolveDimensionOf}).
|
|
117
|
+
*
|
|
118
|
+
* Undefined when any step fails.
|
|
119
|
+
*/
|
|
120
|
+
export function resolveReportingFieldRef(
|
|
121
|
+
ref: string,
|
|
122
|
+
declaring: MetaData,
|
|
123
|
+
root: MetaRoot,
|
|
124
|
+
host?: MetaObject,
|
|
125
|
+
): MetaField | undefined {
|
|
126
|
+
return resolveReportingFieldRefNamed(ref, declaring, root, host)?.field;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A dimension's `@of` field, read as the loader's rule D1 reads it. Without `@via` it is
|
|
131
|
+
* {@link resolveReportingFieldRef} with `from` as the host. With `@via` the host is the
|
|
132
|
+
* entity the `@via` reaches, and a bare entity name in `@of` resolves in THAT entity's
|
|
133
|
+
* package: the path already fixes the entity, so `Realm.name` after a hop onto
|
|
134
|
+
* `world::Realm` names `world::Realm`. The field is read from the reached entity, so a field
|
|
135
|
+
* it redeclares wins. Undefined when the `@via` or the `@of` does not resolve.
|
|
136
|
+
*/
|
|
137
|
+
export function resolveDimensionOf(
|
|
138
|
+
dim: MetaDimension,
|
|
139
|
+
from: MetaObject,
|
|
140
|
+
root: MetaRoot,
|
|
141
|
+
): { readonly named: MetaObject; readonly field: MetaField } | undefined {
|
|
142
|
+
const of = dim.of() ?? "";
|
|
143
|
+
const via = dim.via();
|
|
144
|
+
if (via === undefined) return resolveReportingFieldRefNamed(of, reportingMemberOwner(dim, from), root, from);
|
|
145
|
+
const terminal = reportingViaTerminal(via, reportingMemberOwner(dim, from), from, root);
|
|
146
|
+
if (terminal === undefined) return undefined;
|
|
147
|
+
return resolveReportingFieldRefNamed(of, terminal, root, terminal);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The entity at the end of a `@via` walked from `from` (hops as {@link reportingViaHops}
|
|
152
|
+
* reads them, each target as {@link resolveViaHop} resolves it). Undefined when any hop does
|
|
153
|
+
* not resolve.
|
|
154
|
+
*/
|
|
155
|
+
function reportingViaTerminal(
|
|
156
|
+
via: string,
|
|
157
|
+
declaring: MetaData,
|
|
158
|
+
from: MetaObject,
|
|
159
|
+
root: MetaRoot,
|
|
160
|
+
): MetaObject | undefined {
|
|
161
|
+
const hops = reportingViaHops(via, declaring, from, root);
|
|
162
|
+
if (hops === undefined) return undefined;
|
|
163
|
+
let current: MetaObject = from;
|
|
164
|
+
for (const hop of hops) {
|
|
165
|
+
const target = resolveViaHop(current, hop)?.target;
|
|
166
|
+
if (target === undefined) return undefined;
|
|
167
|
+
current = target;
|
|
168
|
+
}
|
|
169
|
+
return current;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* {@link resolveReportingFieldRef}, also returning `named`: the entity the reference's
|
|
174
|
+
* entity half names (which, with `host`, may be an ancestor of the entity `field` is read
|
|
175
|
+
* from). Table C reads `named`'s primary identity.
|
|
176
|
+
*/
|
|
177
|
+
function resolveReportingFieldRefNamed(
|
|
178
|
+
ref: string,
|
|
179
|
+
declaring: MetaData,
|
|
180
|
+
root: MetaRoot,
|
|
181
|
+
host?: MetaObject,
|
|
182
|
+
): { readonly named: MetaObject; readonly field: MetaField } | undefined {
|
|
183
|
+
// `Entity.field`; a package qualifier uses `::`, so the member separator is the LAST dot.
|
|
184
|
+
const dot = ref.lastIndexOf(CHILD_REF_SEPARATOR);
|
|
185
|
+
if (dot <= 0) return undefined;
|
|
186
|
+
const named = resolveObjectRef(root, ref.slice(0, dot), packageOfKey(declaring.resolutionKey())).node;
|
|
187
|
+
if (!isMetaObject(named)) return undefined;
|
|
188
|
+
if (host !== undefined && !isSelfOrAncestor(named, host)) return undefined;
|
|
189
|
+
// ADR-0039: resolving, so a field inherited through extends is found.
|
|
190
|
+
const field = (host ?? named).fields().find((f) => f.name === ref.slice(dot + 1));
|
|
191
|
+
return field === undefined ? undefined : { named, field };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The hop names of a dimension's `@via` (`Owner.hop[.hop...]`), read as the loader reads it
|
|
196
|
+
* (`validateReporting` rule D2): `Owner` resolves in the package of `declaring`
|
|
197
|
+
* ({@link reportingMemberOwner}) and must be `from` or an entity `from` extends. The walk
|
|
198
|
+
* itself then starts AT `from`, whichever of the two `Owner` named. Undefined when the
|
|
199
|
+
* reference has no owner, no hop, or an owner that is not `from` or an ancestor of it.
|
|
200
|
+
*/
|
|
201
|
+
export function reportingViaHops(
|
|
202
|
+
via: string,
|
|
203
|
+
declaring: MetaData,
|
|
204
|
+
from: MetaObject,
|
|
205
|
+
root: MetaRoot,
|
|
206
|
+
): string[] | undefined {
|
|
207
|
+
// The owner ends at the first `.` after the last `::` (a package qualifier has no `.`).
|
|
208
|
+
const lastSep = via.lastIndexOf(PACKAGE_SEPARATOR);
|
|
209
|
+
const segStart = lastSep === -1 ? 0 : lastSep + PACKAGE_SEPARATOR.length;
|
|
210
|
+
const dot = via.indexOf(CHILD_REF_SEPARATOR, segStart);
|
|
211
|
+
if (dot <= segStart) return undefined;
|
|
212
|
+
const hops = via.slice(dot + CHILD_REF_SEPARATOR.length).split(CHILD_REF_SEPARATOR);
|
|
213
|
+
if (hops.some((h) => h === "")) return undefined;
|
|
214
|
+
const owner = resolveObjectRef(root, via.slice(0, dot), packageOfKey(declaring.resolutionKey())).node;
|
|
215
|
+
if (owner === undefined || !isSelfOrAncestor(owner, from)) return undefined;
|
|
216
|
+
return hops;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function unresolved(reportName: string, what: string): Error {
|
|
220
|
+
return new Error(`report '${reportName}': ${what} does not resolve.`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The hop names of a report's `@spine` (`Owner.hop[.hop...]`), read as
|
|
225
|
+
* {@link reportingViaHops} reads a `@via`, the owner resolving in `from`'s package (loader
|
|
226
|
+
* rule R8): Owner must be `from` or an entity it extends, so a bare name means `from`'s
|
|
227
|
+
* package even when the report lives in another one. Undefined when the report declares no `@spine`. Throws a plain Error
|
|
228
|
+
* naming the report when a declared `@spine` does not resolve (a report that passed
|
|
229
|
+
* `validateReporting` always resolves): reading it as "no spine" would claim a column
|
|
230
|
+
* non-null that a spine row with no facts leaves null.
|
|
231
|
+
*/
|
|
232
|
+
export function reportSpineHops(report: MetaObject, from: MetaObject, root: MetaRoot): string[] | undefined {
|
|
233
|
+
const spine = reportSpine(report);
|
|
234
|
+
if (spine === undefined) return undefined;
|
|
235
|
+
const hops = reportingViaHops(spine, from, from, root);
|
|
236
|
+
if (hops === undefined) throw unresolved(report.name, `@spine '${spine}'`);
|
|
237
|
+
return hops;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* One hop of a `@via`/`@spine` path, as the loader's rule R8 walks it: the `relationship.*`
|
|
242
|
+
* (read through its `@objectRef`) or `identity.reference` (through its `@references`) of
|
|
243
|
+
* `current` named `hopName`, with its target resolved in the package of the node that declares
|
|
244
|
+
* the hop's reference attribute (ADR-0059 R3). Returns
|
|
245
|
+
* `target: undefined` for an unresolvable hop or a ref attr that is not a string / does not
|
|
246
|
+
* reach a metadata object.
|
|
247
|
+
* ADR-0039: resolving children(), so a hop inherited through extends is found.
|
|
248
|
+
*/
|
|
249
|
+
function resolveViaHop(
|
|
250
|
+
current: MetaObject,
|
|
251
|
+
hopName: string,
|
|
252
|
+
): { hop: MetaData; isReference: boolean; target: MetaObject | undefined } | undefined {
|
|
253
|
+
const rel = current.children().find((c) => c.type === TYPE_RELATIONSHIP && c.name === hopName);
|
|
254
|
+
const refNode = rel !== undefined
|
|
255
|
+
? undefined
|
|
256
|
+
: current.children().find(
|
|
257
|
+
(c) => c.type === TYPE_IDENTITY && c.subType === IDENTITY_SUBTYPE_REFERENCE && c.name === hopName,
|
|
258
|
+
);
|
|
259
|
+
const hop = rel ?? refNode;
|
|
260
|
+
if (hop === undefined) return undefined;
|
|
261
|
+
const isReference = rel === undefined;
|
|
262
|
+
const node = refTarget(hop, isReference ? IDENTITY_REFERENCE_ATTR_REFERENCES : RELATIONSHIP_ATTR_OBJECT_REF);
|
|
263
|
+
return { hop, isReference, target: isMetaObject(node) ? node : undefined };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* The spine entity: the object at the end of a report's `@spine`, whose rows are the
|
|
268
|
+
* report's rows. Walked hop by hop from `from` as the loader's rule R8 walks it via
|
|
269
|
+
* {@link resolveViaHop}; an unresolved hop throws, as {@link reportSpineHops} does (a report
|
|
270
|
+
* that passed `validateReporting` always resolves). Undefined when the report declares no
|
|
271
|
+
* `@spine`.
|
|
272
|
+
*/
|
|
273
|
+
export function reportSpineEntity(report: MetaObject, from: MetaObject, root: MetaRoot): MetaObject | undefined {
|
|
274
|
+
const hops = reportSpineHops(report, from, root);
|
|
275
|
+
if (hops === undefined) return undefined;
|
|
276
|
+
let current: MetaObject = from;
|
|
277
|
+
for (const hop of hops) {
|
|
278
|
+
const target = resolveViaHop(current, hop)?.target;
|
|
279
|
+
if (target === undefined) throw unresolved(report.name, `@spine '${reportSpine(report) ?? ""}' hop '${hop}'`);
|
|
280
|
+
current = target;
|
|
281
|
+
}
|
|
282
|
+
return current;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** True when `field` is one of the `@fields` of `entity`'s `identity.primary`. */
|
|
286
|
+
function isPrimaryKeyField(entity: MetaObject, field: MetaField): boolean {
|
|
287
|
+
// ADR-0039: resolving — an identity.primary (and its @fields) inherited from an
|
|
288
|
+
// abstract base counts.
|
|
289
|
+
const pk = entity.primaryIdentity();
|
|
290
|
+
return pk !== undefined && (identityEffectiveFields(pk) ?? []).includes(field.name);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function declaredMember<T extends MetaData>(
|
|
294
|
+
from: MetaObject,
|
|
295
|
+
type: string,
|
|
296
|
+
name: string,
|
|
297
|
+
cls: new (...args: never[]) => T,
|
|
298
|
+
): T | undefined {
|
|
299
|
+
// ADR-0039: resolving children(), so a member declared on an abstract base is found.
|
|
300
|
+
return from.children().find((c): c is T => c.type === type && c.name === name && c instanceof cls);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isTimeGrain(grain: string | undefined): grain is TimeGrain {
|
|
304
|
+
return grain !== undefined && (TIME_GRAINS as readonly string[]).includes(grain);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* True when every hop of `hops`, walked from `from`, is a belongs-to hop over a required
|
|
309
|
+
* foreign key: the hops the view joins INNER, so every row of the report has a match. This
|
|
310
|
+
* is the join-type rule of the view lowering (codegen-ts `walkViaPath`, #209): the reference
|
|
311
|
+
* is held by the entity the hop leaves, its first FK field is required, and every earlier hop
|
|
312
|
+
* is INNER too. `@enforce: false` does not change it. A hop that does not resolve, an
|
|
313
|
+
* ambiguous reference or a has-many hop is not INNER.
|
|
314
|
+
*/
|
|
315
|
+
function viaJoinsInner(hops: readonly string[], from: MetaObject, root: MetaRoot): boolean {
|
|
316
|
+
let current = from;
|
|
317
|
+
for (const hopName of hops) {
|
|
318
|
+
const resolved = resolveViaHop(current, hopName);
|
|
319
|
+
if (resolved === undefined || resolved.target === undefined) return false;
|
|
320
|
+
const { hop, isReference, target } = resolved;
|
|
321
|
+
|
|
322
|
+
// The reference the hop crosses, as the lowering resolves it. A has-many hop, whose FK
|
|
323
|
+
// sits on the child, matches none of the current entity's own references.
|
|
324
|
+
let reference: MetaReferenceIdentity | undefined;
|
|
325
|
+
if (isReference) {
|
|
326
|
+
reference = hop as unknown as MetaReferenceIdentity;
|
|
327
|
+
} else {
|
|
328
|
+
const sourceRefField = hop.attr(RELATIONSHIP_ATTR_SOURCE_REF_FIELD);
|
|
329
|
+
reference = resolveRelationshipReference(
|
|
330
|
+
current, hopName, target, typeof sourceRefField === "string" ? sourceRefField : undefined,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (reference === undefined) return false;
|
|
334
|
+
const fkName = reference.fields[0];
|
|
335
|
+
const fk = fkName === undefined ? undefined : current.findField(fkName);
|
|
336
|
+
if (fk === undefined || !fk.isRequired) return false;
|
|
337
|
+
current = target;
|
|
338
|
+
}
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Table C (`required` of a dimension). Without `@spine`: only a dimension over an `@of` field
|
|
344
|
+
* that is effectively required (`@required`, or a `validator.required` child), and, when it has a `@via`, only when every hop is a
|
|
345
|
+
* required belongs-to reference, the hops the view joins INNER ({@link viaJoinsInner}). With `@spine`: only a dimension
|
|
346
|
+
* whose `@via` hops equal the spine's (a column of the spine entity itself, whose rows are
|
|
347
|
+
* the report's rows) over an `@of` field that is `@required` or a primary-key column of the
|
|
348
|
+
* entity `@of` names. A dimension beyond the spine is reached by a LEFT OUTER join.
|
|
349
|
+
*/
|
|
350
|
+
function dimensionRequired(
|
|
351
|
+
dim: MetaDimension,
|
|
352
|
+
named: MetaObject,
|
|
353
|
+
of: MetaField,
|
|
354
|
+
from: MetaObject,
|
|
355
|
+
root: MetaRoot,
|
|
356
|
+
spine: readonly string[] | undefined,
|
|
357
|
+
reportName: string,
|
|
358
|
+
): boolean {
|
|
359
|
+
const via = dim.via();
|
|
360
|
+
if (spine === undefined) {
|
|
361
|
+
if (via === undefined) return of.isRequired;
|
|
362
|
+
if (!of.isRequired) return false;
|
|
363
|
+
const hops = reportingViaHops(via, reportingMemberOwner(dim, from), from, root);
|
|
364
|
+
if (hops === undefined) throw unresolved(reportName, `dimension '${dim.name}' @via '${via}'`);
|
|
365
|
+
return viaJoinsInner(hops, from, root);
|
|
366
|
+
}
|
|
367
|
+
if (via === undefined) return false; // a column of @from: null in a spine row with no facts
|
|
368
|
+
const hops = reportingViaHops(via, reportingMemberOwner(dim, from), from, root);
|
|
369
|
+
if (hops === undefined) throw unresolved(reportName, `dimension '${dim.name}' @via '${via}'`);
|
|
370
|
+
const onSpine = hops.length === spine.length && hops.every((h, i) => h === spine[i]);
|
|
371
|
+
return onSpine && (of.isRequired || isPrimaryKeyField(named, of));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function dimensionField(
|
|
375
|
+
item: { name: string; grain?: string },
|
|
376
|
+
from: MetaObject,
|
|
377
|
+
root: MetaRoot,
|
|
378
|
+
reportName: string,
|
|
379
|
+
spine: readonly string[] | undefined,
|
|
380
|
+
): ReportField {
|
|
381
|
+
const dim = declaredMember(from, TYPE_DIMENSION, item.name, MetaDimension);
|
|
382
|
+
if (dim === undefined) throw unresolved(reportName, `dimension '${item.name}' on '${from.name}'`);
|
|
383
|
+
const via = dim.via();
|
|
384
|
+
// A @via that does not walk is the @via's error, not the @of's (the @of resolves at its end).
|
|
385
|
+
if (via !== undefined && reportingViaTerminal(via, reportingMemberOwner(dim, from), from, root) === undefined) {
|
|
386
|
+
throw unresolved(reportName, `dimension '${dim.name}' @via '${via}'`);
|
|
387
|
+
}
|
|
388
|
+
const ref = resolveDimensionOf(dim, from, root);
|
|
389
|
+
if (ref === undefined) throw unresolved(reportName, `dimension '${item.name}' @of`);
|
|
390
|
+
const of = ref.field;
|
|
391
|
+
const name = reportDerivedFieldName(item);
|
|
392
|
+
const required = dimensionRequired(dim, ref.named, of, from, root, spine, reportName);
|
|
393
|
+
if (dim.isTime()) {
|
|
394
|
+
// Loader rule R2 guarantees a grain from the closed set; a tree built in code does not.
|
|
395
|
+
const grain = item.grain;
|
|
396
|
+
if (!isTimeGrain(grain)) throw unresolved(reportName, `time dimension '${item.name}' grain '${grain ?? ""}'`);
|
|
397
|
+
if (grain === GRAIN_HOUR) {
|
|
398
|
+
return { name, role: "dimension", subType: FIELD_SUBTYPE_TIMESTAMP, required, typeSource: of, dimension: dim, grain };
|
|
399
|
+
}
|
|
400
|
+
return { name, role: "dimension", subType: FIELD_SUBTYPE_DATE, required, dimension: dim, grain };
|
|
401
|
+
}
|
|
402
|
+
return { name, role: "dimension", subType: of.subType, required, typeSource: of, dimension: dim };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* One `@measures` item, bare (`total`) or dotted (`Sale.total`, loader rule R3). The measure
|
|
407
|
+
* is named by the item's last segment and looked up on `from`; a qualifier resolves in the
|
|
408
|
+
* REPORT's package and must be `from` or an entity `from` extends.
|
|
409
|
+
*/
|
|
410
|
+
function measureField(item: string, report: MetaObject, from: MetaObject, root: MetaRoot): ReportField {
|
|
411
|
+
const reportName = report.name;
|
|
412
|
+
const name = reportMeasureItemName(item);
|
|
413
|
+
const qualifier = reportMeasureItemOwner(item);
|
|
414
|
+
if (qualifier !== undefined) {
|
|
415
|
+
const owner = resolveObjectRef(root, qualifier, packageOfKey(report.resolutionKey())).node;
|
|
416
|
+
if (owner === undefined || !isSelfOrAncestor(owner, from)) {
|
|
417
|
+
throw unresolved(reportName, `measure '${item}' on '${from.name}'`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const m = declaredMember(from, TYPE_MEASURE, name, MetaMeasure);
|
|
421
|
+
if (m === undefined) throw unresolved(reportName, `measure '${item}' on '${from.name}'`);
|
|
422
|
+
const derived = deriveMeasure(m, from, root);
|
|
423
|
+
if (derived === undefined) throw unresolved(reportName, `measure '${name}' @of`);
|
|
424
|
+
// Table C: a count is never null; any other measure is not null when it has a @default.
|
|
425
|
+
const required = (!m.isRatio() && m.agg() === AGG_COUNT) || m.defaultValue() !== undefined;
|
|
426
|
+
const { subType, typeSource } = derived;
|
|
427
|
+
// The key set (no `typeSource` key when there is none) and its order are as before.
|
|
428
|
+
return typeSource === undefined
|
|
429
|
+
? { name, role: "measure", subType, required, measure: m }
|
|
430
|
+
: { name, role: "measure", subType, required, typeSource, measure: m };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** One measure's Table B subtype, and the `@of` field it carries type attrs from (if any). */
|
|
434
|
+
interface MeasureDerivation {
|
|
435
|
+
readonly subType: string;
|
|
436
|
+
readonly typeSource?: MetaField;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Table B for one measure. Undefined when its `@of` does not resolve. */
|
|
440
|
+
function deriveMeasure(m: MetaMeasure, from: MetaObject, root: MetaRoot): MeasureDerivation | undefined {
|
|
441
|
+
if (m.isRatio()) return { subType: FIELD_SUBTYPE_DECIMAL };
|
|
442
|
+
const agg = m.agg();
|
|
443
|
+
if (agg === AGG_COUNT) return { subType: FIELD_SUBTYPE_LONG };
|
|
444
|
+
// any / all are boolean whatever the source (the loader's M4 refuses a non-boolean @of).
|
|
445
|
+
if (agg === AGG_ANY || agg === AGG_ALL) return { subType: FIELD_SUBTYPE_BOOLEAN };
|
|
446
|
+
const of = resolveReportingFieldRef(m.ofColumns()[0] ?? "", reportingMemberOwner(m, from), root, from);
|
|
447
|
+
if (of === undefined) return undefined;
|
|
448
|
+
const src = of.subType;
|
|
449
|
+
if (agg === AGG_SUM) {
|
|
450
|
+
if (src === FIELD_SUBTYPE_CURRENCY) return { subType: FIELD_SUBTYPE_CURRENCY, typeSource: of };
|
|
451
|
+
return {
|
|
452
|
+
subType: SUM_LONG.has(src) ? FIELD_SUBTYPE_LONG : FLOATING.has(src) ? FIELD_SUBTYPE_DOUBLE : FIELD_SUBTYPE_DECIMAL,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
if (agg === AGG_AVG) return { subType: FLOATING.has(src) ? FIELD_SUBTYPE_DOUBLE : FIELD_SUBTYPE_DECIMAL };
|
|
456
|
+
// min / max keep the source field's type.
|
|
457
|
+
return { subType: src, typeSource: of };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Table B's subtype for one measure of `from`, whether or not a report lists it (a
|
|
462
|
+
* ratio's operands are measures a report need not list). The same rule `reportShape`
|
|
463
|
+
* applies to a listed measure. Undefined when its `@of` does not resolve, which the
|
|
464
|
+
* loader (rule M6) reads before the model is known to be valid.
|
|
465
|
+
*/
|
|
466
|
+
export function measureResultSubType(measure: MetaMeasure, from: MetaObject, root: MetaRoot): string | undefined {
|
|
467
|
+
return deriveMeasure(measure, from, root)?.subType;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* {@link measureResultSubType} for a model that passed `validateReporting`. Throws a plain
|
|
472
|
+
* Error naming the measure when its `@of` does not resolve (a valid measure always resolves).
|
|
473
|
+
*/
|
|
474
|
+
export function measureDerivedSubType(measure: MetaMeasure, from: MetaObject, root: MetaRoot): string {
|
|
475
|
+
const subType = measureResultSubType(measure, from, root);
|
|
476
|
+
if (subType === undefined) throw new Error(`measure '${measure.name}' on '${from.name}': @of does not resolve.`);
|
|
477
|
+
return subType;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Table B, with Table C's `required`. Throws a plain Error naming the report when a
|
|
481
|
+
* reference does not resolve (a report that passed `validateReporting` always resolves). */
|
|
482
|
+
export function reportShape(report: MetaObject, root: MetaRoot): ReportShape {
|
|
483
|
+
const fromName = reportFrom(report);
|
|
484
|
+
if (fromName === undefined) throw unresolved(report.name, "@from");
|
|
485
|
+
const from = resolveObjectRef(root, fromName, packageOfKey(report.resolutionKey())).node;
|
|
486
|
+
if (!isMetaObject(from)) throw unresolved(report.name, `@from '${fromName}'`);
|
|
487
|
+
const spine = reportSpineHops(report, from, root);
|
|
488
|
+
const fields = [
|
|
489
|
+
...reportDimensionItems(report).map((item) => dimensionField(item, from, root, report.name, spine)),
|
|
490
|
+
...reportMeasureNames(report).map((item) => measureField(item, report, from, root)),
|
|
491
|
+
];
|
|
492
|
+
return { report, from, fields };
|
|
493
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Reporting concern constants (FR-044) — type names, subtypes, attr keys and
|
|
2
|
+
// the closed sets the registry enforces through `allowedValues`.
|
|
3
|
+
//
|
|
4
|
+
// `dimension`, `measure` and `segment` are children of `object.entity` only
|
|
5
|
+
// (never root-level), and `object.report` (see object-constants.ts) references
|
|
6
|
+
// them by name. The type-name constants live in shared/base-types.ts beside
|
|
7
|
+
// every other base type and are re-exported here so a reporting consumer has
|
|
8
|
+
// one import site.
|
|
9
|
+
|
|
10
|
+
import { AGG_ALL, AGG_ANY } from "../../persistence/origin/origin-constants.js";
|
|
11
|
+
import { TYPE_DIMENSION, TYPE_MEASURE, TYPE_SEGMENT } from "../../shared/base-types.js";
|
|
12
|
+
|
|
13
|
+
export { TYPE_DIMENSION, TYPE_MEASURE, TYPE_SEGMENT };
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Subtypes
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/** Groups by a column value as-is (no grain, no truncation). */
|
|
20
|
+
export const DIMENSION_SUBTYPE_ATTRIBUTE = "attribute";
|
|
21
|
+
/** Groups by a date/timestamp column truncated to a grain. */
|
|
22
|
+
export const DIMENSION_SUBTYPE_TIME = "time";
|
|
23
|
+
export const DIMENSION_SUBTYPES = [DIMENSION_SUBTYPE_ATTRIBUTE, DIMENSION_SUBTYPE_TIME] as const;
|
|
24
|
+
export type DimensionSubType = (typeof DIMENSION_SUBTYPES)[number];
|
|
25
|
+
|
|
26
|
+
/** One aggregate over the declaring entity's own rows. */
|
|
27
|
+
export const MEASURE_SUBTYPE_AGGREGATE = "aggregate";
|
|
28
|
+
/** A quotient of two `measure.aggregate` siblings, `numerator / NULLIF(denominator, 0)`.
|
|
29
|
+
* `measure.derived` is deliberately NOT registered: it waits for FR-037 R5. */
|
|
30
|
+
export const MEASURE_SUBTYPE_RATIO = "ratio";
|
|
31
|
+
export const MEASURE_SUBTYPES = [MEASURE_SUBTYPE_AGGREGATE, MEASURE_SUBTYPE_RATIO] as const;
|
|
32
|
+
export type MeasureSubType = (typeof MEASURE_SUBTYPES)[number];
|
|
33
|
+
|
|
34
|
+
/** A named, reusable row filter. The only concrete segment subtype: every `*.base`
|
|
35
|
+
* in the registry is an abstract anchor, so authors write `segment.filter`. */
|
|
36
|
+
export const SEGMENT_SUBTYPE_FILTER = "filter";
|
|
37
|
+
export const SEGMENT_SUBTYPES = [SEGMENT_SUBTYPE_FILTER] as const;
|
|
38
|
+
export type SegmentSubType = (typeof SEGMENT_SUBTYPES)[number];
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Attrs (on dimension / measure / segment nodes)
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/** Dotted `Entity.field` reference(s) naming the grouped / aggregated column(s). */
|
|
45
|
+
export const REPORTING_ATTR_OF = "of";
|
|
46
|
+
/** Optional dotted to-one relationship path from the owning entity to the `@of` entity. */
|
|
47
|
+
export const REPORTING_ATTR_VIA = "via";
|
|
48
|
+
/** The grains a `dimension.time` supports. */
|
|
49
|
+
export const REPORTING_ATTR_GRAINS = "grains";
|
|
50
|
+
/** The aggregate function of a `measure.aggregate`. */
|
|
51
|
+
export const REPORTING_ATTR_AGG = "agg";
|
|
52
|
+
/** Count distinct values (legal only with `@agg: count`). */
|
|
53
|
+
export const REPORTING_ATTR_DISTINCT = "distinct";
|
|
54
|
+
/** Row scope (an attr.filter) on a `measure.aggregate` or `segment.filter`. */
|
|
55
|
+
export const REPORTING_ATTR_FILTER = "filter";
|
|
56
|
+
/** Name of a segment declared on the same entity. */
|
|
57
|
+
export const REPORTING_ATTR_SEGMENT = "segment";
|
|
58
|
+
/** `measure.ratio` operand names. */
|
|
59
|
+
export const REPORTING_ATTR_NUMERATOR = "numerator";
|
|
60
|
+
export const REPORTING_ATTR_DENOMINATOR = "denominator";
|
|
61
|
+
/** Integer a measure reads when it would otherwise be null (`measure.aggregate` / `measure.ratio`). */
|
|
62
|
+
export const REPORTING_ATTR_DEFAULT = "default";
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Closed sets — mirrored by `allowedValues` in spec/metamodel/reporting.json.
|
|
66
|
+
// Order is part of the contract (it is the order the registry manifest records).
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/** Weeks start on Monday (ISO-8601) in every lowering. */
|
|
70
|
+
export const GRAIN_HOUR = "hour";
|
|
71
|
+
export const GRAIN_DAY = "day";
|
|
72
|
+
export const GRAIN_WEEK = "week";
|
|
73
|
+
export const GRAIN_MONTH = "month";
|
|
74
|
+
export const GRAIN_QUARTER = "quarter";
|
|
75
|
+
export const GRAIN_YEAR = "year";
|
|
76
|
+
export const TIME_GRAINS = [GRAIN_HOUR, GRAIN_DAY, GRAIN_WEEK, GRAIN_MONTH, GRAIN_QUARTER, GRAIN_YEAR] as const;
|
|
77
|
+
export type TimeGrain = (typeof TIME_GRAINS)[number];
|
|
78
|
+
|
|
79
|
+
export const AGG_COUNT = "count";
|
|
80
|
+
export const AGG_SUM = "sum";
|
|
81
|
+
export const AGG_AVG = "avg";
|
|
82
|
+
export const AGG_MIN = "min";
|
|
83
|
+
export const AGG_MAX = "max";
|
|
84
|
+
// `any` / `all`: boolean OR / AND over a `field.boolean` (Postgres `bool_or` / `bool_and`); the
|
|
85
|
+
// measure is boolean. The names, and their constants, are `origin.aggregate`'s own.
|
|
86
|
+
export const MEASURE_AGGS = [AGG_COUNT, AGG_SUM, AGG_AVG, AGG_MIN, AGG_MAX, AGG_ANY, AGG_ALL] as const;
|
|
87
|
+
export type MeasureAgg = (typeof MEASURE_AGGS)[number];
|
|
88
|
+
|
|
89
|
+
/** Separator in an `object.report` `@dimensions` item: `name` or `name:grain`. */
|
|
90
|
+
export const REPORT_DIMENSION_GRAIN_SEPARATOR = ":";
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Relative-date filter values (spec §4 R4; rules F1/F2)
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/** The single key of a relative-date filter value: `{ now: "<ISO-8601 duration>" }`
|
|
97
|
+
* means "the current time plus that duration", evaluated when the view is queried.
|
|
98
|
+
* Legal only in the `@filter` of a `segment`, `measure.aggregate` or `object.report`. */
|
|
99
|
+
export const FILTER_RELATIVE_NOW = "now";
|
|
100
|
+
|
|
101
|
+
/** A signed ISO-8601 duration (`-P7D`, `P1Y2M`, `-PT12H`). The lookaheads refuse the
|
|
102
|
+
* degenerate `P` and `PT` forms (a designator with no component). */
|
|
103
|
+
export const ISO_DURATION_RE =
|
|
104
|
+
/^[+-]?P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/;
|