mobx-keystone 1.24.1 → 2.0.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 +137 -0
- package/README.md +5 -1
- package/dist/mobx-keystone.esm.js +1608 -1072
- package/dist/mobx-keystone.esm.mjs +1608 -1072
- package/dist/mobx-keystone.umd.js +1613 -1072
- package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
- package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
- package/dist/types/deepChange/onDeepChange.d.ts +9 -0
- package/dist/types/parent/detach.d.ts +1 -1
- package/dist/types/parent/findChildren.d.ts +1 -1
- package/dist/types/patch/applyPatches.d.ts +2 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
- package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
- package/dist/types/standardActions/arrayActions.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +0 -4
- package/dist/types/types/TypeCheckError.d.ts +1 -0
- package/dist/types/types/utility/typesRefinement.d.ts +2 -2
- 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 +7 -7
- 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 +42 -11
- package/src/actionMiddlewares/undoMiddleware.ts +76 -60
- 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 +15 -14
- package/src/model/newModel.ts +32 -28
- package/src/model/utils.ts +2 -2
- package/src/modelShared/modelClassInitializer.ts +3 -2
- package/src/modelShared/modelDecorator.ts +34 -14
- package/src/modelShared/modelInfo.ts +1 -1
- package/src/modelShared/prop.ts +6 -9
- package/src/modelShared/sharedInternalModel.ts +16 -3
- package/src/parent/detach.ts +1 -1
- package/src/parent/findChildren.ts +4 -7
- package/src/parent/findParent.ts +14 -9
- package/src/parent/onChildAttachedTo.ts +45 -40
- package/src/parent/path.ts +11 -7
- package/src/parent/setParent.ts +33 -14
- package/src/parent/walkTree.ts +21 -37
- package/src/patch/applyPatches.ts +75 -60
- 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 +18 -2
- 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 +10 -1
- package/src/snapshot/clone.ts +1 -2
- package/src/snapshot/cloneTreeValue.ts +1 -2
- package/src/snapshot/fromModelSnapshot.ts +6 -2
- package/src/snapshot/fromSnapshot.ts +3 -1
- package/src/snapshot/internal.ts +22 -9
- package/src/snapshot/modelInitialData.ts +14 -19
- package/src/snapshot/onSnapshot.ts +12 -1
- package/src/snapshot/reconcileModelSnapshot.ts +16 -8
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -4
- package/src/snapshot/reconcileSnapshot.ts +13 -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/core.ts +2 -4
- package/src/tweaker/tweak.ts +8 -8
- package/src/tweaker/tweakArray.ts +86 -37
- package/src/tweaker/tweakFrozen.ts +0 -1
- package/src/tweaker/tweakModel.ts +0 -1
- package/src/tweaker/tweakPlainObject.ts +78 -29
- package/src/tweaker/typeChecking.ts +132 -33
- package/src/types/TypeCheckError.ts +17 -19
- package/src/types/createCachedTypeCheck.ts +27 -2
- 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 +6 -13
- 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 +3 -3
- package/src/utils/chainFns.ts +4 -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 +34 -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,15 +1,15 @@
|
|
|
1
|
+
import { type IArrayDidChange, type IObjectDidChange, remove, set } from "mobx"
|
|
1
2
|
import { isModelAutoTypeCheckingEnabled } from "../globalConfig/globalConfig"
|
|
2
3
|
import type { AnyModel } from "../model/BaseModel"
|
|
3
4
|
import { getModelMetadata } from "../model/getModelMetadata"
|
|
4
5
|
import { isModel } from "../model/utils"
|
|
5
6
|
import { dataToModelNode } from "../parent/core"
|
|
6
|
-
import {
|
|
7
|
-
import type { PathElement } from "../parent/pathTypes"
|
|
7
|
+
import { fastGetParent } from "../parent/path"
|
|
8
8
|
import { internalApplyPatches } from "../patch/applyPatches"
|
|
9
9
|
import type { InternalPatchRecorder } from "../patch/emitPatch"
|
|
10
10
|
import { internalApplySnapshot } from "../snapshot/applySnapshot"
|
|
11
11
|
import { runWithoutSnapshotOrPatches } from "./core"
|
|
12
|
-
import { isTypeCheckingAllowed } from "./withoutTypeChecking"
|
|
12
|
+
import { isTypeCheckingAllowed, withoutTypeChecking } from "./withoutTypeChecking"
|
|
13
13
|
|
|
14
14
|
// Re-entrancy guard: when true, we are inside a type-check rollback.
|
|
15
15
|
// During rollback, observe callbacks still fire, but we skip the auto
|
|
@@ -35,33 +35,25 @@ function isModelWithTypeChecker(obj: object): obj is AnyModel {
|
|
|
35
35
|
* @internal
|
|
36
36
|
*
|
|
37
37
|
* Walks up parent chain from `obj`, calling `callback` for each typed model
|
|
38
|
-
*
|
|
38
|
+
* including the target itself (nearest-first / bottom-up). Stops if the callback throws.
|
|
39
39
|
*/
|
|
40
40
|
function forEachTypedModelAncestor(obj: object, callback: (model: AnyModel) => void): void {
|
|
41
41
|
// obj might be a $ data object, so resolve to the model if applicable
|
|
42
|
-
|
|
43
|
-
let lastAncestor: AnyModel | undefined
|
|
42
|
+
const start = dataToModelNode(obj)
|
|
44
43
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
callback(
|
|
44
|
+
// Both model-data mutations and whole-model reconciliation must validate
|
|
45
|
+
// the model itself before checking its ancestors.
|
|
46
|
+
if (isModelWithTypeChecker(start)) {
|
|
47
|
+
callback(start)
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!parentPath) break
|
|
56
|
-
|
|
57
|
-
const parent: object = parentPath.parent
|
|
58
|
-
|
|
59
|
-
if (isModelWithTypeChecker(parent) && parent !== lastAncestor) {
|
|
60
|
-
lastAncestor = parent
|
|
50
|
+
// Raw parents never point to model $ objects, so each model is visited once.
|
|
51
|
+
let parent: object | undefined = fastGetParent(start, false)
|
|
52
|
+
while (parent) {
|
|
53
|
+
if (isModelWithTypeChecker(parent)) {
|
|
61
54
|
callback(parent)
|
|
62
55
|
}
|
|
63
|
-
|
|
64
|
-
current = parent
|
|
56
|
+
parent = fastGetParent(parent, false)
|
|
65
57
|
}
|
|
66
58
|
}
|
|
67
59
|
|
|
@@ -86,20 +78,19 @@ export function runTypeCheckingAfterChange(
|
|
|
86
78
|
forEachTypedModelAncestor(obj, (model) => {
|
|
87
79
|
const err = model.typeCheck()
|
|
88
80
|
if (err) {
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
// intermediate state and attempt another rollback, causing infinite recursion.
|
|
81
|
+
// A rejected individual mutation has not updated its snapshot yet,
|
|
82
|
+
// so inverse patches must stay quiet. Snapshot reconciliation has
|
|
83
|
+
// already published changes: its rollback must update snapshots and
|
|
84
|
+
// emit compensating patches as well.
|
|
94
85
|
isRollingBackTypeCheckFailure = true
|
|
95
86
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
if (patchRecorder) {
|
|
88
|
+
runWithoutSnapshotOrPatches(() => {
|
|
98
89
|
internalApplyPatches.call(obj, patchRecorder.invPatches, true)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
90
|
+
})
|
|
91
|
+
} else if (snapshotBeforeChanges) {
|
|
92
|
+
internalApplySnapshot.call(obj, snapshotBeforeChanges)
|
|
93
|
+
}
|
|
103
94
|
} finally {
|
|
104
95
|
isRollingBackTypeCheckFailure = false
|
|
105
96
|
}
|
|
@@ -108,3 +99,111 @@ export function runTypeCheckingAfterChange(
|
|
|
108
99
|
})
|
|
109
100
|
}
|
|
110
101
|
}
|
|
102
|
+
|
|
103
|
+
interface TypeCheckingBatch {
|
|
104
|
+
models: Set<AnyModel>
|
|
105
|
+
undo: (() => void)[]
|
|
106
|
+
recording: boolean
|
|
107
|
+
/** Last recorded change target, so a run of changes to it walks ancestors once. */
|
|
108
|
+
previousTarget?: object
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let typeCheckingBatch: TypeCheckingBatch | undefined
|
|
112
|
+
|
|
113
|
+
/** @internal Called only when automatic type checking is enabled. */
|
|
114
|
+
export function typeCheckAfterCreation(model: AnyModel): void {
|
|
115
|
+
if (typeCheckingBatch?.recording) {
|
|
116
|
+
typeCheckingBatch.models.add(model)
|
|
117
|
+
} else {
|
|
118
|
+
const error = model.typeCheck()
|
|
119
|
+
if (error) error.throw()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @internal
|
|
125
|
+
* Capture stored values before any listener can mutate them again. Restoring
|
|
126
|
+
* live values preserves identity and literal data, without snapshot processors
|
|
127
|
+
* or model defaults changing the state being restored.
|
|
128
|
+
*/
|
|
129
|
+
export function recordTypeCheckingBatchChange(change: IObjectDidChange | IArrayDidChange): void {
|
|
130
|
+
const batch = typeCheckingBatch
|
|
131
|
+
if (!batch?.recording) return
|
|
132
|
+
if (
|
|
133
|
+
change.type === "splice" &&
|
|
134
|
+
change.addedCount === change.removedCount &&
|
|
135
|
+
change.added.every((value, index) => value === change.removed[index])
|
|
136
|
+
)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
const target = change.object
|
|
140
|
+
if (target !== batch.previousTarget) {
|
|
141
|
+
forEachTypedModelAncestor(target, (model) => batch.models.add(model))
|
|
142
|
+
batch.previousTarget = target
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (change.type === "splice") {
|
|
146
|
+
const { object, index, addedCount } = change
|
|
147
|
+
const removed = change.removed.slice()
|
|
148
|
+
batch.undo.push(() => object.spliceWithArray(index, addedCount, removed))
|
|
149
|
+
} else if ("index" in change) {
|
|
150
|
+
const { object, index, oldValue } = change
|
|
151
|
+
batch.undo.push(() => {
|
|
152
|
+
object[index] = oldValue
|
|
153
|
+
})
|
|
154
|
+
} else {
|
|
155
|
+
const { object } = change
|
|
156
|
+
const key = change.name as string
|
|
157
|
+
if (change.type === "add") {
|
|
158
|
+
batch.undo.push(() => remove(object, key))
|
|
159
|
+
} else {
|
|
160
|
+
const { oldValue } = change
|
|
161
|
+
batch.undo.push(() => set(object, key, oldValue))
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @internal
|
|
168
|
+
* Nested calls and synchronous listener edits join the same validation batch.
|
|
169
|
+
* Rollback publishes compensating changes, just like snapshot reconciliation.
|
|
170
|
+
*/
|
|
171
|
+
export function withTypeCheckingBatch(fn: () => void): void {
|
|
172
|
+
if (typeCheckingBatch || !isTypeCheckingAfterChangeEnabled()) {
|
|
173
|
+
fn()
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const batch: TypeCheckingBatch = { models: new Set(), undo: [], recording: true }
|
|
178
|
+
typeCheckingBatch = batch
|
|
179
|
+
let validating = false
|
|
180
|
+
try {
|
|
181
|
+
withoutTypeChecking(fn)
|
|
182
|
+
// MobX has now invalidated dependencies, including added/removed keys.
|
|
183
|
+
validating = true
|
|
184
|
+
for (const model of batch.models) {
|
|
185
|
+
const error = model.typeCheck()
|
|
186
|
+
if (error) error.throw()
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
// Only a rejected validation rolls back. Errors thrown by the batch body
|
|
190
|
+
// itself (an invalid patch, a throwing listener) keep the same partial
|
|
191
|
+
// application they produce while automatic type checking is disabled.
|
|
192
|
+
if (validating) {
|
|
193
|
+
batch.recording = false
|
|
194
|
+
withoutTypeChecking(() => {
|
|
195
|
+
for (let i = batch.undo.length - 1; i >= 0; i--) {
|
|
196
|
+
try {
|
|
197
|
+
batch.undo[i]()
|
|
198
|
+
} catch {
|
|
199
|
+
// Keep restoring after a rollback listener throws. The original
|
|
200
|
+
// failure takes precedence over errors from compensating changes.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
throw error
|
|
206
|
+
} finally {
|
|
207
|
+
typeCheckingBatch = undefined
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dataToModelNode } from "../parent/core"
|
|
2
|
+
import { fastGetParent, fastGetRootPath } from "../parent/path"
|
|
2
3
|
import type { Path } from "../parent/pathTypes"
|
|
3
4
|
import { getSnapshot } from "../snapshot/getSnapshot"
|
|
4
5
|
import { isTweakedObject } from "../tweaker/core"
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
} from "../utils/errorDiagnostics"
|
|
11
12
|
|
|
12
13
|
export interface TypeCheckErrorData {
|
|
14
|
+
/** Path relative to the value being type checked. */
|
|
13
15
|
path: Path
|
|
14
16
|
expectedTypeName: string
|
|
15
17
|
actualValue: any
|
|
@@ -71,8 +73,15 @@ export class TypeCheckError {
|
|
|
71
73
|
*/
|
|
72
74
|
throw(): never {
|
|
73
75
|
const diagnosticsPath = getErrorPathSnapshot()
|
|
76
|
+
const hasTreeParent =
|
|
77
|
+
isTweakedObject(this.typeCheckedValue, true) &&
|
|
78
|
+
fastGetParent(dataToModelNode(this.typeCheckedValue), false) !== undefined
|
|
79
|
+
// Attached values supply their location through the tree. Context supplies
|
|
80
|
+
// the location for detached values, such as models being hydrated.
|
|
74
81
|
const path =
|
|
75
|
-
diagnosticsPath && diagnosticsPath.length > 0
|
|
82
|
+
diagnosticsPath && diagnosticsPath.length > 0 && !hasTreeParent
|
|
83
|
+
? [...diagnosticsPath, ...this.path]
|
|
84
|
+
: this.path
|
|
76
85
|
|
|
77
86
|
throw new TypeCheckErrorFailure({
|
|
78
87
|
path,
|
|
@@ -146,7 +155,7 @@ function resolveTypeCheckErrorData(data: TypeCheckErrorData): {
|
|
|
146
155
|
const modelTrail = data.modelTrail ?? getErrorModelTrailSnapshot()
|
|
147
156
|
const fullPath = resolveFullPath(data.path, data.typeCheckedValue)
|
|
148
157
|
const actualValueSnapshot = isTweakedObject(data.actualValue, true)
|
|
149
|
-
? getSnapshot(data.actualValue)
|
|
158
|
+
? getSnapshot(dataToModelNode(data.actualValue))
|
|
150
159
|
: data.actualValue
|
|
151
160
|
|
|
152
161
|
return {
|
|
@@ -162,22 +171,11 @@ function resolveTypeCheckErrorData(data: TypeCheckErrorData): {
|
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
function resolveFullPath(path: Path, typeCheckedValue: any): Path {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
rootPath
|
|
168
|
-
|
|
169
|
-
return rootPath.length > 0 && !pathStartsWith(path, rootPath) ? [...rootPath, ...path] : path
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function pathStartsWith(path: Path, prefix: Path): boolean {
|
|
173
|
-
if (prefix.length > path.length) {
|
|
174
|
-
return false
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
for (let i = 0; i < prefix.length; i++) {
|
|
178
|
-
if (path[i] !== prefix[i]) {
|
|
179
|
-
return false
|
|
174
|
+
if (isTweakedObject(typeCheckedValue, true)) {
|
|
175
|
+
const rootPath = fastGetRootPath(dataToModelNode(typeCheckedValue), false).path
|
|
176
|
+
if (rootPath.length > 0) {
|
|
177
|
+
return [...rootPath, ...path]
|
|
180
178
|
}
|
|
181
179
|
}
|
|
182
|
-
return
|
|
180
|
+
return path
|
|
183
181
|
}
|
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
import { computed, type IComputedValue, type IObjectDidChange, observe } from "mobx"
|
|
2
|
+
import { dataToModelNode, modelToDataNode } from "../parent/core"
|
|
3
|
+
import { fastGetParent } from "../parent/path"
|
|
2
4
|
import type { Path } from "../parent/pathTypes"
|
|
3
5
|
import { isTweakedObject } from "../tweaker/core"
|
|
4
6
|
import { TypeCheckError } from "./TypeCheckError"
|
|
5
7
|
|
|
8
|
+
let uncachedTypeCheckObjects: Set<object> | undefined
|
|
9
|
+
|
|
10
|
+
/** @internal */
|
|
11
|
+
export function withoutCachedTypeChecking(obj: object, fn: () => void): void {
|
|
12
|
+
const previous = uncachedTypeCheckObjects
|
|
13
|
+
const affected = new Set(previous)
|
|
14
|
+
// Only these containers can retain a cached result for the old shape.
|
|
15
|
+
// Model schema checks run on $ data, while refinements can check the model.
|
|
16
|
+
let current: object | undefined = dataToModelNode(obj)
|
|
17
|
+
while (current) {
|
|
18
|
+
affected.add(current)
|
|
19
|
+
affected.add(modelToDataNode(current))
|
|
20
|
+
current = fastGetParent(current, false)
|
|
21
|
+
}
|
|
22
|
+
uncachedTypeCheckObjects = affected
|
|
23
|
+
try {
|
|
24
|
+
fn()
|
|
25
|
+
} finally {
|
|
26
|
+
uncachedTypeCheckObjects = previous
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
6
30
|
const emptyPath: Path = []
|
|
7
31
|
|
|
8
32
|
function contextualizeCachedError(
|
|
@@ -16,6 +40,7 @@ function contextualizeCachedError(
|
|
|
16
40
|
|
|
17
41
|
return new TypeCheckError({
|
|
18
42
|
path: [...path, ...error.path],
|
|
43
|
+
modelTrail: error.modelTrail,
|
|
19
44
|
expectedTypeName: error.expectedTypeName,
|
|
20
45
|
actualValue: error.actualValue,
|
|
21
46
|
typeCheckedValue,
|
|
@@ -52,7 +77,7 @@ function createAdaptivePerEntryCachedCheck<K>(
|
|
|
52
77
|
iterateEntries(value, (key) => checkEntry(value, key, path, typeCheckedValue))
|
|
53
78
|
|
|
54
79
|
return (value, path, typeCheckedValue) => {
|
|
55
|
-
if (!isTweakedObject(value, true)) {
|
|
80
|
+
if (uncachedTypeCheckObjects?.has(value) || !isTweakedObject(value, true)) {
|
|
56
81
|
return checkAllEntries(value, path, typeCheckedValue)
|
|
57
82
|
}
|
|
58
83
|
|
|
@@ -142,7 +167,7 @@ export function createWholeContainerCachedCheck(
|
|
|
142
167
|
const cache = new WeakMap<object, IComputedValue<TypeCheckError | null>>()
|
|
143
168
|
|
|
144
169
|
return (value, path, typeCheckedValue) => {
|
|
145
|
-
if (!isTweakedObject(value, true)) {
|
|
170
|
+
if (uncachedTypeCheckObjects?.has(value) || !isTweakedObject(value, true)) {
|
|
146
171
|
return check(value, path, typeCheckedValue)
|
|
147
172
|
}
|
|
148
173
|
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
import { modelInfoByClass } from "../../modelShared/modelInfo"
|
|
11
11
|
import { getInternalModelClassPropsInfo } from "../../modelShared/modelPropsInfo"
|
|
12
12
|
import { noDefaultValue } from "../../modelShared/prop"
|
|
13
|
-
import { failure, lazy } from "../../utils"
|
|
13
|
+
import { failure, lazy, setProtoProp } from "../../utils"
|
|
14
14
|
import { getTypeInfo } from "../getTypeInfo"
|
|
15
15
|
import {
|
|
16
16
|
registerStandardTypeResolver,
|
|
@@ -168,12 +168,14 @@ export class DataModelDataTypeInfo extends TypeInfo {
|
|
|
168
168
|
hasDefault = true
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
const propInfo = {
|
|
172
172
|
type,
|
|
173
173
|
typeInfo,
|
|
174
174
|
hasDefault,
|
|
175
175
|
default: defaultValue,
|
|
176
176
|
}
|
|
177
|
+
if (propName === "__proto__") setProtoProp(propTypes, propInfo)
|
|
178
|
+
else propTypes[propName] = propInfo
|
|
177
179
|
})
|
|
178
180
|
return propTypes
|
|
179
181
|
})
|
|
@@ -7,7 +7,7 @@ import type { ModelClass } from "../../modelShared/BaseModelShared"
|
|
|
7
7
|
import { modelInfoByClass } from "../../modelShared/modelInfo"
|
|
8
8
|
import { getInternalModelClassPropsInfo } from "../../modelShared/modelPropsInfo"
|
|
9
9
|
import { noDefaultValue } from "../../modelShared/prop"
|
|
10
|
-
import { isObject, lazy } from "../../utils"
|
|
10
|
+
import { isObject, lazy, setProtoProp } from "../../utils"
|
|
11
11
|
import { getTypeInfo } from "../getTypeInfo"
|
|
12
12
|
import { registerStandardTypeResolver, resolveTypeChecker } from "../resolveTypeChecker"
|
|
13
13
|
import type { AnyStandardType, ModelType } from "../schemas"
|
|
@@ -178,12 +178,14 @@ export class ModelTypeInfo extends TypeInfo {
|
|
|
178
178
|
hasDefault = true
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
const propInfo = {
|
|
182
182
|
type,
|
|
183
183
|
typeInfo,
|
|
184
184
|
hasDefault,
|
|
185
185
|
default: defaultValue,
|
|
186
186
|
}
|
|
187
|
+
if (propName === "__proto__") setProtoProp(propTypes, propInfo)
|
|
188
|
+
else propTypes[propName] = propInfo
|
|
187
189
|
})
|
|
188
190
|
return propTypes
|
|
189
191
|
})
|
|
@@ -2,7 +2,16 @@ import { isObservableObject, keys } from "mobx"
|
|
|
2
2
|
import type { O } from "ts-toolbelt"
|
|
3
3
|
import { Frozen } from "../../frozen/Frozen"
|
|
4
4
|
import type { Path } from "../../parent/pathTypes"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
assertIsFunction,
|
|
7
|
+
assertIsObject,
|
|
8
|
+
copyOwnEnumerableProps,
|
|
9
|
+
getMobxVersion,
|
|
10
|
+
isArray,
|
|
11
|
+
isObject,
|
|
12
|
+
lazy,
|
|
13
|
+
setProtoProp,
|
|
14
|
+
} from "../../utils"
|
|
6
15
|
import { withErrorPathSegment } from "../../utils/errorDiagnostics"
|
|
7
16
|
import { createIndexedPerEntryCachedCheck } from "../createCachedTypeCheck"
|
|
8
17
|
import { getTypeInfo } from "../getTypeInfo"
|
|
@@ -106,27 +115,20 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
106
115
|
obj: Record<string, unknown>,
|
|
107
116
|
processors: ReadonlyArray<SnapshotProcessor | undefined>
|
|
108
117
|
) => {
|
|
109
|
-
const newObj: typeof obj = {}
|
|
110
|
-
|
|
111
118
|
// note: we allow excess properties when checking objects
|
|
112
|
-
|
|
113
|
-
for (let i = 0; i < keys.length; i++) {
|
|
114
|
-
const k = keys[i]
|
|
119
|
+
return copyOwnEnumerableProps({}, obj, (value, k) => {
|
|
115
120
|
const schemaEntry = schemaEntryByPropName[k]
|
|
116
121
|
if (schemaEntry) {
|
|
117
122
|
const processor = processors[schemaEntry.processorIndex]
|
|
118
|
-
|
|
119
|
-
} else {
|
|
120
|
-
// unknown prop, copy as is
|
|
121
|
-
newObj[k] = obj[k]
|
|
123
|
+
return processor ? withErrorPathSegment(k, () => processor(value)) : value
|
|
122
124
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
// unknown prop, copy as is
|
|
126
|
+
return value
|
|
127
|
+
})
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
const checkObjectType = (obj: any, path: Path, typeCheckedValue: any) => {
|
|
129
|
-
if (!isObject(obj) || (frozen && !(obj instanceof Frozen))) {
|
|
131
|
+
if (!isObject(obj) || isArray(obj) || (frozen && !(obj instanceof Frozen))) {
|
|
130
132
|
return new TypeCheckError({
|
|
131
133
|
path,
|
|
132
134
|
expectedTypeName: getTypeName(thisTc),
|
|
@@ -187,7 +189,7 @@ function typesObjectHelper<S>(objFn: S, frozen: boolean, typeInfoGen: TypeInfoGe
|
|
|
187
189
|
typeInfoGen,
|
|
188
190
|
|
|
189
191
|
(obj) => {
|
|
190
|
-
if (!isObject(obj)) {
|
|
192
|
+
if (!isObject(obj) || isArray(obj)) {
|
|
191
193
|
return null
|
|
192
194
|
}
|
|
193
195
|
|
|
@@ -278,7 +280,9 @@ export class ObjectTypeInfo extends TypeInfo {
|
|
|
278
280
|
const propTypes: O.Writable<ObjectTypeInfoProps> = {}
|
|
279
281
|
Object.keys(objSchema).forEach((propName) => {
|
|
280
282
|
const type = resolveStandardType(objSchema[propName])
|
|
281
|
-
|
|
283
|
+
const propInfo = { type, typeInfo: getTypeInfo(type) }
|
|
284
|
+
if (propName === "__proto__") setProtoProp(propTypes, propInfo)
|
|
285
|
+
else propTypes[propName] = propInfo
|
|
282
286
|
})
|
|
283
287
|
return propTypes
|
|
284
288
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isObservableObject, keys } from "mobx"
|
|
2
2
|
import type { Path } from "../../parent/pathTypes"
|
|
3
|
-
import { failure, isObject } from "../../utils"
|
|
3
|
+
import { copyOwnEnumerableProps, failure, isArray, isObject } from "../../utils"
|
|
4
4
|
import { withErrorPathSegment } from "../../utils/errorDiagnostics"
|
|
5
5
|
import { createAdaptiveRecordCachedCheck } from "../createCachedTypeCheck"
|
|
6
6
|
import { getTypeInfo } from "../getTypeInfo"
|
|
@@ -48,16 +48,9 @@ export function typesRecord<T extends AnyType>(valueType: T): RecordType<T> {
|
|
|
48
48
|
return obj
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
for (let i = 0; i < keys.length; i++) {
|
|
55
|
-
const k = keys[i]
|
|
56
|
-
const v = withErrorPathSegment(k, () => processor(obj[k]))
|
|
57
|
-
newObj[k] = v
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return newObj
|
|
51
|
+
return copyOwnEnumerableProps({}, obj, (value, key) =>
|
|
52
|
+
withErrorPathSegment(key, () => processor(value))
|
|
53
|
+
)
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
const iterateRecordEntries = (
|
|
@@ -97,7 +90,7 @@ export function typesRecord<T extends AnyType>(valueType: T): RecordType<T> {
|
|
|
97
90
|
TypeCheckerBaseType.Object,
|
|
98
91
|
|
|
99
92
|
(obj, path, typeCheckedValue) => {
|
|
100
|
-
if (!isObject(obj)) {
|
|
93
|
+
if (!isObject(obj) || isArray(obj)) {
|
|
101
94
|
return new TypeCheckError({
|
|
102
95
|
path,
|
|
103
96
|
expectedTypeName: getTypeName(thisTc),
|
|
@@ -117,7 +110,7 @@ export function typesRecord<T extends AnyType>(valueType: T): RecordType<T> {
|
|
|
117
110
|
typeInfoGen,
|
|
118
111
|
|
|
119
112
|
(obj) => {
|
|
120
|
-
if (!isObject(obj)) {
|
|
113
|
+
if (!isObject(obj) || isArray(obj)) {
|
|
121
114
|
return null
|
|
122
115
|
}
|
|
123
116
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { modelTypeKey } from "../../model/metadata"
|
|
2
2
|
import { modelInfoByClass } from "../../modelShared/modelInfo"
|
|
3
|
-
import { Ref,
|
|
3
|
+
import type { Ref, RefConstructor } from "../../ref/Ref"
|
|
4
4
|
import { isObject } from "../../utils"
|
|
5
5
|
import { typesString } from "../primitiveBased/typesPrimitive"
|
|
6
6
|
import { resolveTypeChecker } from "../resolveTypeChecker"
|
|
@@ -36,7 +36,7 @@ export function typesRef<O extends object>(refConstructor: RefConstructor<O>): M
|
|
|
36
36
|
TypeCheckerBaseType.Object,
|
|
37
37
|
|
|
38
38
|
(value, path, typeCheckedValue) => {
|
|
39
|
-
if (!(value instanceof
|
|
39
|
+
if (!(value instanceof refConstructor.refClass)) {
|
|
40
40
|
return new TypeCheckError({
|
|
41
41
|
path,
|
|
42
42
|
expectedTypeName: typeName,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertIsObject } from "../../utils"
|
|
1
|
+
import { assertIsObject, isEqualOrBothNaN } from "../../utils"
|
|
2
2
|
import type { IdentityType } from "../schemas"
|
|
3
3
|
import { typesOr } from "../utility/typesOr"
|
|
4
4
|
import { typesLiteral } from "./typesPrimitive"
|
|
@@ -17,6 +17,7 @@ export interface EnumLike {
|
|
|
17
17
|
*/
|
|
18
18
|
export function enumValues(e: EnumLike): (string | number)[] {
|
|
19
19
|
const vals: (string | number)[] = []
|
|
20
|
+
const seen = new Set<string | number>()
|
|
20
21
|
for (const k of Object.keys(e)) {
|
|
21
22
|
const v = e[k]
|
|
22
23
|
// we have to do this since TS does something weird
|
|
@@ -24,7 +25,8 @@ export function enumValues(e: EnumLike): (string | number)[] {
|
|
|
24
25
|
// Hi = 0 -> { Hi: 0, 0: "Hi" }
|
|
25
26
|
// and SWC currently generates enum code inconsistent with TS/Babel
|
|
26
27
|
// https://github.com/swc-project/swc/issues/3711
|
|
27
|
-
if (!
|
|
28
|
+
if (!seen.has(v) && ((typeof v !== "string" && v !== +k) || !isEqualOrBothNaN(e[v], +k))) {
|
|
29
|
+
seen.add(v)
|
|
28
30
|
vals.push(v)
|
|
29
31
|
}
|
|
30
32
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertIsPrimitive } from "../../utils"
|
|
1
|
+
import { assertIsPrimitive, isEqualOrBothNaN } from "../../utils"
|
|
2
2
|
import type { PrimitiveValue } from "../../utils/types"
|
|
3
3
|
import { registerStandardTypeResolver, type StandardTypeResolverFn } from "../resolveTypeChecker"
|
|
4
4
|
import type { AnyStandardType, IdentityType } from "../schemas"
|
|
@@ -31,7 +31,12 @@ export function typesLiteral<T extends PrimitiveValue>(literal: T): IdentityType
|
|
|
31
31
|
typeName = "null"
|
|
32
32
|
break
|
|
33
33
|
default:
|
|
34
|
-
typeName =
|
|
34
|
+
typeName =
|
|
35
|
+
typeof literal === "bigint"
|
|
36
|
+
? `${literal}n`
|
|
37
|
+
: typeof literal === "number" && !Number.isFinite(literal)
|
|
38
|
+
? String(literal)
|
|
39
|
+
: JSON.stringify(literal)
|
|
35
40
|
break
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -41,7 +46,7 @@ export function typesLiteral<T extends PrimitiveValue>(literal: T): IdentityType
|
|
|
41
46
|
TypeCheckerBaseType.Primitive,
|
|
42
47
|
|
|
43
48
|
(value, path, typeCheckedValue) =>
|
|
44
|
-
value
|
|
49
|
+
isEqualOrBothNaN(value, literal)
|
|
45
50
|
? null
|
|
46
51
|
: new TypeCheckError({
|
|
47
52
|
path,
|
|
@@ -53,7 +58,7 @@ export function typesLiteral<T extends PrimitiveValue>(literal: T): IdentityType
|
|
|
53
58
|
() => typeName,
|
|
54
59
|
typeInfoGen,
|
|
55
60
|
|
|
56
|
-
(value) => (value
|
|
61
|
+
(value) => (isEqualOrBothNaN(value, literal) ? thisTc : null),
|
|
57
62
|
undefined,
|
|
58
63
|
undefined
|
|
59
64
|
)
|