badrap / valita Goto Github PK
View Code? Open in Web Editor NEWA typesafe validation & parsing library for TypeScript.
License: MIT License
A typesafe validation & parsing library for TypeScript.
License: MIT License
One thing I would like to do is create a generic parsing method for network requests. It would take in shape and return Infer
. Currently it doesn't work fully.
const fetchAndValidate = async <A extends Type>(url: string, responseShape: A): Promise<Infer<A>> => {
const result = await fetch(url);
return responseShape.parse(result); // Type 'unknown' is not assignable to type 'SomethingOutputOf<A>'
}
Am I doing something wrong or there is something off with types?
This is a feature request. I have some code that looks like this:
import * as v from '@badrap/valita';
// setup
const externalSymbol = Symbol('external') as any;
const isExternal = (v: unknown) => v === externalSymbol;
const external = <T>(t: v.Type<T>): v.Type<T> =>
t.default(externalSymbol)
.assert(isExternal, 'field must be empty');
// child type includes fields that are constructed by another parser
// so they must not get values from the input, but must be expected on the type
type Child = v.Infer<typeof Child>;
const Child = v.object({
name: external(v.string()),
color: v.string(),
});
// parent includes the mapping that assigns children their name
type Parent = v.Infer<typeof Parent>
const Parent = v.object({children: v.record(Child)})
.map(parent => {
const children = Object.entries(parent.children);
for (const [childname, child] of children) {
child.name = childname;
}
return parent;
});
// results
// after parsing, example's children know their own names
const okExample = Parent.parse({
children: {
john: {color: 'mauve'},
frank: {color: 'red'},
}
});
console.log(okExample.children.john.name);
// will not parse, child defines external field
const badExample = Parent.parse({
children: {
john: {color: 'mauve', name: 'lucas'},
frank: {color: 'red'},
}
});
Parent
is responsible for completing the construction of Child
(because it has the necessary information). To facilitate this Child
provides type information for the field, but also marks it as external
, this way if the input tries to provide a value it will result in a failure to parse.
The feature request is to add the type modifier .external()
to types to express this use case as follows:
type Child = v.Infer<typeof Child>;
const Child = v.object({
name: v.string().external(),
color: v.string(),
});
Bonus points if forgetting to assign them creates a parse error later!
I might have a Typescript type like this:
type T = string | T[]
But if I try to define this type in valita I cannot pass the identifier to its own instantiator:
const T = v.union(v.string(), v.array(T)); // error: identifier used before it is defined
I mulled over this a bit, but I can't think of a smart way to implement it that doesn't involve somehow the union after it is instantiated, and then having to trick typescript into giving it a different type...
I'm very excited about this library. It has speed and everything I need except tuple
:/
Any plans to add it?
Thanks!
I found a multiple cases where i do
const a = v.union(v.literal("started"));
const b = v.union(v.literal("completed"));
const schema = v.union(...a.options, ...b.options);
So, can we add intersect
(or, maybe with better name), to UnionType, like we did it for ObjectType before (.extend)?
In my TSConfig I set declaration
to true, and then I started getting TS4023 Errors for almost every exported type.
It appears that there is some issue with the fact that ObjectType
is not an export
, and so typescript cannot create declarations for instances that are defined with it.
import * as v from '@badrap/valita'
export const T = v.object({a: v.string()}); // TS4023
This is my tsconfig.json:
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "node",
"outDir": "dist",
"strict": true,
"noImplicitAny": true,
"declaration": true,
},
"include": ["src/**/*"]
}
A quick vanilla example:
type T1<F> = {field: F}
type T2 = {label: 'stringy'}
type X = T1<string> & T2
I want to do this in valita... I expected this to work:
import {Type, Infer, object, string, literal} from '@badrap/valita';
const T1 = <FT extends Type>(F: FT) => object({field: F});
const T1s = T1(string());
const T2 = object({label: literal('stringy')});
const X = T1s.chain(T2.parse);
type X = Infer<typeof T1s> & Infer<typeof T2>;
(Note: in this example the X
type is correctly inferred)
but tsc doesn't accept .chain(T2.parse)
maybe there should be a .isect(T2)
that just accepts the type?
My concern is that anything can be put in it and the value will not be validated according to the given rules.
What is it for and is it needed now?
After writing some mini libraries, I realized that we need to export the classes so we don't have to use type assetions on every line.
// import hierarhy that i suggest
import * as v from '@badrap/valita' // like right now
import * as valitaLib from '@badrap/valita/lib' // export classes for library authors here
I currently have some HTTP request functions with a signature similar to this:
private async _request<TSchema extends v.ObjectType>(
requestPath: string,
schema: TSchema,
): Promise<v.Infer<TSchema>> {
This is really nice, because I can do generic validation of a JSON response body. However, it only works with object types, not with union types.
The type that v.Infer
requires is T extends AbstractType
. However, I cannot write that into my function signature because AbstractType
isn't exported. Would it be possible to export AbstractType
?
is there a way to convert a Valita type instance into a sendable type?
e.g.
const T = v.union(v.string(), v.number());
const sendT = T.sendable();
// this represents sending the type
const sentT = JSON.parse(JSON.stringify(sendT));
const U = v.fromSendable(sentT)
const u = U.parse(4);
Zod had .extend()
method on most types.
I can use v.object({ ...BaseType, <Wriet my stuff>})
It works fine in all cases except when I have v.object({}).assert(...)
. Then I can't get shape out of it. I guess even if I would get it, I would love my assert. Any ideas on how I could improve this?
Is it possible to do validations on the value, which is not part of the type information (e.g. only allowing numbers 0-255, or only strings matching a RegEx)? Or is that out of scope?
There seems to be code to validate the length of arrays, but I think right now this is only used for tuples, which are covered by TypeScript's type system.
const testData = {};
const testSchema = v.object({
test: v.string(),
test2: v.string()
});
testSchema.parse(testData);
The error only speaks of one problem but actually two fields are missing:
ValitaError: missing_key at . (missing key \"test\")
One more:
I have a function that accepts a schema in the library I'm migrating away from and integrates it with yargs. To do that, it examines the expected input type of the schema. I see that Valita does expose a name
property on its schema objects, but that property winds up being useless if you use anything but the bare types exposed by valita (chain, assert, optional, etc...).
It would be nice to be able to associate some user-supplied context with a type, or at least allow .chain an argument to set the name
of a type. Preferably, the orthogonal concerns of "what it is" and "how to treat it" (optional string) are both exposed, and while I'm not eager to tack something like .annotate({type: 'string', optional: true})
everywhere, it does make a simple building block with which more robust behavior can be built.
In general, introspection behavior would be helpful (the ability to traverse and understand the input requirements of a type at runtime)
Example:
try {
type P = {p?: P};
const P: v.Type<P> = v.object({
p: v.lazy(() => P).optional(),
})
P.parse({p: {}});
} catch (e) {
console.error(e);
}
This causes a RangeError: Maximum call stack size exceeded
It gets stuck in an infinite recursion calling genFunc()
on itself
It would be nice to have a way to mark types (array/tuples/objects/records) as readonly.
const schemaX = valita.object({
x: valita.number(),
});
type T = valita.Infer<typeof schemaX>;
// {x: number}
I can use the TS Readonly
type but it gets inconvenient when you have nested types.
type ReadonlyT = Readonly<valita.Infer<typeof schemaX>>;
// {readonly x: number}
const schemaY = valita.object({
y: schemaX,
});
type T2 = valita.Infer<typeof schemaY>;
// {y: {x: number}}
type ReadonlyT2 = Readonly<valita.Infer<typeof schema>>;
// {readonly y: {x: number}}
I know I can use DeepReadonly
type combinators but those tends to fail pretty fast for me because my types are too deep.
I guess one problem with adding a way to flag things as readonly is that it is not clear if we also need to have a runtime check for readonly-ness?
I'm not sure if this can be done with the existing APIs but I want to express something like the following TypeScript type:
type T = {
x: number;
[key: string]: string | number;
}
The best I could come up with is:
const schema = valita
.record(valita.union(valita.string(), valita.number()))
.chain(v =>
valita.object({x: valita.number()}).try(v, {mode: 'passthrough'}),
);
This could be useful for allowing undefined
instead of explicitly specifying v.union(v.undefined)
.
Similarly, when using .shape
I can't use .shape.propName.try(...)
.
I've noticed that the API is reaaaally defensive. I expect this is an intentional choice, but it makes some things quite difficult. The workaround mentioned in #26 is okay (in contrast with extending Type
) for creating new types, but I just hit a problem that I don't think I can get around without access to the actual ObjectType
class.
What I'm trying to do is validate "as much of the data as I currently have" against a subset of the schema I've constructed. ObjectType includes useful class functions (partial, omit, pick, etc...) -- but since I'm pulling the subset from a generic, I can't access them. I also can't use instanceof
to narrow the type so that TypeScript will allow me to call .partial on the schema.
Concrete details follow, but you needn't read further if you agree that it's useful to be able to write:
const unknownSchema: unknown;
if (unknownSchema instanceof v.ObjectType) {
unknownSchema.partial();
}
The setup this time is a hierarchical config schema, where the top-level keys are the config necessary for some component, for example:
const schema = v.object({
common: v.object({
configFile: v.string().default('config.json')
}),
db: v.object({ ... }),
web: v.object({ ... }),
})
I'm using the reasonably common pattern of a default config file location, with the ability to specify an alternate config file to use via environment variables / cli args. The path to the config file is in common
as shown above. So, the application logic is something like this:
At the stage where I'm loading the file, I want to retrieve the contents of only the common key, and force all the values to be optional, so that validation does not fail if some are missing. However, at final config validation, they should not be optional, and the final validated config should be as tight as possible.
I'm doing this essentially by:
schema['shape']['common'].parse(configdata['common'])
... which guarantees me the shape and types of any values that do exist, but requires me to write extra-defensive code. This is fine for this stage, but not desirable all the time once the app is fully bootstrapped.
Hopefully you can agree that this is a worthwhile use-case for exposing the instances (not just the types) of some of the basic tools here (if not the AbstractType class, or the Type class, then at least the leaf nodes -- ObjectType, ArrayType, etc.). Thoughts?
(p.s. sorry for the sudden influx of issues. This library is very useful, which is why I've spent all day attempting a migration to see if it will suit :) it is very close...)
Hi there,
We really wanted to use your lib over Zod (for performance reasons), but this is the only feature that we miss here on Valita :
const stringToNumber = z.string().transform((val) => val.length);
type input = z.input<typeof stringToNumber>; // string
type output = z.output<typeof stringToNumber>; // number
Is there any way to achieve this with Valita or is this feature planned ?
Regards
Consider:
v.unknown().map(u => {throw new Error('oops')}).try(1);
Currently the exception bubbles through, but it should be caught by map
and wrapped into the ValitaError
Report refers to this diff:
The old version used .map(...)
to convert a byte array to a custom type. If validation fails, an Error
is thrown.
The new version uses .chain(...)
.
With map, the error message on validation failure looked like this:
Error: Could not validate EdToNd message: Error: Expected key to be 32 bytes but has 0 bytes
With chain, it looks like this:
Error: Could not validate EdToNd message: ValitaError: custom_error at .essentialData.groups.0.group.profilePicture.updated.blob.key (Expected 32-byte blob key, but found 0 bytes) (+ 2 other issues)
The error message for chain is much better, because it shows the source of the validation error in a complex object. With map, only the exception message itself is reported.
Is there a technical reason why this is the case? Or could exceptions thrown by map functions be caught and handled in a way similar to chain?
Today:
const T = v.union(v.literal('a'), v.literal('b'), v.literal('c'));
Tomorrow:
const T = v.literal('a', 'b', 'c');
Hi guys, is there any interest to support deno?
I am already using valita
with Deno and it is completely compatible. The only issue is that I have to use a bundled version and import it from a CDN like https://cdn.jsdelivr.net/gh/badrap/valita@d5d0125c467fb307addb9cc2badd098bfc66b834/src/index.ts .
Maybe you could implement webhooks pointing to the index.ts
file which would publish the new releases on https://deno.land/x. automatically You can find more about it on Deno's website here.
Thanks!
This could be a nice addition to objects. There are cases when we need to take only a part of the object.
cosnt TestObject = v.object({
id: v.string(),
someValue: v.string().asserts(...)
});
const input = TestObject.pick(['someValue']);
What are your thoughts on this?
It is a nice how chain
and try
works together and you can create nice combinations
import * as v from '@badrap/valita';
const s1 = v.object({
x: v.number(),
});
const s2 = v.object({
y: v.number(),
});
const s3 = s1.chain(value => s2.try(value));
console.log(s3.parse({x: 1, y: 2})); // throws
console.log(s3.parse({x: 1, y: 2}, {mode: 'passthrough'})); // also throws
I think that chain
should take the options
so that the last line above could be made to work.
I use a lot of Uint8Array
values in my objects. Is it possible to validate those?
Here's a reproducer:
import * as v from "@badrap/valita";
const BytesHolder = v.object({
fourBytes: v.array(v.number()).assert((val) => val.length === 4)
});
console.log('Primitive array');
BytesHolder.parse({
fourBytes: [1, 2, 3, 4],
});
console.log('Typed array');
BytesHolder.parse({
fourBytes: new Uint8Array([1, 2, 3, 4]),
});
The second call fails.
Use case: We are using Zod as the primary parser, but would like the option to switch to Valita when validating results.
https://github.com/gajus/slonik#user-content-result-parser-interceptor
Effectively, seeking for a way to convert Zod parser to Valita parser in that interceptor.
The following optimization to use {__proto__: null}
as the [[Prototype]]
of cloned objects
Lines 568 to 578 in 5db630e
causes, Firebase/Firestore to fail its check for "plain objects".
Could we remove the protoless
optimization and use null
as the [[Prototype]]
?
// Using Object.create(protoless) instead of Object.create(null) seems to
// be faster on V8 at the time of writing this (2023-08-07).
Do you have a perf test for this? Is there a V8 tracking bug?
I noticed that it's too easy to make an error when working with .check(...)
. What about a runtime warning or something like this when a developer tries to chain schema with .check(...)
?
or... maybe disallow this chaining like you already do it with .shape
prop in case of .assert()
?
Hi there. I'm trying this library out and quite pleased with it -- particularly the flexibility to define parsing and validation behavior. This is hampered somewhat by a couple things, which I'm filing as separate issues to keep them discreet. The first of them is:
export const foo = (val: string) => {
if (val === 'oh noes') {
return v.err('sad');
}
return v.ok(v);
};
Since the return value of v.err
includes private members, TypeScript fails when doing something like this.
Example usage:
import { foo } from './common.js';
const MyObj = v.object({
foo: v.string().chain(foo)
});
It's okay if you define it in the same file as your schema, but you can't export the helper function. I'm not sure if other things (e.g. CustomError
) would also need to be exported.
Zod and MyZod have many helper methods .int()
.max(10)
.pattern()
.safeParse()
etc.
How do you look at these?
Currently it is possible to solve this all with .assert()
For some reason the error message in Firefox is undefined
instead of showing the path to the key where the validation error occurred. This only happens when the error is logged to console. If we inspect the error object manually we can see that all properties are present as they should be.
Reproduction: https://i6nu3.csb.app/
Is there a way to get fields in the inferred type to be marked as readonly?
I'm thinking of something like this:
export const SCHEMA = v.object({
regular_field: v.string(),
optional_field: v.string().optional(),
readonly_field: v.string().readonly(),
});
export type InferredType = v.Infer<typeof SCHEMA>;
..resulting in:
type InferredType = {
optional_field?: string | undefined;
regular_field: string;
readonly readonly_field: string;
}
This is partly a question, and partly a feature request.
Usually when TypeScript sees a type that matches something you've given a name, it displays that thing's type as the name. Sometimes that's not what I want, and I have to work around it, like so:
type User = {
name: string,
id: number,
}
type Post = {
user: User,
date: Date,
}
const post: Post = {
user: { name: 'myndzi', id: 1 },
date: new Date()
}
type Simplify<T extends {}> = {[K in keyof T]: T[K]} & unknown;
type simplified = Simplify<typeof post>;
// type simplified = {
// user: User;
// date: Date;
// }
In Valita, this seems to be the default (only?) behavior of t.Infer
:
import * as v from '@badrap/valita';
const User = v.object({
name: v.string(),
id: v.number(),
});
const Post = v.object({
user: User,
date: v.string()
});
type post = v.Infer<typeof Post>;
// type post = {
// user: {
// name: string;
// id: number;
// };
// date: string;
// };
Unfortunately, this means that if you include even a single field that is moderately complex, the editor unrolls the full thing, making the "high level view" of the type in question useless -- since it eliminates most of the direct keys in favor of showing the full deep structure instead (the contents of user
above).
It would be nice if t.Infer
did not do this, but I'm not entirely sure where the behavior is coming from. Is Valita explicitly using something like the Simplify
trick above? If so, would you be open to not doing that? (It's easy to turn a "collapsed" object into an "expanded" object, but not the other way around).
An alternative might be to export AbstractType
and allow usage something like:
type User = v.Infer<typeof User>;
const Post = v.object({
user: User as AbstractType<User>,
date: v.string()
});
... or else, something with chain, assert, etc. that lets the user specify a type alias for a complex type.
The desired behavior using the above (trivial) example would be for the type of Post to be shown as:
type post = {
user: User,
date: string
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.