mobx-keystone 1.22.0 → 1.24.0

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 (77) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -21
  3. package/README.md +1 -1
  4. package/dist/mobx-keystone.esm.js +7198 -6242
  5. package/dist/mobx-keystone.esm.mjs +7198 -6242
  6. package/dist/mobx-keystone.umd.js +7217 -6243
  7. package/dist/types/action/runUnprotected.d.ts +8 -4
  8. package/dist/types/model/Model.d.ts +5 -0
  9. package/dist/types/modelShared/modelDecorator.d.ts +1 -1
  10. package/dist/types/modelShared/prop.d.ts +8 -4
  11. package/dist/types/snapshot/modelInitialData.d.ts +1 -0
  12. package/dist/types/tweaker/treeNodeMetadata.d.ts +1 -0
  13. package/dist/types/types/createCachedTypeCheck.d.ts +1 -0
  14. package/dist/types/utils/index.d.ts +24 -0
  15. package/package.json +15 -11
  16. package/src/action/middleware.ts +5 -18
  17. package/src/action/mutationBatch.ts +72 -0
  18. package/src/action/runUnprotected.ts +21 -8
  19. package/src/action/wrapInAction.ts +45 -15
  20. package/src/actionMiddlewares/undoMiddleware.ts +19 -13
  21. package/src/dataModel/BaseDataModel.ts +4 -8
  22. package/src/deepChange/onDeepChange.ts +246 -16
  23. package/src/model/BaseModel.ts +4 -12
  24. package/src/model/Model.ts +5 -0
  25. package/src/model/newModel.ts +68 -36
  26. package/src/modelShared/modelDecorator.ts +64 -53
  27. package/src/modelShared/prop.ts +105 -63
  28. package/src/modelShared/sharedInternalModel.ts +91 -60
  29. package/src/parent/core.ts +16 -10
  30. package/src/parent/coreObjectChildren.ts +219 -21
  31. package/src/parent/path.ts +6 -5
  32. package/src/parent/setParent.ts +5 -5
  33. package/src/patch/emitPatch.ts +102 -20
  34. package/src/ref/Ref.ts +4 -5
  35. package/src/ref/core.ts +2 -2
  36. package/src/snapshot/fromArraySnapshot.ts +11 -2
  37. package/src/snapshot/fromModelSnapshot.ts +10 -3
  38. package/src/snapshot/fromPlainObjectSnapshot.ts +18 -7
  39. package/src/snapshot/fromSnapshot.ts +31 -9
  40. package/src/snapshot/getSnapshot.ts +8 -3
  41. package/src/snapshot/internal.ts +243 -82
  42. package/src/snapshot/modelInitialData.ts +41 -0
  43. package/src/snapshot/reconcileArraySnapshot.ts +9 -3
  44. package/src/snapshot/reconcileModelSnapshot.ts +97 -17
  45. package/src/snapshot/reconcilePlainObjectSnapshot.ts +9 -3
  46. package/src/treeUtils/draft.ts +7 -8
  47. package/src/tweaker/core.ts +30 -5
  48. package/src/tweaker/treeNodeMetadata.ts +38 -0
  49. package/src/tweaker/tweak.ts +15 -4
  50. package/src/tweaker/tweakArray.ts +159 -121
  51. package/src/tweaker/tweakFrozen.ts +2 -2
  52. package/src/tweaker/tweakModel.ts +2 -2
  53. package/src/tweaker/tweakPlainObject.ts +117 -124
  54. package/src/tweaker/typeChecking.ts +10 -1
  55. package/src/types/TypeChecker.ts +261 -15
  56. package/src/types/arrayBased/typesArray.ts +38 -40
  57. package/src/types/arrayBased/typesTuple.ts +51 -34
  58. package/src/types/createCachedTypeCheck.ts +181 -0
  59. package/src/types/objectBased/typesArraySet.ts +42 -32
  60. package/src/types/objectBased/typesDataModelData.ts +18 -11
  61. package/src/types/objectBased/typesModel.ts +1 -1
  62. package/src/types/objectBased/typesObject.ts +57 -28
  63. package/src/types/objectBased/typesObjectMap.ts +51 -40
  64. package/src/types/objectBased/typesRecord.ts +59 -39
  65. package/src/types/objectBased/typesRef.ts +1 -1
  66. package/src/types/primitiveBased/typesPrimitive.ts +9 -9
  67. package/src/types/tProp.ts +80 -44
  68. package/src/types/utility/typesOr.ts +35 -24
  69. package/src/types/utility/typesRefinement.ts +21 -8
  70. package/src/types/utility/typesSkipCheck.ts +21 -8
  71. package/src/types/utility/typesTag.ts +21 -9
  72. package/src/types/utility/typesUnchecked.ts +2 -3
  73. package/src/utils/ModelPool.ts +34 -19
  74. package/src/utils/errorDiagnostics.ts +60 -18
  75. package/src/utils/index.ts +142 -9
  76. package/src/types/createPerEntryCachedCheck.ts +0 -166
  77. /package/dist/types/{types/createPerEntryCachedCheck.d.ts → action/mutationBatch.d.ts} +0 -0
