Skip to main content

farrow-schema

Powerful and extensible schema builder library. Create Schema like type of TypeSccript.

Installation

yarn add farrow-schema

Usage

import { Struct, ID, TypeOf } from "farrow-schema";
import { Validator } from "farrow-schema/validator";

const user = Struct({
id: ID,
name: String,
});

const result = Validator.validate(User, {
id: "foo",
name: "foo name",
});

if (result.isOk) {
console.log(result.value);
}

type User = TypeOf<typeof User>;

// 相同类型
type User = {
id: string;
name: string;
};

API

Schema Builder

Primitive

  • Usage
const V = Number;
SchemaTypeValidation
const V = Numbertype V = numbertypeof V === 'number' && !isNaN(V)
const V = Stringtype V = stringtypeof V === 'string'
const V = Booleantype V = booleantypeof V === 'boolean'
const V = Datetype V = DateDate.isDate(V)
const V = IDtype V = numbertypeof V === 'string'
const V = Inttype V = numberNumber.isInteger(V)
const V = Floattype V = numbertypeof V === 'number' && !isNaN(V)

Literal

  • Usage
const V = Literal(Number Value | String Value | Boolean Value | Date Value);
SchemaTypeValidation
const V = Literal(0)type V = 0V === 0
const V = Literal("foo")type V = "foo"V === "foo"
const V = Literal(true)type V = trueV === true
const V = Literal(C = new Date())type V = DateV === C

List

  • Usage
const V = List($Schema);
  • Example
// Schema
const V = List(Number);

// Type
type V = Array<number>;

// Validation
Array.isArray(V) && V.every((c) => typeof c === "number" && !isNaN(c));
// Schema
const V = List(Literal(0));

// Type
type V = Array<0>;

// Validation
Array.isArray(V) && V.every((c) => c === 0);
// Schema
const V = List(
Struct({
id: ID,
name: String,
})
);

// Type
type V = Array<{
id: string;
name: string;
}>;

// Validation
Array.isArray(V) &&
V.every((c) => {
const cIsObject = typeof c === "object";
const idIsString = typeof c.id === "string";
const nameIsString = typeof c.name === "string";
return cIsObject && idIsString && nameIsString;
});

Struct

  • Usage
const FieldSchema = {
[key: string]: $Schema | $FieldSchema
}
const V = Struct($FieldSchema);
  • Example
// Schema
const V = Struct({
id: ID,
name: String,
});

// Type
type V = {
id: string;
name: string;
};

// Validation
const cIsObject = typeof V === "object";
const idIsString = typeof V.id === "string";
const nameIsString = typeof V.name === "string";
cIsObject && idIsString && nameIsString;
// Schema
const V = Struct({
id: ID,
name: String,
artile: {
title: String,
description: String,
},
});

// Type
type V = {
id: string;
name: string;
article: {
title: string;
description: string;
};
};

// Validation
const cIsObject = typeof V === "object";
const idIsString = typeof V.id === "string";
const nameIsString = typeof V.name === "string";
const articleIsObject = typeof V.article === "object";
const titleIsString = typeof V.article.title === "string";
const descriptionIsString = typeof V.article.description === "string";
cIsObject &&
idIsString &&
nameIsString &&
articleIsObject &&
titleIsString &&
descriptionIsString;
caution

Struct cannot construct recusive type. It will throw error if you make recursive type with this.

ObjectType

  • Usage
class V extends ObjectType {
$FieldSchema;
...
}
  • Example
// Schema
class V extends ObjectType {
id = ID;
name = String;
}

// Type
type V = {
id: string;
name: string;
};

// Validation
const vIsObject = typeof V === "object";
const idIsString = typeof V.id === "string";
const nameIsString = typeof V.name === "string";
vIsObject && idIsString && nameIsString;
// Schema
class V extends ObjectType {
value = Number;
next = Nullable(V);
}

// Type
type V = {
value: number;
next: V | undefined;
};

// Validation
const vIsObject = typeof V === "object";
const vauleIsNumber = typeof V.id === "number";
const nextIsVOrUndefined = V.name === undefined || V.next is V;
vIsObject && vauleIsNumber && nextIsVOrUndefined;
info

ObjectType can construct recursive type.

note

One point to note about recursive type is that the constructed schema cannot be infinitely recursive, and there are three main ways to interrupt infinite recursion: option, lazy, and reference, while in farrow-schema, it is mainly by way of option. Nullable, List can do it.

Meta

const V = Null;
SchemaTypeValidation
const V = Nulltype V = nullV === null
const V = Undefinedtype V = undefinedV === undefined
const V = Anytype V = anytrue
const V = Unknowntype V = Unknowntrue
const V = Jsontype V = JsonTypeJSON.parse(JSON.stringify(V))

Generic

  • Union
const V = Union($Schema, $Schema);
  • Intersect
const V = Intersect($Schema, $Schema);
  • Tuple
const V = Tuple(...$Schema[]);
  • Record
const V = Record($Schema);
  • Nullable
const V = Nullable($Schema);
  • Strict
const V = Strict($Schema);
  • NonStrict
const V = NonStrict($Schema);
  • ReadOnly
const V = ReadOnly($Schema);
  • ReadOnlyDeep
const V = ReadOnlyDeep($Schema);

Declaration

const Foo = Struct({ foo: String });
const Bar = Struct({ bar: Number });
const Baz = Struct({ foo: { bar: String } });
SchemaTypeValidation
const V = Union(String, Number)type V = string \| numberV is string \|\| V is number
const V = Intersect(Foo, Bar)type V = { foo: string } && { bar: number }
const V = Tuple(String, Number, Literal(0))type V = [string, number, 0]Array.isArray(V) && typeof V[0] === "string" && typeof V[1] === "number" && V[2] === 0
const V = Record(Number)type V = Record<string, number>
const V = Strict(Foo)type V = { foo: string }
const V = NonStrict(Foo)type V = { foo: string }
const V = ReadOnly(Foo)type V = { readonly foo: string }
const V = ReadOnly(Baz)type V = { readonly foo: { bar: string } }
const V = ReadOnlyDeep(Baz)type V = { readonly foo: { readonly bar: string } }

