import { modelTypeKey } from "../../model/metadata" import { modelInfoByClass } from "../../modelShared/modelInfo" import { isObject } from "../../utils" import { withErrorPathSegment } from "../../utils/errorDiagnostics" import { ObjectMap } from "../../wrappers/ObjectMap" import { getTypeInfo } from "../getTypeInfo" import { resolveStandardType, resolveTypeChecker } from "../resolveTypeChecker" import type { AnyStandardType, AnyType, ModelType, TypeToData } from "../schemas" import { TypeCheckError } from "../TypeCheckError" import { lateTypeChecker, TypeChecker, TypeCheckerBaseType, TypeInfo, type TypeInfoGen, } from "../TypeChecker" import { resolveCodecSupport } from "../utility/typesCodec" import { typesObject } from "./typesObject" import { typesRecord } from "./typesRecord" /** * A type that represents an object-like map ObjectMap. * * Example: * ```ts * const numberMapType = types.objectMap(types.number) * ``` * * @template T Value type. * @param valueType Value type. * @returns */ export function typesObjectMap( valueType: T ): ModelType>> { const typeInfoGen: TypeInfoGen = (t) => new ObjectMapTypeInfo(t, resolveStandardType(valueType)) return lateTypeChecker(() => { const modelInfo = modelInfoByClass.get(ObjectMap)! const valueChecker = resolveTypeChecker(valueType) const valueSupport = resolveCodecSupport(valueType) const storedValueChecker = resolveTypeChecker(valueSupport.storedType) const getTypeName = (...recursiveTypeCheckers: TypeChecker[]) => `ObjectMap<${valueChecker.getTypeName(...recursiveTypeCheckers, valueChecker)}>` const dataTypeChecker = typesObject(() => ({ items: typesRecord(valueSupport.storedType as any), })) const resolvedDataTypeChecker = resolveTypeChecker(dataTypeChecker) const thisTc: TypeChecker = new TypeChecker( TypeCheckerBaseType.Object, (obj, path, typeCheckedValue) => { if (!(obj instanceof ObjectMap)) { return new TypeCheckError({ path, expectedTypeName: getTypeName(thisTc), actualValue: obj, typeCheckedValue, }) } return resolvedDataTypeChecker.check(obj.$, path, typeCheckedValue) }, getTypeName, typeInfoGen, (obj) => { if (!isObject(obj)) { return null } if (obj[modelTypeKey] !== undefined) { // fast check return obj[modelTypeKey] === modelInfo.name ? thisTc : null } return resolvedDataTypeChecker.snapshotType(obj) ? thisTc : null }, (sn: { items: Record }) => { const newItems: (typeof sn)["items"] = {} withErrorPathSegment("items", () => { for (const k of Object.keys(sn.items)) { newItems[k] = withErrorPathSegment(k, () => storedValueChecker.fromSnapshotProcessor(sn.items[k]) ) } }) return { ...sn, [modelTypeKey]: modelInfo.name, items: newItems, } }, (sn: { items: Record; [modelTypeKey]?: string }) => { const newItems: (typeof sn)["items"] = {} withErrorPathSegment("items", () => { for (const k of Object.keys(sn.items)) { newItems[k] = withErrorPathSegment(k, () => storedValueChecker.toSnapshotProcessor(sn.items[k]) ) } }) const snCopy = { ...sn, items: newItems, } return snCopy } ) return thisTc }, typeInfoGen) as any } /** * `types.objectMap` type info. */ export class ObjectMapTypeInfo extends TypeInfo { readonly kind = "objectMap" readonly valueType: AnyStandardType get valueTypeInfo(): TypeInfo { return getTypeInfo(this.valueType) } constructor(thisType: AnyStandardType, valueType: AnyStandardType) { super(thisType) this.valueType = valueType } }