mobx-keystone 2.0.0 → 2.2.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 (102) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/dist/mobx-keystone.esm.js +2561 -1214
  4. package/dist/mobx-keystone.esm.mjs +2561 -1214
  5. package/dist/mobx-keystone.umd.js +2566 -1220
  6. package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
  7. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +11 -12
  8. package/dist/types/modelShared/modelDecorator.d.ts +5 -2
  9. package/dist/types/modelShared/prop.d.ts +42 -29
  10. package/dist/types/parent/arrayPaths.d.ts +1 -0
  11. package/dist/types/parent/treeOrder.d.ts +1 -0
  12. package/dist/types/patch/applyPatches.d.ts +2 -1
  13. package/dist/types/ref/Ref.d.ts +1 -4
  14. package/dist/types/ref/modelRefIdIndex.d.ts +1 -0
  15. package/dist/types/ref/nodesById.d.ts +1 -0
  16. package/dist/types/ref/refIdIndex.d.ts +1 -0
  17. package/dist/types/snapshot/SnapshotOf.d.ts +4 -4
  18. package/dist/types/snapshot/applySnapshot.d.ts +2 -1
  19. package/dist/types/snapshot/fromSnapshot.d.ts +2 -5
  20. package/dist/types/snapshot/getSnapshot.d.ts +1 -1
  21. package/dist/types/tweaker/changeRollback.d.ts +1 -0
  22. package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
  23. package/dist/types/types/index.d.ts +1 -0
  24. package/dist/types/types/schemas.d.ts +41 -37
  25. package/dist/types/types/tProp.d.ts +12 -6
  26. package/dist/types/types/types.d.ts +6 -6
  27. package/dist/types/types/utility/typeToStoredData.d.ts +14 -10
  28. package/dist/types/types/utility/typesCodecCore.d.ts +3 -0
  29. package/dist/types/utils/ModelPool.d.ts +7 -2
  30. package/dist/types/utils/chunks.d.ts +1 -0
  31. package/dist/types/utils/types.d.ts +6 -0
  32. package/dist/types/wrappers/ArraySet.d.ts +1 -0
  33. package/package.json +5 -3
  34. package/src/action/modelFlow.ts +35 -66
  35. package/src/actionMiddlewares/actionTrackingMiddleware.ts +41 -71
  36. package/src/actionMiddlewares/transactionMiddleware.ts +41 -42
  37. package/src/actionMiddlewares/undoMiddleware.ts +63 -35
  38. package/src/context/context.ts +15 -36
  39. package/src/dataModel/utils.ts +5 -28
  40. package/src/deepChange/onDeepChange.ts +44 -126
  41. package/src/model/BaseModel.ts +7 -5
  42. package/src/model/utils.ts +5 -10
  43. package/src/modelShared/modelDecorator.ts +16 -30
  44. package/src/modelShared/prop.ts +115 -111
  45. package/src/modelShared/sharedInternalModel.ts +2 -2
  46. package/src/parent/arrayPaths.ts +239 -0
  47. package/src/parent/coreObjectChildren.ts +300 -48
  48. package/src/parent/onChildAttachedTo.ts +130 -44
  49. package/src/parent/path.ts +10 -12
  50. package/src/parent/setParent.ts +27 -15
  51. package/src/parent/treeOrder.ts +53 -0
  52. package/src/parent/walkTree.ts +0 -73
  53. package/src/patch/applyPatches.ts +31 -26
  54. package/src/patch/patchRecorder.ts +13 -28
  55. package/src/ref/core.ts +69 -46
  56. package/src/ref/modelRefIdIndex.ts +131 -0
  57. package/src/ref/nodesById.ts +60 -0
  58. package/src/ref/refIdIndex.ts +209 -0
  59. package/src/rootStore/rootStore.ts +1 -3
  60. package/src/snapshot/SnapshotOf.ts +24 -48
  61. package/src/snapshot/applySnapshot.ts +27 -19
  62. package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
  63. package/src/snapshot/fromSnapshot.ts +12 -9
  64. package/src/snapshot/getSnapshot.ts +1 -1
  65. package/src/snapshot/internal.ts +67 -12
  66. package/src/snapshot/reconcileModelSnapshot.ts +5 -3
  67. package/src/snapshot/reconcilePlainObjectSnapshot.ts +3 -1
  68. package/src/treeUtils/draft.ts +6 -1
  69. package/src/treeUtils/sandbox.ts +10 -7
  70. package/src/tweaker/changeRollback.ts +218 -0
  71. package/src/tweaker/core.ts +33 -20
  72. package/src/tweaker/treeNodeMetadata.ts +12 -2
  73. package/src/tweaker/tweak.ts +181 -18
  74. package/src/tweaker/tweakArray.ts +127 -77
  75. package/src/tweaker/tweakPlainObject.ts +65 -46
  76. package/src/tweaker/tweakedChangeListeners.ts +61 -0
  77. package/src/tweaker/typeChecking.ts +5 -104
  78. package/src/types/arrayBased/typesArray.ts +23 -30
  79. package/src/types/arrayBased/typesTuple.ts +16 -25
  80. package/src/types/createCachedTypeCheck.ts +509 -94
  81. package/src/types/index.ts +1 -0
  82. package/src/types/objectBased/typesDataModelData.ts +7 -8
  83. package/src/types/objectBased/typesObject.ts +11 -15
  84. package/src/types/objectBased/typesRecord.ts +12 -19
  85. package/src/types/schemas.ts +59 -71
  86. package/src/types/tProp.ts +29 -29
  87. package/src/types/utility/typeToStoredData.ts +22 -21
  88. package/src/types/utility/typesCodecCore.ts +5 -0
  89. package/src/types/utility/typesCodecSupport.ts +3 -3
  90. package/src/types/utility/typesMap.ts +43 -51
  91. package/src/types/utility/typesRefinement.ts +7 -8
  92. package/src/types/utility/typesSkipCheck.ts +7 -8
  93. package/src/types/utility/typesTag.ts +7 -8
  94. package/src/utils/ModelPool.ts +76 -37
  95. package/src/utils/chunks.ts +271 -0
  96. package/src/utils/decorators.ts +5 -5
  97. package/src/utils/index.ts +38 -20
  98. package/src/utils/types.ts +5 -0
  99. package/src/wrappers/ArraySet.ts +27 -14
  100. package/src/wrappers/ObjectMap.ts +1 -6
  101. package/src/wrappers/asMap.ts +1 -21
  102. package/src/wrappers/asSet.ts +0 -8
