mobx-keystone 1.25.0 → 2.1.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 (141) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/dist/mobx-keystone.esm.js +3627 -1540
  3. package/dist/mobx-keystone.esm.mjs +3627 -1540
  4. package/dist/mobx-keystone.umd.js +3638 -1543
  5. package/dist/types/actionMiddlewares/actionSerialization/dateSerializer.d.ts +1 -1
  6. package/dist/types/actionMiddlewares/transactionMiddleware.d.ts +1 -1
  7. package/dist/types/actionMiddlewares/undoMiddleware.d.ts +17 -10
  8. package/dist/types/dataModel/dataModelInstanceCache.d.ts +1 -0
  9. package/dist/types/deepChange/onDeepChange.d.ts +9 -0
  10. package/dist/types/parent/arrayPaths.d.ts +1 -0
  11. package/dist/types/parent/detach.d.ts +1 -1
  12. package/dist/types/parent/findChildren.d.ts +1 -1
  13. package/dist/types/parent/treeOrder.d.ts +1 -0
  14. package/dist/types/patch/applyPatches.d.ts +3 -0
  15. package/dist/types/redux/connectReduxDevTools.d.ts +2 -1
  16. package/dist/types/ref/modelRefIdIndex.d.ts +1 -0
  17. package/dist/types/ref/nodesById.d.ts +1 -0
  18. package/dist/types/ref/refIdIndex.d.ts +1 -0
  19. package/dist/types/snapshot/SnapshotOf.d.ts +2 -2
  20. package/dist/types/snapshot/applySnapshot.d.ts +1 -0
  21. package/dist/types/snapshot/fromSnapshot.d.ts +0 -3
  22. package/dist/types/standardActions/arrayActions.d.ts +1 -1
  23. package/dist/types/treeUtils/sandbox.d.ts +0 -4
  24. package/dist/types/tweaker/changeRollback.d.ts +1 -0
  25. package/dist/types/tweaker/tweakedChangeListeners.d.ts +1 -0
  26. package/dist/types/types/TypeCheckError.d.ts +1 -0
  27. package/dist/types/types/utility/typesRefinement.d.ts +2 -2
  28. package/dist/types/utils/ModelPool.d.ts +7 -1
  29. package/dist/types/utils/chunks.d.ts +1 -0
  30. package/dist/types/utils/forEachWithDelayedThrow.d.ts +1 -0
  31. package/dist/types/utils/index.d.ts +13 -0
  32. package/dist/types/utils/mapUtils.d.ts +2 -2
  33. package/dist/types/utils/setLike.d.ts +1 -0
  34. package/package.json +2 -2
  35. package/src/action/applyAction.ts +1 -1
  36. package/src/action/middleware.ts +17 -36
  37. package/src/action/modelFlow.ts +4 -5
  38. package/src/action/mutationBatch.ts +10 -10
  39. package/src/action/pendingActions.ts +3 -4
  40. package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +19 -14
  41. package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +29 -20
  42. package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +1 -1
  43. package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +4 -3
  44. package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +4 -12
  45. package/src/actionMiddlewares/actionSerialization/setSerializer.ts +4 -13
  46. package/src/actionMiddlewares/actionTrackingMiddleware.ts +11 -13
  47. package/src/actionMiddlewares/transactionMiddleware.ts +55 -25
  48. package/src/actionMiddlewares/undoMiddleware.ts +112 -59
  49. package/src/actionMiddlewares/utils.ts +2 -1
  50. package/src/computedTree/computedTree.ts +14 -3
  51. package/src/context/context.ts +17 -45
  52. package/src/dataModel/BaseDataModel.ts +20 -16
  53. package/src/dataModel/dataModelInstanceCache.ts +8 -0
  54. package/src/deepChange/onDeepChange.ts +179 -24
  55. package/src/frozen/Frozen.ts +50 -33
  56. package/src/model/BaseModel.ts +10 -13
  57. package/src/model/newModel.ts +21 -24
  58. package/src/model/utils.ts +2 -2
  59. package/src/modelShared/modelClassInitializer.ts +3 -2
  60. package/src/modelShared/modelDecorator.ts +39 -39
  61. package/src/modelShared/modelInfo.ts +1 -1
  62. package/src/modelShared/prop.ts +6 -9
  63. package/src/modelShared/sharedInternalModel.ts +16 -3
  64. package/src/parent/arrayPaths.ts +242 -0
  65. package/src/parent/coreObjectChildren.ts +300 -48
  66. package/src/parent/detach.ts +1 -1
  67. package/src/parent/findChildren.ts +4 -7
  68. package/src/parent/onChildAttachedTo.ts +157 -55
  69. package/src/parent/path.ts +15 -5
  70. package/src/parent/setParent.ts +34 -15
  71. package/src/parent/treeOrder.ts +53 -0
  72. package/src/parent/walkTree.ts +12 -101
  73. package/src/patch/applyPatches.ts +74 -34
  74. package/src/patch/emitPatch.ts +142 -26
  75. package/src/patch/jsonPatch.ts +5 -0
  76. package/src/patch/patchRecorder.ts +18 -14
  77. package/src/redux/connectReduxDevTools.ts +24 -18
  78. package/src/redux/redux.ts +5 -21
  79. package/src/ref/core.ts +81 -29
  80. package/src/ref/modelRefIdIndex.ts +131 -0
  81. package/src/ref/nodesById.ts +60 -0
  82. package/src/ref/refIdIndex.ts +209 -0
  83. package/src/rootStore/attachDetach.ts +27 -16
  84. package/src/rootStore/rootStore.ts +10 -6
  85. package/src/snapshot/SnapshotOf.ts +2 -2
  86. package/src/snapshot/applySnapshot.ts +27 -19
  87. package/src/snapshot/clone.ts +1 -2
  88. package/src/snapshot/cloneTreeValue.ts +1 -2
  89. package/src/snapshot/fromModelSnapshot.ts +6 -2
  90. package/src/snapshot/fromPlainObjectSnapshot.ts +3 -8
  91. package/src/snapshot/fromSnapshot.ts +2 -6
  92. package/src/snapshot/internal.ts +89 -19
  93. package/src/snapshot/onSnapshot.ts +12 -1
  94. package/src/snapshot/reconcileModelSnapshot.ts +6 -6
  95. package/src/snapshot/reconcilePlainObjectSnapshot.ts +5 -4
  96. package/src/standardActions/arrayActions.ts +12 -2
  97. package/src/standardActions/objectActions.ts +3 -2
  98. package/src/transforms/ImmutableDate.ts +4 -0
  99. package/src/transforms/asMap.ts +3 -1
  100. package/src/treeUtils/deepEquals.ts +15 -13
  101. package/src/treeUtils/draft.ts +8 -0
  102. package/src/treeUtils/sandbox.ts +31 -27
  103. package/src/tweaker/changeRollback.ts +218 -0
  104. package/src/tweaker/core.ts +33 -22
  105. package/src/tweaker/treeNodeMetadata.ts +12 -2
  106. package/src/tweaker/tweak.ts +183 -19
  107. package/src/tweaker/tweakArray.ts +176 -33
  108. package/src/tweaker/tweakPlainObject.ts +117 -39
  109. package/src/tweaker/tweakedChangeListeners.ts +61 -0
  110. package/src/tweaker/typeChecking.ts +25 -15
  111. package/src/types/TypeCheckError.ts +17 -19
  112. package/src/types/arrayBased/typesArray.ts +10 -11
  113. package/src/types/createCachedTypeCheck.ts +541 -88
  114. package/src/types/objectBased/typesDataModelData.ts +4 -2
  115. package/src/types/objectBased/typesModel.ts +4 -2
  116. package/src/types/objectBased/typesObject.ts +20 -16
  117. package/src/types/objectBased/typesRecord.ts +8 -18
  118. package/src/types/objectBased/typesRef.ts +2 -2
  119. package/src/types/primitiveBased/typesEnum.ts +4 -2
  120. package/src/types/primitiveBased/typesPrimitive.ts +9 -4
  121. package/src/types/typeCheckErrorUtils.ts +1 -0
  122. package/src/types/utility/typesCodecSupport.ts +97 -56
  123. package/src/types/utility/typesMap.ts +24 -41
  124. package/src/types/utility/typesRefinement.ts +3 -2
  125. package/src/types/utility/typesSet.ts +24 -0
  126. package/src/utils/ModelPool.ts +78 -22
  127. package/src/utils/chainFns.ts +4 -0
  128. package/src/utils/chunks.ts +271 -0
  129. package/src/utils/decorators.ts +1 -1
  130. package/src/utils/errorDiagnostics.ts +6 -2
  131. package/src/utils/forEachWithDelayedThrow.ts +83 -0
  132. package/src/utils/index.ts +71 -2
  133. package/src/utils/mapUtils.ts +3 -7
  134. package/src/utils/setIfDifferent.ts +3 -2
  135. package/src/utils/setLike.ts +134 -0
  136. package/src/wrappers/ArraySet.ts +5 -9
  137. package/src/wrappers/ObjectMap.ts +15 -11
  138. package/src/wrappers/asMap.ts +30 -18
  139. package/src/wrappers/asSet.ts +36 -12
  140. package/dist/types/action/modelFlowPromiseGenerator.d.ts +0 -1
  141. package/src/action/modelFlowPromiseGenerator.ts +0 -127
