/*-------------------------------------------------------------------------- @sinclair/typemap The MIT License (MIT) Copyright (c) 2024-2025 Haydn Paterson (sinclair) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------------------------*/ import * as t from '@sinclair/typebox' import * as z from 'zod' // ------------------------------------------------------------------ // Constraint // ------------------------------------------------------------------ type TConstraint = (input: Input) => Output // ------------------------------------------------------------------ // Any // ------------------------------------------------------------------ type TFromAny = Result function FromAny(_type: t.TAny): z.ZodTypeAny { return z.any() } // ------------------------------------------------------------------ // Array // ------------------------------------------------------------------ type TFromArray>> = Result function FromArray(type: t.TArray): z.ZodTypeAny { const constraints: TConstraint>[] = [] const { minItems, maxItems /* minContains, maxContains, contains */ } = type if (t.ValueGuard.IsNumber(minItems)) constraints.push((input) => input.min(minItems)) if (t.ValueGuard.IsNumber(maxItems)) constraints.push((input) => input.max(maxItems)) const mapped = z.array(FromType(type.items)) return constraints.reduce((type, constraint) => constraint(type), mapped) } // ------------------------------------------------------------------ // BigInt // ------------------------------------------------------------------ type TFromBigInt = Result function FromBigInt(_type: t.TBigInt): z.ZodTypeAny { return z.bigint() } // ------------------------------------------------------------------ // Boolean // ------------------------------------------------------------------ type TFromBoolean = Result function FromBoolean(_type: t.TBoolean): z.ZodTypeAny { return z.boolean() } // ------------------------------------------------------------------ // Date // ------------------------------------------------------------------ type TFromDate = Result function FromDate(_type: t.TDate): z.ZodTypeAny { return z.date() } // ------------------------------------------------------------------ // Function // ------------------------------------------------------------------ // prettier-ignore type TFromFunction > = ( MappedParameters extends [z.ZodTypeAny, ...z.ZodTypeAny[]] | [] ? z.ZodFunction, TFromType> : z.ZodNever ) function FromFunction(type: t.TFunction): z.ZodTypeAny { const mappedParameters = FromTypes(type.parameters) as [] | [z.ZodTypeAny, ...z.ZodTypeAny[]] return z.function(z.tuple(mappedParameters), FromType(type.returns)) } // ------------------------------------------------------------------ // Integer // ------------------------------------------------------------------ type TFromInteger = Result function FromInteger(type: t.TInteger): z.ZodTypeAny { const { exclusiveMaximum, exclusiveMinimum, minimum, maximum, multipleOf } = type const constraints: TConstraint[] = [(value) => value.int()] if (t.ValueGuard.IsNumber(exclusiveMinimum)) constraints.push((input) => input.min(exclusiveMinimum + 1)) if (t.ValueGuard.IsNumber(exclusiveMaximum)) constraints.push((input) => input.min(exclusiveMaximum - 1)) if (t.ValueGuard.IsNumber(maximum)) constraints.push((input) => input.max(maximum)) if (t.ValueGuard.IsNumber(minimum)) constraints.push((input) => input.min(minimum)) if (t.ValueGuard.IsNumber(multipleOf)) constraints.push((input) => input.multipleOf(multipleOf)) return constraints.reduce((input, constraint) => constraint(input), z.number()) } // ------------------------------------------------------------------ // Intersect // ------------------------------------------------------------------ // prettier-ignore type TFromIntersect = ( Types extends [infer Left extends t.TSchema, ...infer Right extends t.TSchema[]] ? TFromIntersect, Result>> : Result ) function FromIntersect(type: t.TIntersect): z.ZodTypeAny { return type.allOf.reduce((result, left) => { return z.intersection(FromType(left), result) as never }, z.unknown()) as never } // ------------------------------------------------------------------ // Literal // ------------------------------------------------------------------ type TFromLiteral> = Result function FromLiteral(type: t.TLiteral): z.ZodTypeAny { return z.literal(type.const) } // ------------------------------------------------------------------ // Object // ------------------------------------------------------------------ // prettier-ignore type TFromObject< Properties extends t.TProperties, Result = z.ZodObject<{ [Key in keyof Properties]: TFromType }>, > = Result // prettier-ignore function FromObject(type: t.TObject): z.ZodTypeAny { const constraints: TConstraint>[] = [] const { additionalProperties } = type if (additionalProperties === false) constraints.push((input) => input.strict()) if (t.KindGuard.IsSchema(additionalProperties)) constraints.push((input) => input.catchall(FromType(additionalProperties))) const properties = globalThis.Object.getOwnPropertyNames(type.properties).reduce((result, key) => ({ ...result, [key]: FromType(type.properties[key]) }), {}) return constraints.reduce((type, constraint) => constraint(type), z.object(properties)) } // ------------------------------------------------------------------ // Promise // ------------------------------------------------------------------ type TFromPromise>> = Result function FromPromise(type: t.TPromise): z.ZodTypeAny { return z.promise(FromType(type.item)) } // ------------------------------------------------------------------ // Record // ------------------------------------------------------------------ type TFromRegExp = Result function FromRegExp(type: t.TRegExp): z.ZodTypeAny { const constraints: TConstraint[] = [(input) => input.regex(new RegExp(type.source), type.flags)] const { minLength, maxLength } = type if (t.ValueGuard.IsNumber(maxLength)) constraints.push((input) => input.max(maxLength)) if (t.ValueGuard.IsNumber(minLength)) constraints.push((input) => input.min(minLength)) return constraints.reduce((type, constraint) => constraint(type), z.string()) } // ------------------------------------------------------------------ // Record // ------------------------------------------------------------------ // prettier-ignore type TFromRecord = ( TFromType extends infer ZodKey extends z.KeySchema ? z.ZodRecord> : z.ZodNever ) // prettier-ignore function FromRecord(type: t.TRecord): z.ZodTypeAny { const pattern = globalThis.Object.getOwnPropertyNames(type.patternProperties)[0] const value = FromType(type.patternProperties[pattern]) return ( pattern === t.PatternBooleanExact ? z.record(z.boolean(), value) : pattern === t.PatternNumberExact ? z.record(z.number(), value) : pattern === t.PatternStringExact ? z.record(z.string(), value) : z.record(z.string().regex(new RegExp(pattern)), value) ) } // ------------------------------------------------------------------ // Never // ------------------------------------------------------------------ type TFromNever = Result function FromNever(type: t.TNever): z.ZodTypeAny { return z.never() } // ------------------------------------------------------------------ // Never // ------------------------------------------------------------------ type TFromNull = Result function FromNull(_type: t.TNull): z.ZodTypeAny { return z.null() } // ------------------------------------------------------------------ // Number // ------------------------------------------------------------------ type TFromNumber = Result function FromNumber(type: t.TNumber): z.ZodTypeAny { const { exclusiveMaximum, exclusiveMinimum, minimum, maximum, multipleOf } = type const constraints: TConstraint[] = [] if (t.ValueGuard.IsNumber(exclusiveMinimum)) constraints.push((input) => input.min(exclusiveMinimum + 1)) if (t.ValueGuard.IsNumber(exclusiveMaximum)) constraints.push((input) => input.min(exclusiveMaximum - 1)) if (t.ValueGuard.IsNumber(maximum)) constraints.push((input) => input.max(maximum)) if (t.ValueGuard.IsNumber(minimum)) constraints.push((input) => input.min(minimum)) if (t.ValueGuard.IsNumber(multipleOf)) constraints.push((input) => input.multipleOf(multipleOf)) return constraints.reduce((input, constraint) => constraint(input), z.number()) } // ------------------------------------------------------------------ // String // ------------------------------------------------------------------ type TFromString = Result // prettier-ignore function FromString(type: t.TString): z.ZodTypeAny { const constraints: TConstraint[] = [] const { minLength, maxLength, pattern, format } = type if (t.ValueGuard.IsNumber(maxLength)) constraints.push((input) => input.max(maxLength)) if (t.ValueGuard.IsNumber(minLength)) constraints.push((input) => input.min(minLength)) if (t.ValueGuard.IsString(pattern)) constraints.push((input) => input.regex(new RegExp(pattern))) if (t.ValueGuard.IsString(format)) constraints.push((input) => format === 'base64' ? input.base64() : format === 'base64url' ? input.base64url() : format === 'cidrv4' ? input.cidr({ version: 'v4' }) : format === 'cidrv6' ? input.cidr({ version: 'v6' }) : format === 'cidr' ? input.cidr() : format === 'cuid' ? input.cuid() : format === 'cuid2' ? input.cuid2() : format === 'date' ? input.date() : format === 'datetime' ? input.datetime() : format === 'duration' ? input.duration() : format === 'email' ? input.email() : format === 'emoji' ? input.emoji() : format === 'ipv4' ? input.ip({ version: 'v4' }) : format === 'ipv6' ? input.ip({ version: 'v6' }) : format === 'ip' ? input.ip() : format === 'jwt' ? input.jwt() : format === 'nanoid' ? input.nanoid() : format === 'time' ? input.time() : format === 'ulid' ? input.ulid() : format === 'url' ? input.url() : format === 'uuid' ? input.uuid() : input, ) return constraints.reduce((type, constraint) => constraint(type), z.string()) } // ------------------------------------------------------------------ // Symbol // ------------------------------------------------------------------ type TFromSymbol = Result function FromSymbol(_type: t.TSymbol): z.ZodTypeAny { return z.symbol() } // ------------------------------------------------------------------ // Tuple // ------------------------------------------------------------------ // prettier-ignore type TFromTuple> = ( Mapped extends [z.ZodTypeAny, ...z.ZodTypeAny[]] | [] ? z.ZodTuple : z.ZodNever ) function FromTuple(type: t.TTuple): z.ZodTypeAny { const mapped = FromTypes(type.items || []) as [] | [z.ZodTypeAny, ...z.ZodTypeAny[]] return z.tuple(mapped) } // ------------------------------------------------------------------ // Undefined // ------------------------------------------------------------------ type TFromUndefined = Result function FromUndefined(_type: t.TUndefined): z.ZodTypeAny { return z.undefined() } // ------------------------------------------------------------------ // Union // ------------------------------------------------------------------ // prettier-ignore type TFromUnion> = ( Mapped extends z.ZodUnionOptions ? z.ZodUnion : z.ZodNever ) function FromUnion(_type: t.TUnion): z.ZodTypeAny { const mapped = FromTypes(_type.anyOf) as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]] return mapped.length >= 1 ? z.union(mapped) : z.never() } // ------------------------------------------------------------------ // TUnknown // ------------------------------------------------------------------ type TFromUnknown = Result function FromUnknown(_type: t.TUnknown): z.ZodTypeAny { return z.unknown() } // ------------------------------------------------------------------ // Void // ------------------------------------------------------------------ type TFromVoid = Result function FromVoid(_type: t.TVoid): z.ZodTypeAny { return z.void() } // ------------------------------------------------------------------ // Types // ------------------------------------------------------------------ // prettier-ignore type TFromTypes = ( Types extends [infer Left extends t.TSchema, ...infer Right extends t.TSchema[]] ? TFromTypes]> : Result ) function FromTypes(types: t.TSchema[]): z.ZodTypeAny[] { return types.map((type) => FromType(type)) } // ------------------------------------------------------------------ // Type // ------------------------------------------------------------------ // prettier-ignore type TFromType = ( Type extends t.TAny ? TFromAny : Type extends t.TArray ? TFromArray : Type extends t.TBigInt ? TFromBigInt : Type extends t.TBoolean ? TFromBoolean : Type extends t.TDate ? TFromDate : Type extends t.TFunction ? TFromFunction : Type extends t.TInteger ? TFromInteger : Type extends t.TIntersect ? TFromIntersect : Type extends t.TLiteral ? TFromLiteral : Type extends t.TNever ? TFromNever : Type extends t.TNull ? TFromNull : Type extends t.TNumber ? TFromNumber : Type extends t.TObject ? TFromObject : Type extends t.TPromise ? TFromPromise : Type extends t.TRecord ? TFromRecord : Type extends t.TRegExp ? TFromRegExp : Type extends t.TString ? TFromString : Type extends t.TSymbol ? TFromSymbol : Type extends t.TTuple ? TFromTuple : Type extends t.TUndefined ? TFromUndefined : Type extends t.TUnion ? TFromUnion : Type extends t.TUnknown ? TFromUnknown : Type extends t.TVoid ? TFromVoid : z.ZodNever ), // Modifier Mapping IsReadonly extends boolean = Type extends t.TReadonly ? true : false, IsOptional extends boolean = Type extends t.TOptional ? true : false, Result extends z.ZodTypeAny | z.ZodEffects = ( [IsReadonly, IsOptional] extends [true, true] ? z.ZodReadonly> : [IsReadonly, IsOptional] extends [false, true] ? z.ZodOptional : [IsReadonly, IsOptional] extends [true, false] ? z.ZodReadonly : Mapped ) > = Result // prettier-ignore function FromType(type: t.TSchema): z.ZodTypeAny | z.ZodEffects { const constraints: TConstraint[] = [] if(!t.ValueGuard.IsUndefined(type.description)) constraints.push(input => input.describe(type.description!)) if(!t.ValueGuard.IsUndefined(type.default)) constraints.push(input => input.default(type.default)) const mapped = constraints.reduce((type, constraint) => constraint(type), ( t.KindGuard.IsAny(type) ? FromAny(type) : t.KindGuard.IsArray(type) ? FromArray(type) : t.KindGuard.IsBigInt(type) ? FromBigInt(type) : t.KindGuard.IsBoolean(type) ? FromBoolean(type) : t.KindGuard.IsDate(type) ? FromDate(type) : t.KindGuard.IsFunction(type) ? FromFunction(type) : t.KindGuard.IsInteger(type) ? FromInteger(type) : t.KindGuard.IsIntersect(type) ? FromIntersect(type) : t.KindGuard.IsLiteral(type) ? FromLiteral(type) : t.KindGuard.IsNever(type) ? FromNever(type) : t.KindGuard.IsNull(type) ? FromNull(type) : t.KindGuard.IsNumber(type) ? FromNumber(type) : t.KindGuard.IsObject(type) ? FromObject(type) : t.KindGuard.IsPromise(type) ? FromPromise(type) : t.KindGuard.IsRegExp(type) ? FromRegExp(type) : t.KindGuard.IsRecord(type) ? FromRecord(type) : t.KindGuard.IsString(type) ? FromString(type) : t.KindGuard.IsSymbol(type) ? FromSymbol(type) : t.KindGuard.IsTuple(type) ? FromTuple(type) : t.KindGuard.IsUndefined(type) ? FromUndefined(type) : t.KindGuard.IsUnion(type) ? FromUnion(type) : t.KindGuard.IsUnknown(type) ? FromUnknown(type) : t.KindGuard.IsVoid(type) ? FromVoid(type) : z.never() )) // Modifier Mapping const isOptional = t.KindGuard.IsOptional(type) const isReadonly = t.KindGuard.IsReadonly(type) const result = ( isOptional && isReadonly ? z.optional(mapped) : isOptional && !isReadonly ? z.optional(mapped) : !isOptional && isReadonly ? mapped : mapped ) return result } // ------------------------------------------------------------------ // ZodFromTypeBox // ------------------------------------------------------------------ /** Creates a Zod type from TypeBox */ // prettier-ignore export type TZodFromTypeBox = TFromType > = Result /** Creates a Zod type from TypeBox */ export function ZodFromTypeBox(type: Type): TZodFromTypeBox { return FromType(type) as never }