@tanstack/db 0.0.4 → 0.0.5

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.
Files changed (102) hide show
  1. package/dist/cjs/collection.cjs +113 -94
  2. package/dist/cjs/collection.cjs.map +1 -1
  3. package/dist/cjs/collection.d.cts +38 -11
  4. package/dist/cjs/index.cjs +1 -0
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/proxy.cjs +87 -248
  7. package/dist/cjs/proxy.cjs.map +1 -1
  8. package/dist/cjs/proxy.d.cts +5 -5
  9. package/dist/cjs/query/compiled-query.cjs +23 -14
  10. package/dist/cjs/query/compiled-query.cjs.map +1 -1
  11. package/dist/cjs/query/compiled-query.d.cts +3 -1
  12. package/dist/cjs/query/evaluators.cjs +20 -20
  13. package/dist/cjs/query/evaluators.cjs.map +1 -1
  14. package/dist/cjs/query/evaluators.d.cts +3 -2
  15. package/dist/cjs/query/extractors.cjs +20 -20
  16. package/dist/cjs/query/extractors.cjs.map +1 -1
  17. package/dist/cjs/query/extractors.d.cts +3 -3
  18. package/dist/cjs/query/group-by.cjs +12 -15
  19. package/dist/cjs/query/group-by.cjs.map +1 -1
  20. package/dist/cjs/query/group-by.d.cts +7 -7
  21. package/dist/cjs/query/joins.cjs +41 -55
  22. package/dist/cjs/query/joins.cjs.map +1 -1
  23. package/dist/cjs/query/joins.d.cts +3 -3
  24. package/dist/cjs/query/order-by.cjs +37 -84
  25. package/dist/cjs/query/order-by.cjs.map +1 -1
  26. package/dist/cjs/query/order-by.d.cts +2 -2
  27. package/dist/cjs/query/pipeline-compiler.cjs +13 -18
  28. package/dist/cjs/query/pipeline-compiler.cjs.map +1 -1
  29. package/dist/cjs/query/pipeline-compiler.d.cts +2 -1
  30. package/dist/cjs/query/query-builder.cjs +0 -12
  31. package/dist/cjs/query/query-builder.cjs.map +1 -1
  32. package/dist/cjs/query/query-builder.d.cts +4 -8
  33. package/dist/cjs/query/schema.d.cts +1 -6
  34. package/dist/cjs/query/select.cjs +35 -24
  35. package/dist/cjs/query/select.cjs.map +1 -1
  36. package/dist/cjs/query/select.d.cts +2 -2
  37. package/dist/cjs/query/types.d.cts +1 -0
  38. package/dist/cjs/transactions.cjs +17 -8
  39. package/dist/cjs/transactions.cjs.map +1 -1
  40. package/dist/cjs/types.d.cts +41 -7
  41. package/dist/esm/collection.d.ts +38 -11
  42. package/dist/esm/collection.js +113 -94
  43. package/dist/esm/collection.js.map +1 -1
  44. package/dist/esm/index.js +2 -1
  45. package/dist/esm/proxy.d.ts +5 -5
  46. package/dist/esm/proxy.js +87 -248
  47. package/dist/esm/proxy.js.map +1 -1
  48. package/dist/esm/query/compiled-query.d.ts +3 -1
  49. package/dist/esm/query/compiled-query.js +23 -14
  50. package/dist/esm/query/compiled-query.js.map +1 -1
  51. package/dist/esm/query/evaluators.d.ts +3 -2
  52. package/dist/esm/query/evaluators.js +21 -21
  53. package/dist/esm/query/evaluators.js.map +1 -1
  54. package/dist/esm/query/extractors.d.ts +3 -3
  55. package/dist/esm/query/extractors.js +20 -20
  56. package/dist/esm/query/extractors.js.map +1 -1
  57. package/dist/esm/query/group-by.d.ts +7 -7
  58. package/dist/esm/query/group-by.js +14 -17
  59. package/dist/esm/query/group-by.js.map +1 -1
  60. package/dist/esm/query/joins.d.ts +3 -3
  61. package/dist/esm/query/joins.js +42 -56
  62. package/dist/esm/query/joins.js.map +1 -1
  63. package/dist/esm/query/order-by.d.ts +2 -2
  64. package/dist/esm/query/order-by.js +39 -86
  65. package/dist/esm/query/order-by.js.map +1 -1
  66. package/dist/esm/query/pipeline-compiler.d.ts +2 -1
  67. package/dist/esm/query/pipeline-compiler.js +14 -19
  68. package/dist/esm/query/pipeline-compiler.js.map +1 -1
  69. package/dist/esm/query/query-builder.d.ts +4 -8
  70. package/dist/esm/query/query-builder.js +0 -12
  71. package/dist/esm/query/query-builder.js.map +1 -1
  72. package/dist/esm/query/schema.d.ts +1 -6
  73. package/dist/esm/query/select.d.ts +2 -2
  74. package/dist/esm/query/select.js +36 -25
  75. package/dist/esm/query/select.js.map +1 -1
  76. package/dist/esm/query/types.d.ts +1 -0
  77. package/dist/esm/transactions.js +17 -8
  78. package/dist/esm/transactions.js.map +1 -1
  79. package/dist/esm/types.d.ts +41 -7
  80. package/package.json +2 -2
  81. package/src/collection.ts +174 -121
  82. package/src/proxy.ts +141 -358
  83. package/src/query/compiled-query.ts +30 -15
  84. package/src/query/evaluators.ts +22 -21
  85. package/src/query/extractors.ts +24 -21
  86. package/src/query/group-by.ts +24 -22
  87. package/src/query/joins.ts +88 -75
  88. package/src/query/order-by.ts +56 -106
  89. package/src/query/pipeline-compiler.ts +34 -37
  90. package/src/query/query-builder.ts +9 -23
  91. package/src/query/schema.ts +1 -10
  92. package/src/query/select.ts +44 -32
  93. package/src/query/types.ts +1 -0
  94. package/src/transactions.ts +22 -13
  95. package/src/types.ts +48 -7
  96. package/dist/cjs/query/key-by.cjs +0 -43
  97. package/dist/cjs/query/key-by.cjs.map +0 -1
  98. package/dist/cjs/query/key-by.d.cts +0 -3
  99. package/dist/esm/query/key-by.d.ts +0 -3
  100. package/dist/esm/query/key-by.js +0 -43
  101. package/dist/esm/query/key-by.js.map +0 -1
  102. package/src/query/key-by.ts +0 -61
