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

tstk

v1.0.8

Published

Type safety made simple.

Downloads

20

Readme

tstk

Type safety made simple.

npm version npm package minimized gzipped size npm license

tstk is a simple, minimal, and declarative runtime type-checking toolkit for TypeScript. Just like its name suggests, it provides small but powerful utilities that help you narrow types easily while handling all the type safety for you.

Why tstk?

Neat
Tired of creating a schema for every single shape and size? Inline your type definitions with simple, composable functions like is, array, record, and union. Never hit F12 on your keyboard (Go to definition) again.

Easy
Checking for a string or a string array? Here you go: union("string", array("string")). Define your types with descriptors that mimic TypeScript as closely as possible. tstk handles the rest for you.

Tiny
With zero dependencies and a featherweight minzipped size, tstk keeps your bundle small. So you can install it guilt-free and ease your bundlephobia.

If you need a handy and lightweight approach to runtime validation, tstk is built just for that.

Install

Use your preferred package manager to install 🧰tstk from the npm registry.

npm install tstk
yarn add tstk
pnpm add tstk

Quick Example

import { array, is, union } from "tstk"

const value = JSON.parse("['hello', 42, 'world']")

if (is(value, array(union("string", "number")))) {
  value
  /**
    ┌──────────────────────────────────┐
    │ const value: (string | number)[] │
    └──────────────────────────────────┘
   */
}

Use Cases

1. Validating API Responses

For simple API responses, tstk cuts out the need to define and parse against a full schema, enabling clean and inline validation that's easier to maintain and integrate into your data flow.

  • With zod
    import { z } from "zod"
    
    const UserSchema = z.object({
      id: z.number(),
      name: z.string(),
    })
    
    fetch("/api/users")
      .then(res => res.json())
      .then((data) => {
        const result = UserSchema.safeParse(data)
        if (result.success) {
          result.data
          /**
            ┌───────────────────────────────────────────────┐
            │ (property) data: { id: number; name: string } │
            └───────────────────────────────────────────────┘
           */
        }
      })
  • With tstk
    import { is } from "tstk"
    
    fetch("/api/users")
      .then(res => res.json())
      .then((data) => {
        if (is(data, { id: "number", name: "string" })) {
          data
          /**
            ┌──────────────────────────────────────────┐
            │ const data: { id: number; name: string } │
            └──────────────────────────────────────────┘
           */
        }
      })

2. Validating Dynamic Query Parameters

When working with runtime data like URL query parameters in a Next.js application, tstk offers a direct and minimalistic approach to validation without the extra overhead of creating a schema.

  • With zod
    import { useSearchParams } from "next/navigation"
    import { z } from "zod"
    
    const QuerySchema = z.object({
      id: z.string(),
    })
    
    function MyComponent() {
      const searchParams = useSearchParams()
      const query = Object.fromEntries(searchParams.entries())
    
      const result = QuerySchema.safeParse(query)
      if (result.success) {
        result.data
        /**
          ┌─────────────────────────────────┐
          │ (property) data: { id: number } │
          └─────────────────────────────────┘
         */
      }
    }
  • With tstk
    import { useSearchParams } from "next/navigation"
    import { is } from "tstk"
    
    function MyComponent() {
      const searchParams = useSearchParams()
      const query = Object.fromEntries(searchParams.entries())
    
      if (is(query, { id: "string" })) {
        query
        /**
          ┌─────────────────────────────┐
          │ const query: { id: string } │
          └─────────────────────────────┘
         */
      }
    }

3. Validating Local Storage Data

For data from sources like local storage where the shape isn't known until runtime, tstk provides a concise and effective method to achieve type safety as opposed to manual type checking.

  • With typeof, etc.
    const data = localStorage.getItem("config")
    if (data) {
      const config = JSON.parse(data) as unknown
      if (
        config
        && typeof config === "object"
        && "theme" in config
        && typeof config.theme === "string"
        && ["light", "dark"].includes(config.theme)
        && "notifications" in config
        && typeof config.notifications === "boolean"
      ) {
        config
        /**
          ┌────────────────────────────────────┐
          │ const config: object               │
          │ & Record<"theme", unknown>         │
          │ & Record<"notifications", unknown> │
          └────────────────────────────────────┘
         */
      }
    }
  • With tstk
    import { is } from "tstk"
    
    const data = localStorage.getItem("config")
    if (data) {
      const config = JSON.parse(data)
      if (is(config, {
        theme: union("light", "dark"),
        notifications: "boolean"
      })) {
        config
        /**
          ┌──────────────────────────────┐
          │ const config: {              │
          │     theme: "light" | "dark"; │
          │     notifications: boolean;  │
          │ }                            │
          └──────────────────────────────┘
         */
      }
    }

