npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tapizlabs/telemetry

v0.2.1

Published

Framework-agnostic error & event capture SDK for Tapiz products. Captures uncaught errors, batches and ships them to a Tapiz ingest endpoint.

Readme

@tapizlabs/telemetry

Framework-agnostic error & event capture SDK for Tapiz products. The client half of Tapiz's self-hosted error monitoring: it captures uncaught errors in the browser, Node, and Edge runtimes, scrubs PII on the client, groups events by a stable fingerprint, batches them, and ships them to an ingest endpoint.

It does not depend on React, Next, or any UI framework.

Backend: events are received and viewed in Tapiz Sentinel, the self-hosted ingest + dashboard ("Tapiz's Sentry"). The SDK works against any endpoint that accepts the wire contract, but Sentinel is the reference server.

Install

npm install @tapizlabs/telemetry

Requires Node 18+ (uses crypto.randomUUID, fetch). ESM-only.

Quick start

import { init, captureError, captureMessage } from "@tapizlabs/telemetry";

init({
  endpoint: process.env.NEXT_PUBLIC_TELEMETRY_URL!, // https://sentinel.tapiz.site/api/ingest
  project: "boards",                                 // must match the Sentinel project slug
  ingestKey: process.env.NEXT_PUBLIC_TELEMETRY_KEY,  // per-app key from Sentinel
  release: process.env.NEXT_PUBLIC_APP_VERSION,      // optional, for release grouping
  environment: process.env.VERCEL_ENV ?? "production",
  sampleRate: 1,                                     // lower under heavy traffic
});

try {
  riskyThing();
} catch (e) {
  captureError(e, { context: { route: "/board" } });
}

captureMessage("checkout started", { severity: "info" });

init() auto-installs global handlers for the detected runtime (window.onerror + unhandledrejection in the browser; uncaughtException + unhandledRejection in Node). Pass installGlobalHandlers: false to opt out. Calling init() again replaces the client and tears down the previous handlers.

Next.js integration

Four small files wire the SDK into a Next.js (App Router) app. After this, both client and server/edge uncaught errors flow to your endpoint automatically.

1. src/lib/telemetry.ts — single init helper:

import { init } from "@tapizlabs/telemetry";

// No-op when the endpoint is unset (handy for local dev).
export function initTelemetry() {
  const endpoint = process.env.NEXT_PUBLIC_TELEMETRY_URL;
  if (!endpoint) return;
  init({
    endpoint,
    project: "boards",
    ingestKey: process.env.NEXT_PUBLIC_TELEMETRY_KEY,
    release: process.env.NEXT_PUBLIC_APP_VERSION,
    environment: process.env.VERCEL_ENV ?? "development",
  });
}

2. src/instrumentation-client.ts — browser:

import { initTelemetry } from "@/lib/telemetry";
initTelemetry();

3. src/instrumentation.ts — server + edge (register() runs once per runtime):

export async function register() {
  const { initTelemetry } = await import("@/lib/telemetry");
  initTelemetry();
}

4. src/app/global-error.tsx — capture React render crashes, then re-render:

"use client";
import { useEffect } from "react";
import { captureError } from "@tapizlabs/telemetry";

export default function GlobalError({ error }: { error: Error }) {
  useEffect(() => {
    captureError(error);
  }, [error]);
  return (
    <html>
      <body>Something went wrong.</body>
    </html>
  );
}

Environment variables (e.g. Vercel project settings):

NEXT_PUBLIC_TELEMETRY_URL = https://sentinel.tapiz.site/api/ingest
NEXT_PUBLIC_TELEMETRY_KEY = <ingest key from Sentinel>
NEXT_PUBLIC_APP_VERSION   = <optional, for release grouping>

Leaving NEXT_PUBLIC_TELEMETRY_URL unset disables telemetry — convenient locally.

Manual capture

import { captureError, captureMessage, addBreadcrumb } from "@tapizlabs/telemetry";

addBreadcrumb({ category: "ui", message: "opened board" });
try {
  saveBoard();
} catch (e) {
  captureError(e, { context: { boardId } });
}
captureMessage("payment started", { severity: "info" });

Performance tracing (opt-in)

Tracing is a separate singleton from error capture — initTracing() is independent of init(). A consumer that only wants error capture never pays for it (no transport, no timers). Nothing is auto-instrumented (no fetch/XHR/ routing auto-capture); you explicitly wrap the operations you want traced.

import { initTracing, startSpan } from "@tapizlabs/telemetry";

initTracing({
  endpoint: `${process.env.NEXT_PUBLIC_TELEMETRY_URL}/tracing`, // .../api/ingest/tracing
  project: "boards",
  ingestKey: process.env.NEXT_PUBLIC_TELEMETRY_KEY,
  release: process.env.NEXT_PUBLIC_APP_VERSION,
  environment: process.env.VERCEL_ENV ?? "production",
  tracesSampleRate: 0.1, // default; much lower than error sampleRate since
                         // transactions fire on every instrumented call, not just failures
});

// One call = one whole transaction. Auto-finishes, marks errors, and
// rethrows (never swallows the original error).
const stats = await startSpan("GET /api/attendance-stats", async () => {
  return computeAttendanceStats(subjectId);
});

For multi-step operations, use startTransaction() to record child spans:

import { startTransaction } from "@tapizlabs/telemetry";

const txn = startTransaction("checkout");
try {
  await txn?.span("charge-card", () => chargeCard(order));
  await txn?.span("send-receipt", () => sendReceipt(order));
} catch (e) {
  txn?.setError();
  throw e;
} finally {
  txn?.finish(); // idempotent
}

