@wrnexus/tracking
Error/event capture, middleware, filtering, and sinks.
Install the package
After WorkRoot approves private registry access, install the release-aligned package:
bun add @wrnexus/tracking@0.8.7Request preview access. Never put registry tokens in source control.
Error tracking for WRNexusJS apps: capture exceptions manually or via middleware and fan them out to pluggable sinks.
Part of the WRNexusJS 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.
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 WRNexusJS 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:
{ 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
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:
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:
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:
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) — theContextandMiddlewaretypes - Sink dispatch is fire-and-forget-safe: all sinks run via
Promise.all, and a
used by tracker.middleware() come from there.
sink that throws is swallowed so it can never break the app.
Complete TypeScript API
Generated from the exact installed package declarations.
import { Middleware } from '@wrnexus/core';
type TelemetryKind = "error" | "event" | "metric" | "span";
interface TelemetryEnvelope {
id: string;
kind: TelemetryKind;
name: string;
timestamp: number;
traceId?: string;
userId?: string;
tenantId?: string;
attributes: Record<string, unknown>;
payload?: unknown;
}
interface TelemetrySink {
name?: string;
send(events: TelemetryEnvelope[]): void | Promise<void>;
}
interface TelemetryPipeline {
emit(input: Omit<TelemetryEnvelope, "id" | "timestamp"> & Partial<Pick<TelemetryEnvelope, "id" | "timestamp">>): Promise<void>;
flush(): Promise<void>;
close(): Promise<void>;
size(): number;
}
interface TelemetryPipelineOptions {
sinks: TelemetrySink[];
batchSize?: number;
flushIntervalMs?: number;
maxQueueSize?: number;
sampleRate?: number;
beforeSend?: (event: TelemetryEnvelope) => TelemetryEnvelope | null;
onSinkError?: (error: unknown, sink: TelemetrySink, events: readonly TelemetryEnvelope[]) => void | Promise<void>;
now?: () => number;
random?: () => number;
}
declare function createTelemetryPipeline(options: TelemetryPipelineOptions): TelemetryPipeline;
declare const telemetryConsoleSink: TelemetrySink;
/**
* @wrnexus/tracking — error tracking with pluggable sinks. Capture exceptions
* manually or via middleware, and fan them out to any sink (console by default;
* write a small sink to forward to Sentry/Datadog/etc.).
*
* const tracker = createTracker({ sinks: [consoleSink] });
* app-middleware: tracker.middleware() // captures + re-throws request errors
* tracker.capture(err, { userId }); // manual
*/
interface ErrorEvent {
error: Error;
/** Arbitrary structured context (request info, user id, tags…). */
context: Record<string, unknown>;
/** Epoch ms. */
timestamp: number;
}
interface ErrorSink {
name?: string;
capture(event: ErrorEvent): void | Promise<void>;
}
interface Tracker {
capture(error: unknown, context?: Record<string, unknown>): Promise<void>;
addSink(sink: ErrorSink): void;
/** Middleware that captures errors thrown downstream, then re-throws them. */
middleware(): Middleware;
}
interface TrackerOptions {
sinks?: ErrorSink[];
now?: () => number;
/** Scrub/enrich an event before it hits sinks (return null to drop it). */
beforeSend?: (event: ErrorEvent) => ErrorEvent | null;
}
/** A sink that logs a compact one-line error to the console. */
declare const consoleSink: ErrorSink;
declare function createTracker(options?: TrackerOptions): Tracker;
export { type ErrorEvent, type ErrorSink, type TelemetryEnvelope, type TelemetryKind, type TelemetryPipeline, type TelemetryPipelineOptions, type TelemetrySink, type Tracker, type TrackerOptions, consoleSink, createTelemetryPipeline, createTracker, telemetryConsoleSink };
Examples
Copy-ready examples from the installed package documentation.
Manual capture
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
import { createTracker, consoleSink } from "@wrnexus/tracking";
const tracker = createTracker({ sinks: [consoleSink] });
app.use(tracker.middleware()); // captures + re-throws downstream errorsA custom sink with beforeSend scrubbing
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