mobx-keystone 1.22.0 → 1.24.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 (77) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -21
  3. package/README.md +1 -1
  4. package/dist/mobx-keystone.esm.js +7198 -6242
  5. package/dist/mobx-keystone.esm.mjs +7198 -6242
  6. package/dist/mobx-keystone.umd.js +7217 -6243
  7. package/dist/types/action/runUnprotected.d.ts +8 -4
  8. package/dist/types/model/Model.d.ts +5 -0
  9. package/dist/types/modelShared/modelDecorator.d.ts +1 -1
  10. package/dist/types/modelShared/prop.d.ts +8 -4
  11. package/dist/types/snapshot/modelInitialData.d.ts +1 -0
  12. package/dist/types/tweaker/treeNodeMetadata.d.ts +1 -0
  13. package/dist/types/types/createCachedTypeCheck.d.ts +1 -0
  14. package/dist/types/utils/index.d.ts +24 -0
  15. package/package.json +15 -11
  16. package/src/action/middleware.ts +5 -18
  17. package/src/action/mutationBatch.ts +72 -0
  18. package/src/action/runUnprotected.ts +21 -8
  19. package/src/action/wrapInAction.ts +45 -15
  20. package/src/actionMiddlewares/undoMiddleware.ts +19 -13
  21. package/src/dataModel/BaseDataModel.ts +4 -8
  22. package/src/deepChange/onDeepChange.ts +246 -16
  23. package/src/model/BaseModel.ts +4 -12
  24. package/src/model/Model.ts +5 -0
  25. package/src/model/newModel.ts +68 -36
  26. package/src/modelShared/modelDecorator.ts +64 -53
  27. package/src/modelShared/prop.ts +105 -63
  28. package/src/modelShared/sharedInternalModel.ts +91 -60
  29. package/src/parent/core.ts +16 -10
  30. package/src/parent/coreObjectChildren.ts +219 -21
  31. package/src/parent/path.ts +6 -5
  32. package/src/parent/setParent.ts +5 -5
  33. package/src/patch/emitPatch.ts +102 -20
  34. package/src/ref/Ref.ts +4 -5
  35. package/src/ref/core.ts +2 -2
  36. package/src/snapshot/fromArraySnapshot.ts +11 -2
  37. package/src/snapshot/fromModelSnapshot.ts +10 -3
  38. package/src/snapshot/fromPlainObjectSnapshot.ts +18 -7
  39. package/src/snapshot/fromSnapshot.ts +31 -9
  40. package/src/snapshot/getSnapshot.ts +8 -3
  41. package/src/snapshot/internal.ts +243 -82
  42. package/src/snapshot/modelInitialData.ts +41 -0
  43. package/src/snapshot/reconcileArraySnapshot.ts +9 -3
  44. package/src/snapshot/reconcileModelSnapshot.ts +97 -17
  45. package/src/snapshot/reconcilePlainObjectSnapshot.ts +9 -3
  46. package/src/treeUtils/draft.ts +7 -8
  47. package/src/tweaker/core.ts +30 -5
  48. package/src/tweaker/treeNodeMetadata.ts +38 -0
  49. package/src/tweaker/tweak.ts +15 -4
  50. package/src/tweaker/tweakArray.ts +159 -121
  51. package/src/tweaker/tweakFrozen.ts +2 -2
  52. package/src/tweaker/tweakModel.ts +2 -2
  53. package/src/tweaker/tweakPlainObject.ts +117 -124
  54. package/src/tweaker/typeChecking.ts +10 -1
  55. package/src/types/TypeChecker.ts +261 -15
  56. package/src/types/arrayBased/typesArray.ts +38 -40
  57. package/src/types/arrayBased/typesTuple.ts +51 -34
  58. package/src/types/createCachedTypeCheck.ts +181 -0
  59. package/src/types/objectBased/typesArraySet.ts +42 -32
  60. package/src/types/objectBased/typesDataModelData.ts +18 -11
  61. package/src/types/objectBased/typesModel.ts +1 -1
  62. package/src/types/objectBased/typesObject.ts +57 -28
  63. package/src/types/objectBased/typesObjectMap.ts +51 -40
  64. package/src/types/objectBased/typesRecord.ts +59 -39
  65. package/src/types/objectBased/typesRef.ts +1 -1
  66. package/src/types/primitiveBased/typesPrimitive.ts +9 -9
  67. package/src/types/tProp.ts +80 -44
  68. package/src/types/utility/typesOr.ts +35 -24
  69. package/src/types/utility/typesRefinement.ts +21 -8
  70. package/src/types/utility/typesSkipCheck.ts +21 -8
  71. package/src/types/utility/typesTag.ts +21 -9
  72. package/src/types/utility/typesUnchecked.ts +2 -3
  73. package/src/utils/ModelPool.ts +34 -19
  74. package/src/utils/errorDiagnostics.ts +60 -18
  75. package/src/utils/index.ts +142 -9
  76. package/src/types/createPerEntryCachedCheck.ts +0 -166
  77. /package/dist/types/{types/createPerEntryCachedCheck.d.ts → action/mutationBatch.d.ts} +0 -0
