# JavaScript & TypeScript

The reference BugWatch SDK: one package for Node services, browsers, edge runtimes and every major JS framework, with first-class TypeScript types.

## Requirements

- Node **20.19+** for server use; evergreen browsers; ships dual ESM and CommonJS builds
- Subpath types resolve on every TypeScript setting including legacy `"moduleResolution": "node"` (the package ships `typesVersions`); never alias `@newinstance/bugwatch/*` in `compilerOptions.paths`
- Edge runtimes (Cloudflare Workers, Vercel Edge) use the core client and must not import `./node` (no AsyncLocalStorage there); Bun and Deno get core plus the Hono adapter
- All framework and logger peers are optional. Minimums: express 4, fastify 4, koa 2, hono 4, hapi 20, nest 9, react 16.8, vue 3, pino 8, winston 3, bunyan 1.8, log4js 6, @opentelemetry/api 1.4

## 1. Install

```bash
npm install @newinstance/bugwatch
```

The credential is the project DSN key (`sk_test_…` or `sk_live_…`, one per environment) from the dashboard project's Settings under DSN keys.

## 2. Initialise

Server (Node):

```ts
import { BugWatch } from "@newinstance/bugwatch";
import { installNodeErrorHandlers } from "@newinstance/bugwatch/node";

const client = BugWatch.init({
  projectKey: process.env.BUGWATCH_KEY!,
  release: process.env.APP_VERSION,
});
installNodeErrorHandlers(client);
```

`init` is idempotent (a singleton on `globalThis`, safe under hot reload) and returns the existing client on repeat calls; captures before init are no-ops that still return a generated id. `installNodeErrorHandlers` is idempotent and returns an uninstall function; `installAsyncScope` + `runWithContext` give per-unit isolation to non-framework code (queues, workers).

Browser (never ship the key): one import, one call since v0.2.0:

```ts
import { initBrowser } from "@newinstance/bugwatch";

export const client = initBrowser({ sessionUrl: "/bugwatch/session" });
```

`initBrowser` is SSR-safe and idempotent, installs the window error and unhandled-rejection handlers, resolves a relative `sessionUrl` against the page origin, and **never throws**: bad config or an unreachable mint endpoint degrades to a working no-op client plus a single console warning. `getBrowserClient()` returns the singleton anywhere else. The pre-0.2.0 `createClient` + `installBrowserErrorHandlers` pair still works but is legacy.

Next.js gets a dedicated browser-safe entry, `@newinstance/bugwatch/next/client` (no Node built-ins, unlike the server-side `./next`): `initBrowser` for `_app.tsx` or a client component in `layout.tsx`, `BugWatchErrorBoundary`, and `captureRenderError(error, { digest })` for App Router `error.tsx`.

Your backend exposes the mint endpoint: Express has a ready-made `bugWatchBrowserSessionHandler`; anything else calls `mintBrowserSession` which returns `{ token, expiresAt }`. The transport refreshes the token 30 seconds before expiry and on a 401.

## 3. Configuration

| Option | Default | What it does |
|--------|---------|--------------|
| `projectKey` / `sessionUrl` | None | Server credential, or the browser mint endpoint |
| `release` / `environment` | None / from key | Labels on every event |
| `enabled` / `debug` | true / false | Master switch; debug prints delivery diagnostics including the server's reason string after the HTTP status |
| `sampleRate` | 1 | Applied **before** `beforeSend`; keep-all-errors sampling belongs in `beforeSend` |
| `captureUnhandledErrors` / `captureUnhandledRejections` | true | Global handlers, chained not replaced |
| `sensitiveFields` | built-in list | Extra redaction keys |
| `beforeSend` | None | May be async; return null to drop, or mutate and return the event |
| `console` | on | Local echo of the native logger: `false` or `{ enabled, level, format, colors }`, where `format` is `"auto"`, `"pretty"` or `"json"` |
| `batchSize` / `flushInterval` | 50 / 5000 ms | Delivery batching; `flushInterval: 0` disables the timer (manual flush only) |
| `maxQueueSize` | 1000 | Overflow drops the oldest queued events |
| `requestTimeout` | 15000 ms | Per-request timeout |
| `retry` | 3 attempts, 200 ms to 5 s | Exponential backoff |
| `traceContextProvider` | None | Auto-attach traceId and spanId (OTel below) |

## 4. Capture API

