@tanstack/db 0.1.6 → 0.1.7
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.
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +5 -5
- package/dist/cjs/query/compiler/group-by.cjs +4 -2
- package/dist/cjs/query/compiler/group-by.cjs.map +1 -1
- package/dist/cjs/query/compiler/index.cjs +2 -1
- package/dist/cjs/query/compiler/index.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +2 -1
- package/dist/cjs/query/ir.cjs +16 -0
- package/dist/cjs/query/ir.cjs.map +1 -1
- package/dist/cjs/query/ir.d.cts +24 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +267 -0
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -0
- package/dist/cjs/query/live/collection-config-builder.d.cts +36 -0
- package/dist/cjs/query/live/collection-subscriber.cjs +263 -0
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -0
- package/dist/cjs/query/live/collection-subscriber.d.cts +28 -0
- package/dist/cjs/query/live/types.d.cts +77 -0
- package/dist/cjs/query/live-query-collection.cjs +3 -417
- package/dist/cjs/query/live-query-collection.cjs.map +1 -1
- package/dist/cjs/query/live-query-collection.d.cts +1 -58
- package/dist/cjs/query/optimizer.cjs +34 -11
- package/dist/cjs/query/optimizer.cjs.map +1 -1
- package/dist/cjs/types.d.cts +12 -0
- package/dist/esm/collection.d.ts +5 -5
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/query/compiler/group-by.js +5 -3
- package/dist/esm/query/compiler/group-by.js.map +1 -1
- package/dist/esm/query/compiler/index.js +3 -2
- package/dist/esm/query/compiler/index.js.map +1 -1
- package/dist/esm/query/index.d.ts +2 -1
- package/dist/esm/query/ir.d.ts +24 -1
- package/dist/esm/query/ir.js +17 -1
- package/dist/esm/query/ir.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +36 -0
- package/dist/esm/query/live/collection-config-builder.js +267 -0
- package/dist/esm/query/live/collection-config-builder.js.map +1 -0
- package/dist/esm/query/live/collection-subscriber.d.ts +28 -0
- package/dist/esm/query/live/collection-subscriber.js +263 -0
- package/dist/esm/query/live/collection-subscriber.js.map +1 -0
- package/dist/esm/query/live/types.d.ts +77 -0
- package/dist/esm/query/live-query-collection.d.ts +1 -58
- package/dist/esm/query/live-query-collection.js +3 -417
- package/dist/esm/query/live-query-collection.js.map +1 -1
- package/dist/esm/query/optimizer.js +35 -12
- package/dist/esm/query/optimizer.js.map +1 -1
- package/dist/esm/types.d.ts +12 -0
- package/package.json +1 -1
- package/src/collection.ts +8 -5
- package/src/query/compiler/group-by.ts +5 -3
- package/src/query/compiler/index.ts +3 -2
- package/src/query/index.ts +2 -1
- package/src/query/ir.ts +48 -1
- package/src/query/live/collection-config-builder.ts +437 -0
- package/src/query/live/collection-subscriber.ts +460 -0
- package/src/query/live/types.ts +93 -0
- package/src/query/live-query-collection.ts +8 -791
- package/src/query/optimizer.ts +66 -18
- package/src/types.ts +74 -0
package/src/query/optimizer.ts
CHANGED
|
@@ -45,6 +45,11 @@
|
|
|
45
45
|
* - **Ordering + Limits**: ORDER BY combined with LIMIT/OFFSET (would change result set)
|
|
46
46
|
* - **Functional Operations**: fnSelect, fnWhere, fnHaving (potential side effects)
|
|
47
47
|
*
|
|
48
|
+
* ### Residual WHERE Clauses
|
|
49
|
+
* For outer joins (LEFT, RIGHT, FULL), WHERE clauses are copied to subqueries for optimization
|
|
50
|
+
* but also kept as "residual" clauses in the main query to preserve semantics. This ensures
|
|
51
|
+
* that NULL values from outer joins are properly filtered according to SQL standards.
|
|
52
|
+
*
|
|
48
53
|
* The optimizer tracks which clauses were actually optimized and only removes those from the
|
|
49
54
|
* main query. Subquery reuse is handled safely through immutable query copies.
|
|
50
55
|
*
|
|
@@ -121,9 +126,12 @@ import {
|
|
|
121
126
|
CollectionRef as CollectionRefClass,
|
|
122
127
|
Func,
|
|
123
128
|
QueryRef as QueryRefClass,
|
|
129
|
+
createResidualWhere,
|
|
130
|
+
getWhereExpression,
|
|
131
|
+
isResidualWhere,
|
|
124
132
|
} from "./ir.js"
|
|
125
133
|
import { isConvertibleToCollectionFilter } from "./compiler/expressions.js"
|
|
126
|
-
import type { BasicExpression, From, QueryIR } from "./ir.js"
|
|
134
|
+
import type { BasicExpression, From, QueryIR, Where } from "./ir.js"
|
|
127
135
|
|
|
128
136
|
/**
|
|
129
137
|
* Represents a WHERE clause after source analysis
|
|
@@ -325,8 +333,13 @@ function applySingleLevelOptimization(query: QueryIR): QueryIR {
|
|
|
325
333
|
return query
|
|
326
334
|
}
|
|
327
335
|
|
|
336
|
+
// Filter out residual WHERE clauses to prevent them from being optimized again
|
|
337
|
+
const nonResidualWhereClauses = query.where.filter(
|
|
338
|
+
(where) => !isResidualWhere(where)
|
|
339
|
+
)
|
|
340
|
+
|
|
328
341
|
// Step 1: Split all AND clauses at the root level for granular optimization
|
|
329
|
-
const splitWhereClauses = splitAndClauses(
|
|
342
|
+
const splitWhereClauses = splitAndClauses(nonResidualWhereClauses)
|
|
330
343
|
|
|
331
344
|
// Step 2: Analyze each WHERE clause to determine which sources it touches
|
|
332
345
|
const analyzedClauses = splitWhereClauses.map((clause) =>
|
|
@@ -337,7 +350,20 @@ function applySingleLevelOptimization(query: QueryIR): QueryIR {
|
|
|
337
350
|
const groupedClauses = groupWhereClauses(analyzedClauses)
|
|
338
351
|
|
|
339
352
|
// Step 4: Apply optimizations by lifting single-source clauses into subqueries
|
|
340
|
-
|
|
353
|
+
const optimizedQuery = applyOptimizations(query, groupedClauses)
|
|
354
|
+
|
|
355
|
+
// Add back any residual WHERE clauses that were filtered out
|
|
356
|
+
const residualWhereClauses = query.where.filter((where) =>
|
|
357
|
+
isResidualWhere(where)
|
|
358
|
+
)
|
|
359
|
+
if (residualWhereClauses.length > 0) {
|
|
360
|
+
optimizedQuery.where = [
|
|
361
|
+
...(optimizedQuery.where || []),
|
|
362
|
+
...residualWhereClauses,
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return optimizedQuery
|
|
341
367
|
}
|
|
342
368
|
|
|
343
369
|
/**
|
|
@@ -424,26 +450,35 @@ function isRedundantSubquery(query: QueryIR): boolean {
|
|
|
424
450
|
* ```
|
|
425
451
|
*/
|
|
426
452
|
function splitAndClauses(
|
|
427
|
-
whereClauses: Array<
|
|
453
|
+
whereClauses: Array<Where>
|
|
428
454
|
): Array<BasicExpression<boolean>> {
|
|
429
455
|
const result: Array<BasicExpression<boolean>> = []
|
|
430
456
|
|
|
431
|
-
for (const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const splitArgs = splitAndClauses(
|
|
435
|
-
clause.args as Array<BasicExpression<boolean>>
|
|
436
|
-
)
|
|
437
|
-
result.push(...splitArgs)
|
|
438
|
-
} else {
|
|
439
|
-
// Preserve non-AND clauses as-is (including OR clauses)
|
|
440
|
-
result.push(clause)
|
|
441
|
-
}
|
|
457
|
+
for (const whereClause of whereClauses) {
|
|
458
|
+
const clause = getWhereExpression(whereClause)
|
|
459
|
+
result.push(...splitAndClausesRecursive(clause))
|
|
442
460
|
}
|
|
443
461
|
|
|
444
462
|
return result
|
|
445
463
|
}
|
|
446
464
|
|
|
465
|
+
// Helper function for recursive splitting of BasicExpression arrays
|
|
466
|
+
function splitAndClausesRecursive(
|
|
467
|
+
clause: BasicExpression<boolean>
|
|
468
|
+
): Array<BasicExpression<boolean>> {
|
|
469
|
+
if (clause.type === `func` && clause.name === `and`) {
|
|
470
|
+
// Recursively split nested AND clauses to handle complex expressions
|
|
471
|
+
const result: Array<BasicExpression<boolean>> = []
|
|
472
|
+
for (const arg of clause.args as Array<BasicExpression<boolean>>) {
|
|
473
|
+
result.push(...splitAndClausesRecursive(arg))
|
|
474
|
+
}
|
|
475
|
+
return result
|
|
476
|
+
} else {
|
|
477
|
+
// Preserve non-AND clauses as-is (including OR clauses)
|
|
478
|
+
return [clause]
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
447
482
|
/**
|
|
448
483
|
* Step 2: Analyze which table sources a WHERE clause touches.
|
|
449
484
|
*
|
|
@@ -588,19 +623,32 @@ function applyOptimizations(
|
|
|
588
623
|
}))
|
|
589
624
|
: undefined
|
|
590
625
|
|
|
591
|
-
// Build the remaining WHERE clauses: multi-source +
|
|
592
|
-
const remainingWhereClauses: Array<
|
|
626
|
+
// Build the remaining WHERE clauses: multi-source + residual single-source clauses
|
|
627
|
+
const remainingWhereClauses: Array<Where> = []
|
|
593
628
|
|
|
594
629
|
// Add multi-source clauses
|
|
595
630
|
if (groupedClauses.multiSource) {
|
|
596
631
|
remainingWhereClauses.push(groupedClauses.multiSource)
|
|
597
632
|
}
|
|
598
633
|
|
|
599
|
-
//
|
|
634
|
+
// Determine if we need residual clauses (when query has outer JOINs)
|
|
635
|
+
const hasOuterJoins =
|
|
636
|
+
query.join &&
|
|
637
|
+
query.join.some(
|
|
638
|
+
(join) =>
|
|
639
|
+
join.type === `left` || join.type === `right` || join.type === `full`
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
// Add single-source clauses
|
|
600
643
|
for (const [source, clause] of groupedClauses.singleSource) {
|
|
601
644
|
if (!actuallyOptimized.has(source)) {
|
|
645
|
+
// Wasn't optimized at all - keep as regular WHERE clause
|
|
602
646
|
remainingWhereClauses.push(clause)
|
|
647
|
+
} else if (hasOuterJoins) {
|
|
648
|
+
// Was optimized AND query has outer JOINs - keep as residual WHERE clause
|
|
649
|
+
remainingWhereClauses.push(createResidualWhere(clause))
|
|
603
650
|
}
|
|
651
|
+
// If optimized and no outer JOINs - don't keep (original behavior)
|
|
604
652
|
}
|
|
605
653
|
|
|
606
654
|
// Create a completely new query object to ensure immutability
|
package/src/types.ts
CHANGED
|
@@ -629,3 +629,77 @@ export type ChangeListener<
|
|
|
629
629
|
T extends object = Record<string, unknown>,
|
|
630
630
|
TKey extends string | number = string | number,
|
|
631
631
|
> = (changes: Array<ChangeMessage<T, TKey>>) => void
|
|
632
|
+
|
|
633
|
+
// Adapted from https://github.com/sindresorhus/type-fest
|
|
634
|
+
// MIT License Copyright (c) Sindre Sorhus
|
|
635
|
+
|
|
636
|
+
type BuiltIns =
|
|
637
|
+
| null
|
|
638
|
+
| undefined
|
|
639
|
+
| string
|
|
640
|
+
| number
|
|
641
|
+
| boolean
|
|
642
|
+
| symbol
|
|
643
|
+
| bigint
|
|
644
|
+
| void
|
|
645
|
+
| Date
|
|
646
|
+
| RegExp
|
|
647
|
+
|
|
648
|
+
type HasMultipleCallSignatures<
|
|
649
|
+
T extends (...arguments_: Array<any>) => unknown,
|
|
650
|
+
> = T extends {
|
|
651
|
+
(...arguments_: infer A): unknown
|
|
652
|
+
(...arguments_: infer B): unknown
|
|
653
|
+
}
|
|
654
|
+
? B extends A
|
|
655
|
+
? A extends B
|
|
656
|
+
? false
|
|
657
|
+
: true
|
|
658
|
+
: true
|
|
659
|
+
: false
|
|
660
|
+
|
|
661
|
+
type WritableMapDeep<MapType extends ReadonlyMap<unknown, unknown>> =
|
|
662
|
+
MapType extends ReadonlyMap<infer KeyType, infer ValueType>
|
|
663
|
+
? Map<WritableDeep<KeyType>, WritableDeep<ValueType>>
|
|
664
|
+
: MapType
|
|
665
|
+
|
|
666
|
+
type WritableSetDeep<SetType extends ReadonlySet<unknown>> =
|
|
667
|
+
SetType extends ReadonlySet<infer ItemType>
|
|
668
|
+
? Set<WritableDeep<ItemType>>
|
|
669
|
+
: SetType
|
|
670
|
+
|
|
671
|
+
type WritableObjectDeep<ObjectType extends object> = {
|
|
672
|
+
-readonly [KeyType in keyof ObjectType]: WritableDeep<ObjectType[KeyType]>
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
type WritableArrayDeep<ArrayType extends ReadonlyArray<unknown>> =
|
|
676
|
+
ArrayType extends readonly []
|
|
677
|
+
? []
|
|
678
|
+
: ArrayType extends readonly [...infer U, infer V]
|
|
679
|
+
? [...WritableArrayDeep<U>, WritableDeep<V>]
|
|
680
|
+
: ArrayType extends readonly [infer U, ...infer V]
|
|
681
|
+
? [WritableDeep<U>, ...WritableArrayDeep<V>]
|
|
682
|
+
: ArrayType extends ReadonlyArray<infer U>
|
|
683
|
+
? Array<WritableDeep<U>>
|
|
684
|
+
: ArrayType extends Array<infer U>
|
|
685
|
+
? Array<WritableDeep<U>>
|
|
686
|
+
: ArrayType
|
|
687
|
+
|
|
688
|
+
export type WritableDeep<T> = T extends BuiltIns
|
|
689
|
+
? T
|
|
690
|
+
: T extends (...arguments_: Array<any>) => unknown
|
|
691
|
+
? {} extends WritableObjectDeep<T>
|
|
692
|
+
? T
|
|
693
|
+
: HasMultipleCallSignatures<T> extends true
|
|
694
|
+
? T
|
|
695
|
+
: ((...arguments_: Parameters<T>) => ReturnType<T>) &
|
|
696
|
+
WritableObjectDeep<T>
|
|
697
|
+
: T extends ReadonlyMap<unknown, unknown>
|
|
698
|
+
? WritableMapDeep<T>
|
|
699
|
+
: T extends ReadonlySet<unknown>
|
|
700
|
+
? WritableSetDeep<T>
|
|
701
|
+
: T extends ReadonlyArray<unknown>
|
|
702
|
+
? WritableArrayDeep<T>
|
|
703
|
+
: T extends object
|
|
704
|
+
? WritableObjectDeep<T>
|
|
705
|
+
: unknown
|