@tanstack/db 0.1.7 → 0.1.9
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 +6 -21
- package/dist/cjs/collection.cjs.map +1 -1
- package/dist/cjs/collection.d.cts +0 -1
- package/dist/cjs/proxy.cjs +9 -58
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.cjs +24 -17
- package/dist/cjs/query/live/collection-config-builder.cjs.map +1 -1
- package/dist/cjs/query/live/collection-config-builder.d.cts +0 -2
- package/dist/cjs/query/live/collection-subscriber.cjs +25 -16
- package/dist/cjs/query/live/collection-subscriber.cjs.map +1 -1
- package/dist/cjs/query/live/collection-subscriber.d.cts +1 -1
- package/dist/cjs/utils.cjs +75 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +5 -0
- package/dist/esm/collection.d.ts +0 -1
- package/dist/esm/collection.js +6 -21
- package/dist/esm/collection.js.map +1 -1
- package/dist/esm/proxy.js +9 -58
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/live/collection-config-builder.d.ts +0 -2
- package/dist/esm/query/live/collection-config-builder.js +24 -17
- package/dist/esm/query/live/collection-config-builder.js.map +1 -1
- package/dist/esm/query/live/collection-subscriber.d.ts +1 -1
- package/dist/esm/query/live/collection-subscriber.js +25 -16
- package/dist/esm/query/live/collection-subscriber.js.map +1 -1
- package/dist/esm/utils.d.ts +5 -0
- package/dist/esm/utils.js +76 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -2
- package/src/collection.ts +9 -26
- package/src/proxy.ts +16 -107
- package/src/query/live/collection-config-builder.ts +30 -21
- package/src/query/live/collection-subscriber.ts +44 -25
- package/src/utils.ts +125 -0
|
@@ -85,20 +85,20 @@ export class CollectionSubscriber<
|
|
|
85
85
|
changes,
|
|
86
86
|
this.collection.config.getKey
|
|
87
87
|
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
88
|
+
|
|
89
|
+
// Do not provide the callback that loads more data
|
|
90
|
+
// if there's no more data to load
|
|
91
|
+
// otherwise we end up in an infinite loop trying to load more data
|
|
92
|
+
const dataLoader = sentChanges > 0 ? callback : undefined
|
|
93
|
+
|
|
94
|
+
// We need to call `maybeRunGraph` even if there's no data to load
|
|
95
|
+
// because we need to mark the collection as ready if it's not already
|
|
96
|
+
// and that's only done in `maybeRunGraph`
|
|
97
|
+
this.collectionConfigBuilder.maybeRunGraph(
|
|
98
|
+
this.config,
|
|
99
|
+
this.syncState,
|
|
100
|
+
dataLoader
|
|
101
|
+
)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Wraps the sendChangesToPipeline function
|
|
@@ -229,7 +229,7 @@ export class CollectionSubscriber<
|
|
|
229
229
|
private subscribeToOrderedChanges(
|
|
230
230
|
whereExpression: BasicExpression<boolean> | undefined
|
|
231
231
|
) {
|
|
232
|
-
const { offset, limit, comparator } =
|
|
232
|
+
const { offset, limit, comparator, dataNeeded } =
|
|
233
233
|
this.collectionConfigBuilder.optimizableOrderByCollections[
|
|
234
234
|
this.collectionId
|
|
235
235
|
]!
|
|
@@ -245,11 +245,18 @@ export class CollectionSubscriber<
|
|
|
245
245
|
// and filter out changes that are bigger than the biggest value we've sent so far
|
|
246
246
|
// because they can't affect the topK
|
|
247
247
|
const splittedChanges = splitUpdates(changes)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
let filteredChanges = splittedChanges
|
|
249
|
+
if (dataNeeded!() === 0) {
|
|
250
|
+
// If the topK is full [..., maxSentValue] then we do not need to send changes > maxSentValue
|
|
251
|
+
// because they can never make it into the topK.
|
|
252
|
+
// However, if the topK isn't full yet, we need to also send changes > maxSentValue
|
|
253
|
+
// because they will make it into the topK
|
|
254
|
+
filteredChanges = filterChangesSmallerOrEqualToMax(
|
|
255
|
+
splittedChanges,
|
|
256
|
+
comparator,
|
|
257
|
+
this.biggest
|
|
258
|
+
)
|
|
259
|
+
}
|
|
253
260
|
this.sendChangesToPipeline(
|
|
254
261
|
filteredChanges,
|
|
255
262
|
this.loadMoreIfNeeded.bind(this)
|
|
@@ -268,11 +275,19 @@ export class CollectionSubscriber<
|
|
|
268
275
|
// This function is called by maybeRunGraph
|
|
269
276
|
// after each iteration of the query pipeline
|
|
270
277
|
// to ensure that the orderBy operator has enough data to work with
|
|
271
|
-
|
|
272
|
-
const
|
|
278
|
+
loadMoreIfNeeded() {
|
|
279
|
+
const orderByInfo =
|
|
273
280
|
this.collectionConfigBuilder.optimizableOrderByCollections[
|
|
274
281
|
this.collectionId
|
|
275
|
-
]
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
if (!orderByInfo) {
|
|
285
|
+
// This query has no orderBy operator
|
|
286
|
+
// so there's no data to load, just return true
|
|
287
|
+
return true
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const { dataNeeded } = orderByInfo
|
|
276
291
|
|
|
277
292
|
if (!dataNeeded) {
|
|
278
293
|
// This should never happen because the topK operator should always set the size callback
|
|
@@ -285,12 +300,15 @@ export class CollectionSubscriber<
|
|
|
285
300
|
// `dataNeeded` probes the orderBy operator to see if it needs more data
|
|
286
301
|
// if it needs more data, it returns the number of items it needs
|
|
287
302
|
const n = dataNeeded()
|
|
303
|
+
let noMoreNextItems = false
|
|
288
304
|
if (n > 0) {
|
|
289
|
-
this.loadNextItems(n)
|
|
305
|
+
const loadedItems = this.loadNextItems(n)
|
|
306
|
+
noMoreNextItems = loadedItems === 0
|
|
290
307
|
}
|
|
291
308
|
|
|
292
309
|
// Indicate that we're done loading data if we didn't need to load more data
|
|
293
|
-
|
|
310
|
+
// or there's no more data to load
|
|
311
|
+
return n === 0 || noMoreNextItems
|
|
294
312
|
}
|
|
295
313
|
|
|
296
314
|
private sendChangesToPipelineWithTracking(
|
|
@@ -322,6 +340,7 @@ export class CollectionSubscriber<
|
|
|
322
340
|
return { type: `insert`, key, value: this.collection.get(key) }
|
|
323
341
|
})
|
|
324
342
|
this.sendChangesToPipelineWithTracking(nextInserts)
|
|
343
|
+
return nextInserts.length
|
|
325
344
|
}
|
|
326
345
|
|
|
327
346
|
private getWhereClauseFromAlias(
|
package/src/utils.ts
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
* Generic utility functions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
interface TypedArray {
|
|
6
|
+
length: number
|
|
7
|
+
[index: number]: number
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* Deep equality function that compares two values recursively
|
|
12
|
+
* Handles primitives, objects, arrays, Date, RegExp, Map, Set, TypedArrays, and Temporal objects
|
|
7
13
|
*
|
|
8
14
|
* @param a - First value to compare
|
|
9
15
|
* @param b - Second value to compare
|
|
@@ -14,6 +20,8 @@
|
|
|
14
20
|
* deepEquals({ a: 1, b: 2 }, { b: 2, a: 1 }) // true (property order doesn't matter)
|
|
15
21
|
* deepEquals([1, { x: 2 }], [1, { x: 2 }]) // true
|
|
16
22
|
* deepEquals({ a: 1 }, { a: 2 }) // false
|
|
23
|
+
* deepEquals(new Date('2023-01-01'), new Date('2023-01-01')) // true
|
|
24
|
+
* deepEquals(new Map([['a', 1]]), new Map([['a', 1]])) // true
|
|
17
25
|
* ```
|
|
18
26
|
*/
|
|
19
27
|
export function deepEquals(a: any, b: any): boolean {
|
|
@@ -37,6 +45,102 @@ function deepEqualsInternal(
|
|
|
37
45
|
// Handle different types
|
|
38
46
|
if (typeof a !== typeof b) return false
|
|
39
47
|
|
|
48
|
+
// Handle Date objects
|
|
49
|
+
if (a instanceof Date) {
|
|
50
|
+
if (!(b instanceof Date)) return false
|
|
51
|
+
return a.getTime() === b.getTime()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle RegExp objects
|
|
55
|
+
if (a instanceof RegExp) {
|
|
56
|
+
if (!(b instanceof RegExp)) return false
|
|
57
|
+
return a.source === b.source && a.flags === b.flags
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Handle Map objects - only if both are Maps
|
|
61
|
+
if (a instanceof Map) {
|
|
62
|
+
if (!(b instanceof Map)) return false
|
|
63
|
+
if (a.size !== b.size) return false
|
|
64
|
+
|
|
65
|
+
// Check for circular references
|
|
66
|
+
if (visited.has(a)) {
|
|
67
|
+
return visited.get(a) === b
|
|
68
|
+
}
|
|
69
|
+
visited.set(a, b)
|
|
70
|
+
|
|
71
|
+
const entries = Array.from(a.entries())
|
|
72
|
+
const result = entries.every(([key, val]) => {
|
|
73
|
+
return b.has(key) && deepEqualsInternal(val, b.get(key), visited)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
visited.delete(a)
|
|
77
|
+
return result
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle Set objects - only if both are Sets
|
|
81
|
+
if (a instanceof Set) {
|
|
82
|
+
if (!(b instanceof Set)) return false
|
|
83
|
+
if (a.size !== b.size) return false
|
|
84
|
+
|
|
85
|
+
// Check for circular references
|
|
86
|
+
if (visited.has(a)) {
|
|
87
|
+
return visited.get(a) === b
|
|
88
|
+
}
|
|
89
|
+
visited.set(a, b)
|
|
90
|
+
|
|
91
|
+
// Convert to arrays for comparison
|
|
92
|
+
const aValues = Array.from(a)
|
|
93
|
+
const bValues = Array.from(b)
|
|
94
|
+
|
|
95
|
+
// Simple comparison for primitive values
|
|
96
|
+
if (aValues.every((val) => typeof val !== `object`)) {
|
|
97
|
+
visited.delete(a)
|
|
98
|
+
return aValues.every((val) => b.has(val))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// For objects in sets, we need to do a more complex comparison
|
|
102
|
+
// This is a simplified approach and may not work for all cases
|
|
103
|
+
const result = aValues.length === bValues.length
|
|
104
|
+
visited.delete(a)
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Handle TypedArrays
|
|
109
|
+
if (
|
|
110
|
+
ArrayBuffer.isView(a) &&
|
|
111
|
+
ArrayBuffer.isView(b) &&
|
|
112
|
+
!(a instanceof DataView) &&
|
|
113
|
+
!(b instanceof DataView)
|
|
114
|
+
) {
|
|
115
|
+
const typedA = a as unknown as TypedArray
|
|
116
|
+
const typedB = b as unknown as TypedArray
|
|
117
|
+
if (typedA.length !== typedB.length) return false
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < typedA.length; i++) {
|
|
120
|
+
if (typedA[i] !== typedB[i]) return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Handle Temporal objects
|
|
127
|
+
// Check if both are Temporal objects of the same type
|
|
128
|
+
if (isTemporal(a) && isTemporal(b)) {
|
|
129
|
+
const aTag = getStringTag(a)
|
|
130
|
+
const bTag = getStringTag(b)
|
|
131
|
+
|
|
132
|
+
// If they're different Temporal types, they're not equal
|
|
133
|
+
if (aTag !== bTag) return false
|
|
134
|
+
|
|
135
|
+
// Use Temporal's built-in equals method if available
|
|
136
|
+
if (typeof a.equals === `function`) {
|
|
137
|
+
return a.equals(b)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Fallback to toString comparison for other types
|
|
141
|
+
return a.toString() === b.toString()
|
|
142
|
+
}
|
|
143
|
+
|
|
40
144
|
// Handle arrays
|
|
41
145
|
if (Array.isArray(a)) {
|
|
42
146
|
if (!Array.isArray(b) || a.length !== b.length) return false
|
|
@@ -84,3 +188,24 @@ function deepEqualsInternal(
|
|
|
84
188
|
// For primitives that aren't strictly equal
|
|
85
189
|
return false
|
|
86
190
|
}
|
|
191
|
+
|
|
192
|
+
const temporalTypes = [
|
|
193
|
+
`Temporal.Duration`,
|
|
194
|
+
`Temporal.Instant`,
|
|
195
|
+
`Temporal.PlainDate`,
|
|
196
|
+
`Temporal.PlainDateTime`,
|
|
197
|
+
`Temporal.PlainMonthDay`,
|
|
198
|
+
`Temporal.PlainTime`,
|
|
199
|
+
`Temporal.PlainYearMonth`,
|
|
200
|
+
`Temporal.ZonedDateTime`,
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
function getStringTag(a: any): any {
|
|
204
|
+
return a[Symbol.toStringTag]
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Checks if the value is a Temporal object by checking for the Temporal brand */
|
|
208
|
+
export function isTemporal(a: any): boolean {
|
|
209
|
+
const tag = getStringTag(a)
|
|
210
|
+
return typeof tag === `string` && temporalTypes.includes(tag)
|
|
211
|
+
}
|