-
-
Notifications
You must be signed in to change notification settings - Fork 281
implement v.partialBy(schema, v.exactOptional)
and v.requiredBy(schema, v.nonNullish)
using HKTs
#1278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
|
eefbb0b
to
94ae254
Compare
v.partialBy(schema, v.exactOptional)
and v.requiredBy(schema, v.nonNullish)
using HKTs
Hey 👋 thanks for working on this! I would like to discuss the API before reviewing your code. What do you think about: const Schema1 = v.make(v.optional, MyObjectSchema);
const Schema2 = v.make(v.nullable, MyObjectSchema); It would be great if we could allow developers to pass default values (e.g. for |
yeah, I'm not sure how possible configuring the modifiers could be - I'll have a play around it could be that something like although i don't think it would be possible to infer separate default types for each schema cleanly maybe a mapped type like v.make(Schema, v.optional, { foo: fooDefault }) ? |
I like the function approach, and I think I know how to implement it correctly, if we limit the default value to |
I've tested the object approach and it works well, it allows each key to correctly infer its configuration specifically |
A drawback with the object approach for the default values might be that it does not work for Another API idea could be: const Schema = v.wrapObjectEntries(MyObjectSchema, {
name: v.nullable,
age: v.optional,
email: (schema) => v.nullish(schema, 'default_value'),
}); The problem with this API is that it doesn't work well with large object schemas. I'm not sure, but I think most developers are looking for an easy way to wrap all entries with the same schema. |
I am not sure yet about the name but here a few names we could combine with
|
Why not? The second argument would just be error messages instead of default values, since that's what they accept.
This is nice, but gets into diminishing returns vs manually modifying each key i feel |
Do you have a favorite API and name right now? |
no preferences on name i think the proposed modifier-per-key approach is nice for rare cases but clunky for more common cases the map-per-argument approach I've pushed works quite nicely for inferring output, but it has some downsides in terms of DX - because it's inferring the entire array of arguments you don't get the visibility of what they should be ahead of time |
wondering about an overloaded function, e.g. v.mapEntries(Schema, v.optional) // make all optional
v.mapEntries(Schema, {
name: v.nullable,
age: v.optional,
email: (schema) => v.nullish(schema, 'default_value'),
}) // manually specify each
// which would also allow the below (if we modified v.entriesFromList a little)
v.mapEntries(Schema, v.entriesFromList(["name", "age"], v.optional))
// maybe even allow
v.mapEntries(Schema, v.optional, { name: v.nullable }) // blanket with overrides
// i don't think this should be allowed though unfortunately
v.mapEntries(Schema, (schema) => v.optional(schema, 'default_value')) |
For official APIs, I try to keep them as simple as possible by limiting the options for achieving the same result. Having multiple overload signatures can make using the function correctly complicated. If we type the argument as |
right, but the point is that we can't properly infer from a user submitted "one fits all" mapper what each entry should look like. For example: const userSchema = v.object({
name: v.string(),
age: v.number()
})
const withDefaults = v.mapEntries(userSchema, (schema) => v.optional(schema, schema.type === "string" ? "John" : 36))
// worst case (no HKTs, just use mapper result):
withDefaults.entries;
// ^? Record<"name" | "age", OptionalSchema<StringSchema<undefined> | NumberSchema<undefined>, string | number>
// best case (mix HKTs and mapper result):
withDefaults.entries;
// ^? { name: OptionalSchema<StringSchema<undefined>, string | number>; age: OptionalSchema<NumberSchema<undefined>, string | number>; } We also can't guarantee that the default provided matches every schema properly, only at least one of them. For example: // no error
const withDefaults = v.mapEntries(userSchema, (schema) => v.optional(schema, "John")) |
That's correct. Perhaps we should focus on creating a simple API similar to |
sure, so just the |
basic demonstration/explanation of HKTs: