@platforma-sdk/model 1.54.9 → 1.54.10

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 (47) hide show
  1. package/dist/components/PlDataTable/table.cjs +17 -8
  2. package/dist/components/PlDataTable/table.cjs.map +1 -1
  3. package/dist/components/PlDataTable/table.d.ts.map +1 -1
  4. package/dist/components/PlDataTable/table.js +17 -8
  5. package/dist/components/PlDataTable/table.js.map +1 -1
  6. package/dist/components/PlDataTable/v5.d.ts +2 -2
  7. package/dist/components/PlDataTable/v5.d.ts.map +1 -1
  8. package/dist/filters/converters/filterToQuery.cjs +18 -11
  9. package/dist/filters/converters/filterToQuery.cjs.map +1 -1
  10. package/dist/filters/converters/filterToQuery.d.ts.map +1 -1
  11. package/dist/filters/converters/filterToQuery.js +18 -11
  12. package/dist/filters/converters/filterToQuery.js.map +1 -1
  13. package/dist/filters/converters/filterUiToExpressionImpl.cjs +80 -100
  14. package/dist/filters/converters/filterUiToExpressionImpl.cjs.map +1 -1
  15. package/dist/filters/converters/filterUiToExpressionImpl.d.ts.map +1 -1
  16. package/dist/filters/converters/filterUiToExpressionImpl.js +81 -101
  17. package/dist/filters/converters/filterUiToExpressionImpl.js.map +1 -1
  18. package/dist/filters/distill.cjs +18 -18
  19. package/dist/filters/distill.cjs.map +1 -1
  20. package/dist/filters/distill.d.ts.map +1 -1
  21. package/dist/filters/distill.js +18 -18
  22. package/dist/filters/distill.js.map +1 -1
  23. package/dist/filters/traverse.cjs +49 -0
  24. package/dist/filters/traverse.cjs.map +1 -0
  25. package/dist/filters/traverse.d.ts +25 -0
  26. package/dist/filters/traverse.d.ts.map +1 -0
  27. package/dist/filters/traverse.js +46 -0
  28. package/dist/filters/traverse.js.map +1 -0
  29. package/dist/package.json.cjs +1 -1
  30. package/dist/package.json.js +1 -1
  31. package/dist/pframe_utils/querySpec.d.ts +1 -1
  32. package/dist/pframe_utils/querySpec.d.ts.map +1 -1
  33. package/dist/render/api.cjs +1 -1
  34. package/dist/render/api.cjs.map +1 -1
  35. package/dist/render/api.d.ts.map +1 -1
  36. package/dist/render/api.js +2 -2
  37. package/dist/render/api.js.map +1 -1
  38. package/package.json +5 -5
  39. package/src/components/PlDataTable/table.ts +28 -10
  40. package/src/components/PlDataTable/v5.ts +1 -2
  41. package/src/filters/converters/filterToQuery.ts +18 -11
  42. package/src/filters/converters/filterUiToExpressionImpl.ts +81 -125
  43. package/src/filters/distill.ts +16 -20
  44. package/src/filters/traverse.test.ts +134 -0
  45. package/src/filters/traverse.ts +64 -0
  46. package/src/pframe_utils/querySpec.ts +1 -1
  47. package/src/render/api.ts +2 -2
