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

@sladg/what-that

v0.2.0

Published

Tiny, dependency-free type guards: is.object(), is.not.number()

Readme

@sladg/what-that

Tiny, dependency-free type guards and comparators with an ergonomic is.* namespace. Every check is a TypeScript type predicate — it narrows, it composes, and impossible comparisons are compile errors.

  • Zero dependencies, ~3 kB gzip
  • ESM + CJS, sideEffects: false, target es2020
  • Strict types everywhere — guards narrow via predicates, negations narrow via Exclude, comparators reject cross-type mixes at compile time
npm install @sladg/what-that
import { is } from '@sladg/what-that'

Type guards — is.*

Every guard is (value: unknown) => value is T:

is.string(x)        // x is string
is.number(x)        // x is number
is.boolean(x)       // x is boolean
is.symbol(x)        // x is symbol
is.function(x)      // x is (...args: unknown[]) => unknown
is.array(x)         // x is unknown[]
is.object(x)        // x is Record<string, unknown> — plain objects only:
                    // not arrays, Date, Map, class instances (proxies of plain objects pass)
is.objectOrArray(x) // x is Record<string, unknown> | unknown[]
is.date(x)          // x is Date
is.regexp(x)        // x is RegExp
is.map(x)           // x is Map<unknown, unknown>
is.set(x)           // x is Set<unknown>
is.weakMap(x)       // x is WeakMap<object, unknown>
is.weakSet(x)       // x is WeakSet<object>
is.promise(x)       // x is Promise<unknown>
is.error(x)         // x is Error — subclasses (TypeError, custom) included
is.iterable(x)      // x is Iterable<unknown> — strings, arrays, Map, Set, generators
is.nil(x)           // x is null | undefined
is.null(x)          // x is null
is.undefined(x)     // x is undefined
is.primitive(x)     // x is string | number | boolean | bigint | symbol | null | undefined
is.numericKey(k)    // string is a non-negative integer index ("0", "42")
const data: unknown = await fetchSomething()

if (is.object(data)) {
  data.name // ok — data is Record<string, unknown>
}

is.instanceOf(value, Ctor)

Narrows to InstanceType of any constructor, abstract classes included:

if (is.instanceOf(err, HttpError)) {
  err.statusCode // ok
}

Negations — is.not.*

Every guard has a mirror that narrows by exclusion (Exclude<T, …>):

const value: string | number = getValue()

if (is.not.string(value)) {
  value.toFixed(2) // value is number
}

const items = [1, null, 2, undefined].filter(is.not.nil)
// items: number[]

is.not.instanceOf(x, Ctor) excludes the instance type from unions the same way.

Emptiness — is.empty / is.not.empty

One overloaded function instead of x.length === 0, Object.keys(x).length === 0, x === '', x.size === 0:

is.empty(null)          // true — nil is empty
is.empty('')            // true
is.empty([])            // true
is.empty({})            // true — own properties only
is.empty(new Map())     // true — size-based
is.empty(new Set())     // true
is.empty(0)             // typed as literal false — numbers are never empty
is.empty(new Date())    // typed as literal false

The types work as hard as the runtime:

declare const name: string | null

if (is.empty(name)) {
  name // '' | null
} else {
  name // string
}

declare const items: number[]

if (is.not.empty(items)) {
  items[0] // number — narrowed to [number, ...number[]], no undefined
}

Deep equality — is.equal / is.not.equal

Structural comparison for arrays, plain objects, Date (by time), RegExp (by source and flags), Map (keys by identity, values deeply), Set (by membership):

is.equal({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] })      // true
is.equal(new Date('2024-01-01'), new Date('2024-01-01'))  // true
is.equal(new Set([1, 2]), new Set([2, 1]))                // true
is.equal(new Map([['a', { x: 1 }]]), new Map([['a', { x: 1 }]])) // true

Comparisons — is.that()

Fluent and unambiguous: the subject goes inside that(), the reference is the argument. Strict mode compares same types only — number, bigint, string (lexicographic), Date:

is.that(price).largerThan(0)
is.that(count).atLeast(10)        // >=
is.that(retries).atMost(3)        // <=
is.that(age).between(18, 65)      // inclusive
is.that('b').smallerThan('c')     // lexicographic
is.that(startDate).smallerThan(endDate)

is.that(5).largerThan(new Date()) // ❌ compile error — cross-type

Literal subjects widen properly: is.that(0).between(1, 5) type-checks and returns false.

Loose comparisons — is.loosely.that()

Cross-representation comparison. The subject picks the domain, so it's never everything-vs-everything:

Numeric subject (number, bigint, `${number}` strings) — compares against numbers, bigints, numeric strings, and Dates as unixMs:

is.loosely.that('42').largerThan(7)      // true — numeric, not lexicographic
is.loosely.that(5n).atLeast('5')         // true — bigint vs numeric string
is.loosely.that(3).smallerThan(5n)       // true — bigint vs number, exact per spec

// No float runoff: integer strings ride BigInt beyond 2^53
is.loosely.that('9007199254740993').largerThan('9007199254740992') // true

Date subject — accepts any date representation (Date, parseable string, unixMs number/bigint), always compared by instant in UTC:

const deadline = new Date('2024-06-01')
is.loosely.that(deadline).between('2024-01-01', '2024-12-31') // true
is.loosely.that(deadline).largerThan(1704067200000)           // vs unixMs

String subject — compares against strings and Dates. Two parseable date strings compare by instant (cross-format, cross-timezone); plain strings fall back to lexicographic:

is.loosely.that('2024-01-01T12:00:00+02:00').largerThan('2024-01-01T09:00:00Z') // true — 10Z > 9Z
is.loosely.that('banana').largerThan('apple')                                   // true — lexicographic

is.loosely.that('banana').largerThan(5) // ❌ compile error — string vs number
is.loosely.that({})                     // ❌ compile error — objects are not comparable

What it will not do

  • Detect proxies — impossible in JS by design; a Proxy of a plain object passes is.object(), which is what you want
  • Schema validation with messages — that's zod's job
  • Locale-aware anything — string order is code-unit lexicographic

License

MIT