-
-
Notifications
You must be signed in to change notification settings - Fork 201
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
Use interface
instead of type
for all Base types
#259
Use interface
instead of type
for all Base types
#259
Conversation
✅ Deploy Preview for valibot ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Please have a look at #257 (comment) |
Using some basic type predicate functions for base schemas, object schemas, and tuple schemas, I refactored the fallback and default methods which simplifies both their types and the logic for each method. Note that because each of these methods relied on a mutable return value typed as `any`, Typescript did not catch any logic errors when calculating default or fallback values. With the refactors, in some cases an `as` type assertion is necessary. For some of the method types, I reorganized them such that they no longer introduce a circular dependency (even if only at the type level).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some context for the refactors I did tonight. Let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both of these interfaces could be simplified because the Output<>
utility type would just extract the TOutput
type that the BaseSchema
was instantiated with.
// Otherwise, check if schema is of kind object or tuple | ||
// If it is an object schema, set object with default value of each entry | ||
if (isObjectSchema(schema)) { | ||
return Object.entries(schema.entries).reduce( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could stay a for loop instead of a reduce
. I did it this way out of habit. One thing to note is that Object.entries()
here is preferable because the in
operator can have unexpected results due to it including properties from an object's prototype chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If using Object.fromEntries
in the async version, why not use it here as well? Is it because of the performance cost of creating an extra array?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose it can be done with Object.fromEntries
here too, since in either case a type assertion is required. I implemented one after the other and solved for type errors along the way, so these were just the solutions I arrived at then. Will make this change when I resolve merge conflicts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's really necessary I can add a test for this type predicate function. It already has coverage thanks to the tests for the refactored methods.
library/src/utils/isNonNullable.ts
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a utility I use in my own libraries. I can copy over the unit test for this too after an initial review.
library/src/utils/isSchema.ts
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add an explicit unit test for this type predicate but I believe it is already well covered by the schema method tests.
It may be worth adding two more functions for Validations and Transforms as a utility for third-party library authors.
I will try to take a closer look at this PR this weekend and give you feedback afterwards. Please don't put too much work into this PR until it's clear how we're going to proceed. I deliberately chose types instead of interfaces initially because I had negative experiences with them in Modular Forms. |
In each case here, `TRest` can possibly be `undefined`, which means the resulting type intersection `(BaseSchema | BaseSchemaAsync) & TRest` where `TRest` is `undefined` causes the whole intersection to be `undefined`. To solve for this, we can instead compare `TRest` to `NonNullable<TRest>` to narrow it down to `BaseSchema | BaseSchemaAsync`.
This is a bugfix for an issue on main
I had a look at this PR and want to thank you for the work you put into it. In general I prefer to use |
Can you respond to my previous comment and explain all the advantages you see in |
Hey, I intend to get back to you this week. I was busy this past week studying for a Japanese language exam. Sorry for the delay! |
No problem, there's no rush. |
Okay, here are a few reasons why I think
Generally, the advice I've been seeing lately is, I couldn't find much supporting evidence for the performance impact of This article by the tRPC team was also an interesting read, and I recommend checking it out as got really buried in search results. Another insightful article pointed out that Unions |
Thank you for your detailed response. I will examine the code and make a decision by the end of the month. |
Short update: I wasn't able to review this PR last month. It's planned to do so in the coming weeks. |
Interfaces vs Types in TypeScript Declaration Merging: Extending: Interfaces: More suited for defining public API's of classes. Readability and Clarity: Interfaces: May be more readable and clear for object-oriented patterns. Performance Considerations Recommendations |
@ariskemper This is inaccurate, because we are talking about Authoring Time and CI Time performance (IDE tooltips, typechecking), in which there are differences between the two. This PR is about improving the developer experience, which includes perf improvements that affect how responsive their coding environment is. |
I'll give this PR an update pass this weekend to merge in the recent changes on main. |
…m/Saeris/valibot into feat/use-interface-for-base-types
b997a0f
to
6d0cde4
Compare
/** | ||
* Schema with maybe default type. | ||
*/ | ||
export interface SchemaWithMaybeDefault<TInput = any, TOutput = TInput> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this into getDefaults.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved these types because they created a circular dependency reference. IMO they are better kept in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does it create a circular dependency reference? I prefer to move it to getDefaults.ts
to be consistent with the rest of the source code. Types that are related to a particular function are placed above the function in the same file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because for example:
export type DefaultValue<
TSchema extends SchemaWithMaybeDefault | SchemaWithMaybeDefaultAsync,
>
in types.ts
would import from getDefaults.ts
, which itself imports DefaultValue
from types,ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will have a look at it. I don't think this is a problem but I could be wrong.
/** | ||
* Schema with maybe default async type. | ||
*/ | ||
export interface SchemaWithMaybeDefaultAsync<TInput = any, TOutput = TInput> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this into getDefaultsAsync.ts
/** | ||
* Schema with maybe fallback type. | ||
*/ | ||
export interface SchemaWithMaybeFallback<TInput = any, TOutput = TInput> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move that into getFallback.ts
.
/** | ||
* Schema with maybe fallback async type. | ||
*/ | ||
export interface SchemaWithMaybeFallbackAsync<TInput = any, TOutput = TInput> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move that into getFallbackAsync.ts
.
I will review this PR as well as #451 today and tomorrow and decide again if I want to change to |
Just merged in the latest from the main branch, will address your feedback and requested changes as soon as I have a chance. This latest commit adds the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few minor details we need to work out, but otherwise this PR seems ready to merge.
I will do a final review later and probably merge this PR. 🙌 |
c2570d6
to
de1d857
Compare
v0.30.0 is available |
let defaults: any; | ||
|
||
// If schema contains a default function, set its default value | ||
>(schema: TSchema): DefaultValues<TSchema> | undefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, as per the documentation for getDefaults
:
The difference to
getDefault
is that for objects and tuples without an explicit default value, this function recursively returns the default values of the subschemas instead ofundefined
.
Did that change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it should work just the same. All unit tests passed, so I do not expect any bugs, but maybe we are missing something. Please create an issue if you encounter any problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok thanks. It just broke the type when I upgraded so I was wondering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to share your code if you thing something is wrong on our side.
See #257 for background.
While this PR touches many files, the bulk of the incoming changes here simply convert type intersections to interface declarations. The intent here is to enable third-party developers to easily pattern match against
BaseSchema
/BaseSchemaAsync
when building type guards, or further refine types to specific schema subtypes such asNumberSchema
.In resolving type errors that came up as a result of these changes within Valibot's codebase, I also made some improvements to the implementations of some utility functions such as
getDefault
andgetFallback
. These functions and their related types were simplified while improving their own internal type safety.Please read the comments below for more context.