ampp
ampp9mo ago

looping through a v.union of v.literals

We have a huge permission list that is a v.union full of v.literal's. We don't want to duplicate and maintain the list in the db init file if possible. And map/foreach doesn't work on a validator, is there any tricks to this.
29 Replies
ian
ian9mo ago
Argument Validation without Repetition
In the first post in this series, the Types and Validators cookbook,, we shared several basic patterns & best practices for reusing types & validators...
erquhart
erquhart9mo ago
I don't think there's an approach that avoids duplication. Colocating might help. The compiler will yell at you if the init value doesn't match the validator, so there shouldn't be much concern for drift between the two.
ian
ian9mo ago
You might like the helper I made called literals which takes …args of string and returns the union validator. So you could have an array of the strings, and spread them into literals for validation.
ampp
amppOP9mo ago
Yeah I'm interested in that, how do i access it?
ian
ian9mo ago
npm
convex-helpers
A collection of useful code to complement the official convex package.. Latest version: 0.1.32, last published: 4 hours 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.
ampp
amppOP9mo ago
Yeah i suppose i could move the insertMany array into the other file for colocating if that is what you mean. We have 3 different permission systems with similar roles but unique permissions, and users can have multiple roles within each system so a simple duplication turns into 3 pairs. so it should be fine with convex ents right, i haven't been looking as much into the helpers as im not exactly sure what it would be like to blend this all together yet.
ian
ian9mo ago
Yeah I think most helpers can work with Ents. Ents even rely on customFunction and such to work. The only helpers that come to mind that aren't as easy would be relationship helpers that take in a DatabaseReader / etc. But Ents makes all of that easy without those helpers. If you find anything that doesn't play nicely, let me know!
ampp
amppOP9mo ago
I cant figure out how to put a array/list or whatever into a literals() other then a manually written list. I may have a knowledge gap but I'm wanting to get the keys from a object and generate a validator of field names and have tried a few different things. So far the only thing that works is: const arr = ["test", "c121", "c1212"] as const; export const vOptionKeys = literals(...arr); I have a object export const OptionsObj = { isVisible: v.boolean(), ... large list, i want to just do Object.keys(OptionsObj) and construct a validator. Which doesnt seem possible.
erquhart
erquhart9mo ago
I can't find a way to do this that doesn't involve declaring the keys as an array of literals. I'm no typescript wizard, so maybe there's a way, but I believe a literal type requires a literal value for static analysis.
ian
ian9mo ago
If they’re all v.boolean() maybe you could start with the list, then make an object of them from that. For types of an object you might need to do some type gymnastics const keys: Array<keyof typeof myObject> = […myObject.keys()] On my phone so haven’t played around with it
Michal Srb
Michal Srb9mo ago
Documentation - Mapped Types
Generating types by re-using an existing type.
ian
ian9mo ago
Here's a way to do it without literals @ampp :
const keys = [...Object.keys(o)] as Array<keyof typeof o>;
const keyLiterals = keys.map(v.literal);
const keyValidators = v.union(keyLiterals[0], keyLiterals[1], ...keyLiterals.slice(2));
const keys = [...Object.keys(o)] as Array<keyof typeof o>;
const keyLiterals = keys.map(v.literal);
const keyValidators = v.union(keyLiterals[0], keyLiterals[1], ...keyLiterals.slice(2));
erquhart
erquhart9mo ago
I get this:
Argument of type 'Validator<"a" | "b" | "c", false, never> | undefined' is not assignable to parameter of type 'Validator<any, false, any>'.
Type 'undefined' is not assignable to type 'Validator<any, false, any>'.ts(2345)
Argument of type 'Validator<"a" | "b" | "c", false, never> | undefined' is not assignable to parameter of type 'Validator<any, false, any>'.
Type 'undefined' is not assignable to type 'Validator<any, false, any>'.ts(2345)
erquhart
erquhart9mo ago
No description
erquhart
erquhart9mo ago
oh wait mine is different than yours ah, my editor is making changes on save, but it doesn't seem to make a difference in the Typescript output
ampp
amppOP9mo ago
yeah this seems to work: const o = { a: v.boolean(), b: v.boolean(), c: v.boolean() }; const keys = [...Object.keys(o)] as Array<keyof typeof o>; const keyLiterals = keys.map(v.literal); const keyValidators = v.union(keyLiterals[0], keyLiterals[1], ...keyLiterals.slice (2)); the only downside is i have to go out to 20 in the validators union const keyValidators = v.union(...keyLiterals) would be very elegant if something like that was possible. maybe mapping to strings and using the convex-helpers literals() chatgpt thinks this should work... const keyValidators = v.union(...keyLiterals.slice(0, 2)); but it doesn't also suggesting just ...keyLiterals, but these days i expect only 30% helpfulness
ian
ian9mo ago
you shouldn't have to go out to 20, the ...keyLiterals.slice(2) will do the rest. This is just a shortcoming of v.union which has two explilcit params to prevent you from doing a union of size one: v.union(v.null()) which is meaningless The [0], [1], ...[2:] pattern should work for any number
erquhart
erquhart9mo ago
@ampp are you saying that last line works with the slice, or it works but you have to put each individual keyLiterals[n] into v.union()? sounds like the latter, just confirming what you meant when you said it seems to work
ampp
amppOP9mo ago
im doing v.union( keyLiterals[0], keyLiterals[1], keyLiterals[2], keyLiterals[3], keyLiterals[4], keyLiterals[5], keyLiterals[6], keyLiterals[7], keyLiterals[8], keyLiterals[9], keyLiterals[10], keyLiterals[11], keyLiterals[12], keyLiterals[13], keyLiterals[14], keyLiterals[15], keyLiterals[16], keyLiterals[17], keyLiterals[18], keyLiterals[19] ); right now
erquhart
erquhart9mo ago
okay so spread is not working for you same for me @ian I shared what I'm seeing above, curious how this is working for you
ian
ian9mo ago
I'm not sure what's different for you, but when I do v.union(keyLiterals[0], keyLiterals[1], ...keyLiterals.slice(2)) it doesn't matter how many object keys there are.
No description
ian
ian9mo ago
The only difference I see is doing Array<keyof typeof o> instead of (keyof typeof o)[] which I suppose has the return type of | undefined for array access
import { Infer, v } from "convex/values";

