Skip to content

ncpa0/Dilswer

Repository files navigation

Dilswer

GitHub GitHub Workflow Status npm Libraries.io dependency status for latest release GitHub last commit

Blazingly fast data validation library with TypeScript integration.

Table of Contents

  1. Quick Start
    1. Compile a Validator
    2. Create Type Definitions
    3. Infer TypeScript Types
    4. Get Validation Details
    5. Assertion Function
    6. Standard Schema Support
  2. Available Types
    1. Primitives
    2. Compound Types
    3. Special Types
  3. Utility Functions
  4. Metadata
  5. JSON Schema Generation
  6. TypeScript Type Generation
  7. Parsing

Quick Start

Compile a Validator

The fastest way to validate data is using the compile() function:

import { compile, Type } from "dilswer";

// Compile a validation function from a type definition
const isString = compile(Type.String);

isString("hello"); // true
isString(123);    // false

For complex types:

import { compile, Type } from "dilswer";

const PersonValidator = compile(
  Type.Record({
    id: Type.String,
    name: Type.String,
    age: Type.Number,
    email: Type.Option(Type.String),
    friends: Type.Option(Type.Array(Type.String)),
  })
);

// Valid data
PersonValidator({
  id: "abc123",
  name: "Alice",
  age: 30,
}); // true

// Invalid data
PersonValidator({
  name: "Bob",
  age: "not a number", // age should be a number
}); // false

Performance note: Compiled validators are extremely fast but provide minimal error messages. For detailed error information, use the validator() function instead.

Create Type Definitions

import { Type } from "dilswer";

// Define a record type with various field types
const PersonDataType = Type.Record({
  id: Type.String,
  name: Type.String,
  age: Type.Number,
  email: Type.Option(Type.String),
  friends: Type.Option(Type.Array(Type.String)),
});

// The equivalent TypeScript type would be:
// {
//   id: string;
//   name: string;
//   age: number;
//   email?: string;
//   friends?: string[];
// }

Infer TypeScript Types from Dilswer Definitions

import { Infer, compile, Type } from "dilswer";

const PersonDataType = Type.Record({
  id: Type.String,
  name: Type.String,
  age: Type.Number,
});

type Person = Infer<typeof PersonDataType>;

// type Person = {
//   id: string;
//   name: string;
//   age: number;
// }

// Works with compiled validators too
const PersonValidator = compile(PersonDataType);
// PersonValidator(v: any): v is {
//   id: string;
//   name: string;
//   age: number;
// }

Get Validation Details

If you need detailed information about validation failures, use validator() with the details option:

import { validator, Type } from "dilswer";

const isPerson = validator(PersonDataType, { details: true });

const result = isPerson({ name: "Alice" });

if (result.success) {
  console.log(result.value.name);
} else {
  console.error("Validation failed:", result.error.message);
  // Access the path where validation failed:
  console.error("Failed at:", result.error.fieldPath);
  console.error("Details:", result.error.details());
  console.error("DetailsObject:", JSON.stringify(result.error.detailsJson()));
}

details() returns a string containing information about the error more detailed than just the error message.

Details Examples

ValidationError: not an array
Path: $.foo.bar
Expected: ArraySchema[ PrimitiveSchema[ string ] ]
Got: object
AggregateValidationError: does not match any of the types in the union
Path: $
Errors:
  ValidationError: not an object
  Path: $
  Expected: RecordSchema[ type=LiteralSchema[ a ]; foo=PrimitiveSchema[ number ]; bar=PrimitiveSchema[ string ] ]
  Got: object

  ValidationError: not an object
  Path: $
  Expected: RecordSchema[ type=LiteralSchema[ b ]; foo=PrimitiveSchema[ string ]; baz=PrimitiveSchema[ string ] ]
  Got: object

  ValidationError: not an object
  Path: $
  Expected: RecordSchema[ type=LiteralSchema[ c ]; foo=PrimitiveSchema[ boolean ]; qux=PrimitiveSchema[ string ] ]
  Got: object

ValidateWith

A shorthand for validate with details.

import { validateWith, Type } from "dilswer";

const result = validateWith(Type.String, value);

if (result.success) {
  console.log(result.value);
} else {
  console.error("Validation failed:", result.error.message);
}

Assertion Function

The assertion function throws an error if validation fails, which is useful for early returns:

import { assertType, Type } from "dilswer";

function greet(value: unknown) {
  try {
    assertType(Type.String, value);
    value.toUpperCase(); // value is now typed as string
  } catch (error) {
    console.error("Value is not a string");
  }
}

