mobx-keystone 1.25.0 → 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.
- package/CHANGELOG.md +123 -0
- package/dist/mobx-keystone.esm.js +1478 -952
- package/dist/mobx-keystone.esm.mjs +1478 -952
- package/dist/mobx-keystone.umd.js +1483 -952
- package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
- package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
- package/dist/types/deepChange/onDeepChange.d.ts +9 -0
- package/dist/types/parent/detach.d.ts +1 -1
- package/dist/types/parent/findChildren.d.ts +1 -1
- package/dist/types/patch/applyPatches.d.ts +2 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
- package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
- package/dist/types/standardActions/arrayActions.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +0 -4
- package/dist/types/types/TypeCheckError.d.ts +1 -0
- package/dist/types/types/utility/typesRefinement.d.ts +2 -2
- 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 +1 -1
- 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 +42 -11
- package/src/actionMiddlewares/undoMiddleware.ts +65 -46
- 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 +4 -9
- 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 +34 -14
- package/src/modelShared/modelInfo.ts +1 -1
- package/src/modelShared/prop.ts +6 -9
- package/src/modelShared/sharedInternalModel.ts +16 -3
- package/src/parent/detach.ts +1 -1
- package/src/parent/findChildren.ts +4 -7
- package/src/parent/onChildAttachedTo.ts +44 -28
- package/src/parent/path.ts +5 -3
- package/src/parent/setParent.ts +7 -0
- package/src/parent/walkTree.ts +21 -37
- package/src/patch/applyPatches.ts +69 -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 +18 -2
- 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 +1 -1
- package/src/snapshot/clone.ts +1 -2
- package/src/snapshot/cloneTreeValue.ts +1 -2
- package/src/snapshot/fromModelSnapshot.ts +6 -2
- package/src/snapshot/internal.ts +22 -9
- 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/core.ts +2 -4
- package/src/tweaker/tweak.ts +2 -1
- package/src/tweaker/tweakArray.ts +83 -20
- package/src/tweaker/tweakPlainObject.ts +76 -23
- package/src/tweaker/typeChecking.ts +124 -15
- package/src/types/TypeCheckError.ts +17 -19
- package/src/types/createCachedTypeCheck.ts +27 -2
- 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 +6 -13
- 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 +3 -3
- package/src/utils/chainFns.ts +4 -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 +34 -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
|
@@ -253,15 +253,13 @@ export function actionTrackingMiddleware(
|
|
|
253
253
|
state: State.Finished,
|
|
254
254
|
})
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
256
|
+
try {
|
|
257
|
+
return hooks.onFinish?.(simpleCtx, ret) || ret
|
|
258
|
+
} finally {
|
|
259
|
+
if (parentResumed) {
|
|
260
|
+
suspend(parentCtx!)
|
|
261
|
+
}
|
|
262
262
|
}
|
|
263
|
-
|
|
264
|
-
return ret
|
|
265
263
|
}
|
|
266
264
|
|
|
267
265
|
const resume = (simpleCtx: SimpleActionContext, real: boolean) => {
|
|
@@ -323,10 +321,11 @@ export function actionTrackingMiddleware(
|
|
|
323
321
|
retObj = finish(simpleCtx, retObj)
|
|
324
322
|
} else {
|
|
325
323
|
try {
|
|
326
|
-
retObj =
|
|
324
|
+
retObj = { result: ActionTrackingResult.Return, value: next() }
|
|
327
325
|
} catch (err) {
|
|
328
|
-
retObj =
|
|
326
|
+
retObj = { result: ActionTrackingResult.Throw, value: err }
|
|
329
327
|
}
|
|
328
|
+
retObj = finish(simpleCtx, retObj)
|
|
330
329
|
}
|
|
331
330
|
|
|
332
331
|
return returnOrThrowActionTrackingReturn(retObj)
|
|
@@ -409,9 +408,8 @@ const simpleDataContextSymbol = Symbol("simpleDataContext")
|
|
|
409
408
|
* @returns Simplified action context.
|
|
410
409
|
*/
|
|
411
410
|
export function simplifyActionContext(ctx: ActionContext): SimpleActionContext {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
411
|
+
// every async step points to the step that spawned its flow (the spawn step points to itself)
|
|
412
|
+
ctx = ctx.spawnAsyncStepContext ?? ctx
|
|
415
413
|
|
|
416
414
|
let simpleCtx = ctx.data[simpleDataContextSymbol]
|
|
417
415
|
if (!simpleCtx) {
|
|
@@ -4,8 +4,10 @@ import { assertIsModel } from "../model/utils"
|
|
|
4
4
|
import { addModelClassInitializer } from "../modelShared/modelClassInitializer"
|
|
5
5
|
import { applyPatches } from "../patch"
|
|
6
6
|
import { internalPatchRecorder, type PatchRecorder } from "../patch/patchRecorder"
|
|
7
|
-
import {
|
|
7
|
+
import { withTypeCheckingBatch } from "../tweaker/typeChecking"
|
|
8
|
+
import { assertIsObject, failure, MobxKeystoneAggregateError } from "../utils"
|
|
8
9
|
import { checkDecoratorContext } from "../utils/decorators"
|
|
10
|
+
import { pushDelayedError } from "../utils/forEachWithDelayedThrow"
|
|
9
11
|
import {
|
|
10
12
|
ActionTrackingResult,
|
|
11
13
|
actionTrackingMiddleware,
|
|
@@ -36,11 +38,15 @@ export function transactionMiddleware<M extends AnyModel>(target: {
|
|
|
36
38
|
|
|
37
39
|
const patchRecorderSymbol = Symbol("patchRecorder")
|
|
38
40
|
function initPatchRecorder(ctx: SimpleActionContext) {
|
|
39
|
-
ctx.rootContext.data[patchRecorderSymbol] =
|
|
40
|
-
recording: false,
|
|
41
|
-
|
|
41
|
+
ctx.rootContext.data[patchRecorderSymbol] = {
|
|
42
|
+
recorder: internalPatchRecorder(undefined, { recording: false }),
|
|
43
|
+
activeCount: 0,
|
|
44
|
+
}
|
|
42
45
|
}
|
|
43
|
-
function
|
|
46
|
+
function getPatchRecorderData(ctx: SimpleActionContext): {
|
|
47
|
+
recorder: PatchRecorder
|
|
48
|
+
activeCount: number
|
|
49
|
+
} {
|
|
44
50
|
return ctx.rootContext.data[patchRecorderSymbol]
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -55,23 +61,48 @@ export function transactionMiddleware<M extends AnyModel>(target: {
|
|
|
55
61
|
initPatchRecorder(ctx)
|
|
56
62
|
}
|
|
57
63
|
},
|
|
64
|
+
// Actions nest, so recording must only stop once every action of the transaction
|
|
65
|
+
// has been suspended, not as soon as the innermost one returns.
|
|
58
66
|
onResume(ctx) {
|
|
59
|
-
|
|
67
|
+
const data = getPatchRecorderData(ctx)
|
|
68
|
+
data.activeCount++
|
|
69
|
+
data.recorder.recording = true
|
|
60
70
|
},
|
|
61
71
|
onSuspend(ctx) {
|
|
62
|
-
|
|
72
|
+
const data = getPatchRecorderData(ctx)
|
|
73
|
+
data.activeCount--
|
|
74
|
+
data.recorder.recording = data.activeCount > 0
|
|
63
75
|
},
|
|
64
76
|
onFinish(ctx, ret) {
|
|
65
77
|
if (ctx === ctx.rootContext) {
|
|
66
|
-
const patchRecorder =
|
|
78
|
+
const patchRecorder = getPatchRecorderData(ctx).recorder
|
|
67
79
|
|
|
68
80
|
try {
|
|
69
81
|
if (ret.result === ActionTrackingResult.Throw) {
|
|
70
82
|
// undo changes (backwards for inverse patches)
|
|
71
83
|
const { events } = patchRecorder
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
const rollbackErrors: unknown[] = []
|
|
85
|
+
withTypeCheckingBatch(() => {
|
|
86
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
87
|
+
const event = events[i]
|
|
88
|
+
// A listener may throw after a patch has already been applied.
|
|
89
|
+
// Continue at patch granularity so one failure cannot skip the
|
|
90
|
+
// remaining restoration, including patches from the same edit.
|
|
91
|
+
for (let j = event.inversePatches.length - 1; j >= 0; j--) {
|
|
92
|
+
try {
|
|
93
|
+
applyPatches(event.target, [event.inversePatches[j]])
|
|
94
|
+
} catch (error) {
|
|
95
|
+
pushDelayedError(rollbackErrors, error)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
if (rollbackErrors.length > 0) {
|
|
101
|
+
// Keep the action error first so the reason for the rollback is not lost.
|
|
102
|
+
throw new MobxKeystoneAggregateError(
|
|
103
|
+
[ret.value, ...rollbackErrors],
|
|
104
|
+
"transaction rollback callbacks failed"
|
|
105
|
+
)
|
|
75
106
|
}
|
|
76
107
|
}
|
|
77
108
|
} finally {
|
|
@@ -19,6 +19,16 @@ import {
|
|
|
19
19
|
} from "../utils"
|
|
20
20
|
import { actionTrackingMiddleware, type SimpleActionContext } from "./actionTrackingMiddleware"
|
|
21
21
|
|
|
22
|
+
function checkMaxLevels(value: number | undefined, name: string): void {
|
|
23
|
+
if (
|
|
24
|
+
value !== undefined &&
|
|
25
|
+
value !== Number.POSITIVE_INFINITY &&
|
|
26
|
+
(!Number.isInteger(value) || value < 0)
|
|
27
|
+
) {
|
|
28
|
+
throw failure(`${name} must be a non-negative integer or Infinity`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
/**
|
|
23
33
|
* An undo/redo event without attached state.
|
|
24
34
|
*/
|
|
@@ -151,15 +161,13 @@ export class UndoStore extends Model({
|
|
|
151
161
|
maxUndoLevels?: number
|
|
152
162
|
maxRedoLevels?: number
|
|
153
163
|
}) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
checkMaxLevels(maxUndoLevels, "maxUndoLevels")
|
|
165
|
+
checkMaxLevels(maxRedoLevels, "maxRedoLevels")
|
|
166
|
+
if (maxUndoLevels !== undefined && this.undoEvents.length > maxUndoLevels) {
|
|
167
|
+
this.undoEvents.splice(0, this.undoEvents.length - maxUndoLevels)
|
|
158
168
|
}
|
|
159
|
-
if (maxRedoLevels !== undefined) {
|
|
160
|
-
|
|
161
|
-
this.redoEvents.shift()
|
|
162
|
-
}
|
|
169
|
+
if (maxRedoLevels !== undefined && this.redoEvents.length > maxRedoLevels) {
|
|
170
|
+
this.redoEvents.splice(0, this.redoEvents.length - maxRedoLevels)
|
|
163
171
|
}
|
|
164
172
|
}
|
|
165
173
|
|
|
@@ -261,10 +269,16 @@ export class UndoStore extends Model({
|
|
|
261
269
|
running = true
|
|
262
270
|
},
|
|
263
271
|
end: () => {
|
|
272
|
+
if (ended) {
|
|
273
|
+
throw failure("cannot end a group when it is already ended")
|
|
274
|
+
}
|
|
264
275
|
if (running) {
|
|
265
276
|
api.pause()
|
|
266
277
|
}
|
|
267
278
|
ended = true
|
|
279
|
+
if (group.events.length === 0) {
|
|
280
|
+
return
|
|
281
|
+
}
|
|
268
282
|
if (parentGroup) {
|
|
269
283
|
this._addUndoToParentGroup(parentGroup, group)
|
|
270
284
|
} else {
|
|
@@ -388,7 +402,7 @@ export class UndoManager {
|
|
|
388
402
|
})
|
|
389
403
|
|
|
390
404
|
// restore the attached state before the operation was made
|
|
391
|
-
if (event.attachedState
|
|
405
|
+
if (event.attachedState) {
|
|
392
406
|
this.options?.attachedState?.restore(event.attachedState.beforeEvent)
|
|
393
407
|
}
|
|
394
408
|
})
|
|
@@ -413,7 +427,7 @@ export class UndoManager {
|
|
|
413
427
|
})
|
|
414
428
|
|
|
415
429
|
// restore the attached state after the operation was made
|
|
416
|
-
if (event.attachedState
|
|
430
|
+
if (event.attachedState) {
|
|
417
431
|
this.options?.attachedState?.restore(event.attachedState.afterEvent)
|
|
418
432
|
}
|
|
419
433
|
})
|
|
@@ -586,14 +600,14 @@ export class UndoManager {
|
|
|
586
600
|
function next(ret: any): void {
|
|
587
601
|
if (ret && typeof ret.then === "function") {
|
|
588
602
|
// an async iterator
|
|
589
|
-
ret.then(next, reject)
|
|
603
|
+
Promise.resolve(ret).then(next, reject).catch(reject)
|
|
590
604
|
} else if (ret.done) {
|
|
591
605
|
// done
|
|
592
606
|
group.end()
|
|
593
607
|
resolve(ret.value)
|
|
594
608
|
} else {
|
|
595
609
|
// continue
|
|
596
|
-
Promise.resolve(ret.value).then(onFulfilled, onRejected)
|
|
610
|
+
Promise.resolve(ret.value).then(onFulfilled, onRejected).catch(reject)
|
|
597
611
|
}
|
|
598
612
|
}
|
|
599
613
|
|
|
@@ -675,6 +689,8 @@ export function undoMiddleware<S>(
|
|
|
675
689
|
options?: UndoMiddlewareOptions<S>
|
|
676
690
|
): UndoManager {
|
|
677
691
|
assertTweakedObject(subtreeRoot, "subtreeRoot")
|
|
692
|
+
checkMaxLevels(options?.maxUndoLevels, "maxUndoLevels")
|
|
693
|
+
checkMaxLevels(options?.maxRedoLevels, "maxRedoLevels")
|
|
678
694
|
|
|
679
695
|
let manager: UndoManager
|
|
680
696
|
|
|
@@ -691,6 +707,7 @@ export function undoMiddleware<S>(
|
|
|
691
707
|
function initPatchRecorder(ctx: SimpleActionContext) {
|
|
692
708
|
const group = manager.store._currentGroup
|
|
693
709
|
|
|
710
|
+
const attachedStateBeforeEvent = options?.attachedState?.save()
|
|
694
711
|
const patchRecorderData: PatchRecorderData = {
|
|
695
712
|
recorder: patchRecorder(subtreeRoot, {
|
|
696
713
|
recording: false,
|
|
@@ -702,7 +719,7 @@ export function undoMiddleware<S>(
|
|
|
702
719
|
undoRootContext: ctx,
|
|
703
720
|
group,
|
|
704
721
|
|
|
705
|
-
attachedStateBeforeEvent
|
|
722
|
+
attachedStateBeforeEvent,
|
|
706
723
|
}
|
|
707
724
|
|
|
708
725
|
ctx.rootContext.data[patchRecorderSymbol] = patchRecorderData
|
|
@@ -733,42 +750,44 @@ export function undoMiddleware<S>(
|
|
|
733
750
|
if (patchRecorderData && patchRecorderData.undoRootContext === ctx) {
|
|
734
751
|
const patchRecorder = patchRecorderData.recorder
|
|
735
752
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
753
|
+
try {
|
|
754
|
+
if (patchRecorder.events.length > 0) {
|
|
755
|
+
const patches: Patch[] = []
|
|
756
|
+
const inversePatches: Patch[] = []
|
|
757
|
+
|
|
758
|
+
for (const event of patchRecorder.events) {
|
|
759
|
+
for (const patch of event.patches) patches.push(patch)
|
|
760
|
+
for (const patch of event.inversePatches) inversePatches.push(patch)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const event = {
|
|
764
|
+
type: UndoEventType.Single,
|
|
765
|
+
targetPath: fastGetRootPath(ctx.target, false).path,
|
|
766
|
+
actionName: ctx.actionName,
|
|
767
|
+
patches,
|
|
768
|
+
inversePatches,
|
|
769
|
+
} as const
|
|
770
|
+
|
|
771
|
+
const parentGroup = patchRecorderData.group
|
|
772
|
+
|
|
773
|
+
if (parentGroup) {
|
|
774
|
+
manager.store._addUndoToParentGroup(parentGroup, event)
|
|
775
|
+
} else {
|
|
776
|
+
manager.store._addUndo({
|
|
777
|
+
event: {
|
|
778
|
+
...event,
|
|
779
|
+
attachedState: {
|
|
780
|
+
beforeEvent: patchRecorderData.attachedStateBeforeEvent,
|
|
781
|
+
afterEvent: options?.attachedState?.save(),
|
|
782
|
+
},
|
|
764
783
|
},
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
784
|
+
maxUndoLevels: options?.maxUndoLevels,
|
|
785
|
+
})
|
|
786
|
+
}
|
|
768
787
|
}
|
|
788
|
+
} finally {
|
|
789
|
+
patchRecorder.dispose()
|
|
769
790
|
}
|
|
770
|
-
|
|
771
|
-
patchRecorder.dispose()
|
|
772
791
|
}
|
|
773
792
|
},
|
|
774
793
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { modelIdKey } from "../model/metadata"
|
|
2
2
|
import { isModel } from "../model/utils"
|
|
3
|
+
import { modelToDataNode } from "../parent/core"
|
|
3
4
|
import type { RootPath } from "../parent/path"
|
|
4
5
|
import type { Path } from "../parent/pathTypes"
|
|
5
6
|
|
|
@@ -26,7 +27,7 @@ export function pathToTargetPathIds(root: any, path: Path): (string | null)[] {
|
|
|
26
27
|
let current = root // we don't care about the root ID
|
|
27
28
|
|
|
28
29
|
for (let i = 0; i < path.length; i++) {
|
|
29
|
-
current = current[path[i]]
|
|
30
|
+
current = modelToDataNode(current)[path[i]]
|
|
30
31
|
const targetObjId = isModel(current) ? (current[modelIdKey] ?? null) : null
|
|
31
32
|
targetPathIds.push(targetObjId)
|
|
32
33
|
}
|
|
@@ -5,7 +5,12 @@ import { isDataModelClass } from "../dataModel/utils"
|
|
|
5
5
|
import { isModelClass } from "../model/utils"
|
|
6
6
|
import { isTreeNode } from "../tweaker/core"
|
|
7
7
|
import { tweak } from "../tweaker/tweak"
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
addLateInitializationFunction,
|
|
10
|
+
failure,
|
|
11
|
+
isEqualOrBothNaN,
|
|
12
|
+
runBeforeOnInitSymbol,
|
|
13
|
+
} from "../utils"
|
|
9
14
|
import { checkDecoratorContext, copyFunctionMetadata } from "../utils/decorators"
|
|
10
15
|
import { getOrCreate } from "../utils/mapUtils"
|
|
11
16
|
|
|
@@ -54,14 +59,20 @@ export function computedTree(...args: any[]): any {
|
|
|
54
59
|
const oldValue = entry.value
|
|
55
60
|
const newValue = entry.computed.get()
|
|
56
61
|
|
|
57
|
-
if (oldValue
|
|
62
|
+
if (isEqualOrBothNaN(oldValue, newValue)) {
|
|
58
63
|
return entry.tweakedValue
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
const oldTweakedValue = entry.tweakedValue
|
|
62
67
|
tweak(oldTweakedValue, undefined)
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
let tweakedValue: unknown
|
|
70
|
+
try {
|
|
71
|
+
tweakedValue = tweakComputedTreeNode(newValue, this, propertyKey)
|
|
72
|
+
} catch (error) {
|
|
73
|
+
entry.tweakedValue = tweakComputedTreeNode(oldTweakedValue, this, propertyKey)
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
65
76
|
entry.value = newValue
|
|
66
77
|
entry.tweakedValue = tweakedValue
|
|
67
78
|
return tweakedValue
|
package/src/context/context.ts
CHANGED
|
@@ -120,56 +120,28 @@ class ContextClass<T> implements Context<T> {
|
|
|
120
120
|
this.nodeAtom.get(node)?.reportChanged()
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
get(node: object): T {
|
|
124
|
+
const provider = this.getProviderNode(node)
|
|
125
|
+
if (provider) {
|
|
126
|
+
return resolveContextValue(this.nodeContextValue.get(provider)!)
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
return resolveContextValue(obsForNode)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const parent = fastGetParent(node, useAtom)
|
|
134
|
-
if (!parent) {
|
|
135
|
-
const overrideValue = this.overrideContextValue.get()
|
|
136
|
-
if (overrideValue) {
|
|
137
|
-
return resolveContextValue(overrideValue)
|
|
138
|
-
}
|
|
139
|
-
return this.getDefault()
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return this.fastGet(parent, useAtom)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
get(node: object) {
|
|
146
|
-
assertTweakedObject(node, "node")
|
|
147
|
-
|
|
148
|
-
return this.fastGet(node, true)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private fastGetProviderNode(node: object, useAtom: boolean): object | undefined {
|
|
152
|
-
if (useAtom) {
|
|
153
|
-
this.reportNodeAtomObserved(node)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const obsForNode = this.nodeContextValue.get(node)
|
|
157
|
-
if (obsForNode) {
|
|
158
|
-
return node
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const parent = fastGetParent(node, useAtom)
|
|
162
|
-
if (!parent) {
|
|
163
|
-
return undefined
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return this.fastGetProviderNode(parent, useAtom)
|
|
129
|
+
const overrideValue = this.overrideContextValue.get()
|
|
130
|
+
return overrideValue ? resolveContextValue(overrideValue) : this.getDefault()
|
|
167
131
|
}
|
|
168
132
|
|
|
169
133
|
getProviderNode(node: object): object | undefined {
|
|
170
134
|
assertTweakedObject(node, "node")
|
|
171
135
|
|
|
172
|
-
|
|
136
|
+
let current: object | undefined = node
|
|
137
|
+
while (current) {
|
|
138
|
+
this.reportNodeAtomObserved(current)
|
|
139
|
+
if (this.nodeContextValue.has(current)) {
|
|
140
|
+
return current
|
|
141
|
+
}
|
|
142
|
+
current = fastGetParent(current, true)
|
|
143
|
+
}
|
|
144
|
+
return undefined
|
|
173
145
|
}
|
|
174
146
|
|
|
175
147
|
getDefault(): T {
|
|
@@ -227,7 +199,7 @@ class ContextClass<T> implements Context<T> {
|
|
|
227
199
|
|
|
228
200
|
try {
|
|
229
201
|
const ret = fn()
|
|
230
|
-
if (isTweakedObject(ret,
|
|
202
|
+
if (isTweakedObject(ret, false)) {
|
|
231
203
|
this.set(ret, value)
|
|
232
204
|
}
|
|
233
205
|
return ret
|
|
@@ -247,7 +219,7 @@ class ContextClass<T> implements Context<T> {
|
|
|
247
219
|
|
|
248
220
|
try {
|
|
249
221
|
const ret = fn()
|
|
250
|
-
if (isTweakedObject(ret,
|
|
222
|
+
if (isTweakedObject(ret, false)) {
|
|
251
223
|
this._setComputed(ret, computedValueFn)
|
|
252
224
|
}
|
|
253
225
|
return ret
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { propsTypeSymbol } from "../modelShared/BaseModelShared"
|
|
2
2
|
import { modelInfoByClass } from "../modelShared/modelInfo"
|
|
3
3
|
import { getInternalModelClassPropsInfo } from "../modelShared/modelPropsInfo"
|
|
4
4
|
import {
|
|
@@ -15,14 +15,13 @@ import { toTreeNode } from "../tweaker/tweak"
|
|
|
15
15
|
import { typesDataModelData } from "../types/objectBased/typesDataModelData"
|
|
16
16
|
import type { TypeCheckError } from "../types/TypeCheckError"
|
|
17
17
|
import { typeCheck } from "../types/typeCheck"
|
|
18
|
-
import { clonePlainObject, failure, isObject } from "../utils"
|
|
18
|
+
import { clonePlainObject, failure, hasOwnProp, isObject, setProtoProp } from "../utils"
|
|
19
19
|
import { getOrCreate } from "../utils/mapUtils"
|
|
20
20
|
import type { DataModelConstructorOptions } from "./DataModelConstructorOptions"
|
|
21
|
+
import { dataModelInstanceCache } from "./dataModelInstanceCache"
|
|
21
22
|
import { getDataModelMetadata } from "./getDataModelMetadata"
|
|
22
23
|
import { internalNewDataModel } from "./newDataModel"
|
|
23
24
|
|
|
24
|
-
const dataModelInstanceCache = new WeakMap<ModelClass<AnyDataModel>, WeakMap<any, AnyDataModel>>()
|
|
25
|
-
|
|
26
25
|
/**
|
|
27
26
|
* Base abstract class for data models. Use `DataModel` instead when extending.
|
|
28
27
|
*
|
|
@@ -101,7 +100,8 @@ export abstract class BaseDataModel<TProps extends ModelProps> {
|
|
|
101
100
|
const k = modelPropsKeys[i]
|
|
102
101
|
const propData = modelProps[k]
|
|
103
102
|
|
|
104
|
-
|
|
103
|
+
const hasInitialValue = hasOwnProp(initialData, k)
|
|
104
|
+
let newValue = hasInitialValue ? initialData[k] : undefined
|
|
105
105
|
let changed = false
|
|
106
106
|
|
|
107
107
|
// apply untransform (if any)
|
|
@@ -117,11 +117,15 @@ export abstract class BaseDataModel<TProps extends ModelProps> {
|
|
|
117
117
|
if (defaultValue !== noDefaultValue) {
|
|
118
118
|
changed = true
|
|
119
119
|
newValue = defaultValue
|
|
120
|
+
} else if (!hasInitialValue) {
|
|
121
|
+
// for mobx4, we need to set up properties even if they are undefined
|
|
122
|
+
changed = true
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
if (changed) {
|
|
124
|
-
|
|
127
|
+
if (k === "__proto__") setProtoProp(initialData, newValue)
|
|
128
|
+
else initialData[k] = newValue
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -144,24 +148,24 @@ export abstract class BaseDataModel<TProps extends ModelProps> {
|
|
|
144
148
|
|
|
145
149
|
Object.setPrototypeOf(this, modelClass.prototype)
|
|
146
150
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
try {
|
|
152
|
+
internalNewDataModel(this, tweakedData as any, {
|
|
153
|
+
modelClass,
|
|
154
|
+
})
|
|
155
|
+
} catch (error) {
|
|
156
|
+
instancesForModelClass.delete(tweakedData)
|
|
157
|
+
throw error
|
|
158
|
+
}
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
toString(options?: { withData?: boolean }) {
|
|
153
|
-
const
|
|
154
|
-
withData: true,
|
|
155
|
-
...options,
|
|
156
|
-
}
|
|
162
|
+
const { withData = true } = options ?? {}
|
|
157
163
|
|
|
158
164
|
const modelInfo = modelInfoByClass.get(this.constructor as any)
|
|
159
165
|
|
|
160
166
|
const firstPart = `${this.constructor.name}#${modelInfo!.name}`
|
|
161
167
|
|
|
162
|
-
return
|
|
163
|
-
? `[${firstPart} ${JSON.stringify(getSnapshot(this))}]`
|
|
164
|
-
: `[${firstPart}]`
|
|
168
|
+
return withData ? `[${firstPart} ${JSON.stringify(getSnapshot(this.$))}]` : `[${firstPart}]`
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
|