Hi there 👋
I'm a frontend engineer based in the Netherlands, currently suffering from hay fever 😿
API responses, form inputs, external data…
In TypeScript, we often end up dealing with unknown,
and handling it properly can become a real pain in day-to-day work.
Yes, unknown has a kind of gravitational pull like the universe.
But still…
we do want to handle types safely, right?
That’s where is-kit comes in a library for composing type guards.
nyaomaru
/
is-kit
Lightweight, zero-dependency toolkit for building `isFoo` style type guards in TypeScript. Runtime-safe 🛡️, composable 🧩, and ergonomic ✨. npm -> https://www.npmjs.com/package/is-kit
is-kit
is-kit is a lightweight, zero-dependency toolkit for building reusable TypeScript type guards.
It helps you write small isFoo functions, compose them into richer runtime checks, and keep TypeScript narrowing natural inside regular control flow.
Runtime-safe 🛡️, composable 🧩, and ergonomic ✨ without asking you to adopt a heavy schema workflow.
- Build and reuse typed guards
-
Compose guards with
and,or,not,oneOf - Validate object shapes and collections
-
Parse or assert
unknownvalues without a large schema framework
Best for app-internal narrowing, filtering, and reusable guards.
🤔 Why use is-kit?
Tired of rewriting the same isFoo checks again and again?
is-kit is a good fit when you want to:
-
write reusable
isXfunctions instead of one-off inline checks - keep runtime validation lightweight and dependency-free
-
narrow values directly in
if,filter, and other TypeScript control flow - compose validation logic…
Libraries like Zod take a schema-first approach.
In contrast, is-kit focuses on narrowing types within your existing code.
Instead of “writing validation”, you can think of it as extending your everyday if statements with type safety.
If Zod is for boundaries (API / input)
is-kit is for inside your application logic
A simple example
Imagine you need to check “strings with length ≤ 3” multiple times.
With is-kit, you can define it once and reuse it:
import { define, isString } from "is-kit";
const isShortString = define<string>(
(value) => isString(value) && value.length <= 3,
);
Then use it like this:
import { isShortString } from "~/utils/is";
declare const input: unknown;
// before → repeating conditions every time
if (typeof input === "string" && input.length <= 3) {
input.toUpperCase();
}
// after → reusable guard
if (isShortString(input)) {
input.toUpperCase();
}
This style works seamlessly with
if, filter, map, and other existing logic.
🐾 How is-kit has evolved
It’s been about 6 months since the v1.0 release.
Back then, is-kit started as a lightweight type guard library
with primitives like:
defineandorstructarrayOf
Since then, up to v1.6, it has gradually evolved into something more practical:
👉 a toolkit for handling unknown values in real-world applications
Let’s look at 5 major improvements.
🪄 1. Distinguishing “missing” vs “undefined” in struct
A common situation in API responses:
- A key is missing entirely
- A key exists but its value is
undefined
These are not the same.
With v1.5.0, optionalKey(...) was introduced:
import { isString, optional, optionalKey, struct } from "is-kit";
const isUser = struct({
id: isString,
nickname: optionalKey(isString),
displayName: optionalKey(optional(isString)),
});
This allows you to express both cases explicitly.
🔑 2. Key-based narrowing (hasKey, hasKeys, narrowKeyTo)
From v1.1.13 and v1.4.0, key-based utilities were added:
import {
hasKeys,
narrowKeyTo,
oneOfValues,
struct,
isString,
isNumber,
} from "is-kit";
const isUser = struct({
id: isString,
age: isNumber,
role: oneOfValues("admin", "guest", "trial"),
});
const hasRoleAndId = hasKeys("role", "id");
const byRole = narrowKeyTo(isUser, "role");
const isGuest = byRole("guest");
This lets you build on top of existing guards instead of redefining everything.
🧪 3. assert for fail-fast validation
Added in v1.2.0:
import { assert, isString } from "is-kit";
declare const input: unknown;
assert(isString, input, "input must be a string");
input.toUpperCase();
You can now use guards in a fail-fast style when needed.
✨ 4. Support for Set and Map
With v1.6.0:
import { mapOf, setOf, isString, isNumber } from "is-kit";
const isTags = setOf(isString);
const isScores = mapOf(isString, isNumber);
Not everything is an array in real-world apps —
this expands coverage to common JS data structures.
🥏 5. Handling numeric edge cases
From v1.1.x:
isIntegerisSafeIntegerisPositiveisNegativeisNaNisInfiniteNumberisZero
These help you express “valid numbers” more precisely.
🌟 More features
There are more utilities available —
feel free to explore the docs:
🎯 Summary
is-kit started as a small, composable type guard library.
Over time, it has evolved into:
👉 a practical toolkit for handling unknown in application code
Key improvements:
- More expressive object schemas (
optionalKey) - Better key-based narrowing
- Fail-fast assertions
- Collection support (
Set,Map) - Rich numeric guards
The goal of is-kit is simple:
👉 Make type-safe code feel natural to write
If you try it and have ideas or feedback, feel free to share!
See you in the next post 👋




Top comments (0)