Skip to main content

@adrper79-dot/crm

CRM primitives: lead upsert, subscription conversion tracking, and a customer health view with churn risk scoring. Depends on @adrper79-dot/neon and @adrper79-dot/analytics.

Installation

npm install @adrper79-dot/crm

Database

import { CREATE_CRM_LEADS_TABLE } from '@adrper79-dot/crm';
await db.execute(CREATE_CRM_LEADS_TABLE);
Schema:
CREATE TABLE IF NOT EXISTS crm_leads (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id     TEXT NOT NULL,
  app_id      TEXT NOT NULL,
  email       TEXT,
  status      TEXT DEFAULT 'lead',
  mrr         NUMERIC DEFAULT 0,
  source      TEXT,
  metadata    JSONB DEFAULT '{}',
  created_at  TIMESTAMPTZ DEFAULT now(),
  updated_at  TIMESTAMPTZ DEFAULT now(),
  UNIQUE (user_id, app_id)
);

API

trackLead(db, opts)

Upserts a CRM lead. Idempotent on (user_id, app_id) conflict.
function trackLead(db: Db, opts: {
  userId: string;
  appId: string;
  email?: string;
  status?: 'lead' | 'trial' | 'active' | 'churned';
  source?: string;
  metadata?: Record<string, unknown>;
}): Promise<CrmLead>;

trackConversion(db, opts, analytics?)

Updates a lead’s status and MRR. Optionally fires a subscription.converted event to @adrper79-dot/analytics.
function trackConversion(
  db: Db,
  opts: {
    userId: string;
    appId: string;
    status: 'trial' | 'active' | 'churned';
    mrr: number;
  },
  analytics?: AnalyticsClient,
): Promise<CrmLead>;

getCustomerView(db, userId)

Returns a unified customer view: lead + subscriptions + recent events + churn risk score.
function getCustomerView(db: Db, userId: string): Promise<CustomerView>;

interface CustomerView {
  lead: CrmLead;
  subscriptions: Subscription[];
  recentEvents: FactoryEvent[];
  churnRisk: 'low' | 'medium' | 'high';
}
Churn risk is computed from days since last event and subscription status.

Example

import { trackLead, trackConversion, getCustomerView } from '@adrper79-dot/crm';

// On sign-up
await trackLead(db, {
  userId: user.id,
  appId: 'focusbro',
  email: user.email,
  status: 'lead',
  source: 'google-ads',
});

// On subscription
await trackConversion(db, {
  userId: user.id,
  appId: 'focusbro',
  status: 'active',
  mrr: 29,
});

// Admin dashboard
const view = await getCustomerView(db, user.id);
console.log(view.churnRisk); // 'low' | 'medium' | 'high'