Detailed Usage

Below is a more comprehensive reference showing how to check for primitives, classes, unions, arrays, records, tuples, and even complex schemas.

  • Primitive type: "string"
    if (is(value, "string")) {
      value
      /**
        ┌─────────────────────┐
        │ const value: string │
        └─────────────────────┘
       */
    }
  • Primitive type: "number"
    if (is(value, "number")) {
      value
      /**
        ┌─────────────────────┐
        │ const value: number │
        └─────────────────────┘
       */
    }
  • Primitive type: "bigint"
    if (is(value, "bigint")) {
      value
      /**
        ┌─────────────────────┐
        │ const value: bigint │
        └─────────────────────┘
       */
    }
  • Primitive type: "boolean"
    if (is(value, "boolean")) {
      value
      /**
        ┌──────────────────────┐
        │ const value: boolean │
        └──────────────────────┘
       */
    }
  • Primitive type: "symbol"
    if (is(value, "symbol")) {
      value
      /**
        ┌─────────────────────┐
        │ const value: symbol │
        └─────────────────────┘
       */
    }
  • Primitive type: "object"
    if (is(value, "object")) {
      value
      /**
        ┌─────────────────────┐
        │ const value: object │
        └─────────────────────┘
       */
    }

[!NOTE] Unlike JavaScript's typeof operator, is(value, "object") includes functions (for which typeof returns "function") and excludes null (an infamous ~~bug~~ feature of typeof).

is({}, "object") // true
is([], "object") // true
is(() => {}, "object") // true
is(null, false) // false
  • Primitive type: "record"
    if (is(value, "record")) {
      value
      /**
        ┌─────────────────────────────────────────┐
        │ const value: Record<keyof any, unknown> │
        └─────────────────────────────────────────┘
       */
    }

[!TIP] Use the "record" primitive to match a plain object only.

is({}, "record") // true
is([], "record") // false
is(() => {}, "record") // false
is(null, false) // false
  • Primitive type: "array"
    if (is(value, "array")) {
      value
      /**
        ┌─────────────────────────────────┐
        │ const value: readonly unknown[] │
        └─────────────────────────────────┘
       */
    }
  • Primitive type: "function"
    if (is(value, "function")) {
      value
      /**
        ┌──────────────────────────────────────────────┐
        │ const value: (...args: unknown[]) => unknown │
        └──────────────────────────────────────────────┘
       */
    }
  • Primitive type: "any"
    if (is(value, "any")) {
      value
      /**
        ┌──────────────────┐
        │ const value: any │
        └──────────────────┘
       */
    }
  • Primitive type: "null"
    if (is(value, "null")) {
      value
      /**
        ┌───────────────────┐
        │ const value: null │
        └───────────────────┘
       */
    }
  • Primitive type: "undefined"
    if (is(value, "undefined")) {
      value
      /**
        ┌────────────────────────┐
        │ const value: undefined │
        └────────────────────────┘
       */
    }
  • Literal type: string value
    if (is(value, "hello")) {
      value
      /**
        ┌──────────────────────┐
        │ const value: "hello" │
        └──────────────────────┘
       */
    }
  • Literal type: number value
    if (is(value, 42)) {
      value
      /**
        ┌─────────────────┐
        │ const value: 42 │
        └─────────────────┘
       */
    }
  • Literal type: bigint value
    if (is(value, 21n)) {
      value
      /**
        ┌──────────────────┐
        │ const value: 21n │
        └──────────────────┘
       */
    }
  • Literal type: boolean value
    if (is(value, true)) {
      value
      /**
        ┌───────────────────┐
        │ const value: true │
        └───────────────────┘
       */
    }
  • Literal type: symbol value
    const $foo = Symbol("foo")
    if (is(value, symbol)) {
      value
      /**
        ┌──────────────────────────┐
        │ const value: typeof $foo │
        └──────────────────────────┘
       */
    }
  • Literal type: null value
    if (is(value, null)) {
      value
      /**
        ┌───────────────────┐
        │ const value: null │
        └───────────────────┘
       */
    }
  • Literal type: literal value
    if (is(value, literal("string"))) {
      value
      /**
        ┌───────────────────────┐
        │ const value: "string" │
        └───────────────────────┘
       */
    }

