Type Safe Object Notation & Validation

Overview

tson

Type Safe Object Notation & Validation

Test and Lint codecov GitHub code size in bytes GitHub GitHub Sponsors Twitch Status

๐Ÿ“Œ Work in Progress, not ready for production...

Features

  • ๐Ÿงฑ Functional
  • ๐Ÿ”ท Immutable
  • โœ… Well tested

Why?

After a contribution to the tRPC project, I wanted to understand more deeply the use of generics and inference in TypeScript. I needed a challenge so I set myself the goal of coding my own schema validation library. This library is heavily inspired by Zod (I try to provide the same API) but in order to avoid cloning it, I challenged myself to not use any classes.

Install

pnpm add @skarab/tson

yarn and npm also works

ES and CommonJS module

import { t } from "tson";
const { t } = require("tson");

Examples

import { t } from "tson";

const name = t.string();

name.parse("nyan"); // return "nyan"
name.parse(42); // throw TypeCheckError
import { t } from "tson";

const user = t.object({
  name: t.string(),
  age: t.number(),
  admin: t.boolean(),
});

user.parse({ name: "nyan", age: 42, admin: true });

type User = t.infer<typeof user>;
// { name: string, age: number, admin: boolean }

Strict mode

TypeScript

It is strongly recommended to activate the strict mode of TypeScript which will activate all checking behaviours that results in stronger guarantees of the program's correctness.

tson

By default tson parse objects in STRICT mode, this means that all undefined values in a scheme will be considered as an error. You can change this behaviour globally or locally, the procedure is documented here.

Table of contents

API

First level types

Primitive types

t.string();
t.number();
t.bigint();
t.boolean();
t.symbol();
t.date();

Numbers types

t.nan();
t.finite();
t.infinity();
t.integer(); // Alias: int()
t.unsignedNumber(); // Alias: unumber()
t.unsignedInteger(); // Alias: uinteger(), uint()

Empty types

t.undefined();
t.null();
t.void();

Catch-all types

t.any();
t.unknown();

Never type

t.never();

literal(value)

const life = t.literal(42);
const love = t.literal(true);
const name = t.literal("nyan");

life.value; // type => 42

array(type)

const arr1 = t.array(t.string()); // string[]
const arr2 = t.array(t.boolean()); // boolean[]

tuple(...type)

const tpl = t.tuple(t.string(), t.number(), t.string()); // [string, number, string]

tuple(type[])

const tpl = t.tuple([t.string(), t.number(), t.string()]); // [string, number, string]

๐Ÿ’” The following code does not work, TypeScript can not infer array values properly. Use the as const workaround to do this.

const types = [t.string(), t.number(), t.string()];
const tpl = t.tuple(types); // [string, number, string]

tuple(type[] as const)

const types = [t.string(), t.number(), t.string()] as const;
const tpl = t.tuple(types); // [string, number, string]

object(schema)

const user = t.object({
  name: t.string(),
  age: t.number(),
  admin: t.boolean(),
});

type User = t.infer<typeof user>;
// { name: string, age: number, admin: boolean }

object(schema, mode)

By default tson parse objects in STRICT mode, but you can change the mode globally or locally.

There are three modes:

  • STRICT: Will raise an error if a key is not defined in the schema.
  • STRIP: Strips undefined keys from the result and does not raise an error.
  • PASSTHROUGH: Keeps undefined keys and does not raise an error.

Change the default mode globally.

t.defaultSettings.objectTypeMode = t.ObjectTypeMode.STRIP;

Change the mode locally.

const schema = { a: t.string(), b: t.string() };
const input = { a: "a", b: "b", c: "c" };

const user = t.object(schema, t.ObjectTypeMode.STRICT);
user.parse(input); // throws an TypeParseError

const user = t.object(schema, t.ObjectTypeMode.STRIP);
user.parse(input); // { a: string, b: string }

const user = t.object(schema, t.ObjectTypeMode.PASSTHROUGH);
user.parse(input); // { a: string, b: string, c: string }

object helpers

.strict()

t.object(schema).strict();
// same as
t.object(schema, t.ObjectTypeMode.STRICT);

.strip()

t.object(schema).strip();
// same as
t.object(schema, t.ObjectTypeMode.STRIP);

.passthrough()

