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

@v-tilt/node

v1.0.0

Published

vTilt analytics SDK for Node.js

Readme

@v-tilt/node

Server-side analytics SDK for vTilt. Capture events, identify users, and track exceptions from Node.js, Cloudflare Workers, Deno, and other server runtimes. The wire format matches the browser SDK, so server and browser events converge on the same person record automatically.

Install

npm install @v-tilt/node
# or: pnpm add @v-tilt/node

Requires Node.js >= 18 (for the global fetch). Works in Cloudflare Workers with nodejs_compat enabled.

Quick start

import { VTiltNode } from "@v-tilt/node";

const vtilt = new VTiltNode(process.env.VTILT_TRACKER_TOKEN!, {
  host: "https://your-vtilt-instance.com",
});

vtilt.capture({
  distinctId: "user_123",
  event: "purchase",
  properties: { amount: 99.99 },
});

// Always flush before the process exits.
await vtilt.shutdown();

capture() is non-blocking: events are queued in memory and flushed in batches. Call flush() to send immediately, or shutdown() before exit.

Per-request context

Set identity once per request instead of repeating it on every call:

app.use((req, res, next) => {
  vtilt.setContext({
    distinctId: req.user?.id,
    anonymousId: req.cookies?.vt_anon,
    ip: req.ip, // enables GeoIP enrichment (see below)
  });
  res.on("finish", () => vtilt.clearContext());
  next();
});

Identify & alias

// Set properties for a known user
vtilt.identify({ distinctId: "user_123", properties: { plan: "pro" } });

// Link an anonymous browser session to the authenticated user
vtilt.identify({
  distinctId: "user_123",
  anonymousId: "anon_from_browser",
  properties: { email: "[email protected]" },
});

// Link two known ids
vtilt.alias({ distinctId: "user_123", alias: "legacy_id_456" });

Enrichment parity with the browser

A server cannot observe the end user's browser, so the SDK never sends $browser / $os / device properties. It can carry GeoIP and the original IP / User-Agent / referrer when you forward them from the incoming request:

vtilt.capture({
  distinctId: "user_123",
  event: "page_view",
  ip: req.ip, // -> $ip, enables GeoIP exactly like the browser
  userAgent: req.headers["user-agent"], // -> $raw_user_agent (optional)
  referrer: req.headers["referer"], // -> $referrer (optional)
});

GeoIP is smart by default: it runs only when an end-user ip is available. When no IP is forwarded the SDK sets $geoip_disable so the caller's own server IP is never geolocated. Force it either way with the disableGeoip option or the per-call disableGeoip flag.

Global (super) properties

vtilt.register({ app_version: "2.1.0", environment: "production" });
vtilt.unregister("app_version");

Registered properties are merged into every event at the lowest precedence (per-event properties win on collisions). You can also pass globalProperties to the constructor.

Error tracking

try {
  doWork();
} catch (err) {
  vtilt.captureException(err, { distinctId: "user_123" });
}

Emits a $exception event with $exception_type, $exception_message, and $exception_stack_trace_raw.

Lifecycle hooks

const off = vtilt.on("error", (err) => console.error("flush failed", err));
vtilt.on("flush", (batch) => metrics.increment("events.sent", batch.length));
// off() to unsubscribe

before_send lets you mutate or drop events before they are queued:

const vtilt = new VTiltNode(token, {
  before_send: (event) => {
    if (event.event === "debug_event") return null; // drop
    event.payload.server = "api-1";
    return event;
  },
});

GDPR

vtilt.optOut(); // drop all events
vtilt.optIn(); // resume
vtilt.isOptedOut();

Serverless / edge

In short-lived runtimes (Lambda, Workers) there may be no later flush. Use the immediate variants, which send a single event and await the request:

await vtilt.captureImmediate({
  distinctId: "user_123",
  event: "webhook_received",
});

Configuration

| Option | Default | Description | | ------------------ | ----------------------- | --------------------------------------------------------- | | host | http://localhost:3000 | API base URL (no trailing slash) | | flushAt | 20 | Queue size that triggers a flush | | flushInterval | 10000 | Periodic flush interval (ms) | | maxBatchSize | 100 | Max events per HTTP request | | maxQueueSize | 1000 | Max queued events; oldest dropped when exceeded | | requestTimeout | 10000 | HTTP request timeout (ms) | | fetchRetryCount | 3 | Retries for failed flushes | | fetchRetryDelay | 3000 | Delay between retries (ms) | | disableGeoip | undefined (smart) | true/false to force; smart mode keys off forwarded IP | | globalProperties | {} | Super properties merged into every event | | before_send | — | Hook(s) to mutate/drop events | | optOut | false | Start opted out (GDPR) | | compression | gzip-js | Body compression (gzip-js or none) | | disabled | false | Kill switch — all methods are no-ops | | fetch | global fetch | Custom fetch implementation | | debug | false | Console debug logging |

License

MIT