```ts
const id = BugWatch.captureException(err, { tags: { route: "/checkout" } });
BugWatch.captureMessage("Payment settled", 30);
BugWatch.captureLog({ level: "warn", message: "Slow query", tags: { db: "orders" } });
BugWatch.setUser({ id: "u_123", email: "ada@example.com" });
BugWatch.setTag("tenant", "acme");
BugWatch.setContext("payment", { provider: "paystack" });
BugWatch.addBreadcrumb({ category: "cart", message: "cart validated", data: { items: 3 } });
BugWatch.setRelease("checkout@2.4.1");
await BugWatch.flush();
await BugWatch.close();
```

- Levels are Pino-compatible numerics 10 to 60 (the `LEVELS` map is exported); `captureLog` takes one object.
- The `captureException` hint accepts `level`, `tags`, `user`, `traceId`, `spanId`; on `captureMessage` the level is the positional second argument and the hint (third argument) carries `tags`, `user` and trace ids.
- Cause chains are automatic: errors built with `new Error(msg, { cause })` (and `AggregateError`) ship a structured `causes[]` array, each cause with its own type, message and stacktrace (depth 3, cycle-safe), rendered as the Caused-by chain on the issue page.
- `addBreadcrumb({ category, type, level, message, data })` keeps a bounded trail (last 50, per-request inside an adapter scope) attached to every event.
- `setContext(name, data)` groups arbitrary structured data on the event (`contexts` on the wire), redacted like everything else.
- Call `await BugWatch.close()` on SIGTERM so the queue drains before exit.

## 5. Framework adapters