Util

  • pick
const V = pick(C = $Struct | $ObjectType, $keyof C);
  • omit
const V = omit(C = $Struct | $ObjectType, $keyof C);
  • keyof
const V = keyof($Struct | $ObjectType);
  • partial
const V = partial($Struct | $ObjectType);

Declaration

const Foo = Struct({ foo0: String, foo1: Number });
const Bar = Struct({ bar0: String, bar1: Number });
SchemaTypeValidation
const V = pick(Foo, "foo0")type V = { foo0: string }
const V = pick(Bar, "bar0")type V = { bar0: string }
const V = omit(Foo, "foo0")type V = { foo1: number }
const V = omit(Bar, "bar0")type V = { bar1: number }
const V = keyof(Foo)type V = "foo0" \| "foo1"
const V = keyof(Bar)type V = "bar0" \| "bar1"
const V = partial(Foo)type V = { foo0?: string, foo1?: number }
const V = partial(Bar)type V = { bar0?: string, bar1?: number }

Formatter

formatSchema

Type Signature:

const formatSchema: <T extends S.SchemaCtor<S.Schema>>(
Ctor: T,
context?: FormatContext | undefined
) => {
typeId: number;
types: FormatTypes;
};

type FormatContext = {
addType: (type: FormatType) => number;
formatCache: WeakMap<Function, number>;
};

Example Usage:

const result = formatSchema(Number)

// alternative
{
typeId: 0,
types: {
'0': {
type: 'Scalar',
valueType: 'number',
valueName: 'Number',
},
},
}

Validator

createSchemaValidator

Type Signature:

const createSchemaValidator: <S extends S.SchemaCtor<S.Schema>>(
SchemaCtor: S,
options?: ValidatorOptions | undefined
) => (input: unknown) => ValidationResult<S.TypeOf<S>>;

Example Usage:

import { ID, Struct } from "farrow-schema";
import { Validator } from "farrow-schema/validator";

const User = Struct({
id: ID,
name: String,
});

const result = Validator.validate(User, {
id: "foo",
name: "foo name",
});

if (result.isOk) {
console.log(result.value);
}

Strict Mode

The strict mode is defaultly open while validating. When a Schema wrapped by NonStrict, the strict mode will be closed. It affects below schema.

tip

You can control the strict mode by option of Validator.validate, like:

const result1 = Validator.validate(Number, "123.32", { strict: false });
  • Number
const result0 = Validator.validate(Number, 123.32);

The result0 always is 123.32(It means validate successfully).

const result1 = Validator.validate(Number, "123.32");
const result1 = Validator.validate(Number, "123.32", { strict: false });

But the result1 will be false when strict mode is open and 123.32 when strict mode is close. It will try to parse string to number.

  • Int
const result0 = Validator.validate(Int, 123);

The result0 always is 123.

const result1 = Validator.validate(Int, 123.32);
const result1 = Validator.validate(Int, 123.32, { strict: false });

But the result1 will be false when strict mode is open and 123 when strict mode is close. It will try to Math.floor the input.

const result2 = Validator.validate(Int, "123");
const result2 = Validator.validate(Int, "123", { strict: false });

But the result2 will be false when strict mode is open and 123 when strict mode is close. It will try to parse string to number.

const result3 = Validator.validate(Int, "123.32");
const result3 = Validator.validate(Int, "123.32", { strict: false });

But the result3 will be false when strict mode is open and 123 when strict mode is close. It will try to parse string to number and Math.floor the result.

  • Float
const result0 = Validator.validate(Float, 123.32);

The result0 always is true.

const result1 = Validator.validate(Float, "123.32");
const result1 = Validator.validate(Float, "123.32", { strict: false });

But the result1 will be false when strict mode is open and 123.32 when strict mode is close. It will try to parse string to number.

  • Boolean
const result0 = Validator.validate(Boolean, true);

The result0 always is true.

const result1 = Validator.validate(Boolean, "true");
const result1 = Validator.validate(Boolean, "true", { strict: false });

But the result1 will be false when strict mode is open and true when strict mode is close. It will try to parse string to boolean.

  • Literal
const result0 = Validator.validate(Literal(123), 123);

The result0 always is true.

const result1 = Validator.validate(Literal(123), "123");
const result1 = Validator.validate(Literal(123), "123", { strict: false });

But the result1 will be false when strict mode is open and 123 when strict mode is close. It will try to parse string to boolean.

  • Struct

Schema declaration

const Foo = Struct({
id: Number,
name: String,
});
const result0 = Validator.validate(Foo, { id: 0, name: "foo" });

The result0 always is true.

const result1 = Validator.validate(Literal(123), '{ id: 0, name: "foo" }');
const result1 = Validator.validate(Literal(123), '{ id: 0, name: "foo" }', {
strict: false,
});

But the result1 will be false when strict mode is open and { id: 0, name: "foo" } when strict mode is close. It will try to parse string to JsonType.

Type Infer

TypeOf

  • Usage
type T = TypeOf<typeof $Schema>
  • Example
type T = TypeOf<typeof Number>;

// alternative
type T = number;
type T = TypeOf<typeof List(Number)>;

// alternative
type T = number[];
type T = TypeOf<typeof Union(Number, String)>;

// alternative
type T = number | string;
type T = TypeOf<typeof Struct({ foo: String })>;

// alternative
type T = { foo: string };

Learn more

Relative Module
Sample