mobx-keystone 1.20.0 → 1.22.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 (174) hide show
  1. package/CHANGELOG.md +864 -853
  2. package/README.md +61 -61
  3. package/dist/mobx-keystone.esm.js +11336 -11148
  4. package/dist/mobx-keystone.esm.mjs +11336 -11148
  5. package/dist/mobx-keystone.umd.js +12229 -11738
  6. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +1 -1
  7. package/dist/types/modelShared/prop.d.ts +5 -0
  8. package/dist/types/redux/connectReduxDevTools.d.ts +17 -4
  9. package/dist/types/ref/Ref.d.ts +1 -1
  10. package/dist/types/treeUtils/sandbox.d.ts +1 -1
  11. package/dist/types/types/TypeChecker.d.ts +1 -1
  12. package/dist/types/types/arrayBased/typesArray.d.ts +1 -1
  13. package/dist/types/types/arrayBased/typesTuple.d.ts +1 -1
  14. package/dist/types/types/objectBased/typesArraySet.d.ts +1 -1
  15. package/dist/types/types/objectBased/typesDataModelData.d.ts +1 -1
  16. package/dist/types/types/objectBased/typesModel.d.ts +1 -1
  17. package/dist/types/types/objectBased/typesObject.d.ts +3 -3
  18. package/dist/types/types/objectBased/typesObjectMap.d.ts +1 -1
  19. package/dist/types/types/objectBased/typesRecord.d.ts +1 -1
  20. package/dist/types/types/primitiveBased/typesPrimitive.d.ts +1 -1
  21. package/dist/types/types/types.d.ts +3 -3
  22. package/dist/types/types/utility/typesCodec.d.ts +1 -6
  23. package/dist/types/types/utility/typesCodecCore.d.ts +1 -1
  24. package/dist/types/types/utility/typesOr.d.ts +1 -1
  25. package/dist/types/types/utility/typesRefinement.d.ts +1 -1
  26. package/dist/types/types/utility/typesSkipCheck.d.ts +1 -1
  27. package/dist/types/types/utility/typesTag.d.ts +1 -1
  28. package/dist/types/wrappers/ObjectMap.d.ts +2 -0
  29. package/dist/types/wrappers/asMap.d.ts +10 -6
  30. package/package.json +14 -12
  31. package/src/action/applyAction.ts +125 -114
  32. package/src/action/applyMethodCall.ts +37 -37
  33. package/src/action/isModelAction.ts +16 -16
  34. package/src/action/middleware.ts +137 -137
  35. package/src/action/modelFlow.ts +240 -240
  36. package/src/action/modelFlowPromiseGenerator.ts +127 -125
  37. package/src/action/pendingActions.ts +42 -42
  38. package/src/action/wrapInAction.ts +156 -156
  39. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +1 -1
  40. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +172 -172
  41. package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +20 -20
  42. package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +17 -17
  43. package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +42 -42
  44. package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +50 -50
  45. package/src/actionMiddlewares/actionSerialization/objectSnapshotSerializer.ts +21 -21
  46. package/src/actionMiddlewares/actionSerialization/plainObjectSerializer.ts +24 -32
  47. package/src/actionMiddlewares/actionSerialization/primitiveSerializer.ts +51 -51
  48. package/src/actionMiddlewares/actionSerialization/setSerializer.ts +37 -37
  49. package/src/actionMiddlewares/actionTrackingMiddleware.ts +3 -3
  50. package/src/actionMiddlewares/onActionMiddleware.ts +2 -2
  51. package/src/actionMiddlewares/readonlyMiddleware.ts +2 -2
  52. package/src/actionMiddlewares/transactionMiddleware.ts +4 -4
  53. package/src/actionMiddlewares/undoMiddleware.ts +804 -796
  54. package/src/actionMiddlewares/utils.ts +2 -2
  55. package/src/computedTree/computedTree.ts +143 -143
  56. package/src/context/context.ts +1 -1
  57. package/src/dataModel/BaseDataModel.ts +194 -192
  58. package/src/dataModel/actions.ts +1 -1
  59. package/src/dataModel/newDataModel.ts +57 -57
  60. package/src/deepChange/index.ts +1 -1
  61. package/src/deepChange/onDeepChange.ts +244 -244
  62. package/src/frozen/Frozen.ts +146 -146
  63. package/src/globalConfig/globalConfig.ts +107 -107
  64. package/src/index.ts +24 -24
  65. package/src/model/BaseModel.ts +275 -273
  66. package/src/model/ModelConstructorOptions.ts +15 -15
  67. package/src/model/getModelMetadata.ts +51 -51
  68. package/src/model/index.ts +6 -6
  69. package/src/model/mixins.ts +288 -288
  70. package/src/model/newModel.ts +301 -301
  71. package/src/model/utils.ts +249 -249
  72. package/src/modelShared/modelDecorator.ts +260 -253
  73. package/src/modelShared/modelInfo.ts +50 -50
  74. package/src/modelShared/modelSymbols.ts +3 -3
  75. package/src/modelShared/newModel.ts +25 -25
  76. package/src/modelShared/prop.ts +713 -684
  77. package/src/modelShared/sharedInternalModel.ts +427 -417
  78. package/src/modelShared/utils.ts +24 -24
  79. package/src/parent/core.ts +65 -65
  80. package/src/parent/coreObjectChildren.ts +180 -180
  81. package/src/parent/detach.ts +47 -47
  82. package/src/parent/findParent.ts +1 -1
  83. package/src/parent/getChildrenObjects.ts +25 -25
  84. package/src/parent/path.ts +386 -381
  85. package/src/parent/setParent.ts +149 -149
  86. package/src/parent/walkTree.ts +1 -1
  87. package/src/patch/applyPatches.ts +187 -187
  88. package/src/patch/jsonPatch.ts +108 -108
  89. package/src/patch/patchRecorder.ts +6 -1
  90. package/src/redux/connectReduxDevTools.ts +39 -7
  91. package/src/redux/redux.ts +6 -2
  92. package/src/ref/Ref.ts +1 -1
  93. package/src/ref/core.ts +339 -345
  94. package/src/ref/customRef.ts +3 -3
  95. package/src/ref/rootRef.ts +100 -100
  96. package/src/rootStore/attachDetach.ts +89 -89
  97. package/src/rootStore/rootStore.ts +1 -1
  98. package/src/snapshot/SnapshotOf.ts +104 -104
  99. package/src/snapshot/SnapshotProcessingError.ts +45 -45
  100. package/src/snapshot/applySnapshot.ts +177 -181
  101. package/src/snapshot/cloneTreeValue.ts +30 -30
  102. package/src/snapshot/fromArraySnapshot.ts +27 -33
  103. package/src/snapshot/fromModelSnapshot.ts +70 -70
  104. package/src/snapshot/fromPlainObjectSnapshot.ts +47 -42
  105. package/src/snapshot/fromSnapshot.ts +198 -198
  106. package/src/snapshot/getSnapshot.ts +80 -79
  107. package/src/snapshot/index.ts +8 -8
  108. package/src/snapshot/internal.ts +230 -230
  109. package/src/snapshot/reconcileArraySnapshot.ts +70 -70
  110. package/src/snapshot/reconcileModelSnapshot.ts +155 -150
  111. package/src/snapshot/reconcilePlainObjectSnapshot.ts +72 -72
  112. package/src/snapshot/reconcileSnapshot.ts +88 -88
  113. package/src/standardActions/actions.ts +64 -64
  114. package/src/standardActions/objectActions.ts +60 -60
  115. package/src/transforms/ImmutableDate.ts +93 -93
  116. package/src/transforms/asMap.ts +50 -50
  117. package/src/transforms/asSet.ts +22 -22
  118. package/src/transforms/bigint.ts +20 -20
  119. package/src/transforms/date.ts +40 -40
  120. package/src/treeUtils/deepEquals.ts +55 -55
  121. package/src/treeUtils/sandbox.ts +6 -3
  122. package/src/tweaker/TweakerPriority.ts +9 -9
  123. package/src/tweaker/tweak.ts +4 -3
  124. package/src/tweaker/tweakArray.ts +530 -475
  125. package/src/tweaker/tweakFrozen.ts +42 -42
  126. package/src/tweaker/tweakModel.ts +1 -1
  127. package/src/tweaker/tweakPlainObject.ts +355 -355
  128. package/src/tweaker/typeChecking.ts +101 -101
  129. package/src/types/SnapshotTypeMismatchError.ts +46 -46
  130. package/src/types/TypeCheckError.ts +183 -184
  131. package/src/types/TypeChecker.ts +202 -185
  132. package/src/types/arrayBased/typesArray.ts +136 -137
  133. package/src/types/arrayBased/typesTuple.ts +138 -139
  134. package/src/types/createPerEntryCachedCheck.ts +166 -160
  135. package/src/types/index.ts +6 -6
  136. package/src/types/objectBased/typesArraySet.ts +133 -134
  137. package/src/types/objectBased/typesDataModelData.ts +203 -204
  138. package/src/types/objectBased/typesModel.ts +220 -221
  139. package/src/types/objectBased/typesObject.ts +308 -310
  140. package/src/types/objectBased/typesObjectMap.ts +142 -143
  141. package/src/types/objectBased/typesRecord.ts +156 -157
  142. package/src/types/objectBased/typesRef.ts +89 -89
  143. package/src/types/primitiveBased/typesEnum.ts +63 -63
  144. package/src/types/primitiveBased/typesPrimitive.ts +219 -220
  145. package/src/types/registerDefaultStandardTypeResolvers.ts +21 -21
  146. package/src/types/resolveTypeChecker.ts +78 -78
  147. package/src/types/schemas.ts +208 -208
  148. package/src/types/tProp.ts +277 -268
  149. package/src/types/typeCheck.ts +21 -21
  150. package/src/types/typeCheckErrorUtils.ts +23 -23
  151. package/src/types/types.ts +142 -142
  152. package/src/types/utility/typeToStoredData.ts +37 -37
  153. package/src/types/utility/typesBigInt.ts +15 -15
  154. package/src/types/utility/typesCodec.ts +41 -38
  155. package/src/types/utility/typesCodecCore.ts +178 -178
  156. package/src/types/utility/typesCodecSupport.ts +806 -806
  157. package/src/types/utility/typesDate.ts +27 -27
  158. package/src/types/utility/typesMap.ts +253 -222
  159. package/src/types/utility/typesOr.ts +263 -259
  160. package/src/types/utility/typesRefinement.ts +131 -125
  161. package/src/types/utility/typesSet.ts +110 -110
  162. package/src/types/utility/typesSkipCheck.ts +69 -70
  163. package/src/types/utility/typesTag.ts +88 -82
  164. package/src/types/utility/typesUnchecked.ts +40 -40
  165. package/src/utils/AnyFunction.ts +4 -4
  166. package/src/utils/ModelPool.ts +59 -59
  167. package/src/utils/chainFns.ts +18 -18
  168. package/src/utils/decorators.ts +274 -274
  169. package/src/utils/errorDiagnostics.ts +204 -204
  170. package/src/utils/index.ts +443 -372
  171. package/src/wrappers/ArraySet.ts +156 -156
  172. package/src/wrappers/ObjectMap.ts +155 -136
  173. package/src/wrappers/asMap.ts +65 -30
  174. package/src/wrappers/asSet.ts +3 -3
