charlie
charlie6mo ago

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
charlie
charlieOP6mo ago
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:
import { vWithRecord } from './vWithRecord'
// ...
export const myType = v.object({
myField: (v as typeof vWithRecord).record(v.string(), myOtherType),
})
import { vWithRecord } from './vWithRecord'
// ...
export const myType = v.object({
myField: (v as typeof vWithRecord).record(v.string(), myOtherType),
})
Are there any risks with this approach?
ballingt
ballingt6mo ago
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.
charlie
charlieOP6mo ago
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.
ballingt
ballingt6mo ago
@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)
charlie
charlieOP6mo ago
Ah interesting! I was wondering what the problematic edge cases were. Makes sense My use case is just that I use a lot of Maps 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>
ballingt
ballingt6mo ago
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.
charlie
charlieOP6mo ago
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 anyway
ballingt
ballingt6mo ago
I 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
charlie
charlieOP6mo ago
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