@platforma-sdk/model 1.63.12 → 1.64.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.
Files changed (162) hide show
  1. package/dist/columns/column_collection_builder.cjs +105 -92
  2. package/dist/columns/column_collection_builder.cjs.map +1 -1
  3. package/dist/columns/column_collection_builder.d.ts +13 -12
  4. package/dist/columns/column_collection_builder.d.ts.map +1 -1
  5. package/dist/columns/column_collection_builder.js +107 -94
  6. package/dist/columns/column_collection_builder.js.map +1 -1
  7. package/dist/columns/column_selector.cjs +8 -80
  8. package/dist/columns/column_selector.cjs.map +1 -1
  9. package/dist/columns/column_selector.d.ts +6 -14
  10. package/dist/columns/column_selector.d.ts.map +1 -1
  11. package/dist/columns/column_selector.js +6 -77
  12. package/dist/columns/column_selector.js.map +1 -1
  13. package/dist/columns/column_snapshot.cjs +3 -3
  14. package/dist/columns/column_snapshot.cjs.map +1 -1
  15. package/dist/columns/column_snapshot.d.ts +3 -3
  16. package/dist/columns/column_snapshot.d.ts.map +1 -1
  17. package/dist/columns/column_snapshot.js +3 -3
  18. package/dist/columns/column_snapshot.js.map +1 -1
  19. package/dist/columns/column_snapshot_provider.cjs +1 -1
  20. package/dist/columns/column_snapshot_provider.cjs.map +1 -1
  21. package/dist/columns/column_snapshot_provider.d.ts +8 -8
  22. package/dist/columns/column_snapshot_provider.d.ts.map +1 -1
  23. package/dist/columns/column_snapshot_provider.js +1 -1
  24. package/dist/columns/column_snapshot_provider.js.map +1 -1
  25. package/dist/columns/ctx_column_sources.cjs.map +1 -1
  26. package/dist/columns/ctx_column_sources.d.ts +2 -1
  27. package/dist/columns/ctx_column_sources.d.ts.map +1 -1
  28. package/dist/columns/ctx_column_sources.js.map +1 -1
  29. package/dist/columns/expand_by_partition.cjs +106 -0
  30. package/dist/columns/expand_by_partition.cjs.map +1 -0
  31. package/dist/columns/expand_by_partition.d.ts +33 -0
  32. package/dist/columns/expand_by_partition.d.ts.map +1 -0
  33. package/dist/columns/expand_by_partition.js +105 -0
  34. package/dist/columns/expand_by_partition.js.map +1 -0
  35. package/dist/columns/index.cjs +1 -0
  36. package/dist/columns/index.d.ts +4 -3
  37. package/dist/columns/index.js +1 -0
  38. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs +26 -0
  39. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs.map +1 -0
  40. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js +25 -0
  41. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js.map +1 -0
  42. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs +68 -0
  43. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs.map +1 -0
  44. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js +67 -0
  45. package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js.map +1 -0
  46. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs +20 -9
  47. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs.map +1 -1
  48. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts +4 -0
  49. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts.map +1 -1
  50. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js +21 -10
  51. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js.map +1 -1
  52. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +257 -175
  53. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
  54. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +37 -21
  55. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
  56. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +260 -175
  57. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
  58. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +64 -0
  59. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -0
  60. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts +17 -0
  61. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts.map +1 -0
  62. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +63 -0
  63. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -0
  64. package/dist/components/PlDataTable/createPlDataTable/index.cjs +2 -1
  65. package/dist/components/PlDataTable/createPlDataTable/index.cjs.map +1 -1
  66. package/dist/components/PlDataTable/createPlDataTable/index.d.ts +2 -1
  67. package/dist/components/PlDataTable/createPlDataTable/index.d.ts.map +1 -1
  68. package/dist/components/PlDataTable/createPlDataTable/index.js +2 -1
  69. package/dist/components/PlDataTable/createPlDataTable/index.js.map +1 -1
  70. package/dist/components/PlDataTable/createPlDataTable/utils.cjs +109 -0
  71. package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -0
  72. package/dist/components/PlDataTable/createPlDataTable/utils.d.ts +19 -0
  73. package/dist/components/PlDataTable/createPlDataTable/utils.d.ts.map +1 -0
  74. package/dist/components/PlDataTable/createPlDataTable/utils.js +102 -0
  75. package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -0
  76. package/dist/components/PlDataTable/index.cjs +3 -1
  77. package/dist/components/PlDataTable/index.d.ts +4 -2
  78. package/dist/components/PlDataTable/index.js +3 -1
  79. package/dist/components/PlDataTable/labels.cjs +25 -11
  80. package/dist/components/PlDataTable/labels.cjs.map +1 -1
  81. package/dist/components/PlDataTable/labels.js +25 -11
  82. package/dist/components/PlDataTable/labels.js.map +1 -1
  83. package/dist/components/PlDataTable/state-migration.cjs +4 -1
  84. package/dist/components/PlDataTable/state-migration.cjs.map +1 -1
  85. package/dist/components/PlDataTable/state-migration.d.ts.map +1 -1
  86. package/dist/components/PlDataTable/state-migration.js +4 -1
  87. package/dist/components/PlDataTable/state-migration.js.map +1 -1
  88. package/dist/components/PlDataTable/typesV5.d.ts +5 -4
  89. package/dist/components/PlDataTable/typesV5.d.ts.map +1 -1
  90. package/dist/components/index.cjs +3 -1
  91. package/dist/components/index.d.ts +3 -1
  92. package/dist/components/index.js +3 -1
  93. package/dist/index.cjs +13 -9
  94. package/dist/index.d.ts +8 -6
  95. package/dist/index.js +6 -4
  96. package/dist/labels/derive_distinct_labels.cjs +39 -27
  97. package/dist/labels/derive_distinct_labels.cjs.map +1 -1
  98. package/dist/labels/derive_distinct_labels.d.ts +15 -15
  99. package/dist/labels/derive_distinct_labels.d.ts.map +1 -1
  100. package/dist/labels/derive_distinct_labels.js +39 -27
  101. package/dist/labels/derive_distinct_labels.js.map +1 -1
  102. package/dist/labels/index.cjs +0 -1
  103. package/dist/labels/index.d.ts +1 -2
  104. package/dist/labels/index.js +0 -1
  105. package/dist/package.cjs +1 -1
  106. package/dist/package.js +1 -1
  107. package/dist/render/api.cjs +10 -3
  108. package/dist/render/api.cjs.map +1 -1
  109. package/dist/render/api.d.ts +2 -2
  110. package/dist/render/api.d.ts.map +1 -1
  111. package/dist/render/api.js +10 -3
  112. package/dist/render/api.js.map +1 -1
  113. package/dist/render/util/column_collection.cjs +3 -3
  114. package/dist/render/util/column_collection.cjs.map +1 -1
  115. package/dist/render/util/column_collection.d.ts.map +1 -1
  116. package/dist/render/util/column_collection.js +3 -3
  117. package/dist/render/util/column_collection.js.map +1 -1
  118. package/dist/render/util/label.cjs +2 -2
  119. package/dist/render/util/label.cjs.map +1 -1
  120. package/dist/render/util/label.js +2 -2
  121. package/dist/render/util/label.js.map +1 -1
  122. package/dist/render/util/pcolumn_data.cjs.map +1 -1
  123. package/dist/render/util/pcolumn_data.d.ts +2 -2
  124. package/dist/render/util/pcolumn_data.d.ts.map +1 -1
  125. package/dist/render/util/pcolumn_data.js.map +1 -1
  126. package/package.json +6 -6
  127. package/src/columns/column_collection_builder.test.ts +40 -27
  128. package/src/columns/column_collection_builder.ts +176 -131
  129. package/src/columns/column_selector.test.ts +17 -399
  130. package/src/columns/column_selector.ts +14 -127
  131. package/src/columns/column_snapshot.ts +5 -5
  132. package/src/columns/column_snapshot_provider.ts +11 -10
  133. package/src/columns/ctx_column_sources.ts +2 -2
  134. package/src/columns/expand_by_partition.test.ts +4 -4
  135. package/src/columns/expand_by_partition.ts +4 -3
  136. package/src/columns/index.ts +1 -0
  137. package/src/components/PlDataTable/createPlDataTable/createPTableDefV2.ts +42 -0
  138. package/src/components/PlDataTable/createPlDataTable/createPTableDefV3.ts +89 -0
  139. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV2.ts +39 -11
  140. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +502 -313
  141. package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +122 -0
  142. package/src/components/PlDataTable/createPlDataTable/index.ts +4 -2
  143. package/src/components/PlDataTable/createPlDataTable/utils.test.ts +257 -0
  144. package/src/components/PlDataTable/createPlDataTable/utils.ts +160 -0
  145. package/src/components/PlDataTable/index.ts +13 -2
  146. package/src/components/PlDataTable/labels.ts +29 -18
  147. package/src/components/PlDataTable/state-migration.ts +3 -1
  148. package/src/components/PlDataTable/typesV5.ts +4 -4
  149. package/src/labels/derive_distinct_labels.test.ts +143 -45
  150. package/src/labels/derive_distinct_labels.ts +102 -49
  151. package/src/labels/index.ts +0 -1
  152. package/src/render/api.ts +15 -5
  153. package/src/render/util/column_collection.ts +4 -3
  154. package/src/render/util/label.ts +2 -2
  155. package/src/render/util/pcolumn_data.ts +5 -3
  156. package/dist/labels/write_labels_to_specs.cjs +0 -14
  157. package/dist/labels/write_labels_to_specs.cjs.map +0 -1
  158. package/dist/labels/write_labels_to_specs.d.ts +0 -7
  159. package/dist/labels/write_labels_to_specs.d.ts.map +0 -1
  160. package/dist/labels/write_labels_to_specs.js +0 -13
  161. package/dist/labels/write_labels_to_specs.js.map +0 -1
  162. package/src/labels/write_labels_to_specs.ts +0 -12