@@ -3,7 +3,7 @@ import type { AnyStandardType } from "../types/schemas"
3
3
  import { isEqualOrBothNaN, lazy } from "../utils"
4
4
  import { runWithErrorDiagnosticsContext, withErrorPathSegment } from "../utils/errorDiagnostics"
5
5
  import { getOrCreate } from "../utils/mapUtils"
6
- import type { Flatten, IsNeverType, IsOptionalValue } from "../utils/types"
6
+ import type { IsNeverType, IsOptionalValue, SimplifyObject } from "../utils/types"
7
7
 
8
8
  /**
9
9
  * @ignore
@@ -71,50 +71,18 @@ export interface ModelProp<
71
71
  * Adds a setter to the property. The setter will be named `set${CapitalizedPropName}`
72
72
  * and will be available in the model instance.
73
73
  */
74
- withSetter(): ModelProp<
75
- TPropValue,
76
- TPropCreationValue,
77
- TTransformedValue,
78
- TTransformedCreationValue,
79
- TIsRequired,
80
- TIsId,
81
- string,
82
- TFromSnapshotOverride,
83
- TToSnapshotOverride
84
- >
74
+ withSetter(): ModelPropWithSetter<this>
85
75
  /**
86
76
  * Adds a setter with a transform to the property. The setter will be named `set${CapitalizedPropName}`
87
77
  * and will be available in the model instance.
88
78
  */
89
79
  withSetter(
90
80
  valueTransform: ModelPropSetterValueTransform<TTransformedValue>
91
- ): ModelProp<
92
- TPropValue,
93
- TPropCreationValue,
94
- TTransformedValue,
95
- TTransformedCreationValue,
96
- TIsRequired,
97
- TIsId,
98
- string,
99
- TFromSnapshotOverride,
100
- TToSnapshotOverride
101
- >
81
+ ): ModelPropWithSetter<this>
102
82
  /**
103
83
  * @deprecated Setter methods are preferred.
104
84
  */
105
- withSetter(
106
- mode: "assign"
107
- ): ModelProp<
108
- TPropValue,
109
- TPropCreationValue,
110
- TTransformedValue,
111
- TTransformedCreationValue,
112
- TIsRequired,
113
- TIsId,
114
- string,
115
- TFromSnapshotOverride,
116
- TToSnapshotOverride
117
- >
85
+ withSetter(mode: "assign"): ModelPropWithSetter<this>
118
86
 
119
87
  /**
120
88
  * Sets a transform for the property instance value.
@@ -130,17 +98,7 @@ export interface ModelProp<
130
98
  */
