UNPKG

redis-rank

Version:

Manage real-time leaderboards using Redis

266 lines (265 loc) 9.2 kB
import { Readable } from 'stream'; import { Redis, RedisKey, ChainableCommander } from 'ioredis'; /** Entry identifier */ export type ID = string; /** Score value */ export type Score = number; /** Position in the leaderboard, determined by the sort policy. 1-based */ export type Rank = number; /** * Sort policy * * * `high-to-low`: sort scores in descending order * * `low-to-high`: sort scores in ascending order */ export type SortPolicy = 'high-to-low' | 'low-to-high'; /** * Update policy * * When an update occurs... * * `replace`: the new score will replace the previous one * * `aggregate`: previous and new scores will be added * * `best`: the best score is kept (determined by the sort policy) */ export type UpdatePolicy = 'replace' | 'aggregate' | 'best'; export type LeaderboardOptions = { /** * Sort policy for this leaderboard * @see SortPolicy */ sortPolicy: SortPolicy; /** * Update policy for this leaderboard * @see UpdatePolicy */ updatePolicy: UpdatePolicy; /** * Keep only the top N entries, determined by the sort policy. * This lets you limit the number of entries stored, thus saving memory * * If not specified, or the value is `0`, then there is no limit */ limitTopN?: number; }; /** * Entry details at the time of the query */ export type Entry = { id: ID; score: Score; rank: Rank; }; export type EntryUpdateQuery = { id: ID; value: number | Score; }; export declare class Leaderboard { private readonly client; private readonly key; private readonly options; /** * Create a new leaderboard * * Note: the Redis key will not be created until an entry is inserted * (aka lazy) * * @param client ioredis client * @param key Redis key for the sorted set. You can use any sorted set, not only the ones created by redis-rank * @param options leaderboard options */ constructor(client: Redis, key: RedisKey, options: LeaderboardOptions); /** * Retrieve the score of an entry. If it doesn't exist, it returns null * * Complexity: `O(1)` * * @param id entry id */ score(id: ID): Promise<Score | null>; /** * Retrieve the rank of an entry. If it doesn't exist, it returns null * * Complexity: `O(log(N))` where N is the number of entries in the * leaderboard * * @param id entry id */ rank(id: ID): Promise<Rank | null>; /** * Retrieve an entry. If it doesn't exist, it returns null * * Complexity: `O(log(N))` where N is the number of entries in the * leaderboard * * @param id entry id */ find(id: ID): Promise<Entry | null>; /** * Retrieve an entry at a specific rank. If the rank is out of bounds, * it returns null * * Complexity: `O(log(N))` where N is the number of entries in the * leaderboard * * Note: This function is an alias for list(rank, rank)[0] * * @param rank rank to query */ at(rank: Rank): Promise<Entry | null>; /** * Update one entry. If the entry does not exists, it will be created. * The update behaviour is determined by the sort and update policies. * * Complexity: `O(log(N))` where N is the number of entries in the * leaderboard * * @param id entry id * @param value amount or score * @param updatePolicy override the default update policy only for this update * @returns if the update policy is `aggregate` or `best` then the final * score otherwise void */ updateOne(id: ID, value: Score | number, updatePolicy?: UpdatePolicy): Promise<Score | void>; /** * Update one or more entries. If one of the entries does not exists, * it will be created. The update behaviour is determined by the sort and * update policies. * * Complexity: `O(log(N))` for each entry updated, where N is the number of * entries in the leaderboard * * @param entries entry or list of entries to update * @param updatePolicy override the default update policy only for this update * @returns if the update policy is `aggregate` or `best` then the final * score for each entry otherwise void */ update(entries: EntryUpdateQuery | EntryUpdateQuery[], updatePolicy?: UpdatePolicy): Promise<Score[] | void[]>; /** * Applies the limit top N restriction (if enabled) * * @param pipeline ioredis pipeline * @returns if the leaderboard has `limitTopN` enabled */ limitPipe(pipeline: ChainableCommander): boolean; /** * Uses IORedis.Pipeline to batch multiple Redis commands * * Note: this method alone will not honor `limitTopN` (use `limitPipe`) * * @see update * @param entries list of entries to update * @param pipeline ioredis pipeline * @param updatePolicy override the default update policy only for this update */ updatePipe(entries: EntryUpdateQuery[], pipeline: ChainableCommander, updatePolicy?: UpdatePolicy): void; /** * Remove one or more entries from the leaderboard * * Complexity: `O(M*log(N))` where N is the number of entries in the * leaderboard and M the number of entries to be removed */ remove(ids: ID | ID[]): Promise<void>; /** * Remove all the entries from the leaderboard * * Note: it will delete the underlying Redis key * * Complexity: `O(N)` where N is the number of entries in the leaderboard */ clear(): Promise<void>; /** * Retrieve entries between ranks * * Complexity: `O(log(N)+M)` where N is the number of entries in the * leaderboard and M the number of entries returned * * @param lower lower bound to query (inclusive) * @param upper upper bound to query (inclusive) */ list(lower: Rank, upper: Rank): Promise<Entry[]>; /** * Retrieve entries between scores * * Complexity: `O(log(N)+M)` where N is the number of entries in the * leaderboard and M the number of entries returned * * @param min min score to query (inclusive) * @param max max score to query (inclusive) */ listByScore(min: Score, max: Score): Promise<Entry[]>; /** * Retrieve the top entries * * Complexity: `O(log(N)+M)` where N is the number of entries in the * leaderboard and M is `max` * * Note: This function is an alias for list(1, max) * * @param max number of entries to return */ top(max?: number): Promise<Entry[]>; /** * Retrieve the bottom entries (from worst to better) * * Complexity: `O(log(N)+M)` where N is the number of entries in the * leaderboard and M is `max` * * @param max number of entries to return */ bottom(max?: number): Promise<Entry[]>; /** * Retrieve the entries around an entry * * Example with distance = 4: * ``` * +-----+-----+-----+-----+-----+-----+-----+-----+-----+------+ * | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th | 10th | * +-----+-----+-----+-----+-----+-----+-----+-----+-----+------+ * ↑ * queried entry * * Without fillBorders: [ 1st, 2nd, 3rd, 4th, 5th, 6th, 7th ] // 2 + 1 + 4 = 7 elements * With fillBorders: [ 1st, 2nd, 3rd, 4th, 5th, 6th, 7th, 8th, 9th ] // 2 + 1 + 6 = 9 elements * ``` * * Complexity: `O(log(N)+M)` where N is the number of entries in the * leaderboard and M is 2*`distance`+1 * * @param id id of the entry at the center * @param distance number of entries at each side of the queried entry * @param fillBorders whether to include entries at the other side if the * entry is too close to one of the borders. In other words, it always * makes sure to return at least 2*`distance`+1 entries (if there are enough * in the leaderboard) */ around(id: ID, distance: number, fillBorders?: boolean): Promise<Entry[]>; /** * Create a readable stream to iterate all the entries in the leaderboard. * Note that the stream guarantees to traverse all entries only if there * are no updates during retrival. * * Complexity: `O(log(N)+M)` each iteration, where N is the number of * entries in the leaderboard and M the batch size * * @param batchSize number of entries to retrieve per iteration * @returns a stream to iterate every entry in the leaderboard (in batches) */ exportStream(batchSize: number): Readable; /** * Retrieve the number of entries in the leaderboard * * Complexity: `O(1)` */ count(): Promise<number>; get redisClient(): Redis; get redisKey(): RedisKey; get sortPolicy(): SortPolicy; get updatePolicy(): UpdatePolicy; /** * Executes a IORedis.Pipeline, throws if any command resulted in error. * * @param pipeline ioredis pipeline * @returns array of each command result */ static execPipeline(pipeline: ChainableCommander): Promise<any[]>; }