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

lightleaderboard

v1.0.0

Published

Official JavaScript/TypeScript SDK for LightLeaderboard — leaderboard-as-a-service for game developers

Readme

lightleaderboard

Official JavaScript / TypeScript SDK for LightLeaderboard — leaderboard-as-a-service for game developers.

Works in Node.js 18+, browsers, and any runtime with the Web Crypto API and native fetch (Deno, Bun, Cloudflare Workers, etc.).


Installation

npm install lightleaderboard
# or
yarn add lightleaderboard
# or
pnpm add lightleaderboard

Quick start

import { LightLeaderboard } from 'lightleaderboard';

const lb = new LightLeaderboard({
  apiKey: 'YOUR_API_KEY',   // from the LightLeaderboard dashboard
  gameId: 'YOUR_GAME_ID',   // your game's reference ID
});

// Submit a score and get back the player's rank immediately
const result = await lb.submitScore({
  score: 9500,
  playerRefId: 'player-123',
  playerName: 'Alice',
});
console.log(`Rank #${result.rank} of ${result.totalPlayers}!`);
// → "Rank #4 of 1024!"

// Fetch the top 10
const { entries } = await lb.getLeaderboard({ limit: 10 });
entries.forEach(e => {
  console.log(`#${e.rank}  ${e.playerName}  ${e.score}`);
});

API reference

new LightLeaderboard(config)

| Option | Type | Required | Description | |--------|------|----------|-------------| | apiKey | string | ✓ | Your game's API key from the dashboard | | gameId | string | ✓ | Your game's reference ID | | baseUrl | string | | Override the API base URL | | scoreSecret | string | | Auto-sign scores with HMAC-SHA256 (see Score signing) |


submitScore(options)

Submit a score. Returns rank, personal-best flag, and total player count in a single call.

const result = await lb.submitScore({
  score: 9500,
  playerRefId: 'player-123',  // your internal player ID
  playerName: 'Alice',         // display name
  playTimeMs: 120_000,         // run duration
  seasonId: 'season-3',        // optional season bucket
  teamId: 'team-red',          // optional team bucket
  submissionId: crypto.randomUUID(), // idempotency key
  metadata: { level: 5, combo: 12 }, // arbitrary JSON
});

console.log(result.rank);          // 4       (player's current rank)
console.log(result.isPersonalBest); // true
console.log(result.totalPlayers);  // 1024
console.log(result.deduped);       // true if submissionId was already seen

Response fields

| Field | Type | Description | |-------|------|-------------| | id | number | Database ID of the created entry | | rank | number \| null | Player's rank right after submission (null if no playerRefId) | | isPersonalBest | boolean | Whether this beats the player's previous best | | totalPlayers | number \| null | Total unique players on this leaderboard | | deduped | boolean? | true when submissionId was already seen |


getLeaderboard(options?)

Fetch the leaderboard. Returns one entry per player (their best score) by default.

const { entries } = await lb.getLeaderboard({
  limit: 20,        // 1–100, default 20
  offset: 0,        // pagination
  period: 'weekly', // 'all' | 'weekly' | 'monthly'
  season: 'season-3',
  team: 'team-red',
  allEntries: false, // true → return every raw submission
});

// Each entry has a `rank` field (1-based, offset-aware)
entries.forEach(e => console.log(e.rank, e.playerName, e.score));

Pagination example

// Page 2 of a weekly board
const page2 = await lb.getLeaderboard({ limit: 20, offset: 20, period: 'weekly' });

getPlayerRank(playerRefId, options?)

Get a player's rank, score, and percentile in a single call.

const rank = await lb.getPlayerRank('player-123', { period: 'weekly' });

console.log(rank.rank);       // 4
console.log(rank.score);      // 9500
console.log(rank.totalPlayers); // 1024
console.log(rank.percentile); // 99.7  → top 0.3%

percentile is 0–100, higher is better. rank 1 of 100percentile 100.


getCentricLeaderboard(playerRefId, options?)

Fetch the leaderboard centered on a specific player — the entries immediately above and below them. Great for in-game "you vs. your neighbors" screens.

const { entries, playerRank } = await lb.getCentricLeaderboard('player-123', {
  limit: 11, // 5 above + player + 5 below
});

getPlayer(playerRefId)

Fetch a player's profile.

const profile = await lb.getPlayer('player-123');
console.log(profile.playerName, profile.avatarUrl, profile.country);

updatePlayer(playerRefId, options)

Create or update a player's profile. Fields are merged — omitted fields keep their current value.

await lb.updatePlayer('player-123', {
  playerName: 'Alice',
  avatarUrl: 'https://example.com/avatar.png',
  country: 'US',
  level: 42,
  device: 'mobile',
});

getPlayerScores(playerRefId, options?)

Fetch all submissions a player has made, ordered newest first. Useful for progression graphs and run-history screens.

const { entries, bestScore, total } = await lb.getPlayerScores('player-123', {
  limit: 50,   // 1–200, default 50
  offset: 0,
});

console.log(`${total} total runs, best: ${bestScore}`);
entries.forEach(e => console.log(e.score, e.createdAt));

Score signing

If you enable "Require signed scores" on your game in the dashboard, every submission must include a valid HMAC-SHA256 signature. Pass scoreSecret to the constructor and the SDK handles signing automatically:

const lb = new LightLeaderboard({
  apiKey: 'YOUR_API_KEY',
  gameId: 'YOUR_GAME_ID',
  scoreSecret: 'YOUR_SCORE_SECRET', // from the dashboard → Signature Secret
});

// Scores are now automatically signed — no extra code needed
await lb.submitScore({ score: 9500, playerRefId: 'p1' });

Signing uses the Web Crypto API (crypto.subtle), available natively in Node 18+, modern browsers, Bun, Deno, and Cloudflare Workers.


Error handling

All methods throw LightLeaderboardError on failure.

import { LightLeaderboard, LightLeaderboardError } from 'lightleaderboard';

try {
  await lb.submitScore({ score: 9500 });
} catch (err) {
  if (err instanceof LightLeaderboardError) {
    console.error(err.message);   // human-readable message from the API
    console.error(err.status);    // HTTP status code
    console.error(err.response);  // raw response body

    if (err.isAuthError)        console.error('Check your API key');
    if (err.isRateLimitError)   console.error('Slow down score submissions');
    if (err.isBillingError)     console.error('Free tier limit reached');
    if (err.isValidationError)  console.error('Invalid score data');
  }
}

TypeScript

The SDK is written in TypeScript and ships full type declarations. All options and return types are exported:

import type {
  SubmitScoreOptions,
  SubmitScoreResult,
  GetLeaderboardOptions,
  GetLeaderboardResult,
  PlayerRankResult,
  LeaderboardEntry,
} from 'lightleaderboard';

License

MIT