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.
Files changed (122) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/README.md +5 -1
  3. package/dist/mobx-keystone.esm.js +1608 -1072
  4. package/dist/mobx-keystone.esm.mjs +1608 -1072
  5. package/dist/mobx-keystone.umd.js +1613 -1072
  6. package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
  7. package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
  8. package/dist/types/deepChange/onDeepChange.d.ts +9 -0
  9. package/dist/types/parent/detach.d.ts +1 -1
  10. package/dist/types/parent/findChildren.d.ts +1 -1
  11. package/dist/types/patch/applyPatches.d.ts +2 -0
  12. package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
  13. package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
  14. package/dist/types/standardActions/arrayActions.d.ts +1 -1
  15. package/dist/types/treeUtils/sandbox.d.ts +0 -4
  16. package/dist/types/types/TypeCheckError.d.ts +1 -0
  17. package/dist/types/types/utility/typesRefinement.d.ts +2 -2
  18. package/dist/types/utils/forEachWithDelayedThrow.d.ts +1 -0
  19. package/dist/types/utils/index.d.ts +13 -0
  20. package/dist/types/utils/mapUtils.d.ts +2 -2
  21. package/dist/types/utils/setLike.d.ts +1 -0
  22. package/package.json +7 -7
  23. package/src/action/applyAction.ts +1 -1
  24. package/src/action/middleware.ts +17 -36
  25. package/src/action/modelFlow.ts +4 -5
  26. package/src/action/mutationBatch.ts +10 -10
  27. package/src/action/pendingActions.ts +3 -4
  28. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +19 -14
  29. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +29 -20
  30. package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +1 -1
  31. package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +4 -3
  32. package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +4 -12
  33. package/src/actionMiddlewares/actionSerialization/setSerializer.ts +4 -13
  34. package/src/actionMiddlewares/actionTrackingMiddleware.ts +11 -13
  35. package/src/actionMiddlewares/transactionMiddleware.ts +42 -11
  36. package/src/actionMiddlewares/undoMiddleware.ts +76 -60
  37. package/src/actionMiddlewares/utils.ts +2 -1
  38. package/src/computedTree/computedTree.ts +14 -3
  39. package/src/context/context.ts +17 -45
  40. package/src/dataModel/BaseDataModel.ts +20 -16
  41. package/src/dataModel/dataModelInstanceCache.ts +8 -0
  42. package/src/deepChange/onDeepChange.ts +179 -24
  43. package/src/frozen/Frozen.ts +50 -33
  44. package/src/model/BaseModel.ts +15 -14
  45. package/src/model/newModel.ts +32 -28
  46. package/src/model/utils.ts +2 -2
  47. package/src/modelShared/modelClassInitializer.ts +3 -2
  48. package/src/modelShared/modelDecorator.ts +34 -14
  49. package/src/modelShared/modelInfo.ts +1 -1
  50. package/src/modelShared/prop.ts +6 -9
  51. package/src/modelShared/sharedInternalModel.ts +16 -3
  52. package/src/parent/detach.ts +1 -1
  53. package/src/parent/findChildren.ts +4 -7
  54. package/src/parent/findParent.ts +14 -9
  55. package/src/parent/onChildAttachedTo.ts +45 -40
  56. package/src/parent/path.ts +11 -7
  57. package/src/parent/setParent.ts +33 -14
  58. package/src/parent/walkTree.ts +21 -37
  59. package/src/patch/applyPatches.ts +75 -60
  60. package/src/patch/emitPatch.ts +142 -26
  61. package/src/patch/jsonPatch.ts +5 -0
  62. package/src/patch/patchRecorder.ts +18 -14
  63. package/src/redux/connectReduxDevTools.ts +24 -18
  64. package/src/redux/redux.ts +5 -21
  65. package/src/ref/core.ts +18 -2
  66. package/src/rootStore/attachDetach.ts +27 -16
  67. package/src/rootStore/rootStore.ts +10 -6
  68. package/src/snapshot/SnapshotOf.ts +2 -2
  69. package/src/snapshot/applySnapshot.ts +10 -1
  70. package/src/snapshot/clone.ts +1 -2
  71. package/src/snapshot/cloneTreeValue.ts +1 -2
  72. package/src/snapshot/fromModelSnapshot.ts +6 -2
  73. package/src/snapshot/fromSnapshot.ts +3 -1
  74. package/src/snapshot/internal.ts +22 -9
  75. package/src/snapshot/modelInitialData.ts +14 -19
  76. package/src/snapshot/onSnapshot.ts +12 -1
  77. package/src/snapshot/reconcileModelSnapshot.ts +16 -8
  78. package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -4
  79. package/src/snapshot/reconcileSnapshot.ts +13 -4
  80. package/src/standardActions/arrayActions.ts +12 -2
  81. package/src/standardActions/objectActions.ts +3 -2
  82. package/src/transforms/ImmutableDate.ts +4 -0
  83. package/src/transforms/asMap.ts +3 -1
  84. package/src/treeUtils/deepEquals.ts +15 -13
  85. package/src/treeUtils/draft.ts +8 -0
  86. package/src/treeUtils/sandbox.ts +31 -27
  87. package/src/tweaker/core.ts +2 -4
  88. package/src/tweaker/tweak.ts +8 -8
  89. package/src/tweaker/tweakArray.ts +86 -37
  90. package/src/tweaker/tweakFrozen.ts +0 -1
  91. package/src/tweaker/tweakModel.ts +0 -1
  92. package/src/tweaker/tweakPlainObject.ts +78 -29
  93. package/src/tweaker/typeChecking.ts +132 -33
  94. package/src/types/TypeCheckError.ts +17 -19
  95. package/src/types/createCachedTypeCheck.ts +27 -2
  96. package/src/types/objectBased/typesDataModelData.ts +4 -2
  97. package/src/types/objectBased/typesModel.ts +4 -2
  98. package/src/types/objectBased/typesObject.ts +20 -16
  99. package/src/types/objectBased/typesRecord.ts +6 -13
  100. package/src/types/objectBased/typesRef.ts +2 -2
  101. package/src/types/primitiveBased/typesEnum.ts +4 -2
  102. package/src/types/primitiveBased/typesPrimitive.ts +9 -4
  103. package/src/types/typeCheckErrorUtils.ts +1 -0
  104. package/src/types/utility/typesCodecSupport.ts +97 -56
  105. package/src/types/utility/typesMap.ts +24 -41
  106. package/src/types/utility/typesRefinement.ts +3 -2
  107. package/src/types/utility/typesSet.ts +24 -0
  108. package/src/utils/ModelPool.ts +3 -3
  109. package/src/utils/chainFns.ts +4 -0
  110. package/src/utils/decorators.ts +1 -1
  111. package/src/utils/errorDiagnostics.ts +6 -2
  112. package/src/utils/forEachWithDelayedThrow.ts +83 -0
  113. package/src/utils/index.ts +34 -2
  114. package/src/utils/mapUtils.ts +3 -7
  115. package/src/utils/setIfDifferent.ts +3 -2
  116. package/src/utils/setLike.ts +134 -0
  117. package/src/wrappers/ArraySet.ts +5 -9
  118. package/src/wrappers/ObjectMap.ts +15 -11
  119. package/src/wrappers/asMap.ts +30 -18
  120. package/src/wrappers/asSet.ts +36 -12
  121. package/dist/types/action/modelFlowPromiseGenerator.d.ts +0 -1
  122. 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 { fastGetParentPathIncludingDataObjects } from "../parent/path"
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
- * ancestor (nearest-first / bottom-up). Stops early if the callback throws.
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
- let current: object | undefined = dataToModelNode(obj)
43
- let lastAncestor: AnyModel | undefined
42
+ const start = dataToModelNode(obj)
44
43
 
