AbsoluteJS

Beacon

@absolutejs/beaconv0.4.6betaObservability

A ~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.

#Installation

BASH
bun add @absolutejs/beacon

#Capabilities

Overview

Tiny, 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.

Why it's not Effect-native (on purpose)

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.

Quick start

Or hold an instance instead of the global:

What it does

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.

Show 10 more

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

API

SSR-safe: imported in a non-DOM environment, createBeacon returns a no-op.

Automatic error capture

window.onerror and unhandledrejection handlers are installed by initBeacon and can be toggled via the instrument option.

Breadcrumb trail

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.

Reliable batching

Events buffer up to maxBatch (default 30) or flushIntervalMs (default 5s) and flush reliably on pagehide and tab-hidden via sendBeacon.

Context and sampling

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.

Session replay seam

getReplayId() stamps each event with the active session-replay id so @absolutejs/replay can cross-link an error to its exact DOM recording.

SSR safe

Imported in a non-DOM environment, createBeacon returns a no-op — safe to ship in server-rendered code paths.

Outcomes

What you can build

Overview

Tiny, zero-dependency browser SDK for the AbsoluteJS observability stack. 2 KB gzipped.

Why it's not Effect-native (on purpose)

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.

Install

Zero runtime dependencies.

Hardening checklist

Production guidance

Make every external boundary explicitPin the deployed @absolutejs/beacon version, replace example or memory-backed dependencies with durable implementations, bound external calls, protect credentials, and emit enough evidence to retry or recover safely.

Follow in order

Troubleshooting path

1
Why it's not Effect-native (on purpose)
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.
2
What it does
Auto-capture — window.onerror + unhandledrejection (toggle via instrument).

#Quick start

Partial snippet

Working example for Quick start.

TS
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" } });
}

#Quick start 2

Partial snippet

Or hold an instance instead of the global:

TS
import { createBeacon } from "@absolutejs/beacon";
const beacon = createBeacon({ project: "web" });
beacon.setUser({ id: currentUserId });
beacon.captureMessage("checkout started", "info");

#API

Partial snippet

Working example for API.

TS
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, addBreadcrumb

#Quick Start

Partial snippet

Initialize the global singleton once; auto-capture takes over, and the global helpers work anywhere after that.

TS
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' } });
}

#Instance API

Partial snippet

Hold an instance instead of the global when you need explicit lifecycle control.

TS
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();
Bytes over machinery
Beacon is deliberately not Effect-native: an Effect client measures around 108 KB gzipped versus about 2 KB here. The browser is a dumb producer of telemetry; validation rigor lives server-side in @absolutejs/errors/ingest.

#API reference

Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.

29 symbols
BeaconLeveltypePermalink

@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

TS
type BeaconLevel = "fatal" | "error" | "warning" | "info";
Exported from @absolutejs/beacon

Current package surface

What ships today

@absolutejs/beaconv0.4.6 · betaObservabilitynpmSource
3entry points30symbols

Import surface · click to copy