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

@igeaman-org/referrals

v0.1.1

Published

Database-agnostic referral attribution, rewards, fraud controls, and analytics

Readme

@igeaman-org/referrals

Database-agnostic referral infrastructure for TypeScript applications. Track first- and last-touch attribution, issue referral codes, record custom conversion events, calculate rewards, enforce campaign limits, add fraud policies, and query analytics while keeping all data in your own database.

PostgreSQL and Supabase are officially supported. Other databases can be integrated through the ReferralStorage interface.

Features

  • First-touch and last-touch attribution, configurable per campaign
  • Custom attribution and visitor-cookie windows
  • Cryptographically secure referral codes and referral URLs
  • Custom conversion events with idempotency
  • Pluggable rewards and commissions
  • Pluggable fraud evaluation
  • Code expiration and atomic usage limits
  • Privacy-preserving IP and user-agent hashing
  • PostgreSQL transactions, indexes, RLS, and touch-event partitioning
  • Browser, server, edge-runtime, and testing entry points

Requirements

  • Node.js 20 or later for the PostgreSQL adapter
  • PostgreSQL 14 or later, including Supabase PostgreSQL
  • Your own server endpoint for accepting browser tracking requests

Do not connect a browser directly to the database or expose a Supabase service-role key.

Installation

pnpm add @igeaman-org/referrals pg

Equivalent npm command:

npm install @igeaman-org/referrals pg

The pg dependency is only needed when using the official PostgreSQL/Supabase adapter.

1. Install the database schema

The SQL migration is included in the npm package:

psql "$DATABASE_URL" \
  -f node_modules/@igeaman-org/referrals/migrations/postgres.sql

For Supabase, open the SQL editor, paste the contents of migrations/postgres.sql, and run it once. The migration creates the referrals schema and enables row-level security without granting browser write access.

2. Create a campaign

Campaign settings live in your database:

INSERT INTO referrals.campaigns (
  key,
  name,
  attribution_model,
  attribution_window_seconds,
  cookie_window_seconds,
  code_length,
  max_uses_per_code
)
VALUES (
  'customer-referrals',
  'Customer referrals',
  'first_touch',
  2592000,
  2592000,
  10,
  NULL
);
  • attribution_model: first_touch or last_touch
  • attribution_window_seconds: maximum time between the credited touch and conversion; NULL means unlimited
  • cookie_window_seconds: maximum age of a touch when a visitor is identified; NULL means unlimited
  • max_uses_per_code: campaign default usage limit; NULL means unlimited

Both first and last touches are stored regardless of the selected credit model. The model can therefore be changed per campaign without losing attribution history.

3. Configure the server engine

import { Pool } from 'pg';
import { ReferralEngine } from '@igeaman-org/referrals';
import { PostgresReferralStorage } from '@igeaman-org/referrals/postgres';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  connectionTimeoutMillis: 5_000,
  idleTimeoutMillis: 30_000,
});

const storage = new PostgresReferralStorage({
  pool,
  statementTimeoutMs: 5_000,
});

export const referrals = new ReferralEngine({
  storage,
  hashSecret: process.env.REFERRAL_HASH_SECRET!,
  rewardCalculator: ({ conversion, referrerId }) => {
    if (conversion.event !== 'purchase' || conversion.value === undefined) {
      return [];
    }

    return [
      {
        beneficiaryId: referrerId,
        amount: conversion.value * 0.1,
        currency: conversion.currency ?? 'USD',
      },
    ];
  },
});

Validate environment variables when the process starts. REFERRAL_HASH_SECRET must be an independently generated secret of at least 32 characters.

4. Create a referral code and URL

Create codes only from an authenticated server route. Derive the referrer ID from the authenticated session rather than accepting it from the request body.

import { buildReferralUrl } from '@igeaman-org/referrals';

const referral = await referrals.createCode({
  campaignKey: 'customer-referrals',
  referrerId: authenticatedUser.id,
  expiresAt: new Date('2027-01-01T00:00:00.000Z'),
  maxUses: 100,
});

const referralUrl = buildReferralUrl('https://product.example/join', referral.code);

To use a custom code:

await referrals.createCode({
  campaignKey: 'customer-referrals',
  referrerId: authenticatedUser.id,
  code: 'AMAN2026',
});

5. Capture a referral touch

Your public server endpoint should validate the request, rate-limit it, obtain the IP address from trusted proxy configuration, and call:

const touch = await referrals.trackTouch({
  campaignKey: body.campaignKey,
  code: body.code,
  visitorId: body.visitorId,
  landingUrl: body.landingUrl,
  referrerUrl: body.referrerUrl,
  ipAddress: trustedClientIp,
  userAgent: request.headers.get('user-agent') ?? undefined,
});

The engine hashes IP and user-agent values before persistence. Do not add raw personal data to metadata.

Browser client

import { ReferralBrowserClient } from '@igeaman-org/referrals/browser';

const referralClient = new ReferralBrowserClient({
  endpoint: 'https://api.product.example/referrals',
  campaignKey: 'customer-referrals',
  requestTimeoutMs: 5_000,
});

await referralClient.capture();

capture() reads the ref query parameter, creates an opaque visitor UUID cookie, and sends the touch to POST {endpoint}/touches. It returns false when the current URL has no referral code.

6. Identify the visitor

After signup or login, associate the anonymous visitor with your authenticated user:

await referrals.identify({
  campaignKey: 'customer-referrals',
  visitorId: body.visitorId,
  subjectId: authenticatedUser.id,
});

The server must derive subjectId from its authenticated session. Do not trust a client-provided user ID.

The browser helper sends its visitor ID to POST {endpoint}/identify:

await referralClient.identify(authenticatedUser.id);

Your server should ignore or verify the supplied subject ID and use its authenticated identity as the authority.

7. Record a conversion

Conversions can represent any business event: signup, subscription, purchase, activation, booking, or a domain-specific event.

const result = await referrals.recordConversion({
  campaignKey: 'customer-referrals',
  subjectId: customer.id,
  event: 'purchase',
  idempotencyKey: `order:${order.id}`,
  value: order.total,
  currency: order.currency,
  metadata: { orderId: order.id },
});

console.log(result.conversion);
console.log(result.rewards);

Call this from a trusted server-side business workflow. Reusing the same idempotency key returns the existing conversion and prevents duplicate rewards.

8. Add fraud policies

Fraud evaluators execute before touches, identity binding, or conversions are persisted:

const referrals = new ReferralEngine({
  storage,
  hashSecret: process.env.REFERRAL_HASH_SECRET!,
  fraudEvaluators: [
    async (context) => {
      if (context.operation === 'conversion' && context.referrerId === context.subjectId) {
        return { allow: false, reason: 'Self-referral is not allowed.' };
      }

      return { allow: true };
    },
  ],
});

Applications should additionally check account age, payment instruments, refund history, IP velocity, geographic anomalies, and repeated identities. Rewards are created as pending; approve them after the applicable refund or chargeback period.

9. Query analytics

const summary = await referrals.getAnalytics(
  'customer-referrals',
  new Date('2026-07-01T00:00:00.000Z'),
  new Date('2026-08-01T00:00:00.000Z'),
);

The result includes touches, unique visitors, conversions, attributed conversions, conversion value, rewards, and reward amount. Date ranges are half-open: from is inclusive and to is exclusive.

Custom databases

Implement the exported ReferralStorage interface and pass the adapter to ReferralEngine:

import type { ReferralStorage } from '@igeaman-org/referrals';

class ApplicationReferralStorage implements ReferralStorage {
  // Implement every method using your database and transaction primitives.
}

Your adapter must enforce code uniqueness, usage limits, attribution updates, conversion idempotency, and conversion-plus-reward creation atomically. The package exports an in-memory implementation for application unit tests:

import { MemoryReferralStorage } from '@igeaman-org/referrals/testing';

The memory adapter is not intended for production persistence.

Package exports

  • @igeaman-org/referrals — server/edge engine, URL helper, errors, and types
  • @igeaman-org/referrals/postgres — PostgreSQL and Supabase adapter
  • @igeaman-org/referrals/browser — browser capture and identify client
  • @igeaman-org/referrals/testing — in-memory application-test adapter
  • @igeaman-org/referrals/migrations/postgres.sql — packaged SQL migration

Production checklist

  • Keep database credentials and hashing secrets on the server.
  • Authenticate code creation, identity binding, conversions, and reward administration.
  • Rate-limit public touch endpoints.
  • Use stable conversion idempotency keys.
  • Obtain client IPs only through correctly configured trusted proxies.
  • Add application-specific fraud policies.
  • Create monthly touch-table partitions before traffic reaches them.
  • Monitor query latency, lock waits, connection usage, fraud rejection rate, and pending reward age.
  • Test database backups and point-in-time recovery.
  • Never use the in-memory adapter in production.

License

MIT