const o = { a: v.boolean(), b: v.boolean(), c: v.boolean(), d: v.boolean(), e: v.string() };
const keys = [...Object.keys(o)] as Array<keyof typeof o>;
const keyLiterals = keys.map(v.literal);
const keyValidators = v.union(keyLiterals[0], keyLiterals[1], ...keyLiterals.slice (2));
type keyTypes = Infer<typeof keyValidators>;
import { Infer, v } from "convex/values";

const o = { a: v.boolean(), b: v.boolean(), c: v.boolean(), d: v.boolean(), e: v.string() };
const keys = [...Object.keys(o)] as Array<keyof typeof o>;
const keyLiterals = keys.map(v.literal);
const keyValidators = v.union(keyLiterals[0], keyLiterals[1], ...keyLiterals.slice (2));
type keyTypes = Infer<typeof keyValidators>;
erquhart
erquhart9mo ago
Weird, not sure why my editor is so certain that Array annotation is superfluous. But I do get the same typescript output either way. I'll double check that I'm not missing something tomorrow, weird that we're getting different results.
ampp
amppOP9mo ago
oh that does work, its possible i probably tried it like const keyValidators = v.union(keyLiterals[0] ...keyLiterals.slice (1)); because i thought union took a minimum of two arguments, but ... must not count. Although i do remember trying it both ways at various points. and isn't helping that randomly i get typescript delays in the minutes.
ian
ian9mo ago
Maybe typescript version is at play here too? I'm on 5.4 I just published convex-helpers@0.1.34-alpha.5 that accepts literals(...keyLiterals) @ampp @erquhart - can you check that it works for you? If so, I'll publish it in 0.1.35
erquhart
erquhart9mo ago
Didn't actually try to use it as a validator, but Typescript is happy:
const o = {
a: v.boolean(),
b: v.boolean(),
c: v.boolean(),
d: v.boolean(),
e: v.boolean(),
}
const keys = [...Object.keys(o)] as (keyof typeof o)[]
const validator = literals(...keys)
const o = {
a: v.boolean(),
b: v.boolean(),
c: v.boolean(),
d: v.boolean(),
e: v.boolean(),
}
const keys = [...Object.keys(o)] as (keyof typeof o)[]
const validator = literals(...keys)
No description
ampp
amppOP9mo ago
I'm using it in several contexts with success so far 🙂
ian
ian9mo ago
Great! It went out in 0.1.34 after I did some validation too
Depender Sethi
Depender Sethi5mo ago
Working great! Thanks @ian
No description

Did you find this page helpful?