mobx-keystone 1.25.0 → 2.1.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 (141) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/dist/mobx-keystone.esm.js +3627 -1540
  3. package/dist/mobx-keystone.esm.mjs +3627 -1540
  4. package/dist/mobx-keystone.umd.js +3638 -1543
  5. package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
  6. package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
  7. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +17 -10
  8. package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
  9. package/dist/types/deepChange/onDeepChange.d.ts +9 -0
  10. package/dist/types/parent/arrayPaths.d.ts +1 -0
  11. package/dist/types/parent/detach.d.ts +1 -1
  12. package/dist/types/parent/findChildren.d.ts +1 -1
  13. package/dist/types/parent/treeOrder.d.ts +1 -0
  14. package/dist/types/patch/applyPatches.d.ts +3 -0
  15. package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
  16. package/dist/types/ref/modelRefIdIndex.d.ts +1 -0
  17. package/dist/types/ref/nodesById.d.ts +1 -0
  18. package/dist/types/ref/refIdIndex.d.ts +1 -0
  19. package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
  20. package/dist/types/snapshot/applySnapshot.d.ts +1 -0
  21. package/dist/types/snapshot/fromSnapshot.d.ts +0 -3
  22. package/dist/types/standardActions/arrayActions.d.ts +1 -1
  23. package/dist/types/treeUtils/sandbox.d.ts +0 -4
  24. package/dist/types/tweaker/changeRollback.d.ts +1 -0
  25. package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
  26. package/dist/types/types/TypeCheckError.d.ts +1 -0
  27. package/dist/types/types/utility/typesRefinement.d.ts +2 -2
  28. package/dist/types/utils/ModelPool.d.ts +7 -1
  29. package/dist/types/utils/chunks.d.ts +1 -0
  30. package/dist/types/utils/forEachWithDelayedThrow.d.ts +1 -0
  31. package/dist/types/utils/index.d.ts +13 -0
  32. package/dist/types/utils/mapUtils.d.ts +2 -2
  33. package/dist/types/utils/setLike.d.ts +1 -0
  34. package/package.json +2 -2
  35. package/src/action/applyAction.ts +1 -1
  36. package/src/action/middleware.ts +17 -36
  37. package/src/action/modelFlow.ts +4 -5
  38. package/src/action/mutationBatch.ts +10 -10
  39. package/src/action/pendingActions.ts +3 -4
  40. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +19 -14
  41. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +29 -20
  42. package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +1 -1
  43. package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +4 -3
  44. package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +4 -12
  45. package/src/actionMiddlewares/actionSerialization/setSerializer.ts +4 -13
  46. package/src/actionMiddlewares/actionTrackingMiddleware.ts +11 -13
  47. package/src/actionMiddlewares/transactionMiddleware.ts +55 -25
  48. package/src/actionMiddlewares/undoMiddleware.ts +112 -59
  49. package/src/actionMiddlewares/utils.ts +2 -1
  50. package/src/computedTree/computedTree.ts +14 -3
  51. package/src/context/context.ts +17 -45
  52. package/src/dataModel/BaseDataModel.ts +20 -16
  53. package/src/dataModel/dataModelInstanceCache.ts +8 -0
  54. package/src/deepChange/onDeepChange.ts +179 -24
  55. package/src/frozen/Frozen.ts +50 -33
  56. package/src/model/BaseModel.ts +10 -13
  57. package/src/model/newModel.ts +21 -24
  58. package/src/model/utils.ts +2 -2
  59. package/src/modelShared/modelClassInitializer.ts +3 -2
  60. package/src/modelShared/modelDecorator.ts +39 -39
  61. package/src/modelShared/modelInfo.ts +1 -1
  62. package/src/modelShared/prop.ts +6 -9
  63. package/src/modelShared/sharedInternalModel.ts +16 -3
  64. package/src/parent/arrayPaths.ts +242 -0
  65. package/src/parent/coreObjectChildren.ts +300 -48
  66. package/src/parent/detach.ts +1 -1
  67. package/src/parent/findChildren.ts +4 -7
  68. package/src/parent/onChildAttachedTo.ts +157 -55
  69. package/src/parent/path.ts +15 -5
  70. package/src/parent/setParent.ts +34 -15
  71. package/src/parent/treeOrder.ts +53 -0
  72. package/src/parent/walkTree.ts +12 -101
  73. package/src/patch/applyPatches.ts +74 -34
  74. package/src/patch/emitPatch.ts +142 -26
  75. package/src/patch/jsonPatch.ts +5 -0
  76. package/src/patch/patchRecorder.ts +18 -14
  77. package/src/redux/connectReduxDevTools.ts +24 -18
  78. package/src/redux/redux.ts +5 -21
  79. package/src/ref/core.ts +81 -29
  80. package/src/ref/modelRefIdIndex.ts +131 -0
  81. package/src/ref/nodesById.ts +60 -0
  82. package/src/ref/refIdIndex.ts +209 -0
  83. package/src/rootStore/attachDetach.ts +27 -16
  84. package/src/rootStore/rootStore.ts +10 -6
  85. package/src/snapshot/SnapshotOf.ts +2 -2
  86. package/src/snapshot/applySnapshot.ts +27 -19
  87. package/src/snapshot/clone.ts +1 -2
  88. package/src/snapshot/cloneTreeValue.ts +1 -2
  89. package/src/snapshot/fromModelSnapshot.ts +6 -2
  90. package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
  91. package/src/snapshot/fromSnapshot.ts +2 -6
  92. package/src/snapshot/internal.ts +89 -19
  93. package/src/snapshot/onSnapshot.ts +12 -1
  94. package/src/snapshot/reconcileModelSnapshot.ts +6 -6
  95. package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -4
  96. package/src/standardActions/arrayActions.ts +12 -2
  97. package/src/standardActions/objectActions.ts +3 -2
  98. package/src/transforms/ImmutableDate.ts +4 -0
  99. package/src/transforms/asMap.ts +3 -1
  100. package/src/treeUtils/deepEquals.ts +15 -13
  101. package/src/treeUtils/draft.ts +8 -0
  102. package/src/treeUtils/sandbox.ts +31 -27
  103. package/src/tweaker/changeRollback.ts +218 -0
  104. package/src/tweaker/core.ts +33 -22
  105. package/src/tweaker/treeNodeMetadata.ts +12 -2
  106. package/src/tweaker/tweak.ts +183 -19
  107. package/src/tweaker/tweakArray.ts +176 -33
  108. package/src/tweaker/tweakPlainObject.ts +117 -39
  109. package/src/tweaker/tweakedChangeListeners.ts +61 -0
  110. package/src/tweaker/typeChecking.ts +25 -15
  111. package/src/types/TypeCheckError.ts +17 -19
  112. package/src/types/arrayBased/typesArray.ts +10 -11
  113. package/src/types/createCachedTypeCheck.ts +541 -88
  114. package/src/types/objectBased/typesDataModelData.ts +4 -2
  115. package/src/types/objectBased/typesModel.ts +4 -2
  116. package/src/types/objectBased/typesObject.ts +20 -16
  117. package/src/types/objectBased/typesRecord.ts +8 -18
  118. package/src/types/objectBased/typesRef.ts +2 -2
  119. package/src/types/primitiveBased/typesEnum.ts +4 -2
  120. package/src/types/primitiveBased/typesPrimitive.ts +9 -4
  121. package/src/types/typeCheckErrorUtils.ts +1 -0
  122. package/src/types/utility/typesCodecSupport.ts +97 -56
  123. package/src/types/utility/typesMap.ts +24 -41
  124. package/src/types/utility/typesRefinement.ts +3 -2
  125. package/src/types/utility/typesSet.ts +24 -0
  126. package/src/utils/ModelPool.ts +78 -22
  127. package/src/utils/chainFns.ts +4 -0
  128. package/src/utils/chunks.ts +271 -0
  129. package/src/utils/decorators.ts +1 -1
  130. package/src/utils/errorDiagnostics.ts +6 -2
  131. package/src/utils/forEachWithDelayedThrow.ts +83 -0
  132. package/src/utils/index.ts +71 -2
  133. package/src/utils/mapUtils.ts +3 -7
  134. package/src/utils/setIfDifferent.ts +3 -2
  135. package/src/utils/setLike.ts +134 -0
  136. package/src/wrappers/ArraySet.ts +5 -9
  137. package/src/wrappers/ObjectMap.ts +15 -11
  138. package/src/wrappers/asMap.ts +30 -18
  139. package/src/wrappers/asSet.ts +36 -12
  140. package/dist/types/action/modelFlowPromiseGenerator.d.ts +0 -1
  141. package/src/action/modelFlowPromiseGenerator.ts +0 -127
