diff --git a/packages/convex-helpers/server/zod.test.ts b/packages/convex-helpers/server/zod.test.ts index d0df2e40..547e0ff1 100644 --- a/packages/convex-helpers/server/zod.test.ts +++ b/packages/convex-helpers/server/zod.test.ts @@ -833,6 +833,9 @@ test("convexToZod complex types", () => { const recordValidator = convexToZod(v.record(v.string(), v.number())); expect(recordValidator.constructor.name).toBe("ZodRecord"); + + const optionalValidator = convexToZod(v.optional(v.string())); + expect(optionalValidator.constructor.name).toBe("ZodOptional"); }); test("convexToZodFields", () => { diff --git a/packages/convex-helpers/server/zod.ts b/packages/convex-helpers/server/zod.ts index 59db1871..0e1c5cb7 100644 --- a/packages/convex-helpers/server/zod.ts +++ b/packages/convex-helpers/server/zod.ts @@ -1314,6 +1314,57 @@ export function zBrand< /** Simple type conversion from a Convex validator to a Zod validator. */ export type ConvexToZod = z.ZodType>; +/** Better type conversion from a Convex validator to a Zod validator where the output is not a generetic ZodType but it's more specific. + * + * ES: z.ZodString instead of z.ZodType + * so you can use methods of z.ZodString like .min() or .email() + */ + +type ZodFromValidatorBase = + V extends VId> + ? Zid + : V extends VString + ? T extends string & { _: infer Brand extends string } + ? z.ZodBranded + : z.ZodString + : V extends VFloat64 + ? z.ZodNumber + : V extends VInt64 + ? z.ZodBigInt + : V extends VBoolean + ? z.ZodBoolean + : V extends VNull + ? z.ZodNull + : V extends VLiteral + ? z.ZodLiteral + : V extends VObject + ? z.ZodObject< + { + [K in keyof Fields]: ZodValidatorFromConvex; + }, + "strip" + > + : V extends VRecord + ? Key extends VId> + ? z.ZodRecord< + Zid, + ZodValidatorFromConvex + > + : z.ZodRecord> + : V extends VArray + ? z.ZodArray> + : V extends VUnion + ? z.ZodUnion< + [ZodValidatorFromConvex] + > + : z.ZodTypeAny; // fallback for unknown validators + +/** Main type with optional handling. */ +export type ZodValidatorFromConvex = + V extends Validator + ? z.ZodOptional> + : ZodFromValidatorBase; + /** * Turn a Convex validator into a Zod validator. * @param convexValidator Convex validator can be any validator from "convex/values" e.g. `v.string()` @@ -1321,7 +1372,7 @@ export type ConvexToZod = z.ZodType>; */ export function convexToZod( convexValidator: V, -): z.ZodType> { +): ZodValidatorFromConvex { const isOptional = (convexValidator as any).isOptional === "optional"; let zodValidator: z.ZodTypeAny; @@ -1393,7 +1444,9 @@ export function convexToZod( throw new Error(`Unknown convex validator type: ${convexValidator.kind}`); } - return isOptional ? z.optional(zodValidator) : zodValidator; + return isOptional + ? (z.optional(zodValidator) as ZodValidatorFromConvex) + : (zodValidator as ZodValidatorFromConvex); } /** @@ -1408,5 +1461,5 @@ export function convexToZodFields( ) { return Object.fromEntries( Object.entries(convexValidators).map(([k, v]) => [k, convexToZod(v)]), - ) as { [k in keyof C]: z.ZodType> }; + ) as { [k in keyof C]: ZodValidatorFromConvex }; }