@@ -0,0 +1,134 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { FilterSpec, FilterSpecLeaf } from "@milaboratories/pl-model-common";
3
+ import { traverseFilterSpec, collectFilterSpecColumns } from "./traverse";
4
+
5
+ type F = FilterSpec<FilterSpecLeaf<string>>;
6
+
7
+ const eq1: F = { type: "equal", column: "c1", x: 1 };
8
+ const eq2: F = { type: "equal", column: "c2", x: 2 };
9
+ const gt: F = { type: "greaterThan", column: "c3", x: 10 };
10
+ const empty: F = { type: undefined };
11
+
12
+ /** Visitor that collects leaf types into a string expression. */
13
+ const toStringVisitor = {
14
+ leaf: (l: FilterSpecLeaf<unknown>) => l.type ?? "empty",
15
+ and: (r: string[]) => `(${r.join(" & ")})`,
16
+ or: (r: string[]) => `(${r.join(" | ")})`,
17
+ not: (r: string) => `!${r}`,
18
+ };
19
+
20
+ describe("traverseFilterSpec", () => {
21
+ it("visits a leaf node", () => {
22
+ expect(traverseFilterSpec(eq1, toStringVisitor)).toBe("equal");
23
+ });
24
+
25
+ it("visits an and node", () => {
26
+ const filter: F = { type: "and", filters: [eq1, gt] };
27
+ expect(traverseFilterSpec(filter, toStringVisitor)).toBe("(equal & greaterThan)");
28
+ });
29
+
30
+ it("visits an or node", () => {
31
+ const filter: F = { type: "or", filters: [eq1, eq2] };
32
+ expect(traverseFilterSpec(filter, toStringVisitor)).toBe("(equal | equal)");
33
+ });
34
+
35
+ it("visits a not node", () => {
36
+ const filter: F = { type: "not", filter: eq1 };
37
+ expect(traverseFilterSpec(filter, toStringVisitor)).toBe("!equal");
38
+ });
39
+
40
+ it("handles nested structure", () => {
41
+ const filter: F = {
42
+ type: "and",
43
+ filters: [
44
+ { type: "not", filter: eq1 },
45
+ { type: "or", filters: [eq2, gt] },
46
+ ],
47
+ };
48
+ expect(traverseFilterSpec(filter, toStringVisitor)).toBe("(!equal & (equal | greaterThan))");
49
+ });
50
+
51
+ it("skips { type: undefined } entries in and", () => {
52
+ const filter: F = { type: "and", filters: [empty, eq1, empty] };
53
+ expect(traverseFilterSpec(filter, toStringVisitor)).toBe("(equal)");
54
+ });
55
+
56
+ it("skips { type: undefined } entries in or", () => {
57
+ const filter: F = { type: "or", filters: [empty, eq1, empty, eq2] };
58
+ expect(traverseFilterSpec(filter, toStringVisitor)).toBe("(equal | equal)");
59
+ });
60
+
61
+ it("returns empty and when all children are undefined", () => {
62
+ const filter: F = { type: "and", filters: [empty, empty] };
63
+ expect(traverseFilterSpec(filter, toStringVisitor)).toBe("()");
64
+ });
65
+
66
+ it("returns empty or when all children are undefined", () => {
67
+ const filter: F = { type: "or", filters: [empty] };
68
+ expect(traverseFilterSpec(filter, toStringVisitor)).toBe("()");
69
+ });
70
+
71
+ it("works with CommonNode/CommonLeaf metadata", () => {
72
+ type MetaLeaf = FilterSpecLeaf<string>;
73
+ type MetaFilter = FilterSpec<MetaLeaf, { _id: number }, { _id: number }>;
74
+
75
+ const filter: MetaFilter = {
76
+ type: "and",
77
+ _id: 1,
78
+ filters: [
79
+ { type: "equal", column: "c1", x: 1, _id: 2 },
80
+ { type: "greaterThan", column: "c2", x: 5, _id: 3 },
81
+ ],
82
+ };
83
+
84
+ const result = traverseFilterSpec(filter, {
85
+ leaf: (l) => [l._id],
86
+ and: (r) => r.flat(),
87
+ or: (r) => r.flat(),
88
+ not: (r) => r,
89
+ });
90
+
91
+ expect(result).toEqual([2, 3]);
92
+ });
93
+ });
94
+
95
+ describe("collectFilterSpecColumns", () => {
96
+ it("collects column from a single leaf", () => {
97
+ expect(collectFilterSpecColumns(eq1)).toEqual(["c1"]);
98
+ });
99
+
100
+ it("collects columns from and", () => {
101
+ const filter: F = { type: "and", filters: [eq1, eq2, gt] };
102
+ expect(collectFilterSpecColumns(filter)).toEqual(["c1", "c2", "c3"]);
103
+ });
104
+
105
+ it("collects columns from nested structure", () => {
106
+ const filter: F = {
107
+ type: "or",
108
+ filters: [
109
+ { type: "not", filter: eq1 },
110
+ { type: "and", filters: [eq2, gt] },
111
+ ],
112
+ };
113
+ expect(collectFilterSpecColumns(filter)).toEqual(["c1", "c2", "c3"]);
114
+ });
115
+
116
+ it("collects rhs fields", () => {
117
+ const filter: F = { type: "greaterThanColumn", column: "c1", rhs: "c2" };
118
+ expect(collectFilterSpecColumns(filter)).toEqual(["c1", "c2"]);
119
+ });
120
+
121
+ it("skips { type: undefined } entries", () => {
122
+ const filter: F = { type: "and", filters: [empty, eq1, empty] };
123
+ expect(collectFilterSpecColumns(filter)).toEqual(["c1"]);
124
+ });
125
+
126
+ it("returns empty array for all-empty and", () => {
127
+ const filter: F = { type: "and", filters: [empty, empty] };
128
+ expect(collectFilterSpecColumns(filter)).toEqual([]);
129
+ });
130
+
131
+ it("returns empty array for empty leaf", () => {
132
+ expect(collectFilterSpecColumns(empty)).toEqual([]);
133
+ });
134
+ });
@@ -0,0 +1,64 @@
1
+ import type { FilterSpecLeaf, FilterSpecNode } from "@milaboratories/pl-model-common";
2
+
3
+ /**
4
+ * Recursively traverses a FilterSpec tree bottom-up, applying visitor callbacks.
5
+ *
6
+ * Entries with `{ type: undefined }` inside `and`/`or` arrays are skipped
7
+ * (these represent unfilled filter slots in the UI).
8
+ *
9
+ * Traversal order:
10
+ * 1. Recurse into child filters (`and`/`or`/`not`)
11
+ * 2. Apply the corresponding visitor callback with already-traversed children
12
+ * 3. For leaf nodes, call `leaf` directly
13
+ */
14
+ export function traverseFilterSpec<Leaf extends FilterSpecLeaf<unknown>, CommonNode, CommonLeaf, R>(
15
+ filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf>,
16
+ visitor: {
17
+ /** Handle a leaf filter node. */
18
+ leaf: (leaf: CommonLeaf & Leaf) => R;
19
+ /** Handle an AND node after children have been traversed. */
20
+ and: (results: R[]) => R;
21
+ /** Handle an OR node after children have been traversed. */
22
+ or: (results: R[]) => R;
23
+ /** Handle a NOT node after the inner filter has been traversed. */
24
+ not: (result: R) => R;
25
+ },
26
+ ): R {
27
+ switch (filter.type) {
28
+ case "and":
29
+ return visitor.and(
30
+ filter.filters
31
+ .filter((f) => f.type !== undefined)
32
+ .map((f) => traverseFilterSpec(f, visitor)),
33
+ );
34
+ case "or":
35
+ return visitor.or(
36
+ filter.filters
37
+ .filter((f) => f.type !== undefined)
38
+ .map((f) => traverseFilterSpec(f, visitor)),
39
+ );
40
+ case "not":
41
+ return visitor.not(traverseFilterSpec(filter.filter, visitor));
42
+ default:
43
+ return visitor.leaf(filter as CommonLeaf & Leaf);
44
+ }
45
+ }
46
+
47
+ /** Collects all column references (`column` and `rhs` fields) from filter leaves. */
48
+ export function collectFilterSpecColumns<
49
+ Leaf extends FilterSpecLeaf<unknown>,
50
+ CommonNode,
51
+ CommonLeaf,
52
+ >(filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf>): string[] {
53
+ return traverseFilterSpec(filter, {
54
+ leaf: (leaf) => {
55
+ const cols: string[] = [];
56
+ if ("column" in leaf && leaf.column !== undefined) cols.push(leaf.column as string);
57
+ if ("rhs" in leaf && leaf.rhs !== undefined) cols.push(leaf.rhs as string);
58
+ return cols;
59
+ },
60
+ and: (results) => results.flat(),
61
+ or: (results) => results.flat(),
62
+ not: (result) => result,
63
+ });
64
+ }
@@ -1 +1 @@
1
- export { collectQueryColumns, isBooleanExpression } from "@milaboratories/pl-model-common";
1
+ export { collectSpecQueryColumns, isBooleanExpression } from "@milaboratories/pl-model-common";
package/src/render/api.ts CHANGED
@@ -28,6 +28,7 @@ import type {
28
28
  } from "@milaboratories/pl-model-common";
