@platforma-sdk/model 1.53.15 → 1.54.8

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 (135) hide show
  1. package/dist/annotations/converter.cjs +8 -32
  2. package/dist/annotations/converter.cjs.map +1 -1
  3. package/dist/annotations/converter.d.ts.map +1 -1
  4. package/dist/annotations/converter.js +7 -31
  5. package/dist/annotations/converter.js.map +1 -1
  6. package/dist/block_model.cjs +1 -0
  7. package/dist/block_model.cjs.map +1 -1
  8. package/dist/block_model.d.ts.map +1 -1
  9. package/dist/block_model.js +1 -0
  10. package/dist/block_model.js.map +1 -1
  11. package/dist/builder.cjs +1 -0
  12. package/dist/builder.cjs.map +1 -1
  13. package/dist/builder.d.ts.map +1 -1
  14. package/dist/builder.js +1 -0
  15. package/dist/builder.js.map +1 -1
  16. package/dist/components/PlDataTable/index.d.ts +5 -0
  17. package/dist/components/PlDataTable/index.d.ts.map +1 -0
  18. package/dist/components/PlDataTable/labels.cjs +91 -0
  19. package/dist/components/PlDataTable/labels.cjs.map +1 -0
  20. package/dist/components/PlDataTable/labels.d.ts +7 -0
  21. package/dist/components/PlDataTable/labels.d.ts.map +1 -0
  22. package/dist/components/PlDataTable/labels.js +88 -0
  23. package/dist/components/PlDataTable/labels.js.map +1 -0
  24. package/dist/components/PlDataTable/state-migration.cjs +162 -0
  25. package/dist/components/PlDataTable/state-migration.cjs.map +1 -0
  26. package/dist/components/PlDataTable/state-migration.d.ts +94 -0
  27. package/dist/components/PlDataTable/state-migration.d.ts.map +1 -0
  28. package/dist/components/PlDataTable/state-migration.js +158 -0
  29. package/dist/components/PlDataTable/state-migration.js.map +1 -0
  30. package/dist/components/PlDataTable/table.cjs +187 -0
  31. package/dist/components/PlDataTable/table.cjs.map +1 -0
  32. package/dist/components/PlDataTable/table.d.ts +26 -0
  33. package/dist/components/PlDataTable/table.d.ts.map +1 -0
  34. package/dist/components/PlDataTable/table.js +183 -0
  35. package/dist/components/PlDataTable/table.js.map +1 -0
  36. package/dist/components/PlDataTable/v4.d.ts +157 -0
  37. package/dist/components/PlDataTable/v4.d.ts.map +1 -0
  38. package/dist/components/PlDataTable/v5.d.ts +114 -0
  39. package/dist/components/PlDataTable/v5.d.ts.map +1 -0
  40. package/dist/filters/converters/filterToQuery.cjs +244 -0
  41. package/dist/filters/converters/filterToQuery.cjs.map +1 -0
  42. package/dist/filters/converters/filterToQuery.d.ts +4 -0
  43. package/dist/filters/converters/filterToQuery.d.ts.map +1 -0
  44. package/dist/filters/converters/filterToQuery.js +242 -0
  45. package/dist/filters/converters/filterToQuery.js.map +1 -0
  46. package/dist/filters/{converter.cjs → converters/filterUiToExpressionImpl.cjs} +3 -2
  47. package/dist/filters/converters/filterUiToExpressionImpl.cjs.map +1 -0
  48. package/dist/filters/{converter.d.ts → converters/filterUiToExpressionImpl.d.ts} +2 -2
  49. package/dist/filters/converters/filterUiToExpressionImpl.d.ts.map +1 -0
  50. package/dist/filters/{converter.js → converters/filterUiToExpressionImpl.js} +3 -2
  51. package/dist/filters/converters/filterUiToExpressionImpl.js.map +1 -0
  52. package/dist/filters/converters/index.d.ts +3 -0
  53. package/dist/filters/converters/index.d.ts.map +1 -0
  54. package/dist/filters/distill.cjs +61 -0
  55. package/dist/filters/distill.cjs.map +1 -0
  56. package/dist/filters/distill.d.ts +7 -0
  57. package/dist/filters/distill.d.ts.map +1 -0
  58. package/dist/filters/distill.js +59 -0
  59. package/dist/filters/distill.js.map +1 -0
  60. package/dist/filters/index.d.ts +2 -1
  61. package/dist/filters/index.d.ts.map +1 -1
  62. package/dist/filters/types.d.ts +1 -117
  63. package/dist/filters/types.d.ts.map +1 -1
  64. package/dist/index.cjs +17 -15
  65. package/dist/index.cjs.map +1 -1
  66. package/dist/index.js +5 -2
  67. package/dist/index.js.map +1 -1
  68. package/dist/package.json.cjs +1 -1
  69. package/dist/package.json.js +1 -1
  70. package/dist/pframe_utils/columns.cjs +2 -0
  71. package/dist/pframe_utils/columns.cjs.map +1 -1
  72. package/dist/pframe_utils/columns.js +2 -0
  73. package/dist/pframe_utils/columns.js.map +1 -1
  74. package/dist/pframe_utils/index.cjs +2 -0
  75. package/dist/pframe_utils/index.cjs.map +1 -1
  76. package/dist/pframe_utils/index.js +2 -0
  77. package/dist/pframe_utils/index.js.map +1 -1
  78. package/dist/pframe_utils/querySpec.d.ts +2 -0
  79. package/dist/pframe_utils/querySpec.d.ts.map +1 -0
  80. package/dist/render/api.cjs +7 -0
  81. package/dist/render/api.cjs.map +1 -1
  82. package/dist/render/api.d.ts +3 -2
  83. package/dist/render/api.d.ts.map +1 -1
  84. package/dist/render/api.js +8 -1
  85. package/dist/render/api.js.map +1 -1
  86. package/dist/render/future.d.ts +1 -1
  87. package/dist/render/index.d.ts +2 -1
  88. package/dist/render/index.d.ts.map +1 -1
  89. package/dist/render/internal.cjs.map +1 -1
  90. package/dist/render/internal.d.ts +4 -1
  91. package/dist/render/internal.d.ts.map +1 -1
  92. package/dist/render/internal.js.map +1 -1
  93. package/dist/render/util/column_collection.cjs.map +1 -1
  94. package/dist/render/util/column_collection.d.ts +1 -1
  95. package/dist/render/util/column_collection.d.ts.map +1 -1
  96. package/dist/render/util/column_collection.js.map +1 -1
  97. package/dist/render/util/pcolumn_data.cjs.map +1 -1
  98. package/dist/render/util/pcolumn_data.d.ts +1 -1
  99. package/dist/render/util/pcolumn_data.d.ts.map +1 -1
  100. package/dist/render/util/pcolumn_data.js.map +1 -1
  101. package/package.json +8 -6
  102. package/src/annotations/converter.ts +12 -40
  103. package/src/block_model.ts +1 -0
  104. package/src/builder.ts +1 -0
  105. package/src/components/PlDataTable/index.ts +24 -0
  106. package/src/components/PlDataTable/labels.ts +101 -0
  107. package/src/components/PlDataTable/state-migration.ts +285 -0
  108. package/src/components/PlDataTable/table.ts +278 -0
  109. package/src/components/PlDataTable/v4.ts +193 -0
  110. package/src/components/PlDataTable/v5.ts +140 -0
  111. package/src/filters/converters/filterToQuery.test.ts +417 -0
  112. package/src/filters/converters/filterToQuery.ts +258 -0
  113. package/src/filters/{converter.test.ts → converters/filterUiToExpressionImpl.test.ts} +2 -2
  114. package/src/filters/{converter.ts → converters/filterUiToExpressionImpl.ts} +3 -2
  115. package/src/filters/converters/index.ts +2 -0
  116. package/src/filters/distill.test.ts +191 -0
  117. package/src/filters/distill.ts +67 -0
  118. package/src/filters/index.ts +2 -1
  119. package/src/filters/types.ts +7 -48
  120. package/src/pframe_utils/querySpec.ts +1 -0
  121. package/src/render/api.ts +13 -6
  122. package/src/render/index.ts +2 -1
  123. package/src/render/internal.ts +11 -0
  124. package/src/render/util/column_collection.ts +1 -1
  125. package/src/render/util/pcolumn_data.ts +1 -1
  126. package/dist/components/PlDataTable.cjs +0 -307
  127. package/dist/components/PlDataTable.cjs.map +0 -1
  128. package/dist/components/PlDataTable.d.ts +0 -366
  129. package/dist/components/PlDataTable.d.ts.map +0 -1
  130. package/dist/components/PlDataTable.js +0 -297
  131. package/dist/components/PlDataTable.js.map +0 -1
  132. package/dist/filters/converter.cjs.map +0 -1
  133. package/dist/filters/converter.d.ts.map +0 -1
  134. package/dist/filters/converter.js.map +0 -1
  135. package/src/components/PlDataTable.ts +0 -794
