redis-rank
Advanced tools
Comparing version 1.2.1 to 1.3.0
@@ -72,4 +72,5 @@ import { Redis, KeyType, Pipeline } from 'ioredis'; | ||
* Retrieve the top entries | ||
* This function is an alias for list(1, max) | ||
* @param max max number of entries to return | ||
* | ||
* Note: This function is an alias for list(1, max) | ||
*/ | ||
@@ -76,0 +77,0 @@ top(max?: number): Promise<Entry[]>; |
@@ -39,2 +39,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var Common_1 = require("./Common"); | ||
var Leaderboard = /** @class */ (function () { | ||
@@ -206,4 +207,5 @@ /** | ||
* Retrieve the top entries | ||
* This function is an alias for list(1, max) | ||
* @param max max number of entries to return | ||
* | ||
* Note: This function is an alias for list(1, max) | ||
*/ | ||
@@ -265,17 +267,3 @@ Leaderboard.prototype.top = function (max) { | ||
return [2 /*return*/, []]; | ||
return [4 /*yield*/, this.client.eval("local r=redis.call('z" + (this.options.lowToHigh ? '' : 'rev') + "rank',KEYS[1],ARGV[1])" + | ||
"if r==false then return{0,{}} end " + | ||
"local c=redis.call('zcard',KEYS[1])" + | ||
"local l=math.max(0, r-ARGV[2])" + | ||
(fillBorders ? | ||
"local h=l+2*ARGV[2]" + | ||
"if h>c then " + | ||
"h=math.min(c, r+ARGV[2])" + | ||
"l=math.max(0,h-2*ARGV[2]-1)" + | ||
"end " | ||
: | ||
"local h=math.min(c, r+ARGV[2])") + | ||
("return{l,redis.call('z" + (this.options.lowToHigh ? '' : 'rev') + "range',KEYS[1],l,h,'WITHSCORES')}"), | ||
// 289 bytes vs 20 bytes using EVALSHA should consider it | ||
1, this.options.path, id, distance)]; | ||
return [4 /*yield*/, this.client.eval(Common_1.buildScript("\n local range = aroundRange(KEYS[1], ARGV[1], ARGV[2], ARGV[3], ARGV[4]);\n if range[1] == -1 then return { 0, {} } end\n return {\n range[1],\n redis.call(ARGV[1] and 'zrange' or 'zrevrange', KEYS[1], range[1], range[2], 'WITHSCORES')\n }\n "), 1, this.options.path, this.isLowToHigh(), id, distance, fillBorders)]; | ||
case 1: | ||
@@ -282,0 +270,0 @@ result = _a.sent(); |
@@ -30,2 +30,10 @@ import { Redis } from 'ioredis'; | ||
}; | ||
export declare type MatrixEntry = { | ||
/** identifier */ | ||
id: ID; | ||
/** ranking */ | ||
rank: number; | ||
/** feature scores */ | ||
[feature: string]: ID | number; | ||
}; | ||
export declare class LeaderboardMatrix { | ||
@@ -64,2 +72,38 @@ /** ioredis client */ | ||
}, dimensions?: string[]): Promise<void>; | ||
/** | ||
* Retrieve an entry from the leaderboard | ||
* | ||
* @param feature only provide feature if you need the entry to be ranked | ||
*/ | ||
peek(id: ID, dimension: string, feature?: string): Promise<MatrixEntry | null>; | ||
/** | ||
* Retrieve the entries ranked between some boundaries (one-based) | ||
* | ||
* @param low lower bound to query (inclusive) | ||
* @param high higher bound to query (inclusive) | ||
*/ | ||
list(dimension: string, feature: string, low: number, high: number): Promise<MatrixEntry[]>; | ||
/** | ||
* Retrieve the top entries | ||
* @param max max number of entries to return | ||
* | ||
* Note: This function is an alias for list(1, max) | ||
*/ | ||
top(dimension: string, feature: string, max?: number): Promise<MatrixEntry[]>; | ||
/** | ||
* Retrieve the entries around an entry | ||
* | ||
* @param distance number of entries at each side of the queried entry | ||
* @param fillBorders include entries at the other side if the entry is too close to one of the borders. | ||
* | ||
* @see Leaderboard.around for details | ||
*/ | ||
around(dimension: string, feature: string, id: ID, distance: number, fillBorders?: boolean): Promise<MatrixEntry[]>; | ||
/** | ||
* Parses the result of the lua function 'retrieveEntries' | ||
* | ||
* @param data from retrieveEntries | ||
* @param low rank of the first entry | ||
*/ | ||
private parseEntries; | ||
} |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var PeriodicLeaderboard_1 = require("./PeriodicLeaderboard"); | ||
var Common_1 = require("./Common"); | ||
var LeaderboardMatrix = /** @class */ (function () { | ||
@@ -82,4 +119,118 @@ function LeaderboardMatrix(client, options) { | ||
}; | ||
/** | ||
* Retrieve an entry from the leaderboard | ||
* | ||
* @param feature only provide feature if you need the entry to be ranked | ||
*/ | ||
LeaderboardMatrix.prototype.peek = function (id, dimension, feature) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var list, result, entry; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
if (!feature) return [3 /*break*/, 2]; | ||
return [4 /*yield*/, this.around(dimension, feature, id, 1, false)]; | ||
case 1: | ||
list = _a.sent(); | ||
return [2 /*return*/, list.length == 0 ? null : list[0]]; | ||
case 2: | ||
// else, only query the features across the dimension | ||
if (this.options.dimensions.find(function (dim) { return dim.name === dimension; }) === undefined) | ||
return [2 /*return*/, null]; | ||
return [4 /*yield*/, this.client.eval(Common_1.buildScript("\n return retrieveEntry(ARGV[1], KEYS)\n "), this.options.features.length, this.options.features.map(function (f) { return _this.get(dimension, f.name).getPath(); }), id)]; | ||
case 3: | ||
result = _a.sent(); | ||
if (result.every(function (e) { return e === null; })) | ||
return [2 /*return*/, null]; | ||
entry = { id: id, rank: 0 }; | ||
this.options.features.map(function (f, f_i) { | ||
entry[f.name] = parseInt(result[f_i], 10); | ||
}); | ||
return [2 /*return*/, entry]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Retrieve the entries ranked between some boundaries (one-based) | ||
* | ||
* @param low lower bound to query (inclusive) | ||
* @param high higher bound to query (inclusive) | ||
*/ | ||
LeaderboardMatrix.prototype.list = function (dimension, feature, low, high) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var lb, result; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
lb = this.get(dimension, feature); | ||
if (!lb) | ||
return [2 /*return*/, []]; | ||
return [4 /*yield*/, this.client.eval(Common_1.buildScript("\n return retrieveEntries(KEYS[1], ARGV[1], slice(KEYS, 2, ARGV[2]+1), ARGV[3], ARGV[4])\n "), this.options.features.length + 1, lb.getPath(), this.options.features.map(function (f) { return _this.get(dimension, f.name).getPath(); }), lb.isLowToHigh(), this.options.features.length, low - 1, high - 1)]; | ||
case 1: | ||
result = _a.sent(); | ||
return [2 /*return*/, this.parseEntries(result, low)]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Retrieve the top entries | ||
* @param max max number of entries to return | ||
* | ||
* Note: This function is an alias for list(1, max) | ||
*/ | ||
LeaderboardMatrix.prototype.top = function (dimension, feature, max) { | ||
if (max === void 0) { max = 10; } | ||
return this.list(dimension, feature, 1, max); | ||
}; | ||
/** | ||
* Retrieve the entries around an entry | ||
* | ||
* @param distance number of entries at each side of the queried entry | ||
* @param fillBorders include entries at the other side if the entry is too close to one of the borders. | ||
* | ||
* @see Leaderboard.around for details | ||
*/ | ||
LeaderboardMatrix.prototype.around = function (dimension, feature, id, distance, fillBorders) { | ||
if (fillBorders === void 0) { fillBorders = false; } | ||
return __awaiter(this, void 0, void 0, function () { | ||
var lb, result; | ||
var _this = this; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
lb = this.get(dimension, feature); | ||
if (!lb) | ||
return [2 /*return*/, []]; | ||
if (distance < 0) | ||
return [2 /*return*/, []]; | ||
return [4 /*yield*/, this.client.eval(Common_1.buildScript("\n local range = aroundRange(KEYS[1], ARGV[1], ARGV[2], ARGV[3], ARGV[4]);\n if range[1] == -1 then return { {}, { {},{} } } end\n return {\n range[1],\n retrieveEntries(KEYS[1], ARGV[1], slice(KEYS, 2, ARGV[5]+1), range[1], range[2])\n }\n "), this.options.features.length + 1, lb.getPath(), this.options.features.map(function (f) { return _this.get(dimension, f.name).getPath(); }), lb.isLowToHigh(), id, distance, fillBorders, this.options.features.length)]; | ||
case 1: | ||
result = _a.sent(); | ||
return [2 /*return*/, this.parseEntries(result[1], parseInt(result[0], 10) + 1)]; | ||
} | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Parses the result of the lua function 'retrieveEntries' | ||
* | ||
* @param data from retrieveEntries | ||
* @param low rank of the first entry | ||
*/ | ||
LeaderboardMatrix.prototype.parseEntries = function (result, low) { | ||
var _this = this; | ||
return result[0].map(function (id, index) { | ||
var entry = { id: id, rank: low + index }; | ||
_this.options.features.map(function (f, f_i) { | ||
entry[f.name] = parseInt(result[1][f_i][index], 10); | ||
}); | ||
return entry; | ||
}); | ||
}; | ||
return LeaderboardMatrix; | ||
}()); | ||
exports.LeaderboardMatrix = LeaderboardMatrix; |
{ | ||
"name": "redis-rank", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"description": "Back-end to generate and manage leaderboards using Redis. Written in TypeScript and Promise-based.", | ||
@@ -28,3 +28,3 @@ "keywords": [ | ||
"scripts": { | ||
"build": "tsc", | ||
"build": "tsc && cpx src/common.lua dist", | ||
"prepare": "npm run test && npm run build", | ||
@@ -39,2 +39,3 @@ "test": "jest --coverage --verbose --runInBand", | ||
"codecov": "^3.6.1", | ||
"cpx": "^1.5.0", | ||
"jest": "^24.9.0", | ||
@@ -41,0 +42,0 @@ "nodemon": "^1.19.4", |
@@ -221,2 +221,16 @@ # redis-rank | ||
To list entries within the matrix, yo can use `top` and `around` based on a dimension like this: | ||
```javascript | ||
lm.top('weekly', 'kills', 3); | ||
// example | ||
[ | ||
{ id: 'pepe', rank: 1, kills: 36, coins: 92, time: 342 }, | ||
{ id: '....', rank: 2, kills: 27, coins: 123, time: 295 }, | ||
{ id: '....', rank: 3, kills: 16, coins: 77, time: 420 } | ||
] | ||
// also | ||
lm.around('monthly', 'time', pepe, 15); | ||
``` | ||
To access a single leaderboard you can use the `get` function: | ||
@@ -223,0 +237,0 @@ ```javascript |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50698
14
924
261
9
2