29
29
  import {
30
30
  AnchoredIdDeriver,
31
+ collectSpecQueryColumns,
31
32
  ensurePColumn,
32
33
  extractAllColumns,
33
34
  isDataInfo,
@@ -62,7 +63,6 @@ import type { APColumnSelectorWithSplit } from "./util/split_selectors";
62
63
  import { patchInSetFilters } from "./util/pframe_upgraders";
63
64
  import { allPColumnsReady } from "./util/pcolumn_data";
64
65
  import type { PColumnDataUniversal } from "./internal";
65
- import { collectQueryColumns } from "@milaboratories/pl-model-common";
66
66
 
67
67
  /**
68
68
  * Helper function to match domain objects
@@ -682,7 +682,7 @@ export abstract class RenderCtxBase<Args, Data> {
682
682
  }
683
683
 
684
684
  public createPTableV2(def: PTableDefV2<PColumn<PColumnDataUniversal>>): PTableHandle | undefined {
685
- const columns = collectQueryColumns(def.query);
685
+ const columns = collectSpecQueryColumns(def.query);
686
686
  this.verifyInlineAndExplicitColumnsSupport(columns);
687
687
  if (!allPColumnsReady(columns)) return undefined;
688
688
  return this.ctx.createPTableV2(mapPTableDefV2(def, (po) => transformPColumnData(po)));