@@ -2,17 +2,24 @@ import type {
2
2
  AxisQualification,
3
3
  ColumnAxesWithQualifications,
4
4
  DiscoverColumnsConstraints,
5
- DiscoverColumnsStepInfo,
5
+ DiscoverColumnsRequest,
6
+ DiscoverColumnsResponse,
6
7
  MultiColumnSelector,
7
8
  NativePObjectId,
9
+ PColumnIdAndSpec,
8
10
  PColumnSpec,
9
- PlRef,
10
11
  PObjectId,
11
12
  SUniversalPColumnId,
12
13
  } from "@milaboratories/pl-model-common";
13
- import { AnchoredIdDeriver, deriveNativeId, isPlRef } from "@milaboratories/pl-model-common";
14
- import type { ColumnSelectorInput } from "./column_selector";
15
- import { normalizeSelectors } from "./column_selector";
14
+ import {
15
+ AnchoredIdDeriver,
16
+ canonicalizeJson,
17
+ deriveNativeId,
18
+ getAxesId,
19
+ isPColumnSpec,
20
+ } from "@milaboratories/pl-model-common";
21
+ import type { ColumnSelector, RelaxedColumnSelector } from "./column_selector";
22
+ import { convertColumnSelectorToMultiColumnSelector } from "./column_selector";
16
23
  import { TreeNodeAccessor } from "../render/accessor";