@@ -9,24 +9,25 @@ import {
9
9
  observe,
10
10
  } from "mobx"
11
11
  import { assertCanWrite } from "../action/protection"
12
- import { DeepChangeType, emitDeepChange, getIsInInitPhase } from "../deepChange/onDeepChange"
12
+ import { emitArraySpliceDeepChange, emitArrayUpdateDeepChange } from "../deepChange/onDeepChange"
13
13
  import { getGlobalConfig } from "../globalConfig"
14
14
  import type { ParentPath } from "../parent/path"
15
- import type { Path } from "../parent/pathTypes"
16
15
  import { setParent } from "../parent/setParent"
17
- import { InternalPatchRecorder } from "../patch/emitPatch"
16
+ import { hasPatchListenersFor, InternalPatchRecorder } from "../patch/emitPatch"
18
17
  import type { Patch } from "../patch/Patch"
19
18
  import {
19
+ flushInternalSnapshot,
20
20
  freezeInternalSnapshot,
21
21
  getInternalSnapshot,
22
22
  setNewInternalSnapshot,
23
23
  updateInternalSnapshot,
24
24
  } from "../snapshot/internal"
25
25
  import { failure, inDevMode, isArray, isPrimitive } from "../utils"
26
- import { runningWithoutSnapshotOrPatches, tweakedObjects } from "./core"
26
+ import { runningWithoutSnapshotOrPatches, setTweakedObjectUntweakers } from "./core"
27
27
  import { TweakerPriority } from "./TweakerPriority"
28
+ import { markAsTweakedObject } from "./treeNodeMetadata"
28
29
  import { registerTweaker, tweak } from "./tweak"
29
- import { runTypeCheckingAfterChange } from "./typeChecking"
30
+ import { isTypeCheckingAfterChangeEnabled, runTypeCheckingAfterChange } from "./typeChecking"
30
31
 