45
- // If we started from a $ data object, check the model itself
46
- if (current !== obj && isModelWithTypeChecker(current)) {
47
- lastAncestor = current
48
- callback(current)
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
- // Walk up parent chain
52
- while (current !== undefined) {
53
- const parentPath: { parent: object; path: PathElement } | undefined =
54
- fastGetParentPathIncludingDataObjects(current, false)
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
- // quietly apply inverse patches (do not generate patches, snapshots, actions, etc)
90
- // The re-entrancy guard prevents auto type checking during rollback:
91
- // rollback mutations trigger observe callbacks which call this function,
92
- // and with "all" scope those re-entrant checks could see partially-restored
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
- runWithoutSnapshotOrPatches(() => {
97
- if (patchRecorder) {
87
+ if (patchRecorder) {
88
+ runWithoutSnapshotOrPatches(() => {
98
89
  internalApplyPatches.call(obj, patchRecorder.invPatches, true)
99
- } else if (snapshotBeforeChanges) {
100
- internalApplySnapshot.call(obj, snapshotBeforeChanges)
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 { fastGetRootPath } from "../parent/path"
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 ? [...diagnosticsPath, ...this.path] : this.path
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
- let rootPath: Path = []
166
- if (typeCheckedValue && isTweakedObject(typeCheckedValue, true)) {
167
- rootPath = fastGetRootPath(typeCheckedValue, false).path
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 true
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
- propTypes[propName] = {
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
- propTypes[propName] = {
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 { assertIsFunction, assertIsObject, getMobxVersion, isObject, lazy } from "../../utils"
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
- const keys = Object.keys(obj)
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
- newObj[k] = processor ? withErrorPathSegment(k, () => processor(obj[k])) : obj[k]
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
- return newObj
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
- propTypes[propName] = { type, typeInfo: getTypeInfo(type) }
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
- const newObj: typeof obj = {}
52
-
53
- const keys = Object.keys(obj)
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, type RefConstructor } from "../../ref/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 Ref)) {
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 (!vals.includes(v) && ((typeof v !== "string" && v !== +k) || e[v] !== +k)) {
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 = JSON.stringify(literal)
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 === literal
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 === literal ? thisTc : null),
61
+ (value) => (isEqualOrBothNaN(value, literal) ? thisTc : null),
57
62
  undefined,
58
63
  undefined
59
64
  )
@@ -16,6 +16,7 @@ export function prependPathElementToTypeCheckError(
16
16
 
17
17
  return new TypeCheckError({
18
18
  path: fullPath,
19
+ modelTrail: valueError.modelTrail,
19
20
  expectedTypeName: valueError.expectedTypeName,
20
21
  actualValue: valueError.actualValue,
21
22
  typeCheckedValue,