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

wiretype

v0.4.4

Published

Record real API traffic and generate TypeScript types, zod schemas, MSW mocks, and OpenAPI 3.1 specs — from what your backend actually returns.

Readme

wiretype

The docs lie. The wire doesn't.

Record real API traffic and generate TypeScript types, zod schemas, MSW mocks, and OpenAPI 3.1 specs — from what your backend actually returns.

CI npm license

English | 한국어


Every frontend team knows this pain: the API docs say avatarUrl: string, but production returns null. The spec swears role is a free string, but it's always one of three values. Your MSW mock data drifted from the real server months ago, and nobody noticed until the demo broke.

wiretype fixes this by going to the source of truth — the actual bytes on the wire. Put its recording proxy in front of your dev API (or drop the Vite plugin into your dev server), use your app like you normally would, and generate:

wiretype-generated/
  types.ts      TypeScript interfaces for every endpoint (params / query / request / response)
  schemas.ts    zod schemas + z.infer type aliases
  handlers.ts   MSW v2 handlers seeded with real captured responses
  openapi.json  OpenAPI 3.1 spec
  model.json    the raw observed model — input for `wiretype diff` (drift detection)

wiretype demo — record, generate, catch drift

Try it in 30 seconds

npx wiretype demo

No setup, no config, nothing leaves your machine. It spins up a local demo API, records real traffic through the proxy, generates all five outputs — then "six months later" the backend quietly changes, and wiretype diff catches every breaking change. The whole loop, one command.

Quickstart

npm i -D wiretype

Option A — standalone proxy (works with anything):

# 1. Put the recording proxy in front of your API
npx wiretype record --target http://localhost:8080 --port 5050

# 2. Point your app at :5050 and click around. Every request is recorded.

# 3. Generate all four outputs
npx wiretype gen

# 4. Explore recordings, inferred types, and generated code in a dashboard
npx wiretype ui

Option B — Vite plugin (zero workflow change):

// vite.config.ts — keep your server.proxy as is; wiretype steps in front of
// these prefixes only while recording and stays inert otherwise
import wiretypeRecorder from 'wiretype/vite';

export default defineConfig({
  plugins: [
    wiretypeRecorder({
      target: 'http://localhost:8080',
      prefixes: ['/api'],
    }),
  ],
});
vite --mode record   # recording on; plain `vite` leaves the plugin inert

Heads-up on --mode record: changing the Vite mode changes which .env.* files load (.env.record instead of .env.development). If your app needs .env.development, use the env-var switch instead — it records without touching the mode:

WIRETYPE=1 vite                      # macOS / Linux
npx cross-env WIRETYPE=1 vite       # cross-platform

Leave the plugin in the array permanently — it only records when the dev server runs in mode record or the WIRETYPE env var is set (pass enabled to override). Develop as usual, then npx wiretype gen — with a single recording in the store, no flags needed. Recordings land in .wiretype/, where wiretype drops a .gitignore so captured payloads never end up in git.

What gets inferred

wiretype merges every observed sample per endpoint, so the more you click, the more accurate it gets:

| Feature | Example | |---|---| | Optional fields | key missing in some samples → avatarUrl?: string | | Nullable | lastLoginAt: string \| null | | Enums (conservative) | repeated token-like strings only → role: "admin" \| "editor" \| "viewer" | | String formats | uuid, date-time, date, email, uri → z.string().uuid(), .datetime(), ... | | Integer vs float | z.number().int() | | URL normalization | /api/users/42, /api/users/7GET /api/users/:userId (numeric / uuid / hash segments) | | Per-status responses | 200 and 404 become separate types (GetUserResponse, GetUserResponse404) | | Query params | ?page=2&limit=10{ page: number; limit: number } |

Enum detection is deliberately conservative: only token-like strings (admin, in_progress) that actually repeat across 4+ samples become literal unions — so ids and titles never get frozen into enums.

Example output

// types.ts — generated by wiretype
export interface GetApiUsersByUserIdResponse {
  /** format: uuid */ id: string;
  name: string;
  role: "admin" | "editor" | "viewer";
  /** format: date-time */ createdAt: string;
  lastLoginAt: string | null;
  avatarUrl?: string;
}

