Functional, tree-shakeable, schema validation
npm install @stackup/validateA functional schema validation library.
- Lightweight - My data validation library shouldn't be bigger than React.
- Tree-shakeable - I don't want to take a hit for functionality that I'm not using.
- Composable - I want to validate my data with tiny, single-purpose functions.
- Type-safe - I want my validations to enforce my TypeScript types.
#### Table of Contents
- Example
- Installation
- Operators
- schema(shape)
- assert(predicate, message?, path?)
- refute(predicate, message?, path?)
- map(transform)
- optional(validator)
- nullable(validator)
- maybe(validator)
- when(predicate, validator)
- each(validator)
- defaultTo(value)
- Validator
- validate(input)
- then(validator)
- SchemaValidator
- extend(shape)
- Predicates
- isString(value)
- isNumber(value)
- isObject(value)
- isBoolean(value)
- isUndefined(value)
- isNull(value)
- isNil(value)
- isBlank(value)
- TypeScript
- Type Narrowing
- Enforce an existing type with a Validator
- Extract type information from a Validator
``javascript
import { schema, assert, isString, isBlank, isNumber } from "@stackup/validate";
const userSchema = schema({
name: assert(isString).then(refute(isBlank, "Can't be blank")),
age: assert(isNumber).then(assert(age => age >= 18, "Must be 18 or older"))
});
const result = await userSchema.validate(data);
if (result.valid) {
console.log(result.value);
} else {
console.log(result.errors);
}
`
Install the package from NPM:
$ yarn add @stackup/validate
An operator is a function that returns a new Validator. You can chain
together multiple operators with then:
`javascript`
assert(isString)
.then(refute(isBlank))
.then(map(value => value.trim()))
.validate("hello!")
.then(result => {
if (result.valid) {
console.log(result.value);
} else {
console.log(result.errors);
}
});
#### schema(shape)
Describes an object's properties.
`javascript`
schema({
name: assert(isString),
age: assert(isNumber)
});
#### assert(predicate, message?, path?)
Produces an error if a condition is not met.
`javascript`
assert(isString, "must be a string");
#### refute(predicate, message?, path?)
Produces an error if a condition is met.
`javascript`
refute(isBlank, "can't be blank");
#### map(transform)
Transforms the current value to a new value.
`javascript`
map(value => value.trim());
#### optional(validator)
Runs the given validator unless the value is undefined.
`javascript`
optional(assert(isString));
#### nullable(validator)
Runs the given validator unless the value is null.
`javascript`
nullable(assert(isString));
#### maybe(validator)
Runs the given validator unless the value is either null or undefined.
`javascript`
maybe(assert(isString));
#### when(predicate, validator)
Runs the given validator when the predicate is truthy.
`javascript`
when(isString, refute(isBlank));
#### each(validator)
Runs the given validator against each item in an array.
`javascript`
each(assert(isString));
#### defaultTo(value)
Provide a default value to replace null or undefined values.
`javascript`
defaultTo(0);
#### pass()
Skip validation for this field.
`javascript`
schema({
name: pass(),
age: pass()
});
A Validator represents a step in the validation sequence. You probably won't
create a validator directly, but you certainly could:
`javascript`
const blankValidator = new Validator(input => {
if (isBlank(value)) {
return Validator.reject("can't be blank");
} else {
return Validator.resolve(value);
}
});
#### validate(input)
Runs the validator against user input. This function returns a Promise.
`javascript
const result = await validator.validate(data);
if (result.valid) {
console.log(result.value);
} else {
console.log(result.errors);
}
`
#### then(validator)
Adds another validator to the current validation chain. This method returns an entirely new validator.
`javascript`
validator.then(refute(isBlank));
#### extend(shape)
Add or overwrite the fields that a schema validates.
`javascript
const user = schema({
name: assert(isString)
});
const admin = user.extend({
role: assert(role => role === "admin")
});
`
A predicate is a function that takes a value and returns true or false.
`javascript`
const isBlank = value => {
return value.trim() === "";
};
This library only includes the most essential predicate functions, because you
can find thousands of predicate functions in the NPM ecosystem. Here are a few examples:
- lodash
- ramda
- is-email
- is-url
#### isString(value)
Checks if the value is a string.
#### isNumber(value)
Checks if the value is a number.
#### isObject(value)
Checks if the value is an object.
#### isBoolean(value)
Checks if the value is boolean.
#### isUndefined(value)
Checks if the value is undefined.
#### isNull(value)
Checks if the value is null.
#### isNil(value)
Checks if the value is null or undefined.
#### isBlank(value)
Checks if a string is blank.
#### Type Narrowing
This library assumes that all input is unknown by default. That means, you'll need
to narrow types before calling certain functions.
Here's an example that would cause a compile-time error:
`typescript`
schema({
// Error: 'unknown' is not assignable to type 'string'
name: refute(isBlank)
});
This is by design. To address this, you'll need to narrow types by passing a type guard to assert:
`typescript`
schema({
name: assert(isString).then(refute(isBlank))
});
This library includes a few type guards out of the box, but you can also write your own:
`typescript`
const isStringOrNumber = (value: unknown): value is string | number => {
return typeof value === "string" || typeof value === "number";
};
#### Enforce an existing type with a Validator
You can ensure that your validators enforce your types.
`typescript
interface User {
name: string;
}
// Error: Property 'name' is missing in type '{}'
schema
// Error: 'number' is not assignable to type 'string'
schema
`
#### Extract type information from a Validator
You can also generate new types from validators that you've defined:
`typescript
const userSchema = schema({
name: assert(isString),
age: assert(isNumber);
});
type User = InferType
``