Skip to content

Latest commit

 

History

History
272 lines (230 loc) · 12.8 KB

Leaderboard.md

File metadata and controls

272 lines (230 loc) · 12.8 KB

Leaderboard

Plain and simple leaderboard. Ranks are 1-based.

Types

Constructor

Arguments

  • client: Redis connection object
  • key: KeyType Redis key for the sorted set (usually a string)
  • options: LeaderboardOptions configuration
    • sortPolicy: SortPolicy determines which scores are better than others
      Allowed values:
      • 'high-to-low': sort scores in descending order
      • 'low-to-high': sort scores in ascending order
    • updatePolicy: UpdatePolicy determines what happens between old and new scores
      Allowed values:
      • '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)
    • limitTopN?: number: 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

Example

const lb = new Leaderboard(client, 'lb:test', {
  sortPolicy: 'high-to-low',
  updatePolicy: 'replace'
  // limitTopN: 1000 (disabled, no limit)
});

Insert/update entries

Note that when you update an entry that doesn't exist, it will be created, so update/insert is the same operation.

  • updateOne(id: ID, value: Score | number, updatePolicy?: UpdatePolicy): Promise<Score | void> update a single entry

    • id: ID id of the entry to update
    • value: Score | number score or number to add
    • updatePolicy?: UpdatePolicy override the default update policy only for this update

    The update behaviour is determined by the sort and update policies.

    Return

    If the update policy is aggregate or best then the method will return the final score (the addition or the score which was better), otherwise void.

    Example

    await lb.updateOne("player-1", 999);
    
    // override update policy
    await lb.updateOne("player-1", 999, 'replace');

    Complexity

    O(log(N)) where N is the number of entries in the leaderboard.

    Note: why Score | number? When the update policy is set to replace or best the value should be a Score, but when the update policy is set to aggregate it behaves more like an amount than a full score. Either way, both are number.

  • update(entries: EntryUpdateQuery | EntryUpdateQuery[], updatePolicy?: UpdatePolicy): Promise<Score[] | void[]> update one or more entries

    This method is very similar to updateOne, but it lets you update multiple entries in one go.

    Return

    Analogous to the return of updateOne but as an array, where each value matches the order of the entries in the input.

    Example

    // single
    await lb.update({ id: "player-1", value: 999 });
    // multiple
    await lb.update([
      { id: "player-1", value: 123 },
      { id: "player-2", value: 420 },
      { id: "player-3", value: 777 },
      { id: "player-4", value: 696 }
    ]);
    // override update policy
    await lb.update({ id: "player-1", value: 999 }, 'replace');

    Complexity

    O(log(N)) for each entry updated, where N is the number of entries in the leaderboard.

Remove entries

  • remove(ids: ID | ID[]): Promise<void> remove one or more entries from the leaderboard

    • ids: ID | ID[] id or ids to remove

    Example

    // single
    await lb.remove("player-1");
    // multiple
    await lb.remove(["player-1", "player-2", "player-3"]);

    Complexity

    O(M*log(N)) where N is the number of entries in the leaderboard and M the number of entries to be removed.

  • clear(): Promise<void> remove all the entries from the leaderboard
    Note: it will delete the underlying Redis key

    Example

    await lb.clear();
    // leaderboard is no more

    Complexity

    O(N) where N is the number of entries in the leaderboard.

Find entries

  • score(id: ID): Promise<Score | null> retrieve the score of an entry, null if it doesn't exist

    • id: ID id of the entry

    Example

    await lb.score("player-1"); /// === 999
    await lb.score("non-existant"); /// === null

    Complexity

    O(1)

  • rank(id: ID): Promise<Rank | null> retrieve the rank of an entry, null if it doesn't exist

    • id: ID id of the entry

    Example

    await lb.rank("player-1"); /// === 3
    await lb.rank("non-existant"); /// === null

    Complexity

    O(log(N)) where N is the number of entries in the leaderboard.

  • find(id: ID): Promise<Entry | null> retrieve an entry, null if it doesn't exist

    • id: ID id of the entry

    Example

    await lb.find("player-1"); /// === { "id": "player-1", score: 999, rank: 3 }
    await lb.find("non-existant"); /// === null

    Complexity

    O(log(N)) where N is the number of entries in the leaderboard.

  • at(rank: Rank): Promise<Entry | null> retrieve an entry at a specific rank, null if out of bounds

    • rank: Rank rank to query

    Example

    await lb.rank(3); /// === { "id": "player-1", score: 999, rank: 3 }
    await lb.rank(10000000); /// === null

    Complexity

    O(log(N)) where N is the number of entries in the leaderboard.

