-
Notifications
You must be signed in to change notification settings - Fork 45
Fix incorrect type inference: ensure ZodString instead of generic ZodType<string> on convexToZod function types #692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1314,14 +1314,65 @@ export function zBrand< | |
/** Simple type conversion from a Convex validator to a Zod validator. */ | ||
export type ConvexToZod<V extends GenericValidator> = z.ZodType<Infer<V>>; | ||
|
||
/** 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<string, z.ZodTypeDef, string> | ||
* so you can use methods of z.ZodString like .min() or .email() | ||
*/ | ||
|
||
type ZodFromValidatorBase<V extends GenericValidator> = | ||
V extends VId<GenericId<infer TableName extends string>> | ||
? Zid<TableName> | ||
: V extends VString<any, any> | ||
? z.ZodString | ||
: V extends VFloat64<any, any> | ||
? z.ZodNumber | ||
: V extends VInt64<any, any> | ||
? z.ZodBigInt | ||
: V extends VBoolean<any, any> | ||
? z.ZodBoolean | ||
: V extends VNull<any, any> | ||
? z.ZodNull | ||
: V extends VLiteral<any, any> | ||
? z.ZodLiteral<V["value"]> | ||
Comment on lines
+1336
to
+1337
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be |
||
: V extends VObject<any, any, any, any> | ||
? z.ZodObject<{ | ||
[K in keyof V["fields"]]: ZodValidatorFromConvex< | ||
Comment on lines
+1338
to
+1340
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here for extracting the type instead of using |
||
V["fields"][K] | ||
>; | ||
}> | ||
: V extends VRecord<any, infer Key, any, any, any> | ||
? Key extends VId<GenericId<infer TableName>> | ||
? z.ZodRecord< | ||
Zid<TableName>, | ||
ZodValidatorFromConvex<V["value"]> | ||
> | ||
: z.ZodRecord< | ||
z.ZodString, | ||
ZodValidatorFromConvex<V["value"]> | ||
> | ||
: V extends VArray<any, any> | ||
? z.ZodArray<ZodValidatorFromConvex<V["element"]>> | ||
: V extends VUnion<any, any, any, any> | ||
? z.ZodUnion< | ||
[ZodValidatorFromConvex<V["members"][number]>] | ||
> | ||
: z.ZodTypeAny; // fallback for unknown validators | ||
|
||
/** Main type with optional handling. */ | ||
export type ZodValidatorFromConvex<V extends GenericValidator> = | ||
V extends Validator<any, "optional", any> | ||
? z.ZodOptional<ZodFromValidatorBase<V>> | ||
: ZodFromValidatorBase<V>; | ||
|
||
/** | ||
* Turn a Convex validator into a Zod validator. | ||
* @param convexValidator Convex validator can be any validator from "convex/values" e.g. `v.string()` | ||
* @returns Zod validator (e.g. `z.string()`) with inferred type matching the Convex validator | ||
*/ | ||
export function convexToZod<V extends GenericValidator>( | ||
convexValidator: V, | ||
): z.ZodType<Infer<V>> { | ||
): ZodValidatorFromConvex<V> { | ||
const isOptional = (convexValidator as any).isOptional === "optional"; | ||
|
||
let zodValidator: z.ZodTypeAny; | ||
|
@@ -1393,7 +1444,9 @@ export function convexToZod<V extends GenericValidator>( | |
throw new Error(`Unknown convex validator type: ${convexValidator.kind}`); | ||
} | ||
|
||
return isOptional ? z.optional(zodValidator) : zodValidator; | ||
return isOptional | ||
? (z.optional(zodValidator) as ZodValidatorFromConvex<V>) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are optional types going through? I fear above that we're losing that. |
||
: (zodValidator as ZodValidatorFromConvex<V>); | ||
} | ||
|
||
/** | ||
|
@@ -1408,5 +1461,5 @@ export function convexToZodFields<C extends PropertyValidators>( | |
) { | ||
return Object.fromEntries( | ||
Object.entries(convexValidators).map(([k, v]) => [k, convexToZod(v)]), | ||
) as { [k in keyof C]: z.ZodType<Infer<C[k]>> }; | ||
) as { [k in keyof C]: ZodValidatorFromConvex<C[k]> }; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we lose branded types here?
e.g. the
brandedStringhelper results in a
VString<string & {_brand: "foo" },type - can we pass that through to a more specific
ZodString` type?