David Alonso
David Alonso6mo ago

`schemaValidation: false` for some parts of the schema

Hi, we love having schemaValidation turned on for most of our tables and parts of our documents. However, we have several documents that have different properties based on a type field, and ideally we'd still like to have nice type inference during development, without being strictly limited to storing foreign properties in a document. A helpful example might be to think about how Notion lets you convert between blocks that have different properties, but if you go from todo to heading and back to todo it still remembers the properties of the original todo block. Does that make sense?
5 Replies
lee
lee6mo ago
you could consider a helper like pretend https://github.com/get-convex/convex-helpers/blob/d5cfe601c38042f4b20c4e27c6d85d97c0c5e7ec/packages/convex-helpers/validators.ts#L172 that disables validation while still using the validator to infer typescript types
GitHub
convex-helpers/packages/convex-helpers/validators.ts at d5cfe601c38...
A collection of useful code to complement the official packages. - get-convex/convex-helpers
David Alonso
David AlonsoOP6mo ago
Oh wow, that’s exactly what we need, thank you thank you!!!
Michal Srb
Michal Srb6mo ago
Note that it's very easy to inline that helper:
someField: v.any() as Validator<{someShape: string}>
someField: v.any() as Validator<{someShape: string}>
add v.optional if needed
David Alonso
David AlonsoOP6mo ago
Let's take this one step further and suppose I want this field to be somewhat flexible, but still get some runtime validation, I quickly hacked something that works but might not be the best:
export const pretendWithConstraint = <T extends GenericValidator, C extends GenericValidator>(
_typeToImmitate: T,
constraint: C
): T => constraint as unknown as T;
export const pretendWithConstraint = <T extends GenericValidator, C extends GenericValidator>(
_typeToImmitate: T,
constraint: C
): T => constraint as unknown as T;
Would love to get your input! this comes up a lot in our case where C is the union of the possible T's for that document field, so we want flexibility but still want to make sure the field is of type C For my specific case of unions maybe this is better:
export function pretendWithUnionConstraint<
T extends GenericValidator,
U extends GenericValidator[]
>(
_typeToImmitate: T,
unionConstraint: ReturnType<typeof v.union<U>>
): T extends U[number] ? T : never {
// This type assertion ensures that T is part of the union
return unionConstraint as unknown as T extends U[number] ? T : never;
}
export function pretendWithUnionConstraint<
T extends GenericValidator,
U extends GenericValidator[]
>(
_typeToImmitate: T,
unionConstraint: ReturnType<typeof v.union<U>>
): T extends U[number] ? T : never {
// This type assertion ensures that T is part of the union
return unionConstraint as unknown as T extends U[number] ? T : never;
}
Thoughts? Also cc’ing @ian
ian
ian6mo ago
Am I understanding that you have a discriminated union and want to narrow the type to one of the objects, but that it’s all able to be represented by regular validators? And I guess the extra behavior is that when you change types all the old fields are optional? I would add a v.optional for all non-required fields. About to take off on a flight for PTO through the weekend Eg v.union( v.object({ type: “todo”, completed: v.boolean(), headingLevel: v.optional(v.number())}), v.object({ type: “heading”, completed: v.optional(v.boolean()), headingLevel: v.number()}), ) You could generically do it by making a partial of all of them combined, and spread that in before the specifically required fields For fancy type narrowing helpers I have some code snippets from ai town but not sure if they’re still in active use

Did you find this page helpful?