@platforma-sdk/model 1.72.0 → 1.73.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,6 +5,10 @@ const require_ctx_column_sources = require("../../columns/ctx_column_sources.cjs
5
5
  const require_enrichment_discovery = require("./enrichment_discovery.cjs");
6
6
  let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
7
7
  //#region src/components/PlDatasetSelector/build_dataset_options.ts
8
+ function toPredicate(opt) {
9
+ if (opt === void 0) return () => true;
10
+ return typeof opt === "function" ? opt : (0, _milaboratories_pl_model_common.multiColumnSelectorsToPredicate)(opt);
11
+ }
8
12
  /**
9
13
  * Usage:
10
14
  * ```ts
@@ -12,30 +16,35 @@ let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common")
12
16
  * ```
13
17
  */
14
18
  function buildDatasetOptions(ctx, opts) {
15
- const primary = opts?.primary;
16
- const primaryPredicate = primary === void 0 ? () => true : typeof primary === "function" ? primary : (0, _milaboratories_pl_model_common.multiColumnSelectorsToPredicate)(primary);
19
+ const primaryPredicate = toPredicate(opts?.primary);
20
+ const filterPredicate = toPredicate(opts?.filter);
17
21
  const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });
18
22
  if (options.length === 0) return [];
19
- const columnSources = require_ctx_column_sources.collectCtxColumnSnapshotProviders(ctx);
20
23
  const refMap = require_filter_discovery.buildRefMap(ctx.resultPool.getSpecs().entries);
21
24
  const pframeSpec = ctx.getService("pframeSpec");
