https://github.com/ncpa0/dilswer
Blazingly fast data validation library with TypeScript integration.
https://github.com/ncpa0/dilswer
json-schema typescript validation validator
Last synced: about 1 month ago
JSON representation
Blazingly fast data validation library with TypeScript integration.
- Host: GitHub
- URL: https://github.com/ncpa0/dilswer
- Owner: ncpa0
- License: mit
- Created: 2021-11-10T11:23:24.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2025-03-31T12:59:37.000Z (about 2 months ago)
- Last Synced: 2025-04-05T22:32:41.793Z (about 2 months ago)
- Topics: json-schema, typescript, validation, validator
- Language: TypeScript
- Homepage:
- Size: 3.59 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Dilswer


[](https://www.npmjs.com/package/dilswer)

Blazingly fast data validation library with TypeScript integration.
## Table Of Contents
1. [Quick Start](#quick-start)
1. [Create type definitions](#create-type-definitions)
2. [Create a TypeScript type from a Dilswer definition](#create-a-typescript-type-from-a-dilswer-definition)
3. [Create a validation function](#create-a-validation-function)
4. [Assertion function](#assertion-function)
5. [Standard Schema support](#standard-schema-support)
2. [Other features](#other-features)
1. [Json Schema generation](#json-schema-generation)
2. [TypeScript type definition generation](#typescript-type-definition-generation)
3. [Availabla Type's](#availabla-types)
1. [Number](#typenumber)
2. [Int](#typeint)
3. [String](#typestring)
4. [StringNumeral](#typestringnumeral)
5. [StringInt](#typestringint)
6. [StringMatching](#typestringmatchingregex)
7. [Boolean](#typeboolean)
8. [Symbol](#typesymbol)
9. [Null](#typenull)
10. [Undefined](#typeundefined)
11. [Function](#typefunction)
12. [Unknown](#typeunknown)
13. [OneOf](#typeoneoftypes)
14. [AllOf](#typealloftypes)
15. [Array](#typearraytypes)
16. [Record](#typerecordrecordstring-fielddescriptor)
17. [Dict](#typedicttypes)
18. [Set](#typesettypes)
19. [Literal](#typeliteralstring--number--boolean)
20. [InstanceOf](#typeinstanceofclass)
21. [Enum](#typeenumenum)
22. [EnumMember](#typeenummemberenum-member)
23. [Recursive](#typerecursivefunction)
24. [Custom](#typecustomfunction)
4. [Utility Functions](#utility-functions)
1. [And](#and)
2. [Omit](#omit)
3. [Pick](#pick)
4. [Partial](#partial)
5. [Required](#required)
6. [Exclude](#exclude)
5. [Metadata](#metadata)
1. [Assign Metadata](#assign-metadata)
2. [Read Metadata](#read-metadata)
3. [Metadata and JSON Schema's](#metadata-and-json-schemas)
6. [Parsing](#parsing)## Quick Start
#### Create type definitions
```ts
// person-type.ts
import { Type } from "dilswer";// Record property types can be defined in a few different ways:
const PersonDataType = Type.Record({
id: Type.String,
name: Type.String,
age: { type: Type.Number },
email: Type.Option(Type.String),
friends: { type: Type.Array(Type.String), required: false },
});// A TypeScript equivalent type of the above would be:
// type Person = {
// id: string;
// name: string;
// age: number;
// email?: string;
// friends?: string[];
// };
```**NOTE:** the `required` attribute in a RecordOf fields is set to `true` by
default.#### Create a TypeScript type from a Dilswer definition
It is possible to infer a TypeScript type from a Dilswer definition:
```ts
import { Infer } from "dilswer";
import { PersonDataType } from "./person-type.ts";type Person = Infer;
// Result:
// type Person: {
// id: string;
// name: string;
// age: number;
// email?: string;
// friends?: string[];
// }
```#### Create a validation function
```ts
import { validator } from "dilswer";
import { PersonDataType } from "./person-type.ts";const isPerson = validator(PersonDataType);
// Result:
// const isPerson: (data: unknown) => data is {
// friends?: string[];
// id: string;
// name: string;
// age: number;
// }const person = await axios
.get("https://my-api.io/get-person/1")
.then((r) => r.data);if (isPerson(person)) {
console.log("Name: ", person.name);
// do something with person
} else {
console.error("`person` variable is not of expected type.");
// handle the validation failure
}
```##### validation details
if you want to obtain more details about validation failures, validator can be passed a second argument that will change the bahavior of the validate function:
```ts
import { validator } from "dilswer";
import { PersonDataType } from "./person-type.ts";const isPerson = validator(PersonDataType, { details: true });
// Result:
// const isPerson: (data: unknown) => {
// success: false;
// error: ValidationError;
// } | {
// success: true;
// value: {
// friends?: string[];
// id: string;
// name: string;
// age: number;
// };
// }
```#### Assertion function
Assertion function can change the type of a variable by simply being called:
```ts
import { Type, assertType } from "dilswer";function foo(value: any) {
try {
assertType(Type.String, value);
value.toUpperCase(); // value is now treated as string by TypeScript
} catch (error) {
console.error("Value is not a string");
}
}
```### Standard Schema support
Dilswer can be used with any library that supports the Standard Schema validation, like tRPC, OpenAuth and others.
```ts
import { Type } from "dilswer";
import { initTRPC } from "@trpc/server";const t = initTRPC.create();
const router = t.router({
greeting: t.procedure
.input(Type.Record({
name: Type.String,
}))
.query(async ({ input }) => {
return `Hello, ${input.name}!`;
}),
})
```For the best performance, type schemas passed to the other libraries should get compiled via the `.compile()` method.
The compiled schemas do not provide as detailed error messages but are order of magnitude faster.```ts
const router = t.router({
greeting: t.procedure
.input(
Type.Record({
name: Type.String,
}).compile()
)
.query(async ({ input }) => {
return `Hello, ${input.name}!`;
}),
})
```## Other features
### Json Schema generation
Translates given Type into a JSON Schema.
```ts
const toJsonSchema: (
type: AnyType,
options: ParseToJsonSchemaOptions = {},
include$schemaProperty = true
) => JSONSchema6 | undefined;
```#### ParseToJsonSchemaOptions
```ts
type ParseToJsonSchemaOptions = {
/**
* Defines how to handle DataTypes that do not have an
* equivalent type in JSON Schema. (Set's, undefined, Symbols,
* etc.)
*
* - `throw` (default): Throw an error if an incompatible type is
* encountered.
* - `omit`: Omits incompatible properties from the JSON Schema.
* - `set-as-any`: Adds the type to the schema without a "type"
* property but with a name equivalent to the given
* DataType.
*/
incompatibleTypes?: "throw" | "omit" | "set-as-any";
/**
* Determines if the schemas generated for Record's should have
* additional properties set to `true` or `false`.
*/
additionalProperties?: boolean;
/**
* Custom Parser's are methods used to parse incompatible
* DataTypes to JSON Schema's.
*
* By default a strategy defined in `incompatibleTypes` is
* used, if a method is defined, that method will be used
* instead.
*/
customParser?: {
Set?: (
setItemsSchemas: JSONSchema6[],
original: SetType,
options: ParseToJsonSchemaOptions,
) => JSONSchema6 | undefined;
Custom?: (
validateFunction: Custom["custom"],
original: Custom,
options: ParseToJsonSchemaOptions,
) => JSONSchema6 | undefined;
Undefined?: (
dataType: BasicType,
options: ParseToJsonSchemaOptions,
) => JSONSchema6 | undefined;
Symbol?: (
dataType: BasicType,
options: ParseToJsonSchemaOptions,
) => JSONSchema6 | undefined;
Function?: (
dataType: BasicType,
options: ParseToJsonSchemaOptions,
) => JSONSchema6 | undefined;
};
};
```#### TypeScript type definition generation
Translates given Type into a TypeScript type definition. This is not very
useful at runtime, and is mostly intended for generating type definitions with
JSDoc comments that can be bundled with libraries.```ts
const toTsType: (
dataType: AnyType,
options?: Partial,
) => string;
```#### TsParsingOptions
```ts
type TsParsingOptions = {
/**
* Defines how to parse the type.
*
* - `compact` - the type will be parsed into a single type
* definition
* - `fully-expanded` - the type will be split into multiple type
* definitions, and the main DataType type definition will
* reference them.
* - `named-expanded` - similar to `fully-expanded`, but only the
* types that have titles assigned will be split into
* separate type definitions.
*
* @default `compact`
*/
mode: TsParsingMode;
/**
* Defines how to export the generated types.
*
* - `main` - only the main DataType type will be exported
* - `all` - all types generated will be exported
* - `named` - only the types with titles will be exported
* - `none` - nothing will be exported
*
* @default `main`
*/
exports: "main" | "named" | "all" | "none";
/**
* Defines whether to generate the type as a declaration or
* not.
*
* The difference is that declaration will generate each type
* definition with a `declare` keyword preceding it.
*
* @default `false`
*/
declaration: boolean;
/**
* Defines how to handle duplicate names.
*
* - `error` - will throw an error if a duplicate name is
* encountered
* - `rename` - will rename the duplicate type
*
* @default `error`
*/
onDuplicateName: "error" | "rename";
/**
* Some DataType can reference enums or classes, in which case
* it's sometimes impossible to generate a valid TypeScript
* type for them. By default just the name of that class/enum
* will be used, and if that name is not available in the
* global scope, TS will resolve it to `any`. This option
* allows to define a custom import path for such types.
*
* @example
* // foo.ts
* export class Foo {}
*
* // data-type.ts
* import { Foo } from "./foo";
*
* export const dt = Type.RecordOf({
* foo: Type.InstanceOf(Foo),
* });
*
* // ts-type-generator.ts
* import { dt } from "./data-type";
* import { Foo } from "./foo";
*
* const tsType = toTsType(dt, {
* getExternalTypeImport: (t) => {
* if (t.instanceOf === Foo) {
* return {
* typeName: "Foo",
* path: "./foo",
* };
* }
* },
* });
* // tsType:
* //"
* // import { Foo } from "./foo";
* //
* // export type Record1 = {
* // foo: InstanceType;
* // }
* //"
*/
getExternalTypeImport?: (
type: EnumType | EnumMemberType | InstanceType | CustomType | FunctionType,
) => ExternalTypeImport | undefined;
};type TsParsingMode = "compact" | "fully-expanded" | "named-expanded";
type ExternalTypeImport = {
/**
* Path to the file containing the external type. If the path
* is not specified, the import statement will be omitted, so
* for the generated declarations to be valid, you will have to
* include that yourself or make the specified type available
* in the global scope.
*/
path?: string;
/**
* Name of the type as it is to be used within the generated
* declarations.
*
* If original name is not provided this is also the name of
* the imported type.
*/
typeName: string;
/**
* Name of the type that will be used in the generated import
* statement.
*/
originalName?: string;
/**
* Whether the imported name is a "value" or a "type". If it is
* a "value" it will be referenced with a `typeof` keyword.
*/
valueImport?: boolean;
};
```### Availabla Type's
#### Type.Number
will match any number values and translate to the standard `number` type in
TypeScript.#### Type.Int
will match any integer values and translate to the standard `number` type in
TypeScript. TypeScript does not have any way of distinguishing float and
integers therefore both are assigned the same TypeScript type.#### Type.String
will match any string values and translate to the standard `string` type in
TypeScript.#### Type.StringNumeral
will match any string containing only numeric values and translate to a
`` `${number}` `` type in TypeScript. A value successfully validated with
`StringNumeral` is safe to convert into a number and will never produce a `NaN`
value.#### Type.StringInt
will match any string containing only numbers and translate to a
`` `${number}` `` type in TypeScript. Strings with floating point numbers are
not matched by this type. A value successfully validated with `StringInt` is
safe to convert into a number and will never produce a `NaN` value.#### Type.StringMatching(regex)
will match any string matching the provided regular expression and translate to
a the standard `string` type in TypeScript.#### Type.Boolean
will match any `true` and `false` values and translate to the standard `boolean`
type in TypeScript.#### Type.Symbol
will match any symbolic values and translate to the `symbol` type in TypeScript.
#### Type.Null
will match only `null` value and translate to the standard `null` type in
TypeScript.#### Type.Undefined
will match only `undefined` value and translate to the standard `undefined` type
in TypeScript.#### Type.Function
will match any function and translate to the `Function` type in TypeScript.
#### Type.Unknown
will match any value and translate to the `unknown` type in TypeScript.
#### Type.OneOf(...Type's)
will match any value matching one of the DataType's provided in the arguments
and translate to an TypeScript union type.Example
```ts
const foo = Type.OneOf(Type.String, Type.Number);type T = Infer; // type T = (string | number)
```#### Type.AllOf(...Type's)
will match values matching every DataType provided and translate to a TypeScript
intersection of all those DataType's.Mostly useful to intersect multiple RecordOf's.
Example
```ts
const foo = Type.Record({ foo: string });
const bar = Type.Record({ bar: string });const combined = Type.AllOf(foo, bar);
type T = Infer; // type T = { foo: string; bar: string; }
```#### Type.Array(...Type's)
will match any array which contains only values matching any of the DataType's
provided in the arguments and translate to the `Array<...>` type in TypeScript.Example
```ts
const foo = Type.Array(Type.String, Type.Number);type T = Infer; // type T = (string | number)[]
```#### Type.Record(Record)
will match any object which structure matches the key-value pairs of object
properties and FieldDescriptor's passed to the argument.Example
```ts
const foo = Type.Record({
foo: Type.Boolean,
bar: Type.String,
baz: { type: Type.Number, required: false },
qux: Type.Option(Type.String),
});type T = Infer;
// type T = { foo: boolean, bar: string, baz?: number | undefined; qux?: string | undefined; }
```#### Type.Dict(...Type's)
will match any object which properties match against the provided DataTypes's,
and translates to a Record type in TypeScript.Example
```ts
const dictOfFunctions = Type.Dict(Type.Function);type T = Infer; // type T = Record
```#### Type.Set(...Type's)
will match any Set object which contains only values matching any of the
DataType's provided in the arguments and translate to the `Set<...>` type in
TypeScript.Example
```ts
const foo = Type.Set(Type.String, Type.Number);type T = Infer; // type T = Set
```#### Type.Literal(string | number | boolean)
will match any value that exactly matches the passed argument and translate to
the literal type of that value in TypeScript.Example's
```ts
const foo = Type.Literal("some-string-literal");type T0 = GetDataType; // type T0 = "some-string-literal"
``````ts
const bar = Type.Literal(123);type T1 = GetDataType; // type T1 = 123
``````ts
const baz = Type.Literal(true);type T2 = GetDataType; // type T2 = true
```#### Type.InstanceOf(class)
will match any value that is an instance of the passed class and translate to
the `InstanceType` type of that class in TypeScript.```ts
class FooBar {
// ...
}const foo = Type.InstanceOf(FooBar);
type T = Infer; // type T = InstanceType
```#### Type.Enum(enum)
will match any value that belongs to an TypeScript enum and translate to that
enum type.```ts
enum MyEnum {
A = "A",
B = "B",
}const foo = Type.Enum(MyEnum);
type T = Infer; // type T = MyEnum
const validate = validator(foo);
validate(MyEnum.A); // => true
validate(MyEnum.B); // => true
```#### Type.EnumMember(enum member)
will match any value that equals to the specified TypeScript enum member and
translate to that enum member type.```ts
enum MyEnum {
A = "VALUE_A",
B = "VALUE_B",
}const foo = Type.EnumMember(MyEnum.A);
type T = Infer; // type T = MyEnum.A
const validate = validator(foo);
validate("VALUE_A"); // => true
validate(MyEnum.A); // => true
validate(MyEnum.B); // => false
```#### Type.Recursive(Function)
Allows to define types that reference themselves. The function it accepts should
always return a valid DataType, which the reference provided to that function
will point to.Example
```ts
const Node = Type.Recursive((self) =>
Type.Record({
tag: Type.String,
children: Type.ArrayOf(self),
})
);// this is equivalent to the following type:
type Node = {
tag: string;
children: Node[];
};
```Type definitions given for circular DataTypes via `GetDataType` and validation
methods will not however include infinite recursion as they should. (this does
not affect the runtime validation) Due to the TypeScript limitations, it's
impossible for a inferred type to include a reference to itself, so to get a
usable type we use some TypeScript magic to create a similar type that is
4-levels deep. For the above example the actual type you will get will look like
this:```ts
type Node = {
tag: string;
children: Array<{
tag: string;
children: Array<{
tag: string;
children: Array<{
tag: string;
children: Array;
}>;
}>;
}>;
};
```If you absolutely need to get a type that has infinite recursion, you can use
[toTsType](#totstype) utility to generate TypeScript code which will meet that
need.#### Type.Custom(Function)
will test the data with the provided function, provided function should return a
boolean indicating if the tested value passed the validation, passed function
should also have a type definition that looks like this: `(v: any) => v is T`,
where T is any valid TS type.Example
```ts
const NonEmptyString = Type.Custom(
(v: any): v is string => typeof v === "string" && v.length > 0
);type T = Infer; // type T = string
const validate = validator(NonEmptyString);
validate("foo"); // => true
validate(""); // => false
```### Utility Functions
#### And()
`And()` utility function can combine two Record Type Definitions into one. If
any of the properties between the two combined Type Defs have the same key-name,
the definition of the second one takes priority.```ts
const typeDefOne = Type.Record({
foo: Type.Number,
bar: Type.Number,
});const typeDefTwo = Type.Record({
bar: Type.Array(Type.String),
baz: Type.Boolean,
});const typeDefSum = And(typeDefOne, typeDefTwo);
// typeDefSum = {
// foo: number;
// bar: string[];
// baz: boolean;
// }
```#### Omit()
`Omit()` utility function removes specified keys from a Record Type Definition.
```ts
const typeDefOne = Type.Record({
foo: Type.Number,
bar: Type.Number,
baz: Type.Number,
qux: Type.Number,
});const typeDefOmitted = Omit(typeDefOne, "bar", "qux");
// typeDefOmitted = {
// foo: number;
// baz: number;
// }
```#### Pick()
`Pick()` utility function removes all not specified keys from a Record Type
Definition.```ts
const typeDefOne = Type.Record({
foo: Type.Number,
bar: Type.Number,
baz: Type.Number,
qux: Type.Number,
});const typeDefPick = Pick(typeDefOne, "bar", "qux");
// typeDefPick = {
// bar: number;
// qux: number;
// }
```#### Partial()
`Partial()` utility type makes all the Record's Type Definition keys optional.
```ts
const typeDefOne = Type.Record({
foo: Type.Number,
bar: Type.String,
baz: Type.Array(Type.Number),
});const typeDefPartial = Partial(typeDefOne);
// typeDefPartial = {
// foo?: number | undefined;
// bar?: string | undefined;
// baz?: number[] | undefined;
// }
```#### Required()
`Required()` utility type makes all the Record's Type Definition keys to be
required (vs optional).```ts
const typeDefOne = Type.Record({
foo: { type: Type.Number, required: false },
bar: { type: Type.String, required: false },
baz: { type: Type.Array(Type.Number), required: false },
});const typeDefRequired = Required(typeDefOne);
// typeDefRequired = {
// foo: number;
// bar: string;
// baz: number[];
// }
```#### Exclude()
`Exclude()` utility function removes Type Definitions from an Type Def union.
```ts
const typeDefOne = Type.OneOf(
Type.String,
Type.Number,
Type.Boolean,
);const typeDefExcluded = Exclude(typeDefOne, DataType.Number);
// typeDefExcluded = string | boolean;
```## Metadata
Each Type can have metadata attached to it, this metadata can be used to
provide additional information about the data type, for example, you can attach
a description to a Type, or a title, or format.**Metadata is completely ignored by the validation process**
### Assign Metadata
```ts
import { Type } from "dilswer";const UserNameDT = Type.String.setTitle("User Name").setDescription(
"The user's name.",
);const User = Type.Record({
name: UserNameDT,
id: Type.String.setTitle("User ID").setFormat("uuid"),
friends: Type.Array(Type.String).setDescription(
"A list of the user's friends names.",
),
})
.setTitle("User")
.setDescription(
"A user object. Contains the user's name, id and friends list.",
);
```### Read Metadata
```ts
import { Type, getMetadata } from "dilswer";const userNameMetadata = getMetadata(UserNameDT);
// userNameMetadata = {
// title: "User Name",
// description: "The user's name.",
// }const userMetadata = getMetadata(User);
// userMetadata = {
// title: "User",
// description: "A user object. Contains the user's name, id and friends list.",
// }
```### Metadata and JSON Schema's
Metadata is also used when generating JSON Schema, if a DataType has a title,
description or format, it will be included in the generated JSON Schema.```ts
import { Type, toJsonSchema } from "dilswer";const UserDT = Type.Record({
name: Type.String.setTitle("User Name").setDescription(
"The user's name.",
),
id: Type.String.setTitle("User ID").setFormat("uuid"),
friends: Type.Array(Type.String).setDescription(
"A list of the user's friends names.",
),
})
.setTitle("User")
.setDescription(
"A user object. Contains the user's name, id and friends list.",
);const jsonSchema = toJsonSchema(UserDT);
// jsonSchema = {
// title: "User",
// description: "A user object. Contains the user's name, id and friends list.",
// properties: {
// name: {
// type: "string",
// title: "User Name",
// description: "The user's name.",
// },
// id: {
// type: "string",
// title: "User ID",
// format: "uuid",
// },
// friends: {
// type: "array",
// items: {
// type: "string",
// },
// },
// },
// required: ["name", "id", "friends"],
// }
```### Parsing
Dilswer data types can be easily parsed into any arbitrary data structure via
`parseWith` function.This function takes a `visitor` object, which should contain a `visit` method,
this method should generate a node of the new, desired data structure.This method is also used internally by `toJsonSchema` and `toTsType` functions.
You can see the implementation of these functions in the source code
[here](./src/json-schema-parser/to-json-schema.ts) and
[here](./src/ts-type-generator/to-ts-type.ts).#### Example
```ts
import { AnyType, Type, parseWith } from "dilswer";// Define how the new structure should look like
type TypeNode = {
typeName: string;
children?: TypeNode[] | Record;
};// Create a visitor which will be used to translate Dilswer's data types into `Node`s
const visitor = {
visit(
type: AnyType,
children?: TypeNode[] | RecordOfVisitChild[],
): TypeNode {
switch (type.kind) {
case "simple":
return { typeName: type.simpleType };
case "record":
return {
typeName: "record",
children: children
? Object.fromEntries(
(children as RecordOfVisitChild[]).map(
({ propertyName, child }) => [propertyName, child],
),
)
: undefined,
};
default:
return { typeName: type.kind, children: children as Node[] };
}
},
};// use the visitor on a Dilser data type
const type = Type.Record({
foo: Type.String,
bar: Type.Array(Type.Number),
baz: Type.OneOf(Type.String, Type.Number),
});const nodeTree = parseWith(visitor, type);
```##### Example output
```json
{
"typeName": "record",
"children": {
"foo": {
"typeName": "string"
},
"bar": {
"typeName": "array",
"children": [
{
"typeName": "number"
}
]
},
"baz": {
"typeName": "union",
"children": [
{
"typeName": "string"
},
{
"typeName": "number"
}
]
}
}
}
```