@@ -1,6 +1,7 @@
1
1
  import { HookAction } from "../action/hookActions"
2
2
  import { wrapModelMethodInActionIfNeeded } from "../action/wrapInAction"
3
3
  import type { AnyDataModel } from "../dataModel/BaseDataModel"
4
+ import { dataModelInstanceCache } from "../dataModel/dataModelInstanceCache"
4
5
  import { isDataModelClass } from "../dataModel/utils"
5
6
  import { enterInitPhase, exitInitPhase } from "../deepChange/onDeepChange"
6
7
  import { getGlobalConfig } from "../globalConfig"
@@ -18,6 +19,7 @@ import {
18
19
  addLateInitializationFunction,
19
20
  failure,
20
21
  getMobxVersion,
22
+ hasStoredMobxAnnotations,
21
23
  logWarning,
22
24
  makeObservableCompat,
23
25
  runAfterNewSymbol,
@@ -41,49 +43,25 @@ export const model =
41
43
  ): MC =>
42
44
  internalModel(name, clazz) as any
43
45
 
46
+ interface AfterClassInitializationData {
47
+ needsMakeObservable: boolean | undefined
48
+ type: "class" | "data"
49
+ }
50
+
44
51
  const afterClassInitializationData = new WeakMap<
45
52
  ModelClass<AnyModel | AnyDataModel>,