@@ -1,5 +1,5 @@
1
1
  import type { ModelPropTransform } from "../../modelShared/prop"
2
- import { isMap } from "../../utils"
2
+ import { isMap, setProtoProp } from "../../utils"
3
3
  import { asMap } from "../../wrappers/asMap"
4
4
  import { typesArray } from "../arrayBased/typesArray"
5
5
  import { typesTuple } from "../arrayBased/typesTuple"
@@ -19,6 +19,15 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
19
19
  keyAdapter: RuntimeAdapter<TKeyStored, TKeyRuntime>,
20
20
  valueAdapter: RuntimeAdapter<TValueStored, TValueRuntime>
21
21
  ): Map<TKeyRuntime, TValueRuntime> {
22
+ // Writes through the decoded value must land back on the entry it came from.
23
+ const toRuntimeValue = (storedKey: TKeyStored, storedValue: TValueStored): TValueRuntime =>
24
+ valueAdapter.toRuntime(storedValue, (newStoredValue) => {
25
+ storedMap.set(storedKey, newStoredValue)
26
+ })
27
+
28
+ const getValue = (storedKey: TKeyStored): TValueRuntime =>
29
+ toRuntimeValue(storedKey, storedMap.get(storedKey) as TValueStored)
30
+
22
31
  return new Proxy(storedMap, {
23
32
  get(target, prop, receiver) {
24
33
  switch (prop) {
@@ -29,43 +38,24 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
29
38
  return undefined
30
39
  }
31
40
 
32
- return valueAdapter.toRuntime(
33
- target.get(storedKey) as TValueStored,
34
- (newStoredValue) => {
35
- target.set(storedKey, newStoredValue)
36
- }
37
- )
41
+ return getValue(storedKey)
38
42
  }
39
43
  case "getOrInsert":
40
44
  return (key: TKeyRuntime, defaultValue: TValueRuntime) => {
41
45
  const storedKey = keyAdapter.toStored(key)
42
- if (target.has(storedKey)) {
43
- return valueAdapter.toRuntime(
44
- target.get(storedKey) as TValueStored,
45
- (newStoredValue) => {
46
- target.set(storedKey, newStoredValue)
47
- }
48
- )
46
+ if (!target.has(storedKey)) {
47
+ target.set(storedKey, valueAdapter.toStored(defaultValue))
49
48
  }
50
-
51
- target.set(storedKey, valueAdapter.toStored(defaultValue))
52
- return defaultValue
49
+ return getValue(storedKey)
53
50
  }
54
51
  case "getOrInsertComputed":
55
52
  return (key: TKeyRuntime, callback: (key: TKeyRuntime) => TValueRuntime) => {
56
53
  const storedKey = keyAdapter.toStored(key)
57
- if (target.has(storedKey)) {
58
- return valueAdapter.toRuntime(
59
- target.get(storedKey) as TValueStored,
60
- (newStoredValue) => {
61
- target.set(storedKey, newStoredValue)
62
- }
63
- )
54
+ if (!target.has(storedKey)) {
55
+ const value = callback(key)
56
+ target.set(storedKey, valueAdapter.toStored(value))
64
57
  }
65
-
66
- const value = callback(key)
67
- target.set(storedKey, valueAdapter.toStored(value))
68
- return value
58
+ return getValue(storedKey)
69
59
  }
70
60
  case "set":
71
61
  return (key: TKeyRuntime, value: TValueRuntime) => {
@@ -92,9 +82,7 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
92
82
  target.forEach((value, key) => {
93
83
  callback.call(
94
84
  thisArg,
95
- valueAdapter.toRuntime(value, (newStoredValue) => {
96
- target.set(key, newStoredValue)
97
- }),
85
+ toRuntimeValue(key, value),
98
86
  keyAdapter.toRuntime(key),
99
87
  receiver
100
88
  )
@@ -104,12 +92,7 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
104
92
  case Symbol.iterator:
105
93
  return function* () {
106
94
  for (const [key, value] of target.entries()) {
107
- yield [
108
- keyAdapter.toRuntime(key),
109
- valueAdapter.toRuntime(value, (newStoredValue) => {
110
- target.set(key, newStoredValue)
111
- }),
112
- ] as const
95
+ yield [keyAdapter.toRuntime(key), toRuntimeValue(key, value)] as const
113
96
  }
114
97
  }
115
98
  case "keys":
@@ -121,9 +104,7 @@ function makeMapProxy<TKeyStored, TKeyRuntime, TValueStored, TValueRuntime>(
121
104
  case "values":
122
105
  return function* () {
123
106
  for (const [key, value] of target.entries()) {
124
- yield valueAdapter.toRuntime(value, (newStoredValue) => {
125
- target.set(key, newStoredValue)
126
- })
107
+ yield toRuntimeValue(key, value)
127
108
  }
128
109
  }
129
110
  default:
@@ -158,7 +139,9 @@ function makeObjectBackedMapTransform<TStoredValue, TRuntimeValue>(
158
139
 
159
140
  const result: Record<string, TStoredValue> = {}
160
141
  transformedValue.forEach((value, key) => {
161
- result[key] = valueAdapter.toStored(value)
142
+ const storedValue = valueAdapter.toStored(value)
143
+ if (key === "__proto__") setProtoProp(result, storedValue)
144
+ else result[key] = storedValue
162
145
  })
163
146
  return result
164
147
  },
@@ -41,8 +41,8 @@ import {
41
41
  * @template T Base type.
42
42
  * @param baseType Base type.
43
43
  * @param checkFn Function that will receive the data (if it passes the base type
44
- * check) and return null or false if there were no errors or either a TypeCheckError instance or
45
- * true if there were.
44
+ * check) and return null or true if there were no errors or either a TypeCheckError instance or
45
+ * false if there were.
46
46
  * @returns
47
47
  */
48
48
  export function typesRefinement<T extends AnyType>(
@@ -89,6 +89,7 @@ export function typesRefinement<T extends AnyType>(
89
89
  // override typeCheckedValue
90
90
  return new TypeCheckError({
91
91
  path: refinementErr.path,
92
+ modelTrail: refinementErr.modelTrail,
92
93
  expectedTypeName: refinementErr.expectedTypeName,
93
94
  actualValue: refinementErr.actualValue,
94
95
  typeCheckedValue,
@@ -1,6 +1,15 @@
1
1
  import type { ObservableSet } from "mobx"
2
2
  import type { ModelPropTransform } from "../../modelShared/prop"
3
3
  import { isSet } from "../../utils"
4
+ import {
5
+ isSetLikeDisjointFrom,
6
+ isSetLikeSubsetOf,
7
+ isSetLikeSupersetOf,
8
+ setLikeDifference,
9
+ setLikeIntersection,
10
+ setLikeSymmetricDifference,
11
+ setLikeUnion,
12
+ } from "../../utils/setLike"
4
13
  import { asSet } from "../../wrappers/asSet"
5
14
  import { typesArray } from "../arrayBased/typesArray"
6
15
  import type { AnyType, ArrayType, TypeToData } from "../schemas"
@@ -52,6 +61,21 @@ function makeArrayBackedSetTransform<TStored, TRuntime>(
52
61
  callback.call(thisArg, runtimeValue, runtimeValue, receiver)
53
62
  })
54
63
  }
64
+ case "union":
65
+ return (other: ReadonlySetLike<unknown>) => setLikeUnion(receiver, other)
66
+ case "intersection":
67
+ return (other: ReadonlySetLike<unknown>) => setLikeIntersection(receiver, other)
68
+ case "difference":
69
+ return (other: ReadonlySetLike<unknown>) => setLikeDifference(receiver, other)
70
+ case "symmetricDifference":
71
+ return (other: ReadonlySetLike<unknown>) =>
72
+ setLikeSymmetricDifference(receiver, other)
73
+ case "isSubsetOf":
74
+ return (other: ReadonlySetLike<unknown>) => isSetLikeSubsetOf(receiver, other)
75
+ case "isSupersetOf":
76
+ return (other: ReadonlySetLike<unknown>) => isSetLikeSupersetOf(receiver, other)
77
+ case "isDisjointFrom":
78
+ return (other: ReadonlySetLike<unknown>) => isSetLikeDisjointFrom(receiver, other)
55
79
  case "entries":
56
80
  return function* () {
57
81
  for (const value of target.values()) {
@@ -6,25 +6,45 @@ import type { ModelClass } from "../modelShared/BaseModelShared"
6
6
  import { getModelInfoForName } from "../modelShared/modelInfo"
7
7
  import { dataToModelNode } from "../parent/core"
8
8
  import {
9
- getDeepObjectChildren,
9
+ type DeepObjectChildrenExtensionAccessor,
10
+ type PinnedDeepObjectChildrenExtensionData,
10
11
  registerDeepObjectChildrenExtension,
11
12
  } from "../parent/coreObjectChildren"
13
+ import { getOrCreate } from "./mapUtils"
12
14
 
13
15
  type ModelsById = Map<string, AnyModel>
14
16
  type ModelsByTypeAndId = Map<string, ModelsById>
15
17
 
18
+ interface DeepChildrenModels {
19
+ readonly byTypeAndId: ModelsByTypeAndId
20
+ /** Id each model is indexed under, since ids can change later. */
21
+ readonly indexedIds: Map<AnyModel, string>
22
+ /** Whether some model was shadowed by another one with the same type and id. */
23
+ hasDuplicates: boolean
24
+ }
25
+
26
+ /**
27
+ * A view of the models under a node, as they were when the pool was created.
28
+ * Must be released once no longer used.
29
+ */
16
30
  export class ModelPool {
17
- private pool: ReadonlyMap<string, ReadonlyMap<string, AnyModel>>
31
+ private readonly pool: ReadonlyMap<string, ReadonlyMap<string, AnyModel>>
32
+ private readonly pinned: PinnedDeepObjectChildrenExtensionData<DeepChildrenModels>
18
33
 
19
34
  constructor(root: object) {
20
35
  // make sure we don't use the sub-data $ object
21
36
  root = dataToModelNode(root)
22
37
 
23
- this.pool = getDeepChildrenModels(getDeepObjectChildren(root))
38
+ this.pinned = getDeepChildrenModels().pin(root)
39
+ this.pool = this.pinned.data.byTypeAndId
40
+ }
41
+
42
+ release(): void {
43
+ this.pinned.release()
24
44
  }
25
45
 
26
46
  findModelByTypeAndId(modelType: string, modelId: string | undefined): AnyModel | undefined {
27
- return modelId ? this.pool.get(modelType)?.get(modelId) : undefined
47
+ return modelId !== undefined ? this.pool.get(modelType)?.get(modelId) : undefined
28
48
  }
29
49
 
30
50
  findModelForSnapshot(sn: any): AnyModel | undefined {
@@ -36,39 +56,75 @@ export class ModelPool {
36
56
  const modelInfo = getModelInfoForName(modelType)!
37
57
  const modelIdPropertyName = getModelIdPropertyName(modelInfo.class as ModelClass<AnyModel>)
38
58
 
39
- return modelIdPropertyName
59
+ return modelIdPropertyName !== undefined
40
60
  ? this.findModelByTypeAndId(modelType, (sn as any)[modelIdPropertyName])
41
61
  : undefined
42
62
  }
43
63
  }
44
64
 
45
- type GetDeepChildrenModels = (data: ReturnType<typeof getDeepObjectChildren>) => ModelsByTypeAndId
65
+ let deepChildrenModels: DeepObjectChildrenExtensionAccessor<DeepChildrenModels> | undefined
46
66
 
47
- let getDeepChildrenModels: GetDeepChildrenModels = (data) => {
48
- // Register on first use to keep ModelPool out of the coreObjectChildren
49
- // module-initialization cycle. The extension getter backfills its index from
50
- // data.deep, so registering after a deep-children set already exists is safe.
51
- const getRegisteredDeepChildrenModels = registerDeepObjectChildrenExtension<ModelsByTypeAndId>({
67
+ // Registered on first use to keep ModelPool out of the coreObjectChildren
68
+ // module-initialization cycle.
69
+ function getDeepChildrenModels() {
70
+ deepChildrenModels ??= registerDeepObjectChildrenExtension<DeepChildrenModels>({
52
71
  initData() {
53
- return new Map()
72
+ return {
73
+ byTypeAndId: new Map(),
74
+ indexedIds: new Map(),
75
+ hasDuplicates: false,
76
+ }
54
77
  },
55
78
 
56
- addNode(node, extensionData) {
79
+ // last one wins when rebuilding
80
+ addNode(node, data) {
57
81
  if (isModel(node)) {
58
82
  const id = node[modelIdKey]
59
- if (id) {
60
- const modelType = node[modelTypeKey]
61
- let modelsById = extensionData.get(modelType)
62
- if (!modelsById) {
63
- modelsById = new Map()
64
- extensionData.set(modelType, modelsById)
83
+ if (id !== undefined) {
84
+ const modelsById = getOrCreate(data.byTypeAndId, node[modelTypeKey], () => new Map())
85
+ const previous = modelsById.get(id)
86
+ if (previous) {
87
+ data.hasDuplicates = true
88
+ data.indexedIds.delete(previous)
65
89
  }
66
90
  modelsById.set(id, node)
91
+ data.indexedIds.set(node, id)
67
92
  }
68
93
  }
69
94
  },
70
- })
71
95
 
72
- getDeepChildrenModels = getRegisteredDeepChildrenModels
73
- return getRegisteredDeepChildrenModels(data)
96
+ addNodeIncrementally(node, data) {
97
+ if (isModel(node)) {
98
+ const id = node[modelIdKey]
99
+ if (id !== undefined) {
100
+ const modelsById = getOrCreate(data.byTypeAndId, node[modelTypeKey], () => new Map())
101
+ if (modelsById.has(id)) {
102
+ // which duplicate wins depends on the deep children order
103
+ return false
104
+ }
105
+ modelsById.set(id, node)
106
+ data.indexedIds.set(node, id)
107
+ }
108
+ }
109
+ return true
110
+ },
111
+
112
+ removeNode(node, data) {
113
+ if (isModel(node)) {
114
+ if (data.hasDuplicates) {
115
+ // a shadowed duplicate might need to take its place
116
+ return false
117
+ }
118
+ const id = data.indexedIds.get(node)
119
+ if (id !== undefined) {
120
+ data.indexedIds.delete(node)
121
+ data.byTypeAndId.get(node[modelTypeKey])!.delete(id)
122
+ }
123
+ }
124
+ return true
125
+ },
126
+
127
+ dependsOnModelIds: true,
128
+ })
129
+ return deepChildrenModels
74
130
  }
@@ -4,6 +4,10 @@ export function chainFns<F extends Function>(...fns: (F | undefined)[]): F | und
4
4
  return undefined
5
5
  }
6
6
 
7
+ if (definedFns.length === 1) {
8
+ return definedFns[0]
9
+ }
10
+
7
11
  const chainedFn = (v: any, ...args: any[]) => {
8
12
  let ret = v
9
13
 
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Target size of the chunks large collections are split in, so a change only
3
+ * needs to revisit one chunk instead of every item.
4
+ *
5
+ * @internal
6
+ */
7
+ export const chunkSize = 128
8
+
9
+ /**
10
+ * @internal
11
+ */
12
+ export interface SequenceChunk<T> {
13
+ items: T[]
14
+ }
15
+
16
+ /**
17
+ * An ordered sequence split in chunks, which splices keep near `chunkSize`:
18
+ * chunks are split above twice that size and merged below a quarter of it.
19
+ * Subclasses decide what a chunk keeps track of through the hooks.
20
+ *
21
+ * @internal
22
+ */
23
+ export abstract class ChunkedSequence<T, C extends SequenceChunk<T>> {
24
+ chunks: C[] = []
25
+
26
+ protected abstract createChunk(items: T[]): C
27
+
28
+ /**
29
+ * Called once items were removed from or inserted into a chunk, before
30
+ * resizing it. The items from `from` on changed their position in it.
31
+ */
32
+ protected abstract onSpliced(chunk: C, from: number): void
33
+
34
+ /**
35
+ * Called when a resize moves items to another chunk, starting at `from`.
36
+ * The chunk they come from was already passed to `onSpliced`.
37
+ */
38
+ protected abstract onItemsMoved(chunk: C, from: number): void
39
+
40
+ /** Called for each chunk after the spliced ones when a splice changes the length. */
41
+ protected onShifted(_chunk: C): void {}
42
+
43
+ /** Splits items in new chunks, appended to the current ones. */
44
+ protected appendChunks(items: readonly T[]): void {
45
+ for (let i = 0; i < items.length; i += chunkSize) {
46
+ this.chunks.push(this.createChunk(items.slice(i, i + chunkSize)))
47
+ }
48
+ }
49
+
50
+ /** Replaces every chunk with new ones for the items. */
51
+ protected reset(items: readonly T[]): void {
52
+ this.chunks = []
53
+ this.appendChunks(items)
54
+ }
55
+
56
+ /** Chunk containing the index, and the index within it. Appends go to the last chunk. */
57
+ locate(index: number): [number, number] {
58
+ const chunks = this.chunks
59
+ let c = 0
60
+ let local = index
61
+ while (c < chunks.length - 1 && local >= chunks[c].items.length) {
62
+ local -= chunks[c].items.length
63
+ c++
64
+ }
65
+ return [c, local]
66
+ }
67
+
68
+ /**
69
+ * Removes and inserts items, like `Array.prototype.splice`. `onRemoved` is
70
+ * called with each removed item, the chunk it was in and its index there.
71
+ */
72
+ protected splice(
73
+ index: number,
74
+ removedCount: number,
75
+ added: readonly T[],
76
+ onRemoved?: (item: T, chunk: C, local: number) => void
77
+ ): void {
78
+ const chunks = this.chunks
79
+ if (chunks.length === 0) {
80
+ if (added.length === 0) {
81
+ return
82
+ }
83
+ chunks.push(this.createChunk([]))
84
+ }
85
+
86
+ const [first, start] = this.locate(index)
87
+ let last = first
88
+ let remaining = removedCount
89
+ let local = start
90
+ for (let c = first; remaining > 0; c++) {
91
+ const chunk = chunks[c]
92
+ const count = Math.min(remaining, chunk.items.length - local)
93
+ const removed = chunk.items.splice(local, count)
94
+ if (onRemoved) {
95
+ for (let i = 0; i < removed.length; i++) {
96
+ onRemoved(removed[i], chunk, local + i)
97
+ }
98
+ }
99
+ remaining -= count
100
+ local = 0
101
+ last = c
102
+ }
103
+
104
+ const firstChunk = chunks[first]
105
+ if (added.length > 0) {
106
+ firstChunk.items = firstChunk.items
107
+ .slice(0, start)
108
+ .concat(added as T[], firstChunk.items.slice(start))
109
+ }
110
+
111
+ this.onSpliced(firstChunk, start + added.length)
112
+ for (let c = first + 1; c <= last; c++) {
113
+ this.onSpliced(chunks[c], 0)
114
+ }
115
+ if (added.length !== removedCount) {
116
+ for (let c = last + 1; c < chunks.length; c++) {
117
+ this.onShifted(chunks[c])
118
+ }
119
+ }
120
+
121
+ // backwards, so merging or splitting a chunk does not move the ones left to visit
122
+ for (let c = last; c >= first; c--) {
123
+ this.resize(c)
124
+ }
125
+ }
126
+
127
+ /** Splits, merges or drops a changed chunk to keep its size near the target. */
128
+ private resize(c: number) {
129
+ const chunks = this.chunks
130
+ const chunk = chunks[c]
131
+ const length = chunk.items.length
132
+
133
+ if (length > 2 * chunkSize) {
134
+ const pieceCount = Math.ceil(length / chunkSize)
135
+ const pieceSize = Math.ceil(length / pieceCount)
136
+ const pieces: C[] = []
137
+ for (let i = pieceSize; i < length; i += pieceSize) {
138
+ const piece = this.createChunk(chunk.items.slice(i, i + pieceSize))
139
+ this.onItemsMoved(piece, 0)
140
+ pieces.push(piece)
141
+ }
142
+ chunk.items = chunk.items.slice(0, pieceSize)
143
+ this.chunks = chunks.slice(0, c + 1).concat(pieces, chunks.slice(c + 1))
144
+ } else if (length < chunkSize / 4) {
145
+ const previous = chunks[c - 1] as C | undefined
146
+ const next = chunks[c + 1] as C | undefined
147
+ if (previous && previous.items.length + length <= 2 * chunkSize) {
148
+ const from = previous.items.length
149
+ previous.items = previous.items.concat(chunk.items)
150
+ this.onItemsMoved(previous, from)
151
+ chunks.splice(c, 1)
152
+ } else if (next && next.items.length + length <= 2 * chunkSize) {
153
+ next.items = chunk.items.concat(next.items)
154
+ this.onItemsMoved(next, 0)
155
+ chunks.splice(c, 1)
156
+ } else if (length === 0) {
157
+ chunks.splice(c, 1)
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * An unordered set split in chunks. Items are added to the last chunk, and a
165
+ * chunk left under a quarter of `chunkSize` by removals is merged into a
166
+ * neighbor (or dropped once empty). Subclasses decide what a chunk keeps track
167
+ * of through the hooks.
168
+ *
169
+ * @internal
170
+ */
171
+ export abstract class ChunkedSet<T, C extends SequenceChunk<T>> {
172
+ chunks: C[] = []
173
+ /** Chunk of each item, only needed (and kept) once an item is looked up. */
174
+ private chunkOf: Map<T, C> | undefined
175
+
176
+ protected abstract createChunk(items: T[]): C
177
+
178
+ /** Called when items were added to or removed from a chunk. */
179
+ protected abstract onChunkChanged(chunk: C): void
180
+
181
+ /** Called when chunks were added or removed. */
182
+ protected abstract onChunksChanged(): void
183
+
184
+ /** Called for a chunk removed after its items were moved to another one. */
185
+ protected onChunkDropped(_chunk: C): void {}
186
+
187
+ /** Splits items in new chunks, appended to the current ones. */
188
+ protected appendChunks(items: readonly T[]): void {
189
+ for (let i = 0; i < items.length; i += chunkSize) {
190
+ this.chunks.push(this.createChunk(items.slice(i, i + chunkSize)))
191
+ }
192
+ }
193
+
194
+ /** Replaces every chunk with new ones for the items. */
195
+ protected reset(items: readonly T[]): void {
196
+ this.chunks = []
197
+ this.chunkOf = undefined
198
+ this.appendChunks(items)
199
+ }
200
+
201
+ add(item: T): void {
202
+ let chunk = this.chunks[this.chunks.length - 1] as C | undefined
203
+ if (chunk && chunk.items.length < chunkSize) {
204
+ chunk.items.push(item)
205
+ this.onChunkChanged(chunk)
206
+ } else {
207
+ chunk = this.createChunk([item])
208
+ this.chunks.push(chunk)
209
+ this.onChunksChanged()
210
+ }
211
+ this.chunkOf?.set(item, chunk)
212
+ }
213
+
214
+ /** Chunk containing the item, if any. */
215
+ getChunkOf(item: T): C | undefined {
216
+ return this.getChunkOfMap().get(item)
217
+ }
218
+
219
+ private getChunkOfMap(): Map<T, C> {
220
+ if (!this.chunkOf) {
221
+ this.chunkOf = new Map()
222
+ for (const chunk of this.chunks) {
223
+ for (const i of chunk.items) {
224
+ this.chunkOf.set(i, chunk)
225
+ }
226
+ }
227
+ }
228
+ return this.chunkOf
229
+ }
230
+
231
+ /** Returns false if the item was not in the set. */
232
+ delete(item: T): boolean {
233
+ const chunkOf = this.getChunkOfMap()
234
+ const chunk = chunkOf.get(item)
235
+ if (!chunk) {
236
+ return false
237
+ }
238
+ chunkOf.delete(item)
239
+ // (the order within a chunk does not matter, so the last item fills the gap)
240
+ const items = chunk.items
241
+ const last = items.pop()!
242
+ if (last !== item) {
243
+ items[items.lastIndexOf(item)] = last
244
+ }
245
+ this.onChunkChanged(chunk)
246
+ if (chunk.items.length >= chunkSize / 4) {
247
+ return true
248
+ }
249
+
250
+ // merge it into a neighbor, or drop it once empty
251
+ const chunks = this.chunks
252
+ const c = chunks.indexOf(chunk)
253
+ if (chunk.items.length > 0) {
254
+ const neighbor = [chunks[c - 1], chunks[c + 1]].find(
255
+ (n) => n && n.items.length + chunk.items.length <= 2 * chunkSize
256
+ )
257
+ if (!neighbor) {
258
+ return true
259
+ }
260
+ for (const i of chunk.items) {
261
+ chunkOf.set(i, neighbor)
262
+ }
263
+ neighbor.items = neighbor.items.concat(chunk.items)
264
+ this.onChunkChanged(neighbor)
265
+ }
266
+ chunks.splice(c, 1)
267
+ this.onChunkDropped(chunk)
268
+ this.onChunksChanged()
269
+ return true
270
+ }
271
+ }
@@ -110,7 +110,7 @@ export function decorateWrapMethodOrField(
110
110
  const baseDescriptor: PropertyDescriptor | undefined = args[2]
111
111
 
112
112
  checkModelDecoratorTaget(decoratorName, target)
113
- checkDecoratorContext("transaction", propertyKey, false)
113
+ checkDecoratorContext(decoratorName, propertyKey, false)
114
114
 
115
115
  const data = getActionNameAndContextOverride(target, propertyKey, true)
116
116
 
@@ -190,7 +190,7 @@ function formatErrorPathForMessage(path: Path | undefined = getErrorPathSnapshot
190
190
  /**
191
191
  * @internal
192
192
  */
193
- function getSafeErrorValuePreview(value: unknown, maxLen = 200): string {
193
+ export function getSafeErrorValuePreview(value: unknown, maxLen = 200): string {
194
194
  let str: string | undefined
195
195
  try {
196
196
  str = JSON.stringify(value)
@@ -203,7 +203,11 @@ function getSafeErrorValuePreview(value: unknown, maxLen = 200): string {
203
203
  try {
204
204
  str = String(value)
205
205
  } catch {
206
- str = Object.prototype.toString.call(value)
206
+ try {
207
+ str = Object.prototype.toString.call(value)
208
+ } catch {
209
+ str = "<unprintable value>"
210
+ }
207
211
  }
208
212
  }
209
213