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

@risenexa/tracking

v0.1.0

Published

Node.js SDK for tracking user events with Risenexa

Readme

@risenexa/tracking

Node.js SDK for tracking user registration and conversion events with Risenexa.

Requirements

  • Node.js 18 or later (uses built-in fetch and crypto.randomUUID())
  • Zero runtime dependencies

Installation

npm install @risenexa/tracking

Quick Start

Global Configuration (recommended for single-startup apps)

import { configure, trackRegistration, trackConversion } from "@risenexa/tracking";

// Call once at app boot
configure({
  apiKey: "rxt_live_your_api_key",
  startupSlug: "my-startup",
});

// Track a new user registration
await trackRegistration({ userId: "usr_123" });

// Track a user becoming a paying customer
await trackConversion({ userId: "usr_123" });

Per-Instance Configuration (for multi-startup apps)

import { RisenexaTracking } from "@risenexa/tracking";

const client = new RisenexaTracking({
  apiKey: "rxt_live_your_api_key",
  startupSlug: "my-startup",
});

await client.trackRegistration({ userId: "usr_456" });
await client.trackConversion({ userId: "usr_456" });

Multiple instances are fully independent — no shared state:

const startupA = new RisenexaTracking({ apiKey: "key-a", startupSlug: "startup-a" });
const startupB = new RisenexaTracking({ apiKey: "key-b", startupSlug: "startup-b" });

await startupA.trackRegistration({ userId: "user-1" });
await startupB.trackConversion({ userId: "user-2" });

API Reference

configure(options)

Configure the global SDK instance. Call once at app startup before using module-level functions.

configure({
  apiKey: "rxt_live_abc123",       // required
  startupSlug: "my-startup",        // required
  baseUrl: "https://app.risenexa.com", // optional, default shown
  timeout: 2000,                    // optional, default: 2000ms
  maxRetries: 3,                    // optional, default: 3
});

trackRegistration({ userId, ...options })

Track a user registration event (event_type: "user_registered").

await trackRegistration({
  userId: "usr_123",                          // required
  eventId: "550e8400-e29b-41d4-a716-...",    // optional — auto-generated UUID v4
  occurredAt: "2026-04-01T12:00:00Z",         // optional — ISO 8601 UTC
  metadata: { plan: "free", source: "web" },  // optional — arbitrary JSONB
  action: "add",                              // optional — "add" | "remove", default: "add"
});

trackConversion({ userId, ...options })

Track a user conversion event (event_type: "user_converted").

await trackConversion({
  userId: "usr_123",
  metadata: { plan: "pro", mrr: 29 },
});

track({ eventType, userId, ...options })

Low-level method that accepts all event fields directly.

await track({
  eventType: "user_registered",               // required
  userId: "usr_123",                          // required
  eventId: "550e8400-e29b-41d4-a716-...",    // optional
  occurredAt: "2026-04-01T12:00:00Z",         // optional
  metadata: { plan: "pro" },                  // optional
  action: "add",                              // optional
});

new RisenexaTracking(options)

Create a per-instance client with independent configuration.

const client = new RisenexaTracking({
  apiKey: string;        // required
  startupSlug: string;   // required
  baseUrl?: string;      // default: "https://app.risenexa.com"
  timeout?: number;      // default: 2000 (ms)
  maxRetries?: number;   // default: 3
});

// Same methods as module-level functions:
await client.trackRegistration({ userId });
await client.trackConversion({ userId });
await client.track({ eventType, userId, ... });

Configuration Options

| Option | Required | Default | Description | |--------|----------|---------|-------------| | apiKey | Yes | — | Bearer token with tracking:write scope from Risenexa dashboard | | startupSlug | Yes | — | The slug of your startup in Risenexa | | baseUrl | No | "https://app.risenexa.com" | Override for staging or self-hosted instances | | timeout | No | 2000 | Per-request timeout in milliseconds; resets per retry attempt | | maxRetries | No | 3 | Maximum retry attempts; set to 0 to disable retries |

Error Handling

All errors extend RisenexaTrackingError, which extends Error.

