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

@metricflow-ai/node

v0.1.2

Published

Server-side Node.js SDK for MetricFlow analytics.

Downloads

549

Readme

@metricflow-ai/node

Server-side Node.js SDK for MetricFlow analytics.

Table of Contents


Installation

npm install @metricflow-ai/node

Requirements

  • Node.js >= 18.17
  • A MetricFlow clientId + clientSecret (server-side credential — never expose clientSecret to a browser)

Quick Start

const { MetricFlow } = require('@metricflow-ai/node');

const metricflow = new MetricFlow({
  clientId: process.env.METRICFLOW_CLIENT_ID,
  clientSecret: process.env.METRICFLOW_CLIENT_SECRET,
});

await metricflow.track('payment_succeeded', {
  distinct_id: 'user_123',
  amount: 99,
});

await metricflow.identify('user_123', undefined, { plan: 'pro' });

await metricflow.revenue.paymentSucceeded({
  userId: 'user_123',
  eventId: 'stripe_evt_123',
  amount: 99,
  currency: 'USD',
  provider: 'stripe',
});

await metricflow.people.set('user_123', { plan: 'pro' });
await metricflow.flush();

Every method also accepts a Node-style (error, result) callback as the last argument instead of using the returned Promise — pick whichever style fits your codebase.

Backend SDKs are stateless by design. Pass distinct_id, user_id, or anonymous_id on each event. For request-linked backend events, pass request context so MetricFlow can enrich IP, browser, OS, and device from the original visitor request.


Identity Resolution

Identity is resolved in this priority order, from the options argument or top-level properties:

  1. userId (or properties.user_id) — the strongest identifier.
  2. distinctId (or properties.distinct_id) — used as user_id only if userId isn't set.
  3. anonymousId (or properties.anonymous_id) — for events with no known user.

At least one of these is required, or the call throws.

Linking a backend event to a user from your frontend SDK

To attribute a backend event (e.g. a Stripe webhook firing revenue.paymentSucceeded) to the same person already tracked by your browser or React Native app, pass the exact same userId that SDK used in its own identify() call:

// Browser or React Native app, at login:
//   metricflow.identify("user_123", { email: "...", plan: "free" });

// Backend, later — same user_id, different SDK:
await metricflow.revenue.paymentSucceeded({
  userId: 'user_123',  // must match the frontend identify() call exactly
  eventId: 'stripe_evt_123',
  amount: 99,
  currency: 'USD',
  provider: 'stripe',
});

Both events resolve to the same user profile automatically — no extra field, session ID, or setup is needed. userId values are compared exactly (case-insensitive only when the value looks like an email); IDs like usr_abc123 must match byte-for-byte across every SDK sending them.


Configuration

const metricflow = new MetricFlow({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  batchSize: 50,
  flushIntervalMs: 5000,
  requestTimeoutMs: 90000,
  maxRetries: 3,
  maxQueueSize: 10000,
  autoFlush: true,
  debug: false,
});

| Field | Type | Required? | Description | | --- | --- | --- | --- | | clientId | string | Required | Your project's client ID. | | clientSecret | string | Required | Your project's server-side secret. Never expose this in browser code. | | apiHost | string | Optional | Base API host. Only needed to point at a custom deployment — most integrations don't need this. | | endpoint | string | Optional | Override the full track endpoint URL directly instead of deriving it from apiHost. | | batchSize | number | Optional | Events per batch flush. Default: 50. | | flushIntervalMs | number | Optional | Auto-flush interval in milliseconds. Default: 5000. | | requestTimeoutMs | number | Optional | HTTP request timeout. Default: 90000. | | maxRetries | number | Optional | Retry attempts on transient failures. Default: 3. | | maxQueueSize | number | Optional | Max events buffered before oldest events are dropped. Default: 10000. | | autoFlush | boolean | Optional | Automatically flush on the flushIntervalMs timer. Default: true. | | debug | boolean | Optional | Verbose logging to the console. Default: false. |


Core API

track(eventName, properties, options?)

Queue a custom event. Batched and flushed automatically (or manually via flush()).

await metricflow.track('workspace_created', {
  distinct_id: 'user_123',
  workspace_name: 'Acme Inc',
});

options (all optional): distinctId, userId, anonymousId, timestamp, insertId, idempotencyKey, context.

track_batch(events)

Track multiple events in one call — each item is { event, properties?, distinct_id?, user_id?, anonymous_id?, timestamp?, insert_id?, idempotency_key?, context? }.

await metricflow.track_batch([
  { event: 'signup_started', distinct_id: 'user_123' },
  { event: 'signup_completed', distinct_id: 'user_123' },
]);

