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
@@ -23,6 +23,24 @@ export class MobxKeystoneError extends Error {
23
23
  }
24
24
  }
25
25
 
26
+ /**
27
+ * A mobx-keystone error that groups several failures that happened while delivering
28
+ * a single notification, such as multiple patch listeners or root store hooks throwing.
29
+ *
30
+ * It is only used when more than one callback failed; a lone failure is rethrown as is.
31
+ */
32
+ export class MobxKeystoneAggregateError extends MobxKeystoneError {
33
+ /**
34
+ * The values thrown by the failing callbacks, in the order they were thrown.
35
+ */
36
+ readonly errors: readonly unknown[]
37
+
38
+ constructor(errors: readonly unknown[], msg: string) {
39
+ super(msg)
40
+ this.errors = errors
41
+ }
42
+ }
43
+
26
44
  /**
27
45
  * @internal
28
46
  */
@@ -107,6 +125,18 @@ export let hasOwnProp: (object: object, propName: PropertyKey) => boolean = (
107
125
  return impl(object, propName)
108
126
  }
109
127
 
128
+ /**
129
+ * Like `===`, except that `NaN` equals `NaN`.
130
+ * This is the `SameValueZero` equality used by `Map`/`Set` keys and by
131
+ * `Array.prototype.includes`, so `0` and `-0` are the same value.
132
+ *
133
+ * @internal
134
+ */
135
+ export function isEqualOrBothNaN(a: unknown, b: unknown): boolean {
136
+ // biome-ignore lint/suspicious/noSelfCompare: NaN check
137
+ return a === b || (a !== a && b !== b)
138
+ }
139
+
110
140
  /**
111
141
  * @internal
112
142
  */
@@ -224,7 +254,9 @@ declare const process:
224
254
  * @internal
225
255
  */
226
256
  export const inDevMode =
227
- process?.env?.NODE_ENV !== undefined && process.env.NODE_ENV !== "production"
257
+ typeof process !== "undefined" &&
258
+ process?.env?.NODE_ENV !== undefined &&
259
+ process.env.NODE_ENV !== "production"
228
260
 
229
261
  /**
230
262
  * @internal
@@ -545,7 +577,7 @@ export function makeObservableCompat(target: object, annotations?: object): void
545
577
  * @internal
546
578
  */
547
579
  export function propNameToSetterName(propName: string): string {
548
- return `set${propName[0].toUpperCase()}${propName.slice(1)}`
580
+ return `set${propName.charAt(0).toUpperCase()}${propName.slice(1)}`
549
581
  }
550
582
 
551
583
  let cachedMobxVersion: number | undefined
@@ -1,15 +1,11 @@
1
1
  type AnyMap<V = any> = Map<any, V> | WeakMap<any, V>
2
2
 
3
- export function getOrCreate<K, V, C = V>(map: Map<K, V>, key: K, create: () => C): V
4
- export function getOrCreate<K extends object, V, C = V>(
5
- map: WeakMap<K, V>,
6
- key: K,
7
- create: () => C
8
- ): V
3
+ export function getOrCreate<K, V>(map: Map<K, V>, key: K, create: () => V): V
4
+ export function getOrCreate<K extends object, V>(map: WeakMap<K, V>, key: K, create: () => V): V
9
5
 
