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

@codepup/simbly-node

v0.1.2

Published

Node.js SDK for Simbly - CodePup's AI-First Analytics Agent

Readme

Simbly SDK for Node.js

The official Node.js SDK for Simbly - AI-First Engagement Platform.

Track user behavior, identify users, and build data-driven engagement workflows with simple, production-ready APIs.

npm version TypeScript


Features

  • Simple API - Track events with one line of code
  • Auto-batching - Efficient event buffering and batching (up to 500 events)
  • Type-safe - Full TypeScript support with IntelliSense
  • Automatic retries - Built-in exponential backoff for failed requests
  • Schema auto-detection - Automatically infers event schemas
  • Clerk integration - First-class support for Clerk authentication
  • Production-ready - Automatic flush on process exit, error handling

Installation

npm install @codepup/simbly-node

Or with other package managers:

# yarn
yarn add @codepup/simbly-node

# pnpm
pnpm add @codepup/simbly-node

# bun
bun add @codepup/simbly-node

Quick Start

1. Initialize the SDK

import { createClient } from '@codepup/simbly-node'

const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY // Get your API key from dashboard
})

2. Track Your First Event

await simbly.track('user.signed_up', {
  user: {
    userId: 'user_123',
    email: '[email protected]'
  },
  properties: {
    plan: 'pro',
    source: 'landing_page'
  }
})

3. Identify Users

await simbly.identify({
  user: {
    userId: 'user_123',
    email: '[email protected]'
  },
  traits: {
    name: 'John Doe',
    plan: 'pro',
    company: 'Acme Corp'
  }
})

That's it! You're now tracking events in production.


Core Concepts

Events

Events represent actions that users take in your application. Examples:

  • user.signed_up
  • subscription.created
  • feature.enabled
  • page.viewed

Users

Every event is associated with a user. You can identify users with:

  • userId - Your unique user ID (can be Clerk ID, internal ID, etc.)
  • email - User's email address (optional)
  • anonymousId - For anonymous users (before signup/login)

Properties

Properties are custom data attached to events. Examples:

  • Plan type: { plan: 'pro' }
  • Button clicked: { button: 'upgrade' }
  • Page URL: { url: '/pricing' }

API Reference

createClient(config)

Creates a new Simbly client instance.

const simbly = createClient({
  apiKey: 'simbly_workspace-id_secret',

  // Optional configuration
  debug: false,                         // Enable debug logging
  sync: false,                          // Wait for events to send
  maxRetries: 3,                        // Retry failed requests
  timeout: 30000,                       // Request timeout (ms)
  autoFlushOnExit: true,                // Auto-flush on exit
  flushInterval: 5000,                  // Buffer flush interval (ms)

  // Error handler
  onError: (error, events) => {
    console.error('Simbly error:', error)
  }
})

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | Required | Your Simbly API key | | debug | boolean | false | Enable debug logging | | sync | boolean | false | Send events synchronously (waits for response) | | maxRetries | number | 3 | Maximum retry attempts for failed requests | | timeout | number | 30000 | Request timeout in milliseconds | | autoFlushOnExit | boolean | true | Automatically flush events on process exit | | flushInterval | number | 5000 | Time-based flush interval in milliseconds | | onError | function | undefined | Callback for handling errors |


track(eventName, payload)

Track a user event.

await simbly.track('button.clicked', {
  user: {
    userId: 'user_123',
    email: '[email protected]'
  },
  properties: {
    button: 'upgrade',
    location: 'navbar',
    plan: 'free'
  },
  context: {
    ip: '192.168.1.1',
    userAgent: 'Mozilla/5.0...'
  }
})

Parameters

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | eventName | string | Yes | Name of the event (e.g., "user.signed_up") | | payload.user | User | No* | User identification | | payload.properties | object | No | Custom event properties | | payload.context | object | No | Additional context | | payload.idempotencyKey | string | No | Unique key for deduplication | | payload.event_id | string | No | Custom event ID | | payload.timestamp | string\|number | No | Custom timestamp |

*At least one user identifier is required (userId, email or anonymousId)

User Object

interface User {
  userId?: string         // Your unique user ID (Clerk ID, internal ID, etc.)
  email?: string          // User's email address
  anonymousId?: string    // Anonymous session ID (pre-auth)
  traits?: object         // Custom user traits
}

Event Naming Conventions

Use descriptive, dot-separated names:

Good:

  • user.signed_up
  • subscription.created
  • page.viewed

