@tanstack/db 0.5.24 → 0.5.26
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.map +1 -1
- package/dist/cjs/query/builder/index.d.cts +1 -1
- package/dist/cjs/query/compiler/evaluators.cjs +2 -2
- package/dist/cjs/query/compiler/evaluators.cjs.map +1 -1
- package/dist/cjs/query/compiler/joins.cjs +3 -2
- package/dist/cjs/query/compiler/joins.cjs.map +1 -1
- package/dist/cjs/query/index.d.cts +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +9 -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.d.ts +1 -1
- package/dist/esm/query/builder/index.js.map +1 -1
- package/dist/esm/query/compiler/evaluators.js +2 -2
- package/dist/esm/query/compiler/evaluators.js.map +1 -1
- package/dist/esm/query/compiler/joins.js +3 -2
- package/dist/esm/query/compiler/joins.js.map +1 -1
- package/dist/esm/query/index.d.ts +1 -1
- package/dist/esm/query/live/collection-config-builder.js +9 -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 +2 -2
- 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 +18 -0
- package/src/query/compiler/evaluators.ts +2 -2
- package/src/query/compiler/joins.ts +3 -2
- package/src/query/index.ts +17 -0
- package/src/query/live/collection-config-builder.ts +13 -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
|
}
|
|
@@ -910,8 +910,26 @@ export type QueryResult<T> = GetResult<ExtractContext<T>>
|
|
|
910
910
|
// Export the types from types.ts for convenience
|
|
911
911
|
export type {
|
|
912
912
|
Context,
|
|
913
|
+
ContextSchema,
|
|
913
914
|
Source,
|
|
914
915
|
GetResult,
|
|
915
916
|
RefLeaf as Ref,
|
|
916
917
|
InferResultType,
|
|
918
|
+
// Types used in public method signatures that must be exported
|
|
919
|
+
// for declaration emit to work (see https://github.com/TanStack/db/issues/1012)
|
|
920
|
+
SchemaFromSource,
|
|
921
|
+
InferCollectionType,
|
|
922
|
+
MergeContextWithJoinType,
|
|
923
|
+
MergeContextForJoinCallback,
|
|
924
|
+
ApplyJoinOptionalityToMergedSchema,
|
|
925
|
+
ResultTypeFromSelect,
|
|
926
|
+
WithResult,
|
|
927
|
+
JoinOnCallback,
|
|
928
|
+
RefsForContext,
|
|
929
|
+
WhereCallback,
|
|
930
|
+
OrderByCallback,
|
|
931
|
+
GroupByCallback,
|
|
932
|
+
SelectObject,
|
|
933
|
+
FunctionalHavingRow,
|
|
934
|
+
Prettify,
|
|
917
935
|
} from './types.js'
|
|
@@ -336,7 +336,7 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
336
336
|
const valueEvaluator = compiledArgs[0]!
|
|
337
337
|
const arrayEvaluator = compiledArgs[1]!
|
|
338
338
|
return (data) => {
|
|
339
|
-
const value = valueEvaluator(data)
|
|
339
|
+
const value = normalizeValue(valueEvaluator(data))
|
|
340
340
|
const array = arrayEvaluator(data)
|
|
341
341
|
// In 3-valued logic, if the value is null/undefined, return UNKNOWN
|
|
342
342
|
if (isUnknown(value)) {
|
|
@@ -345,7 +345,7 @@ function compileFunction(func: Func, isSingleRow: boolean): (data: any) => any {
|
|
|
345
345
|
if (!Array.isArray(array)) {
|
|
346
346
|
return false
|
|
347
347
|
}
|
|
348
|
-
return array.
|
|
348
|
+
return array.some((item) => normalizeValue(item) === value)
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
351
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
UnsupportedJoinSourceTypeError,
|
|
12
12
|
UnsupportedJoinTypeError,
|
|
13
13
|
} from '../../errors.js'
|
|
14
|
+
import { normalizeValue } from '../../utils/comparison.js'
|
|
14
15
|
import { ensureIndexForField } from '../../indexes/auto-index.js'
|
|
15
16
|
import { PropRef, followRef } from '../ir.js'
|
|
16
17
|
import { inArray } from '../builder/functions.js'
|
|
@@ -188,7 +189,7 @@ function processJoin(
|
|
|
188
189
|
let mainPipeline = pipeline.pipe(
|
|
189
190
|
map(([currentKey, namespacedRow]) => {
|
|
190
191
|
// Extract the join key from the main source expression
|
|
191
|
-
const mainKey = compiledMainExpr(namespacedRow)
|
|
192
|
+
const mainKey = normalizeValue(compiledMainExpr(namespacedRow))
|
|
192
193
|
|
|
193
194
|
// Return [joinKey, [originalKey, namespacedRow]]
|
|
194
195
|
return [mainKey, [currentKey, namespacedRow]] as [
|
|
@@ -205,7 +206,7 @@ function processJoin(
|
|
|
205
206
|
const namespacedRow: NamespacedRow = { [joinedSource]: row }
|
|
206
207
|
|
|
207
208
|
// Extract the join key from the joined source expression
|
|
208
|
-
const joinedKey = compiledJoinedExpr(namespacedRow)
|
|
209
|
+
const joinedKey = normalizeValue(compiledJoinedExpr(namespacedRow))
|
|
209
210
|
|
|
210
211
|
// Return [joinKey, [originalKey, namespacedRow]]
|
|
211
212
|
return [joinedKey, [currentKey, namespacedRow]] as [
|
package/src/query/index.ts
CHANGED
|
@@ -7,11 +7,28 @@ export {
|
|
|
7
7
|
type InitialQueryBuilder,
|
|
8
8
|
type QueryBuilder,
|
|
9
9
|
type Context,
|
|
10
|
+
type ContextSchema,
|
|
10
11
|
type Source,
|
|
11
12
|
type GetResult,
|
|
12
13
|
type InferResultType,
|
|
13
14
|
type ExtractContext,
|
|
14
15
|
type QueryResult,
|
|
16
|
+
// Types needed for declaration emit (https://github.com/TanStack/db/issues/1012)
|
|
17
|
+
type SchemaFromSource,
|
|
18
|
+
type InferCollectionType,
|
|
19
|
+
type MergeContextWithJoinType,
|
|
20
|
+
type MergeContextForJoinCallback,
|
|
21
|
+
type ApplyJoinOptionalityToMergedSchema,
|
|
22
|
+
type ResultTypeFromSelect,
|
|
23
|
+
type WithResult,
|
|
24
|
+
type JoinOnCallback,
|
|
25
|
+
type RefsForContext,
|
|
26
|
+
type WhereCallback,
|
|
27
|
+
type OrderByCallback,
|
|
28
|
+
type GroupByCallback,
|
|
29
|
+
type SelectObject,
|
|
30
|
+
type FunctionalHavingRow,
|
|
31
|
+
type Prettify,
|
|
15
32
|
} from './builder/index.js'
|
|
16
33
|
|
|
17
34
|
// Expression functions exports
|
|
@@ -336,6 +336,7 @@ export class CollectionConfigBuilder<
|
|
|
336
336
|
|
|
337
337
|
// Always run the graph if subscribed (eager execution)
|
|
338
338
|
if (syncState.subscribedToAllCollections) {
|
|
339
|
+
let callbackCalled = false
|
|
339
340
|
while (syncState.graph.pendingWork()) {
|
|
340
341
|
syncState.graph.run()
|
|
341
342
|
// Flush accumulated changes after each graph step to commit them as one transaction.
|
|
@@ -343,6 +344,14 @@ export class CollectionConfigBuilder<
|
|
|
343
344
|
// duplicate key errors when the full join result arrives in the same step.
|
|
344
345
|
syncState.flushPendingChanges?.()
|
|
345
346
|
callback?.()
|
|
347
|
+
callbackCalled = true
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Ensure the callback runs at least once even when the graph has no pending work.
|
|
351
|
+
// This handles lazy loading scenarios where setWindow() increases the limit or
|
|
352
|
+
// an async loadSubset completes and we need to re-check if more data is needed.
|
|
353
|
+
if (!callbackCalled) {
|
|
354
|
+
callback?.()
|
|
346
355
|
}
|
|
347
356
|
|
|
348
357
|
// On the initial run, we may need to do an empty commit to ensure that
|
|
@@ -830,17 +839,16 @@ export class CollectionConfigBuilder<
|
|
|
830
839
|
return
|
|
831
840
|
}
|
|
832
841
|
|
|
842
|
+
const subscribedToAll = this.currentSyncState?.subscribedToAllCollections
|
|
843
|
+
const allReady = this.allCollectionsReady()
|
|
844
|
+
const isLoading = this.liveQueryCollection?.isLoadingSubset
|
|
833
845
|
// Mark ready when:
|
|
834
846
|
// 1. All subscriptions are set up (subscribedToAllCollections)
|
|
835
847
|
// 2. All source collections are ready
|
|
836
848
|
// 3. The live query collection is not loading subset data
|
|
837
849
|
// This prevents marking the live query ready before its data is processed
|
|
838
850
|
// (fixes issue where useLiveQuery returns isReady=true with empty data)
|
|
839
|
-
if (
|
|
840
|
-
this.currentSyncState?.subscribedToAllCollections &&
|
|
841
|
-
this.allCollectionsReady() &&
|
|
842
|
-
!this.liveQueryCollection?.isLoadingSubset
|
|
843
|
-
) {
|
|
851
|
+
if (subscribedToAll && allReady && !isLoading) {
|
|
844
852
|
markReady()
|
|
845
853
|
}
|
|
846
854
|
}
|