131
99
  withTransform<TTV>(
132
100
  transform: ModelPropTransform<NonNullable<TPropValue>, TTV>
133
- ): ModelProp<
134
- TPropValue,
135
- TPropCreationValue,
136
- TTV | Extract<TPropValue, null | undefined>,
137
- TTV | Extract<TPropCreationValue, null | undefined>,
138
- TIsRequired,
139
- TIsId,
140
- THasSetter,
141
- TFromSnapshotOverride,
142
- TToSnapshotOverride
143
- >
101
+ ): ModelPropWithTransform<this, TTV>
144
102
 
145
103
  /**
146
104
  * Sets snapshot processors for this property.
@@ -158,18 +116,69 @@ export interface ModelProp<
158
116
  >(processor: {
159
117
  fromSnapshot?: (sn: FS) => ModelPropFromSnapshot<This>
160
118
  toSnapshot?: (sn: ModelPropToSnapshot<This>) => TS
161
- }): ModelProp<
162
- TPropValue,
163
- TPropCreationValue,
164
- TTransformedValue,
165
- TTransformedCreationValue,
166
- TIsRequired,
167
- TIsId,
168
- THasSetter,
169
- FS,
170
- TS
171
- >
119
+ }): ModelPropWithSnapshotProcessor<this, FS, TS>
120
+ }
121
+
122
+ type ModelPropStoredMetadataKey = "$storedValueType" | "$storedCreationValueType"
123
+
124
+ // keeps the stored value metadata that `tProp` adds (e.g. the encoded type of a codec)
125
+ // when a model prop is derived through one of its modifiers
126
+ type ModelPropStoredMetadata<MP, K extends PropertyKey> = MP extends {
127
+ $storedValueType: unknown
172
128
  }
129
+ ? Pick<MP, Extract<keyof MP, K>>
130
+ : {}
131
+
132
+ // the modifier result types are exported so emitted declaration files can reference them by name
133
+ // instead of expanding them inline in every exported model class
134
+
135
+ /**
136
+ * The model property returned by `withSetter`.
137
+ */
138
+ export type ModelPropWithSetter<MP extends AnyModelProp> = ModelProp<
139
+ MP["$valueType"],
140
+ MP["$creationValueType"],
141
+ MP["$transformedValueType"],
142
+ MP["$transformedCreationValueType"],
143
+ MP["$isRequired"],
144
+ MP["$isId"],
145
+ string,
146
+ MP["$fromSnapshotOverride"],
147
+ MP["$toSnapshotOverride"]
148
+ > &
149
+ ModelPropStoredMetadata<MP, ModelPropStoredMetadataKey | "$typedFromSnapshotOverride">
150
+
151
+ /**
152
+ * The model property returned by `withTransform`.
153
+ */
154
+ export type ModelPropWithTransform<MP extends AnyModelProp, TTV> = ModelProp<
155
+ MP["$valueType"],
156
+ MP["$creationValueType"],
157
+ TTV | Extract<MP["$valueType"], null | undefined>,
158
+ TTV | Extract<MP["$creationValueType"], null | undefined>,
159
+ MP["$isRequired"],
160
+ MP["$isId"],
161
+ MP["$hasSetter"],
162
+ MP["$fromSnapshotOverride"],
163
+ MP["$toSnapshotOverride"]
164
+ > &
165
+ ModelPropStoredMetadata<MP, ModelPropStoredMetadataKey | "$typedFromSnapshotOverride">
166
+
167
+ /**
168
+ * The model property returned by `withSnapshotProcessor`.
169
+ */
170
+ export type ModelPropWithSnapshotProcessor<MP extends AnyModelProp, FS, TS> = ModelProp<
171
+ MP["$valueType"],
172
+ MP["$creationValueType"],
173
+ MP["$transformedValueType"],
174
+ MP["$transformedCreationValueType"],
175
+ MP["$isRequired"],
176
+ MP["$isId"],
177
+ MP["$hasSetter"],
178
+ FS,
179
+ TS
180
+ > &
181
+ ModelPropStoredMetadata<MP, ModelPropStoredMetadataKey>
173
182
 
