oscklm
oscklm6mo ago

Return value validation question?

If i wanna use the return value validation in a function. And say that function returns an array of documents. How may i define the return value, as the doc. I have a myValidator exported from a validators.ts file that i can use like returns: v.array(videoValidator) But that ends up erroring, since what im returning is the result of a .collect() so basically what ends up coming through is system fields aswell. I don't see anything regarding validating returned objects containing system fields in the docs.
9 Replies
erquhart
erquhart6mo ago
Any reason not to add the system fields to your validator, or make a variant that has the system fields? I think that's what you want here, unless you don't want the system fields in your return value. Eg., add { _id: v.id('tableName'), _creationTime: v.number() } to the validator It would be cool to have a v.doc('tableName') validator though
sshader
sshader6mo ago
I want to polish this up and add it to convex-helpers, but here's an example of what you're talking about (not super battle tested, but it seems to work for one of my apps)
import { Validator, v } from 'convex/values'
import { Doc, TableNames } from '../_generated/dataModel'
import schema from '../schema'

export const betterV = {
id: <T extends TableNames>(tableName: T) => {
return v.id(tableName)
},
doc: <T extends TableNames>(
tableName: T
): Validator<Doc<T>, 'required', any> => {
const validator = schema.tables[tableName].validator;
if (validator.kind !== 'object') {
throw new Error(`Not an object validator`)
}
return v.object({
...validator.fields,
_id: v.id(tableName),
_creationTime: v.number(),
})
},
mergeObjects: <
O1 extends Record<string, any>,
O2 extends Record<string, any>
>(
validator1: Validator<O1, 'required', any>,
validator2: Validator<O2, 'required', any>
): Validator<Omit<O1, keyof O2> & O2, 'required', any> => {
if (validator1.kind !== 'object' || validator2.kind !== 'object') {
throw new Error('Not object validators')
}
return v.object({
...validator1.fields,
...validator2.fields,
}) as any
},
}
import { Validator, v } from 'convex/values'
import { Doc, TableNames } from '../_generated/dataModel'
import schema from '../schema'

export const betterV = {
id: <T extends TableNames>(tableName: T) => {
return v.id(tableName)
},
doc: <T extends TableNames>(
tableName: T
): Validator<Doc<T>, 'required', any> => {
const validator = schema.tables[tableName].validator;
if (validator.kind !== 'object') {
throw new Error(`Not an object validator`)
}
return v.object({
...validator.fields,
_id: v.id(tableName),
_creationTime: v.number(),
})
},
mergeObjects: <
O1 extends Record<string, any>,
O2 extends Record<string, any>
>(
validator1: Validator<O1, 'required', any>,
validator2: Validator<O2, 'required', any>
): Validator<Omit<O1, keyof O2> & O2, 'required', any> => {
if (validator1.kind !== 'object' || validator2.kind !== 'object') {
throw new Error('Not object validators')
}
return v.object({
...validator1.fields,
...validator2.fields,
}) as any
},
}
erquhart
erquhart6mo ago
oohh that's awesome could have a docWithoutSystemFields counterpart too love this
oscklm
oscklmOP6mo ago
That's real nice! thanks for sharing! Indeed a better V haha yeah quickly started missing something like that, when i began using the return validation earlier today
erquhart
erquhart6mo ago
You could just pull this out from the code above and use it in your validators without replacing v:
import { Validator, v } from 'convex/values'
import { Doc, TableNames } from '../_generated/dataModel'
import schema from '../schema'

const docValidator = <T extends TableNames>(
tableName: T
): Validator<Doc<T>, 'required', any> => {
const validator = schema.tables[tableName].validator;
if (validator.kind !== 'object') {
throw new Error(`Not an object validator`)
}
return v.object({
...validator.fields,
_id: v.id(tableName),
_creationTime: v.number(),
})
}
import { Validator, v } from 'convex/values'
import { Doc, TableNames } from '../_generated/dataModel'
import schema from '../schema'

const docValidator = <T extends TableNames>(
tableName: T
): Validator<Doc<T>, 'required', any> => {
const validator = schema.tables[tableName].validator;
if (validator.kind !== 'object') {
throw new Error(`Not an object validator`)
}
return v.object({
...validator.fields,
_id: v.id(tableName),
_creationTime: v.number(),
})
}
So you could do:
returns: {
v.array(docValidator('videos'))
}
returns: {
v.array(docValidator('videos'))
}
oscklm
oscklmOP6mo ago
That is great!
erquhart
erquhart6mo ago
Big thanks to @sshader, I didn't realize the schema came with validators before reading her betterV implementation
oscklm
oscklmOP6mo ago
Yeah totally. Really nice to learn that it does 👍
sshader
sshader6mo ago
I didn't realize the schema came with validators
Yeah this was part of 1.13 (or at least making them easier to access) since we figured folks would want return value validators for Doc<"foo"> . Note that this betterV can only be used in argument validators / return value validators, and not schema definitions themselves (or else we'd have a circular dependency). The reason v is less powerful in some ways is because it's designed to work in both schema definitions + validators.

Did you find this page helpful?