Standard Schema Support

Dilswer schemas are compatible with libraries that support the Standard Schema specification, such as tRPC and OpenAPI.

import { initTRPC } from "@trpc/server";
import { compile, Type } from "dilswer";

const t = initTRPC.create();

// For maximum performance, compile schemas before passing them to procedures
const router = t.router({
  greeting: t.procedure
    .input(
      Type.Record({
        name: Type.String,
      }).compile(),
    )
    .query(async ({ input }) => {
      return `Hello, ${input.name}!`;
    }),
});

Uncompiled schemas also work but may have slightly lower performance:

const router = t.router({
  greeting: t.procedure
    .input(
      Type.Record({
        name: Type.String,
      })
    )
    .query(async ({ input }) => {
      return `Hello, ${input.name}!`;
    }),
});

Available Types

Primitives

Type.String

Matches any string value.

const validator = compile(Type.String);
validator("hello"); // true
validator(123);     // false

Type.Number

Matches any numeric value (including floats and integers).

const validator = compile(Type.Number);
validator(1.5);    // true
validator(42);    // true
validator("123"); // false

Type.Int

Matches only integer values.

const validator = compile(Type.Int);
validator(42);    // true
validator(1.5);   // false

Type.Boolean

Matches true and false values.

const validator = compile(Type.Boolean);
validator(true);  // true
validator(false); // true
validator(1);     // false

Type.Symbol

Matches symbol values.

const validator = compile(Type.Symbol);
validator(Symbol("test")); // true
validator("sym");          // false

Type.Null

Matches only the null value.

const validator = compile(Type.Null);
validator(null);     // true
validator(undefined); // false

Type.Undefined

Matches only the undefined value.

const validator = compile(Type.Undefined);
validator(undefined); // true
validator(null);      // false

Type.Function

Matches any function.

const validator = compile(Type.Function);
validator(() => {});     // true
validator(async () => {}); // true
validator("not a fn");   // false

Type.Unknown

Matches any value.

const validator = compile(Type.Unknown);
validator("anything");  // true
validator(123);         // true
validator(null);       // true

String Variations

// Matches strings containing only numeric characters
// Safe to convert to number without producing NaN
const numeralValidator = compile(Type.String.Float);
numeralValidator("123");    // true
numeralValidator("12.5");   // true (float strings match)
numeralValidator("abc");    // false

// Matches strings containing only integer characters
const intValidator = compile(Type.String.Int);
intValidator("123");    // true
intValidator("12.5");   // false

// Matches strings containing only positive integer characters
const positiveIntValidator = compile(Type.String.Int.positive());
positiveIntValidator("123");    // true
positiveIntValidator("-1");     // false

// Matches strings that conform to a regular expression
const hexValidator = compile(Type.String.matching(/^[0-9a-f]+$/i));
hexValidator("abc123"); // true
hexValidator("xyz");    // false

// Matches strings with length contraints
const lenValidator = compile(Type.String.len({ min: 2, max: 5 }));
lenValidator("abc");      // true
lenValidator("x");        // false
lenValidator("12345678"); // false

Compound Types

Type.Record

Matches objects with specified properties.

const PersonValidator = compile(
  Type.Record({
    name: Type.String,
    age: Type.Number,
    active: Type.Option(Type.Boolean),
  })
);

PersonValidator({ name: "Alice", age: 30 });          // true
PersonValidator({ name: "Bob", age: 25, active: true }); // true
PersonValidator({ name: "Carol" });                     // false (age is required)

Type.Array

Matches arrays containing elements of specified types.

const ArrayValidator = compile(Type.Array(Type.String, Type.Number));

ArrayValidator(["a", "b", 1, 2]); // true
ArrayValidator(["a", "b", "c"]);  // true
ArrayValidator([1, 2, 3]);         // true
ArrayValidator("not an array");    // false

Type.Tuple

Matches arrays with a fixed number of elements of specific types.

const TupleValidator = compile(
  Type.Tuple(Type.String, Type.Number, Type.Boolean)
);

TupleValidator(["hello", 42, true]);  // true
TupleValidator(["hello", 42]);         // false (wrong length)
TupleValidator(["hello", "world"]);    // false (second element is not a number)

Type.Set

Matches Set objects containing elements of specified types.

const SetValidator = compile(Type.Set(Type.String));

SetValidator(new Set(["a", "b", "c"])); // true
SetValidator(new Set([1, 2, 3]));        // false
SetValidator(["a", "b"]);                // false (not a Set)

Type.Dict

