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

@rooguys/sdk

v1.0.0

Published

Official Node.js SDK for Rooguys API

Readme

Rooguys Node.js SDK

The official Node.js SDK for the Rooguys Gamification API with full TypeScript support.

Installation

npm install @rooguys/sdk

Initialization

import { Rooguys, RooguysOptions } from '@rooguys/sdk';

const options: RooguysOptions = {
  baseUrl: 'https://api.rooguys.com/v1', // Optional
  timeout: 10000, // Optional, defaults to 10s
  // Rate limit handling
  onRateLimitWarning: (info) => {
    console.warn(`Rate limit: ${info.remaining}/${info.limit} remaining`);
  },
  autoRetry: true,
  maxRetries: 3,
};

const client = new Rooguys('YOUR_API_KEY', options);

Migration Guide (v1.x to v2.x)

Breaking Changes

  1. Event Tracking Endpoint: The SDK now uses /v1/events instead of /v1/event

    // Old (deprecated, still works with warning)
    await client.events.trackLegacy('event_name', 'user_id', properties);
       
    // New (recommended)
    await client.events.track('event-name', 'user_id', properties);
  2. Global Leaderboard Endpoint: Now uses /v1/leaderboards/global with timeframe query parameter

    // Both signatures work
    await client.leaderboards.getGlobal('weekly', 1, 10);
    await client.leaderboards.getGlobal({ timeframe: 'weekly', page: 1, limit: 10 });
  3. Response Format: All responses now follow standardized format { success: true, data: {...} }

New Features

  • Batch event tracking (events.trackBatch)
  • User management (users.create, users.update, users.createBatch)
  • User search (users.search)
  • Field selection for user profiles
  • Leaderboard filters (persona, level range, date range)
  • "Around me" leaderboard view (leaderboards.getAroundUser)
  • Health check endpoints
  • Rate limit handling with auto-retry
  • Full TypeScript type definitions

Usage Examples

Events

Track a Single Event

import { TrackEventResponse, TrackOptions } from '@rooguys/sdk';

const options: TrackOptions = {
  includeProfile: true,
  idempotencyKey: 'unique-request-id'
};

const response: TrackEventResponse = await client.events.track(
  'level-completed',
  'user_123',
  { difficulty: 'hard', score: 1500 },
  options
);

console.log(`Event status: ${response.status}`);
if (response.profile) {
  console.log(`Updated points: ${response.profile.points}`);
}

Track Events with Custom Timestamp

// Track historical events (up to 7 days in the past)
const response = await client.events.track(
  'purchase',
  'user_123',
  { amount: 99.99 },
  { timestamp: new Date('2024-01-15T10:30:00Z') }
);

Batch Event Tracking

import { BatchEvent, BatchTrackResponse, BatchOptions } from '@rooguys/sdk';

const events: BatchEvent[] = [
  { eventName: 'page-view', userId: 'user_123', properties: { page: '/home' } },
  { eventName: 'button-click', userId: 'user_123', properties: { button: 'signup' } },
  { eventName: 'purchase', userId: 'user_456', properties: { amount: 50 }, timestamp: new Date() }
];

const options: BatchOptions = { idempotencyKey: 'batch-123' };
const response: BatchTrackResponse = await client.events.trackBatch(events, options);

// Check individual results
response.results.forEach((result, index) => {
  if (result.status === 'queued') {
    console.log(`Event ${index} queued successfully`);
  } else {
    console.error(`Event ${index} failed:`, result.error);
  }
});

Users

Create a New User

import { CreateUserData, UserProfile } from '@rooguys/sdk';

const userData: CreateUserData = {
  userId: 'user_123',
  displayName: 'John Doe',
  email: '[email protected]',
  firstName: 'John',
  lastName: 'Doe',
  metadata: { plan: 'premium' }
};

const user: UserProfile = await client.users.create(userData);

Update User Profile

import { UpdateUserData, UserProfile } from '@rooguys/sdk';

// Partial update - only sends provided fields
const updateData: UpdateUserData = {
  displayName: 'Johnny Doe',
  metadata: { plan: 'enterprise' }
};

const updated: UserProfile = await client.users.update('user_123', updateData);

Batch User Creation

import { CreateUserData, BatchCreateResponse } from '@rooguys/sdk';

const users: CreateUserData[] = [
  { userId: 'user_1', displayName: 'User One', email: '[email protected]' },
  { userId: 'user_2', displayName: 'User Two', email: '[email protected]' },
  // ... up to 100 users
];

const response: BatchCreateResponse = await client.users.createBatch(users);

Get User Profile with Field Selection

import { GetUserOptions, UserProfile } from '@rooguys/sdk';

const options: GetUserOptions = {
  fields: ['points', 'level', 'badges']
};

const user: UserProfile = await client.users.get('user_123', options);

Search Users

import { SearchOptions, PaginatedResponse, UserProfile } from '@rooguys/sdk';

const options: SearchOptions = {
  page: 1,
  limit: 20,
  fields: ['userId', 'displayName', 'points']
};

const results: PaginatedResponse<UserProfile> = await client.users.search('john', options);

