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 🙏

© 2025 – Pkg Stats / Ryan Hefner

ruti

v0.0.17

Published

Runtime type checking

Downloads

3

Readme

WORK IN PROGRESS!

Ruti is still in development. I won't call it production ready, but the test coverage is close to 100% and I've been using it in production for months. Everything is subject to change, so pay close attention when updating (even between semver patches)!


Ruti

Ruti is a TypeScript library for type checking at runtime.

Introduction

TypeScript is missing one major feature: runtime type checking. This library is an attempt at solving this issue.

But to do type checking at runtime you first need to make your types accessible at runtime. A simple way to achieve this is to declare your types as JavaScript values inside your code instead of TypeScript types, and to then generate TypeScript types from those values. That way you can use the TypeScript types as usual, but you can also use the JavaScript values at runtime. This is the approach Ruti takes.

The JavaScript values, later referred to as Arg (placeholder name!), are human-readable and resemble TypeScript type declarations somewhat. Because they are optimized for human usability they are not used directly for type checking. Instead they are used to generate machine friendly data structures, referred to as Templates. These templates, together with the generated TypeScript types and the value you want to type check, can then be passed to Ruti's type checking function at any time in your code for runtime type checking!

I'm also experimenting with ways of safely merging two objects of the same type (merge_state).

Examples

You can find code examples in the "examples" directory.

Use npm run example <name> to run an example (replace <name> with help for more information).

Quickstart

The following code is also hosted on CodeSandbox, in case you prefer a more interactive experience.

Setting up a template

// Create a type declaration object
// (The somewhat weird way you declare types for Ruti)
const person_arg = {
  name: 'string',
  age: 'number',
  address: {
    street: 'string',
    zip_code: 'number',
  },
  nicknames: [['string']],
} as const;

// Generate a template
// (Creates machine-friendly data from the human-readable data)
const person_template = create_template(person_arg);

// Generate a TypeScript type
type Person = FromTTypeArg<typeof person_arg>

Creating and updating state

// Create initial state
const person: Person = {
  name: 'Peter',
  age: 30,
  address: {
    street: 'Somewhere 2',
    zip_code: 12345,
  },
  nicknames: ['Pete', 'Big P'],
};

// Update state
// Note: This does not manipulate "person", instead it returns a copy
const updated_person = merge_state(person_template, person, { age: 31 });

// person.age === 30
// updated_person.age === 31

Checking types

const person = {
  name: 'Johan',
  age: 55,
  address: {
    street: 'Somewhere 4',
    zip_code: 12345,
  },
  nicknames: ['Mr J'],
};

if (is_type<Person>(person_template, person)) {
  // Note: is_type uses a type guard, so "person" is of type "Person" inside this scope!
  console.log(`"person" is a Person! Their name is ${person.name}.`);
} else {
  console.log('"person" is not a Person. :(');
}

Terms

There are three main concepts in Ruti: arg, template and state.

Note: Arg and State are not very fitting terms. They should be replaced.

Arg is the human-friendly type declaration that the TypeScript type and template are generated from.

Template is the machine-friendly translation of the type declaration. It is used at runtime to determine what types and shapes different states should have (to remain type safe).

State is the data that is type checked against a template at runtime. This includes both the data being modified and the data modifying it.

Functions

create_template(arg)

This generates a template from an arg. The purpose is to make the type declarations more concise and readable (and also to make it easier to generate TypeScript types from). This function is optional, you can write templates by hand or load them at runtime.

Arguments

  • arg: Arg to generate a template from.

Returns A template corresponding to arg. (TODO: Be more specific & list edge cases)

Throws if ...

  • arg is not a valid Arg.

merge_state(template, a, b, opts?)

Merge b into a and return the result. No argument is modified by this function. Objects are merged recursively (and arrays are not).

Arguments

  • template: Template that a already conforms to, and b is compared against.
  • a: Current state.
  • b: State to apply to a.
  • opts: Options for how to merge the states. These options are applied to child objects recursively. (Optional)

Returns ...

  • a, if a and b are strictly equal.
  • b, if b is a primitive value (boolean, number, string, null or undefined).
  • A new array, if b is an array. The new array is a copy of b.
  • A new object, if b is an object. The new object is a copy of a (or an empty object if a is not an object) with all properties (listed in template.children) of b applied to it (if a property is an object, then this function is applied to that as well).

Throws if ...

  • b is not of a type listed in template.types.
  • b is an array, and contains a value of a type not that is not listed in template.contents.
  • b is an array, and template.contents contains object (because merging arrays of objects is not supported!).
  • b is an object, and contains a property that is not listed in template.children (unless opts.ignore_extra is true).
  • b is an object, and contains a property that is listed in template.children, but the types of the properties does not match.
  • b is an object, a is not an object, and b does not contain every property listed in template.children.
  • template is not a valid template (this may not catch all template issues).

is_type(template, value, opts?, on_fail?)

Check if value conforms to template.

Arguments

  • template: Template to check conformity against.
  • value: Value to check conformity with.
  • opts: Options for how to perform the conformity check. These options are applied to child objects recursively. (Optional)
  • on_fail: Called if value does not conform to template. Arguments contains information about how it failed. (Optional)

Returns true if value comforms to template, otherwise false.

Throws if ...

  • template is not a valid template (this may not catch all template issues).

Arg structure

Data types

There are 7 different data types, and they are divided into two categories:

Primitive: boolean, number, string, null and undefined.

Advanced: object and array.

Syntax

Each value can be one of the following:

Format: value => type

  • A single primitive type
    • Example: 'string' => string
  • An array of primitive types (this results in a union of all the types)
    • Example: ['string', 'number'] => string | number
  • An array inside another array (this results in an array)
    • Example: [['boolean']] => boolean[]
    • Example 2: [['string', 'number']] => (string | number)[]
    • Example 3: [['boolean'], ['string', 'number']] => boolean[] | (string | number)[]
  • An object with primitive or advanced types
    • Example: { x: 'number' } => { x: number }
    • Example: { y: [['string']] } => { y: string[] }
    • Example: { z: { w: 'string' } } => { z: { w: string } }

Notes:

  • Unions may not contain more than one object (including inside an array if it is part of the union).
    • [[ { x: 'number' } ], 'string'] and [[ 'number' ], { y: 'string' }] are fine.
    • [[ { x: 'number' } ], { y: 'string' }] is forbidden.
  • Arrays only support primitive types and up to one object. No nested arrays!
    • [[ { x: 'number' } ]] and [[ { x: 'number' }, 'string' ]] are fine.
    • [[ { x: 'number' }, { y: 'string' } ]] and [[ { x: 'number' }, ['string'] ]] are forbidden.
  • Arrays with objects are NOT supported by merge_state (because I haven't decided what way they should merge).
  • Ruti treats null as its own primitive (even though it's treated as an object by Javascript).