Avoid:

  • signup (too vague)
  • user_signed_up (use dots, not underscores)
  • USER.SIGNED.UP (use lowercase)

identify(payload)

Identify a user and update their profile.

await simbly.identify({
  user: {
    userId: 'user_123',
    email: '[email protected]'
  },
  traits: {
    name: 'John Doe',
    plan: 'pro',
    company: 'Acme Corp',
    createdAt: '2024-01-15T00:00:00Z'
  }
})

When to Use Identify

  • After user signs up
  • When user updates profile
  • When user upgrades/downgrades plan
  • On login (to refresh user data)

alias(payload)

Link an anonymous user to a known identity.

// Step 1: Track with anonymous ID before signup
await simbly.track('page.viewed', {
  user: { anonymousId: 'anon_abc123' },
  properties: { page: 'pricing' }
})

// Step 2: User signs up - link identities
await simbly.alias({
  previousId: 'anon_abc123',
  userId: 'user_xyz789'
})

// Step 3: Identify with user data
await simbly.identify({
  user: { userId: 'user_xyz789', email: '[email protected]' },
  traits: { name: 'John Doe', plan: 'pro' }
})

Recommended Flow

  1. Pre-login: Track with anonymousId
  2. On signup: Call alias() to link identities
  3. After signup: Call identify() with user traits
  4. Post-login: Track with userId

aliasAndIdentify(previousId, payload)

Convenience method combining alias() and identify().

await simbly.aliasAndIdentify('anon_abc123', {
  user: {
    userId: 'user_xyz789',
    email: '[email protected]'
  },
  traits: {
    name: 'John Doe',
    plan: 'pro'
  }
})

withContext(context)

Create a scoped client with default context applied to all events.

// Create scoped client
const scopedClient = simbly.withContext({
  tenantId: 'acme',
  environment: 'production'
})

// All events from this client include the context
await scopedClient.track('button.clicked', {
  user: { userId: 'user_123' },
  properties: { button: 'export' }
})
// Context automatically includes: { tenantId: 'acme', environment: 'production' }

flush()

Immediately flush all buffered events.

await simbly.flush()

When to Use Flush

  • Before Lambda/serverless function exits
  • Before long-running process checkpoint
  • When you need to ensure events are sent immediately

shutdown()

Gracefully shutdown the client (flush and cleanup).

await simbly.shutdown()

Usage Examples

Express.js API

import express from 'express'
import { createClient } from '@codepup/simbly-node'

const app = express()
const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY
})

app.post('/api/signup', async (req, res) => {
  const { email, name } = req.body

  // Create user in your database
  const user = await db.users.create({ email, name })

  // Track signup event
  await simbly.track('user.signed_up', {
    user: { userId: user.id, email: user.email },
    properties: { plan: 'free', source: 'web' }
  })

  res.json({ success: true })
})

// Graceful shutdown
process.on('SIGTERM', async () => {
  await simbly.shutdown()
  process.exit(0)
})

Next.js API Route

// app/api/track/route.ts
import { createClient } from '@codepup/simbly-node'
import { NextResponse } from 'next/server'

const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!
})

export async function POST(request: Request) {
  const { eventName, userId, properties } = await request.json()

  await simbly.track(eventName, {
    user: { userId },
    properties
  })

  return NextResponse.json({ success: true })
}

Clerk Integration

import { createClient } from '@codepup/simbly-node'
import { clerkClient } from '@clerk/nextjs/server'

const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!
})

// After user signs up via Clerk
export async function POST(request: Request) {
  const { userId } = await request.json()

  // Get Clerk user data
  const user = await clerkClient.users.getUser(userId)

  // Track signup with Clerk data
  await simbly.track('user.signed_up', {
    user: {
      userId: user.id,  // Clerk user ID
      email: user.emailAddresses[0].emailAddress
    },
    properties: {
      firstName: user.firstName,
      lastName: user.lastName
    }
  })

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

AWS Lambda

import { createClient } from '@codepup/simbly-node'

const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!,
  sync: true  // Wait for events to send
})

export const handler = async (event: any) => {
  await simbly.track('lambda.invoked', {
    user: { userId: event.userId },
    properties: { functionName: process.env.AWS_LAMBDA_FUNCTION_NAME }
  })

  // Events are sent synchronously, safe to exit
  return { statusCode: 200 }
}

Background Job (Bull/BullMQ)

