@tanstack/db 0.5.6 → 0.5.8
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/subscription.cjs +10 -1
- package/dist/cjs/collection/subscription.cjs.map +1 -1
- package/dist/cjs/proxy.cjs +294 -167
- package/dist/cjs/proxy.cjs.map +1 -1
- package/dist/cjs/query/predicate-utils.cjs +15 -0
- package/dist/cjs/query/predicate-utils.cjs.map +1 -1
- package/dist/esm/collection/subscription.js +11 -2
- package/dist/esm/collection/subscription.js.map +1 -1
- package/dist/esm/proxy.js +294 -167
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query/predicate-utils.js +15 -0
- package/dist/esm/query/predicate-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/collection/subscription.ts +14 -2
- package/src/proxy.ts +492 -250
- package/src/query/predicate-utils.ts +44 -0
package/src/proxy.ts
CHANGED
|
@@ -5,6 +5,453 @@
|
|
|
5
5
|
|
|
6
6
|
import { deepEquals, isTemporal } from "./utils"
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Set of array methods that iterate with callbacks and may return elements.
|
|
10
|
+
* Hoisted to module scope to avoid creating a new Set on every property access.
|
|
11
|
+
*/
|
|
12
|
+
const CALLBACK_ITERATION_METHODS = new Set([
|
|
13
|
+
`find`,
|
|
14
|
+
`findLast`,
|
|
15
|
+
`findIndex`,
|
|
16
|
+
`findLastIndex`,
|
|
17
|
+
`filter`,
|
|
18
|
+
`map`,
|
|
19
|
+
`flatMap`,
|
|
20
|
+
`forEach`,
|
|
21
|
+
`some`,
|
|
22
|
+
`every`,
|
|
23
|
+
`reduce`,
|
|
24
|
+
`reduceRight`,
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set of array methods that modify the array in place.
|
|
29
|
+
*/
|
|
30
|
+
const ARRAY_MODIFYING_METHODS = new Set([
|
|
31
|
+
`pop`,
|
|
32
|
+
`push`,
|
|
33
|
+
`shift`,
|
|
34
|
+
`unshift`,
|
|
35
|
+
`splice`,
|
|
36
|
+
`sort`,
|
|
37
|
+
`reverse`,
|
|
38
|
+
`fill`,
|
|
39
|
+
`copyWithin`,
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Set of Map/Set methods that modify the collection in place.
|
|
44
|
+
*/
|
|
45
|
+
const MAP_SET_MODIFYING_METHODS = new Set([`set`, `delete`, `clear`, `add`])
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Set of Map/Set iterator methods.
|
|
49
|
+
*/
|
|
50
|
+
const MAP_SET_ITERATOR_METHODS = new Set([
|
|
51
|
+
`entries`,
|
|
52
|
+
`keys`,
|
|
53
|
+
`values`,
|
|
54
|
+
`forEach`,
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if a value is a proxiable object (not Date, RegExp, or Temporal)
|
|
59
|
+
*/
|
|
60
|
+
function isProxiableObject(
|
|
61
|
+
value: unknown
|
|
62
|
+
): value is Record<string | symbol, unknown> {
|
|
63
|
+
return (
|
|
64
|
+
value !== null &&
|
|
65
|
+
typeof value === `object` &&
|
|
66
|
+
!((value as any) instanceof Date) &&
|
|
67
|
+
!((value as any) instanceof RegExp) &&
|
|
68
|
+
!isTemporal(value)
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Creates a handler for array iteration methods that ensures proxied elements
|
|
74
|
+
* are passed to callbacks and returned from methods like find/filter.
|
|
75
|
+
*/
|
|
76
|
+
function createArrayIterationHandler<T extends object>(
|
|
77
|
+
methodName: string,
|
|
78
|
+
methodFn: (...args: Array<unknown>) => unknown,
|
|
79
|
+
changeTracker: ChangeTracker<T>,
|
|
80
|
+
memoizedCreateChangeProxy: (
|
|
81
|
+
obj: Record<string | symbol, unknown>,
|
|
82
|
+
parent?: {
|
|
83
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
84
|
+
prop: string | symbol
|
|
85
|
+
}
|
|
86
|
+
) => { proxy: Record<string | symbol, unknown> }
|
|
87
|
+
): ((...args: Array<unknown>) => unknown) | undefined {
|
|
88
|
+
if (!CALLBACK_ITERATION_METHODS.has(methodName)) {
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return function (...args: Array<unknown>) {
|
|
93
|
+
const callback = args[0]
|
|
94
|
+
if (typeof callback !== `function`) {
|
|
95
|
+
return methodFn.apply(changeTracker.copy_, args)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Create a helper to get proxied version of an array element
|
|
99
|
+
const getProxiedElement = (element: unknown, index: number): unknown => {
|
|
100
|
+
if (isProxiableObject(element)) {
|
|
101
|
+
const nestedParent = {
|
|
102
|
+
tracker: changeTracker as unknown as ChangeTracker<
|
|
103
|
+
Record<string | symbol, unknown>
|
|
104
|
+
>,
|
|
105
|
+
prop: String(index),
|
|
106
|
+
}
|
|
107
|
+
const { proxy: elementProxy } = memoizedCreateChangeProxy(
|
|
108
|
+
element,
|
|
109
|
+
nestedParent
|
|
110
|
+
)
|
|
111
|
+
return elementProxy
|
|
112
|
+
}
|
|
113
|
+
return element
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Wrap the callback to pass proxied elements
|
|
117
|
+
const wrappedCallback = function (
|
|
118
|
+
this: unknown,
|
|
119
|
+
element: unknown,
|
|
120
|
+
index: number,
|
|
121
|
+
array: unknown
|
|
122
|
+
) {
|
|
123
|
+
const proxiedElement = getProxiedElement(element, index)
|
|
124
|
+
return callback.call(this, proxiedElement, index, array)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// For reduce/reduceRight, the callback signature is different
|
|
128
|
+
if (methodName === `reduce` || methodName === `reduceRight`) {
|
|
129
|
+
const reduceCallback = function (
|
|
130
|
+
this: unknown,
|
|
131
|
+
accumulator: unknown,
|
|
132
|
+
element: unknown,
|
|
133
|
+
index: number,
|
|
134
|
+
array: unknown
|
|
135
|
+
) {
|
|
136
|
+
const proxiedElement = getProxiedElement(element, index)
|
|
137
|
+
return callback.call(this, accumulator, proxiedElement, index, array)
|
|
138
|
+
}
|
|
139
|
+
return methodFn.apply(changeTracker.copy_, [
|
|
140
|
+
reduceCallback,
|
|
141
|
+
...args.slice(1),
|
|
142
|
+
])
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const result = methodFn.apply(changeTracker.copy_, [
|
|
146
|
+
wrappedCallback,
|
|
147
|
+
...args.slice(1),
|
|
148
|
+
])
|
|
149
|
+
|
|
150
|
+
// For find/findLast, proxy the returned element if it's an object
|
|
151
|
+
if (
|
|
152
|
+
(methodName === `find` || methodName === `findLast`) &&
|
|
153
|
+
result &&
|
|
154
|
+
typeof result === `object`
|
|
155
|
+
) {
|
|
156
|
+
const foundIndex = (
|
|
157
|
+
changeTracker.copy_ as unknown as Array<unknown>
|
|
158
|
+
).indexOf(result)
|
|
159
|
+
if (foundIndex !== -1) {
|
|
160
|
+
return getProxiedElement(result, foundIndex)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// For filter, proxy each element in the result array
|
|
165
|
+
if (methodName === `filter` && Array.isArray(result)) {
|
|
166
|
+
return result.map((element) => {
|
|
167
|
+
const originalIndex = (
|
|
168
|
+
changeTracker.copy_ as unknown as Array<unknown>
|
|
169
|
+
).indexOf(element)
|
|
170
|
+
if (originalIndex !== -1) {
|
|
171
|
+
return getProxiedElement(element, originalIndex)
|
|
172
|
+
}
|
|
173
|
+
return element
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Creates a Symbol.iterator handler for arrays that yields proxied elements.
|
|
183
|
+
*/
|
|
184
|
+
function createArrayIteratorHandler<T extends object>(
|
|
185
|
+
changeTracker: ChangeTracker<T>,
|
|
186
|
+
memoizedCreateChangeProxy: (
|
|
187
|
+
obj: Record<string | symbol, unknown>,
|
|
188
|
+
parent?: {
|
|
189
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
190
|
+
prop: string | symbol
|
|
191
|
+
}
|
|
192
|
+
) => { proxy: Record<string | symbol, unknown> }
|
|
193
|
+
): () => Iterator<unknown> {
|
|
194
|
+
return function () {
|
|
195
|
+
const array = changeTracker.copy_ as unknown as Array<unknown>
|
|
196
|
+
let index = 0
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
next() {
|
|
200
|
+
if (index >= array.length) {
|
|
201
|
+
return { done: true, value: undefined }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const element = array[index]
|
|
205
|
+
let proxiedElement = element
|
|
206
|
+
|
|
207
|
+
if (isProxiableObject(element)) {
|
|
208
|
+
const nestedParent = {
|
|
209
|
+
tracker: changeTracker as unknown as ChangeTracker<
|
|
210
|
+
Record<string | symbol, unknown>
|
|
211
|
+
>,
|
|
212
|
+
prop: String(index),
|
|
213
|
+
}
|
|
214
|
+
const { proxy: elementProxy } = memoizedCreateChangeProxy(
|
|
215
|
+
element,
|
|
216
|
+
nestedParent
|
|
217
|
+
)
|
|
218
|
+
proxiedElement = elementProxy
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
index++
|
|
222
|
+
return { done: false, value: proxiedElement }
|
|
223
|
+
},
|
|
224
|
+
[Symbol.iterator]() {
|
|
225
|
+
return this
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Creates a wrapper for methods that modify a collection (array, Map, Set).
|
|
233
|
+
* The wrapper calls the method and marks the change tracker as modified.
|
|
234
|
+
*/
|
|
235
|
+
function createModifyingMethodHandler<T extends object>(
|
|
236
|
+
methodFn: (...args: Array<unknown>) => unknown,
|
|
237
|
+
changeTracker: ChangeTracker<T>,
|
|
238
|
+
markChanged: (tracker: ChangeTracker<T>) => void
|
|
239
|
+
): (...args: Array<unknown>) => unknown {
|
|
240
|
+
return function (...args: Array<unknown>) {
|
|
241
|
+
const result = methodFn.apply(changeTracker.copy_, args)
|
|
242
|
+
markChanged(changeTracker)
|
|
243
|
+
return result
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Creates handlers for Map/Set iterator methods (entries, keys, values, forEach).
|
|
249
|
+
* Returns proxied values for iteration to enable change tracking.
|
|
250
|
+
*/
|
|
251
|
+
function createMapSetIteratorHandler<T extends object>(
|
|
252
|
+
methodName: string,
|
|
253
|
+
prop: string | symbol,
|
|
254
|
+
methodFn: (...args: Array<unknown>) => unknown,
|
|
255
|
+
target: Map<unknown, unknown> | Set<unknown>,
|
|
256
|
+
changeTracker: ChangeTracker<T>,
|
|
257
|
+
memoizedCreateChangeProxy: (
|
|
258
|
+
obj: Record<string | symbol, unknown>,
|
|
259
|
+
parent?: {
|
|
260
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
261
|
+
prop: string | symbol
|
|
262
|
+
}
|
|
263
|
+
) => { proxy: Record<string | symbol, unknown> },
|
|
264
|
+
markChanged: (tracker: ChangeTracker<T>) => void
|
|
265
|
+
): ((...args: Array<unknown>) => unknown) | undefined {
|
|
266
|
+
const isIteratorMethod =
|
|
267
|
+
MAP_SET_ITERATOR_METHODS.has(methodName) || prop === Symbol.iterator
|
|
268
|
+
|
|
269
|
+
if (!isIteratorMethod) {
|
|
270
|
+
return undefined
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return function (this: unknown, ...args: Array<unknown>) {
|
|
274
|
+
const result = methodFn.apply(changeTracker.copy_, args)
|
|
275
|
+
|
|
276
|
+
// For forEach, wrap the callback to track changes
|
|
277
|
+
if (methodName === `forEach`) {
|
|
278
|
+
const callback = args[0]
|
|
279
|
+
if (typeof callback === `function`) {
|
|
280
|
+
const wrappedCallback = function (
|
|
281
|
+
this: unknown,
|
|
282
|
+
value: unknown,
|
|
283
|
+
key: unknown,
|
|
284
|
+
collection: unknown
|
|
285
|
+
) {
|
|
286
|
+
const cbresult = callback.call(this, value, key, collection)
|
|
287
|
+
markChanged(changeTracker)
|
|
288
|
+
return cbresult
|
|
289
|
+
}
|
|
290
|
+
return methodFn.apply(target, [wrappedCallback, ...args.slice(1)])
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// For iterators (entries, keys, values, Symbol.iterator)
|
|
295
|
+
const isValueIterator =
|
|
296
|
+
methodName === `entries` ||
|
|
297
|
+
methodName === `values` ||
|
|
298
|
+
methodName === Symbol.iterator.toString() ||
|
|
299
|
+
prop === Symbol.iterator
|
|
300
|
+
|
|
301
|
+
if (isValueIterator) {
|
|
302
|
+
const originalIterator = result as Iterator<unknown>
|
|
303
|
+
|
|
304
|
+
// For values() iterator on Maps, create a value-to-key mapping
|
|
305
|
+
const valueToKeyMap = new Map()
|
|
306
|
+
if (methodName === `values` && target instanceof Map) {
|
|
307
|
+
for (const [key, mapValue] of (
|
|
308
|
+
changeTracker.copy_ as unknown as Map<unknown, unknown>
|
|
309
|
+
).entries()) {
|
|
310
|
+
valueToKeyMap.set(mapValue, key)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// For Set iterators, create an original-to-modified mapping
|
|
315
|
+
const originalToModifiedMap = new Map()
|
|
316
|
+
if (target instanceof Set) {
|
|
317
|
+
for (const setValue of (
|
|
318
|
+
changeTracker.copy_ as unknown as Set<unknown>
|
|
319
|
+
).values()) {
|
|
320
|
+
originalToModifiedMap.set(setValue, setValue)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Return a wrapped iterator that proxies values
|
|
325
|
+
return {
|
|
326
|
+
next() {
|
|
327
|
+
const nextResult = originalIterator.next()
|
|
328
|
+
|
|
329
|
+
if (
|
|
330
|
+
!nextResult.done &&
|
|
331
|
+
nextResult.value &&
|
|
332
|
+
typeof nextResult.value === `object`
|
|
333
|
+
) {
|
|
334
|
+
// For entries, the value is a [key, value] pair
|
|
335
|
+
if (
|
|
336
|
+
methodName === `entries` &&
|
|
337
|
+
Array.isArray(nextResult.value) &&
|
|
338
|
+
nextResult.value.length === 2
|
|
339
|
+
) {
|
|
340
|
+
if (
|
|
341
|
+
nextResult.value[1] &&
|
|
342
|
+
typeof nextResult.value[1] === `object`
|
|
343
|
+
) {
|
|
344
|
+
const mapKey = nextResult.value[0]
|
|
345
|
+
const mapParent = {
|
|
346
|
+
tracker: changeTracker as unknown as ChangeTracker<
|
|
347
|
+
Record<string | symbol, unknown>
|
|
348
|
+
>,
|
|
349
|
+
prop: mapKey as string | symbol,
|
|
350
|
+
updateMap: (newValue: unknown) => {
|
|
351
|
+
if (changeTracker.copy_ instanceof Map) {
|
|
352
|
+
;(changeTracker.copy_ as Map<unknown, unknown>).set(
|
|
353
|
+
mapKey,
|
|
354
|
+
newValue
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
}
|
|
359
|
+
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
360
|
+
nextResult.value[1] as Record<string | symbol, unknown>,
|
|
361
|
+
mapParent as unknown as {
|
|
362
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
363
|
+
prop: string | symbol
|
|
364
|
+
}
|
|
365
|
+
)
|
|
366
|
+
nextResult.value[1] = valueProxy
|
|
367
|
+
}
|
|
368
|
+
} else if (
|
|
369
|
+
methodName === `values` ||
|
|
370
|
+
methodName === Symbol.iterator.toString() ||
|
|
371
|
+
prop === Symbol.iterator
|
|
372
|
+
) {
|
|
373
|
+
// For Map values(), use the key mapping
|
|
374
|
+
if (methodName === `values` && target instanceof Map) {
|
|
375
|
+
const mapKey = valueToKeyMap.get(nextResult.value)
|
|
376
|
+
if (mapKey !== undefined) {
|
|
377
|
+
const mapParent = {
|
|
378
|
+
tracker: changeTracker as unknown as ChangeTracker<
|
|
379
|
+
Record<string | symbol, unknown>
|
|
380
|
+
>,
|
|
381
|
+
prop: mapKey as string | symbol,
|
|
382
|
+
updateMap: (newValue: unknown) => {
|
|
383
|
+
if (changeTracker.copy_ instanceof Map) {
|
|
384
|
+
;(changeTracker.copy_ as Map<unknown, unknown>).set(
|
|
385
|
+
mapKey,
|
|
386
|
+
newValue
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
}
|
|
391
|
+
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
392
|
+
nextResult.value as Record<string | symbol, unknown>,
|
|
393
|
+
mapParent as unknown as {
|
|
394
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
395
|
+
prop: string | symbol
|
|
396
|
+
}
|
|
397
|
+
)
|
|
398
|
+
nextResult.value = valueProxy
|
|
399
|
+
}
|
|
400
|
+
} else if (target instanceof Set) {
|
|
401
|
+
// For Set, track modifications
|
|
402
|
+
const setOriginalValue = nextResult.value
|
|
403
|
+
const setParent = {
|
|
404
|
+
tracker: changeTracker as unknown as ChangeTracker<
|
|
405
|
+
Record<string | symbol, unknown>
|
|
406
|
+
>,
|
|
407
|
+
prop: setOriginalValue as unknown as string | symbol,
|
|
408
|
+
updateSet: (newValue: unknown) => {
|
|
409
|
+
if (changeTracker.copy_ instanceof Set) {
|
|
410
|
+
;(changeTracker.copy_ as Set<unknown>).delete(
|
|
411
|
+
setOriginalValue
|
|
412
|
+
)
|
|
413
|
+
;(changeTracker.copy_ as Set<unknown>).add(newValue)
|
|
414
|
+
originalToModifiedMap.set(setOriginalValue, newValue)
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
419
|
+
nextResult.value as Record<string | symbol, unknown>,
|
|
420
|
+
setParent as unknown as {
|
|
421
|
+
tracker: ChangeTracker<Record<string | symbol, unknown>>
|
|
422
|
+
prop: string | symbol
|
|
423
|
+
}
|
|
424
|
+
)
|
|
425
|
+
nextResult.value = valueProxy
|
|
426
|
+
} else {
|
|
427
|
+
// For other cases, use a symbol placeholder
|
|
428
|
+
const tempKey = Symbol(`iterator-value`)
|
|
429
|
+
const { proxy: valueProxy } = memoizedCreateChangeProxy(
|
|
430
|
+
nextResult.value as Record<string | symbol, unknown>,
|
|
431
|
+
{
|
|
432
|
+
tracker: changeTracker as unknown as ChangeTracker<
|
|
433
|
+
Record<string | symbol, unknown>
|
|
434
|
+
>,
|
|
435
|
+
prop: tempKey,
|
|
436
|
+
}
|
|
437
|
+
)
|
|
438
|
+
nextResult.value = valueProxy
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return nextResult
|
|
444
|
+
},
|
|
445
|
+
[Symbol.iterator]() {
|
|
446
|
+
return this
|
|
447
|
+
},
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return result
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
8
455
|
/**
|
|
9
456
|
* Simple debug utility that only logs when debug mode is enabled
|
|
10
457
|
* Set DEBUG to true in localStorage to enable debug logging
|
|
@@ -392,271 +839,66 @@ export function createChangeProxy<
|
|
|
392
839
|
// For Array methods that modify the array
|
|
393
840
|
if (Array.isArray(ptarget)) {
|
|
394
841
|
const methodName = prop.toString()
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
842
|
+
|
|
843
|
+
if (ARRAY_MODIFYING_METHODS.has(methodName)) {
|
|
844
|
+
return createModifyingMethodHandler(
|
|
845
|
+
value,
|
|
846
|
+
changeTracker,
|
|
847
|
+
markChanged
|
|
848
|
+
)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Handle array iteration methods (find, filter, forEach, etc.)
|
|
852
|
+
const iterationHandler = createArrayIterationHandler(
|
|
853
|
+
methodName,
|
|
854
|
+
value,
|
|
855
|
+
changeTracker,
|
|
856
|
+
memoizedCreateChangeProxy
|
|
857
|
+
)
|
|
858
|
+
if (iterationHandler) {
|
|
859
|
+
return iterationHandler
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Handle array Symbol.iterator for for...of loops
|
|
863
|
+
if (prop === Symbol.iterator) {
|
|
864
|
+
return createArrayIteratorHandler(
|
|
865
|
+
changeTracker,
|
|
866
|
+
memoizedCreateChangeProxy
|
|
867
|
+
)
|
|
413
868
|
}
|
|
414
869
|
}
|
|
415
870
|
|
|
416
871
|
// For Map and Set methods that modify the collection
|
|
417
872
|
if (ptarget instanceof Map || ptarget instanceof Set) {
|
|
418
873
|
const methodName = prop.toString()
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
`shift`,
|
|
427
|
-
`unshift`,
|
|
428
|
-
`splice`,
|
|
429
|
-
`sort`,
|
|
430
|
-
`reverse`,
|
|
431
|
-
])
|
|
432
|
-
|
|
433
|
-
if (modifyingMethods.has(methodName)) {
|
|
434
|
-
return function (...args: Array<unknown>) {
|
|
435
|
-
const result = value.apply(changeTracker.copy_, args)
|
|
436
|
-
markChanged(changeTracker)
|
|
437
|
-
return result
|
|
438
|
-
}
|
|
874
|
+
|
|
875
|
+
if (MAP_SET_MODIFYING_METHODS.has(methodName)) {
|
|
876
|
+
return createModifyingMethodHandler(
|
|
877
|
+
value,
|
|
878
|
+
changeTracker,
|
|
879
|
+
markChanged
|
|
880
|
+
)
|
|
439
881
|
}
|
|
440
882
|
|
|
441
883
|
// Handle iterator methods for Map and Set
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
// For forEach, we need to wrap the callback to track changes
|
|
455
|
-
if (methodName === `forEach`) {
|
|
456
|
-
const callback = args[0]
|
|
457
|
-
if (typeof callback === `function`) {
|
|
458
|
-
// Replace the original callback with our wrapped version
|
|
459
|
-
const wrappedCallback = function (
|
|
460
|
-
this: unknown,
|
|
461
|
-
// eslint-disable-next-line
|
|
462
|
-
value: unknown,
|
|
463
|
-
key: unknown,
|
|
464
|
-
collection: unknown
|
|
465
|
-
) {
|
|
466
|
-
// Call the original callback
|
|
467
|
-
const cbresult = callback.call(
|
|
468
|
-
this,
|
|
469
|
-
value,
|
|
470
|
-
key,
|
|
471
|
-
collection
|
|
472
|
-
)
|
|
473
|
-
// Mark as changed since the callback might have modified the value
|
|
474
|
-
markChanged(changeTracker)
|
|
475
|
-
return cbresult
|
|
476
|
-
}
|
|
477
|
-
// Call forEach with our wrapped callback
|
|
478
|
-
return value.apply(ptarget, [
|
|
479
|
-
wrappedCallback,
|
|
480
|
-
...args.slice(1),
|
|
481
|
-
])
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// For iterators (entries, keys, values, Symbol.iterator)
|
|
486
|
-
if (
|
|
487
|
-
methodName === `entries` ||
|
|
488
|
-
methodName === `values` ||
|
|
489
|
-
methodName === Symbol.iterator.toString() ||
|
|
490
|
-
prop === Symbol.iterator
|
|
491
|
-
) {
|
|
492
|
-
// If it's an iterator, we need to wrap the returned iterator
|
|
493
|
-
// to track changes when the values are accessed and potentially modified
|
|
494
|
-
const originalIterator = result
|
|
495
|
-
|
|
496
|
-
// For values() iterator on Maps, we need to create a value-to-key mapping
|
|
497
|
-
const valueToKeyMap = new Map()
|
|
498
|
-
if (methodName === `values` && ptarget instanceof Map) {
|
|
499
|
-
// Build a mapping from value to key for reverse lookup
|
|
500
|
-
// Use the copy_ (which is the current state) to build the mapping
|
|
501
|
-
for (const [
|
|
502
|
-
key,
|
|
503
|
-
mapValue,
|
|
504
|
-
] of changeTracker.copy_.entries()) {
|
|
505
|
-
valueToKeyMap.set(mapValue, key)
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// For Set iterators, we need to create an original-to-modified mapping
|
|
510
|
-
const originalToModifiedMap = new Map()
|
|
511
|
-
if (ptarget instanceof Set) {
|
|
512
|
-
// Initialize with original values
|
|
513
|
-
for (const setValue of changeTracker.copy_.values()) {
|
|
514
|
-
originalToModifiedMap.set(setValue, setValue)
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Create a proxy for the iterator that will mark changes when next() is called
|
|
519
|
-
return {
|
|
520
|
-
next() {
|
|
521
|
-
const nextResult = originalIterator.next()
|
|
522
|
-
|
|
523
|
-
// If we have a value and it's an object, we need to track it
|
|
524
|
-
if (
|
|
525
|
-
!nextResult.done &&
|
|
526
|
-
nextResult.value &&
|
|
527
|
-
typeof nextResult.value === `object`
|
|
528
|
-
) {
|
|
529
|
-
// For entries, the value is a [key, value] pair
|
|
530
|
-
if (
|
|
531
|
-
methodName === `entries` &&
|
|
532
|
-
Array.isArray(nextResult.value) &&
|
|
533
|
-
nextResult.value.length === 2
|
|
534
|
-
) {
|
|
535
|
-
// The value is at index 1 in the [key, value] pair
|
|
536
|
-
if (
|
|
537
|
-
nextResult.value[1] &&
|
|
538
|
-
typeof nextResult.value[1] === `object`
|
|
539
|
-
) {
|
|
540
|
-
const mapKey = nextResult.value[0]
|
|
541
|
-
// Create a special parent tracker that knows how to update the Map
|
|
542
|
-
const mapParent = {
|
|
543
|
-
tracker: changeTracker,
|
|
544
|
-
prop: mapKey,
|
|
545
|
-
updateMap: (newValue: unknown) => {
|
|
546
|
-
// Update the Map in the copy
|
|
547
|
-
if (changeTracker.copy_ instanceof Map) {
|
|
548
|
-
changeTracker.copy_.set(mapKey, newValue)
|
|
549
|
-
}
|
|
550
|
-
},
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Create a proxy for the value and replace it in the result
|
|
554
|
-
const { proxy: valueProxy } =
|
|
555
|
-
memoizedCreateChangeProxy(
|
|
556
|
-
nextResult.value[1],
|
|
557
|
-
mapParent
|
|
558
|
-
)
|
|
559
|
-
nextResult.value[1] = valueProxy
|
|
560
|
-
}
|
|
561
|
-
} else if (
|
|
562
|
-
methodName === `values` ||
|
|
563
|
-
methodName === Symbol.iterator.toString() ||
|
|
564
|
-
prop === Symbol.iterator
|
|
565
|
-
) {
|
|
566
|
-
// If the value is an object, create a proxy for it
|
|
567
|
-
if (
|
|
568
|
-
typeof nextResult.value === `object` &&
|
|
569
|
-
nextResult.value !== null
|
|
570
|
-
) {
|
|
571
|
-
// For Map values(), try to find the key using our mapping
|
|
572
|
-
if (
|
|
573
|
-
methodName === `values` &&
|
|
574
|
-
ptarget instanceof Map
|
|
575
|
-
) {
|
|
576
|
-
const mapKey = valueToKeyMap.get(nextResult.value)
|
|
577
|
-
if (mapKey !== undefined) {
|
|
578
|
-
// Create a special parent tracker for this Map value
|
|
579
|
-
const mapParent = {
|
|
580
|
-
tracker: changeTracker,
|
|
581
|
-
prop: mapKey,
|
|
582
|
-
updateMap: (newValue: unknown) => {
|
|
583
|
-
// Update the Map in the copy
|
|
584
|
-
if (changeTracker.copy_ instanceof Map) {
|
|
585
|
-
changeTracker.copy_.set(mapKey, newValue)
|
|
586
|
-
}
|
|
587
|
-
},
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
const { proxy: valueProxy } =
|
|
591
|
-
memoizedCreateChangeProxy(
|
|
592
|
-
nextResult.value,
|
|
593
|
-
mapParent
|
|
594
|
-
)
|
|
595
|
-
nextResult.value = valueProxy
|
|
596
|
-
}
|
|
597
|
-
} else if (ptarget instanceof Set) {
|
|
598
|
-
// For Set, we need to track modifications and update the Set accordingly
|
|
599
|
-
const setOriginalValue = nextResult.value
|
|
600
|
-
const setParent = {
|
|
601
|
-
tracker: changeTracker,
|
|
602
|
-
prop: setOriginalValue, // Use the original value as the prop
|
|
603
|
-
updateSet: (newValue: unknown) => {
|
|
604
|
-
// Update the Set in the copy by removing old value and adding new one
|
|
605
|
-
if (changeTracker.copy_ instanceof Set) {
|
|
606
|
-
changeTracker.copy_.delete(setOriginalValue)
|
|
607
|
-
changeTracker.copy_.add(newValue)
|
|
608
|
-
// Update our mapping for future iterations
|
|
609
|
-
originalToModifiedMap.set(
|
|
610
|
-
setOriginalValue,
|
|
611
|
-
newValue
|
|
612
|
-
)
|
|
613
|
-
}
|
|
614
|
-
},
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const { proxy: valueProxy } =
|
|
618
|
-
memoizedCreateChangeProxy(
|
|
619
|
-
nextResult.value,
|
|
620
|
-
setParent
|
|
621
|
-
)
|
|
622
|
-
nextResult.value = valueProxy
|
|
623
|
-
} else {
|
|
624
|
-
// For other cases, use a symbol as a placeholder
|
|
625
|
-
const tempKey = Symbol(`iterator-value`)
|
|
626
|
-
const { proxy: valueProxy } =
|
|
627
|
-
memoizedCreateChangeProxy(nextResult.value, {
|
|
628
|
-
tracker: changeTracker,
|
|
629
|
-
prop: tempKey,
|
|
630
|
-
})
|
|
631
|
-
nextResult.value = valueProxy
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
return nextResult
|
|
638
|
-
},
|
|
639
|
-
[Symbol.iterator]() {
|
|
640
|
-
return this
|
|
641
|
-
},
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return result
|
|
646
|
-
}
|
|
884
|
+
const iteratorHandler = createMapSetIteratorHandler(
|
|
885
|
+
methodName,
|
|
886
|
+
prop,
|
|
887
|
+
value,
|
|
888
|
+
ptarget,
|
|
889
|
+
changeTracker,
|
|
890
|
+
memoizedCreateChangeProxy,
|
|
891
|
+
markChanged
|
|
892
|
+
)
|
|
893
|
+
if (iteratorHandler) {
|
|
894
|
+
return iteratorHandler
|
|
647
895
|
}
|
|
648
896
|
}
|
|
649
897
|
return value.bind(ptarget)
|
|
650
898
|
}
|
|
651
899
|
|
|
652
900
|
// If the value is an object (but not Date, RegExp, or Temporal), create a proxy for it
|
|
653
|
-
if (
|
|
654
|
-
value &&
|
|
655
|
-
typeof value === `object` &&
|
|
656
|
-
!((value as any) instanceof Date) &&
|
|
657
|
-
!((value as any) instanceof RegExp) &&
|
|
658
|
-
!isTemporal(value)
|
|
659
|
-
) {
|
|
901
|
+
if (isProxiableObject(value)) {
|
|
660
902
|
// Create a parent reference for the nested object
|
|
661
903
|
const nestedParent = {
|
|
662
904
|
tracker: changeTracker,
|