UNPKG

redis-rank

Version:

Manage real-time leaderboards using Redis

139 lines (138 loc) 5.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PeriodicLeaderboard = void 0; var Leaderboard_1 = require("./Leaderboard"); /** * Used by `getWeekNumber`. Needed because Date.getTime returns the time in UTC * and we use local time. */ var msTimezoneOffset = new Date().getTimezoneOffset() * 60 * 1000; /** * Get the week number since January 1st, 1970 * * 345600000 = 4 days in milliseconds * 604800000 = 1 week in milliseconds * * Note: we add 4 days because January 1st, 1970 was thursday (and weeks start * on sunday). I think it should be 3 days and not 4, but 3 result in * incorrect values. ¯\_(ツ)_/¯ */ var getWeekNumber = function (time) { return Math.floor((time.getTime() + 345600000 - msTimezoneOffset) / 604800000); }; /** * Pad a number with leading zeroes * * @param input the number * @param digits number of digits */ var padNumber = function (input, digits) { if (digits === void 0) { digits = 2; } return (input + '').padStart(digits, '0'); }; /** * Note: default functions use local time to determine keys. * Tip: You can specify the `now()` function in the periodic leaderboard options * to offset the time however you like. * * Examples: * * `yearly`: `y2020` * * `weekly`: `w2650` (week number since epoch) * * `monthly`: `y2020-m05` * * `daily`: `y2020-m05-d15` * * `hourly`: `y2020-m05-d15-h22` * * `minute`: `y2020-m05-d15-h22-m53` */ var CYCLE_FUNCTIONS = { 'yearly': function (time) { return "y".concat(time.getFullYear()); }, 'weekly': function (time) { return "w".concat(padNumber(getWeekNumber(time), 4)); }, 'monthly': function (time) { return "".concat(CYCLE_FUNCTIONS['yearly'](time), "-m").concat(padNumber(time.getMonth())); }, 'daily': function (time) { return "".concat(CYCLE_FUNCTIONS['monthly'](time), "-d").concat(padNumber(time.getDate())); }, 'hourly': function (time) { return "".concat(CYCLE_FUNCTIONS['daily'](time), "-h").concat(padNumber(time.getHours())); }, 'minute': function (time) { return "".concat(CYCLE_FUNCTIONS['hourly'](time), "-m").concat(padNumber(time.getMinutes())); } }; var PeriodicLeaderboard = /** @class */ (function () { /** * Create a new periodic leaderboard * * Use `getCurrentLeaderboard` to get the leaderboard of the current cycle * * @param client ioredis client * @param baseKey prefix for all the leaderboards * @param options periodic leaderboard options */ function PeriodicLeaderboard(client, baseKey, options) { this.client = client; this.baseKey = baseKey; this.options = options; this.leaderboards = new Map(); } /** * Get the cycle key at a specified date and time * * @param time the time */ PeriodicLeaderboard.prototype.getKey = function (time) { return (CYCLE_FUNCTIONS[this.options.cycle] || this.options.cycle)(time); }; /** * Get the leaderboard for the provided cycle key * * @param key cycle key */ PeriodicLeaderboard.prototype.getLeaderboard = function (key) { var finalKey = "".concat(this.baseKey, ":").concat(key); var lb = this.leaderboards.get(finalKey); if (lb) return lb; // hit cache // Note: avoid leaking leaderboards if (this.leaderboards.size > 100) this.leaderboards.clear(); lb = new Leaderboard_1.Leaderboard(this.client, finalKey, this.options.leaderboardOptions); this.leaderboards.set(finalKey, lb); return lb; }; /** * Get the leaderboard at the specified date and time. If `time` is not * provided, it will use the time returned by `now()`. * * @param time the time */ PeriodicLeaderboard.prototype.getLeaderboardAt = function (time) { return this.getLeaderboard(time ? this.getKey(time) : this.getKeyNow()); }; /** * Get the cycle key that should be used based on the time returned * by `now()` */ PeriodicLeaderboard.prototype.getKeyNow = function () { return this.getKey(this.options.now ? this.options.now() : new Date()); }; /** * Get the current leaderboard based on the time returned by `now()` */ PeriodicLeaderboard.prototype.getLeaderboardNow = function () { return this.getLeaderboard(this.getKeyNow()); }; /** * Find all the active cycle keys in the database. * Use this function sparsely, it uses `SCAN` over the whole database to * find matches. * * Complexity: `O(N)` where N is the number of keys in the Redis database */ PeriodicLeaderboard.prototype.getExistingKeys = function () { var _this = this; return new Promise(function (resolve, reject) { var stream = _this.client.scanStream({ match: "".concat(_this.baseKey, ":*"), count: 100 }); var keys = new Set(); var addKey = keys.add.bind(keys); stream.on('data', function (batch) { return batch.map(addKey); }); stream.on('error', reject); stream.on('end', function () { return resolve(Array.from(keys).map(function (key) { return key.slice(_this.baseKey.length + 1); })); }); }); }; return PeriodicLeaderboard; }()); exports.PeriodicLeaderboard = PeriodicLeaderboard;