# @wrnexus/tracking

> Error tracking for WrNexus apps: capture exceptions manually or via middleware and fan them out to pluggable sinks.

Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.

## Overview

`@wrnexus/tracking` is a small, server-side error-capture layer. You create a
tracker with one or more **sinks**, then feed it errors — either manually with
`tracker.capture(err, context)` or automatically by mounting `tracker.middleware()`
in your request pipeline. A `consoleSink` is included; forwarding to Sentry,
Datadog, or any other backend is just a matter of writing a tiny sink. Reach for
it when you want a single, sink-agnostic place to route application errors. Sinks
run best-effort — a throwing sink never breaks the request.

## Installation

```bash
bun add @wrnexus/tracking
```

> Private package — the machine must be authenticated to the `wrnexus` npm org
> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported).

## API

### `createTracker(options?): Tracker`

Creates a tracker. `TrackerOptions`:

| Option       | Type                                        | Description                                                                 |
| ------------ | ------------------------------------------- | --------------------------------------------------------------------------- |
| `sinks`      | `ErrorSink[]`                               | Initial sinks to fan events out to. Defaults to `[]`.                       |
| `now`        | `() => number`                              | Clock used for `event.timestamp` (epoch ms). Defaults to `Date.now`.        |
| `beforeSend` | `(event: ErrorEvent) => ErrorEvent \| null` | Scrub/enrich an event before it reaches any sink. Return `null` to drop it. |

The returned `Tracker`:

| Member       | Signature                                                              | Description                                                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `capture`    | `(error: unknown, context?: Record<string, unknown>) => Promise<void>` | Normalizes any thrown value into an `Error`, builds an `ErrorEvent`, runs `beforeSend`, then dispatches to all sinks. Non-`Error` values are wrapped in an `Error` named `NonError`. |
| `addSink`    | `(sink: ErrorSink) => void`                                            | Registers an additional sink at runtime.                                                                                                                                             |
| `middleware` | `() => Middleware`                                                     | Returns a WrNexus `Middleware` that captures any error thrown downstream, then re-throws it so the framework's error handler still produces the response.                            |

The middleware attaches this context to captured events:

```ts
{ method: ctx.req.method, path: ctx.url.pathname, requestId: ctx.locals.requestId }
```

### `consoleSink: ErrorSink`

A built-in sink that logs a compact one-line message via `console.error`, e.g.
`[error] TypeError: cannot read x {"userId":42}`.

### Types

```ts
interface ErrorEvent {
  error: Error;
  context: Record<string, unknown>; // request info, user id, tags…
  timestamp: number; // epoch ms
}

interface ErrorSink {
  name?: string;
  capture(event: ErrorEvent): void | Promise<void>;
}
```

## Usage

Manual capture:

```ts
import { createTracker, consoleSink } from "@wrnexus/tracking";

const tracker = createTracker({ sinks: [consoleSink] });

try {
  await doWork();
} catch (err) {
  await tracker.capture(err, { userId: 42, op: "doWork" });
  throw err;
}
```

As request middleware:

```ts
import { createTracker, consoleSink } from "@wrnexus/tracking";

const tracker = createTracker({ sinks: [consoleSink] });

app.use(tracker.middleware()); // captures + re-throws downstream errors
```

A custom sink with `beforeSend` scrubbing:

```ts
import { createTracker, type ErrorSink } from "@wrnexus/tracking";

const sentrySink: ErrorSink = {
  name: "sentry",
  async capture(event) {
    await Sentry.captureException(event.error, { extra: event.context });
  },
};

const tracker = createTracker({
  sinks: [sentrySink],
  beforeSend(event) {
    delete event.context.password; // scrub secrets
    return event; // return null to drop the event entirely
  },
});

tracker.addSink(anotherSink); // add more sinks later
```

## Requirements / Notes

- Runs on **Bun** only (Node is not supported).
- Peer package: [`@wrnexus/core`](../core) — the `Context` and `Middleware` types
  used by `tracker.middleware()` come from there.
- Sink dispatch is fire-and-forget-safe: all sinks run via `Promise.all`, and a
  sink that throws is swallowed so it can never break the app.
