typeofweb / schema Goto Github PK
View Code? Open in Web Editor NEW@typeofweb/schema: Lightweight validator with 100% TypeScript support and sane coercion rules.
Home Page: https://schema.typeofweb.com
License: MIT License
@typeofweb/schema: Lightweight validator with 100% TypeScript support and sane coercion rules.
Home Page: https://schema.typeofweb.com
License: MIT License
The following declaration in the package.json
is invalid.
{
"sideEffects": false
}
The @typeofweb/schema
library does have side effects and cannot be tree shaked (dead code removed) when not used but other variables are imported from the same module containing variables from @typeofweb/schema
.
If you use:
You would see @typeofweb/schema
is identified as having a side effect here.
Class ValidationError
shouldn't have side effects
const atLeast2 = validate(minLength(2)(number()));
const notOk = atLeast2(1) // <- Error!
const ok = atLeast2(10) // <- {foo:10}!
This is just my suggestion, it can be rejected if not necessary
Hi ;)
pipe
schema validation is executed in reversed order than expected.
const schema = pipe(string, minStringLength(10));
First minStringLength
will be validated instead of string
and if you try to validate e.g. undefined
the type error will be thrown. See https://codesandbox.io/s/adoring-galileo-76vtn?file=/src/index.ts
import * as s from "@typeofweb/schema"
const a = s.object({
key1: s.optional(s.string()),
key2: s.optional(s.string()),
})()
const validatorA = s.validate(a)
const value = validatorA({
key1: "hello"
})
console.log(value) // { key1: "hello", key2: undefined }
I don't know whether the above is intentional, but I believe it'd be at least unexpected for many other users like me.
Please consider not to return undefined values; in other words, I'd expect the above to be the following instead:
console.log(value) // { key1: "hello" }
If anyone stumbles upon it and wants and immediate solution, you could use the following:
import * as s from "@typeofweb/schema"
export const validate = <S extends s.SomeSchema<any>>(schema: S) => {
const validator = s.validate(schema)
const validatorModified: typeof validator = (value) => {
const withoutUndefinedValues = validator(value)
for (const key of Object.keys(withoutUndefinedValues)) {
if (withoutUndefinedValues[key] === undefined) {
delete withoutUndefinedValues[key]
}
}
return withoutUndefinedValues
}
return validatorModified
}
Just fyi @mmiszy the docs website started to return 404
Since nextNotValid
is throwing the validation, meaning the refine
won't return the value:
never
, so the return type is unaffected?React hook form is a popular form validation library that supports forms schema resolving by resolver API (https://github.com/react-hook-form/resolvers). Resolvers are already implemented for validators like yup, superstruct, etc which are natural competitors for this library.
For me, it seems like a common use-case to use schema definitions to validate forms. But to do that we need to first support reporting errors per object attribute (#13).
Resolver draft (trying to do it here):
export const schemaResolver = (schema) => {
const validator = validate(schema);
return (values) => {
try {
return {
values: validator(values),
errors: {}
};
} catch (e) {
const errors = /* get errors per schema attribute, each error should be a string */
return {
values: {},
errors: errors
};
}
};
}
Blocked by #45.
Consider following schema
const mySchema = object({
id: string(),
name: pipe(string, minStringLength(10))
})
Now, I would like to create another schema to validate only part of mySchema
. For that reason I can create another schema and pick only a few validators from mySchema
, potentially like this:
const anotherSchema = object({
name: mySchema.name
})
But the object's validator shape is not available after the validator is built. I took a look at the code and this is potentially possible but the types in refine
function are too complex for me at the moment :)
As is it presented in documentation object schema validation should convert its values into the expected ones, for instance: strings into numbers. Instead of this we get error with invalid type.
Version: 0.6.1
Use case: https://schema.typeofweb.com/docs/validate#example
Expected result:
{
dateFrom: new Date('2020-10-15T00:00:00.000Z'), // date instance
dateTo: new Date('2020-10-15T00:00:00.000Z'), // date instance
resultsPerPage: 10 // number
}
Received result:
ValidationError: Invalid type! Expected { dateFrom: Date, dateTo: Date, resultsPerPage: number } but got {"dateFrom":"2020-10-15","dateTo":"2020-10-15","resultsPerPage":"10"}!
Currently, it is impossible to safely validate against multiple validators.
export const validateSafe = <V, Validators extends ((v) => any)[]>(
value: V,
validators: Validators,
) => {
let result: V | undefined
for (const validator of validators) {
try {
result = validator(value)
} catch {}
}
return result
}
const body = validateSafe(value, [validator1, validator2])
New higher order function refine
provided by the library:
refine(refinement);
const refinement = (value, t) => {
return value + 1; // continue validation on transformed value
return t.left(value); // bail out with error
return t.right(value); // bail out with success
return t.right(value + 1); // bail out with success and transformed value
return t.toEither(value > 10); // bail out with error or success depending on the condition
};
The result of calling refine
is a function which can (optionally) take another schema as an argument and it might be passed to schemas:
const myRefinement = refine(refinement);
const validator = pipe(string, myRefinement, date, validate);
// alternatively
// validate(date(myRefinement(string())));
const even = refine((value, t) => t.toEither(value % 2 === 0));
pipe(number, even, validate)(12); // 12
// validate(even(number()))(12); // 12
pipe(number, even, validate)(3); // ValidationError
// validate(even(number()))(3); // ValidationError
const noDuplicateItems = refine((arr, t) => {
const allUnique = arr.every((item, index) => index === arr.indexOf(item));
return allUnique ? arr : t.left(arr);
});
const uniqueStringArrayValidator = pipe(array(string), noDuplicateItems, validate);
// const uniqueStringArrayValidator = validate(noDuplicateItems(array(string())));
uniqueStringArrayValidator(['a', 'b']); // ['a', 'b']
uniqueStringArrayValidator(['a', 'b', 'a']); // ValidationError
const allowTimestamps = refine((value) => (Number.isInteger(value) ? new Date(value) : value));
pipe(allowTimestamps, date, validate)(1231231231231); // Date(2009-01-06T08:40:31.231Z)
// validate(date(allowTimestamps()))(1231231231231); // Date(2009-01-06T08:40:31.231Z)
pipe(allowTimestamps, date, validate)(Infinity); // ValidationError
// validate(date(allowTimestamps()))(Infinity); // ValidationError
const usDateString = refine((value) => {
const result = typeof value === 'string' && value.match(/(?<m>\d\d)\/(?<d>\d\d)\/(?<y>\d\d\d\d)/);
if (!result) {
return value;
}
const { y, m, d } = result.groups;
return `${y}-${m}-${d}`;
});
pipe(string, usDateString, date, validate)('01/12/2021'); // Date(2021-01-12T00:00:00.000Z)
// validate(date(usDateString(string())))('01/12/2021'); // Date(2021-01-12T00:00:00.000Z)
pipe(string, usDateString, date, validate)('14/12/2021'); // ValidationError
// validate(date(usDateString(string())))('14/12/2021'); // ValidationError
const integer = refine<Integer>((value, t) => t.toEither(Number.isInteger(value)));
pipe(number, integer, validate)(1); // Integer
// validate(integer(number()))(1); // Integer
pipe(number, integer, validate)(1.5); // ValidationError
// validate(integer(number()))(1.5); // ValidationError
const email = refine<Email>((value, t) => t.toEither(value.includes('@')));
pipe(string, email, validate)('[email protected]'); // Email '[email protected]'
// validate(email(string()))('[email protected]'); // Email '[email protected]'
pipe(string, email, validate)('just hello'); // ValidationError
// validate(email(string()))('just hello'); // ValidationError
const allowFalse = refine((value, t) => (value === false ? t.right(value) : value));
pipe(allowFalse, string, validate())(false); // false
// validate(string(allowFalse()))(false); // false
pipe(allowFalse, string, validate())(true); // ValidationError
// validate(string(allowFalse()))(true); // ValidationError
const isInObject = <T extends object>(object: T, key: keyof any): key is keyof T =>
Object.prototype.hasOwnProperty.call(object, key);
const hasProperties = (properties: string[]) =>
refine((value, t) =>
properties.every((prop) => isInObject(value, prop)) ? value : t.left(value),
);
pipe(hasProperties(['foo']), validate)({ foo: 'bar' }); // { foo: "bar" }
// validate(hasProperties(["foo"])())({ foo: "bar" }); // { foo: "bar" }
pipe(hasProperties(['foo']), validate)({ bar: 'foo' }); // ValidationError
// validate(hasProperties(["foo"])())({ bar: "foo" }); // ValidationError
const url = refine<URL>((value, t) => t.toEither(value.includes('https://')));
pipe(string, url, validate)('https://github.com/') // URL 'https://github.com/'
validate(url(string()))('https://github.com/') // URL 'https://github.com/'
pipe(string, url, validate)('abc') // ValidationError
validate(url(string()))('abc') // ValidationError
validate(evenNumber)(12); // 12
validate(evenNumber)(3); // ValidationError
validate(noDuplicateItems)(['a', 'b']); // ['a', 'b']
validate(noDuplicateItems)(['a', 'b', 'a']); // ValidationError
validate(timestamp(date()))(1231231231231); // Date(2009-01-06T08:40:31.231Z)
validate(timestamp(date()))(Infinity); // ValidationError
validate(weirdDate(date()))('01/12/2021'); // Date(2021-01-12T00:00:00.000Z)
validate(weirdDate(date()))('14/12/2021'); // ValidationError
type Integer = Nominal<number>; // `Nominal` is provided by user
const integer = (x: unknown): x is Integer => { ?? } // shape of this function is to be designed
validate(integer())(1); // Integer
validate(integer())(1.5); // ValidationError
type Email = Nominal<string>; // `Nominal` is provided by user
const email = (x: unknown): x is Email => { ?? } // shape of this function is to be designed
validate(email())('[email protected]'); // Email
validate(email())('just hello'); // ValidationError
validate(allowFalse(string()))(false); // false
validate(allowFalse(string()))(true); // ValidationError
New higher order function refine
and refineAfter
provided by the library:
refineAfter(beforeSchema)(refinement);
refine(refinement);
const refinement = (value, t) => {
return value + 1; // continue validation on transformed value
return t.left(value); // bail out with error
return t.right(value); // bail out with success
return t.right(value + 1); // bail out with success and transformed value
return t.toEither(value > 10); // bail out with error or success depending on the condition
};
const evenNumber = refineAfter(number())((value, t) => t.toEither(value % 2 === 0));
validate(evenNumber)(12); // 12
validate(evenNumber)(3); // ValidationError
const noDuplicateItems = (arr, t) => {
const allUnique = arr.every((item, index) => index === arr.indexOf(item));
return allUnique ? arr : t.left(arr);
};
const uniqueStringArray = refineAfter(array(string()))(noDuplicateItems);
validate(uniqueStringArray)(['a', 'b']); // ['a', 'b']
validate(uniqueStringArray)(['a', 'b', 'a']); // ValidationError
const withTimestamps = refine((value) => (Number.isInteger(value) ? new Date(value) : value));
validate(withTimestamps(date()))(1231231231231); // Date(2009-01-06T08:40:31.231Z)
validate(withTimestamps(date()))(Infinity); // ValidationError
const parseUsDateString = (value: string) => {
const result = value.match(/(?<m>\d\d)\/(?<d>\d\d)\/(?<y>\d\d\d\d)/);
if (!result) {
return null;
}
const { y, m, d } = result.groups;
return `${y}-${m}-${d}`;
};
const weirdDate = refineAfter(string())((value) => parseUsDateString(value) ?? value);
validate(weirdDate(date()))('01/12/2021'); // Date(2021-01-12T00:00:00.000Z)
validate(weirdDate(date()))('14/12/2021'); // ValidationError
const integer = refineAfter<Integer>(number())((value, t) => t.toEither(Number.isInteger(value)));
validate(integer())(1); // Integer
validate(integer())(1.5); // ValidationError
const email = refineAfter<Email>(string())((value, t) => t.toEither(value.includes('@')));
validate(email())('[email protected]'); // Email
validate(email())('just hello'); // ValidationError
const allowFalse = refine((value, t) => (value === false ? t.right(value) : value));
validate(allowFalse(string()))(false); // false
validate(allowFalse(string()))(true); // ValidationError
For example:
oneOf([number(), false])
I'm interested how schema compares with ts-json-validator, zod, and io-ts.
When it's feature-ready, getting it featured in https://github.com/moltar/typescript-runtime-type-benchmarks could be beneficial for easy comparison with the 19 libraries already there.
Blocked by #2
After adding recursive types, we should be able to add json
validator that will match every JSON.
const arrOfStrings = array(string());
should have the same effect as
const arrOfStrings = array([string()]);
In the first docs example:
const personSchema = object({
name: string(),
age: number(),
email: optional(string()),
});
const mark = {
name: 'Mark',
age: 29,
};
mark
will fail to validate because it's really the value that's optional and not the field itself.
Can we think of a way to make whole fields optional?
Thanks!
Currently, we have one huge function responsible for validation. We should split it into separate functions bundled together with specific validators. The aim is to solve two problems:
This should not be a breaking change in the user-facing API.
Validators implementation details should be considered private.
Hey, a friend just shared this library with me. It looks pretty cool, and the benchmarks seem impressive :)
Reading the README I'm not sure how do I extract a type from validator?
Looking at the examples, I'd guess that this is possible, but it seems to be missing from the README. (I'm sorry if it's there and I missed it)
type Person = FromSchema<typeof personSchema>
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.