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.
- package/CHANGELOG.md +145 -0
- package/dist/mobx-keystone.esm.js +3627 -1540
- package/dist/mobx-keystone.esm.mjs +3627 -1540
- package/dist/mobx-keystone.umd.js +3638 -1543
- package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +17 -10
- package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
- package/dist/types/deepChange/onDeepChange.d.ts +9 -0
- package/dist/types/parent/arrayPaths.d.ts +1 -0
- package/dist/types/parent/detach.d.ts +1 -1
- package/dist/types/parent/findChildren.d.ts +1 -1
- package/dist/types/parent/treeOrder.d.ts +1 -0
- package/dist/types/patch/applyPatches.d.ts +3 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
- package/dist/types/ref/modelRefIdIndex.d.ts +1 -0
- package/dist/types/ref/nodesById.d.ts +1 -0
- package/dist/types/ref/refIdIndex.d.ts +1 -0
- package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
- package/dist/types/snapshot/applySnapshot.d.ts +1 -0
- package/dist/types/snapshot/fromSnapshot.d.ts +0 -3
- package/dist/types/standardActions/arrayActions.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +0 -4
- package/dist/types/tweaker/changeRollback.d.ts +1 -0
- package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
- package/dist/types/types/TypeCheckError.d.ts +1 -0
- package/dist/types/types/utility/typesRefinement.d.ts +2 -2
- package/dist/types/utils/ModelPool.d.ts +7 -1
- package/dist/types/utils/chunks.d.ts +1 -0
- package/dist/types/utils/forEachWithDelayedThrow.d.ts +1 -0
- package/dist/types/utils/index.d.ts +13 -0
- package/dist/types/utils/mapUtils.d.ts +2 -2
- package/dist/types/utils/setLike.d.ts +1 -0
- package/package.json +2 -2
- package/src/action/applyAction.ts +1 -1
- package/src/action/middleware.ts +17 -36
- package/src/action/modelFlow.ts +4 -5
- package/src/action/mutationBatch.ts +10 -10
- package/src/action/pendingActions.ts +3 -4
- package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +19 -14
- package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +29 -20
- package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +1 -1
- package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +4 -3
- package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +4 -12
- package/src/actionMiddlewares/actionSerialization/setSerializer.ts +4 -13
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +11 -13
- package/src/actionMiddlewares/transactionMiddleware.ts +55 -25
- package/src/actionMiddlewares/undoMiddleware.ts +112 -59
- package/src/actionMiddlewares/utils.ts +2 -1
- package/src/computedTree/computedTree.ts +14 -3
- package/src/context/context.ts +17 -45
- package/src/dataModel/BaseDataModel.ts +20 -16
- package/src/dataModel/dataModelInstanceCache.ts +8 -0
- package/src/deepChange/onDeepChange.ts +179 -24
- package/src/frozen/Frozen.ts +50 -33
- package/src/model/BaseModel.ts +10 -13
- package/src/model/newModel.ts +21 -24
- package/src/model/utils.ts +2 -2
- package/src/modelShared/modelClassInitializer.ts +3 -2
- package/src/modelShared/modelDecorator.ts +39 -39
- package/src/modelShared/modelInfo.ts +1 -1
- package/src/modelShared/prop.ts +6 -9
- package/src/modelShared/sharedInternalModel.ts +16 -3
- package/src/parent/arrayPaths.ts +242 -0
- package/src/parent/coreObjectChildren.ts +300 -48
- package/src/parent/detach.ts +1 -1
- package/src/parent/findChildren.ts +4 -7
- package/src/parent/onChildAttachedTo.ts +157 -55
- package/src/parent/path.ts +15 -5
- package/src/parent/setParent.ts +34 -15
- package/src/parent/treeOrder.ts +53 -0
- package/src/parent/walkTree.ts +12 -101
- package/src/patch/applyPatches.ts +74 -34
- package/src/patch/emitPatch.ts +142 -26
- package/src/patch/jsonPatch.ts +5 -0
- package/src/patch/patchRecorder.ts +18 -14
- package/src/redux/connectReduxDevTools.ts +24 -18
- package/src/redux/redux.ts +5 -21
- package/src/ref/core.ts +81 -29
- package/src/ref/modelRefIdIndex.ts +131 -0
- package/src/ref/nodesById.ts +60 -0
- package/src/ref/refIdIndex.ts +209 -0
- package/src/rootStore/attachDetach.ts +27 -16
- package/src/rootStore/rootStore.ts +10 -6
- package/src/snapshot/SnapshotOf.ts +2 -2
- package/src/snapshot/applySnapshot.ts +27 -19
- package/src/snapshot/clone.ts +1 -2
- package/src/snapshot/cloneTreeValue.ts +1 -2
- package/src/snapshot/fromModelSnapshot.ts +6 -2
- package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
- package/src/snapshot/fromSnapshot.ts +2 -6
- package/src/snapshot/internal.ts +89 -19
- package/src/snapshot/onSnapshot.ts +12 -1
- package/src/snapshot/reconcileModelSnapshot.ts +6 -6
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -4
- package/src/standardActions/arrayActions.ts +12 -2
- package/src/standardActions/objectActions.ts +3 -2
- package/src/transforms/ImmutableDate.ts +4 -0
- package/src/transforms/asMap.ts +3 -1
- package/src/treeUtils/deepEquals.ts +15 -13
- package/src/treeUtils/draft.ts +8 -0
- package/src/treeUtils/sandbox.ts +31 -27
- package/src/tweaker/changeRollback.ts +218 -0
- package/src/tweaker/core.ts +33 -22
- package/src/tweaker/treeNodeMetadata.ts +12 -2
- package/src/tweaker/tweak.ts +183 -19
- package/src/tweaker/tweakArray.ts +176 -33
- package/src/tweaker/tweakPlainObject.ts +117 -39
- package/src/tweaker/tweakedChangeListeners.ts +61 -0
- package/src/tweaker/typeChecking.ts +25 -15
- package/src/types/TypeCheckError.ts +17 -19
- package/src/types/arrayBased/typesArray.ts +10 -11
- package/src/types/createCachedTypeCheck.ts +541 -88
- package/src/types/objectBased/typesDataModelData.ts +4 -2
- package/src/types/objectBased/typesModel.ts +4 -2
- package/src/types/objectBased/typesObject.ts +20 -16
- package/src/types/objectBased/typesRecord.ts +8 -18
- package/src/types/objectBased/typesRef.ts +2 -2
- package/src/types/primitiveBased/typesEnum.ts +4 -2
- package/src/types/primitiveBased/typesPrimitive.ts +9 -4
- package/src/types/typeCheckErrorUtils.ts +1 -0
- package/src/types/utility/typesCodecSupport.ts +97 -56
- package/src/types/utility/typesMap.ts +24 -41
- package/src/types/utility/typesRefinement.ts +3 -2
- package/src/types/utility/typesSet.ts +24 -0
- package/src/utils/ModelPool.ts +78 -22
- package/src/utils/chainFns.ts +4 -0
- package/src/utils/chunks.ts +271 -0
- package/src/utils/decorators.ts +1 -1
- package/src/utils/errorDiagnostics.ts +6 -2
- package/src/utils/forEachWithDelayedThrow.ts +83 -0
- package/src/utils/index.ts +71 -2
- package/src/utils/mapUtils.ts +3 -7
- package/src/utils/setIfDifferent.ts +3 -2
- package/src/utils/setLike.ts +134 -0
- package/src/wrappers/ArraySet.ts +5 -9
- package/src/wrappers/ObjectMap.ts +15 -11
- package/src/wrappers/asMap.ts +30 -18
- package/src/wrappers/asSet.ts +36 -12
- package/dist/types/action/modelFlowPromiseGenerator.d.ts +0 -1
- 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
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
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
|
|
93
|
+
const tag = afterClassInitializationData.get(clazz)!
|
|
116
94
|
|
|
117
|
-
|
|
95
|
+
if (tag.type !== "data") {
|
|
96
|
+
const instance = new (clazz as any)(...args)
|
|
97
|
+
runAfterClassInitialization(tag, instance)
|
|
98
|
+
return instance
|
|
99
|
+
}
|
|
118
100
|
|
|
119
|
-
|
|
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 = {
|
package/src/modelShared/prop.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|