UNPKG

zod-invertible

Version:

declare zod schemas that can be inverted to format from output to input

178 lines (170 loc) 5.82 kB
import z from 'zod' export interface ZodInvertibleDef< I extends z.ZodTypeAny, O extends z.ZodTypeAny, > extends z.ZodPipelineDef<z.ZodEffects<I, z.input<O>>, O> { inputSchema: I outputSchema: O parse: ( value: z.output<I>, ctx: z.RefinementCtx ) => z.input<O> | Promise<z.input<O>> format: ( value: z.output<O>, ctx: z.RefinementCtx ) => z.output<I> | Promise<z.output<I>> } export class ZodInvertible< I extends z.ZodTypeAny, O extends z.ZodTypeAny, > extends z.ZodPipeline<z.ZodEffects<I, z.input<O>>, O> { declare _def: ZodInvertibleDef<I, O> constructor( inputSchema: I, parse: ( out: z.output<I>, ctx: z.RefinementCtx ) => z.input<O> | Promise<z.input<O>>, outputSchema: O, format: ( out: z.output<O>, ctx: z.RefinementCtx ) => z.output<I> | Promise<z.output<I>> ) { super({ ...inputSchema.transform(parse).pipe(outputSchema)._def, inputSchema, outputSchema, parse, format, } as ZodInvertibleDef<I, O>) } } export function invertible<T extends z.ZodTypeAny, U extends z.ZodTypeAny>( schema: T, parse: ( value: z.output<T>, ctx: z.RefinementCtx ) => z.input<U> | Promise<z.input<U>>, outSchema: U, format: ( value: z.output<U>, ctx: z.RefinementCtx ) => z.output<T> | Promise<z.output<T>> ) { return new ZodInvertible(schema, parse, outSchema, format) } export const IgnoreEffect = Symbol('IgnoreEffect') export function ignoreEffect<T extends z.ZodEffects<any, any, any>>( schema: T ): T { ;(schema as any)[IgnoreEffect] = true return schema } export function invert<T extends z.ZodTypeAny>( schema: T ): z.ZodType<z.input<T>, any, z.output<T>> { switch (schema._def.typeName) { case z.ZodFirstPartyTypeKind.ZodArray: return z.array(invert((schema as any as z.ZodArray<any>).element)) case z.ZodFirstPartyTypeKind.ZodObject: { const { shape, _def } = schema as any as z.AnyZodObject const invertedShape = Object.fromEntries( Object.entries(shape).map(([key, value]) => [key, invert(value as any)]) ) return new z.ZodObject({ ..._def, catchall: invert(_def.catchall), shape: () => invertedShape, }) } case z.ZodFirstPartyTypeKind.ZodUnion: return z.union((schema as any as z.ZodUnion<any>).options.map(invert)) case z.ZodFirstPartyTypeKind.ZodDiscriminatedUnion: { const { discriminator, options } = schema as any as z.ZodDiscriminatedUnion<any, any> return z.discriminatedUnion(discriminator, options.map(invert)) } case z.ZodFirstPartyTypeKind.ZodIntersection: { const { left, right } = (schema as any as z.ZodIntersection<any, any>) ._def return z.intersection(invert(left), invert(right)) } case z.ZodFirstPartyTypeKind.ZodTuple: { const { items, _def: { rest }, } = schema as any as z.ZodTuple<any, any> const result = z.tuple(items.map(invert)) return rest ? result.rest(invert(rest)) : result } case z.ZodFirstPartyTypeKind.ZodRecord: { const { keySchema, valueSchema } = schema as any as z.ZodRecord<any, any> return z.record(invert(keySchema), invert(valueSchema)) } case z.ZodFirstPartyTypeKind.ZodMap: { const { keySchema, valueSchema } = schema as any as z.ZodMap<any, any> return z.map(invert(keySchema), invert(valueSchema)) } case z.ZodFirstPartyTypeKind.ZodSet: return z.set(invert((schema as any as z.ZodSet<any>)._def.valueType)) case z.ZodFirstPartyTypeKind.ZodFunction: { const { _def: { args, returns }, } = schema as any as z.ZodFunction<any, any> return z.function(args.map(invert), invert(returns)) } case z.ZodFirstPartyTypeKind.ZodLazy: return z.lazy(() => invert((schema as any as z.ZodLazy<any>).schema)) case z.ZodFirstPartyTypeKind.ZodEffects: { const { _def: { effect, schema: innerType }, } = schema as any as z.ZodEffects<any> switch (effect.type) { case 'refinement': { const { refinement } = effect return z.any().superRefine(refinement).pipe(invert(innerType)) } case 'preprocess': case 'transform': if ((schema as any)[IgnoreEffect]) { return invert(innerType) } throw new Error(`effect not supported: ${effect.type}`) } break } case z.ZodFirstPartyTypeKind.ZodOptional: return invert((schema as any as z.ZodOptional<any>).unwrap()).optional() case z.ZodFirstPartyTypeKind.ZodNullable: return invert((schema as any as z.ZodNullable<any>).unwrap()).nullable() case z.ZodFirstPartyTypeKind.ZodDefault: return invert((schema as any as z.ZodDefault<any>).removeDefault()) case z.ZodFirstPartyTypeKind.ZodCatch: return invert((schema as any as z.ZodCatch<any>).removeCatch()) case z.ZodFirstPartyTypeKind.ZodPromise: return z.promise(invert((schema as any as z.ZodPromise<any>)._def.type)) case z.ZodFirstPartyTypeKind.ZodBranded: return invert((schema as any as z.ZodBranded<any, any>)._def.type) case z.ZodFirstPartyTypeKind.ZodPipeline: { if (schema instanceof ZodInvertible) { const { _def: { inputSchema, outputSchema, parse, format }, } = schema return invertible( invert(outputSchema), format, invert(inputSchema), parse ) } const { _def } = schema as any as z.ZodPipeline<any, any> return invert(_def.out).pipe(invert(_def.in)) } case z.ZodFirstPartyTypeKind.ZodReadonly: return invert( (schema as any as z.ZodReadonly<any>)._def.innerType ).readonly() } return schema }