oscklm
oscklm14mo ago

Possibility of making a object validator all optional for use when patching fields

Say i have this:
// Schema
export const submissionFields = {
userId: v.id('users'),
profileIndex: v.number(),
videoId: v.id('videos'),

// Details
title: v.optional(v.string()),
storageId: v.id('_storage'),

// Settings
privacy: v.optional(v.union(v.literal('public'), v.literal('private'))),
isApproved: v.boolean(),
}


// Mutation example
const { userId, ...fields } = submissionFields

export const update = mutation({
args: {
id: v.id('submissions'),
...fields,
},
handler: async (ctx, { id, ...fields }) => {
const user = await authenticateUser(ctx)

if (!user) {
throw new Error('User not authenticated')
}

return await ctx.db.patch(id, {
...fields,
})
},
})
// Schema
export const submissionFields = {
userId: v.id('users'),
profileIndex: v.number(),
videoId: v.id('videos'),

// Details
title: v.optional(v.string()),
storageId: v.id('_storage'),

// Settings
privacy: v.optional(v.union(v.literal('public'), v.literal('private'))),
isApproved: v.boolean(),
}


// Mutation example
const { userId, ...fields } = submissionFields

export const update = mutation({
args: {
id: v.id('submissions'),
...fields,
},
handler: async (ctx, { id, ...fields }) => {
const user = await authenticateUser(ctx)

if (!user) {
throw new Error('User not authenticated')
}

return await ctx.db.patch(id, {
...fields,
})
},
})
In this case, i cant just pass what i want but need to pass all values that aren't already optional
const handleChangePrivacy = (id: Id<'submissions'>, value: boolean) => {
updateSubmission({
id,
privacy: value ? 'public' : 'private',
})
}
const handleChangePrivacy = (id: Id<'submissions'>, value: boolean) => {
updateSubmission({
id,
privacy: value ? 'public' : 'private',
})
}
I might have missed some smart way here. But this is a solid wall i hit very often.
8 Replies
ballingt
ballingt14mo ago
How are you typing updateSubmission(), that's a server function yeah?
oscklm
oscklmOP14mo ago
If i understand your question correctly. updateSubmission() is in this case:
const updateSubmission = useMutation(api.submissions.mutations.update)
const updateSubmission = useMutation(api.submissions.mutations.update)
ian
ian14mo ago
You can write a function that returns v.optional wrapping each validator in an object. Getting the types right is a bit tricky but doable. something like type Opt<V> = V extends Validator<infer T, any, infer FP> ? Validator<T | undefined, true, FP> : never then the overall object is something like
ReturnType<typeof v.object<{
[key in keyof Fields]: Opt<Fields[key]>;
}>
ReturnType<typeof v.object<{
[key in keyof Fields]: Opt<Fields[key]>;
}>
just throwing some things out there. If you get something working please share. I've been meaning to write a partial(fields) utility function that returns such a thing with the right types I have a Table helper in convex-helpers that gives you fields with and without system fields and a doc wrapped in v.object but it could compose well with a partial utility too one caveat is you might not want to wrap optionals: v.optional(v.optional(v.number())) but I'm not sure if that would be a problem
oscklm
oscklmOP14mo ago
Cool thanks Ian. I'll try and fiddle around with it and report back! So i might be on the wrong chat completely, but something in the area of this is what im trying to. The types gets lost in the args though, and havent figured out yet why.
import { PropertyValidators, Validator, v } from 'convex/values'
import { mutation } from './_generated/server'

export const bookmarkSchema = {
userId: v.id('users'),
profileIndex: v.number(),
videoId: v.id('videos'),
}

export const create = mutation({
args: { bookmark: v.object(bookmarkSchema) },
handler: async (ctx, { bookmark }) => {
return ctx.db.insert('bookmarks', bookmark)
},
})

type OptionalValidator<T> = { [K in keyof T]?: Validator<any, true, any> }

const partial = <T extends PropertyValidators>(schema: T): OptionalValidator<T> => {
return Object.keys(schema).reduce((acc, key) => {
const typedKey = key as keyof T
acc[typedKey] = v.optional(schema[typedKey])
return acc
}, {} as OptionalValidator<T>)
}

export const patch = mutation({
args: {
id: v.id('bookmarks'),
bookmark: v.object(partial(bookmarkSchema)), // This is where we need the helper function to generate a version of the schema with all optional
},
handler: async (ctx, { id, bookmark }) => {
return ctx.db.patch(id, bookmark)
},
})
import { PropertyValidators, Validator, v } from 'convex/values'
import { mutation } from './_generated/server'

export const bookmarkSchema = {
userId: v.id('users'),
profileIndex: v.number(),
videoId: v.id('videos'),
}

export const create = mutation({
args: { bookmark: v.object(bookmarkSchema) },
handler: async (ctx, { bookmark }) => {
return ctx.db.insert('bookmarks', bookmark)
},
})

type OptionalValidator<T> = { [K in keyof T]?: Validator<any, true, any> }

const partial = <T extends PropertyValidators>(schema: T): OptionalValidator<T> => {
return Object.keys(schema).reduce((acc, key) => {
const typedKey = key as keyof T
acc[typedKey] = v.optional(schema[typedKey])
return acc
}, {} as OptionalValidator<T>)
}

export const patch = mutation({
args: {
id: v.id('bookmarks'),
bookmark: v.object(partial(bookmarkSchema)), // This is where we need the helper function to generate a version of the schema with all optional
},
handler: async (ctx, { id, bookmark }) => {
return ctx.db.patch(id, bookmark)
},
})
The bookmark fields comes through as unknown I'm still learning advanced typescript. Not really there yet. But this is what i've gotten so far
oscklm
oscklmOP14mo ago
No description
oscklm
oscklmOP14mo ago
^ what the bookmark from args comes through as Seems like also doing reduce, isnt the way about it here since its having a tough time inferring the keys . Dunno
Michal Srb
Michal Srb14mo ago
I know that this is not the answer you're looking for, but I'd suggest to explicitly define the function argument validator - otherwise as your schema evolves, you might make a field editable from any client without realizing it... That said, you'd want this for OptionalValidator:
const argsValidator = {
x: v.string(),
}

type OptionalValidator<T extends PropertyValidators> = { [K in keyof T]?: T[K] }

type Foo = OptionalValidator<typeof argsValidator>;
const argsValidator = {
x: v.string(),
}

type OptionalValidator<T extends PropertyValidators> = { [K in keyof T]?: T[K] }

type Foo = OptionalValidator<typeof argsValidator>;
oscklm
oscklmOP14mo ago
Thanks a lot Michael. I can see how what you mean and how that could be an issue. Thanks for the help with the OptionalValidator I’ll try that out when I have the opportunity

Did you find this page helpful?