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

@kanban-pm/sdk-node

v0.1.0

Published

Server-side Node.js analytics SDK for Kanban PM

Readme

@kanban-pm/sdk-node

Server-side Node.js analytics SDK for Kanban PM. Built for reliability — every server event matters.

Installation

npm install @kanban-pm/sdk-node

Quick Start — Long-Running Server

For Express, Fastify, NestJS, or any long-running process:

import Analytics from '@kanban-pm/sdk-node';

// Initialize once at application startup
await Analytics.init(process.env.ANALYTICS_KEY!, {
  endpoint: 'https://your-app.com/api/ingest',
  validateOnInit: true,   // validate API key eagerly at boot
  batchMode: true,        // batch events for throughput
  flushInterval: 2000,
  serviceName: 'api-server',
  serviceVersion: process.env.APP_VERSION,
});

// Track events — fire and forget by default
Analytics.track({
  userId: 'user_123',
  event: 'payment_completed',
  properties: { value: 99, currency: 'USD' },
});

Quick Start — Serverless (Vercel Functions, AWS Lambda)

In serverless, the process may exit before a batch flush completes. Use direct sends with await mode for critical events:

import Analytics from '@kanban-pm/sdk-node';

await Analytics.init(process.env.ANALYTICS_KEY!, {
  endpoint: 'https://your-app.com/api/ingest',
  validateOnInit: false,  // skip network call on cold start
  batchMode: false,       // no batching in serverless
  serviceName: 'payment-webhook',
});

// Await critical events to ensure delivery before the function exits
await Analytics.track(
  { userId: 'user_123', event: 'payment_completed', properties: { value: 99 } },
  { await: true },
);

Quick Start — Edge Runtime (Vercel Edge, Cloudflare Workers)

Edge runtimes have no process, os, or shutdown hooks. The SDK handles this automatically:

import Analytics from '@kanban-pm/sdk-node';

await Analytics.init(process.env.ANALYTICS_KEY!, {
  endpoint: 'https://your-app.com/api/ingest',
  validateOnInit: false,
  batchMode: false,
  serviceName: 'edge-middleware',
});

// Use await mode for reliability
await Analytics.track(
  { userId: 'user_123', event: 'edge_request' },
  { await: true },
);

Fire and Forget vs Await Mode

By default, track(), identify(), and group() are fire-and-forget: they return void immediately and send the event in the background.

// Fire and forget (default) — returns void
Analytics.track({ userId: 'u1', event: 'page_viewed' });

// Await mode — returns Promise<void>, throws on failure
await Analytics.track(
  { userId: 'u1', event: 'payment_completed', properties: { value: 99 } },
  { await: true },
);

When to use each:

| Mode | Use when... | |------|-------------| | Fire and forget | Non-critical events (page views, clicks, feature usage) | | Await | Critical events (payments, subscriptions, user creation) where you need delivery confirmation |

In await mode, the SDK retries on network errors and 5xx responses with exponential backoff. It throws typed errors on non-retryable failures (400, 401).

Batching Guide

Enable batch mode for high-throughput long-running servers:

await Analytics.init(apiKey, {
  batchMode: true,
  batchSize: 50,        // flush when 50 events accumulate
  flushInterval: 2000,  // or every 2 seconds, whichever comes first
});

// Events are queued and sent in batches
Analytics.track({ userId: 'u1', event: 'item_viewed' });
Analytics.track({ userId: 'u2', event: 'item_viewed' });

// Manual flush
const result = await Analytics.flush();
console.log(`Sent: ${result.sent}, Failed: ${result.failed}`);

The SDK automatically flushes on SIGTERM, SIGINT, and beforeExit to prevent data loss during deployments.

Request-Scoped Tracking with withContext()

Attach a requestId or traceId to every event fired during a single request without passing it to every track() call:

import Analytics from '@kanban-pm/sdk-node';

// Express middleware example
app.use((req, res, next) => {
  req.analytics = Analytics.withContext({
    requestContext: {
      requestId: req.id,
      traceId: req.headers['x-trace-id'],
      ip: req.ip,
    },
  });
  next();
});