startTransaction()/startSpan() are no-ops (warn once, run fn anyway) before initTracing() has been called, so they're safe to leave in code paths that run in environments where tracing isn't configured.

Source-map upload (tapiz-sourcemaps CLI)

Ship readable stack traces without shipping source maps to the client. Add the bundled CLI as a Next.js postbuild script:

{
  "scripts": {
    "postbuild": "tapiz-sourcemaps upload"
  }
}

It walks .next for *.map files and uploads each to Sentinel's /api/sourcemaps endpoint, tagged with the release so Sentinel can remap stack traces server-side on ingest. Required env (same per-project key the SDK already uses):

NEXT_PUBLIC_TELEMETRY_URL     = https://sentinel.tapiz.site
NEXT_PUBLIC_TELEMETRY_PROJECT = boards
TELEMETRY_KEY                 = <ingest key from Sentinel>
NEXT_PUBLIC_TAPIZ_RELEASE     = <stable release id, e.g. $VERCEL_GIT_COMMIT_SHA>

Missing env is a no-op with a warning, not a build failure — a build must never fail just because source-map upload isn't configured yet.

Viewing events (Tapiz Sentinel)

The SDK only sends events. To receive, group, and inspect them you run Tapiz Sentinel — a self-hosted Next.js + MySQL ingest service and dashboard. Per consuming app:

  1. Register the project in Sentinel to mint an ingest key:
    PROJECT_SLUG=boards PROJECT_NAME="Tapiz Boards" npm run seed:project
    # → prints TELEMETRY_KEY=...
    The slug must equal the project you pass to init().
  2. Configure the app with NEXT_PUBLIC_TELEMETRY_URL (Sentinel's /api/ingest) and NEXT_PUBLIC_TELEMETRY_KEY (the key from step 1).
  3. Inspect at Sentinel's /admin/issues — grouped issues with stack traces, context, breadcrumbs, first/last seen, and occurrence counts.

Sentinel authenticates the batch via the x-tapiz-key header (sent automatically when ingestKey is set), rate-limits, and deduplicates by (project, fingerprint).

What it does

  • CapturecaptureError(err), captureMessage(msg), plus auto global handlers.
  • Scrub — emails, long digit runs, bearer/JWT-like tokens, secret key=value pairs, and sensitive keys (password, token, email, jmbg, …) are redacted across the entire event (message, stack frames, url, context, breadcrumbs) before anything leaves the process. See scrub.ts.
  • Fingerprint — events group by error type + top in-app stack frames, not by message, so User 4821 not found and User 9 not found are one issue. See fingerprint.ts.
  • Batch — buffered and flushed by size (20) or age (3s), with a sendBeacon flush on page hide so fatal errors aren't lost on unload.
  • Sample / filtersampleRate drops a fraction before work; beforeSend can mutate or drop any event (the privacy scrub still runs after it).

What it deliberately does NOT do

  • No session replay or release-health.
  • No auto-instrumentation for tracing (no fetch/XHR/routing auto-capture) — see Performance tracing; you wrap what you want traced explicitly.
  • It does not re-queue on a failed flush — an error storm against a down endpoint must not grow memory unbounded. Delivery is best-effort.
  • Source-map remapping happens server-side on ingest; the SDK only tags release so the server can associate maps.

Configuration

init(options) accepts:

| Option | Default | Purpose | | --- | --- | --- | | endpoint (required) | — | Ingest URL, e.g. https://sentinel.tapiz.site/api/ingest. | | project (required) | — | App/project slug; must match the Sentinel project. | | ingestKey | — | Per-app key, sent as the x-tapiz-key header. | | release | — | App version, for release grouping / source-map association. | | environment | "production" | Environment label. | | sampleRate | 1 | 0..1 fraction of events actually sent. | | maxBreadcrumbs | 20 | Max breadcrumbs retained per event. | | beforeSend | — | Last-chance hook to mutate or drop an event (return null to drop). | | installGlobalHandlers | true | Auto-attach runtime error handlers. | | fetchImpl | global fetch | Injectable fetch for non-browser / testing. | | debug | false | Surface internal SDK failures instead of staying silent. |

API

| Export | Purpose | | --- | --- | | init(options) | Create the shared client + install global handlers. | | captureError(err, opts?) | Capture an Error / thrown value. | | captureMessage(msg, opts?) | Capture a message event. | | addBreadcrumb(crumb) | Attach a recent-action trail to later events. | | flush() | Force-send buffered events (await before process exit). | | getClient() | The active TelemetryClient, or null. | | TelemetryClient | Class for isolated/multi-tenant use without the singleton. | | computeFingerprint, parseStack, scrub* | Pure helpers, also used by the ingest service. | | initTracing(config) | Create the tracing singleton (separate from init()). | | getTracingClient() | The active TracingClient, or null. | | startTransaction(name) | Start a transaction; returns .span()/.setError()/.finish(). | | startSpan(name, fn) | Wrap fn as a whole transaction; auto-finishes, rethrows on error. |

Wire contract

TelemetryEvent / IngestBatch / IngestResponse in types.ts are the contract shared with the ingest service. The SDK POSTs an IngestBatch as JSON to endpoint, with x-tapiz-key set when ingestKey is provided. Any server honoring this contract can act as the backend.

Development

npm install
npm run build
npm run typecheck
npm test

License

MIT © Tapiz Labs