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.
Files changed (83) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -1
  3. package/dist/mobx-keystone.esm.js +555 -516
  4. package/dist/mobx-keystone.esm.mjs +555 -516
  5. package/dist/mobx-keystone.umd.js +558 -519
  6. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +2 -10
  7. package/dist/types/modelShared/modelDecorator.d.ts +5 -2
  8. package/dist/types/modelShared/prop.d.ts +42 -29
  9. package/dist/types/ref/Ref.d.ts +1 -4
  10. package/dist/types/snapshot/SnapshotOf.d.ts +4 -4
  11. package/dist/types/snapshot/applySnapshot.d.ts +1 -1
  12. package/dist/types/snapshot/fromSnapshot.d.ts +2 -2
  13. package/dist/types/snapshot/getSnapshot.d.ts +1 -1
  14. package/dist/types/types/SnapshotTypeMismatchError.d.ts +3 -1
  15. package/dist/types/types/index.d.ts +1 -0
  16. package/dist/types/types/modelTypeMatching.d.ts +1 -0
  17. package/dist/types/types/schemas.d.ts +41 -37
  18. package/dist/types/types/tProp.d.ts +12 -6
  19. package/dist/types/types/types.d.ts +6 -6
  20. package/dist/types/types/utility/typeToStoredData.d.ts +14 -10
  21. package/dist/types/types/utility/typesCodecCore.d.ts +3 -0
  22. package/dist/types/utils/ModelPool.d.ts +0 -1
  23. package/dist/types/utils/types.d.ts +6 -0
  24. package/dist/types/wrappers/ArraySet.d.ts +1 -0
  25. package/package.json +13 -11
  26. package/src/action/modelFlow.ts +35 -66
  27. package/src/actionMiddlewares/actionTrackingMiddleware.ts +41 -71
  28. package/src/actionMiddlewares/transactionMiddleware.ts +1 -1
  29. package/src/actionMiddlewares/undoMiddleware.ts +16 -22
  30. package/src/context/context.ts +15 -36
  31. package/src/dataModel/utils.ts +5 -28
  32. package/src/deepChange/onDeepChange.ts +44 -126
  33. package/src/model/BaseModel.ts +1 -1
  34. package/src/model/utils.ts +5 -10
  35. package/src/modelShared/modelDecorator.ts +13 -10
  36. package/src/modelShared/modelInfo.ts +75 -0
  37. package/src/modelShared/prop.ts +115 -111
  38. package/src/modelShared/sharedInternalModel.ts +2 -2
  39. package/src/parent/arrayPaths.ts +1 -4
  40. package/src/parent/path.ts +0 -10
  41. package/src/patch/patchRecorder.ts +13 -28
  42. package/src/ref/core.ts +6 -19
  43. package/src/rootStore/rootStore.ts +1 -3
  44. package/src/snapshot/SnapshotOf.ts +24 -48
  45. package/src/snapshot/applySnapshot.ts +1 -1
  46. package/src/snapshot/fromSnapshot.ts +10 -3
  47. package/src/snapshot/getSnapshot.ts +1 -1
  48. package/src/snapshot/internal.ts +0 -2
  49. package/src/snapshot/reconcileModelSnapshot.ts +5 -3
  50. package/src/snapshot/reconcilePlainObjectSnapshot.ts +3 -1
  51. package/src/treeUtils/draft.ts +6 -1
  52. package/src/treeUtils/sandbox.ts +10 -7
  53. package/src/tweaker/tweakArray.ts +20 -50
  54. package/src/tweaker/tweakPlainObject.ts +9 -15
  55. package/src/types/SnapshotTypeMismatchError.ts +3 -1
  56. package/src/types/arrayBased/typesArray.ts +13 -19
  57. package/src/types/arrayBased/typesTuple.ts +16 -25
  58. package/src/types/createCachedTypeCheck.ts +17 -30
  59. package/src/types/index.ts +1 -0
  60. package/src/types/modelTypeMatching.ts +276 -0
  61. package/src/types/objectBased/typesDataModelData.ts +7 -8
  62. package/src/types/objectBased/typesModel.ts +23 -6
  63. package/src/types/objectBased/typesObject.ts +11 -15
  64. package/src/types/objectBased/typesRecord.ts +11 -15
  65. package/src/types/objectBased/typesRef.ts +23 -4
  66. package/src/types/schemas.ts +59 -71
  67. package/src/types/tProp.ts +29 -29
  68. package/src/types/utility/typeToStoredData.ts +22 -21
  69. package/src/types/utility/typesCodecCore.ts +8 -1
  70. package/src/types/utility/typesCodecSupport.ts +16 -10
  71. package/src/types/utility/typesMap.ts +43 -51
  72. package/src/types/utility/typesOr.ts +38 -8
  73. package/src/types/utility/typesRefinement.ts +7 -8
  74. package/src/types/utility/typesSkipCheck.ts +7 -8
  75. package/src/types/utility/typesTag.ts +7 -8
  76. package/src/utils/ModelPool.ts +1 -18
  77. package/src/utils/decorators.ts +5 -5
  78. package/src/utils/index.ts +1 -20
  79. package/src/utils/types.ts +5 -0
  80. package/src/wrappers/ArraySet.ts +27 -14
  81. package/src/wrappers/ObjectMap.ts +1 -6
  82. package/src/wrappers/asMap.ts +1 -21
  83. package/src/wrappers/asSet.ts +0 -8