31
32
  /**
32
33
  * @internal
@@ -55,15 +56,9 @@ export function tweakArray<T extends any[]>(
55
56
  buffer = new Array(arrLn)
56
57
  }
57
58
 
58
- let interceptDisposer: () => void
59
- let observeDisposer: () => void
60
-
61
- const untweak = () => {
62
- interceptDisposer()
63
- observeDisposer()
64
- }
65
-
66
- tweakedObjects.set(tweakedArr, untweak)
59
+ // Mark it as tweaked before processing children. The cleanup functions are
60
+ // installed after the MobX handlers have been registered.
61
+ markAsTweakedObject(tweakedArr)
67
62
  setParent(
68
63
  tweakedArr, // value
69
64
  parentPath,
@@ -76,37 +71,10 @@ export function tweakArray<T extends any[]>(
76
71
  const untransformedSn: any[] = []
77
72
  untransformedSn.length = arrLn
78
73
 
79
- // externally provided observable arrays keep unchanged slots untouched, but
80
- // consecutive child replacements can still be applied as single splice ranges
81
- let replacementRangeStart = -1
82
- let replacementRangeItems: any[] | undefined
83
-
84
- const flushReplacementRange = () => {
85
- if (replacementRangeStart >= 0) {
86
- tweakedArr.spliceWithArray(
87
- replacementRangeStart,
88
- replacementRangeItems!.length,
89
- replacementRangeItems!
90
- )
91
- replacementRangeStart = -1
92
- replacementRangeItems = undefined
93
- }
94
- }
95
-
96
- const queueReplacementIfDifferent = (index: number, value: unknown) => {
97
- if (tweakedArr[index] === value && index in tweakedArr) {
98
- flushReplacementRange()
99
- return
100
- }
101
-
102
- if (replacementRangeStart + (replacementRangeItems?.length ?? 0) === index) {
103
- replacementRangeItems!.push(value)
104
- } else {
105
- flushReplacementRange()
106
- replacementRangeStart = index
107
- replacementRangeItems = [value]
108
- }
109
- }
74
+ // Only externally supplied observable arrays need replacement ranges. Fresh
75
+ // arrays use the bulk buffer path, while snapshot arrays are already populated.
76
+ const replacementQueue =
77
+ !buffer && !childrenAlreadyTweaked ? new ObservableArrayReplacementQueue(tweakedArr) : undefined
110
78
 
111
79
  // substitute initial values by proxied values
112
80
  for (let i = 0; i < arrLn; i++) {
@@ -116,7 +84,7 @@ export function tweakArray<T extends any[]>(
116
84
  if (buffer) {
117
85
  buffer[i] = v
118
86
  } else if (!childrenAlreadyTweaked) {
119
- queueReplacementIfDifferent(i, v)
87
+ replacementQueue!.queueIfDifferent(i, v)
120
88
  }
121
89
 
122
90
  untransformedSn[i] = v
@@ -141,7 +109,7 @@ export function tweakArray<T extends any[]>(
141
109
  if (buffer) {
142
110
  buffer[i] = tweakedValue
143
111
  } else if (!childrenAlreadyTweaked) {
144
- queueReplacementIfDifferent(i, tweakedValue)
112
+ replacementQueue!.queueIfDifferent(i, tweakedValue)
145
113
  }
146
114
 
147
115
  const valueSn = getInternalSnapshot(tweakedValue)!
@@ -154,12 +122,16 @@ export function tweakArray<T extends any[]>(
154
122
  // is a single plain mobx bulk insertion
155
123
  tweakedArr.replace(buffer)
156
124
  } else {
157
- flushReplacementRange()
125
+ replacementQueue?.flush()
158
126
  }
159
127
  setNewInternalSnapshot(tweakedArr, untransformedSn, undefined)
160
128
 
161
- interceptDisposer = intercept(tweakedArr, interceptArrayMutation.bind(undefined, tweakedArr))
162
- observeDisposer = observe(tweakedArr, arrayDidChange)
129
+ const interceptDisposer = intercept(
130
+ tweakedArr,
131
+ interceptArrayMutation.bind(undefined, tweakedArr)
132
+ )
133
+ const observeDisposer = observe(tweakedArr, arrayDidChange)
134
+ setTweakedObjectUntweakers(tweakedArr, interceptDisposer, observeDisposer)
163
135
 
164
136
  return tweakedArr as any
165
137
  }
@@ -174,43 +146,18 @@ function mutateSplice(index: number, removedCount: number, addedItems: any[], sn
174
146
 
175
147
  const patchRecorder = new InternalPatchRecorder()
176
148
 
177
- const emptyPath: Path = Object.freeze([])
178
-
179
149
  function arrayDidChange(change: IArrayDidChange) {
180
150
  const arr = change.object
181
- const oldSnapshot = getInternalSnapshot(arr as Array<unknown>)!.untransformed
182
151
 
183
152
  patchRecorder.reset()
184
153
 
185
- let mutate: ((sn: any[]) => void) | undefined
186
-
187
154
  switch (change.type) {
188
155
  case "splice":
189
- mutate = arrayDidChangeSplice(change, oldSnapshot)
190
- // Emit deep change for splice
191
- emitDeepChange(arr, {
192
- type: DeepChangeType.ArraySplice,
193
- target: arr,
194
- path: emptyPath,
195
- index: change.index as number,
196
- addedValues: change.added,
197
- removedValues: change.removed,
198
- isInit: getIsInInitPhase(),
199
- })
156
+ emitArraySpliceDeepChange(arr, arr, change.index as number, change.added, change.removed)
200
157
  break
201
158
 
202
159
  case "update":
203
- mutate = arrayDidChangeUpdate(change, oldSnapshot)
204
- // Emit deep change for update
205
- emitDeepChange(arr, {
206
- type: DeepChangeType.ArrayUpdate,
207
- target: arr,
208
- path: emptyPath,
209
- index: change.index as number,
210
- newValue: change.newValue,
211
- oldValue: change.oldValue,
212
- isInit: getIsInInitPhase(),
213
- })
160
+ emitArrayUpdateDeepChange(arr, arr, change.index as number, change.newValue, change.oldValue)
214
161
 
215
162
  break
216
163
 
@@ -218,9 +165,38 @@ function arrayDidChange(change: IArrayDidChange) {
218
165
  break
219
166
  }
220
167
 
221
- runTypeCheckingAfterChange(arr, patchRecorder)
168
+ if (runningWithoutSnapshotOrPatches) {
169
+ return
170
+ }
171
+
172
+ const oldSnapshot = getInternalSnapshot(arr as Array<unknown>)!.untransformed
173
+ const shouldEmitPatches = hasPatchListenersFor(arr)
174
+ const shouldBuildInversePatches = shouldEmitPatches || isTypeCheckingAfterChangeEnabled()
175
+
176
+ let mutate: ((sn: any[]) => void) | undefined
177
+ switch (change.type) {
178
+ case "splice":
179
+ mutate = arrayDidChangeSplice(
180
+ change,
181
+ oldSnapshot,
182
+ shouldEmitPatches,
183
+ shouldBuildInversePatches
184
+ )
185
+ break
186
+ case "update":
187
+ mutate = arrayDidChangeUpdate(
188
+ change,
189
+ oldSnapshot,
190
+ shouldEmitPatches,
191
+ shouldBuildInversePatches
192
+ )
193
+ break
194
+ default:
195
+ break
196
+ }
222
197
 
223
- if (!runningWithoutSnapshotOrPatches && mutate) {
198
+ runTypeCheckingAfterChange(arr, patchRecorder)
199
+ if (mutate) {
224
200
  updateInternalSnapshot(arr, mutate)
225
201
  patchRecorder.emit(arr)
226
202
  }
@@ -229,7 +205,12 @@ function arrayDidChange(change: IArrayDidChange) {
229
205
  const undefinedInsideArrayErrorMsg =
230
206
  "undefined is not supported inside arrays since it is not serializable in JSON, consider using null instead"
231
207
 
232
- function arrayDidChangeUpdate(change: any /*IArrayDidChange*/, oldSnapshot: any) {
208
+ function arrayDidChangeUpdate(
209
+ change: any /*IArrayDidChange*/,
210
+ oldSnapshot: any,
211
+ shouldEmitPatches: boolean,
212
+ shouldBuildInversePatches: boolean
213
+ ) {
233
214
  const k = change.index
234
215
  const val = change.newValue
235
216
  const oldVal = oldSnapshot[k]
@@ -242,28 +223,25 @@ function arrayDidChangeUpdate(change: any /*IArrayDidChange*/, oldSnapshot: any)
242
223
  }
243
224
  const mutate = mutateSet.bind(undefined, k, newVal)
244
225
 
245
- const path = [k]
246
-
247
- patchRecorder.record(
248
- [
249
- {
250
- op: "replace",
251
- path,
252
- value: freezeInternalSnapshot(newVal),
253
- },
254
- ],
255
- [
256
- {
257
- op: "replace",
258
- path,
259
- value: freezeInternalSnapshot(oldVal),
260
- },
261
- ]
262
- )
226
+ if (shouldEmitPatches || shouldBuildInversePatches) {
227
+ const path = [k]
228
+ const patches = shouldEmitPatches
229
+ ? [{ op: "replace" as const, path, value: freezeInternalSnapshot(newVal) }]
230
+ : undefined
231
+ const invPatches = shouldBuildInversePatches
232
+ ? [{ op: "replace" as const, path, value: freezeInternalSnapshot(oldVal) }]
233
+ : undefined
234
+ patchRecorder.record(patches, invPatches)
235
+ }
263
236
  return mutate
264
237
  }