import {
  RisenexaTracking,
  AuthenticationError,
  AuthorizationError,
  ValidationError,
  StartupNotFoundError,
  RateLimitError,
  MaxRetriesExceededError,
  ConnectionError,
  ConfigurationError,
} from "@risenexa/tracking";

try {
  await client.trackRegistration({ userId: "usr_123" });
} catch (err) {
  if (err instanceof ConfigurationError) {
    // Missing apiKey or startupSlug — fix your configure() call
    console.error("SDK not configured:", err.message);
  } else if (err instanceof AuthenticationError) {
    // HTTP 401 — invalid or missing API token
    console.error("Invalid API key:", err.message);
  } else if (err instanceof AuthorizationError) {
    // HTTP 403 — token lacks tracking:write scope
    console.error("Token lacks required scope:", err.message);
  } else if (err instanceof StartupNotFoundError) {
    // HTTP 404 — wrong startupSlug or startup not in your account
    console.error("Startup not found:", err.message);
  } else if (err instanceof ValidationError) {
    // HTTP 422 — invalid payload
    console.error("Validation errors:", err.errors); // string[]
  } else if (err instanceof RateLimitError) {
    // All retries exhausted on 429 responses
    console.error(`Rate limited. Retry after ${err.retryAfter}s`);
  } else if (err instanceof MaxRetriesExceededError) {
    // All retries exhausted on 5xx responses
    console.error("Max retries exceeded:", err.message);
  } else if (err instanceof ConnectionError) {
    // All retries exhausted on transport errors (timeouts, connection refused)
    console.error("Connection failed:", err.message);
  }
}

Return Value

On success, all tracking methods return a TrackingResult:

interface TrackingResult {
  success: boolean;               // true when HTTP 202 received
  statusCode: number;             // 202
  eventId: string;                // UUID v4 sent with the request
  body: Record<string, unknown>;  // { status: "accepted" }
}

Retry Behavior

The SDK automatically retries on transient failures with exponential backoff and ±20% jitter:

| Condition | Action | |-----------|--------| | HTTP 429 (rate limited) | Retry; honor Retry-After header if present | | HTTP 500, 502, 503 | Retry with exponential backoff | | Request timeout (2000ms) | Retry; timeout resets per attempt | | Connection error | Retry with exponential backoff | | HTTP 401, 403, 404, 422 | Never retry — permanent failure, throws immediately |

Backoff formula:

delay = min(1.0 * 2^attempt, 30.0) seconds  (±20% random jitter)

| Before Retry | Base Delay | Jitter Range | |--------------|------------|--------------| | Retry 1 | 1.0s | [0.8s, 1.2s] | | Retry 2 | 2.0s | [1.6s, 2.4s] | | Retry 3 | 4.0s | [3.2s, 4.8s] |

Idempotency

Each tracking call generates a UUID v4 event_id before the first HTTP attempt. This event_id is reused on all retry attempts, ensuring exactly-once counter increments even if a request is retried after a transient failure.

TypeScript

All types are included in the package. No @types/* packages required.

import type {
  ConfigOptions,
  ConvenienceOptions,
  TrackOptions,
  TrackingResult,
} from "@risenexa/tracking";

CJS and ESM

The package ships both CommonJS (.cjs) and ES module (.js) builds with TypeScript declarations.

// CommonJS
const { configure, trackRegistration } = require("@risenexa/tracking");

// ES modules
import { configure, trackRegistration } from "@risenexa/tracking";

Next.js Example

// lib/risenexa.ts
import { configure } from "@risenexa/tracking";

configure({
  apiKey: process.env.RISENEXA_API_KEY!,
  startupSlug: process.env.RISENEXA_STARTUP_SLUG!,
});

export { trackRegistration, trackConversion } from "@risenexa/tracking";
// app/api/auth/signup/route.ts
import { trackRegistration } from "@/lib/risenexa";

export async function POST(request: Request) {
  const { userId } = await createUser(request);

  // Non-blocking fire-and-forget
  trackRegistration({ userId }).catch(console.error);

  return Response.json({ success: true });
}

License

MIT — Copyright (c) 2026 Patrick Espake