@platforma-sdk/model 1.60.0 → 1.61.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.
@@ -5,44 +5,26 @@ import type {
5
5
  SUniversalPColumnId,
6
6
  } from "@milaboratories/pl-model-common";
7
7
  import { AnchoredIdDeriver } from "@milaboratories/pl-model-common";
8
+ import { SpecDriver } from "@milaboratories/pf-spec-driver";
8
9
 
9
- import { describe, expect, test } from "vitest";
10
+ import { afterEach, describe, expect, test } from "vitest";
10
11
  import type { ColumnSnapshotProvider } from "./column_snapshot_provider";
11
12
  import type { ColumnSnapshot } from "./column_snapshot";
12
13
  import { ColumnCollectionBuilder } from "./column_collection_builder";
13
14
 
14
- // --- Mock SpecFrameCtx ---
15
+ const drivers: SpecDriver[] = [];
15
16
 
16
17
  function createSpecFrameCtx() {
17
- const frames = new Map<string, Record<string, PColumnSpec>>();
18
- let nextId = 0;
19
-
20
- return {
21
- createSpecFrame: (specs: Record<string, PColumnSpec>) => {
22
- const handle = `frame-${nextId++}`;
23
- frames.set(handle, specs);
24
- return handle;
25
- },
26
- specFrameDiscoverColumns: (handle: string, _request: any) => {
27
- const specs = frames.get(handle)!;
28
- return {
29
- hits: Object.entries(specs).map(([columnId, spec]) => ({
30
- hit: { columnId: columnId as PObjectId, spec },
31
- mappingVariants: [
32
- {
33
- qualifications: { forQueries: [], forHit: [] },
34
- distinctiveQualifications: { forQueries: [], forHit: [] },
35
- },
36
- ],
37
- })),
38
- };
39
- },
40
- specFrameDispose: (handle: string) => {
41
- frames.delete(handle);
42
- },
43
- };
18
+ const driver = new SpecDriver();
19
+ drivers.push(driver);
20
+ return driver;
44
21
  }
45
22
 
23
+ afterEach(() => {
24
+ for (const driver of drivers) driver.dispose();
25
+ drivers.length = 0;
26
+ });
27
+
46
28
  // --- Helpers ---
47
29
 
48
30
  /** Default axis used when none specified — WASM requires at least one axis. */
@@ -173,15 +155,16 @@ describe("ColumnCollection.findColumns", () => {
173
155
  expect(collection.findColumns()).toHaveLength(2);
174
156
  });
175
157
 
176
- test("exclude throws not implemented", () => {
158
+ test("exclude filters out matching columns", () => {
177
159
  const s1 = createSnapshot("id1", createSpec("col1"));
160
+ const s2 = createSnapshot("id2", createSpec("col2"));
178
161
  const builder = new ColumnCollectionBuilder(createSpecFrameCtx());
179
- builder.addSource([s1]);
162
+ builder.addSource([s1, s2]);
180
163
 
181
164
  const collection = builder.build()!;
182
- expect(() => collection.findColumns({ exclude: { name: "col1" } })).toThrow(
183
- /not yet implemented/i,
184
- );
165
+ const results = collection.findColumns({ exclude: { name: "col1" } });
166
+ expect(results).toHaveLength(1);
167
+ expect(results[0].spec.name).toBe("col2");
185
168
  });
186
169
  });
187
170
 
@@ -377,15 +360,16 @@ describe("AnchoredColumnCollection", () => {
377
360
  expect(matches[0].variants).toBeDefined();
378
361
  });
379
362
 