identify(userId, anonymousId?, traits?)

Link a known user to their profile traits.

await metricflow.identify('user_123', undefined, {
  name: 'Ananya Rao',
  email: '[email protected]',
  plan: 'pro',
});

alias(newId, originalId)

Merge two identities (e.g. anonymous ID → logged-in user ID).

await metricflow.alias('user_123', 'anon_abc');

Flush / Close

await metricflow.flush();  // send whatever's queued right now
await metricflow.close();  // flush + stop the auto-flush timer (call on process shutdown)

User Profiles (people)

Set and update persistent user profile properties.

await metricflow.people.set('user_123', { plan: 'pro', company: 'Acme Inc' });
await metricflow.people.set_once('user_123', { first_seen: new Date().toISOString() });
await metricflow.people.increment('user_123', 'login_count', 1);
await metricflow.people.append('user_123', 'tags', 'beta_user');
await metricflow.people.union('user_123', 'roles', ['admin']);
await metricflow.people.unset('user_123', ['temp_flag']);

// Revenue-style running totals on the profile
await metricflow.people.track_charge('user_123', 49, { plan: 'pro' });
await metricflow.people.clear_charges('user_123');

| Method | Description | | --- | --- | | set(distinctId, properties) | Overwrite profile properties. | | set_once(distinctId, properties) | Set only if the property doesn't already exist. | | increment(distinctId, property, value?) | Increment a numeric property (default +1). | | append(distinctId, property, value) | Append a value to a list property. | | union(distinctId, property, value) | Add values to a list property without duplicates. | | unset(distinctId, properties[]) | Remove properties from the profile. | | track_charge(distinctId, amount, properties?) | Append a transaction record. | | clear_charges(distinctId) | Clear all transaction records. |


Revenue Tracking

Revenue is server-authenticated only — always initialise with a clientSecret. Revenue sent from a browser/web context is rejected by the backend, so a malicious client can't inflate your numbers.

There are two ways to record revenue:

1. Custom events — add a $revenue property

Mark any event as revenue by attaching a $revenue amount and a currency. This is the easiest path for in-app purchases with your own event names.

await metricflow.track('order_completed', {
  distinct_id: 'user_123',
  $revenue: 24.99,        // the amount — negative = refund
  currency: 'USD',        // any 3-letter ISO code (USD, EUR, INR, …)
  insert_id: 'order_789', // unique id → dedup (required)
  plan: 'pro',            // any extra business props are fine
});

// A refund — negative $revenue
await metricflow.track('order_refunded', {
  distinct_id: 'user_123',
  $revenue: -24.99,
  currency: 'USD',
  insert_id: 'refund_789',
});

$revenue is the only $-prefixed property the backend accepts; all other $…/__… keys are rejected.

2. Named billing events — revenue.* helpers

For payment-provider webhooks (Stripe / Apple / Google) with canonical names, use the typed helpers. The amount comes from amount; refundCreated is stored as negative revenue automatically.

await metricflow.revenue.paymentSucceeded({
  userId: 'user_123',
  eventId: 'stripe_evt_123', // → insert_id + idempotency_key
  amount: 99,
  currency: 'USD',
  provider: 'stripe',
});

Helpers: paymentSucceeded, invoicePaid, refundCreated, subscriptionCreated, subscriptionUpdated, subscriptionCancelled.

Required fields (both paths)

| Field | Notes | |-------|-------| | identity | distinct_id / userId — required | | amount | $revenue (non-zero) or amount (> 0) | | currency | 3-letter ISO code, case-insensitive | | dedup id | insert_id / idempotency_key (helpers derive it from eventId) |

Missing any of these → the event is rejected and no revenue is recorded. Revenue is aggregated downstream on the computed revenue property.


Error Handling

All API calls reject with a subclass of MetricFlowError:

const { MetricFlowConfigError, MetricFlowHttpError } = require('@metricflow-ai/node');

try {
  await metricflow.track('event_name', { distinct_id: 'user_123' });
} catch (err) {
  if (err instanceof MetricFlowHttpError) {
    console.error('HTTP failure:', err.status, err.body);
  } else if (err instanceof MetricFlowConfigError) {
    console.error('Bad config:', err.message);
  } else {
    throw err;
  }
}

| Error | Thrown when | | --- | --- | | MetricFlowConfigError | Invalid constructor options (missing clientId/clientSecret, bad URL, non-positive numeric option). | | MetricFlowHttpError | The ingestion request failed. Has .status, .body, and .retryAfterMs. | | MetricFlowError | Base class for both — catch this to handle any SDK error generically. |

Failed batches are retried automatically up to maxRetries times before the error is raised.


License

MIT — see LICENSE.