first commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# @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.
|
||||
Reference in New Issue
Block a user