25
+ const withEnrichments = opts?.withEnrichments ?? false;
26
+ const filterSource = new require_ctx_column_sources.ResultPoolColumnSnapshotProvider(ctx.resultPool);
27
+ const enrichmentSources = withEnrichments ? require_ctx_column_sources.collectCtxColumnSnapshotProviders(ctx) : void 0;
22
28
  return options.map((primary) => {
23
29
  const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);
24
30
  if (!datasetSpec) return { primary };
25
- const builder = new require_column_collection_builder.ColumnCollectionBuilder(pframeSpec);
26
- for (const src of columnSources) builder.addSource(src);
27
- const collection = builder.build({ anchors: { main: datasetSpec } });
28
- if (!collection) return { primary };
31
+ let filterCollection;
32
+ let enrichmentCollection;
29
33
  try {
30
- const filterMatches = require_filter_discovery.findFilterColumns(collection);
34
+ filterCollection = new require_column_collection_builder.ColumnCollectionBuilder(pframeSpec).addSource(filterSource).build({
35
+ anchors: { main: datasetSpec },
36
+ allowPartialColumnList: true
37
+ });
38
+ enrichmentCollection = enrichmentSources !== void 0 ? new require_column_collection_builder.ColumnCollectionBuilder(pframeSpec).addSources(enrichmentSources).build({ anchors: { main: datasetSpec } }) : void 0;
39
+ const filterMatches = require_filter_discovery.findFilterColumns(filterCollection).filter((m) => filterPredicate(m.column.spec));
31
40
  const filters = filterMatches.length === 0 ? void 0 : require_filter_discovery.filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);
32
41
  let enrichments;
33
- if (opts?.withEnrichments !== void 0) {
34
- const enrichmentVariants = require_enrichment_discovery.findEnrichmentColumns(collection, {
35
- maxHops: opts.enrichmentMaxHops,
36
- ...typeof opts.withEnrichments === "function" ? { predicate: opts.withEnrichments } : { include: opts.withEnrichments }
42
+ if (enrichmentCollection && withEnrichments) {
43
+ const enrichmentVariants = require_enrichment_discovery.findEnrichmentColumns(enrichmentCollection, {
44
+ maxHops: opts?.enrichmentMaxHops,
45
+ ...typeof withEnrichments === "function" ? { predicate: withEnrichments } : { include: withEnrichments }
37
46
  });
38
- if (enrichmentVariants.length > 0) enrichments = require_enrichment_discovery.enrichmentVariantsToRefs(enrichmentVariants, opts.labelOptions);
47
+ if (enrichmentVariants.length > 0) enrichments = require_enrichment_discovery.enrichmentVariantsToRefs(enrichmentVariants, opts?.labelOptions);
39
48
  }
40
49
  return {
41
50
  primary,
@@ -43,7 +52,8 @@ function buildDatasetOptions(ctx, opts) {
43
52
  ...enrichments !== void 0 && enrichments.length > 0 ? { enrichments } : {}
44
53
  };
45
54
  } finally {
46
- collection.dispose();
55
+ filterCollection?.dispose();
56
+ enrichmentCollection?.dispose();
47
57
  }
48
58
  });
49
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"build_dataset_options.cjs","names":["collectCtxColumnSnapshotProviders","buildRefMap","ColumnCollectionBuilder","findFilterColumns","filterMatchesToOptions","findEnrichmentColumns","enrichmentVariantsToRefs"],"sources":["../../../src/components/PlDatasetSelector/build_dataset_options.ts"],"sourcesContent":["import type { MultiColumnSelector, Option, PObjectSpec } from \"@milaboratories/pl-model-common\";\nimport { multiColumnSelectorsToPredicate } from \"@milaboratories/pl-model-common\";\nimport type { DeriveLabelsOptions } from \"../../labels/derive_distinct_labels\";\nimport type { RenderCtxBase } from \"../../render\";\nimport { ColumnCollectionBuilder } from \"../../columns/column_collection_builder\";\nimport { collectCtxColumnSnapshotProviders } from \"../../columns/ctx_column_sources\";\nimport type { DatasetOption } from \"./dataset_selection\";\nimport { buildRefMap, filterMatchesToOptions, findFilterColumns } from \"./filter_discovery\";\nimport { enrichmentVariantsToRefs, findEnrichmentColumns } from \"./enrichment_discovery\";\n\nexport type BuildDatasetOptions = {\n /** Which result pool columns qualify as datasets. Defaults to all. */\n primary?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);\n /** Formatting options for filter labels. */\n labelOptions?: DeriveLabelsOptions;\n /**\n * Enables enrichment discovery and filters hits attached to\n * `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.\n */\n withEnrichments?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);\n /** Maximum linker hops considered. Only used when `withEnrichments` is set. */\n enrichmentMaxHops?: number;\n};\n\n/**\n * Usage:\n * ```ts\n * .output(\"datasetOptions\", (ctx) => buildDatasetOptions(ctx))\n * ```\n */\nexport function buildDatasetOptions(\n ctx: RenderCtxBase,\n opts?: BuildDatasetOptions,\n): DatasetOption[] | undefined {\n const primary = opts?.primary;\n const primaryPredicate =\n primary === undefined\n ? () => true\n : typeof primary === \"function\"\n ? primary\n : multiColumnSelectorsToPredicate(primary);\n const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });\n if (options.length === 0) return [];\n\n const columnSources = collectCtxColumnSnapshotProviders(ctx);\n const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);\n const pframeSpec = ctx.getService(\"pframeSpec\");\n\n return options.map((primary: Option): DatasetOption => {\n const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);\n if (!datasetSpec) return { primary };\n\n const builder = new ColumnCollectionBuilder(pframeSpec);\n for (const src of columnSources) builder.addSource(src);\n const collection = builder.build({ anchors: { main: datasetSpec } });\n if (!collection) return { primary };\n\n try {\n const filterMatches = findFilterColumns(collection);\n const filters =\n filterMatches.length === 0\n ? undefined\n : filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);\n\n let enrichments;\n if (opts?.withEnrichments !== undefined) {\n const enrichmentVariants = findEnrichmentColumns(collection, {\n maxHops: opts.enrichmentMaxHops,\n ...(typeof opts.withEnrichments === \"function\"\n ? { predicate: opts.withEnrichments }\n : { include: opts.withEnrichments }),\n });\n if (enrichmentVariants.length > 0) {\n enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts.labelOptions);\n }\n }\n\n return {\n primary,\n ...(filters !== undefined && filters.length > 0 ? { filters } : {}),\n ...(enrichments !== undefined && enrichments.length > 0 ? { enrichments } : {}),\n };\n } finally {\n collection.dispose();\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;;AA8BA,SAAgB,oBACd,KACA,MAC6B;CAC7B,MAAM,UAAU,MAAM;CACtB,MAAM,mBACJ,YAAY,KAAA,UACF,OACN,OAAO,YAAY,aACjB,WAAA,GAAA,gCAAA,iCACgC,QAAQ;CAChD,MAAM,UAAU,IAAI,WAAW,WAAW,kBAAkB,EAAE,qBAAqB,MAAM,CAAC;AAC1F,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAEnC,MAAM,gBAAgBA,2BAAAA,kCAAkC,IAAI;CAC5D,MAAM,SAASC,yBAAAA,YAAY,IAAI,WAAW,UAAU,CAAC,QAAQ;CAC7D,MAAM,aAAa,IAAI,WAAW,aAAa;AAE/C,QAAO,QAAQ,KAAK,YAAmC;EACrD,MAAM,cAAc,IAAI,WAAW,oBAAoB,QAAQ,IAAI;AACnE,MAAI,CAAC,YAAa,QAAO,EAAE,SAAS;EAEpC,MAAM,UAAU,IAAIC,kCAAAA,wBAAwB,WAAW;AACvD,OAAK,MAAM,OAAO,cAAe,SAAQ,UAAU,IAAI;EACvD,MAAM,aAAa,QAAQ,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,EAAE,CAAC;AACpE,MAAI,CAAC,WAAY,QAAO,EAAE,SAAS;AAEnC,MAAI;GACF,MAAM,gBAAgBC,yBAAAA,kBAAkB,WAAW;GACnD,MAAM,UACJ,cAAc,WAAW,IACrB,KAAA,IACAC,yBAAAA,uBAAuB,eAAe,QAAQ,MAAM,aAAa;GAEvE,IAAI;AACJ,OAAI,MAAM,oBAAoB,KAAA,GAAW;IACvC,MAAM,qBAAqBC,6BAAAA,sBAAsB,YAAY;KAC3D,SAAS,KAAK;KACd,GAAI,OAAO,KAAK,oBAAoB,aAChC,EAAE,WAAW,KAAK,iBAAiB,GACnC,EAAE,SAAS,KAAK,iBAAiB;KACtC,CAAC;AACF,QAAI,mBAAmB,SAAS,EAC9B,eAAcC,6BAAAA,yBAAyB,oBAAoB,KAAK,aAAa;;AAIjF,UAAO;IACL;IACA,GAAI,YAAY,KAAA,KAAa,QAAQ,SAAS,IAAI,EAAE,SAAS,GAAG,EAAE;IAClE,GAAI,gBAAgB,KAAA,KAAa,YAAY,SAAS,IAAI,EAAE,aAAa,GAAG,EAAE;IAC/E;YACO;AACR,cAAW,SAAS;;GAEtB"}
1
+ {"version":3,"file":"build_dataset_options.cjs","names":["buildRefMap","ResultPoolColumnSnapshotProvider","collectCtxColumnSnapshotProviders","ColumnCollectionBuilder","findFilterColumns","filterMatchesToOptions","findEnrichmentColumns","enrichmentVariantsToRefs"],"sources":["../../../src/components/PlDatasetSelector/build_dataset_options.ts"],"sourcesContent":["import type { MultiColumnSelector, Option, PObjectSpec } from \"@milaboratories/pl-model-common\";\nimport { multiColumnSelectorsToPredicate } from \"@milaboratories/pl-model-common\";\nimport type { DeriveLabelsOptions } from \"../../labels/derive_distinct_labels\";\nimport type { RenderCtxBase } from \"../../render\";\nimport type { AnchoredColumnCollection } from \"../../columns/column_collection_builder\";\nimport { ColumnCollectionBuilder } from \"../../columns/column_collection_builder\";\nimport {\n ResultPoolColumnSnapshotProvider,\n collectCtxColumnSnapshotProviders,\n} from \"../../columns/ctx_column_sources\";\nimport type { DatasetOption } from \"./dataset_selection\";\nimport { buildRefMap, filterMatchesToOptions, findFilterColumns } from \"./filter_discovery\";\nimport { enrichmentVariantsToRefs, findEnrichmentColumns } from \"./enrichment_discovery\";\n\ntype SpecPredicateOption =\n | MultiColumnSelector\n | MultiColumnSelector[]\n | ((spec: PObjectSpec) => boolean);\n\nfunction toPredicate(opt: SpecPredicateOption | undefined): (spec: PObjectSpec) => boolean {\n if (opt === undefined) return () => true;\n return typeof opt === \"function\" ? opt : multiColumnSelectorsToPredicate(opt);\n}\n\nexport type BuildDatasetOptions = {\n /** Which result pool columns qualify as datasets. Defaults to all. */\n primary?: SpecPredicateOption;\n /**\n * Restricts which result pool columns are considered as filters. Intersected\n * with the built-in `pl7.app/isSubset: \"true\"` constraint. Defaults to\n * accept-all.\n */\n filter?: SpecPredicateOption;\n /** Formatting options for filter labels. */\n labelOptions?: DeriveLabelsOptions;\n /**\n * Enables enrichment discovery and filters hits attached to\n * `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.\n */\n withEnrichments?: SpecPredicateOption;\n /** Maximum linker hops considered. Only used when `withEnrichments` is set. */\n enrichmentMaxHops?: number;\n};\n\n/**\n * Usage:\n * ```ts\n * .output(\"datasetOptions\", (ctx) => buildDatasetOptions(ctx))\n * ```\n */\nexport function buildDatasetOptions(\n ctx: RenderCtxBase,\n opts?: BuildDatasetOptions,\n): DatasetOption[] | undefined {\n const primaryPredicate = toPredicate(opts?.primary);\n const filterPredicate = toPredicate(opts?.filter);\n\n const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });\n if (options.length === 0) return [];\n\n const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);\n const pframeSpec = ctx.getService(\"pframeSpec\");\n\n const withEnrichments = opts?.withEnrichments ?? false;\n const filterSource = new ResultPoolColumnSnapshotProvider(ctx.resultPool);\n // Hoisted out of the per-option loop: collectCtxColumnSnapshotProviders\n // walks the entire output tree, so calling it once per dataset option would\n // be O(N × tree).\n const enrichmentSources = withEnrichments ? collectCtxColumnSnapshotProviders(ctx) : undefined;\n\n return options.map((primary: Option): DatasetOption => {\n const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);\n if (!datasetSpec) return { primary };\n\n // Allocations happen inside try so a throw on the second build()\n // still disposes the first collection.\n let filterCollection: AnchoredColumnCollection | undefined;\n let enrichmentCollection: AnchoredColumnCollection | undefined;\n try {\n // ResultPoolColumnSnapshotProvider is always complete;\n // allowPartialColumnList narrows the return type to non-undefined.\n filterCollection = new ColumnCollectionBuilder(pframeSpec)\n .addSource(filterSource)\n .build({ anchors: { main: datasetSpec }, allowPartialColumnList: true });\n\n enrichmentCollection =\n enrichmentSources !== undefined\n ? new ColumnCollectionBuilder(pframeSpec)\n .addSources(enrichmentSources)\n .build({ anchors: { main: datasetSpec } })\n : undefined;\n\n const filterMatches = findFilterColumns(filterCollection).filter((m) =>\n filterPredicate(m.column.spec),\n );\n const filters =\n filterMatches.length === 0\n ? undefined\n : filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);\n\n let enrichments;\n if (enrichmentCollection && withEnrichments) {\n const enrichmentVariants = findEnrichmentColumns(enrichmentCollection, {\n maxHops: opts?.enrichmentMaxHops,\n ...(typeof withEnrichments === \"function\"\n ? { predicate: withEnrichments }\n : { include: withEnrichments }),\n });\n if (enrichmentVariants.length > 0) {\n enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts?.labelOptions);\n }\n }\n\n return {\n primary,\n ...(filters !== undefined && filters.length > 0 ? { filters } : {}),\n ...(enrichments !== undefined && enrichments.length > 0 ? { enrichments } : {}),\n };\n } finally {\n filterCollection?.dispose();\n enrichmentCollection?.dispose();\n }\n });\n}\n"],"mappings":";;;;;;;AAmBA,SAAS,YAAY,KAAsE;AACzF,KAAI,QAAQ,KAAA,EAAW,cAAa;AACpC,QAAO,OAAO,QAAQ,aAAa,OAAA,GAAA,gCAAA,iCAAsC,IAAI;;;;;;;;AA6B/E,SAAgB,oBACd,KACA,MAC6B;CAC7B,MAAM,mBAAmB,YAAY,MAAM,QAAQ;CACnD,MAAM,kBAAkB,YAAY,MAAM,OAAO;CAEjD,MAAM,UAAU,IAAI,WAAW,WAAW,kBAAkB,EAAE,qBAAqB,MAAM,CAAC;AAC1F,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAEnC,MAAM,SAASA,yBAAAA,YAAY,IAAI,WAAW,UAAU,CAAC,QAAQ;CAC7D,MAAM,aAAa,IAAI,WAAW,aAAa;CAE/C,MAAM,kBAAkB,MAAM,mBAAmB;CACjD,MAAM,eAAe,IAAIC,2BAAAA,iCAAiC,IAAI,WAAW;CAIzE,MAAM,oBAAoB,kBAAkBC,2BAAAA,kCAAkC,IAAI,GAAG,KAAA;AAErF,QAAO,QAAQ,KAAK,YAAmC;EACrD,MAAM,cAAc,IAAI,WAAW,oBAAoB,QAAQ,IAAI;AACnE,MAAI,CAAC,YAAa,QAAO,EAAE,SAAS;EAIpC,IAAI;EACJ,IAAI;AACJ,MAAI;AAGF,sBAAmB,IAAIC,kCAAAA,wBAAwB,WAAW,CACvD,UAAU,aAAa,CACvB,MAAM;IAAE,SAAS,EAAE,MAAM,aAAa;IAAE,wBAAwB;IAAM,CAAC;AAE1E,0BACE,sBAAsB,KAAA,IAClB,IAAIA,kCAAAA,wBAAwB,WAAW,CACpC,WAAW,kBAAkB,CAC7B,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,EAAE,CAAC,GAC5C,KAAA;GAEN,MAAM,gBAAgBC,yBAAAA,kBAAkB,iBAAiB,CAAC,QAAQ,MAChE,gBAAgB,EAAE,OAAO,KAAK,CAC/B;GACD,MAAM,UACJ,cAAc,WAAW,IACrB,KAAA,IACAC,yBAAAA,uBAAuB,eAAe,QAAQ,MAAM,aAAa;GAEvE,IAAI;AACJ,OAAI,wBAAwB,iBAAiB;IAC3C,MAAM,qBAAqBC,6BAAAA,sBAAsB,sBAAsB;KACrE,SAAS,MAAM;KACf,GAAI,OAAO,oBAAoB,aAC3B,EAAE,WAAW,iBAAiB,GAC9B,EAAE,SAAS,iBAAiB;KACjC,CAAC;AACF,QAAI,mBAAmB,SAAS,EAC9B,eAAcC,6BAAAA,yBAAyB,oBAAoB,MAAM,aAAa;;AAIlF,UAAO;IACL;IACA,GAAI,YAAY,KAAA,KAAa,QAAQ,SAAS,IAAI,EAAE,SAAS,GAAG,EAAE;IAClE,GAAI,gBAAgB,KAAA,KAAa,YAAY,SAAS,IAAI,EAAE,aAAa,GAAG,EAAE;IAC/E;YACO;AACR,qBAAkB,SAAS;AAC3B,yBAAsB,SAAS;;GAEjC"}
@@ -4,14 +4,21 @@ import { DatasetOption } from "./dataset_selection.js";
4
4
  import { MultiColumnSelector, PObjectSpec } from "@milaboratories/pl-model-common";
5
5
 
6
6
  //#region src/components/PlDatasetSelector/build_dataset_options.d.ts
7
+ type SpecPredicateOption = MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);
7
8
  type BuildDatasetOptions = {
8
- /** Which result pool columns qualify as datasets. Defaults to all. */primary?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean); /** Formatting options for filter labels. */
9
+ /** Which result pool columns qualify as datasets. Defaults to all. */primary?: SpecPredicateOption;
10
+ /**
11
+ * Restricts which result pool columns are considered as filters. Intersected
12
+ * with the built-in `pl7.app/isSubset: "true"` constraint. Defaults to
13
+ * accept-all.
14
+ */
15
+ filter?: SpecPredicateOption; /** Formatting options for filter labels. */
9
16
  labelOptions?: DeriveLabelsOptions;
10
17
  /**
11
18
  * Enables enrichment discovery and filters hits attached to
12
19
  * `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.
13
20
  */
14
- withEnrichments?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean); /** Maximum linker hops considered. Only used when `withEnrichments` is set. */
21
+ withEnrichments?: SpecPredicateOption; /** Maximum linker hops considered. Only used when `withEnrichments` is set. */
15
22
  enrichmentMaxHops?: number;