export interface ApiEndpoints {
  "GET /api/users/:userId": {
    params: GetApiUsersByUserIdParams;
    query: never;
    request: never;
    response: GetApiUsersByUserIdResponse;
  };
  // ...every recorded endpoint
}
// handlers.ts — MSW v2, seeded with the real response your server sent
export const handlers = [
  http.get('*/api/users/:userId', () =>
    HttpResponse.json({ id: '2b8f0a3e-...', name: 'Ada Lovelace', role: 'admin', ... }, { status: 200 }),
  ),
];

Wiring the generated handlers into MSW is the usual three lines:

// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from '../../wiretype-generated/handlers';

export const worker = setupWorker(...handlers);

Prefer mock data out of handler code? wiretype gen --msw-fixtures writes each body to fixtures/<operationId>.<status>.json and emits a thin handlers.ts that imports them — re-recording refreshes the JSON without ever touching handler code. (JSON imports may need resolveJsonModule: true in your tsconfig.)

CLI

wiretype demo    [--dir .wiretype] [--out wiretype-demo]
wiretype record  [--target <url>] [--port 5050] [--name session] [--dir .wiretype]
                 [--include <prefix...>] [--exclude <prefix...>]
wiretype gen     [--name <recording>] [--dir .wiretype] [--out wiretype-generated]
                 [--targets ts,zod,msw,openapi,model] [--msw-fixtures]
wiretype claims  --map claims.map.json [--out claims.json] [--tsconfig <file>] [--strict]
wiretype diff    <a> <b> | --claims <a> --observed <b>
                 [--dir .wiretype] [--json] [--md] [--lang en|ko]
                 [--fail-on breaking|risky|info] [--ignore-unmatched]
wiretype list    [--dir .wiretype]
wiretype ui      [--dir .wiretype] [--port 5099]

Repeated flags can live in a config file — wiretype.config.mjs (or .js / .json) in the working directory, shared by every command AND the Vite plugin (explicit flags/options always win):

// wiretype.config.mjs
import { defineConfig } from 'wiretype';

export default defineConfig({
  target: 'http://localhost:8080',
  prefixes: ['/api'],
  dir: '.wiretype',
  name: 'dev',
});

With the config in place the Vite plugin shrinks to wiretypeRecorder().

wiretype ui serves a zero-dependency dark-theme dashboard: per-endpoint inferred type trees, raw request/response explorer, all four generated outputs with copy buttons — and a Drift tab that runs the deterministic diff in the browser: pick a baseline (an older recording, or a claims.json/model.json file) and see exactly which endpoints changed, severity-grouped with sample-count confidence markers. The two everyday questions — "did the API drift from my MSW mocks?" and "are my TS/zod types lying?" — answered without leaving the browser.

Vite plugin options

wiretypeRecorder({
  target: 'http://localhost:8080', // upstream API base URL (required)
  prefixes: ['/api'],              // paths to intercept + record (required)
  name: 'dev-session',             // recording name (default "vite")
  dir: '.wiretype',                // store directory
  enabled: true,                   // override auto-detect (default: mode "record" or WIRETYPE env)
  excludePrefixes: ['/api/noisy'], // proxied but not recorded
  maxBodyBytes: 1_048_576,         // capture cap (bodies beyond are truncated)
  redactHeaders: ['authorization'],// default: authorization, cookie, set-cookie, x-api-key
})

How it works

  1. Record — a zero-dependency reverse proxy (Node built-ins only) streams traffic through untouched, capturing method, path, query, headers (sensitive ones redacted), and JSON bodies. Gzip/brotli responses are forwarded raw and decoded only for capture.
  2. Infer — paths are normalized into patterns, then every JSON body per endpoint per status is merged into a shape AST: unions, optionals, nullability, formats, and enums fall out of the merge rules.
  3. Generate — four emitters render the same model into TypeScript, zod, MSW, and OpenAPI. Output is deterministic and compiles under tsc --strict.

Schema drift detection

Recording once gives you types. Recording twice gives you a contract test. wiretype diff compares two observed models (or a model against a committed baseline) and grades every change:

$ wiretype diff v1 v2
wiretype diff — a: v1 (1 endpoints) vs b: v2 (2 endpoints)
  1 breaking, 2 risky, 3 info · 1 compared, 0 only-in-a, 1 only-in-b

BREAKING (1)
breaking | field-removed       | GET /api/items/:itemId | [200] name   | string → -

RISKY (2)
risky    | format-changed      | GET /api/items/:itemId | [200] sku    | string (uuid) → string
risky    | enum-values-changed | GET /api/items/:itemId | [200] status | "active" | "archived" → "active" | "archived" | "draft"

INFO (3)
info     | type-changed        | GET /api/items/:itemId | [200] price  | number (integer) → number
...

Semantics: side a is what consumers believe (an older recording, a committed baseline, or a claims model extracted from your source), side b is observed reality — breaking means code written against a breaks under b. Gate it in CI:

# record against the new deploy, then:
wiretype gen --targets model --out baseline-check
wiretype diff baseline/model.json baseline-check/model.json --fail-on breaking

# Markdown report for PR comments, localized (en|ko):
wiretype diff baseline/model.json baseline-check/model.json --md --lang ko

--md prints a Markdown report (summary + one findings table per severity); --lang localizes the headings and labels while machine fields (endpoints, paths, before → after types) stay untranslated. --json is never localized.

The full rule set (nullability, optionality, enum widening, format loss, status changes, ...) is deterministic and documented in docs/ARCHITECTURE.md.

Every finding carries the number of observed samples backing it (the Samples (b) column); findings with fewer than 3 samples are marked ⚠ — wiretype tells you when the wire hasn't said enough yet, instead of presenting a thin inference as fact.

Audit your code against the wire — wiretype claims

The drift engine can also judge your hand-written types. Point a claims map at the exported types your code uses, and the TypeScript compiler translates them into a claims model — deterministically, no guessing:

// claims.map.json
{
  "entries": [
    { "method": "GET", "pattern": "/api/users/:userId",
      "response": "src/apis/user/types.ts#UserDetail" }
  ]
}
npx wiretype claims --map claims.map.json --out claims.json
npx wiretype diff --claims claims.json --observed dev-session --ignore-unmatched

breaking now means: this interface lies about the real API. Anything the compiler cannot translate faithfully (unresolved generics, Date, functions, recursive types) is refused and listed as not-auditable — never silently guessed. Generic wrappers are claimed via a one-line exported shim: export type GetUserClaim = ApiResponse<UserDetail>.

The generated types.ts/handlers.ts are verification and mock artifacts — wiretype audits and fixes your types rather than asking you to adopt generated names like GetApiUsersByUserIdResponse in app code.

Claude agent plugin

The repo ships a Claude Code / Cowork plugin (claude-plugin/) with an api-drift-audit skill: the agent finds your API call sites and points a claims map at the types they use; wiretype claims translates those types with the TypeScript compiler and wiretype diff delivers the verdict — then the agent maps every breaking/risky finding to file:line and offers to fix types, refresh MSW mocks, or add zod guards. The agent only discovers and explains; both the translation and the judgment stay deterministic.

claude plugin marketplace add ehdrms785/wiretype
claude plugin install wiretype

The skills trigger from natural language ("audit src/apis against the real API", "refresh my MSW mocks from real traffic"), or you can invoke the slash commands explicitly:

  • /wiretype-audit [folder] — drift-audit code types/zod/mocks vs real traffic
  • /wiretype-msw — reconcile MSW mock data with freshly recorded responses

How is this different from…

  • openapi-typescript / orval — those need a spec that's correct. wiretype needs no spec at all; it derives one (and catches where the real API disagrees with the docs).
  • quicktype — quicktype types one JSON sample. wiretype merges many samples per endpoint (that's where optionals, nullables, and enums come from) and understands HTTP: routes, params, query, status codes.
  • HAR-based generators — HAR exports are one-shot and manual. wiretype records continuously while you develop and regenerates in one command.

Limitations

  • Inference is observation-based: fields never seen are never typed. Click more, get more.
  • REST/JSON only — no GraphQL.
  • WebSocket traffic passes through unrecorded.
  • Bodies over 1 MiB are truncated and skipped for JSON parsing.

License

MIT © daro