46
- {
47
- needsMakeObservable: boolean | undefined
48
- type: "class" | "data"
49
- }
53
+ AfterClassInitializationData
50
54
  >()
51
55
 
52
- const runAfterClassInitialization = (
53
- target: ModelClass<AnyModel | AnyDataModel>,
54
- instance: any
55
- ) => {
56
+ const runAfterClassInitialization = (tag: AfterClassInitializationData, instance: any) => {
56
57
  runLateInitializationFunctions(instance, runAfterNewSymbol)
57
58
 
58
- const tag = afterClassInitializationData.get(target)!
59
-
60
- // compatibility with mobx 6 and 7
59
+ // MobX 6 legacy decorators need makeObservable, while MobX 7 annotations (see
60
+ // decoratedModel) and 2022.3 decorators initialize themselves. Otherwise it
61
+ // would only add an empty observable administration to every instance.
62
+ tag.needsMakeObservable ??= getMobxVersion() === 6 && hasStoredMobxAnnotations(instance)
61
63
  if (tag.needsMakeObservable) {
62
- // we know it can be done and shouldn't fail
63
64
  makeObservableCompat(instance)
64
- } else if (tag.needsMakeObservable === undefined) {
65
- if (getMobxVersion() >= 6) {
66
- try {
67
- makeObservableCompat(instance)
68
- tag.needsMakeObservable = true
69
- } catch (e) {
70
- const err = e as Error
71
- if (
72
- err.message !==
73
- "[MobX] No annotations were passed to makeObservable, but no decorator members have been found either" &&
74
- err.message !==
75
- "[MobX] No annotations were passed to makeObservable, but no decorated members have been found either"
76
- ) {
77
- throw err
78
- }
79
-
80
- // sadly we need to use this hack since the PR to do this the proper way
81
- // was rejected on the mobx side
82
- tag.needsMakeObservable = false
83
- }
84
- } else {
85
- tag.needsMakeObservable = false
86
- }
87
65
  }
88
66
 
89
67
  // the object is ready
@@ -112,11 +90,33 @@ const runAfterClassInitialization = (
112
90
 
113
91
  const proxyClassHandler: ProxyHandler<ModelClass<AnyModel | AnyDataModel>> = {
114
92
  construct(clazz, args) {
115
- const instance = new (clazz as any)(...args)
93
+ const tag = afterClassInitializationData.get(clazz)!
116
94
 
117
- runAfterClassInitialization(clazz, instance)
95
+ if (tag.type !== "data") {
96
+ const instance = new (clazz as any)(...args)
97
+ runAfterClassInitialization(tag, instance)
98
+ return instance
99
+ }
118
100
 
119
- return instance
101
+ // Data models are cached by their backing data, so constructing one over data that
102
+ // already has an instance must not rerun its class fields nor its lazy initializer.
103
+ const modelClass = args[1]?.modelClass ?? modelInfoByClass.get(clazz)!.class
104
+ const cached = dataModelInstanceCache.get(modelClass)?.get(args[0])
105
+ if (cached) {
106
+ return cached
107
+ }
108
+
109
+ let instance: any
110
+ try {
111
+ instance = new (clazz as any)(...args)
112
+ runAfterClassInitialization(tag, instance)
113
+ return instance
114
+ } catch (error) {
115
+ // The base constructor cleans up after itself, but class fields and late
116
+ // initialization run after it returns, so discard those instances too.
117
+ dataModelInstanceCache.get(modelClass)?.delete(instance ? instance.$ : args[0])
118
+ throw error
119
+ }
120
120
  },
121
121
  }
122
122
 
@@ -245,7 +245,7 @@ export function decoratedModel<M, MC extends abstract new (...ags: any) => M>(
245
245
  })
246
246
  }
247
247
 
248
- return (name ? model(name)(clazz as unknown as ModelClass<AnyModel>) : clazz) as MC
248
+ return (name !== undefined ? model(name)(clazz as unknown as ModelClass<AnyModel>) : clazz) as MC
249
249
  }