[!TIP] Use literal to match a literal primitive type like "string" or "number".

  • Class type
    if (is(value, Date)) {
      value
      /**
        ┌───────────────────┐
        │ const value: Date │
        └───────────────────┘
       */
    }
  • Union type
    if (is(value, union("string", "number"))) {
      value
      /**
        ┌──────────────────────────────┐
        │ const value: string | number │
        └──────────────────────────────┘
       */
    }
  • Joint type
    if (is(value, joint({ foo: "string" }, { bar: "number" }))) {
      value
      /**
        ┌───────────────────────────────────────────┐
        │ const value: { foo: string; bar: number } │
        └───────────────────────────────────────────┘
       */
    }
  • Array type
    if (is(value, array("string"))) {
      value
      /**
        ┌───────────────────────┐
        │ const value: string[] │
        └───────────────────────┘
       */
    }
  • Tuple type
    if (is(value, ["string", "number"])) {
      value
      /**
        ┌───────────────────────────────┐
        │ const value: [string, number] │
        └───────────────────────────────┘
       */
    }

[!NOTE] tuple can also be used to define a tuple type.

if (is(value, tuple("string", "number"))) {
  value
  /**
    ┌───────────────────────────────┐
    │ const value: [string, number] │
    └───────────────────────────────┘
   */
}
  • Record type: collective keys
    if (is(value, record("string", "number"))) {
      value
      /**
        ┌─────────────────────────────────────┐
        │ const value: Record<string, number> │
        └─────────────────────────────────────┘
       */
    }
  • Record type: concrete keys
    if (is(value, record(["foo", "bar"], "string"))) {
      value
      /**
        ┌────────────────────────────────────────────┐
        │ const value: Record<"foo" | "bar", string> │
        └────────────────────────────────────────────┘
       */
    }
  • Simple schema
    if (is(value, { foo: "string" })) {
      value
      /**
        ┌──────────────────────────────┐
        │ const value: { foo: string } │
        └──────────────────────────────┘
       */
    }

[!NOTE] By default, is does an exact match on the schema. To allow extra properties, pass false as the third argument.

is({ foo: 1, bar: 2 }, { foo: "number" }) // false
is({ foo: 1, bar: 2 }, { foo: "number" }, false) // true
  • Complex schema
    if (is(value, Profile)) {
      value
      /**
        ┌───────────────────────────────────────────────┐
        │ const value: {                                │
        │     user: {                                   │
        │         userid: string;                       │
        │         name: string;                         │
        │         age: number;                          │
        │         email: string;                        │
        │         deleted: boolean;                     │
        │     };                                        │
        │     address: {                                │
        │         street: string;                       │
        │         city: string;                         │
        │         zipcode: string;                      │
        │         country: string;                      │
        │     };                                        │
        │     settings: {                               │
        │         theme: "light" | "dark";              │
        │         notifications: {                      │
        │             email?: boolean | undefined;      │
        │             sms?: boolean | undefined;        │
        │         };                                    │
        │     };                                        │
        │     roles: ("admin" | "editor" | "viewer")[]; │
        │     posts: {                                  │
        │         id: string;                           │
        │         title: string;                        │
        │         body: string;                         │
        │         attachment?: string | undefined;      │
        │         publishedAt: number;                  │
        │         tags: string[];                       │
        │     }[];                                      │
        │     friends: {                                │
        │         userid: string;                       │
        │         name: string;                         │
        │         startedAt: number;                    │
        │     }[];                                      │
        │ }                                             │
        └───────────────────────────────────────────────┘
       */
    }
const User = {
  userid: primitive("string"),
  name: primitive("string"),
  age: primitive("number"),
  email: primitive("string"),
  deleted: primitive("boolean"),
}