package/src/ref/core.ts CHANGED
@@ -1,345 +1,339 @@
1
- import {
2
- action,
3
- ObservableSet,
4
- observable,
5
- reaction,
6
- when,
7
- } from "mobx"
8
- import { isModel } from "../model/utils"
9
- import type { ModelClass } from "../modelShared/BaseModelShared"
10
- import { model } from "../modelShared/modelDecorator"
11
- import {
12
- getDeepObjectChildren,
13
- registerDeepObjectChildrenExtension,
14
- } from "../parent/coreObjectChildren"
15
- import { fastGetRoot } from "../parent/path"
16
- import { ComputedWalkTreeAggregate, computedWalkTreeAggregate } from "../parent/walkTree"
17
- import { assertTweakedObject } from "../tweaker/core"
18
- import { assertIsObject, failure } from "../utils"
19
- import { getOrCreate } from "../utils/mapUtils"
20
- import { Ref, RefConstructor } from "./Ref"
21
-
22
- interface BackRefs<T extends object> {
23
- all: ObservableSet<Ref<T>>
24
- byType: WeakMap<RefConstructor<T>, ObservableSet<Ref<T>>>
25
- }
26
-
27
- /**
28
- * Back-references from object to the references that point to it.
29
- */
30
- const objectBackRefs = new WeakMap<object, BackRefs<any>>()
31
-
32
- /**
33
- * Reference resolver type.
34
- */
35
- export type RefResolver<T extends object> = (ref: Ref<T>) => T | undefined
36
-
37
- /**
38
- * Reference ID resolver type.
39
- */
40
- export type RefIdResolver = (target: object) => string | undefined
41
-
42
- /**
43
- * Type for the callback called when a reference resolved value changes.
44
- */
45
- export type RefOnResolvedValueChange<T extends object> = (
46
- ref: Ref<T>,
47
- newValue: T | undefined,
48
- oldValue: T | undefined
49
- ) => void
50
-
51
- const trackedRefs = new WeakSet<Ref<object>>()
52
-
53
- /**
54
- * @internal
55
- */
56
- export function internalCustomRef<T extends object>(
57
- modelTypeId: string,
58
- resolverGen: (ref: Ref<T>) => RefResolver<T>,
59
- getId: RefIdResolver,
60
- onResolvedValueChange: RefOnResolvedValueChange<T> | undefined
61
- ): RefConstructor<T> {
62
- class CustomRefBase extends Ref<T> {
63
- private resolver?: RefResolver<T>
64
- private trackingStarted = false
65
-
66
- protected resolve(): T | undefined {
67
- if (!this.resolver) {
68
- this.resolver = resolverGen(this)
69
- }
70
-
71
- return this.resolver(this)
72
- }
73
-
74
- private savedOldTarget: T | undefined
75
-
76
- protected internalForceUpdateBackRefs(newTarget: T | undefined) {
77
- const oldTarget = this.savedOldTarget
78
- // update early in case of thrown exceptions
79
- this.savedOldTarget = newTarget
80
-
81
- if (newTarget === oldTarget) {
82
- return
83
- }
84
-
85
- updateBackRefs(this, thisRefConstructor, newTarget, oldTarget)
86
- }
87
-
88
- protected startTracking(initialTarget: T | undefined): boolean {
89
- if (this.trackingStarted) {
90
- return false
91
- }
92
-
93
- this.trackingStarted = true
94
- trackedRefs.add(this as Ref<object>)
95
- this.internalForceUpdateBackRefs(initialTarget)
96
-
97
- // according to mwestrate this won't leak as long as we don't keep the disposer around
98
- reaction(
99
- () => this.maybeCurrent,
100
- (newTarget, oldTarget) => {
101
- this.internalForceUpdateBackRefs(newTarget)
102
-
103
- if (onResolvedValueChange && newTarget !== oldTarget) {
104
- onResolvedValueChange(this, newTarget, oldTarget)
105
- }
106
- }
107
- )
108
-
109
- return true
110
- }
111
-
112
- @action
113
- forceUpdateBackRefs() {
114
- const currentTarget = this.maybeCurrent
115
- if (!this.startTracking(currentTarget)) {
116
- this.internalForceUpdateBackRefs(currentTarget)
117
- }
118
- }
119
- }
120
-
121
- const CustomRef = (() => {
122
- if (onResolvedValueChange) {
123
- @model(modelTypeId)
124
- class TrackedCustomRef extends CustomRefBase {
125
- onInit() {
126
- this.startTracking(this.maybeCurrent)
127
- }
128
- }
129
-
130
- return TrackedCustomRef
131
- }
132
-
133
- @model(modelTypeId)
134
- class LazyCustomRef extends CustomRefBase {}
135
-
136
- return LazyCustomRef
137
- })()
138
-
139
- const fn = (target: T) => {
140
- let id: string | undefined
141
- if (typeof target === "string") {
142
- id = target
143
- } else {
144
- assertIsObject(target, "target")
145
- id = getId(target)
146
- }
147
-
148
- if (typeof id !== "string") {
149
- throw failure("ref target object must have an id of string type")
150
- }
151
-
152
- const ref = new CustomRef({
153
- id,
154
- })
155
-
156
- return ref
157
- }
158
- fn.refClass = CustomRef
159
-
160
- const thisRefConstructor = fn as any as RefConstructor<T>
161
-
162
- return thisRefConstructor
163
- }
164
-
165
- /**
166
- * Uses a model `getRefId()` method whenever possible to get a reference ID.
167
- * If the model does not have an implementation of that method it returns `undefined`.
168
- * If the model has an implementation, but that implementation returns anything other than
169
- * a `string` it will throw.
170
- *
171
- * @param target Target object to get the ID from.
172
- * @returns The string ID or `undefined`.
173
- */
174
- export function getModelRefId(target: object): string | undefined {
175
- if (isModel(target)) {
176
- const id = target.getRefId()
177
- if (id !== undefined && typeof id !== "string") {
178
- throw failure("'getRefId()' must return a string or undefined when present")
179
- }
180
- return id
181
- }
182
- return undefined
183
- }
184
-
185
- // one computed id tree per id function
186
- const computedIdTrees = new WeakMap<
187
- (node: object) => string | undefined,
188
- ComputedWalkTreeAggregate<string>
189
- >()
190
-
191
- /**
192
- * Resolves a node given its ID.
193
- *
194
- * @template T Target object type.
195
- * @param root Node where to start the search. The search will be done on it and all its children.
196
- * @param id ID to search for.
197
- * @param getId Function that will be used to get the ID from an object (`getModelRefId` by default).
198
- * @returns The node found or `undefined` if none.
199
- */
200
- export function resolveId<T extends object>(
201
- root: object,
202
- id: string,
203
- getId: RefIdResolver = getModelRefId
204
- ): T | undefined {
205
- // cache/reuse computedIdTrees for same getId function
206
- const computedIdTree = getOrCreate(computedIdTrees, getId, () =>
207
- computedWalkTreeAggregate<string>((node) => getId(node))
208
- )
209
-
210
- const idMap = computedIdTree.walk(root)
211
- return idMap ? (idMap.get(id) as T | undefined) : undefined
212
- }
213
-
214
- function getBackRefs<T extends object>(
215
- target: T,
216
- refType?: RefConstructor<T>
217
- ): ObservableSet<Ref<T>> {
218
- let backRefs = objectBackRefs.get(target) as BackRefs<T> | undefined
219
- if (!backRefs) {
220
- backRefs = {
221
- all: observable.set(undefined, { deep: false }),
222
- byType: new WeakMap(),
223
- }
224
- objectBackRefs.set(target, backRefs)
225
- }
226
-
227
- if (refType) {
228
- let byType = backRefs.byType.get(refType)
229
- if (!byType) {
230
- byType = observable.set(undefined, { deep: false })
231
- backRefs.byType.set(refType, byType)
232
- }
233
- return byType
234
- } else {
235
- return backRefs.all
236
- }
237
- }
238
-
239
- /**
240
- * Gets all references that resolve to a given object.
241
- *
242
- * @template T Referenced object type.
243
- * @param target Node the references point to.
244
- * @param refType Pass it to filter by only references of a given type, or omit / pass `undefined` to get references of any type.
245
- * @param options Options.
246
- * @returns An observable set with all reference objects that point to the given object.
247
- */
248
- export function getRefsResolvingTo<T extends object>(
249
- target: T,
250
- refType?: RefConstructor<T>,
251
- options?: {
252
- updateAllRefsIfNeeded?: boolean
253
- }
254
- ): ObservableSet<Ref<T>> {
255
- assertTweakedObject(target, "target")
256
-
257
- const shouldForceUpdateTrackedRefs = options?.updateAllRefsIfNeeded && isReactionDelayed()
258
- const refsChecked = new Set<Ref<object>>()
259
- const updateRef = (ref: Ref<any>) => {
260
- if (!refsChecked.has(ref)) {
261
- if (
262
- (!refType || ref instanceof refType.refClass) &&
263
- (shouldForceUpdateTrackedRefs || !trackedRefs.has(ref as Ref<object>))
264
- ) {
265
- ref.forceUpdateBackRefs()
266
- }
267
- refsChecked.add(ref)
268
- }
269
- }
270
-
271
- if (shouldForceUpdateTrackedRefs) {
272
- // in this case the reference update might have been delayed
273
- // so we will make a best effort to update them
274
- const oldBackRefs = getBackRefs(target, refType)
275
- oldBackRefs.forEach(updateRef)
276
- }
277
-
278
- const refsChildrenOfRoot = getDeepChildrenRefs(getDeepObjectChildren(fastGetRoot(target, true)))
279
- let refs: Set<Ref<object>> | undefined
280
- if (refType) {
281
- refs = refsChildrenOfRoot.byType.get(refType.refClass)
282
- } else {
283
- refs = refsChildrenOfRoot.all
284
- }
285
- refs?.forEach(updateRef)
286
-
287
- return getBackRefs(target, refType)
288
- }
289
-
290
- const updateBackRefs = action(
291
- "updateBackRefs",
292
- <T extends object>(
293
- ref: Ref<T>,
294
- refClass: RefConstructor<T>,
295
- newTarget: T | undefined,
296
- oldTarget: T | undefined
297
- ) => {
298
- if (newTarget === oldTarget) {
299
- return
300
- }
301
-
302
- if (oldTarget) {
303
- getBackRefs(oldTarget).delete(ref)
304
- getBackRefs(oldTarget, refClass as RefConstructor<any>).delete(ref)
305
- }
306
- if (newTarget) {
307
- getBackRefs(newTarget).add(ref)
308
- getBackRefs(newTarget, refClass as RefConstructor<any>).add(ref)
309
- }
310
- }
311
- )
312
-
313
- function isReactionDelayed() {
314
- let reactionDelayed = true
315
- const dispose = when(
316
- () => true,
317
- () => {
318
- reactionDelayed = false
319
- }
320
- )
321
- dispose()
322
- return reactionDelayed
323
- }
324
-
325
- interface DeepChildrenRefs {
326
- all: Set<Ref<any>>
327
- byType: WeakMap<ModelClass<Ref<any>>, Set<Ref<any>>>
328
- }
329
-
330
- const getDeepChildrenRefs = registerDeepObjectChildrenExtension<DeepChildrenRefs>({
331
- initData() {
332
- return {
333
- all: new Set(),
334
- byType: new WeakMap(),
335
- }
336
- },
337
-
338
- addNode(node, data) {
339
- if (node instanceof Ref) {
340
- data.all.add(node)
341
- const refsByThisType = getOrCreate(data.byType, node.constructor, () => new Set())
342
- refsByThisType.add(node)
343
- }
344
- },
345
- })
1
+ import { action, type ObservableSet, observable, reaction, when } from "mobx"
2
+ import { isModel } from "../model/utils"
3
+ import type { ModelClass } from "../modelShared/BaseModelShared"
4
+ import { model } from "../modelShared/modelDecorator"
5
+ import {
6
+ getDeepObjectChildren,
7
+ registerDeepObjectChildrenExtension,
8
+ } from "../parent/coreObjectChildren"
9
+ import { fastGetRoot } from "../parent/path"
10
+ import { type ComputedWalkTreeAggregate, computedWalkTreeAggregate } from "../parent/walkTree"
11
+ import { assertTweakedObject } from "../tweaker/core"
12
+ import { assertIsObject, failure } from "../utils"
13
+ import { getOrCreate } from "../utils/mapUtils"
14
+ import { Ref, type RefConstructor } from "./Ref"
15
+
16
+ interface BackRefs<T extends object> {
17
+ all: ObservableSet<Ref<T>>
18
+ byType: WeakMap<RefConstructor<T>, ObservableSet<Ref<T>>>
19
+ }
20
+
21
+ /**
22
+ * Back-references from object to the references that point to it.
23
+ */
24
+ const objectBackRefs = new WeakMap<object, BackRefs<any>>()
25
+
26
+ /**
27
+ * Reference resolver type.
28
+ */
29
+ export type RefResolver<T extends object> = (ref: Ref<T>) => T | undefined
30
+
31
+ /**
32
+ * Reference ID resolver type.
33
+ */
34
+ export type RefIdResolver = (target: object) => string | undefined
35
+
36
+ /**
37
+ * Type for the callback called when a reference resolved value changes.
38
+ */
39
+ export type RefOnResolvedValueChange<T extends object> = (
40
+ ref: Ref<T>,
41
+ newValue: T | undefined,
42
+ oldValue: T | undefined
43
+ ) => void
44
+
45
+ const trackedRefs = new WeakSet<Ref<object>>()
46
+
47
+ /**
48
+ * @internal
49
+ */
50
+ export function internalCustomRef<T extends object>(
51
+ modelTypeId: string,
52
+ resolverGen: (ref: Ref<T>) => RefResolver<T>,
53
+ getId: RefIdResolver,
54
+ onResolvedValueChange: RefOnResolvedValueChange<T> | undefined
55
+ ): RefConstructor<T> {
56
+ class CustomRefBase extends Ref<T> {
57
+ private resolver?: RefResolver<T>
58
+ private trackingStarted = false
59
+
60
+ protected resolve(): T | undefined {
61
+ if (!this.resolver) {
62
+ this.resolver = resolverGen(this)
63
+ }
64
+
65
+ return this.resolver(this)
66
+ }
67
+
68
+ private savedOldTarget: T | undefined
69
+
70
+ protected internalForceUpdateBackRefs(newTarget: T | undefined) {
71
+ const oldTarget = this.savedOldTarget
72
+ // update early in case of thrown exceptions
73
+ this.savedOldTarget = newTarget
74
+
75
+ if (newTarget === oldTarget) {
76
+ return
77
+ }
78
+
79
+ updateBackRefs(this, thisRefConstructor, newTarget, oldTarget)
80
+ }
81
+
82
+ protected startTracking(initialTarget: T | undefined): boolean {
83
+ if (this.trackingStarted) {
84
+ return false
85
+ }
86
+
87
+ this.trackingStarted = true
88
+ trackedRefs.add(this as Ref<object>)
89
+ this.internalForceUpdateBackRefs(initialTarget)
90
+
91
+ // according to mwestrate this won't leak as long as we don't keep the disposer around
92
+ reaction(
93
+ () => this.maybeCurrent,
94
+ (newTarget, oldTarget) => {
95
+ this.internalForceUpdateBackRefs(newTarget)
96
+
97
+ if (onResolvedValueChange && newTarget !== oldTarget) {
98
+ onResolvedValueChange(this, newTarget, oldTarget)
99
+ }
100
+ }
101
+ )
102
+
103
+ return true
104
+ }
105
+
106
+ @action
107
+ forceUpdateBackRefs() {
108
+ const currentTarget = this.maybeCurrent
109
+ if (!this.startTracking(currentTarget)) {
110
+ this.internalForceUpdateBackRefs(currentTarget)
111
+ }
112
+ }
113
+ }
114
+
115
+ const CustomRef = (() => {
116
+ if (onResolvedValueChange) {
117
+ @model(modelTypeId)
118
+ class TrackedCustomRef extends CustomRefBase {
119
+ onInit() {
120
+ this.startTracking(this.maybeCurrent)
121
+ }
122
+ }
123
+
124
+ return TrackedCustomRef
125
+ }
126
+
127
+ @model(modelTypeId)
128
+ class LazyCustomRef extends CustomRefBase {}
129
+
130
+ return LazyCustomRef
131
+ })()
132
+
133
+ const fn = (target: T) => {
134
+ let id: string | undefined
135
+ if (typeof target === "string") {
136
+ id = target
137
+ } else {
138
+ assertIsObject(target, "target")
139
+ id = getId(target)
140
+ }
141
+
142
+ if (typeof id !== "string") {
143
+ throw failure("ref target object must have an id of string type")
144
+ }
145
+
146
+ const ref = new CustomRef({
147
+ id,
148
+ })
149
+
150
+ return ref
151
+ }
152
+ fn.refClass = CustomRef
153
+
154
+ const thisRefConstructor = fn as any as RefConstructor<T>
155
+
156
+ return thisRefConstructor
157
+ }
158
+
159
+ /**
160
+ * Uses a model `getRefId()` method whenever possible to get a reference ID.
161
+ * If the model does not have an implementation of that method it returns `undefined`.
162
+ * If the model has an implementation, but that implementation returns anything other than
163
+ * a `string` it will throw.
164
+ *
165
+ * @param target Target object to get the ID from.
166
+ * @returns The string ID or `undefined`.
167
+ */
168
+ export function getModelRefId(target: object): string | undefined {
169
+ if (isModel(target)) {
170
+ const id = target.getRefId()
171
+ if (id !== undefined && typeof id !== "string") {
172
+ throw failure("'getRefId()' must return a string or undefined when present")
173
+ }
174
+ return id
175
+ }
176
+ return undefined
177
+ }
178
+
179
+ // one computed id tree per id function
180
+ const computedIdTrees = new WeakMap<
181
+ (node: object) => string | undefined,
182
+ ComputedWalkTreeAggregate<string>
183
+ >()
184
+
185
+ /**
186
+ * Resolves a node given its ID.
187
+ *
188
+ * @template T Target object type.
189
+ * @param root Node where to start the search. The search will be done on it and all its children.
190
+ * @param id ID to search for.
191
+ * @param getId Function that will be used to get the ID from an object (`getModelRefId` by default).
192
+ * @returns The node found or `undefined` if none.
193
+ */
194
+ export function resolveId<T extends object>(
195
+ root: object,
196
+ id: string,
197
+ getId: RefIdResolver = getModelRefId
198
+ ): T | undefined {
199
+ // cache/reuse computedIdTrees for same getId function
200
+ const computedIdTree = getOrCreate(computedIdTrees, getId, () =>
201
+ computedWalkTreeAggregate<string>((node) => getId(node))
202
+ )
203
+
204
+ const idMap = computedIdTree.walk(root)
205
+ return idMap ? (idMap.get(id) as T | undefined) : undefined
206
+ }
207
+
208
+ function getBackRefs<T extends object>(
209
+ target: T,
210
+ refType?: RefConstructor<T>
211
+ ): ObservableSet<Ref<T>> {
212
+ let backRefs = objectBackRefs.get(target) as BackRefs<T> | undefined
213
+ if (!backRefs) {
214
+ backRefs = {
215
+ all: observable.set(undefined, { deep: false }),
216
+ byType: new WeakMap(),
217
+ }
218
+ objectBackRefs.set(target, backRefs)
219
+ }
220
+
221
+ if (refType) {
222
+ let byType = backRefs.byType.get(refType)
223
+ if (!byType) {
224
+ byType = observable.set(undefined, { deep: false })
225
+ backRefs.byType.set(refType, byType)
226
+ }
227
+ return byType
228
+ } else {
229
+ return backRefs.all
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Gets all references that resolve to a given object.
235
+ *
236
+ * @template T Referenced object type.
237
+ * @param target Node the references point to.
238
+ * @param refType Pass it to filter by only references of a given type, or omit / pass `undefined` to get references of any type.
239
+ * @param options Options.
240
+ * @returns An observable set with all reference objects that point to the given object.
241
+ */
242
+ export function getRefsResolvingTo<T extends object>(
243
+ target: T,
244
+ refType?: RefConstructor<T>,
245
+ options?: {
246
+ updateAllRefsIfNeeded?: boolean
247
+ }
248
+ ): ObservableSet<Ref<T>> {
249
+ assertTweakedObject(target, "target")
250
+
251
+ const shouldForceUpdateTrackedRefs = options?.updateAllRefsIfNeeded && isReactionDelayed()
252
+ const refsChecked = new Set<Ref<object>>()
253
+ const updateRef = (ref: Ref<any>) => {
254
+ if (!refsChecked.has(ref)) {
255
+ if (
256
+ (!refType || ref instanceof refType.refClass) &&
257
+ (shouldForceUpdateTrackedRefs || !trackedRefs.has(ref as Ref<object>))
258
+ ) {
259
+ ref.forceUpdateBackRefs()
260
+ }
261
+ refsChecked.add(ref)
262
+ }
263
+ }
264
+
265
+ if (shouldForceUpdateTrackedRefs) {
266
+ // in this case the reference update might have been delayed
267
+ // so we will make a best effort to update them
268
+ const oldBackRefs = getBackRefs(target, refType)
269
+ oldBackRefs.forEach(updateRef)
270
+ }
271
+
272
+ const refsChildrenOfRoot = getDeepChildrenRefs(getDeepObjectChildren(fastGetRoot(target, true)))
273
+ let refs: Set<Ref<object>> | undefined
274
+ if (refType) {
275
+ refs = refsChildrenOfRoot.byType.get(refType.refClass)
276
+ } else {
277
+ refs = refsChildrenOfRoot.all
278
+ }
279
+ refs?.forEach(updateRef)
280
+
281
+ return getBackRefs(target, refType)
282
+ }
283
+
284
+ const updateBackRefs = action(
285
+ "updateBackRefs",
286
+ <T extends object>(
287
+ ref: Ref<T>,
288
+ refClass: RefConstructor<T>,
289
+ newTarget: T | undefined,
290
+ oldTarget: T | undefined
291
+ ) => {
292
+ if (newTarget === oldTarget) {
293
+ return
294
+ }
295
+
296
+ if (oldTarget) {
297
+ getBackRefs(oldTarget).delete(ref)
298
+ getBackRefs(oldTarget, refClass as RefConstructor<any>).delete(ref)
299
+ }
300
+ if (newTarget) {
301
+ getBackRefs(newTarget).add(ref)
302
+ getBackRefs(newTarget, refClass as RefConstructor<any>).add(ref)
303
+ }
304
+ }
305
+ )
306
+
307
+ function isReactionDelayed() {
308
+ let reactionDelayed = true
309
+ const dispose = when(
310
+ () => true,
311
+ () => {
312
+ reactionDelayed = false
313
+ }
314
+ )
315
+ dispose()
316
+ return reactionDelayed
317
+ }
318
+
319
+ interface DeepChildrenRefs {
320
+ all: Set<Ref<any>>
321
+ byType: WeakMap<ModelClass<Ref<any>>, Set<Ref<any>>>
322
+ }
323
+
324
+ const getDeepChildrenRefs = registerDeepObjectChildrenExtension<DeepChildrenRefs>({
325
+ initData() {
326
+ return {
327
+ all: new Set(),
328
+ byType: new WeakMap(),
329
+ }
330
+ },
331
+
332
+ addNode(node, data) {
333
+ if (node instanceof Ref) {
334
+ data.all.add(node)
335
+ const refsByThisType = getOrCreate(data.byType, node.constructor, () => new Set())
336
+ refsByThisType.add(node)
337
+ }
338
+ },
339
+ })