16
23
  };
17
24
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"build_dataset_options.d.ts","names":[],"sources":["../../../src/components/PlDatasetSelector/build_dataset_options.ts"],"mappings":";;;;;;KAUY,mBAAA;wEAEV,OAAA,GAAU,mBAAA,GAAsB,mBAAA,OAA0B,IAAA,EAAM,WAAA,eAFtD;EAIV,YAAA,GAAe,mBAAA;;;;;EAKf,eAAA,GAAkB,mBAAA,GAAsB,mBAAA,OAA0B,IAAA,EAAM,WAAA,eAAtD;EAElB,iBAAA;AAAA;;;;;;;iBASc,mBAAA,CACd,GAAA,EAAK,aAAA,EACL,IAAA,GAAO,mBAAA,GACN,aAAA"}
1
+ {"version":3,"file":"build_dataset_options.d.ts","names":[],"sources":["../../../src/components/PlDatasetSelector/build_dataset_options.ts"],"mappings":";;;;;;KAcK,mBAAA,GACD,mBAAA,GACA,mBAAA,OACE,IAAA,EAAM,WAAA;AAAA,KAOA,mBAAA;wEAEV,OAAA,GAAU,mBAAA;EAZY;;;;;EAkBtB,MAAA,GAAS,mBAAA,EAfY;EAiBrB,YAAA,GAAe,mBAAA;EAlBb;;;;EAuBF,eAAA,GAAkB,mBAAA,EAfR;EAiBV,iBAAA;AAAA;;;;;;;iBASc,mBAAA,CACd,GAAA,EAAK,aAAA,EACL,IAAA,GAAO,mBAAA,GACN,aAAA"}
@@ -1,9 +1,13 @@
1
1
  import { buildRefMap, filterMatchesToOptions, findFilterColumns } from "./filter_discovery.js";
2
2
  import { ColumnCollectionBuilder } from "../../columns/column_collection_builder.js";
3
- import { collectCtxColumnSnapshotProviders } from "../../columns/ctx_column_sources.js";
3
+ import { ResultPoolColumnSnapshotProvider, collectCtxColumnSnapshotProviders } from "../../columns/ctx_column_sources.js";
4
4
  import { enrichmentVariantsToRefs, findEnrichmentColumns } from "./enrichment_discovery.js";
5
5
  import { multiColumnSelectorsToPredicate } from "@milaboratories/pl-model-common";
6
6
  //#region src/components/PlDatasetSelector/build_dataset_options.ts
7
+ function toPredicate(opt) {
8
+ if (opt === void 0) return () => true;
9
+ return typeof opt === "function" ? opt : multiColumnSelectorsToPredicate(opt);
10
+ }
7
11
  /**
8
12
  * Usage:
9
13
  * ```ts
@@ -11,30 +15,35 @@ import { multiColumnSelectorsToPredicate } from "@milaboratories/pl-model-common
11
15
  * ```
12
16
  */
13
17
  function buildDatasetOptions(ctx, opts) {
14
- const primary = opts?.primary;
15
- const primaryPredicate = primary === void 0 ? () => true : typeof primary === "function" ? primary : multiColumnSelectorsToPredicate(primary);
18
+ const primaryPredicate = toPredicate(opts?.primary);
19
+ const filterPredicate = toPredicate(opts?.filter);
16
20
  const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });
17
21
  if (options.length === 0) return [];
18
- const columnSources = collectCtxColumnSnapshotProviders(ctx);
19
22
  const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);
20
23
  const pframeSpec = ctx.getService("pframeSpec");
24
+ const withEnrichments = opts?.withEnrichments ?? false;
25
+ const filterSource = new ResultPoolColumnSnapshotProvider(ctx.resultPool);
26
+ const enrichmentSources = withEnrichments ? collectCtxColumnSnapshotProviders(ctx) : void 0;
21
27
  return options.map((primary) => {
22
28
  const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);
23
29
  if (!datasetSpec) return { primary };
24
- const builder = new ColumnCollectionBuilder(pframeSpec);
25
- for (const src of columnSources) builder.addSource(src);
26
- const collection = builder.build({ anchors: { main: datasetSpec } });
27
- if (!collection) return { primary };
30
+ let filterCollection;
31
+ let enrichmentCollection;
28
32
  try {
29
- const filterMatches = findFilterColumns(collection);
33
+ filterCollection = new ColumnCollectionBuilder(pframeSpec).addSource(filterSource).build({
34
+ anchors: { main: datasetSpec },
35
+ allowPartialColumnList: true
36
+ });
37
+ enrichmentCollection = enrichmentSources !== void 0 ? new ColumnCollectionBuilder(pframeSpec).addSources(enrichmentSources).build({ anchors: { main: datasetSpec } }) : void 0;
38
+ const filterMatches = findFilterColumns(filterCollection).filter((m) => filterPredicate(m.column.spec));
30
39
  const filters = filterMatches.length === 0 ? void 0 : filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);
31
40
  let enrichments;
32
- if (opts?.withEnrichments !== void 0) {
33
- const enrichmentVariants = findEnrichmentColumns(collection, {
34
- maxHops: opts.enrichmentMaxHops,
35
- ...typeof opts.withEnrichments === "function" ? { predicate: opts.withEnrichments } : { include: opts.withEnrichments }
41
+ if (enrichmentCollection && withEnrichments) {
42
+ const enrichmentVariants = findEnrichmentColumns(enrichmentCollection, {
43
+ maxHops: opts?.enrichmentMaxHops,
44
+ ...typeof withEnrichments === "function" ? { predicate: withEnrichments } : { include: withEnrichments }
36
45
  });
37
- if (enrichmentVariants.length > 0) enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts.labelOptions);
46
+ if (enrichmentVariants.length > 0) enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts?.labelOptions);
38
47
  }
39
48
  return {
40
49
  primary,
@@ -42,7 +51,8 @@ function buildDatasetOptions(ctx, opts) {
42
51
  ...enrichments !== void 0 && enrichments.length > 0 ? { enrichments } : {}
43
52
  };
44
53
  } finally {
45
- collection.dispose();
54
+ filterCollection?.dispose();
55
+ enrichmentCollection?.dispose();
46
56
  }
47
57
  });
