v.record
Hi Convex team,
Is there a release date for
v.record()
? I haven't upgraded Convex since 1.2.1 because I rely on a v.record()
type (that unfortunately my desktop app is also now relying on) but really need to upgrade.
Or, is there a way I can use the internal/unofficial version (https://github.com/get-convex/convex-backend/blob/main/npm-packages/convex/src/values/validator.ts#L128-L144) ?9 Replies
Current hacked-together solution:
Add a file
convex/vWithRecord.ts
(see attached file -- I just copied the export declare const v
block from validator.d.ts
and uncommented the line for v.record
)
Then in schema.ts
:
Are there any risks with this approach?The risk would just be v.record() is released with different semantics. The one you've copied has different behavior based on whether the value is optional, and that's the top thing that might change.
Gotcha, thanks Tom! I suppose I was already taking that risk by staying on 1.2.1 and using the
v.record()
from that version.@charlie I think it's a good bet that a
v.record(v.string(), v.number())
will work as you'd expect (same as Record<string, number>
in TypeScript, obviously not every possible string has to be present in the object so all keys are optional at runtime), the behavior around edge cases is why it keeps getting pushed. Helpful to hear you want this, we might be able to do it soon.
edge cases like v.record(v.union(v.literal('a'), v.literal('b')), v.optional(v.number()))
; are the keys optional (they aren't in TypeScript Record<'a' | 'b', number>) and what does a value of v.optional(v.number())
even mean? (probably that's not allowed, that makes no sense)Ah interesting! I was wondering what the problematic edge cases were. Makes sense
My use case is just that I use a lot of
Map
s and POJOs in my app, and those values need to be strongly typed. The biggest example: this is a calendar app and there's a field events: Record<string, CalEvent>
where CalEvent
really needs to be typechecked. So using v.any()
and .fromEntries
as suggested is problematic
I actually don't care about validating the keys -- in my particular case I'm always doing Record<string, CalEvent>
or Record<string, number>
yeah most cases I use are Record<string, ?> or number too
The only possible thing then is if you've made the calEvent validator optional somehow; that might stop working. It doesn't make sense, "optional" means a key might not exist in an object.
Although I haven't thought it through, my first instinct is I'd rather have a Record validator that just throws when passed a
v.optional()
, vs. no Record validator at all
Alternately... maybe v.optional()
in that situation is actually just a no-op?
For infinite-valued key types like v.string()
For finite-valued key types, maybe an optional value just gets inferred as a POJO and a non-optional value as a proper TS Record ?
v.record(v.union(v.literal('a'), v.literal('b')), v.number())
=> Record<'a'|'b', number>
v.record(v.union(v.literal('a'), v.literal('b')), v.optional(v.number()))
=> {[inferredKey: 'a' | 'b']: number}
Just brainstorming
The feature would be super useful, even if that edge case threw or had a weird resolution -- it's not like anyone is super likely to need v.record(?, v.optional(?))
since it doesn't have a direct analogue in TS anywayI believe we have answers to all of these, just not sure what they are
I think throwing is the plan, since an optional value doesn't make sense
For finite-valued key types, maybe an optional value just gets inferred as a POJO and a non-optional value as a proper TS Record ?That's what a prev version of this did but I think it is not what we are doing, but I'm not up to date Totally, don't worry it's coming! Just prioritization
FWIW the commented-out type in
validators.d.ts
works as you outlined above
Of course, you guys have been shipping amazing work! Just wanted to bring some more attention to it, as it is an important primitive