import { Queue, Worker } from 'bullmq'
import { createClient } from '@codepup/simbly-node'

const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!
})

const worker = new Worker('email-queue', async (job) => {
  await sendEmail(job.data.email)

  // Track email sent
  await simbly.track('email.sent', {
    user: { email: job.data.email },
    properties: {
      templateId: job.data.templateId,
      jobId: job.id
    }
  })
})

// Flush on shutdown
process.on('SIGTERM', async () => {
  await simbly.shutdown()
  await worker.close()
})

Best Practices

1. Initialize Once

Create a single client instance and reuse it throughout your application.

// ✅ Good - singleton pattern
// lib/simbly.ts
export const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!
})

// ❌ Bad - creates new client on every request
app.post('/track', (req, res) => {
  const simbly = createClient({ apiKey: '...' })  // Don't do this!
})

2. Use Async Mode (Default)

For better performance, use async mode (default) which batches events.

// ✅ Good - async mode (default)
const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!
})

// ✅ Only use sync mode when necessary (Lambda, scripts)
const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!,
  sync: true  // Events sent immediately
})

3. Handle Errors Gracefully

Use the onError callback to handle failures without breaking your app.

const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!,
  onError: (error, events) => {
    // Log to your error tracking service
    console.error('Simbly tracking failed:', error)
    // Don't throw - let app continue
  }
})

4. Use Descriptive Event Names

Follow a consistent naming pattern.

// ✅ Good - clear hierarchy
await simbly.track('user.signed_up', { ... })
await simbly.track('subscription.created', { ... })
await simbly.track('subscription.cancelled', { ... })

// ❌ Bad - inconsistent
await simbly.track('signup', { ... })
await simbly.track('new_subscription', { ... })
await simbly.track('cancel', { ... })

5. Don't Track PII in Properties

The SDK automatically redacts sensitive fields, but avoid tracking PII when possible.

// ✅ Good - no PII in properties
await simbly.track('payment.completed', {
  user: { userId: 'user_123' },
  properties: {
    amount: 49.99,
    currency: 'USD',
    plan: 'pro'
  }
})

// ❌ Bad - PII in properties
await simbly.track('payment.completed', {
  user: { userId: 'user_123' },
  properties: {
    creditCard: '4242-4242-4242-4242',  // Will be redacted
    password: 'secret123',              // Will be redacted
    ssn: '123-45-6789'                 // Will be redacted
  }
})

Automatically redacted fields:

  • password, passwd, pwd
  • secret, api_key, apikey, token
  • authorization, auth
  • ssn, social_security
  • credit_card, creditcard, cvv, ccv

6. Flush Before Exit

Always flush events before your process exits.

// Graceful shutdown
process.on('SIGTERM', async () => {
  await simbly.flush()
  process.exit(0)
})

process.on('SIGINT', async () => {
  await simbly.flush()
  process.exit(0)
})

Or use shutdown() for complete cleanup:

process.on('SIGTERM', async () => {
  await simbly.shutdown()  // Flushes and cleans up
  process.exit(0)
})

Advanced Features

Custom Event IDs

Provide your own event IDs for deduplication.

await simbly.track('order.created', {
  user: { userId: 'user_123' },
  event_id: `order_${orderId}`,  // Custom event ID
  properties: { orderId, amount: 99.99 }
})

Custom Timestamps

Track events with past or future timestamps.

await simbly.track('meeting.scheduled', {
  user: { userId: 'user_123' },
  timestamp: '2024-03-15T10:00:00Z',  // ISO8601 format
  properties: { meetingId: 'meet_123' }
})

// Or use epoch milliseconds
await simbly.track('meeting.scheduled', {
  user: { userId: 'user_123' },
  timestamp: 1710496800000,  // Epoch ms
  properties: { meetingId: 'meet_123' }
})

Timestamp bounds:

  • Cannot be more than 24 hours in the future
  • Cannot be more than 90 days in the past

Schema Registration

Explicitly register event schemas for better validation and documentation.

await simbly.registerEvents([
  {
    name: 'user.signed_up',
    description: 'User completed signup',
    properties: {
      email: { type: 'string', required: true },
      plan: { type: 'string', enum: ['free', 'pro', 'enterprise'] },
      source: { type: 'string' }
    },
    tags: ['user', 'conversion']
  }
])

Batch Processing