**Every framework below has its own dedicated page** (in this section's sidebar) with install, wiring order, identity, tracing, a complete runnable example and troubleshooting: Express, Fastify, Koa, Hono, Hapi, NestJS, Next.js, React, Vue, Browser, and Node Workers. The table here is the quick summary.

Every adapter accepts a `getUser` option, resolved lazily at capture time, which replaces the auth-middleware pattern entirely; an explicit `setRequestUser` still wins. Adapters never swallow errors: they capture, then rethrow or call `next(err)`.

Every server adapter also reads the inbound W3C `traceparent` header into the request scope, so captures and logs join the caller's trace automatically; when the header is absent the scope synthesizes a fresh trace id, so every request is traceable end to end either way (see **Distributed tracing** below).

| Stack | Subpath | Entry points and notes |
|-------|---------|------------------------|
| Express | `./express` | Request handler first, error handler last; `bugWatchBrowserSessionHandler` for the mint route |
| Fastify / Koa / Hono / Hapi | `./fastify` etc. | `bugWatchFastify`, `bugWatchKoa`, `bugWatchHono` (pass `opts.context` to read Hono's `c`), `createBugWatchHapiPlugin` (opens a per-request scope at onRequest, so `setRequestUser`/`setRequestTag`/`setRequestContext` work inside handlers) |
| NestJS | `./nest` | `BugWatchExceptionFilter`, `bugWatchNestMiddleware` |
| Next.js and serverless | `./next` (server) · `./next/client` (browser) | `withBugWatchRouteHandler` opens a per-request scope and flushes before return (same wrapper pattern for Lambda, Vercel, Netlify); the client entry ships `initBrowser`, `BugWatchErrorBoundary` and `captureRenderError` with no Node built-ins |
| React / Vue | `./react`, `./vue` | `BugWatchErrorBoundary` (captures render errors with component stack; renders nothing without a `fallback`), `createBugWatchVuePlugin` (preserves an existing errorHandler) |

## 6. Logger integrations

**Each logger has its own dedicated page** in this section's sidebar (Pino, Winston, Bunyan, log4js, Console, Native Logger) covering wiring, level and tag mapping, error handling and a complete example. Quick summary:

| Logger | Factory |
|--------|---------|
| Native | `createLogger()` with `child(bindings)`, zero deps, echoed locally per the `console` option |
| Pino | `createBugWatchPinoDestination` (only scalar fields become tags; objects and arrays are dropped) |
| Winston | `createBugWatchWinstonTransport` (error 50, warn 40, info 30, verbose and debug 20, silly 10) |
| Bunyan / log4js | `createBugWatchBunyanStream`, `bugWatchLog4jsAppender` |
| console | `captureConsole(client, { levels })`, returns a `restore()` |

## 7. Testing

```ts
import { InMemoryTransport } from "@newinstance/bugwatch/testing";
const transport = new InMemoryTransport();
const client = new BugWatchClient(opts, { transport });
transport.events; transport.find(pred); transport.reset();
```

`InMemoryTransport` intercepts events only; spans export separately over `fetch`. In tests either init with `enabled: false`, avoid flushing spans, or inject a fake `fetch` through the `SpanExporter` constructor.

## 8. Distributed tracing

The SDK ties errors and logs to end-to-end traces and creates spans of its own, so BugWatch reconstructs a whole request across services (JS, PHP, and anything speaking W3C `traceparent`). Set `serviceName` at init so spans land on the right node of the service map.

**Propagation is automatic.** Server adapters read the inbound `traceparent` header; requests without one get a synthesized trace id so nothing is untraceable. For outbound calls, `wrapFetch(client)` returns a traced `fetch` that opens a client-kind span, injects `traceparent` (never overwriting an existing one), tags the response status and marks 5xx or network failures as error spans. Where `fetch` is not in play, `BugWatch.traceHeaders()` returns `{ traceparent }` for any HTTP client or message payload, and `parseTraceparent(header)` + `BugWatch.setTraceContext(traceId, spanId)` join a trace on the receiving side.

**Spans.** `withSpan(name, fn, opts)` times a callback, links every capture and log inside it to the span, records a thrown exception on the span (with stacktrace and error status) and rethrows. `startSpan(name, opts)` gives manual control with `setAttr`, `recordException`, `end(status)` and `span.traceparent()` for outbound headers. Options: `kind` (1 internal, 2 server, 3 client, 4 producer, 5 consumer), `attrs`, `traceId`, `parentSpanId`, and `links` (point a queue consumer's span at the producer's `{ traceId, spanId }` and the service map draws the async edge). Attach `code.filepath`, `code.lineno` and `code.function` attributes and the span detail shows the source location.

```ts
const result = await BugWatch.withSpan("db.query load-cart", async (span) => {
  span?.setAttr("db.system", "postgresql");
  return loadCart(cartId);
}, { kind: 3 });
```

**Limits and delivery.** 50 attributes (200-char keys and values, scalars only), 20 events, 10 links, 200-char names, 8000-char exception stacktraces, 200-span buffer with oldest dropped. Spans post as OTLP JSON to `POST /v1/traces` with the same DSN key and flush together with events on `flush()`/`close()`. Spans need a `projectKey`: browser `sessionUrl` mode captures errors and logs but does not create spans. If your app already runs OpenTelemetry, skip BugWatch spans and pass `traceContextProvider: otelTraceContextProvider()` from `./otel` so events join your OTel traces instead.

## 9. Readable stack traces

Node stacks are readable as-is. React Native bundles resolve through the source map you upload in CI (see [Mobile → React Native](/mobile)). For minified web bundles, server-side source-map resolution is not available yet: keep source maps deployed next to your bundles so browser devtools resolve frames.

## Production checklist

- `await BugWatch.close()` on SIGTERM; `installNodeErrorHandlers` at boot.
- Keep the DSN key out of the repo and rotate it from the dashboard on exposure.
- Browsers get `sessionUrl` through `initBrowser`, never `projectKey`.

## Troubleshooting

- **401 Invalid or inactive API key** or **400 This API key is not a BugWatch project key**: you are using an org key; use the project DSN key from Settings under DSN keys.
- **Events missing from a short-lived script**: the batch timer never fired; call `await BugWatch.flush()`.
- **Pino fields missing as tags**: only string, number, boolean and bigint values map to tags.
- **`BugWatchResolutionError` in the console**: a bundler resolved `@newinstance/bugwatch/browser` to a type-declaration stub, almost always a `tsconfig` `paths` alias rewriting runtime imports. Delete the alias; types resolve without it since v0.2.0. The app keeps running on a no-op client meanwhile.
- **Spans missing but errors arriving**: browser `sessionUrl` mode creates no spans (server-side `projectKey` only); with `flushInterval: 0` spans leave only on `flush()`/`close()`.
- **A trace does not join the caller's**: the inbound `traceparent` was malformed, so the request minted a fresh trace id; validate the header with `parseTraceparent` on the sending side.

Wire endpoints used: `POST /api/v1/bugwatch/ingest` (NDJSON, up to 5000 events per request), `POST /api/v1/bugwatch/browser-session`, `POST /api/v1/bugwatch/ingest/browser`, and `POST /v1/traces` (OTLP JSON, spans).
