UNPKG

@moostjs/zod

Version:
441 lines (435 loc) 18.4 kB
import { z } from "zod"; import { TPipePriority, definePipeFn, getMoostMate, useEventLogger } from "moost"; //#region packages/zod/src/primitives.ts const primitivesConstructorMap = new WeakMap(); primitivesConstructorMap.set(String, (opts) => z.string(opts)); primitivesConstructorMap.set(Number, (opts) => z.number(opts)); primitivesConstructorMap.set(Boolean, (opts) => z.boolean(opts)); primitivesConstructorMap.set(Array, (opts) => z.array(z.unknown(), opts)); primitivesConstructorMap.set(Date, (opts) => z.array(z.date(), opts)); primitivesConstructorMap.set(Object, (opts) => z.any(opts)); primitivesConstructorMap.set(Set, (opts) => z.any(opts)); primitivesConstructorMap.set(Map, (opts) => z.any(opts)); primitivesConstructorMap.set(Promise, (opts) => z.any(opts)); primitivesConstructorMap.set(Function, (opts) => z.any(opts)); const primitivesMap = new Map(); primitivesMap.set("undefined", (opts) => z.undefined(opts)); primitivesMap.set("boolean", (opts) => z.boolean(opts)); primitivesMap.set("number", (opts) => z.number(opts)); primitivesMap.set("bigint", (opts) => z.bigint(opts)); primitivesMap.set("string", (opts) => z.string(opts)); primitivesMap.set("symbol", (opts) => z.symbol(opts)); function resolveZodPrimitive(from, opts) { const zodFactory = primitivesMap.get(from); if (zodFactory) return zodFactory(opts); throw new Error(`Could not retrieve ZodType for type "${from}".`); } function resolveZodPrimitiveByConstructor(type, opts) { const factory = primitivesConstructorMap.get(type); if (factory) return factory(opts); } //#endregion //#region packages/zod/src/zod.mate.ts function getZodMate() { return getMoostMate(); } //#endregion //#region packages/zod/src/validate.ts const mate$1 = getZodMate(); const cachedTypes = new WeakMap(); const cachedPropsTypes = new WeakMap(); async function validate(data, dto, opts, safe, logger) { if (dto instanceof z.ZodType) return safe === true ? await dto.safeParseAsync(data) : await dto.parseAsync(data); const zodType = getZodType({ type: dto }, opts, logger); if (zodType) return await validate(data, zodType, opts, safe); return safe === true ? { data, success: true } : data; } function getZodTypeForProp(origin, target, opts, logger) { let store = cachedPropsTypes.get(origin.type); if (!store) { store = { props: new Map(), params: new Map() }; cachedPropsTypes.set(origin.type, store); } if (typeof origin.index === "number") { const cached = store.params.get(origin.key); if (cached) { if (!cached[origin.index]) cached[origin.index] = getZodType(target, opts, logger); return cached[origin.index]; } else { const zodType = getZodType(target, opts, logger); const paramsArray = []; paramsArray[origin.index] = zodType; store.params.set(origin.key, paramsArray); return zodType; } } else { const cached = store.props.get(origin.key); if (cached) return cached; else { const zodType = getZodType(target, opts, logger); store.props.set(origin.key, zodType); return zodType; } } } function getZodType(target, opts, logger) { const { type, value, additionalMeta } = target; const typeOfValue = typeof value; const ownMeta = type && mate$1.read(type) || undefined; let needToCache = false; let toWrapIntoArray = false; const overrideZodType = additionalMeta?.zodType; if (!overrideZodType) { const cached = lookupInCache(); if (cached) return processResult(cached); } const zodInitialType = resolveInitialType(); return processResult(ownMeta ? applyZodFunctions(zodInitialType, { description: ownMeta.description, zodDefault: ownMeta.zodDefault, zodPreprocess: ownMeta.zodPreprocess, zodFn: ownMeta.zodFn, zodClassName: ownMeta.zodClassName, zodPropName: ownMeta.zodPropName, zodParamIndex: ownMeta.zodParamIndex }, logger) : zodInitialType); function lookupInCache() { if (type) return cachedTypes.get(type); } function resolveInitialType() { if (overrideZodType) { const overridenType = typeof overrideZodType === "function" ? overrideZodType(opts) : overrideZodType; if (type === Array && !(overridenType instanceof z.ZodTuple)) toWrapIntoArray = true; return overridenType; } if (type) { if ((type === Object || type === Array) && typeOfValue !== "undefined") { const { zt, isArray } = resolveZodTypeByValue(value, opts); if (isArray) toWrapIntoArray = true; return zt; } const zodType = resolveZodPrimitiveByConstructor(type, opts); if (zodType) return zodType; needToCache = true; if (ownMeta?.zodType) return typeof ownMeta.zodType === "function" ? ownMeta.zodType(opts) : ownMeta.zodType; const fakeInstance = new type(); const keys = [...Object.keys(fakeInstance), ...ownMeta?.properties || []]; const shape = {}; for (const key of keys) { const propMeta = mate$1.read(type, key); try { shape[key] = getZodType({ type: propMeta?.type, value: fakeInstance[key], additionalMeta: propMeta }, { ...opts, coerce: propMeta?.zodCoerce || undefined }, logger); } catch (error) { throw new Error(`Could not create ZodType for class "${type.name}". Type of property "${key}" (${propMeta?.type?.name || ""}) is unknown:\n${error.message}`); } } const obj = z.object(shape); if (ownMeta?.zodObj === "passthrough") return obj.passthrough(); if (ownMeta?.zodObj === "strip") return obj.strip(); return obj.strict(); } else { const { zt, isArray } = resolveZodTypeByValue(value, opts); if (isArray) toWrapIntoArray = true; return zt; } } function processResult(zt) { const toReturn = applyZodFunctions(zt, { optional: additionalMeta?.optional, description: additionalMeta?.description, zodDefault: additionalMeta?.zodDefault, zodPreprocess: additionalMeta?.zodPreprocess, zodFn: additionalMeta?.zodFn, wrapIntoArray: additionalMeta?.zodMarkedAsArray ? false : toWrapIntoArray, zodClassName: additionalMeta?.zodClassName, zodPropName: additionalMeta?.zodPropName, zodParamIndex: additionalMeta?.zodParamIndex }, logger); if (needToCache && type && !overrideZodType) { Object.assign(zt, { __type_ref: type }); Object.assign(toReturn, { __type_ref: type }); cachedTypes.set(type, zt); } return toReturn; } } function resolveZodTypeByValue(value, opts, logger) { const type = typeof value; if (type === "object") { if (Array.isArray(value)) { if (value.length > 0) { const subType = resolveZodTypeByValue(value[0], opts, logger); return { isArray: true, zt: subType.isArray ? subType.zt.array() : subType.zt }; } throw new Error("Could not resolve array item type."); } else if (value === null) return { zt: z.null(opts) }; else if (value instanceof Date) return { zt: z.date(opts) }; } return { zt: resolveZodPrimitive(typeof value, opts) }; } function applyZodFunctions(type, toApply, logger) { if (toApply) { let newType = type; if (toApply.zodFn?.length) Array.from(toApply.zodFn).reverse().forEach((zodFn) => { try { newType = zodFn.fn(newType); } catch (error) { const cl = toApply.zodClassName; const prop = toApply.zodPropName; const i = toApply.zodParamIndex; const arg = typeof i === "number"; const message = `${"\x1B[4m"}@${zodFn.decorator}${"\x1B[24m"} skipped for ${arg ? `${"\x1B[36m"}argument[${i}]${"\x1B[33m"} at ` : ""}${"\x1B[36m"}${cl}${prop ? `.${prop}` : ""}${"\x1B[33m"}, ` + `${"\x1B[4m"}${newType._def.typeName}${"\x1B[24m"} does not support such function.`; (logger || console).warn(`${"\x1B[33m"}${message} ${"\x1B[37m\x1B[2m"}Runtime error: ${error.message}${"\x1B[0m"}`); } }); if (toApply.zodPreprocess) { let prev = newType; for (let i = toApply.zodPreprocess.length - 1; i >= 0; i--) { const fn = toApply.zodPreprocess[i]; newType = z.preprocess(fn, prev); prev = newType; } } if (toApply.wrapIntoArray) newType = newType.array(); if (toApply.optional && !(newType instanceof z.ZodOptional) && !newType.isOptional()) newType = newType.optional(); if (toApply.description) newType = newType.describe(toApply.description); if (toApply.zodDefault !== undefined) newType = newType.default(toApply.zodDefault); return newType; } return type; } //#endregion //#region packages/zod/src/zod.decorators.ts const mate = getZodMate(); function ZodFn(decorator, fn) { return (target, propertyKey, descriptor) => { mate.decorate((meta, level, key, index) => { if (!meta.zodFn) meta.zodFn = []; if (!meta.zodPropName && ["PARAM", "PROP"].includes(level)) { meta.zodPropName = key; if (level === "PARAM") meta.zodParamIndex = index; } meta.zodFn.push({ decorator, fn }); if (!meta.zodClassName) meta.zodClassName = target.name || target.constructor.name; return meta; })(target, propertyKey, descriptor); }; } const Validatable = () => mate.decorate("zodValidate", true); const Strict = () => mate.decorate("zodObj", "strict"); const Passthrough = () => mate.decorate("zodObj", "passthrough"); const Strip = () => mate.decorate("zodObj", "strip"); const Zod = (type) => { const decorator = mate.decorate("zodType", type); const getZt = (opts) => typeof type === "function" ? type(opts) : type; decorator.optional = () => mate.decorate("zodType", (opts) => getZt(opts).optional()); decorator.nullable = () => mate.decorate("zodType", (opts) => getZt(opts).nullable()); decorator.nullish = () => mate.decorate("zodType", (opts) => getZt(opts).nullish()); return decorator; }; const ZodSkip = () => mate.decorate("zodSkip", true); const LazyType = (getter, opts) => Zod((_opts) => z.lazy(() => toZodType(getter()), { ...opts, ..._opts })); const Coerce = () => mate.decorate("zodCoerce", true); const Default = (value) => mate.decorate("zodDefault", value); const IsArray = (types, opts) => { const decorators = [ZodFn("IsArray", (t) => t.array()), mate.decorate("zodMarkedAsArray", true)]; if (types) if (Array.isArray(types)) { if (types.length > 1) { const zodType = z.union(types.map((t) => toZodType(t, opts))); decorators.push(Zod(zodType)); } else if (types.length === 1) decorators.push(Zod(toZodType(types[0], opts))); } else decorators.push(Zod(toZodType(types, opts))); const decorator = mate.apply(...decorators); decorator.optional = () => { decorators[0] = ZodFn("IsArray", (t) => t.array().optional()); return mate.apply(...decorators); }; decorator.nullable = () => { decorators[0] = ZodFn("IsArray", (t) => t.array().nullable()); return mate.apply(...decorators); }; decorator.nullish = () => { decorators[0] = ZodFn("IsArray", (t) => t.array().nullish()); return mate.apply(...decorators); }; return decorator; }; const Refine = (...args) => ZodFn("Refine", (t) => t.refine(...args)); const SuperRefine = (...args) => ZodFn("SuperRefine", (t) => t.superRefine(...args)); const Trim = () => ZodFn("Trim", (t) => t.trim()); const Transform = (...args) => ZodFn("Transform", (t) => t.transform(...args)); const OnCatch = (def) => ZodFn("OnCatch", (t) => t.catch(...[def])); const HasLength = (...args) => ZodFn("HasLength", (t) => t.length(...args)); const Min = (...args) => ZodFn("Min", (t) => t.min(...args)); const Max = (...args) => ZodFn("Max", (t) => t.max(...args)); const DateFrom = (...args) => ZodFn("DateFrom", (t) => t.min(...args)); const DateTo = (...args) => ZodFn("DateTo", (t) => t.max(...args)); const IsNullable = () => ZodFn("IsNullable", (t) => t.nullable()); const IsNullish = () => ZodFn("IsNullish", (t) => t.nullish()); const IsEmail = (...args) => ZodFn("IsEmail", (t) => t.email(...args)); const IsUrl = (...args) => ZodFn("IsUrl", (t) => t.url(...args)); const IsEmoji = (...args) => ZodFn("IsEmoji", (t) => t.emoji(...args)); const IsUuid = (...args) => ZodFn("IsUuid", (t) => t.uuid(...args)); const IsCuid = (...args) => ZodFn("IsCuid", (t) => t.cuid(...args)); const IsCuid2 = (...args) => ZodFn("IsCuid2", (t) => t.cuid2(...args)); const IsUlid = (...args) => ZodFn("IsUlid", (t) => t.ulid(...args)); const IsDatetime = (...args) => ZodFn("IsDatetime", (t) => t.datetime(...args)); const IsIp = (...args) => ZodFn("IsIp", (t) => t.ip(...args)); const MatchesRegex = (...args) => ZodFn("MatchesRegex", (t) => t.regex(...args)); const StartsWith = (...args) => ZodFn("StartsWith", (t) => t.startsWith(...args)); const EndsWith = (...args) => ZodFn("EndsWith", (t) => t.endsWith(...args)); const IsGt = (...args) => ZodFn("IsGt", (t) => t.gt(...args)); const IsGte = (...args) => ZodFn("IsGte", (t) => t.gte(...args)); const IsLt = (...args) => ZodFn("IsLt", (t) => t.lt(...args)); const IsLte = (...args) => ZodFn("IsLte", (t) => t.lte(...args)); const IsInt = (...args) => ZodFn("IsInt", (t) => t.int(...args)); const IsPositive = (...args) => ZodFn("IsPositive", (t) => t.positive(...args)); const IsNonnegative = (...args) => ZodFn("IsNonnegative", (t) => t.nonnegative(...args)); const IsNegative = (...args) => ZodFn("IsNegative", (t) => t.negative(...args)); const IsNonpositive = (...args) => ZodFn("IsNonpositive", (t) => t.nonpositive(...args)); const IsMultipleOf = (...args) => ZodFn("IsMultipleOf", (t) => t.multipleOf(...args)); const IsFinite = (...args) => ZodFn("IsFinite", (t) => t.finite(...args)); const IsSafeNumber = (...args) => ZodFn("IsSafeNumber", (t) => t.safe(...args)); const Includes = (...args) => ZodFn("Includes", (t) => t.includes(...args)); const IsString = (...args) => Zod((opts) => z.string(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsNumber = (...args) => Zod((opts) => z.number(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsBigint = (...args) => Zod((opts) => z.bigint(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsBoolean = (...args) => Zod((opts) => z.boolean(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsDate = (...args) => Zod((opts) => z.date(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsSymbol = (...args) => Zod((opts) => z.symbol(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsUndefined = (...args) => Zod((opts) => z.undefined(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsNull = (...args) => Zod((opts) => z.null(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsVoid = (...args) => Zod((opts) => z.void(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsAny = (...args) => Zod((opts) => z.any(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsUnknown = (...args) => Zod((opts) => z.unknown(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsNever = (...args) => Zod((opts) => z.never(...opts ? [{ ...args[0], ...opts }, ...args.slice(1)] : args)); const IsTuple = (schemas) => Zod(z.tuple(schemas.map((s) => toZodType(s)))); const IsEnum = (...args) => Zod(z.enum(...args)); const IsNativeEnum = (...args) => Zod(z.nativeEnum(...args)); const IsSet = (type) => Zod(z.set(toZodType(type))); const IsMap = (type, type2) => Zod(z.map(toZodType(type), toZodType(type2))); const IsLiteral = (...args) => Zod(z.literal(...args)); const IsNaN = (...args) => Zod(z.nan(...args)); const IsRecord = (type, type2) => Zod(z.record(toZodType(type), type2 ? toZodType(type2) : undefined)); const IsUnion = (...types) => Zod(z.union(types.map((t) => toZodType(t)))); const IsDiscriminatedUnion = (discriminator, options) => Zod(z.discriminatedUnion(discriminator, options.map((t) => toZodType(t)))); const IsIntersection = (left, right) => Zod(z.intersection(toZodType(left), toZodType(right))); const IsPromise = (type) => Zod(z.promise(toZodType(type))); const Preprocess = (fn) => mate.decorate("zodPreprocess", fn, true); const ToNumber = () => Preprocess((val) => typeof val === "string" && !!val ? Number(val) : val); const ToBoolean = (truthful = [ "true", "True", "TRUE", 1 ], falsy = [ "false", "False", "FALSE", 0 ]) => Preprocess((val) => { if (typeof val === "boolean") return val; if (truthful.includes(val)) return true; if (falsy.includes(val)) return false; return val; }); const IsCustom = (...args) => Zod(z.custom(...args)); const And = (type) => ZodFn("And", (t) => t.and(toZodType(type))); const Or = (type) => ZodFn("Or", (t) => t.or(toZodType(type))); function toZodType(type, opts) { if (type instanceof z.ZodType) return type; if (typeof type === "string") return resolveZodPrimitive(type); return getZodType({ type }, opts); } //#endregion //#region packages/zod/src/zod.pipe.ts const ZodPipeline = (opts) => definePipeFn(async (value, metas, level) => { const { targetMeta } = metas; if (!targetMeta || targetMeta.zodSkip || metas.classMeta?.zodSkip || metas.methodMeta?.zodSkip || metas.paramMeta?.zodSkip) return value; const validatableMeta = getZodMate().read(targetMeta.type); if (!targetMeta.zodValidate && !validatableMeta?.zodValidate && ![ String, Number, Date, Boolean, BigInt ].includes(targetMeta.type)) return value; const logger = useEventLogger("@moostjs/zod"); if (targetMeta.zodType || targetMeta.type) { const zodType = getZodTypeForProp({ type: metas.type, key: metas.key, index: metas.index }, { type: targetMeta.type, additionalMeta: targetMeta }, opts, logger); const check = await zodType.spa(value); if (check.success) return check.data; else { if (opts?.formatError) throw opts.formatError(check.error, value, metas, level); throw check.error; } } return value; }, TPipePriority.VALIDATE); //#endregion export { And, Coerce, DateFrom, DateTo, Default, EndsWith, HasLength, Includes, IsAny, IsArray, IsBigint, IsBoolean, IsCuid, IsCuid2, IsCustom, IsDate, IsDatetime, IsDiscriminatedUnion, IsEmail, IsEmoji, IsEnum, IsFinite, IsGt, IsGte, IsInt, IsIntersection, IsIp, IsLiteral, IsLt, IsLte, IsMap, IsMultipleOf, IsNaN, IsNativeEnum, IsNegative, IsNever, IsNonnegative, IsNonpositive, IsNull, IsNullable, IsNullish, IsNumber, IsPositive, IsPromise, IsRecord, IsSafeNumber, IsSet, IsString, IsSymbol, IsTuple, IsUlid, IsUndefined, IsUnion, IsUnknown, IsUrl, IsUuid, IsVoid, LazyType, MatchesRegex, Max, Min, OnCatch, Or, Passthrough, Preprocess, Refine, StartsWith, Strict, Strip, SuperRefine, ToBoolean, ToNumber, Transform, Trim, Validatable, Zod, ZodPipeline, ZodSkip, getZodMate, getZodType, getZodTypeForProp, validate, z };