@milaboratories/pl-model-common 1.46.4 → 1.47.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/dist/columns/accessor_traversal.cjs +119 -0
- package/dist/columns/accessor_traversal.cjs.map +1 -0
- package/dist/columns/accessor_traversal.d.ts +52 -0
- package/dist/columns/accessor_traversal.d.ts.map +1 -0
- package/dist/columns/accessor_traversal.js +114 -0
- package/dist/columns/accessor_traversal.js.map +1 -0
- package/dist/columns/column_registry.cjs +40 -0
- package/dist/columns/column_registry.cjs.map +1 -0
- package/dist/columns/column_registry.d.ts +31 -0
- package/dist/columns/column_registry.d.ts.map +1 -0
- package/dist/columns/column_registry.js +40 -0
- package/dist/columns/column_registry.js.map +1 -0
- package/dist/columns/column_selector.cjs +49 -0
- package/dist/columns/column_selector.cjs.map +1 -0
- package/dist/columns/column_selector.d.ts +33 -0
- package/dist/columns/column_selector.d.ts.map +1 -0
- package/dist/columns/column_selector.js +47 -0
- package/dist/columns/column_selector.js.map +1 -0
- package/dist/columns/dedup.cjs +47 -0
- package/dist/columns/dedup.cjs.map +1 -0
- package/dist/columns/dedup.d.ts +27 -0
- package/dist/columns/dedup.d.ts.map +1 -0
- package/dist/columns/dedup.js +47 -0
- package/dist/columns/dedup.js.map +1 -0
- package/dist/columns/index.cjs +18 -0
- package/dist/columns/index.d.ts +8 -0
- package/dist/columns/index.js +6 -0
- package/dist/columns/providers.cjs +62 -0
- package/dist/columns/providers.cjs.map +1 -0
- package/dist/columns/providers.d.ts +39 -0
- package/dist/columns/providers.d.ts.map +1 -0
- package/dist/columns/providers.js +61 -0
- package/dist/columns/providers.js.map +1 -0
- package/dist/columns/types.d.ts +108 -0
- package/dist/columns/types.d.ts.map +1 -0
- package/dist/driver_kit.d.ts +1 -2
- package/dist/driver_kit.d.ts.map +1 -1
- package/dist/drivers/columns/columns_collection_driver.d.ts +124 -0
- package/dist/drivers/columns/columns_collection_driver.d.ts.map +1 -0
- package/dist/drivers/columns/discover_columns_options.cjs +28 -0
- package/dist/drivers/columns/discover_columns_options.cjs.map +1 -0
- package/dist/drivers/columns/discover_columns_options.d.ts +64 -0
- package/dist/drivers/columns/discover_columns_options.d.ts.map +1 -0
- package/dist/drivers/columns/discover_columns_options.js +28 -0
- package/dist/drivers/columns/discover_columns_options.js.map +1 -0
- package/dist/drivers/columns/index.d.ts +3 -0
- package/dist/drivers/index.cjs +32 -7
- package/dist/drivers/index.d.ts +12 -9
- package/dist/drivers/index.js +6 -4
- package/dist/drivers/pframe/driver.d.ts +1 -2
- package/dist/drivers/pframe/driver.d.ts.map +1 -1
- package/dist/drivers/pframe/index.cjs +30 -7
- package/dist/drivers/pframe/index.d.ts +9 -8
- package/dist/drivers/pframe/index.js +5 -4
- package/dist/drivers/pframe/query/index.d.ts +2 -2
- package/dist/drivers/pframe/query/query_common.d.ts +24 -1
- package/dist/drivers/pframe/query/query_common.d.ts.map +1 -1
- package/dist/drivers/pframe/query/query_spec.d.ts +25 -24
- package/dist/drivers/pframe/query/query_spec.d.ts.map +1 -1
- package/dist/drivers/pframe/query/utils.cjs +18 -8
- package/dist/drivers/pframe/query/utils.cjs.map +1 -1
- package/dist/drivers/pframe/query/utils.d.ts +5 -1
- package/dist/drivers/pframe/query/utils.d.ts.map +1 -1
- package/dist/drivers/pframe/query/utils.js +18 -8
- package/dist/drivers/pframe/query/utils.js.map +1 -1
- package/dist/drivers/pframe/spec/anchored.d.ts +2 -2
- package/dist/drivers/pframe/spec/discovered_column.cjs +31 -15
- package/dist/drivers/pframe/spec/discovered_column.cjs.map +1 -1
- package/dist/drivers/pframe/spec/discovered_column.d.ts +23 -13
- package/dist/drivers/pframe/spec/discovered_column.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/discovered_column.js +26 -12
- package/dist/drivers/pframe/spec/discovered_column.js.map +1 -1
- package/dist/drivers/pframe/spec/filtered_column.cjs +46 -0
- package/dist/drivers/pframe/spec/filtered_column.cjs.map +1 -1
- package/dist/drivers/pframe/spec/filtered_column.d.ts +39 -1
- package/dist/drivers/pframe/spec/filtered_column.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/filtered_column.js +41 -1
- package/dist/drivers/pframe/spec/filtered_column.js.map +1 -1
- package/dist/drivers/pframe/spec/ids.cjs +62 -10
- package/dist/drivers/pframe/spec/ids.cjs.map +1 -1
- package/dist/drivers/pframe/spec/ids.d.ts +48 -15
- package/dist/drivers/pframe/spec/ids.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/ids.js +60 -9
- package/dist/drivers/pframe/spec/ids.js.map +1 -1
- package/dist/drivers/pframe/spec/index.cjs +30 -7
- package/dist/drivers/pframe/spec/index.d.ts +6 -5
- package/dist/drivers/pframe/spec/index.js +5 -4
- package/dist/drivers/pframe/spec/native_id.cjs +1 -0
- package/dist/drivers/pframe/spec/native_id.cjs.map +1 -1
- package/dist/drivers/pframe/spec/native_id.js +1 -0
- package/dist/drivers/pframe/spec/native_id.js.map +1 -1
- package/dist/drivers/pframe/spec/overridden.cjs +212 -0
- package/dist/drivers/pframe/spec/overridden.cjs.map +1 -0
- package/dist/drivers/pframe/spec/overridden.d.ts +67 -0
- package/dist/drivers/pframe/spec/overridden.d.ts.map +1 -0
- package/dist/drivers/pframe/spec/overridden.js +202 -0
- package/dist/drivers/pframe/spec/overridden.js.map +1 -0
- package/dist/drivers/pframe/spec/selectors.cjs +1 -0
- package/dist/drivers/pframe/spec/selectors.cjs.map +1 -1
- package/dist/drivers/pframe/spec/selectors.d.ts +3 -0
- package/dist/drivers/pframe/spec/selectors.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/selectors.js +1 -0
- package/dist/drivers/pframe/spec/selectors.js.map +1 -1
- package/dist/drivers/pframe/spec/spec.cjs.map +1 -1
- package/dist/drivers/pframe/spec/spec.d.ts +2 -3
- package/dist/drivers/pframe/spec/spec.d.ts.map +1 -1
- package/dist/drivers/pframe/spec/spec.js.map +1 -1
- package/dist/drivers/pframe/spec_driver.d.ts +2 -2
- package/dist/drivers/pframe/spec_driver.d.ts.map +1 -1
- package/dist/drivers/pframe/table_calculate.cjs +2 -2
- package/dist/drivers/pframe/table_calculate.cjs.map +1 -1
- package/dist/drivers/pframe/table_calculate.d.ts +6 -2
- package/dist/drivers/pframe/table_calculate.d.ts.map +1 -1
- package/dist/drivers/pframe/table_calculate.js +2 -2
- package/dist/drivers/pframe/table_calculate.js.map +1 -1
- package/dist/drivers/pframe/table_common.cjs.map +1 -1
- package/dist/drivers/pframe/table_common.d.ts +11 -4
- package/dist/drivers/pframe/table_common.d.ts.map +1 -1
- package/dist/drivers/pframe/table_common.js.map +1 -1
- package/dist/index.cjs +63 -10
- package/dist/index.d.ts +20 -11
- package/dist/index.js +15 -6
- package/dist/pool/index.cjs +18 -0
- package/dist/pool/index.d.ts +2 -2
- package/dist/pool/index.js +3 -0
- package/dist/pool/spec.cjs +59 -11
- package/dist/pool/spec.cjs.map +1 -1
- package/dist/pool/spec.d.ts +24 -9
- package/dist/pool/spec.d.ts.map +1 -1
- package/dist/pool/spec.js +50 -10
- package/dist/pool/spec.js.map +1 -1
- package/dist/services/index.d.ts +2 -2
- package/dist/services/service_declarations.cjs +22 -0
- package/dist/services/service_declarations.cjs.map +1 -1
- package/dist/services/service_declarations.d.ts +5 -3
- package/dist/services/service_declarations.d.ts.map +1 -1
- package/dist/services/service_declarations.js +22 -0
- package/dist/services/service_declarations.js.map +1 -1
- package/dist/services/service_registry.cjs.map +1 -1
- package/dist/services/service_registry.d.ts +3 -3
- package/dist/services/service_registry.d.ts.map +1 -1
- package/dist/services/service_registry.js.map +1 -1
- package/dist/services/service_types.cjs.map +1 -1
- package/dist/services/service_types.d.ts +25 -9
- package/dist/services/service_types.d.ts.map +1 -1
- package/dist/services/service_types.js.map +1 -1
- package/package.json +2 -1
- package/src/columns/accessor_traversal.ts +139 -0
- package/src/columns/column_registry.ts +41 -0
- package/src/columns/column_selector.ts +100 -0
- package/src/columns/dedup.ts +49 -0
- package/src/columns/index.ts +6 -0
- package/src/columns/providers.ts +74 -0
- package/src/columns/types.ts +116 -0
- package/src/drivers/columns/columns_collection_driver.ts +146 -0
- package/src/drivers/columns/discover_columns_options.ts +91 -0
- package/src/drivers/columns/index.ts +2 -0
- package/src/drivers/index.ts +1 -0
- package/src/drivers/pframe/index.ts +0 -1
- package/src/drivers/pframe/query/query_common.ts +24 -0
- package/src/drivers/pframe/query/query_spec.ts +38 -25
- package/src/drivers/pframe/query/utils.test.ts +6 -6
- package/src/drivers/pframe/query/utils.ts +27 -7
- package/src/drivers/pframe/spec/discovered_column.ts +43 -28
- package/src/drivers/pframe/spec/filtered_column.ts +66 -0
- package/src/drivers/pframe/spec/ids.ts +142 -17
- package/src/drivers/pframe/spec/index.ts +1 -0
- package/src/drivers/pframe/spec/overridden.ts +285 -0
- package/src/drivers/pframe/spec/selectors.ts +3 -0
- package/src/drivers/pframe/spec/spec.ts +7 -3
- package/src/drivers/pframe/spec_driver.ts +3 -3
- package/src/drivers/pframe/table_calculate.ts +13 -3
- package/src/drivers/pframe/table_common.ts +10 -3
- package/src/index.ts +1 -0
- package/src/pool/spec.ts +69 -23
- package/src/services/service_declarations.ts +31 -0
- package/src/services/service_registry.ts +8 -4
- package/src/services/service_types.ts +35 -9
|
@@ -1,35 +1,39 @@
|
|
|
1
|
-
import { Branded, throwError } from "@milaboratories/helpers";
|
|
1
|
+
import { type Branded, throwError } from "@milaboratories/helpers";
|
|
2
|
+
import { type CanonicalizedJson, canonicalizeJson, parseJsonSafely } from "../../../json";
|
|
2
3
|
import { PObjectId } from "../../../pool";
|
|
3
4
|
import { AxisQualification } from "./selectors";
|
|
4
|
-
import {
|
|
5
|
+
import { ColumnUniversalId } from "./ids";
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
|
|
7
|
+
export interface ColumnDiscoveredKey {
|
|
8
|
+
__isDiscovered: true;
|
|
9
|
+
column: ColumnUniversalId;
|
|
8
10
|
path?: PathItem[];
|
|
9
11
|
columnQualifications?: AxisQualification[];
|
|
10
12
|
queriesQualifications?: Record<PObjectId, AxisQualification[]>;
|
|
11
|
-
}
|
|
13
|
+
}
|
|
12
14
|
|
|
13
|
-
export type
|
|
15
|
+
export type ColumnDiscoveredId = Branded<
|
|
16
|
+
CanonicalizedJson<ColumnDiscoveredKey>,
|
|
17
|
+
"ColumnDiscoveredId"
|
|
18
|
+
>;
|
|
14
19
|
|
|
15
20
|
type PathItem = {
|
|
16
21
|
type: "linker";
|
|
17
|
-
column:
|
|
22
|
+
column: ColumnUniversalId;
|
|
18
23
|
};
|
|
19
24
|
|
|
20
|
-
export function
|
|
21
|
-
return
|
|
22
|
-
typeof obj === "object" &&
|
|
23
|
-
obj !== null &&
|
|
24
|
-
"path" in obj &&
|
|
25
|
-
"column" in obj &&
|
|
26
|
-
"columnQualifications" in obj &&
|
|
27
|
-
"queriesQualifications" in obj
|
|
28
|
-
);
|
|
25
|
+
export function isColumnDiscoveredKey(obj: unknown): obj is ColumnDiscoveredKey {
|
|
26
|
+
return typeof obj === "object" && obj !== null && "__isDiscovered" in obj;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
export function
|
|
29
|
+
export function isColumnDiscoveredId(str: unknown): str is ColumnDiscoveredId {
|
|
30
|
+
if (typeof str !== "string") return false;
|
|
31
|
+
return isColumnDiscoveredKey(parseJsonSafely(str));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function distillColumnDiscoveredKey(props: ColumnDiscoveredKey): ColumnDiscoveredKey {
|
|
32
35
|
return {
|
|
36
|
+
__isDiscovered: true,
|
|
33
37
|
column: props.column,
|
|
34
38
|
path: Array.isArray(props.path) && props.path.length > 0 ? props.path : undefined,
|
|
35
39
|
columnQualifications:
|
|
@@ -43,30 +47,41 @@ export function distillDiscoveredPColumn(props: DiscoveredPColumn): DiscoveredPC
|
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
export function
|
|
47
|
-
column:
|
|
50
|
+
export function createColumnDiscoveredId(props: {
|
|
51
|
+
column: ColumnUniversalId;
|
|
52
|
+
path?: PathItem[];
|
|
53
|
+
columnQualifications?: AxisQualification[];
|
|
54
|
+
queriesQualifications?: Record<PObjectId, AxisQualification[]>;
|
|
55
|
+
}): ColumnDiscoveredId {
|
|
56
|
+
return stringifyColumnDiscoveredId(props);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createColumnDiscoveredKey(props: {
|
|
60
|
+
column: ColumnUniversalId;
|
|
48
61
|
path?: PathItem[];
|
|
49
62
|
columnQualifications?: AxisQualification[];
|
|
50
63
|
queriesQualifications?: Record<PObjectId, AxisQualification[]>;
|
|
51
|
-
}):
|
|
52
|
-
return
|
|
64
|
+
}): ColumnDiscoveredKey {
|
|
65
|
+
return distillColumnDiscoveredKey({ __isDiscovered: true, ...props });
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
export function
|
|
68
|
+
export function parseColumnDiscoveredId(id: ColumnDiscoveredId): ColumnDiscoveredKey {
|
|
56
69
|
try {
|
|
57
70
|
const parsed = JSON.parse(id);
|
|
58
|
-
return
|
|
71
|
+
return isColumnDiscoveredKey(parsed)
|
|
59
72
|
? parsed
|
|
60
73
|
: throwError("Parsed object is not a valid DiscoveredPColumn");
|
|
61
74
|
} catch {
|
|
62
75
|
throw new Error(
|
|
63
|
-
"Invalid
|
|
76
|
+
"Invalid ColumnDiscoveredId: not a valid JSON or does not conform to DiscoveredPColumn structure",
|
|
64
77
|
);
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
export function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
export function stringifyColumnDiscoveredId(
|
|
82
|
+
id: Omit<ColumnDiscoveredKey, "__isDiscovered">,
|
|
83
|
+
): ColumnDiscoveredId {
|
|
84
|
+
return canonicalizeJson<ColumnDiscoveredKey>(
|
|
85
|
+
distillColumnDiscoveredKey({ __isDiscovered: true, ...id }),
|
|
86
|
+
) as ColumnDiscoveredId;
|
|
72
87
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { type Branded } from "@milaboratories/helpers";
|
|
2
|
+
import { type CanonicalizedJson, canonicalizeJson, parseJsonSafely } from "../../../json";
|
|
1
3
|
import type { AnchoredPColumnId } from "./selectors";
|
|
4
|
+
import { ColumnUniversalId } from "./ids";
|
|
5
|
+
import type { PColumnSpec } from "./spec";
|
|
2
6
|
|
|
3
7
|
/** Value of an axis filter */
|
|
4
8
|
export type AxisFilterValue = number | string;
|
|
@@ -29,13 +33,75 @@ export type FilteredPColumn<CID = AnchoredPColumnId, AFI = AxisFilter> = {
|
|
|
29
33
|
axisFilters: AFI[];
|
|
30
34
|
};
|
|
31
35
|
|
|
36
|
+
/** @deprecated */
|
|
32
37
|
export type FilteredPColumnId = FilteredPColumn<AnchoredPColumnId, AxisFilterByIdx>;
|
|
33
38
|
|
|
34
39
|
/**
|
|
35
40
|
* Checks if a given value is a FilteredPColumn
|
|
36
41
|
* @param id - The value to check
|
|
37
42
|
* @returns True if the value is a FilteredPColumn, false otherwise
|
|
43
|
+
* @deprecated use {@link isColumnFilteredKey} for the key form and `source` checks for the id form
|
|
38
44
|
*/
|
|
39
45
|
export function isFilteredPColumn(id: unknown): id is FilteredPColumn {
|
|
40
46
|
return typeof id === "object" && id !== null && "source" in id && "axisFilters" in id;
|
|
41
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `source` is either a leaf {@link PObjectId} or a {@link ColumnDiscoveredId}.
|
|
51
|
+
* Filtered never nests inside Filtered (flat-merge invariant), and Filtered is
|
|
52
|
+
* never the outer wrapper around Overridden — Overridden is always outermost.
|
|
53
|
+
*/
|
|
54
|
+
export interface ColumnFilteredKey {
|
|
55
|
+
__isFiltered: true;
|
|
56
|
+
source: ColumnUniversalId;
|
|
57
|
+
axisFilters: AxisFilterByIdx[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type ColumnFilteredId = Branded<CanonicalizedJson<ColumnFilteredKey>, "ColumnFilteredId">;
|
|
61
|
+
|
|
62
|
+
export function stringifyColumnFilteredId(
|
|
63
|
+
key: Omit<ColumnFilteredKey, "__isFiltered">,
|
|
64
|
+
): ColumnFilteredId {
|
|
65
|
+
return canonicalizeJson<ColumnFilteredKey>(createColumnFilteredKey(key)) as ColumnFilteredId;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function isColumnFilteredKey(obj: unknown): obj is ColumnFilteredKey {
|
|
69
|
+
return typeof obj === "object" && obj !== null && "__isFiltered" in obj;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isColumnFilteredId(id: unknown): id is ColumnFilteredId {
|
|
73
|
+
if (typeof id !== "string") return false;
|
|
74
|
+
return isColumnFilteredKey(parseJsonSafely(id));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createColumnFilteredId(props: {
|
|
78
|
+
source: ColumnUniversalId;
|
|
79
|
+
axisFilters: AxisFilterByIdx[];
|
|
80
|
+
}): ColumnFilteredId {
|
|
81
|
+
return stringifyColumnFilteredId(props);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createColumnFilteredKey(props: {
|
|
85
|
+
source: ColumnUniversalId;
|
|
86
|
+
axisFilters: AxisFilterByIdx[];
|
|
87
|
+
}): ColumnFilteredKey {
|
|
88
|
+
// `axisFilters` must be ordered by axis index so logically
|
|
89
|
+
// identical filtered columns produce one canonical id.
|
|
90
|
+
const axisFilters = props.axisFilters.toSorted((a, b) => a[0] - b[0]);
|
|
91
|
+
return { __isFiltered: true, source: props.source, axisFilters };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Drop the axes pinned by `axisFilters` from a {@link PColumnSpec}'s
|
|
96
|
+
* `axesSpec`. Indices are positional against `spec.axesSpec`. Returns `spec`
|
|
97
|
+
* unchanged when there are no filters.
|
|
98
|
+
*
|
|
99
|
+
* Single source of the Filtered-layer spec math — shared by
|
|
100
|
+
* `reconstructSpecFromId` (host, id-walking) and `ColumnFilteredRecipe.getSpec`
|
|
101
|
+
* (sandbox, recipe-graph walking).
|
|
102
|
+
*/
|
|
103
|
+
export function applyAxisFilters(spec: PColumnSpec, axisFilters: AxisFilterByIdx[]): PColumnSpec {
|
|
104
|
+
if (axisFilters.length === 0) return spec;
|
|
105
|
+
const fixed = new Set(axisFilters.map(([i]) => i));
|
|
106
|
+
return { ...spec, axesSpec: spec.axesSpec.filter((_, i) => !fixed.has(i)) };
|
|
107
|
+
}
|
|
@@ -1,34 +1,159 @@
|
|
|
1
|
-
import type { Branded } from "../../../branding";
|
|
2
1
|
import type { AnchoredPColumnId } from "./selectors";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import {
|
|
3
|
+
applyAxisFilters,
|
|
4
|
+
isColumnFilteredKey,
|
|
5
|
+
type ColumnFilteredId,
|
|
6
|
+
type ColumnFilteredKey,
|
|
7
|
+
type FilteredPColumnId,
|
|
8
|
+
} from "./filtered_column";
|
|
9
|
+
import {
|
|
10
|
+
createPObjectId,
|
|
11
|
+
isPObjectId,
|
|
12
|
+
isPObjectKey,
|
|
13
|
+
LocalPObjectKey,
|
|
14
|
+
type GlobalPObjectId,
|
|
15
|
+
type GlobalPObjectKey,
|
|
16
|
+
type LocalPObjectId,
|
|
17
|
+
type PObjectId,
|
|
18
|
+
} from "../../../pool";
|
|
19
|
+
import {
|
|
20
|
+
isColumnDiscoveredKey,
|
|
21
|
+
type ColumnDiscoveredId,
|
|
22
|
+
type ColumnDiscoveredKey,
|
|
23
|
+
} from "./discovered_column";
|
|
24
|
+
import { throwError } from "@milaboratories/helpers";
|
|
25
|
+
import {
|
|
26
|
+
applySpecOverrides,
|
|
27
|
+
isColumnOverriddenKey,
|
|
28
|
+
type ColumnOverriddenId,
|
|
29
|
+
type ColumnOverriddenKey,
|
|
30
|
+
} from "./overridden";
|
|
31
|
+
import { canonicalizeJson } from "../../../json";
|
|
32
|
+
import { AxisSpec, PColumnSpec } from "./spec";
|
|
33
|
+
import { isString } from "es-toolkit";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Per-axis patches keyed by positional index in the base spec's `axesSpec`.
|
|
37
|
+
*
|
|
38
|
+
* Using position rather than `name` lets us disambiguate linker-style specs
|
|
39
|
+
* that carry multiple axes with the same `name` differentiated by `domain` /
|
|
40
|
+
* `contextDomain` (e.g. a `group`, `group/primary`, `group/secondary` triple).
|
|
41
|
+
*
|
|
42
|
+
* A patch at index `>= base.axesSpec.length` appends a new axis at that slot.
|
|
43
|
+
*/
|
|
44
|
+
export type AxisPatches = Record<number, Partial<AxisSpec>>;
|
|
7
45
|
|
|
8
46
|
/**
|
|
9
47
|
* Universal column identifier optionally anchored and optionally filtered.
|
|
48
|
+
* @deprecated use {@link ColumnUniversalKey}
|
|
10
49
|
*/
|
|
11
|
-
export type UniversalPColumnId = AnchoredPColumnId | FilteredPColumnId
|
|
50
|
+
export type UniversalPColumnId = AnchoredPColumnId | FilteredPColumnId;
|
|
12
51
|
|
|
13
52
|
/**
|
|
14
53
|
* Canonically serialized {@link UniversalPColumnId}.
|
|
54
|
+
* @deprecated use {@link ColumnUniversalId}
|
|
15
55
|
*/
|
|
16
|
-
export type SUniversalPColumnId =
|
|
56
|
+
export type SUniversalPColumnId = ColumnUniversalId;
|
|
57
|
+
// export type SUniversalPColumnId = Branded<PObjectId, "SUniversalPColumnId", "__pl_model_brand_2__">;
|
|
58
|
+
|
|
59
|
+
export type ColumnUniversalKey =
|
|
60
|
+
| LocalPObjectKey
|
|
61
|
+
| GlobalPObjectKey
|
|
62
|
+
| ColumnFilteredKey
|
|
63
|
+
| ColumnDiscoveredKey
|
|
64
|
+
| ColumnOverriddenKey;
|
|
65
|
+
|
|
66
|
+
export type ColumnUniversalId =
|
|
67
|
+
| LocalPObjectId
|
|
68
|
+
| GlobalPObjectId
|
|
69
|
+
| ColumnFilteredId
|
|
70
|
+
| ColumnDiscoveredId
|
|
71
|
+
| ColumnOverriddenId;
|
|
17
72
|
|
|
18
73
|
/**
|
|
19
|
-
* Canonically serializes a
|
|
20
|
-
* @
|
|
21
|
-
*
|
|
74
|
+
* Canonically serializes a column key to a branded string id. Accepts both
|
|
75
|
+
* the new {@link ColumnUniversalKey} and the deprecated {@link UniversalPColumnId}
|
|
76
|
+
* (anchored / old filtered object form).
|
|
22
77
|
*/
|
|
23
|
-
export function stringifyColumnId(id: UniversalPColumnId):
|
|
24
|
-
return
|
|
78
|
+
export function stringifyColumnId(id: ColumnUniversalKey | UniversalPColumnId): ColumnUniversalId {
|
|
79
|
+
return canonicalizeJson(id) as ColumnUniversalId;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parses a canonically serialized column id back to its key form.
|
|
84
|
+
*/
|
|
85
|
+
export function parseColumnId(str: ColumnUniversalId): ColumnUniversalKey {
|
|
86
|
+
return JSON.parse(str) as ColumnUniversalKey;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function parseColumnIdSafely(
|
|
90
|
+
str: ColumnUniversalId,
|
|
91
|
+
fallback = undefined,
|
|
92
|
+
): ColumnUniversalKey | typeof fallback {
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(str) as ColumnUniversalKey;
|
|
95
|
+
} catch {
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
25
98
|
}
|
|
26
99
|
|
|
27
100
|
/**
|
|
28
|
-
*
|
|
29
|
-
* @param str - The string to parse
|
|
30
|
-
* @returns The parsed column identifier
|
|
101
|
+
* Walk a rich column id down to its terminal leaf {@link PObjectId}.
|
|
31
102
|
*/
|
|
32
|
-
export function
|
|
33
|
-
|
|
103
|
+
export function extractPObjectId(id: ColumnUniversalId | ColumnUniversalKey): PObjectId {
|
|
104
|
+
if (isString(id)) {
|
|
105
|
+
if (isPObjectId(id)) return id;
|
|
106
|
+
|
|
107
|
+
const parsed =
|
|
108
|
+
parseColumnIdSafely(id) ??
|
|
109
|
+
throwError(`extractPObjectId: id "${id}" is not a valid canonical column id`);
|
|
110
|
+
return extractPObjectId(parsed);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (isPObjectKey(id)) return createPObjectId(id);
|
|
114
|
+
if (isColumnFilteredKey(id)) return extractPObjectId(id.source);
|
|
115
|
+
if (isColumnOverriddenKey(id)) return extractPObjectId(id.source);
|
|
116
|
+
if (isColumnDiscoveredKey(id)) return extractPObjectId(id.column);
|
|
117
|
+
|
|
118
|
+
throw new Error(`extractPObjectId: unrecognized column id structure: ${JSON.stringify(id)}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Reconstruct the effective {@link PColumnSpec} for a rich column id by walking
|
|
123
|
+
* the id chain from leaf to outermost wrapper, applying each layer's spec
|
|
124
|
+
* transformation in the same order the corresponding recipe would.
|
|
125
|
+
*
|
|
126
|
+
* Layer semantics:
|
|
127
|
+
* - Leaf ({@link LocalPObjectKey} / {@link GlobalPObjectKey}): no transformation.
|
|
128
|
+
* - {@link ColumnDiscoveredKey}: pass-through, descends into `column`.
|
|
129
|
+
* - {@link ColumnFilteredKey}: drops the axes whose positional index appears in
|
|
130
|
+
* `axisFilters[i][0]` from the inner spec's `axesSpec` — mirrors
|
|
131
|
+
* `ColumnFilteredRecipe.getSpec()`.
|
|
132
|
+
* - {@link ColumnOverriddenKey}: applies `specOverrides` via
|
|
133
|
+
* {@link applySpecOverrides} on top of the inner spec.
|
|
134
|
+
*/
|
|
135
|
+
export function reconstructSpecFromId(
|
|
136
|
+
baseSpec: PColumnSpec,
|
|
137
|
+
id: ColumnUniversalId | ColumnUniversalKey,
|
|
138
|
+
): PColumnSpec {
|
|
139
|
+
if (isString(id)) {
|
|
140
|
+
if (isPObjectId(id)) return baseSpec;
|
|
141
|
+
|
|
142
|
+
const parsed =
|
|
143
|
+
parseColumnIdSafely(id) ??
|
|
144
|
+
throwError(`reconstructSpecFromId: id "${id}" is not a valid canonical column id`);
|
|
145
|
+
return reconstructSpecFromId(baseSpec, parsed);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (isPObjectKey(id)) return baseSpec;
|
|
149
|
+
if (isColumnDiscoveredKey(id)) return reconstructSpecFromId(baseSpec, id.column);
|
|
150
|
+
if (isColumnFilteredKey(id)) {
|
|
151
|
+
return applyAxisFilters(reconstructSpecFromId(baseSpec, id.source), id.axisFilters);
|
|
152
|
+
}
|
|
153
|
+
if (isColumnOverriddenKey(id)) {
|
|
154
|
+
const inner = reconstructSpecFromId(baseSpec, id.source);
|
|
155
|
+
return applySpecOverrides(inner, id.specOverrides);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
throw new Error(`reconstructSpecFromId: unrecognized column id structure: ${JSON.stringify(id)}`);
|
|
34
159
|
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { type Branded, throwError, type Mutable, Nil } from "@milaboratories/helpers";
|
|
2
|
+
import { type CanonicalizedJson, canonicalizeJson } from "../../../json";
|
|
3
|
+
import { AxisPatches, type ColumnUniversalId, parseColumnIdSafely } from "./ids";
|
|
4
|
+
import type { AxisSpec, PColumnSpec } from "./spec";
|
|
5
|
+
import { isNil, isString } from "es-toolkit";
|
|
6
|
+
|
|
7
|
+
export type SpecOverrides = Pick<PColumnSpec, "domain" | "contextDomain" | "annotations"> & {
|
|
8
|
+
axesSpec?: AxisPatches;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `source` can reference a leaf or a Filtered/Discovered id, but never another
|
|
13
|
+
* Overridden id — there is no `Overridden<Overridden<...>>`. Repeated overrides
|
|
14
|
+
* merge at the outer wrapper via {@link mergeSpecOverrides}.
|
|
15
|
+
*/
|
|
16
|
+
export interface ColumnOverriddenKey {
|
|
17
|
+
__isOverridden: true;
|
|
18
|
+
source: Exclude<ColumnUniversalId, ColumnOverriddenId>;
|
|
19
|
+
specOverrides: SpecOverrides;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ColumnOverriddenId = Branded<
|
|
23
|
+
CanonicalizedJson<ColumnOverriddenKey>,
|
|
24
|
+
"ColumnOverriddenId"
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
export function isColumnOverriddenKey(obj: unknown): obj is ColumnOverriddenKey {
|
|
28
|
+
return typeof obj === "object" && obj !== null && "__isOverridden" in obj;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function distillColumnOverriddenKey(props: ColumnOverriddenKey): ColumnOverriddenKey {
|
|
32
|
+
return {
|
|
33
|
+
__isOverridden: true,
|
|
34
|
+
source: props.source,
|
|
35
|
+
specOverrides: props.specOverrides,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createColumnOverriddenId(props: {
|
|
40
|
+
source: ColumnUniversalId;
|
|
41
|
+
specOverrides: SpecOverrides;
|
|
42
|
+
}): ColumnOverriddenId {
|
|
43
|
+
return stringifyColumnOverriddenId(createColumnOverriddenKey(props));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createColumnOverriddenKey(props: {
|
|
47
|
+
source: ColumnUniversalId;
|
|
48
|
+
specOverrides: SpecOverrides;
|
|
49
|
+
}): ColumnOverriddenKey {
|
|
50
|
+
const { source, specOverrides } = props;
|
|
51
|
+
const unwrapped = unwrapOverrides(source);
|
|
52
|
+
const baseSource = unwrapped
|
|
53
|
+
? unwrapped.source
|
|
54
|
+
: (source as Exclude<ColumnUniversalId, ColumnOverriddenId>);
|
|
55
|
+
const mergedOverrides = unwrapped
|
|
56
|
+
? mergeSpecOverrides(unwrapped.specOverrides, specOverrides)
|
|
57
|
+
: specOverrides;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
__isOverridden: true,
|
|
61
|
+
source: baseSource,
|
|
62
|
+
specOverrides: mergedOverrides,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function parseColumnOverriddenId(id: ColumnUniversalId): ColumnOverriddenKey {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(id);
|
|
69
|
+
return isColumnOverriddenKey(parsed)
|
|
70
|
+
? parsed
|
|
71
|
+
: throwError("Parsed object is not a valid OverriddenPColumn");
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"Invalid ColumnOverriddenId: not a valid JSON or does not conform to OverriddenPColumn structure",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function stringifyColumnOverriddenId(id: ColumnOverriddenKey): ColumnOverriddenId {
|
|
80
|
+
return canonicalizeJson<ColumnOverriddenKey>(
|
|
81
|
+
distillColumnOverriddenKey(id),
|
|
82
|
+
) as ColumnOverriddenId;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Peel one override-wrap layer.
|
|
87
|
+
*
|
|
88
|
+
* Invariant: `{source, specOverrides}` can only appear at the top level —
|
|
89
|
+
* the inner `source` is never itself an override-wrap. Anything else throws.
|
|
90
|
+
*/
|
|
91
|
+
export function unwrapOverrides(id: ColumnUniversalId): ColumnOverriddenKey | undefined {
|
|
92
|
+
const parsed = parseColumnIdSafely(id);
|
|
93
|
+
if (parsed === undefined || !isColumnOverriddenKey(parsed)) return undefined;
|
|
94
|
+
const inner = parseColumnIdSafely(parsed.source);
|
|
95
|
+
if (inner !== undefined && isColumnOverriddenKey(inner)) {
|
|
96
|
+
throw new Error("nested override-wrap detected — invariant broken");
|
|
97
|
+
}
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Diff two specs into a {@link SpecOverrides} patch that, when applied on top
|
|
103
|
+
* of `base`, reconstructs `next`.
|
|
104
|
+
*
|
|
105
|
+
* - For top-level `annotations` / `domain` / `contextDomain`: include keys
|
|
106
|
+
* whose value in `next` differs from `base` (or is missing in `base`).
|
|
107
|
+
* Keys present in `base` but missing in `next` are not emitted — overrides
|
|
108
|
+
* merge, they cannot delete.
|
|
109
|
+
* - For `axesSpec`: matched by **positional index**. Each `next[i]` is diffed
|
|
110
|
+
* against `base[i]`; only changed fields are emitted as a patch under key
|
|
111
|
+
* `i`. Indices past `base.length` carry the full new axis (append).
|
|
112
|
+
*/
|
|
113
|
+
export function deriveSpecDelta(base: PColumnSpec, next: PColumnSpec): SpecOverrides {
|
|
114
|
+
const annotations = recordDelta(base.annotations, next.annotations);
|
|
115
|
+
const domain = recordDelta(base.domain, next.domain);
|
|
116
|
+
const contextDomain = recordDelta(base.contextDomain, next.contextDomain);
|
|
117
|
+
const axesSpec = deriveAxesDelta(base.axesSpec, next.axesSpec);
|
|
118
|
+
return {
|
|
119
|
+
...(axesSpec && { axesSpec }),
|
|
120
|
+
...(annotations && { annotations }),
|
|
121
|
+
...(domain && { domain }),
|
|
122
|
+
...(contextDomain && { contextDomain }),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function isEmptySpecDelta(delta: SpecOverrides): boolean {
|
|
127
|
+
return (
|
|
128
|
+
(!delta.annotations || Object.keys(delta.annotations).length === 0) &&
|
|
129
|
+
(!delta.domain || Object.keys(delta.domain).length === 0) &&
|
|
130
|
+
(!delta.contextDomain || Object.keys(delta.contextDomain).length === 0) &&
|
|
131
|
+
(!delta.axesSpec || Object.keys(delta.axesSpec).length === 0)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function deriveAxesDelta(
|
|
136
|
+
base: readonly AxisSpec[],
|
|
137
|
+
next: readonly AxisSpec[],
|
|
138
|
+
): AxisPatches | undefined {
|
|
139
|
+
const out: AxisPatches = {};
|
|
140
|
+
let any = false;
|
|
141
|
+
for (let i = 0; i < next.length; i++) {
|
|
142
|
+
const a = next[i];
|
|
143
|
+
const b = base[i];
|
|
144
|
+
if (b === undefined) {
|
|
145
|
+
out[i] = a;
|
|
146
|
+
any = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const nameChanged = a.name !== b.name;
|
|
150
|
+
const typeChanged = a.type !== b.type;
|
|
151
|
+
const annotations = recordDelta(b.annotations, a.annotations);
|
|
152
|
+
const domain = recordDelta(b.domain, a.domain);
|
|
153
|
+
const contextDomain = recordDelta(b.contextDomain, a.contextDomain);
|
|
154
|
+
if (!nameChanged && !typeChanged && !annotations && !domain && !contextDomain) continue;
|
|
155
|
+
out[i] = {
|
|
156
|
+
...(nameChanged && { name: a.name }),
|
|
157
|
+
...(typeChanged && { type: a.type }),
|
|
158
|
+
...(annotations && { annotations }),
|
|
159
|
+
...(domain && { domain }),
|
|
160
|
+
...(contextDomain && { contextDomain }),
|
|
161
|
+
};
|
|
162
|
+
any = true;
|
|
163
|
+
}
|
|
164
|
+
return any ? out : undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function recordDelta(
|
|
168
|
+
base: Record<string, string> | undefined,
|
|
169
|
+
next: Record<string, string> | undefined,
|
|
170
|
+
): Record<string, string> | undefined {
|
|
171
|
+
if (!next) return undefined;
|
|
172
|
+
let any = false;
|
|
173
|
+
const diff: Record<string, string> = {};
|
|
174
|
+
for (const [k, v] of Object.entries(next)) {
|
|
175
|
+
if (!base || base[k] !== v) {
|
|
176
|
+
diff[k] = v;
|
|
177
|
+
any = true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return any ? diff : undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Apply `specOverrides` from a rich id (`SUniversalPColumnId`) on top of a
|
|
185
|
+
* resolved {@link PColumnSpec}. Returns `base` unchanged when the id carries
|
|
186
|
+
* no overrides.
|
|
187
|
+
*/
|
|
188
|
+
export function applySpecOverrides(
|
|
189
|
+
base: PColumnSpec,
|
|
190
|
+
idOrOverride: Nil | SpecOverrides | ColumnUniversalId,
|
|
191
|
+
): PColumnSpec {
|
|
192
|
+
if (isNil(idOrOverride)) {
|
|
193
|
+
return base;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const overrides = isString(idOrOverride)
|
|
197
|
+
? unwrapOverrides(idOrOverride)?.specOverrides
|
|
198
|
+
: idOrOverride;
|
|
199
|
+
|
|
200
|
+
if (isNil(overrides)) {
|
|
201
|
+
return base;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const result = { ...base };
|
|
205
|
+
if (overrides.annotations)
|
|
206
|
+
result.annotations = mergeRecord(base.annotations, overrides.annotations);
|
|
207
|
+
if (overrides.domain) result.domain = mergeRecord(base.domain, overrides.domain);
|
|
208
|
+
if (overrides.contextDomain)
|
|
209
|
+
result.contextDomain = mergeRecord(base.contextDomain, overrides.contextDomain);
|
|
210
|
+
if (overrides.axesSpec) result.axesSpec = applyAxesPatches(base.axesSpec, overrides.axesSpec);
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Compose two override patches: applying `mergeSpecOverrides(prior, next)` on
|
|
216
|
+
* top of a base spec produces the same result as applying `prior` then `next`.
|
|
217
|
+
*/
|
|
218
|
+
export function mergeSpecOverrides(prior: SpecOverrides, next: SpecOverrides): SpecOverrides {
|
|
219
|
+
const result: Mutable<SpecOverrides> = {};
|
|
220
|
+
const axesSpec = mergeAxesPatches(prior.axesSpec, next.axesSpec);
|
|
221
|
+
if (axesSpec !== undefined) result.axesSpec = axesSpec;
|
|
222
|
+
const annotations = mergeRecord(prior.annotations, next.annotations);
|
|
223
|
+
if (annotations !== undefined) result.annotations = annotations;
|
|
224
|
+
const domain = mergeRecord(prior.domain, next.domain);
|
|
225
|
+
if (domain !== undefined) result.domain = domain;
|
|
226
|
+
const contextDomain = mergeRecord(prior.contextDomain, next.contextDomain);
|
|
227
|
+
if (contextDomain !== undefined) result.contextDomain = contextDomain;
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Apply positional `patches` onto `base.axesSpec`. Patches deep-merge
|
|
233
|
+
* `annotations` / `domain` / `contextDomain` and shallow-override `name` /
|
|
234
|
+
* `type` at their slot. Indices `>= base.length` append new axes; intermediate
|
|
235
|
+
* gaps (if any) are filled with the patch itself, so callers should not leave
|
|
236
|
+
* holes between base.length and the highest patched index.
|
|
237
|
+
*/
|
|
238
|
+
function applyAxesPatches(base: readonly AxisSpec[], patches: AxisPatches): AxisSpec[] {
|
|
239
|
+
const result: AxisSpec[] = base.slice();
|
|
240
|
+
for (const [k, p] of Object.entries(patches)) {
|
|
241
|
+
const i = Number(k);
|
|
242
|
+
const existing = result[i];
|
|
243
|
+
result[i] = (existing === undefined ? p : mergeAxisPatch(existing, p)) as AxisSpec;
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Compose two positional patch maps: `mergeAxesPatches(prior, next)` applied to
|
|
250
|
+
* a base spec must equal applying `prior` then `next`. Patches sharing an
|
|
251
|
+
* index deep-merge field-by-field.
|
|
252
|
+
*/
|
|
253
|
+
function mergeAxesPatches(
|
|
254
|
+
prior: AxisPatches | undefined,
|
|
255
|
+
next: AxisPatches | undefined,
|
|
256
|
+
): AxisPatches | undefined {
|
|
257
|
+
if (!prior || Object.keys(prior).length === 0) return next;
|
|
258
|
+
if (!next || Object.keys(next).length === 0) return prior;
|
|
259
|
+
const result: AxisPatches = { ...prior };
|
|
260
|
+
for (const [k, p] of Object.entries(next)) {
|
|
261
|
+
const i = Number(k);
|
|
262
|
+
const existing = result[i];
|
|
263
|
+
result[i] = existing === undefined ? p : mergeAxisPatch(existing, p);
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function mergeAxisPatch<A extends Partial<AxisSpec>>(axis: A, patch: Partial<AxisSpec>): A {
|
|
269
|
+
const result: Mutable<A> = { ...axis, ...patch };
|
|
270
|
+
if (patch.annotations) result.annotations = mergeRecord(axis.annotations, patch.annotations);
|
|
271
|
+
if (patch.domain) result.domain = mergeRecord(axis.domain, patch.domain);
|
|
272
|
+
if (patch.contextDomain) {
|
|
273
|
+
result.contextDomain = mergeRecord(axis.contextDomain, patch.contextDomain);
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function mergeRecord(
|
|
279
|
+
a: Record<string, string> | undefined,
|
|
280
|
+
b: Record<string, string> | undefined,
|
|
281
|
+
): Record<string, string> | undefined {
|
|
282
|
+
if (!b) return a;
|
|
283
|
+
if (!a) return b;
|
|
284
|
+
return { ...a, ...b };
|
|
285
|
+
}
|
|
@@ -64,6 +64,8 @@ export interface SingleAxisSelector {
|
|
|
64
64
|
type?: AxisValueType;
|
|
65
65
|
/** Domain requirements (optional) */
|
|
66
66
|
domain?: Domain;
|
|
67
|
+
/** Context-domain requirements (optional) */
|
|
68
|
+
contextDomain?: Domain;
|
|
67
69
|
/** Parent axes requirements (optional) */
|
|
68
70
|
parentAxes?: SingleAxisSelector[];
|
|
69
71
|
}
|
|
@@ -170,6 +172,7 @@ export interface PColumnSelector extends AnchoredPColumnSelector {
|
|
|
170
172
|
/**
|
|
171
173
|
* Strict identifier for PColumns in an anchored context
|
|
172
174
|
* Unlike APColumnMatcher, this requires exact matches on domain and axes
|
|
175
|
+
* @deprecated This is part of the legacy column matching API. The new Columns API (see sdk/model/src/columns/) now handles column and axis
|
|
173
176
|
*/
|
|
174
177
|
export interface AnchoredPColumnId extends AnchoredPColumnSelector {
|
|
175
178
|
/** Name is required for exact column identification */
|