Replies: 6 comments 5 replies
-
Thanks for sharing your ideas. @Demivan suggested a different, but kind of similar API to me last week. I will try to investigate both proposals this week. |
Beta Was this translation helpful? Give feedback.
-
Here are some other methods of cascading calling: Built-in cascading method in schemaconst Giraffe = object({
name: string()
.to(coerce, String)
.to(optional, 'Skyler')
.to(fallback, 'unnamed')
.to(brand, 'Giraffe')
birthday: date()
.to(coerce, Date)
.to(nullable, it)
hobbies: array(string()).to(optional),
}) This writing style provides the best readability and DX:
To implement this, we need to add a Using pipeline operatorconst Giraffe = object({
name: string()
|> coerce(%, String)
|> optional(%, 'Skyler')
|> fallback(%, 'unnamed')
|> brand(%, 'Giraffe')
birthday: date()
|> coerce(%, Date)
|> nullable(%, it)
hobbies: array(string()) |> optional(%),
}) This approach uses the yet-to-be-implemented pipeline operator syntax. This provides great readability. Compared to the |
Beta Was this translation helpful? Give feedback.
-
I assume this API design would require a complete rewrite of the library. How would you implement it? How would you make it typesafe? Are you interested in writing a small prof of concept for |
Beta Was this translation helpful? Give feedback.
-
Thanks to valibot's good API design, most APIs work perfectly with cascading calling, here is a small prof: The only exception here is I also recommend putting the |
Beta Was this translation helpful? Give feedback.
-
@xcfox What do you think about API like this? const Giraffe = object({
name: pipe(string(),
coerce(String),
optional('Skyler'),
fallback('unnamed'),
brand('Giraffe')),
birthday: pipe(date(),
coerce(Date),
nullable()),
hobbies: pipe(array(string()), optional()),
}) I think it is a little bit less verbose. Really similar to your pipeline operator example, but implemented as a function. |
Beta Was this translation helpful? Give feedback.
-
Both API proposals solve function nesting in many cases. I like that! @xcfox where would you put pipe actions like @Demivan proposal makes Valibot even more modular, which reduces the bundle size in some cases and allows us to list everything in a logical order inside // Multiple nested pipes
const NameSchema = v.pipe(
v.optional(v.pipe(v.string(), v.toTrimmed(), v.minLength(1)), 'Skyler'),
v.brand('Name')
);
// Current Valibot API
const NameSchema = v.brand(
v.optional(v.string([v.toTrimmed(), v.minLength(1)]), 'Skyler'),
'Name'
); // Transform string or number to number
const NumberSchema = v.pipe(
v.union([v.pipe(v.string(), v.decimal()), v.pipe(v.number(), v.integer())]),
v.transform(Number)
);
// Current Valibot API
const UnionSchema = v.transform(
v.union([v.string([v.decimal()]), v.number([v.integer()])]),
Number
); // Simple login schema
const LoginSchema = v.object({
email: v.pipe(v.string(), v.minLength(1), v.email()),
password: v.pipe(v.string(), v.minLength(8)),
});
// Current Valibot API
const LoginSchema = v.object({
email: v.string([v.minLength(1), v.email()]),
password: v.string([v.minLength(8)]),
}); Another cool thing is that const DateSchema = v.pipe(
v.union([v.string(), v.number(), v.date()]),
v.transform((input) => new Date(input)),
v.date(),
v.minValue(new Date('2000-01-01')),
v.brand('Birthday'),
v.description('Text...')
); I am very grateful for your further ideas and feedback! With currently more than 80K weekly downloads, it is a big decision for me at this point to change the current API, but I will do it if I believe it will benefit most users in the long run. |
Beta Was this translation helpful? Give feedback.
-
What's the problem?
valibot currently only uses a nested methods, which is a little less readable than the chained calls of yup and zod.
Let's define a complex schema:
The valibot version is less readable for two reasons:
It's harder to see the type of each field:
zod
is close to the field name and at the beginning of the statement.valibot
hidesstring()
in the middle of a long sentence.It's harder to understand the meaning of each argument:
zod
uses chained calls, each argument is close to the method it applies to.valibot
uses nested calls, arguments and method names are far apart. For example, it takes a bit of thinking to realize that"Skyler"
is the default value, not the fallback value.What is the solution?
We can add utils for cascading calling to optimize readability:
using chained calls
using functional calls
This can also be used to add metadata
Cascading calls makes it easy to modify the schema, so we don't have to go through the trouble of implementing the metadata. We just need to add various little methods like
fallback()
:Beta Was this translation helpful? Give feedback.
All reactions