@tanstack/db 0.1.8 → 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 +3 -20
- 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/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 +3 -20
- 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/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 +3 -25
- package/src/proxy.ts +16 -107
- package/src/utils.ts +125 -0
package/src/proxy.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* and provides a way to retrieve those changes.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { deepEquals, isTemporal } from "./utils"
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Simple debug utility that only logs when debug mode is enabled
|
|
8
10
|
* Set DEBUG to true in localStorage to enable debug logging
|
|
@@ -133,6 +135,13 @@ function deepClone<T extends unknown>(
|
|
|
133
135
|
return clone as unknown as T
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
// Handle Temporal objects
|
|
139
|
+
if (isTemporal(obj)) {
|
|
140
|
+
// Temporal objects are immutable, so we can return them directly
|
|
141
|
+
// This preserves all their internal state correctly
|
|
142
|
+
return obj
|
|
143
|
+
}
|
|
144
|
+
|
|
136
145
|
const clone = {} as Record<string | symbol, unknown>
|
|
137
146
|
visited.set(obj as object, clone)
|
|
138
147
|
|
|
@@ -156,107 +165,6 @@ function deepClone<T extends unknown>(
|
|
|
156
165
|
return clone as T
|
|
157
166
|
}
|
|
158
167
|
|
|
159
|
-
/**
|
|
160
|
-
* Deep equality check that handles special types like Date, RegExp, Map, and Set
|
|
161
|
-
*/
|
|
162
|
-
function deepEqual<T>(a: T, b: T): boolean {
|
|
163
|
-
// Handle primitive types
|
|
164
|
-
if (a === b) return true
|
|
165
|
-
|
|
166
|
-
// If either is null or not an object, they're not equal
|
|
167
|
-
if (
|
|
168
|
-
a === null ||
|
|
169
|
-
b === null ||
|
|
170
|
-
typeof a !== `object` ||
|
|
171
|
-
typeof b !== `object`
|
|
172
|
-
) {
|
|
173
|
-
return false
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Handle Date objects
|
|
177
|
-
if (a instanceof Date && b instanceof Date) {
|
|
178
|
-
return a.getTime() === b.getTime()
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Handle RegExp objects
|
|
182
|
-
if (a instanceof RegExp && b instanceof RegExp) {
|
|
183
|
-
return a.source === b.source && a.flags === b.flags
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Handle Map objects
|
|
187
|
-
if (a instanceof Map && b instanceof Map) {
|
|
188
|
-
if (a.size !== b.size) return false
|
|
189
|
-
|
|
190
|
-
const entries = Array.from(a.entries())
|
|
191
|
-
for (const [key, val] of entries) {
|
|
192
|
-
if (!b.has(key) || !deepEqual(val, b.get(key))) {
|
|
193
|
-
return false
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return true
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Handle Set objects
|
|
201
|
-
if (a instanceof Set && b instanceof Set) {
|
|
202
|
-
if (a.size !== b.size) return false
|
|
203
|
-
|
|
204
|
-
// Convert to arrays for comparison
|
|
205
|
-
const aValues = Array.from(a)
|
|
206
|
-
const bValues = Array.from(b)
|
|
207
|
-
|
|
208
|
-
// Simple comparison for primitive values
|
|
209
|
-
if (aValues.every((val) => typeof val !== `object`)) {
|
|
210
|
-
return aValues.every((val) => b.has(val))
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// For objects in sets, we need to do a more complex comparison
|
|
214
|
-
// This is a simplified approach and may not work for all cases
|
|
215
|
-
return aValues.length === bValues.length
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Handle arrays
|
|
219
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
220
|
-
if (a.length !== b.length) return false
|
|
221
|
-
|
|
222
|
-
for (let i = 0; i < a.length; i++) {
|
|
223
|
-
if (!deepEqual(a[i], b[i])) return false
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return true
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Handle TypedArrays
|
|
230
|
-
if (
|
|
231
|
-
ArrayBuffer.isView(a) &&
|
|
232
|
-
ArrayBuffer.isView(b) &&
|
|
233
|
-
!(a instanceof DataView) &&
|
|
234
|
-
!(b instanceof DataView)
|
|
235
|
-
) {
|
|
236
|
-
const typedA = a as unknown as TypedArray
|
|
237
|
-
const typedB = b as unknown as TypedArray
|
|
238
|
-
if (typedA.length !== typedB.length) return false
|
|
239
|
-
|
|
240
|
-
for (let i = 0; i < typedA.length; i++) {
|
|
241
|
-
if (typedA[i] !== typedB[i]) return false
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return true
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Handle plain objects
|
|
248
|
-
const keysA = Object.keys(a as object)
|
|
249
|
-
const keysB = Object.keys(b as object)
|
|
250
|
-
|
|
251
|
-
if (keysA.length !== keysB.length) return false
|
|
252
|
-
|
|
253
|
-
return keysA.every(
|
|
254
|
-
(key) =>
|
|
255
|
-
Object.prototype.hasOwnProperty.call(b, key) &&
|
|
256
|
-
deepEqual((a as any)[key], (b as any)[key])
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
168
|
let count = 0
|
|
261
169
|
function getProxyCount() {
|
|
262
170
|
count += 1
|
|
@@ -392,7 +300,7 @@ export function createChangeProxy<
|
|
|
392
300
|
)
|
|
393
301
|
|
|
394
302
|
// If the value is not equal to original, something is still changed
|
|
395
|
-
if (!
|
|
303
|
+
if (!deepEquals(currentValue, originalValue)) {
|
|
396
304
|
debugLog(`Property ${String(prop)} is different, returning false`)
|
|
397
305
|
return false
|
|
398
306
|
}
|
|
@@ -411,7 +319,7 @@ export function createChangeProxy<
|
|
|
411
319
|
const originalValue = (state.originalObject as any)[sym]
|
|
412
320
|
|
|
413
321
|
// If the value is not equal to original, something is still changed
|
|
414
|
-
if (!
|
|
322
|
+
if (!deepEquals(currentValue, originalValue)) {
|
|
415
323
|
debugLog(`Symbol property is different, returning false`)
|
|
416
324
|
return false
|
|
417
325
|
}
|
|
@@ -741,12 +649,13 @@ export function createChangeProxy<
|
|
|
741
649
|
return value.bind(ptarget)
|
|
742
650
|
}
|
|
743
651
|
|
|
744
|
-
// If the value is an object, create a proxy for it
|
|
652
|
+
// If the value is an object (but not Date, RegExp, or Temporal), create a proxy for it
|
|
745
653
|
if (
|
|
746
654
|
value &&
|
|
747
655
|
typeof value === `object` &&
|
|
748
656
|
!((value as any) instanceof Date) &&
|
|
749
|
-
!((value as any) instanceof RegExp)
|
|
657
|
+
!((value as any) instanceof RegExp) &&
|
|
658
|
+
!isTemporal(value)
|
|
750
659
|
) {
|
|
751
660
|
// Create a parent reference for the nested object
|
|
752
661
|
const nestedParent = {
|
|
@@ -779,11 +688,11 @@ export function createChangeProxy<
|
|
|
779
688
|
)
|
|
780
689
|
|
|
781
690
|
// Only track the change if the value is actually different
|
|
782
|
-
if (!
|
|
691
|
+
if (!deepEquals(currentValue, value)) {
|
|
783
692
|
// Check if the new value is equal to the original value
|
|
784
693
|
// Important: Use the originalObject to get the true original value
|
|
785
694
|
const originalValue = changeTracker.originalObject[prop as keyof T]
|
|
786
|
-
const isRevertToOriginal =
|
|
695
|
+
const isRevertToOriginal = deepEquals(value, originalValue)
|
|
787
696
|
debugLog(
|
|
788
697
|
`value:`,
|
|
789
698
|
value,
|
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
|
+
}
|