The SDK automatically batches events for efficiency:

  • Default buffer size: 50 events
  • Default flush interval: 5 seconds
  • Maximum batch size: 500 events
  • Batches >500 are automatically split
// All events are buffered and sent in batches
for (let i = 0; i < 1000; i++) {
  await simbly.track('event.tracked', {
    user: { userId: `user_${i}` },
    properties: { index: i }
  })
}
// Events are automatically sent in 2 batches of 500

await simbly.flush()  // Send remaining buffered events

Error Handling

Handling Errors

const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!,
  onError: (error, events) => {
    if (error.message.includes('401')) {
      console.error('Invalid API key')
    } else if (error.message.includes('429')) {
      console.error('Rate limited')
    } else {
      console.error('Tracking error:', error)
    }
  }
})

Common Errors

| Error | Cause | Solution | |-------|-------|----------| | Invalid API key | Wrong or expired API key | Check your API key in dashboard | | Event name must be a non-empty string | Missing event name | Provide valid event name | | Missing identity | No user identifier | Provide userId, email or anonymousId | | Maximum 500 events per batch | Batch too large | SDK auto-splits, but check if manually sending |


TypeScript Support

The SDK is written in TypeScript and provides full type safety.

import { createClient, TrackPayload, User } from '@codepup/simbly-node'

const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!
})

// Full IntelliSense support
const user: User = {
  userId: 'user_123',
  email: '[email protected]'
}

const payload: TrackPayload = {
  user,
  properties: {
    plan: 'pro',
    source: 'web'
  }
}

await simbly.track('user.signed_up', payload)

Runtime Support

The SDK works in multiple JavaScript runtimes:

  • ✅ Node.js (v14+)
  • ✅ Bun
  • ✅ Deno
  • ✅ AWS Lambda
  • ✅ Cloudflare Workers (with modifications)
  • ✅ Vercel Functions

Performance

Benchmarks

  • Sync mode: ~5ms per event (network bound)
  • Async mode: ~0.1ms per event (buffered)
  • Memory: ~1KB per buffered event
  • Network: Automatic batching reduces requests by 50x

Tips for High-Volume Applications

  1. Use async mode (default)
  2. Increase buffer size for high throughput
  3. Disable time-based flushing if needed
const simbly = createClient({
  apiKey: process.env.SIMBLY_API_KEY!,
  sync: false,         // Async mode
  flushInterval: 0     // Disable time-based flush (flush on buffer full only)
})

FAQ

When should I call flush()?

  • Serverless functions: Always flush before function exits
  • Background jobs: Flush after job completes
  • Long-running servers: Not needed (auto-flush every 5s)
  • Scripts: Flush at the end

Should I use sync or async mode?

  • Async (default): For web servers, APIs, long-running processes
  • Sync: For Lambda, scripts, short-lived processes

How does batching work?

Events are automatically batched based on:

  1. Buffer size (50 events by default)
  2. Time interval (5 seconds by default)
  3. Process exit (auto-flush)

Can I track events from the browser?

No, this SDK is for server-side tracking only. For browser tracking, use our JavaScript SDK (coming soon).

How do I get an API key?

Contact us at [email protected] to get started with Simbly analytics for your application.


Troubleshooting

Events not appearing in dashboard

  1. ✅ Check API key is valid
  2. ✅ Ensure you called flush() before exit
  3. ✅ Check onError callback for errors
  4. ✅ Enable debug mode: { debug: true }

High memory usage

  1. ✅ Reduce buffer size: new EventBuffer(10)
  2. ✅ Reduce flush interval: { flushInterval: 1000 }
  3. ✅ Enable sync mode: { sync: true }

Events being dropped

  1. ✅ Check for validation errors in onError
  2. ✅ Ensure event names are valid
  3. ✅ Ensure at least one user identifier is provided
  4. ✅ Check properties are objects (not arrays)

Support

For any questions, API access, or support:

📧 Email: [email protected]

We're here to help you get started with Simbly analytics!


License

This SDK is provided for use with CodePup's Simbly analytics service. API access requires an active subscription.

Contact [email protected] for licensing and pricing information.


Changelog

v0.1.0 (Current)

  • ✅ Initial release
  • ✅ Track, identify, and alias APIs
  • ✅ Auto-batching (up to 500 events)
  • ✅ Automatic retries with exponential backoff
  • ✅ TypeScript support
  • ✅ Clerk integration
  • ✅ Schema auto-detection

Ready to get started? Contact us at [email protected] to get your API key and start tracking events.