174
183
  /**
175
184
  * The snapshot in type of a model property.
@@ -189,23 +198,19 @@ export type ModelPropToSnapshot<MP extends AnyModelProp> = IsNeverType<
189
198
  MP["$toSnapshotOverride"]
190
199
  >
191
200
 
192
- export type ModelPropStoredValue<MP extends AnyModelProp> = MP extends {
193
- $storedValueType: infer TStoredValue
194
- }
195
- ? TStoredValue
201
+ export type ModelPropStoredValue<MP extends AnyModelProp> = "$storedValueType" extends keyof MP
202
+ ? MP["$storedValueType" & keyof MP]
196
203
  : MP["$valueType"]
197
204
 
198
- export type ModelPropStoredCreationValue<MP extends AnyModelProp> = MP extends {
199
- $storedCreationValueType: infer TStoredCreationValue
200
- }
201
- ? TStoredCreationValue
202
- : MP["$creationValueType"]
205
+ export type ModelPropStoredCreationValue<MP extends AnyModelProp> =
206
+ "$storedCreationValueType" extends keyof MP
207
+ ? MP["$storedCreationValueType" & keyof MP]
208
+ : MP["$creationValueType"]
203
209
 
204
- export type ModelPropFromSnapshotOverride<MP extends AnyModelProp> = MP extends {
205
- $typedFromSnapshotOverride: infer TFromSnapshotOverride
206
- }
207
- ? TFromSnapshotOverride
208
- : MP["$fromSnapshotOverride"]
210
+ export type ModelPropFromSnapshotOverride<MP extends AnyModelProp> =
211
+ "$typedFromSnapshotOverride" extends keyof MP
212
+ ? MP["$typedFromSnapshotOverride" & keyof MP]
213
+ : MP["$fromSnapshotOverride"]
209
214
 
210
215
  /**
211
216
  * A model prop transform.
@@ -245,13 +250,13 @@ export type RequiredModelProps<MP extends ModelProps> = {
245
250
  [K in keyof MP]: MP[K]["$isRequired"] & K
246
251
  }[keyof MP]
247
252
 
248
- export type ModelPropsToUntransformedData<MP extends ModelProps> = Flatten<{
253
+ export type ModelPropsToUntransformedData<MP extends ModelProps> = {
249
254
  [k in keyof MP]: ModelPropStoredValue<MP[k]>
250
- }>
255
+ } & {}
251
256
 
252
- export type ModelPropsToSnapshotData<MP extends ModelProps> = Flatten<{
257
+ export type ModelPropsToSnapshotData<MP extends ModelProps> = {
253
258
  [k in keyof MP]: ModelPropToSnapshot<MP[k]> extends infer R ? R : never
254
- }>
259
+ } & {}
255
260
 
256
261
  // we don't use O.Optional anymore since it generates unions too heavy
257
262
  // also if we use Pick over the optional props we will loose the ability to infer generics
@@ -264,23 +269,29 @@ export type ModelPropsToUntransformedCreationData<MP extends ModelProps> = {
264
269
 
265
270
  // we don't use O.Optional anymore since it generates unions too heavy
266
271
  // also if we use Pick over the optional props we will loose the ability to infer generics
267
- export type ModelPropsToSnapshotCreationData<MP extends ModelProps> = Flatten<
272
+ export type ModelPropsToSnapshotCreationData<MP extends ModelProps> = SimplifyObject<
268
273
  {
269
274
  [k in keyof MP]?: ModelPropFromSnapshot<MP[k]> extends infer R ? R : never
270
275
  } & {
271
- [k in {
272
- [K in keyof MP]: IsNeverType<
273
- ModelPropFromSnapshotOverride<MP[K]>,
274
- MP[K]["$isRequired"] & K, // no override
275
- IsOptionalValue<ModelPropFromSnapshotOverride<MP[K]>, never, K> // with override
276
- >
277
- }[keyof MP]]: ModelPropFromSnapshot<MP[k]> extends infer R ? R : never
276
+ [k in SnapshotCreationRequiredKeys<MP>]: ModelPropFromSnapshot<MP[k]> extends infer R
277
+ ? R
278
+ : never
278
279
  }
279
280
  >
280
281
 
281
- export type ModelPropsToTransformedData<MP extends ModelProps> = Flatten<{
282
+ // without a snapshot processor a key is required when the prop is required,
283
+ // with one when the processor input does not accept undefined
284
+ type SnapshotCreationRequiredKeys<MP extends ModelProps> = {
285
+ [K in keyof MP]: IsNeverType<
286
+ ModelPropFromSnapshotOverride<MP[K]>,
287
+ MP[K]["$isRequired"] & K,
288
+ IsOptionalValue<ModelPropFromSnapshotOverride<MP[K]>, never, K>
289
+ >
290
+ }[keyof MP]
291
+
292
+ export type ModelPropsToTransformedData<MP extends ModelProps> = {
282
293
  [k in keyof MP]: MP[k]["$transformedValueType"]
283
- }>
294
+ } & {}
284
295
 
285
296
  // we don't use O.Optional anymore since it generates unions too heavy
286
297
  // also if we use Pick over the optional props we will loose the ability to infer generics
@@ -292,11 +303,11 @@ export type ModelPropsToTransformedCreationData<MP extends ModelProps> = {
292
303
  [k in RequiredModelProps<MP>]: MP[k]["$transformedCreationValueType"]
293
304
  }
294
305
 
295
- export type ModelPropsToSetter<MP extends ModelProps> = Flatten<{
306
+ export type ModelPropsToSetter<MP extends ModelProps> = {
296
307
  [k in keyof MP as MP[k]["$hasSetter"] & `set${Capitalize<k & string>}`]: (
297
308
  value: MP[k]["$transformedValueType"]
298
309
  ) => void
299
- }>
310
+ } & {}
300
311
 
301
312
  export type ModelIdProp<T extends string = string> = ModelProp<
302
313
  T,
@@ -307,7 +318,12 @@ export type ModelIdProp<T extends string = string> = ModelProp<
307
318
  true
308
319
  >
309
320
 
310
- type TypedModelIdProp<T extends string = string, THasSetter = never> = Omit<
321
+ // exported (and publicly re-exported) so emitted declaration files can reference it by name
322
+ // instead of expanding it inline in every exported model class, which is much slower and larger
323
+ /**
324
+ * The type of `idProp`.
325
+ */
326
+ export type TypedModelIdProp<T extends string = string, THasSetter = never> = Omit<
311
327
  ModelProp<T, T | undefined, T, T | undefined, never, true, THasSetter>,
