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 🙏

© 2026 – Pkg Stats / Ryan Hefner

ts-narrow

v0.31.0

Published

typescript type predicates with no dependencies

Readme

ts-narrow

TypeScript type predicates with no runtime dependencies.

ts-narrow is a small collection of composable predicate functions for turning unknown or broad values into useful TypeScript types. It is designed for guards at API boundaries, event payloads, optional data, and any place where TypeScript needs runtime evidence before it can safely narrow a value.

The core API returns booleans and narrows types through user-defined type predicates. It is intentionally lighter than a schema validator: predicates do not collect issues, transform data, or produce parse results. If a check returns true, TypeScript sees the narrowed type inside that branch.

import { hasPropOf, isNumber, isObjectOf, isString } from "ts-narrow";

const isUser = isObjectOf({
  id: isNumber,
  name: isString,
  profile: hasPropOf("avatarUrl", isString),
});

const value: unknown = await response.json();

if (isUser(value)) {
  value.id;
  value.name;
  value.profile.avatarUrl;
}

Install

npm install ts-narrow

Concepts

Every narrowing function follows the same shape:

type NarrowOf<T> = (element: unknown) => element is T;

Factories such as isArrayOf, isObjectOf, hasPropOf, isOneOf, and isUnionOf accept other narrowing functions, so small checks can be composed into larger checks.

isObject follows JavaScript's typeof target === "object" behavior, so it returns true for null. Use hasProp, hasPropOf, isObjectOf, or isRecordOf when you need object-like data that can actually be inspected.

API

assert(condition, error?)

Throws when condition is falsy. The optional error can be a string or an Error.

assert(userId, "Expected user id");

assertOf(target, narrow, error?)

Throws when narrow(target) is false and narrows target after the call.

assertOf(value, isString);
value.toUpperCase();

ensure(target, error?)

Returns target when it is not null or undefined; otherwise throws.

const user = ensure(maybeUser, "User is required");

ensureOf(narrow)(target, error?)

Returns target when it matches narrow; otherwise throws. The returned value is narrowed to the predicate type.

const getString = ensureOf(isString);
const name = getString(value, "Expected a string");

hasElementAt(index)

Checks that the target is an array and contains an element at index.

if (hasElementAt(0)(value)) {
  value[0];
}

hasElementAtOf(index, narrow)

Checks that the target is an array, contains an element at index, and that the element matches narrow.

const hasStringFirstItem = hasElementAtOf(0, isString);

hasElementsOf(elements)

Checks that the target is an array with exactly the same length as elements, and that each value matches the predicate at the same index.

const isPoint = hasElementsOf([isNumber, isNumber]);

hasProp(propName)

Checks that the target is a non-null object and has propName.

const hasId = hasProp("id");

hasPropOf(propName, narrow)

Checks that the target is a non-null object, has propName, and that the property value matches narrow.

const hasNumericId = hasPropOf("id", isNumber);

isArray(target)

Checks that the target is an array.

if (isArray(value)) {
  value.length;
}

isArrayOf(narrow)

Checks that the target is an array and every element matches narrow.

const isStringArray = isArrayOf(isString);

isBoolean(target)

Checks that the target is a boolean.

isBoolean(true);

isEnum(target)

Checks that the target looks like a TypeScript enum object.

enum Status {
  Open = "open",
  Closed = "closed",
}

isEnum(Status);

isEnumOf(enumToCompare)

Checks that the target is the same enum object shape as enumToCompare.

const isStatusEnum = isEnumOf(Status);

isEnumValueOf(enumObject)

Checks that the target is one of the enum object's values.

const isStatus = isEnumValueOf(Status);

isIndexOf(record)

Checks that the target is a key of record.

const isKnownKey = isIndexOf({ id: true, name: true });

isInstanceOf(Class)

Checks that the target is an instance of a class.

const isDate = isInstanceOf(Date);

isLiteral(literal)

Checks that the target is exactly equal to literal.

const isReady = isLiteral("ready");

isNumber(target)

Checks that the target is a number.

isNumber(123);

isObject(target)

Checks typeof target === "object". This matches JavaScript behavior, so arrays and null return true.

isObject({});
isObject([]);
isObject(null);

isObjectOf(shape)

Checks that the target matches every predicate in shape. Extra properties are allowed.

const isUser = isObjectOf({
  id: isNumber,
  name: isString,
});

isOneOf(...narrowFunctions)

Checks that the target matches at least one predicate. The inferred type is a union of the predicate types.

const isStringOrNumber = isOneOf(isString, isNumber);

isOneStringLiteralOf(...values)

Checks that the target is one of the provided strings.

const isRole = isOneStringLiteralOf("admin", "member", "guest");

isRecordOf(narrow)

Checks that the target is a non-null object and every enumerable value matches narrow.

const isStringRecord = isRecordOf(isString);

isString(target)

Checks that the target is a string.

isString("hello");

isTruthy(target)

Checks that the target is not null and not undefined.

if (isTruthy(value)) {
  value;
}

isUnionOf(...narrowFunctions)

Checks that the target matches every predicate. The inferred type is an intersection of the predicate types.

const hasNameAndAge = isUnionOf(
  hasPropOf("name", isString),
  hasPropOf("age", isNumber)
);

InferNarrow<T>

Extracts the narrowed type from a NarrowOf<T> function.

type User = InferNarrow<typeof isUser>;

NarrowOf<T>

The base predicate type used by the library.

const isNonEmptyString: NarrowOf<string> = (value): value is string =>
  isString(value) && value.length > 0;

validate(narrow, target)

Runs a predicate and returns a validation result instead of a bare boolean. Successful results include the original value narrowed to the predicate type. Failed results include every issue that ts-narrow can collect from library-created predicates.

const isUser = isObjectOf({
  id: isNumber,
  name: isString,
});

const result = validate(isUser, { id: "1" });

if (!result.success) {
  result.errors;
  // [
  //   {
  //     path: ["id"],
  //     code: "invalid_type",
  //     expected: "number",
  //     received: "string",
  //     message: "Expected id to be number, received string."
  //   },
  //   {
  //     path: ["name"],
  //     code: "missing_property",
  //     expected: "present property",
  //     received: "undefined",
  //     message: "Expected name to be present."
  //   }
  // ]
}

validate keeps the existing predicate-first design intact. The predicates still return booleans, and validate reads optional diagnostics metadata attached to predicates created by this library.

type ValidationIssue = {
  path: Array<string | number | symbol>;
  code: string;
  expected: string;
  received: string;
  message: string;
};

type ValidationResult<T> =
  | { success: true; value: T; errors: [] }
  | { success: false; errors: ValidationIssue[] };

Custom predicates still work with validate, but they cannot explain why they failed unless they were composed from ts-narrow predicates. In that case, validate returns a generic custom issue.