@milaboratories/pl-model-common 1.24.1 → 1.24.2

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.
@@ -0,0 +1,821 @@
1
+ import type { PObjectId } from '../../../pool';
2
+ import type { JsonDataInfo } from '../data_info';
3
+ import type { AxisValueType, ColumnValueType } from '../spec';
4
+
5
+ // ============ Type Spec Types ============
6
+
7
+ /**
8
+ * Specification of column/frame structure.
9
+ *
10
+ * Defines the shape of data: which axes (dimensions) the data has
11
+ * and what value types are stored in columns.
12
+ */
13
+ export type TypeSpec = {
14
+ /** List of axis value types defining the dimensions of the data */
15
+ axes: AxisValueType[];
16
+ /** List of column value types defining the data stored */
17
+ columns: ColumnValueType[];
18
+ };
19
+
20
+ // ============ Operand Types ============
21
+
22
+ /**
23
+ * Unary mathematical operation kinds.
24
+ *
25
+ * These operations take a single numeric input and produce a numeric output.
26
+ * **Null handling**: If input is null, result is null.
27
+ *
28
+ * Operations:
29
+ * - `abs` - Absolute value: |x|
30
+ * - `ceil` - Round up to nearest integer
31
+ * - `floor` - Round down to nearest integer
32
+ * - `round` - Round to nearest integer (banker's rounding)
33
+ * - `sqrt` - Square root (returns NaN for negative inputs)
34
+ * - `log` - Natural logarithm (ln)
35
+ * - `log2` - Base-2 logarithm
36
+ * - `log10` - Base-10 logarithm
37
+ * - `exp` - Exponential function (e^x)
38
+ * - `negate` - Negation (-x)
39
+ */
40
+ export type NumericUnaryOperand = 'abs' | 'ceil' | 'floor' | 'round' | 'sqrt' | 'log' | 'log2' | 'log10' | 'exp' | 'negate';
41
+
42
+ /**
43
+ * Binary mathematical operation kinds.
44
+ *
45
+ * These operations take two numeric inputs and produce a numeric result.
46
+ * **Null handling**: If either operand is null, result is null.
47
+ *
48
+ * Operations:
49
+ * - `add` - Addition: left + right
50
+ * - `sub` - Subtraction: left - right
51
+ * - `mul` - Multiplication: left * right
52
+ * - `div` - Division: left / right (division by zero returns Infinity or NaN)
53
+ * - `mod` - Modulo: left % right
54
+ */
55
+ export type NumericBinaryOperand = 'add' | 'sub' | 'mul' | 'div' | 'mod';
56
+
57
+ /**
58
+ * Numeric comparison operation kinds.
59
+ *
60
+ * These operations compare two numeric inputs and produce a boolean result.
61
+ * **Null handling**: If either operand is null, result is null.
62
+ *
63
+ * Operations:
64
+ * - `eq` - Equal: left == right
65
+ * - `ne` - Not equal: left != right
66
+ * - `lt` - Less than: left < right
67
+ * - `le` - Less or equal: left <= right
68
+ * - `gt` - Greater than: left > right
69
+ * - `ge` - Greater or equal: left >= right
70
+ */
71
+ export type NumericComparisonOperand = 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge';
72
+
73
+ // ============ Geometric Types ============
74
+
75
+ /**
76
+ * 2D point coordinates.
77
+ *
78
+ * Used for geometric operations like point-in-polygon tests.
79
+ * Common use case: flow cytometry gating where points are tested
80
+ * against user-defined polygon regions.
81
+ */
82
+ export type Point2D = {
83
+ x: number;
84
+ y: number;
85
+ };
86
+
87
+ // ============ Constant Expression ============
88
+
89
+ /**
90
+ * Constant value expression.
91
+ *
92
+ * Represents a literal constant value in an expression tree.
93
+ * The value can be a string, number, or boolean.
94
+ *
95
+ * @example
96
+ * // Constant number
97
+ * { type: 'constant', value: 42 }
98
+ *
99
+ * // Constant string
100
+ * { type: 'constant', value: 'hello' }
101
+ *
102
+ * // Constant boolean
103
+ * { type: 'constant', value: true }
104
+ */
105
+ export type ExprConstant = {
106
+ type: 'constant';
107
+ value: string | number | boolean;
108
+ };
109
+
110
+ // ============ Generic Expression Interfaces ============
111
+ // I = expression type (recursive), S = selector type
112
+
113
+ /**
114
+ * Unary mathematical expression.
115
+ *
116
+ * Applies a unary mathematical function to a single input expression.
117
+ * **Input**: One expression that evaluates to a numeric value.
118
+ * **Output**: Numeric value.
119
+ * **Null handling**: If input is null, result is null.
120
+ *
121
+ * @template I - The expression type (for recursion)
122
+ *
123
+ * @example
124
+ * // Absolute value of column "value"
125
+ * { type: 'unaryMath', operand: 'abs', input: columnRef }
126
+ *
127
+ * // Natural log of expression
128
+ * { type: 'unaryMath', operand: 'log', input: someExpr }
129
+ *
130
+ * @see NumericUnaryOperand for available operations
131
+ */
132
+ export interface ExprNumericUnary<I> {
133
+ type: 'numericUnary';
134
+ /** The mathematical operation to apply */
135
+ operand: NumericUnaryOperand;
136
+ /** Input expression (must evaluate to numeric) */
137
+ input: I;
138
+ }
139
+
140
+ /**
141
+ * Binary mathematical expression.
142
+ *
143
+ * Applies a binary arithmetic operation to two input expressions.
144
+ * **Input**: Two expressions that evaluate to numeric values.
145
+ * **Output**: Numeric value.
146
+ * **Null handling**: If either operand is null, result is null.
147
+ *
148
+ * @template I - The expression type (for recursion)
149
+ *
150
+ * @example
151
+ * // Addition: col_a + col_b
152
+ * { type: 'binaryMath', operand: 'add', left: colA, right: colB }
153
+ *
154
+ * // Division: col_a / 2
155
+ * { type: 'binaryMath', operand: 'div', left: colA, right: { type: 'constant', value: 2 } }
156
+ *
157
+ * @see NumericBinaryOperand for available operations
158
+ */
159
+ export interface ExprNumericBinary<I> {
160
+ type: 'numericBinary';
161
+ /** The arithmetic operation to apply */
162
+ operand: NumericBinaryOperand;
163
+ /** Left operand expression */
164
+ left: I;
165
+ /** Right operand expression */
166
+ right: I;
167
+ }
168
+
169
+ /**
170
+ * Numeric comparison expression.
171
+ *
172
+ * Compares two numeric expressions and produces a boolean result.
173
+ * **Input**: Two expressions that evaluate to numeric values.
174
+ * **Output**: Boolean.
175
+ * **Null handling**: If either operand is null, result is null.
176
+ *
177
+ * @template I - The expression type (for recursion)
178
+ *
179
+ * @example
180
+ * // Greater than: col_a > 10
181
+ * { type: 'numericComparison', operand: 'gt', left: colA, right: { type: 'constant', value: 10 } }
182
+ *
183
+ * // Equality: col_a == col_b
184
+ * { type: 'numericComparison', operand: 'eq', left: colA, right: colB }
185
+ *
186
+ * // Range check (combine with logical AND): 0 <= x && x < 100
187
+ * // { type: 'logical', operand: 'and', input: [
188
+ * // { type: 'numericComparison', operand: 'ge', left: colX, right: { type: 'constant', value: 0 } },
189
+ * // { type: 'numericComparison', operand: 'lt', left: colX, right: { type: 'constant', value: 100 } }
190
+ * // ]}
191
+ *
192
+ * @see NumericComparisonOperand for available operations
193
+ */
194
+ export interface ExprNumericComparison<I> {
195
+ type: 'numericComparison';
196
+ /** The comparison operation to apply */
197
+ operand: NumericComparisonOperand;
198
+ /** Left operand expression */
199
+ left: I;
200
+ /** Right operand expression */
201
+ right: I;
202
+ }
203
+
204
+ /**
205
+ * String equality check.
206
+ *
207
+ * Compares input string to a reference value.
208
+ * **Input**: Expression evaluating to a string.
209
+ * **Output**: Boolean.
210
+ * **Null handling**: Returns false if input is null.
211
+ *
212
+ * @template I - The expression type (for recursion)
213
+ *
214
+ * @example
215
+ * // Check if name equals "John" (case-sensitive)
216
+ * // Matches only: "John"
217
+ * { type: 'stringEquals', input: nameColumn, value: 'John' }
218
+ *
219
+ * @example
220
+ * // Check if name equals "John" (case-insensitive)
221
+ * // Matches: "john", "JOHN", "John", "jOhN"
222
+ * { type: 'stringEquals', input: nameColumn, value: 'John', caseInsensitive: true }
223
+ */
224
+ export interface ExprStringEquals<I> {
225
+ type: 'stringEquals';
226
+ /** Input expression (must evaluate to string) */
227
+ input: I;
228
+ /** Reference string to compare against */
229
+ value: string;
230
+ /** If true, comparison ignores case */
231
+ caseInsensitive: boolean;
232
+ }
233
+
234
+ /**
235
+ * Regular expression match check.
236
+ *
237
+ * Tests if input string matches a regular expression pattern.
238
+ * **Input**: Expression evaluating to a string.
239
+ * **Output**: Boolean (true if pattern matches).
240
+ * **Null handling**: Returns false if input is null.
241
+ *
242
+ * @template I - The expression type (for recursion)
243
+ *
244
+ * @example
245
+ * // Check if value matches email pattern
246
+ * { type: 'stringRegex', input: emailColumn, value: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$' }
247
+ *
248
+ * // Check if starts with "prefix"
249
+ * { type: 'stringRegex', input: valueColumn, value: '^prefix' }
250
+ *
251
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions | MDN Regular Expressions Guide}
252
+ */
253
+ export interface ExprStringRegex<I> {
254
+ type: 'stringRegex';
255
+ /** Input expression (must evaluate to string) */
256
+ input: I;
257
+ /** Regular expression pattern */
258
+ value: string;
259
+ }
260
+
261
+ /**
262
+ * Substring containment check.
263
+ *
264
+ * Tests if input string contains a specified substring.
265
+ * **Input**: Expression evaluating to a string.
266
+ * **Output**: Boolean (true if substring is found).
267
+ * **Null handling**: Returns false if input is null.
268
+ *
269
+ * @template I - The expression type (for recursion)
270
+ *
271
+ * @example
272
+ * // Case-sensitive contains
273
+ * { type: 'stringContains', input: descColumn, value: 'error', caseInsensitive: false }
274
+ *
275
+ * // Case-insensitive contains
276
+ * { type: 'stringContains', input: descColumn, value: 'ERROR', caseInsensitive: true }
277
+ */
278
+ export interface ExprStringContains<I> {
279
+ type: 'stringContains';
280
+ /** Input expression (must evaluate to string) */
281
+ input: I;
282
+ /** Substring to search for */
283
+ value: string;
284
+ /** If true, comparison ignores case */
285
+ caseInsensitive: boolean;
286
+ }
287
+
288
+ /**
289
+ * Fuzzy string containment check with edit distance.
290
+ *
291
+ * Tests if input string approximately matches a pattern within a specified edit distance.
292
+ * Uses Levenshtein distance (or substitution-only distance) for fuzzy matching.
293
+ * **Input**: Expression evaluating to a string.
294
+ * **Output**: Boolean (true if approximate match found within maxEdits).
295
+ * **Null handling**: Returns false if input is null.
296
+ *
297
+ * @template I - The expression type (for recursion)
298
+ *
299
+ * @example
300
+ * // Match "color" with up to 1 edit (catches "colour", "colr", etc.)
301
+ * {
302
+ * type: 'stringContainsFuzzy',
303
+ * input: textColumn,
304
+ * value: 'color',
305
+ * maxEdits: 1,
306
+ * caseInsensitive: true,
307
+ * substitutionsOnly: false,
308
+ * wildcard: null
309
+ * }
310
+ *
311
+ * // Match with wildcard (? matches any single character)
312
+ * {
313
+ * type: 'stringContainsFuzzy',
314
+ * input: textColumn,
315
+ * value: 'te?t',
316
+ * maxEdits: 0,
317
+ * caseInsensitive: false,
318
+ * substitutionsOnly: false,
319
+ * wildcard: '?'
320
+ * }
321
+ */
322
+ export interface ExprStringContainsFuzzy<I> {
323
+ type: 'stringContainsFuzzy';
324
+ /** Input expression (must evaluate to string) */
325
+ input: I;
326
+ /** Pattern to match against */
327
+ value: string;
328
+ /**
329
+ * Maximum edit distance (Levenshtein distance).
330
+ * 0 = exact match only, 1 = one edit allowed, etc.
331
+ */
332
+ maxEdits: number;
333
+ /** If true, comparison ignores case */
334
+ caseInsensitive: boolean;
335
+ /**
336
+ * If true, only substitutions count as edits (no insertions/deletions).
337
+ * Useful when you want to match strings of same length with typos.
338
+ */
339
+ substitutionsOnly: boolean;
340
+ /**
341
+ * Optional wildcard character that matches any single character.
342
+ * Example: '?' in "te?t" matches "test", "text", "tent", etc.
343
+ * Set to null to disable wildcard matching.
344
+ */
345
+ wildcard: null | string;
346
+ }
347
+
348
+ /**
349
+ * Logical NOT expression.
350
+ *
351
+ * Negates a boolean expression.
352
+ * **Input**: Expression evaluating to boolean.
353
+ * **Output**: Boolean (inverted).
354
+ * **Null handling**: NOT null = null.
355
+ *
356
+ * @template I - The expression type (for recursion)
357
+ *
358
+ * @example
359
+ * // NOT (value > 10)
360
+ * { type: 'not', input: comparisonExpr }
361
+ */
362
+ export interface ExprLogicalUnary<I> {
363
+ type: 'not';
364
+ /** Input boolean expression to negate */
365
+ input: I;
366
+ }
367
+
368
+ /**
369
+ * Logical AND/OR expression.
370
+ *
371
+ * Combines multiple boolean expressions using AND or OR logic.
372
+ * **Input**: Array of expressions evaluating to boolean (minimum 2).
373
+ * **Output**: Boolean.
374
+ *
375
+ * **Null handling**
376
+ * - AND: null AND true = null, null AND false = false
377
+ * - OR: null OR true = true, null OR false = null
378
+ *
379
+ * @template I - The expression type (for recursion)
380
+ *
381
+ * @example
382
+ * // (a > 0) AND (b < 100)
383
+ * { type: 'and', input: [exprA, exprB] }
384
+ *
385
+ * // (status == 'active') OR (status == 'pending')
386
+ * { type: 'or', input: [statusActive, statusPending] }
387
+ */
388
+ export interface ExprLogicalVariadic<I> {
389
+ /** Logical operation: 'and' or 'or' */
390
+ type: 'and' | 'or';
391
+ /** Array of boolean expressions to combine (minimum 2 elements) */
392
+ input: I[];
393
+ }
394
+
395
+ /**
396
+ * Set membership check expression.
397
+ *
398
+ * Tests if a value is present in a predefined set of values.
399
+ * **Input**: Expression evaluating to string or number.
400
+ * **Output**: Boolean.
401
+ * **Null handling**: Returns false if input is null.
402
+ *
403
+ * @template I - The expression type (for recursion)
404
+ * @template T - The type of set elements (string or number)
405
+ *
406
+ * @example
407
+ * // Check if status is in ['active', 'pending', 'review']
408
+ * {
409
+ * type: 'isIn',
410
+ * input: statusColumn,
411
+ * set: ['active', 'pending', 'review']
412
+ * }
413
+ */
414
+ export interface ExprIsIn<I, T extends string | number> {
415
+ type: 'isIn';
416
+ /** Input expression to test */
417
+ input: I;
418
+ /** Set of allowed values */
419
+ set: T[];
420
+ }
421
+
422
+ // ============ Reference Expression Types ============
423
+
424
+ /**
425
+ * Axis reference expression.
426
+ *
427
+ * References an axis value for use in expressions (filtering, sorting, etc.).
428
+ * The axis identifier type varies by context (spec vs data layer).
429
+ *
430
+ * @template A - Axis identifier type (e.g., SingleAxisSelector for spec, number for data)
431
+ *
432
+ * @example
433
+ * // Reference axis by selector (spec layer)
434
+ * { type: 'axisRef', value: { name: 'sample' } }
435
+ *
436
+ * // Reference axis by index (data layer)
437
+ * { type: 'axisRef', value: 0 }
438
+ */
439
+ export interface ExprAxisRef<A> {
440
+ type: 'axisRef';
441
+ /** Axis identifier (selector or index depending on context) */
442
+ value: A;
443
+ }
444
+
445
+ /**
446
+ * Column reference expression.
447
+ *
448
+ * References a column value for use in expressions (filtering, arithmetic, etc.).
449
+ * The column identifier type varies by context (spec vs data layer).
450
+ *
451
+ * @template C - Column identifier type (e.g., PObjectId for spec, number for data)
452
+ *
453
+ * @example
454
+ * // Reference column by ID (spec layer)
455
+ * { type: 'columnRef', value: 'col_abc123' }
456
+ *
457
+ * // Reference column by index (data layer)
458
+ * { type: 'columnRef', value: 0 }
459
+ */
460
+ export interface ExprColumnRef<C> {
461
+ type: 'columnRef';
462
+ /** Column identifier (ID or index depending on context) */
463
+ value: C;
464
+ }
465
+
466
+ export type InferBooleanExpressionUnion<E> = [
467
+ E extends ExprNumericComparison<unknown> ? Extract<E, { type: 'numericComparison' }> : never,
468
+ E extends ExprStringEquals<unknown> ? Extract<E, { type: 'stringEquals' }> : never,
469
+ E extends ExprStringContains<unknown> ? Extract<E, { type: 'stringContains' }> : never,
470
+ E extends ExprStringContainsFuzzy<unknown> ? Extract<E, { type: 'stringContainsFuzzy' }> : never,
471
+ E extends ExprStringRegex<unknown> ? Extract<E, { type: 'stringRegex' }> : never,
472
+ E extends ExprLogicalUnary<unknown> ? Extract<E, { type: 'not' }> : never,
473
+ E extends ExprLogicalVariadic<unknown> ? Extract<E, { type: 'and' | 'or' }> : never,
474
+ E extends ExprIsIn<unknown, string | number> ? Extract<E, { type: 'isIn' }> : never,
475
+ ][number];
476
+
477
+ // ============ Generic Query Types ============
478
+ // A = Axis ID type, S = Selector type, Q = Query type, E = Expression type
479
+ // AF = Axis filter type, SE = Sort entry type, JE = Join entry type, SO = Spec override type
480
+
481
+ /**
482
+ * Selector for referencing an axis in queries.
483
+ *
484
+ * Used to identify a specific axis dimension in operations like:
485
+ * - Sorting by axis values
486
+ * - Partitioning for window functions
487
+ * - Filtering/slicing axes
488
+ *
489
+ * @template A - Axis identifier type (typically string name or numeric index)
490
+ *
491
+ * @example
492
+ * // Select axis by name
493
+ * { type: 'axis', id: 'sample' }
494
+ *
495
+ * // Select axis by index
496
+ * { type: 'axis', id: 0 }
497
+ */
498
+ export interface QueryAxisSelector<A> {
499
+ type: 'axis';
500
+ /** Axis identifier (name or index depending on context) */
501
+ id: A;
502
+ }
503
+
504
+ /**
505
+ * Selector for referencing a column in queries.
506
+ *
507
+ * Used to identify a specific column in operations like:
508
+ * - Sorting by column values
509
+ * - Partitioning for window functions
510
+ * - Aggregation expressions
511
+ *
512
+ * @template C - Column identifier type (typically string name or numeric index)
513
+ *
514
+ * @example
515
+ * // Select column by name
516
+ * { type: 'column', id: 'expression_value' }
517
+ *
518
+ * // Select column by index
519
+ * { type: 'column', id: 0 }
520
+ */
521
+ export interface QueryColumnSelector<C> {
522
+ type: 'column';
523
+ /** Column identifier (name or index depending on context) */
524
+ id: C;
525
+ }
526
+
527
+ /**
528
+ * Left outer join query operation.
529
+ *
530
+ * Joins a primary query with one or more secondary queries using left outer join semantics.
531
+ * All records from the primary are preserved; matching records from secondaries are joined,
532
+ * non-matching positions are filled with nulls.
533
+ *
534
+ * **Join behavior**:
535
+ * - All records from `primary` are preserved
536
+ * - For each secondary, matching records (by axis keys) are joined
537
+ * - Missing matches from secondaries are filled with null values
538
+ * - Empty `secondary` array acts as identity (returns primary unchanged)
539
+ *
540
+ * **Null handling**: Null join keys don't match; positions without matches get null values.
541
+ *
542
+ * @template JE - Join entry type
543
+ *
544
+ * @example
545
+ * // Left join samples with optional annotations
546
+ * {
547
+ * type: 'outerJoin',
548
+ * primary: samplesQuery,
549
+ * secondary: [annotationsQuery, metadataQuery]
550
+ * }
551
+ * // Result has all samples; annotations/metadata are null where not available
552
+ */
553
+ export interface QueryOuterJoin<JE extends QueryJoinEntry<unknown>> {
554
+ type: 'outerJoin';
555
+ /** Primary query - all its records are preserved */
556
+ primary: JE;
557
+ /** Secondary queries - joined where keys match, null where they don't */
558
+ secondary: JE[];
559
+ }
560
+
561
+ /**
562
+ * Axis slicing query operation.
563
+ *
564
+ * Filters data by fixing one or more axes to specific constant values.
565
+ * Each filtered axis is removed from the resulting data shape (reduces dimensionality).
566
+ *
567
+ * **Behavior**:
568
+ * - Each axis filter selects records where that axis equals the constant
569
+ * - Filtered axes are removed from the output spec
570
+ * - Multiple filters apply conjunctively (AND)
571
+ *
572
+ * @template Q - Input query type
573
+ * @template A - Axis selector type
574
+ *
575
+ * @example
576
+ * // Slice 3D data [sample, gene, condition] to 1D [gene]
577
+ * {
578
+ * type: 'sliceAxes',
579
+ * input: fullDataQuery,
580
+ * axisFilters: [
581
+ * { type: 'constant', axisSelector: { type: 'axis', id: 'sample' }, constant: 'Sample1' },
582
+ * { type: 'constant', axisSelector: { type: 'axis', id: 'condition' }, constant: 'Treatment' }
583
+ * ]
584
+ * }
585
+ */
586
+ export interface QuerySliceAxes<Q, A extends QueryAxisSelector<unknown>> {
587
+ type: 'sliceAxes';
588
+ /** Input query to slice */
589
+ input: Q;
590
+ /** List of axis filters to apply (at least one required) */
591
+ axisFilters: {
592
+ /** Selector identifying which axis to filter */
593
+ axisSelector: A;
594
+ /** The constant value to filter the axis to */
595
+ constant: string | number;
596
+ }[];
597
+ }
598
+
599
+ /**
600
+ * Sort query operation.
601
+ *
602
+ * Reorders records by one or more axes or columns.
603
+ * Does not change data shape or values, only record order.
604
+ *
605
+ * **Behavior**:
606
+ * - Sort entries are applied in priority order (first entry = primary sort key)
607
+ * - Ties in first sort key are broken by second, etc.
608
+ * - All axes and columns pass through unchanged
609
+ * - Only the physical ordering of records changes
610
+ *
611
+ * @template Q - Input query type
612
+ * @template SE - Sort entry type
613
+ *
614
+ * @example
615
+ * // Sort by score descending, then by name ascending for ties
616
+ * {
617
+ * type: 'sort',
618
+ * input: dataQuery,
619
+ * sortBy: [
620
+ * { axisOrColumn: { type: 'column', id: 'score' }, ascending: false, nullsFirst: null },
621
+ * { axisOrColumn: { type: 'axis', id: 'name' }, ascending: true, nullsFirst: null }
622
+ * ]
623
+ * }
624
+ */
625
+ export interface QuerySort<Q, E> {
626
+ type: 'sort';
627
+ /** Input query to sort */
628
+ input: Q;
629
+ /** Sort criteria in priority order (at least one required) */
630
+ sortBy: {
631
+ data: E;
632
+ /** If true, sort ascending (A-Z, 0-9); if false, descending */
633
+ ascending: boolean;
634
+ /**
635
+ * Null placement control:
636
+ * - true: nulls sort before non-null values
637
+ * - false: nulls sort after non-null values
638
+ * - null: use default behavior (typically nulls last)
639
+ */
640
+ nullsFirst: null | boolean;
641
+ }[];
642
+ }
643
+
644
+ /**
645
+ * Filter query operation.
646
+ *
647
+ * Filters records based on a boolean predicate expression.
648
+ * Only records where predicate evaluates to true are kept.
649
+ *
650
+ * **Behavior**:
651
+ * - Evaluates predicate for each record
652
+ * - Keeps records where predicate is true
653
+ * - Discards records where predicate is false or null
654
+ * - Data shape (axes, columns) is preserved
655
+ *
656
+ * **Null handling**: Records with null predicate result are excluded (null ≠ true).
657
+ *
658
+ * @template Q - Input query type
659
+ * @template E - Expression type
660
+ *
661
+ * @example
662
+ * // Filter to records where value > 10 AND status == 'active'
663
+ * {
664
+ * type: 'filter',
665
+ * input: dataQuery,
666
+ * predicate: {
667
+ * type: 'logical',
668
+ * operand: 'and',
669
+ * input: [
670
+ * { type: 'numericComparison', operand: 'gt', left: valueRef, right: { type: 'constant', value: 10 } },
671
+ * { type: 'stringEquals', input: statusRef, value: 'active' }
672
+ * ]
673
+ * }
674
+ * }
675
+ */
676
+ export interface QueryFilter<Q, E> {
677
+ type: 'filter';
678
+ /** Input query to filter */
679
+ input: Q;
680
+ /** Boolean predicate expression - only true records pass */
681
+ predicate: E;
682
+ }
683
+
684
+ /**
685
+ * Column reference query (leaf node).
686
+ *
687
+ * References an existing column by its unique identifier.
688
+ * This is a leaf node in the query tree that retrieves actual data.
689
+ *
690
+ * The column must exist in the dataset and its spec (axes, value type)
691
+ * becomes the output spec of this query node.
692
+ *
693
+ * @example
694
+ * // Reference column by ID
695
+ * { type: 'column', columnId: 'col_abc123' }
696
+ */
697
+ export interface QueryColumn {
698
+ type: 'column';
699
+ /** Unique identifier of the column to reference */
700
+ columnId: PObjectId;
701
+ }
702
+
703
+ /**
704
+ * Inline column query (leaf node).
705
+ *
706
+ * Creates a column with inline/embedded data and type specification.
707
+ * Useful for creating constant columns or injecting computed data.
708
+ *
709
+ * The data is provided via dataInfo which contains the actual values
710
+ * or reference to where data is stored.
711
+ *
712
+ * @template T - Type spec type
713
+ *
714
+ * @example
715
+ * // Create inline column with constant values
716
+ * {
717
+ * type: 'inlineColumn',
718
+ * spec: { axes: ['sample'], columns: ['Int'] },
719
+ * dataInfo: { ... } // JsonDataInfo object
720
+ * }
721
+ */
722
+ export interface QueryInlineColumn<T> {
723
+ type: 'inlineColumn';
724
+ /** Type specification defining axes and column types */
725
+ spec: T;
726
+ /** Data information containing or referencing the actual values */
727
+ dataInfo: JsonDataInfo;
728
+ }
729
+
730
+ /**
731
+ * Sparse to dense column query operation.
732
+ *
733
+ * Expands a column across additional axes using Cartesian product.
734
+ * Creates all combinations of existing axis values with new axis values.
735
+ *
736
+ * **Use case**: When you need to repeat/broadcast a column across
737
+ * new dimensions that it doesn't originally have.
738
+ *
739
+ * **Behavior**:
740
+ * - Takes an existing column
741
+ * - Expands it across specified axes indices
742
+ * - Result has Cartesian product of original axes × new axes
743
+ *
744
+ * @template SO - Spec override type
745
+ *
746
+ * @example
747
+ * // Expand column across axes at indices 0 and 2
748
+ * {
749
+ * type: 'sparseToDenseColumn',
750
+ * columnId: 'col_abc123',
751
+ * axesIndices: [0, 2],
752
+ * specOverride: { ... } // optional spec modifications
753
+ * }
754
+ */
755
+ export interface QuerySparseToDenseColumn<SO> {
756
+ type: 'sparseToDenseColumn';
757
+ /** ID of the column to cross-join */
758
+ columnId: PObjectId;
759
+ /** Optional override for the column specification */
760
+ specOverride?: SO;
761
+ /** Indices of axes to expand across */
762
+ axesIndices: number[];
763
+ }
764
+
765
+ /**
766
+ * Symmetric join query operation (inner join or full outer join).
767
+ *
768
+ * Joins multiple queries symmetrically (order doesn't affect result semantics).
769
+ *
770
+ * **Inner Join** (`type: 'innerJoin'`):
771
+ * - Returns only records that exist in ALL entries
772
+ * - Null join keys don't match, so records with null keys are excluded
773
+ * - Result contains intersection of all entries by axis keys
774
+ *
775
+ * **Full Join** (`type: 'fullJoin'`):
776
+ * - Returns all records from ALL entries
777
+ * - Missing values are filled with nulls
778
+ * - Null join keys create separate groups
779
+ * - Result contains union of all entries by axis keys
780
+ *
781
+ * **Single entry**: Acts as identity (returns entry unchanged).
782
+ *
783
+ * @template JE - Join entry type
784
+ *
785
+ * @example
786
+ * // Inner join: only records present in all queries
787
+ * {
788
+ * type: 'innerJoin',
789
+ * entries: [query1Entry, query2Entry, query3Entry]
790
+ * }
791
+ *
792
+ * // Full join: all records from all queries, nulls for missing
793
+ * {
794
+ * type: 'fullJoin',
795
+ * entries: [query1Entry, query2Entry]
796
+ * }
797
+ */
798
+ export interface QuerySymmetricJoin<JE extends QueryJoinEntry<unknown>> {
799
+ /** 'innerJoin' for intersection, 'fullJoin' for union with nulls */
800
+ type: 'innerJoin' | 'fullJoin';
801
+ /** Queries to join (at least one required) */
802
+ entries: JE[];
803
+ }
804
+
805
+ /**
806
+ * Join entry wrapper.
807
+ *
808
+ * Wraps a query to be used as an entry in join operations.
809
+ * The wrapper allows for additional metadata or configuration
810
+ * on each joined query (e.g., specifying join keys, aliases).
811
+ *
812
+ * @template Q - Query type
813
+ *
814
+ * @example
815
+ * // Wrap a query for use in join
816
+ * { entry: someQuery }
817
+ */
818
+ export interface QueryJoinEntry<Q> {
819
+ /** The query to be joined */
820
+ entry: Q;
821
+ }