@platforma-sdk/model 1.80.13 → 1.81.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/column.cjs +9 -6
- package/dist/columns/column.cjs.map +1 -1
- package/dist/columns/column.d.ts +5 -5
- package/dist/columns/column.d.ts.map +1 -1
- package/dist/columns/column.js +10 -7
- package/dist/columns/column.js.map +1 -1
- package/dist/columns/column_providers/index.cjs.map +1 -1
- package/dist/columns/column_providers/index.d.ts.map +1 -1
- package/dist/columns/column_providers/index.js.map +1 -1
- package/dist/columns/column_providers/providers.cjs +5 -5
- package/dist/columns/column_providers/providers.cjs.map +1 -1
- package/dist/columns/column_providers/providers.d.ts +8 -8
- package/dist/columns/column_providers/providers.d.ts.map +1 -1
- package/dist/columns/column_providers/providers.js +5 -5
- package/dist/columns/column_providers/providers.js.map +1 -1
- package/dist/columns/column_providers/types.d.ts +3 -3
- package/dist/columns/column_providers/types.d.ts.map +1 -1
- package/dist/columns/column_recipes/column_discovered_recipe.cjs +1 -1
- package/dist/columns/column_recipes/column_discovered_recipe.cjs.map +1 -1
- package/dist/columns/column_recipes/column_discovered_recipe.d.ts +1 -1
- package/dist/columns/column_recipes/column_discovered_recipe.js +1 -1
- package/dist/columns/column_recipes/column_discovered_recipe.js.map +1 -1
- package/dist/columns/column_recipes/column_overrided_recipe.cjs +47 -2
- package/dist/columns/column_recipes/column_overrided_recipe.cjs.map +1 -1
- package/dist/columns/column_recipes/column_overrided_recipe.d.ts +30 -3
- package/dist/columns/column_recipes/column_overrided_recipe.d.ts.map +1 -1
- package/dist/columns/column_recipes/column_overrided_recipe.js +47 -3
- package/dist/columns/column_recipes/column_overrided_recipe.js.map +1 -1
- package/dist/columns/column_recipes/index.cjs +4 -14
- package/dist/columns/column_recipes/index.cjs.map +1 -1
- package/dist/columns/column_recipes/index.d.ts +3 -10
- package/dist/columns/column_recipes/index.d.ts.map +1 -1
- package/dist/columns/column_recipes/index.js +5 -14
- package/dist/columns/column_recipes/index.js.map +1 -1
- package/dist/columns/{column_lazy.cjs → data_column.cjs} +57 -54
- package/dist/columns/data_column.cjs.map +1 -0
- package/dist/columns/data_column.d.ts +152 -0
- package/dist/columns/data_column.d.ts.map +1 -0
- package/dist/columns/{column_lazy.js → data_column.js} +55 -52
- package/dist/columns/data_column.js.map +1 -0
- package/dist/columns/derive_axis_values_labels.cjs +4 -4
- package/dist/columns/derive_axis_values_labels.cjs.map +1 -1
- package/dist/columns/derive_axis_values_labels.d.ts +2 -2
- package/dist/columns/derive_axis_values_labels.js +4 -4
- package/dist/columns/derive_axis_values_labels.js.map +1 -1
- package/dist/columns/index.cjs +9 -9
- package/dist/columns/index.d.ts +5 -5
- package/dist/columns/index.js +5 -5
- package/dist/columns/{expand_by_partition.cjs → split_by_axes.cjs} +13 -8
- package/dist/columns/split_by_axes.cjs.map +1 -0
- package/dist/columns/{expand_by_partition.d.ts → split_by_axes.d.ts} +18 -8
- package/dist/columns/split_by_axes.d.ts.map +1 -0
- package/dist/columns/{expand_by_partition.js → split_by_axes.js} +14 -9
- package/dist/columns/split_by_axes.js.map +1 -0
- package/dist/columns/utils.cjs +50 -43
- package/dist/columns/utils.cjs.map +1 -1
- package/dist/columns/utils.d.ts +44 -19
- package/dist/columns/utils.d.ts.map +1 -1
- package/dist/columns/utils.js +50 -44
- package/dist/columns/utils.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs +3 -3
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js +3 -3
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs +2 -2
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js +2 -2
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +2 -2
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +2 -2
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
- package/dist/components/PlDatasetSelector/enrichment_discovery.cjs +1 -1
- package/dist/components/PlDatasetSelector/enrichment_discovery.cjs.map +1 -1
- package/dist/components/PlDatasetSelector/enrichment_discovery.js +2 -2
- package/dist/components/PlDatasetSelector/enrichment_discovery.js.map +1 -1
- package/dist/index.cjs +9 -9
- package/dist/index.d.ts +5 -5
- package/dist/index.js +5 -5
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/package.json +9 -9
- package/src/columns/__test_helpers__/collection_driver.ts +6 -6
- package/src/columns/__test_helpers__/stub_registry.ts +4 -6
- package/src/columns/column.ts +20 -9
- package/src/columns/column_providers/index.ts +9 -3
- package/src/columns/column_providers/providers.ts +16 -15
- package/src/columns/column_providers/types.ts +3 -3
- package/src/columns/column_recipes/column_discovered_recipe.ts +1 -1
- package/src/columns/column_recipes/column_overrided_recipe.ts +58 -4
- package/src/columns/column_recipes/column_recipes.test.ts +2 -2
- package/src/columns/column_recipes/index.ts +5 -20
- package/src/columns/{column_lazy.test.ts → data_column.test.ts} +19 -19
- package/src/columns/{column_lazy.ts → data_column.ts} +105 -67
- package/src/columns/derive_axis_values_labels.ts +4 -4
- package/src/columns/index.ts +2 -2
- package/src/columns/predicates.test.ts +124 -0
- package/src/columns/{expand_by_partition.test.ts → split_by_axes.test.ts} +29 -21
- package/src/columns/{expand_by_partition.ts → split_by_axes.ts} +22 -9
- package/src/columns/utils.ts +53 -48
- package/src/components/PlDataTable/columnResolver.test.ts +3 -3
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV2.ts +3 -3
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV2.ts +2 -2
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +2 -2
- package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +2 -2
- package/src/components/PlDataTable/createPlDataTable/utils.test.ts +3 -3
- package/src/components/PlDatasetSelector/enrichment_discovery.ts +2 -2
- package/dist/columns/column_lazy.cjs.map +0 -1
- package/dist/columns/column_lazy.d.ts +0 -137
- package/dist/columns/column_lazy.d.ts.map +0 -1
- package/dist/columns/column_lazy.js.map +0 -1
- package/dist/columns/expand_by_partition.cjs.map +0 -1
- package/dist/columns/expand_by_partition.d.ts.map +0 -1
- package/dist/columns/expand_by_partition.js.map +0 -1
|
@@ -13,9 +13,10 @@ import {
|
|
|
13
13
|
} from "@milaboratories/pl-model-common";
|
|
14
14
|
import { describe, expect, test } from "vitest";
|
|
15
15
|
import type { PColumnDataUniversal } from "../render/internal";
|
|
16
|
-
import type {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import type { DataColumnRecipe } from "./data_column";
|
|
17
|
+
import { DataColumnImpl } from "./data_column";
|
|
18
|
+
import type { SplitByAxesOpts } from "./split_by_axes";
|
|
19
|
+
import { splitByAxes } from "./split_by_axes";
|
|
19
20
|
|
|
20
21
|
// --- Helpers ---
|
|
21
22
|
|
|
@@ -33,13 +34,13 @@ function createSpec(name: string, axes: AxisSpec[]): PColumnSpec {
|
|
|
33
34
|
} as PColumnSpec;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
/** Build a synthetic
|
|
37
|
+
/** Build a synthetic DataColumnRecipe whose data is a JsonPartitioned DataInfoEntries. */
|
|
37
38
|
function createReadyLazy(
|
|
38
39
|
id: string,
|
|
39
40
|
spec: PColumnSpec,
|
|
40
41
|
partitionKeyLength: number,
|
|
41
42
|
parts: { key: (string | number)[]; value: unknown }[],
|
|
42
|
-
):
|
|
43
|
+
): DataColumnRecipe {
|
|
43
44
|
const dataEntries: JsonPartitionedDataInfoEntries<unknown> = {
|
|
44
45
|
type: "JsonPartitioned",
|
|
45
46
|
partitionKeyLength,
|
|
@@ -48,15 +49,15 @@ function createReadyLazy(
|
|
|
48
49
|
// `data` is typed `PColumnDataUniversal` but `getUniquePartitionKeys`
|
|
49
50
|
// duck-types DataInfoEntries via `isDataInfoEntries`, so the JsonPartitioned
|
|
50
51
|
// payload works at runtime.
|
|
51
|
-
return
|
|
52
|
+
return DataColumnImpl.fromColumn({
|
|
52
53
|
id: id as PObjectId,
|
|
53
54
|
spec,
|
|
54
55
|
data: dataEntries as unknown as PColumnDataUniversal,
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
function createComputingLazy(id: string, spec: PColumnSpec):
|
|
59
|
-
return
|
|
59
|
+
function createComputingLazy(id: string, spec: PColumnSpec): DataColumnRecipe {
|
|
60
|
+
return DataColumnImpl.fromColumn({
|
|
60
61
|
id: id as PObjectId,
|
|
61
62
|
spec,
|
|
62
63
|
data: undefined,
|
|
@@ -74,14 +75,21 @@ function extractTrace(recipe: { getSpec(): PColumnSpec }): Trace[] {
|
|
|
74
75
|
return raw ? (JSON.parse(raw) as Trace[]) : [];
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* `axisValuesLabels` defaults to `deriveAxisValuesLabels()`, which needs the
|
|
80
|
+
* ambient render ctx these unit tests deliberately run without. Every case
|
|
81
|
+
* below is about the split itself, so opt out of label resolution explicitly.
|
|
82
|
+
*/
|
|
83
|
+
const NO_LABELS: SplitByAxesOpts = { axisValuesLabels: () => undefined };
|
|
84
|
+
|
|
77
85
|
// --- Tests ---
|
|
78
86
|
|
|
79
|
-
describe("
|
|
87
|
+
describe("splitByAxes", () => {
|
|
80
88
|
test("no split axes returns inputs unchanged", () => {
|
|
81
89
|
const s = createReadyLazy("col1", createSpec("c", [createAxis("a")]), 1, [
|
|
82
90
|
{ key: ["x"], value: {} },
|
|
83
91
|
]);
|
|
84
|
-
const result =
|
|
92
|
+
const result = splitByAxes([s], []);
|
|
85
93
|
expect(result).toBeDefined();
|
|
86
94
|
expect(result).toHaveLength(1);
|
|
87
95
|
expect(result![0]).toBe(s); // same reference
|
|
@@ -99,7 +107,7 @@ describe("expandByPartition", () => {
|
|
|
99
107
|
],
|
|
100
108
|
);
|
|
101
109
|
|
|
102
|
-
const result =
|
|
110
|
+
const result = splitByAxes([s], [{ idx: 0 }], NO_LABELS);
|
|
103
111
|
expect(result).toBeDefined();
|
|
104
112
|
// unique values on axis 0: s1, s2 → 2 recipes
|
|
105
113
|
expect(result).toHaveLength(2);
|
|
@@ -124,7 +132,7 @@ describe("expandByPartition", () => {
|
|
|
124
132
|
],
|
|
125
133
|
);
|
|
126
134
|
|
|
127
|
-
const result =
|
|
135
|
+
const result = splitByAxes([s], [{ idx: 0 }, { idx: 1 }], NO_LABELS);
|
|
128
136
|
expect(result).toBeDefined();
|
|
129
137
|
// a: a1, a2 (2) × b: b1, b2 (2) = 4
|
|
130
138
|
expect(result).toHaveLength(4);
|
|
@@ -148,7 +156,7 @@ describe("expandByPartition", () => {
|
|
|
148
156
|
],
|
|
149
157
|
);
|
|
150
158
|
|
|
151
|
-
const result =
|
|
159
|
+
const result = splitByAxes([s], [{ idx: 0 }], NO_LABELS);
|
|
152
160
|
expect(result).toBeDefined();
|
|
153
161
|
expect(result).toHaveLength(2);
|
|
154
162
|
|
|
@@ -183,7 +191,7 @@ describe("expandByPartition", () => {
|
|
|
183
191
|
};
|
|
184
192
|
const s = createReadyLazy("col1", spec, 1, [{ key: ["s1"], value: {} }]);
|
|
185
193
|
|
|
186
|
-
const result =
|
|
194
|
+
const result = splitByAxes([s], [{ idx: 0 }], NO_LABELS);
|
|
187
195
|
expect(result).toBeDefined();
|
|
188
196
|
expect(result).toHaveLength(1);
|
|
189
197
|
|
|
@@ -205,7 +213,7 @@ describe("expandByPartition", () => {
|
|
|
205
213
|
s2: "Sample Two",
|
|
206
214
|
};
|
|
207
215
|
|
|
208
|
-
const result =
|
|
216
|
+
const result = splitByAxes([s], [{ idx: 0 }], {
|
|
209
217
|
axisValuesLabels: () => labels,
|
|
210
218
|
});
|
|
211
219
|
|
|
@@ -216,7 +224,7 @@ describe("expandByPartition", () => {
|
|
|
216
224
|
|
|
217
225
|
test("computing input (data undefined) returns undefined", () => {
|
|
218
226
|
const s = createComputingLazy("col1", createSpec("c", [createAxis("a")]));
|
|
219
|
-
const result =
|
|
227
|
+
const result = splitByAxes([s], [{ idx: 0 }], NO_LABELS);
|
|
220
228
|
expect(result).toBeUndefined();
|
|
221
229
|
});
|
|
222
230
|
|
|
@@ -228,7 +236,7 @@ describe("expandByPartition", () => {
|
|
|
228
236
|
[], // no parts → no unique keys
|
|
229
237
|
);
|
|
230
238
|
|
|
231
|
-
const result =
|
|
239
|
+
const result = splitByAxes([s], [{ idx: 0 }], NO_LABELS);
|
|
232
240
|
expect(result).toBeDefined();
|
|
233
241
|
expect(result).toHaveLength(0);
|
|
234
242
|
});
|
|
@@ -244,7 +252,7 @@ describe("expandByPartition", () => {
|
|
|
244
252
|
],
|
|
245
253
|
);
|
|
246
254
|
|
|
247
|
-
const result =
|
|
255
|
+
const result = splitByAxes([s], [{ idx: 0 }], NO_LABELS);
|
|
248
256
|
expect(result).toBeDefined();
|
|
249
257
|
expect(result).toHaveLength(2);
|
|
250
258
|
|
|
@@ -274,7 +282,7 @@ describe("expandByPartition", () => {
|
|
|
274
282
|
],
|
|
275
283
|
);
|
|
276
284
|
|
|
277
|
-
const result =
|
|
285
|
+
const result = splitByAxes([s], [{ idx: 0 }], NO_LABELS);
|
|
278
286
|
expect(result).toBeDefined();
|
|
279
287
|
expect(result).toHaveLength(2);
|
|
280
288
|
|
|
@@ -306,7 +314,7 @@ describe("expandByPartition", () => {
|
|
|
306
314
|
],
|
|
307
315
|
);
|
|
308
316
|
|
|
309
|
-
const result =
|
|
317
|
+
const result = splitByAxes([s1, s2], [{ idx: 0 }], NO_LABELS);
|
|
310
318
|
expect(result).toBeDefined();
|
|
311
319
|
// s1: 2 unique + s2: 3 unique = 5 total
|
|
312
320
|
expect(result).toHaveLength(5);
|
|
@@ -320,6 +328,6 @@ describe("expandByPartition", () => {
|
|
|
320
328
|
[{ key: ["x"], value: {} }],
|
|
321
329
|
);
|
|
322
330
|
|
|
323
|
-
expect(() =>
|
|
331
|
+
expect(() => splitByAxes([s], [{ idx: 1 }], NO_LABELS)).toThrow(/Not enough partition keys/);
|
|
324
332
|
});
|
|
325
333
|
});
|
|
@@ -13,11 +13,12 @@ import {
|
|
|
13
13
|
} from "@milaboratories/pl-model-common";
|
|
14
14
|
import { TreeNodeAccessor } from "../render/accessor";
|
|
15
15
|
import { getUniquePartitionKeys } from "../render/util/pcolumn_data";
|
|
16
|
+
import { deriveAxisValuesLabels } from "./derive_axis_values_labels";
|
|
16
17
|
import type { ColumnRecipe } from "./column_recipes";
|
|
17
18
|
import { ColumnFilteredRecipe } from "./column_recipes/column_filtered_recipe";
|
|
18
19
|
import { ColumnOverriddenRecipe } from "./column_recipes/column_overrided_recipe";
|
|
19
20
|
import { Column } from "./column";
|
|
20
|
-
import {
|
|
21
|
+
import { hasReachableData } from "./utils";
|
|
21
22
|
|
|
22
23
|
// --- Types ---
|
|
23
24
|
|
|
@@ -26,8 +27,15 @@ export interface SplitAxis {
|
|
|
26
27
|
readonly idx: number;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export interface
|
|
30
|
-
/**
|
|
30
|
+
export interface SplitByAxesOpts {
|
|
31
|
+
/**
|
|
32
|
+
* Resolve axis values to human-readable labels.
|
|
33
|
+
*
|
|
34
|
+
* Defaults to {@link deriveAxisValuesLabels} over the ambient render ctx
|
|
35
|
+
* (current block outputs + result pool), resolved lazily on the first
|
|
36
|
+
* lookup. Pass an explicit callback to narrow the label source, or
|
|
37
|
+
* `() => undefined` to keep raw axis values.
|
|
38
|
+
*/
|
|
31
39
|
axisValuesLabels?: (axisId: AxisId) => undefined | Record<string | number, string>;
|
|
32
40
|
}
|
|
33
41
|
|
|
@@ -36,8 +44,11 @@ export interface ExpandByPartitionOpts {
|
|
|
36
44
|
const MAX_KEY_COMBINATIONS = 10_000;
|
|
37
45
|
|
|
38
46
|
/**
|
|
39
|
-
*
|
|
40
|
-
* {@link ColumnRecipe} per Cartesian combination of
|
|
47
|
+
* Split each input column along the requested axes into one
|
|
48
|
+
* {@link ColumnRecipe} per Cartesian combination of the axes' distinct values.
|
|
49
|
+
*
|
|
50
|
+
* The split axes must be partition-key axes of the input's data — the distinct
|
|
51
|
+
* values are read off the partition keys.
|
|
41
52
|
*
|
|
42
53
|
* Each split is produced as `ColumnOverriddenRecipe.wrap(ColumnFilteredRecipe.wrap(inner, axisFilters), { domain, annotations })`:
|
|
43
54
|
* - `ColumnFilteredRecipe` pins all split axes at once (one wrap call with
|
|
@@ -55,22 +66,24 @@ const MAX_KEY_COMBINATIONS = 10_000;
|
|
|
55
66
|
* Returns `undefined` when any input's `getData()` is not a
|
|
56
67
|
* {@link TreeNodeAccessor} — partition inspection is not yet possible.
|
|
57
68
|
*/
|
|
58
|
-
export function
|
|
69
|
+
export function splitByAxes(
|
|
59
70
|
inputs: Column[],
|
|
60
71
|
splitAxes: SplitAxis[],
|
|
61
|
-
opts?:
|
|
72
|
+
opts?: SplitByAxesOpts,
|
|
62
73
|
): ColumnRecipe[] | undefined {
|
|
63
74
|
if (splitAxes.length === 0) {
|
|
64
75
|
return [...inputs];
|
|
65
76
|
}
|
|
66
77
|
|
|
78
|
+
const axisValuesLabels = opts?.axisValuesLabels ?? deriveAxisValuesLabels();
|
|
79
|
+
|
|
67
80
|
const splitAxisIdxs = splitAxes.map((a) => a.idx).sort((a, b) => a - b);
|
|
68
81
|
const maxSplitIdx = splitAxisIdxs[splitAxisIdxs.length - 1];
|
|
69
82
|
|
|
70
83
|
const result: ColumnRecipe[] = [];
|
|
71
84
|
|
|
72
85
|
for (const inner of inputs) {
|
|
73
|
-
const data =
|
|
86
|
+
const data = hasReachableData(inner) ? inner.getData() : undefined;
|
|
74
87
|
// Partition inspection requires either a live tree-accessor or already
|
|
75
88
|
// parsed DataInfoEntries. Anything else means the input is not yet ready.
|
|
76
89
|
if (!(data instanceof TreeNodeAccessor) && !isDataInfoEntries(data)) {
|
|
@@ -90,7 +103,7 @@ export function expandByPartition(
|
|
|
90
103
|
const axesSpec = spec.axesSpec;
|
|
91
104
|
const splitAxisSpecs = splitAxisIdxs.map((idx) => axesSpec[idx]);
|
|
92
105
|
const splitAxisIds = splitAxisSpecs.map((axisSpec) => getAxisId(axisSpec));
|
|
93
|
-
const axesLabels = splitAxisIds.map((axisId) =>
|
|
106
|
+
const axesLabels = splitAxisIds.map((axisId) => axisValuesLabels(axisId));
|
|
94
107
|
|
|
95
108
|
const existingTraceRaw = readAnnotation(spec, Annotation.Trace);
|
|
96
109
|
const baseTrace: TraceEntry[] = existingTraceRaw
|
package/src/columns/utils.ts
CHANGED
|
@@ -11,10 +11,13 @@ import { throwError } from "@milaboratories/helpers";
|
|
|
11
11
|
import type { GlobalCfgRenderCtx } from "../render/internal";
|
|
12
12
|
import { TreeNodeAccessor } from "../render";
|
|
13
13
|
import { deriveDistinctLabels, type DeriveLabelsOptions } from "../labels/derive_distinct_labels";
|
|
14
|
-
import {
|
|
14
|
+
import { DataColumnRecipe, DataColumnImpl, type ColumnData } from "./data_column";
|
|
15
15
|
import { ColumnDiscoveredRecipe } from "./column_recipes/column_discovered_recipe";
|
|
16
16
|
import { ColumnFilteredRecipe } from "./column_recipes/column_filtered_recipe";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
ColumnOverriddenRecipe,
|
|
19
|
+
DataColumnOverriddenRecipe,
|
|
20
|
+
} from "./column_recipes/column_overrided_recipe";
|
|
18
21
|
import type { ColumnRecipe } from "./column_recipes/types";
|
|
19
22
|
import { ColumnsCollection, isColumnsCollection } from "./columns_collection";
|
|
20
23
|
import type { ColumnsSource } from "./column_providers/types";
|
|
@@ -26,7 +29,7 @@ import type { ColumnsSource } from "./column_providers/types";
|
|
|
26
29
|
* other engine-consumed column.
|
|
27
30
|
*
|
|
28
31
|
* Pure query walk — no registry access. Use {@link collectLinkerColumns} for
|
|
29
|
-
* the resolved {@link
|
|
32
|
+
* the resolved {@link DataColumnRecipe} variant.
|
|
30
33
|
*/
|
|
31
34
|
export function collectLinkerIds(recipe: ColumnRecipe): PObjectId[] {
|
|
32
35
|
const hit = extractPObjectId(recipe.id);
|
|
@@ -47,16 +50,16 @@ export function collectLinkerIds(recipe: ColumnRecipe): PObjectId[] {
|
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
52
|
* {@link collectLinkerIds} resolved against the ambient context as
|
|
50
|
-
* {@link
|
|
53
|
+
* {@link DataColumnRecipe} leaves. Throws if any id fails to resolve —
|
|
51
54
|
* matches the contract of the legacy `resolveLinkers` it replaces.
|
|
52
55
|
*/
|
|
53
56
|
export function collectLinkerColumns(
|
|
54
57
|
recipe: ColumnRecipe,
|
|
55
58
|
opts: { ctx?: GlobalCfgRenderCtx } = {},
|
|
56
|
-
):
|
|
59
|
+
): DataColumnRecipe<PObjectId>[] {
|
|
57
60
|
return collectLinkerIds(recipe).map(
|
|
58
61
|
(id) =>
|
|
59
|
-
|
|
62
|
+
DataColumnImpl.fromId(id, opts) ??
|
|
60
63
|
throwError(`materializeLinkers: linker ${id} not resolvable`),
|
|
61
64
|
);
|
|
62
65
|
}
|
|
@@ -95,7 +98,7 @@ export function queriesQualifications(
|
|
|
95
98
|
*
|
|
96
99
|
* Walks every physical leaf the recipe depends on via
|
|
97
100
|
* {@link ColumnRecipe.getReferencedIds} (resolves each id back to a
|
|
98
|
-
* {@link
|
|
101
|
+
* {@link DataColumnRecipe} through the ambient ctx) and ANDs `hasData()` across
|
|
99
102
|
* all of them. Inline {@link PColumnValues} payloads count as present.
|
|
100
103
|
*
|
|
101
104
|
* Returns `false` if any leaf cannot be re-resolved in `opts.ctx` (treat as
|
|
@@ -107,15 +110,15 @@ export function hasColumnData(
|
|
|
107
110
|
opts: { ctx?: GlobalCfgRenderCtx } = {},
|
|
108
111
|
): boolean {
|
|
109
112
|
for (const id of recipe.getReferencedIds()) {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
if (
|
|
113
|
-
if (!
|
|
113
|
+
const leaf = DataColumnImpl.fromId(id, opts);
|
|
114
|
+
if (leaf === undefined) return false;
|
|
115
|
+
if (leaf.getDataStatus() !== "present") return false;
|
|
116
|
+
if (!isLeafDataPresent(leaf.getData())) return false;
|
|
114
117
|
}
|
|
115
118
|
return true;
|
|
116
119
|
}
|
|
117
120
|
|
|
118
|
-
function
|
|
121
|
+
function isLeafDataPresent(data: ColumnData): boolean {
|
|
119
122
|
if (data === undefined) return false;
|
|
120
123
|
if (Array.isArray(data)) return true;
|
|
121
124
|
if (data instanceof TreeNodeAccessor) return data.hasData();
|
|
@@ -151,51 +154,53 @@ function findDiscovered(recipe: ColumnRecipe): undefined | ColumnDiscoveredRecip
|
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
157
|
+
* Whether the recipe reads exactly one data column — its own — rather than
|
|
158
|
+
* reaching through others.
|
|
159
|
+
*
|
|
160
|
+
* True for a bare leaf and for projections over one (spec overrides, axis
|
|
161
|
+
* slicing): those add layers but no new columns. False when the recipe gets at
|
|
162
|
+
* its data through further columns, which happens when discovery reached it via
|
|
163
|
+
* a linker chain.
|
|
156
164
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
165
|
+
* **Why callers care.** A recipe over a single data column is co-indexed by its
|
|
166
|
+
* own axes, so its spec is the whole truth about them. One that needs other
|
|
167
|
+
* columns only lines up after they are joined in, and therefore drags axes into
|
|
168
|
+
* the join that its own spec never mentions — which is why it cannot serve as a
|
|
169
|
+
* primary column, and why `createPlDataTableV3` splits on this.
|
|
170
|
+
*
|
|
171
|
+
* The count comes from {@link ColumnRecipe.getReferencedIds}, which resolves
|
|
172
|
+
* through the whole wrapper chain, so no class walk is involved and foreign
|
|
173
|
+
* `ColumnRecipe` implementations are handled too.
|
|
161
174
|
*/
|
|
162
|
-
export function
|
|
163
|
-
return
|
|
175
|
+
export function hasSingleDataColumn(recipe: ColumnRecipe): boolean {
|
|
176
|
+
return recipe.getReferencedIds().length === 1;
|
|
164
177
|
}
|
|
165
178
|
|
|
166
179
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* {@link
|
|
170
|
-
* chain reaches a {@link ColumnDiscoveredRecipe}); the symmetric counterpart
|
|
171
|
-
* of {@link isLeafColumn} returning `false`.
|
|
180
|
+
* @deprecated Renamed to {@link hasSingleDataColumn} — "leaf" read as "you can
|
|
181
|
+
* get data out of it", which is a different question (see
|
|
182
|
+
* {@link hasReachableData}). Same semantics.
|
|
172
183
|
*/
|
|
173
|
-
export
|
|
174
|
-
if (!isLeafColumn(recipe)) {
|
|
175
|
-
throw new Error(`getLeafColumnData: recipe ${recipe.id} is not a leaf column`);
|
|
176
|
-
}
|
|
177
|
-
return extractLeafColumn(recipe)?.getData();
|
|
178
|
-
}
|
|
184
|
+
export const isLeafColumn = hasSingleDataColumn;
|
|
179
185
|
|
|
180
186
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
187
|
+
* Whether the column's data can be read here and now, consistent with its
|
|
188
|
+
* spec — narrows to {@link DataColumnRecipe}, which exposes `getData()`.
|
|
189
|
+
*
|
|
190
|
+
* True for a bare leaf and for a spec-override over a bare leaf. False for an
|
|
191
|
+
* axis-filtered recipe (its spec has dropped axes the underlying data still
|
|
192
|
+
* carries, so the two no longer line up) and for anything reached through
|
|
193
|
+
* other columns (nothing to read until the engine joins them). In both of
|
|
194
|
+
* those cases the data exists only engine-side: pass `recipe.id` to
|
|
195
|
+
* `createPFrame` / `createPTable` instead of trying to read it.
|
|
196
|
+
*
|
|
197
|
+
* The distinction is settled when the recipe is built, not here — recipes that
|
|
198
|
+
* carry data are instances of a class that declares `getData`. So a `true`
|
|
199
|
+
* result narrows to a value that really has the method, and there is no
|
|
200
|
+
* throwing path behind the guard.
|
|
185
201
|
*/
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
while (true) {
|
|
189
|
-
if (current instanceof ColumnLazyImpl) return current;
|
|
190
|
-
if (current instanceof ColumnOverriddenRecipe || current instanceof ColumnFilteredRecipe) {
|
|
191
|
-
current = current.getInner();
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
if (current instanceof ColumnDiscoveredRecipe) {
|
|
195
|
-
throw new Error(`extractLeafColumn: recipe ${recipe.id} is not a leaf column`);
|
|
196
|
-
}
|
|
197
|
-
throw new Error(`extractLeafColumn: unrecognized recipe layer for ${recipe.id}`);
|
|
198
|
-
}
|
|
202
|
+
export function hasReachableData(recipe: ColumnRecipe): recipe is DataColumnRecipe {
|
|
203
|
+
return recipe instanceof DataColumnImpl || recipe instanceof DataColumnOverriddenRecipe;
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
/** Drop-down option built over a {@link ColumnsCollection} — universal-id valued. */
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type PTableColumnId,
|
|
10
10
|
type SUniversalPColumnId,
|
|
11
11
|
} from "@milaboratories/pl-model-common";
|
|
12
|
-
import {
|
|
12
|
+
import { DataColumnImpl } from "../../columns";
|
|
13
13
|
import type { ColumnRecipe } from "../../columns";
|
|
14
14
|
import { createColumnResolver } from "./columnResolver";
|
|
15
15
|
|
|
@@ -20,7 +20,7 @@ function wrap(id: PObjectId | SUniversalPColumnId, tag: string): SUniversalPColu
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// DataColumnRecipe constructor pulls `getCfgRenderCtx()` from globalThis on some
|
|
24
24
|
// paths — keep a minimal mock the same way p_column_lazy.test.ts does.
|
|
25
25
|
const mockCtx = {
|
|
26
26
|
getAccessorHandleByName: () => undefined,
|
|
@@ -36,7 +36,7 @@ afterEach(() => {
|
|
|
36
36
|
|
|
37
37
|
function lazyOf(id: PObjectId, axes: AxisSpec[] = []) {
|
|
38
38
|
const spec = { kind: "PColumn", name: "stub", axesSpec: axes } as unknown as PColumnSpec;
|
|
39
|
-
return
|
|
39
|
+
return DataColumnImpl.fromColumn({
|
|
40
40
|
id,
|
|
41
41
|
spec,
|
|
42
42
|
data: undefined,
|
|
@@ -10,7 +10,7 @@ import type { PColumnDataUniversal } from "../../../render";
|
|
|
10
10
|
import { isFunction } from "es-toolkit";
|
|
11
11
|
import type { PlDataTableFilters } from "../typesV8";
|
|
12
12
|
import { createPTableDefV3 } from "./createPTableDefV3";
|
|
13
|
-
import {
|
|
13
|
+
import { DataColumn } from "../../../columns";
|
|
14
14
|
|
|
15
15
|
export function createPTableDefV2(params: {
|
|
16
16
|
columns: PColumn<undefined | PColumnDataUniversal>[];
|
|
@@ -33,8 +33,8 @@ export function createPTableDefV2(params: {
|
|
|
33
33
|
secondaryColumns.push(...params.labelColumns);
|
|
34
34
|
|
|
35
35
|
return createPTableDefV3({
|
|
36
|
-
primary: coreColumns.map((column) =>
|
|
37
|
-
secondary: secondaryColumns.map((column) =>
|
|
36
|
+
primary: coreColumns.map((column) => DataColumn.fromColumn(column)),
|
|
37
|
+
secondary: secondaryColumns.map((column) => DataColumn.fromColumn(column)),
|
|
38
38
|
primaryJoinType: params.coreJoinType,
|
|
39
39
|
filters: params.filters,
|
|
40
40
|
sorting: params.sorting,
|
|
@@ -31,7 +31,7 @@ import { getMatchingLabelColumns } from "../labels";
|
|
|
31
31
|
import { collectFilterSpecColumns, traverseFilterSpec } from "../../../filters/traverse";
|
|
32
32
|
import { createPTableDefV2 } from "./createPTableDefV2";
|
|
33
33
|
import { isColumnOptional } from "./utils";
|
|
34
|
-
import {
|
|
34
|
+
import { DataColumn } from "../../../columns";
|
|
35
35
|
import { createColumnResolver, type ColumnResolver } from "../columnResolver";
|
|
36
36
|
import { isNil, type Nil } from "@milaboratories/helpers";
|
|
37
37
|
|
|
@@ -83,7 +83,7 @@ export function createPlDataTableV2<A, U>(
|
|
|
83
83
|
const fullColumns = [...columns, ...fullLabelColumns];
|
|
84
84
|
|
|
85
85
|
const resolver = createColumnResolver(
|
|
86
|
-
fullColumns.map((c) =>
|
|
86
|
+
fullColumns.map((c) => DataColumn.fromColumn(c)),
|
|
87
87
|
{ warn: ctx.logWarn.bind(ctx) },
|
|
88
88
|
);
|
|
89
89
|
|
|
@@ -27,7 +27,7 @@ import { upgradePlDataTableStateV2 } from "../state-migration";
|
|
|
27
27
|
import type { PlDataTableStateV2 } from "../state-migration";
|
|
28
28
|
import type { MatchingMode } from "@milaboratories/pl-model-common";
|
|
29
29
|
import {
|
|
30
|
-
|
|
30
|
+
hasSingleDataColumn,
|
|
31
31
|
hitQualifications,
|
|
32
32
|
collectLinkerColumns,
|
|
33
33
|
queriesQualifications,
|
|
@@ -261,7 +261,7 @@ function splitByTopology(columns: ColumnRecipe[]): {
|
|
|
261
261
|
const direct: ColumnRecipe[] = [];
|
|
262
262
|
const linked: ColumnRecipe[] = [];
|
|
263
263
|
for (const c of columns) {
|
|
264
|
-
if (
|
|
264
|
+
if (hasSingleDataColumn(c)) direct.push(c);
|
|
265
265
|
else linked.push(c);
|
|
266
266
|
}
|
|
267
267
|
return { direct, linked };
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
import { PColumnName } from "@milaboratories/pl-model-common";
|
|
8
8
|
import type { RenderCtxBase } from "../../../render";
|
|
9
9
|
import type { ColumnRecipe, ColumnsSource } from "../../../columns";
|
|
10
|
-
import { ColumnsCollection,
|
|
10
|
+
import { ColumnsCollection, hasSingleDataColumn } from "../../../columns";
|
|
11
11
|
import type { ColumnsSelectorConfig } from "./createPlDataTableV3";
|
|
12
12
|
|
|
13
13
|
export type DiscoverTableColumnOptions = {
|
|
@@ -38,7 +38,7 @@ export function discoverTableColumns(
|
|
|
38
38
|
const primary: ColumnRecipe[] = [];
|
|
39
39
|
const secondary: ColumnRecipe[] = [];
|
|
40
40
|
for (const col of discoveredColumns) {
|
|
41
|
-
if (
|
|
41
|
+
if (hasSingleDataColumn(col)) primary.push(col);
|
|
42
42
|
else secondary.push(col);
|
|
43
43
|
}
|
|
44
44
|
return { primary, secondary };
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
8
8
|
import { deriveAllLabels, evaluateRules, type LabelableColumn } from "./utils";
|
|
9
9
|
import type { ColumnOrderRule, ColumnVisibilityRule } from "./createPlDataTableV3";
|
|
10
|
-
import {
|
|
10
|
+
import { DataColumn, type DataColumnRecipe } from "../../../columns";
|
|
11
11
|
import {
|
|
12
12
|
createTestCollectionDriver,
|
|
13
13
|
type TestCollectionDriverHandle,
|
|
@@ -156,11 +156,11 @@ function gid(name: string): PObjectId {
|
|
|
156
156
|
return createGlobalPObjectId("test-block", name);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
function makeLazyColumn(name: string, spec: Partial<PColumnSpec> = {}):
|
|
159
|
+
function makeLazyColumn(name: string, spec: Partial<PColumnSpec> = {}): DataColumnRecipe {
|
|
160
160
|
const s = makeSpec({ axesSpec: [{ name: "id", type: "String" }], ...spec });
|
|
161
161
|
const id = gid(name);
|
|
162
162
|
driverHandle.register([{ id, spec: s }]);
|
|
163
|
-
return
|
|
163
|
+
return DataColumn.fromColumn({ id, spec: s, data: undefined as never });
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
describe("evaluateRules", () => {
|
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
} from "@milaboratories/pl-model-common";
|
|
16
16
|
import type { ColumnRecipe, ColumnsSource } from "../../columns";
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
hasSingleDataColumn,
|
|
19
19
|
ColumnsCollection,
|
|
20
20
|
collectLinkerIds,
|
|
21
21
|
hitQualifications,
|
|
@@ -67,7 +67,7 @@ export function findEnrichmentColumns(options: FindEnrichmentColumnsOptions): Co
|
|
|
67
67
|
|
|
68
68
|
const predicate = options.predicate;
|
|
69
69
|
return columns.filter((v) => {
|
|
70
|
-
if (
|
|
70
|
+
if (hasSingleDataColumn(v)) return false;
|
|
71
71
|
if (predicate !== undefined && !predicate(v.getSpec())) return false;
|
|
72
72
|
if (!isGlobalPObjectId(extractPObjectId(v.id))) return false;
|
|
73
73
|
if (collectLinkerIds(v).some((id) => !isGlobalPObjectId(id))) return false;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"column_lazy.cjs","names":["ColumnOverriddenRecipe","ColumnRegistry","getCtxProviders","LRUCache"],"sources":["../../src/columns/column_lazy.ts"],"sourcesContent":["import {\n ColumnRegistry,\n createGlobalPObjectId,\n isPColumn,\n isPlRef,\n PlRef,\n PObjectId,\n type ColumnUniversalId,\n type LeafEntry,\n type PColumn,\n type PColumnSpec,\n type SpecOverrides,\n type SpecQuery,\n} from \"@milaboratories/pl-model-common\";\nimport type { GlobalCfgRenderCtx, PColumnDataUniversal } from \"../render/internal\";\nimport { getCtxProviders } from \"./column_providers\";\nimport { isNil } from \"es-toolkit\";\nimport { LRUCache } from \"lru-cache\";\nimport { TreeNodeAccessor } from \"../render\";\nimport type {\n ColumnFieldStatus,\n ColumnRecipe,\n ColumnResolutionStatus,\n} from \"./column_recipes/types\";\nimport { ColumnOverriddenRecipe } from \"./column_recipes/column_overrided_recipe\";\n\nexport type ColumnLazyId = PObjectId;\nexport type ColumnLazyData = undefined | PColumnDataUniversal;\nexport type { ColumnFieldStatus, ColumnResolutionStatus } from \"./column_recipes/types\";\n\n/**\n * Thrown by leaf-recipe factories when the requested column is provably\n * absent in the active render ctx — i.e. every relevant accessor reports\n * `inputsLocked` and the column did not appear. Distinct from the\n * `undefined` return that the factories still use for the \"resolving\" case\n * (the column may still appear later).\n *\n * Catch at the boundary (resolver / filter+sort wiring / data-table assembly)\n * when partial absence should be surfaced to the user rather than silently\n * producing an empty result.\n */\nexport class ColumnAbsentError extends Error {\n constructor(public readonly id: ColumnUniversalId) {\n super(`Column is absent in the active render ctx and will not appear: ${id}`);\n this.name = \"ColumnAbsentError\";\n }\n}\n\n/**\n * ColumnLazy is the leaf-recipe building block: a {@link ColumnRecipe} whose\n * `id` is a bare {@link PObjectId} and whose readers are bound to a single\n * tree-accessor leaf. Layered encodings (Overridden / Discovered / Filtered)\n * are reified through their dedicated recipe classes and reference leaf\n * columns by id.\n */\nexport class ColumnLazyImpl implements ColumnRecipe<PObjectId> {\n private specCache?: { readonly value: PColumnSpec };\n private dataCache?: { readonly value: ColumnLazyData };\n private dataStatusCache?: { readonly value: ColumnFieldStatus };\n\n private constructor(\n public readonly id: PObjectId,\n private readonly options: {\n getSpec: () => PColumnSpec;\n getData: () => ColumnLazyData;\n getDataStatus: () => ColumnFieldStatus;\n },\n ) {}\n\n /** A leaf-recipe references exactly one column — its own id. */\n getReferencedIds(): PObjectId[] {\n return [this.id];\n }\n\n getSpec(): PColumnSpec {\n if (this.specCache === undefined) {\n this.specCache = { value: this.options.getSpec() };\n }\n return this.specCache.value;\n }\n\n /** Leaf-shaped — points straight at `this.id`. */\n getQuery(): SpecQuery {\n return { type: \"column\", column: this.id } as SpecQuery;\n }\n\n /** Leaf-only: not on the recipe interface — only the leaf can read data directly. */\n getData(): ColumnLazyData {\n if (this.dataCache === undefined) this.dataCache = { value: this.options.getData() };\n return this.dataCache.value;\n }\n\n getDataStatus(): ColumnFieldStatus {\n if (this.dataStatusCache === undefined) {\n this.dataStatusCache = { value: this.options.getDataStatus() };\n }\n return this.dataStatusCache.value;\n }\n\n /**\n * Overlay overrides → produces a {@link ColumnOverriddenRecipe} wrapping\n * this leaf. ColumnLazy itself stays bare (id remains a plain PObjectId);\n * layering lives entirely in the recipe wrappers.\n */\n withSpecs(overrides: SpecOverrides): ColumnRecipe {\n return ColumnOverriddenRecipe.wrap(this, overrides);\n }\n\n /**\n * Resolve via the ambient {@link ColumnRegistry}. Spec is resolved eagerly:\n * returns `undefined` if the leaf isn't reachable yet (resolving). Throws\n * {@link ColumnAbsentError} when every relevant accessor is `inputsLocked`\n * and the column did not appear — the column will not exist in this ctx.\n * Data and dataStatus stay lazy.\n */\n static fromId(id: PObjectId, { ctx }: { ctx?: GlobalCfgRenderCtx } = {}): undefined | ColumnLazy {\n const registry = new ColumnRegistry(getCtxProviders({ ctx }));\n const leaf = registry.resolve(id);\n if (isNil(leaf)) {\n if (registry.isFinal()) throw new ColumnAbsentError(id);\n return undefined;\n }\n const spec = readSpecAccessor(leaf);\n if (isNil(spec)) {\n if (leaf.accessor.getInputsLocked()) throw new ColumnAbsentError(id);\n return undefined;\n }\n if (!spec.hasData()) return undefined;\n return new ColumnLazyImpl(id, {\n getSpec: () => spec.getDataAsJson<PColumnSpec>(),\n getData: () => readDataAccessor(leaf),\n getDataStatus: () => readDataStatus(leaf),\n });\n }\n\n /** {@link PlRef} wrapper over {@link fromId}. */\n static fromPlRef(ref: PlRef): undefined | ColumnLazy {\n return ColumnLazyImpl.fromId(createGlobalPObjectId(ref.blockId, ref.name));\n }\n\n /**\n * Bind directly to an accessor-backed {@link LeafEntry} — no registry.\n * Throws {@link ColumnAbsentError} if the leaf has no spec field and its\n * accessor is `inputsLocked`. Returns `undefined` while still resolving.\n */\n static fromAccessor(entry: LeafEntry<TreeNodeAccessor>): undefined | ColumnLazy {\n const spec = readSpecAccessor(entry);\n if (isNil(spec)) {\n if (entry.accessor.getInputsLocked()) throw new ColumnAbsentError(entry.id);\n return undefined;\n }\n if (!spec.hasData()) return undefined;\n return new ColumnLazyImpl(entry.id, {\n getSpec: () => spec.getDataAsJson<PColumnSpec>(),\n getData: () => readDataAccessor(entry),\n getDataStatus: () => readDataStatus(entry),\n });\n }\n\n /**\n * Wrap a materialised {@link PColumn}. If the input is already a\n * {@link ColumnLazy} it is returned as-is.\n */\n static fromColumn(column: PColumn<ColumnLazyData> | ColumnLazy): ColumnLazy {\n if (column instanceof ColumnLazyImpl) return column;\n return new ColumnLazyImpl(column.id, {\n getSpec: () => column.spec,\n getData: () => column.data,\n getDataStatus: () => \"present\",\n });\n }\n\n /**\n * Distinguishes `present` / `resolving` / `absent` for a {@link PObjectId}\n * in the active render ctx. Falls back to the registry's `isFinal()`\n * when the id has no entry — only then we can say `absent` instead of\n * `resolving`.\n */\n static getStatusById(\n id: PObjectId,\n { ctx }: { ctx?: GlobalCfgRenderCtx } = {},\n ): ColumnResolutionStatus {\n const registry = new ColumnRegistry(getCtxProviders({ ctx }));\n const leaf = registry.resolve(id);\n if (isNil(leaf)) return registry.isFinal() ? \"absent\" : \"resolving\";\n return getLeafEntryStatus(leaf);\n }\n\n /** {@link PlRef} wrapper over {@link getStatusById}. */\n static getStatusByPlRef(\n ref: PlRef,\n opts: { ctx?: GlobalCfgRenderCtx } = {},\n ): ColumnResolutionStatus {\n return ColumnLazyImpl.getStatusById(createGlobalPObjectId(ref.blockId, ref.name), opts);\n }\n\n /** No registry — reads straight off the entry's accessor. */\n static getStatusByAccessor(entry: LeafEntry<TreeNodeAccessor>): ColumnResolutionStatus {\n return getLeafEntryStatus(entry);\n }\n}\n\n/**\n * Public type alias — `ColumnLazy` (the type) refers to the underlying\n * {@link ColumnLazyImpl} class instance. `ColumnLazy` (the value) is the\n * callable below with the static factories attached.\n */\nexport type ColumnLazy = ColumnLazyImpl;\n\n/**\n * Unified dispatcher — picks the right `ColumnLazyImpl.fromX` by source\n * shape. For ambiguous inputs callers can still use the explicit factories\n * (also attached as properties: `ColumnLazy.fromId`, `.fromPlRef`,\n * `.fromAccessor`, `.fromColumn`).\n */\nfunction ColumnLazyDispatch(\n source: PObjectId | PlRef | LeafEntry<TreeNodeAccessor> | PColumn<ColumnLazyData> | ColumnLazy,\n opts: { ctx?: GlobalCfgRenderCtx } = {},\n): undefined | ColumnLazy {\n if (typeof source === \"string\") return ColumnLazyImpl.fromId(source, opts);\n if (source instanceof ColumnLazyImpl) return source;\n if (\"accessor\" in source) return ColumnLazyImpl.fromAccessor(source);\n if (isPlRef(source)) return ColumnLazyImpl.fromPlRef(source);\n if (isPColumn(source)) return ColumnLazyImpl.fromColumn(source);\n throw new Error(\"ColumnLazy: unknown source shape\");\n}\n\n/**\n * Polymorphic counterpart to {@link ColumnLazyDispatch}: returns the\n * {@link ColumnResolutionStatus} for any factory input without constructing\n * the recipe. For already-materialised sources ({@link PColumn} value,\n * existing {@link ColumnLazy}) status is `present` by construction.\n */\nfunction ColumnLazyGetStatus(\n source: PObjectId | PlRef | LeafEntry<TreeNodeAccessor> | PColumn<ColumnLazyData> | ColumnLazy,\n opts: { ctx?: GlobalCfgRenderCtx } = {},\n): ColumnResolutionStatus {\n if (typeof source === \"string\") return ColumnLazyImpl.getStatusById(source, opts);\n if (source instanceof ColumnLazyImpl) return \"present\";\n if (\"accessor\" in source) return ColumnLazyImpl.getStatusByAccessor(source);\n if (isPlRef(source)) return ColumnLazyImpl.getStatusByPlRef(source, opts);\n if (isPColumn(source)) return \"present\";\n throw new Error(\"ColumnLazy.getStatus: unknown source shape\");\n}\n\nexport const ColumnLazy = Object.assign(ColumnLazyDispatch, {\n fromId: ColumnLazyImpl.fromId,\n fromPlRef: ColumnLazyImpl.fromPlRef,\n fromAccessor: ColumnLazyImpl.fromAccessor,\n fromColumn: ColumnLazyImpl.fromColumn,\n getStatus: ColumnLazyGetStatus,\n getStatusById: ColumnLazyImpl.getStatusById,\n getStatusByPlRef: ColumnLazyImpl.getStatusByPlRef,\n getStatusByAccessor: ColumnLazyImpl.getStatusByAccessor,\n});\n\n/**\n * Type-guard narrowing to the leaf recipe. Prefer this over\n * `instanceof ColumnLazyImpl` — `ColumnLazy` (the value) is the dispatcher\n * with statics, not the class, so `instanceof` requires the impl symbol.\n */\nexport function isColumnLazy(value: unknown): value is ColumnLazy {\n return value instanceof ColumnLazyImpl;\n}\n\nconst readSpecAccessor = memoizeByEntry(\n ({ accessor, name }: LeafEntry<TreeNodeAccessor>): undefined | TreeNodeAccessor =>\n accessor.traverse({ field: `${name}.spec`, assertFieldType: \"Input\", ignoreError: true }),\n);\n\n/**\n * Per-entry counterpart to {@link readDataStatus}: tells whether the leaf's\n * **spec** can be read in this ctx, and — for the negative cases —\n * distinguishes `resolving` from `absent` via `getInputsLocked()`.\n *\n * - spec field not yet on the entry's accessor + inputs locked → `absent`\n * - spec field not yet on the entry's accessor + still resolving → `resolving`\n * - spec resource present but bytes not yet written → `resolving`\n * (transient — the spec resource is connected, just unfilled)\n * - spec resource present and `hasData()` → `present`\n */\nfunction getLeafEntryStatus(entry: LeafEntry<TreeNodeAccessor>): ColumnResolutionStatus {\n const spec = readSpecAccessor(entry);\n if (isNil(spec)) return entry.accessor.getInputsLocked() ? \"absent\" : \"resolving\";\n if (!spec.hasData()) return \"resolving\";\n return \"present\";\n}\n\nconst readDataAccessor = memoizeByEntry(\n ({ accessor, name }: LeafEntry<TreeNodeAccessor>): undefined | TreeNodeAccessor =>\n accessor.traverse({ field: `${name}.data`, assertFieldType: \"Input\", ignoreError: true }),\n);\n\nconst readDataStatus = memoizeByEntry(\n ({ accessor, name }: LeafEntry<TreeNodeAccessor>): ColumnFieldStatus => {\n if (accessor.listInputFields().includes(`${name}.data`)) return \"present\";\n return accessor.getInputsLocked() ? \"absent\" : \"resolving\";\n },\n);\n\nfunction memoizeByEntry<R>(\n fn: (entry: LeafEntry<TreeNodeAccessor>) => R,\n): (entry: LeafEntry<TreeNodeAccessor>) => R {\n const cache = new LRUCache<string, { value: R }>({ max: 1000 });\n return (entry) => {\n const key = `${entry.accessor.handle}:${entry.name}`;\n let hit = cache.get(key);\n if (!hit) cache.set(key, (hit = { value: fn(entry) }));\n return hit.value;\n };\n}\n"],"mappings":"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"}
|