t.object(schema).passthrough();
// same as
t.object(schema, t.ObjectTypeMode.PASSTHROUGH);

union(...type)

const uni = t.union(t.string(), t.number()); // string | number

union(type[])

const tpl = t.union([t.string(), t.number(), t.string()]); // string | number

๐Ÿ’” The following code does not work, TypeScript can not infer array values properly. Use the as const workaround to do this.

const types = [t.string(), t.number(), t.string()];
const tpl = t.union(types); // string | number

union(type[] as const)

const types = [t.string(), t.number(), t.string()] as const;
const tpl = t.union(types); // string | number

optional(type)

const user = t.object({
  name: t.string(),
  age: t.optional(t.number()),
});
// { name: string, age?: number }

enum(...string)

const myEnum = t.enum("UP", "DOWN", "LEFT", "RIGHT");

Access enum properties

myEnum.enum.UP; // === "UP"
myEnum.enum.PLOP; // error: PLOP does not exists
myEnum.enum.DOWN = "prout"; // error: it is read-only

(property) enum: {
  readonly UP: "UP";
  readonly DOWN: "DOWN";
  readonly LEFT: "LEFT";
  readonly RIGHT: "RIGHT";
}

Access enum values

myEnum.options[1]; // === "DOWN"

(property) options: ["UP", "DOWN", "LEFT", "RIGHT"]

Test enum values

myEnum.parse(myEnum.enum.LEFT); // => "LEFT"
myEnum.parse("LEFT"); // => "LEFT"
myEnum.parse("2"); // => "LEFT"
myEnum.parse(2); // => "LEFT"
myEnum.parse("PLOP"); // error: expected '0|1|2|3|UP|DOWN|LEFT|RIGHT' got 'string'

Infer enum type

type MyEnum = t.infer<typeof myEnum>; // => "UP" | "DOWN" | "LEFT" | "RIGHT"

function move(direction: MyEnum) {
  // direction === "DOWN"
}

move(myEnum.enum.DOWN);

enum(string[])

const myEnum = t.enum(["UP", "DOWN", "LEFT", "RIGHT"]);

๐Ÿ’” The following code does not work, TypeScript can not infer array values properly. Use the as const workaround to do this.

const values = ["UP", "DOWN", "LEFT", "RIGHT"];
const myEnum = t.enum(values);

enum(string[] as const)

const myEnum = t.enum(["UP", "DOWN", "LEFT", "RIGHT"] as const);
const values = ["UP", "DOWN", "LEFT", "RIGHT"] as const;
const myEnum = t.enum(values);

enum(object)

const myEnum = t.enum({ UP: "UP", DOWN: "DOWN", LEFT: 42, RIGHT: 43 });

๐Ÿ’” The following code does not work, TypeScript can not infer object properties properly. Use the as const workaround to do this.

const values = { UP: "UP", DOWN: "DOWN", LEFT: 42, RIGHT: 43 };
const myEnum = t.enum(values);

enum(object as const)

const values = { UP: "UP", DOWN: "DOWN", LEFT: 42, RIGHT: 43 } as const;
const myEnum = t.enum(values);

enum(enum)

enum MyEnum {
  UP = "UP",
  DOWN = "DOWN",
  LEFT = 42,
  RIGHT,
}

const myEnum = t.enum(MyEnum);

nativeEnum(enum)

Alias: enum(enum)

enum MyEnum {
  UP = "UP",
  DOWN = "DOWN",
  LEFT = 42,
  RIGHT,
}

const myEnum = t.nativeEnum(MyEnum);

instanceof(type)

class MyClass {}

const instance = new MyClass();

t.instanceof(MyClass).parse(instance); // passes
t.instanceof(MyClass).parse("nyan"); // fail

date()

t.date().parse(new Date()); // passes
t.date().parse("2022-01-12T00:00:00.000Z"); // passes
t.date().parse("not a string date"); // fail

record(type)

t.record(t.string()); // { [x: string]: string }
t.record(t.number()); // { [x: string]: number }
t.record(t.date()); // { [x: string]:  Date }

set(type)

Testing a single type on the entire set

t.set(t.string()); // Set<string>

Testing a union of types on the entire set

t.set(t.union(t.string(), t.boolean(), t.string())); // Set<string|boolean>

set(...type)

Same as tuple(...type) but test if the input is an instance of Set.

set([type, ...type])

