Skip to main content

Introduction

A TypeScript type-level test framework and a large library of type utilities. Zero runtime cost.

What is duck-ttest?

@gentleduck/ttest is two things in one package:

  1. A small, focused type-level test frameworkAssertTrue, AssertFalse, Equal, and friends. When a test fails, the compiler surfaces a readable message at the failure site.
  2. A library of type utilities organized into 37 narrow modules — any, aria, assert, async, bit, boolean, brand, class, conditional, css, date, discriminated, emitter, equality, extraction, format, fp, fs, function, geometry, guard, http, json, literal, locale, number, object, pattern, predicates, primitive, promise, result, router, sql, template, tuple, union.

Each utility is a type, so the entire package is erased at compile time. There is no runtime, no bundle bytes, and nothing to ship.


Why duck-ttest?

The TypeScript ecosystem already has tsd, expect-type, and type-fest. duck-ttest covers ground each of them touches separately:

  • tsd runs assertions through a CLI on top of tsc. duck-ttest assertions are types that fail your normal type-check — no extra runner needed.
  • expect-type is fluent and ergonomic, but stops at the test framework boundary. duck-ttest ships the framework and the utilities that the framework asserts against.
  • type-fest is a beloved utility library, but has no test side. duck-ttest gives you both: write a utility and assert it the same way the test suite does.

If you build schema generators, ORMs, validation libraries, or type-heavy SDKs, you typically need: a way to declare invariants, a way to check them, and a way to ship them with zero runtime cost. That is what duck-ttest is.


Features

FeatureDescription
Compile-time onlypackage.json ships src/ only. Every export is a type nothing reaches the JS output of consumers.
37 utility modulesSub-path imports such as @gentleduck/ttest/object, @gentleduck/ttest/sql, @gentleduck/ttest/router. Tree-shake by importing only what you need.
AssertionsAssertTrue<T, Msg> and AssertFalse<T, Msg> produce a typed error containing Msg when the assertion fails.
Strict equalityEqual<X, Y> uses the conditional-distribution trick to distinguish any from unknown and preserve variance narrower than X extends Y && Y extends X.
Branded typesBrand<T, 'UserId'> and Unbrand<T> for nominal typing without runtime tagging.
SQL schema inferenceInferSchema<'CREATE TABLE ...'> parses DDL into a typed row shape.
Higher-kinded typesFn / Apply / MapTuple / Reduce / ComposeFn first-class type-level functions.
Domain unionsCurated ARIA roles, CSS keywords, HTTP statuses, ISO locales tiny, accurate, with no string widening unless you ask for it.
Result / OptionRust-style Result<T, E> and Option<T> as type-level discriminated unions.
Router patternsPathParams<'/users/:id'> { id: string }. BuildPath, MatchRoute, QueryParams.
Glob matchingMatchesGlob<S, P> with *, ?, and ** support.

A 30-second tour

import type { AssertTrue, Equal } from '@gentleduck/ttest/assert'
import type { IsAny, IsNever } from '@gentleduck/ttest/any'
import type { InferSchema } from '@gentleduck/ttest/sql'

// 1. Test a type the same way you'd test a function.
type t1 = AssertTrue<Equal<IsAny<unknown>, false>, 'unknown is not any'>
type t2 = AssertTrue<Equal<IsNever<never>, true>, 'never should be detected'>

// 2. Use the utilities directly in your own types.
type User = InferSchema<'CREATE TABLE users (id INT PRIMARY KEY, email TEXT NOT NULL)'>
//   ^? { id?: number; email: string }

If Equal<IsAny<unknown>, false> ever became false, the message 'unknown is not any' would surface as a Type 'string' is not assignable to type 'never' error pointing at t1.


Architecture

Loading diagram...

There is no runtime layer in this diagram. The tsc step is the runner.


Documentation map

SectionWhat it covers
InstallationInstall the package, configure tsconfig, and import a sub-path.
Getting startedFive-minute tutorial: write a failing test, see the error, fix it.
ComparisonHonest tradeoffs versus tsd, expect-type, and type-fest.
Migrationv0.x v1.0 breaking renames.
Core — AssertionsAssertTrue, AssertFalse, Equal, Extends.
Core — Type-level testingWorkflow with tsc, vitest @ts-expect-error, and tsd.
Core — Composing testsNaming, organization, and pitfalls.
API referenceEvery exported type across all 35 modules.
GuidesBranded types, discriminated unions, schema builders, regression suites.

Philosophy

In a schema generator, ORM, or framework, the type is the contract. duck-ttest closes the loop between authoring a contract and verifying it — at the same compile step that catches every other type error.