10
6
  export function getOrCreate<V>(map: AnyMap<V>, key: any, create: () => V) {
11
7
  let value = map.get(key)
12
- if (value === undefined) {
8
+ if (value === undefined && !map.has(key)) {
13
9
  value = create()
14
10
  map.set(key, value)
15
11
  }
@@ -1,13 +1,14 @@
1
1
  import { set } from "mobx"
2
+ import { isEqualOrBothNaN } from "./index"
2
3
 
3
4
  export function setIfDifferent(target: any, key: PropertyKey, value: unknown): void {
4
- if (target[key] !== value || !(key in target)) {
5
+ if (!isEqualOrBothNaN(target[key], value) || !(key in target)) {
5
6
  set(target, key, value)
6
7
  }
7
8
  }
8
9
 
9
10
  export function setIfDifferentWithReturn(target: any, key: PropertyKey, value: unknown): boolean {
10
- if (target[key] !== value || !(key in target)) {
11
+ if (!isEqualOrBothNaN(target[key], value) || !(key in target)) {
11
12
  set(target, key, value)
12
13
  return true
13
14
  }
@@ -0,0 +1,134 @@
1
+ import { failure } from "./index"
2
+
3
+ // keys() need only return an iterator, not an iterable. Wrapping it in one also lets
4
+ // for-of close that iterator when a predicate exits early.
5
+ function keysOf<T>(set: ReadonlySetLike<T>): Iterable<T> {
6
+ return { [Symbol.iterator]: () => set.keys() }
7
+ }
8
+
9
+ // Read these once, in protocol order, even when a size check can decide the result.
10
+ function getSetRecord<T>(set: ReadonlySetLike<T>) {
11
+ if ((typeof set !== "object" || set === null) && typeof set !== "function") {
12
+ throw failure("expected a set-like object")
13
+ }
14
+ const numberSize = +set.size
15
+ if (Number.isNaN(numberSize)) throw failure("set-like size must be a number")
16
+ const size = Math.trunc(numberSize)
17
+ if (size < 0) throw failure("set-like size must not be negative")
18
+ const has = set.has
19
+ if (typeof has !== "function") throw failure("set-like has must be a function")
20
+ const keys = set.keys
21
+ if (typeof keys !== "function") throw failure("set-like keys must be a function")
22
+ return {
23
+ size,
24
+ has: (value: T) => has.call(set, value),
25
+ keys: { [Symbol.iterator]: () => keys.call(set) } as Iterable<T>,
26
+ }
27
+ }
28
+
29
+ /** @internal */
30
+ export function isSetLikeSubsetOf<T>(
31
+ set: ReadonlySetLike<T>,
32
+ other: ReadonlySetLike<unknown>
33
+ ): boolean {
34
+ const otherRecord = getSetRecord(other)
35
+ if (set.size > otherRecord.size) return false
36
+ for (const value of keysOf(set)) {
37
+ if (!otherRecord.has(value)) return false
38
+ }
39
+ return true
40
+ }
41
+
42
+ /** @internal */
43
+ export function isSetLikeSupersetOf<T>(
44
+ set: ReadonlySetLike<T>,
45
+ other: ReadonlySetLike<unknown>
46
+ ): boolean {
47
+ const otherRecord = getSetRecord(other)
48
+ if (set.size < otherRecord.size) return false
49
+ for (const value of otherRecord.keys) {
50
+ if (!set.has(value as T)) return false
51
+ }
52
+ return true
53
+ }
54
+
55
+ /** @internal */
56
+ export function isSetLikeDisjointFrom<T>(
57
+ set: ReadonlySetLike<T>,
58
+ other: ReadonlySetLike<unknown>
59
+ ): boolean {
60
+ const otherRecord = getSetRecord(other)
61
+ if (set.size <= otherRecord.size) {
62
+ for (const value of keysOf(set)) {
63
+ if (otherRecord.has(value)) return false
64
+ }
65
+ } else {
66
+ for (const value of otherRecord.keys) {
67
+ if (set.has(value as T)) return false
68
+ }
69
+ }
70
+ return true
71
+ }
72
+
73
+ /** @internal */
74
+ export function setLikeUnion<T, U>(set: ReadonlySetLike<T>, other: ReadonlySetLike<U>): Set<T | U> {
75
+ const otherRecord = getSetRecord(other)
76
+ const result = new Set<T | U>(keysOf(set))
77
+ for (const value of otherRecord.keys) result.add(value)
78
+ return result
79
+ }
80
+
81
+ /** @internal */
82
+ export function setLikeIntersection<T, U>(
83
+ set: ReadonlySetLike<T>,
84
+ other: ReadonlySetLike<U>
85
+ ): Set<T & U> {
86
+ const otherRecord = getSetRecord(other)
87
+ const result = new Set<T & U>()
88
+ if (set.size <= otherRecord.size) {
89
+ for (const value of keysOf(set)) {
90
+ if (otherRecord.has(value as unknown as U)) result.add(value as T & U)
91
+ }
92
+ } else {
93
+ for (const value of otherRecord.keys) {
94
+ if (set.has(value as unknown as T)) result.add(value as T & U)
95
+ }
96
+ }
97
+ return result
98
+ }
99
+
100
+ /** @internal */
101
+ export function setLikeDifference<T>(
102
+ set: ReadonlySetLike<T>,
103
+ other: ReadonlySetLike<unknown>
104
+ ): Set<T> {
105
+ const otherRecord = getSetRecord(other)
106
+ const result = new Set<T>()
107
+ if (set.size <= otherRecord.size) {
108
+ for (const value of keysOf(set)) {
109
+ if (!otherRecord.has(value)) result.add(value)
110
+ }
111
+ } else {
112
+ for (const value of keysOf(set)) result.add(value)
113
+ for (const value of otherRecord.keys) result.delete(value as T)
114
+ }
115
+ return result
116
+ }
117
+
118
+ /** @internal */
119
+ export function setLikeSymmetricDifference<T, U>(
120
+ set: ReadonlySetLike<T>,
121
+ other: ReadonlySetLike<U>
122
+ ): Set<T | U> {
123
+ const otherRecord = getSetRecord(other)
124
+ const result = new Set<T | U>(keysOf(set))
125
+ // Remember keys already processed: duplicate keys must not toggle membership.
126
+ // Use the decoded result's equality, just like the native Set result.
127
+ const seen = new Set<U>()
128
+ for (const value of otherRecord.keys) {
129
+ if (seen.has(value)) continue
130
+ seen.add(value)
131
+ if (!result.delete(value)) result.add(value)
132
+ }
133
+ return result
134
+ }
@@ -1,4 +1,3 @@
1
- import { values } from "mobx"
2
1
  import { modelAction } from "../action/modelAction"
3
2
  import type { AnyModel } from "../model/BaseModel"
4
3
  import { Model } from "../model/Model"
@@ -48,7 +47,7 @@ export class ArraySet<V> extends arraySetBase implements Set<V> {
48
47
  delete(value: V): boolean {
49
48
  const items = this.items
50
49
 
51
- const index = items.indexOf(value)
50
+ const index = Number.isNaN(value) ? items.findIndex(Number.isNaN) : items.indexOf(value)
52
51
  if (index >= 0) {
53
52
  items.splice(index, 1)
54
53
  return true
@@ -75,15 +74,12 @@ export class ArraySet<V> extends arraySetBase implements Set<V> {
75
74
  return this.items.length
76
75
  }
77
76
 
78
- *keys(): ReturnType<Set<V>["keys"]> {
79
- // yes, values
80
- for (const value of values(this.items)) {
81
- yield value as V
82
- }
77
+ keys(): ReturnType<Set<V>["keys"]> {
78
+ return this.values()
83
79
  }
84
80
 
85
81
  *values(): ReturnType<Set<V>["values"]> {
86
- for (const value of values(this.items)) {
82
+ for (const value of this.items) {
87
83
  yield value as V
88
84
  }
89
85
  }
@@ -150,7 +146,7 @@ export interface ArraySet<V> {
150
146
  * @param [values] Optional initial values.
151
147
  */
152
148
  export function arraySet<V>(values?: ReadonlyArray<V> | null): ArraySet<V> {
153
- const initialArr: V[] = values ? values.slice() : []
149
+ const initialArr: V[] = values ? Array.from(new Set(values)) : []
154
150
 
155
151
  return new ArraySet({ items: initialArr })
156
152
  }
@@ -8,7 +8,7 @@ import { idProp } from "../modelShared/prop"
8
8
  import { typesRecord } from "../types/objectBased/typesRecord"
9
9
  import { tProp } from "../types/tProp"
10
10
  import { typesUnchecked } from "../types/utility/typesUnchecked"
11
- import { namespace } from "../utils"
11
+ import { namespace, setProtoProp } from "../utils"
12
12
  import { setIfDifferent } from "../utils/setIfDifferent"
13
13
 
14
14
  const objectMapBase = Model({
@@ -34,11 +34,11 @@ export class ObjectMap<V> extends objectMapBase implements Map<string, V> {
34
34
  clear(): void {
35
35
  const items = this.items
36
36
 
37
- const keys = Object.keys(items)
38
- const len = keys.length
37
+ // Object.keys is enough here: this is a write, so there is nothing to observe.
38
+ const itemKeys = Object.keys(items)
39
+ const len = itemKeys.length
39
40
  for (let i = 0; i < len; i++) {
40
- const k = keys[i]
41
- remove(items, k)
41
+ remove(items, itemKeys[i])
42
42
  }
43
43
  }
44
44
 
@@ -57,10 +57,10 @@ export class ObjectMap<V> extends objectMapBase implements Map<string, V> {
57
57
  // we cannot use the map implementation since we need to pass this as map
58
58
  const items = this.items
59
59
 
60
- const keys = Object.keys(items)
61
- const len = keys.length
60
+ const itemKeys = keys(items)
61
+ const len = itemKeys.length
62
62
  for (let i = 0; i < len; i++) {
63
- const k = keys[i]
63
+ const k = itemKeys[i] as string
64
64
  callbackfn.call(thisArg, items[k], k, this)
65
65
  }
66
66
  }
@@ -75,7 +75,7 @@ export class ObjectMap<V> extends objectMapBase implements Map<string, V> {
75
75
  }
76
76
 
77
77
  this.set(key, defaultValue)
78
- return defaultValue
78
+ return this.get(key) as V
79
79
  }
80
80
 
81
81
  getOrInsertComputed(key: string, callback: (key: string) => V): V {
@@ -85,7 +85,7 @@ export class ObjectMap<V> extends objectMapBase implements Map<string, V> {
85
85
 
86
86
  const value = callback(key)
87
87
  this.set(key, value)
88
- return value
88
+ return this.get(key) as V
89
89
  }
90
90
 
91
91
  has(key: string): boolean {
@@ -147,7 +147,11 @@ export function objectMap<V>(entries?: ReadonlyArray<readonly [string, V]> | nul
147
147
  const len = entries.length
148
148
  for (let i = 0; i < len; i++) {
149
149
  const entry = entries[i]
150
- initialObj[entry[0]] = entry[1]
150
+ if (entry[0] === "__proto__") {
151
+ setProtoProp(initialObj, entry[1])
152
+ } else {
153
+ initialObj[entry[0]] = entry[1]
154
+ }
151
155
  }
152
156
  }
153
157
 
@@ -20,8 +20,11 @@ import {
20
20
  assertIsObservableObject,
21
21
  failure,
22
22
  getMobxVersion,
23
+ hasOwnProp,
23
24
  inDevMode,
24
25
  isArray,
26
+ isEqualOrBothNaN,
27
+ setProtoProp,
25
28
  } from "../utils"
26
29
  import { setIfDifferent } from "../utils/setIfDifferent"
27
30
  import { tag } from "../utils/tag"
@@ -46,7 +49,7 @@ function addTs6MapExtras<K, V>(map: ObservableMap<K, V>): ObservableMap<K, V> &
46
49
  }
47
50
 
48
51
  this.set(key, defaultValue)
49
- return defaultValue
52
+ return this.get(key) as V
50
53
  }
51
54
  }
52
55
 
@@ -58,7 +61,7 @@ function addTs6MapExtras<K, V>(map: ObservableMap<K, V>): ObservableMap<K, V> &
58
61
 
59
62
  const value = callback(key)
60
63
  this.set(key, value)
61
- return value
64
+ return this.get(key) as V
62
65
  }
63
66
  }
64
67
 
@@ -75,7 +78,7 @@ const observableMapBackedByObservableObject = action(
75
78
 
76
79
  const map = transaction(() =>
77
80
  untracked(() => {
78
- const map = observable.map<string, T>()
81
+ const map = observable.map<string, T>(undefined, { deep: false })
79
82
 
80
83
  const keys = Object.keys(obj)
81
84
  for (let i = 0; i < keys.length; i++) {
@@ -143,11 +146,14 @@ const observableMapBackedByObservableObject = action(
143
146
  case "add":
144
147
  case "update": {
145
148
  setIfDifferent(obj, change.name, change.newValue)
149
+ if (!hasOwnProp(obj, change.name)) return null
150
+ change.newValue = (obj as Record<string, T>)[change.name]
146
151
  break
147
152
  }
148
153
 
149
154
  case "delete": {
150
155
  remove(obj, change.name)
156
+ if (hasOwnProp(obj, change.name)) return null
151
157
  break
152
158
  }
153
159
 
@@ -166,20 +172,20 @@ const observableMapBackedByObservableObject = action(
166
172
  }
167
173
  )
168
174
 
169
- const observableMapBackedByObservableArray = <T>(
170
- array: IObservableArray<[string, T]>
171
- ): ObservableMapWithDataObject<string, T, typeof array> => {
175
+ const observableMapBackedByObservableArray = <K, T>(
176
+ array: IObservableArray<[K, T]>
177
+ ): ObservableMapWithDataObject<K, T, typeof array> => {
172
178
  if (inDevMode) {
173
179
  if (!isObservableArray(array)) {
174
180
  throw failure("assertion failed: expected an observable array")
175
181
  }
176
182
  }
177
183
 
178
- const map: ObservableMap<string, T> = untracked(() => {
184
+ const map: ObservableMap<K, T> = untracked(() => {
179
185
  if (getMobxVersion() >= 6) {
180
- return observable.map<string, T>(array)
186
+ return observable.map<K, T>(array, { deep: false })
181
187
  } else {
182
- const map = observable.map<string, T>()
188
+ const map = observable.map<K, T>(undefined, { deep: false })
183
189
  runInAction(() => {
184
190
  array.forEach(([k, v]) => {
185
191
  map.set(k, v)
@@ -204,7 +210,7 @@ const observableMapBackedByObservableArray = <T>(
204
210
  // when the array changes the map changes
205
211
  observe(
206
212
  array,
207
- action((change: any /*IArrayDidChange<[string, T]>*/) => {
213
+ action((change: any /*IArrayDidChange<[K, T]>*/) => {
208
214
  if (mapAlreadyChanged) {
209
215
  return
210
216
  }
@@ -248,7 +254,7 @@ const observableMapBackedByObservableArray = <T>(
248
254
  // when the map changes also change the array
249
255
  intercept(
250
256
  map,
251
- action((change: IMapWillChange<string, T>) => {
257
+ action((change: IMapWillChange<K, T>) => {
252
258
  if (mapAlreadyChanged) {
253
259
  return null
254
260
  }
@@ -263,21 +269,23 @@ const observableMapBackedByObservableArray = <T>(
263
269
  switch (change.type) {
264
270
  case "update": {
265
271
  // replace the whole tuple to keep tuple immutability
266
- const i = array.findIndex((i) => i[0] === change.name)
272
+ const i = array.findIndex((entry) => isEqualOrBothNaN(entry[0], change.name))
267
273
  array[i] = [change.name, change.newValue!]
274
+ change.newValue = array[i][1]
268
275
  break
269
276
  }
270
277
 
271
278
  case "add": {
279
+ const oldLength = array.length
272
280
  array.push([change.name, change.newValue!])
281
+ if (array.length === oldLength) return null
282
+ change.newValue = array[array.length - 1][1]
273
283
  break
274
284
  }
275
285
 
276
286
  case "delete": {
277
- const i = array.findIndex((i) => i[0] === change.name)
278
- if (i >= 0) {
279
- array.splice(i, 1)
280
- }
287
+ const i = array.findIndex((entry) => isEqualOrBothNaN(entry[0], change.name))
288
+ if (i >= 0 && array.splice(i, 1).length === 0) return null
281
289
  break
282
290
  }
283
291
 
@@ -295,7 +303,7 @@ const observableMapBackedByObservableArray = <T>(
295
303
  return mapWithExtras as any
296
304
  }
297
305
 
298
- const asMapTag = tag((objOrArray: Record<string, any> | Array<[string, any]>) => {
306
+ const asMapTag = tag((objOrArray: Record<string, unknown> | Array<[unknown, unknown]>) => {
299
307
  if (isArray(objOrArray)) {
300
308
  assertIsObservableArray(objOrArray, "objOrArray")
301
309
  return observableMapBackedByObservableArray(objOrArray)
@@ -347,7 +355,11 @@ export function mapToObject<T>(map: Pick<Map<string, T>, "forEach">): Record<str
347
355
 
348
356
  const obj: Record<string, T> = {}
349
357
  map.forEach((v, k) => {
350
- obj[k] = v
358
+ if (k === "__proto__") {
359
+ setProtoProp(obj, v)
360
+ } else {
361
+ obj[k] = v
362
+ }
351
363
  })
352
364
 
353
365
  return obj
@@ -11,7 +11,14 @@ import {
11
11
  transaction,
12
12
  untracked,
13
13
  } from "mobx"
14
- import { assertIsObservableArray, assertIsSet, failure, getMobxVersion, inDevMode } from "../utils"
14
+ import {
15
+ assertIsObservableArray,
16
+ assertIsSet,
17
+ failure,
18
+ getMobxVersion,
19
+ inDevMode,
20
+ isEqualOrBothNaN,
21
+ } from "../utils"
15
22
  import { tag } from "../utils/tag"
16
23
 
17
24
  const observableSetBackedByObservableArray = <T>(
@@ -26,9 +33,9 @@ const observableSetBackedByObservableArray = <T>(
26
33
  const set = transaction(() =>
27
34
  untracked(() => {
28
35
  if (getMobxVersion() >= 6) {
29
- return observable.set(array)
36
+ return observable.set(array, { deep: false })
30
37
  } else {
31
- const set = observable.set()
38
+ const set = observable.set<T>(undefined, { deep: false })
32
39
  runInAction(() => {
33
40
  array.forEach((item) => {
34
41
  set.add(item)
@@ -98,28 +105,45 @@ const observableSetBackedByObservableArray = <T>(
98
105
  intercept(
99
106
  set,
100
107
  action((change: ISetWillChange<T>) => {
101
- if (setAlreadyChanged) {
102
- return null
103
- }
104
-
105
108
  if (arrayAlreadyChanged) {
106
109
  return change
107
110
  }
108
111
 
112
+ if (setAlreadyChanged) {
113
+ return null
114
+ }
115
+
109
116
  setAlreadyChanged = true
110
117
 
111
118
  try {
112
119
  switch (change.type) {
113
120
  case "add": {
114
- array.push(change.newValue)
121
+ if (!set.has(change.newValue)) {
122
+ const oldLength = array.length
123
+ array.push(change.newValue)
124
+ if (array.length === oldLength) return null
125
+ const storedValue = array[array.length - 1]
126
+ if (getMobxVersion() >= 6) {
127
+ change.newValue = storedValue
128
+ } else if (!isEqualOrBothNaN(storedValue, change.newValue)) {
129
+ // MobX 4/5 ignore replacement values returned by set interceptors.
130
+ arrayAlreadyChanged = true
131
+ try {
132
+ set.add(storedValue)
133
+ } finally {
134
+ arrayAlreadyChanged = false
135
+ }
136
+ return null
137
+ }
138
+ }
115
139
  break
116
140
  }
117
141
 
118
142
  case "delete": {
119
- const i = array.indexOf(change.oldValue)
120
- if (i >= 0) {
121
- array.splice(i, 1)
122
- }
143
+ const i = Number.isNaN(change.oldValue)
144
+ ? array.findIndex(Number.isNaN)
145
+ : array.indexOf(change.oldValue)
146
+ if (i >= 0 && array.splice(i, 1).length === 0) return null
123
147
  break
124
148
  }
125
149
 
@@ -1 +0,0 @@
1
- export declare function promiseGenerator(this: Promise<any>): any;