@tanstack/db 0.5.23 → 0.5.25
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/change-events.cjs +1 -1
- package/dist/cjs/collection/change-events.cjs.map +1 -1
- package/dist/cjs/collection/changes.cjs +6 -1
- package/dist/cjs/collection/changes.cjs.map +1 -1
- package/dist/cjs/collection/lifecycle.cjs +11 -0
- package/dist/cjs/collection/lifecycle.cjs.map +1 -1
- package/dist/cjs/collection/subscription.cjs +18 -5
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/collection/subscription.d.cts +7 -1
- package/dist/cjs/indexes/base-index.cjs.map +1 -1
- package/dist/cjs/indexes/base-index.d.cts +10 -6
- package/dist/cjs/indexes/btree-index.cjs +64 -24
- package/dist/cjs/indexes/btree-index.cjs.map +1 -1
- package/dist/cjs/indexes/btree-index.d.cts +31 -9
- package/dist/cjs/indexes/reverse-index.cjs +6 -0
- package/dist/cjs/indexes/reverse-index.cjs.map +1 -1
- package/dist/cjs/indexes/reverse-index.d.cts +4 -2
- package/dist/cjs/query/builder/index.cjs +2 -2
- package/dist/cjs/query/builder/index.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +4 -1
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.cjs +111 -30
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +5 -0
- package/dist/cjs/types.d.cts +16 -0
- package/dist/cjs/utils/comparison.cjs +16 -0
- package/dist/cjs/utils/comparison.cjs.map +1 -1
- package/dist/cjs/utils/comparison.d.cts +21 -0
- package/dist/esm/collection/change-events.js +1 -1
- package/dist/esm/collection/change-events.js.map +1 -1
- package/dist/esm/collection/changes.js +6 -1
- package/dist/esm/collection/changes.js.map +1 -1
- package/dist/esm/collection/lifecycle.js +11 -0
- package/dist/esm/collection/lifecycle.js.map +1 -1
- package/dist/esm/collection/subscription.d.ts +7 -1
- package/dist/esm/collection/subscription.js +18 -5
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/indexes/base-index.d.ts +10 -6
- package/dist/esm/indexes/base-index.js.map +1 -1
- package/dist/esm/indexes/btree-index.d.ts +31 -9
- package/dist/esm/indexes/btree-index.js +65 -25
- package/dist/esm/indexes/btree-index.js.map +1 -1
- package/dist/esm/indexes/reverse-index.d.ts +4 -2
- package/dist/esm/indexes/reverse-index.js +6 -0
- package/dist/esm/indexes/reverse-index.js.map +1 -1
- package/dist/esm/query/builder/index.js +2 -2
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.js +4 -1
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.d.ts +5 -0
- package/dist/esm/query/live/collection-subscriber.js +112 -31
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/types.d.ts +16 -0
- package/dist/esm/utils/comparison.d.ts +21 -0
- package/dist/esm/utils/comparison.js +16 -0
- package/dist/esm/utils/comparison.js.map +1 -1
- package/package.json +1 -1
- package/src/collection/change-events.ts +1 -1
- package/src/collection/changes.ts +6 -1
- package/src/collection/lifecycle.ts +14 -0
- package/src/collection/subscription.ts +38 -10
- package/src/indexes/base-index.ts +19 -6
- package/src/indexes/btree-index.ts +101 -30
- package/src/indexes/reverse-index.ts +13 -2
- package/src/query/builder/index.ts +4 -4
- package/src/query/live/collection-config-builder.ts +4 -5
- package/src/query/live/collection-subscriber.ts +173 -50
- package/src/types.ts +16 -0
- package/src/utils/comparison.ts +34 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { compareKeys } from '@tanstack/db-ivm'
|
|
2
2
|
import { BTree } from '../utils/btree.js'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
defaultComparator,
|
|
5
|
+
denormalizeUndefined,
|
|
6
|
+
normalizeForBTree,
|
|
7
|
+
} from '../utils/comparison.js'
|
|
4
8
|
import { BaseIndex } from './base-index.js'
|
|
5
9
|
import type { CompareOptions } from '../query/builder/types.js'
|
|
6
10
|
import type { BasicExpression } from '../query/ir.js'
|
|
@@ -29,7 +33,7 @@ export interface RangeQueryOptions {
|
|
|
29
33
|
* This maintains items in sorted order and provides efficient range operations
|
|
30
34
|
*/
|
|
31
35
|
export class BTreeIndex<
|
|
32
|
-
TKey extends string | number = string | number,
|
|
36
|
+
TKey extends string | number | undefined = string | number | undefined,
|
|
33
37
|
> extends BaseIndex<TKey> {
|
|
34
38
|
public readonly supportedOperations = new Set<IndexOperation>([
|
|
35
39
|
`eq`,
|
|
@@ -55,7 +59,16 @@ export class BTreeIndex<
|
|
|
55
59
|
options?: any,
|
|
56
60
|
) {
|
|
57
61
|
super(id, expression, name, options)
|
|
58
|
-
|
|
62
|
+
|
|
63
|
+
// Get the base compare function
|
|
64
|
+
const baseCompareFn = options?.compareFn ?? defaultComparator
|
|
65
|
+
|
|
66
|
+
// Wrap it to denormalize sentinels before comparison
|
|
67
|
+
// This ensures UNDEFINED_SENTINEL is converted back to undefined
|
|
68
|
+
// before being passed to the baseCompareFn (which can be user-provided and is unaware of the UNDEFINED_SENTINEL)
|
|
69
|
+
this.compareFn = (a: any, b: any) =>
|
|
70
|
+
baseCompareFn(denormalizeUndefined(a), denormalizeUndefined(b))
|
|
71
|
+
|
|
59
72
|
if (options?.compareOptions) {
|
|
60
73
|
this.compareOptions = options!.compareOptions
|
|
61
74
|
}
|
|
@@ -78,7 +91,7 @@ export class BTreeIndex<
|
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
// Normalize the value for Map key usage
|
|
81
|
-
const normalizedValue =
|
|
94
|
+
const normalizedValue = normalizeForBTree(indexedValue)
|
|
82
95
|
|
|
83
96
|
// Check if this value already exists
|
|
84
97
|
if (this.valueMap.has(normalizedValue)) {
|
|
@@ -111,7 +124,7 @@ export class BTreeIndex<
|
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
// Normalize the value for Map key usage
|
|
114
|
-
const normalizedValue =
|
|
127
|
+
const normalizedValue = normalizeForBTree(indexedValue)
|
|
115
128
|
|
|
116
129
|
if (this.valueMap.has(normalizedValue)) {
|
|
117
130
|
const keySet = this.valueMap.get(normalizedValue)!
|
|
@@ -207,7 +220,7 @@ export class BTreeIndex<
|
|
|
207
220
|
* Performs an equality lookup
|
|
208
221
|
*/
|
|
209
222
|
equalityLookup(value: any): Set<TKey> {
|
|
210
|
-
const normalizedValue =
|
|
223
|
+
const normalizedValue = normalizeForBTree(value)
|
|
211
224
|
return new Set(this.valueMap.get(normalizedValue) ?? [])
|
|
212
225
|
}
|
|
213
226
|
|
|
@@ -219,10 +232,15 @@ export class BTreeIndex<
|
|
|
219
232
|
const { from, to, fromInclusive = true, toInclusive = true } = options
|
|
220
233
|
const result = new Set<TKey>()
|
|
221
234
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
const
|
|
235
|
+
// Check if from/to were explicitly provided (even if undefined)
|
|
236
|
+
// vs not provided at all (should use min/max key)
|
|
237
|
+
const hasFrom = `from` in options
|
|
238
|
+
const hasTo = `to` in options
|
|
239
|
+
|
|
240
|
+
const fromKey = hasFrom
|
|
241
|
+
? normalizeForBTree(from)
|
|
242
|
+
: this.orderedEntries.minKey()
|
|
243
|
+
const toKey = hasTo ? normalizeForBTree(to) : this.orderedEntries.maxKey()
|
|
226
244
|
|
|
227
245
|
this.orderedEntries.forRange(
|
|
228
246
|
fromKey,
|
|
@@ -250,29 +268,43 @@ export class BTreeIndex<
|
|
|
250
268
|
*/
|
|
251
269
|
rangeQueryReversed(options: RangeQueryOptions = {}): Set<TKey> {
|
|
252
270
|
const { from, to, fromInclusive = true, toInclusive = true } = options
|
|
271
|
+
const hasFrom = `from` in options
|
|
272
|
+
const hasTo = `to` in options
|
|
273
|
+
|
|
274
|
+
// Swap from/to for reversed query, respecting explicit undefined values
|
|
253
275
|
return this.rangeQuery({
|
|
254
|
-
from: to
|
|
255
|
-
to: from
|
|
276
|
+
from: hasTo ? to : this.orderedEntries.maxKey(),
|
|
277
|
+
to: hasFrom ? from : this.orderedEntries.minKey(),
|
|
256
278
|
fromInclusive: toInclusive,
|
|
257
279
|
toInclusive: fromInclusive,
|
|
258
280
|
})
|
|
259
281
|
}
|
|
260
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Internal method for taking items from the index.
|
|
285
|
+
* @param n - The number of items to return
|
|
286
|
+
* @param nextPair - Function to get the next pair from the BTree
|
|
287
|
+
* @param from - Already normalized! undefined means "start from beginning/end", sentinel means "start from the key undefined"
|
|
288
|
+
* @param filterFn - Optional filter function
|
|
289
|
+
* @param reversed - Whether to reverse the order of keys within each value
|
|
290
|
+
*/
|
|
261
291
|
private takeInternal(
|
|
262
292
|
n: number,
|
|
263
293
|
nextPair: (k?: any) => [any, any] | undefined,
|
|
264
|
-
from
|
|
294
|
+
from: any,
|
|
265
295
|
filterFn?: (key: TKey) => boolean,
|
|
266
296
|
reversed: boolean = false,
|
|
267
297
|
): Array<TKey> {
|
|
268
298
|
const keysInResult: Set<TKey> = new Set()
|
|
269
299
|
const result: Array<TKey> = []
|
|
270
300
|
let pair: [any, any] | undefined
|
|
271
|
-
let key =
|
|
301
|
+
let key = from // Use as-is - it's already normalized by the caller
|
|
272
302
|
|
|
273
303
|
while ((pair = nextPair(key)) !== undefined && result.length < n) {
|
|
274
304
|
key = pair[0]
|
|
275
|
-
const keys = this.valueMap.get(key)
|
|
305
|
+
const keys = this.valueMap.get(key) as
|
|
306
|
+
| Set<Exclude<TKey, undefined>>
|
|
307
|
+
| undefined
|
|
276
308
|
if (keys && keys.size > 0) {
|
|
277
309
|
// Sort keys for deterministic order, reverse if needed
|
|
278
310
|
const sorted = Array.from(keys).sort(compareKeys)
|
|
@@ -291,29 +323,60 @@ export class BTreeIndex<
|
|
|
291
323
|
}
|
|
292
324
|
|
|
293
325
|
/**
|
|
294
|
-
* Returns the next n items after the provided item
|
|
326
|
+
* Returns the next n items after the provided item.
|
|
295
327
|
* @param n - The number of items to return
|
|
296
|
-
* @param from - The item to start from (exclusive).
|
|
297
|
-
* @returns The next n items after the provided key.
|
|
328
|
+
* @param from - The item to start from (exclusive).
|
|
329
|
+
* @returns The next n items after the provided key.
|
|
298
330
|
*/
|
|
299
|
-
take(n: number, from
|
|
331
|
+
take(n: number, from: any, filterFn?: (key: TKey) => boolean): Array<TKey> {
|
|
300
332
|
const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)
|
|
301
|
-
|
|
333
|
+
// Normalize the from value
|
|
334
|
+
const normalizedFrom = normalizeForBTree(from)
|
|
335
|
+
return this.takeInternal(n, nextPair, normalizedFrom, filterFn)
|
|
302
336
|
}
|
|
303
337
|
|
|
304
338
|
/**
|
|
305
|
-
* Returns the
|
|
339
|
+
* Returns the first n items from the beginning.
|
|
306
340
|
* @param n - The number of items to return
|
|
307
|
-
* @param
|
|
308
|
-
* @returns The
|
|
341
|
+
* @param filterFn - Optional filter function
|
|
342
|
+
* @returns The first n items
|
|
343
|
+
*/
|
|
344
|
+
takeFromStart(n: number, filterFn?: (key: TKey) => boolean): Array<TKey> {
|
|
345
|
+
const nextPair = (k?: any) => this.orderedEntries.nextHigherPair(k)
|
|
346
|
+
// Pass undefined to mean "start from beginning" (BTree's native behavior)
|
|
347
|
+
return this.takeInternal(n, nextPair, undefined, filterFn)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Returns the next n items **before** the provided item (in descending order).
|
|
352
|
+
* @param n - The number of items to return
|
|
353
|
+
* @param from - The item to start from (exclusive). Required.
|
|
354
|
+
* @returns The next n items **before** the provided key.
|
|
309
355
|
*/
|
|
310
356
|
takeReversed(
|
|
311
357
|
n: number,
|
|
312
|
-
from
|
|
358
|
+
from: any,
|
|
359
|
+
filterFn?: (key: TKey) => boolean,
|
|
360
|
+
): Array<TKey> {
|
|
361
|
+
const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)
|
|
362
|
+
// Normalize the from value
|
|
363
|
+
const normalizedFrom = normalizeForBTree(from)
|
|
364
|
+
return this.takeInternal(n, nextPair, normalizedFrom, filterFn, true)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Returns the last n items from the end.
|
|
369
|
+
* @param n - The number of items to return
|
|
370
|
+
* @param filterFn - Optional filter function
|
|
371
|
+
* @returns The last n items
|
|
372
|
+
*/
|
|
373
|
+
takeReversedFromEnd(
|
|
374
|
+
n: number,
|
|
313
375
|
filterFn?: (key: TKey) => boolean,
|
|
314
376
|
): Array<TKey> {
|
|
315
377
|
const nextPair = (k?: any) => this.orderedEntries.nextLowerPair(k)
|
|
316
|
-
|
|
378
|
+
// Pass undefined to mean "start from end" (BTree's native behavior)
|
|
379
|
+
return this.takeInternal(n, nextPair, undefined, filterFn, true)
|
|
317
380
|
}
|
|
318
381
|
|
|
319
382
|
/**
|
|
@@ -323,7 +386,7 @@ export class BTreeIndex<
|
|
|
323
386
|
const result = new Set<TKey>()
|
|
324
387
|
|
|
325
388
|
for (const value of values) {
|
|
326
|
-
const normalizedValue =
|
|
389
|
+
const normalizedValue = normalizeForBTree(value)
|
|
327
390
|
const keys = this.valueMap.get(normalizedValue)
|
|
328
391
|
if (keys) {
|
|
329
392
|
keys.forEach((key) => result.add(key))
|
|
@@ -341,17 +404,25 @@ export class BTreeIndex<
|
|
|
341
404
|
get orderedEntriesArray(): Array<[any, Set<TKey>]> {
|
|
342
405
|
return this.orderedEntries
|
|
343
406
|
.keysArray()
|
|
344
|
-
.map((key) => [
|
|
407
|
+
.map((key) => [
|
|
408
|
+
denormalizeUndefined(key),
|
|
409
|
+
this.valueMap.get(key) ?? new Set(),
|
|
410
|
+
])
|
|
345
411
|
}
|
|
346
412
|
|
|
347
413
|
get orderedEntriesArrayReversed(): Array<[any, Set<TKey>]> {
|
|
348
|
-
return this.
|
|
349
|
-
key,
|
|
414
|
+
return this.takeReversedFromEnd(this.orderedEntries.size).map((key) => [
|
|
415
|
+
denormalizeUndefined(key),
|
|
350
416
|
this.valueMap.get(key) ?? new Set(),
|
|
351
417
|
])
|
|
352
418
|
}
|
|
353
419
|
|
|
354
420
|
get valueMapData(): Map<any, Set<TKey>> {
|
|
355
|
-
|
|
421
|
+
// Return a new Map with denormalized keys
|
|
422
|
+
const result = new Map<any, Set<TKey>>()
|
|
423
|
+
for (const [key, value] of this.valueMap) {
|
|
424
|
+
result.set(denormalizeUndefined(key), value)
|
|
425
|
+
}
|
|
426
|
+
return result
|
|
356
427
|
}
|
|
357
428
|
}
|
|
@@ -36,18 +36,29 @@ export class ReverseIndex<
|
|
|
36
36
|
return this.originalIndex.rangeQuery(options)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
take(n: number, from
|
|
39
|
+
take(n: number, from: any, filterFn?: (key: TKey) => boolean): Array<TKey> {
|
|
40
40
|
return this.originalIndex.takeReversed(n, from, filterFn)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
takeFromStart(n: number, filterFn?: (key: TKey) => boolean): Array<TKey> {
|
|
44
|
+
return this.originalIndex.takeReversedFromEnd(n, filterFn)
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
takeReversed(
|
|
44
48
|
n: number,
|
|
45
|
-
from
|
|
49
|
+
from: any,
|
|
46
50
|
filterFn?: (key: TKey) => boolean,
|
|
47
51
|
): Array<TKey> {
|
|
48
52
|
return this.originalIndex.take(n, from, filterFn)
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
takeReversedFromEnd(
|
|
56
|
+
n: number,
|
|
57
|
+
filterFn?: (key: TKey) => boolean,
|
|
58
|
+
): Array<TKey> {
|
|
59
|
+
return this.originalIndex.takeFromStart(n, filterFn)
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
get orderedEntriesArray(): Array<[any, Set<TKey>]> {
|
|
52
63
|
return this.originalIndex.orderedEntriesArrayReversed
|
|
53
64
|
}
|
|
@@ -413,9 +413,9 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
413
413
|
*/
|
|
414
414
|
having(callback: WhereCallback<TContext>): QueryBuilder<TContext> {
|
|
415
415
|
const aliases = this._getCurrentAliases()
|
|
416
|
-
// Add $selected namespace if SELECT clause exists
|
|
416
|
+
// Add $selected namespace if SELECT clause exists (either regular or functional)
|
|
417
417
|
const refProxy = (
|
|
418
|
-
this.query.select
|
|
418
|
+
this.query.select || this.query.fnSelect
|
|
419
419
|
? createRefProxyWithSelected(aliases)
|
|
420
420
|
: createRefProxy(aliases)
|
|
421
421
|
) as RefsForContext<TContext>
|
|
@@ -516,9 +516,9 @@ export class BaseQueryBuilder<TContext extends Context = Context> {
|
|
|
516
516
|
options: OrderByDirection | OrderByOptions = `asc`,
|
|
517
517
|
): QueryBuilder<TContext> {
|
|
518
518
|
const aliases = this._getCurrentAliases()
|
|
519
|
-
// Add $selected namespace if SELECT clause exists
|
|
519
|
+
// Add $selected namespace if SELECT clause exists (either regular or functional)
|
|
520
520
|
const refProxy = (
|
|
521
|
-
this.query.select
|
|
521
|
+
this.query.select || this.query.fnSelect
|
|
522
522
|
? createRefProxyWithSelected(aliases)
|
|
523
523
|
: createRefProxy(aliases)
|
|
524
524
|
) as RefsForContext<TContext>
|
|
@@ -830,17 +830,16 @@ export class CollectionConfigBuilder<
|
|
|
830
830
|
return
|
|
831
831
|
}
|
|
832
832
|
|
|
833
|
+
const subscribedToAll = this.currentSyncState?.subscribedToAllCollections
|
|
834
|
+
const allReady = this.allCollectionsReady()
|
|
835
|
+
const isLoading = this.liveQueryCollection?.isLoadingSubset
|
|
833
836
|
// Mark ready when:
|
|
834
837
|
// 1. All subscriptions are set up (subscribedToAllCollections)
|
|
835
838
|
// 2. All source collections are ready
|
|
836
839
|
// 3. The live query collection is not loading subset data
|
|
837
840
|
// This prevents marking the live query ready before its data is processed
|
|
838
841
|
// (fixes issue where useLiveQuery returns isReady=true with empty data)
|
|
839
|
-
if (
|
|
840
|
-
this.currentSyncState?.subscribedToAllCollections &&
|
|
841
|
-
this.allCollectionsReady() &&
|
|
842
|
-
!this.liveQueryCollection?.isLoadingSubset
|
|
843
|
-
) {
|
|
842
|
+
if (subscribedToAll && allReady && !isLoading) {
|
|
844
843
|
markReady()
|
|
845
844
|
}
|
|
846
845
|
}
|