Experimental schema builder using TypeScript template strings.
The inner workings of how this library works are explained in this blog post: Build your own schema language with TypeScript's infer keyword.
Use compileSchema
to create a schema.
import { compileSchema } from "strema";
const schema = compileSchema(`{
message: string;
size: number <positive, int>;
tags: string[];
author: {
name: string;
email: string <email>;
age: number <int, min(18)>;
};
}`);
To get the type from the schema
, use ExtractSchemaType
.
import { ExtractSchemaType } from "strema";
type Body = ExtractSchemaType<typeof schema>;
To validate and parse incoming data, use the parseSync
method on schema
.
// Throws an error if req.body does not conform to the schema
const body = schema.parseSync(req.body);
This library supports four field types.
There are three primitive types.
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
value: string;
}`); |
{ value: string } |
// Optional 'value' field
const schema = compileSchema(`{
value?: string;
}`); |
{ value: string | null } |
// Optional 'value' field with default value
const schema = compileSchema(`{
value?: string = "Hello, world";
}`); |
{ value: string } |
// Apply email rule to 'to' field
const schema = compileSchema(`{
to: string <email>;
}`); |
{ to: string } |
min(n)
sets a minimum length for the string.max(n)
sets a maximum length for the string.length(n)
equivalent tomin(n), max(n)
.email
the string value must be an email address.uuid
the string value must be a uuid.
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
value: number;
}`); |
{ value: number } |
// Optional 'value' field
const schema = compileSchema(`{
value?: number;
}`); |
{ value: number | null } |
// Optional 'value' field with default value
const schema = compileSchema(`{
value?: number = 1;
}`); |
{ value: number } |
// Apply min rule to 'value' field
const schema = compileSchema(`{
value: number <min(1)>;
}`); |
{ value: number } |
min(n)
sets a minimum value for the number.max(n)
sets a maximum value for the number.int
the value must be an integer.positive
equivalent tomin(0)
.
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
include: boolean;
}`); |
{ include: boolean } |
// Optional 'include' field
const schema = compileSchema(`{
include?: boolean;
}`); |
{ include: boolean | null } |
// Optional 'include' field with default value
const schema = compileSchema(`{
include?: boolean = false;
}`); |
{ include: boolean } |
Booleans do not support any rules.
Objects fields represent a collection of sub-fields (key-value pairs).
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
book: {
name: string;
description: string;
author: {
name: string;
age: number;
};
};
}`); |
{
book: {
name: string;
description: string;
author: {
name: string;
age: number;
};
};
} |
To create an object with dynamic keys, use a Record.
Arrays represent a list of values. The list may be multidimensional.
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
tags: string[];
}`); |
{ tags: string[] } |
const schema = compileSchema(`{
matrix: number[][];
}`); |
{ matrix: number[][] } |
const schema = compileSchema(`{
items: { name: string }[];
}`); |
{ items: Array<{ name: string }> } |
You can create arrays of any type. Here's how you would represent an array of objects.
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
books: {
name: string;
description: string;
}[];
}`); |
{
books: Array<{
name: string;
description: string;
}>;
} |
Records represent a collection key-value pairs with dynamic keys (i.e. hash maps, dictionaries, associative arrays). The record syntax is Record<K, V>
where K
is the key type and V
is the value type.
K
must be either string
or number
. V
can be any value that this library supports.
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
map: Record<string, number>;
}`); |
{ map: Record<string, number> } |
const schema = compileSchema(`{
map: Record<string, {
value: number;
}>;
}`); |
{ map: Record<string, { value: number }> } |
const schema = compileSchema(`{
map: Record<string, number[]>;
}`); |
{ map: Record<string, number[]> } |
Primitive types support rules to perform basic validation. Rules are specified inside of <>
after the type name and before ;
with multiple rules separated by ,
. If the rule takes an argument, provide it inside of ()
after the rule name.
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
age: number <positive, int>;
email: string <email>;
password: string <min(8)>;
}`); |
{
age: number;
email: string;
password: string;
} |
The available rules can be found here:
- String rules
- Number rules
- Booleans do not support rules
Rules may also be applied to arrays (and multidimensional arrays). In those cases, specify the rules after the []
array notation.
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
tags: string[] <min(1)>;
coords: number[][] <int>;
}`); |
{
tags: string[];
coords: number[][];
} |
Rules can not be applied directly to arrays or objects.
By default, all fields are required. To mark a field as optional, add a ?
after the field name:
Schema | TypeScript type |
---|---|
const schema = compileSchema(`{
description?: string;
}`); |
{ description: string | null } |
When a primitive field is optional, null
and undefined
values are not rejected.
const schema = compileSchema(`{
description?: string;
}`);
const output = schema.parseSync({ description: undefined });
console.log(output);
//=> { description: null }
Optional arrays behave in the same way as primitives.
const schema = compileSchema(`{
tags?: string[];
}`);
const output = schema.parseSync({ tags: undefined });
console.log(output);
//=> { tags: null }
Object fields behave the same as primitives and arrays, with the exception that object fields with no required fields accept null
and undefined
.
const schema = compileSchema(`{
options: { notify?: boolean; delay?: number };
}`);
const output = schema.parseSync({ options: undefined });
console.log(output.options);
//=> { notify: null, delay: null }
However, if the object is optional, it resolves to null
when null
or undefined
are provided.
const schema = compileSchema(`{
options?: { notify?: boolean; delay?: number };
}`);
const output = schema.parseSync({ options: undefined });
console.log(output.options);
//=> null
Records fields are always optional. Using the ?:
optional notation throws an error.
const schema = compileSchema(`{
record?: Record<string, string>;
}`);
// Throws: Type 'record' cannot be optional