// In your route handler — requestId is automatically included
app.post('/api/checkout', async (req, res) => {
  await req.analytics.track(
    { userId: req.user.id, event: 'checkout_completed', properties: { total: 99 } },
    { await: true },
  );
  res.json({ success: true });
});

The scoped instance shares the parent's queue and transport — it only adds context.

Typed Event Catalog

For compile-time safety over which events and properties are valid:

import { AnalyticsNode, createCatalog } from '@kanban-pm/sdk-node';

const analytics = new AnalyticsNode();
await analytics.init(apiKey, options);

const catalog = createCatalog(analytics, {
  events: {
    payment_completed: {
      value: 0,           // number
      currency: '',       // string
      planId: '',         // string
    },
    subscription_created: {
      planId: '',
      billingInterval: '' as 'monthly' | 'annual',
      trialDays: 0,
    },
  },
});

// TypeScript enforces correct event names and property shapes:
catalog.track(
  { userId: 'u1' },
  'payment_completed',
  { value: 99, currency: 'USD', planId: 'pro' },
); // ✓ compiles

catalog.track(
  { userId: 'u1' },
  'payment_completed',
  { amount: 99 },  // ✗ TypeScript error — 'amount' not in schema
);

catalog.track(
  { userId: 'u1' },
  'wrong_event',   // ✗ TypeScript error — event not in catalog
  {},
);

Error Handling Reference

All errors extend AnalyticsError with a code property:

| Error | Code | When | |-------|------|------| | AuthenticationError | AUTHENTICATION_ERROR | Invalid API key (401) | | ValidationError | VALIDATION_ERROR | Missing identity, SDK not initialized | | NetworkError | NETWORK_ERROR | Network failure, non-retryable HTTP errors | | RateLimitError | RATE_LIMIT_ERROR | Server returned 429. Has retryAfterMs | | TimeoutError | TIMEOUT_ERROR | Request exceeded timeout |

import { AuthenticationError, RateLimitError } from '@kanban-pm/sdk-node';

try {
  await Analytics.track({ userId, event: 'payment' }, { await: true });
} catch (err) {
  if (err instanceof AuthenticationError) {
    // API key is invalid — check configuration
  } else if (err instanceof RateLimitError) {
    // Back off for err.retryAfterMs
  }
}

In fire-and-forget mode, errors are silently swallowed (or logged to console in debug mode).

Middleware

Add middleware to enrich, filter, or transform events:

// Enrich all events with deployment info
Analytics.use((event, next) => {
  next({
    ...event,
    properties: {
      ...event.properties,
      deploymentId: process.env.DEPLOYMENT_ID,
    },
  });
});

// Drop internal/test events
Analytics.use((event, next) => {
  if (event.properties?.internal) return; // dropped
  next(event);
});

Environment Variables

| Variable | Description | |----------|-------------| | ANALYTICS_KEY | Your project API key | | NODE_ENV | Auto-populated in context.server.environment | | APP_VERSION | Pass as serviceVersion option |

Identity Model

Three identity patterns:

// 1. User-attributed event
Analytics.track({ userId: 'user_123', event: 'purchase' });

// 2. System event (no user)
Analytics.track({ anonymousId: 'cron', event: 'daily_digest_sent' });

// 3. Tenant/group event (B2B)
Analytics.track({ groupId: 'org_456', event: 'seat_added' });

At least one of userId, anonymousId, or groupId must be present. The SDK throws ValidationError synchronously if all three are absent.

API Reference

Analytics.init(apiKey, options?)

Initialize the SDK. Call once at application startup.

Analytics.track(event, options?)

Track an event. Returns void (fire-and-forget) or Promise<void> (await mode).

Analytics.identify(event, options?)

Identify a user with traits. userId is required.

Analytics.group(event, options?)

Associate a user with a group. groupId is required.

Analytics.flush()

Manually flush all queued events. Returns Promise<{ sent, failed }>.

Analytics.shutdown()

Graceful shutdown: flush queue, wait for in-flight events, teardown.

Analytics.withContext(options)

Create a scoped instance with pre-set request context.

Analytics.use(middleware)

Add a middleware function to the pipeline.