const Address = record(["street", "city", "zipcode", "country"], "string")

const Settings = {
  theme: union("light", "dark"),
  notifications: partial(record(["email", "sms"], "boolean")),
}

const Role = union("admin", "editor", "viewer")

const Post = {
  id: primitive("string"),
  title: primitive("string"),
  body: primitive("string"),
  attachment: optional("string"),
  publishedAt: primitive("number"),
  tags: array("string"),
}

const Friend = joint(
  pick(User, ["userid", "name"]),
  { startedAt: primitive("number") },
)

const Profile = {
  user: User,
  address: Address,
  settings: Settings,
  roles: array(Role),
  posts: array(Post),
  friends: array(Friend),
}

API

Core Functionality

is(value, type, exact?)
Check if value matches type, allowing extra properties if exact is false.

has(value, prop, type?, exact?)
Check if value has property prop that matches some optional type, allowing extra properties if exact is false.

assert(condition, message)
Throw an error with message if condition is false.

[!TIP] Combine assert with is or has to narrow types at runtime effectively.

assert(is(value, "string"), "Value must be a string")
value
/**
  ┌─────────────────────┐
  │ const value: string │
  └─────────────────────┘
 */

Type Descriptors

primitive(type)
Define a primitive type such as "string" or "number".

[!TIP] Use primitive to define a primitive property in a schema.

const Foo = { foo: primitive("number") }
/**
  ┌──────────────────────────────┐
  │ const Foo: { foo: "number" } │
  └──────────────────────────────┘
 */
if (is(value, Foo)) {
  value
  /**
    ┌──────────────────────────────┐
    │ const value: { foo: number } │
    └──────────────────────────────┘
   */
}

literal(type)
Define a literal type such as literal("hello") or literal(42).

[!TIP] Use literal to define a literal property in a schema and/or to match a literal primitive type.

const Bar = { bar: literal("number") }
/**
  ┌───────────────────────────────────────┐
  │ const Bar: { bar: Literal<"number"> } │
  └───────────────────────────────────────┘
 */
if (is(value, Bar)) {
  value
  /**
    ┌────────────────────────────────┐
    │ const value: { bar: "number" } │
    └────────────────────────────────┘
   */
}

union(...types)
Define a union type that matches one of types.

joint(...types)
Define a joint type that matches all of types.

array(type)
Define an array type where every element matches type.

tuple(...types)
Define a tuple type where every element matches the corresponding type in types.

[!IMPORTANT] The length must be exactly the same as types.

record(props, type)
Define a record type that matches a plain object with props, where all values match type.

[!NOTE] A collective record such as record("string", "number") checks that every prop matches props.

A concrete record such as record(["foo", "bar"], "number") checks that all props are present.

partial(record)
Convert all properties of record to optional.

[!NOTE] partial only works with concrete records or schemas. To create a partial collective schema, wrap the value type in optional instead.

optional(type)
Define an optional property that matches type.

readonly(type)
Define a readonly property that matches type.

Utility Functions

json(value)
Check if value is a JSON value.

propertyKey(value)
Check if value is a property key.

get(object, prop)
Get the value of prop for object, binding to object if applicable.

keys(object)
Get all property keys of object, casting to integers if applicable.

filter(array, type)
Return a new array including only elements that match type.

reject(array, type)
Return a new array excluding elements that match type.

pick(object, props)
Return a new object including only props from the original.

omit(object, props)
Return a new object excluding props from the original.

remap(object, mapping)
Return a new object whose keys are remapped using mapping.

merge(target, ...sources)
Copy properties from each source into target, with last taking precedence.

Contributing

Contributions, issues, and feature requests are welcome!

  1. Fork the repository.
  2. Create your feature branch:
    git checkout -b my-new-feature
  3. Commit your changes:
    git commit -am 'My feature'
  4. Push to the branch:
    git push origin my-new-feature
  5. Submit a PR.

Please submit your feedback, suggestions, and bug reports on the issues page.

License

MIT © David Xie

Acknowledgments

Inspired by 🎆type-fest and 🛠️lodash.

If tstk helps you, star the repo or share it with your team!

Happy type checking!

Maintained with ❤️ from 🇸🇬.