48
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"build_dataset_options.js","names":[],"sources":["../../../src/components/PlDatasetSelector/build_dataset_options.ts"],"sourcesContent":["import type { MultiColumnSelector, Option, PObjectSpec } from \"@milaboratories/pl-model-common\";\nimport { multiColumnSelectorsToPredicate } from \"@milaboratories/pl-model-common\";\nimport type { DeriveLabelsOptions } from \"../../labels/derive_distinct_labels\";\nimport type { RenderCtxBase } from \"../../render\";\nimport { ColumnCollectionBuilder } from \"../../columns/column_collection_builder\";\nimport { collectCtxColumnSnapshotProviders } from \"../../columns/ctx_column_sources\";\nimport type { DatasetOption } from \"./dataset_selection\";\nimport { buildRefMap, filterMatchesToOptions, findFilterColumns } from \"./filter_discovery\";\nimport { enrichmentVariantsToRefs, findEnrichmentColumns } from \"./enrichment_discovery\";\n\nexport type BuildDatasetOptions = {\n /** Which result pool columns qualify as datasets. Defaults to all. */\n primary?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);\n /** Formatting options for filter labels. */\n labelOptions?: DeriveLabelsOptions;\n /**\n * Enables enrichment discovery and filters hits attached to\n * `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.\n */\n withEnrichments?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);\n /** Maximum linker hops considered. Only used when `withEnrichments` is set. */\n enrichmentMaxHops?: number;\n};\n\n/**\n * Usage:\n * ```ts\n * .output(\"datasetOptions\", (ctx) => buildDatasetOptions(ctx))\n * ```\n */\nexport function buildDatasetOptions(\n ctx: RenderCtxBase,\n opts?: BuildDatasetOptions,\n): DatasetOption[] | undefined {\n const primary = opts?.primary;\n const primaryPredicate =\n primary === undefined\n ? () => true\n : typeof primary === \"function\"\n ? primary\n : multiColumnSelectorsToPredicate(primary);\n const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });\n if (options.length === 0) return [];\n\n const columnSources = collectCtxColumnSnapshotProviders(ctx);\n const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);\n const pframeSpec = ctx.getService(\"pframeSpec\");\n\n return options.map((primary: Option): DatasetOption => {\n const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);\n if (!datasetSpec) return { primary };\n\n const builder = new ColumnCollectionBuilder(pframeSpec);\n for (const src of columnSources) builder.addSource(src);\n const collection = builder.build({ anchors: { main: datasetSpec } });\n if (!collection) return { primary };\n\n try {\n const filterMatches = findFilterColumns(collection);\n const filters =\n filterMatches.length === 0\n ? undefined\n : filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);\n\n let enrichments;\n if (opts?.withEnrichments !== undefined) {\n const enrichmentVariants = findEnrichmentColumns(collection, {\n maxHops: opts.enrichmentMaxHops,\n ...(typeof opts.withEnrichments === \"function\"\n ? { predicate: opts.withEnrichments }\n : { include: opts.withEnrichments }),\n });\n if (enrichmentVariants.length > 0) {\n enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts.labelOptions);\n }\n }\n\n return {\n primary,\n ...(filters !== undefined && filters.length > 0 ? { filters } : {}),\n ...(enrichments !== undefined && enrichments.length > 0 ? { enrichments } : {}),\n };\n } finally {\n collection.dispose();\n }\n });\n}\n"],"mappings":";;;;;;;;;;;;AA8BA,SAAgB,oBACd,KACA,MAC6B;CAC7B,MAAM,UAAU,MAAM;CACtB,MAAM,mBACJ,YAAY,KAAA,UACF,OACN,OAAO,YAAY,aACjB,UACA,gCAAgC,QAAQ;CAChD,MAAM,UAAU,IAAI,WAAW,WAAW,kBAAkB,EAAE,qBAAqB,MAAM,CAAC;AAC1F,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAEnC,MAAM,gBAAgB,kCAAkC,IAAI;CAC5D,MAAM,SAAS,YAAY,IAAI,WAAW,UAAU,CAAC,QAAQ;CAC7D,MAAM,aAAa,IAAI,WAAW,aAAa;AAE/C,QAAO,QAAQ,KAAK,YAAmC;EACrD,MAAM,cAAc,IAAI,WAAW,oBAAoB,QAAQ,IAAI;AACnE,MAAI,CAAC,YAAa,QAAO,EAAE,SAAS;EAEpC,MAAM,UAAU,IAAI,wBAAwB,WAAW;AACvD,OAAK,MAAM,OAAO,cAAe,SAAQ,UAAU,IAAI;EACvD,MAAM,aAAa,QAAQ,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,EAAE,CAAC;AACpE,MAAI,CAAC,WAAY,QAAO,EAAE,SAAS;AAEnC,MAAI;GACF,MAAM,gBAAgB,kBAAkB,WAAW;GACnD,MAAM,UACJ,cAAc,WAAW,IACrB,KAAA,IACA,uBAAuB,eAAe,QAAQ,MAAM,aAAa;GAEvE,IAAI;AACJ,OAAI,MAAM,oBAAoB,KAAA,GAAW;IACvC,MAAM,qBAAqB,sBAAsB,YAAY;KAC3D,SAAS,KAAK;KACd,GAAI,OAAO,KAAK,oBAAoB,aAChC,EAAE,WAAW,KAAK,iBAAiB,GACnC,EAAE,SAAS,KAAK,iBAAiB;KACtC,CAAC;AACF,QAAI,mBAAmB,SAAS,EAC9B,eAAc,yBAAyB,oBAAoB,KAAK,aAAa;;AAIjF,UAAO;IACL;IACA,GAAI,YAAY,KAAA,KAAa,QAAQ,SAAS,IAAI,EAAE,SAAS,GAAG,EAAE;IAClE,GAAI,gBAAgB,KAAA,KAAa,YAAY,SAAS,IAAI,EAAE,aAAa,GAAG,EAAE;IAC/E;YACO;AACR,cAAW,SAAS;;GAEtB"}
1
+ {"version":3,"file":"build_dataset_options.js","names":[],"sources":["../../../src/components/PlDatasetSelector/build_dataset_options.ts"],"sourcesContent":["import type { MultiColumnSelector, Option, PObjectSpec } from \"@milaboratories/pl-model-common\";\nimport { multiColumnSelectorsToPredicate } from \"@milaboratories/pl-model-common\";\nimport type { DeriveLabelsOptions } from \"../../labels/derive_distinct_labels\";\nimport type { RenderCtxBase } from \"../../render\";\nimport type { AnchoredColumnCollection } from \"../../columns/column_collection_builder\";\nimport { ColumnCollectionBuilder } from \"../../columns/column_collection_builder\";\nimport {\n ResultPoolColumnSnapshotProvider,\n collectCtxColumnSnapshotProviders,\n} from \"../../columns/ctx_column_sources\";\nimport type { DatasetOption } from \"./dataset_selection\";\nimport { buildRefMap, filterMatchesToOptions, findFilterColumns } from \"./filter_discovery\";\nimport { enrichmentVariantsToRefs, findEnrichmentColumns } from \"./enrichment_discovery\";\n\ntype SpecPredicateOption =\n | MultiColumnSelector\n | MultiColumnSelector[]\n | ((spec: PObjectSpec) => boolean);\n\nfunction toPredicate(opt: SpecPredicateOption | undefined): (spec: PObjectSpec) => boolean {\n if (opt === undefined) return () => true;\n return typeof opt === \"function\" ? opt : multiColumnSelectorsToPredicate(opt);\n}\n\nexport type BuildDatasetOptions = {\n /** Which result pool columns qualify as datasets. Defaults to all. */\n primary?: SpecPredicateOption;\n /**\n * Restricts which result pool columns are considered as filters. Intersected\n * with the built-in `pl7.app/isSubset: \"true\"` constraint. Defaults to\n * accept-all.\n */\n filter?: SpecPredicateOption;\n /** Formatting options for filter labels. */\n labelOptions?: DeriveLabelsOptions;\n /**\n * Enables enrichment discovery and filters hits attached to\n * `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.\n */\n withEnrichments?: SpecPredicateOption;\n /** Maximum linker hops considered. Only used when `withEnrichments` is set. */\n enrichmentMaxHops?: number;\n};\n\n/**\n * Usage:\n * ```ts\n * .output(\"datasetOptions\", (ctx) => buildDatasetOptions(ctx))\n * ```\n */\nexport function buildDatasetOptions(\n ctx: RenderCtxBase,\n opts?: BuildDatasetOptions,\n): DatasetOption[] | undefined {\n const primaryPredicate = toPredicate(opts?.primary);\n const filterPredicate = toPredicate(opts?.filter);\n\n const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });\n if (options.length === 0) return [];\n\n const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);\n const pframeSpec = ctx.getService(\"pframeSpec\");\n\n const withEnrichments = opts?.withEnrichments ?? false;\n const filterSource = new ResultPoolColumnSnapshotProvider(ctx.resultPool);\n // Hoisted out of the per-option loop: collectCtxColumnSnapshotProviders\n // walks the entire output tree, so calling it once per dataset option would\n // be O(N × tree).\n const enrichmentSources = withEnrichments ? collectCtxColumnSnapshotProviders(ctx) : undefined;\n\n return options.map((primary: Option): DatasetOption => {\n const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);\n if (!datasetSpec) return { primary };\n\n // Allocations happen inside try so a throw on the second build()\n // still disposes the first collection.\n let filterCollection: AnchoredColumnCollection | undefined;\n let enrichmentCollection: AnchoredColumnCollection | undefined;\n try {\n // ResultPoolColumnSnapshotProvider is always complete;\n // allowPartialColumnList narrows the return type to non-undefined.\n filterCollection = new ColumnCollectionBuilder(pframeSpec)\n .addSource(filterSource)\n .build({ anchors: { main: datasetSpec }, allowPartialColumnList: true });\n\n enrichmentCollection =\n enrichmentSources !== undefined\n ? new ColumnCollectionBuilder(pframeSpec)\n .addSources(enrichmentSources)\n .build({ anchors: { main: datasetSpec } })\n : undefined;\n\n const filterMatches = findFilterColumns(filterCollection).filter((m) =>\n filterPredicate(m.column.spec),\n );\n const filters =\n filterMatches.length === 0\n ? undefined\n : filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);\n\n let enrichments;\n if (enrichmentCollection && withEnrichments) {\n const enrichmentVariants = findEnrichmentColumns(enrichmentCollection, {\n maxHops: opts?.enrichmentMaxHops,\n ...(typeof withEnrichments === \"function\"\n ? { predicate: withEnrichments }\n : { include: withEnrichments }),\n });\n if (enrichmentVariants.length > 0) {\n enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts?.labelOptions);\n }\n }\n\n return {\n primary,\n ...(filters !== undefined && filters.length > 0 ? { filters } : {}),\n ...(enrichments !== undefined && enrichments.length > 0 ? { enrichments } : {}),\n };\n } finally {\n filterCollection?.dispose();\n enrichmentCollection?.dispose();\n }\n });\n}\n"],"mappings":";;;;;;AAmBA,SAAS,YAAY,KAAsE;AACzF,KAAI,QAAQ,KAAA,EAAW,cAAa;AACpC,QAAO,OAAO,QAAQ,aAAa,MAAM,gCAAgC,IAAI;;;;;;;;AA6B/E,SAAgB,oBACd,KACA,MAC6B;CAC7B,MAAM,mBAAmB,YAAY,MAAM,QAAQ;CACnD,MAAM,kBAAkB,YAAY,MAAM,OAAO;CAEjD,MAAM,UAAU,IAAI,WAAW,WAAW,kBAAkB,EAAE,qBAAqB,MAAM,CAAC;AAC1F,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAEnC,MAAM,SAAS,YAAY,IAAI,WAAW,UAAU,CAAC,QAAQ;CAC7D,MAAM,aAAa,IAAI,WAAW,aAAa;CAE/C,MAAM,kBAAkB,MAAM,mBAAmB;CACjD,MAAM,eAAe,IAAI,iCAAiC,IAAI,WAAW;CAIzE,MAAM,oBAAoB,kBAAkB,kCAAkC,IAAI,GAAG,KAAA;AAErF,QAAO,QAAQ,KAAK,YAAmC;EACrD,MAAM,cAAc,IAAI,WAAW,oBAAoB,QAAQ,IAAI;AACnE,MAAI,CAAC,YAAa,QAAO,EAAE,SAAS;EAIpC,IAAI;EACJ,IAAI;AACJ,MAAI;AAGF,sBAAmB,IAAI,wBAAwB,WAAW,CACvD,UAAU,aAAa,CACvB,MAAM;IAAE,SAAS,EAAE,MAAM,aAAa;IAAE,wBAAwB;IAAM,CAAC;AAE1E,0BACE,sBAAsB,KAAA,IAClB,IAAI,wBAAwB,WAAW,CACpC,WAAW,kBAAkB,CAC7B,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,EAAE,CAAC,GAC5C,KAAA;GAEN,MAAM,gBAAgB,kBAAkB,iBAAiB,CAAC,QAAQ,MAChE,gBAAgB,EAAE,OAAO,KAAK,CAC/B;GACD,MAAM,UACJ,cAAc,WAAW,IACrB,KAAA,IACA,uBAAuB,eAAe,QAAQ,MAAM,aAAa;GAEvE,IAAI;AACJ,OAAI,wBAAwB,iBAAiB;IAC3C,MAAM,qBAAqB,sBAAsB,sBAAsB;KACrE,SAAS,MAAM;KACf,GAAI,OAAO,oBAAoB,aAC3B,EAAE,WAAW,iBAAiB,GAC9B,EAAE,SAAS,iBAAiB;KACjC,CAAC;AACF,QAAI,mBAAmB,SAAS,EAC9B,eAAc,yBAAyB,oBAAoB,MAAM,aAAa;;AAIlF,UAAO;IACL;IACA,GAAI,YAAY,KAAA,KAAa,QAAQ,SAAS,IAAI,EAAE,SAAS,GAAG,EAAE;IAClE,GAAI,gBAAgB,KAAA,KAAa,YAAY,SAAS,IAAI,EAAE,aAAa,GAAG,EAAE;IAC/E;YACO;AACR,qBAAkB,SAAS;AAC3B,yBAAsB,SAAS;;GAEjC"}
@@ -21,28 +21,32 @@ function findFilterColumns(collection) {
21
21
  /**
22
22
  * Derive labeled options from filter column matches, for use in DatasetOption.filters.
23
23
  *
24
+ * Entries whose column id has no PlRef in `refsByObjectId` are silently
25
+ * skipped — they cannot be exposed as user-selectable options.
26
+ *
24
27
  * @param matches - from findFilterColumns()
25
28
  * @param refsByObjectId - from {@link buildRefMap}
26
29
  * @param labelOptions - forwarded to deriveDistinctLabels()
27
30
  */
28
31
  function filterMatchesToOptions(matches, refsByObjectId, labelOptions) {
29
32
  if (matches.length === 0) return [];
30
- const flattened = matches.flatMap((m) => m.variants.map((v) => ({
31
- match: m,
32
- variant: v
33
- })));
33
+ const flattened = matches.flatMap((match) => {
34
+ const ref = refsByObjectId.get(match.column.id);
35
+ if (ref === void 0) return [];
36
+ return match.variants.map((variant) => ({
37
+ match,
38
+ variant,
39
+ ref
40
+ }));
41
+ });
34
42
  const labels = require_derive_distinct_labels.deriveDistinctLabels(flattened.map(({ match, variant }) => ({
35
43
  spec: match.column.spec,
36
44
  linkerPath: variant.path.map((p) => ({ spec: p.linker.spec }))
37
45
  })), labelOptions);
38
- return flattened.map(({ match }, i) => {
39
- const ref = refsByObjectId.get(match.column.id);
40
- if (ref === void 0) throw new Error(`no PlRef found for filter column ${match.column.spec.name} (id: ${match.column.id})`);
41
- return {
42
- ref,
43
- label: labels[i]
44
- };
45
- });
46
+ return flattened.map(({ ref }, i) => ({
47
+ ref,
48
+ label: labels[i]
49
+ }));
46
50
  }
47
51
  /**
48
52
  * Usage: `buildRefMap(ctx.resultPool.getSpecs().entries)`
@@ -1 +1 @@
1
- {"version":3,"file":"filter_discovery.cjs","names":["Annotation","deriveDistinctLabels"],"sources":["../../../src/components/PlDatasetSelector/filter_discovery.ts"],"sourcesContent":["import { Annotation } from \"@milaboratories/pl-model-common\";\nimport type { Option, PlRef, PObjectId } from \"@milaboratories/pl-model-common\";\nimport canonicalize from \"canonicalize\";\nimport type {\n AnchoredColumnCollection,\n ColumnMatch,\n} from \"../../columns/column_collection_builder\";\nimport {\n deriveDistinctLabels,\n type DeriveLabelsOptions,\n type Entry,\n} from \"../../labels/derive_distinct_labels\";\n\n/**\n * Matches columns annotated `pl7.app/isSubset: \"true\"` whose axes ⊆ anchor axes.\n *\n * The axes-subset constraint is enforced by `mode: \"enrichment\"`, which sets\n * `allowFloatingHitAxes: false` — every axis of the matched column must be\n * present in the anchor's axes. See `matchingModeToConstraints()` in\n * `column_collection_builder.ts`.\n */\nexport function findFilterColumns(collection: AnchoredColumnCollection): ColumnMatch[] {\n return collection.findColumns({\n mode: \"enrichment\",\n include: {\n annotations: { [Annotation.IsSubset]: \"true\" },\n },\n });\n}\n\n/**\n * Derive labeled options from filter column matches, for use in DatasetOption.filters.\n *\n * @param matches - from findFilterColumns()\n * @param refsByObjectId - from {@link buildRefMap}\n * @param labelOptions - forwarded to deriveDistinctLabels()\n */\nexport function filterMatchesToOptions(\n matches: ColumnMatch[],\n refsByObjectId: ReadonlyMap<PObjectId, PlRef>,\n labelOptions?: DeriveLabelsOptions,\n): Option[] {\n if (matches.length === 0) return [];\n\n // Each ColumnMatch can be reached via multiple variants (different linker\n // paths / qualifications). We emit one Option per variant so the user can\n // pick a specific path — `deriveDistinctLabels` disambiguates labels by\n // path.\n const flattened = matches.flatMap((m) => m.variants.map((v) => ({ match: m, variant: v })));\n\n const entries: Entry[] = flattened.map(({ match, variant }) => ({\n spec: match.column.spec,\n linkerPath: variant.path.map((p) => ({ spec: p.linker.spec })),\n }));\n\n const labels = deriveDistinctLabels(entries, labelOptions);\n\n return flattened.map(({ match }, i) => {\n const ref = refsByObjectId.get(match.column.id);\n if (ref === undefined)\n throw new Error(\n `no PlRef found for filter column ${match.column.spec.name} (id: ${match.column.id})`,\n );\n return { ref, label: labels[i] };\n });\n}\n\n/**\n * Usage: `buildRefMap(ctx.resultPool.getSpecs().entries)`\n */\nexport function buildRefMap(entries: readonly { readonly ref: PlRef }[]): Map<PObjectId, PlRef> {\n const map = new Map<PObjectId, PlRef>();\n for (const entry of entries) {\n map.set(canonicalize(entry.ref)! as PObjectId, entry.ref);\n }\n return map;\n}\n"],"mappings":";;;;;;;;;;;;;;AAqBA,SAAgB,kBAAkB,YAAqD;AACrF,QAAO,WAAW,YAAY;EAC5B,MAAM;EACN,SAAS,EACP,aAAa,GAAGA,gCAAAA,WAAW,WAAW,QAAQ,EAC/C;EACF,CAAC;;;;;;;;;AAUJ,SAAgB,uBACd,SACA,gBACA,cACU;AACV,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAMnC,MAAM,YAAY,QAAQ,SAAS,MAAM,EAAE,SAAS,KAAK,OAAO;EAAE,OAAO;EAAG,SAAS;EAAG,EAAE,CAAC;CAO3F,MAAM,SAASC,+BAAAA,qBALU,UAAU,KAAK,EAAE,OAAO,eAAe;EAC9D,MAAM,MAAM,OAAO;EACnB,YAAY,QAAQ,KAAK,KAAK,OAAO,EAAE,MAAM,EAAE,OAAO,MAAM,EAAE;EAC/D,EAAE,EAE0C,aAAa;AAE1D,QAAO,UAAU,KAAK,EAAE,SAAS,MAAM;EACrC,MAAM,MAAM,eAAe,IAAI,MAAM,OAAO,GAAG;AAC/C,MAAI,QAAQ,KAAA,EACV,OAAM,IAAI,MACR,oCAAoC,MAAM,OAAO,KAAK,KAAK,QAAQ,MAAM,OAAO,GAAG,GACpF;AACH,SAAO;GAAE;GAAK,OAAO,OAAO;GAAI;GAChC;;;;;AAMJ,SAAgB,YAAY,SAAoE;CAC9F,MAAM,sBAAM,IAAI,KAAuB;AACvC,MAAK,MAAM,SAAS,QAClB,KAAI,KAAA,GAAA,aAAA,SAAiB,MAAM,IAAI,EAAgB,MAAM,IAAI;AAE3D,QAAO"}
1
+ {"version":3,"file":"filter_discovery.cjs","names":["Annotation","deriveDistinctLabels"],"sources":["../../../src/components/PlDatasetSelector/filter_discovery.ts"],"sourcesContent":["import { Annotation } from \"@milaboratories/pl-model-common\";\nimport type { Option, PlRef, PObjectId } from \"@milaboratories/pl-model-common\";\nimport canonicalize from \"canonicalize\";\nimport type {\n AnchoredColumnCollection,\n ColumnMatch,\n} from \"../../columns/column_collection_builder\";\nimport {\n deriveDistinctLabels,\n type DeriveLabelsOptions,\n type Entry,\n} from \"../../labels/derive_distinct_labels\";\n\n/**\n * Matches columns annotated `pl7.app/isSubset: \"true\"` whose axes ⊆ anchor axes.\n *\n * The axes-subset constraint is enforced by `mode: \"enrichment\"`, which sets\n * `allowFloatingHitAxes: false` — every axis of the matched column must be\n * present in the anchor's axes. See `matchingModeToConstraints()` in\n * `column_collection_builder.ts`.\n */\nexport function findFilterColumns(collection: AnchoredColumnCollection): ColumnMatch[] {\n return collection.findColumns({\n mode: \"enrichment\",\n include: {\n annotations: { [Annotation.IsSubset]: \"true\" },\n },\n });\n}\n\n/**\n * Derive labeled options from filter column matches, for use in DatasetOption.filters.\n *\n * Entries whose column id has no PlRef in `refsByObjectId` are silently\n * skipped — they cannot be exposed as user-selectable options.\n *\n * @param matches - from findFilterColumns()\n * @param refsByObjectId - from {@link buildRefMap}\n * @param labelOptions - forwarded to deriveDistinctLabels()\n */\nexport function filterMatchesToOptions(\n matches: ColumnMatch[],\n refsByObjectId: ReadonlyMap<PObjectId, PlRef>,\n labelOptions?: DeriveLabelsOptions,\n): Option[] {\n if (matches.length === 0) return [];\n\n // Each ColumnMatch can be reached via multiple variants (different linker\n // paths / qualifications). We emit one Option per variant so the user can\n // pick a specific path — `deriveDistinctLabels` disambiguates labels by\n // path. All variants of a match share a column id, so the ref lookup\n // happens once per match.\n const flattened = matches.flatMap((match) => {\n const ref = refsByObjectId.get(match.column.id);\n if (ref === undefined) return [];\n return match.variants.map((variant) => ({ match, variant, ref }));\n });\n\n const entries: Entry[] = flattened.map(({ match, variant }) => ({\n spec: match.column.spec,\n linkerPath: variant.path.map((p) => ({ spec: p.linker.spec })),\n }));\n\n const labels = deriveDistinctLabels(entries, labelOptions);\n\n return flattened.map(({ ref }, i) => ({ ref, label: labels[i] }));\n}\n\n/**\n * Usage: `buildRefMap(ctx.resultPool.getSpecs().entries)`\n */\nexport function buildRefMap(entries: readonly { readonly ref: PlRef }[]): Map<PObjectId, PlRef> {\n const map = new Map<PObjectId, PlRef>();\n for (const entry of entries) {\n map.set(canonicalize(entry.ref)! as PObjectId, entry.ref);\n }\n return map;\n}\n"],"mappings":";;;;;;;;;;;;;;AAqBA,SAAgB,kBAAkB,YAAqD;AACrF,QAAO,WAAW,YAAY;EAC5B,MAAM;EACN,SAAS,EACP,aAAa,GAAGA,gCAAAA,WAAW,WAAW,QAAQ,EAC/C;EACF,CAAC;;;;;;;;;;;;AAaJ,SAAgB,uBACd,SACA,gBACA,cACU;AACV,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAOnC,MAAM,YAAY,QAAQ,SAAS,UAAU;EAC3C,MAAM,MAAM,eAAe,IAAI,MAAM,OAAO,GAAG;AAC/C,MAAI,QAAQ,KAAA,EAAW,QAAO,EAAE;AAChC,SAAO,MAAM,SAAS,KAAK,aAAa;GAAE;GAAO;GAAS;GAAK,EAAE;GACjE;CAOF,MAAM,SAASC,+BAAAA,qBALU,UAAU,KAAK,EAAE,OAAO,eAAe;EAC9D,MAAM,MAAM,OAAO;EACnB,YAAY,QAAQ,KAAK,KAAK,OAAO,EAAE,MAAM,EAAE,OAAO,MAAM,EAAE;EAC/D,EAAE,EAE0C,aAAa;AAE1D,QAAO,UAAU,KAAK,EAAE,OAAO,OAAO;EAAE;EAAK,OAAO,OAAO;EAAI,EAAE;;;;;AAMnE,SAAgB,YAAY,SAAoE;CAC9F,MAAM,sBAAM,IAAI,KAAuB;AACvC,MAAK,MAAM,SAAS,QAClB,KAAI,KAAA,GAAA,aAAA,SAAiB,MAAM,IAAI,EAAgB,MAAM,IAAI;AAE3D,QAAO"}
@@ -15,6 +15,9 @@ declare function findFilterColumns(collection: AnchoredColumnCollection): Column
15
15
  /**
16
16
  * Derive labeled options from filter column matches, for use in DatasetOption.filters.
17
17
  *
18
+ * Entries whose column id has no PlRef in `refsByObjectId` are silently
19
+ * skipped — they cannot be exposed as user-selectable options.
20
+ *
18
21
  * @param matches - from findFilterColumns()
19
22
  * @param refsByObjectId - from {@link buildRefMap}
20
23
  * @param labelOptions - forwarded to deriveDistinctLabels()
@@ -1 +1 @@
1
- {"version":3,"file":"filter_discovery.d.ts","names":[],"sources":["../../../src/components/PlDatasetSelector/filter_discovery.ts"],"mappings":";;;;;;;AAqBA;;;;;;iBAAgB,iBAAA,CAAkB,UAAA,EAAY,wBAAA,GAA2B,WAAA;;;AAgBzE;;;;;iBAAgB,sBAAA,CACd,OAAA,EAAS,WAAA,IACT,cAAA,EAAgB,WAAA,CAAY,SAAA,EAAW,KAAA,GACvC,YAAA,GAAe,mBAAA,GACd,MAAA;;;;iBA6Ba,WAAA,CAAY,OAAA;EAAA,SAA6B,GAAA,EAAK,KAAA;AAAA,MAAY,GAAA,CAAI,SAAA,EAAW,KAAA"}
1
+ {"version":3,"file":"filter_discovery.d.ts","names":[],"sources":["../../../src/components/PlDatasetSelector/filter_discovery.ts"],"mappings":";;;;;;;AAqBA;;;;;;iBAAgB,iBAAA,CAAkB,UAAA,EAAY,wBAAA,GAA2B,WAAA;;;AAmBzE;;;;;;;;iBAAgB,sBAAA,CACd,OAAA,EAAS,WAAA,IACT,cAAA,EAAgB,WAAA,CAAY,SAAA,EAAW,KAAA,GACvC,YAAA,GAAe,mBAAA,GACd,MAAA;;;;iBA2Ba,WAAA,CAAY,OAAA;EAAA,SAA6B,GAAA,EAAK,KAAA;AAAA,MAAY,GAAA,CAAI,SAAA,EAAW,KAAA"}
@@ -19,28 +19,32 @@ function findFilterColumns(collection) {
19
19
  /**
20
20
  * Derive labeled options from filter column matches, for use in DatasetOption.filters.
21
21
  *
22
+ * Entries whose column id has no PlRef in `refsByObjectId` are silently
23
+ * skipped — they cannot be exposed as user-selectable options.
24
+ *
22
25
  * @param matches - from findFilterColumns()
23
26
  * @param refsByObjectId - from {@link buildRefMap}
24
27
  * @param labelOptions - forwarded to deriveDistinctLabels()
25
28
  */
26
29
  function filterMatchesToOptions(matches, refsByObjectId, labelOptions) {
27
30
  if (matches.length === 0) return [];
28
- const flattened = matches.flatMap((m) => m.variants.map((v) => ({
29
- match: m,
30
- variant: v
31
- })));
31
+ const flattened = matches.flatMap((match) => {
32
+ const ref = refsByObjectId.get(match.column.id);
33
+ if (ref === void 0) return [];
34
+ return match.variants.map((variant) => ({
35
+ match,
36
+ variant,
37
+ ref
38
+ }));
39
+ });
32
40
  const labels = deriveDistinctLabels(flattened.map(({ match, variant }) => ({
33
41
  spec: match.column.spec,
34
42
  linkerPath: variant.path.map((p) => ({ spec: p.linker.spec }))
35
43
  })), labelOptions);
36
- return flattened.map(({ match }, i) => {
37
- const ref = refsByObjectId.get(match.column.id);
38
- if (ref === void 0) throw new Error(`no PlRef found for filter column ${match.column.spec.name} (id: ${match.column.id})`);
39
- return {
40
- ref,
41
- label: labels[i]
42
- };
43
- });
44
+ return flattened.map(({ ref }, i) => ({
45
+ ref,
46
+ label: labels[i]
47
+ }));
44
48
  }
45
49
  /**
46
50
  * Usage: `buildRefMap(ctx.resultPool.getSpecs().entries)`
@@ -1 +1 @@
1
- {"version":3,"file":"filter_discovery.js","names":[],"sources":["../../../src/components/PlDatasetSelector/filter_discovery.ts"],"sourcesContent":["import { Annotation } from \"@milaboratories/pl-model-common\";\nimport type { Option, PlRef, PObjectId } from \"@milaboratories/pl-model-common\";\nimport canonicalize from \"canonicalize\";\nimport type {\n AnchoredColumnCollection,\n ColumnMatch,\n} from \"../../columns/column_collection_builder\";\nimport {\n deriveDistinctLabels,\n type DeriveLabelsOptions,\n type Entry,\n} from \"../../labels/derive_distinct_labels\";\n\n/**\n * Matches columns annotated `pl7.app/isSubset: \"true\"` whose axes ⊆ anchor axes.\n *\n * The axes-subset constraint is enforced by `mode: \"enrichment\"`, which sets\n * `allowFloatingHitAxes: false` — every axis of the matched column must be\n * present in the anchor's axes. See `matchingModeToConstraints()` in\n * `column_collection_builder.ts`.\n */\nexport function findFilterColumns(collection: AnchoredColumnCollection): ColumnMatch[] {\n return collection.findColumns({\n mode: \"enrichment\",\n include: {\n annotations: { [Annotation.IsSubset]: \"true\" },\n },\n });\n}\n\n/**\n * Derive labeled options from filter column matches, for use in DatasetOption.filters.\n *\n * @param matches - from findFilterColumns()\n * @param refsByObjectId - from {@link buildRefMap}\n * @param labelOptions - forwarded to deriveDistinctLabels()\n */\nexport function filterMatchesToOptions(\n matches: ColumnMatch[],\n refsByObjectId: ReadonlyMap<PObjectId, PlRef>,\n labelOptions?: DeriveLabelsOptions,\n): Option[] {\n if (matches.length === 0) return [];\n\n // Each ColumnMatch can be reached via multiple variants (different linker\n // paths / qualifications). We emit one Option per variant so the user can\n // pick a specific path — `deriveDistinctLabels` disambiguates labels by\n // path.\n const flattened = matches.flatMap((m) => m.variants.map((v) => ({ match: m, variant: v })));\n\n const entries: Entry[] = flattened.map(({ match, variant }) => ({\n spec: match.column.spec,\n linkerPath: variant.path.map((p) => ({ spec: p.linker.spec })),\n }));\n\n const labels = deriveDistinctLabels(entries, labelOptions);\n\n return flattened.map(({ match }, i) => {\n const ref = refsByObjectId.get(match.column.id);\n if (ref === undefined)\n throw new Error(\n `no PlRef found for filter column ${match.column.spec.name} (id: ${match.column.id})`,\n );\n return { ref, label: labels[i] };\n });\n}\n\n/**\n * Usage: `buildRefMap(ctx.resultPool.getSpecs().entries)`\n */\nexport function buildRefMap(entries: readonly { readonly ref: PlRef }[]): Map<PObjectId, PlRef> {\n const map = new Map<PObjectId, PlRef>();\n for (const entry of entries) {\n map.set(canonicalize(entry.ref)! as PObjectId, entry.ref);\n }\n return map;\n}\n"],"mappings":";;;;;;;;;;;;AAqBA,SAAgB,kBAAkB,YAAqD;AACrF,QAAO,WAAW,YAAY;EAC5B,MAAM;EACN,SAAS,EACP,aAAa,GAAG,WAAW,WAAW,QAAQ,EAC/C;EACF,CAAC;;;;;;;;;AAUJ,SAAgB,uBACd,SACA,gBACA,cACU;AACV,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAMnC,MAAM,YAAY,QAAQ,SAAS,MAAM,EAAE,SAAS,KAAK,OAAO;EAAE,OAAO;EAAG,SAAS;EAAG,EAAE,CAAC;CAO3F,MAAM,SAAS,qBALU,UAAU,KAAK,EAAE,OAAO,eAAe;EAC9D,MAAM,MAAM,OAAO;EACnB,YAAY,QAAQ,KAAK,KAAK,OAAO,EAAE,MAAM,EAAE,OAAO,MAAM,EAAE;EAC/D,EAAE,EAE0C,aAAa;AAE1D,QAAO,UAAU,KAAK,EAAE,SAAS,MAAM;EACrC,MAAM,MAAM,eAAe,IAAI,MAAM,OAAO,GAAG;AAC/C,MAAI,QAAQ,KAAA,EACV,OAAM,IAAI,MACR,oCAAoC,MAAM,OAAO,KAAK,KAAK,QAAQ,MAAM,OAAO,GAAG,GACpF;AACH,SAAO;GAAE;GAAK,OAAO,OAAO;GAAI;GAChC;;;;;AAMJ,SAAgB,YAAY,SAAoE;CAC9F,MAAM,sBAAM,IAAI,KAAuB;AACvC,MAAK,MAAM,SAAS,QAClB,KAAI,IAAI,aAAa,MAAM,IAAI,EAAgB,MAAM,IAAI;AAE3D,QAAO"}
1
+ {"version":3,"file":"filter_discovery.js","names":[],"sources":["../../../src/components/PlDatasetSelector/filter_discovery.ts"],"sourcesContent":["import { Annotation } from \"@milaboratories/pl-model-common\";\nimport type { Option, PlRef, PObjectId } from \"@milaboratories/pl-model-common\";\nimport canonicalize from \"canonicalize\";\nimport type {\n AnchoredColumnCollection,\n ColumnMatch,\n} from \"../../columns/column_collection_builder\";\nimport {\n deriveDistinctLabels,\n type DeriveLabelsOptions,\n type Entry,\n} from \"../../labels/derive_distinct_labels\";\n\n/**\n * Matches columns annotated `pl7.app/isSubset: \"true\"` whose axes ⊆ anchor axes.\n *\n * The axes-subset constraint is enforced by `mode: \"enrichment\"`, which sets\n * `allowFloatingHitAxes: false` — every axis of the matched column must be\n * present in the anchor's axes. See `matchingModeToConstraints()` in\n * `column_collection_builder.ts`.\n */\nexport function findFilterColumns(collection: AnchoredColumnCollection): ColumnMatch[] {\n return collection.findColumns({\n mode: \"enrichment\",\n include: {\n annotations: { [Annotation.IsSubset]: \"true\" },\n },\n });\n}\n\n/**\n * Derive labeled options from filter column matches, for use in DatasetOption.filters.\n *\n * Entries whose column id has no PlRef in `refsByObjectId` are silently\n * skipped — they cannot be exposed as user-selectable options.\n *\n * @param matches - from findFilterColumns()\n * @param refsByObjectId - from {@link buildRefMap}\n * @param labelOptions - forwarded to deriveDistinctLabels()\n */\nexport function filterMatchesToOptions(\n matches: ColumnMatch[],\n refsByObjectId: ReadonlyMap<PObjectId, PlRef>,\n labelOptions?: DeriveLabelsOptions,\n): Option[] {\n if (matches.length === 0) return [];\n\n // Each ColumnMatch can be reached via multiple variants (different linker\n // paths / qualifications). We emit one Option per variant so the user can\n // pick a specific path — `deriveDistinctLabels` disambiguates labels by\n // path. All variants of a match share a column id, so the ref lookup\n // happens once per match.\n const flattened = matches.flatMap((match) => {\n const ref = refsByObjectId.get(match.column.id);\n if (ref === undefined) return [];\n return match.variants.map((variant) => ({ match, variant, ref }));\n });\n\n const entries: Entry[] = flattened.map(({ match, variant }) => ({\n spec: match.column.spec,\n linkerPath: variant.path.map((p) => ({ spec: p.linker.spec })),\n }));\n\n const labels = deriveDistinctLabels(entries, labelOptions);\n\n return flattened.map(({ ref }, i) => ({ ref, label: labels[i] }));\n}\n\n/**\n * Usage: `buildRefMap(ctx.resultPool.getSpecs().entries)`\n */\nexport function buildRefMap(entries: readonly { readonly ref: PlRef }[]): Map<PObjectId, PlRef> {\n const map = new Map<PObjectId, PlRef>();\n for (const entry of entries) {\n map.set(canonicalize(entry.ref)! as PObjectId, entry.ref);\n }\n return map;\n}\n"],"mappings":";;;;;;;;;;;;AAqBA,SAAgB,kBAAkB,YAAqD;AACrF,QAAO,WAAW,YAAY;EAC5B,MAAM;EACN,SAAS,EACP,aAAa,GAAG,WAAW,WAAW,QAAQ,EAC/C;EACF,CAAC;;;;;;;;;;;;AAaJ,SAAgB,uBACd,SACA,gBACA,cACU;AACV,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAOnC,MAAM,YAAY,QAAQ,SAAS,UAAU;EAC3C,MAAM,MAAM,eAAe,IAAI,MAAM,OAAO,GAAG;AAC/C,MAAI,QAAQ,KAAA,EAAW,QAAO,EAAE;AAChC,SAAO,MAAM,SAAS,KAAK,aAAa;GAAE;GAAO;GAAS;GAAK,EAAE;GACjE;CAOF,MAAM,SAAS,qBALU,UAAU,KAAK,EAAE,OAAO,eAAe;EAC9D,MAAM,MAAM,OAAO;EACnB,YAAY,QAAQ,KAAK,KAAK,OAAO,EAAE,MAAM,EAAE,OAAO,MAAM,EAAE;EAC/D,EAAE,EAE0C,aAAa;AAE1D,QAAO,UAAU,KAAK,EAAE,OAAO,OAAO;EAAE;EAAK,OAAO,OAAO;EAAI,EAAE;;;;;AAMnE,SAAgB,YAAY,SAAoE;CAC9F,MAAM,sBAAM,IAAI,KAAuB;AACvC,MAAK,MAAM,SAAS,QAClB,KAAI,IAAI,aAAa,MAAM,IAAI,EAAgB,MAAM,IAAI;AAE3D,QAAO"}
package/dist/package.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.72.0";
2
+ var version = "1.73.0";
3
3
  //#endregion
4
4
  Object.defineProperty(exports, "version", {
5
5
  enumerable: true,
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.72.0";
2
+ var version = "1.73.0";
3
3
  //#endregion
4
4
  export { version };
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/model",
3
- "version": "1.72.0",
3
+ "version": "1.73.0",
4
4
  "description": "Platforma.bio SDK / Block Model",
5
5
  "files": [
6
6
  "./dist/**/*",
@@ -31,8 +31,8 @@
31
31
  "utility-types": "^3.11.0",
32
32
  "zod": "~3.25.76",
33
33
  "@milaboratories/helpers": "1.14.1",
34
- "@milaboratories/pl-model-common": "1.39.0",
35
34
  "@milaboratories/pl-error-like": "1.12.10",
35
+ "@milaboratories/pl-model-common": "1.39.0",
36
36
  "@milaboratories/ptabler-expression-js": "1.2.20",
37
37
  "@milaboratories/pl-model-middle-layer": "1.18.10"
38
38
  },
@@ -41,8 +41,8 @@
41
41
  "fast-json-patch": "^3.1.1",
42
42
  "typescript": "~5.9.3",
43
43
  "vitest": "^4.1.3",
44
- "@milaboratories/pf-driver": "1.4.5",
45
44
  "@milaboratories/build-configs": "2.0.0",
45
+ "@milaboratories/pf-driver": "1.4.5",
46
46
  "@milaboratories/ts-configs": "1.2.3",
47
47
  "@milaboratories/pf-spec-driver": "1.3.9",
48
48
  "@milaboratories/ts-builder": "1.3.2"
@@ -2,22 +2,42 @@ import type { MultiColumnSelector, Option, PObjectSpec } from "@milaboratories/p
2
2
  import { multiColumnSelectorsToPredicate } from "@milaboratories/pl-model-common";
3
3
  import type { DeriveLabelsOptions } from "../../labels/derive_distinct_labels";
4
4
  import type { RenderCtxBase } from "../../render";
5
+ import type { AnchoredColumnCollection } from "../../columns/column_collection_builder";
5
6
  import { ColumnCollectionBuilder } from "../../columns/column_collection_builder";
6
- import { collectCtxColumnSnapshotProviders } from "../../columns/ctx_column_sources";
7
+ import {
8
+ ResultPoolColumnSnapshotProvider,
9
+ collectCtxColumnSnapshotProviders,
10
+ } from "../../columns/ctx_column_sources";
7
11
  import type { DatasetOption } from "./dataset_selection";
8
12
  import { buildRefMap, filterMatchesToOptions, findFilterColumns } from "./filter_discovery";
9
13
  import { enrichmentVariantsToRefs, findEnrichmentColumns } from "./enrichment_discovery";
10
14
 
15
+ type SpecPredicateOption =
16
+ | MultiColumnSelector
17
+ | MultiColumnSelector[]
18
+ | ((spec: PObjectSpec) => boolean);
19
+
20
+ function toPredicate(opt: SpecPredicateOption | undefined): (spec: PObjectSpec) => boolean {
21
+ if (opt === undefined) return () => true;
22
+ return typeof opt === "function" ? opt : multiColumnSelectorsToPredicate(opt);
23
+ }
24
+
11
25
  export type BuildDatasetOptions = {
12
26
  /** Which result pool columns qualify as datasets. Defaults to all. */
13
- primary?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);
27
+ primary?: SpecPredicateOption;
28
+ /**
29
+ * Restricts which result pool columns are considered as filters. Intersected
30
+ * with the built-in `pl7.app/isSubset: "true"` constraint. Defaults to
31
+ * accept-all.
32
+ */
33
+ filter?: SpecPredicateOption;
14
34
  /** Formatting options for filter labels. */
15
35
  labelOptions?: DeriveLabelsOptions;
16
36
  /**
17
37
  * Enables enrichment discovery and filters hits attached to
18
38
  * `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.
19
39
  */
20
- withEnrichments?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);
40
+ withEnrichments?: SpecPredicateOption;
21
41
  /** Maximum linker hops considered. Only used when `withEnrichments` is set. */
22
42
  enrichmentMaxHops?: number;
23
43
  };
@@ -32,46 +52,62 @@ export function buildDatasetOptions(
32
52
  ctx: RenderCtxBase,
33
53
  opts?: BuildDatasetOptions,
34
54
  ): DatasetOption[] | undefined {
35
- const primary = opts?.primary;
36
- const primaryPredicate =
37
- primary === undefined
38
- ? () => true
39
- : typeof primary === "function"
40
- ? primary
41
- : multiColumnSelectorsToPredicate(primary);
55
+ const primaryPredicate = toPredicate(opts?.primary);
56
+ const filterPredicate = toPredicate(opts?.filter);
57
+
42
58
  const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });
43
59
  if (options.length === 0) return [];
44
60
 
45
- const columnSources = collectCtxColumnSnapshotProviders(ctx);
46
61
  const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);
47
62
  const pframeSpec = ctx.getService("pframeSpec");
48
63
 
64
+ const withEnrichments = opts?.withEnrichments ?? false;
65
+ const filterSource = new ResultPoolColumnSnapshotProvider(ctx.resultPool);
66
+ // Hoisted out of the per-option loop: collectCtxColumnSnapshotProviders
67
+ // walks the entire output tree, so calling it once per dataset option would
68
+ // be O(N × tree).
69
+ const enrichmentSources = withEnrichments ? collectCtxColumnSnapshotProviders(ctx) : undefined;
70
+
49
71
  return options.map((primary: Option): DatasetOption => {
50
72
  const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);
51
73
  if (!datasetSpec) return { primary };
52
74
 
53
- const builder = new ColumnCollectionBuilder(pframeSpec);
54
- for (const src of columnSources) builder.addSource(src);
55
- const collection = builder.build({ anchors: { main: datasetSpec } });
56
- if (!collection) return { primary };
57
-
75
+ // Allocations happen inside try so a throw on the second build()
76
+ // still disposes the first collection.
77
+ let filterCollection: AnchoredColumnCollection | undefined;
78
+ let enrichmentCollection: AnchoredColumnCollection | undefined;
58
79
  try {
59
- const filterMatches = findFilterColumns(collection);
80
+ // ResultPoolColumnSnapshotProvider is always complete;
81
+ // allowPartialColumnList narrows the return type to non-undefined.
82
+ filterCollection = new ColumnCollectionBuilder(pframeSpec)
83
+ .addSource(filterSource)
84
+ .build({ anchors: { main: datasetSpec }, allowPartialColumnList: true });
85
+
86
+ enrichmentCollection =
87
+ enrichmentSources !== undefined
88
+ ? new ColumnCollectionBuilder(pframeSpec)
89
+ .addSources(enrichmentSources)
90
+ .build({ anchors: { main: datasetSpec } })
91
+ : undefined;
92
+
93
+ const filterMatches = findFilterColumns(filterCollection).filter((m) =>
94
+ filterPredicate(m.column.spec),
95
+ );
60
96
  const filters =
61
97
  filterMatches.length === 0
62
98
  ? undefined
63
99
  : filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);
64
100
 
65
101
  let enrichments;
66
- if (opts?.withEnrichments !== undefined) {
67
- const enrichmentVariants = findEnrichmentColumns(collection, {
68
- maxHops: opts.enrichmentMaxHops,
69
- ...(typeof opts.withEnrichments === "function"
70
- ? { predicate: opts.withEnrichments }
71
- : { include: opts.withEnrichments }),
102
+ if (enrichmentCollection && withEnrichments) {
103
+ const enrichmentVariants = findEnrichmentColumns(enrichmentCollection, {
104
+ maxHops: opts?.enrichmentMaxHops,
105
+ ...(typeof withEnrichments === "function"
106
+ ? { predicate: withEnrichments }
107
+ : { include: withEnrichments }),
72
108
  });
73
109
  if (enrichmentVariants.length > 0) {
74
- enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts.labelOptions);
110
+ enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts?.labelOptions);
75
111
  }
76
112
  }
77
113
 
@@ -81,7 +117,8 @@ export function buildDatasetOptions(
81
117
  ...(enrichments !== undefined && enrichments.length > 0 ? { enrichments } : {}),
82
118
  };
83
119
  } finally {
84
- collection.dispose();
120
+ filterCollection?.dispose();
121
+ enrichmentCollection?.dispose();
85
122
  }
86
123
  });
