Skip to content

JavaScript / TypeScript SDK

kestrel-sdk — zero dependencies, ~1 KB gzip, passive capture only.

Install

bash
npm install kestrel-sdk
# or: bun add kestrel-sdk / pnpm add kestrel-sdk / yarn add kestrel-sdk

Initialize

Call init() once, as early as possible in your app — before any code that might throw.

ts
import { init } from 'kestrel-sdk'

init({
  endpoint: 'https://kestrel.example.com/api/v1/ingest',
  token: process.env.KESTREL_TOKEN!, // never inline a token; load it from env
  release: process.env.GIT_COMMIT,    // optional, auto-detected when omitted
})

init() registers listeners for the runtime's unhandled-error hooks and returns immediately. Once attached, the following are captured automatically:

  • Browserwindow.error, window.unhandledrejection.
  • Node / Bun / DenouncaughtException, unhandledRejection.

That's the entire integration. Done.

Configuration

ts
init({
  endpoint: string,           // required — your /api/v1/ingest URL
  token: string,              // required — project ingest token
  release?: string,           // build identifier; auto-detected from common env conventions
  fetch?: typeof fetch,       // override fetch (SSR / tests)
  recordConsole?: boolean,    // see "console buffer" below; default false
})

The SDK auto-detects release from any of these (in order): import.meta.env.GIT_COMMIT, import.meta.env.VITE_GIT_COMMIT, import.meta.env.PUBLIC_GIT_COMMIT, globalThis.KESTREL_RELEASE, process.env.KESTREL_RELEASE, process.env.GIT_COMMIT, process.env.npm_package_version. Whichever resolves first wins.

Manual capture

Sometimes you need to record an exception without throwing it. The second argument is a free-form context object attached to that single event:

ts
import { capture } from 'kestrel-sdk'

try {
  await thingThatMightFail()
} catch (err) {
  capture(err, { userId, requestId })
  // ...your fallback logic
}

Manual capture exists, but it's not advertised as the primary integration. The SDK's job is to catch what your code missed; if you're calling capture() everywhere, you're paying for an SDK to do less than a console.error does.

Console buffer (opt-in)

If you want the last N console.* calls attached to each captured event:

ts
init({ ..., recordConsole: true })

This wraps console.log / info / warn / error / debug and keeps a 50-entry ring buffer. The wrapper preserves original behavior (your logs still go to stdout / the browser console).

This is the only thing the SDK monkey-patches, and it's gated behind an explicit opt-in for that reason. The default contract — passive capture only — is honored when recordConsole is omitted.

Sourcemaps

For minified browser bundles, upload the corresponding .map files at deploy time:

bash
kestrel sourcemaps upload ./dist --release "$(git rev-parse --short HEAD)"

The server resolves frames at read time; both the web UI and MCP get_error return both the original (minified) stack and the resolved stack.

Bundle size

bun run build && du -h dist/index.js should print < 5 KB. CI fails the SDK build if it doesn't.

Released under the MIT License.