
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
Type safety made simple.
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.
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.
Use your preferred package manager to install 🧰tstk from the npm registry.
npm install tstk
yarn add tstk
pnpm add tstk
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)[] │
└──────────────────────────────────┘
*/
}
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.
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 } │
└───────────────────────────────────────────────┘
*/
}
})
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 } │
└──────────────────────────────────────────┘
*/
}
})
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.
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 } │
└─────────────────────────────────┘
*/
}
}
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 } │
└─────────────────────────────┘
*/
}
}
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.
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> │
└────────────────────────────────────┘
*/
}
}
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; │
│ } │
└──────────────────────────────┘
*/
}
}
Below is a more comprehensive reference showing how to check for primitives, classes, unions, arrays, records, tuples, and even complex schemas.
if (is(value, "string")) {
value
/**
┌─────────────────────┐
│ const value: string │
└─────────────────────┘
*/
}
if (is(value, "number")) {
value
/**
┌─────────────────────┐
│ const value: number │
└─────────────────────┘
*/
}
if (is(value, "bigint")) {
value
/**
┌─────────────────────┐
│ const value: bigint │
└─────────────────────┘
*/
}
if (is(value, "boolean")) {
value
/**
┌──────────────────────┐
│ const value: boolean │
└──────────────────────┘
*/
}
if (is(value, "symbol")) {
value
/**
┌─────────────────────┐
│ const value: symbol │
└─────────────────────┘
*/
}
if (is(value, "object")) {
value
/**
┌─────────────────────┐
│ const value: object │
└─────────────────────┘
*/
}
[!NOTE] Unlike JavaScript's
typeofoperator,is(value, "object")includes functions (for whichtypeofreturns "function") and excludes null (an infamousbugfeature oftypeof).is({}, "object") // true is([], "object") // true is(() => {}, "object") // true is(null, false) // false
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
if (is(value, "array")) {
value
/**
┌─────────────────────────────────┐
│ const value: readonly unknown[] │
└─────────────────────────────────┘
*/
}
if (is(value, "function")) {
value
/**
┌──────────────────────────────────────────────┐
│ const value: (...args: unknown[]) => unknown │
└──────────────────────────────────────────────┘
*/
}
if (is(value, "any")) {
value
/**
┌──────────────────┐
│ const value: any │
└──────────────────┘
*/
}
if (is(value, "null")) {
value
/**
┌───────────────────┐
│ const value: null │
└───────────────────┘
*/
}
if (is(value, "undefined")) {
value
/**
┌────────────────────────┐
│ const value: undefined │
└────────────────────────┘
*/
}
if (is(value, "hello")) {
value
/**
┌──────────────────────┐
│ const value: "hello" │
└──────────────────────┘
*/
}
if (is(value, 42)) {
value
/**
┌─────────────────┐
│ const value: 42 │
└─────────────────┘
*/
}
if (is(value, 21n)) {
value
/**
┌──────────────────┐
│ const value: 21n │
└──────────────────┘
*/
}
if (is(value, true)) {
value
/**
┌───────────────────┐
│ const value: true │
└───────────────────┘
*/
}
const $foo = Symbol("foo")
if (is(value, symbol)) {
value
/**
┌──────────────────────────┐
│ const value: typeof $foo │
└──────────────────────────┘
*/
}
if (is(value, null)) {
value
/**
┌───────────────────┐
│ const value: null │
└───────────────────┘
*/
}
literal value
if (is(value, literal("string"))) {
value
/**
┌───────────────────────┐
│ const value: "string" │
└───────────────────────┘
*/
}
[!TIP] Use
literalto match a literal primitive type like "string" or "number".
if (is(value, Date)) {
value
/**
┌───────────────────┐
│ const value: Date │
└───────────────────┘
*/
}
if (is(value, union("string", "number"))) {
value
/**
┌──────────────────────────────┐
│ const value: string | number │
└──────────────────────────────┘
*/
}
if (is(value, joint({ foo: "string" }, { bar: "number" }))) {
value
/**
┌───────────────────────────────────────────┐
│ const value: { foo: string; bar: number } │
└───────────────────────────────────────────┘
*/
}
if (is(value, array("string"))) {
value
/**
┌───────────────────────┐
│ const value: string[] │
└───────────────────────┘
*/
}
if (is(value, ["string", "number"])) {
value
/**
┌───────────────────────────────┐
│ const value: [string, number] │
└───────────────────────────────┘
*/
}
[!NOTE]
tuplecan also be used to define a tuple type.if (is(value, tuple("string", "number"))) { value /** ┌───────────────────────────────┐ │ const value: [string, number] │ └───────────────────────────────┘ */ }
if (is(value, record("string", "number"))) {
value
/**
┌─────────────────────────────────────┐
│ const value: Record<string, number> │
└─────────────────────────────────────┘
*/
}
if (is(value, record(["foo", "bar"], "string"))) {
value
/**
┌────────────────────────────────────────────┐
│ const value: Record<"foo" | "bar", string> │
└────────────────────────────────────────────┘
*/
}
if (is(value, { foo: "string" })) {
value
/**
┌──────────────────────────────┐
│ const value: { foo: string } │
└──────────────────────────────┘
*/
}
[!NOTE] By default,
isdoes an exact match on the schema. To allow extra properties, passfalseas the third argument.is({ foo: 1, bar: 2 }, { foo: "number" }) // false is({ foo: 1, bar: 2 }, { foo: "number" }, false) // true
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; │
│ }[]; │
│ } │
└───────────────────────────────────────────────┘
*/
}
Profile schemaconst 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),
}
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
assertwithisorhasto narrow types at runtime effectively.assert(is(value, "string"), "Value must be a string") value /** ┌─────────────────────┐ │ const value: string │ └─────────────────────┘ */
primitive(type)
Define a primitive type such as "string" or "number".
[!TIP] Use
primitiveto 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
literalto 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 matchesprops.A concrete record such as
record(["foo", "bar"], "number")checks that allpropsare present.
partial(record)
Convert all properties of record to optional.
[!NOTE]
partialonly works with concrete records or schemas. To create a partial collective schema, wrap the value type inoptionalinstead.
optional(type)
Define an optional property that matches type.
readonly(type)
Define a readonly property that matches type.
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.
Contributions, issues, and feature requests are welcome!
git checkout -b my-new-feature
git commit -am 'My feature'
git push origin my-new-feature
Please submit your feedback, suggestions, and bug reports on the issues page.
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 🇸🇬.
FAQs
Type safety made simple.
The npm package tstk receives a total of 2 weekly downloads. As such, tstk popularity was classified as not popular.
We found that tstk demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.