17
24
  import type { ColumnSnapshot } from "./column_snapshot";
18
25
  import { createColumnSnapshot } from "./column_snapshot";
@@ -20,15 +27,17 @@ import type { ColumnSnapshotProvider, ColumnSource } from "./column_snapshot_pro
20
27
  import { ArrayColumnProvider, toColumnSnapshotProvider } from "./column_snapshot_provider";
21
28
 
22
29
  import type { PFrameSpecDriver, PoolEntry, SpecFrameHandle } from "@milaboratories/pl-model-common";
30
+ import { throwError } from "@milaboratories/helpers";
31
+ import { uniqBy } from "es-toolkit";
23
32
 
24
33
  // --- FindColumnsOptions ---
25
34
 
26
35
  /** Options for plain collection findColumns. */
27
36
  export interface FindColumnsOptions {
28
37
  /** Include columns matching these selectors. If omitted, includes all columns. */
29
- include?: ColumnSelectorInput;
38
+ include?: ColumnSelector;
30
39
  /** Exclude columns matching these selectors. */
31
- exclude?: ColumnSelectorInput;
40
+ exclude?: ColumnSelector;
32
41
  }
33
42
 
34
43
  // --- ColumnCollection ---
@@ -37,6 +46,7 @@ export interface FindColumnsOptions {
37
46
  export interface ColumnCollection extends Disposable {
38
47
  /** Release the underlying spec frame WASM resource. */
39
48
  dispose(): void;
49
+
40
50
  /** Point lookup by provider-native ID. */
41
51
  getColumn(id: PObjectId): undefined | ColumnSnapshot<PObjectId>;
42
52
 
@@ -52,6 +62,10 @@ export interface ColumnCollection extends Disposable {
52
62
  export interface AnchoredColumnCollection extends Disposable {
53
63
  /** Release the underlying spec frame WASM resource. */
54
64
  dispose(): void;
65
+
66
+ /** List of anchors used for discovery, with their resolved specs. */
67
+ getAnchors(): Map<string, PColumnIdAndSpec>;
68
+
55
69
  /** Point lookup by anchored ID. */
56
70
  getColumn(id: SUniversalPColumnId): undefined | ColumnSnapshot<SUniversalPColumnId>;
57
71
 
@@ -79,7 +93,10 @@ export interface ColumnMatch {
79
93
  /** Match variants — different paths/qualifications that reach this column. */
80
94
  readonly variants: MatchVariant[];
81
95
  /** Linker steps traversed to reach this hit; empty for direct matches. */
82
- readonly path: DiscoverColumnsStepInfo[];
96
+ readonly path: {
97
+ linker: ColumnSnapshot<SUniversalPColumnId>;
98
+ qualifications: AxisQualification[];
99
+ }[];
83
100
  }
84
101
 
85
102
  /** Qualifications needed for both query (already-integrated) columns and the hit column. */
@@ -104,8 +121,10 @@ export interface BuildOptions {
104
121
  allowPartialColumnList?: true;
105
122
  }
106
123
 
124
+ export type AnchorEntry = PObjectId | PColumnSpec | RelaxedColumnSelector;
125
+
107
126
  export interface AnchoredBuildOptions extends BuildOptions {
108
- anchors: Record<string, PlRef | PObjectId | PColumnSpec>;
127
+ anchors: Record<string, AnchorEntry>;
109
128
  }
110
129
 
111
130
  // --- ColumnCollectionBuilder ---
@@ -163,84 +182,45 @@ export class ColumnCollectionBuilder {
163
182
  | (ColumnCollection & { readonly columnListComplete: boolean })
164
183
  | (AnchoredColumnCollection & { readonly columnListComplete: boolean }) {
165
184
  const allowPartial = options?.allowPartialColumnList === true;
166
- const hasAnchors = options !== undefined && "anchors" in options;
167
185
 
168
186
  // Check column list completeness
169
187
  const allComplete = this.providers.every((p) => p.isColumnListComplete());
170
188
  if (!allComplete && !allowPartial) return undefined;
171
189
 
172
190
  // Collect all columns, dedup by native ID (first source wins)
173
- const columnMap = this.collectColumns();
191
+ const columns = collectColumns(this.providers);
192
+ const hasAnchors = options !== undefined && "anchors" in options;
174
193
 
175
194
  if (hasAnchors) {
176
- const anchorSpecs = resolveAnchorSpecs(options.anchors, columnMap);
177
- const idDeriver = new AnchoredIdDeriver(anchorSpecs);
178
-
179
195
  return new AnchoredColumnCollectionImpl(this.specDriver, {
180
- columns: columnMap,
181
- idDeriver,
182
- anchorSpecs,
183
- columnListComplete: allowPartial ? allComplete : false,
196
+ anchors: options.anchors,
197
+ columns,
184
198
  });
185
199
  } else {
186
200
  return new ColumnCollectionImpl(this.specDriver, {
187
- columns: columnMap,
188
- columnListComplete: allowPartial ? allComplete : false,
201
+ columns,
189
202
  });
190
203
  }
191
204
  }
192
-
193
- /**
194
- * Collect all columns from all providers, dedup by NativePObjectId.
195
- * First source wins.
196
- */
197
- private collectColumns(): Map<PObjectId, ColumnSnapshot<PObjectId>> {
198
- const seen = new Set<NativePObjectId>();
199
- const result = new Map<PObjectId, ColumnSnapshot<PObjectId>>();
200
-
201
- for (const provider of this.providers) {
202
- const columns = provider.getAllColumns();
203
- for (const col of columns) {
204
- const nativeId = deriveNativeId(col.spec);
205
- if (seen.has(nativeId)) continue;
206
- seen.add(nativeId);
207
- result.set(col.id, col);
208
- }
209
- }
210
-
211
- return result;
212
- }
213
205
  }
214
206
 
215
- // --- Permissive constraints for plain (non-anchored) filtering ---
216
-
217
- const PLAIN_CONSTRAINTS: DiscoverColumnsConstraints = {
218
- allowFloatingSourceAxes: true,
219
- allowFloatingHitAxes: true,
220
- allowSourceQualifications: false,
221
- allowHitQualifications: false,
222
- };
223
-
224
207
  // --- ColumnCollectionImpl ---
225
208
 
226
209
  interface ColumnCollectionImplOptions {
227
- readonly columns: Map<PObjectId, ColumnSnapshot<PObjectId>>;
228
- readonly columnListComplete?: boolean;
210
+ readonly columns: ColumnSnapshot<PObjectId>[];
229
211
  }
230
212
 
231
213
  class ColumnCollectionImpl implements ColumnCollection, Disposable {
232
214
  private readonly columns: Map<PObjectId, ColumnSnapshot<PObjectId>>;
233
215
  private readonly specFrameEntry: PoolEntry<SpecFrameHandle>;
234
- public readonly columnListComplete: boolean;
235
216
 
236
217
  constructor(
237
218
  private readonly specDriver: PFrameSpecDriver,
238
219
  options: ColumnCollectionImplOptions,
239
220
  ) {
240
- this.columns = options.columns;
241
- this.columnListComplete = options.columnListComplete ?? false;
221
+ this.columns = new Map(options.columns.map((col) => [col.id, col]));
242
222
  this.specFrameEntry = this.specDriver.createSpecFrame(
243
- Object.fromEntries(Array.from(this.columns.entries(), ([id, col]) => [id, col.spec])),
223
+ Object.fromEntries(options.columns.map((col) => [col.id, col.spec])),
244
224
  );
245
225
  }
246
226
 
@@ -267,7 +247,7 @@ class ColumnCollectionImpl implements ColumnCollection, Disposable {
267
247
  excludeColumns,
268
248
  axes: [],
269
249
  maxHops: 0,
270
- constraints: PLAIN_CONSTRAINTS,
250
+ constraints: matchingModeToConstraints("enrichment"),
271
251
  });
272
252
 
273
253
  // Map hits back to snapshots
@@ -287,44 +267,47 @@ class ColumnCollectionImpl implements ColumnCollection, Disposable {
287
267
  // --- AnchoredColumnCollectionImpl ---
288
268
 
289
269
  interface AnchoredColumnCollectionImplOptions extends ColumnCollectionImplOptions {
290
- readonly idDeriver: AnchoredIdDeriver;
291
- readonly anchorSpecs: Record<string, PColumnSpec>;
270
+ readonly anchors: Record<string, AnchorEntry>;
292
271
  }
293
272
 
294
273
  class AnchoredColumnCollectionImpl implements AnchoredColumnCollection, Disposable {
295
- private readonly columns: Map<PObjectId, ColumnSnapshot<PObjectId>>;
274
+ private readonly anchorsMap: Map<string, PColumnIdAndSpec>;
275
+ private readonly columnsMap: Map<PObjectId, ColumnSnapshot<PObjectId>>;
276
+
296
277
  private readonly idDeriver: AnchoredIdDeriver;
278
+ private readonly uniqAnchorAxes: ColumnAxesWithQualifications[];
279
+ private readonly idToOriginalIdMap: Map<SUniversalPColumnId, PObjectId>;
297
280
  private readonly specFrameEntry: PoolEntry<SpecFrameHandle>;
298
- private readonly anchorAxes: ColumnAxesWithQualifications[];
299
- /** Reverse lookup: SUniversalPColumnId → PObjectId */
300
- private readonly idToOriginal: Map<SUniversalPColumnId, PObjectId>;
301
- public readonly columnListComplete: boolean;
302
281
 
303
282
  constructor(
304
283
  private readonly specDriver: PFrameSpecDriver,
305
284
  options: AnchoredColumnCollectionImplOptions,
306
285
  ) {
307
- this.columns = options.columns;
308
- this.idDeriver = options.idDeriver;
309
- this.columnListComplete = options.columnListComplete ?? false;
310
-
311
286
  // Create spec frame from all collected columns
312
287
  this.specFrameEntry = this.specDriver.createSpecFrame(
313
- Object.fromEntries(Array.from(this.columns.entries(), ([id, col]) => [id, col.spec])),
288
+ Object.fromEntries(options.columns.map((col) => [col.id, col.spec])),
314
289
  );
315
-
316
- // Build anchor axes for discovery requests
317
- this.anchorAxes = Object.values(options.anchorSpecs).map((spec) => ({
318
- axesSpec: spec.axesSpec,
319
- qualifications: [],
320
- }));
321
-
322
- // Build reverse lookup map
323
- this.idToOriginal = new Map(
324
- Array.from(this.columns.entries()).map(
325
- ([id, col]) => [this.idDeriver.deriveS(col.spec), id] as const,
290
+ this.columnsMap = new Map(options.columns.map((col) => [col.id, col]));
291
+ this.anchorsMap = resolveAnchorMap(
292
+ options.anchors,
293
+ options.columns,
294
+ this.specDriver.discoverColumns.bind(this.specDriver, this.specFrameEntry.key),
295
+ );
296
+ this.idDeriver = new AnchoredIdDeriver(
297
+ Object.fromEntries(
298
+ Array.from(this.anchorsMap.entries()).map(([k, v]) => [k, v.spec] as const),
326
299
  ),
327
300
  );
301
+ this.uniqAnchorAxes = uniqBy(
302
+ Array.from(this.anchorsMap.values(), ({ spec }) => ({
303
+ axesSpec: spec.axesSpec,
304
+ qualifications: [],
305
+ })),
306
+ (axis) => canonicalizeJson(getAxesId(axis.axesSpec)) + canonicalizeJson(axis.qualifications),
307
+ );
308
+ this.idToOriginalIdMap = new Map(
309
+ options.columns.map((col) => [this.idDeriver.deriveS(col.spec), col.id] as const),
310
+ );
328
311
  }
329
312
 
330
313
  dispose(): void {
@@ -335,12 +318,16 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection, Disposab
335
318
  this.dispose();
336
319
  }
337
320
 
321
+ getAnchors(): Map<string, PColumnIdAndSpec> {
322
+ return this.anchorsMap;
323
+ }
324
+
338
325
  getColumn(id: SUniversalPColumnId): undefined | ColumnSnapshot<SUniversalPColumnId> {
339
- const origId = this.idToOriginal.get(id);
326
+ const origId = this.idToOriginalIdMap.get(id);
340
327
  if (origId === undefined) return undefined;
341
- const col = this.columns.get(origId);
328
+ const col = this.columnsMap.get(origId);
342
329
  if (col === undefined) return undefined;
343
- return this.toSnapshot(id, col);
330
+ return remapSnapshot(id, col);
344
331
  }
345
332
 
346
333
  findColumns(options?: AnchoredFindColumnsOptions): ColumnMatch[] {
@@ -353,40 +340,60 @@ class AnchoredColumnCollectionImpl implements AnchoredColumnCollection, Disposab
353
340
  includeColumns,
354
341
  excludeColumns,
355
342
  constraints,
356
- axes: this.anchorAxes,
357
343
  maxHops: options?.maxHops ?? 4,
344
+ axes: this.uniqAnchorAxes,
358
345
  });
359
346
 
360
- // Map hits back to ColumnMatch entries
361
- const results = response.hits
362
- .map((hit) => {
363
- const origId = hit.hit.columnId as PObjectId;
364
- const col = this.columns.get(origId);
365
- if (!col) return undefined;
366
- const universalId = this.idDeriver.deriveS(col.spec);
367
- return {
368
- column: this.toSnapshot(universalId, col),
369
- originalId: origId,
370
- variants: hit.mappingVariants.map(
371
- (v): MatchVariant => ({
372
- qualifications: v.qualifications,
373
- distinctiveQualifications: v.distinctiveQualifications,
374
- }),
347
+ // Map every WASM discovery hit to a ColumnMatch.
348
+ // The same physical column may appear multiple times when reachable through
349
+ // different linker paths — each hit becomes a separate ColumnMatch so that
350
+ // the caller can expand them into distinct table columns.
351
+ const results: ColumnMatch[] = [];
352
+ for (const hit of response.hits) {
353
+ const origId = hit.hit.columnId as PObjectId;
354
+ const col =
355
+ this.columnsMap.get(origId) ??
356
+ throwError(`Column with id ${origId} not found in collection`);
357
+ const associatedId = this.idDeriver.deriveS(col.spec);
358
+
359
+ results.push({
360
+ path: hit.path.map((step) => ({
361
+ linker: remapSnapshot(
362
+ this.idDeriver.deriveS(step.linker.spec),
363
+ this.columnsMap.get(step.linker.columnId) ??
364
+ throwError(`Linker column with id ${step.linker.columnId} not found in collection`),
375
365
  ),
376
- path: hit.path,
377
- } satisfies ColumnMatch;
378
- })
379
- .filter((m): m is ColumnMatch => m !== undefined);
366
+ qualifications: step.qualifications,
367
+ })),
368
+ column: remapSnapshot(associatedId, col),
369
+ variants: hit.mappingVariants,
370
+ originalId: origId,
371
+ });
372
+ }
380
373
 
381
374
  return results;
382
375
  }
376
+ }
383
377
 
384
- private toSnapshot(
385
- universalId: SUniversalPColumnId,
386
- col: ColumnSnapshot<PObjectId>,
387
- ): ColumnSnapshot<SUniversalPColumnId> {
388
- return remapSnapshot(universalId, col);
378
+ /**
379
+ * Collect all columns from all providers, dedup by NativePObjectId.
380
+ * First source wins.
381
+ */
382
+ function collectColumns(providers: ColumnSnapshotProvider[]): ColumnSnapshot<PObjectId>[] {
383
+ const seen = new Set<NativePObjectId>();
384
+ const result: ColumnSnapshot<PObjectId>[] = [];
385
+
386
+ for (const provider of providers) {
387
+ const columns = provider.getAllColumns();
388
+ for (const col of columns) {
389
+ const nativeId = deriveNativeId(col.spec);
390
+ if (seen.has(nativeId)) continue;
391
+ seen.add(nativeId);
392
+ result.push(col);
393
+ }
389
394
  }
395
+
396
+ return result;
390
397
  }
391
398
 
392
399
  // --- Shared snapshot helpers ---
@@ -396,12 +403,12 @@ function remapSnapshot<Id extends PObjectId>(
396
403
  id: Id,
397
404
  col: ColumnSnapshot<PObjectId>,
398
405
  ): ColumnSnapshot<Id> {
399
- return createColumnSnapshot(id, col.spec, col.dataStatus, col.data);
406
+ return createColumnSnapshot(id, col.spec, col.data, col.dataStatus);
400
407
  }
401
408
 
402
409
  /** Normalize SDK ColumnSelectorInput to MultiColumnSelector[]. */
403
- function toMultiColumnSelectors(input: ColumnSelectorInput): MultiColumnSelector[] {
404
- return normalizeSelectors(input);
410
+ function toMultiColumnSelectors(input: ColumnSelector): MultiColumnSelector[] {
411
+ return convertColumnSelectorToMultiColumnSelector(input);
405
412
  }
406
413
 
407
414
  // --- Anchor resolution ---
@@ -410,29 +417,67 @@ function toMultiColumnSelectors(input: ColumnSelectorInput): MultiColumnSelector
410
417
  * Resolve each anchor value to a PColumnSpec.
411
418
  * - PColumnSpec: used directly
412
419
  * - PObjectId (string): looked up in the collected column map
413
- * - PlRef: not supported at this level — caller must resolve before building
414
420
  */
415
- function resolveAnchorSpecs(
416
- anchors: Record<string, PlRef | PObjectId | PColumnSpec>,
417
- columnMap: Map<PObjectId, ColumnSnapshot<PObjectId>>,
418
- ): Record<string, PColumnSpec> {
419
- const result: Record<string, PColumnSpec> = {};
421
+ function resolveAnchorMap(
422
+ anchors: Record<string, AnchorEntry>,
423
+ columns: ColumnSnapshot<PObjectId>[],
424
+ discoverColumns: (request: DiscoverColumnsRequest) => DiscoverColumnsResponse,
425
+ ): Map<string, PColumnIdAndSpec> {
426
+ const result = new Map<string, PColumnIdAndSpec>();
427
+ const resovedIds = new Set<PObjectId>();
428
+ const getDuplicateError = (key: string) =>
429
+ `Anchor "${key}": selector matched a column that was already matched by another anchor; please refine the selector to match a different column`;
430
+
420
431
  for (const [key, anchor] of Object.entries(anchors)) {
421
432
  if (typeof anchor === "string") {
422
- // PObjectId — look up in collected columns
423
- const col = columnMap.get(anchor as PObjectId);
424
- if (!col) throw new Error(`Anchor "${key}": column with id "${anchor}" not found in sources`);
425
- result[key] = col.spec;
426
- } else if (isPlRef(anchor)) {
427
- throw new Error(
428
- `Anchor "${key}": PlRef anchors must be resolved to PColumnSpec before building. ` +
429
- `Use the column's spec directly or pass its PObjectId.`,
430
- );
433
+ const found =
434
+ columns.find((col) => col.id === anchor) ??
435
+ throwError(`Anchor "${key}": column with id "${anchor}" not found in sources`);
436
+ if (resovedIds.has(found.id)) {
437
+ throwError(getDuplicateError(key));
438
+ }
439
+ result.set(key, { columnId: found.id, spec: found.spec });
440
+ resovedIds.add(found.id);
441
+ } else if ("kind" in anchor) {
442
+ if (!isPColumnSpec(anchor)) throwError(`Anchor "${key}": invalid PColumnSpec`);
443
+ const nativeId = deriveNativeId(anchor);
444
+ const found =
445
+ columns.find((col) => deriveNativeId(col.spec) === nativeId) ??
446
+ throwError(`Anchor "${key}": no column matching spec found in sources`);
447
+ if (resovedIds.has(found.id)) {
448
+ throwError(getDuplicateError(key));
449
+ }
450
+ result.set(key, { columnId: found.id, spec: anchor });
451
+ resovedIds.add(found.id);
431
452
  } else {
432
- // PColumnSpec
433
- result[key] = anchor;
453
+ const matched = discoverColumns({
454
+ includeColumns: toMultiColumnSelectors(anchor),
455
+ excludeColumns: undefined,
456
+ axes: [],
457
+ maxHops: 0,
458
+ constraints: matchingModeToConstraints("exact"),
459
+ });
460
+ if (matched.hits.length === 0) {
461
+ throwError(`Anchor "${key}": no columns matched selector`);
462
+ }
463
+ if (matched.hits.length > 1) {
464
+ throwError(
465
+ `Anchor "${key}": selector is ambiguous and matched multiple columns; please refine the selector to match exactly one column`,
466
+ );
467
+ }
468
+ if (resovedIds.has(matched.hits[0].hit.columnId as PObjectId)) {
469
+ throwError(getDuplicateError(key));
470
+ }
471
+
472
+ result.set(key, matched.hits[0].hit);
473
+ resovedIds.add(matched.hits[0].hit.columnId);
434
474
  }
435
475
  }
476
+
477
+ if (resovedIds.size === 0) {
478
+ throwError("At least one anchor must be resolved to a valid column");
479
+ }
480
+
436
481
  return result;
437
482
  }
438
483
 
@@ -443,9 +488,9 @@ function matchingModeToConstraints(mode: MatchingMode): DiscoverColumnsConstrain
443
488
  case "enrichment":
444
489
  return {
445
490
  allowFloatingSourceAxes: true,
446
- allowFloatingHitAxes: true,
447
- allowSourceQualifications: false,
448
- allowHitQualifications: false,
491
+ allowFloatingHitAxes: false,
492
+ allowSourceQualifications: true,
493
+ allowHitQualifications: true,
449
494
  };
450
495
  case "related":
451
496
  return {