tsnapi
Library public API snapshot testing for runtime exports and type declarations.
tsnapi: public API snapshot testing for TypeScript libraries
tsnapi captures a package's public API, both runtime exports and type declarations, into committed snapshots that fail the build on unexpected change.
What tsnapi captures
tsnapi is a tool for library public API snapshot testing that covers both runtime exports and type declarations. It captures your public API surface into human-readable snapshot files that you commit alongside your code, so when the API changes unexpectedly you find out. The README compares it to Vitest's snapshot testing but aimed at your package's public contract. When maintaining a library it is easy to remove or rename an export, change a function signature, break a type declaration, or introduce unintended public surface, and tsnapi makes these changes visible in your git diff. Every build produces a pair of snapshot files per entry point: a dot-snapshot.js file for what the package exports at runtime and a dot-snapshot.d.ts file for what it exports as types. These files are committed to the repo, and when they change you review the diff like any other code change. The recommended way to use it is as a Rolldown or tsdown plugin, where the first build writes snapshots and later builds compare against them and fail the build with a diff if the API changed. You update snapshots intentionally with an update flag or environment variable, so the snapshots stay a deliberate record rather than an auto-mutating artifact that hides regressions. Committing the snapshots means a team reviews API changes in the same pull request as the code that caused them, so the contract and the implementation move together.
The breaking changes guard
When you update snapshots, tsnapi classifies the change as additive or breaking. A breaking change aborts the update without overwriting the snapshot, so you cannot silently ship a breaking API change through a routine update flag. The guard is deliberately lossy: it favors not blocking a change to avoid false positives, while reliably catching the important case of something being removed. A change is treated as breaking only when a declaration loses something. Removing an export, removing an interface member or parameter or union arm, or replacing a type with an unrelated one is breaking. Adding a new export, adding an interface property, widening a parameter or return type with a union, or adding a parameter is additive and passes through. Some genuinely breaking changes, such as widening a return type that callers may not expect, are intentionally allowed to keep false positives low. A normal comparison build still fails on any change, additive or breaking, so every change lands in your git diff regardless. When a change really is intended you opt out with a CLI flag, an environment variable, or a plugin option, and the README shows each form, which keeps the safety net in place while still allowing deliberate evolution of the public surface. The lossy guard prefers to avoid false alarms, which keeps the safety net trusted rather than something engineers learn to bypass after a few annoying interruptions.
Usage paths
tsnapi offers several ways to run. As a CLI you snapshot the current package by reading its package.json exports and parsing the built dist files, after you have run the build. As an interactive inspector, `tsnapi ui` visualizes the public API of every package in a workspace as a graph and traces how it changed between two git refs, built on devframe and an antfu design package. The inspector only reads already-generated snapshot files and never regenerates them, reading historical refs through git show and the working tree from disk. With Vitest, tsnapi provides higher-level integration that stores snapshots as individual files and intercepts updates to apply the same breaking change guard. For monorepos, a describePackagesApiSnapshots helper auto-discovers workspace packages from pnpm-workspace.yaml or the workspaces field and creates a describe block per package, with filter, beforeEach, and afterEach callbacks that receive a package context. A low-level generateApiSnapshot function lets you use tsnapi with Vitest's built-in snapshot system directly. As a library you can call snapshotPackage and inspect the result's hasChanges and diff fields in your own code, so the tool fits both build pipelines and custom checks. The inspector reads only existing snapshots, so it never changes the recorded API while you browse differences between revisions in the browser view it builds.
Options and output shape
tsnapi exposes options that shape the snapshot. The output directory defaults to a snapshots folder, and the runtime and dts extensions default to dot-snapshot.js and dot-snapshot.d.ts. Argument names are omitted from function signatures by default, and type widening is on by default so literal values are widened to base types and implementation details are hidden. Reference tracing depth controls how many hops of non-exported types reachable from exports are inlined into a dedicated region, defaulting to one hop so a referenced internal type's shape appears in the snapshot and changes to it participate in the guard. Comments are stripped from snapshots, but a deprecated JSDoc marker is kept on the line above a declaration so adding or removing the tag shows in the diff. The tool is inspired by rolldown-plugin-dts-snapshot for the AST parsing approach and by vitest-package-exports for the idea of snapshotting package exports for regression detection. tsnapi is released under the MIT license and is authored under the antfu namespace, with 198 stars and zero open issues at the time of the last repository update, which suggests a small and stable surface that a single maintainer can keep current. The antfu namespace and small open issue count suggest a focused tool that one maintainer keeps aligned with the tsdown bundler and the surrounding TypeScript ecosystem.
Editorial conclusion
tsnapi is written in TypeScript, distributed under the MIT license, and its repository was last updated on 2026-08-24.
Community notes