Matches objects where all values match the specified types.

const DictValidator = compile(Type.Dict(Type.String));

DictValidator({ a: "x", b: "y" });    // true
DictValidator({ a: 1, b: 2 });          // false
DictValidator("not an object");         // false

Type.OneOf

Matches values that match any one of the provided types (union).

const UnionValidator = compile(Type.OneOf(Type.String, Type.Number));

UnionValidator("hello"); // true
UnionValidator(123);     // true
UnionValidator(true);   // false

Type.AllOf

Matches values that match all of the provided types (intersection).

const A = Type.Record({ foo: Type.String });
const B = Type.Record({ bar: Type.Number });

const IntersectedValidator = compile(Type.AllOf(A, B));

IntersectedValidator({ foo: "hello", bar: 42 }); // true
IntersectedValidator({ foo: "hello" });           // false (missing bar)

Type.Literal

Matches exact values.

const TrueValidator = compile(Type.Literal(true));
const StrValidator = compile(Type.Literal("hello"));
const NumValidator = compile(Type.Literal(42));

TrueValidator(true);            // true
TrueValidator(false);           // false
StrValidator("hello");          // true
StrValidator("world");          // false
NumValidator(42);               // true
NumValidator(100);              // false

Type.Enum

Matches TypeScript enum values.

enum MyEnum {
  A = "A",
  B = "B",
}

const EnumValidator = compile(Type.Enum(MyEnum));

EnumValidator(MyEnum.A); // true
EnumValidator(MyEnum.B); // true
EnumValidator("C");      // false

Type.EnumMember

Matches a specific enum member.

enum MyEnum {
  A = "VALUE_A",
  B = "VALUE_B",
}

const MemberValidator = compile(Type.EnumMember(MyEnum.A));

MemberValidator(MyEnum.A);    // true
MemberValidator("VALUE_A");   // true (string value matches)
MemberValidator(MyEnum.B);     // false

Type.InstanceOf

Matches instances of a class.

class User {
  constructor(public name: string) {}
}

const UserValidator = compile(Type.InstanceOf(User));

UserValidator(new User("Alice")); // true
UserValidator({ name: "Bob" });    // false

Type.Custom

Matches values using a custom validation function. The function must be a type guard ((v: any) => v is T).

const NonEmptyString = Type.Custom(
  (v: any): v is string => typeof v === "string" && v.length > 0
);

const NonEmptyValidator = compile(NonEmptyString);

NonEmptyValidator("hello"); // true
NonEmptyValidator("");      // false
NonEmptyValidator(123);     // false

Special Types

Type.Recursive

Defines types that reference themselves.

const Node = Type.Recursive(self =>
  Type.Record({
    tag: Type.String,
    children: Type.Array(self),
  })
);

const NodeValidator = compile(Node);

NodeValidator({
  tag: "div",
  children: [
    { tag: "span", children: [] },
    { tag: "p", children: [] },
  ],
}); // true

Type.Option

Makes a field optional (can be undefined).

const Validator = compile(
  Type.Record({
    name: Type.String,
    email: Type.Option(Type.String),
  })
);

Validator({ name: "Alice" });              // true
Validator({ name: "Bob", email: "bob@example.com" }); // true
Validator({ name: "Carol", email: undefined });        // true
Validator({ email: "dan@example.com" });              // false (name is required)

Utility Functions

And()

Combines two Record types. Properties from the second type override conflicting properties from the first.

const TypeA = Type.Record({
  foo: Type.String,
  bar: Type.Number,
});

const TypeB = Type.Record({
  bar: Type.Boolean,
  baz: Type.String,
});

const Combined = compile(And(TypeA, TypeB));
// Validates: { foo: string, bar: boolean, baz: string }

Omit()

Removes specified keys from a Record type.

const Original = Type.Record({
  foo: Type.String,
  bar: Type.Number,
  baz: Type.Boolean,
});

const Simplified = compile(Omit(Original, "bar", "baz"));
// Validates: { foo: string }

Pick()

Keeps only the specified keys from a Record type.

const Original = Type.Record({
  foo: Type.String,
  bar: Type.Number,
  baz: Type.Boolean,
});

const Selected = compile(Pick(Original, "foo", "bar"));
// Validates: { foo: string, bar: number }

Partial()

Makes all properties of a Record type optional.

const Original = Type.Record({
  name: Type.String,
  age: Type.Number,
});

const OptionalProps = compile(Partial(Original));
// Validates: { name?: string, age?: number }

Required()

Makes all properties of a Record type required (removes optionality).