results.users?.forEach(user => {
  console.log(`${user.display_name}: ${user.points} points`);
});

Access Enhanced Profile Data

const user: UserProfile = await client.users.get('user_123');

// Activity summary
if (user.activitySummary) {
  console.log(`Last active: ${user.activitySummary.lastEventAt}`);
  console.log(`Total events: ${user.activitySummary.eventCount}`);
  console.log(`Days active: ${user.activitySummary.daysActive}`);
}

// Streak information
if (user.streak) {
  console.log(`Current streak: ${user.streak.currentStreak} days`);
  console.log(`Longest streak: ${user.streak.longestStreak} days`);
}

// Inventory summary
if (user.inventory) {
  console.log(`Items owned: ${user.inventory.itemCount}`);
  console.log(`Active effects: ${user.inventory.activeEffects.join(', ')}`);
}

Leaderboards

Global Leaderboard with Filters

import { LeaderboardFilterOptions, LeaderboardResult } from '@rooguys/sdk';

const options: LeaderboardFilterOptions = {
  timeframe: 'weekly',
  page: 1,
  limit: 10,
  persona: 'competitor',
  minLevel: 5,
  maxLevel: 20,
  startDate: new Date('2024-01-01'),
  endDate: new Date('2024-01-31')
};

const leaderboard: LeaderboardResult = await client.leaderboards.getGlobal(options);

// Access cache metadata
if (leaderboard.cacheMetadata) {
  console.log(`Cached at: ${leaderboard.cacheMetadata.cachedAt}`);
  console.log(`TTL: ${leaderboard.cacheMetadata.ttl}s`);
}

// Rankings include percentile
leaderboard.rankings.forEach(entry => {
  console.log(`#${entry.rank} ${entry.user_id}: ${entry.points} pts (top ${entry.percentile}%)`);
});

Custom Leaderboard with Filters

const customLb: LeaderboardResult = await client.leaderboards.getCustom('leaderboard_id', {
  page: 1,
  limit: 10,
  persona: 'achiever',
  minLevel: 10
});

"Around Me" View

import { AroundUserResponse } from '@rooguys/sdk';

const aroundMe: AroundUserResponse = await client.leaderboards.getAroundUser(
  'leaderboard_id',
  'user_123',
  5  // 5 entries above and below
);

aroundMe.rankings.forEach(entry => {
  const marker = entry.user_id === 'user_123' ? '→' : ' ';
  console.log(`${marker} #${entry.rank} ${entry.user_id}: ${entry.points}`);
});

Get User Rank with Percentile

import { UserRank } from '@rooguys/sdk';

const rank: UserRank = await client.leaderboards.getUserRank('leaderboard_id', 'user_123');

console.log(`Rank: #${rank.rank}`);
console.log(`Score: ${rank.points}`);
console.log(`Percentile: top ${rank.percentile}%`);

Health Checks

import { HealthCheckResponse } from '@rooguys/sdk';

// Full health check
const health: HealthCheckResponse = await client.health.check();
console.log(`Status: ${health.status}`);
console.log(`Version: ${health.version}`);

// Quick availability check
const isReady: boolean = await client.health.isReady();
if (isReady) {
  console.log('API is ready');
}

Aha Score

import { AhaDeclarationResult, AhaScoreResult } from '@rooguys/sdk';

// Declare user activation milestone (1-5)
const result: AhaDeclarationResult = await client.aha.declare('user_123', 4);
console.log(result.message);

// Get user's aha score
const score: AhaScoreResult = await client.aha.getUserScore('user_123');
console.log(`Current Score: ${score.data.current_score}`);
console.log(`Status: ${score.data.status}`);

TypeScript Types

The SDK exports comprehensive TypeScript types for all operations:

Core Types

import {
  // SDK Options
  RooguysOptions,
  
  // User Types
  UserProfile,
  CreateUserData,
  UpdateUserData,
  GetUserOptions,
  SearchOptions,
  ActivitySummary,
  StreakInfo,
  InventorySummary,
  
  // Event Types
  TrackEventResponse,
  TrackOptions,
  BatchEvent,
  BatchTrackResponse,
  BatchOptions,
  
  // Leaderboard Types
  LeaderboardResult,
  LeaderboardFilterOptions,
  LeaderboardEntry,
  AroundUserResponse,
  UserRank,
  Timeframe,
  
  // Batch Types
  BatchCreateResponse,
  PaginatedResponse,
  
  // Other Types
  Badge,
  Level,
  Questionnaire,
  HealthCheckResponse,
  AhaDeclarationResult,
  AhaScoreResult,
} from '@rooguys/sdk';

HTTP Client Types

import {
  RateLimitInfo,
  CacheMetadata,
  Pagination,
  ApiResponse,
  RequestConfig,
  HttpClientOptions,
} from '@rooguys/sdk';

API Reference

Events

| Method | Return Type | Description | |--------|-------------|-------------| | track(eventName, userId, properties?, options?) | Promise<TrackEventResponse> | Track a single event | | trackBatch(events, options?) | Promise<BatchTrackResponse> | Track multiple events (max 100) | | trackLegacy(eventName, userId, properties?, options?) | Promise<TrackEventResponse> | Deprecated |

