Skip to content

AssertCheckDefine what your code must never accept.

Production-grade assertion library for TypeScript. Explicit contracts, fail-fast execution, richly formatted errors — zero overhead when silent.

TypeScript types vanish at runtime. Assertions don't.

Static types are a compile-time promise. At runtime, that promise is gone. Every null you thought was impossible, every shape you assumed was enforced — they all arrive anyway. The standard response is defensive code: if (!x) return. Silent. Invisible. The bug propagates.

AssertCheck replaces that with a contract system: declare what must never happen, fire exactly where it happens, and surface exactly what went wrong.

ts
function chargeOrder(order: Order) {
  if (!order) return                       // absorbed — caller gets undefined, no trace
  if (!order.amount) return               // absorbed — negative amounts silently pass
  if (order.status !== "pending") return  // absorbed — double-charge possible

  // Three broken assumptions. None of them surfaced. Ever.
}
ts
function chargeOrder(order: Order) {
  assert.notNil(order, "order is required")
  assert.positive(order.amount, "order amount must be positive")
  assert.equal(order.status, "pending", {
    msg:    "order must be pending before charge",
    actual: "order.status",
    note:   "call resetOrder() before retrying",
  })

  // Every precondition is named, enforced, and traceable.
}

What a failure actually looks like

When order.status is "paid" instead of "pending", you get a formatted diagnostic — not a cryptic stack trace.

══════════════════ ● order must be pending before charge ══════

── values ──────────────────────────────────────────────────────
  + expected        "pending"
  ✗ order.status    "paid"

── note ────────────────────────────────────────────────────────
  call resetOrder() before retrying

════════════════════════════════════════════════════════════════

Exact location. Exact values. Exact next step. No debugger, no guessing, no context-switching.

Deep equality failures go further — a field-by-field structural diff:

══════════════════ ● User shape mismatch ══════════════════════

── diff ────────────────────────────────────────────────────────
  ·  id           "usr_123"
  ~  status
       expected   "active"
       actual     "banned"
  +  role         "admin"   ← missing in actual

════════════════════════════════════════════════════════════════

Output adapts automatically: ANSI on TTY, plain text in CI, console.groupCollapsed in the browser.


Why not Zod? Why not if/return?

if/returnnode:assertzod / yupassertcheck
Fails loudly in devYesYesYes
Zero overhead in prodYes (disabled mode)
Type narrowingYesYes
Structured, readable errorsPartialPartialYes
Chainable fluent APIYes
Enforces function invariantsYes
AI Copilot skills includedYes

Zod and Yup are schema validators — they validate data that comes in from the outside (forms, APIs, JSON). AssertCheck is a contract system — it enforces invariants at every internal boundary: function arguments, state transitions, external responses, collection shapes. The two are complementary, not competing.


50+ assertions. One consistent pattern.

Every assertion accepts an optional opts parameter with a message, an actual-value label, and a hint for the developer.

ts
import { assert, check } from "assertcheck"

// Existence & type guards
assert.notNil(user, "user is required")
assert.string(userId, "userId must be a string")
assert.positive(price, "price must be positive")

// Deep equality with structural diff
assert.deepEqual(result, expected, "API response shape changed")

// Chainable invariants on collections
check(orders)
  .notEmpty("cart must not be empty before checkout")
  .noNils("no null order items allowed")
  .all(o => o.amount > 0, "all orders must have a positive amount")
  .uniqueBy("id", "duplicate order IDs detected")

// Object shape guards
check(config)
  .hasKeys(["host", "port", "database"], "missing required config keys")
  .dig("database.pool.max", 10, "pool size must be at least 10")

50+ methods covering existence, types, numerics, equality, arrays, objects, and function purity.


Works across the entire TypeScript ecosystem

No configuration. No polyfills. No runtime dependencies beyond lodash.

  • Node.js 18+ — full ANSI output on TTY
  • Bun 1+ — native, tested first
  • Deno / JSRdeno add jsr:assertcheck
  • Browser — DevTools-friendly console.groupCollapsed output
  • Edge runtimes — no Node.js APIs required

Built by Vagabond Studio

AssertCheck is maintained by Vagabond Studio — a fully remote, senior-only collective of engineers and designers.

We build TypeScript, Vue.js, Rails, and Django products from greenfield to production. AssertCheck is how we guard every internal boundary in every service we ship.

Book a discovery call · hello@vagabond.work

Released under the Apache 2.0 License. Built by Vagabond Studio — senior-only for growing companies.