Testing a tuple of types on the Set

t.set(t.string(), t.boolean(), t.string()); // Set<[string, boolean, string]>
t.set([t.string(), t.boolean(), t.string()]); // Set<[string, boolean, string]>

map(keyType, valueType)

t.map(t.string(), t.number()); // Map<string, number>
t.map(t.date(), t.string()); // Map<Date, string>

map(schema)

Same as object(schema) but test if the input is an instance of Map.

const map = new Map();

t.map({ name: t.string(), size: t.string() }).parse(map);

promise(type)

const promise = t.promise(t.number());

await promise.parse(Promise.resolve(42)); // resolve: 42
await promise.parse(Promise.resolve("42")); // reject: expected 'number' got 'string'
await promise.parse(42); // reject: expected 'Promise' got 'number'

function()

const func = t.function();

type Func = t.infer<typeof func>; // () => void

function(args)

const func = t.function([t.string(), t.number()]);

type Func = t.infer<typeof func>; // (arg_0: string, arg_1: number) => void

function(args, returns)

const func = t.function([t.string()], t.boolean());

type Func = t.infer<typeof func>; // (arg_0: string) => boolean

function(args, returns, implement)

const args = [t.string(), t.boolean()] as const;

const returns = t.union(t.string(), t.number());

const func = t.function(args, returns, (input, toInt) => {
  // input type is string and toInt type is boolean
  return toInt ? parseInt(input) : input.toUpperCase();
});

type Func = t.infer<typeof func>; // (arg_0: string, arg_1: boolean) => string | number

preprocess(filter, type)

If you want to modify the input before it is parsed you can use the preprocess type as follows.

const toString = t.preprocess((input) => String(input), t.string());

toString.parse("42"); // => "42"
toString.parse(42); // => "42"

postprocess(filter, type)

If you want to modify the output after it is parsed you can use the postprocess type as follows.

const postprocess = t.postprocess((input) => input + 2, t.number());

postprocess.parse(40); // => 42
postprocess.parse("42"); // throws: "expected 'number' got 'string'"

postprocess(filter, inputType, outputType)

If you want to modify the output after it is parsed you can use the postprocess type as follows.

const postprocess = t.postprocess(
  (input) => String(input),
  t.number(),
  t.string(),
);

postprocess.parse(40); // => "42"
postprocess.parse("42"); // => throws: "expected 'number' got 'string'"

Type helpers

safeParse(input)

If you want to avoid the parse method throws an error you can use the .safeParse() method instead.

t.bigint().safeParse(42n);
// => { success: true, data: 42n }

t.bigint().safeParse(42);
// => {
//   "error": [TypeParseError: expected 'bigint|undefined' got 'number'],
//   "success": false,
// }

optional()

t.bigint().optional(); // => bigint | undefined

// same as
t.optional(t.bigint());

preprocess()

t.string().preprocess((input) => String(input));

// same as
t.preprocess((input) => String(input), t.string());

postprocess()

Alias: .transform()

t.number().postprocess((input) => input + 2);

// same as
t.postprocess((input) => input + 2, t.number());

Contributing ๐Ÿ’œ

See CONTRIBUTING.md

You might also like...

A next-gen framework for type-safe command-line applications

