Packages@robojs/xp
leaderboard
Variable: leaderboard
const leaderboard: {
get: (guildId, offset, limit, options?) => Promise<{
entries: LeaderboardEntry[];
total: number;
}>;
getRank: (guildId, userId, options?) => Promise<
| {
rank: number;
total: number;
}
| null>;
invalidateCache: (guildId, options?) => void;
};Leaderboard API for ranking and leaderboard operations
High-performance leaderboard system with intelligent caching for fast queries on large servers. Designed to handle 10k+ users with under 200ms response times.
Features:
- In-memory caching of top 100 users per guild
- Automatic cache invalidation on XP changes
- Paginated leaderboard retrieval
- Efficient rank position lookup
- Stable sort (XP desc, userId asc)
- Cache warming on first query
Caching Behavior:
- Cache TTL: 60 seconds (auto-refresh on expiry)
- Cache size: Top 100 users per guild (ranked 1-100)
- Invalidation: Automatic on xpChange, levelUp, levelDown events
- Cold start: First query builds cache (O(n log n))
- Warm cache: Subsequent queries are O(1)
Type declaration
| Name | Type | Default value | Description |
|---|---|---|---|
get | (guildId, offset, limit, options?) => Promise<{ entries: LeaderboardEntry[]; total: number; }> | getLeaderboardCore | Get paginated leaderboard entries (offset, limit) |
getRank | (guildId, userId, options?) => Promise< | { rank: number; total: number; } | null> | getUserRankCore | Get user's rank position (1-indexed) |
invalidateCache | (guildId, options?) => void | invalidateCacheCore | Manually invalidate cache for a guild (usually automatic) Supports two modes: - Specific store: Pass { storeId: 'name' } to invalidate only that store - All stores: Pass { all: true } to invalidate all stores for the guild Param Guild ID Param Optional Flashcore options or { all: true } to invalidate all stores |
Examples
Get Top Users (Paginated)
import { leaderboard } from '@robojs/xp'
// Get top 10 users from default store
const top10 = await leaderboard.get('guildId', 0, 10)
top10.entries.forEach(entry => {
console.log(`#${entry.rank}: ${entry.userId} - Level ${entry.level} (${entry.xp} XP)`)
})
// Get top 10 users from custom reputation store
const repTop10 = await leaderboard.get('guildId', 0, 10, { storeId: 'reputation' })
repTop10.entries.forEach(entry => {
console.log(`#${entry.rank}: ${entry.userId} - Rep Level ${entry.level}`)
})Build Leaderboard Command
import { leaderboard } from '@robojs/xp'
import { CommandInteraction } from 'discord.js'
async function handleLeaderboardCommand(interaction: CommandInteraction) {
const page = interaction.options.getInteger('page') ?? 1
const pageSize = 10
const offset = (page - 1) * pageSize
// Get leaderboard entries for this page
const entries = await leaderboard.get(interaction.guildId, offset, pageSize)
if (entries.length === 0) {
await interaction.reply('No users on this page!')
return
}
// Build leaderboard embed
const description = entries
.map(entry => `#${entry.rank}: <@${entry.userId}> - Level ${entry.level} (${entry.xp} XP)`)
.join('\n')
await interaction.reply({
embeds: [{
title: `Leaderboard - Page ${page}`,
description
}]
})
}Get User's Rank Position (multi-store)
import { leaderboard } from '@robojs/xp'
// Get user's rank in default store
const defaultRank = await leaderboard.getRank('guildId', 'userId')
if (defaultRank) {
console.log(`Default rank: #${defaultRank.rank} out of ${defaultRank.total}`)
}
// Get user's rank in custom reputation store
const repRank = await leaderboard.getRank('guildId', 'userId', { storeId: 'reputation' })
if (repRank) {
console.log(`Reputation rank: #${repRank.rank} out of ${repRank.total}`)
}Build Rank Command
import { leaderboard } from '@robojs/xp'
import { CommandInteraction } from 'discord.js'
async function handleRankCommand(interaction: CommandInteraction) {
const userId = interaction.options.getUser('user')?.id ?? interaction.user.id
const rankInfo = await leaderboard.getRank(interaction.guildId, userId)
if (!rankInfo) {
await interaction.reply('This user has no XP yet!')
return
}
await interaction.reply({
embeds: [{
title: `Rank for <@${userId}>`,
fields: [
{ name: 'Rank', value: `#${rankInfo.rank}`, inline: true },
{ name: 'Level', value: `${rankInfo.level}`, inline: true },
{ name: 'XP', value: `${rankInfo.xp}`, inline: true }
],
footer: { text: `Out of ${rankInfo.total} users` }
}]
})
}Manual Cache Invalidation (Advanced)
import { leaderboard } from '@robojs/xp'
// Usually automatic, but can be manually triggered
leaderboard.invalidateCache('guildId')
// Next leaderboard.get() call will rebuild cache from Flashcore
const fresh = await leaderboard.get('guildId', 0, 10)Remarks
- Cache is automatically invalidated on XP changes (via event listeners)
- First query after invalidation rebuilds cache (O(n log n))
- Subsequent queries use cached data (O(1) for top 100)
- Leaderboard entries are sorted: XP desc, userId asc (stable sort)
- Rank positions are 1-indexed (rank 1 = top user)
getRank()returnsnullfor users with no XP record- Cache TTL is 60 seconds - auto-refreshes on expiry
- Manual invalidation is rarely needed (automatic via events)
