npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@skarab/tson

v1.5.1

Published

Type Safe Object Notation & Validation

Downloads

557

Readme

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