const Original = Type.Record({
  name: Type.Option(Type.String),
  age: Type.Option(Type.Number),
});

const RequiredProps = compile(Required(Original));
// Validates: { name: string, age: number }

Exclude()

Removes types from a union.

const Union = Type.OneOf(Type.String, Type.Number, Type.Boolean);

const StringOrBool = compile(Exclude(Union, Type.Number));
// Validates: string | boolean

Metadata

Each type can have metadata attached to it, such as titles, descriptions, and formats. Metadata is ignored during validation but is used by code generators.

Assign Metadata

const UserDT = Type.Record({
  name: Type.String.meta.title("User Name").meta.description("The user's full name"),
  email: Type.String.meta.title("Email").meta.format("email"),
}).meta.title("User")
  .meta.description("A user account with contact information");

Read Metadata

import { getMetadata, Type } from "dilswer";

const EmailDT = Type.String.meta.title("Email Address").meta.format("email");

const metadata = getMetadata(EmailDT);
// metadata = {
//   title: "Email Address",
//   format: "email",
// }

JSON Schema Generation

Generate JSON Schema from Dilswer types:

import { toJsonSchema, Type } from "dilswer";

const UserSchema = Type.Record({
  name: Type.String.meta.title("Name"),
  age: Type.Number,
});

const schema = toJsonSchema(UserSchema);
// schema = {
//   type: "object",
//   properties: {
//     name: { type: "string", title: "Name" },
//     age: { type: "number" },
//   },
//   required: ["name", "age"],
// }

ParseToJsonSchemaOptions

type ParseToJsonSchemaOptions = {
  /** How to handle types without JSON Schema equivalents (Sets, Symbols, etc.) */
  incompatibleTypes?: "throw" | "omit" | "set-as-any";
  /** Whether to allow additional properties in records */
  additionalProperties?: boolean;
  /** Custom parsers for specific types */
  customParser?: {
    Set?: (schemas: JSONSchema6[], type: SetType, options: ParseToJsonSchemaOptions) => JSONSchema6 | undefined;
    Custom?: (fn: Function, type: CustomType, options: ParseToJsonSchemaOptions) => JSONSchema6 | undefined;
    Undefined?: (type: BasicType, options: ParseToJsonSchemaOptions) => JSONSchema6 | undefined;
    Symbol?: (type: BasicType, options: ParseToJsonSchemaOptions) => JSONSchema6 | undefined;
    Function?: (type: BasicType, options: ParseToJsonSchemaOptions) => JSONSchema6 | undefined;
  };
};

TypeScript Type Generation

Generate TypeScript type definitions from Dilswer types:

import { toTsType, Type } from "dilswer";

const UserDT = Type.Record({
  name: Type.String,
  age: Type.Number,
}).meta.title("User");

const tsDefinition = toTsType(UserDT);
// Output:
// export type User = {
//   name: string;
//   age: number;
// };

TsParsingOptions

type TsParsingOptions = {
  /** How to structure the output type */
  mode?: "compact" | "fully-expanded" | "named-expanded";
  /** Which types to export */
  exports?: "main" | "named" | "all" | "none";
  /** Generate type declarations */
  declaration?: boolean;
  /** How to handle duplicate names */
  onDuplicateName?: "error" | "rename";
  /** Custom import paths for external types */
  getExternalTypeImport?: (type: EnumType | EnumMemberType | InstanceOfType | CustomType | FunctionType) => ExternalTypeImport | undefined;
};

Parsing

Dilswer types can be parsed into custom data structures using parseWith():

import { parseWith, Type } from "dilswer";

type TypeNode = {
  typeName: string;
  children?: TypeNode[] | Record<string, TypeNode>;
};

const visitor = {
  visit(type: AnyType, children?: any): TypeNode {
    switch (type.kind) {
      case "simple":
        return { typeName: type.simpleType };
      case "record":
        return {
          typeName: "record",
          children: children ? Object.fromEntries(children.map(({ propertyName, child }) => [propertyName, child])) : undefined,
        };
      default:
        return { typeName: type.kind, children: children as TypeNode[] };
    }
  },
};

const MyType = Type.Record({
  foo: Type.String,
  bar: Type.Array(Type.Number),
});

const tree = parseWith(visitor, MyType);
Example output
{
  "typeName": "record",
  "children": {
    "foo": { "typeName": "string" },
    "bar": { "typeName": "array", "children": [{ "typeName": "number" }]}
  }
}

About

Blazingly fast data validation library with TypeScript integration.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors