@jfrader/observability
v0.4.0
Published
Strategy-based observability for small apps: error tracking (Sentry, pluggable) and analytics (PostHog, Plausible/Umami-style beacon, pluggable) behind one tiny API. Zero runtime dependencies.
Maintainers
Readme
@jfrader/observability
Strategy-based observability for small apps, one tiny API, zero runtime dependencies. Error tracking (Sentry by default) and analytics (PostHog by default) behind a strategy pattern, so you can switch providers — or drop them — by changing one import and one env var.
It is the shared observability layer used by all jfrader apps (MiFulbo,
Huertoku, Civiku, Trucoshi, jfrader.com, Gurisitos). See
changelog-lib for the sibling release-notes system.
Why a strategy pattern
Small apps have few users and little budget. The default choices here are free-tier-friendly and privacy-first:
| Concern | Default provider | Free tier | Alternative strategies | |---|---|---|---| | Errors | Sentry | 5k errors/mo | noop | | Analytics | PostHog | 1M events/mo | noop, custom beacon (Plausible/Umami-compatible) |
Both are behind interfaces (ErrorReporter, AnalyticsProvider), so any
provider can be swapped without touching call sites. Provider SDKs are
optional peer dependencies: install only what you actually use.
Install
npm install @jfrader/observability # core (always)
npm install @sentry/react posthog-js # only if you use those providers
# server-side: @sentry/node posthog-nodeReact (browser) quick start
// src/observability.ts
import { createBrowserObservability, readBrowserEnv } from "@jfrader/observability/browser";
import { createSentryBrowserErrorReporter } from "@jfrader/observability/providers/sentry-browser";
import { createPosthogBrowserAnalytics } from "@jfrader/observability/providers/posthog-browser";
const env = readBrowserEnv(import.meta.env); // VITE_SENTRY_DSN, VITE_PUBLIC_POSTHOG_KEY, ...
export const observability = createBrowserObservability({
appName: "huertoku",
environment: import.meta.env.MODE,
errorReporter: env.sentryDsn
? createSentryBrowserErrorReporter({ dsn: env.sentryDsn, environment: import.meta.env.MODE })
: undefined,
analytics: env.posthogKey
? createPosthogBrowserAnalytics({ key: env.posthogKey, host: env.posthogHost ?? "https://us.i.posthog.com" })
: undefined, // or createBeaconAnalytics({ endpoint: env.analyticsUrl })
});// main.tsx
import { ObservabilityProvider, ObservabilityErrorBoundary } from "@jfrader/observability/browser";
import { observability } from "./observability";
createRoot(document.getElementById("root")!).render(
<ObservabilityProvider value={observability}>
<ObservabilityErrorBoundary fallback={<AppErrorScreen />}>
<App />
</ObservabilityErrorBoundary>
</ObservabilityProvider>,
);// anywhere
const track = useTrack();
track("game_completed", { score: 12 });Version reload
New-deploy detection lives in @jfrader/pwa-updater. The deprecated re-export
from observability was removed in 0.4.0 — import useVersionReload and
isDynamicImportError from @jfrader/pwa-updater instead.
Node (server) quick start
// src/observability.ts
import { createNodeObservability, readNodeEnv } from "@jfrader/observability/node";
import { createSentryNodeErrorReporter } from "@jfrader/observability/providers/sentry-node";
const env = readNodeEnv(); // SENTRY_DSN, POSTHOG_KEY, ...
export const observability = createNodeObservability({
appName: "mifulbo-api",
environment: process.env.NODE_ENV ?? "development",
errorReporter: env.sentryDsn ? createSentryNodeErrorReporter({ dsn: env.sentryDsn }) : undefined,
});
// Fastify:
app.setErrorHandler((error, request, reply) => {
captureRequestError(observability, request, error);
reply.send(error);
});Switching providers
| Want | Change |
|---|---|
| No analytics (dev) | omit analytics (or set env vars empty) — noop fallback |
| PostHog → Plausible/Umami/own endpoint | import createBeaconAnalytics instead of the PostHog adapter; set VITE_ANALYTICS_URL |
| Sentry → nothing | omit errorReporter |
| Any future provider | implement ErrorReporter / AnalyticsProvider (one file) |
Package entries
| Entry | Contents | Peer deps |
|---|---|---|
| @jfrader/observability | types, facade, noop, redaction utils, env readers | — |
| @jfrader/observability/browser | facade + React (ObservabilityProvider, useTrack, ObservabilityErrorBoundary) | react (optional) |
| @jfrader/observability/node | facade + captureRequestError | — |
| @jfrader/observability/providers/sentry-browser | createSentryBrowserErrorReporter | @sentry/react |
| @jfrader/observability/providers/sentry-node | createSentryNodeErrorReporter | @sentry/node |
| @jfrader/observability/providers/posthog-browser | createPosthogBrowserAnalytics | posthog-js |
| @jfrader/observability/providers/posthog-node | createPosthogNodeAnalytics | posthog-node |
Env conventions
Browser (readBrowserEnv(import.meta.env, prefix)):
VITE_SENTRY_DSN— enable SentryVITE_PUBLIC_POSTHOG_KEY(+VITE_PUBLIC_POSTHOG_HOST, defaulthttps://us.i.posthog.com)VITE_ANALYTICS_URL— custom beacon endpoint
Node (readNodeEnv()): same names without the VITE_ prefix.
Privacy defaults
- Sentry adapters scrub credentials/tokens from URLs and breadcrumbs by
default (
redactoption), send no PII, errors only (tracesSampleRate: 0). - PostHog browser adapter: autocapture off, session recording off, sensitive
URL values scrubbed before send (
redactaccepts app-specific query keys). - Beacon adapter: sends
{ name, data, url, ts }, supportsredactUrlto strip share codes; never throws. - Noop fallbacks: no provider configured → no network calls, no console noise.
Agent skill
The package ships skills/observability/SKILL.md, an agent-facing integration
workflow covering browser and server setup, privacy and identity boundaries,
event design, release/source-map conventions, and live verification. Agent
clients that discover dependency skills can load it directly from the installed
package; other clients can point their project skill configuration at that file.
Publishing
Primary flow (like changelog): push an annotated v<package-version> tag from
main. The release workflow checks tag + lockfile, builds and tests once,
packs one immutable tarball, then publishes or verifies that exact artifact on
npmjs (trusted publisher) and GitHub Packages (scoped GITHUB_TOKEN).
Retries are safe: an existing matching artifact is accepted, a different
artifact at the same version fails.
Manual CLI fallback for npmjs (no tag needed): plain npm publish works
because the repo .npmrc pins the @jfrader scope to npmjs (the user-level
~/.npmrc maps it to GitHub Packages).
History: 0.1.0 was CLI-published (its manifest carried
publishConfig.registry, which overrides registry flags — incompatible with
workflow republishing); 0.1.1+ go through the workflow. GitHub Packages
packages are created public when published from a public repository.
