Bumps all 47 packages, the root manifest and the VS Code extension to 0.8.6, and rebuilds the editor compiler, language server and extension bundles that embed the version. The release carries the output delivery fix: camelCase outputs now reach parent bindings, and 18 components emit through output.* instead of hand-built CustomEvents. See the 0.8.6 migration entry for what changes for consumers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@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
bun add @wrnexus/tracking
Private package — the machine must be authenticated to the
wrnexusnpm 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:
{ 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— theContextandMiddlewaretypes used bytracker.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.