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

@atrix.dev/analytics-node

v0.1.1

Published

Atrix Analytics for Node.js and Bun servers: batched, gzipped, retrying event capture that never throws.

Readme

@atrix.dev/analytics-node

Server-side Atrix Analytics for Node.js ≥ 18 and Bun. Events are queued in memory, batched and gzipped, and sent in the background with retries. No method ever throws, and a missing or invalid key turns the client into a silent no-op, so analytics can never take your API down.

bun add @atrix.dev/analytics-node      # or: npm i @atrix.dev/analytics-node

Quickstart

import { Atrix } from "@atrix.dev/analytics-node";

const atrix = new Atrix(process.env.ATRIX_PUBLIC_KEY!);   // atx_pk_eu_production_…

atrix.capture({
  distinctId: user.id,                                     // your user id, never an email or phone number
  event: "subscription_renewed",
  properties: { plan: "growth", amount: 14900, currency: "USD" },   // money: integer minor units + ISO currency
  groups: { company: user.companyId },
});

// Link the browser/app visitor to the account at sign-in (anonId comes from the client SDK).
atrix.identify({ distinctId: user.id, anonId: req.body.atrixAnonId, set: { plan: "growth" } });

// Link a billing customer to the user, so revenue webhooks from the provider are attributed to them
// ($revenue_customer). provider: "stripe" | "revenuecat" | "app_store" | "google_play"; unknown values are dropped.
atrix.setRevenueCustomer({ distinctId: user.id, customerId: stripeCustomer.id, provider: "stripe" });

// What serving a customer cost you ($cost), so the revenue `margin` insight can show gross margin per group.
// costMinor is an integer in minor units of `currency`; kind is ai | infra | support | ...; invalid cost is dropped.
atrix.captureCost({
  distinctId: user.id,
  costMinor: 125,
  currency: "USD",
  kind: "ai",
  groups: { organization: org.id },
  model: "claude-sonnet", tokensIn: 1200, tokensOut: 340,
});

// Company/team profiles.
atrix.groupIdentify({ groupType: "company", groupKey: "acme", properties: { name: "Acme", seats: 40 } });

// Before the process exits (serverless handlers: at the end of every invocation).
await atrix.shutdown();

The region's capture host comes from the key: atx_pk_<region>_<env>_<22> sends to https://<region>.i.analytics.atrix.dev. Pass host to use a proxy.

Feature flags

const atrix = new Atrix(process.env.ATRIX_KEY!, { flagsSecretKey: process.env.ATRIX_FLAGS_SECRET });
if (await atrix.isFeatureEnabled("new-checkout", user.id, { personProperties: { plan: user.plan } })) { ... }
const variant = await atrix.getFeatureFlag("pricing", user.id);      // "test" | "control" | false | undefined
const config = await atrix.getFeatureFlagPayload("pricing", user.id);

With flagsSecretKey flags are evaluated in-process from /flags/definitions (refreshed every 30 s with an ETag); a flag that needs something you did not pass (a cohort, a missing property) falls back to /flags. Without it every read calls /flags. Reads emit $feature_flag_called once per user, flag and value.

Options

| Option | Default | | | --- | --- | --- | | host | from the key | Capture origin override. | | flushAt | 20 | Send when this many events are queued. | | flushIntervalMs | 30 000 | Send at least this often. | | maxBatchSize | 100 | Events per request. | | maxQueueSize / maxQueueBytes | 1000 / 5 MB | In-memory bound. The oldest events are dropped and reported as $dropped_events. | | requestTimeoutMs | 10 000 | Per request. | | consent | granted | pending holds events in memory until setConsent("granted"); denied never touches the network. | | compression | true | Gzip bodies. | | beforeSend | — | (event) => event \| null: edit or drop every event. A throwing hook drops the event. | | propertyDenylist | [] | Keys stripped from properties, $set and $set_once after beforeSend. | | superProperties | {} | Stamped on every event, e.g. { service: "billing-api" }. register() adds more at runtime. | | flushOnExit | true | Flush when the event loop empties (beforeExit). | | debug | false | Log problems with console.warn. |

Behaviour

  • Every event gets a UUIDv7 when it is captured; retries resend the same uuid and capture dedupes on it. Pass your own uuid (a UUIDv7) to make a capture idempotent across your own retries.
  • Flushes at 20 events, every 30 s, on flush() and on shutdown(). One request in flight at a time, at most 100 events per request.
  • A 413 splits the batch in half, recursively; a single event that is still too large is dropped. Network errors, timeouts, 408, 429 and 5xx retry with exponential backoff and jitter, honouring Retry-After. Other 4xx drop the batch.
  • Timers are unref'd: the SDK never keeps your process alive. Call await atrix.shutdown() to make sure the queue is delivered; flushOnExit covers scripts that simply end.
  • Server-side identity is stateless. Each call names its distinctId; junk ids (guest, null, undefined, …) are refused. There is no alias: merges happen server-side from identify({ anonId }).
  • Conformance: this SDK passes the shared sdks/spec suite under both Bun and Node (bun run conformance).

MIT licensed.