← back to gallery

SDK Canary

Type-level breaking-change diffs for every AI SDK on npm

dev-toolsnpmtypescriptai-sdksdiffanthropicopenaivercel-aimcplangchain
Open product ↗

sdk-canary

Live early-warning system for type-level breaking changes in popular AI SDKs on npm.

sdk-canary downloads every published version of @anthropic-ai/sdk, openai, Vercel ai, @modelcontextprotocol/sdk, langchain, and ~20 other AI SDKs straight from the npm registry, extracts the .d.ts files inside each tarball, parses every top-level export, and computes a per-version diff. The dashboard surfaces what's been added, removed, and modified — and flags releases that ship a breaking change in a non-major bump as a silent break.

Why this exists

The AI SDK ecosystem ships every 5–10 days. messages[].content quietly changes from string to (string | ContentBlock[]); beta.messages.create is moved to messages.create; tool-result enums get renamed. Nobody plots that history publicly. Devs find out at runtime, after the bump.

sdk-canary is the missing source of truth — a TypeScript-diff equivalent of an npm release feed.

Data sources (no mocks, no seeds, no Math.random())

Every export and diff in the database is traceable to a real fetch from one of these public endpoints. If a fetch fails the prior good value is preserved and last_error is recorded; the next cron retries. The dashboard never shows fabricated numbers.

| Source | URL | Auth | Cadence |
|------------------------------------:|----------------------------------------------------------|------|-------------------------------|
| npm registry — package metadata | https://registry.npmjs.org/<pkg> | none | every 2 h per package |
| npm registry — package tarball | <tarball_url> from registry response | none | once per version (cached) |
| npm downloads (weekly point) | https://api.npmjs.org/downloads/point/last-week/<pkg> | none | every 2 h (rotated 12 at a time) |
| daily full sweep | (combined above) | none | 03:13 UTC |

Stack

Quick start

cp .env.example .env
npm install
npm start
# open http://127.0.0.1:4897/sdk-canary/

The DB is created at ./data.db on first boot. The seed list lives at data/seed-packages.json — add/remove entries there, no code changes required.

On first boot the dashboard shows the empty-state copy First fetch in progress — refresh in 2 minutes. The boot kicks off one immediate meta+tarball cycle so the table populates within ~30 s for the first few packages and within ~2 weeks at the polite default cadence for the full backlog.

Endpoints (all public)

| Method | Path | Purpose |
|-------:|-------------------------------------------------|------------------------------------------------------------------------|
| GET | /sdk-canary/health | {ok:true, uptime, version} |
| GET | /sdk-canary/ | SPA |
| GET | /sdk-canary/api/stats | Totals + last-fetch timestamp |
| GET | /sdk-canary/api/packages | All tracked packages with 30-day breaking score |
| GET | /sdk-canary/api/packages/:name | One package: metadata + recent versions + recent diffs |
| GET | /sdk-canary/api/packages/:name/versions | All versions of one package |
| GET | /sdk-canary/api/packages/:name/diff?from=A&to=B | Stored or on-the-fly diff between two versions |
| GET | /sdk-canary/api/changes?days=7&breakingOnly=1 | Recent breaking-change events across all packages |
| GET | /sdk-canary/api/search?q=foo | Search export names in the latest version of every package |
| GET | /sdk-canary/api/runs?limit=50 | Last N fetch-run log entries |
| GET | /sdk-canary/api/feed.json | JSON Feed of recent diffs |
| GET | /sdk-canary/api/feed.atom | Atom feed |
| POST | /sdk-canary/api/refresh | Manual full refresh (debounced 5 min) |
| POST | /sdk-canary/api/refresh/:name | Manual single-package refresh (debounced 60 s) |

Schema

packages       → one row per tracked npm package
versions       → one row per (package, version)
exports        → one row per (version, kind, name) parsed from .d.ts
diffs          → one row per (package, from_version, to_version) with scores
diff_entries   → per-export added / removed / modified records
fetches        → audit log: every HTTP call with status, code, ms, bytes

Scoring

For each from → to pair:

breaking_score = removed * 10 + modified * 5
honest_semver  = 1 iff (breaking_score == 0) OR (bump in [major, premajor]) OR (from < 1.0.0)

Diffs with honest_semver = 0 are the flagship signal — a breaking change shipped in a patch or minor bump.

Known limitations

License

MIT.