87
124
  }
@@ -140,17 +140,23 @@ describe("filterMatchesToOptions", () => {
140
140
  expect(filterMatchesToOptions([], new Map())).toEqual([]);
141
141
  });
142
142
 
143
- test("throws when ref not found in map", () => {
144
- const filterSpec1 = spec("orphan", [axis("sample")], { [Annotation.IsSubset]: "true" });
145
- const f1Snap = snap("orphan-id", filterSpec1);
143
+ test("skips entries whose ref is not found in map", () => {
144
+ const knownRef = createPlRef("b1", "known");
145
+ const knownSpec = spec("known", [axis("sample")], { [Annotation.IsSubset]: "true" });
146
+ const orphanSpec = spec("orphan", [axis("sample")], { [Annotation.IsSubset]: "true" });
147
+ const knownSnap = snap(canonicalize(knownRef)! as string, knownSpec);
148
+ const orphanSnap = snap("orphan-id", orphanSpec);
146
149
 
147
150
  const builder = new ColumnCollectionBuilder(createSpecFrameCtx());
148
- builder.addSource([f1Snap, anchorSnap]);
151
+ builder.addSource([knownSnap, orphanSnap, anchorSnap]);
149
152
  const collection = builder.build({ anchors: { main: anchorSpec } })!;
150
153
 
151
154
  const matches = findFilterColumns(collection);
152
- expect(matches.length).toBe(1);
155
+ expect(matches.length).toBe(2);
153
156
 
154
- expect(() => filterMatchesToOptions(matches, new Map())).toThrow(/no PlRef found/);
157
+ const refMap = buildRefMap([{ ref: knownRef }]);
158
+ const options = filterMatchesToOptions(matches, refMap);
159
+ expect(options).toHaveLength(1);
160
+ expect(options[0].ref).toBe(knownRef);
155
161
  });
156
162
  });
