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.
- package/CHANGELOG.md +864 -853
- package/README.md +61 -61
- package/dist/mobx-keystone.esm.js +11336 -11148
- package/dist/mobx-keystone.esm.mjs +11336 -11148
- package/dist/mobx-keystone.umd.js +12229 -11738
- package/dist/types/actionMiddlewares/undoMiddleware.d.ts +1 -1
- package/dist/types/modelShared/prop.d.ts +5 -0
- package/dist/types/redux/connectReduxDevTools.d.ts +17 -4
- package/dist/types/ref/Ref.d.ts +1 -1
- package/dist/types/treeUtils/sandbox.d.ts +1 -1
- package/dist/types/types/TypeChecker.d.ts +1 -1
- package/dist/types/types/arrayBased/typesArray.d.ts +1 -1
- package/dist/types/types/arrayBased/typesTuple.d.ts +1 -1
- package/dist/types/types/objectBased/typesArraySet.d.ts +1 -1
- package/dist/types/types/objectBased/typesDataModelData.d.ts +1 -1
- package/dist/types/types/objectBased/typesModel.d.ts +1 -1
- package/dist/types/types/objectBased/typesObject.d.ts +3 -3
- package/dist/types/types/objectBased/typesObjectMap.d.ts +1 -1
- package/dist/types/types/objectBased/typesRecord.d.ts +1 -1
- package/dist/types/types/primitiveBased/typesPrimitive.d.ts +1 -1
- package/dist/types/types/types.d.ts +3 -3
- package/dist/types/types/utility/typesCodec.d.ts +1 -6
- package/dist/types/types/utility/typesCodecCore.d.ts +1 -1
- package/dist/types/types/utility/typesOr.d.ts +1 -1
- package/dist/types/types/utility/typesRefinement.d.ts +1 -1
- package/dist/types/types/utility/typesSkipCheck.d.ts +1 -1
- package/dist/types/types/utility/typesTag.d.ts +1 -1
- package/dist/types/wrappers/ObjectMap.d.ts +2 -0
- package/dist/types/wrappers/asMap.d.ts +10 -6
- package/package.json +14 -12
- package/src/action/applyAction.ts +125 -114
- package/src/action/applyMethodCall.ts +37 -37
- package/src/action/isModelAction.ts +16 -16
- package/src/action/middleware.ts +137 -137
- package/src/action/modelFlow.ts +240 -240
- package/src/action/modelFlowPromiseGenerator.ts +127 -125
- package/src/action/pendingActions.ts +42 -42
- package/src/action/wrapInAction.ts +156 -156
- package/src/actionMiddlewares/actionSerialization/actionSerialization.ts +1 -1
- package/src/actionMiddlewares/actionSerialization/applySerializedAction.ts +172 -172
- package/src/actionMiddlewares/actionSerialization/arraySerializer.ts +20 -20
- package/src/actionMiddlewares/actionSerialization/dateSerializer.ts +17 -17
- package/src/actionMiddlewares/actionSerialization/mapSerializer.ts +42 -42
- package/src/actionMiddlewares/actionSerialization/objectPathSerializer.ts +50 -50
- package/src/actionMiddlewares/actionSerialization/objectSnapshotSerializer.ts +21 -21
- package/src/actionMiddlewares/actionSerialization/plainObjectSerializer.ts +24 -32
- package/src/actionMiddlewares/actionSerialization/primitiveSerializer.ts +51 -51
- package/src/actionMiddlewares/actionSerialization/setSerializer.ts +37 -37
- package/src/actionMiddlewares/actionTrackingMiddleware.ts +3 -3
- package/src/actionMiddlewares/onActionMiddleware.ts +2 -2
- package/src/actionMiddlewares/readonlyMiddleware.ts +2 -2
- package/src/actionMiddlewares/transactionMiddleware.ts +4 -4
- package/src/actionMiddlewares/undoMiddleware.ts +804 -796
- package/src/actionMiddlewares/utils.ts +2 -2
- package/src/computedTree/computedTree.ts +143 -143
- package/src/context/context.ts +1 -1
- package/src/dataModel/BaseDataModel.ts +194 -192
- package/src/dataModel/actions.ts +1 -1
- package/src/dataModel/newDataModel.ts +57 -57
- package/src/deepChange/index.ts +1 -1
- package/src/deepChange/onDeepChange.ts +244 -244
- package/src/frozen/Frozen.ts +146 -146
- package/src/globalConfig/globalConfig.ts +107 -107
- package/src/index.ts +24 -24
- package/src/model/BaseModel.ts +275 -273
- package/src/model/ModelConstructorOptions.ts +15 -15
- package/src/model/getModelMetadata.ts +51 -51
- package/src/model/index.ts +6 -6
- package/src/model/mixins.ts +288 -288
- package/src/model/newModel.ts +301 -301
- package/src/model/utils.ts +249 -249
- package/src/modelShared/modelDecorator.ts +260 -253
- package/src/modelShared/modelInfo.ts +50 -50
- package/src/modelShared/modelSymbols.ts +3 -3
- package/src/modelShared/newModel.ts +25 -25
- package/src/modelShared/prop.ts +713 -684
- package/src/modelShared/sharedInternalModel.ts +427 -417
- package/src/modelShared/utils.ts +24 -24
- package/src/parent/core.ts +65 -65
- package/src/parent/coreObjectChildren.ts +180 -180
- package/src/parent/detach.ts +47 -47
- package/src/parent/findParent.ts +1 -1
- package/src/parent/getChildrenObjects.ts +25 -25
- package/src/parent/path.ts +386 -381
- package/src/parent/setParent.ts +149 -149
- package/src/parent/walkTree.ts +1 -1
- package/src/patch/applyPatches.ts +187 -187
- package/src/patch/jsonPatch.ts +108 -108
- package/src/patch/patchRecorder.ts +6 -1
- package/src/redux/connectReduxDevTools.ts +39 -7
- package/src/redux/redux.ts +6 -2
- package/src/ref/Ref.ts +1 -1
- package/src/ref/core.ts +339 -345
- package/src/ref/customRef.ts +3 -3
- package/src/ref/rootRef.ts +100 -100
- package/src/rootStore/attachDetach.ts +89 -89
- package/src/rootStore/rootStore.ts +1 -1
- package/src/snapshot/SnapshotOf.ts +104 -104
- package/src/snapshot/SnapshotProcessingError.ts +45 -45
- package/src/snapshot/applySnapshot.ts +177 -181
- package/src/snapshot/cloneTreeValue.ts +30 -30
- package/src/snapshot/fromArraySnapshot.ts +27 -33
- package/src/snapshot/fromModelSnapshot.ts +70 -70
- package/src/snapshot/fromPlainObjectSnapshot.ts +47 -42
- package/src/snapshot/fromSnapshot.ts +198 -198
- package/src/snapshot/getSnapshot.ts +80 -79
- package/src/snapshot/index.ts +8 -8
- package/src/snapshot/internal.ts +230 -230
- package/src/snapshot/reconcileArraySnapshot.ts +70 -70
- package/src/snapshot/reconcileModelSnapshot.ts +155 -150
- package/src/snapshot/reconcilePlainObjectSnapshot.ts +72 -72
- package/src/snapshot/reconcileSnapshot.ts +88 -88
- package/src/standardActions/actions.ts +64 -64
- package/src/standardActions/objectActions.ts +60 -60
- package/src/transforms/ImmutableDate.ts +93 -93
- package/src/transforms/asMap.ts +50 -50
- package/src/transforms/asSet.ts +22 -22
- package/src/transforms/bigint.ts +20 -20
- package/src/transforms/date.ts +40 -40
- package/src/treeUtils/deepEquals.ts +55 -55
- package/src/treeUtils/sandbox.ts +6 -3
- package/src/tweaker/TweakerPriority.ts +9 -9
- package/src/tweaker/tweak.ts +4 -3
- package/src/tweaker/tweakArray.ts +530 -475
- package/src/tweaker/tweakFrozen.ts +42 -42
- package/src/tweaker/tweakModel.ts +1 -1
- package/src/tweaker/tweakPlainObject.ts +355 -355
- package/src/tweaker/typeChecking.ts +101 -101
- package/src/types/SnapshotTypeMismatchError.ts +46 -46
- package/src/types/TypeCheckError.ts +183 -184
- package/src/types/TypeChecker.ts +202 -185
- package/src/types/arrayBased/typesArray.ts +136 -137
- package/src/types/arrayBased/typesTuple.ts +138 -139
- package/src/types/createPerEntryCachedCheck.ts +166 -160
- package/src/types/index.ts +6 -6
- package/src/types/objectBased/typesArraySet.ts +133 -134
- package/src/types/objectBased/typesDataModelData.ts +203 -204
- package/src/types/objectBased/typesModel.ts +220 -221
- package/src/types/objectBased/typesObject.ts +308 -310
- package/src/types/objectBased/typesObjectMap.ts +142 -143
- package/src/types/objectBased/typesRecord.ts +156 -157
- package/src/types/objectBased/typesRef.ts +89 -89
- package/src/types/primitiveBased/typesEnum.ts +63 -63
- package/src/types/primitiveBased/typesPrimitive.ts +219 -220
- package/src/types/registerDefaultStandardTypeResolvers.ts +21 -21
- package/src/types/resolveTypeChecker.ts +78 -78
- package/src/types/schemas.ts +208 -208
- package/src/types/tProp.ts +277 -268
- package/src/types/typeCheck.ts +21 -21
- package/src/types/typeCheckErrorUtils.ts +23 -23
- package/src/types/types.ts +142 -142
- package/src/types/utility/typeToStoredData.ts +37 -37
- package/src/types/utility/typesBigInt.ts +15 -15
- package/src/types/utility/typesCodec.ts +41 -38
- package/src/types/utility/typesCodecCore.ts +178 -178
- package/src/types/utility/typesCodecSupport.ts +806 -806
- package/src/types/utility/typesDate.ts +27 -27
- package/src/types/utility/typesMap.ts +253 -222
- package/src/types/utility/typesOr.ts +263 -259
- package/src/types/utility/typesRefinement.ts +131 -125
- package/src/types/utility/typesSet.ts +110 -110
- package/src/types/utility/typesSkipCheck.ts +69 -70
- package/src/types/utility/typesTag.ts +88 -82
- package/src/types/utility/typesUnchecked.ts +40 -40
- package/src/utils/AnyFunction.ts +4 -4
- package/src/utils/ModelPool.ts +59 -59
- package/src/utils/chainFns.ts +18 -18
- package/src/utils/decorators.ts +274 -274
- package/src/utils/errorDiagnostics.ts +204 -204
- package/src/utils/index.ts +443 -372
- package/src/wrappers/ArraySet.ts +156 -156
- package/src/wrappers/ObjectMap.ts +155 -136
- package/src/wrappers/asMap.ts +65 -30
- package/src/wrappers/asSet.ts +3 -3
|
@@ -1,806 +1,806 @@
|
|
|
1
|
-
import { isObservableArray, isObservableObject, remove } from "mobx"
|
|
2
|
-
import type { ModelPropTransform } from "../../modelShared/prop"
|
|
3
|
-
import { isArray, isObject, lazy } from "../../utils"
|
|
4
|
-
import { setIfDifferent } from "../../utils/setIfDifferent"
|
|
5
|
-
import { ArrayTypeInfo, typesArray } from "../arrayBased/typesArray"
|
|
6
|
-
import { TupleTypeInfo, typesTuple } from "../arrayBased/typesTuple"
|
|
7
|
-
import { getTypeInfo } from "../getTypeInfo"
|
|
8
|
-
import { isDataModelDataStandardType } from "../objectBased/typesDataModelData"
|
|
9
|
-
import { isModelStandardType } from "../objectBased/typesModel"
|
|
10
|
-
import { ObjectTypeInfo, typesObject } from "../objectBased/typesObject"
|
|
11
|
-
import { RecordTypeInfo, typesRecord } from "../objectBased/typesRecord"
|
|
12
|
-
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
13
|
-
import type { AnyStandardType, AnyType } from "../schemas"
|
|
14
|
-
import { lateTypeChecker } from "../TypeChecker"
|
|
15
|
-
import {
|
|
16
|
-
getCodecMetadata,
|
|
17
|
-
identityRuntimeAdapter,
|
|
18
|
-
type ResolvedCodecSupport,
|
|
19
|
-
type RuntimeAdapter,
|
|
20
|
-
} from "./typesCodecCore"
|
|
21
|
-
import { OrTypeInfo, typesOr } from "./typesOr"
|
|
22
|
-
import { RefinementTypeInfo } from "./typesRefinement"
|
|
23
|
-
import { SkipCheckTypeInfo, typesSkipCheck } from "./typesSkipCheck"
|
|
24
|
-
import { TagTypeInfo, typesTag } from "./typesTag"
|
|
25
|
-
|
|
26
|
-
const codecSupportCache = new WeakMap<AnyStandardType, ResolvedCodecSupport>()
|
|
27
|
-
|
|
28
|
-
function getArrayIndex(prop: PropertyKey): number | undefined {
|
|
29
|
-
// Proxy get/set traps always receive string|symbol keys (ECMAScript ToPropertyKey),
|
|
30
|
-
// so we only need to check for string numeric indices.
|
|
31
|
-
if (typeof prop === "string" && /^(0|[1-9]\d*)$/.test(prop)) {
|
|
32
|
-
return Number(prop)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return undefined
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function normalizeArrayIndex(index: number, length: number): number {
|
|
39
|
-
if (index < 0) {
|
|
40
|
-
return Math.max(length + index, 0)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return Math.min(index, length)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function resolveArrayAtIndex(index: number, length: number): number | undefined {
|
|
47
|
-
const resolvedIndex = index < 0 ? length + index : index
|
|
48
|
-
|
|
49
|
-
if (resolvedIndex < 0 || resolvedIndex >= length) {
|
|
50
|
-
return undefined
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return resolvedIndex
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Common proxy traps shared between object and record runtime adapters.
|
|
58
|
-
* Only deleteProperty needs special handling for MobX observable objects.
|
|
59
|
-
*/
|
|
60
|
-
const objectRecordProxyTraps: Pick<ProxyHandler<Record<string, unknown>>, "deleteProperty"> = {
|
|
61
|
-
deleteProperty(target, prop) {
|
|
62
|
-
if (typeof prop === "string" && isObservableObject(target)) {
|
|
63
|
-
remove(target, prop)
|
|
64
|
-
return true
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return Reflect.deleteProperty(target, prop)
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Converts a runtime object to its stored form using a cache.
|
|
73
|
-
* Shared by object and record adapters.
|
|
74
|
-
*/
|
|
75
|
-
function objectLikeToStored(
|
|
76
|
-
runtime: unknown,
|
|
77
|
-
storedByRuntime: WeakMap<object, Record<string, unknown>>,
|
|
78
|
-
convertValue: (key: string, value: unknown) => unknown
|
|
79
|
-
): Record<string, unknown> {
|
|
80
|
-
if (isObject(runtime)) {
|
|
81
|
-
const cachedStored = storedByRuntime.get(runtime)
|
|
82
|
-
if (cachedStored) {
|
|
83
|
-
return cachedStored
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (isArray(runtime)) {
|
|
87
|
-
return runtime as never
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
return runtime as never
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const stored: Record<string, unknown> = {}
|
|
94
|
-
for (const key of Object.keys(runtime)) {
|
|
95
|
-
stored[key] = convertValue(key, runtime[key])
|
|
96
|
-
}
|
|
97
|
-
return stored
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Checks the stored cache for a runtime object.
|
|
102
|
-
* Shared by object and record adapters.
|
|
103
|
-
*/
|
|
104
|
-
function objectLikeToStoredIfCached(
|
|
105
|
-
runtime: unknown,
|
|
106
|
-
storedByRuntime: WeakMap<object, Record<string, unknown>>
|
|
107
|
-
): { found: true; value: Record<string, unknown> } | { found: false } {
|
|
108
|
-
if (isObject(runtime) && !isArray(runtime)) {
|
|
109
|
-
const cachedStored = storedByRuntime.get(runtime)
|
|
110
|
-
if (cachedStored) {
|
|
111
|
-
return { found: true, value: cachedStored }
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return { found: false }
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function createObjectRuntimeAdapter(
|
|
119
|
-
getChildSupports: () => Record<string, ResolvedCodecSupport>
|
|
120
|
-
): RuntimeAdapter<Record<string, unknown>, Record<string, unknown>> {
|
|
121
|
-
const runtimeByStored = new WeakMap<object, Record<string, unknown>>()
|
|
122
|
-
const storedByRuntime = new WeakMap<object, Record<string, unknown>>()
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
toRuntime(stored) {
|
|
126
|
-
if (!isObject(stored) || isArray(stored)) {
|
|
127
|
-
return stored as never
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const cachedRuntime = runtimeByStored.get(stored)
|
|
131
|
-
if (cachedRuntime) {
|
|
132
|
-
return cachedRuntime
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const proxy = new Proxy(stored, {
|
|
136
|
-
get(target, prop, receiver) {
|
|
137
|
-
if (typeof prop === "string") {
|
|
138
|
-
const childSupport = getChildSupports()[prop]
|
|
139
|
-
if (childSupport && Object.hasOwn(target, prop)) {
|
|
140
|
-
return childSupport.adapter.toRuntime(target[prop], (newStoredValue) => {
|
|
141
|
-
target[prop] = newStoredValue
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return Reflect.get(target, prop, receiver)
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
set(target, prop, value) {
|
|
150
|
-
if (typeof prop === "string") {
|
|
151
|
-
const childSupport = getChildSupports()[prop]
|
|
152
|
-
if (childSupport) {
|
|
153
|
-
setIfDifferent(target, prop, childSupport.adapter.toStored(value))
|
|
154
|
-
return true
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
Reflect.set(target, prop, value)
|
|
159
|
-
return true
|
|
160
|
-
},
|
|
161
|
-
|
|
162
|
-
...objectRecordProxyTraps,
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
runtimeByStored.set(stored, proxy)
|
|
166
|
-
storedByRuntime.set(proxy, stored)
|
|
167
|
-
|
|
168
|
-
return proxy
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
toStored(runtime) {
|
|
172
|
-
const childSupports = getChildSupports()
|
|
173
|
-
return objectLikeToStored(runtime, storedByRuntime, (key, value) => {
|
|
174
|
-
const childSupport = childSupports[key]
|
|
175
|
-
return childSupport ? childSupport.adapter.toStored(value) : value
|
|
176
|
-
})
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
toStoredIfCached(runtime) {
|
|
180
|
-
return objectLikeToStoredIfCached(runtime, storedByRuntime)
|
|
181
|
-
},
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function createRecordRuntimeAdapter(
|
|
186
|
-
getValueSupport: () => ResolvedCodecSupport
|
|
187
|
-
): RuntimeAdapter<Record<string, unknown>, Record<string, unknown>> {
|
|
188
|
-
const runtimeByStored = new WeakMap<object, Record<string, unknown>>()
|
|
189
|
-
const storedByRuntime = new WeakMap<object, Record<string, unknown>>()
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
toRuntime(stored) {
|
|
193
|
-
if (!isObject(stored) || isArray(stored)) {
|
|
194
|
-
return stored as never
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const cachedRuntime = runtimeByStored.get(stored)
|
|
198
|
-
if (cachedRuntime) {
|
|
199
|
-
return cachedRuntime
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const proxy = new Proxy(stored, {
|
|
203
|
-
get(target, prop, receiver) {
|
|
204
|
-
if (typeof prop === "string" && Object.hasOwn(target, prop)) {
|
|
205
|
-
const valueSupport = getValueSupport()
|
|
206
|
-
return valueSupport.adapter.toRuntime(target[prop], (newStoredValue) => {
|
|
207
|
-
target[prop] = newStoredValue
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return Reflect.get(target, prop, receiver)
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
set(target, prop, value) {
|
|
215
|
-
if (typeof prop === "string") {
|
|
216
|
-
setIfDifferent(target, prop, getValueSupport().adapter.toStored(value))
|
|
217
|
-
return true
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
Reflect.set(target, prop, value)
|
|
221
|
-
return true
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
...objectRecordProxyTraps,
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
runtimeByStored.set(stored, proxy)
|
|
228
|
-
storedByRuntime.set(proxy, stored)
|
|
229
|
-
|
|
230
|
-
return proxy
|
|
231
|
-
},
|
|
232
|
-
|
|
233
|
-
toStored(runtime) {
|
|
234
|
-
const valueSupport = getValueSupport()
|
|
235
|
-
return objectLikeToStored(runtime, storedByRuntime, (_key, value) =>
|
|
236
|
-
valueSupport.adapter.toStored(value)
|
|
237
|
-
)
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
toStoredIfCached(runtime) {
|
|
241
|
-
return objectLikeToStoredIfCached(runtime, storedByRuntime)
|
|
242
|
-
},
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function createArrayLikeRuntimeAdapter(
|
|
247
|
-
getItemAdapter: (index: number) => RuntimeAdapter<unknown, unknown>
|
|
248
|
-
): RuntimeAdapter<unknown[], unknown[]> {
|
|
249
|
-
const runtimeByStored = new WeakMap<unknown[], unknown[]>()
|
|
250
|
-
const storedByRuntime = new WeakMap<unknown[], unknown[]>()
|
|
251
|
-
|
|
252
|
-
const decodeIndex = (target: unknown[], index: number) =>
|
|
253
|
-
index < 0 || index >= target.length || !Reflect.has(target, index)
|
|
254
|
-
? undefined
|
|
255
|
-
: getItemAdapter(index).toRuntime(target[index], (newStoredValue) => {
|
|
256
|
-
target[index] = newStoredValue
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
const callReadOnlyRuntimeMethod = (
|
|
260
|
-
receiver: unknown[],
|
|
261
|
-
methodName: PropertyKey,
|
|
262
|
-
args: unknown[]
|
|
263
|
-
) => {
|
|
264
|
-
const method = Reflect.get(Array.prototype, methodName, receiver)
|
|
265
|
-
return Reflect.apply(method, receiver, args)
|
|
266
|
-
}
|
|
267
|
-
const materializeRuntimeValues = (target: unknown[]) =>
|
|
268
|
-
Array.from({ length: target.length }, (_, index) => decodeIndex(target, index))
|
|
269
|
-
const replaceStoredValues = (target: unknown[], runtimeValues: unknown[]) => {
|
|
270
|
-
target.splice(
|
|
271
|
-
0,
|
|
272
|
-
target.length,
|
|
273
|
-
...runtimeValues.map((value, index) => getItemAdapter(index).toStored(value))
|
|
274
|
-
)
|
|
275
|
-
}
|
|
276
|
-
const mutateViaRuntime = (
|
|
277
|
-
target: unknown[],
|
|
278
|
-
receiver: unknown[],
|
|
279
|
-
fn: (values: unknown[]) => void
|
|
280
|
-
) => {
|
|
281
|
-
const runtimeValues = materializeRuntimeValues(target)
|
|
282
|
-
fn(runtimeValues)
|
|
283
|
-
replaceStoredValues(target, runtimeValues)
|
|
284
|
-
return receiver
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return {
|
|
288
|
-
toRuntime(stored) {
|
|
289
|
-
if (!isArray(stored)) {
|
|
290
|
-
return stored as never
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const cachedRuntime = runtimeByStored.get(stored)
|
|
294
|
-
if (cachedRuntime) {
|
|
295
|
-
return cachedRuntime
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const proxy: unknown[] = new Proxy(stored, {
|
|
299
|
-
get(target, prop, receiver) {
|
|
300
|
-
const index = getArrayIndex(prop)
|
|
301
|
-
if (index !== undefined) {
|
|
302
|
-
return decodeIndex(target, index)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// MobX 4 observable arrays are array-like but fail `Array.isArray`, so native
|
|
306
|
-
// `Array.prototype.concat` won't spread this proxy unless we opt in explicitly.
|
|
307
|
-
if (
|
|
308
|
-
prop === Symbol.isConcatSpreadable &&
|
|
309
|
-
isObservableArray(target) &&
|
|
310
|
-
!Array.isArray(target)
|
|
311
|
-
) {
|
|
312
|
-
return true
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
switch (prop) {
|
|
316
|
-
case "at":
|
|
317
|
-
return (indexValue: number) => {
|
|
318
|
-
const resolvedIndex = resolveArrayAtIndex(indexValue, target.length)
|
|
319
|
-
return resolvedIndex === undefined ? undefined : decodeIndex(target, resolvedIndex)
|
|
320
|
-
}
|
|
321
|
-
case "push":
|
|
322
|
-
return (...items: unknown[]) =>
|
|
323
|
-
target.push(
|
|
324
|
-
...items.map((item, offset) =>
|
|
325
|
-
getItemAdapter(target.length + offset).toStored(item)
|
|
326
|
-
)
|
|
327
|
-
)
|
|
328
|
-
case "pop":
|
|
329
|
-
return () => {
|
|
330
|
-
if (target.length <= 0) {
|
|
331
|
-
return undefined
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const indexValue = target.length - 1
|
|
335
|
-
const value = target.pop()
|
|
336
|
-
return getItemAdapter(indexValue).toRuntime(value)
|
|
337
|
-
}
|
|
338
|
-
case "shift":
|
|
339
|
-
return () => {
|
|
340
|
-
if (target.length <= 0) {
|
|
341
|
-
return undefined
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const value = target.shift()
|
|
345
|
-
return getItemAdapter(0).toRuntime(value)
|
|
346
|
-
}
|
|
347
|
-
case "unshift":
|
|
348
|
-
return (...items: unknown[]) =>
|
|
349
|
-
target.unshift(
|
|
350
|
-
...items.map((item, indexValue) => getItemAdapter(indexValue).toStored(item))
|
|
351
|
-
)
|
|
352
|
-
case "splice":
|
|
353
|
-
return (start: number, deleteCount?: number, ...items: unknown[]) => {
|
|
354
|
-
const normalizedStart = normalizeArrayIndex(start, target.length)
|
|
355
|
-
const actualDeleteCount =
|
|
356
|
-
deleteCount === undefined
|
|
357
|
-
? target.length - normalizedStart
|
|
358
|
-
: Math.max(0, Math.min(deleteCount, target.length - normalizedStart))
|
|
359
|
-
|
|
360
|
-
const removedValues = target
|
|
361
|
-
.slice(normalizedStart, normalizedStart + actualDeleteCount)
|
|
362
|
-
.map((value, indexValue) =>
|
|
363
|
-
getItemAdapter(normalizedStart + indexValue).toRuntime(value)
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
target.splice(
|
|
367
|
-
normalizedStart,
|
|
368
|
-
actualDeleteCount,
|
|
369
|
-
...items.map((item, indexValue) =>
|
|
370
|
-
getItemAdapter(normalizedStart + indexValue).toStored(item)
|
|
371
|
-
)
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
return removedValues
|
|
375
|
-
}
|
|
376
|
-
case "reverse":
|
|
377
|
-
return () => mutateViaRuntime(target, receiver, (v) => v.reverse())
|
|
378
|
-
case "sort":
|
|
379
|
-
return (compareFn?: (a: unknown, b: unknown) => number) =>
|
|
380
|
-
mutateViaRuntime(target, receiver, (v) => v.sort(compareFn))
|
|
381
|
-
case "fill":
|
|
382
|
-
return (value: unknown, start?: number, end?: number) =>
|
|
383
|
-
mutateViaRuntime(target, receiver, (v) => v.fill(value, start, end))
|
|
384
|
-
case "copyWithin":
|
|
385
|
-
return (targetIndex: number, start?: number, end?: number) =>
|
|
386
|
-
mutateViaRuntime(target, receiver, (v) =>
|
|
387
|
-
v.copyWithin(targetIndex, start ?? 0, end)
|
|
388
|
-
)
|
|
389
|
-
case "concat":
|
|
390
|
-
case "flat":
|
|
391
|
-
case "flatMap":
|
|
392
|
-
case "filter":
|
|
393
|
-
case "find":
|
|
394
|
-
case "findIndex":
|
|
395
|
-
case "forEach":
|
|
396
|
-
case "every":
|
|
397
|
-
case "includes":
|
|
398
|
-
case "indexOf":
|
|
399
|
-
case "join":
|
|
400
|
-
case "lastIndexOf":
|
|
401
|
-
case "map":
|
|
402
|
-
case "reduce":
|
|
403
|
-
case "reduceRight":
|
|
404
|
-
case "slice":
|
|
405
|
-
case "some":
|
|
406
|
-
case "findLast":
|
|
407
|
-
case "findLastIndex":
|
|
408
|
-
case "toReversed":
|
|
409
|
-
case "toSorted":
|
|
410
|
-
case "toSpliced":
|
|
411
|
-
case "toString":
|
|
412
|
-
case "toLocaleString":
|
|
413
|
-
case "with":
|
|
414
|
-
return (...args: unknown[]) => callReadOnlyRuntimeMethod(receiver, prop, args)
|
|
415
|
-
case "entries":
|
|
416
|
-
return function* () {
|
|
417
|
-
for (let i = 0; i < target.length; i++) {
|
|
418
|
-
yield [i, decodeIndex(target, i)] as const
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
case "keys":
|
|
422
|
-
return function* () {
|
|
423
|
-
for (let i = 0; i < target.length; i++) {
|
|
424
|
-
yield i
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
case "values":
|
|
428
|
-
case Symbol.iterator:
|
|
429
|
-
return function* () {
|
|
430
|
-
for (let i = 0; i < target.length; i++) {
|
|
431
|
-
yield decodeIndex(target, i)
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
default:
|
|
435
|
-
return Reflect.get(target, prop, receiver)
|
|
436
|
-
}
|
|
437
|
-
},
|
|
438
|
-
|
|
439
|
-
set(target, prop, value) {
|
|
440
|
-
const index = getArrayIndex(prop)
|
|
441
|
-
if (index !== undefined) {
|
|
442
|
-
target[index] = getItemAdapter(index).toStored(value)
|
|
443
|
-
return true
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
Reflect.set(target, prop, value)
|
|
447
|
-
return true
|
|
448
|
-
},
|
|
449
|
-
})
|
|
450
|
-
|
|
451
|
-
runtimeByStored.set(stored, proxy)
|
|
452
|
-
storedByRuntime.set(proxy, stored)
|
|
453
|
-
|
|
454
|
-
return proxy
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
toStored(runtime) {
|
|
458
|
-
if (isArray(runtime)) {
|
|
459
|
-
const cachedStored = storedByRuntime.get(runtime)
|
|
460
|
-
if (cachedStored) {
|
|
461
|
-
return cachedStored
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return Array.from(runtime, (value, index) => getItemAdapter(index).toStored(value))
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
return runtime as never
|
|
468
|
-
},
|
|
469
|
-
|
|
470
|
-
toStoredIfCached(runtime) {
|
|
471
|
-
if (isArray(runtime)) {
|
|
472
|
-
const cachedStored = storedByRuntime.get(runtime)
|
|
473
|
-
if (cachedStored) {
|
|
474
|
-
return { found: true, value: cachedStored }
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return { found: false }
|
|
479
|
-
},
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function createCodecLeafRuntimeAdapter(
|
|
484
|
-
transform: ModelPropTransform<unknown, unknown>
|
|
485
|
-
): RuntimeAdapter<unknown, unknown> {
|
|
486
|
-
const runtimeByStored = new WeakMap<object, unknown>()
|
|
487
|
-
const storedByRuntime = new WeakMap<object, unknown>()
|
|
488
|
-
|
|
489
|
-
return {
|
|
490
|
-
toRuntime(stored, setStored) {
|
|
491
|
-
const cachedRuntime = isObject(stored) ? runtimeByStored.get(stored) : undefined
|
|
492
|
-
|
|
493
|
-
const runtime = transform.transform({
|
|
494
|
-
originalValue: stored,
|
|
495
|
-
cachedTransformedValue: cachedRuntime,
|
|
496
|
-
setOriginalValue(newStoredValue) {
|
|
497
|
-
if (setStored) {
|
|
498
|
-
setStored(newStoredValue)
|
|
499
|
-
}
|
|
500
|
-
},
|
|
501
|
-
})
|
|
502
|
-
|
|
503
|
-
if (isObject(runtime)) {
|
|
504
|
-
storedByRuntime.set(runtime, stored)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (isObject(stored)) {
|
|
508
|
-
runtimeByStored.set(stored, runtime)
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return runtime
|
|
512
|
-
},
|
|
513
|
-
|
|
514
|
-
toStored(runtime) {
|
|
515
|
-
const cachedStored = isObject(runtime) ? storedByRuntime.get(runtime) : undefined
|
|
516
|
-
if (cachedStored !== undefined) {
|
|
517
|
-
return cachedStored
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const stored = transform.untransform({
|
|
521
|
-
transformedValue: runtime,
|
|
522
|
-
cacheTransformedValue: () => {},
|
|
523
|
-
})
|
|
524
|
-
|
|
525
|
-
if (isObject(runtime)) {
|
|
526
|
-
storedByRuntime.set(runtime, stored)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (isObject(stored)) {
|
|
530
|
-
runtimeByStored.set(stored, runtime)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return stored
|
|
534
|
-
},
|
|
535
|
-
|
|
536
|
-
toStoredIfCached(runtime) {
|
|
537
|
-
if (isObject(runtime)) {
|
|
538
|
-
const cachedStored = storedByRuntime.get(runtime)
|
|
539
|
-
if (cachedStored !== undefined) {
|
|
540
|
-
return { found: true, value: cachedStored }
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return { found: false }
|
|
545
|
-
},
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function createOrRuntimeAdapter(
|
|
550
|
-
childTypes: ReadonlyArray<AnyStandardType>,
|
|
551
|
-
getChildSupports: () => ReadonlyArray<ResolvedCodecSupport>,
|
|
552
|
-
dispatcher: OrTypeInfo["dispatcher"]
|
|
553
|
-
): RuntimeAdapter<unknown, unknown> {
|
|
554
|
-
const getRuntimeSupportForValue = (value: unknown) => {
|
|
555
|
-
const supports = getChildSupports()
|
|
556
|
-
for (let i = 0; i < supports.length; i++) {
|
|
557
|
-
const childType = childTypes[i]
|
|
558
|
-
const tc = resolveTypeChecker(childType)
|
|
559
|
-
if (tc.skipCheck) {
|
|
560
|
-
// For skipCheck branches, use snapshotType for structural matching
|
|
561
|
-
// instead of check (which always passes and would match any value)
|
|
562
|
-
if (tc.snapshotType(value)) {
|
|
563
|
-
return supports[i]
|
|
564
|
-
}
|
|
565
|
-
} else if (!tc.check(value, [], value)) {
|
|
566
|
-
return supports[i]
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
return supports[0]
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const getStoredSupportForValue = (value: unknown) => {
|
|
574
|
-
if (dispatcher) {
|
|
575
|
-
return resolveCodecSupport(dispatcher(value))
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const supports = getChildSupports()
|
|
579
|
-
for (let i = 0; i < supports.length; i++) {
|
|
580
|
-
const storedTypeChecker = resolveTypeChecker(supports[i].storedType)
|
|
581
|
-
if (storedTypeChecker.snapshotType(value)) {
|
|
582
|
-
return supports[i]
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
return supports[0]
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
return {
|
|
590
|
-
toRuntime(stored, setStored) {
|
|
591
|
-
return getStoredSupportForValue(stored).adapter.toRuntime(stored, setStored)
|
|
592
|
-
},
|
|
593
|
-
|
|
594
|
-
toStored(runtime) {
|
|
595
|
-
return getRuntimeSupportForValue(runtime).adapter.toStored(runtime)
|
|
596
|
-
},
|
|
597
|
-
|
|
598
|
-
toStoredIfCached(runtime) {
|
|
599
|
-
return getRuntimeSupportForValue(runtime).adapter.toStoredIfCached(runtime)
|
|
600
|
-
},
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function noCodecSupport(type: AnyStandardType): ResolvedCodecSupport {
|
|
605
|
-
return { hasCodec: false, storedType: type, adapter: identityRuntimeAdapter }
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function resolveCodecSupportForStandardType(type: AnyStandardType): ResolvedCodecSupport {
|
|
609
|
-
const cachedSupport = codecSupportCache.get(type)
|
|
610
|
-
if (cachedSupport) {
|
|
611
|
-
return cachedSupport
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
let resolvedSupport: ResolvedCodecSupport | undefined
|
|
615
|
-
let resolvedStoredType: AnyStandardType | undefined
|
|
616
|
-
|
|
617
|
-
const placeholderSupport: ResolvedCodecSupport = {
|
|
618
|
-
hasCodec: false,
|
|
619
|
-
storedType: lateTypeChecker(
|
|
620
|
-
() => resolveTypeChecker(resolvedStoredType ?? type),
|
|
621
|
-
() => getTypeInfo(resolvedStoredType ?? type)
|
|
622
|
-
) as any,
|
|
623
|
-
adapter: {
|
|
624
|
-
toRuntime(stored, setStored) {
|
|
625
|
-
return (resolvedSupport?.adapter ?? identityRuntimeAdapter).toRuntime(stored, setStored)
|
|
626
|
-
},
|
|
627
|
-
|
|
628
|
-
toStored(runtime) {
|
|
629
|
-
return (resolvedSupport?.adapter ?? identityRuntimeAdapter).toStored(runtime)
|
|
630
|
-
},
|
|
631
|
-
|
|
632
|
-
toStoredIfCached(runtime) {
|
|
633
|
-
return (resolvedSupport?.adapter ?? identityRuntimeAdapter).toStoredIfCached(runtime)
|
|
634
|
-
},
|
|
635
|
-
},
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
codecSupportCache.set(type, placeholderSupport)
|
|
639
|
-
|
|
640
|
-
const codecMetadata = getCodecMetadata(type)
|
|
641
|
-
if (codecMetadata) {
|
|
642
|
-
resolvedSupport = {
|
|
643
|
-
hasCodec: true,
|
|
644
|
-
storedType: resolveStandardType(codecMetadata.encodedType),
|
|
645
|
-
adapter: createCodecLeafRuntimeAdapter(codecMetadata.transform),
|
|
646
|
-
}
|
|
647
|
-
} else if (isModelStandardType(type) || isDataModelDataStandardType(type)) {
|
|
648
|
-
resolvedSupport = noCodecSupport(type)
|
|
649
|
-
} else {
|
|
650
|
-
const typeInfo = getTypeInfo(type)
|
|
651
|
-
|
|
652
|
-
if (typeInfo instanceof ArrayTypeInfo) {
|
|
653
|
-
const itemSupport = lazy(() => resolveCodecSupportForStandardType(typeInfo.itemType))
|
|
654
|
-
|
|
655
|
-
resolvedSupport = !itemSupport().hasCodec
|
|
656
|
-
? noCodecSupport(type)
|
|
657
|
-
: {
|
|
658
|
-
hasCodec: true,
|
|
659
|
-
storedType: typesArray(itemSupport().storedType),
|
|
660
|
-
adapter: createArrayLikeRuntimeAdapter(() => itemSupport().adapter),
|
|
661
|
-
}
|
|
662
|
-
} else if (typeInfo instanceof TupleTypeInfo) {
|
|
663
|
-
const itemSupports = lazy(() =>
|
|
664
|
-
typeInfo.itemTypes.map((itemType) => resolveCodecSupportForStandardType(itemType))
|
|
665
|
-
)
|
|
666
|
-
|
|
667
|
-
resolvedSupport = !itemSupports().some((itemSupport) => itemSupport.hasCodec)
|
|
668
|
-
? noCodecSupport(type)
|
|
669
|
-
: {
|
|
670
|
-
hasCodec: true,
|
|
671
|
-
storedType: typesTuple(...itemSupports().map((itemSupport) => itemSupport.storedType)),
|
|
672
|
-
adapter: createArrayLikeRuntimeAdapter(
|
|
673
|
-
(index) => itemSupports()[index]?.adapter ?? identityRuntimeAdapter
|
|
674
|
-
),
|
|
675
|
-
}
|
|
676
|
-
} else if (typeInfo instanceof ObjectTypeInfo) {
|
|
677
|
-
const childSupports = lazy(() => {
|
|
678
|
-
const supports: Record<string, ResolvedCodecSupport> = {}
|
|
679
|
-
const props = typeInfo.props
|
|
680
|
-
for (const propName of Object.keys(props)) {
|
|
681
|
-
supports[propName] = resolveCodecSupportForStandardType(props[propName].type)
|
|
682
|
-
}
|
|
683
|
-
return supports
|
|
684
|
-
})
|
|
685
|
-
|
|
686
|
-
resolvedSupport = !Object.values(childSupports()).some(
|
|
687
|
-
(childSupport) => childSupport.hasCodec
|
|
688
|
-
)
|
|
689
|
-
? noCodecSupport(type)
|
|
690
|
-
: {
|
|
691
|
-
hasCodec: true,
|
|
692
|
-
storedType: typesObject(() => {
|
|
693
|
-
const storedProps: Record<string, AnyStandardType> = {}
|
|
694
|
-
const props = typeInfo.props
|
|
695
|
-
for (const propName of Object.keys(props)) {
|
|
696
|
-
storedProps[propName] = childSupports()[propName].storedType
|
|
697
|
-
}
|
|
698
|
-
return storedProps
|
|
699
|
-
}),
|
|
700
|
-
adapter: createObjectRuntimeAdapter(childSupports),
|
|
701
|
-
}
|
|
702
|
-
} else if (typeInfo instanceof RecordTypeInfo) {
|
|
703
|
-
const valueSupport = lazy(() => resolveCodecSupportForStandardType(typeInfo.valueType))
|
|
704
|
-
|
|
705
|
-
resolvedSupport = !valueSupport().hasCodec
|
|
706
|
-
? noCodecSupport(type)
|
|
707
|
-
: {
|
|
708
|
-
hasCodec: true,
|
|
709
|
-
storedType: typesRecord(valueSupport().storedType),
|
|
710
|
-
adapter: createRecordRuntimeAdapter(valueSupport),
|
|
711
|
-
}
|
|
712
|
-
} else if (typeInfo instanceof TagTypeInfo) {
|
|
713
|
-
const baseSupport = resolveCodecSupportForStandardType(typeInfo.baseType)
|
|
714
|
-
|
|
715
|
-
resolvedSupport = !baseSupport.hasCodec
|
|
716
|
-
? noCodecSupport(type)
|
|
717
|
-
: {
|
|
718
|
-
hasCodec: true,
|
|
719
|
-
storedType: typesTag(baseSupport.storedType, typeInfo.tag, typeInfo.typeName),
|
|
720
|
-
adapter: baseSupport.adapter,
|
|
721
|
-
}
|
|
722
|
-
} else if (typeInfo instanceof RefinementTypeInfo) {
|
|
723
|
-
const baseSupport = resolveCodecSupportForStandardType(typeInfo.baseType)
|
|
724
|
-
|
|
725
|
-
resolvedSupport = baseSupport.hasCodec ? baseSupport : noCodecSupport(type)
|
|
726
|
-
} else if (typeInfo instanceof SkipCheckTypeInfo) {
|
|
727
|
-
const baseSupport = resolveCodecSupportForStandardType(typeInfo.baseType)
|
|
728
|
-
|
|
729
|
-
// Always wrap storedType with skipCheck, even when hasCodec is false.
|
|
730
|
-
// resolveStoredType() feeds model-internal automatic validation in sharedInternalModel.ts,
|
|
731
|
-
// so the skipCheck wrapper must propagate to prevent the stored type from being validated.
|
|
732
|
-
resolvedSupport = {
|
|
733
|
-
hasCodec: baseSupport.hasCodec,
|
|
734
|
-
storedType: typesSkipCheck(baseSupport.storedType),
|
|
735
|
-
adapter: baseSupport.adapter,
|
|
736
|
-
}
|
|
737
|
-
} else if (typeInfo instanceof OrTypeInfo) {
|
|
738
|
-
const childSupports = lazy(() =>
|
|
739
|
-
typeInfo.orTypes.map((orType) => resolveCodecSupportForStandardType(orType))
|
|
740
|
-
)
|
|
741
|
-
|
|
742
|
-
resolvedSupport = !childSupports().some((childSupport) => childSupport.hasCodec)
|
|
743
|
-
? noCodecSupport(type)
|
|
744
|
-
: {
|
|
745
|
-
hasCodec: true,
|
|
746
|
-
storedType: typeInfo.dispatcher
|
|
747
|
-
? typesOr(
|
|
748
|
-
(snapshot) => resolveStoredType(typeInfo.dispatcher!(snapshot)),
|
|
749
|
-
...childSupports().map((childSupport) => childSupport.storedType)
|
|
750
|
-
)
|
|
751
|
-
: typesOr(...childSupports().map((childSupport) => childSupport.storedType)),
|
|
752
|
-
adapter: createOrRuntimeAdapter(typeInfo.orTypes, childSupports, typeInfo.dispatcher),
|
|
753
|
-
}
|
|
754
|
-
} else {
|
|
755
|
-
resolvedSupport = noCodecSupport(type)
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
resolvedStoredType = resolvedSupport.storedType
|
|
760
|
-
placeholderSupport.hasCodec = resolvedSupport.hasCodec
|
|
761
|
-
placeholderSupport.storedType = resolvedSupport.storedType
|
|
762
|
-
placeholderSupport.adapter = resolvedSupport.adapter
|
|
763
|
-
|
|
764
|
-
return placeholderSupport
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
export function resolveCodecSupport(type: AnyType) {
|
|
768
|
-
return resolveCodecSupportForStandardType(resolveStandardType(type))
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
export function resolveStoredType(type: AnyType) {
|
|
772
|
-
return resolveCodecSupport(type).storedType
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
export function resolveStoredTypeChecker(type: AnyType) {
|
|
776
|
-
return resolveTypeChecker(resolveCodecSupport(type).storedType)
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
export function createCodecPropTransform(type: AnyType): ModelPropTransform<any, unknown> {
|
|
780
|
-
const adapter = resolveCodecSupport(type).adapter
|
|
781
|
-
|
|
782
|
-
return {
|
|
783
|
-
transform({ originalValue, cachedTransformedValue, setOriginalValue }) {
|
|
784
|
-
// For leaf codecs backed by primitive stored data (e.g. timestamps, ISO strings),
|
|
785
|
-
// the adapter's WeakMap cache can't work. Thread the model's cachedTransformedValue
|
|
786
|
-
// to avoid creating new runtime objects when the stored value hasn't changed.
|
|
787
|
-
if (cachedTransformedValue !== undefined) {
|
|
788
|
-
const cachedStored = adapter.toStoredIfCached(cachedTransformedValue)
|
|
789
|
-
if (cachedStored.found && cachedStored.value === originalValue) {
|
|
790
|
-
return cachedTransformedValue
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
return adapter.toRuntime(originalValue, setOriginalValue)
|
|
794
|
-
},
|
|
795
|
-
|
|
796
|
-
untransform({ transformedValue, cacheTransformedValue }) {
|
|
797
|
-
const cachedStored = adapter.toStoredIfCached(transformedValue)
|
|
798
|
-
if (cachedStored.found) {
|
|
799
|
-
cacheTransformedValue()
|
|
800
|
-
return cachedStored.value
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
return adapter.toStored(transformedValue)
|
|
804
|
-
},
|
|
805
|
-
}
|
|
806
|
-
}
|
|
1
|
+
import { isObservableArray, isObservableObject, remove } from "mobx"
|
|
2
|
+
import type { ModelPropTransform } from "../../modelShared/prop"
|
|
3
|
+
import { isArray, isObject, lazy } from "../../utils"
|
|
4
|
+
import { setIfDifferent } from "../../utils/setIfDifferent"
|
|
5
|
+
import { ArrayTypeInfo, typesArray } from "../arrayBased/typesArray"
|
|
6
|
+
import { TupleTypeInfo, typesTuple } from "../arrayBased/typesTuple"
|
|
7
|
+
import { getTypeInfo } from "../getTypeInfo"
|
|
8
|
+
import { isDataModelDataStandardType } from "../objectBased/typesDataModelData"
|
|
9
|
+
import { isModelStandardType } from "../objectBased/typesModel"
|
|
10
|
+
import { ObjectTypeInfo, typesObject } from "../objectBased/typesObject"
|
|
11
|
+
import { RecordTypeInfo, typesRecord } from "../objectBased/typesRecord"
|
|
12
|
+
import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker"
|
|
13
|
+
import type { AnyStandardType, AnyType } from "../schemas"
|
|
14
|
+
import { lateTypeChecker } from "../TypeChecker"
|
|
15
|
+
import {
|
|
16
|
+
getCodecMetadata,
|
|
17
|
+
identityRuntimeAdapter,
|
|
18
|
+
type ResolvedCodecSupport,
|
|
19
|
+
type RuntimeAdapter,
|
|
20
|
+
} from "./typesCodecCore"
|
|
21
|
+
import { OrTypeInfo, typesOr } from "./typesOr"
|
|
22
|
+
import { RefinementTypeInfo } from "./typesRefinement"
|
|
23
|
+
import { SkipCheckTypeInfo, typesSkipCheck } from "./typesSkipCheck"
|
|
24
|
+
import { TagTypeInfo, typesTag } from "./typesTag"
|
|
25
|
+
|
|
26
|
+
const codecSupportCache = new WeakMap<AnyStandardType, ResolvedCodecSupport>()
|
|
27
|
+
|
|
28
|
+
function getArrayIndex(prop: PropertyKey): number | undefined {
|
|
29
|
+
// Proxy get/set traps always receive string|symbol keys (ECMAScript ToPropertyKey),
|
|
30
|
+
// so we only need to check for string numeric indices.
|
|
31
|
+
if (typeof prop === "string" && /^(0|[1-9]\d*)$/.test(prop)) {
|
|
32
|
+
return Number(prop)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeArrayIndex(index: number, length: number): number {
|
|
39
|
+
if (index < 0) {
|
|
40
|
+
return Math.max(length + index, 0)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return Math.min(index, length)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveArrayAtIndex(index: number, length: number): number | undefined {
|
|
47
|
+
const resolvedIndex = index < 0 ? length + index : index
|
|
48
|
+
|
|
49
|
+
if (resolvedIndex < 0 || resolvedIndex >= length) {
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return resolvedIndex
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Common proxy traps shared between object and record runtime adapters.
|
|
58
|
+
* Only deleteProperty needs special handling for MobX observable objects.
|
|
59
|
+
*/
|
|
60
|
+
const objectRecordProxyTraps: Pick<ProxyHandler<Record<string, unknown>>, "deleteProperty"> = {
|
|
61
|
+
deleteProperty(target, prop) {
|
|
62
|
+
if (typeof prop === "string" && isObservableObject(target)) {
|
|
63
|
+
remove(target, prop)
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return Reflect.deleteProperty(target, prop)
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Converts a runtime object to its stored form using a cache.
|
|
73
|
+
* Shared by object and record adapters.
|
|
74
|
+
*/
|
|
75
|
+
function objectLikeToStored(
|
|
76
|
+
runtime: unknown,
|
|
77
|
+
storedByRuntime: WeakMap<object, Record<string, unknown>>,
|
|
78
|
+
convertValue: (key: string, value: unknown) => unknown
|
|
79
|
+
): Record<string, unknown> {
|
|
80
|
+
if (isObject(runtime)) {
|
|
81
|
+
const cachedStored = storedByRuntime.get(runtime)
|
|
82
|
+
if (cachedStored) {
|
|
83
|
+
return cachedStored
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isArray(runtime)) {
|
|
87
|
+
return runtime as never
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
return runtime as never
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const stored: Record<string, unknown> = {}
|
|
94
|
+
for (const key of Object.keys(runtime)) {
|
|
95
|
+
stored[key] = convertValue(key, runtime[key])
|
|
96
|
+
}
|
|
97
|
+
return stored
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Checks the stored cache for a runtime object.
|
|
102
|
+
* Shared by object and record adapters.
|
|
103
|
+
*/
|
|
104
|
+
function objectLikeToStoredIfCached(
|
|
105
|
+
runtime: unknown,
|
|
106
|
+
storedByRuntime: WeakMap<object, Record<string, unknown>>
|
|
107
|
+
): { found: true; value: Record<string, unknown> } | { found: false } {
|
|
108
|
+
if (isObject(runtime) && !isArray(runtime)) {
|
|
109
|
+
const cachedStored = storedByRuntime.get(runtime)
|
|
110
|
+
if (cachedStored) {
|
|
111
|
+
return { found: true, value: cachedStored }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { found: false }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createObjectRuntimeAdapter(
|
|
119
|
+
getChildSupports: () => Record<string, ResolvedCodecSupport>
|
|
120
|
+
): RuntimeAdapter<Record<string, unknown>, Record<string, unknown>> {
|
|
121
|
+
const runtimeByStored = new WeakMap<object, Record<string, unknown>>()
|
|
122
|
+
const storedByRuntime = new WeakMap<object, Record<string, unknown>>()
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
toRuntime(stored) {
|
|
126
|
+
if (!isObject(stored) || isArray(stored)) {
|
|
127
|
+
return stored as never
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const cachedRuntime = runtimeByStored.get(stored)
|
|
131
|
+
if (cachedRuntime) {
|
|
132
|
+
return cachedRuntime
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const proxy = new Proxy(stored, {
|
|
136
|
+
get(target, prop, receiver) {
|
|
137
|
+
if (typeof prop === "string") {
|
|
138
|
+
const childSupport = getChildSupports()[prop]
|
|
139
|
+
if (childSupport && Object.hasOwn(target, prop)) {
|
|
140
|
+
return childSupport.adapter.toRuntime(target[prop], (newStoredValue) => {
|
|
141
|
+
target[prop] = newStoredValue
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return Reflect.get(target, prop, receiver)
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
set(target, prop, value) {
|
|
150
|
+
if (typeof prop === "string") {
|
|
151
|
+
const childSupport = getChildSupports()[prop]
|
|
152
|
+
if (childSupport) {
|
|
153
|
+
setIfDifferent(target, prop, childSupport.adapter.toStored(value))
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Reflect.set(target, prop, value)
|
|
159
|
+
return true
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
...objectRecordProxyTraps,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
runtimeByStored.set(stored, proxy)
|
|
166
|
+
storedByRuntime.set(proxy, stored)
|
|
167
|
+
|
|
168
|
+
return proxy
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
toStored(runtime) {
|
|
172
|
+
const childSupports = getChildSupports()
|
|
173
|
+
return objectLikeToStored(runtime, storedByRuntime, (key, value) => {
|
|
174
|
+
const childSupport = childSupports[key]
|
|
175
|
+
return childSupport ? childSupport.adapter.toStored(value) : value
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
toStoredIfCached(runtime) {
|
|
180
|
+
return objectLikeToStoredIfCached(runtime, storedByRuntime)
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function createRecordRuntimeAdapter(
|
|
186
|
+
getValueSupport: () => ResolvedCodecSupport
|
|
187
|
+
): RuntimeAdapter<Record<string, unknown>, Record<string, unknown>> {
|
|
188
|
+
const runtimeByStored = new WeakMap<object, Record<string, unknown>>()
|
|
189
|
+
const storedByRuntime = new WeakMap<object, Record<string, unknown>>()
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
toRuntime(stored) {
|
|
193
|
+
if (!isObject(stored) || isArray(stored)) {
|
|
194
|
+
return stored as never
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const cachedRuntime = runtimeByStored.get(stored)
|
|
198
|
+
if (cachedRuntime) {
|
|
199
|
+
return cachedRuntime
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const proxy = new Proxy(stored, {
|
|
203
|
+
get(target, prop, receiver) {
|
|
204
|
+
if (typeof prop === "string" && Object.hasOwn(target, prop)) {
|
|
205
|
+
const valueSupport = getValueSupport()
|
|
206
|
+
return valueSupport.adapter.toRuntime(target[prop], (newStoredValue) => {
|
|
207
|
+
target[prop] = newStoredValue
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return Reflect.get(target, prop, receiver)
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
set(target, prop, value) {
|
|
215
|
+
if (typeof prop === "string") {
|
|
216
|
+
setIfDifferent(target, prop, getValueSupport().adapter.toStored(value))
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Reflect.set(target, prop, value)
|
|
221
|
+
return true
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
...objectRecordProxyTraps,
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
runtimeByStored.set(stored, proxy)
|
|
228
|
+
storedByRuntime.set(proxy, stored)
|
|
229
|
+
|
|
230
|
+
return proxy
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
toStored(runtime) {
|
|
234
|
+
const valueSupport = getValueSupport()
|
|
235
|
+
return objectLikeToStored(runtime, storedByRuntime, (_key, value) =>
|
|
236
|
+
valueSupport.adapter.toStored(value)
|
|
237
|
+
)
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
toStoredIfCached(runtime) {
|
|
241
|
+
return objectLikeToStoredIfCached(runtime, storedByRuntime)
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function createArrayLikeRuntimeAdapter(
|
|
247
|
+
getItemAdapter: (index: number) => RuntimeAdapter<unknown, unknown>
|
|
248
|
+
): RuntimeAdapter<unknown[], unknown[]> {
|
|
249
|
+
const runtimeByStored = new WeakMap<unknown[], unknown[]>()
|
|
250
|
+
const storedByRuntime = new WeakMap<unknown[], unknown[]>()
|
|
251
|
+
|
|
252
|
+
const decodeIndex = (target: unknown[], index: number) =>
|
|
253
|
+
index < 0 || index >= target.length || !Reflect.has(target, index)
|
|
254
|
+
? undefined
|
|
255
|
+
: getItemAdapter(index).toRuntime(target[index], (newStoredValue) => {
|
|
256
|
+
target[index] = newStoredValue
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const callReadOnlyRuntimeMethod = (
|
|
260
|
+
receiver: unknown[],
|
|
261
|
+
methodName: PropertyKey,
|
|
262
|
+
args: unknown[]
|
|
263
|
+
) => {
|
|
264
|
+
const method = Reflect.get(Array.prototype, methodName, receiver)
|
|
265
|
+
return Reflect.apply(method, receiver, args)
|
|
266
|
+
}
|
|
267
|
+
const materializeRuntimeValues = (target: unknown[]) =>
|
|
268
|
+
Array.from({ length: target.length }, (_, index) => decodeIndex(target, index))
|
|
269
|
+
const replaceStoredValues = (target: unknown[], runtimeValues: unknown[]) => {
|
|
270
|
+
target.splice(
|
|
271
|
+
0,
|
|
272
|
+
target.length,
|
|
273
|
+
...runtimeValues.map((value, index) => getItemAdapter(index).toStored(value))
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
const mutateViaRuntime = (
|
|
277
|
+
target: unknown[],
|
|
278
|
+
receiver: unknown[],
|
|
279
|
+
fn: (values: unknown[]) => void
|
|
280
|
+
) => {
|
|
281
|
+
const runtimeValues = materializeRuntimeValues(target)
|
|
282
|
+
fn(runtimeValues)
|
|
283
|
+
replaceStoredValues(target, runtimeValues)
|
|
284
|
+
return receiver
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
toRuntime(stored) {
|
|
289
|
+
if (!isArray(stored)) {
|
|
290
|
+
return stored as never
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const cachedRuntime = runtimeByStored.get(stored)
|
|
294
|
+
if (cachedRuntime) {
|
|
295
|
+
return cachedRuntime
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const proxy: unknown[] = new Proxy(stored, {
|
|
299
|
+
get(target, prop, receiver) {
|
|
300
|
+
const index = getArrayIndex(prop)
|
|
301
|
+
if (index !== undefined) {
|
|
302
|
+
return decodeIndex(target, index)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// MobX 4 observable arrays are array-like but fail `Array.isArray`, so native
|
|
306
|
+
// `Array.prototype.concat` won't spread this proxy unless we opt in explicitly.
|
|
307
|
+
if (
|
|
308
|
+
prop === Symbol.isConcatSpreadable &&
|
|
309
|
+
isObservableArray(target) &&
|
|
310
|
+
!Array.isArray(target)
|
|
311
|
+
) {
|
|
312
|
+
return true
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
switch (prop) {
|
|
316
|
+
case "at":
|
|
317
|
+
return (indexValue: number) => {
|
|
318
|
+
const resolvedIndex = resolveArrayAtIndex(indexValue, target.length)
|
|
319
|
+
return resolvedIndex === undefined ? undefined : decodeIndex(target, resolvedIndex)
|
|
320
|
+
}
|
|
321
|
+
case "push":
|
|
322
|
+
return (...items: unknown[]) =>
|
|
323
|
+
target.push(
|
|
324
|
+
...items.map((item, offset) =>
|
|
325
|
+
getItemAdapter(target.length + offset).toStored(item)
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
case "pop":
|
|
329
|
+
return () => {
|
|
330
|
+
if (target.length <= 0) {
|
|
331
|
+
return undefined
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const indexValue = target.length - 1
|
|
335
|
+
const value = target.pop()
|
|
336
|
+
return getItemAdapter(indexValue).toRuntime(value)
|
|
337
|
+
}
|
|
338
|
+
case "shift":
|
|
339
|
+
return () => {
|
|
340
|
+
if (target.length <= 0) {
|
|
341
|
+
return undefined
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const value = target.shift()
|
|
345
|
+
return getItemAdapter(0).toRuntime(value)
|
|
346
|
+
}
|
|
347
|
+
case "unshift":
|
|
348
|
+
return (...items: unknown[]) =>
|
|
349
|
+
target.unshift(
|
|
350
|
+
...items.map((item, indexValue) => getItemAdapter(indexValue).toStored(item))
|
|
351
|
+
)
|
|
352
|
+
case "splice":
|
|
353
|
+
return (start: number, deleteCount?: number, ...items: unknown[]) => {
|
|
354
|
+
const normalizedStart = normalizeArrayIndex(start, target.length)
|
|
355
|
+
const actualDeleteCount =
|
|
356
|
+
deleteCount === undefined
|
|
357
|
+
? target.length - normalizedStart
|
|
358
|
+
: Math.max(0, Math.min(deleteCount, target.length - normalizedStart))
|
|
359
|
+
|
|
360
|
+
const removedValues = target
|
|
361
|
+
.slice(normalizedStart, normalizedStart + actualDeleteCount)
|
|
362
|
+
.map((value, indexValue) =>
|
|
363
|
+
getItemAdapter(normalizedStart + indexValue).toRuntime(value)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
target.splice(
|
|
367
|
+
normalizedStart,
|
|
368
|
+
actualDeleteCount,
|
|
369
|
+
...items.map((item, indexValue) =>
|
|
370
|
+
getItemAdapter(normalizedStart + indexValue).toStored(item)
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return removedValues
|
|
375
|
+
}
|
|
376
|
+
case "reverse":
|
|
377
|
+
return () => mutateViaRuntime(target, receiver, (v) => v.reverse())
|
|
378
|
+
case "sort":
|
|
379
|
+
return (compareFn?: (a: unknown, b: unknown) => number) =>
|
|
380
|
+
mutateViaRuntime(target, receiver, (v) => v.sort(compareFn))
|
|
381
|
+
case "fill":
|
|
382
|
+
return (value: unknown, start?: number, end?: number) =>
|
|
383
|
+
mutateViaRuntime(target, receiver, (v) => v.fill(value, start, end))
|
|
384
|
+
case "copyWithin":
|
|
385
|
+
return (targetIndex: number, start?: number, end?: number) =>
|
|
386
|
+
mutateViaRuntime(target, receiver, (v) =>
|
|
387
|
+
v.copyWithin(targetIndex, start ?? 0, end)
|
|
388
|
+
)
|
|
389
|
+
case "concat":
|
|
390
|
+
case "flat":
|
|
391
|
+
case "flatMap":
|
|
392
|
+
case "filter":
|
|
393
|
+
case "find":
|
|
394
|
+
case "findIndex":
|
|
395
|
+
case "forEach":
|
|
396
|
+
case "every":
|
|
397
|
+
case "includes":
|
|
398
|
+
case "indexOf":
|
|
399
|
+
case "join":
|
|
400
|
+
case "lastIndexOf":
|
|
401
|
+
case "map":
|
|
402
|
+
case "reduce":
|
|
403
|
+
case "reduceRight":
|
|
404
|
+
case "slice":
|
|
405
|
+
case "some":
|
|
406
|
+
case "findLast":
|
|
407
|
+
case "findLastIndex":
|
|
408
|
+
case "toReversed":
|
|
409
|
+
case "toSorted":
|
|
410
|
+
case "toSpliced":
|
|
411
|
+
case "toString":
|
|
412
|
+
case "toLocaleString":
|
|
413
|
+
case "with":
|
|
414
|
+
return (...args: unknown[]) => callReadOnlyRuntimeMethod(receiver, prop, args)
|
|
415
|
+
case "entries":
|
|
416
|
+
return function* () {
|
|
417
|
+
for (let i = 0; i < target.length; i++) {
|
|
418
|
+
yield [i, decodeIndex(target, i)] as const
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
case "keys":
|
|
422
|
+
return function* () {
|
|
423
|
+
for (let i = 0; i < target.length; i++) {
|
|
424
|
+
yield i
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
case "values":
|
|
428
|
+
case Symbol.iterator:
|
|
429
|
+
return function* () {
|
|
430
|
+
for (let i = 0; i < target.length; i++) {
|
|
431
|
+
yield decodeIndex(target, i)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
default:
|
|
435
|
+
return Reflect.get(target, prop, receiver)
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
set(target, prop, value) {
|
|
440
|
+
const index = getArrayIndex(prop)
|
|
441
|
+
if (index !== undefined) {
|
|
442
|
+
target[index] = getItemAdapter(index).toStored(value)
|
|
443
|
+
return true
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
Reflect.set(target, prop, value)
|
|
447
|
+
return true
|
|
448
|
+
},
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
runtimeByStored.set(stored, proxy)
|
|
452
|
+
storedByRuntime.set(proxy, stored)
|
|
453
|
+
|
|
454
|
+
return proxy
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
toStored(runtime) {
|
|
458
|
+
if (isArray(runtime)) {
|
|
459
|
+
const cachedStored = storedByRuntime.get(runtime)
|
|
460
|
+
if (cachedStored) {
|
|
461
|
+
return cachedStored
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return Array.from(runtime, (value, index) => getItemAdapter(index).toStored(value))
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return runtime as never
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
toStoredIfCached(runtime) {
|
|
471
|
+
if (isArray(runtime)) {
|
|
472
|
+
const cachedStored = storedByRuntime.get(runtime)
|
|
473
|
+
if (cachedStored) {
|
|
474
|
+
return { found: true, value: cachedStored }
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return { found: false }
|
|
479
|
+
},
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function createCodecLeafRuntimeAdapter(
|
|
484
|
+
transform: ModelPropTransform<unknown, unknown>
|
|
485
|
+
): RuntimeAdapter<unknown, unknown> {
|
|
486
|
+
const runtimeByStored = new WeakMap<object, unknown>()
|
|
487
|
+
const storedByRuntime = new WeakMap<object, unknown>()
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
toRuntime(stored, setStored) {
|
|
491
|
+
const cachedRuntime = isObject(stored) ? runtimeByStored.get(stored) : undefined
|
|
492
|
+
|
|
493
|
+
const runtime = transform.transform({
|
|
494
|
+
originalValue: stored,
|
|
495
|
+
cachedTransformedValue: cachedRuntime,
|
|
496
|
+
setOriginalValue(newStoredValue) {
|
|
497
|
+
if (setStored) {
|
|
498
|
+
setStored(newStoredValue)
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
if (isObject(runtime)) {
|
|
504
|
+
storedByRuntime.set(runtime, stored)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (isObject(stored)) {
|
|
508
|
+
runtimeByStored.set(stored, runtime)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return runtime
|
|
512
|
+
},
|
|
513
|
+
|
|
514
|
+
toStored(runtime) {
|
|
515
|
+
const cachedStored = isObject(runtime) ? storedByRuntime.get(runtime) : undefined
|
|
516
|
+
if (cachedStored !== undefined) {
|
|
517
|
+
return cachedStored
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const stored = transform.untransform({
|
|
521
|
+
transformedValue: runtime,
|
|
522
|
+
cacheTransformedValue: () => {},
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
if (isObject(runtime)) {
|
|
526
|
+
storedByRuntime.set(runtime, stored)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (isObject(stored)) {
|
|
530
|
+
runtimeByStored.set(stored, runtime)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return stored
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
toStoredIfCached(runtime) {
|
|
537
|
+
if (isObject(runtime)) {
|
|
538
|
+
const cachedStored = storedByRuntime.get(runtime)
|
|
539
|
+
if (cachedStored !== undefined) {
|
|
540
|
+
return { found: true, value: cachedStored }
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return { found: false }
|
|
545
|
+
},
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function createOrRuntimeAdapter(
|
|
550
|
+
childTypes: ReadonlyArray<AnyStandardType>,
|
|
551
|
+
getChildSupports: () => ReadonlyArray<ResolvedCodecSupport>,
|
|
552
|
+
dispatcher: OrTypeInfo["dispatcher"]
|
|
553
|
+
): RuntimeAdapter<unknown, unknown> {
|
|
554
|
+
const getRuntimeSupportForValue = (value: unknown) => {
|
|
555
|
+
const supports = getChildSupports()
|
|
556
|
+
for (let i = 0; i < supports.length; i++) {
|
|
557
|
+
const childType = childTypes[i]
|
|
558
|
+
const tc = resolveTypeChecker(childType)
|
|
559
|
+
if (tc.skipCheck) {
|
|
560
|
+
// For skipCheck branches, use snapshotType for structural matching
|
|
561
|
+
// instead of check (which always passes and would match any value)
|
|
562
|
+
if (tc.snapshotType(value)) {
|
|
563
|
+
return supports[i]
|
|
564
|
+
}
|
|
565
|
+
} else if (!tc.check(value, [], value)) {
|
|
566
|
+
return supports[i]
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return supports[0]
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const getStoredSupportForValue = (value: unknown) => {
|
|
574
|
+
if (dispatcher) {
|
|
575
|
+
return resolveCodecSupport(dispatcher(value))
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const supports = getChildSupports()
|
|
579
|
+
for (let i = 0; i < supports.length; i++) {
|
|
580
|
+
const storedTypeChecker = resolveTypeChecker(supports[i].storedType)
|
|
581
|
+
if (storedTypeChecker.snapshotType(value)) {
|
|
582
|
+
return supports[i]
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return supports[0]
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
toRuntime(stored, setStored) {
|
|
591
|
+
return getStoredSupportForValue(stored).adapter.toRuntime(stored, setStored)
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
toStored(runtime) {
|
|
595
|
+
return getRuntimeSupportForValue(runtime).adapter.toStored(runtime)
|
|
596
|
+
},
|
|
597
|
+
|
|
598
|
+
toStoredIfCached(runtime) {
|
|
599
|
+
return getRuntimeSupportForValue(runtime).adapter.toStoredIfCached(runtime)
|
|
600
|
+
},
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function noCodecSupport(type: AnyStandardType): ResolvedCodecSupport {
|
|
605
|
+
return { hasCodec: false, storedType: type, adapter: identityRuntimeAdapter }
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function resolveCodecSupportForStandardType(type: AnyStandardType): ResolvedCodecSupport {
|
|
609
|
+
const cachedSupport = codecSupportCache.get(type)
|
|
610
|
+
if (cachedSupport) {
|
|
611
|
+
return cachedSupport
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
let resolvedSupport: ResolvedCodecSupport | undefined
|
|
615
|
+
let resolvedStoredType: AnyStandardType | undefined
|
|
616
|
+
|
|
617
|
+
const placeholderSupport: ResolvedCodecSupport = {
|
|
618
|
+
hasCodec: false,
|
|
619
|
+
storedType: lateTypeChecker(
|
|
620
|
+
() => resolveTypeChecker(resolvedStoredType ?? type),
|
|
621
|
+
() => getTypeInfo(resolvedStoredType ?? type)
|
|
622
|
+
) as any,
|
|
623
|
+
adapter: {
|
|
624
|
+
toRuntime(stored, setStored) {
|
|
625
|
+
return (resolvedSupport?.adapter ?? identityRuntimeAdapter).toRuntime(stored, setStored)
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
toStored(runtime) {
|
|
629
|
+
return (resolvedSupport?.adapter ?? identityRuntimeAdapter).toStored(runtime)
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
toStoredIfCached(runtime) {
|
|
633
|
+
return (resolvedSupport?.adapter ?? identityRuntimeAdapter).toStoredIfCached(runtime)
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
codecSupportCache.set(type, placeholderSupport)
|
|
639
|
+
|
|
640
|
+
const codecMetadata = getCodecMetadata(type)
|
|
641
|
+
if (codecMetadata) {
|
|
642
|
+
resolvedSupport = {
|
|
643
|
+
hasCodec: true,
|
|
644
|
+
storedType: resolveStandardType(codecMetadata.encodedType),
|
|
645
|
+
adapter: createCodecLeafRuntimeAdapter(codecMetadata.transform),
|
|
646
|
+
}
|
|
647
|
+
} else if (isModelStandardType(type) || isDataModelDataStandardType(type)) {
|
|
648
|
+
resolvedSupport = noCodecSupport(type)
|
|
649
|
+
} else {
|
|
650
|
+
const typeInfo = getTypeInfo(type)
|
|
651
|
+
|
|
652
|
+
if (typeInfo instanceof ArrayTypeInfo) {
|
|
653
|
+
const itemSupport = lazy(() => resolveCodecSupportForStandardType(typeInfo.itemType))
|
|
654
|
+
|
|
655
|
+
resolvedSupport = !itemSupport().hasCodec
|
|
656
|
+
? noCodecSupport(type)
|
|
657
|
+
: {
|
|
658
|
+
hasCodec: true,
|
|
659
|
+
storedType: typesArray(itemSupport().storedType),
|
|
660
|
+
adapter: createArrayLikeRuntimeAdapter(() => itemSupport().adapter),
|
|
661
|
+
}
|
|
662
|
+
} else if (typeInfo instanceof TupleTypeInfo) {
|
|
663
|
+
const itemSupports = lazy(() =>
|
|
664
|
+
typeInfo.itemTypes.map((itemType) => resolveCodecSupportForStandardType(itemType))
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
resolvedSupport = !itemSupports().some((itemSupport) => itemSupport.hasCodec)
|
|
668
|
+
? noCodecSupport(type)
|
|
669
|
+
: {
|
|
670
|
+
hasCodec: true,
|
|
671
|
+
storedType: typesTuple(...itemSupports().map((itemSupport) => itemSupport.storedType)),
|
|
672
|
+
adapter: createArrayLikeRuntimeAdapter(
|
|
673
|
+
(index) => itemSupports()[index]?.adapter ?? identityRuntimeAdapter
|
|
674
|
+
),
|
|
675
|
+
}
|
|
676
|
+
} else if (typeInfo instanceof ObjectTypeInfo) {
|
|
677
|
+
const childSupports = lazy(() => {
|
|
678
|
+
const supports: Record<string, ResolvedCodecSupport> = {}
|
|
679
|
+
const props = typeInfo.props
|
|
680
|
+
for (const propName of Object.keys(props)) {
|
|
681
|
+
supports[propName] = resolveCodecSupportForStandardType(props[propName].type)
|
|
682
|
+
}
|
|
683
|
+
return supports
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
resolvedSupport = !Object.values(childSupports()).some(
|
|
687
|
+
(childSupport) => childSupport.hasCodec
|
|
688
|
+
)
|
|
689
|
+
? noCodecSupport(type)
|
|
690
|
+
: {
|
|
691
|
+
hasCodec: true,
|
|
692
|
+
storedType: typesObject(() => {
|
|
693
|
+
const storedProps: Record<string, AnyStandardType> = {}
|
|
694
|
+
const props = typeInfo.props
|
|
695
|
+
for (const propName of Object.keys(props)) {
|
|
696
|
+
storedProps[propName] = childSupports()[propName].storedType
|
|
697
|
+
}
|
|
698
|
+
return storedProps
|
|
699
|
+
}),
|
|
700
|
+
adapter: createObjectRuntimeAdapter(childSupports),
|
|
701
|
+
}
|
|
702
|
+
} else if (typeInfo instanceof RecordTypeInfo) {
|
|
703
|
+
const valueSupport = lazy(() => resolveCodecSupportForStandardType(typeInfo.valueType))
|
|
704
|
+
|
|
705
|
+
resolvedSupport = !valueSupport().hasCodec
|
|
706
|
+
? noCodecSupport(type)
|
|
707
|
+
: {
|
|
708
|
+
hasCodec: true,
|
|
709
|
+
storedType: typesRecord(valueSupport().storedType),
|
|
710
|
+
adapter: createRecordRuntimeAdapter(valueSupport),
|
|
711
|
+
}
|
|
712
|
+
} else if (typeInfo instanceof TagTypeInfo) {
|
|
713
|
+
const baseSupport = resolveCodecSupportForStandardType(typeInfo.baseType)
|
|
714
|
+
|
|
715
|
+
resolvedSupport = !baseSupport.hasCodec
|
|
716
|
+
? noCodecSupport(type)
|
|
717
|
+
: {
|
|
718
|
+
hasCodec: true,
|
|
719
|
+
storedType: typesTag(baseSupport.storedType, typeInfo.tag, typeInfo.typeName),
|
|
720
|
+
adapter: baseSupport.adapter,
|
|
721
|
+
}
|
|
722
|
+
} else if (typeInfo instanceof RefinementTypeInfo) {
|
|
723
|
+
const baseSupport = resolveCodecSupportForStandardType(typeInfo.baseType)
|
|
724
|
+
|
|
725
|
+
resolvedSupport = baseSupport.hasCodec ? baseSupport : noCodecSupport(type)
|
|
726
|
+
} else if (typeInfo instanceof SkipCheckTypeInfo) {
|
|
727
|
+
const baseSupport = resolveCodecSupportForStandardType(typeInfo.baseType)
|
|
728
|
+
|
|
729
|
+
// Always wrap storedType with skipCheck, even when hasCodec is false.
|
|
730
|
+
// resolveStoredType() feeds model-internal automatic validation in sharedInternalModel.ts,
|
|
731
|
+
// so the skipCheck wrapper must propagate to prevent the stored type from being validated.
|
|
732
|
+
resolvedSupport = {
|
|
733
|
+
hasCodec: baseSupport.hasCodec,
|
|
734
|
+
storedType: typesSkipCheck(baseSupport.storedType),
|
|
735
|
+
adapter: baseSupport.adapter,
|
|
736
|
+
}
|
|
737
|
+
} else if (typeInfo instanceof OrTypeInfo) {
|
|
738
|
+
const childSupports = lazy(() =>
|
|
739
|
+
typeInfo.orTypes.map((orType) => resolveCodecSupportForStandardType(orType))
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
resolvedSupport = !childSupports().some((childSupport) => childSupport.hasCodec)
|
|
743
|
+
? noCodecSupport(type)
|
|
744
|
+
: {
|
|
745
|
+
hasCodec: true,
|
|
746
|
+
storedType: typeInfo.dispatcher
|
|
747
|
+
? typesOr(
|
|
748
|
+
(snapshot) => resolveStoredType(typeInfo.dispatcher!(snapshot)),
|
|
749
|
+
...childSupports().map((childSupport) => childSupport.storedType)
|
|
750
|
+
)
|
|
751
|
+
: typesOr(...childSupports().map((childSupport) => childSupport.storedType)),
|
|
752
|
+
adapter: createOrRuntimeAdapter(typeInfo.orTypes, childSupports, typeInfo.dispatcher),
|
|
753
|
+
}
|
|
754
|
+
} else {
|
|
755
|
+
resolvedSupport = noCodecSupport(type)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
resolvedStoredType = resolvedSupport.storedType
|
|
760
|
+
placeholderSupport.hasCodec = resolvedSupport.hasCodec
|
|
761
|
+
placeholderSupport.storedType = resolvedSupport.storedType
|
|
762
|
+
placeholderSupport.adapter = resolvedSupport.adapter
|
|
763
|
+
|
|
764
|
+
return placeholderSupport
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export function resolveCodecSupport(type: AnyType) {
|
|
768
|
+
return resolveCodecSupportForStandardType(resolveStandardType(type))
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export function resolveStoredType(type: AnyType) {
|
|
772
|
+
return resolveCodecSupport(type).storedType
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
export function resolveStoredTypeChecker(type: AnyType) {
|
|
776
|
+
return resolveTypeChecker(resolveCodecSupport(type).storedType)
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function createCodecPropTransform(type: AnyType): ModelPropTransform<any, unknown> {
|
|
780
|
+
const adapter = resolveCodecSupport(type).adapter
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
transform({ originalValue, cachedTransformedValue, setOriginalValue }) {
|
|
784
|
+
// For leaf codecs backed by primitive stored data (e.g. timestamps, ISO strings),
|
|
785
|
+
// the adapter's WeakMap cache can't work. Thread the model's cachedTransformedValue
|
|
786
|
+
// to avoid creating new runtime objects when the stored value hasn't changed.
|
|
787
|
+
if (cachedTransformedValue !== undefined) {
|
|
788
|
+
const cachedStored = adapter.toStoredIfCached(cachedTransformedValue)
|
|
789
|
+
if (cachedStored.found && cachedStored.value === originalValue) {
|
|
790
|
+
return cachedTransformedValue
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return adapter.toRuntime(originalValue, setOriginalValue)
|
|
794
|
+
},
|
|
795
|
+
|
|
796
|
+
untransform({ transformedValue, cacheTransformedValue }) {
|
|
797
|
+
const cachedStored = adapter.toStoredIfCached(transformedValue)
|
|
798
|
+
if (cachedStored.found) {
|
|
799
|
+
cacheTransformedValue()
|
|
800
|
+
return cachedStored.value
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return adapter.toStored(transformedValue)
|
|
804
|
+
},
|
|
805
|
+
}
|
|
806
|
+
}
|