package/src/ref/Ref.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { computed } from "mobx"
2
1
  import { Model } from "../model/Model"
3
2
  import { modelTypeKey } from "../model/metadata"
4
3
  import type { ModelClass } from "../modelShared/BaseModelShared"
5
4
  import { typesString } from "../types/primitiveBased/typesPrimitive"
6
5
  import { tProp } from "../types/tProp"
7
- import { failure } from "../utils"
6
+ import { failure, mobxComputed } from "../utils"
8
7
 
9
8
  /**
10
9
  * A reference model base type.
@@ -21,7 +20,7 @@ export abstract class Ref<T extends object> extends Model({
21
20
  /**
22
21
  * The object this reference points to, or `undefined` if the reference is currently invalid.
23
22
  */
24
- @computed
23
+ @mobxComputed
25
24
  get maybeCurrent(): T | undefined {
26
25
  return this.resolve()
27
26
  }
@@ -29,7 +28,7 @@ export abstract class Ref<T extends object> extends Model({
29
28
  /**
30
29
  * If the reference is currently valid.
31
30
  */
32
- @computed
31
+ @mobxComputed
33
32
  get isValid(): boolean {
34
33
  return !!this.maybeCurrent
35
34
  }
@@ -37,7 +36,7 @@ export abstract class Ref<T extends object> extends Model({
37
36
  /**
38
37
  * The object this reference points to, or throws if invalid.
39
38
  */
40
- @computed
39
+ @mobxComputed
41
40
  get current(): T {
42
41
  const current = this.maybeCurrent
43
42
 
package/src/ref/core.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  import { fastGetRoot } from "../parent/path"
10
10
  import { type ComputedWalkTreeAggregate, computedWalkTreeAggregate } from "../parent/walkTree"
11
11
  import { assertTweakedObject } from "../tweaker/core"
12
- import { assertIsObject, failure } from "../utils"
12
+ import { assertIsObject, failure, mobxAction } from "../utils"
13
13
  import { getOrCreate } from "../utils/mapUtils"
14
14
  import { Ref, type RefConstructor } from "./Ref"
15
15
 
@@ -103,7 +103,7 @@ export function internalCustomRef<T extends object>(
103
103
  return true
104
104
  }
105
105
 
106
- @action
106
+ @mobxAction
107
107
  forceUpdateBackRefs() {
108
108
  const currentTarget = this.maybeCurrent
109
109
  if (!this.startTracking(currentTarget)) {
@@ -1,6 +1,6 @@
1
1
  import { tweakArray } from "../tweaker/tweakArray"
2
2
  import { isArray } from "../utils"
3
- import { withErrorPathSegment } from "../utils/errorDiagnostics"
3
+ import { getCurrentErrorDiagnosticsContext } from "../utils/errorDiagnostics"
4
4
  import { type FromSnapshotContext, internalFromSnapshot, registerSnapshotter } from "./fromSnapshot"
5
5
  import type { SnapshotInOfObject } from "./SnapshotOf"
6
6
  import { SnapshotterAndReconcilerPriority } from "./SnapshotterAndReconcilerPriority"
@@ -8,8 +8,17 @@ import { SnapshotterAndReconcilerPriority } from "./SnapshotterAndReconcilerPrio
8
8
  function fromArraySnapshot(sn: SnapshotInOfObject<any>, ctx: FromSnapshotContext): any[] {
9
9
  const ln = sn.length
10
10
  const arr: any[] = new Array(ln)
11
+ // Hydration visits every slot. Keep this direct stack use rather than
12
+ // withErrorPathSegment: it avoids one callback closure per item and has a
13
+ // measured production hydration win.
14
+ const errorDiagnosticsContext = getCurrentErrorDiagnosticsContext()
11
15
  for (let i = 0; i < ln; i++) {
12
- arr[i] = withErrorPathSegment(i, () => internalFromSnapshot(sn[i], ctx))
16
+ errorDiagnosticsContext?.pushPath(i)
17
+ try {
18
+ arr[i] = internalFromSnapshot(sn[i], ctx)
19
+ } finally {
20
+ errorDiagnosticsContext?.popPath()
21
+ }
13
22
  }
14
23
  return tweakArray(arr, undefined, true)
15
24
  }
@@ -5,7 +5,7 @@ import { modelTypeKey } from "../model/metadata"
5
5
  import { getModelOrSnapshotTypeAndId, getSnapshotModelType } from "../model/utils"
6
6
  import type { ModelClass } from "../modelShared/BaseModelShared"
7
7
  import { getModelInfoForName, getModelNotRegisteredErrorMessage } from "../modelShared/modelInfo"
8
- import { withErrorModelTrailEntry } from "../utils/errorDiagnostics"
8
+ import { getCurrentErrorDiagnosticsContext } from "../utils/errorDiagnostics"
9
9
  import { type FromSnapshotContext, registerSnapshotter } from "./fromSnapshot"
10
10
  import type { SnapshotInOfModel } from "./SnapshotOf"
11
11
  import { SnapshotProcessingError } from "./SnapshotProcessingError"
@@ -40,7 +40,12 @@ function fromModelSnapshot(sn: SnapshotInOfModel<AnyModel>, ctx: FromSnapshotCon
40
40
  const modelTypeAndId = getModelOrSnapshotTypeAndId(sn)
41
41
  const modelId = modelTypeAndId?.modelId
42
42
 
43
- return withErrorModelTrailEntry(type, modelId, () => {
43
+ // Hydration constructs every model. Keep this direct stack use rather than
44
+ // withErrorModelTrailEntry: it avoids one callback closure per model and
45
+ // has a measured production hydration win.
46
+ const errorDiagnosticsContext = getCurrentErrorDiagnosticsContext()
47
+ errorDiagnosticsContext?.pushModelTrail(type, modelId)
48
+ try {
44
49
  return new (modelInfo.class as any)(undefined, {
45
50
  snapshotInitialData: {
46
51
  unprocessedSnapshot: sn,
@@ -54,7 +59,9 @@ function fromModelSnapshot(sn: SnapshotInOfModel<AnyModel>, ctx: FromSnapshotCon
54
59
  },
55
60
  generateNewIds: ctx.options.generateNewIds,
56
61
  } satisfies ModelConstructorOptions)
57
- })
62
+ } finally {
63
+ errorDiagnosticsContext?.popModelTrail()
64
+ }
58
65
  }
59
66
 
60
67
  /**
@@ -1,7 +1,7 @@
1
1
  import { observable } from "mobx"
2
2
  import { tweakPlainObject } from "../tweaker/tweakPlainObject"
3
- import { isPlainObject, setOwnProp } from "../utils"
4
- import { withErrorPathSegment } from "../utils/errorDiagnostics"
3
+ import { isPlainObject, setProtoProp } from "../utils"
4
+ import { getCurrentErrorDiagnosticsContext } from "../utils/errorDiagnostics"
5
5
  import {
6
6
  type FromSnapshotContext,
7
7
  internalFromSnapshot,
@@ -13,17 +13,28 @@ import { SnapshotterAndReconcilerPriority } from "./SnapshotterAndReconcilerPrio
13
13
 
14
14
  function fromPlainObjectSnapshot(sn: SnapshotInOfObject<any>, ctx: FromSnapshotContext): object {
15
15
  const plainObj: Record<string, unknown> = {}
16
+ // Hydration visits every property. Keep this direct stack use rather than
17
+ // withErrorPathSegment: it avoids one callback closure per property and has
18
+ // a measured production hydration win.
19
+ const errorDiagnosticsContext = getCurrentErrorDiagnosticsContext()
16
20
 
17
21
  const snKeys = Object.keys(sn)
18
22
  const snKeysLen = snKeys.length
19
23
  for (let i = 0; i < snKeysLen; i++) {
20
24
  const k = snKeys[i]
21
25
  const v = sn[k]
22
- setOwnProp(
23
- plainObj,
24
- k,
25
- withErrorPathSegment(k, () => internalFromSnapshot(v, ctx))
26
- )
26
+ errorDiagnosticsContext?.pushPath(k)
27
+ let snapshotValue: unknown
28
+ try {
29
+ snapshotValue = internalFromSnapshot(v, ctx)
30
+ } finally {
31
+ errorDiagnosticsContext?.popPath()
32
+ }
33
+ if (k === "__proto__") {
34
+ setProtoProp(plainObj, snapshotValue)
35
+ } else {
36
+ plainObj[k] = snapshotValue
37
+ }
27
38
  }
28
39
  return tweakPlainObject(
29
40
  observable.object(plainObj, undefined, observableOptions),
@@ -5,8 +5,12 @@ import { resolveStandardTypeNoThrow, resolveTypeChecker } from "../types/resolve
5
5
  import type { AnyType, TypeToData, TypeToSnapshotIn } from "../types/schemas"
6
6
  import { isLateTypeChecker, TypeChecker } from "../types/TypeChecker"
7
7
  import { resolveCodecSupport } from "../types/utility/typesCodec"
8
- import { isMap, isPrimitive, isSet, setOwnProp } from "../utils"
9
- import { runWithErrorDiagnosticsContext, withErrorPathSegment } from "../utils/errorDiagnostics"
8
+ import { isMap, isPrimitive, isSet, setProtoProp } from "../utils"
9
+ import {
10
+ getCurrentErrorDiagnosticsContext,
11
+ runWithErrorDiagnosticsContext,
12
+ } from "../utils/errorDiagnostics"
13
+ import { setModelInitialDataSnapshot } from "./modelInitialData"
10
14
  import { registerDefaultSnapshotters } from "./registerDefaultSnapshotters"
11
15
  import type { SnapshotInOf, SnapshotInOfModel, SnapshotOutOf } from "./SnapshotOf"
12
16
  import { SnapshotProcessingError } from "./SnapshotProcessingError"
@@ -87,9 +91,8 @@ export function fromSnapshot<T>(arg1: any, arg2: any, arg3?: any): T {
87
91
 
88
92
  if (codecSupport.hasCodec) {
89
93
  unprocessedSnapshot = arg2
90
- snapshot = resolveTypeChecker(codecSupport.storedType).fromSnapshotProcessor(
91
- unprocessedSnapshot
92
- )
94
+ const processor = resolveTypeChecker(codecSupport.storedType).getFromSnapshotProcessor()
95
+ snapshot = processor ? processor(unprocessedSnapshot) : unprocessedSnapshot
93
96
  options = arg3
94
97
  const storedValue = fromSnapshotAction(snapshot, unprocessedSnapshot, options)
95
98
  return codecSupport.adapter.toRuntime(storedValue)
@@ -97,7 +100,8 @@ export function fromSnapshot<T>(arg1: any, arg2: any, arg3?: any): T {
97
100
 
98
101
  const typeChecker = resolveTypeChecker(resolvedType)
99
102
  unprocessedSnapshot = arg2
100
- snapshot = typeChecker.fromSnapshotProcessor(unprocessedSnapshot)
103
+ const processor = typeChecker.getFromSnapshotProcessor()
104
+ snapshot = processor ? processor(unprocessedSnapshot) : unprocessedSnapshot
101
105
  options = arg3
102
106
  } else {
103
107
  snapshot = arg1
@@ -179,6 +183,11 @@ function snapshotToInitialData(
179
183
  processedSn: SnapshotInOfModel<AnyModel>
180
184
  ): any {
181
185
  const initialData: Record<string, unknown> = {}
186
+ let allValuesPrimitive = true
187
+ // Model hydration visits every data slot. Keep this direct stack use rather
188
+ // than withErrorPathSegment: it avoids one callback closure per slot and
189
+ // has a measured production hydration win.
190
+ const errorDiagnosticsContext = getCurrentErrorDiagnosticsContext()
182
191
 
183
192
  const processedSnKeys = Object.keys(processedSn)
184
193
  const processedSnKeysLen = processedSnKeys.length
@@ -186,11 +195,24 @@ function snapshotToInitialData(
186
195
  const k = processedSnKeys[i]
187
196
  if (!isReservedModelKey(k)) {
188
197
  const v = processedSn[k]
189
- const snapshotValue = withErrorPathSegment(k, () => internalFromSnapshot(v, ctx))
190
- setOwnProp(initialData, k, snapshotValue)
198
+ errorDiagnosticsContext?.pushPath(k)
199
+ let snapshotValue: unknown
200
+ try {
201
+ snapshotValue = internalFromSnapshot(v, ctx)
202
+ } finally {
203
+ errorDiagnosticsContext?.popPath()
204
+ }
205
+ allValuesPrimitive &&= isPrimitive(snapshotValue)
206
+ if (k === "__proto__") {
207
+ setProtoProp(initialData, snapshotValue)
208
+ } else {
209
+ initialData[k] = snapshotValue
210
+ }
191
211
  }
192
212
  }
193
- return observable.object(initialData, undefined, observableOptions)
213
+ const observableInitialData = observable.object(initialData, undefined, observableOptions)
214
+ setModelInitialDataSnapshot(observableInitialData, initialData, allValuesPrimitive)
215
+ return observableInitialData
194
216
  }
195
217
 
196
218
  export const observableOptions = {
@@ -5,6 +5,7 @@ import type { AnyType, TypeToData, TypeToSnapshotOut } from "../types/schemas"
5
5
  import { resolveCodecSupport } from "../types/utility/typesCodec"
6
6
  import { failure, identityFn, isPrimitive } from "../utils"
7
7
  import {
8
+ flushInternalSnapshot,
8
9
  freezeInternalSnapshot,
9
10
  getInternalSnapshot,
10
11
  reportInternalSnapshotObserved,
@@ -48,16 +49,19 @@ export function getSnapshot(...args: [arg1: any] | [arg1: any, arg2: any]): any
48
49
  const storedValue = codecSupport.adapter.toStored(arg2)
49
50
 
50
51
  if (isPrimitive(storedValue)) {
51
- return storedTypeChecker.toSnapshotProcessor(storedValue)
52
+ const processor = storedTypeChecker.getToSnapshotProcessor()
53
+ return processor ? processor(storedValue) : storedValue
52
54
  }
53
55
 
54
56
  const storedTree = isTweakedObject(storedValue, true)
55
57
  ? storedValue
56
58
  : toTreeNode(codecSupport.storedType, storedValue)
57
- return storedTypeChecker.toSnapshotProcessor(getSnapshot(storedTree))
59
+ const storedSnapshot = getSnapshot(storedTree)
60
+ const processor = storedTypeChecker.getToSnapshotProcessor()
61
+ return processor ? processor(storedSnapshot) : storedSnapshot
58
62
  }
59
63
 
60
- toSnapshotProcessor = resolveTypeChecker(arg1).toSnapshotProcessor
64
+ toSnapshotProcessor = resolveTypeChecker(arg1).getToSnapshotProcessor() ?? identityFn
61
65
  nodeOrPrimitive = arg2
62
66
  } else {
63
67
  nodeOrPrimitive = arg1
@@ -74,6 +78,7 @@ export function getSnapshot(...args: [arg1: any] | [arg1: any, arg2: any]): any
74
78
  throw failure("getSnapshot is not supported for this kind of object")
75
79
  }
76
80
 
81
+ flushInternalSnapshot(nodeOrPrimitive, true)
77
82
  freezeInternalSnapshot(snapshot.transformed)
78
83
  reportInternalSnapshotObserved(snapshot)
79
84
  return toSnapshotProcessor(snapshot.transformed)