@@ -65,68 +65,53 @@ export function flow<R, Args extends any[]>({
65
65
  const genThrow = gen.throw!.bind(gen)
66
66
 
67
67
  const promise = new Promise<R>((resolve, reject) => {
68
- function onFulfilled(res: any): void {
68
+ function finish(
69
+ stepType: ActionContextAsyncStepType,
70
+ resolution: FlowFinisher["resolution"],
71
+ value: unknown
72
+ ): void {
73
+ wrapInAction({
74
+ nameOrNameFn: name,
75
+ // we use a flow finisher to allow middlewares to tweak the return value before resolution
76
+ fn: (val: any): FlowFinisher => ({
77
+ value: val,
78
+ resolution,
79
+ accepter: resolve,
80
+ rejecter: reject,
81
+ }),
82
+ actionType: ActionContextActionType.Async,
83
+ overrideContext: ctxOverride(stepType),
84
+ isFlowFinisher: true,
85
+ }).call(target, value)
86
+ }
87
+
88
+ function resume(
89
+ genFn: (value: any) => IteratorResult<any>,
90
+ stepType: ActionContextAsyncStepType,
91
+ value: unknown
92
+ ): void {
69
93
  let ret: unknown
70
94
  try {
71
95
  ret = wrapInAction({
72
96
  nameOrNameFn: name,
73
- fn: genNext,
97
+ fn: genFn,
74
98
  actionType: ActionContextActionType.Async,
75
- overrideContext: ctxOverride(ActionContextAsyncStepType.Resume),
76
- }).call(target, res)
99
+ overrideContext: ctxOverride(stepType),
100
+ }).call(target, value)
77
101
  } catch (e) {
78
- wrapInAction({
79
- nameOrNameFn: name,
80
- fn: (err: any) => {
81
- // we use a flow finisher to allow middlewares to tweak the return value before resolution
82
- return {
83
- value: err,
84
- resolution: "reject",
85
-
86
- accepter: resolve,
87
- rejecter: reject,
88
- } as FlowFinisher
89
- },
90
- actionType: ActionContextActionType.Async,
91
- overrideContext: ctxOverride(ActionContextAsyncStepType.Throw),
92
- isFlowFinisher: true,
93
- }).call(target, e)
102
+ finish(ActionContextAsyncStepType.Throw, "reject", e)
94
103
  return
95
104
  }
96
105
 
97
106
  next(ret)
98
107
  }
99
108
 
100
- function onRejected(err: unknown): void {
101
- let ret: unknown
102
- try {
103
- ret = wrapInAction({
104
- nameOrNameFn: name,
105
- fn: genThrow,
106
- actionType: ActionContextActionType.Async,
107
- overrideContext: ctxOverride(ActionContextAsyncStepType.ResumeError),
108
- }).call(target, err)
109
- } catch (e) {
110
- wrapInAction({
111
- nameOrNameFn: name,
112
- fn: (err: any) => {
113
- // we use a flow finisher to allow middlewares to tweak the return value before resolution
114
- return {
115
- value: err,
116
- resolution: "reject",
117
-
118
- accepter: resolve,
119
- rejecter: reject,
120
- } as FlowFinisher
121
- },
122
- actionType: ActionContextActionType.Async,
123
- overrideContext: ctxOverride(ActionContextAsyncStepType.Throw),
124
- isFlowFinisher: true,
125
- }).call(target, e)
126
- return
127
- }
109
+ function onFulfilled(res: unknown): void {
110
+ resume(genNext, ActionContextAsyncStepType.Resume, res)
111
+ }
128
112
 
129
- next(ret)
113
+ function onRejected(err: unknown): void {
114
+ resume(genThrow, ActionContextAsyncStepType.ResumeError, err)
130
115
  }
131
116
 
132
117
  function next(ret: any): void {
@@ -134,23 +119,7 @@ export function flow<R, Args extends any[]>({
134
119
  // an async iterator
135
120
  Promise.resolve(ret).then(next, reject).catch(reject)
136
121
  } else if (ret.done) {
137
- // done
138
- wrapInAction({
139
- nameOrNameFn: name,
140
- fn: (val: any) => {
141
- // we use a flow finisher to allow middlewares to tweak the return value before resolution
142
- return {
143
- value: val,
144
- resolution: "accept",
145
-
146
- accepter: resolve,
147
- rejecter: reject,
148
- } as FlowFinisher
149
- },
150
- actionType: ActionContextActionType.Async,
151
- overrideContext: ctxOverride(ActionContextAsyncStepType.Return),
152
- isFlowFinisher: true,
153
- }).call(target, ret.value)
122
+ finish(ActionContextAsyncStepType.Return, "accept", ret.value)
154
123
  } else {
155
124
  // continue
156
125
  Promise.resolve(ret.value).then(onFulfilled, onRejected).catch(reject)
@@ -178,8 +178,11 @@ export function actionTrackingMiddleware(
178
178
  const resumeSuspendSupport = !!hooks.onResume || !!hooks.onSuspend
179
179
 
180
180
  const filter: ActionMiddleware["filter"] = (ctx) => {
181
- if (ctx.type === ActionContextActionType.Sync) {
182
- // start and finish is on the same context
181
+ if (
182
+ ctx.type === ActionContextActionType.Sync ||
183
+ ctx.asyncStepType === ActionContextAsyncStepType.Spawn
184
+ ) {
185
+ // (for sync actions start and finish are on the same context)
183
186
  const accepted = userFilter(ctx)
184
187
  if (accepted) {
185
188
  setCtxData(ctx, {
@@ -188,39 +191,21 @@ export function actionTrackingMiddleware(
188
191
  })
189
192
  }
190
193
  return accepted
191
- } else {
192
- switch (ctx.asyncStepType) {
193
- case ActionContextAsyncStepType.Spawn: {
194
- const accepted = userFilter(ctx)
195
- if (accepted) {
196
- setCtxData(ctx, {
197
- startAccepted: true,
198
- state: State.Idle,
199
- })
200
- }
201
- return accepted
202
- }
194
+ }
203
195
 
204
- case ActionContextAsyncStepType.Return:
205
- case ActionContextAsyncStepType.Throw: {
206
- // depends if the spawn one was accepted or not
207
- const data = getCtxData(ctx.spawnAsyncStepContext!)
208
- return data ? data.startAccepted : false
209
- }
196
+ // the other steps depend on whether the spawn one was accepted or not
197
+ const spawnAccepted = () => !!getCtxData(ctx.spawnAsyncStepContext!)?.startAccepted
198
+ switch (ctx.asyncStepType) {
199
+ case ActionContextAsyncStepType.Return:
200
+ case ActionContextAsyncStepType.Throw:
201
+ return spawnAccepted()
210
202
 
211
- case ActionContextAsyncStepType.Resume:
212
- case ActionContextAsyncStepType.ResumeError:
213
- if (resumeSuspendSupport) {
214
- // depends if the spawn one was accepted or not
215
- const data = getCtxData(ctx.spawnAsyncStepContext!)
216
- return data ? data.startAccepted : false
217
- } else {
218
- return false
219
- }
203
+ case ActionContextAsyncStepType.Resume:
204
+ case ActionContextAsyncStepType.ResumeError:
205
+ return resumeSuspendSupport && spawnAccepted()
220
206
 
221
- default:
222
- return false
223
- }
207
+ default:
208
+ return false
224
209
  }
225
210
  }
226
211
 
@@ -311,57 +296,42 @@ export function actionTrackingMiddleware(
311
296
  }
312
297
  }
313
298
 
314
- if (ctx.type === ActionContextActionType.Sync) {
315
- let retObj = start(simpleCtx)
316
-
317
- if (retObj) {
299
+ if (
300
+ ctx.type === ActionContextActionType.Sync ||
301
+ ctx.asyncStepType === ActionContextAsyncStepType.Spawn
302
+ ) {
303
+ const overriddenRetObj = start(simpleCtx)
304
+ if (overriddenRetObj) {
318
305
  // action canceled / overridden by onStart
319
306
  resume(simpleCtx, true)
320
307
  suspend(simpleCtx)
321
- retObj = finish(simpleCtx, retObj)
322
- } else {
323
- try {
324
- retObj = { result: ActionTrackingResult.Return, value: next() }
325
- } catch (err) {
326
- retObj = { result: ActionTrackingResult.Throw, value: err }
327
- }
328
- retObj = finish(simpleCtx, retObj)
308
+ return returnOrThrowActionTrackingReturn(finish(simpleCtx, overriddenRetObj))
309
+ }
310
+
311
+ if (ctx.type !== ActionContextActionType.Sync) {
312
+ // a flow finishes in its return / throw step
313
+ return next()
329
314
  }
330
315
 
331
- return returnOrThrowActionTrackingReturn(retObj)
316
+ let retObj: ActionTrackingReturn
317
+ try {
318
+ retObj = { result: ActionTrackingResult.Return, value: next() }
319
+ } catch (err) {
320
+ retObj = { result: ActionTrackingResult.Throw, value: err }
321
+ }
322
+ return returnOrThrowActionTrackingReturn(finish(simpleCtx, retObj))
332
323
  } else {
333
324
  // async
334
325
 
335
326
  switch (ctx.asyncStepType) {
336
- case ActionContextAsyncStepType.Spawn: {
337
- let retObj = start(simpleCtx)
338
- if (retObj) {
339
- // action canceled / overridden by onStart
340
- resume(simpleCtx, true)
341
- suspend(simpleCtx)
342
- retObj = finish(simpleCtx, retObj)
343
- return returnOrThrowActionTrackingReturn(retObj)
344
- } else {
345
- return next()
346
- }
347
- }
348
-
349
- case ActionContextAsyncStepType.Return: {
350
- const flowFinisher: FlowFinisher = next()
351
- const retObj = finish(simpleCtx, {
352
- result: ActionTrackingResult.Return,
353
- value: flowFinisher.value,
354
- })
355
- flowFinisher.resolution =
356
- retObj.result === ActionTrackingResult.Return ? "accept" : "reject"
357
- flowFinisher.value = retObj.value
358
- return flowFinisher
359
- }
360
-
327
+ case ActionContextAsyncStepType.Return:
361
328
  case ActionContextAsyncStepType.Throw: {
362
329
  const flowFinisher: FlowFinisher = next()
363
330
  const retObj = finish(simpleCtx, {
364
- result: ActionTrackingResult.Throw,
331
+ result:
332
+ ctx.asyncStepType === ActionContextAsyncStepType.Return
333
+ ? ActionTrackingResult.Return
334
+ : ActionTrackingResult.Throw,
365
335
  value: flowFinisher.value,
366
336
  })
367
337
  flowFinisher.resolution =
@@ -122,7 +122,7 @@ export function transaction(...args: any[]): void {
122
122
 
123
123
  checkDecoratorContext("transaction", ctx.name, ctx.static)
124
124
  if (ctx.kind !== "method" && ctx.kind !== "field") {
125
- throw failure(`@transaction can only be used on fields or methods}`)
125
+ throw failure(`@transaction can only be used on fields or methods`)
126
126
  }
127
127
 
128
128
  ctx.addInitializer(function (this: any) {
@@ -428,11 +428,11 @@ export class UndoManager {
428
428
  const event = events[events.length - 1].data
429
429
 
430
430
  withoutUndo(() => {
431
- toSingleEvents(event)
432
- .reverse()
433
- .forEach((e) => {
434
- applyPatches(this.subtreeRoot, e.inversePatches, true)
435
- })
431
+ applyPatches(
432
+ this.subtreeRoot,
433
+ toSingleEvents(event).map((e) => e.inversePatches),
434
+ true
435
+ )
436
436
 
437
437
  // restore the attached state before the operation was made
438
438
  if (event.attachedState) {
@@ -456,9 +456,10 @@ export class UndoManager {
456
456
  const event = events[events.length - 1].data
457
457
 
458
458
  withoutUndo(() => {
459
- toSingleEvents(event).forEach((e) => {
460
- applyPatches(this.subtreeRoot, e.patches)
461
- })
459
+ applyPatches(
460
+ this.subtreeRoot,
461
+ toSingleEvents(event).map((e) => e.patches)
462
+ )
462
463
 
463
464
  // restore the attached state after the operation was made
464
465
  if (event.attachedState) {
@@ -601,11 +602,11 @@ export class UndoManager {
601
602
  const genThrow = gen.throw.bind(gen)
602
603
 
603
604
  const promise = new Promise<R>((resolve, reject) => {
604
- function onFulfilled(res: any): void {
605
+ function resume(genFn: (value: any) => IteratorResult<any>, value: unknown): void {
605
606
  group.resume()
606
607
  let ret: unknown
607
608
  try {
608
- ret = genNext(res)
609
+ ret = genFn(value)
609
610
  } catch (e) {
610
611
  group.end()
611
612
  reject(e as Error)
@@ -616,19 +617,12 @@ export class UndoManager {
616
617
  next(ret)
617
618
  }
618
619
 
619
- function onRejected(err: unknown): void {
620
- group.resume()
621
- let ret: unknown
622
- try {
623
- ret = genThrow(err)
624
- } catch (e) {
625
- group.end()
626
- reject(e as Error)
627
- return
628
- }
620
+ function onFulfilled(res: unknown): void {
621
+ resume(genNext, res)
622
+ }
629
623
 
630
- group.pause()
631
- next(ret)
624
+ function onRejected(err: unknown): void {
625
+ resume(genThrow, err)
632
626
  }
633
627
 
634
628
  function next(ret: any): void {
@@ -162,25 +162,19 @@ class ContextClass<T> implements Context<T> {
162
162
  })
163
163
  })
164
164
 
165
- set = action((node: object, value: T) => {
166
- assertTweakedObject(node, "node")
167
-
168
- this.nodeContextValue.set(node, {
169
- type: "value",
170
- value,
171
- })
172
- this.reportNodeAtomChanged(node)
173
- })
174
-
175
- private _setComputed(node: object, computedValueFn: IComputedValue<T>) {
165
+ private setNodeContextValue(node: object, contextValue: ContextValue<T>) {
176
166
  assertTweakedObject(node, "node")
177
167
 
178
- this.nodeContextValue.set(node, { type: "computed", value: computedValueFn })
168
+ this.nodeContextValue.set(node, contextValue)
179
169
  this.reportNodeAtomChanged(node)
180
170
  }
181
171
 
172
+ set = action((node: object, value: T) => {
173
+ this.setNodeContextValue(node, { type: "value", value })
174
+ })
175
+
182
176
  setComputed = action((node: object, valueFn: () => T) => {
183
- this._setComputed(node, computed(valueFn))
177
+ this.setNodeContextValue(node, { type: "computed", value: computed(valueFn) })
184
178
  })
185
179
 
186
180
  unset = action((node: object) => {
@@ -190,42 +184,27 @@ class ContextClass<T> implements Context<T> {
190
184
  this.reportNodeAtomChanged(node)
191
185
  })
192
186
 
193
- apply = action(<R>(fn: () => R, value: T): R => {
187
+ private applyOverride<R>(fn: () => R, contextValue: ContextValue<T>): R {
194
188
  const old = this.overrideContextValue.get()
195
- this.overrideContextValue.set({
196
- type: "value",
197
- value,
198
- })
189
+ this.overrideContextValue.set(contextValue)
199
190
 
200
191
  try {
201
192
  const ret = fn()
202
193
  if (isTweakedObject(ret, false)) {
203
- this.set(ret, value)
194
+ this.setNodeContextValue(ret, contextValue)
204
195
  }
205
196
  return ret
206
197
  } finally {
207
198
  this.overrideContextValue.set(old)
208
199
  }
200
+ }
201
+
202
+ apply = action(<R>(fn: () => R, value: T): R => {
203
+ return this.applyOverride(fn, { type: "value", value })
209
204
  })
210
205
 
211
206
  applyComputed = action(<R>(fn: () => R, valueFn: () => T): R => {
212
- const computedValueFn = computed(valueFn)
213
-
214
- const old = this.overrideContextValue.get()
215
- this.overrideContextValue.set({
216
- type: "computed",
217
- value: computedValueFn,
218
- })
219
-
220
- try {
221
- const ret = fn()
222
- if (isTweakedObject(ret, false)) {
223
- this._setComputed(ret, computedValueFn)
224
- }
225
- return ret
226
- } finally {
227
- this.overrideContextValue.set(old)
228
- }
207
+ return this.applyOverride(fn, { type: "computed", value: computed(valueFn) })
229
208
  })
230
209
 
231
210
  constructor(defaultValue?: T) {
@@ -13,37 +13,14 @@ export function isDataModel(model: unknown): model is AnyDataModel {
13
13
  return model instanceof BaseDataModel
14
14
  }
15
15
 
16
- /**
17
- * @internal
18
- *
19
- * Asserts something is actually a data model.
20
- *
21
- * @param model
22
- * @param argName
23
- */
24
- export function assertIsDataModel(
25
- model: unknown,
26
- argName: string,
27
- customErrMsg = "must be a data model instance"
28
- ): asserts model is AnyDataModel {
29
- if (!isDataModel(model)) {
30
- throw failure(`${argName} ${customErrMsg}`)
31
- }
32
- }
33
-
34
16
  /**
35
17
  * @internal
36
18
  */
37
19
  export function isDataModelClass(modelClass: unknown): modelClass is ModelClass<AnyDataModel> {
38
- if (typeof modelClass !== "function") {
39
- return false
40
- }
41
-
42
- if (modelClass !== BaseDataModel && !(modelClass.prototype instanceof BaseDataModel)) {
43
- return false
44
- }
45
-
46
- return true
20
+ return (
21
+ typeof modelClass === "function" &&
22
+ (modelClass === BaseDataModel || modelClass.prototype instanceof BaseDataModel)
23
+ )
47
24
  }
48
25
 
49
26
  /**
@@ -57,7 +34,7 @@ export function assertIsDataModelClass(
57
34
  throw failure(`${argName} must be a class`)
58
35
  }
59
36
 
60
- if (modelClass !== BaseDataModel && !(modelClass.prototype instanceof BaseDataModel)) {
37
+ if (!isDataModelClass(modelClass)) {
61
38
  throw failure(`${argName} must extend DataModel`)
62
39
  }
63
40
  }