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.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -21
- package/README.md +1 -1
- package/dist/mobx-keystone.esm.js +7198 -6242
- package/dist/mobx-keystone.esm.mjs +7198 -6242
- package/dist/mobx-keystone.umd.js +7217 -6243
- package/dist/types/action/runUnprotected.d.ts +8 -4
- package/dist/types/model/Model.d.ts +5 -0
- package/dist/types/modelShared/modelDecorator.d.ts +1 -1
- package/dist/types/modelShared/prop.d.ts +8 -4
- package/dist/types/snapshot/modelInitialData.d.ts +1 -0
- package/dist/types/tweaker/treeNodeMetadata.d.ts +1 -0
- package/dist/types/types/createCachedTypeCheck.d.ts +1 -0
- package/dist/types/utils/index.d.ts +24 -0
- package/package.json +15 -11
- package/src/action/middleware.ts +5 -18
- package/src/action/mutationBatch.ts +72 -0
- package/src/action/runUnprotected.ts +21 -8
- package/src/action/wrapInAction.ts +45 -15
- package/src/actionMiddlewares/undoMiddleware.ts +19 -13
- package/src/dataModel/BaseDataModel.ts +4 -8
- package/src/deepChange/onDeepChange.ts +246 -16
- package/src/model/BaseModel.ts +4 -12
- package/src/model/Model.ts +5 -0
- package/src/model/newModel.ts +68 -36
- package/src/modelShared/modelDecorator.ts +64 -53
- package/src/modelShared/prop.ts +105 -63
- package/src/modelShared/sharedInternalModel.ts +91 -60
- package/src/parent/core.ts +16 -10
- package/src/parent/coreObjectChildren.ts +219 -21
- package/src/parent/path.ts +6 -5
- package/src/parent/setParent.ts +5 -5
- package/src/patch/emitPatch.ts +102 -20
- package/src/ref/Ref.ts +4 -5
- package/src/ref/core.ts +2 -2
- package/src/snapshot/fromArraySnapshot.ts +11 -2
- package/src/snapshot/fromModelSnapshot.ts +10 -3
- package/src/snapshot/fromPlainObjectSnapshot.ts +18 -7
- package/src/snapshot/fromSnapshot.ts +31 -9
- package/src/snapshot/getSnapshot.ts +8 -3
- package/src/snapshot/internal.ts +243 -82
- package/src/snapshot/modelInitialData.ts +41 -0
- package/src/snapshot/reconcileArraySnapshot.ts +9 -3
- package/src/snapshot/reconcileModelSnapshot.ts +97 -17
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +9 -3
- package/src/treeUtils/draft.ts +7 -8
- package/src/tweaker/core.ts +30 -5
- package/src/tweaker/treeNodeMetadata.ts +38 -0
- package/src/tweaker/tweak.ts +15 -4
- package/src/tweaker/tweakArray.ts +159 -121
- package/src/tweaker/tweakFrozen.ts +2 -2
- package/src/tweaker/tweakModel.ts +2 -2
- package/src/tweaker/tweakPlainObject.ts +117 -124
- package/src/tweaker/typeChecking.ts +10 -1
- package/src/types/TypeChecker.ts +261 -15
- package/src/types/arrayBased/typesArray.ts +38 -40
- package/src/types/arrayBased/typesTuple.ts +51 -34
- package/src/types/createCachedTypeCheck.ts +181 -0
- package/src/types/objectBased/typesArraySet.ts +42 -32
- package/src/types/objectBased/typesDataModelData.ts +18 -11
- package/src/types/objectBased/typesModel.ts +1 -1
- package/src/types/objectBased/typesObject.ts +57 -28
- package/src/types/objectBased/typesObjectMap.ts +51 -40
- package/src/types/objectBased/typesRecord.ts +59 -39
- package/src/types/objectBased/typesRef.ts +1 -1
- package/src/types/primitiveBased/typesPrimitive.ts +9 -9
- package/src/types/tProp.ts +80 -44
- package/src/types/utility/typesOr.ts +35 -24
- package/src/types/utility/typesRefinement.ts +21 -8
- package/src/types/utility/typesSkipCheck.ts +21 -8
- package/src/types/utility/typesTag.ts +21 -9
- package/src/types/utility/typesUnchecked.ts +2 -3
- package/src/utils/ModelPool.ts +34 -19
- package/src/utils/errorDiagnostics.ts +60 -18
- package/src/utils/index.ts +142 -9
- package/src/types/createPerEntryCachedCheck.ts +0 -166
- /package/dist/types/{types/createPerEntryCachedCheck.d.ts → action/mutationBatch.d.ts} +0 -0
package/src/utils/ModelPool.ts
CHANGED
|
@@ -4,28 +4,27 @@ import { modelIdKey, modelTypeKey } from "../model/metadata"
|
|
|
4
4
|
import { getSnapshotModelType, isModel } from "../model/utils"
|
|
5
5
|
import type { ModelClass } from "../modelShared/BaseModelShared"
|
|
6
6
|
import { getModelInfoForName } from "../modelShared/modelInfo"
|
|
7
|
-
import {
|
|
7
|
+
import { dataToModelNode } from "../parent/core"
|
|
8
8
|
import {
|
|
9
9
|
getDeepObjectChildren,
|
|
10
10
|
registerDeepObjectChildrenExtension,
|
|
11
11
|
} from "../parent/coreObjectChildren"
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
13
|
+
type ModelsById = Map<string, AnyModel>
|
|
14
|
+
type ModelsByTypeAndId = Map<string, ModelsById>
|
|
16
15
|
|
|
17
16
|
export class ModelPool {
|
|
18
|
-
private pool: ReadonlyMap<string, AnyModel
|
|
17
|
+
private pool: ReadonlyMap<string, ReadonlyMap<string, AnyModel>>
|
|
19
18
|
|
|
20
19
|
constructor(root: object) {
|
|
21
20
|
// make sure we don't use the sub-data $ object
|
|
22
|
-
root =
|
|
21
|
+
root = dataToModelNode(root)
|
|
23
22
|
|
|
24
23
|
this.pool = getDeepChildrenModels(getDeepObjectChildren(root))
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
findModelByTypeAndId(modelType: string, modelId: string | undefined): AnyModel | undefined {
|
|
28
|
-
return modelId ? this.pool.get(
|
|
27
|
+
return modelId ? this.pool.get(modelType)?.get(modelId) : undefined
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
findModelForSnapshot(sn: any): AnyModel | undefined {
|
|
@@ -43,17 +42,33 @@ export class ModelPool {
|
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
type GetDeepChildrenModels = (data: ReturnType<typeof getDeepObjectChildren>) => ModelsByTypeAndId
|
|
46
|
+
|
|
47
|
+
let getDeepChildrenModels: GetDeepChildrenModels = (data) => {
|
|
48
|
+
// Register on first use to keep ModelPool out of the coreObjectChildren
|
|
49
|
+
// module-initialization cycle. The extension getter backfills its index from
|
|
50
|
+
// data.deep, so registering after a deep-children set already exists is safe.
|
|
51
|
+
const getRegisteredDeepChildrenModels = registerDeepObjectChildrenExtension<ModelsByTypeAndId>({
|
|
52
|
+
initData() {
|
|
53
|
+
return new Map()
|
|
54
|
+
},
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
addNode(node, extensionData) {
|
|
57
|
+
if (isModel(node)) {
|
|
58
|
+
const id = node[modelIdKey]
|
|
59
|
+
if (id) {
|
|
60
|
+
const modelType = node[modelTypeKey]
|
|
61
|
+
let modelsById = extensionData.get(modelType)
|
|
62
|
+
if (!modelsById) {
|
|
63
|
+
modelsById = new Map()
|
|
64
|
+
extensionData.set(modelType, modelsById)
|
|
65
|
+
}
|
|
66
|
+
modelsById.set(id, node)
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
getDeepChildrenModels = getRegisteredDeepChildrenModels
|
|
73
|
+
return getRegisteredDeepChildrenModels(data)
|
|
74
|
+
}
|
|
@@ -1,13 +1,59 @@
|
|
|
1
1
|
import type { Path, PathElement, WritablePath } from "../parent/pathTypes"
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export class ErrorDiagnosticsContext {
|
|
7
|
+
private readonly path: WritablePath = []
|
|
8
|
+
private readonly modelTrailTypes: string[] = []
|
|
9
|
+
private readonly modelTrailIds: (string | undefined)[] = []
|
|
10
|
+
|
|
11
|
+
pushPath(segment: PathElement): void {
|
|
12
|
+
this.path.push(segment)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
popPath(): void {
|
|
16
|
+
this.path.pop()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getPathLength(): number {
|
|
20
|
+
return this.path.length
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
truncatePath(length: number): void {
|
|
24
|
+
this.path.length = length
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pushModelTrail(modelType: string, modelId: string | undefined): void {
|
|
28
|
+
this.modelTrailTypes.push(modelType)
|
|
29
|
+
this.modelTrailIds.push(modelId)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
popModelTrail(): void {
|
|
33
|
+
this.modelTrailTypes.pop()
|
|
34
|
+
this.modelTrailIds.pop()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getPathSnapshot(): Path {
|
|
38
|
+
return [...this.path]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getModelTrailSnapshot(): readonly string[] | undefined {
|
|
42
|
+
if (this.modelTrailTypes.length <= 0) {
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
return this.modelTrailTypes.map((modelType, index) =>
|
|
46
|
+
formatErrorModelTrailEntry(modelType, this.modelTrailIds[index])
|
|
47
|
+
)
|
|
48
|
+
}
|
|
6
49
|
}
|
|
7
50
|
|
|
8
51
|
const errorDiagnosticsContextStack: ErrorDiagnosticsContext[] = []
|
|
9
52
|
|
|
10
|
-
|
|
53
|
+
/**
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export function getCurrentErrorDiagnosticsContext(): ErrorDiagnosticsContext | undefined {
|
|
11
57
|
return errorDiagnosticsContextStack.length > 0
|
|
12
58
|
? errorDiagnosticsContextStack[errorDiagnosticsContextStack.length - 1]
|
|
13
59
|
: undefined
|
|
@@ -22,10 +68,7 @@ export function runWithErrorDiagnosticsContext<T>(fn: () => T): T {
|
|
|
22
68
|
return fn()
|
|
23
69
|
}
|
|
24
70
|
|
|
25
|
-
const ctx
|
|
26
|
-
path: [],
|
|
27
|
-
modelTrail: [],
|
|
28
|
-
}
|
|
71
|
+
const ctx = new ErrorDiagnosticsContext()
|
|
29
72
|
errorDiagnosticsContextStack.push(ctx)
|
|
30
73
|
try {
|
|
31
74
|
return fn()
|
|
@@ -43,11 +86,11 @@ export function withErrorPathSegment<T>(segment: PathElement, fn: () => T): T {
|
|
|
43
86
|
return fn()
|
|
44
87
|
}
|
|
45
88
|
|
|
46
|
-
ctx.
|
|
89
|
+
ctx.pushPath(segment)
|
|
47
90
|
try {
|
|
48
91
|
return fn()
|
|
49
92
|
} finally {
|
|
50
|
-
ctx.
|
|
93
|
+
ctx.popPath()
|
|
51
94
|
}
|
|
52
95
|
}
|
|
53
96
|
|
|
@@ -64,16 +107,16 @@ export function withErrorPathSegments<T>(segments: readonly PathElement[], fn: (
|
|
|
64
107
|
return fn()
|
|
65
108
|
}
|
|
66
109
|
|
|
67
|
-
const initialLen = ctx.
|
|
110
|
+
const initialLen = ctx.getPathLength()
|
|
68
111
|
const segmentsLen = segments.length
|
|
69
112
|
for (let i = 0; i < segmentsLen; i++) {
|
|
70
|
-
ctx.
|
|
113
|
+
ctx.pushPath(segments[i]!)
|
|
71
114
|
}
|
|
72
115
|
|
|
73
116
|
try {
|
|
74
117
|
return fn()
|
|
75
118
|
} finally {
|
|
76
|
-
ctx.
|
|
119
|
+
ctx.truncatePath(initialLen)
|
|
77
120
|
}
|
|
78
121
|
}
|
|
79
122
|
|
|
@@ -112,12 +155,11 @@ export function withErrorModelTrailEntry<T>(
|
|
|
112
155
|
return fn()
|
|
113
156
|
}
|
|
114
157
|
|
|
115
|
-
|
|
116
|
-
ctx.modelTrail.push(entry)
|
|
158
|
+
ctx.pushModelTrail(modelType, modelId)
|
|
117
159
|
try {
|
|
118
160
|
return fn()
|
|
119
161
|
} finally {
|
|
120
|
-
ctx.
|
|
162
|
+
ctx.popModelTrail()
|
|
121
163
|
}
|
|
122
164
|
}
|
|
123
165
|
|
|
@@ -126,7 +168,7 @@ export function withErrorModelTrailEntry<T>(
|
|
|
126
168
|
*/
|
|
127
169
|
export function getErrorPathSnapshot(): Path | undefined {
|
|
128
170
|
const ctx = getCurrentErrorDiagnosticsContext()
|
|
129
|
-
return ctx
|
|
171
|
+
return ctx?.getPathSnapshot()
|
|
130
172
|
}
|
|
131
173
|
|
|
132
174
|
/**
|
|
@@ -134,7 +176,7 @@ export function getErrorPathSnapshot(): Path | undefined {
|
|
|
134
176
|
*/
|
|
135
177
|
export function getErrorModelTrailSnapshot(): readonly string[] | undefined {
|
|
136
178
|
const ctx = getCurrentErrorDiagnosticsContext()
|
|
137
|
-
return ctx
|
|
179
|
+
return ctx?.getModelTrailSnapshot()
|
|
138
180
|
}
|
|
139
181
|
|
|
140
182
|
/**
|
package/src/utils/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as mobx from "mobx"
|
|
2
2
|
import {
|
|
3
|
+
type IComputedValue,
|
|
3
4
|
type IObservableArray,
|
|
4
5
|
isObservableArray,
|
|
5
6
|
isObservableMap,
|
|
@@ -109,12 +110,7 @@ export let hasOwnProp: (object: object, propName: PropertyKey) => boolean = (
|
|
|
109
110
|
/**
|
|
110
111
|
* @internal
|
|
111
112
|
*/
|
|
112
|
-
export function
|
|
113
|
-
if (propName !== "__proto__") {
|
|
114
|
-
target[propName] = value
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
113
|
+
export function setProtoProp(target: object, value: unknown): void {
|
|
118
114
|
Object.defineProperty(target, "__proto__", {
|
|
119
115
|
enumerable: true,
|
|
120
116
|
writable: true,
|
|
@@ -136,7 +132,12 @@ export function copyOwnEnumerableProps<TTarget extends Record<string, any>>(
|
|
|
136
132
|
for (let i = 0; i < len; i++) {
|
|
137
133
|
const key = keys[i]
|
|
138
134
|
const value = source[key]
|
|
139
|
-
|
|
135
|
+
const mappedValue = mapValue ? mapValue(value, key) : value
|
|
136
|
+
if (key === "__proto__") {
|
|
137
|
+
setProtoProp(target, mappedValue)
|
|
138
|
+
} else {
|
|
139
|
+
;(target as Record<PropertyKey, unknown>)[key] = mappedValue
|
|
140
|
+
}
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
return target
|
|
@@ -419,6 +420,127 @@ export const mobx6 = {
|
|
|
419
420
|
],
|
|
420
421
|
}
|
|
421
422
|
|
|
423
|
+
/**
|
|
424
|
+
* MobX method action decorator compatible with both legacy decorators and
|
|
425
|
+
* MobX 7's standard decorator API.
|
|
426
|
+
*
|
|
427
|
+
* This is a decorator compatibility helper, not a replacement for other
|
|
428
|
+
* `action` overloads such as `action(fn)`. Field decorators are not supported.
|
|
429
|
+
*
|
|
430
|
+
* @hidden
|
|
431
|
+
*/
|
|
432
|
+
export function mobxAction<This, Args extends unknown[], Return>(
|
|
433
|
+
value: (this: This, ...args: Args) => Return,
|
|
434
|
+
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
|
|
435
|
+
): (this: This, ...args: Args) => Return
|
|
436
|
+
export function mobxAction<This, Args extends unknown[], Return>(
|
|
437
|
+
target: object,
|
|
438
|
+
propertyKey: string | symbol,
|
|
439
|
+
descriptor: TypedPropertyDescriptor<(this: This, ...args: Args) => Return>
|
|
440
|
+
): TypedPropertyDescriptor<(this: This, ...args: Args) => Return> | void
|
|
441
|
+
export function mobxAction(...args: any[]): any {
|
|
442
|
+
if (isStandardDecoratorContext(args[1])) {
|
|
443
|
+
return (mobx.action as any)(args[0], args[1])
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (getMobxVersion() < 7) {
|
|
447
|
+
return (mobx.action as any)(...args)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const descriptor = args[2] as PropertyDescriptor | undefined
|
|
451
|
+
if (!descriptor?.value) {
|
|
452
|
+
return descriptor
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (mobx.isAction(descriptor.value)) {
|
|
456
|
+
return descriptor
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
...descriptor,
|
|
461
|
+
value: mobx.action(String(args[1]), descriptor.value),
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* MobX getter computed decorator compatible with both legacy decorators and
|
|
467
|
+
* MobX 7's standard decorator API.
|
|
468
|
+
*
|
|
469
|
+
* This is a decorator compatibility helper, not a replacement for other
|
|
470
|
+
* `computed` overloads. Field decorators and decorator options are not supported.
|
|
471
|
+
* Under MobX 7's legacy decorator transform the getter is backed by a computed
|
|
472
|
+
* value, but is not registered as a computed property for MobX introspection.
|
|
473
|
+
*
|
|
474
|
+
* @hidden
|
|
475
|
+
*/
|
|
476
|
+
export function mobxComputed<This, Value>(
|
|
477
|
+
value: (this: This) => Value,
|
|
478
|
+
context: ClassGetterDecoratorContext<This, Value>
|
|
479
|
+
): ((this: This) => Value) | void
|
|
480
|
+
export function mobxComputed<Value>(
|
|
481
|
+
target: object,
|
|
482
|
+
propertyKey: string | symbol,
|
|
483
|
+
descriptor: TypedPropertyDescriptor<Value>
|
|
484
|
+
): TypedPropertyDescriptor<Value> | void
|
|
485
|
+
export function mobxComputed(...args: any[]): any {
|
|
486
|
+
if (isStandardDecoratorContext(args[1])) {
|
|
487
|
+
return (mobx.computed as any)(args[0], args[1])
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (getMobxVersion() < 7) {
|
|
491
|
+
return (mobx.computed as any)(...args)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const descriptor = args[2] as PropertyDescriptor | undefined
|
|
495
|
+
if (!descriptor?.get) {
|
|
496
|
+
return descriptor
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const getter = descriptor.get
|
|
500
|
+
const computedValues = new WeakMap<object, IComputedValue<unknown>>()
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
...descriptor,
|
|
504
|
+
get(this: object) {
|
|
505
|
+
let computedValue = computedValues.get(this)
|
|
506
|
+
if (!computedValue) {
|
|
507
|
+
const newComputedValue = mobx.computed(() => getter.call(this), {
|
|
508
|
+
name: String(args[1]),
|
|
509
|
+
})
|
|
510
|
+
computedValues.set(this, newComputedValue)
|
|
511
|
+
computedValue = newComputedValue
|
|
512
|
+
}
|
|
513
|
+
return computedValue.get()
|
|
514
|
+
},
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Mirrors MobX's standard-decorator call detection. Legacy decorator calls use
|
|
519
|
+
// a string or symbol property key as their second argument, so checking `kind`
|
|
520
|
+
// distinguishes the call shapes without claiming to validate the full context.
|
|
521
|
+
function isStandardDecoratorContext(value: unknown): boolean {
|
|
522
|
+
return (
|
|
523
|
+
typeof value === "object" &&
|
|
524
|
+
value !== null &&
|
|
525
|
+
typeof (value as { kind?: unknown }).kind === "string"
|
|
526
|
+
)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Calls `makeObservable` across MobX versions. MobX 7 requires an annotations
|
|
531
|
+
* object, while MobX 6 and older versions use decorator metadata when no
|
|
532
|
+
* annotations are passed.
|
|
533
|
+
*
|
|
534
|
+
* @internal
|
|
535
|
+
*/
|
|
536
|
+
export function makeObservableCompat(target: object, annotations?: object): void {
|
|
537
|
+
if (getMobxVersion() >= 7) {
|
|
538
|
+
mobx6.makeObservable(target, annotations ?? {})
|
|
539
|
+
} else {
|
|
540
|
+
mobx6.makeObservable(target)
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
422
544
|
/**
|
|
423
545
|
* @internal
|
|
424
546
|
*/
|
|
@@ -426,15 +548,26 @@ export function propNameToSetterName(propName: string): string {
|
|
|
426
548
|
return `set${propName[0].toUpperCase()}${propName.slice(1)}`
|
|
427
549
|
}
|
|
428
550
|
|
|
551
|
+
let cachedMobxVersion: number | undefined
|
|
552
|
+
|
|
429
553
|
/**
|
|
430
554
|
* @internal
|
|
431
555
|
*/
|
|
432
556
|
export function getMobxVersion(): number {
|
|
557
|
+
if (cachedMobxVersion !== undefined) {
|
|
558
|
+
return cachedMobxVersion
|
|
559
|
+
}
|
|
560
|
+
|
|
433
561
|
if (mobx6.makeObservable!) {
|
|
434
|
-
|
|
562
|
+
const version = (mobx as any)._getGlobalState?.().version
|
|
563
|
+
cachedMobxVersion = typeof version === "number" && version >= 7 ? version : 6
|
|
564
|
+
} else if ("extendShallowObservable" in mobx) {
|
|
565
|
+
cachedMobxVersion = 4
|
|
435
566
|
} else {
|
|
436
|
-
|
|
567
|
+
cachedMobxVersion = 5
|
|
437
568
|
}
|
|
569
|
+
|
|
570
|
+
return cachedMobxVersion
|
|
438
571
|
}
|
|
439
572
|
|
|
440
573
|
/**
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
computed,
|
|
3
|
-
type IArrayDidChange,
|
|
4
|
-
type IComputedValue,
|
|
5
|
-
type IObjectDidChange,
|
|
6
|
-
observe,
|
|
7
|
-
} from "mobx"
|
|
8
|
-
import type { Path } from "../parent/pathTypes"
|
|
9
|
-
import { isTweakedObject } from "../tweaker/core"
|
|
10
|
-
import type { TypeCheckError } from "./TypeCheckError"
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @internal
|
|
14
|
-
*
|
|
15
|
-
* Per-entry cache map type used by `createPerEntryCachedCheck`.
|
|
16
|
-
*/
|
|
17
|
-
export type PerEntryCache = Map<PropertyKey, IComputedValue<TypeCheckError | null>>
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @internal
|
|
21
|
-
*
|
|
22
|
-
* Callback that sets up reactive pruning of stale per-entry cache entries.
|
|
23
|
-
* Called once per observable value when the aggregation is first created.
|
|
24
|
-
* Should install an observe listener that removes entries from the cache
|
|
25
|
-
* when the collection structure changes (e.g. key removal, array shrink).
|
|
26
|
-
*/
|
|
27
|
-
export type CachePruningSetup = (value: any, capturedEntries: PerEntryCache) => void
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* @internal
|
|
31
|
-
*
|
|
32
|
-
* Pruning strategy for record / plain-object types.
|
|
33
|
-
* Removes a per-entry cached computed when an object key is removed.
|
|
34
|
-
*/
|
|
35
|
-
export const recordCachePruning: CachePruningSetup = (value, capturedEntries) => {
|
|
36
|
-
observe(value, (change: IObjectDidChange) => {
|
|
37
|
-
if (change.type === "remove") {
|
|
38
|
-
capturedEntries.delete(change.name)
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @internal
|
|
45
|
-
*
|
|
46
|
-
* Pruning strategy for array types.
|
|
47
|
-
* After a splice that shrinks the array, removes per-entry cached computeds
|
|
48
|
-
* for indices that no longer exist (i.e. index >= newLength).
|
|
49
|
-
*/
|
|
50
|
-
export const arrayCachePruning: CachePruningSetup = (value, capturedEntries) => {
|
|
51
|
-
observe(value, (change: IArrayDidChange) => {
|
|
52
|
-
if (change.type === "splice") {
|
|
53
|
-
const newLength = (value as unknown[]).length
|
|
54
|
-
if (capturedEntries.size > newLength) {
|
|
55
|
-
for (const key of capturedEntries.keys()) {
|
|
56
|
-
if ((key as number) >= newLength) {
|
|
57
|
-
capturedEntries.delete(key)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @internal
|
|
67
|
-
*
|
|
68
|
-
* Debug registry mapping observable values to their per-entry cache Maps.
|
|
69
|
-
* Used by tests to verify that stale entries are actually pruned.
|
|
70
|
-
*/
|
|
71
|
-
const __debugPerEntryCaches = new WeakMap<object, PerEntryCache>()
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* @internal
|
|
75
|
-
*
|
|
76
|
-
* Returns the number of entries in the per-entry cache for the given observable value,
|
|
77
|
-
* or `undefined` if the value has no cached entries. Used by tests to verify pruning.
|
|
78
|
-
*/
|
|
79
|
-
export function __getPerEntryCacheSize(value: object): number | undefined {
|
|
80
|
-
return __debugPerEntryCaches.get(value)?.size
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @internal
|
|
85
|
-
*
|
|
86
|
-
* Creates a check function that uses per-entry MobX computeds + an aggregation computed
|
|
87
|
-
* for fine-grained reactive type checking.
|
|
88
|
-
*
|
|
89
|
-
* For tweaked (observable) values:
|
|
90
|
-
* 1. Per-entry computeds — each reads only one entry's observable, so changing entry A
|
|
91
|
-
* only invalidates entry A's computed.
|
|
92
|
-
* 2. Aggregation computed — iterates per-entry computeds. Thanks to MobX's change propagation,
|
|
93
|
-
* if a per-entry computed re-evaluates but returns the same result (e.g. null → null),
|
|
94
|
-
* the aggregation body is skipped entirely.
|
|
95
|
-
* 3. Stale entries are pruned reactively via an optional `setupCachePruning` callback
|
|
96
|
-
* (e.g. MobX `observe` listener) that fires on structural mutations, preventing
|
|
97
|
-
* memory leaks for dynamic collections.
|
|
98
|
-
*
|
|
99
|
-
* For non-tweaked values (plain snapshots), falls back to imperative checking.
|
|
100
|
-
*
|
|
101
|
-
* @param iterateEntries Called both inside the aggregation computed (cached path) and
|
|
102
|
-
* for the imperative fallback. Receives a `checkEntry(key)` callback that either
|
|
103
|
-
* returns a cached computed result or performs a direct check, depending on the path.
|
|
104
|
-
* Should call `checkEntry(key)` for each entry and return the first error, or null.
|
|
105
|
-
* @param checkEntry Checks a single entry. For tweaked values, the utility wraps this
|
|
106
|
-
* in a non-keepAlive computed automatically. For non-tweaked values, called directly.
|
|
107
|
-
* @param setupCachePruning Optional callback to install reactive pruning of stale entries.
|
|
108
|
-
* Called once per observable value. Use `recordCachePruning` for records/objects,
|
|
109
|
-
* `arrayCachePruning` for arrays, or omit for fixed-schema types (objects, tuples).
|
|
110
|
-
*/
|
|
111
|
-
export function createPerEntryCachedCheck<K extends PropertyKey>(
|
|
112
|
-
iterateEntries: (
|
|
113
|
-
value: any,
|
|
114
|
-
checkEntry: (entryKey: K) => TypeCheckError | null
|
|
115
|
-
) => TypeCheckError | null,
|
|
116
|
-
checkEntry: (value: any, entryKey: K, path: Path, typeCheckedValue: any) => TypeCheckError | null,
|
|
117
|
-
setupCachePruning?: CachePruningSetup
|
|
118
|
-
): (value: any, path: Path, typeCheckedValue: any) => TypeCheckError | null {
|
|
119
|
-
const perEntryCache = new WeakMap<object, PerEntryCache>()
|
|
120
|
-
const aggregationCache = new WeakMap<object, IComputedValue<TypeCheckError | null>>()
|
|
121
|
-
|
|
122
|
-
// Mutable call context updated before each `.get()` so computed closures
|
|
123
|
-
// always use the latest path/typeCheckedValue. Safe because MobX computed
|
|
124
|
-
// evaluation is synchronous and single-threaded.
|
|
125
|
-
let currentPath: Path
|
|
126
|
-
let currentTypeCheckedValue: any
|
|
127
|
-
|
|
128
|
-
return (value: any, path: Path, typeCheckedValue: any): TypeCheckError | null => {
|
|
129
|
-
if (!isTweakedObject(value, true)) {
|
|
130
|
-
return iterateEntries(value, (key) => checkEntry(value, key, path, typeCheckedValue))
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Update mutable scope before evaluation.
|
|
134
|
-
currentPath = path
|
|
135
|
-
currentTypeCheckedValue = typeCheckedValue
|
|
136
|
-
|
|
137
|
-
let aggr = aggregationCache.get(value)
|
|
138
|
-
if (!aggr) {
|
|
139
|
-
let entries = perEntryCache.get(value)
|
|
140
|
-
if (!entries) {
|
|
141
|
-
entries = new Map()
|
|
142
|
-
perEntryCache.set(value, entries)
|
|
143
|
-
__debugPerEntryCaches.set(value, entries)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const capturedEntries = entries
|
|
147
|
-
|
|
148
|
-
// Install reactive pruning if a strategy was provided.
|
|
149
|
-
setupCachePruning?.(value, capturedEntries)
|
|
150
|
-
|
|
151
|
-
const cachedCheckEntry = (key: K): TypeCheckError | null => {
|
|
152
|
-
let c = capturedEntries.get(key)
|
|
153
|
-
if (!c) {
|
|
154
|
-
c = computed(() => checkEntry(value, key, currentPath, currentTypeCheckedValue))
|
|
155
|
-
capturedEntries.set(key, c)
|
|
156
|
-
}
|
|
157
|
-
return c.get()
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
aggr = computed(() => iterateEntries(value, cachedCheckEntry), { keepAlive: true })
|
|
161
|
-
aggregationCache.set(value, aggr)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return aggr.get()
|
|
165
|
-
}
|
|
166
|
-
}
|
|
File without changes
|