Custom assertions
assertcheck exposes its internal formatting primitives so you can build domain-specific assertions that match the library's output style exactly — same format, same structure, same developer experience.
When to write a custom assertion
The built-in assertions cover generic invariants. Write a custom assertion when you have a domain-specific invariant that appears in multiple places and deserves a named contract:
assertValidOrderId— "must be a non-empty string inord_*format"assertSupportedCurrency— "must be one of the ISO 4217 codes we accept"assertValidEmail— "must match RFC 5322 format"assertPositiveInteger— "must be an integer strictly greater than 0"
A named custom assertion communicates intent. assert.string(v) + assert.matches(v, /^ord_/) works, but assertValidOrderId(v) is self-documenting and produces a richer error.
Building a custom assertion
import { buildBlock, color, fmtValue, fail, parseOpts } from "assertcheck"
import type { AssertOptions } from "assertcheck"
export function assertPositiveInteger(
value: unknown,
opts?: string | AssertOptions
): asserts value is number {
if (typeof value === "number" && Number.isInteger(value) && value > 0) return
const o = parseOpts(opts)
fail({
assertion: "positiveInteger",
message: buildBlock({
assertion: "positiveInteger",
title: o.msg ?? "Expected a positive integer",
rows: [
{ label: "expected", value: "integer > 0", indicator: color.added("+") },
{ label: "received", value: fmtValue(value), indicator: color.removed("✗") },
],
note: o.note,
}),
actual: value,
expected: "positive integer",
})
}The output matches the assertcheck style exactly:
══════════════════ ● Expected a positive integer ══════════════
── values ──────────────────────────────────────────────────────
+ expected integer > 0
✗ received -3
════════════════════════════════════════════════════════════════A domain-specific example: order ID format
import { buildBlock, color, fmtValue, fail, parseOpts } from "assertcheck"
import type { AssertOptions } from "assertcheck"
const ORDER_ID_PATTERN = /^ord_[a-z0-9]{16}$/
export function assertValidOrderId(
value: unknown,
opts?: string | AssertOptions
): asserts value is string {
if (typeof value === "string" && ORDER_ID_PATTERN.test(value)) return
const o = parseOpts(opts)
fail({
assertion: "validOrderId",
message: buildBlock({
assertion: "validOrderId",
title: o.msg ?? "Invalid order ID format",
rows: [
{ label: "expected format", value: "ord_<16 hex chars>", indicator: color.added("+") },
{ label: "received", value: fmtValue(value), indicator: color.removed("✗") },
],
note: o.note ?? "Order IDs are generated by createOrder() — do not construct them manually.",
}),
actual: value,
expected: "valid order ID",
})
}Usage:
assertValidOrderId(orderId, {
msg: "orderId must be a valid order ID",
note: "check that the ID comes from the orders service",
})Available formatting primitives
| Export | Purpose |
|---|---|
buildBlock(def) | Build the full formatted error string from a BlockDef |
fmtValue(v) | Format any value for display — handles nesting, truncation, type labels |
color | ANSI colour helpers: color.added, color.removed, color.index, etc. |
parseOpts(opts) | Normalise string | AssertOptions | undefined → { msg, note, actual } |
fail(opts) | Output the formatted error and throw AssertionError |
diffObjects(a, b) | Generate diff rows between two objects (for deep equality assertions) |
Negation: assert.not instead of assertNotX
Rather than writing a separate assertNotPositiveInteger, use assert.not:
assert.not(assertPositiveInteger, value)
// passes if assertPositiveInteger would throw — i.e. value is NOT a positive integerThis works with any assertion function — built-in or custom.
Composing with built-in assertions
Custom assertions can call built-in ones internally. This is the cleanest way to build layered contracts:
export function assertValidUser(value: unknown): asserts value is User {
assert.object(value, "user must be an object")
assert.hasKeys(value as object, ["id", "email", "role"], "user is missing required fields")
assertValidEmail((value as any).email, {
msg: "user.email must be a valid email address",
note: "check the registration form validation",
})
}Each layer adds domain-specific meaning while reusing the built-in primitives for low-level checks.