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

@tailglow/core

v0.4.0

Published

Platform-agnostic analytics core for Tailglow. Used by @tailglow/browser, @tailglow/node, @tailglow/react-native, and other platform packages.

Readme

@tailglow/core

Platform-agnostic analytics core for Tailglow. Used internally by @tailglow/browser.

Supported runtimes

| Runtime | Floor | Notes | | --------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | Node.js | >=22 | Enforced via engines.node. Native fetch and globalThis.crypto required. | | Bun | Any | Modern JS by default. | | React Native | >=0.74 (recommended) | globalThis.crypto.getRandomValues is native from RN 0.71. We document 0.74+ as the supported floor. | | Modern browsers | Chrome 90+, Firefox 90+, Safari 14+, Edge 90+ | Released 2021+. ESM, fetch, crypto, URLSearchParams, Blob all native. | | Cloudflare Workers / Vercel Edge / Deno | Any recent | Web Crypto + fetch first-class. |

ESM only. No CJS bundle. If your toolchain can't load ESM, upgrade your toolchain.

Direct usage

For environments without a published platform package, you can compose TailglowCore directly. You own the lifecycle (calling flush()/destroy() at the right time) and any auto-collection.

import { TailglowCore } from "@tailglow/core";

const tg = new TailglowCore({
  url: "https://ingest.example.com",
  key: "tg_ingest_..."
});

tg.track("job_completed", { job_id: "abc" });

process.on("SIGTERM", async () => {
  await tg.flush();
  await tg.destroy();
});

Building a platform package

The intended pattern: compose a TailglowCore and wire platform-specific lifecycle (AppState on RN, process.on on Node, visibilitychange on browser) and any auto-collectors that emit records via core.track(name, props) (preferred) or, for adapter authors, core.capture(prebuiltRecord).

See @tailglow/browser for a reference implementation.

Pipeline order

When a record passes through core.track() or core.capture():

rateLimit gate → stamp → redact → maxRecordBytes check → onBeforeSend → queue → flush → transport
  • rateLimit: global token-bucket volume gate (when enabled). It runs before the rest of the pipeline, so a record the bucket drops is never stamped, redacted, size-checked, passed to onBeforeSend, or queued. Drops surface as a collapsed tglow_rate_limited self-event
  • stamp: SDK metadata (event_id, session_id, event_time, user_id, device_id) plus any sticky context fields
  • redact: URL token redaction, email redaction, field allow/deny lists
  • maxRecordBytes: drop oversized records (default 1MB; Infinity disables) and emit a tglow_record_dropped self-event
  • onBeforeSend: consumer hook sees the post-redaction record; return null to drop
  • queue: in-memory buffer
  • flush: group records by collection, serialize each batch, POST to ?collection=<slug> (typically events/errors/logs), on permanent failure call onTransportError

onBeforeSend does NOT see pre-redaction values. To get raw payloads, set redact.enabled: false.

Identity

| Mechanism | Source | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | session_id | SessionManager. Renews after sessionTimeout of inactivity. Force-rotate via rotateSession(). The onSessionRotate config hook fires after every rotation with a snapshot of the ended session (platform packages use it for session summaries). | | user_id | identify(user_id). Pre-identify records still in the buffer OR mid-flight in pending batches are retroactively backfilled in the same session. Clear via unidentify(). | | device_id | setDeviceId(id). Customer-supplied. Core does not generate or read hardware IDs. |

Sticky sampling resolves in cascade: user_iddevice_idsession_id. Without an identifier above session, sampling resets per session, the privacy-preserving default. Caveat: an anonymous user (sampled by session_id) who later calls identify() shifts their sampling key to user_id. The verdict can flip in/out mid-session. Acceptable noise for sampleRate >= 0.1; for tighter rates, identify before any tracking.

Identity changes (logout/login, org switch)

There is no reset() method by design. To clear and rebuild identity:

await tg.flush(); // best-effort drain pending records under the old identity
await tg.destroy(); // remove listeners, stop the queue interval
tg = new TailglowCore({ ...new config() });

Records tracked between flush() returning and destroy() are dropped with the old instance. For most telemetry this is acceptable.

Opt-out

optOut() stops tracking: records are dropped at the source (not collected, queued, or sent) until optIn(). It drops any un-sent queue without a final flush, so call await tg.flush() first if you want to deliver what is already queued. isOptedOut() reports status. There is no consent state machine; for a CMP-gated setup, construct the SDK only after the user accepts and destroy() to revoke.

Lifecycle

| Method | Type | Notes | | ----------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | flush() | async | Force-drain queue. Returns when all batches are acknowledged or have failed. | | destroy() | async | Final flush + stop queue interval. After this, the instance is inert. | | Browser hide / Node SIGTERM | sync best-effort | Platform packages handle these. They use sendBeacon (browser) or unawaited transport calls (Node). Delivery is not guaranteed if the runtime exits before the request completes. |

Public API

  • TailglowCore: the core class. track / identify / setContext / optOut / flush / destroy etc.
  • Transport, Queue, SessionManager: composable building blocks.
  • buildIngestUrl, serializeBatch, groupByCollection, INGEST_CONTENT_TYPE, COLLECTION_FIELD: shared transport primitives.
  • stampRecord, backfillUserId, generateSessionId, generateRecordId, randomBytes: record stamping and identity helpers.
  • redactUrl, redactEmails, redactRecord: PII/token redaction.
  • isSampledIn, hashToUnitInterval: sticky sampling.
  • isBrowser, isNode, isLocalhostOrigin, isDoNotTrack, isGpc: runtime detection helpers.
  • StorageAdapter interface, deriveStorageKey, saveToStorageAsync, restoreFromStorageAsync, clearStorageAsync: async storage helpers.

Routing & collections

The SDK manages three configurable collections. Defaults shown:

new TailglowCore({
  url,
  key,
  collections: {
    events: "events", // default destination for track() + auto-collectors
    errors: "errors", // captureException / captureMessage / console.error
    logs: "logs" // console wrappers (log/warn/info/debug)
  }
});

track(name, props) routes the record to the configured events collection and stamps name as the type field:

tg.track("purchase", { amount: 99, currency: "USD" });
// → POST ?collection=events with { type: "purchase", amount: 99, currency: "USD", ... }

Use the object form of track() to override the collection per call (rare; useful for high-volume telemetry with its own retention/schema):

tg.track({ type: "audit_event", collection: "audit_log", actor: "..." });
// → POST ?collection=audit_log with { type: "audit_event", actor: "...", ... }

The string form (track(name, props)) is the 99% case; the object form is the override. There is no third positional argument — pass type, collection, and props in one object.

Records are grouped by collection at flush time and POSTed in per-collection batches. Tailglow auto-tracks schema versions per collection on the server.

Setting all three collections to the same slug merges everything into one timeline:

new TailglowCore({ url, key, collections: { events: "all", errors: "all", logs: "all" } });

Typed event schemas

Augment TailglowEventTypes to declare typed props per event name (the type field). This is the canonical augmentation location. Augmenting platform packages like @tailglow/browser does not work; the schema is re-exported from core, not redeclared.

declare module "@tailglow/core" {
  interface TailglowEventTypes {
    signup: { plan: "free" | "pro" };
    purchase: { amount: number; currency: string };
  }
}

tg.track("signup", { plan: "pro" }); // ✓ typed
tg.track("signup", { plan: "wrong" }); // ✗ TS error
tg.track("anything_else", { whatever: true }); // ✓ falls back to Record<string, unknown>

The augmented keys describe the type field of records routed to the configured events collection, not separate collections per key.