mobx-keystone 2.1.0 → 2.3.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 +17 -0
- package/README.md +1 -1
- package/dist/mobx-keystone.esm.js +555 -516
- package/dist/mobx-keystone.esm.mjs +555 -516
- package/dist/mobx-keystone.umd.js +558 -519
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +2 -10
- package/dist/types/modelShared/modelDecorator.d.ts +5 -2
- package/dist/types/modelShared/prop.d.ts +42 -29
- package/dist/types/ref/Ref.d.ts +1 -4
- package/dist/types/snapshot/SnapshotOf.d.ts +4 -4
- package/dist/types/snapshot/applySnapshot.d.ts +1 -1
- package/dist/types/snapshot/fromSnapshot.d.ts +2 -2
- package/dist/types/snapshot/getSnapshot.d.ts +1 -1
- package/dist/types/types/SnapshotTypeMismatchError.d.ts +3 -1
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/modelTypeMatching.d.ts +1 -0
- package/dist/types/types/schemas.d.ts +41 -37
- package/dist/types/types/tProp.d.ts +12 -6
- package/dist/types/types/types.d.ts +6 -6
- package/dist/types/types/utility/typeToStoredData.d.ts +14 -10
- package/dist/types/types/utility/typesCodecCore.d.ts +3 -0
- package/dist/types/utils/ModelPool.d.ts +0 -1
- package/dist/types/utils/types.d.ts +6 -0
- package/dist/types/wrappers/ArraySet.d.ts +1 -0
- package/package.json +13 -11
- package/src/action/modelFlow.ts +35 -66
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +41 -71
- package/src/actionMiddlewares/transactionMiddleware.ts +1 -1
- package/src/actionMiddlewares/undoMiddleware.ts +16 -22
- package/src/context/context.ts +15 -36
- package/src/dataModel/utils.ts +5 -28
- package/src/deepChange/onDeepChange.ts +44 -126
- package/src/model/BaseModel.ts +1 -1
- package/src/model/utils.ts +5 -10
- package/src/modelShared/modelDecorator.ts +13 -10
- package/src/modelShared/modelInfo.ts +75 -0
- package/src/modelShared/prop.ts +115 -111
- package/src/modelShared/sharedInternalModel.ts +2 -2
- package/src/parent/arrayPaths.ts +1 -4
- package/src/parent/path.ts +0 -10
- package/src/patch/patchRecorder.ts +13 -28
- package/src/ref/core.ts +6 -19
- package/src/rootStore/rootStore.ts +1 -3
- package/src/snapshot/SnapshotOf.ts +24 -48
- package/src/snapshot/applySnapshot.ts +1 -1
- package/src/snapshot/fromSnapshot.ts +10 -3
- package/src/snapshot/getSnapshot.ts +1 -1
- package/src/snapshot/internal.ts +0 -2
- package/src/snapshot/reconcileModelSnapshot.ts +5 -3
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +3 -1
- package/src/treeUtils/draft.ts +6 -1
- package/src/treeUtils/sandbox.ts +10 -7
- package/src/tweaker/tweakArray.ts +20 -50
- package/src/tweaker/tweakPlainObject.ts +9 -15
- package/src/types/SnapshotTypeMismatchError.ts +3 -1
- package/src/types/arrayBased/typesArray.ts +13 -19
- package/src/types/arrayBased/typesTuple.ts +16 -25
- package/src/types/createCachedTypeCheck.ts +17 -30
- package/src/types/index.ts +1 -0
- package/src/types/modelTypeMatching.ts +276 -0
- package/src/types/objectBased/typesDataModelData.ts +7 -8
- package/src/types/objectBased/typesModel.ts +23 -6
- package/src/types/objectBased/typesObject.ts +11 -15
- package/src/types/objectBased/typesRecord.ts +11 -15
- package/src/types/objectBased/typesRef.ts +23 -4
- package/src/types/schemas.ts +59 -71
- package/src/types/tProp.ts +29 -29
- package/src/types/utility/typeToStoredData.ts +22 -21
- package/src/types/utility/typesCodecCore.ts +8 -1
- package/src/types/utility/typesCodecSupport.ts +16 -10
- package/src/types/utility/typesMap.ts +43 -51
- package/src/types/utility/typesOr.ts +38 -8
- package/src/types/utility/typesRefinement.ts +7 -8
- package/src/types/utility/typesSkipCheck.ts +7 -8
- package/src/types/utility/typesTag.ts +7 -8
- package/src/utils/ModelPool.ts +1 -18
- package/src/utils/decorators.ts +5 -5
- package/src/utils/index.ts +1 -20
- package/src/utils/types.ts +5 -0
- package/src/wrappers/ArraySet.ts +27 -14
- package/src/wrappers/ObjectMap.ts +1 -6
- package/src/wrappers/asMap.ts +1 -21
- package/src/wrappers/asSet.ts +0 -8
|
@@ -209,21 +209,9 @@ function changeWithPath(change: DeepChange, path: Path): DeepChange {
|
|
|
209
209
|
* @internal
|
|
210
210
|
*/
|
|
211
211
|
export function emitDeepChange(obj: object, change: DeepChange): void {
|
|
212
|
-
if (
|
|
213
|
-
|
|
212
|
+
if (hasAnyDeepChangeListeners()) {
|
|
213
|
+
emitLazyDeepChange(obj, () => change)
|
|
214
214
|
}
|
|
215
|
-
|
|
216
|
-
const emission = beginDeepChangeEmission()
|
|
217
|
-
try {
|
|
218
|
-
attachEmission(change)
|
|
219
|
-
emitGlobalDeepChange(obj, change)
|
|
220
|
-
if (deepChangeListenerCount > 0) {
|
|
221
|
-
emitDeepChangeToListeners(obj, change)
|
|
222
|
-
}
|
|
223
|
-
} finally {
|
|
224
|
-
currentEmission = emission.parent
|
|
225
|
-
}
|
|
226
|
-
rethrowDeepChangeListenerError(emission)
|
|
227
215
|
}
|
|
228
216
|
|
|
229
217
|
/**
|
|
@@ -308,98 +296,52 @@ function emitDeepChangeFromValues(
|
|
|
308
296
|
addedValues?: unknown[],
|
|
309
297
|
removedValues?: unknown[]
|
|
310
298
|
): void {
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const emission = beginDeepChangeEmission()
|
|
316
|
-
try {
|
|
317
|
-
emitDeepChangeFromValuesInEmission(
|
|
318
|
-
node,
|
|
319
|
-
type,
|
|
320
|
-
target,
|
|
321
|
-
keyOrIndex,
|
|
322
|
-
newValue,
|
|
323
|
-
oldValue,
|
|
324
|
-
addedValues,
|
|
325
|
-
removedValues
|
|
299
|
+
if (hasAnyDeepChangeListeners()) {
|
|
300
|
+
emitLazyDeepChange(node, () =>
|
|
301
|
+
buildDeepChange(type, target, keyOrIndex, newValue, oldValue, addedValues, removedValues)
|
|
326
302
|
)
|
|
327
|
-
} finally {
|
|
328
|
-
currentEmission = emission.parent
|
|
329
303
|
}
|
|
330
|
-
rethrowDeepChangeListenerError(emission)
|
|
331
304
|
}
|
|
332
305
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
removedValues?: unknown[]
|
|
342
|
-
): void {
|
|
343
|
-
let change: DeepChange | undefined
|
|
344
|
-
|
|
345
|
-
if (globalDeepChangeListeners.length > 0) {
|
|
346
|
-
change = createDeepChange(
|
|
347
|
-
type,
|
|
348
|
-
target,
|
|
349
|
-
keyOrIndex,
|
|
350
|
-
newValue,
|
|
351
|
-
oldValue,
|
|
352
|
-
addedValues,
|
|
353
|
-
removedValues
|
|
354
|
-
)
|
|
355
|
-
emitGlobalDeepChange(node, change)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (deepChangeListenerCount === 0) {
|
|
359
|
-
return
|
|
360
|
-
}
|
|
306
|
+
/**
|
|
307
|
+
* Delivers a change to the global listeners and to the listeners of the node
|
|
308
|
+
* and its ancestors. The change is only created if some listener gets it.
|
|
309
|
+
*/
|
|
310
|
+
function emitLazyDeepChange(node: object, createChange: () => DeepChange): void {
|
|
311
|
+
const emission = beginDeepChangeEmission()
|
|
312
|
+
try {
|
|
313
|
+
let change: DeepChange | undefined
|
|
361
314
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const reversedPrefix: WritablePath = []
|
|
366
|
-
let current: object | undefined = node
|
|
367
|
-
while (current) {
|
|
368
|
-
const listenersForObject = deepChangeListeners.get(current)
|
|
369
|
-
if (listenersForObject && listenersForObject.length > 0) {
|
|
370
|
-
change ??= createDeepChange(
|
|
371
|
-
type,
|
|
372
|
-
target,
|
|
373
|
-
keyOrIndex,
|
|
374
|
-
newValue,
|
|
375
|
-
oldValue,
|
|
376
|
-
addedValues,
|
|
377
|
-
removedValues
|
|
378
|
-
)
|
|
379
|
-
emitDeepChangeForTarget(current, change, reversedPrefix, reversedPrefix.length)
|
|
315
|
+
if (globalDeepChangeListeners.length > 0) {
|
|
316
|
+
change = attachEmission(createChange())
|
|
317
|
+
emitGlobalDeepChange(node, change)
|
|
380
318
|
}
|
|
381
319
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
320
|
+
if (deepChangeListenerCount > 0) {
|
|
321
|
+
// Segments are pushed in child-to-root order (O(1) each) rather than unshifted
|
|
322
|
+
// at the front (O(depth) each), keeping the whole walk O(depth) instead of
|
|
323
|
+
// O(depth^2) on deep trees.
|
|
324
|
+
const reversedPrefix: WritablePath = []
|
|
325
|
+
let current: object | undefined = node
|
|
326
|
+
while (current) {
|
|
327
|
+
const listenersForObject = deepChangeListeners.get(current)
|
|
328
|
+
if (listenersForObject && listenersForObject.length > 0) {
|
|
329
|
+
change ??= attachEmission(createChange())
|
|
330
|
+
emitDeepChangeForTarget(listenersForObject, change, reversedPrefix)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const parentPath: ParentPath<object> | undefined = fastGetParentPath(current, false)
|
|
334
|
+
if (!parentPath) {
|
|
335
|
+
break
|
|
336
|
+
}
|
|
337
|
+
reversedPrefix.push(parentPath.path)
|
|
338
|
+
current = parentPath.parent
|
|
339
|
+
}
|
|
385
340
|
}
|
|
386
|
-
|
|
387
|
-
|
|
341
|
+
} finally {
|
|
342
|
+
currentEmission = emission.parent
|
|
388
343
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
function createDeepChange(
|
|
392
|
-
type: DeepChangeType,
|
|
393
|
-
target: object | unknown[],
|
|
394
|
-
keyOrIndex: string | number,
|
|
395
|
-
newValue: unknown,
|
|
396
|
-
oldValue: unknown,
|
|
397
|
-
addedValues: unknown[] | undefined,
|
|
398
|
-
removedValues: unknown[] | undefined
|
|
399
|
-
): DeepChange {
|
|
400
|
-
return attachEmission(
|
|
401
|
-
buildDeepChange(type, target, keyOrIndex, newValue, oldValue, addedValues, removedValues)
|
|
402
|
-
)
|
|
344
|
+
rethrowDeepChangeListenerError(emission)
|
|
403
345
|
}
|
|
404
346
|
|
|
405
347
|
function buildDeepChange(
|
|
@@ -489,39 +431,15 @@ function rememberDeepChangeListenerError(error: unknown): void {
|
|
|
489
431
|
emission.error = addDelayedError(emission.error, error, "multiple deep change listeners failed")
|
|
490
432
|
}
|
|
491
433
|
|
|
492
|
-
function emitDeepChangeToListeners(obj: object, change: DeepChange): void {
|
|
493
|
-
// Segments are pushed in child-to-root order (O(1) each) rather than unshifted
|
|
494
|
-
// at the front (O(depth) each), keeping the whole walk O(depth) instead of
|
|
495
|
-
// O(depth^2) on deep trees.
|
|
496
|
-
const reversedPrefix: WritablePath = []
|
|
497
|
-
|
|
498
|
-
emitDeepChangeForTarget(obj, change, reversedPrefix, 0)
|
|
499
|
-
|
|
500
|
-
// and also emit subtree listeners all the way to the root
|
|
501
|
-
let parentPath = fastGetParentPath(obj, false)
|
|
502
|
-
while (parentPath) {
|
|
503
|
-
reversedPrefix.push(parentPath.path)
|
|
504
|
-
emitDeepChangeForTarget(parentPath.parent, change, reversedPrefix, reversedPrefix.length)
|
|
505
|
-
|
|
506
|
-
parentPath = fastGetParentPath(parentPath.parent, false)
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
434
|
// `reversedPrefix` holds the path segments from the changed node up to (but not
|
|
511
|
-
// including)
|
|
512
|
-
// reversed (root-to-child).
|
|
435
|
+
// including) the listened node, in child-to-root order; the emitted path
|
|
436
|
+
// prepends it reversed (root-to-child).
|
|
513
437
|
function emitDeepChangeForTarget(
|
|
514
|
-
|
|
438
|
+
listenersForObject: readonly ListenerSubscription<OnDeepChangeListener>[],
|
|
515
439
|
change: DeepChange,
|
|
516
|
-
reversedPrefix: readonly PathElement[]
|
|
517
|
-
prefixCount: number
|
|
440
|
+
reversedPrefix: readonly PathElement[]
|
|
518
441
|
): void {
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
if (!listenersForObject || listenersForObject.length === 0) {
|
|
522
|
-
return
|
|
523
|
-
}
|
|
524
|
-
|
|
442
|
+
const prefixCount = reversedPrefix.length
|
|
525
443
|
const prefixedChange =
|
|
526
444
|
prefixCount > 0
|
|
527
445
|
? changeWithPath(change, buildPrefixedPath(reversedPrefix, prefixCount, change.path))
|
package/src/model/BaseModel.ts
CHANGED
package/src/model/utils.ts
CHANGED
|
@@ -39,15 +39,10 @@ export function assertIsModel(
|
|
|
39
39
|
* @internal
|
|
40
40
|
*/
|
|
41
41
|
export function isModelClass(modelClass: unknown): modelClass is ModelClass<AnyModel> {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (modelClass !== BaseModel && !(modelClass.prototype instanceof BaseModel)) {
|
|
47
|
-
return false
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return true
|
|
42
|
+
return (
|
|
43
|
+
typeof modelClass === "function" &&
|
|
44
|
+
(modelClass === BaseModel || modelClass.prototype instanceof BaseModel)
|
|
45
|
+
)
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
/**
|
|
@@ -61,7 +56,7 @@ export function assertIsModelClass(
|
|
|
61
56
|
throw failure(`${argName} must be a class`)
|
|
62
57
|
}
|
|
63
58
|
|
|
64
|
-
if (
|
|
59
|
+
if (!isModelClass(modelClass)) {
|
|
65
60
|
throw failure(`${argName} must extend Model`)
|
|
66
61
|
}
|
|
67
62
|
}
|
|
@@ -9,7 +9,7 @@ import type { AnyModel } from "../model/BaseModel"
|
|
|
9
9
|
import { modelTypeKey } from "../model/metadata"
|
|
10
10
|
import { isModelClass } from "../model/utils"
|
|
11
11
|
import { type ModelClass, modelInitializedSymbol } from "../modelShared/BaseModelShared"
|
|
12
|
-
import { modelInfoByClass, modelInfoByName } from "../modelShared/modelInfo"
|
|
12
|
+
import { modelInfoByClass, modelInfoByName, setModelInfo } from "../modelShared/modelInfo"
|
|
13
13
|
import {
|
|
14
14
|
modelUnwrappedClassSymbol,
|
|
15
15
|
runAfterModelDecoratorSymbol,
|
|
@@ -37,11 +37,17 @@ import type { AnyFunction } from "../utils/AnyFunction"
|
|
|
37
37
|
*/
|
|
38
38
|
export const model =
|
|
39
39
|
(name: string) =>
|
|
40
|
-
<MC extends
|
|
41
|
-
clazz
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
<MC extends ModelDecoratorTarget>(clazz: MC, _context?: ClassDecoratorContext<MC>): MC =>
|
|
41
|
+
internalModel(name, clazz as unknown as ModelClass<AnyModel | AnyDataModel>) as any
|
|
42
|
+
|
|
43
|
+
// the model instance type is checked through `prototype` rather than through a typed constructor
|
|
44
|
+
// signature, since comparing constructor signatures would compute the (expensive) model creation data
|
|
45
|
+
// type of every decorated class
|
|
46
|
+
type ModelDecoratorTarget = (new (
|
|
47
|
+
...args: any
|
|
48
|
+
) => any) & {
|
|
49
|
+
prototype: AnyModel | AnyDataModel
|
|
50
|
+
}
|
|
45
51
|
|
|
46
52
|
interface AfterClassInitializationData {
|
|
47
53
|
needsMakeObservable: boolean | undefined
|
|
@@ -161,10 +167,7 @@ const internalModel = <MC extends ModelClass<AnyModel | AnyDataModel>>(
|
|
|
161
167
|
class: decoratedClass,
|
|
162
168
|
}
|
|
163
169
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
modelInfoByClass.set(decoratedClass, modelInfo)
|
|
167
|
-
modelInfoByClass.set(clazz, modelInfo)
|
|
170
|
+
setModelInfo(modelInfo, clazz)
|
|
168
171
|
|
|
169
172
|
runLateInitializationFunctions(clazz, runAfterModelDecoratorSymbol)
|
|
170
173
|
|
|
@@ -22,6 +22,81 @@ export const modelInfoByName: {
|
|
|
22
22
|
*/
|
|
23
23
|
export const modelInfoByClass = new WeakMap<ModelClass<AnyModel | AnyDataModel>, ModelInfo>()
|
|
24
24
|
|
|
25
|
+
// registered model prototypes (looked up rather than reading `constructor`, since that might be
|
|
26
|
+
// a model prop)
|
|
27
|
+
const modelInfoByPrototype = new WeakMap<object, ModelInfo>()
|
|
28
|
+
|
|
29
|
+
// for each registered model, how many registered models below each of its base models
|
|
30
|
+
// (including itself, at 0) it is, by name
|
|
31
|
+
const ancestorDistancesByModelInfo = new WeakMap<ModelInfo, ReadonlyMap<string, number>>()
|
|
32
|
+
// the same, by name for the current registration of each model name
|
|
33
|
+
const ancestorDistancesByModelName = new Map<string, ReadonlyMap<string, number>>()
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Finds the info of the first registered model class in a prototype chain.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export function findModelInfoForPrototype(proto: object | null): ModelInfo | undefined {
|
|
41
|
+
for (let p = proto; p; p = Object.getPrototypeOf(p)) {
|
|
42
|
+
const modelInfo = modelInfoByPrototype.get(p)
|
|
43
|
+
if (modelInfo) {
|
|
44
|
+
return modelInfo
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Registers a model.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export function setModelInfo(modelInfo: ModelInfo, unwrappedClass: ModelClass<any>): void {
|
|
56
|
+
// the base classes were registered before, so their distances are already known; names are
|
|
57
|
+
// used rather than classes since with hot reloading a subclass might still extend a previous
|
|
58
|
+
// registration of a model
|
|
59
|
+
const baseModelInfo = findModelInfoForPrototype(Object.getPrototypeOf(unwrappedClass.prototype))
|
|
60
|
+
const ancestorDistances = new Map<string, number>()
|
|
61
|
+
baseModelInfo &&
|
|
62
|
+
ancestorDistancesByModelInfo.get(baseModelInfo)?.forEach((distance, name) => {
|
|
63
|
+
ancestorDistances.set(name, distance + 1)
|
|
64
|
+
})
|
|
65
|
+
ancestorDistances.set(modelInfo.name, 0)
|
|
66
|
+
ancestorDistancesByModelInfo.set(modelInfo, ancestorDistances)
|
|
67
|
+
ancestorDistancesByModelName.set(modelInfo.name, ancestorDistances)
|
|
68
|
+
|
|
69
|
+
modelInfoByName[modelInfo.name] = modelInfo
|
|
70
|
+
modelInfoByClass.set(modelInfo.class, modelInfo)
|
|
71
|
+
modelInfoByClass.set(unwrappedClass, modelInfo)
|
|
72
|
+
modelInfoByPrototype.set(unwrappedClass.prototype, modelInfo)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* How many registered models a model is below a base model (0 if it is that model), or -1 if
|
|
77
|
+
* it does not extend it.
|
|
78
|
+
*
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
export function getModelSubclassDistance(modelInfo: ModelInfo, baseModelName: string): number {
|
|
82
|
+
return ancestorDistancesByModelInfo.get(modelInfo)?.get(baseModelName) ?? -1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* How many registered models the model currently registered with a name is below a base model
|
|
87
|
+
* (0 if it is that model), -1 if it does not extend it, or `undefined` if the name is not
|
|
88
|
+
* registered.
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export function getModelSubclassDistanceForName(
|
|
93
|
+
modelName: string,
|
|
94
|
+
baseModelName: string
|
|
95
|
+
): number | undefined {
|
|
96
|
+
const ancestorDistances = ancestorDistancesByModelName.get(modelName)
|
|
97
|
+
return ancestorDistances ? (ancestorDistances.get(baseModelName) ?? -1) : undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
25
100
|
/**
|
|
26
101
|
* Returns the model info for a model type name, or `undefined` if not found.
|
|
27
102
|
*
|