Users

| Method | Return Type | Description | |--------|-------------|-------------| | create(userData) | Promise<UserProfile> | Create a new user | | update(userId, userData) | Promise<UserProfile> | Update user profile | | createBatch(users) | Promise<BatchCreateResponse> | Create multiple users (max 100) | | get(userId, options?) | Promise<UserProfile> | Get user profile | | search(query, options?) | Promise<PaginatedResponse<UserProfile>> | Search users | | getBulk(userIds) | Promise<{ users: UserProfile[] }> | Get multiple profiles | | getBadges(userId) | Promise<{ badges: UserBadge[] }> | Get user's badges | | getRank(userId, timeframe?) | Promise<UserRank> | Get user's global rank | | submitAnswers(userId, questionnaireId, answers) | Promise<{ status, message }> | Submit answers |

Leaderboards

| Method | Return Type | Description | |--------|-------------|-------------| | getGlobal(timeframeOrOptions?, page?, limit?, options?) | Promise<LeaderboardResult> | Get global leaderboard | | list(pageOrOptions?, limit?, search?) | Promise<LeaderboardListResult> | List all leaderboards | | getCustom(leaderboardId, pageOrOptions?, ...) | Promise<LeaderboardResult> | Get custom leaderboard | | getUserRank(leaderboardId, userId) | Promise<UserRank> | Get user's rank | | getAroundUser(leaderboardId, userId, range?) | Promise<AroundUserResponse> | Get entries around user |

Badges

| Method | Return Type | Description | |--------|-------------|-------------| | list(page?, limit?, activeOnly?) | Promise<BadgeListResult> | List all badges |

Levels

| Method | Return Type | Description | |--------|-------------|-------------| | list(page?, limit?) | Promise<LevelListResult> | List all levels |

Questionnaires

| Method | Return Type | Description | |--------|-------------|-------------| | get(slug) | Promise<Questionnaire> | Get questionnaire by slug | | getActive() | Promise<Questionnaire> | Get active questionnaire |

Aha Score

| Method | Return Type | Description | |--------|-------------|-------------| | declare(userId, value) | Promise<AhaDeclarationResult> | Declare aha score (1-5) | | getUserScore(userId) | Promise<AhaScoreResult> | Get user's aha score |

Health

| Method | Return Type | Description | |--------|-------------|-------------| | check() | Promise<HealthCheckResponse> | Get full health status | | isReady() | Promise<boolean> | Quick availability check |

Error Handling

The SDK provides typed error classes for different error scenarios:

import {
  Rooguys,
  ValidationError,
  AuthenticationError,
  NotFoundError,
  ConflictError,
  RateLimitError,
  ServerError,
  FieldError
} from '@rooguys/sdk';

try {
  await client.users.create({ userId: 'user_123', email: 'invalid-email' });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation failed:', error.message);
    console.error('Field errors:', error.fieldErrors);
    console.error('Error code:', error.code);
  } else if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof NotFoundError) {
    console.error('Resource not found');
  } else if (error instanceof ConflictError) {
    console.error('Resource already exists');
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
  } else if (error instanceof ServerError) {
    console.error('Server error:', error.message);
  }
  
  // All errors include requestId for debugging
  if (error instanceof RooguysError) {
    console.error('Request ID:', error.requestId);
  }
}

Error Types

| Error Class | HTTP Status | Properties | |-------------|-------------|------------| | ValidationError | 400 | fieldErrors?: FieldError[] | | AuthenticationError | 401 | - | | ForbiddenError | 403 | - | | NotFoundError | 404 | - | | ConflictError | 409 | - | | RateLimitError | 429 | retryAfter: number | | ServerError | 500+ | - |

Common Error Properties

All errors extend RooguysError and include:

  • message: string - Human-readable error message
  • code: string - Machine-readable error code
  • requestId?: string - Unique request identifier
  • statusCode: number - HTTP status code

FieldError Type

interface FieldError {
  field: string;
  message: string;
}

Rate Limiting

The SDK provides built-in rate limit handling:

const client = new Rooguys('YOUR_API_KEY', {
  // Get notified when 80% of rate limit is consumed
  onRateLimitWarning: (info: RateLimitInfo) => {
    console.warn(`Rate limit: ${info.remaining}/${info.limit} remaining`);
    console.warn(`Resets at: ${new Date(info.reset * 1000)}`);
  },
  
  // Automatically retry rate-limited requests
  autoRetry: true,
  maxRetries: 3
});

RateLimitInfo Type

interface RateLimitInfo {
  limit: number;      // Total requests allowed
  remaining: number;  // Requests remaining
  reset: number;      // Unix timestamp when limit resets
}

Testing

npm test              # Run all tests
npm run test:coverage # Run with coverage report

The SDK maintains >90% test coverage with:

  • Unit tests for all API methods
  • Property-based tests using fast-check
  • TypeScript type validation
  • Error handling tests

Requirements

  • Node.js >= 18.x
  • TypeScript >= 4.7 (for TypeScript users)

License

MIT