Zors ๐Ÿฅ‡ Next-gen framework for building modern, type-safe command-line applications. ๐Ÿ“ฆ Tiny (zero dependencies) ๐Ÿ’ก Runtime agonistic (supports both D

Dec 1, 2022

A compiled-away, type-safe, readable RegExp alternative

๐Ÿฆ„ magic-regexp A compiled-away, type-safe, readable RegExp alternative โœจ Changelog ๐Ÿ“– Documentation โ–ถ๏ธ Online playground Features Runtime is zero-dep

Jan 8, 2023

A fully type-safe and lightweight internationalization library for all your TypeScript and JavaScript projects.

A fully type-safe and lightweight internationalization library for all your TypeScript and JavaScript projects.

๐ŸŒ typesafe-i18n A fully type-safe and lightweight internationalization library for all your TypeScript and JavaScript projects. Advantages ๐Ÿค lightwe

Jan 4, 2023

A script that combines a folder of SVG files into a single sprites file and generates type definitions for safe usage.

remix-sprites-example A script that combines a folder of .svg files into a single sprites.svg file and type definitions for safe usage. Technical Over

Nov 9, 2022

A functional, immutable, type safe and simple dependency injection library inspired by angular.

func-di English | ็ฎ€ไฝ“ไธญๆ–‡ A functional, immutable, type safe and simple dependency injection library inspired by Angular. Why func-di Installation Usage

Dec 11, 2022

๐Ÿ‘ฉโ€๐ŸŽค Headless, type-safe, UI components for the next generation Web3.Storage APIs.

๐Ÿ‘ฉโ€๐ŸŽค Headless, type-safe, UI components for the next generation Web3.Storage APIs.

Headless, type-safe, UI components for the next generation Web3.Storage APIs. Documentation beta.ui.web3.storage Examples React Sign up / Sign in Sing

Dec 22, 2022

Type safe library for interacting with Mindbody's Public API (v6) and Webhooks

Mindbody API Type safe library for interacting with Mindbody's Public API (v6) and Webhooks โš ๏ธ Read before installing This library is typed according

Dec 9, 2022

A fully type-safe and lightweight way of using exceptions instead of throwing errors

๐Ÿ›ก๏ธ exceptionally A fully type-safe and lightweight way of using exceptions instead of throwing errors ๐Ÿฆบ fully typesafe ๐Ÿค lightweight (209 bytes) ๐Ÿƒ

Jan 4, 2023

Type-safe session for all Astro SSR project

Astro Session Why use Astro Session? When building server application with Astro, you will often need session system to identify request coming from t

Dec 19, 2022
Releases(v1.5.1)
Owner
In JavaScript We Trust // Mangeur de code
null
A lightweight (~850 B) library for easy mac/window shortcut notation. kbd-txt convert shortcut text depending on the type of OS (window/linux/mac).

kbd-txt A lightweight (~850 B) library for easy mac/window shortcut notation. kbd-txt convert shortcut text depending on the type of OS (window/linux/

Minung Han 6 Jan 1, 2023
100% type-safe query builder for node-postgres :: Generated types, call any function, tree-shakable, implicit type casts, and more

โš ๏ธ This library is currently in alpha. Contributors wanted! tusken Postgres client from a galaxy far, far away. your database is the source-of-truth f

alloc 54 Dec 29, 2022
A string of four operations of the library, can solve the js digital calculation accuracy of scientific notation and formatting problems, support for thousands of decimal point formatting output operations

A string of four operations of the library, can solve the js digital calculation accuracy of scientific notation and formatting problems, support for thousands of decimal point formatting output operations

null 10 Apr 6, 2022
Decompose algorithms in commutator notation.

Commutator Decompose algorithms in commutator notation. Let be any group. If , then the commutator of and is the element . The expression denotes the

nbwzx 5 Dec 15, 2022
RenderIf is a function that receives a validation as a parameter, and if that validation is true, the content passed as children will be displayed. Try it!

RenderIf RenderIf is a function that receives a validation as a parameter, and if that validation is true, the content passed as children will be disp

Oscar Cornejo Aguila 6 Jul 12, 2022
Runtime object parsing and validation with static TypeScript typing.

TypeParse Runtime object transformation, parsing and validation with inferred static TypeScript typing. Install Using npm npm install typeparse Using

Kenneth Herrera 4 May 5, 2022
Build type-safe web apps with PureScript.

PUX Build type-safe web applications with PureScript. Documentation | Examples | Chat Pux is a PureScript library for building web applications. Inter

Alex Mingoia 567 Jun 18, 2022
A zero-dependency, buildless, terse, and type-safe way to write HTML in JavaScript.

hdot A sensible way to write HTML in JavaScript. Type-safe. Helps you follow the HTML spec. Terse. Almost character for character with plain HTML. Bui

Will Martin 31 Oct 24, 2022
Cloudy is a set of constructs for the AWS Cloud Development Kit that aim to improve the DX by providing a faster and type-safe code environment.

cloudy-ts These packages aren't yet published on npm. This is still highly experimental. Need to figure out a few things before releasing the first ve

Cristian Pallarรฉs 5 Nov 3, 2022
Zero runtime type-safe CSS in the same file as components

macaron comptime-css is now called macaron! macaron is a zero-runtime and type-safe CSS-in-JS library made with performance in mind Powered by vanilla

Mokshit Jain 205 Jan 4, 2023