265
238
 
266
- function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any) {
239
+ function arrayDidChangeSplice(
240
+ change: any /*IArrayDidChange*/,
241
+ oldSnapshot: any,
242
+ shouldEmitPatches: boolean,
243
+ shouldBuildInversePatches: boolean
244
+ ) {
267
245
  const index = change.index as number
268
246
  const addedCount = change.addedCount as number
269
247
  const removedCount = change.removedCount as number
@@ -279,11 +257,19 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
279
257
  }
280
258
  }
281
259
 
282
- const oldLen = oldSnapshot.length
283
260
  const mutate = mutateSplice.bind(undefined, index, removedCount, addedItems)
284
261
 
285
- const patches: Patch[] = []
286
- const invPatches: Patch[] = []
262
+ // With neither patch listeners nor type checking, there is no inverse-patch
263
+ // data to build. The snapshot mutation above is all the post-change work we
264
+ // need to retain.
265
+ if (!shouldEmitPatches && !shouldBuildInversePatches) {
266
+ return mutate
267
+ }
268
+
269
+ const oldLen = oldSnapshot.length
270
+
271
+ const patches: Patch[] | undefined = shouldEmitPatches ? [] : undefined
272
+ const invPatches: Patch[] | undefined = shouldBuildInversePatches ? [] : undefined
287
273
 