package/src/proxy.ts CHANGED
@@ -18,6 +18,7 @@ function debugLog(...args: Array<unknown>): void {
18
18
  }
19
19
  // In Node.js environment, check for environment variable (though this is primarily for browser)
20
20
  else if (
21
+ // true
21
22
  !isBrowser &&
22
23
  typeof process !== `undefined` &&
23
24
  process.env.DEBUG === `true`
@@ -34,13 +35,13 @@ interface TypedArray {
34
35
 
35
36
  // Update type for ChangeTracker
36
37
  interface ChangeTracker<T extends object> {
37
- changes: Record<string | symbol, unknown>
38
38
  originalObject: T
39
39
  modified: boolean
40
- copy_?: T
40
+ copy_: T
41
+ proxyCount: number
41
42
  assigned_: Record<string | symbol, boolean>
42
43
  parent?: {
43
- tracker: ChangeTracker<object>
44
+ tracker: ChangeTracker<Record<string | symbol, unknown>>
44
45
  prop: string | symbol
45
46
  }
46
47
  target: T
@@ -245,6 +246,12 @@ function deepEqual<T>(a: T, b: T): boolean {
245
246
  )
246
247
  }
247
248
 
249
+ let count = 0
250
+ function getProxyCount() {
251
+ count += 1
252
+ return count
253
+ }
254
+
248
255
  /**
249
256
  * Creates a proxy that tracks changes to the target object
250
257
  *
@@ -252,43 +259,90 @@ function deepEqual<T>(a: T, b: T): boolean {
252
259
  * @param parent Optional parent information
253
260
  * @returns An object containing the proxy and a function to get the changes
254
261
  */
255
- export function createChangeProxy<T extends object>(
262
+ export function createChangeProxy<
263
+ T extends Record<string | symbol, any | undefined>,
264
+ >(
256
265
  target: T,
257
- parent?: { tracker: ChangeTracker<object>; prop: string | symbol }
266
+ parent?: {
267
+ tracker: ChangeTracker<Record<string | symbol, unknown>>
268
+ prop: string | symbol
269
+ }
258
270
  ): {
259
271
  proxy: T
260
272
 
261
273
  getChanges: () => Record<string | symbol, any>
262
274
  } {
275
+ const changeProxyCache = new Map<object, object>()
276
+
277
+ function memoizedCreateChangeProxy<
278
+ TInner extends Record<string | symbol, any | undefined>,
279
+ >(
280
+ innerTarget: TInner,
281
+ innerParent?: {
282
+ tracker: ChangeTracker<Record<string | symbol, unknown>>
283
+ prop: string | symbol
284
+ }
285
+ ): {
286
+ proxy: TInner
287
+ getChanges: () => Record<string | symbol, any>
288
+ } {
289
+ debugLog(`Object ID:`, innerTarget.constructor.name)
290
+ if (changeProxyCache.has(innerTarget)) {
291
+ return changeProxyCache.get(innerTarget) as {
292
+ proxy: TInner
293
+ getChanges: () => Record<string | symbol, any>
294
+ }
295
+ } else {
296
+ const changeProxy = createChangeProxy(innerTarget, innerParent)
297
+ changeProxyCache.set(innerTarget, changeProxy)
298
+ return changeProxy
299
+ }
300
+ }
263
301
  // Create a WeakMap to cache proxies for nested objects
264
302
  // This prevents creating multiple proxies for the same object
265
303
  // and handles circular references
266
- const proxyCache = new WeakMap<object, object>()
304
+ const proxyCache = new Map<object, object>()
267
305
 
268
306
  // Create a change tracker to track changes to the object
269
307
  const changeTracker: ChangeTracker<T> = {
270
- changes: {},
271
- originalObject: deepClone(target), // Create a deep clone to preserve the original state
308
+ copy_: deepClone(target),
309
+ originalObject: deepClone(target),
310
+ proxyCount: getProxyCount(),
272
311
  modified: false,
273
312
  assigned_: {},
274
313
  parent,
275
314
  target, // Store reference to the target object
276
315
  }
277
316
 
317
+ debugLog(
318
+ `createChangeProxy called for target`,
319
+ target,
320
+ changeTracker.proxyCount
321
+ )
278
322
  // Mark this object and all its ancestors as modified
323
+ // Also propagate the actual changes up the chain
279
324
  function markChanged(state: ChangeTracker<object>) {
280
325
  if (!state.modified) {
281
326
  state.modified = true
327
+ }
282
328
 
283
- // Propagate the change up the parent chain
284
- if (state.parent) {
285
- markChanged(state.parent.tracker)
286
- }
329
+ // Propagate the change up the parent chain
330
+ if (state.parent) {
331
+ debugLog(`propagating change to parent`)
332
+
333
+ // Update parent's copy with this object's current state
334
+ state.parent.tracker.copy_[state.parent.prop] = state.copy_
335
+ state.parent.tracker.assigned_[state.parent.prop] = true
336
+
337
+ // Mark parent as changed
338
+ markChanged(state.parent.tracker)
287
339
  }
288
340
  }
289
341
 
290
342
  // Check if all properties in the current state have reverted to original values
291
- function checkIfReverted(state: ChangeTracker<object>): boolean {
343
+ function checkIfReverted(
344
+ state: ChangeTracker<Record<string | symbol, unknown>>
345
+ ): boolean {
292
346
  debugLog(
293
347
  `checkIfReverted called with assigned keys:`,
294
348
  Object.keys(state.assigned_)
@@ -307,7 +361,7 @@ export function createChangeProxy<T extends object>(
307
361
  for (const prop in state.assigned_) {
308
362
  // If this property is marked as assigned
309
363
  if (state.assigned_[prop] === true) {
310
- const currentValue = state.copy_ ? (state.copy_ as any)[prop] : null
364
+ const currentValue = state.copy_[prop]
311
365
  const originalValue = (state.originalObject as any)[prop]
312
366
 
313
367
  debugLog(
@@ -332,8 +386,8 @@ export function createChangeProxy<T extends object>(
332
386
  // Check each assigned symbol property
333
387
  const symbolProps = Object.getOwnPropertySymbols(state.assigned_)
334
388
  for (const sym of symbolProps) {
335
- if (state.assigned_[sym.toString()] === true) {
336
- const currentValue = state.copy_ ? (state.copy_ as any)[sym] : null
389
+ if (state.assigned_[sym] === true) {
390
+ const currentValue = (state.copy_ as any)[sym]
337
391
  const originalValue = (state.originalObject as any)[sym]
338
392
 
339
393
  // If the value is not equal to original, something is still changed
@@ -341,7 +395,7 @@ export function createChangeProxy<T extends object>(
341
395
  debugLog(`Symbol property is different, returning false`)
342
396
  return false
343
397
  }
344
- } else if (state.assigned_[sym.toString()] === false) {
398
+ } else if (state.assigned_[sym] === false) {
345
399
  // Property was deleted, so it's different from original
346
400
  debugLog(`Symbol property was deleted, returning false`)
347
401
  return false
@@ -353,50 +407,9 @@ export function createChangeProxy<T extends object>(
353
407
  return true
354
408
  }
355
409
 
356
- // Recursively check and update modified status based on child objects
357
- function updateModifiedStatus(state: ChangeTracker<object>): boolean {
358
- debugLog(
359
- `updateModifiedStatus called, assigned keys:`,
360
- Object.keys(state.assigned_)
361
- )
362
-
363
- // Only check for reverts if we actually have changes
364
- if (
365
- Object.keys(state.assigned_).length === 0 &&
366
- Object.getOwnPropertySymbols(state.assigned_).length === 0
367
- ) {
368
- debugLog(`No assigned properties, returning false`)
369
- return false
370
- }
371
-
372
- // If this object has direct changes that aren't reverted, it's modified
373
- const isReverted = checkIfReverted(state)
374
- debugLog(`checkIfReverted returned:`, isReverted)
375
-
376
- if (!isReverted) {
377
- debugLog(`Object has changes that aren't reverted, returning true`)
378
- return true
379
- }
380
-
381
- debugLog(`All changes reverted, clearing tracking`)
382
- // All changes have been reverted, clear the tracking
383
- state.modified = false
384
- state.changes = {}
385
- state.assigned_ = {}
386
-
387
- // If we have a parent, update its status too
388
- if (state.parent) {
389
- debugLog(`Checking parent status for prop:`, state.parent.prop)
390
- // Tell the parent this child has reverted
391
- checkParentStatus(state.parent.tracker, state.parent.prop)
392
- }
393
-
394
- return false
395
- }
396
-
397
410
  // Update parent status based on child changes
398
411
  function checkParentStatus(
399
- parentState: ChangeTracker<object>,
412
+ parentState: ChangeTracker<Record<string | symbol, unknown>>,
400
413
  childProp: string | symbol
401
414
  ) {
402
415
  debugLog(`checkParentStatus called for child prop:`, childProp)
@@ -409,7 +422,6 @@ export function createChangeProxy<T extends object>(
409
422
  debugLog(`Parent is fully reverted, clearing tracking`)
410
423
  // If everything is reverted, clear the tracking
411
424
  parentState.modified = false
412
- parentState.changes = {}
413
425
  parentState.assigned_ = {}
414
426
 
415
427
  // Continue up the chain
@@ -422,15 +434,24 @@ export function createChangeProxy<T extends object>(
422
434
 
423
435
  // Create a proxy for the target object
424
436
  function createObjectProxy<TObj extends object>(obj: TObj): TObj {
437
+ debugLog(`createObjectProxy`, obj)
425
438
  // If we've already created a proxy for this object, return it
426
439
  if (proxyCache.has(obj)) {
440
+ debugLog(`proxyCache found match`)
427
441
  return proxyCache.get(obj) as TObj
428
442
  }
429
443
 
430
444
  // Create a proxy for the object
431
445
  const proxy = new Proxy(obj, {
432
446
  get(ptarget, prop) {
433
- const value = ptarget[prop as keyof TObj]
447
+ debugLog(`get`, ptarget, prop)
448
+ const value =
449
+ changeTracker.copy_[prop as keyof T] ??
450
+ changeTracker.originalObject[prop as keyof T]
451
+
452
+ const originalValue = changeTracker.originalObject[prop as keyof T]
453
+
454
+ debugLog(`value (at top of proxy get)`, value)
434
455
 
435
456
  // If it's a getter, return the value directly
436
457
  const desc = Object.getOwnPropertyDescriptor(ptarget, prop)
@@ -459,7 +480,7 @@ export function createChangeProxy<T extends object>(
459
480
 
460
481
  if (modifyingMethods.has(methodName)) {
461
482
  return function (...args: Array<unknown>) {
462
- const result = value.apply(ptarget, args)
483
+ const result = value.apply(changeTracker.copy_, args)
463
484
  markChanged(changeTracker)
464
485
  return result
465
486
  }
@@ -476,7 +497,7 @@ export function createChangeProxy<T extends object>(
476
497
 
477
498
  if (iteratorMethods.has(methodName) || prop === Symbol.iterator) {
478
499
  return function (this: unknown, ...args: Array<unknown>) {
479
- const result = value.apply(ptarget, args)
500
+ const result = value.apply(changeTracker.copy_, args)
480
501
 
481
502
  // For forEach, we need to wrap the callback to track changes
482
503
  if (methodName === `forEach`) {
@@ -544,16 +565,14 @@ export function createChangeProxy<T extends object>(
544
565
  typeof nextResult.value[1] === `object`
545
566
  ) {
546
567
  // Create a proxy for the value and replace it in the result
547
- const { proxy: valueProxy } = createChangeProxy(
548
- nextResult.value[1],
549
- {
568
+ const { proxy: valueProxy } =
569
+ memoizedCreateChangeProxy(nextResult.value[1], {
550
570
  tracker: changeTracker,
551
571
  prop:
552
572
  typeof nextResult.value[0] === `symbol`
553
573
  ? nextResult.value[0]
554
574
  : String(nextResult.value[0]),
555
- }
556
- )
575
+ })
557
576
  nextResult.value[1] = valueProxy
558
577
  }
559
578
  } else if (
@@ -570,13 +589,11 @@ export function createChangeProxy<T extends object>(
570
589
  // For Map, we would need the key, but we don't have it here
571
590
  // So we'll use a symbol as a placeholder
572
591
  const tempKey = Symbol(`iterator-value`)
573
- const { proxy: valueProxy } = createChangeProxy(
574
- nextResult.value,
575
- {
592
+ const { proxy: valueProxy } =
593
+ memoizedCreateChangeProxy(nextResult.value, {
576
594
  tracker: changeTracker,
577
595
  prop: tempKey,
578
- }
579
- )
596
+ })
580
597
  nextResult.value = valueProxy
581
598
  }
582
599
  }
@@ -601,8 +618,8 @@ export function createChangeProxy<T extends object>(
601
618
  if (
602
619
  value &&
603
620
  typeof value === `object` &&
604
- !(value instanceof Date) &&
605
- !(value instanceof RegExp)
621
+ !((value as any) instanceof Date) &&
622
+ !((value as any) instanceof RegExp)
606
623
  ) {
607
624
  // Create a parent reference for the nested object
608
625
  const nestedParent = {
@@ -611,7 +628,10 @@ export function createChangeProxy<T extends object>(
611
628
  }
612
629
 
613
630
  // Create a proxy for the nested object
614
- const { proxy: nestedProxy } = createChangeProxy(value, nestedParent)
631
+ const { proxy: nestedProxy } = memoizedCreateChangeProxy(
632
+ originalValue,
633
+ nestedParent
634
+ )
615
635
 
616
636
  // Cache the proxy
617
637
  proxyCache.set(value, nestedProxy)
@@ -623,7 +643,7 @@ export function createChangeProxy<T extends object>(
623
643
  },
624
644
 
625
645
  set(sobj, prop, value) {
626
- const currentValue = sobj[prop as keyof TObj]
646
+ const currentValue = changeTracker.copy_[prop as keyof T]
627
647
  debugLog(
628
648
  `set called for property ${String(prop)}, current:`,
629
649
  currentValue,
@@ -631,28 +651,6 @@ export function createChangeProxy<T extends object>(
631
651
  value
632
652
  )
633
653
 
634
- // Special handling for array length changes
635
- if (Array.isArray(sobj) && prop === `length`) {
636
- const newLength = Number(value)
637
- const oldLength = sobj.length
638
-
639
- // Create a new array with the desired length
640
- const newArray = Array.from({ length: newLength }, (_, i) =>
641
- i < oldLength ? sobj[i] : undefined
642
- )
643
-
644
- // Track the change in the parent object since 'arr' is the property name
645
- if (parent) {
646
- parent.tracker.changes[parent.prop] = newArray
647
- parent.tracker.assigned_[parent.prop] = true
648
- markChanged(parent.tracker)
649
- }
650
-
651
- // Update the original array
652
- sobj.length = newLength
653
- return true
654
- }
655
-
656
654
  // Only track the change if the value is actually different
657
655
  if (!deepEqual(currentValue, value)) {
658
656
  // Check if the new value is equal to the original value
@@ -660,7 +658,9 @@ export function createChangeProxy<T extends object>(
660
658
  const originalValue = changeTracker.originalObject[prop as keyof T]
661
659
  const isRevertToOriginal = deepEqual(value, originalValue)
662
660
  debugLog(
663
- `Value different, original:`,
661
+ `value:`,
662
+ value,
663
+ `original:`,
664
664
  originalValue,
665
665
  `isRevertToOriginal:`,
666
666
  isRevertToOriginal
@@ -669,14 +669,11 @@ export function createChangeProxy<T extends object>(
669
669
  if (isRevertToOriginal) {
670
670
  debugLog(`Reverting property ${String(prop)} to original value`)
671
671
  // If the value is reverted to its original state, remove it from changes
672
- delete changeTracker.changes[prop.toString()]
673
672
  delete changeTracker.assigned_[prop.toString()]
674
673
 
675
674
  // Make sure the copy is updated with the original value
676
- if (changeTracker.copy_) {
677
- debugLog(`Updating copy with original value for ${String(prop)}`)
678
- changeTracker.copy_[prop as keyof T] = deepClone(originalValue)
679
- }
675
+ debugLog(`Updating copy with original value for ${String(prop)}`)
676
+ changeTracker.copy_[prop as keyof T] = deepClone(originalValue)
680
677
 
681
678
  // Check if all properties in this object have been reverted
682
679
  debugLog(`Checking if all properties reverted`)
@@ -687,7 +684,6 @@ export function createChangeProxy<T extends object>(
687
684
  debugLog(`All properties reverted, clearing tracking`)
688
685
  // If all have been reverted, clear tracking
689
686
  changeTracker.modified = false
690
- changeTracker.changes = {}
691
687
  changeTracker.assigned_ = {}
692
688
 
693
689
  // If we're a nested object, check if the parent needs updating
@@ -702,25 +698,15 @@ export function createChangeProxy<T extends object>(
702
698
  }
703
699
  } else {
704
700
  debugLog(`Setting new value for property ${String(prop)}`)
705
- // Create a copy of the object if it doesn't exist
706
- prepareCopy(changeTracker)
707
701
 
708
702
  // Set the value on the copy
709
- if (changeTracker.copy_) {
710
- changeTracker.copy_[prop as keyof T] = value
711
- }
712
-
713
- // Set the value on the original object
714
- obj[prop as keyof TObj] = value
703
+ changeTracker.copy_[prop as keyof T] = value
715
704
 
716
705
  // Track that this property was assigned - store using the actual property (symbol or string)
717
706
  changeTracker.assigned_[prop.toString()] = true
718
707
 
719
- // Track the change directly with the property as the key
720
- changeTracker.changes[prop.toString()] = deepClone(value)
721
-
722
708
  // Mark this object and its ancestors as modified
723
- debugLog(`Marking object and ancestors as modified`)
709
+ debugLog(`Marking object and ancestors as modified`, changeTracker)
724
710
  markChanged(changeTracker)
725
711
  }
726
712
  } else {
@@ -731,24 +717,24 @@ export function createChangeProxy<T extends object>(
731
717
  },
732
718
 
733
719
  defineProperty(ptarget, prop, descriptor) {
734
- const result = Reflect.defineProperty(ptarget, prop, descriptor)
735
- if (result) {
736
- // Track the change if the property has a value
737
- if (`value` in descriptor) {
738
- changeTracker.changes[prop.toString()] = deepClone(descriptor.value)
739
- changeTracker.assigned_[prop.toString()] = true
740
- markChanged(changeTracker)
741
- }
720
+ // const result = Reflect.defineProperty(
721
+ // changeTracker.copy_,
722
+ // prop,
723
+ // descriptor
724
+ // )
725
+ // if (result) {
726
+ if (`value` in descriptor) {
727
+ changeTracker.copy_[prop as keyof T] = deepClone(descriptor.value)
728
+ changeTracker.assigned_[prop.toString()] = true
729
+ markChanged(changeTracker)
742
730
  }
743
- return result
744
- },
745
-
746
- setPrototypeOf(ptarget, proto) {
747
- // Allow setting prototype but don't track it as a change
748
- return Object.setPrototypeOf(ptarget, proto)
731
+ // }
732
+ // return result
733
+ return true
749
734
  },
750
735
 
751
736
  deleteProperty(dobj, prop) {
737
+ debugLog(`deleteProperty`, dobj, prop)
752
738
  const stringProp = typeof prop === `symbol` ? prop.toString() : prop
753
739
 
754
740
  if (stringProp in dobj) {
@@ -756,25 +742,14 @@ export function createChangeProxy<T extends object>(
756
742
  const hadPropertyInOriginal =
757
743
  stringProp in changeTracker.originalObject
758
744
 
759
- // Create a copy of the object if it doesn't exist
760
- prepareCopy(changeTracker)
761
-
762
745
  // Delete the property from the copy
763
- if (changeTracker.copy_) {
764
- // Use type assertion to tell TypeScript this is allowed
765
- delete (changeTracker.copy_ as Record<string | symbol, unknown>)[
766
- prop
767
- ]
768
- }
769
-
770
- // Delete the property from the original object
771
746
  // Use type assertion to tell TypeScript this is allowed
772
- delete (dobj as Record<string | symbol, unknown>)[prop]
747
+ delete (changeTracker.copy_ as Record<string | symbol, unknown>)[prop]
773
748
 
774
749
  // If the property didn't exist in the original object, removing it
775
750
  // should revert to the original state
776
751
  if (!hadPropertyInOriginal) {
777
- delete changeTracker.changes[stringProp]
752
+ delete changeTracker.copy_[stringProp]
778
753
  delete changeTracker.assigned_[stringProp]
779
754
 
780
755
  // If this is the last change and we're not a nested object,
@@ -791,7 +766,7 @@ export function createChangeProxy<T extends object>(
791
766
  } else {
792
767
  // Mark this property as deleted
793
768
  changeTracker.assigned_[stringProp] = false
794
- changeTracker.changes[stringProp] = undefined
769
+ changeTracker.copy_[stringProp as keyof T] = undefined as T[keyof T]
795
770
  markChanged(changeTracker)
796
771
  }
797
772
  }
@@ -813,12 +788,8 @@ export function createChangeProxy<T extends object>(
813
788
  return {
814
789
  proxy,
815
790
  getChanges: () => {
816
- debugLog(
817
- `getChanges called, modified:`,
818
- changeTracker.modified,
819
- `assigned keys:`,
820
- Object.keys(changeTracker.assigned_)
821
- )
791
+ debugLog(`getChanges called, modified:`, changeTracker.modified)
792
+ debugLog(changeTracker)
822
793
 
823
794
  // First, check if the object is still considered modified
824
795
  if (!changeTracker.modified) {
@@ -826,181 +797,33 @@ export function createChangeProxy<T extends object>(
826
797
  return {}
827
798
  }
828
799
 
829
- // For deeply nested changes, we need to verify explicitly
800
+ // If we have a copy, return it directly
801
+ // Check if valueObj is actually an object
830
802
  if (
831
- Object.keys(changeTracker.assigned_).length === 0 &&
832
- Object.getOwnPropertySymbols(changeTracker.assigned_).length === 0
803
+ typeof changeTracker.copy_ !== `object` ||
804
+ Array.isArray(changeTracker.copy_)
833
805
  ) {
834
- debugLog(`No assigned properties, checking deep equality`)
835
-
836
- // If there are no assigned properties but the object is still marked as modified,
837
- // we should check deep equality with the original object
838
- if (changeTracker.copy_) {
839
- debugLog(`Comparing copy with original`)
840
- if (deepEqual(changeTracker.copy_, changeTracker.originalObject)) {
841
- debugLog(`Copy equals original, returning empty object`)
842
- changeTracker.modified = false
843
- return {}
844
- }
845
- } else if (deepEqual(target, changeTracker.originalObject)) {
846
- debugLog(`Target equals original, returning empty object`)
847
- changeTracker.modified = false
848
- changeTracker.changes = {}
849
- changeTracker.assigned_ = {}
850
- return {}
851
- }
852
- }
853
-
854
- debugLog(`Forcing full check for reverted state`)
855
- // Force a full check for reverted state, which will update the modified flag accordingly
856
- updateModifiedStatus(changeTracker)
857
-
858
- // If we're no longer modified after the check, return empty changes
859
- // eslint-disable-next-line
860
- if (!changeTracker.modified) {
861
- debugLog(`No longer modified after check, returning empty object`)
862
- return {}
806
+ return changeTracker.copy_
863
807
  }
864
808
 
865
- // Handle optimization case - if the object is marked modified but actually is equal to original
866
- // eslint-disable-next-line
867
- if (changeTracker.modified) {
868
- const objToCheck = changeTracker.copy_ || target
869
- debugLog(
870
- `Checking if object is equal to original:`,
871
- objToCheck,
872
- changeTracker.originalObject
873
- )
874
- if (deepEqual(objToCheck, changeTracker.originalObject)) {
875
- debugLog(`Object equals original, returning empty object`)
876
- changeTracker.modified = false
877
- changeTracker.changes = {}
878
- changeTracker.assigned_ = {}
879
- return {}
880
- }
881
- }
882
-
883
- // If there are assigned properties, return the changes
884
- if (
885
- Object.keys(changeTracker.assigned_).length > 0 ||
886
- Object.getOwnPropertySymbols(changeTracker.assigned_).length > 0
887
- ) {
888
- // If we have a copy, use it to construct the changes
889
- if (changeTracker.copy_) {
890
- const changes: Record<string | symbol, unknown> = {}
891
-
892
- // Add all assigned properties
893
- for (const key in changeTracker.assigned_) {
894
- if (changeTracker.assigned_[key] === true) {
895
- // Property was assigned
896
- changes[key] = deepClone(changeTracker.copy_[key as keyof T])
897
- } else if (changeTracker.assigned_[key] === false) {
898
- // Property was deleted
899
- changes[key] = undefined
900
- }
901
- }
902
-
903
- // Handle symbol properties - this needs special handling
904
- const symbolProps = Object.getOwnPropertySymbols(
905
- changeTracker.assigned_
906
- )
907
- for (const sym of symbolProps) {
908
- if (changeTracker.assigned_[sym.toString()] === true) {
909
- // Use the symbol directly instead of its string representation
910
-
911
- const value = (changeTracker.copy_ as any)[sym]
912
-
913
- changes[sym.toString()] = deepClone(value)
914
- }
915
- }
916
-
917
- return changes
918
- }
919
-
920
- // Fall back to the existing changes object if no copy exists
921
- return changeTracker.changes
809
+ if (Object.keys(changeTracker.assigned_).length === 0) {
810
+ return changeTracker.copy_
922
811
  }
923
812
 
924
- // If the object is modified but has no direct changes (nested changes),
925
- // but we're the root object, recursively check if unknown changes exist
926
- // eslint-disable-next-line
927
- if (changeTracker.modified && !parent) {
928
- debugLog(`Root object with nested changes, checking deep equality`)
929
-
930
- const currentState = changeTracker.copy_ || (target as any)
813
+ const result: Record<string, any | undefined> = {}
931
814
 
932
- debugLog(
933
- `Comparing current state with original:`,
934
- currentState,
935
- changeTracker.originalObject
936
- )
937
- if (deepEqual(currentState, changeTracker.originalObject)) {
938
- // The entire object has been reverted to its original state
939
- debugLog(`Current state equals original, returning empty object`)
940
- changeTracker.modified = false
941
- return {}
942
- }
943
-
944
- // One more deep check - compare the actual values
945
- // This is needed for the case where nested properties are modified and then reverted
946
- debugLog(
947
- `Comparing target with original:`,
948
- target,
949
- changeTracker.originalObject
950
- )
951
- if (deepEqual(target, changeTracker.originalObject)) {
952
- debugLog(`Target equals original, returning empty object`)
953
- changeTracker.modified = false
954
- changeTracker.changes = {}
955
- changeTracker.assigned_ = {}
956
- return {}
957
- }
958
-
959
- // Special case for nested object reverts
960
- // If we're here, we need to check if the nested objects have been reverted
961
- // even if the parent object still shows as modified
962
- // eslint-disable-next-line
963
- if (typeof target === `object` && target !== null) {
964
- let allNestedReverted = true
965
-
966
- // Check each property to see if it's been reverted to original
967
- for (const key in target) {
968
- if (Object.prototype.hasOwnProperty.call(target, key)) {
969
- const currentValue = target[key]
970
- const originalValue = changeTracker.originalObject[key as keyof T]
971
-
972
- // If this property is different from original, not all are reverted
973
- if (!deepEqual(currentValue, originalValue)) {
974
- allNestedReverted = false
975
- break
976
- }
977
- }
978
- }
979
-
980
- // If all nested properties match original values, return empty changes
981
- if (allNestedReverted) {
982
- debugLog(
983
- `All nested properties match original values, returning empty object`
984
- )
985
- changeTracker.modified = false
986
- changeTracker.changes = {}
987
- changeTracker.assigned_ = {}
988
- return {}
989
- }
815
+ // Iterate through keys in keyObj
816
+ for (const key in changeTracker.copy_) {
817
+ // If the key's value is true and the key exists in valueObj
818
+ if (
819
+ changeTracker.assigned_[key] === true &&
820
+ key in changeTracker.copy_
821
+ ) {
822
+ result[key] = changeTracker.copy_[key]
990
823
  }
991
-
992
- debugLog(
993
- `Changes detected, returning full object:`,
994
- changeTracker.copy_ || target
995
- )
996
- // Convert the copy or target to a Record type before returning
997
- const result = changeTracker.copy_ || target
998
- return result as unknown as Record<string | symbol, unknown>
999
824
  }
1000
-
1001
- // No changes
1002
- debugLog(`No changes detected, returning empty object`)
1003
- return {}
825
+ debugLog(`Returning copy:`, result)
826
+ return result as unknown as Record<string | symbol, unknown>
1004
827
  },
1005
828
  }
1006
829
  }
@@ -1062,43 +885,3 @@ export function withArrayChangeTracking<T extends object>(
1062
885
 
1063
886
  return getChanges()
1064
887
  }
1065
-
1066
- /**
1067
- * Creates a shallow copy of the target object if it doesn't already exist
1068
- */
1069
- function prepareCopy<T extends object>(state: ChangeTracker<T>) {
1070
- if (!state.copy_) {
1071
- state.copy_ = shallowCopy(state.originalObject)
1072
- }
1073
- }
1074
-
1075
- /**
1076
- * Creates a shallow copy of an object
1077
- */
1078
- function shallowCopy<T>(obj: T): T {
1079
- if (Array.isArray(obj)) {
1080
- return [...obj] as unknown as T
1081
- }
1082
-
1083
- if (obj instanceof Map) {
1084
- return new Map(obj) as unknown as T
1085
- }
1086
-
1087
- if (obj instanceof Set) {
1088
- return new Set(obj) as unknown as T
1089
- }
1090
-
1091
- if (obj instanceof Date) {
1092
- return new Date(obj.getTime()) as unknown as T
1093
- }
1094
-
1095
- if (obj instanceof RegExp) {
1096
- return new RegExp(obj.source, obj.flags) as unknown as T
1097
- }
1098
-
1099
- if (obj !== null && typeof obj === `object`) {
1100
- return { ...obj } as T
1101
- }
1102
-
1103
- return obj
1104
- }