mobx-keystone 1.25.0 → 2.1.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 +145 -0
- package/dist/mobx-keystone.esm.js +3627 -1540
- package/dist/mobx-keystone.esm.mjs +3627 -1540
- package/dist/mobx-keystone.umd.js +3638 -1543
- package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
- package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +17 -10
- package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
- package/dist/types/deepChange/onDeepChange.d.ts +9 -0
- package/dist/types/parent/arrayPaths.d.ts +1 -0
- package/dist/types/parent/detach.d.ts +1 -1
- package/dist/types/parent/findChildren.d.ts +1 -1
- package/dist/types/parent/treeOrder.d.ts +1 -0
- package/dist/types/patch/applyPatches.d.ts +3 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
- package/dist/types/ref/modelRefIdIndex.d.ts +1 -0
- package/dist/types/ref/nodesById.d.ts +1 -0
- package/dist/types/ref/refIdIndex.d.ts +1 -0
- package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
- package/dist/types/snapshot/applySnapshot.d.ts +1 -0
- package/dist/types/snapshot/fromSnapshot.d.ts +0 -3
- package/dist/types/standardActions/arrayActions.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +0 -4
- package/dist/types/tweaker/changeRollback.d.ts +1 -0
- package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
- package/dist/types/types/TypeCheckError.d.ts +1 -0
- package/dist/types/types/utility/typesRefinement.d.ts +2 -2
- package/dist/types/utils/ModelPool.d.ts +7 -1
- package/dist/types/utils/chunks.d.ts +1 -0
- 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 +2 -2
- 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 +55 -25
- package/src/actionMiddlewares/undoMiddleware.ts +112 -59
- 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 +10 -13
- package/src/model/newModel.ts +21 -24
- package/src/model/utils.ts +2 -2
- package/src/modelShared/modelClassInitializer.ts +3 -2
- package/src/modelShared/modelDecorator.ts +39 -39
- package/src/modelShared/modelInfo.ts +1 -1
- package/src/modelShared/prop.ts +6 -9
- package/src/modelShared/sharedInternalModel.ts +16 -3
- package/src/parent/arrayPaths.ts +242 -0
- package/src/parent/coreObjectChildren.ts +300 -48
- package/src/parent/detach.ts +1 -1
- package/src/parent/findChildren.ts +4 -7
- package/src/parent/onChildAttachedTo.ts +157 -55
- package/src/parent/path.ts +15 -5
- package/src/parent/setParent.ts +34 -15
- package/src/parent/treeOrder.ts +53 -0
- package/src/parent/walkTree.ts +12 -101
- package/src/patch/applyPatches.ts +74 -34
- 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 +81 -29
- package/src/ref/modelRefIdIndex.ts +131 -0
- package/src/ref/nodesById.ts +60 -0
- package/src/ref/refIdIndex.ts +209 -0
- 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 +27 -19
- package/src/snapshot/clone.ts +1 -2
- package/src/snapshot/cloneTreeValue.ts +1 -2
- package/src/snapshot/fromModelSnapshot.ts +6 -2
- package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
- package/src/snapshot/fromSnapshot.ts +2 -6
- package/src/snapshot/internal.ts +89 -19
- package/src/snapshot/onSnapshot.ts +12 -1
- package/src/snapshot/reconcileModelSnapshot.ts +6 -6
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -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/changeRollback.ts +218 -0
- package/src/tweaker/core.ts +33 -22
- package/src/tweaker/treeNodeMetadata.ts +12 -2
- package/src/tweaker/tweak.ts +183 -19
- package/src/tweaker/tweakArray.ts +176 -33
- package/src/tweaker/tweakPlainObject.ts +117 -39
- package/src/tweaker/tweakedChangeListeners.ts +61 -0
- package/src/tweaker/typeChecking.ts +25 -15
- package/src/types/TypeCheckError.ts +17 -19
- package/src/types/arrayBased/typesArray.ts +10 -11
- package/src/types/createCachedTypeCheck.ts +541 -88
- 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 +8 -18
- 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 +78 -22
- package/src/utils/chainFns.ts +4 -0
- package/src/utils/chunks.ts +271 -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 +71 -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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { MobxKeystoneAggregateError } from "./index"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A failure kept aside while the rest of a delivery runs. It is boxed so that a thrown
|
|
5
|
+
* `undefined` / `null` can still be rethrown as is, and holds a `MobxKeystoneAggregateError`
|
|
6
|
+
* once more than one callback of the same delivery has failed.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export type DelayedError =
|
|
10
|
+
| { value: unknown; aggregateErrors?: undefined }
|
|
11
|
+
| { value: MobxKeystoneAggregateError; aggregateErrors: unknown[] }
|
|
12
|
+
| undefined
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Adds a failure to the ones already kept aside for the same delivery: a lone failure is kept
|
|
16
|
+
* as is, several are grouped in a `MobxKeystoneAggregateError`. Nested delivery is flattened,
|
|
17
|
+
* so `errors` is always a flat list of callback failures. The accumulator is updated in place.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function addDelayedError(
|
|
21
|
+
current: DelayedError,
|
|
22
|
+
error: unknown,
|
|
23
|
+
msg = "multiple callbacks failed"
|
|
24
|
+
): DelayedError {
|
|
25
|
+
if (!current) {
|
|
26
|
+
return { value: error }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let errors = current.aggregateErrors
|
|
30
|
+
if (!errors) {
|
|
31
|
+
// Start from a copy: an aggregate thrown by a callback is not ours to mutate.
|
|
32
|
+
errors = flattenDelayedError(current.value)
|
|
33
|
+
current = { value: new MobxKeystoneAggregateError(errors, msg), aggregateErrors: errors }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pushDelayedError(errors, error)
|
|
37
|
+
return current
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reports the failures kept aside for a delivery, if there were any.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function throwDelayedError(delayedError: DelayedError): void {
|
|
45
|
+
if (delayedError) {
|
|
46
|
+
throw delayedError.value
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Runs every callback, then reports the failures (if any) once delivery has finished.
|
|
52
|
+
* Iterates the collection directly, so appended array items are also visited.
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export function forEachWithDelayedThrow<T>(items: Iterable<T>, fn: (item: T) => void): void {
|
|
56
|
+
let delayedError: DelayedError
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
try {
|
|
59
|
+
fn(item)
|
|
60
|
+
} catch (error) {
|
|
61
|
+
delayedError = addDelayedError(delayedError, error)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throwDelayedError(delayedError)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @internal */
|
|
68
|
+
export function flattenDelayedError(error: unknown): unknown[] {
|
|
69
|
+
return error instanceof MobxKeystoneAggregateError ? [...error.errors] : [error]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** @internal */
|
|
73
|
+
export function pushDelayedError(errors: unknown[], error: unknown): void {
|
|
74
|
+
if (error instanceof MobxKeystoneAggregateError) {
|
|
75
|
+
const nestedErrors = error.errors
|
|
76
|
+
const length = nestedErrors.length
|
|
77
|
+
for (let i = 0; i < length; i++) {
|
|
78
|
+
errors.push(nestedErrors[i])
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
errors.push(error)
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -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
|
|
257
|
+
typeof process !== "undefined" &&
|
|
258
|
+
process?.env?.NODE_ENV !== undefined &&
|
|
259
|
+
process.env.NODE_ENV !== "production"
|
|
228
260
|
|
|
229
261
|
/**
|
|
230
262
|
* @internal
|
|
@@ -526,6 +558,43 @@ function isStandardDecoratorContext(value: unknown): boolean {
|
|
|
526
558
|
)
|
|
527
559
|
}
|
|
528
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Options for the observable objects of tree nodes. Their values are never
|
|
563
|
+
* functions nor getters, so the shared `observable.ref` annotation (MobX 7
|
|
564
|
+
* `observableRef`) is equivalent to `deep: false` alone, which MobX 6+ would
|
|
565
|
+
* allocate an annotation for on every object.
|
|
566
|
+
*
|
|
567
|
+
* @internal
|
|
568
|
+
*/
|
|
569
|
+
export const nodeObservableOptions = {
|
|
570
|
+
deep: false,
|
|
571
|
+
defaultDecorator:
|
|
572
|
+
// (computed so bundlers do not warn about the export missing in MobX < 7)
|
|
573
|
+
(mobx as any)[["observable", "Ref"].join("")] ?? (mobx.observable as any).ref,
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// the key MobX 6 stores legacy decorator annotations under (null if unknown)
|
|
577
|
+
let storedMobxAnnotationsKey: symbol | null | undefined
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Whether an instance has legacy decorator annotations for MobX 6
|
|
581
|
+
* `makeObservable` to apply. When their key cannot be found it answers true,
|
|
582
|
+
* so `makeObservable` is still called.
|
|
583
|
+
*
|
|
584
|
+
* @internal
|
|
585
|
+
*/
|
|
586
|
+
export function hasStoredMobxAnnotations(instance: object): boolean {
|
|
587
|
+
if (storedMobxAnnotationsKey === undefined) {
|
|
588
|
+
// a legacy decorator call stores its annotation under that key
|
|
589
|
+
const probe = {}
|
|
590
|
+
;(mobx.observable as any)(probe, "probe")
|
|
591
|
+
storedMobxAnnotationsKey = Object.getOwnPropertySymbols(probe)[0] ?? null
|
|
592
|
+
}
|
|
593
|
+
return (
|
|
594
|
+
storedMobxAnnotationsKey === null || (instance as any)[storedMobxAnnotationsKey] !== undefined
|
|
595
|
+
)
|
|
596
|
+
}
|
|
597
|
+
|
|
529
598
|
/**
|
|
530
599
|
* Calls `makeObservable` across MobX versions. MobX 7 requires an annotations
|
|
531
600
|
* object, while MobX 6 and older versions use decorator metadata when no
|
|
@@ -545,7 +614,7 @@ export function makeObservableCompat(target: object, annotations?: object): void
|
|
|
545
614
|
* @internal
|
|
546
615
|
*/
|
|
547
616
|
export function propNameToSetterName(propName: string): string {
|
|
548
|
-
return `set${propName
|
|
617
|
+
return `set${propName.charAt(0).toUpperCase()}${propName.slice(1)}`
|
|
549
618
|
}
|
|
550
619
|
|
|
551
620
|
let cachedMobxVersion: number | undefined
|
package/src/utils/mapUtils.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
type AnyMap<V = any> = Map<any, V> | WeakMap<any, V>
|
|
2
2
|
|
|
3
|
-
export function getOrCreate<K, V
|
|
4
|
-
export function getOrCreate<K extends object, 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]
|
|
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]
|
|
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
|
+
}
|
package/src/wrappers/ArraySet.ts
CHANGED
|
@@ -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
|
-
|
|
79
|
-
|
|
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
|
|
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 ?
|
|
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
|
-
|
|
38
|
-
const
|
|
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
|
-
|
|
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
|
|
61
|
-
const len =
|
|
60
|
+
const itemKeys = keys(items)
|
|
61
|
+
const len = itemKeys.length
|
|
62
62
|
for (let i = 0; i < len; i++) {
|
|
63
|
-
const k =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
package/src/wrappers/asMap.ts
CHANGED
|
@@ -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
|
|
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
|
|
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<[
|
|
171
|
-
): ObservableMapWithDataObject<
|
|
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<
|
|
184
|
+
const map: ObservableMap<K, T> = untracked(() => {
|
|
179
185
|
if (getMobxVersion() >= 6) {
|
|
180
|
-
return observable.map<
|
|
186
|
+
return observable.map<K, T>(array, { deep: false })
|
|
181
187
|
} else {
|
|
182
|
-
const map = observable.map<
|
|
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<[
|
|
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<
|
|
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((
|
|
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((
|
|
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,
|
|
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
|
-
|
|
358
|
+
if (k === "__proto__") {
|
|
359
|
+
setProtoProp(obj, v)
|
|
360
|
+
} else {
|
|
361
|
+
obj[k] = v
|
|
362
|
+
}
|
|
351
363
|
})
|
|
352
364
|
|
|
353
365
|
return obj
|