Typescript: Checking if value is part of a v.union(v.literal()) type
How do I accomplish the following check?

46 Replies
The types are TS types, they cannot be used to perform runtime checks.
You'll need a runtime value, like a set:
hmm i see, I'm trying to find a solution that avoids duplication. Is there a way I can do the
as GridBlock
cast with better safety? I was initially thinking of putting a validator vGridBlock
on the argument of patchBlockPropertiesWithNewGridLayout
but I don't think that works out of the box. I apologize if this doesn't make sense, I'm quite inexperienced with validators and robust type checking, so very open to get feedback on how you'd implement this. I'm also checking https://stack.convex.dev/typescript-zod-function-validation
Zod with TypeScript for Server-side Validation and End-to-End Types
Use Zod with TypeScript for argument validation on your server functions allows you to both protect against invalid data, and define TypeScript types ...
Hi @Michal Srb maybe somewhat related to this in the sense that I'm trying to do type reuse to avoid things going out of sync
Suppose I have the following:
Am I able to extract a validator object that is the same as v.union(v.literal("string"),v.literal("int"))? I want to use validator as the type of an argument to a mutation for instance, let me know if there's an easier way 🙂
Also would be curious in what cases you'd make use of an auxiliary enum, e.g.
and if so, if there's an easy way to go from an enum definition to a validator that's a union of literals?
@sshader if you're online I'd appreciate your input on this, we make heavy use of literals everywhere and we're getting stuck in a few places whenever we have to use validators and would really like to understand the recommended approach
looking into this potentially: https://www.npmjs.com/package/convex-helpers#validator-utilities
npm
convex-helpers
A collection of useful code to complement the official convex package.. Latest version: 0.1.41, last published: 17 days ago. Start using convex-helpers in your project by running
npm i convex-helpers
. There are no other projects in the npm registry using convex-helpers.Going down the rabbithole... trying to use Table for my existing tables:
I cannot pass in a validator and I'm not sure how to represent this union in the expected format
Maybe I can use this pattern inside convex function args?
but I guess this is not the same as:
? @ian
If I try to use this pattern inside a validator object (e.g. for a table schema definition) I get a weird warning saying that Doc is not generic...
apparently I can do this instead:
Hey @David Alonso - very glad to be hearing these questions, since we're actively improving this and I'm glad to get the validation that it's useful!
I think you've discovered a lot already. I really like the type hacking to reach into the discriminated union - that seems like a good way to get the type-time validation.
The runtime (and type time!) validation will be easier with the changes we're making, which allows you to introspect validators and maintain types. So you could do
vDataSource.members[0].fields.type.value
and the runtime value and typescript type would be "firestore"
. So you can extract the values for validation, along with the types - though I'm not sure if simply doing vDataSource.members.map(m => m.fields.type.value)
would give you the good type union, I hope it would! cc @ballingt who is working on getting this out soon (~days or ~weeks)Thanks for the input @ian , I look forward to these changes!
do you know how I can get around this? (it's a bit unrelated)
What do you want
.withoutSystemFields
to return? Just getting the type out? If the union truly only differs with type
I wonder if you could do something like:
sorry my example wasn't good, here's a more representative one:
this is a pattern we use a TON in our schema ~50% of our tables
gotcha - yeah I often try to hide the extra data in a sub-field, like
So tactically (and hoping the new way ships in a few days), I wonder if this would work:
That's all I have in me tonight - good luck!
Thanks so much! Please keep me posted on when you ship new stuff! I wasn't able to get the above to work (maybe due to incorrect imports). Is dataSourceObjects what you'd pass into Table()?
In this case I would not use Table. Table is a handy utility for keeping references to the fields / validator for the doc / etc, but it isn't quite as obvious what it should do for unions for e.g.
withoutSystemFields
- should it be an array?
Where are you hoping to use it like Table? Each use case hopefully can just compose these thingsThe reason I wanted to use a Table everywhere is because of access to these utils, which seem super useful:
In the next release, this syntax works (and note no helpers needed!):
and you can do
schema.tables.dataSources.validator.fields
to get withoutSystemFields
🎉that's great! two questions:
1. when is this next release?
2. what would be the equivalent of pick and omit helpers in this new release to get partial validators?
1. As soon as tomorrow
2. You could still pick & omit on the .fields of an object validator
Hey @ian , I'm on 1.12.2 bu this doesn't work yet:
is this expected?
We haven't made the release yet
is it in 1.13?
Yup
Hey @ian is there a blog post or something coming with some more usage examples? Would be really helpful to start applying this in our codebase
@David Alonso not immediately planned, any questions about it? try something like
schema.tables.users.validator
and tab complete your way to success 🙂
There's no pick or omit yet. but you do this manually by spreading (for omit) and choosing (for pick) from validator.fields.
@David Alonso feel free to write questions here even if you can figure them out, it'll be helpful for when we write more docs about this.okay, my immediate questions were about pick and omit
can you give a brief working example of spreading and choosing? maybe helpful for others as well
Here's adding a field and omitting a field: TypeScript Playground
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
@David Alonso is this what you're looking for, or do you need functions that do this? I'd love to hear what you're thinking
You also don't need to do this fancy spreading, just
works too.
damn that's so cool, this example really crystalizes it!
for now this is what we're planning to use it for, but will let you know if any other use cases pop up
Here's one of your earlier examples, getting the members of a union
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
How do I handle such a case with Validators?
With regular types it works, but in this case I get
Block-scoped variable 'vCompositeFilter' used before its declaration.ts(2448)
I think you cannot declare recursive types with Convex validators atm.
Previously I've used this kind of pattern as a way to select individual entries in a union
by doing e.g.
FireviewFilterExpressionOperator.notIn
or v.literal(FireviewFilterExpressionOperator.notIn)
The only way I've found to do this with validators is vFirestoreFilterExpressionOperator.members[0]
- is this the recommended approach? It's a little error prone it seems, since it depends on the order of elements in the literal union...
maybe a better way to phrase my question is: how do you recommend doing the pick and omit operations that @ballingt shared but over elements of a union - e.g. a union of literals like:
any plans to support this? we'd ideally love to have all our types defined as validators and then just use Infer<typeof v...> when needed. This is especially the case now that return validators can be passed to functionsTS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
It would help to see a code sample of what you'd like — could be that I'm missing it or could be it's not possible but we can recommend something else.
No concrete plans to support recursive types, you may not be able to get full database validation if you need this. I'd write your own validation instead, and write wrappers to cast the data to your recursive type when you need to work with it; similar to converting a number to a Date object after you get it out of the database.
that's definitely better but it has limitations for selecting items in the middle efficiently and being immune to changes in the order of the literal union
once you've put them in the array it'll be hard to do as much with them, so I'd start with the subarrays you need and build up by concating
I'm trying to do this:
but it doesn't seem like the check for the layoutType narrows down the specific member of tableViews that contains the nested type I'm looking for, so I find myself having to do [0] and relying on not changing the order of things at any point
another small thing: how do I get the validator with system fields in here?
seems to work as a
returns
validator on queries that return the full document which is a little confusing to me
^ lmk if this would be the recommended way to check if a query returns a type of document or if there's an easier way - but v.doc() doesn't exist afaik
this is a concrete example of what I'm doing atm:
So if I need to pass an object of this type into a mutation should I just set the validator to v.any()?
To add system fields, you could spread in the fields, or use a
convex-helpers
validator utility like systemFields("firestoreFields")
or get all of the fields using withSystemFields("firestoreFields", fireviewSchema.tables.firestoreFields.validator.fields)
.
There's a utility I've started writing that would work like doc(schema, "firestoreFields")
but there's some limitations around it returning a generic validator type instead of specifically a VUnion
/ VObject
based on what kind you had in your schema. but happy to share the in-progress idea if you want to play with it
@sshader has also played with making a more powerful v
, let's call it vv
, that you could make like const vv = betterV(schema)
and do things like vv.doc("firestoreFields")
and a type-safe vv.id("onlyValidTableNamesHere")
This:
withSystemFields("firestoreFields", fireviewSchema.tables.firestoreFields.validator.fields)
doesn't work for this type since it's a union. I tried appending `.map((m) => m.fields) but that also didn't work for some reason, I'll stick with spreading for now but definitely keep me posted!
Recursive validators would especially be super helpful to have full coverage of our types
This is the closest I can get:
but this doesn't work sadlyTry this for now:
like
doc(schema, 'firestoreFields')
where schema
is imported from "./schema"
it doesn't give the full .members
introspection but the document typescript types and runtime validators should be right
I haven't tested it, and just caught a bug but hopefully it's a good start until I can circle back here with something more robust. Also lmk if you end up with something worth sharing!this is already super useful! Thanks 🫶
I do see these errors in the console:
It's surprising that the type has "false" for Validator's second param - it should be "required" or "optional" - is that from some code you have somewhere?
I wonder if you're using an older version of convex-helpers that's using the older convex package that had it as a boolean
ah it's because of an old version of the helpers, resolved!
Question: can I create recursive validators by creating a recursive zod type and then using
zodToConvex
?I don't think so (in the sense that it won't do the runtime validation recursively, that's not supported - it might pretend to give you a recursive type though)
hmm okay so if I want the recursive runtime validation I should probably use
zCustomQuery
or sth like that?
okay, when I do this:
vLocalFilterExpression
is never
so I guess I'll have to try with zCustomQuery
Okay zCustomQuery
also doesn't work...
How hard is it to support this? Seems like Zod supports doing zRecursiveType.parse()
so I can do it manually for now I guess?Yeah, you can definitely validate in JS.
One day we might add support for it in the Convex backend. We just need an equivalent to
z.lazy
that'll work across the boundary in Rust.That would be great, I'm sure this won't be the last recursive type we use for function interfaces / schema definition