@browsonic/svelte
v1.2.17
Published
Svelte / SvelteKit adapter for @browsonic/sdk — handleError hook factory, user store subscriber, capture helpers. Apache-2.0.
Maintainers
Readme
@browsonic/svelte
Svelte / SvelteKit adapter for @browsonic/sdk — SvelteKit handleError hook factory, Svelte-store user-context bridge, ergonomic capture wrappers, navigation breadcrumb instrumentation, SvelteKit form-action wrapper, and a +error.svelte integration helper.
Status: published on npm — check
package.jsonor npm for the current version (both read1.2.13on 2026-07-27). The0.1/0.2/0.3labels inAGENTS.md, the source file headers and someCHANGELOG.mdentries are internal feature waves, not package versions. Pure-TypeScript helpers, no compiled.sveltefiles. Boundary capture relies on Svelte 5's native<svelte:boundary>. Load-bearing surfaces: SvelteKithandleErrorhook (generic overApp.Error), Svelte store user-context bridge, navigation breadcrumb instrumentation (instrumentNavigation+trackNavigationSvelte action), SvelteKit form-action wrapper (withBrowsonicAction), and a+error.sveltepage helper (reportErrorPage).
Why this adapter
Two things differentiate Svelte from the React / Vue adapters:
- Svelte 5 has a native
<svelte:boundary>. We don't ship a competing boundary — use the framework primitive. The adapter's job is to give youronerrorhandler a one-liner SDK forwarder. - SvelteKit's error handling is hook-driven.
src/hooks.client.tsexports ahandleErrorfunction that the framework calls when a load function throws or a rendered component crashes. This package gives you the factory.
Install
npm install @browsonic/sdk @browsonic/svelte@browsonic/sdk (>=3.12.0) is a peer dependency. svelte (^4.0.0 || ^5.0.0) is a peer dependency, but the recommended target is Svelte 5 — only Svelte 5 has <svelte:boundary>.
Quickstart — SvelteKit
// src/hooks.client.ts
import { handleErrorWithBrowsonic } from '@browsonic/svelte';
import { getBrowsonic } from '@browsonic/sdk';
const sdk = getBrowsonic();
sdk.init({
apiEndpoint: 'https://your-ingest-endpoint.test', // origin only — the SDK appends /v1/events
appKey: 'web',
apiKey: 'pk_live_...', // publishable key — safe to ship in the browser
});
export const handleError = handleErrorWithBrowsonic();There is no server-side companion for hooks.server.ts. The SDK is a browser library and Browsonic publishes no Node/server runtime package — every package in this repo is browser-side. Server-runtime telemetry needs a logger of your own alongside this adapter.
Quickstart — Svelte 5 boundary
<script lang="ts">
import { captureError } from '@browsonic/svelte';
</script>
<svelte:boundary onerror={(err, reset) => {
captureError(err instanceof Error ? err : new Error(String(err)));
}}>
<RiskySubtree />
{#snippet failed(error)}
<p>Crashed: {error.message}</p>
{/snippet}
</svelte:boundary>Quickstart — User identity from a Svelte store
<script lang="ts">
import { writable } from 'svelte/store';
import { onDestroy } from 'svelte';
import type { UserContext } from '@browsonic/sdk';
import { subscribeUser } from '@browsonic/svelte';
export const user = writable<UserContext | null>(null);
const off = subscribeUser(user);
onDestroy(off);
</script>Every store change is mirrored as sdk.setUser(value); setting the store to null calls sdk.clearUser().
Ordering matters. subscribeUser resolves the SDK once, at call time, and captures the result for the life of the subscription. If no SDK is reachable at that moment (the global has not been exposed yet), the subscription stays a silent no-op forever — later store changes will not retry the lookup. Call subscribeUser after sdk.init(), or pass the instance explicitly: subscribeUser(user, { sdk }). (The capture wrappers and instrumentNavigation do re-resolve on every call, so they do not have this ordering constraint.)
API
The three SvelteKit-aware captures — handleErrorWithBrowsonic, withBrowsonicAction, reportErrorPage — each also set a sveltekit context bucket (kind = handleError / action / errorPage, plus path / routeId / status when available); that bucket is what the dashboard's SvelteKit card renders. All of their tag / metadata / context writes run inside sdk.withScope(), so they ride on that one capture instead of sticking to every later event.
handleErrorWithBrowsonic(options?)
import { handleErrorWithBrowsonic } from '@browsonic/svelte';
export const handleError = handleErrorWithBrowsonic({
sdk, // optional; default = window.Browsonic.getBrowsonic()
chain: (input) => ({ message: '…' }), // optional; runs after the SDK has been notified
});Returns a SvelteKit-compatible handleError hook. Forwards the thrown value (coerced to Error) to sdk.captureError, records the URL pathname under sveltekitPath metadata, and chains into your own handler if you pass one.
subscribeUser(store, options?)
Subscribe a Svelte readable store to the SDK user context. Accepts any object with a subscribe(fn) => unsubscribe shape — Readable, Writable, custom stores. Returns the unsubscribe handle. Resolves the SDK once at call time — see the ordering note above.
captureError / captureMessage / addBreadcrumb
Ergonomic standalone wrappers that resolve the SDK from window. Use these when you don't already have a reference to the SDK in scope. All three are no-ops when the SDK is unreachable.
import { captureError, captureMessage, addBreadcrumb } from '@browsonic/svelte';
captureError(new Error('purchase failed'));
captureMessage('checkout step 2', 'info');
addBreadcrumb({ category: 'navigation', message: '/checkout' });instrumentNavigation(options?) / trackNavigation Svelte action
Subscribe to SvelteKit / SPA navigation and emit a category: 'navigation' breadcrumb on every URL change. Two surfaces over one engine:
// Programmatic — call once at app init
import { instrumentNavigation } from '@browsonic/svelte';
const off = instrumentNavigation();
// optional: off() on app teardown<!-- Action form — drop on a layout root -->
<script lang="ts">
import { trackNavigation } from '@browsonic/svelte';
</script>
<div use:trackNavigation>
<slot />
</div>History API patches (pushState / replaceState) are ref-counted so multiple callers share one set of patches and the last unsubscribe restores the originals. Any navigation that goes through pushState / replaceState fires the synthetic browsonic:locationchange event; back/forward fire via popstate. Works without a @sveltejs/kit peer dep — the package has no dependency on it at all.
That cuts both ways: the tests drive pushState / replaceState / popstate directly, never a real goto(), so whether SvelteKit's goto() routes through the History API — and therefore emits a breadcrumb — is UNVERIFIED (2026-07-27).
withBrowsonicAction(handler, options?)
Wraps a SvelteKit actions: {} handler so unhandled throws are reported (with sveltekit.action.name / sveltekit.action.method tags + sveltekitPath metadata) and then re-thrown so SvelteKit returns the action's failure to the client unchanged.
// src/routes/login/+page.server.ts
import type { RequestEvent } from '@sveltejs/kit'; // type-only — no runtime dep
import { withBrowsonicAction } from '@browsonic/svelte';
export const actions = {
default: withBrowsonicAction(
async (event: RequestEvent) => {
const data = await event.request.formData();
// ... business logic that may throw
},
{ actionName: 'login.default' },
),
};Annotate the handler's parameter. Left unannotated, the generic falls back to this package's structural ActionEventLike shape — url.pathname, optional request.method, optional route.id, nothing else — and request.formData() fails to compile under strict.
Re-throw order matters — consuming the error here would mask every reported failure as a successful 200 response.
reportErrorPage(error, options?)
One-shot, idempotent capture for +error.svelte's <script> block. Reference-keyed de-dupe via module-scope WeakSet so a reactive $: binding doesn't re-report on every store tick.
<!-- src/routes/+error.svelte -->
<script lang="ts">
import { page } from '$app/stores';
import { reportErrorPage } from '@browsonic/svelte';
$: reportErrorPage($page.error, {
status: $page.status,
pathname: $page.url.pathname,
});
</script>
<h1>{$page.status}: {$page.error?.message ?? 'Something went wrong'}</h1>Browser-only — SSR (typeof window === 'undefined') and "no SDK reachable" cases short-circuit to false so the helper is safe to call unconditionally.
resolveSdk(explicit?)
Lower-level helper for when you need explicit SDK access. Returns the explicit instance, or window.Browsonic.getBrowsonic(), or null.
Defensive contract
Every public surface follows the same rule:
- The host app must never crash because reporting failed.
- SDK calls are wrapped in
try { ... } catch {}. If the wrapper, factory, or store subscriber can't reach the SDK, it stays silent. subscribeUserreturns a no-op unsubscribe when the input is not a store, instead of throwing.
What this package does NOT do
- No
<BrowsonicErrorBoundary>component. Use Svelte 5's native<svelte:boundary>and forward the thrown error fromonerrorviacaptureError. Svelte 4 has no clean boundary primitive — the SvelteKithandleErrorhook is the next-best mitigation. - Server-side capture. SvelteKit's
hooks.server.tsruns in Node. The SDK is browser-only.withBrowsonicActionruns on the server too — it captures if a browser SDK is reachable (rare in pure server contexts) and otherwise re-throws cleanly. - No App Atlas page views.
instrumentNavigationemits navigation breadcrumbs only; nothing in this package callssdk.trackPageView(), so it feeds Atlas no route templates and no screen names. The Vue, Next.js, React and Angular adapters do; this one does not. - Svelte 3 / pre-Composition Svelte. Svelte 3 is end-of-life; no back-port.
License
Apache-2.0. See the repo root LICENSE and the package NOTICE.