@@ -31,6 +31,9 @@ export function findFilterColumns(collection: AnchoredColumnCollection): ColumnM
31
31
  /**
32
32
  * Derive labeled options from filter column matches, for use in DatasetOption.filters.
33
33
  *
34
+ * Entries whose column id has no PlRef in `refsByObjectId` are silently
35
+ * skipped — they cannot be exposed as user-selectable options.
36
+ *
34
37
  * @param matches - from findFilterColumns()
35
38
  * @param refsByObjectId - from {@link buildRefMap}
36
39
  * @param labelOptions - forwarded to deriveDistinctLabels()
@@ -45,8 +48,13 @@ export function filterMatchesToOptions(
45
48
  // Each ColumnMatch can be reached via multiple variants (different linker
46
49
  // paths / qualifications). We emit one Option per variant so the user can
47
50
  // pick a specific path — `deriveDistinctLabels` disambiguates labels by
48
- // path.
49
- const flattened = matches.flatMap((m) => m.variants.map((v) => ({ match: m, variant: v })));
51
+ // path. All variants of a match share a column id, so the ref lookup
52
+ // happens once per match.
53
+ const flattened = matches.flatMap((match) => {
54
+ const ref = refsByObjectId.get(match.column.id);
55
+ if (ref === undefined) return [];
56
+ return match.variants.map((variant) => ({ match, variant, ref }));
57
+ });
50
58
 
51
59
  const entries: Entry[] = flattened.map(({ match, variant }) => ({
52
60
  spec: match.column.spec,
@@ -55,14 +63,7 @@ export function filterMatchesToOptions(
55
63
 
56
64
  const labels = deriveDistinctLabels(entries, labelOptions);
57
65
 
58
- return flattened.map(({ match }, i) => {
59
- const ref = refsByObjectId.get(match.column.id);
60
- if (ref === undefined)
61
- throw new Error(
62
- `no PlRef found for filter column ${match.column.spec.name} (id: ${match.column.id})`,
63
- );
64
- return { ref, label: labels[i] };
65
- });
66
+ return flattened.map(({ ref }, i) => ({ ref, label: labels[i] }));
66
67
  }
67
68
 
68
69
  /**