312
328
  "withSetter"
313
329
  > & {
@@ -569,21 +585,17 @@ export function prop(...args: [] | [def: any]): AnyModelProp {
569
585
 
570
586
  const [def] = args
571
587
 
572
- let p = propCache.get(def)
573
-
574
- if (!p) {
575
- p = Object.create(baseProp)
588
+ return getOrCreate(propCache, def, () => {
589
+ const p: AnyModelProp = Object.create(baseProp)
576
590
 
577
591
  if (typeof def === "function") {
578
- p!._defaultFn = def
592
+ p._defaultFn = def
579
593
  } else {
580
- p!._defaultValue = def
594
+ p._defaultValue = def
581
595
  }
582
596
 
583
- propCache.set(def, p!)
584
- }
585
-
586
- return p!
597
+ return p
598
+ })
587
599
  }
588
600
 
589
601
  function parseSetterConfig(
@@ -599,19 +611,11 @@ function parseSetterConfig(
599
611
  }
600
612
  }
601
613
 
602
- if (
603
- modeOrValueTransform === undefined ||
604
- typeof modeOrValueTransform === "boolean" ||
605
- modeOrValueTransform === "assign"
606
- ) {
607
- return {
608
- mode: modeOrValueTransform ?? true,
609
- valueTransform: undefined,
610
- }
611
- }
612
-
613
614
  return {
614
- mode: true,
615
+ mode:
616
+ typeof modeOrValueTransform === "boolean" || modeOrValueTransform === "assign"
617
+ ? modeOrValueTransform
618
+ : true,
615
619
  valueTransform: undefined,
616
620
  }
617
621
  }
@@ -241,7 +241,7 @@ export function sharedInternalModel<
241
241
  const idPropGenerator = idProp._idGenerator
242
242
  const baseIdProp = baseModelProps?.[idKey]
243
243
  const baseIdPropGenerator = baseIdProp?._idGenerator
244
- const isOverridingBaseIdProp = !!baseIdProp?._isId && Object.hasOwn(modelProps, idKey)
244
+ const isOverridingBaseIdProp = !!baseIdProp?._isId && hasOwnProp(modelProps, idKey)
245
245
  if (isOverridingBaseIdProp && baseIdPropGenerator !== idPropGenerator) {
246
246
  throw failure(
247
247
  `expected same idProp.withGenerator function when overriding a base idProp, but got different references`
@@ -315,7 +315,7 @@ export function sharedInternalModel<
315
315
  // make sure abstract classes do not override prototype props
316
316
  if (!propsToDeleteFromBase) {
317
317
  propsToDeleteFromBase = Object.keys(modelProps).filter(
318
- (p) => !basePropNames.has(p as any) && Object.hasOwn(baseModel, p)
318
+ (p) => !basePropNames.has(p as any) && hasOwnProp(baseModel, p)
319
319
  )
320
320
  }
321
321
 
@@ -0,0 +1,239 @@
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
+ treeNodeMetadata.get(node)!.parentPath = entry
239
+ }