Overview
Tiny, zero-dependency browser SDK for the AbsoluteJS observability stack. 2 KB gzipped.
@absolutejs/beaconv0.4.6betaObservabilityA ~2 KB gzipped browser error and breadcrumb SDK that ships envelopes to your own @absolutejs/errors ingest endpoint.
A tiny, zero-dependency browser SDK that captures uncaught errors and unhandled rejections, records breadcrumbs, batches events and POSTs an envelope to the @absolutejs/errors /ingest endpoint via navigator.sendBeacon or fetch keepalive. It stays around 2 KB gzipped by keeping Effect and Schema validation server-side, while a compile-time assertion contract-locks the envelope shape to the ingest endpoint — change either side and the build breaks.
bun add @absolutejs/beaconTiny, zero-dependency browser SDK for the AbsoluteJS observability stack. 2 KB gzipped.
Captures uncaught errors + unhandled rejections, records breadcrumbs (console / click / fetch / navigation), batches, and POSTs an envelope to @absolutejs/errors/ingest via navigator.sendBeacon / fetch keepalive.
A browser SDK loads on every page for every user, so bytes are the dominant cost. Measured: an Effect-native client is 108 KB gz; this is 2 KB gz. The client has no trust boundary — it's a dumb producer of telemetry — so the Effect/Schema rigor lives server-side in @absolutejs/errors/ingest, which validates the untrusted POST body.
You lose nothing on type safety: the envelope is contract-locked to the ingest endpoint's accepted shape by a compile-time assertion (the type spans the wire; the runtime machinery does not). Change the shape on either side and the build breaks.
Or hold an instance instead of the global:
Auto-capture — window.onerror + unhandledrejection (toggle via instrument).
Breadcrumbs — console.error/warn, clicks, fetch (skipping its own
ingest endpoint), and SPA navigations, in a ring buffer attached to each event.
Batching — buffers up to maxBatch (default 30) / flushIntervalMs
(default 5s); flushes reliably on pagehide / tab-hidden via sendBeacon.
Context — setTags, setUser, per-call tags/extra, a per-session id.
Cause chains — preserves nested Error.cause stacks and diagnostic fields
in extra.errorCauses, including database driver error codes and details.
Sampling + redaction — sampleRate, a beforeSend(event) hook
(return null to drop), and default credential/context redaction after the hook so host customization cannot accidentally reintroduce secrets. URL query/hash values, secret-bearing fields, bearer/JWT values, and breadcrumb text are sanitized. Set redact: false only when a trusted boundary replaces it.
Noise filtering — known browser-host/scanner failures such as CefSharp's
Object Not Found Matching Id rejection are dropped by default (filterKnownNoise: false opts out).
Resource policy — instrument.resourceErrors accepts a predicate so
SSR-safe: imported in a non-DOM environment, createBeacon returns a no-op.
window.onerror and unhandledrejection handlers are installed by initBeacon and can be toggled via the instrument option.
console.error/warn calls, clicks, fetch requests (skipping its own ingest endpoint) and SPA navigations are kept in a ring buffer attached to each event.
Events buffer up to maxBatch (default 30) or flushIntervalMs (default 5s) and flush reliably on pagehide and tab-hidden via sendBeacon.
setTags, setUser, per-call tags and extra, and a per-session id enrich every event; sampleRate and a beforeSend hook drop or redact before sending.
getReplayId() stamps each event with the active session-replay id so @absolutejs/replay can cross-link an error to its exact DOM recording.
Imported in a non-DOM environment, createBeacon returns a no-op — safe to ship in server-rendered code paths.
Outcomes
Tiny, zero-dependency browser SDK for the AbsoluteJS observability stack. 2 KB gzipped.
A browser SDK loads on every page for every user, so bytes are the dominant cost. Measured: an Effect-native client is 108 KB gz; this is 2 KB gz. The client has no trust boundary — it's a dumb producer of telemetry — so the Effect/Schema rigor lives server-side in @absolutejs/errors/ingest, which validates the untrusted POST body.
Zero runtime dependencies.
Hardening checklist
Follow in order
Working example for Quick start.
import { initBeacon, captureException } from "@absolutejs/beacon";
initBeacon({
project: "web",
endpoint: "https://api.example.com/ingest",
release: import.meta.env.VITE_RELEASE,
environment: "production",
});
// Uncaught errors + unhandled rejections are captured automatically.
// Manual capture anywhere:
try {
await checkout();
} catch (e) {
captureException(e, { tags: { component: "billing" } });
}Or hold an instance instead of the global:
import { createBeacon } from "@absolutejs/beacon";
const beacon = createBeacon({ project: "web" });
beacon.setUser({ id: currentUserId });
beacon.captureMessage("checkout started", "info");Working example for API.
createBeacon(options) => Beacon
initBeacon(options) => Beacon // also sets the global singleton
getBeacon() => Beacon | undefined
// Beacon:
captureException(error, { level?, traceId?, spanId?, tags?, extra? })
captureMessage(message, level?)
addBreadcrumb({ message, type?, data? })
setTags(tags) · setUser(user | null)
flush() => Promise<void> // buffered events out now
close() => Promise<void> // remove listeners + final flush
// Typed names for event.tags.signal in beforeSend policies:
BEACON_SIGNAL.FETCH_FAILED
BEACON_SIGNAL.SLOW_RESPONSE
BEACON_SIGNAL.HTTP_5XX
BEACON_TRACE_HEADER // "x-absolute-trace-id"
// Global helpers (no-op until initBeacon): captureException, captureMessage, addBreadcrumbInitialize the global singleton once; auto-capture takes over, and the global helpers work anywhere after that.
import { captureException, initBeacon } from '@absolutejs/beacon';
initBeacon({
endpoint: 'https://api.example.com/ingest',
environment: 'production',
project: 'web',
release: import.meta.env.VITE_RELEASE
});
// Uncaught errors + unhandled rejections are captured automatically.
// Manual capture anywhere:
try {
await checkout();
} catch (e) {
captureException(e, { tags: { component: 'billing' } });
}Hold an instance instead of the global when you need explicit lifecycle control.
import { createBeacon } from '@absolutejs/beacon';
const beacon = createBeacon({ project: 'web' });
beacon.setUser({ id: currentUserId });
beacon.captureMessage('checkout started', 'info');
// Buffered events out now:
await beacon.flush();
// Remove listeners + final flush:
await beacon.close();Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.
@absolutejs/beacon — tiny, zero-dependency browser SDK for the AbsoluteJS observability stack. It is deliberately NOT Effect-native: a browser SDK loads on every page for every user, so bytes are the dominant cost (measured: an Effect client is 108 KB gz; this is 2-5 KB gz). The client has no trust boundary — it's a dumb producer of telemetry — so the Effect/Schema rigor lives server-side in @absolutejs/errors/ingest, which validates the untrusted POST body. Type safety is preserved end-to-end W
type BeaconLevel = "fatal" | "error" | "warning" | "info";@absolutejs/beaconCurrent package surface
Import surface · click to copy