mobx-keystone 1.24.0 → 1.25.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 +18 -0
- package/LICENSE +21 -21
- package/README.md +16 -11
- package/dist/mobx-keystone.esm.js +204 -202
- package/dist/mobx-keystone.esm.mjs +204 -202
- package/dist/mobx-keystone.umd.js +204 -202
- package/dist/types/modelShared/BaseModelShared.d.ts +7 -1
- package/dist/types/modelShared/prop.d.ts +5 -0
- package/dist/types/types/TypeChecker.d.ts +79 -0
- package/dist/types/types/schemas.d.ts +15 -0
- package/package.json +10 -9
- package/src/actionMiddlewares/undoMiddleware.ts +11 -14
- package/src/model/BaseModel.ts +11 -5
- package/src/model/newModel.ts +11 -4
- package/src/modelShared/BaseModelShared.ts +1 -1
- package/src/modelShared/prop.ts +1 -1
- package/src/parent/findParent.ts +14 -9
- package/src/parent/onChildAttachedTo.ts +1 -12
- package/src/parent/path.ts +6 -4
- package/src/parent/setParent.ts +26 -14
- package/src/patch/applyPatches.ts +10 -30
- package/src/snapshot/applySnapshot.ts +9 -0
- package/src/snapshot/fromSnapshot.ts +3 -1
- package/src/snapshot/modelInitialData.ts +14 -19
- package/src/snapshot/reconcileModelSnapshot.ts +11 -3
- package/src/snapshot/reconcileSnapshot.ts +13 -4
- package/src/tweaker/tweak.ts +6 -7
- package/src/tweaker/tweakArray.ts +3 -17
- package/src/tweaker/tweakFrozen.ts +0 -1
- package/src/tweaker/tweakModel.ts +0 -1
- package/src/tweaker/tweakPlainObject.ts +2 -6
- package/src/tweaker/typeChecking.ts +9 -19
- package/src/types/TypeChecker.ts +6 -6
- package/src/types/schemas.ts +2 -2
|
@@ -2,7 +2,7 @@ import { AnyDataModel } from '../dataModel/BaseDataModel';
|
|
|
2
2
|
import { AnyModel } from '../model/BaseModel';
|
|
3
3
|
import { modelTypeKey } from '../model/metadata';
|
|
4
4
|
import { Flatten, IsNeverType } from '../utils/types';
|
|
5
|
-
import { ModelPropsToSnapshotCreationData, ModelPropsToSnapshotData, ModelPropsToTransformedCreationData, ModelPropsToTransformedData, ModelPropsToUntransformedCreationData } from './prop';
|
|
5
|
+
import { ModelProps, ModelPropsToSnapshotCreationData, ModelPropsToSnapshotData, ModelPropsToTransformedCreationData, ModelPropsToTransformedData, ModelPropsToUntransformedCreationData } from './prop';
|
|
6
6
|
/**
|
|
7
7
|
* @ignore
|
|
8
8
|
*/
|
|
@@ -27,6 +27,12 @@ export interface ModelClass<M extends AnyModel | AnyDataModel> {
|
|
|
27
27
|
* Extracts the instance type of an abstract model class.
|
|
28
28
|
*/
|
|
29
29
|
export type AbstractModelClass<M extends AnyModel | AnyDataModel> = abstract new (initialData: any) => M;
|
|
30
|
+
/**
|
|
31
|
+
* @ignore
|
|
32
|
+
*/
|
|
33
|
+
export interface ModelWithProps {
|
|
34
|
+
[propsTypeSymbol]: ModelProps;
|
|
35
|
+
}
|
|
30
36
|
/**
|
|
31
37
|
* The props of a model.
|
|
32
38
|
*/
|
|
@@ -7,6 +7,11 @@ import { Flatten, IsNeverType, IsOptionalValue } from '../utils/types';
|
|
|
7
7
|
export declare const noDefaultValue: unique symbol;
|
|
8
8
|
type SetterMode = boolean | "assign";
|
|
9
9
|
export type ModelPropSetterValueTransform<T> = (value: T) => T;
|
|
10
|
+
/** @ignore */
|
|
11
|
+
export interface FullModelPropTransform {
|
|
12
|
+
transform: (original: unknown, model: object, propName: PropertyKey, setOriginalValue: (newOriginalValue: unknown) => void) => unknown;
|
|
13
|
+
untransform: (transformed: unknown, model: object, propName: PropertyKey) => unknown;
|
|
14
|
+
}
|
|
10
15
|
/**
|
|
11
16
|
* A model property.
|
|
12
17
|
*/
|
|
@@ -1,4 +1,78 @@
|
|
|
1
|
+
import { Path } from '../parent/pathTypes';
|
|
1
2
|
import { AnyStandardType } from './schemas';
|
|
3
|
+
import { TypeCheckError } from './TypeCheckError';
|
|
4
|
+
type CheckFunction = (value: any, path: Path, typeCheckedValue: any) => TypeCheckError | null;
|
|
5
|
+
/** @ignore */
|
|
6
|
+
export type SnapshotProcessor = (snapshot: any) => unknown;
|
|
7
|
+
/**
|
|
8
|
+
* A lazily discovered snapshot-processor node. Dependencies are deliberately
|
|
9
|
+
* TypeCheckers rather than processors so recursive graphs can be discovered
|
|
10
|
+
* before any processor body is compiled.
|
|
11
|
+
*
|
|
12
|
+
* @ignore
|
|
13
|
+
*/
|
|
14
|
+
export interface SnapshotProcessorPlan {
|
|
15
|
+
readonly intrinsic?: boolean;
|
|
16
|
+
readonly getDependencies: () => ReadonlyArray<TypeChecker>;
|
|
17
|
+
readonly compile: (dependencyProcessors: ReadonlyArray<SnapshotProcessor | undefined>) => SnapshotProcessor | undefined;
|
|
18
|
+
}
|
|
19
|
+
type SnapshotProcessorSource = SnapshotProcessor | SnapshotProcessorPlan | undefined;
|
|
20
|
+
type SnapshotProcessorDirection = "from" | "to";
|
|
21
|
+
/** @ignore */
|
|
22
|
+
export declare class SnapshotProcessorPlanNode {
|
|
23
|
+
private readonly owner;
|
|
24
|
+
private readonly direction;
|
|
25
|
+
private readonly source;
|
|
26
|
+
private presence;
|
|
27
|
+
private dependencies;
|
|
28
|
+
private discovering;
|
|
29
|
+
private compiling;
|
|
30
|
+
private compiled;
|
|
31
|
+
private compiledProcessor;
|
|
32
|
+
private forwardingProcessor;
|
|
33
|
+
constructor(owner: TypeChecker | undefined, direction: SnapshotProcessorDirection, source: SnapshotProcessorSource);
|
|
34
|
+
resolve(): SnapshotProcessor | undefined;
|
|
35
|
+
private discover;
|
|
36
|
+
private compile;
|
|
37
|
+
private compileForDependency;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @ignore
|
|
41
|
+
*/
|
|
42
|
+
export declare enum TypeCheckerBaseType {
|
|
43
|
+
Object = "object",
|
|
44
|
+
Array = "array",
|
|
45
|
+
Primitive = "primitive",
|
|
46
|
+
Any = "any"
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @ignore
|
|
50
|
+
*/
|
|
51
|
+
export declare class TypeChecker {
|
|
52
|
+
unchecked: boolean;
|
|
53
|
+
skipCheck: boolean;
|
|
54
|
+
readonly baseType: TypeCheckerBaseType;
|
|
55
|
+
private readonly _check;
|
|
56
|
+
readonly getTypeName: (...recursiveTypeCheckers: TypeChecker[]) => string;
|
|
57
|
+
readonly typeInfoGen: TypeInfoGen;
|
|
58
|
+
readonly snapshotType: (sn: unknown) => TypeChecker | null;
|
|
59
|
+
private readonly fromSnapshotProcessorSource;
|
|
60
|
+
private readonly toSnapshotProcessorSource;
|
|
61
|
+
private fromSnapshotProcessorPlanNode;
|
|
62
|
+
private toSnapshotProcessorPlanNode;
|
|
63
|
+
check(value: any, path: Path, typeCheckedValue: any): TypeCheckError | null;
|
|
64
|
+
private _cachedTypeInfoGen;
|
|
65
|
+
get typeInfo(): TypeInfo;
|
|
66
|
+
constructor(baseType: TypeCheckerBaseType, _check: CheckFunction | null, getTypeName: (...recursiveTypeCheckers: TypeChecker[]) => string, typeInfoGen: TypeInfoGen, snapshotType: (sn: unknown) => TypeChecker | null, fromSnapshotProcessor: SnapshotProcessorSource, toSnapshotProcessor: SnapshotProcessorSource);
|
|
67
|
+
getSnapshotProcessorPlanNode(direction: SnapshotProcessorDirection): SnapshotProcessorPlanNode;
|
|
68
|
+
private resolveSnapshotProcessor;
|
|
69
|
+
getFromSnapshotProcessor(): SnapshotProcessor | undefined;
|
|
70
|
+
getToSnapshotProcessor(): SnapshotProcessor | undefined;
|
|
71
|
+
fromSnapshotProcessor: (sn: any) => unknown;
|
|
72
|
+
private readonly _toSnapshotProcessorCache;
|
|
73
|
+
invalidateSnapshotProcessorCachedResult(obj: object): void;
|
|
74
|
+
toSnapshotProcessor: (sn: any) => unknown;
|
|
75
|
+
}
|
|
2
76
|
/**
|
|
3
77
|
* Type info base class.
|
|
4
78
|
*/
|
|
@@ -7,3 +81,8 @@ export declare class TypeInfo {
|
|
|
7
81
|
readonly thisType: AnyStandardType;
|
|
8
82
|
constructor(thisType: AnyStandardType);
|
|
9
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* @ignore
|
|
86
|
+
*/
|
|
87
|
+
export type TypeInfoGen = (t: AnyStandardType) => TypeInfo;
|
|
88
|
+
export {};
|
|
@@ -3,6 +3,7 @@ import { AnyDataModel } from '../dataModel/BaseDataModel';
|
|
|
3
3
|
import { AnyModel } from '../model/BaseModel';
|
|
4
4
|
import { ModelClass } from '../modelShared/BaseModelShared';
|
|
5
5
|
import { SnapshotInOf, SnapshotOutOf } from '../snapshot/SnapshotOf';
|
|
6
|
+
import { IsOptionalValue } from '../utils/types';
|
|
6
7
|
export interface Type<Name, Data> {
|
|
7
8
|
/** @ignore */
|
|
8
9
|
$$type: Name;
|
|
@@ -36,6 +37,20 @@ export interface ObjectOfTypes {
|
|
|
36
37
|
/** @ignore */
|
|
37
38
|
[k: string]: AnyType;
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Name of the properties of an object that can be set to undefined, any or unknown.
|
|
42
|
+
* @ignore
|
|
43
|
+
*/
|
|
44
|
+
export type UndefinablePropsNames<T> = {
|
|
45
|
+
[K in keyof T]: IsOptionalValue<T[K], K, never>;
|
|
46
|
+
}[keyof T];
|
|
47
|
+
/**
|
|
48
|
+
* Computes the optional keys for an object type schema.
|
|
49
|
+
* @ignore
|
|
50
|
+
*/
|
|
51
|
+
export type ObjectOptionalKeys<S> = UndefinablePropsNames<{
|
|
52
|
+
[k in keyof S]: TypeToDataOpt<S[k]> extends infer R ? R : never;
|
|
53
|
+
}>;
|
|
39
54
|
type ObjectData<S> = O.Optional<{
|
|
40
55
|
[k in keyof S]: TypeToData<S[k]> extends infer R ? R : never;
|
|
41
56
|
}, ObjectOptionalKeys<S>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobx-keystone",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "A MobX powered state management solution based on data trees with first class support for TypeScript, snapshots, patches and much more",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mobx",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"quick-build": "tsc",
|
|
55
55
|
"quick-build-tests": "tsc -p test",
|
|
56
56
|
"copy-root-files": "shx cp ../../README.md . && shx cp ../../LICENSE . && shx cp ../../CHANGELOG.md .",
|
|
57
|
-
"build": "pnpm run quick-build && pnpm run copy-root-files && shx rm -rf dist && vite build && shx cp dist/mobx-keystone.esm.mjs dist/mobx-keystone.esm.js",
|
|
57
|
+
"build": "pnpm run quick-build && pnpm run copy-root-files && shx rm -rf dist && vite build && pnpm run check-declarations && shx cp dist/mobx-keystone.esm.mjs dist/mobx-keystone.esm.js",
|
|
58
|
+
"check-declarations": "tsc -p scripts/tsconfig.check-declarations.json",
|
|
58
59
|
"test": "vitest run",
|
|
59
60
|
"test:ci": "vitest run --coverage",
|
|
60
61
|
"test:perf": "pnpm run build && pnpm run test:perf:run",
|
|
@@ -68,15 +69,15 @@
|
|
|
68
69
|
"devDependencies": {
|
|
69
70
|
"@babel/core": "^8.0.1",
|
|
70
71
|
"@babel/plugin-proposal-decorators": "^8.0.2",
|
|
71
|
-
"@babel/plugin-transform-class-static-block": "^8.0.1",
|
|
72
72
|
"@babel/plugin-transform-class-properties": "^8.0.1",
|
|
73
|
+
"@babel/plugin-transform-class-static-block": "^8.0.1",
|
|
73
74
|
"@babel/plugin-transform-typescript": "^8.0.1",
|
|
74
75
|
"@babel/preset-env": "^8.0.2",
|
|
75
|
-
"@swc/core": "^1.
|
|
76
|
+
"@swc/core": "^1.16.2",
|
|
76
77
|
"@types/babel__core": "^7.20.5",
|
|
77
|
-
"@types/node": "^26.
|
|
78
|
+
"@types/node": "^26.4.1",
|
|
78
79
|
"@typescript/typescript6": "^6.0.2",
|
|
79
|
-
"@vitest/coverage-v8": "^
|
|
80
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
80
81
|
"mobx-v4": "npm:mobx@^4.15.7",
|
|
81
82
|
"mobx-v5": "npm:mobx@^5.15.7",
|
|
82
83
|
"mobx-v6": "npm:mobx@^6.0.0",
|
|
@@ -86,9 +87,9 @@
|
|
|
86
87
|
"ts-node": "^10.9.2",
|
|
87
88
|
"typedoc": "^0.28.20",
|
|
88
89
|
"typescript": "^7.0.2",
|
|
89
|
-
"vite": "^8.
|
|
90
|
-
"vite-plugin-dts": "^5.0
|
|
91
|
-
"vitest": "^
|
|
90
|
+
"vite": "^8.2.2",
|
|
91
|
+
"vite-plugin-dts": "^5.1.0",
|
|
92
|
+
"vitest": "^5.0.0"
|
|
92
93
|
},
|
|
93
94
|
"dependencies": {
|
|
94
95
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -99,21 +99,16 @@ export interface UndoEventGroup {
|
|
|
99
99
|
|
|
100
100
|
function toSingleEvents(
|
|
101
101
|
event: UndoEventWithoutAttachedState,
|
|
102
|
-
|
|
103
|
-
):
|
|
102
|
+
array: UndoSingleEvent[] = []
|
|
103
|
+
): UndoSingleEvent[] {
|
|
104
104
|
if (event.type === UndoEventType.Single) {
|
|
105
|
-
|
|
105
|
+
array.push(event)
|
|
106
106
|
} else {
|
|
107
|
-
const array: UndoSingleEvent[] = []
|
|
108
107
|
for (const e of event.events) {
|
|
109
|
-
|
|
110
|
-
array.unshift(...toSingleEvents(e, true))
|
|
111
|
-
} else {
|
|
112
|
-
array.push(...toSingleEvents(e, false))
|
|
113
|
-
}
|
|
108
|
+
toSingleEvents(e, array)
|
|
114
109
|
}
|
|
115
|
-
return array
|
|
116
110
|
}
|
|
111
|
+
return array
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
/**
|
|
@@ -386,9 +381,11 @@ export class UndoManager {
|
|
|
386
381
|
const event = this.undoQueue[this.undoQueue.length - 1]
|
|
387
382
|
|
|
388
383
|
withoutUndo(() => {
|
|
389
|
-
toSingleEvents(event
|
|
390
|
-
|
|
391
|
-
|
|
384
|
+
toSingleEvents(event)
|
|
385
|
+
.reverse()
|
|
386
|
+
.forEach((e) => {
|
|
387
|
+
applyPatches(this.subtreeRoot, e.inversePatches, true)
|
|
388
|
+
})
|
|
392
389
|
|
|
393
390
|
// restore the attached state before the operation was made
|
|
394
391
|
if (event.attachedState?.beforeEvent) {
|
|
@@ -411,7 +408,7 @@ export class UndoManager {
|
|
|
411
408
|
const event = this.redoQueue[this.redoQueue.length - 1]
|
|
412
409
|
|
|
413
410
|
withoutUndo(() => {
|
|
414
|
-
toSingleEvents(event
|
|
411
|
+
toSingleEvents(event).forEach((e) => {
|
|
415
412
|
applyPatches(this.subtreeRoot, e.patches)
|
|
416
413
|
})
|
|
417
414
|
|
package/src/model/BaseModel.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ModelPropsToUntransformedData,
|
|
13
13
|
} from "../modelShared/prop"
|
|
14
14
|
import { getSnapshot } from "../snapshot/getSnapshot"
|
|
15
|
+
import { setModelInitialDataSnapshot } from "../snapshot/modelInitialData"
|
|
15
16
|
import type { SnapshotInOfModel, SnapshotOutOfModel } from "../snapshot/SnapshotOf"
|
|
16
17
|
import { typesModel } from "../types/objectBased/typesModel"
|
|
17
18
|
import type { TypeCheckError } from "../types/TypeCheckError"
|
|
@@ -146,11 +147,16 @@ export abstract class BaseModel<
|
|
|
146
147
|
// plain new
|
|
147
148
|
assertIsObject(initialData, "initialData")
|
|
148
149
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
// Empty input is filled by the constructor below. Track its primitive
|
|
151
|
+
// defaults directly instead of rereading observable fields for the snapshot.
|
|
152
|
+
const useInitialDataSnapshot = Reflect.ownKeys(initialData).length === 0
|
|
153
|
+
const observableInitialData = observable.object(initialData as any, undefined, {
|
|
154
|
+
deep: false,
|
|
155
|
+
})
|
|
156
|
+
if (useInitialDataSnapshot) {
|
|
157
|
+
setModelInitialDataSnapshot(observableInitialData, {})
|
|
158
|
+
}
|
|
159
|
+
internalNewModel(this, observableInitialData, modelClass!, useInitialDataSnapshot)
|
|
154
160
|
}
|
|
155
161
|
}
|
|
156
162
|
|
package/src/model/newModel.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
hasGlobalPatchListeners,
|
|
20
20
|
} from "../patch/emitPatch"
|
|
21
21
|
import type { Patch } from "../patch/Patch"
|
|
22
|
-
import {
|
|
22
|
+
import { updateModelInitialDataSnapshot } from "../snapshot/modelInitialData"
|
|
23
23
|
import { tweakModel } from "../tweaker/tweakModel"
|
|
24
24
|
import { tweakPlainObject } from "../tweaker/tweakPlainObject"
|
|
25
25
|
import { failure, inDevMode, makePropReadonly } from "../utils"
|
|
@@ -38,7 +38,8 @@ export const internalNewModel = action(
|
|
|
38
38
|
<M extends AnyModel>(
|
|
39
39
|
origModelObj: M,
|
|
40
40
|
initialData: ModelCreationData<M>,
|
|
41
|
-
modelClass: ModelClass<AnyModel
|
|
41
|
+
modelClass: ModelClass<AnyModel>,
|
|
42
|
+
useInitialDataSnapshot: boolean
|
|
42
43
|
): void => {
|
|
43
44
|
if (inDevMode) {
|
|
44
45
|
assertIsModelClass(modelClass, "modelClass")
|
|
@@ -56,6 +57,9 @@ export const internalNewModel = action(
|
|
|
56
57
|
id = (modelIdPropData._defaultFn as () => string)()
|
|
57
58
|
}
|
|
58
59
|
setIfDifferent(initialData, modelIdPropertyName, id)
|
|
60
|
+
if (useInitialDataSnapshot) {
|
|
61
|
+
updateModelInitialDataSnapshot(initialData, modelIdPropertyName, id)
|
|
62
|
+
}
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
const modelObj = origModelObj as O.Writable<M>
|
|
@@ -99,6 +103,9 @@ export const internalNewModel = action(
|
|
|
99
103
|
if (changed) {
|
|
100
104
|
// setIfDifferent not required
|
|
101
105
|
set(initialData, k, newValue)
|
|
106
|
+
if (useInitialDataSnapshot) {
|
|
107
|
+
updateModelInitialDataSnapshot(initialData, k, newValue)
|
|
108
|
+
}
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
|
|
@@ -166,7 +173,7 @@ export const internalFromSnapshotModel = action(
|
|
|
166
173
|
const valueChanged = setIfDifferentWithReturn(initialData, modelIdPropertyName, id)
|
|
167
174
|
|
|
168
175
|
if (valueChanged) {
|
|
169
|
-
|
|
176
|
+
updateModelInitialDataSnapshot(initialData, modelIdPropertyName, id)
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
if (valueChanged && shouldEmitPatches) {
|
|
@@ -210,7 +217,7 @@ export const internalFromSnapshotModel = action(
|
|
|
210
217
|
if (changed) {
|
|
211
218
|
// setIfDifferent not required
|
|
212
219
|
set(initialData, k, newValue)
|
|
213
|
-
|
|
220
|
+
updateModelInitialDataSnapshot(initialData, k, newValue)
|
|
214
221
|
|
|
215
222
|
if (newValue !== initialValue) {
|
|
216
223
|
if (shouldEmitPatches) {
|
package/src/modelShared/prop.ts
CHANGED
package/src/parent/findParent.ts
CHANGED
|
@@ -19,8 +19,7 @@ export function findParent<T extends object = any>(
|
|
|
19
19
|
predicate: (parentNode: object) => boolean,
|
|
20
20
|
maxDepth = 0
|
|
21
21
|
): T | undefined {
|
|
22
|
-
|
|
23
|
-
return foundParentPath ? foundParentPath.parent : undefined
|
|
22
|
+
return findParentInternal<T>(child, predicate, maxDepth)
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
/**
|
|
@@ -56,22 +55,28 @@ export function findParentPath<T extends object = any>(
|
|
|
56
55
|
predicate: (parentNode: object) => boolean,
|
|
57
56
|
maxDepth = 0
|
|
58
57
|
): FoundParentPath<T> | undefined {
|
|
59
|
-
assertTweakedObject(child, "child")
|
|
60
|
-
|
|
61
58
|
const path: WritablePath = []
|
|
59
|
+
const parent = findParentInternal<T>(child, predicate, maxDepth, path)
|
|
60
|
+
return parent ? { parent, path: path.reverse() } : undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function findParentInternal<T extends object>(
|
|
64
|
+
child: object,
|
|
65
|
+
predicate: (parentNode: object) => boolean,
|
|
66
|
+
maxDepth: number,
|
|
67
|
+
path?: WritablePath
|
|
68
|
+
): T | undefined {
|
|
69
|
+
assertTweakedObject(child, "child")
|
|
62
70
|
|
|
63
71
|
let current: any = child
|
|
64
72
|
let depth = 0
|
|
65
73
|
|
|
66
74
|
let parentPath: ParentPath<any> | undefined
|
|
67
75
|
while ((parentPath = fastGetParentPath(current, true))) {
|
|
68
|
-
path
|
|
76
|
+
path?.push(parentPath.path)
|
|
69
77
|
current = parentPath.parent
|
|
70
78
|
if (predicate(current)) {
|
|
71
|
-
return
|
|
72
|
-
parent: current,
|
|
73
|
-
path,
|
|
74
|
-
}
|
|
79
|
+
return current
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
depth++
|
|
@@ -59,18 +59,7 @@ export function onChildAttachedTo(
|
|
|
59
59
|
const t = target()
|
|
60
60
|
assertTweakedObject(t, "target()")
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const set = new Set<object>()
|
|
65
|
-
|
|
66
|
-
const iter = children.values()
|
|
67
|
-
let cur = iter.next()
|
|
68
|
-
while (!cur.done) {
|
|
69
|
-
set.add(cur.value)
|
|
70
|
-
cur = iter.next()
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return set
|
|
62
|
+
return new Set(getChildrenObjects(t, getChildrenObjectOpts))
|
|
74
63
|
}
|
|
75
64
|
|
|
76
65
|
const currentChildren = opts.fireForCurrentChildren ? new Set<object>() : getCurrentChildren()
|
package/src/parent/path.ts
CHANGED
|
@@ -175,10 +175,12 @@ export function fastGetRootPath<T extends object = any>(
|
|
|
175
175
|
let parentPath: ParentPath<any> | undefined
|
|
176
176
|
while ((parentPath = fastGetParentPath(root, useAtom))) {
|
|
177
177
|
root = parentPath.parent
|
|
178
|
-
path.
|
|
179
|
-
pathObjects.
|
|
178
|
+
path.push(parentPath.path)
|
|
179
|
+
pathObjects.push(parentPath.parent)
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
path.reverse()
|
|
183
|
+
pathObjects.reverse()
|
|
182
184
|
return { root, path, pathObjects } as RootPath<any>
|
|
183
185
|
}
|
|
184
186
|
|
|
@@ -376,11 +378,11 @@ export function fastGetParentToChildPath(
|
|
|
376
378
|
let current = toChild
|
|
377
379
|
let parentPath: ParentPath<any> | undefined
|
|
378
380
|
while ((parentPath = fastGetParentPath(current, useAtom))) {
|
|
379
|
-
path.
|
|
381
|
+
path.push(parentPath.path)
|
|
380
382
|
|
|
381
383
|
current = parentPath.parent
|
|
382
384
|
if (current === fromParent) {
|
|
383
|
-
return path
|
|
385
|
+
return path.reverse()
|
|
384
386
|
}
|
|
385
387
|
}
|
|
386
388
|
return undefined
|
package/src/parent/setParent.ts
CHANGED
|
@@ -19,6 +19,31 @@ import {
|
|
|
19
19
|
import { addObjectChild, removeObjectChild } from "./coreObjectChildren"
|
|
20
20
|
import { fastGetParentPath, fastGetRoot, type ParentPath } from "./path"
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Updates the unchanged tail of an array before a splice is applied. These
|
|
24
|
+
* children keep their parent, so neither attachment nor child-index work is
|
|
25
|
+
* needed. One action batches path notifications for the entire tail.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export const reindexArrayChildren = action(
|
|
29
|
+
"reindexArrayChildren",
|
|
30
|
+
(array: readonly unknown[], oldStart: number, newStart: number): void => {
|
|
31
|
+
for (let i = oldStart, j = newStart; i < array.length; i++, j++) {
|
|
32
|
+
const value = array[i]
|
|
33
|
+
if (isPrimitive(value)) {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
const metadata = treeNodeMetadata.get(value as object)
|
|
37
|
+
if (inDevMode && metadata?.parentPath?.parent !== array) {
|
|
38
|
+
throw failure("assertion failed: reindexed child must already belong to the array")
|
|
39
|
+
}
|
|
40
|
+
// Replace the path object: callers may have retained the previous path.
|
|
41
|
+
metadata!.parentPath = { parent: array, path: j }
|
|
42
|
+
reportParentPathChanged(value as object)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
22
47
|
/**
|
|
23
48
|
* @internal
|
|
24
49
|
*/
|
|
@@ -27,7 +52,6 @@ export const setParent = action(
|
|
|
27
52
|
(
|
|
28
53
|
value: any,
|
|
29
54
|
parentPath: ParentPath<any> | undefined,
|
|
30
|
-
indexChangeAllowed: boolean,
|
|
31
55
|
isDataObject: boolean,
|
|
32
56
|
cloneIfApplicable: boolean
|
|
33
57
|
): any => {
|
|
@@ -36,11 +60,6 @@ export const setParent = action(
|
|
|
36
60
|
}
|
|
37
61
|
|
|
38
62
|
if (inDevMode) {
|
|
39
|
-
if (indexChangeAllowed && cloneIfApplicable) {
|
|
40
|
-
throw failure(
|
|
41
|
-
"assertion failed: 'indexChangeAllowed' and 'cloneIfApplicable' cannot be set at the same time"
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
63
|
if (typeof value === "function" || typeof value === "symbol") {
|
|
45
64
|
throw failure(`assertion failed: value cannot be a function or a symbol`)
|
|
46
65
|
}
|
|
@@ -92,14 +111,7 @@ export const setParent = action(
|
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
if (oldParentPath && parentPath) {
|
|
95
|
-
|
|
96
|
-
// just changing the index
|
|
97
|
-
treeNodeMetadata.get(value)!.parentPath = parentPath
|
|
98
|
-
reportParentPathChanged(value)
|
|
99
|
-
return value
|
|
100
|
-
} else {
|
|
101
|
-
throw failure("an object cannot be assigned a new parent when it already has one")
|
|
102
|
-
}
|
|
114
|
+
throw failure("an object cannot be assigned a new parent when it already has one")
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
const postUntweaker = parentPath ? undefined : tryUntweak(value)
|
|
@@ -3,7 +3,6 @@ import { BuiltInAction } from "../action/builtInActions"
|
|
|
3
3
|
import { ActionContextActionType } from "../action/context"
|
|
4
4
|
import { wrapInAction } from "../action/wrapInAction"
|
|
5
5
|
import { modelToDataNode } from "../parent/core"
|
|
6
|
-
import type { PathElement } from "../parent/pathTypes"
|
|
7
6
|
import type { Patch } from "../patch/Patch"
|
|
8
7
|
import { reconcileSnapshot } from "../snapshot/reconcileSnapshot"
|
|
9
8
|
import { assertTweakedObject } from "../tweaker/core"
|
|
@@ -84,7 +83,16 @@ const wrappedInternalApplyPatches = lazy(() =>
|
|
|
84
83
|
)
|
|
85
84
|
|
|
86
85
|
function applySinglePatch(obj: object, patch: Patch, modelPool: ModelPool): void {
|
|
87
|
-
const
|
|
86
|
+
const path = patch.path
|
|
87
|
+
if (inDevMode && !isArray(path)) {
|
|
88
|
+
throw failure(`invalid path: ${path}`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let target: any = modelToDataNode(obj)
|
|
92
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
93
|
+
target = modelToDataNode(target[path[i]])
|
|
94
|
+
}
|
|
95
|
+
const prop = path.length > 0 ? path[path.length - 1] : undefined
|
|
88
96
|
|
|
89
97
|
if (isArray(target)) {
|
|
90
98
|
switch (patch.op) {
|
|
@@ -157,31 +165,3 @@ function applySinglePatchWithPath(obj: object, patch: Patch, modelPool: ModelPoo
|
|
|
157
165
|
applySinglePatch(obj, patch, modelPool)
|
|
158
166
|
})
|
|
159
167
|
}
|
|
160
|
-
|
|
161
|
-
function pathArrayToObjectAndProp(
|
|
162
|
-
obj: object,
|
|
163
|
-
path: Patch["path"]
|
|
164
|
-
): { target: any; prop?: PathElement } {
|
|
165
|
-
if (inDevMode) {
|
|
166
|
-
if (!isArray(path)) {
|
|
167
|
-
throw failure(`invalid path: ${path}`)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
let target: any = modelToDataNode(obj)
|
|
172
|
-
|
|
173
|
-
if (path.length === 0) {
|
|
174
|
-
return {
|
|
175
|
-
target,
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
for (let i = 0; i <= path.length - 2; i++) {
|
|
180
|
-
target = modelToDataNode(target[path[i]])
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
target,
|
|
185
|
-
prop: path[path.length - 1],
|
|
186
|
-
}
|
|
187
|
-
}
|
|
@@ -17,6 +17,7 @@ import { assertTweakedObject } from "../tweaker/core"
|
|
|
17
17
|
import { assertIsObject, inDevMode, isArray, isMap, isPlainObject, isSet, lazy } from "../utils"
|
|
18
18
|
import { runWithErrorDiagnosticsContext } from "../utils/errorDiagnostics"
|
|
19
19
|
import { ModelPool } from "../utils/ModelPool"
|
|
20
|
+
import { flushInternalSnapshot, getInternalSnapshot } from "./internal"
|
|
20
21
|
import { reconcileSnapshot } from "./reconcileSnapshot"
|
|
21
22
|
import type { SnapshotInOf, SnapshotOutOf } from "./SnapshotOf"
|
|
22
23
|
import { SnapshotProcessingError } from "./SnapshotProcessingError"
|
|
@@ -52,6 +53,14 @@ export function internalApplySnapshot<T extends object>(
|
|
|
52
53
|
const obj = this
|
|
53
54
|
|
|
54
55
|
const reconcile = () => {
|
|
56
|
+
// Avoid building a model pool (and its deep children traversal) when the
|
|
57
|
+
// node already holds this exact snapshot. Child updates may still await
|
|
58
|
+
// propagation within an action, so flush before trusting the reference.
|
|
59
|
+
flushInternalSnapshot(obj, false)
|
|
60
|
+
if (getInternalSnapshot(obj)?.transformed === sn) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
const modelPool = new ModelPool(obj)
|
|
56
65
|
const ret = reconcileSnapshot(obj, sn, modelPool, undefined)
|
|
57
66
|
|
|
@@ -211,7 +211,9 @@ function snapshotToInitialData(
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
const observableInitialData = observable.object(initialData, undefined, observableOptions)
|
|
214
|
-
|
|
214
|
+
if (allValuesPrimitive) {
|
|
215
|
+
setModelInitialDataSnapshot(observableInitialData, initialData)
|
|
216
|
+
}
|
|
215
217
|
return observableInitialData
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -1,40 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
readonly snapshot: Record<string, unknown>
|
|
3
|
-
readonly allValuesPrimitive: boolean
|
|
4
|
-
reusable: boolean
|
|
5
|
-
}
|
|
1
|
+
import { isPrimitive, setProtoProp } from "../utils"
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
// Only primitive-only initial data can double as a snapshot without tweaking.
|
|
4
|
+
const modelInitialDataSnapshots = new WeakMap<object, Record<string, unknown>>()
|
|
8
5
|
|
|
9
6
|
/**
|
|
10
7
|
* @internal
|
|
11
8
|
*/
|
|
12
|
-
export function setModelInitialDataSnapshot(
|
|
13
|
-
data
|
|
14
|
-
snapshot: Record<string, unknown>,
|
|
15
|
-
allValuesPrimitive: boolean
|
|
16
|
-
): void {
|
|
17
|
-
modelInitialDataSnapshots.set(data, {
|
|
18
|
-
snapshot,
|
|
19
|
-
allValuesPrimitive,
|
|
20
|
-
reusable: true,
|
|
21
|
-
})
|
|
9
|
+
export function setModelInitialDataSnapshot(data: object, snapshot: Record<string, unknown>): void {
|
|
10
|
+
modelInitialDataSnapshots.set(data, snapshot)
|
|
22
11
|
}
|
|
23
12
|
|
|
24
13
|
/**
|
|
25
14
|
* @internal
|
|
26
15
|
*/
|
|
27
|
-
export function
|
|
16
|
+
export function updateModelInitialDataSnapshot(data: object, key: string, value: unknown): void {
|
|
28
17
|
const initialDataSnapshot = modelInitialDataSnapshots.get(data)
|
|
29
18
|
if (initialDataSnapshot) {
|
|
30
|
-
|
|
19
|
+
if (!isPrimitive(value)) {
|
|
20
|
+
modelInitialDataSnapshots.delete(data)
|
|
21
|
+
} else if (key === "__proto__") {
|
|
22
|
+
setProtoProp(initialDataSnapshot, value)
|
|
23
|
+
} else {
|
|
24
|
+
initialDataSnapshot[key] = value
|
|
25
|
+
}
|
|
31
26
|
}
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
/**
|
|
35
30
|
* @internal
|
|
36
31
|
*/
|
|
37
|
-
export function takeModelInitialDataSnapshot(data: object):
|
|
32
|
+
export function takeModelInitialDataSnapshot(data: object): Record<string, unknown> | undefined {
|
|
38
33
|
const initialDataSnapshot = modelInitialDataSnapshots.get(data)
|
|
39
34
|
modelInitialDataSnapshots.delete(data)
|
|
40
35
|
return initialDataSnapshot
|