@@ -0,0 +1,285 @@
1
+ import type {
2
+ AxisId,
3
+ CanonicalizedJson,
4
+ PObjectId,
5
+ PTableColumnId,
6
+ PTableColumnSpec,
7
+ PTableRecordFilter,
8
+ PTableSorting,
9
+ } from "@milaboratories/pl-model-common";
10
+ import { canonicalizeJson } from "@milaboratories/pl-model-common";
11
+ import { distillFilterSpec } from "../../filters";
12
+ import type { PlDataTableFilterState, PlTableFilter } from "./v4";
13
+ import type {
14
+ PlDataTableFiltersWithMeta,
15
+ PlDataTableGridStateCore,
16
+ PlDataTableSheetState,
17
+ PlDataTableStateV2CacheEntry,
18
+ PlDataTableStateV2Normalized,
19
+ PTableParamsV2,
20
+ } from "./v5";
21
+
22
+ /**
23
+ * PlDataTableV2 persisted state
24
+ */
25
+ export type PlDataTableStateV2 =
26
+ // Old versions of the state
27
+ | {
28
+ // no version
29
+ gridState: {
30
+ columnOrder?: {
31
+ orderedColIds: CanonicalizedJson<PTableColumnSpec>[];
32
+ };
33
+ sort?: {
34
+ sortModel: {
35
+ colId: CanonicalizedJson<PTableColumnSpec>;
36
+ sort: "asc" | "desc";
37
+ }[];
38
+ };
39
+ columnVisibility?: {
40
+ hiddenColIds: CanonicalizedJson<PTableColumnSpec>[];
41
+ };
42
+ sourceId?: string;
43
+ sheets?: Record<CanonicalizedJson<AxisId>, string | number>;
44
+ };
45
+ pTableParams?: {
46
+ sorting?: PTableSorting[];
47
+ filters?: PTableRecordFilter[];
48
+ };
49
+ }
50
+ | {
51
+ version: 2;
52
+ stateCache: {
53
+ sourceId: string;
54
+ gridState: {
55
+ columnOrder?: {
56
+ orderedColIds: CanonicalizedJson<PTableColumnSpec>[];
57
+ };
58
+ sort?: {
59
+ sortModel: {
60
+ colId: CanonicalizedJson<PTableColumnSpec>;
61
+ sort: "asc" | "desc";
62
+ }[];
63
+ };
64
+ columnVisibility?: {
65
+ hiddenColIds: CanonicalizedJson<PTableColumnSpec>[];
66
+ };
67
+ };
68
+ sheetsState: PlDataTableSheetState[];
69
+ }[];
70
+ pTableParams: {
71
+ hiddenColIds: PObjectId[] | null;
72
+ filters: PTableRecordFilter[];
73
+ sorting: PTableSorting[];
74
+ };
75
+ }
76
+ | {
77
+ version: 3;
78
+ stateCache: {
79
+ sourceId: string;
80
+ gridState: {
81
+ columnOrder?: {
82
+ orderedColIds: CanonicalizedJson<PTableColumnSpec>[];
83
+ };
84
+ sort?: {
85
+ sortModel: {
86
+ colId: CanonicalizedJson<PTableColumnSpec>;
87
+ sort: "asc" | "desc";
88
+ }[];
89
+ };
90
+ columnVisibility?: {
91
+ hiddenColIds: CanonicalizedJson<PTableColumnSpec>[];
92
+ };
93
+ };
94
+ sheetsState: PlDataTableSheetState[];
95
+ filtersState: PlDataTableFilterState[];
96
+ }[];
97
+ pTableParams: PTableParamsV2;
98
+ }
99
+ | {
100
+ version: 4;
101
+ stateCache: {
102
+ sourceId: string;
103
+ gridState: PlDataTableGridStateCore;
104
+ sheetsState: PlDataTableSheetState[];
105
+ filtersState: PlDataTableFilterState[];
106
+ }[];
107
+ /** Old format; only fields used in migration are typed */
108
+ pTableParams: {
109
+ sourceId: string | null;
110
+ hiddenColIds: PObjectId[] | null;
111
+ sorting: PTableSorting[];
112
+ };
113
+ }
114
+ // Normalized state
115
+ | PlDataTableStateV2Normalized;
116
+
117
+ /** Upgrade PlDataTableStateV2 to the latest version */
118
+ export function upgradePlDataTableStateV2(
119
+ state: PlDataTableStateV2 | undefined,
120
+ ): PlDataTableStateV2Normalized {
121
+ // Block just added, had no state, model started earlier than the UI
122
+ if (!state) {
123
+ return createPlDataTableStateV2();
124
+ }
125
+ // v1 -> v2
126
+ if (!("version" in state)) {
127
+ // Non upgradeable as sourceId calculation algorithm has changed, resetting state to default
128
+ state = createPlDataTableStateV2();
129
+ }
130
+ // v2 -> v3
131
+ if (state.version === 2) {
132
+ state = {
133
+ version: 3,
134
+ stateCache: state.stateCache.map((entry) => ({
135
+ ...entry,
136
+ filtersState: [],
137
+ })),
138
+ pTableParams: createDefaultPTableParams(),
139
+ };
140
+ }
141
+ // v3 -> v4
142
+ if (state.version === 3) {
143
+ // Non upgradeable as column ids calculation algorithm has changed, resetting state to default
144
+ state = createPlDataTableStateV2();
145
+ }
146
+ // v4 -> v5: migrate per-column filters to tree-based format
147
+ if (state.version === 4) {
148
+ state = migrateV4toV5(state);
149
+ }
150
+ return state;
151
+ }
152
+
153
+ /** Migrate v4 state to v5: convert per-column filters to tree-based format */
154
+ function migrateV4toV5(
155
+ state: Extract<PlDataTableStateV2, { version: 4 }>,
156
+ ): PlDataTableStateV2Normalized {
157
+ let idCounter = 0;
158
+ const nextId = () => ++idCounter;
159
+
160
+ const migratedCache: PlDataTableStateV2CacheEntry[] = state.stateCache.map((entry) => {
161
+ const leaves: PlDataTableFiltersWithMeta[] = [];
162
+ for (const f of entry.filtersState) {
163
+ if (f.filter !== null && !f.filter.disabled) {
164
+ const column = canonicalizeJson<PTableColumnId>(f.id);
165
+ leaves.push(migrateTableFilter(column, f.filter.value, nextId));
166
+ }
167
+ }
168
+ const filtersState: PlDataTableFiltersWithMeta | null =
169
+ leaves.length > 0 ? { id: nextId(), type: "and", filters: leaves } : null;
170
+
171
+ return {
172
+ sourceId: entry.sourceId,
173
+ gridState: entry.gridState,
174
+ sheetsState: entry.sheetsState,
175
+ filtersState,
176
+ };
177
+ });
178
+
179
+ const oldSourceId = state.pTableParams.sourceId;
180
+ const currentCache = oldSourceId
181
+ ? migratedCache.find((e) => e.sourceId === oldSourceId)
182
+ : undefined;
183
+
184
+ return {
185
+ version: 5,
186
+ stateCache: migratedCache,
187
+ pTableParams:
188
+ currentCache && oldSourceId
189
+ ? {
190
+ sourceId: oldSourceId,
191
+ hiddenColIds: state.pTableParams.hiddenColIds,
192
+ filters: distillFilterSpec(currentCache.filtersState),
193
+ sorting: state.pTableParams.sorting,
194
+ }
195
+ : createDefaultPTableParams(),
196
+ };
197
+ }
198
+
199
+ /** Migrate a single per-column PlTableFilter to a tree-based FilterSpec node */
200
+ function migrateTableFilter(
201
+ column: string,
202
+ filter: PlTableFilter,
203
+ nextId: () => number,
204
+ ): PlDataTableFiltersWithMeta {
205
+ const id = nextId();
206
+ switch (filter.type) {
207
+ case "isNA":
208
+ return { id, type: "isNA", column };
209
+ case "isNotNA":
210
+ return { id, type: "isNotNA", column };
211
+ case "number_equals":
212
+ return { id, type: "equal", column, x: filter.reference };
213
+ case "number_notEquals":
214
+ return { id, type: "notEqual", column, x: filter.reference };
215
+ case "number_greaterThan":
216
+ return { id, type: "greaterThan", column, x: filter.reference };
217
+ case "number_greaterThanOrEqualTo":
218
+ return { id, type: "greaterThanOrEqual", column, x: filter.reference };
219
+ case "number_lessThan":
220
+ return { id, type: "lessThan", column, x: filter.reference };
221
+ case "number_lessThanOrEqualTo":
222
+ return { id, type: "lessThanOrEqual", column, x: filter.reference };
223
+ case "number_between":
224
+ return {
225
+ id,
226
+ type: "and",
227
+ filters: [
228
+ filter.includeLowerBound
229
+ ? { id: nextId(), type: "greaterThanOrEqual" as const, column, x: filter.lowerBound }
230
+ : { id: nextId(), type: "greaterThan" as const, column, x: filter.lowerBound },
231
+ filter.includeUpperBound
232
+ ? { id: nextId(), type: "lessThanOrEqual" as const, column, x: filter.upperBound }
233
+ : { id: nextId(), type: "lessThan" as const, column, x: filter.upperBound },
234
+ ],
235
+ };
236
+ case "string_equals":
237
+ return { id, type: "patternEquals", column, value: filter.reference };
238
+ case "string_notEquals":
239
+ return { id, type: "patternNotEquals", column, value: filter.reference };
240
+ case "string_contains":
241
+ return { id, type: "patternContainSubsequence", column, value: filter.reference };
242
+ case "string_doesNotContain":
243
+ return { id, type: "patternNotContainSubsequence", column, value: filter.reference };
244
+ case "string_matches":
245
+ return { id, type: "patternMatchesRegularExpression", column, value: filter.reference };
246
+ case "string_doesNotMatch":
247
+ return {
248
+ id,
249
+ type: "not",
250
+ filter: {
251
+ id: nextId(),
252
+ type: "patternMatchesRegularExpression",
253
+ column,
254
+ value: filter.reference,
255
+ },
256
+ };
257
+ case "string_containsFuzzyMatch":
258
+ return {
259
+ id,
260
+ type: "patternFuzzyContainSubsequence",
261
+ column,
262
+ value: filter.reference,
263
+ maxEdits: filter.maxEdits,
264
+ substitutionsOnly: filter.substitutionsOnly,
265
+ ...(filter.wildcard !== undefined ? { wildcard: filter.wildcard } : {}),
266
+ };
267
+ }
268
+ }
269
+
270
+ export function createDefaultPTableParams(): PTableParamsV2 {
271
+ return {
272
+ sourceId: null,
273
+ hiddenColIds: null,
274
+ filters: null,
275
+ sorting: [],
276
+ };
277
+ }
278
+
279
+ export function createPlDataTableStateV2(): PlDataTableStateV2Normalized {
280
+ return {
281
+ version: 5,
282
+ stateCache: [],
283
+ pTableParams: createDefaultPTableParams(),
284
+ };
285
+ }
@@ -0,0 +1,278 @@
1
+ import type {
2
+ AxisId,
3
+ AxisSpec,
4
+ DataInfo,
5
+ PColumn,
6
+ PColumnIdAndSpec,
7
+ PColumnValues,
8
+ PObjectId,
9
+ PTableColumnId,
10
+ PTableColumnIdAxis,
11
+ PTableColumnIdColumn,
12
+ PTableDefV2,
13
+ PTableSorting,
14
+ SpecQuery,
15
+ SingleAxisSelector,
16
+ SpecQueryExpression,
17
+ SpecQueryJoinEntry,
18
+ } from "@milaboratories/pl-model-common";
19
+ import {
20
+ Annotation,
21
+ canonicalizeJson,
22
+ getAxisId,
23
+ getColumnIdAndSpec,
24
+ isLinkerColumn,
25
+ readAnnotation,
26
+ uniqueBy,
27
+ isBooleanExpression,
28
+ } from "@milaboratories/pl-model-common";
29
+ import { filterSpecToSpecQueryExpr } from "../../filters";
30
+ import type { RenderCtxBase, TreeNodeAccessor, PColumnDataUniversal } from "../../render";
31
+ import { allPColumnsReady, deriveLabels } from "../../render";
32
+ import { identity, isFunction, isNil } from "es-toolkit";
33
+ import { distillFilterSpec } from "../../filters/distill";
34
+ import type { CreatePlDataTableOps, PlDataTableFilters, PlDataTableModel } from "./v5";
35
+ import { upgradePlDataTableStateV2 } from "./state-migration";
36
+ import type { PlDataTableStateV2 } from "./state-migration";
37
+ import type { PlDataTableSheet } from "./v5";
38
+ import { getAllLabelColumns, getMatchingLabelColumns } from "./labels";
39
+
40
+ /** Convert a PTableColumnId to a SpecQueryExpression reference. */
41
+ function columnIdToExpr(col: PTableColumnId): SpecQueryExpression {
42
+ if (col.type === "axis") {
43
+ return { type: "axisRef", value: col.id as SingleAxisSelector };
44
+ }
45
+ return { type: "columnRef", value: col.id };
46
+ }
47
+
48
+ /** Wrap a SpecQuery as a SpecQueryJoinEntry with empty qualifications. */
49
+ function joinEntry<C>(input: SpecQuery<C>): SpecQueryJoinEntry<C> {
50
+ return { entry: input, qualifications: [] };
51
+ }
52
+
53
+ function createPTableDef(params: {
54
+ columns: PColumn<PColumnDataUniversal>[];
55
+ labelColumns: PColumn<PColumnDataUniversal>[];
56
+ coreJoinType: "inner" | "full";
57
+ filters: null | PlDataTableFilters;
58
+ sorting: PTableSorting[];
59
+ coreColumnPredicate?: (spec: PColumnIdAndSpec) => boolean;
60
+ }): PTableDefV2<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>> {
61
+ let coreColumns = params.columns;
62
+ const secondaryColumns: typeof params.columns = [];
63
+
64
+ if (isFunction(params.coreColumnPredicate)) {
65
+ coreColumns = [];
66
+ for (const c of params.columns)
67
+ if (params.coreColumnPredicate(getColumnIdAndSpec(c))) coreColumns.push(c);
68
+ else secondaryColumns.push(c);
69
+ }
70
+
71
+ secondaryColumns.push(...params.labelColumns);
72
+
73
+ // Build SpecQuery directly from columns
74
+ const coreJoinQuery: SpecQuery<
75
+ PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>
76
+ > = {
77
+ type: params.coreJoinType === "inner" ? "innerJoin" : "fullJoin",
78
+ entries: coreColumns.map((c) => joinEntry({ type: "column", column: c })),
79
+ };
80
+
81
+ let query: SpecQuery<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>> = {
82
+ type: "outerJoin",
83
+ primary: joinEntry(coreJoinQuery),
84
+ secondary: secondaryColumns.map((c) => joinEntry({ type: "column", column: c })),
85
+ };
86
+
87
+ // Apply filters
88
+ if (params.filters !== null) {
89
+ const nonEmpty = distillFilterSpec(params.filters);
90
+
91
+ if (!isNil(nonEmpty)) {
92
+ const pridicate = filterSpecToSpecQueryExpr(nonEmpty);
93
+ if (!isBooleanExpression(pridicate)) {
94
+ throw new Error(
95
+ `Filter conversion produced a non-boolean expression (got type "${pridicate.type}"), expected a boolean predicate for query filtering`,
96
+ );
97
+ }
98
+ query = {
99
+ type: "filter",
100
+ input: query,
101
+ predicate: pridicate,
102
+ };
103
+ }
104
+ }
105
+
106
+ // Apply sorting
107
+ if (params.sorting.length > 0) {
108
+ query = {
109
+ type: "sort",
110
+ input: query,
111
+ sortBy: params.sorting.map((s) => ({
112
+ expression: columnIdToExpr(s.column),
113
+ ascending: s.ascending,
114
+ nullsFirst: s.ascending === s.naAndAbsentAreLeastValues,
115
+ })),
116
+ };
117
+ }
118
+
119
+ return { query };
120
+ }
121
+
122
+ /** Check if column should be omitted from the table */
123
+ export function isColumnHidden(spec: { annotations?: Annotation }): boolean {
124
+ return readAnnotation(spec, Annotation.Table.Visibility) === "hidden";
125
+ }
126
+
127
+ /** Check if column is hidden by default */
128
+ export function isColumnOptional(spec: { annotations?: Annotation }): boolean {
129
+ return readAnnotation(spec, Annotation.Table.Visibility) === "optional";
130
+ }
131
+
132
+ /**
133
+ * Create p-table spec and handle given ui table state
134
+ *
135
+ * @param ctx context
136
+ * @param columns column list
137
+ * @param tableState table ui state
138
+ * @returns PlAgDataTableV2 table source
139
+ */
140
+ export function createPlDataTableV2<A, U>(
141
+ ctx: RenderCtxBase<A, U>,
142
+ columns: PColumn<PColumnDataUniversal>[],
143
+ tableState: PlDataTableStateV2 | undefined,
144
+ ops?: CreatePlDataTableOps,
145
+ ): PlDataTableModel | undefined {
146
+ if (columns.length === 0) return undefined;
147
+
148
+ const tableStateNormalized = upgradePlDataTableStateV2(tableState);
149
+
150
+ const allLabelColumns = getAllLabelColumns(ctx.resultPool);
151
+ if (!allLabelColumns) return undefined;
152
+
153
+ let fullLabelColumns = getMatchingLabelColumns(columns.map(getColumnIdAndSpec), allLabelColumns);
154
+ fullLabelColumns = deriveLabels(fullLabelColumns, identity, { includeNativeLabel: true }).map(
155
+ (v) => {
156
+ return {
157
+ ...v.value,
158
+ spec: {
159
+ ...v.value.spec,
160
+ annotations: {
161
+ ...v.value.spec.annotations,
162
+ [Annotation.Label]: v.label,
163
+ },
164
+ },
165
+ };
166
+ },
167
+ );
168
+
169
+ const fullColumns = [...columns, ...fullLabelColumns];
170
+
171
+ const fullColumnsAxes = uniqueBy(
172
+ fullColumns.flatMap((c) => c.spec.axesSpec.map((a) => getAxisId(a))),
173
+ (a) => canonicalizeJson<AxisId>(a),
174
+ );
175
+ const fullColumnsIds: PTableColumnId[] = [
176
+ ...fullColumnsAxes.map((a) => ({ type: "axis", id: a }) satisfies PTableColumnIdAxis),
177
+ ...fullColumns.map((c) => ({ type: "column", id: c.id }) satisfies PTableColumnIdColumn),
178
+ ];
179
+ const fullColumnsIdsSet = new Set(fullColumnsIds.map((c) => canonicalizeJson<PTableColumnId>(c)));
180
+ const isValidColumnId = (id: PTableColumnId): boolean =>
181
+ fullColumnsIdsSet.has(canonicalizeJson<PTableColumnId>(id));
182
+
183
+ const coreJoinType = ops?.coreJoinType ?? "full";
184
+ const filters = tableStateNormalized.pTableParams.filters;
185
+ const sorting: PTableSorting[] = uniqueBy(
186
+ [...tableStateNormalized.pTableParams.sorting, ...(ops?.sorting ?? [])],
187
+ (s) => canonicalizeJson<PTableColumnId>(s.column),
188
+ ).filter((s) => {
189
+ const valid = isValidColumnId(s.column);
190
+ if (!valid)
191
+ ctx.logWarn(`Sorting ${JSON.stringify(s)} does not match provided columns, skipping`);
192
+ return valid;
193
+ });
194
+
195
+ const fullDef = createPTableDef({
196
+ columns,
197
+ labelColumns: fullLabelColumns,
198
+ coreJoinType,
199
+ filters,
200
+ sorting,
201
+ coreColumnPredicate: ops?.coreColumnPredicate,
202
+ });
203
+ const fullHandle = ctx.createPTableV2(fullDef);
204
+ if (!fullHandle) return undefined;
205
+
206
+ const hiddenColumns = new Set<PObjectId>(
207
+ ((): PObjectId[] => {
208
+ // Inner join works as a filter - all columns must be present
209
+ if (coreJoinType === "inner") return [];
210
+
211
+ const hiddenColIds = tableStateNormalized.pTableParams.hiddenColIds;
212
+ if (hiddenColIds) return hiddenColIds;
213
+
214
+ return columns.filter((c) => isColumnOptional(c.spec)).map((c) => c.id);
215
+ })(),
216
+ );
217
+
218
+ // Preserve linker columns
219
+ columns.filter((c) => isLinkerColumn(c.spec)).forEach((c) => hiddenColumns.delete(c.id));
220
+
221
+ // Preserve core columns as they change the shape of join.
222
+ const coreColumnPredicate = ops?.coreColumnPredicate;
223
+ if (coreColumnPredicate) {
224
+ const coreColumns = columns.flatMap((c) =>
225
+ coreColumnPredicate(getColumnIdAndSpec(c)) ? [c.id] : [],
226
+ );
227
+ coreColumns.forEach((c) => hiddenColumns.delete(c));
228
+ }
229
+
230
+ // Sorting changes the order of result rows — preserve sorted columns from being hidden
231
+ sorting
232
+ .map((s) => s.column)
233
+ .filter((c): c is PTableColumnIdColumn => c.type === "column")
234
+ .forEach((c) => hiddenColumns.delete(c.id));
235
+
236
+ const visibleColumns = columns.filter((c) => !hiddenColumns.has(c.id));
237
+ const visibleLabelColumns = getMatchingLabelColumns(
238
+ visibleColumns.map(getColumnIdAndSpec),
239
+ allLabelColumns,
240
+ );
241
+
242
+ // if at least one column is not yet computed, we can't show the table
243
+ if (!allPColumnsReady([...visibleColumns, ...visibleLabelColumns])) return undefined;
244
+
245
+ const visibleDef = createPTableDef({
246
+ columns: visibleColumns,
247
+ labelColumns: visibleLabelColumns,
248
+ coreJoinType,
249
+ filters,
250
+ sorting,
251
+ coreColumnPredicate,
252
+ });
253
+ const visibleHandle = ctx.createPTableV2(visibleDef);
254
+ if (!visibleHandle) return undefined;
255
+
256
+ return {
257
+ sourceId: tableStateNormalized.pTableParams.sourceId,
258
+ fullTableHandle: fullHandle,
259
+ visibleTableHandle: visibleHandle,
260
+ } satisfies PlDataTableModel;
261
+ }
262
+
263
+ /** Create sheet entries for PlDataTable */
264
+ export function createPlDataTableSheet<A, U>(
265
+ ctx: RenderCtxBase<A, U>,
266
+ axis: AxisSpec,
267
+ values: (string | number)[],
268
+ ): PlDataTableSheet {
269
+ const labels = ctx.resultPool.findLabels(axis);
270
+ return {
271
+ axis,
272
+ options: values.map((v) => ({
273
+ value: v,
274
+ label: labels?.[v] ?? v.toString(),
275
+ })),
276
+ defaultValue: values[0],
277
+ };
278
+ }