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.
@@ -1,4 +1,4 @@
1
- import { remove } from "mobx"
1
+ import { remove, set } from "mobx"
2
2
  import type { AnyModel } from "../model/BaseModel"
3
3
  import { getModelIdPropertyName } from "../model/getModelMetadata"
4
4
  import { isReservedModelKey, modelIdKey, modelTypeKey } from "../model/metadata"
@@ -17,7 +17,7 @@ import {
17
17
  runTypeCheckingAfterChange,
18
18
  } from "../tweaker/typeChecking"
19
19
  import { withoutTypeChecking } from "../tweaker/withoutTypeChecking"
20
- import { isArray } from "../utils"
20
+ import { isArray, isPrimitive } from "../utils"
21
21
  import { withErrorModelTrailEntry, withErrorPathSegment } from "../utils/errorDiagnostics"
22
22
  import type { ModelPool } from "../utils/ModelPool"
23
23
  import { setIfDifferent } from "../utils/setIfDifferent"
@@ -51,7 +51,7 @@ function reconcileModelSnapshot(
51
51
  // the canonical output snapshot. A successful canonical no-op therefore
52
52
  // cannot produce a diagnostic, so keep this hottest path outside the
53
53
  // model-trail bookkeeping and defer id metadata until needed. The
54
- // positional value was already flushed by the getSnapshot call in
54
+ // positional value was already flushed by the internal snapshot read in
55
55
  // reconcileSnapshot, so its transformed snapshot is current.
56
56
  if (isCanonicalSnapshotNoOp(value, type, sn)) {
57
57
  return value
@@ -157,6 +157,14 @@ function reconcileModelSnapshot(
157
157
  const v = processedSn[k]
158
158
 
159
159
  const oldValue = data[k]
160
+ // Non-null primitives need neither reconciliation nor detachment.
161
+ // Nullish values must still pass through default handling below.
162
+ if (v != null && isPrimitive(v)) {
163
+ if (v !== oldValue) {
164
+ set(data, k, v)
165
+ }
166
+ continue
167
+ }
160
168
  let newValue = withErrorPathSegment(k, () =>
161
169
  reconcileSnapshot(oldValue, v, modelPool, modelObj)
162
170
  )
@@ -5,6 +5,7 @@ import { fastGetParentPathIncludingDataObjects } from "../parent"
5
5
  import { isMap, isPrimitive, isSet } from "../utils"
6
6
  import type { ModelPool } from "../utils/ModelPool"
7
7
  import { getSnapshot } from "./getSnapshot"
8
+ import { flushInternalSnapshot, getInternalSnapshot } from "./internal"
8
9
  import { registerDefaultReconcilers } from "./registerDefaultReconcilers"
9
10
  import { SnapshotProcessingError } from "./SnapshotProcessingError"
10
11
 
@@ -28,10 +29,18 @@ export function reconcileSnapshot(value: any, sn: any, modelPool: ModelPool, par
28
29
  return sn
29
30
  }
30
31
 
31
- // if the snapshot passed is exactly the same as the current one
32
- // then it is already reconciled
33
- if (getSnapshot(value) === sn) {
34
- return value
32
+ // Unlike getSnapshot, this comparison never exposes the snapshot, so it needs
33
+ // neither freezing nor observation registration.
34
+ const currentSnapshot = getInternalSnapshot(value)
35
+ if (currentSnapshot) {
36
+ flushInternalSnapshot(value, false)
37
+ if (currentSnapshot.transformed === sn) {
38
+ return value
39
+ }
40
+ } else if (!isPrimitive(value)) {
41
+ // A non-primitive without an internal snapshot is not a valid reconciliation
42
+ // target (e.g. a non-node object); let getSnapshot raise the proper error.
43
+ getSnapshot(value)
35
44
  }
36
45
 
37
46
  registerDefaultReconcilers()
@@ -86,16 +86,11 @@ export function registerTweaker<T>(priority: number, tweaker: Tweaker<T>): void
86
86
  }
87
87
 
88
88
  function internalTweak<T>(value: T, parentPath: ParentPath<any> | undefined): T {
89
- if (isPrimitive(value)) {
90
- return value
91
- }
92
-
93
89
  // already tweaked
94
90
  if (isTweakedObject(value, true)) {
95
91
  value = setParent(
96
92
  value,
97
93
  parentPath,
98
- false, // indexChangeAllowed
99
94
  false, // isDataObject
100
95
  true // cloneIfApplicable
101
96
  )
@@ -135,10 +130,15 @@ function internalTweak<T>(value: T, parentPath: ParentPath<any> | undefined): T
135
130
  )
136
131
  }
137
132
 
133
+ const tweakNonPrimitive = action("tweak", internalTweak)
134
+
138
135
  /**
139
136
  * @internal
140
137
  */
141
- export const tweak = action("tweak", internalTweak)
138
+ export function tweak<T>(value: T, parentPath: ParentPath<any> | undefined): T {
139
+ // Primitives need no tree bookkeeping or MobX action boundary.
140
+ return isPrimitive(value) ? value : tweakNonPrimitive(value, parentPath)
141
+ }
142
142
 
143
143
  /**
144
144
  * @internal
@@ -169,7 +169,6 @@ export function tryUntweak(value: any): (() => void) | undefined {
169
169
  setParent(
170
170
  children[i], // value
171
171
  undefined, // parentPath
172
- false, // indexChangeAllowed
173
172
  false, // isDataObject
174
173
  // no need to clone if unsetting the parent
175
174
  false // cloneIfApplicable
@@ -12,7 +12,7 @@ import { assertCanWrite } from "../action/protection"
12
12
  import { emitArraySpliceDeepChange, emitArrayUpdateDeepChange } from "../deepChange/onDeepChange"
13
13
  import { getGlobalConfig } from "../globalConfig"
14
14
  import type { ParentPath } from "../parent/path"
15
- import { setParent } from "../parent/setParent"
15
+ import { reindexArrayChildren, setParent } from "../parent/setParent"
16
16
  import { hasPatchListenersFor, InternalPatchRecorder } from "../patch/emitPatch"
17
17
  import type { Patch } from "../patch/Patch"
18
18
  import {
@@ -62,7 +62,6 @@ export function tweakArray<T extends any[]>(
62
62
  setParent(
63
63
  tweakedArr, // value
64
64
  parentPath,
65
- false, // indexChangeAllowed
66
65
  false, // isDataObject
67
66
  // arrays shouldn't be cloned anyway
68
67
  false // cloneIfApplicable
@@ -97,7 +96,6 @@ export function tweakArray<T extends any[]>(
97
96
  setParent(
98
97
  tweakedValue, // value
99
98
  path, // parentPath
100
- false, // indexChangeAllowed
101
99
  false, // isDataObject
102
100
  // the value is already a new value (the result of a fromSnapshot)
103
101
  false // cloneIfApplicable
@@ -482,20 +480,8 @@ function interceptArrayMutationSplice(change: IArrayWillSplice) {
482
480
  const oldNextIndex = change.index + change.removedCount
483
481
  const newNextIndex = change.index + change.added.length
484
482
 
485
- if (oldNextIndex !== newNextIndex) {
486
- for (let i = oldNextIndex, j = newNextIndex; i < change.object.length; i++, j++) {
487
- setParent(
488
- change.object[i], // value
489
- {
490
- parent: change.object,
491
- path: j,
492
- }, // parentPath
493
- true, // indexChangeAllowed
494
- false, // isDataObject
495
- // just re-indexing
496
- false // cloneIfApplicable
497
- )
498
- }
483
+ if (oldNextIndex !== newNextIndex && oldNextIndex < change.object.length) {
484
+ reindexArrayChildren(change.object, oldNextIndex, newNextIndex)
499
485
  }
500
486
  }
501
487
 
@@ -17,7 +17,6 @@ export function tweakFrozen<T extends Frozen<any>>(
17
17
  setParent(
18
18
  frozenObj, // value
19
19
  parentPath,
20
- false, // indexChangeAllowed
21
20
  false, // isDataObject
22
21
  // a frozen is not a value-type
23
22
  false // cloneIfApplicable
@@ -13,7 +13,6 @@ export function tweakModel<T extends object>(value: T, parentPath: ParentPath<an
13
13
  setParent(
14
14
  value,
15
15
  parentPath,
16
- false, // indexChangeAllowed
17
16
  false, // isDataObject
18
17
  true // cloneIfApplicable
19
18
  )
@@ -59,7 +59,6 @@ export function tweakPlainObject<T extends Record<string, any>>(
59
59
  setParent(
60
60
  tweakedObj, // value
61
61
  parentPath,
62
- false, // indexChangeAllowed
63
62
  isDataObject,
64
63
  // an object shouldn't be cloned
65
64
  false // cloneIfApplicable
@@ -67,12 +66,10 @@ export function tweakPlainObject<T extends Record<string, any>>(
67
66
 
68
67
  const initialDataSnapshot =
69
68
  isDataObject && snapshotModelType ? takeModelInitialDataSnapshot(tweakedObj) : undefined
70
- const reuseInitialDataSnapshot =
71
- !!initialDataSnapshot?.reusable && initialDataSnapshot.allValuesPrimitive
72
- const untransformedSn: any = reuseInitialDataSnapshot ? initialDataSnapshot.snapshot : {}
69
+ const untransformedSn: any = initialDataSnapshot ?? {}
73
70
 
74
71
  // substitute initial values by tweaked values
75
- const originalObjKeys = reuseInitialDataSnapshot ? [] : Object.keys(originalObj)
72
+ const originalObjKeys = initialDataSnapshot ? [] : Object.keys(originalObj)
76
73
  const originalObjKeysLen = originalObjKeys.length
77
74
  for (let i = 0; i < originalObjKeysLen; i++) {
78
75
  const k = originalObjKeys[i]
@@ -96,7 +93,6 @@ export function tweakPlainObject<T extends Record<string, any>>(
96
93
  setParent(
97
94
  tweakedValue, // value
98
95
  path, // parentPath
99
- false, // indexChangeAllowed
100
96
  false, // isDataObject
101
97
  // the value is already a new value (the result of a fromSnapshot)
102
98
  false // cloneIfApplicable
@@ -3,8 +3,7 @@ import type { AnyModel } from "../model/BaseModel"
3
3
  import { getModelMetadata } from "../model/getModelMetadata"
4
4
  import { isModel } from "../model/utils"
5
5
  import { dataToModelNode } from "../parent/core"
6
- import { fastGetParentPathIncludingDataObjects } from "../parent/path"
7
- import type { PathElement } from "../parent/pathTypes"
6
+ import { fastGetParent } from "../parent/path"
8
7
  import { internalApplyPatches } from "../patch/applyPatches"
9
8
  import type { InternalPatchRecorder } from "../patch/emitPatch"
10
9
  import { internalApplySnapshot } from "../snapshot/applySnapshot"
@@ -39,29 +38,20 @@ function isModelWithTypeChecker(obj: object): obj is AnyModel {
39
38
  */
40
39
  function forEachTypedModelAncestor(obj: object, callback: (model: AnyModel) => void): void {
41
40
  // 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
41
+ const start = dataToModelNode(obj)
44
42
 
45
43
  // If we started from a $ data object, check the model itself
46
- if (current !== obj && isModelWithTypeChecker(current)) {
47
- lastAncestor = current
48
- callback(current)
44
+ if (start !== obj && isModelWithTypeChecker(start)) {
45
+ callback(start)
49
46
  }
50
47
 
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
48
+ // Raw parents never point to model $ objects, so each model is visited once.
49
+ let parent: object | undefined = fastGetParent(start, false)
50
+ while (parent) {
51
+ if (isModelWithTypeChecker(parent)) {
61
52
  callback(parent)
62
53
  }
63
-
64
- current = parent
54
+ parent = fastGetParent(parent, false)
65
55
  }
66
56
  }
67
57
 
@@ -6,7 +6,7 @@ import type { TypeCheckError } from "./TypeCheckError"
6
6
 
7
7
  type CheckFunction = (value: any, path: Path, typeCheckedValue: any) => TypeCheckError | null
8
8
 
9
- /** @internal */
9
+ /** @ignore */
10
10
  export type SnapshotProcessor = (snapshot: any) => unknown
11
11
 
12
12
  /**
@@ -14,7 +14,7 @@ export type SnapshotProcessor = (snapshot: any) => unknown
14
14
  * TypeCheckers rather than processors so recursive graphs can be discovered
15
15
  * before any processor body is compiled.
16
16
  *
17
- * @internal
17
+ * @ignore
18
18
  */
19
19
  export interface SnapshotProcessorPlan {
20
20
  readonly intrinsic?: boolean
@@ -36,7 +36,7 @@ export function snapshotProcessorPlan(
36
36
  type SnapshotProcessorSource = SnapshotProcessor | SnapshotProcessorPlan | undefined
37
37
  type SnapshotProcessorDirection = "from" | "to"
38
38
 
39
- /** @internal */
39
+ /** @ignore */
40
40
  export class SnapshotProcessorPlanNode {
41
41
  private readonly owner: TypeChecker | undefined
42
42
  private readonly direction: SnapshotProcessorDirection
@@ -196,7 +196,7 @@ export class SnapshotProcessorPlanNode {
196
196
  const absentSnapshotProcessorPlanNode = new SnapshotProcessorPlanNode(undefined, "from", undefined)
197
197
 
198
198
  /**
199
- * @internal
199
+ * @ignore
200
200
  */
201
201
  export enum TypeCheckerBaseType {
202
202
  Object = "object",
@@ -243,7 +243,7 @@ export function invalidateCachedToSnapshotProcessorResult(obj: object) {
243
243
  }
244
244
 
245
245
  /**
246
- * @internal
246
+ * @ignore
247
247
  */
248
248
  export class TypeChecker {
249
249
  unchecked: boolean
@@ -443,6 +443,6 @@ export class TypeInfo {
443
443
  }
444
444
 
445
445
  /**
446
- * @internal
446
+ * @ignore
447
447
  */
448
448
  export type TypeInfoGen = (t: AnyStandardType) => TypeInfo
@@ -64,7 +64,7 @@ export interface ObjectOfTypes {
64
64
 
65
65
  /**
66
66
  * Name of the properties of an object that can be set to undefined, any or unknown.
67
- * @internal
67
+ * @ignore
68
68
  */
69
69
  export type UndefinablePropsNames<T> = {
70
70
  [K in keyof T]: IsOptionalValue<T[K], K, never>
@@ -72,7 +72,7 @@ export type UndefinablePropsNames<T> = {
72
72
 
73
73
  /**
74
74
  * Computes the optional keys for an object type schema.
75
- * @internal
75
+ * @ignore
76
76
  */
77
77
  export type ObjectOptionalKeys<S> = UndefinablePropsNames<{
78
78
  [k in keyof S]: TypeToDataOpt<S[k]> extends infer R ? R : never