380
- test("findColumns exclude throws not implemented", () => {
381
- const snap = createSnapshot("id1", createSpec("col1", { axesSpec: [sampleAxis("sample")] }));
363
+ test("findColumns exclude filters out matching columns", () => {
364
+ const snap1 = createSnapshot("id1", createSpec("col1", { axesSpec: [sampleAxis("sample")] }));
365
+ const snap2 = createSnapshot("id2", createSpec("col2", { axesSpec: [sampleAxis("sample")] }));
382
366
  const builder = new ColumnCollectionBuilder(createSpecFrameCtx());
383
- builder.addSource([snap]);
367
+ builder.addSource([snap1, snap2]);
384
368
 
385
369
  const collection = builder.build({ anchors: { main: anchorSpec } })!;
386
- expect(() => collection.findColumns({ exclude: { name: "col1" } })).toThrow(
387
- /not yet implemented/i,
388
- );
370
+ const results = collection.findColumns({ exclude: { name: "col1" } });
371
+ expect(results).toHaveLength(1);
372
+ expect(results[0].column.spec.name).toBe("col2");
389
373
  });
390
374
 
391
375
  test("allowPartialColumnList with anchors tracks completeness", () => {
@@ -2,6 +2,7 @@ import type {
2
2
  AxisQualification,
3
3
  ColumnAxesWithQualifications,
4
4
  DiscoverColumnsConstraints,
5
+ DiscoverColumnsStepInfo,
5
6
  MultiColumnSelector,
6
7
  NativePObjectId,
7
8
  PColumnSpec,
@@ -23,7 +24,7 @@ import type { GlobalCfgRenderCtxMethods } from "../render/internal";
23
24
  /** Subset of render context methods needed for spec frame operations. */
24
25
  type SpecFrameCtx = Pick<
25
26
  GlobalCfgRenderCtxMethods,
26
- "createSpecFrame" | "specFrameDiscoverColumns" | "specFrameDispose"
27
+ "createSpecFrame" | "specFrameDiscoverColumns" | "disposeSpecFrame"
27
28
  >;
28
29
 
29
30
  // --- FindColumnsOptions ---
@@ -79,6 +80,8 @@ export interface ColumnMatch {
79
80
  readonly originalId: PObjectId;
80
81
  /** Match variants — different paths/qualifications that reach this column. */
81
82
  readonly variants: MatchVariant[];
83
+ /** Linker steps traversed to reach this hit; empty for direct matches. */
84
+ readonly path: DiscoverColumnsStepInfo[];
82
85
  }
83
86
 
84
87
  /** Qualifications needed for both query (already-integrated) columns and the hit column. */
@@ -253,24 +256,23 @@ class ColumnCollectionImpl implements ColumnCollection {
253
256
  }
254
257
 
255
258
  findColumns(options?: FindColumnsOptions): ColumnSnapshot<PObjectId>[] {
256
- const columnFilter = options?.include ? toMultiColumnSelectors(options.include) : [];
259
+ const includeColumns = options?.include ? toMultiColumnSelectors(options.include) : undefined;
260
+ const excludeColumns = options?.exclude ? toMultiColumnSelectors(options.exclude) : undefined;
257
261
 
258
262
  const response = this.ctx.specFrameDiscoverColumns(this.specFrameHandle, {
259
- columnFilter,
263
+ includeColumns,
264
+ excludeColumns,
260
265
  axes: [],
266
+ maxHops: 0,
261
267
  constraints: PLAIN_CONSTRAINTS,
262
268
  });
263
269
 
264
270
  // Map hits back to snapshots
265
- let results = response.hits
271
+ const results = response.hits
266
272
  .map((hit) => this.columns.get(hit.hit.columnId as PObjectId))
267
273
  .filter((col): col is ColumnSnapshot<PObjectId> => col !== undefined)
268
274
  .map((col) => this.toSnapshot(col));
269
275
 
270
- if (options?.exclude) {
271
- throw new Error("Exclude filter is not yet implemented for plain ColumnCollection");
272
- }
273
-
274
276
  return results;
275
277
  }
276
278
 
@@ -336,16 +338,19 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection {
336
338
  findColumns(options?: AnchoredFindColumnsOptions): ColumnMatch[] {
337
339
  const mode = options?.mode ?? "enrichment";
338
340
  const constraints = matchingModeToConstraints(mode);
339
- const columnFilter = options?.include ? toMultiColumnSelectors(options.include) : [];
341
+ const includeColumns = options?.include ? toMultiColumnSelectors(options.include) : undefined;
342
+ const excludeColumns = options?.exclude ? toMultiColumnSelectors(options.exclude) : undefined;
340
343
 
341
344
  const response = this.ctx.specFrameDiscoverColumns(this.specFrameHandle, {
342
- columnFilter,
345
+ includeColumns,
346
+ excludeColumns,
343
347
  constraints,
344
348
  axes: this.anchorAxes,
349
+ maxHops: options?.maxHops ?? 4,
345
350
  });
346
351
 
347
352
  // Map hits back to ColumnMatch entries
348
- let results = response.hits
353
+ const results = response.hits
349
354
  .map((hit) => {
350
355
  const origId = hit.hit.columnId as PObjectId;
351
356
  const col = this.columns.get(origId);
@@ -360,14 +365,11 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection {
360
365
  distinctiveQualifications: v.distinctiveQualifications,
361
366
  }),
362
367
  ),
368
+ path: hit.path,
363
369
  } satisfies ColumnMatch;
364
370
  })
365
371
  .filter((m): m is ColumnMatch => m !== undefined);
366
372
 
367
- if (options?.exclude) {
368
- throw new Error("Exclude filter is not yet implemented for AnchoredColumnCollection");
369
- }
370
-
371
373
  return results;
372
374
  }
373
375
 
@@ -1,9 +1,10 @@
1
1
  import type {
2
+ AxisValueType,
3
+ ColumnValueType,
2
4
  MultiAxisSelector,
3
5
  MultiColumnSelector,
4
6
  PColumnSpec,
5
7
  StringMatcher,
6
- ValueType,
7
8
  } from "@milaboratories/pl-model-common";
8
9
 
9
10
  export type { StringMatcher } from "@milaboratories/pl-model-common";
@@ -19,7 +20,7 @@ export type RelaxedRecord = Record<string, RelaxedStringMatchers>;
19
20
  /** Relaxed axis selector — accepts plain strings where strict requires StringMatcher[]. */
20
21
  export interface RelaxedAxisSelector {
21
22
  name?: RelaxedStringMatchers;
22
- type?: ValueType | ValueType[];
23
+ type?: AxisValueType | AxisValueType[];
23
24
  domain?: RelaxedRecord;
24
25
  contextDomain?: RelaxedRecord;
25
26
  annotations?: RelaxedRecord;
@@ -28,7 +29,7 @@ export interface RelaxedAxisSelector {
28
29
  /** Relaxed column selector — convenient hand-written form. */
29
30
  export interface RelaxedColumnSelector {
30
31
  name?: RelaxedStringMatchers;
31
- type?: ValueType | ValueType[];
32
+ type?: ColumnValueType | ColumnValueType[];
32
33
  domain?: RelaxedRecord;
33
34
  contextDomain?: RelaxedRecord;
34
35
  annotations?: RelaxedRecord;
@@ -57,7 +58,7 @@ function normalizeRecord(input: RelaxedRecord): Record<string, StringMatcher[]>
57
58
  return result;
58
59
  }
59
60
 
60
- function normalizeTypes(input: ValueType | ValueType[]): ValueType[] {
61
+ function normalizeTypes<T>(input: T | T[]): T[] {
61
62
  return Array.isArray(input) ? input : [input];
62
63
  }
63
64
 
@@ -140,7 +141,7 @@ function matchAxisSelector(
140
141
  selector: MultiAxisSelector,
141
142
  ): boolean {
142
143
  if (selector.name !== undefined && !matchStringValue(axis.name, selector.name)) return false;
143
- if (selector.type !== undefined && !selector.type.includes(axis.type as ValueType)) return false;
144
+ if (selector.type !== undefined && !selector.type.includes(axis.type)) return false;
144
145
  if (selector.domain !== undefined && !matchRecordField(axis.domain, selector.domain))
145
146
  return false;
146
147
  if (
package/src/render/api.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type {
2
2
  AnchoredPColumnSelector,
3
3
  AnyFunction,
4
+ AxesId,
5
+ AxesSpec,
4
6
  AxisId,
5
7
  DataInfo,
6
8
  DiscoverColumnsRequest,
@@ -17,6 +19,8 @@ import type {
17
19
  PObjectId,
18
20
  PObjectSpec,
19
21
  PSpecPredicate,
22
+ PTableColumnId,
23
+ PTableColumnSpec,
20
24
  PTableDef,
21
25
  PTableDefV2,
22
26
  PTableHandle,
@@ -25,6 +29,7 @@ import type {
25
29
  PlRef,
26
30
  ResolveAnchorsOptions,
27
31
  ResultCollection,
32
+ SingleAxisSelector,
28
33
  SUniversalPColumnId,
29
34
  ValueOrError,
30
35
  } from "@milaboratories/pl-model-common";
@@ -727,8 +732,24 @@ export abstract class RenderCtxBase<Args = unknown, Data = unknown> {
727
732
  return this.ctx.specFrameDiscoverColumns(handle, request);
728
733
  }
729
734
 
730
- public specFrameDispose(handle: string): void {
731
- this.ctx.specFrameDispose(handle);
735
+ public disposeSpecFrame(handle: string): void {
736
+ this.ctx.disposeSpecFrame(handle);
737
+ }
738
+
739
+ public expandAxes(spec: AxesSpec): AxesId {
740
+ return this.ctx.expandAxes(spec);
741
+ }
742
+
743
+ public collapseAxes(ids: AxesId): AxesSpec {
744
+ return this.ctx.collapseAxes(ids);
745
+ }
746
+
747
+ public findAxis(spec: AxesSpec, selector: SingleAxisSelector): number {
748
+ return this.ctx.findAxis(spec, selector);
749
+ }
750
+
751
+ public findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number {
752
+ return this.ctx.findTableColumn(tableSpec, selector);
732
753
  }
733
754
 
734
755
  public getCurrentUnstableMarker(): string | undefined {
@@ -1,8 +1,13 @@
1
1
  import type { Optional } from "utility-types";
2
2
  import type {
3
+ AxesId,
4
+ AxesSpec,
3
5
  Branded,
4
6
  DiscoverColumnsRequest,
5
7
  DiscoverColumnsResponse,
8
+ PTableColumnId,
9
+ PTableColumnSpec,
10
+ SingleAxisSelector,
6
11
  StringifiedJson,
7
12
  } from "@milaboratories/pl-model-common";
8
13
  import type { CommonFieldTraverseOps, FieldTraversalStep, ResourceType } from "./traversal_ops";
@@ -174,7 +179,19 @@ export interface GlobalCfgRenderCtxMethods<AHandle = AccessorHandle, FHandle = F
174
179
  request: DiscoverColumnsRequest,
175
180
  ): DiscoverColumnsResponse;
176
181
 
177
- specFrameDispose(handle: string): void;
182
+ disposeSpecFrame(handle: string): void;
183
+
184
+ /** Expand index-based parentAxes in AxesSpec to resolved AxisId parents in AxesId. */
185
+ expandAxes(spec: AxesSpec): AxesId;
186
+
187
+ /** Collapse resolved AxisId parents back to index-based parentAxes in AxesSpec. */
188
+ collapseAxes(ids: AxesId): AxesSpec;
189
+
190
+ /** Find the index of an axis matching the given selector. Returns -1 if not found. */
191
+ findAxis(spec: AxesSpec, selector: SingleAxisSelector): number;
192
+
193
+ /** Find the flat index of a table column matching the given selector. Returns -1 if not found. */
194
+ findTableColumn(tableSpec: PTableColumnSpec[], selector: PTableColumnId): number;
178
195
 
179
196
  //
180
197
  // Computable