250
250
 
251
251
  type MobxAnnotation = {
@@ -15,7 +15,7 @@ export interface ModelInfo {
15
15
  */
16
16
  export const modelInfoByName: {
17
17
  [name: string]: ModelInfo
18
- } = {}
18
+ } = Object.create(null)
19
19
 
20
20
  /**
21
21
  * @internal
@@ -1,6 +1,6 @@
1
1
  import type { SnapshotInOf, SnapshotOutOf } from "../snapshot/SnapshotOf"
2
2
  import type { AnyStandardType } from "../types/schemas"
3
- import { lazy } from "../utils"
3
+ import { isEqualOrBothNaN, lazy } from "../utils"
4
4
  import { runWithErrorDiagnosticsContext, withErrorPathSegment } from "../utils/errorDiagnostics"
5
5
  import { getOrCreate } from "../utils/mapUtils"
6
6
  import type { Flatten, IsNeverType, IsOptionalValue } from "../utils/types"
@@ -618,11 +618,6 @@ function parseSetterConfig(
618
618
 
619
619
  const propCache = new Map<unknown, AnyModelProp>()
620
620
 
621
- let cacheTransformResult = false
622
- const cacheTransformedValueFn = () => {
623
- cacheTransformResult = true
624
- }
625
-
626
621
  /** @internal */
627
622
  export function toFullModelPropTransform(
628
623
  transformObject: ModelPropTransform<unknown, unknown>
@@ -651,7 +646,7 @@ export function toFullModelPropTransform(
651
646
  const modelCache = getOrCreate(cache, model, () => new Map())
652
647
 
653
648
  let propCache = modelCache.get(propName)
654
- if (propCache?.originalValue !== originalValue) {
649
+ if (!propCache || !isEqualOrBothNaN(propCache.originalValue, originalValue)) {
655
650
  // original changed, invalidate cache
656
651
  modelCache.delete(propName)
657
652
  propCache = undefined
@@ -674,10 +669,12 @@ export function toFullModelPropTransform(
674
669
  untransform(transformedValue: unknown, model: object, propName: PropertyKey) {
675
670
  const modelCache = getOrCreate(cache, model, () => new Map())
676
671
 
677
- cacheTransformResult = false
672
+ let cacheTransformResult = false
678
673
  const originalValue = untransform({
679
674
  transformedValue,
680
- cacheTransformedValue: cacheTransformedValueFn,
675
+ cacheTransformedValue: () => {
676
+ cacheTransformResult = true
677
+ },
681
678
  })
682
679
  if (cacheTransformResult) {
683
680
  modelCache.set(propName, { originalValue, transformedValue })
@@ -19,7 +19,14 @@ import type { AnyType } from "../types/schemas"
19
19
  import { tProp } from "../types/tProp"
20
20
  import { resolveStoredType } from "../types/utility/typesCodec"
21
21
  import { typesUnchecked } from "../types/utility/typesUnchecked"
22
- import { addHiddenProp, assertIsObject, failure, propNameToSetterName } from "../utils"
22
+ import {
23
+ addHiddenProp,
24
+ assertIsObject,
25
+ failure,
26
+ hasOwnProp,
27
+ propNameToSetterName,
28
+ setProtoProp,
29
+ } from "../utils"
23
30
  import { chainFns } from "../utils/chainFns"
24
31
  import { withErrorPathSegment } from "../utils/errorDiagnostics"
25
32
  import { type ModelClass, modelInitializedSymbol } from "./BaseModelShared"
@@ -275,7 +282,9 @@ export function sharedInternalModel<
275
282
  dataTypeChecker = typesObject(() => {
276
283
  const typeCheckerObj: Record<string, AnyType> = {}
277
284
  for (const [k, mp] of Object.entries(composedModelProps)) {
278
- typeCheckerObj[k] = mp._typeChecker ? resolveStoredType(mp._typeChecker) : typesUnchecked()
285
+ const typeChecker = mp._typeChecker ? resolveStoredType(mp._typeChecker) : typesUnchecked()
286
+ if (k === "__proto__") setProtoProp(typeCheckerObj, typeChecker)
287
+ else typeCheckerObj[k] = typeChecker
279
288
  }
280
289
  return typeCheckerObj
281
290
  })
@@ -451,7 +460,11 @@ function getModelPropsSnapshotProcessor(
451
460
  return (sn) => {
452
461
  const newSn = { ...sn }
453
462
  for (const [propName, processor] of propsWithProcessor) {
454
- newSn[propName] = withErrorPathSegment(propName, () => processor(sn[propName]))
463
+ const value = withErrorPathSegment(propName, () =>
464
+ processor(hasOwnProp(sn, propName) ? sn[propName] : undefined)
465
+ )
466
+ if (propName === "__proto__") setProtoProp(newSn, value)
467
+ else newSn[propName] = value
455
468
  }
456
469
  return newSn
457
470
  }
@@ -0,0 +1,242 @@
1
+ import { createAtom, type IAtom } from "mobx"
2
+ import { treeNodeMetadata } from "../tweaker/treeNodeMetadata"
3
+ import { isPrimitive } from "../utils"
4
+ import { ChunkedSequence, chunkSize, type SequenceChunk } from "../utils/chunks"
5
+ import { reportParentPathChanged } from "./core"
6
+ import type { ParentPath } from "./path"
7
+
8
+ /** Arrays whose splices would reindex more children than this switch to lazy indexes. */
9
+ const lazyIndexesThreshold = 1024
10
+
11
+ class PathChunk implements SequenceChunk<LazyArrayParentPath | undefined> {
12
+ /** Path of each item, or undefined for primitives. */
13
+ items: (LazyArrayParentPath | undefined)[]
14
+ offset = 0
15
+ /**
16
+ * Changed when the index of any of its items changes, or when items move
17
+ * out of it, so observers of their paths follow them to their new chunk.
18
+ */
19
+ readonly atom: IAtom = createAtom("arrayPathChunk")
20
+ readonly owner: ArrayPaths
21
+
22
+ constructor(owner: ArrayPaths, items: (LazyArrayParentPath | undefined)[]) {
23
+ this.owner = owner
24
+ this.items = items
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Parent path of an item of a large array. Its index is the offset of its chunk
30
+ * plus its position within it, so a splice only renumbers one chunk and moves
31
+ * the offsets of the chunks after it, instead of reindexing every item after it.
32
+ * Once removed, it keeps the index it had.
33
+ *
34
+ * @internal
35
+ */
36
+ export class LazyArrayParentPath implements ParentPath<object> {
37
+ // declared rather than defined fields: there are as many instances as array
38
+ // items, and defined fields compile to an Object.defineProperty call each
39
+ declare readonly parent: object
40
+ /** Undefined once removed from the array. */
41
+ declare chunk: PathChunk | undefined
42
+ /** Position within the chunk, or the final index once removed. */
43
+ declare local: number
44
+
45
+ constructor(parent: object, chunk: PathChunk, local: number) {
46
+ this.parent = parent
47
+ this.chunk = chunk
48
+ this.local = local
49
+ }
50
+
51
+ get path(): number {
52
+ const chunk = this.chunk
53
+ if (!chunk) {
54
+ return this.local
55
+ }
56
+ chunk.owner.updateOffsets()
57
+ return chunk.offset + this.local
58
+ }
59
+
60
+ /**
61
+ * An immutable copy for callers outside the library, which may keep it,
62
+ * reused while the index stays the same.
63
+ */
64
+ toParentPath(): ParentPath<object> {
65
+ const path = this.path
66
+ let publicPath = publicPaths.get(this)
67
+ if (publicPath?.path !== path) {
68
+ publicPath = { parent: this.parent, path }
69
+ publicPaths.set(this, publicPath)
70
+ }
71
+ return publicPath
72
+ }
73
+
74
+ /** Makes observers of this path also react to index changes. */
75
+ reportObserved(): void {
76
+ this.chunk?.atom.reportObserved()
77
+ }
78
+ }
79
+
80
+ // kept apart, since few paths are ever handed out
81
+ const publicPaths = new WeakMap<LazyArrayParentPath, ParentPath<object>>()
82
+
83
+ class ArrayPaths extends ChunkedSequence<LazyArrayParentPath | undefined, PathChunk> {
84
+ readonly array: readonly unknown[]
85
+ private offsetsDirty = false
86
+
87
+ constructor(array: readonly unknown[]) {
88
+ super()
89
+ this.array = array
90
+ const length = array.length
91
+ for (let start = 0; start < length; start += chunkSize) {
92
+ // a slice is much faster than indexed reads through an observable array
93
+ const items = array.slice(start, start + chunkSize)
94
+ const chunk = new PathChunk(this, new Array(items.length))
95
+ for (let i = 0; i < items.length; i++) {
96
+ const item = items[i]
97
+ if (!isPrimitive(item)) {
98
+ const entry = new LazyArrayParentPath(array, chunk, i)
99
+ chunk.items[i] = entry
100
+ treeNodeMetadata.get(item as object)!.parentPath = entry
101
+ // current observers of its path do not observe its chunk yet
102
+ reportParentPathChanged(item as object)
103
+ }
104
+ }
105
+ this.chunks.push(chunk)
106
+ }
107
+ this.offsetsDirty = true
108
+ }
109
+
110
+ updateOffsets(): void {
111
+ if (!this.offsetsDirty) {
112
+ return
113
+ }
114
+ this.offsetsDirty = false
115
+ let offset = 0
116
+ for (const chunk of this.chunks) {
117
+ chunk.offset = offset
118
+ offset += chunk.items.length
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Paths for values to be inserted at an index, undefined for primitives. They
124
+ * already resolve to their final index, but are only added by `splice`, so
125
+ * attaching the values can fail without leaving them behind.
126
+ */
127
+ createPaths(index: number, values: readonly unknown[]): (LazyArrayParentPath | undefined)[] {
128
+ this.updateOffsets()
129
+ if (this.chunks.length === 0) {
130
+ this.chunks.push(this.createChunk([]))
131
+ }
132
+ const [c, start] = this.locate(index)
133
+ const chunk = this.chunks[c]
134
+ const paths: (LazyArrayParentPath | undefined)[] = new Array(values.length)
135
+ for (let i = 0; i < values.length; i++) {
136
+ if (!isPrimitive(values[i])) {
137
+ paths[i] = new LazyArrayParentPath(this.array, chunk, start + i)
138
+ }
139
+ }
140
+ return paths
141
+ }
142
+
143
+ /** Applies a splice, freezing the removed paths and adding ones from `createPaths`. */
144
+ applySplice(
145
+ index: number,
146
+ removedCount: number,
147
+ addedPaths: (LazyArrayParentPath | undefined)[]
148
+ ): void {
149
+ this.updateOffsets()
150
+ this.splice(index, removedCount, addedPaths, freezeRemovedPath)
151
+ this.offsetsDirty = true
152
+ }
153
+
154
+ /** Replaces the path of an updated item, freezing the old one. */
155
+ set(index: number, value: unknown): LazyArrayParentPath | undefined {
156
+ this.updateOffsets()
157
+ const [c, local] = this.locate(index)
158
+ const chunk = this.chunks[c]
159
+ const old = chunk.items[local]
160
+ if (old) {
161
+ freezeRemovedPath(old, chunk, local)
162
+ }
163
+ const entry = isPrimitive(value) ? undefined : new LazyArrayParentPath(this.array, chunk, local)
164
+ chunk.items[local] = entry
165
+ return entry
166
+ }
167
+
168
+ protected createChunk(items: (LazyArrayParentPath | undefined)[]): PathChunk {
169
+ return new PathChunk(this, items)
170
+ }
171
+
172
+ protected onSpliced(chunk: PathChunk, from: number): void {
173
+ // the items after the splice moved within the chunk, and may move to other
174
+ // chunks when it is resized, where observers will follow them
175
+ renumber(chunk, from)
176
+ chunk.atom.reportChanged()
177
+ }
178
+
179
+ protected onItemsMoved(chunk: PathChunk, from: number): void {
180
+ renumber(chunk, from)
181
+ }
182
+
183
+ protected onShifted(chunk: PathChunk): void {
184
+ // the index of every item after the splice changed
185
+ chunk.atom.reportChanged()
186
+ }
187
+ }
188
+
189
+ function freezeRemovedPath(
190
+ entry: LazyArrayParentPath | undefined,
191
+ chunk: PathChunk,
192
+ local: number
193
+ ): void {
194
+ if (entry) {
195
+ entry.local = chunk.offset + local
196
+ entry.chunk = undefined
197
+ }
198
+ }
199
+
200
+ function renumber(chunk: PathChunk, from: number): void {
201
+ const items = chunk.items
202
+ for (let i = from; i < items.length; i++) {
203
+ const entry = items[i]
204
+ if (entry) {
205
+ entry.chunk = chunk
206
+ entry.local = i
207
+ }
208
+ }
209
+ }
210
+
211
+ const arrayPaths = new WeakMap<object, ArrayPaths>()
212
+
213
+ /**
214
+ * Lazy indexes of an array, created once a splice would reindex many of its
215
+ * items.
216
+ *
217
+ * @internal
218
+ */
219
+ export function getArrayPaths(
220
+ array: readonly unknown[],
221
+ reindexedCount: number
222
+ ): ArrayPaths | undefined {
223
+ let paths = arrayPaths.get(array)
224
+ if (!paths && reindexedCount > lazyIndexesThreshold) {
225
+ paths = new ArrayPaths(array)
226
+ arrayPaths.set(array, paths)
227
+ }
228
+ return paths
229
+ }
230
+
231
+ /**
232
+ * Makes an attached value use its lazy path, even if it was attached with an
233
+ * equal one.
234
+ *
235
+ * @internal
236
+ */
237
+ export function setLazyArrayParentPath(entry: LazyArrayParentPath, node: object): void {
238
+ const metadata = treeNodeMetadata.get(node)!
239
+ if (metadata.parentPath !== entry) {
240
+ metadata.parentPath = entry
241
+ }
242
+ }