mobx-keystone 1.20.0 → 1.22.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 (174) hide show
  1. package/CHANGELOG.md +864 -853
  2. package/README.md +61 -61
  3. package/dist/mobx-keystone.esm.js +11336 -11148
  4. package/dist/mobx-keystone.esm.mjs +11336 -11148
  5. package/dist/mobx-keystone.umd.js +12229 -11738
  6. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +1 -1
  7. package/dist/types/modelShared/prop.d.ts +5 -0
  8. package/dist/types/redux/connectReduxDevTools.d.ts +17 -4
  9. package/dist/types/ref/Ref.d.ts +1 -1
  10. package/dist/types/treeUtils/sandbox.d.ts +1 -1
  11. package/dist/types/types/TypeChecker.d.ts +1 -1
  12. package/dist/types/types/arrayBased/typesArray.d.ts +1 -1
  13. package/dist/types/types/arrayBased/typesTuple.d.ts +1 -1
  14. package/dist/types/types/objectBased/typesArraySet.d.ts +1 -1
  15. package/dist/types/types/objectBased/typesDataModelData.d.ts +1 -1
  16. package/dist/types/types/objectBased/typesModel.d.ts +1 -1
  17. package/dist/types/types/objectBased/typesObject.d.ts +3 -3
  18. package/dist/types/types/objectBased/typesObjectMap.d.ts +1 -1
  19. package/dist/types/types/objectBased/typesRecord.d.ts +1 -1
  20. package/dist/types/types/primitiveBased/typesPrimitive.d.ts +1 -1
  21. package/dist/types/types/types.d.ts +3 -3
  22. package/dist/types/types/utility/typesCodec.d.ts +1 -6
  23. package/dist/types/types/utility/typesCodecCore.d.ts +1 -1
  24. package/dist/types/types/utility/typesOr.d.ts +1 -1
  25. package/dist/types/types/utility/typesRefinement.d.ts +1 -1
  26. package/dist/types/types/utility/typesSkipCheck.d.ts +1 -1
  27. package/dist/types/types/utility/typesTag.d.ts +1 -1
  28. package/dist/types/wrappers/ObjectMap.d.ts +2 -0
  29. package/dist/types/wrappers/asMap.d.ts +10 -6
  30. package/package.json +14 -12
  31. package/src/action/applyAction.ts +125 -114
  32. package/src/action/applyMethodCall.ts +37 -37
  33. package/src/action/isModelAction.ts +16 -16
  34. package/src/action/middleware.ts +137 -137
  35. package/src/action/modelFlow.ts +240 -240
  36. package/src/action/modelFlowPromiseGenerator.ts +127 -125
  37. package/src/action/pendingActions.ts +42 -42
  38. package/src/action/wrapInAction.ts +156 -156
  39. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +1 -1
  40. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +172 -172
  41. package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +20 -20
  42. package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +17 -17
  43. package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +42 -42
  44. package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +50 -50
  45. package/src/actionMiddlewares/actionSerialization/objectSnapshotSerializer.ts +21 -21
  46. package/src/actionMiddlewares/actionSerialization/plainObjectSerializer.ts +24 -32
  47. package/src/actionMiddlewares/actionSerialization/primitiveSerializer.ts +51 -51
  48. package/src/actionMiddlewares/actionSerialization/setSerializer.ts +37 -37
  49. package/src/actionMiddlewares/actionTrackingMiddleware.ts +3 -3
  50. package/src/actionMiddlewares/onActionMiddleware.ts +2 -2
  51. package/src/actionMiddlewares/readonlyMiddleware.ts +2 -2
  52. package/src/actionMiddlewares/transactionMiddleware.ts +4 -4
  53. package/src/actionMiddlewares/undoMiddleware.ts +804 -796
  54. package/src/actionMiddlewares/utils.ts +2 -2
  55. package/src/computedTree/computedTree.ts +143 -143
  56. package/src/context/context.ts +1 -1
  57. package/src/dataModel/BaseDataModel.ts +194 -192
  58. package/src/dataModel/actions.ts +1 -1
  59. package/src/dataModel/newDataModel.ts +57 -57
  60. package/src/deepChange/index.ts +1 -1
  61. package/src/deepChange/onDeepChange.ts +244 -244
  62. package/src/frozen/Frozen.ts +146 -146
  63. package/src/globalConfig/globalConfig.ts +107 -107
  64. package/src/index.ts +24 -24
  65. package/src/model/BaseModel.ts +275 -273
  66. package/src/model/ModelConstructorOptions.ts +15 -15
  67. package/src/model/getModelMetadata.ts +51 -51
  68. package/src/model/index.ts +6 -6
  69. package/src/model/mixins.ts +288 -288
  70. package/src/model/newModel.ts +301 -301
  71. package/src/model/utils.ts +249 -249
  72. package/src/modelShared/modelDecorator.ts +260 -253
  73. package/src/modelShared/modelInfo.ts +50 -50
  74. package/src/modelShared/modelSymbols.ts +3 -3
  75. package/src/modelShared/newModel.ts +25 -25
  76. package/src/modelShared/prop.ts +713 -684
  77. package/src/modelShared/sharedInternalModel.ts +427 -417
  78. package/src/modelShared/utils.ts +24 -24
  79. package/src/parent/core.ts +65 -65
  80. package/src/parent/coreObjectChildren.ts +180 -180
  81. package/src/parent/detach.ts +47 -47
  82. package/src/parent/findParent.ts +1 -1
  83. package/src/parent/getChildrenObjects.ts +25 -25
  84. package/src/parent/path.ts +386 -381
  85. package/src/parent/setParent.ts +149 -149
  86. package/src/parent/walkTree.ts +1 -1
  87. package/src/patch/applyPatches.ts +187 -187
  88. package/src/patch/jsonPatch.ts +108 -108
  89. package/src/patch/patchRecorder.ts +6 -1
  90. package/src/redux/connectReduxDevTools.ts +39 -7
  91. package/src/redux/redux.ts +6 -2
  92. package/src/ref/Ref.ts +1 -1
  93. package/src/ref/core.ts +339 -345
  94. package/src/ref/customRef.ts +3 -3
  95. package/src/ref/rootRef.ts +100 -100
  96. package/src/rootStore/attachDetach.ts +89 -89
  97. package/src/rootStore/rootStore.ts +1 -1
  98. package/src/snapshot/SnapshotOf.ts +104 -104
  99. package/src/snapshot/SnapshotProcessingError.ts +45 -45
  100. package/src/snapshot/applySnapshot.ts +177 -181
  101. package/src/snapshot/cloneTreeValue.ts +30 -30
  102. package/src/snapshot/fromArraySnapshot.ts +27 -33
  103. package/src/snapshot/fromModelSnapshot.ts +70 -70
  104. package/src/snapshot/fromPlainObjectSnapshot.ts +47 -42
  105. package/src/snapshot/fromSnapshot.ts +198 -198
  106. package/src/snapshot/getSnapshot.ts +80 -79
  107. package/src/snapshot/index.ts +8 -8
  108. package/src/snapshot/internal.ts +230 -230
  109. package/src/snapshot/reconcileArraySnapshot.ts +70 -70
  110. package/src/snapshot/reconcileModelSnapshot.ts +155 -150
  111. package/src/snapshot/reconcilePlainObjectSnapshot.ts +72 -72
  112. package/src/snapshot/reconcileSnapshot.ts +88 -88
  113. package/src/standardActions/actions.ts +64 -64
  114. package/src/standardActions/objectActions.ts +60 -60
  115. package/src/transforms/ImmutableDate.ts +93 -93
  116. package/src/transforms/asMap.ts +50 -50
  117. package/src/transforms/asSet.ts +22 -22
  118. package/src/transforms/bigint.ts +20 -20
  119. package/src/transforms/date.ts +40 -40
  120. package/src/treeUtils/deepEquals.ts +55 -55
  121. package/src/treeUtils/sandbox.ts +6 -3
  122. package/src/tweaker/TweakerPriority.ts +9 -9
  123. package/src/tweaker/tweak.ts +4 -3
  124. package/src/tweaker/tweakArray.ts +530 -475
  125. package/src/tweaker/tweakFrozen.ts +42 -42
  126. package/src/tweaker/tweakModel.ts +1 -1
  127. package/src/tweaker/tweakPlainObject.ts +355 -355
  128. package/src/tweaker/typeChecking.ts +101 -101
  129. package/src/types/SnapshotTypeMismatchError.ts +46 -46
  130. package/src/types/TypeCheckError.ts +183 -184
  131. package/src/types/TypeChecker.ts +202 -185
  132. package/src/types/arrayBased/typesArray.ts +136 -137
  133. package/src/types/arrayBased/typesTuple.ts +138 -139
  134. package/src/types/createPerEntryCachedCheck.ts +166 -160
  135. package/src/types/index.ts +6 -6
  136. package/src/types/objectBased/typesArraySet.ts +133 -134
  137. package/src/types/objectBased/typesDataModelData.ts +203 -204
  138. package/src/types/objectBased/typesModel.ts +220 -221
  139. package/src/types/objectBased/typesObject.ts +308 -310
  140. package/src/types/objectBased/typesObjectMap.ts +142 -143
  141. package/src/types/objectBased/typesRecord.ts +156 -157
  142. package/src/types/objectBased/typesRef.ts +89 -89
  143. package/src/types/primitiveBased/typesEnum.ts +63 -63
  144. package/src/types/primitiveBased/typesPrimitive.ts +219 -220
  145. package/src/types/registerDefaultStandardTypeResolvers.ts +21 -21
  146. package/src/types/resolveTypeChecker.ts +78 -78
  147. package/src/types/schemas.ts +208 -208
  148. package/src/types/tProp.ts +277 -268
  149. package/src/types/typeCheck.ts +21 -21
  150. package/src/types/typeCheckErrorUtils.ts +23 -23
  151. package/src/types/types.ts +142 -142
  152. package/src/types/utility/typeToStoredData.ts +37 -37
  153. package/src/types/utility/typesBigInt.ts +15 -15
  154. package/src/types/utility/typesCodec.ts +41 -38
  155. package/src/types/utility/typesCodecCore.ts +178 -178
  156. package/src/types/utility/typesCodecSupport.ts +806 -806
  157. package/src/types/utility/typesDate.ts +27 -27
  158. package/src/types/utility/typesMap.ts +253 -222
  159. package/src/types/utility/typesOr.ts +263 -259
  160. package/src/types/utility/typesRefinement.ts +131 -125
  161. package/src/types/utility/typesSet.ts +110 -110
  162. package/src/types/utility/typesSkipCheck.ts +69 -70
  163. package/src/types/utility/typesTag.ts +88 -82
  164. package/src/types/utility/typesUnchecked.ts +40 -40
  165. package/src/utils/AnyFunction.ts +4 -4
  166. package/src/utils/ModelPool.ts +59 -59
  167. package/src/utils/chainFns.ts +18 -18
  168. package/src/utils/decorators.ts +274 -274
  169. package/src/utils/errorDiagnostics.ts +204 -204
  170. package/src/utils/index.ts +443 -372
  171. package/src/wrappers/ArraySet.ts +156 -156
  172. package/src/wrappers/ObjectMap.ts +155 -136
  173. package/src/wrappers/asMap.ts +65 -30
  174. package/src/wrappers/asSet.ts +3 -3
