David Alonso
David Alonso3mo ago

Help with `convexToZod` and spreading fields

I've been trying to get this (and variations of it) to work but I can't figure it out
export const zUser = z.object({
...convexToZod(authTables.users.validator),
email: z.string(),
})
export const zUser = z.object({
...convexToZod(authTables.users.validator),
email: z.string(),
})
What's the right way to do this?
11 Replies
Convex Bot
Convex Bot3mo ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
ampp
ampp3mo ago
So i have gone about this with a different approach - zod first rather than validator first? I'm curious who else is here. I have even more incentive to do this across my whole project because of the agent component use of zod. I'm currently doing
export const zodTestTableSchema = z.object({
**inspectionName**: z.string(),
formId: zid('forms'),
})

testTable: defineTable(
zodOutputToConvex(
zodTestTableSchema.extend({
lastUpdate: z.number() // fields that are not likely to be used in mutations etc.
})
export const zodTestTableSchema = z.object({
**inspectionName**: z.string(),
formId: zid('forms'),
})

testTable: defineTable(
zodOutputToConvex(
zodTestTableSchema.extend({
lastUpdate: z.number() // fields that are not likely to be used in mutations etc.
})
then i can make use of extends and omit and covert to convex validators if needed. I'm very close to just redoing 100+ validators and infer types from zod instead of convex. Its a huge undertaking. But i think will pay off
David Alonso
David AlonsoOP3mo ago
yeah this is my overall approach as well, just struggling with this case where I have authTables.users.validator from Convex auth
ampp
ampp3mo ago
Ah yeah, i dont use auth but shouldn't this work
const myZod = convexToZod(authTables.users.validator)
export const zUser = myZod.extends({
email: z.string(),
})
const myZod = convexToZod(authTables.users.validator)
export const zUser = myZod.extends({
email: z.string(),
})
David Alonso
David AlonsoOP3mo ago
Property 'extends' does not exist on type 'ZodType<{ name?: string | undefined; email?: string | undefined; phone?: string | undefined; image?: string | undefined; emailVerificationTime?: number | undefined; phoneVerificationTime?: number | undefined; isAnonymous?: boolean | undefined; }, ZodTypeDef, { ...; }>'.ts(2339)
Property 'extends' does not exist on type 'ZodType<{ name?: string | undefined; email?: string | undefined; phone?: string | undefined; image?: string | undefined; emailVerificationTime?: number | undefined; phoneVerificationTime?: number | undefined; isAnonymous?: boolean | undefined; }, ZodTypeDef, { ...; }>'.ts(2339)
ampp
ampp3mo ago
Yeah i think i ran into that already also 🙃 . I guess the question is what is the trick..
David Alonso
David AlonsoOP3mo ago
Hoping Ian can chip in tomorrow
ampp
ampp3mo ago
claude 4 says do
function convexObjectToZod<V extends VObject<any, any>>(
convexValidator: V,
): z.ZodObject<{ [K in keyof V['fields']]: z.ZodType<Infer<V['fields'][K]>> }> {
const result = convexToZod(convexValidator);
return result as any; // We know this is safe for object validators
}

// Usage:
const myZod = convexObjectToZod(authTables.users.validator)
export const zUser = myZod.extend({
email: z.string(),
})
function convexObjectToZod<V extends VObject<any, any>>(
convexValidator: V,
): z.ZodObject<{ [K in keyof V['fields']]: z.ZodType<Infer<V['fields'][K]>> }> {
const result = convexToZod(convexValidator);
return result as any; // We know this is safe for object validators
}

// Usage:
const myZod = convexObjectToZod(authTables.users.validator)
export const zUser = myZod.extend({
email: z.string(),
})
ian
ian2mo ago
I believe your code ends up looking like:
export const zUser = z.object({
...z.object({ <user fields as zod }),
email: z.string(),
})
export const zUser = z.object({
...z.object({ <user fields as zod }),
email: z.string(),
})
whereas I suspect you want
export const zUser = z.object({
...{ <user fields as zod },
email: z.string(),
})
export const zUser = z.object({
...{ <user fields as zod },
email: z.string(),
})
which would be
export const zUser = z.object({
...convexToZodFields(authTables.users.validator.fields),
email: z.string(),
})
export const zUser = z.object({
...convexToZodFields(authTables.users.validator.fields),
email: z.string(),
})
David Alonso
David AlonsoOP2mo ago
Cool!!! That fixes it! Now having another issue though. When I have the following:
export const zUser = z.object({
...convexToZodFields(authTables.users.validator.fields),
email: z.string(),
});

export const zUserDoc = z.object(withSystemFields("users", zUser.shape));

export const usersTable = defineTable(zodToConvex(zUser));
export const zUser = z.object({
...convexToZodFields(authTables.users.validator.fields),
email: z.string(),
});

export const zUserDoc = z.object(withSystemFields("users", zUser.shape));

export const usersTable = defineTable(zodToConvex(zUser));
For some reason, any query that returns a user document, e.g.
export const getUnimpersonatedUser = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (userId === null) {
return null;
}
return await ctx.db.get(userId);
},
});
export const getUnimpersonatedUser = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (userId === null) {
return null;
}
return await ctx.db.get(userId);
},
});
returns a GenericDocument type. Any ideas on what's going on/what I'm doing wrong? The above should hopefully be easy to repro for you 🙏 lmk if you observe the same
ian
ian2mo ago
Confirming: getAuthUserId has return type Id<"users"> that you observe on userId? I'd debug by doing things like
const table = zodToConvex(zUser);
type t = typeof table;
type ut = typeof usersTable;
type s = typeof schema["tables"]
type u = s["users"]
const table = zodToConvex(zUser);
type t = typeof table;
type ut = typeof usersTable;
type s = typeof schema["tables"]
type u = s["users"]
or whether this works:
export const zUserFields = {
...convexToZodFields(authTables.users.validator.fields),
email: z.string(),
};

export const zUserDoc = z.object(withSystemFields("users", zUserFields));

export const usersTable = defineTable(zodToConvexFields(zUser));
export const zUserFields = {
...convexToZodFields(authTables.users.validator.fields),
email: z.string(),
};

export const zUserDoc = z.object(withSystemFields("users", zUserFields));

export const usersTable = defineTable(zodToConvexFields(zUser));
etc. At some point the schema is either not able to get the type of userId or the DataModel doesn't have data on the user table.

Did you find this page helpful?