288
274
  // optimization: if we add as many as we remove then remove/readd instead
289
275
 
@@ -294,8 +280,8 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
294
280
  // validation might fail
295
281
 
296
282
  if (addedCount === removedCount) {
297
- const readdPatches: Patch[] = []
298
- const readdInvPatches: Patch[] = []
283
+ const readdPatches: Patch[] | undefined = shouldEmitPatches ? [] : undefined
284
+ const readdInvPatches: Patch[] | undefined = shouldBuildInversePatches ? [] : undefined
299
285
  let removed = 0
300
286
 
301
287
  for (let i = 0; i < addedCount; i++) {
@@ -306,11 +292,11 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
306
292
 
307
293
  if (newVal !== oldVal) {
308
294
  const removePath = [realIndex - removed]
309
- patches.push({
295
+ patches?.push({
310
296
  op: "remove",
311
297
  path: removePath,
312
298
  })
313
- invPatches.push({
299
+ invPatches?.push({
314
300
  op: "remove",
315
301
  path: removePath,
316
302
  })
@@ -318,13 +304,13 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
318
304
  removed++
319
305
 
320
306
  const readdPath = [realIndex]
321
- readdPatches.push({
307
+ readdPatches?.push({
322
308
  op: "add",
323
309
  path: readdPath,
324
310
  value: freezeInternalSnapshot(newVal),
325
311
  })
326
312
 
327
- readdInvPatches.push({
313
+ readdInvPatches?.push({
328
314
  op: "add",
329
315
  path: readdPath,
330
316
  value: freezeInternalSnapshot(oldVal),
@@ -332,10 +318,17 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
332
318
  }
333
319
  }
334
320
 
335
- patches.push(...readdPatches)
336
- invPatches.push(...readdInvPatches)
337
- // we need to reverse since inverse patches are applied in reverse
338
- invPatches.reverse()
321
+ if (patches && readdPatches && readdPatches.length > 0) {
322
+ patches.push(...readdPatches)
323
+ }
324
+ if (invPatches && readdInvPatches && readdInvPatches.length > 0) {
325
+ invPatches.push(...readdInvPatches)
326
+ }
327
+ // We need to reverse once since inverse patches are applied in reverse.
328
+ // Repeated unshift calls here would make a large splice quadratic.
329
+ if (invPatches && invPatches.length > 0) {
330
+ invPatches.reverse()
331
+ }
339
332
  } else {
340
333
  const interimLen = oldLen - removedCount
341
334
 
@@ -344,7 +337,7 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
344
337
  // optimization, when removing from the end set the length instead
345
338
  const removeUsingSetLength = index >= interimLen
346
339
  if (removeUsingSetLength) {
347
- patches.push({
340
+ patches?.push({
348
341
  op: "replace",
349
342
  path: ["length"],
350
343
  value: interimLen,
@@ -357,14 +350,14 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
357
350
 
358
351
  if (!removeUsingSetLength) {
359
352
  // remove ...2, 1, 0
360
- patches.push({
353
+ patches?.push({
361
354
  op: "remove",
362
355
  path,
363
356
  })
364
357
  }
365
358
 
366
359
  // add 0, 1, 2... since inverse patches are applied in reverse
367
- invPatches.push({
360
+ invPatches?.push({
368
361
  op: "add",
369
362
  path,
370
363
  value: freezeInternalSnapshot(oldSnapshot[realIndex]),
@@ -377,7 +370,7 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
377
370
  // optimization, for inverse patches, when adding from the end set the length to restore instead
378
371
  const restoreUsingSetLength = index >= interimLen
379
372
  if (restoreUsingSetLength) {
380
- invPatches.push({
373
+ invPatches?.push({
381
374
  op: "replace",
382
375
  path: ["length"],
383
376
  value: interimLen,
@@ -389,7 +382,7 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
389
382
  const path = [realIndex]
390
383
 
391
384
  // add 0, 1, 2...
392
- patches.push({
385
+ patches?.push({
393
386
  op: "add",
394
387
  path,
395
388
  value: freezeInternalSnapshot(
@@ -399,7 +392,7 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
399
392
 
400
393
  // remove ...2, 1, 0 since inverse patches are applied in reverse
401
394
  if (!restoreUsingSetLength) {
402
- invPatches.push({
395
+ invPatches?.push({
403
396
  op: "remove",
404
397
  path,
405
398
  })
@@ -408,7 +401,9 @@ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any)
408
401
  }
409
402
  }
410
403
 
411
- patchRecorder.record(patches, invPatches)
404
+ if (patches || invPatches) {
405
+ patchRecorder.record(patches, invPatches)
406
+ }
412
407
  return mutate
413
408
  }
414
409
 
@@ -421,10 +416,16 @@ function interceptArrayMutation(
421
416
 
422
417
  switch (change.type) {
423
418
  case "splice":
419
+ validateArrayMutationSplice(change)
420
+ // Flush before removed values are untweaked and indexes are reassigned.
421
+ flushInternalSnapshot(array, false)
424
422
  interceptArrayMutationSplice(change)
425
423
  break
426
424
 
427
425
  case "update":
426
+ validateArrayMutationUpdate(change)
427
+ // Flush before the old value is detached below.
428
+ flushInternalSnapshot(array, false)
428
429
  interceptArrayMutationUpdate(change, array)
429
430
  break
430
431
 
@@ -434,7 +435,7 @@ function interceptArrayMutation(
434
435
  return change
435
436
  }
436
437
 
437
- function interceptArrayMutationUpdate(change: IArrayWillChange, array: IObservableArray) {
438
+ function validateArrayMutationUpdate(change: IArrayWillChange) {
438
439
  if (
439
440
  inDevMode &&
440
441
  !getGlobalConfig().allowUndefinedArrayElements &&
@@ -442,7 +443,9 @@ function interceptArrayMutationUpdate(change: IArrayWillChange, array: IObservab
442
443
  ) {
443
444
  throw failure(undefinedInsideArrayErrorMsg)
444
445
  }
446
+ }
445
447
 
448
+ function interceptArrayMutationUpdate(change: IArrayWillChange, array: IObservableArray) {
446
449
  // TODO: should be change.object, but mobx is bugged and doesn't send the proxy
447
450
  const oldVal = array[change.index]
448
451
  tweak(oldVal, undefined) // set old prop obj parent to undefined
@@ -450,7 +453,7 @@ function interceptArrayMutationUpdate(change: IArrayWillChange, array: IObservab
450
453
  change.newValue = tweak(change.newValue, { parent: array, path: change.index })
451
454
  }
452
455
 
453
- function interceptArrayMutationSplice(change: IArrayWillSplice) {
456
+ function validateArrayMutationSplice(change: IArrayWillSplice) {
454
457
  if (inDevMode && !getGlobalConfig().allowUndefinedArrayElements) {
455
458
  const len = change.added.length
456
459
  for (let i = 0; i < len; i++) {
@@ -460,7 +463,9 @@ function interceptArrayMutationSplice(change: IArrayWillSplice) {
460
463
  }
461
464
  }
462
465
  }
466
+ }
463
467
 
468
+ function interceptArrayMutationSplice(change: IArrayWillSplice) {
464
469
  for (let i = 0; i < change.removedCount; i++) {
465
470
  const removedValue = change.object[change.index + i]
466
471
  tweak(removedValue, undefined)
@@ -510,6 +515,39 @@ const observableOptions = {
510
515
  deep: false,
511
516
  }
512
517
 
518
+ class ObservableArrayReplacementQueue {
519
+ private readonly array: IObservableArray<any>
520
+ private rangeStart = -1
521
+ private rangeItems: any[] | undefined
522
+
523
+ constructor(array: IObservableArray<any>) {
524
+ this.array = array
525
+ }
526
+
527
+ queueIfDifferent(index: number, value: unknown): void {
528
+ if (this.array[index] === value && index in this.array) {
529
+ this.flush()
530
+ return
531
+ }
532
+
533
+ if (this.rangeStart + (this.rangeItems?.length ?? 0) === index) {
534
+ this.rangeItems!.push(value)
535
+ } else {
536
+ this.flush()
537
+ this.rangeStart = index
538
+ this.rangeItems = [value]
539
+ }
540
+ }
541
+
542
+ flush(): void {
543
+ if (this.rangeStart >= 0) {
544
+ this.array.spliceWithArray(this.rangeStart, this.rangeItems!.length, this.rangeItems!)
545
+ this.rangeStart = -1
546
+ this.rangeItems = undefined
547
+ }
548
+ }
549
+ }
550
+
513
551
  function getValueAfterSplice<T>(
514
552
  array: readonly T[],
515
553
  i: number,
@@ -2,8 +2,8 @@ import { Frozen, frozenKey } from "../frozen/Frozen"
2
2
  import type { ParentPath } from "../parent/path"
3
3
  import { setParent } from "../parent/setParent"
4
4
  import { setNewInternalSnapshot } from "../snapshot/internal"
5
- import { tweakedObjects } from "./core"
6
5
  import { TweakerPriority } from "./TweakerPriority"
6
+ import { markAsTweakedObject } from "./treeNodeMetadata"
7
7
  import { registerTweaker } from "./tweak"
8
8
 
9
9
  /**
@@ -13,7 +13,7 @@ export function tweakFrozen<T extends Frozen<any>>(
13
13
  frozenObj: T,
14
14
  parentPath: ParentPath<any> | undefined
15
15
  ): T {
16
- tweakedObjects.set(frozenObj, undefined)
16
+ markAsTweakedObject(frozenObj)
17
17
  setParent(
18
18
  frozenObj, // value
19
19
  parentPath,
@@ -1,15 +1,15 @@
1
1
  import { isModel } from "../model/utils"
2
2
  import type { ParentPath } from "../parent/path"
3
3
  import { setParent } from "../parent/setParent"
4
- import { tweakedObjects } from "./core"
5
4
  import { TweakerPriority } from "./TweakerPriority"
5
+ import { markAsTweakedObject } from "./treeNodeMetadata"
6
6
  import { registerTweaker } from "./tweak"
7
7
 
8
8
  /**
9
9
  * @internal
10
10
  */
11
11
  export function tweakModel<T extends object>(value: T, parentPath: ParentPath<any> | undefined): T {
12
- tweakedObjects.set(value, undefined)
12
+ markAsTweakedObject(value)
13
13
  setParent(
14
14
  value,
15
15
  parentPath,