@@ -1,475 +1,530 @@
1
- import {
2
- IArrayDidChange,
3
- IArrayWillChange,
4
- IArrayWillSplice,
5
- IObservableArray,
6
- intercept,
7
- isObservableArray,
8
- observable,
9
- observe,
10
- } from "mobx"
11
- import { Path } from "parent/pathTypes"
12
- import { assertCanWrite } from "../action/protection"
13
- import { DeepChangeType, emitDeepChange, getIsInInitPhase } from "../deepChange/onDeepChange"
14
- import { getGlobalConfig } from "../globalConfig"
15
- import type { ParentPath } from "../parent/path"
16
- import { setParent } from "../parent/setParent"
17
- import { InternalPatchRecorder } from "../patch/emitPatch"
18
- import type { Patch } from "../patch/Patch"
19
- import {
20
- freezeInternalSnapshot,
21
- getInternalSnapshot,
22
- setNewInternalSnapshot,
23
- updateInternalSnapshot,
24
- } from "../snapshot/internal"
25
- import { failure, inDevMode, isArray, isPrimitive } from "../utils"
26
- import { setIfDifferent } from "../utils/setIfDifferent"
27
- import { runningWithoutSnapshotOrPatches, tweakedObjects } from "./core"
28
- import { TweakerPriority } from "./TweakerPriority"
29
- import { registerTweaker, tweak } from "./tweak"
30
- import { runTypeCheckingAfterChange } from "./typeChecking"
31
-
32
- /**
33
- * @internal
34
- */
35
- export function tweakArray<T extends any[]>(
36
- value: T,
37
- parentPath: ParentPath<any> | undefined,
38
- doNotTweakChildren: boolean
39
- ): T {
40
- const originalArr: ReadonlyArray<any> = value
41
- const arrLn = originalArr.length
42
- const tweakedArr = isObservableArray(originalArr)
43
- ? originalArr
44
- : observable.array(undefined, observableOptions)
45
- if (tweakedArr !== originalArr) {
46
- tweakedArr.length = originalArr.length
47
- }
48
-
49
- let interceptDisposer: () => void
50
- let observeDisposer: () => void
51
-
52
- const untweak = () => {
53
- interceptDisposer()
54
- observeDisposer()
55
- }
56
-
57
- tweakedObjects.set(tweakedArr, untweak)
58
- setParent(
59
- tweakedArr, // value
60
- parentPath,
61
- false, // indexChangeAllowed
62
- false, // isDataObject
63
- // arrays shouldn't be cloned anyway
64
- false // cloneIfApplicable
65
- )
66
-
67
- const untransformedSn: any[] = []
68
- untransformedSn.length = arrLn
69
-
70
- // substitute initial values by proxied values
71
- for (let i = 0; i < arrLn; i++) {
72
- const v = originalArr[i]
73
-
74
- if (isPrimitive(v)) {
75
- if (!doNotTweakChildren) {
76
- setIfDifferent(tweakedArr, i, v)
77
- }
78
-
79
- untransformedSn[i] = v
80
- } else {
81
- const path = { parent: tweakedArr, path: i }
82
-
83
- let tweakedValue: any
84
- if (doNotTweakChildren) {
85
- tweakedValue = v
86
- setParent(
87
- tweakedValue, // value
88
- path, // parentPath
89
- false, // indexChangeAllowed
90
- false, // isDataObject
91
- // the value is already a new value (the result of a fromSnapshot)
92
- false // cloneIfApplicable
93
- )
94
- } else {
95
- tweakedValue = tweak(v, path)
96
- setIfDifferent(tweakedArr, i, tweakedValue)
97
- }
98
-
99
- const valueSn = getInternalSnapshot(tweakedValue)!
100
- untransformedSn[i] = valueSn.transformed
101
- }
102
- }
103
-
104
- setNewInternalSnapshot(tweakedArr, untransformedSn, undefined)
105
-
106
- interceptDisposer = intercept(tweakedArr, interceptArrayMutation.bind(undefined, tweakedArr))
107
- observeDisposer = observe(tweakedArr, arrayDidChange)
108
-
109
- return tweakedArr as any
110
- }
111
-
112
- function mutateSet(k: number, v: unknown, sn: unknown[]) {
113
- sn[k] = v
114
- }
115
-
116
- function mutateSplice(index: number, removedCount: number, addedItems: any[], sn: any[]) {
117
- sn.splice(index, removedCount, ...addedItems)
118
- }
119
-
120
- const patchRecorder = new InternalPatchRecorder()
121
-
122
- const emptyPath: Path = Object.freeze([])
123
-
124
- function arrayDidChange(change: IArrayDidChange) {
125
- const arr = change.object
126
- const oldSnapshot = getInternalSnapshot(arr as Array<unknown>)!.untransformed
127
-
128
- patchRecorder.reset()
129
-
130
- let mutate: ((sn: any[]) => void) | undefined
131
-
132
- switch (change.type) {
133
- case "splice":
134
- mutate = arrayDidChangeSplice(change, oldSnapshot)
135
- // Emit deep change for splice
136
- emitDeepChange(arr, {
137
- type: DeepChangeType.ArraySplice,
138
- target: arr,
139
- path: emptyPath,
140
- index: change.index as number,
141
- addedValues: change.added,
142
- removedValues: change.removed,
143
- isInit: getIsInInitPhase(),
144
- })
145
- break
146
-
147
- case "update":
148
- mutate = arrayDidChangeUpdate(change, oldSnapshot)
149
- // Emit deep change for update
150
- emitDeepChange(arr, {
151
- type: DeepChangeType.ArrayUpdate,
152
- target: arr,
153
- path: emptyPath,
154
- index: change.index as number,
155
- newValue: change.newValue,
156
- oldValue: change.oldValue,
157
- isInit: getIsInInitPhase(),
158
- })
159
-
160
- break
161
-
162
- default:
163
- break
164
- }
165
-
166
- runTypeCheckingAfterChange(arr, patchRecorder)
167
-
168
- if (!runningWithoutSnapshotOrPatches && mutate) {
169
- updateInternalSnapshot(arr, mutate)
170
- patchRecorder.emit(arr)
171
- }
172
- }
173
-
174
- const undefinedInsideArrayErrorMsg =
175
- "undefined is not supported inside arrays since it is not serializable in JSON, consider using null instead"
176
-
177
- function arrayDidChangeUpdate(change: any /*IArrayDidChange*/, oldSnapshot: any) {
178
- const k = change.index
179
- const val = change.newValue
180
- const oldVal = oldSnapshot[k]
181
- let newVal: any
182
- if (isPrimitive(val)) {
183
- newVal = val
184
- } else {
185
- const valueSn = getInternalSnapshot(val)!
186
- newVal = valueSn.transformed
187
- }
188
- const mutate = mutateSet.bind(undefined, k, newVal)
189
-
190
- const path = [k]
191
-
192
- patchRecorder.record(
193
- [
194
- {
195
- op: "replace",
196
- path,
197
- value: freezeInternalSnapshot(newVal),
198
- },
199
- ],
200
- [
201
- {
202
- op: "replace",
203
- path,
204
- value: freezeInternalSnapshot(oldVal),
205
- },
206
- ]
207
- )
208
- return mutate
209
- }
210
-
211
- function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any) {
212
- const index = change.index as number
213
- const addedCount = change.addedCount as number
214
- const removedCount = change.removedCount as number
215
-
216
- const addedItems: any[] = []
217
- addedItems.length = addedCount
218
- for (let i = 0; i < addedCount; i++) {
219
- const v = change.added[i]
220
- if (isPrimitive(v)) {
221
- addedItems[i] = v
222
- } else {
223
- addedItems[i] = getInternalSnapshot(v)!.transformed
224
- }
225
- }
226
-
227
- const oldLen = oldSnapshot.length
228
- const mutate = mutateSplice.bind(undefined, index, removedCount, addedItems)
229
-
230
- const patches: Patch[] = []
231
- const invPatches: Patch[] = []
232
-
233
- // optimization: if we add as many as we remove then remove/readd instead
234
-
235
- // we cannot replace since we might end up in a situation where the same node
236
- // might attempt to be temporarily twice inside the same tree (e.g. sorting)
237
-
238
- // it would be faster to keep holes rather than remove/readd, but if we do that then
239
- // validation might fail
240
-
241
- if (addedCount === removedCount) {
242
- const readdPatches: Patch[] = []
243
- const readdInvPatches: Patch[] = []
244
- let removed = 0
245
-
246
- for (let i = 0; i < addedCount; i++) {
247
- const realIndex = index + i
248
-
249
- const newVal = getValueAfterSplice(oldSnapshot, realIndex, index, removedCount, addedItems)
250
- const oldVal = oldSnapshot[realIndex]
251
-
252
- if (newVal !== oldVal) {
253
- const removePath = [realIndex - removed]
254
- patches.push({
255
- op: "remove",
256
- path: removePath,
257
- })
258
- invPatches.push({
259
- op: "remove",
260
- path: removePath,
261
- })
262
-
263
- removed++
264
-
265
- const readdPath = [realIndex]
266
- readdPatches.push({
267
- op: "add",
268
- path: readdPath,
269
- value: freezeInternalSnapshot(newVal),
270
- })
271
-
272
- readdInvPatches.push({
273
- op: "add",
274
- path: readdPath,
275
- value: freezeInternalSnapshot(oldVal),
276
- })
277
- }
278
- }
279
-
280
- patches.push(...readdPatches)
281
- invPatches.push(...readdInvPatches)
282
- // we need to reverse since inverse patches are applied in reverse
283
- invPatches.reverse()
284
- } else {
285
- const interimLen = oldLen - removedCount
286
-
287
- // first remove items
288
- if (removedCount > 0) {
289
- // optimization, when removing from the end set the length instead
290
- const removeUsingSetLength = index >= interimLen
291
- if (removeUsingSetLength) {
292
- patches.push({
293
- op: "replace",
294
- path: ["length"],
295
- value: interimLen,
296
- })
297
- }
298
-
299
- for (let i = removedCount - 1; i >= 0; i--) {
300
- const realIndex = index + i
301
- const path = [realIndex]
302
-
303
- if (!removeUsingSetLength) {
304
- // remove ...2, 1, 0
305
- patches.push({
306
- op: "remove",
307
- path,
308
- })
309
- }
310
-
311
- // add 0, 1, 2... since inverse patches are applied in reverse
312
- invPatches.push({
313
- op: "add",
314
- path,
315
- value: freezeInternalSnapshot(oldSnapshot[realIndex]),
316
- })
317
- }
318
- }
319
-
320
- // then add items
321
- if (addedCount > 0) {
322
- // optimization, for inverse patches, when adding from the end set the length to restore instead
323
- const restoreUsingSetLength = index >= interimLen
324
- if (restoreUsingSetLength) {
325
- invPatches.push({
326
- op: "replace",
327
- path: ["length"],
328
- value: interimLen,
329
- })
330
- }
331
-
332
- for (let i = 0; i < addedCount; i++) {
333
- const realIndex = index + i
334
- const path = [realIndex]
335
-
336
- // add 0, 1, 2...
337
- patches.push({
338
- op: "add",
339
- path,
340
- value: freezeInternalSnapshot(
341
- getValueAfterSplice(oldSnapshot, realIndex, index, removedCount, addedItems)
342
- ),
343
- })
344
-
345
- // remove ...2, 1, 0 since inverse patches are applied in reverse
346
- if (!restoreUsingSetLength) {
347
- invPatches.push({
348
- op: "remove",
349
- path,
350
- })
351
- }
352
- }
353
- }
354
- }
355
-
356
- patchRecorder.record(patches, invPatches)
357
- return mutate
358
- }
359
-
360
- // TODO: remove array parameter and just use change.object once mobx update event is fixed
361
- function interceptArrayMutation(
362
- array: IObservableArray,
363
- change: IArrayWillChange | IArrayWillSplice
364
- ) {
365
- assertCanWrite()
366
-
367
- switch (change.type) {
368
- case "splice":
369
- interceptArrayMutationSplice(change)
370
- break
371
-
372
- case "update":
373
- interceptArrayMutationUpdate(change, array)
374
- break
375
-
376
- default:
377
- break
378
- }
379
- return change
380
- }
381
-
382
- function interceptArrayMutationUpdate(change: IArrayWillChange, array: IObservableArray) {
383
- if (
384
- inDevMode &&
385
- !getGlobalConfig().allowUndefinedArrayElements &&
386
- change.newValue === undefined
387
- ) {
388
- throw failure(undefinedInsideArrayErrorMsg)
389
- }
390
-
391
- // TODO: should be change.object, but mobx is bugged and doesn't send the proxy
392
- const oldVal = array[change.index]
393
- tweak(oldVal, undefined) // set old prop obj parent to undefined
394
-
395
- change.newValue = tweak(change.newValue, { parent: array, path: change.index })
396
- }
397
-
398
- function interceptArrayMutationSplice(change: IArrayWillSplice) {
399
- if (inDevMode && !getGlobalConfig().allowUndefinedArrayElements) {
400
- const len = change.added.length
401
- for (let i = 0; i < len; i++) {
402
- const v = change.added[i]
403
- if (v === undefined) {
404
- throw failure(undefinedInsideArrayErrorMsg)
405
- }
406
- }
407
- }
408
-
409
- for (let i = 0; i < change.removedCount; i++) {
410
- const removedValue = change.object[change.index + i]
411
- tweak(removedValue, undefined)
412
- }
413
-
414
- for (let i = 0; i < change.added.length; i++) {
415
- change.added[i] = tweak(change.added[i], {
416
- parent: change.object,
417
- path: change.index + i,
418
- })
419
- }
420
-
421
- // we might also need to update the parent of the next indexes
422
- const oldNextIndex = change.index + change.removedCount
423
- const newNextIndex = change.index + change.added.length
424
-
425
- if (oldNextIndex !== newNextIndex) {
426
- for (let i = oldNextIndex, j = newNextIndex; i < change.object.length; i++, j++) {
427
- setParent(
428
- change.object[i], // value
429
- {
430
- parent: change.object,
431
- path: j,
432
- }, // parentPath
433
- true, // indexChangeAllowed
434
- false, // isDataObject
435
- // just re-indexing
436
- false // cloneIfApplicable
437
- )
438
- }
439
- }
440
- }
441
-
442
- /**
443
- * @internal
444
- */
445
- export function registerArrayTweaker() {
446
- registerTweaker(TweakerPriority.Array, (value, parentPath) => {
447
- if (isArray(value)) {
448
- return tweakArray(value, parentPath, false)
449
- }
450
- return undefined
451
- })
452
- }
453
-
454
- const observableOptions = {
455
- deep: false,
456
- }
457
-
458
- function getValueAfterSplice<T>(
459
- array: readonly T[],
460
- i: number,
461
- index: number,
462
- remove: number,
463
- addedItems: readonly T[]
464
- ) {
465
- const base = i - index
466
- if (base < 0) {
467
- return array[i]
468
- }
469
-
470
- if (base < addedItems.length) {
471
- return addedItems[base]
472
- }
473
-
474
- return array[i - addedItems.length + remove]
475
- }
1
+ import {
2
+ type IArrayDidChange,
3
+ type IArrayWillChange,
4
+ type IArrayWillSplice,
5
+ type IObservableArray,
6
+ intercept,
7
+ isObservableArray,
8
+ observable,
9
+ observe,
10
+ } from "mobx"
11
+ import { assertCanWrite } from "../action/protection"
12
+ import { DeepChangeType, emitDeepChange, getIsInInitPhase } from "../deepChange/onDeepChange"
13
+ import { getGlobalConfig } from "../globalConfig"
14
+ import type { ParentPath } from "../parent/path"
15
+ import type { Path } from "../parent/pathTypes"
16
+ import { setParent } from "../parent/setParent"
17
+ import { InternalPatchRecorder } from "../patch/emitPatch"
18
+ import type { Patch } from "../patch/Patch"
19
+ import {
20
+ freezeInternalSnapshot,
21
+ getInternalSnapshot,
22
+ setNewInternalSnapshot,
23
+ updateInternalSnapshot,
24
+ } from "../snapshot/internal"
25
+ import { failure, inDevMode, isArray, isPrimitive } from "../utils"
26
+ import { runningWithoutSnapshotOrPatches, tweakedObjects } from "./core"
27
+ import { TweakerPriority } from "./TweakerPriority"
28
+ import { registerTweaker, tweak } from "./tweak"
29
+ import { runTypeCheckingAfterChange } from "./typeChecking"
30
+
31
+ /**
32
+ * @internal
33
+ */
34
+ export function tweakArray<T extends any[]>(
35
+ value: T,
36
+ parentPath: ParentPath<any> | undefined,
37
+ childrenAlreadyTweaked: boolean
38
+ ): T {
39
+ const originalArr: ReadonlyArray<any> = value
40
+ const arrLn = originalArr.length
41
+ const originalArrIsObservable = isObservableArray(originalArr)
42
+ let tweakedArr: IObservableArray<any>
43
+ // Fresh arrays whose children still need tweaking are collected into a plain
44
+ // buffer and inserted in one bulk replace at the end.
45
+ let buffer: any[] | undefined
46
+
47
+ if (originalArrIsObservable) {
48
+ tweakedArr = originalArr as IObservableArray<any>
49
+ } else if (childrenAlreadyTweaked) {
50
+ // Snapshot arrays already contain tweaked children, so MobX can initialize
51
+ // the observable array directly from the source values.
52
+ tweakedArr = observable.array(originalArr as any[], observableOptions)
53
+ } else {
54
+ tweakedArr = observable.array(undefined, observableOptions)
55
+ buffer = new Array(arrLn)
56
+ }
57
+
58
+ let interceptDisposer: () => void
59
+ let observeDisposer: () => void
60
+
61
+ const untweak = () => {
62
+ interceptDisposer()
63
+ observeDisposer()
64
+ }
65
+
66
+ tweakedObjects.set(tweakedArr, untweak)
67
+ setParent(
68
+ tweakedArr, // value
69
+ parentPath,
70
+ false, // indexChangeAllowed
71
+ false, // isDataObject
72
+ // arrays shouldn't be cloned anyway
73
+ false // cloneIfApplicable
74
+ )
75
+
76
+ const untransformedSn: any[] = []
77
+ untransformedSn.length = arrLn
78
+
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
+ }
110
+
111
+ // substitute initial values by proxied values
112
+ for (let i = 0; i < arrLn; i++) {
113
+ const v = originalArr[i]
114
+
115
+ if (isPrimitive(v)) {
116
+ if (buffer) {
117
+ buffer[i] = v
118
+ } else if (!childrenAlreadyTweaked) {
119
+ queueReplacementIfDifferent(i, v)
120
+ }
121
+
122
+ untransformedSn[i] = v
123
+ } else {
124
+ const path = { parent: tweakedArr, path: i }
125
+
126
+ let tweakedValue: any
127
+ if (childrenAlreadyTweaked) {
128
+ tweakedValue = v
129
+ setParent(
130
+ tweakedValue, // value
131
+ path, // parentPath
132
+ false, // indexChangeAllowed
133
+ false, // isDataObject
134
+ // the value is already a new value (the result of a fromSnapshot)
135
+ false // cloneIfApplicable
136
+ )
137
+ } else {
138
+ tweakedValue = tweak(v, path)
139
+ }
140
+
141
+ if (buffer) {
142
+ buffer[i] = tweakedValue
143
+ } else if (!childrenAlreadyTweaked) {
144
+ queueReplacementIfDifferent(i, tweakedValue)
145
+ }
146
+
147
+ const valueSn = getInternalSnapshot(tweakedValue)!
148
+ untransformedSn[i] = valueSn.transformed
149
+ }
150
+ }
151
+
152
+ if (buffer) {
153
+ // done before the intercept/observe handlers are installed, so this
154
+ // is a single plain mobx bulk insertion
155
+ tweakedArr.replace(buffer)
156
+ } else {
157
+ flushReplacementRange()
158
+ }
159
+ setNewInternalSnapshot(tweakedArr, untransformedSn, undefined)
160
+
161
+ interceptDisposer = intercept(tweakedArr, interceptArrayMutation.bind(undefined, tweakedArr))
162
+ observeDisposer = observe(tweakedArr, arrayDidChange)
163
+
164
+ return tweakedArr as any
165
+ }
166
+
167
+ function mutateSet(k: number, v: unknown, sn: unknown[]) {
168
+ sn[k] = v
169
+ }
170
+
171
+ function mutateSplice(index: number, removedCount: number, addedItems: any[], sn: any[]) {
172
+ sn.splice(index, removedCount, ...addedItems)
173
+ }
174
+
175
+ const patchRecorder = new InternalPatchRecorder()
176
+
177
+ const emptyPath: Path = Object.freeze([])
178
+
179
+ function arrayDidChange(change: IArrayDidChange) {
180
+ const arr = change.object
181
+ const oldSnapshot = getInternalSnapshot(arr as Array<unknown>)!.untransformed
182
+
183
+ patchRecorder.reset()
184
+
185
+ let mutate: ((sn: any[]) => void) | undefined
186
+
187
+ switch (change.type) {
188
+ 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
+ })
200
+ break
201
+
202
+ 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
+ })
214
+
215
+ break
216
+
217
+ default:
218
+ break
219
+ }
220
+
221
+ runTypeCheckingAfterChange(arr, patchRecorder)
222
+
223
+ if (!runningWithoutSnapshotOrPatches && mutate) {
224
+ updateInternalSnapshot(arr, mutate)
225
+ patchRecorder.emit(arr)
226
+ }
227
+ }
228
+
229
+ const undefinedInsideArrayErrorMsg =
230
+ "undefined is not supported inside arrays since it is not serializable in JSON, consider using null instead"
231
+
232
+ function arrayDidChangeUpdate(change: any /*IArrayDidChange*/, oldSnapshot: any) {
233
+ const k = change.index
234
+ const val = change.newValue
235
+ const oldVal = oldSnapshot[k]
236
+ let newVal: any
237
+ if (isPrimitive(val)) {
238
+ newVal = val
239
+ } else {
240
+ const valueSn = getInternalSnapshot(val)!
241
+ newVal = valueSn.transformed
242
+ }
243
+ const mutate = mutateSet.bind(undefined, k, newVal)
244
+
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
+ )
263
+ return mutate
264
+ }
265
+
266
+ function arrayDidChangeSplice(change: any /*IArrayDidChange*/, oldSnapshot: any) {
267
+ const index = change.index as number
268
+ const addedCount = change.addedCount as number
269
+ const removedCount = change.removedCount as number
270
+
271
+ const addedItems: any[] = []
272
+ addedItems.length = addedCount
273
+ for (let i = 0; i < addedCount; i++) {
274
+ const v = change.added[i]
275
+ if (isPrimitive(v)) {
276
+ addedItems[i] = v
277
+ } else {
278
+ addedItems[i] = getInternalSnapshot(v)!.transformed
279
+ }
280
+ }
281
+
282
+ const oldLen = oldSnapshot.length
283
+ const mutate = mutateSplice.bind(undefined, index, removedCount, addedItems)
284
+
285
+ const patches: Patch[] = []
286
+ const invPatches: Patch[] = []
287
+
288
+ // optimization: if we add as many as we remove then remove/readd instead
289
+
290
+ // we cannot replace since we might end up in a situation where the same node
291
+ // might attempt to be temporarily twice inside the same tree (e.g. sorting)
292
+
293
+ // it would be faster to keep holes rather than remove/readd, but if we do that then
294
+ // validation might fail
295
+
296
+ if (addedCount === removedCount) {
297
+ const readdPatches: Patch[] = []
298
+ const readdInvPatches: Patch[] = []
299
+ let removed = 0
300
+
301
+ for (let i = 0; i < addedCount; i++) {
302
+ const realIndex = index + i
303
+
304
+ const newVal = getValueAfterSplice(oldSnapshot, realIndex, index, removedCount, addedItems)
305
+ const oldVal = oldSnapshot[realIndex]
306
+
307
+ if (newVal !== oldVal) {
308
+ const removePath = [realIndex - removed]
309
+ patches.push({
310
+ op: "remove",
311
+ path: removePath,
312
+ })
313
+ invPatches.push({
314
+ op: "remove",
315
+ path: removePath,
316
+ })
317
+
318
+ removed++
319
+
320
+ const readdPath = [realIndex]
321
+ readdPatches.push({
322
+ op: "add",
323
+ path: readdPath,
324
+ value: freezeInternalSnapshot(newVal),
325
+ })
326
+
327
+ readdInvPatches.push({
328
+ op: "add",
329
+ path: readdPath,
330
+ value: freezeInternalSnapshot(oldVal),
331
+ })
332
+ }
333
+ }
334
+
335
+ patches.push(...readdPatches)
336
+ invPatches.push(...readdInvPatches)
337
+ // we need to reverse since inverse patches are applied in reverse
338
+ invPatches.reverse()
339
+ } else {
340
+ const interimLen = oldLen - removedCount
341
+
342
+ // first remove items
343
+ if (removedCount > 0) {
344
+ // optimization, when removing from the end set the length instead
345
+ const removeUsingSetLength = index >= interimLen
346
+ if (removeUsingSetLength) {
347
+ patches.push({
348
+ op: "replace",
349
+ path: ["length"],
350
+ value: interimLen,
351
+ })
352
+ }
353
+
354
+ for (let i = removedCount - 1; i >= 0; i--) {
355
+ const realIndex = index + i
356
+ const path = [realIndex]
357
+
358
+ if (!removeUsingSetLength) {
359
+ // remove ...2, 1, 0
360
+ patches.push({
361
+ op: "remove",
362
+ path,
363
+ })
364
+ }
365
+
366
+ // add 0, 1, 2... since inverse patches are applied in reverse
367
+ invPatches.push({
368
+ op: "add",
369
+ path,
370
+ value: freezeInternalSnapshot(oldSnapshot[realIndex]),
371
+ })
372
+ }
373
+ }
374
+
375
+ // then add items
376
+ if (addedCount > 0) {
377
+ // optimization, for inverse patches, when adding from the end set the length to restore instead
378
+ const restoreUsingSetLength = index >= interimLen
379
+ if (restoreUsingSetLength) {
380
+ invPatches.push({
381
+ op: "replace",
382
+ path: ["length"],
383
+ value: interimLen,
384
+ })
385
+ }
386
+
387
+ for (let i = 0; i < addedCount; i++) {
388
+ const realIndex = index + i
389
+ const path = [realIndex]
390
+
391
+ // add 0, 1, 2...
392
+ patches.push({
393
+ op: "add",
394
+ path,
395
+ value: freezeInternalSnapshot(
396
+ getValueAfterSplice(oldSnapshot, realIndex, index, removedCount, addedItems)
397
+ ),
398
+ })
399
+
400
+ // remove ...2, 1, 0 since inverse patches are applied in reverse
401
+ if (!restoreUsingSetLength) {
402
+ invPatches.push({
403
+ op: "remove",
404
+ path,
405
+ })
406
+ }
407
+ }
408
+ }
409
+ }
410
+
411
+ patchRecorder.record(patches, invPatches)
412
+ return mutate
413
+ }
414
+
415
+ // TODO: remove array parameter and just use change.object once mobx update event is fixed
416
+ function interceptArrayMutation(
417
+ array: IObservableArray,
418
+ change: IArrayWillChange | IArrayWillSplice
419
+ ) {
420
+ assertCanWrite()
421
+
422
+ switch (change.type) {
423
+ case "splice":
424
+ interceptArrayMutationSplice(change)
425
+ break
426
+
427
+ case "update":
428
+ interceptArrayMutationUpdate(change, array)
429
+ break
430
+
431
+ default:
432
+ break
433
+ }
434
+ return change
435
+ }
436
+
437
+ function interceptArrayMutationUpdate(change: IArrayWillChange, array: IObservableArray) {
438
+ if (
439
+ inDevMode &&
440
+ !getGlobalConfig().allowUndefinedArrayElements &&
441
+ change.newValue === undefined
442
+ ) {
443
+ throw failure(undefinedInsideArrayErrorMsg)
444
+ }
445
+
446
+ // TODO: should be change.object, but mobx is bugged and doesn't send the proxy
447
+ const oldVal = array[change.index]
448
+ tweak(oldVal, undefined) // set old prop obj parent to undefined
449
+
450
+ change.newValue = tweak(change.newValue, { parent: array, path: change.index })
451
+ }
452
+
453
+ function interceptArrayMutationSplice(change: IArrayWillSplice) {
454
+ if (inDevMode && !getGlobalConfig().allowUndefinedArrayElements) {
455
+ const len = change.added.length
456
+ for (let i = 0; i < len; i++) {
457
+ const v = change.added[i]
458
+ if (v === undefined) {
459
+ throw failure(undefinedInsideArrayErrorMsg)
460
+ }
461
+ }
462
+ }
463
+
464
+ for (let i = 0; i < change.removedCount; i++) {
465
+ const removedValue = change.object[change.index + i]
466
+ tweak(removedValue, undefined)
467
+ }
468
+
469
+ for (let i = 0; i < change.added.length; i++) {
470
+ change.added[i] = tweak(change.added[i], {
471
+ parent: change.object,
472
+ path: change.index + i,
473
+ })
474
+ }
475
+
476
+ // we might also need to update the parent of the next indexes
477
+ const oldNextIndex = change.index + change.removedCount
478
+ const newNextIndex = change.index + change.added.length
479
+
480
+ if (oldNextIndex !== newNextIndex) {
481
+ for (let i = oldNextIndex, j = newNextIndex; i < change.object.length; i++, j++) {
482
+ setParent(
483
+ change.object[i], // value
484
+ {
485
+ parent: change.object,
486
+ path: j,
487
+ }, // parentPath
488
+ true, // indexChangeAllowed
489
+ false, // isDataObject
490
+ // just re-indexing
491
+ false // cloneIfApplicable
492
+ )
493
+ }
494
+ }
495
+ }
496
+
497
+ /**
498
+ * @internal
499
+ */
500
+ export function registerArrayTweaker() {
501
+ registerTweaker(TweakerPriority.Array, (value, parentPath) => {
502
+ if (isArray(value)) {
503
+ return tweakArray(value, parentPath, false)
504
+ }
505
+ return undefined
506
+ })
507
+ }
508
+
509
+ const observableOptions = {
510
+ deep: false,
511
+ }
512
+
513
+ function getValueAfterSplice<T>(
514
+ array: readonly T[],
515
+ i: number,
516
+ index: number,
517
+ remove: number,
518
+ addedItems: readonly T[]
519
+ ) {
520
+ const base = i - index
521
+ if (base < 0) {
522
+ return array[i]
523
+ }
524
+
525
+ if (base < addedItems.length) {
526
+ return addedItems[base]
527
+ }
528
+
529
+ return array[i - addedItems.length + remove]
530
+ }