List entries

  • top(max: number): Promise<Entry[]> retrieve the top entries

    • max: number number of entries to return

    Example

    await lb.top(10); /// === [{ id: "n1", score: 999, rank: 1 }, ... 9 more]

    Complexity

    O(log(N)+M) where N is the number of entries in the leaderboard and M is max

  • bottom(max: number): Promise<Entry[]> retrieve the bottom entries (from worst to better)

    • max: number number of entries to return

    Example

    await lb.bottom(10); /// === [{ id: "n10", score: 111, rank: 10 }, ... 9 more]

    Complexity

    O(log(N)+M) where N is the number of entries in the leaderboard and M is max

  • list(low: Rank, high: Rank): Promise<Entry[]> retrieve entries between ranks

    • low: Rank lower bound to query (inclusive)
    • high: Rank higher bound to query (inclusive)

    Example

    await lb.list(100, 200); /// === [{ id: "n100", score: 100, rank: 100 }, ... 100 more]

    Complexity

    O(log(N)+M) where N is the number of entries in the leaderboard and M the number of entries returned (high - low + 1)

  • around(id: ID, distance: number, fillBorders?: boolean = false): Promise<Entry[]> retrieve the entries around an entry

    • id: ID id of the entry at the center
    • distance: number number of entries at each side of the queried entry
    • fillBorders?: boolean 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)

    Fill borders

    Let's say we have the following entries and we query the 3rd entry:

    1st 2dn 3rd 4th 5th 6th 7th 8th 9th 10th

    Now we use around("3rd", 4, fillBorders) with

    • fillBorders=false → [ 1st, 2nd, 3rd, 4th, 5th, 6th, 7th ] (default)
      • got 2 + 1 + 4 = 7 elements
    • fillBorders=true → [ 1st, 2nd, 3rd, 4th, 5th, 6th, 7th, 8th, 9th ]
      • got 2 + 1 + 6 = 9 elements

    Example

    await lb.around("3rd", 4); // fillBorders=false by default
    /// === [
    /// { id: "1st", score: 99, rank: 1 },
    /// { id: "2nd", score: 88, rank: 2 },
    /// { id: "3rd", score: 77, rank: 3 },
    /// ... 4 more
    /// ]
    await lb.around("3rd", 4, true);
    /// === [
    /// { id: "1st", score: 99, rank: 1 },
    /// { id: "2nd", score: 88, rank: 2 },
    /// { id: "3rd", score: 77, rank: 3 },
    /// ... 6 more
    /// ]

    Complexity

    O(log(N)+M) where N is the number of entries in the leaderboard and M is (2*distance+1)

  • listByScore(min: Score, max: Score): Promise<Entry[]> retrieve entries within a score range

    • min: Score min score to query (inclusive)
    • max: Score max score to query (inclusive)

    Example

    await lb.listByScore(20, 30);
    /// === [
    /// { id: "ecd", score: 20, rank: 37 },
    /// { id: "yug", score: 22, rank: 38 },
    /// { id: "bls", score: 27, rank: 39 }
    /// ]

    Complexity

    O(log(N)+M) where N is the number of entries in the leaderboard and M the number of entries returned

Export

  • exportStream(batchSize: number): Readable create a readable stream to iterate all entries in the leaderboard
    • batchSize: number number of entries to retrieve per iteration

    Example

    const stream = lb.exportStream(100);
    stream.on("data", (entries) => {
      // process entries
      // note: (use pause and resume if you need to do async work, check out EXAMPLES.md)
    });
    stream.on("close", () => {
      // done
    });

    Complexity

    O(log(N)+M) each iteration, where N is the number of entries in the leaderboard and M the batch size

Information

  • count(): Promise<number> returns the number of entries stored in the leaderboard. Complexity: O(1)
  • redisClient: Redis redis connection
  • redisKey: KeyType sorted set key
  • sortPolicy: SortPolicy sort policy
  • updatePolicy: UpdatePolicy update policy