Skip to content
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

feat: More intuitive Object generics, faster types #1540

Merged
merged 10 commits into from
Dec 28, 2021
Merged

Conversation

jquense
Copy link
Owner

@jquense jquense commented Dec 28, 2021

Changes the object generics to store the derived type instead of the object shape. This imposes a few limitations on type accuracy at the edges, but dramatically speeds up the type processing by tsc on the command line and in editor. It also removes the need for the SchemaOf helper which worked...poorly.

Instead the ObjectSchema class accepts a plain type as its first generic:

interface Folder {
  id: ObjectId,
  label: string,
  files?: File[]
}

- const folder: SchemaOf<Folder, ObjectId | File> = object({
-   id: mixed<ObjectId>().defined(),
-   label: string().defined(), 
-   files: array(mixed<File>().defined()) 
- })
+ const folder: ObjectSchema<Folder> = object({ 
+  id: mixed<ObjectId>().defined(),
+  label: string().defined(), 
+  files: array(mixed<File>().defined())
+ })

It's a small diff, but big improvement in type accuracy and usability, especially with custom schema for class instances.

Note that the generics on the object() factory method are still the "object shape, meaning object<Folder>() won't work as expected. This is a compromise between the two strategies for handling generics and allows for an accurate type on object().getDefault()

A number of the improvements here are made possible by simplifications to yup's API and logic, this introduces a few breaking changes though most are small and easily migrated from.

Nullability and presence

This is the largest, and likely most disruptive change. Prior yup allowed for patterns like:

const nullableRequiredString = string().nullable().required()

nullableRequiredString.cast(null) // -> null

nullableRequiredString.validate(null) // ValidationError("this is required and cannot be null")

This may seem unintuitive behavior (and it is) but allowed for a common client side validation case, where we want to use a single schema to parse server data, as well as validate user input. In other words, a server might return invalid "default" values that should still fail when trying to submit.

Now, nullable(), defined and required are all mutually dependent methods. Meaning string().nullable().defined().required() produces a schema where the value must be a string, and not null or undefined. The effect of this is that the type of a cast() is now accurate and the same as the type returned from validate.

When signature

The following API has been simplified to be clearer, and easier to type

string().when('foo', {
  is: 'bar',
-  then: string().required()
+ then: schema => schema.required()
})

Concat behavior

Current concat doesn't work super well and will likely be removed as it's not very useful. It's primary use was supporting the now-defunct when() api above. More on this coming later

@jquense jquense changed the base branch from master to next December 28, 2021 16:36
@jquense jquense merged commit 912e0be into next Dec 28, 2021
@jquense jquense deleted the ts-round-1204235 branch December 28, 2021 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant