feat: simulador GPS con cron job y pub/sub Redis
This commit is contained in:
20
backend/env
Normal file
20
backend/env
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
PORT=3000
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5433
|
||||||
|
DB_NAME=recoleccion_db
|
||||||
|
DB_USER=recoleccion
|
||||||
|
DB_PASSWORD=recoleccion123
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6380
|
||||||
|
JWT_SECRET=hackathon_celaya_2026_secret_key
|
||||||
|
JWT_EXPIRES_IN=7d
|
||||||
|
|
||||||
|
# Simulador GPS
|
||||||
|
# TIME_MULTIPLIER: cuántos segundos simulados por segundo real
|
||||||
|
# 1 = tiempo real | 60 = demo (1 min real = 1 hora simulada)
|
||||||
|
TIME_MULTIPLIER=60
|
||||||
|
|
||||||
|
# CRON_SCHEDULE: cada cuánto corre el simulador
|
||||||
|
# '*/1 * * * *' = cada 1 minuto (recomendado)
|
||||||
|
# '*/30 * * * * *' = cada 30 segundos (más responsivo en demo)
|
||||||
|
CRON_SCHEDULE=*/1 * * * *
|
||||||
119
backend/node_modules/.package-lock.json
generated
vendored
119
backend/node_modules/.package-lock.json
generated
vendored
@@ -4,6 +4,12 @@
|
|||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
"node_modules/@ioredis/commands": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
@@ -238,6 +244,15 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cluster-key-slot": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -321,6 +336,15 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -782,6 +806,53 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ioredis": {
|
||||||
|
"version": "5.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
|
||||||
|
"integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ioredis/commands": "1.5.1",
|
||||||
|
"cluster-key-slot": "^1.1.0",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"lodash.defaults": "^4.2.0",
|
||||||
|
"lodash.isarguments": "^3.1.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0",
|
||||||
|
"standard-as-callback": "^2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.22.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ioredis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ioredis/node_modules/debug": {
|
||||||
|
"version": "4.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ioredis/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -886,12 +957,24 @@
|
|||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.defaults": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.isarguments": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.isboolean": {
|
"node_modules/lodash.isboolean": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
@@ -1028,6 +1111,15 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nodemon": {
|
"node_modules/nodemon": {
|
||||||
"version": "3.1.14",
|
"version": "3.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
|
||||||
@@ -1362,6 +1454,27 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -1545,6 +1658,12 @@
|
|||||||
"node": ">= 10.x"
|
"node": ">= 10.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/standard-as-callback": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||||
|
|||||||
23
backend/node_modules/@ioredis/commands/LICENSE
generated
vendored
Normal file
23
backend/node_modules/@ioredis/commands/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2022 Zihua Li
|
||||||
|
Copyright (c) 2015 NodeRedis
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
43
backend/node_modules/@ioredis/commands/README.md
generated
vendored
Normal file
43
backend/node_modules/@ioredis/commands/README.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Redis Commands
|
||||||
|
|
||||||
|
This module exports all the commands that Redis supports.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ npm install @ioredis/commands
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
const commands = require('@ioredis/commands');
|
||||||
|
```
|
||||||
|
|
||||||
|
`.list` is an array contains all the lowercased commands:
|
||||||
|
|
||||||
|
```js
|
||||||
|
commands.list.forEach((command) => {
|
||||||
|
console.log(command);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`.exists()` is used to check if the command exists:
|
||||||
|
|
||||||
|
```js
|
||||||
|
commands.exists('set') // true
|
||||||
|
commands.exists('other-command') // false
|
||||||
|
```
|
||||||
|
|
||||||
|
`.hasFlag()` is used to check if the command has the flag:
|
||||||
|
|
||||||
|
```js
|
||||||
|
commands.hasFlag('set', 'readonly') // false
|
||||||
|
```
|
||||||
|
|
||||||
|
`.getKeyIndexes()` is used to get the indexes of keys in the command arguments:
|
||||||
|
|
||||||
|
```js
|
||||||
|
commands.getKeyIndexes('set', ['key', 'value']) // [0]
|
||||||
|
commands.getKeyIndexes('mget', ['key1', 'key2']) // [0, 1]
|
||||||
|
```
|
||||||
2578
backend/node_modules/@ioredis/commands/built/commands.json
generated
vendored
Normal file
2578
backend/node_modules/@ioredis/commands/built/commands.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
34
backend/node_modules/@ioredis/commands/built/index.d.ts
generated
vendored
Normal file
34
backend/node_modules/@ioredis/commands/built/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
/**
|
||||||
|
* Redis command list
|
||||||
|
*
|
||||||
|
* All commands are lowercased.
|
||||||
|
*/
|
||||||
|
export declare const list: string[];
|
||||||
|
/**
|
||||||
|
* Check if the command exists
|
||||||
|
*/
|
||||||
|
export declare function exists(commandName: string, options?: {
|
||||||
|
caseInsensitive?: boolean;
|
||||||
|
}): boolean;
|
||||||
|
/**
|
||||||
|
* Check if the command has the flag
|
||||||
|
*
|
||||||
|
* Some of possible flags: readonly, noscript, loading
|
||||||
|
*/
|
||||||
|
export declare function hasFlag(commandName: string, flag: string, options?: {
|
||||||
|
nameCaseInsensitive?: boolean;
|
||||||
|
}): boolean;
|
||||||
|
/**
|
||||||
|
* Get indexes of keys in the command arguments
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* getKeyIndexes('set', ['key', 'value']) // [0]
|
||||||
|
* getKeyIndexes('mget', ['key1', 'key2']) // [0, 1]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export declare function getKeyIndexes(commandName: string, args: (string | Buffer | number)[], options?: {
|
||||||
|
parseExternalKey?: boolean;
|
||||||
|
nameCaseInsensitive?: boolean;
|
||||||
|
}): number[];
|
||||||
217
backend/node_modules/@ioredis/commands/built/index.js
generated
vendored
Normal file
217
backend/node_modules/@ioredis/commands/built/index.js
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
"use strict";
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.getKeyIndexes = exports.hasFlag = exports.exists = exports.list = void 0;
|
||||||
|
const commands_json_1 = __importDefault(require("./commands.json"));
|
||||||
|
/**
|
||||||
|
* Redis command list
|
||||||
|
*
|
||||||
|
* All commands are lowercased.
|
||||||
|
*/
|
||||||
|
exports.list = Object.keys(commands_json_1.default);
|
||||||
|
const flags = {};
|
||||||
|
exports.list.forEach((commandName) => {
|
||||||
|
flags[commandName] = commands_json_1.default[commandName].flags.reduce(function (flags, flag) {
|
||||||
|
flags[flag] = true;
|
||||||
|
return flags;
|
||||||
|
}, {});
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Check if the command exists
|
||||||
|
*/
|
||||||
|
function exists(commandName, options) {
|
||||||
|
commandName = (options === null || options === void 0 ? void 0 : options.caseInsensitive)
|
||||||
|
? String(commandName).toLowerCase()
|
||||||
|
: commandName;
|
||||||
|
return Boolean(commands_json_1.default[commandName]);
|
||||||
|
}
|
||||||
|
exports.exists = exists;
|
||||||
|
/**
|
||||||
|
* Check if the command has the flag
|
||||||
|
*
|
||||||
|
* Some of possible flags: readonly, noscript, loading
|
||||||
|
*/
|
||||||
|
function hasFlag(commandName, flag, options) {
|
||||||
|
commandName = (options === null || options === void 0 ? void 0 : options.nameCaseInsensitive)
|
||||||
|
? String(commandName).toLowerCase()
|
||||||
|
: commandName;
|
||||||
|
if (!flags[commandName]) {
|
||||||
|
throw new Error("Unknown command " + commandName);
|
||||||
|
}
|
||||||
|
return Boolean(flags[commandName][flag]);
|
||||||
|
}
|
||||||
|
exports.hasFlag = hasFlag;
|
||||||
|
/**
|
||||||
|
* Get indexes of keys in the command arguments
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* getKeyIndexes('set', ['key', 'value']) // [0]
|
||||||
|
* getKeyIndexes('mget', ['key1', 'key2']) // [0, 1]
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function getKeyIndexes(commandName, args, options) {
|
||||||
|
commandName = (options === null || options === void 0 ? void 0 : options.nameCaseInsensitive)
|
||||||
|
? String(commandName).toLowerCase()
|
||||||
|
: commandName;
|
||||||
|
const command = commands_json_1.default[commandName];
|
||||||
|
if (!command) {
|
||||||
|
throw new Error("Unknown command " + commandName);
|
||||||
|
}
|
||||||
|
if (!Array.isArray(args)) {
|
||||||
|
throw new Error("Expect args to be an array");
|
||||||
|
}
|
||||||
|
const keys = [];
|
||||||
|
const parseExternalKey = Boolean(options && options.parseExternalKey);
|
||||||
|
const takeDynamicKeys = (args, startIndex) => {
|
||||||
|
const keys = [];
|
||||||
|
const keyStop = Number(args[startIndex]);
|
||||||
|
for (let i = 0; i < keyStop; i++) {
|
||||||
|
keys.push(i + startIndex + 1);
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
const takeKeyAfterToken = (args, startIndex, token) => {
|
||||||
|
for (let i = startIndex; i < args.length - 1; i += 1) {
|
||||||
|
if (String(args[i]).toLowerCase() === token.toLowerCase()) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
switch (commandName) {
|
||||||
|
case "zunionstore":
|
||||||
|
case "zinterstore":
|
||||||
|
case "zdiffstore":
|
||||||
|
keys.push(0, ...takeDynamicKeys(args, 1));
|
||||||
|
break;
|
||||||
|
case "eval":
|
||||||
|
case "evalsha":
|
||||||
|
case "eval_ro":
|
||||||
|
case "evalsha_ro":
|
||||||
|
case "fcall":
|
||||||
|
case "fcall_ro":
|
||||||
|
case "blmpop":
|
||||||
|
case "bzmpop":
|
||||||
|
keys.push(...takeDynamicKeys(args, 1));
|
||||||
|
break;
|
||||||
|
case "sintercard":
|
||||||
|
case "lmpop":
|
||||||
|
case "zunion":
|
||||||
|
case "zinter":
|
||||||
|
case "zmpop":
|
||||||
|
case "zintercard":
|
||||||
|
case "zdiff": {
|
||||||
|
keys.push(...takeDynamicKeys(args, 0));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "georadius": {
|
||||||
|
keys.push(0);
|
||||||
|
const storeKey = takeKeyAfterToken(args, 5, "STORE");
|
||||||
|
if (storeKey)
|
||||||
|
keys.push(storeKey);
|
||||||
|
const distKey = takeKeyAfterToken(args, 5, "STOREDIST");
|
||||||
|
if (distKey)
|
||||||
|
keys.push(distKey);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "georadiusbymember": {
|
||||||
|
keys.push(0);
|
||||||
|
const storeKey = takeKeyAfterToken(args, 4, "STORE");
|
||||||
|
if (storeKey)
|
||||||
|
keys.push(storeKey);
|
||||||
|
const distKey = takeKeyAfterToken(args, 4, "STOREDIST");
|
||||||
|
if (distKey)
|
||||||
|
keys.push(distKey);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "sort":
|
||||||
|
case "sort_ro":
|
||||||
|
keys.push(0);
|
||||||
|
for (let i = 1; i < args.length - 1; i++) {
|
||||||
|
let arg = args[i];
|
||||||
|
if (typeof arg !== "string") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const directive = arg.toUpperCase();
|
||||||
|
if (directive === "GET") {
|
||||||
|
i += 1;
|
||||||
|
arg = args[i];
|
||||||
|
if (arg !== "#") {
|
||||||
|
if (parseExternalKey) {
|
||||||
|
keys.push([i, getExternalKeyNameLength(arg)]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (directive === "BY") {
|
||||||
|
i += 1;
|
||||||
|
if (parseExternalKey) {
|
||||||
|
keys.push([i, getExternalKeyNameLength(args[i])]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (directive === "STORE") {
|
||||||
|
i += 1;
|
||||||
|
keys.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "migrate":
|
||||||
|
if (args[2] === "") {
|
||||||
|
for (let i = 5; i < args.length - 1; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
if (typeof arg === "string" && arg.toUpperCase() === "KEYS") {
|
||||||
|
for (let j = i + 1; j < args.length; j++) {
|
||||||
|
keys.push(j);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys.push(2);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "xreadgroup":
|
||||||
|
case "xread":
|
||||||
|
// Keys are 1st half of the args after STREAMS argument.
|
||||||
|
for (let i = commandName === "xread" ? 0 : 3; i < args.length - 1; i++) {
|
||||||
|
if (String(args[i]).toUpperCase() === "STREAMS") {
|
||||||
|
for (let j = i + 1; j <= i + (args.length - 1 - i) / 2; j++) {
|
||||||
|
keys.push(j);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Step has to be at least one in this case, otherwise the command does
|
||||||
|
// not contain a key.
|
||||||
|
if (command.step > 0) {
|
||||||
|
const keyStart = command.keyStart - 1;
|
||||||
|
const keyStop = command.keyStop > 0
|
||||||
|
? command.keyStop
|
||||||
|
: args.length + command.keyStop + 1;
|
||||||
|
for (let i = keyStart; i < keyStop; i += command.step) {
|
||||||
|
keys.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
exports.getKeyIndexes = getKeyIndexes;
|
||||||
|
function getExternalKeyNameLength(key) {
|
||||||
|
if (typeof key !== "string") {
|
||||||
|
key = String(key);
|
||||||
|
}
|
||||||
|
const hashPos = key.indexOf("->");
|
||||||
|
return hashPos === -1 ? key.length : hashPos;
|
||||||
|
}
|
||||||
52
backend/node_modules/@ioredis/commands/package.json
generated
vendored
Normal file
52
backend/node_modules/@ioredis/commands/package.json
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "@ioredis/commands",
|
||||||
|
"version": "1.5.1",
|
||||||
|
"description": "Redis commands",
|
||||||
|
"main": "built/index.js",
|
||||||
|
"files": [
|
||||||
|
"built/",
|
||||||
|
"commands.json"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"test": "mocha",
|
||||||
|
"build": "rm -rf built && tsc",
|
||||||
|
"gen": "node tools/build",
|
||||||
|
"lint": "standard --fix --verbose | snazzy",
|
||||||
|
"release": "release-it"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ioredis/commands.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"redis",
|
||||||
|
"commands",
|
||||||
|
"prefix"
|
||||||
|
],
|
||||||
|
"author": "Zihua Li <i@zihua.li> (http://zihua.li)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ioredis/commands/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ioredis/commands",
|
||||||
|
"devDependencies": {
|
||||||
|
"@release-it/conventional-changelog": "^4.2.0",
|
||||||
|
"@semantic-release/changelog": "^6.0.1",
|
||||||
|
"@semantic-release/commit-analyzer": "^9.0.2",
|
||||||
|
"@semantic-release/git": "^10.0.1",
|
||||||
|
"chai": "^4.3.6",
|
||||||
|
"ioredis": "^5.0.6",
|
||||||
|
"mocha": "^9.2.1",
|
||||||
|
"release-it": "^14.12.5",
|
||||||
|
"safe-stable-stringify": "^2.3.1",
|
||||||
|
"semantic-release": "^19.0.2",
|
||||||
|
"snazzy": "^9.0.0",
|
||||||
|
"standard": "^16.0.4",
|
||||||
|
"typescript": "^4.6.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
backend/node_modules/cluster-key-slot/.eslintrc
generated
vendored
Normal file
16
backend/node_modules/cluster-key-slot/.eslintrc
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "airbnb-base/legacy",
|
||||||
|
"parserOptions":{
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"experimentalObjectRestSpread": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"max-len": 0,
|
||||||
|
"no-plusplus": 0,
|
||||||
|
"no-bitwise": 0,
|
||||||
|
"no-param-reassign": 0,
|
||||||
|
"no-undef": 0
|
||||||
|
},
|
||||||
|
"globals": {}
|
||||||
|
}
|
||||||
13
backend/node_modules/cluster-key-slot/LICENSE
generated
vendored
Normal file
13
backend/node_modules/cluster-key-slot/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Copyright (c) 2018 Mike Diarmid (Salakar) <mike.diarmid@gmail.com>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this library except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
61
backend/node_modules/cluster-key-slot/README.md
generated
vendored
Normal file
61
backend/node_modules/cluster-key-slot/README.md
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
[](https://coveralls.io/github/Salakar/cluster-key-slot?branch=master)
|
||||||
|

|
||||||
|
[](https://www.npmjs.com/package/cluster-key-slot)
|
||||||
|
[](https://david-dm.org/Salakar/cluster-key-slot)
|
||||||
|
[](/LICENSE)
|
||||||
|
<a href="https://twitter.com/mikediarmid"><img src="https://img.shields.io/twitter/follow/mikediarmid.svg?style=social&label=Follow" alt="Follow on Twitter"></a>
|
||||||
|
|
||||||
|
# Redis Key Slot Calculator
|
||||||
|
|
||||||
|
A high performance redis cluster key slot calculator for node redis clients e.g. [node_redis](https://github.com/NodeRedis/node_redis), [ioredis](https://github.com/luin/ioredis) and [redis-clustr](https://github.com/gosquared/redis-clustr/).
|
||||||
|
|
||||||
|
This also handles key tags such as `somekey{actualTag}`.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Install with [NPM](https://npmjs.org/):
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install cluster-key-slot --save
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
const calculateSlot = require('cluster-key-slot');
|
||||||
|
const calculateMultipleSlots = require('cluster-key-slot').generateMulti;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// a single slot number
|
||||||
|
const slot = calculateSlot('test:key:{butOnlyThis}redis');
|
||||||
|
// Buffer is also supported
|
||||||
|
const anotherSlot = calculateSlot(Buffer.from([0x7b, 0x7d, 0x2a]));
|
||||||
|
|
||||||
|
// multiple keys - multi returns a single key slot number, returns -1 if any
|
||||||
|
// of the keys does not match the base slot number (base is defaulted to first keys slot)
|
||||||
|
// This is useful to quickly determine a singe slot for multi keys operations.
|
||||||
|
const slotForRedisMulti = calculateMultipleSlots([
|
||||||
|
'test:key:{butOnlyThis}redis',
|
||||||
|
'something:key45:{butOnlyThis}hello',
|
||||||
|
'example:key46:{butOnlyThis}foobar',
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
`OLD` in these benchmarks refers to the `ioredis` crc calc and many of the other calculators that use `Buffer`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
node -v ✔ 16.38G RAM 10:29:07
|
||||||
|
v10.15.3
|
||||||
|
|
||||||
|
NEW tags x 721,445 ops/sec ±0.44% (90 runs sampled)
|
||||||
|
OLD tags x 566,777 ops/sec ±0.97% (96 runs sampled)
|
||||||
|
NEW without tags x 2,054,845 ops/sec ±1.77% (92 runs sampled)
|
||||||
|
OLD without tags x 865,839 ops/sec ±0.43% (96 runs sampled)
|
||||||
|
NEW without tags singular x 6,354,097 ops/sec ±1.25% (94 runs sampled)
|
||||||
|
OLD without tags singular x 4,012,250 ops/sec ±0.96% (94 runs sampled)
|
||||||
|
NEW tags (Buffer) x 552,346 ops/sec ±1.35% (92 runs sampled)
|
||||||
|
```
|
||||||
|
|
||||||
10
backend/node_modules/cluster-key-slot/index.d.ts
generated
vendored
Normal file
10
backend/node_modules/cluster-key-slot/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
declare module 'cluster-key-slot' {
|
||||||
|
// Convert a string or Buffer into a redis slot hash.
|
||||||
|
function calculate(value: string | Buffer): number;
|
||||||
|
|
||||||
|
// Convert an array of multiple strings or Buffers into a redis slot hash.
|
||||||
|
// Returns -1 if one of the keys is not for the same slot as the others
|
||||||
|
export function generateMulti(values: Array<string | Buffer>): number;
|
||||||
|
|
||||||
|
export = calculate;
|
||||||
|
}
|
||||||
166
backend/node_modules/cluster-key-slot/lib/index.js
generated
vendored
Normal file
166
backend/node_modules/cluster-key-slot/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2001-2010 Georges Menie (www.menie.org)
|
||||||
|
* Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style)
|
||||||
|
* Copyright 2015 Zihua Li (http://zihua.li) (ported to JavaScript)
|
||||||
|
* Copyright 2016 Mike Diarmid (http://github.com/salakar) (re-write for performance, ~700% perf inc)
|
||||||
|
* All rights reserved.
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the University of California, Berkeley nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* CRC16 implementation according to CCITT standards.
|
||||||
|
*
|
||||||
|
* Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
|
||||||
|
* following parameters:
|
||||||
|
*
|
||||||
|
* Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
|
||||||
|
* Width : 16 bit
|
||||||
|
* Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
|
||||||
|
* Initialization : 0000
|
||||||
|
* Reflect Input byte : False
|
||||||
|
* Reflect Output CRC : False
|
||||||
|
* Xor constant to output CRC : 0000
|
||||||
|
* Output for "123456789" : 31C3
|
||||||
|
*/
|
||||||
|
|
||||||
|
var lookup = [
|
||||||
|
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
||||||
|
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
|
||||||
|
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
|
||||||
|
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
|
||||||
|
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
|
||||||
|
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
||||||
|
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
|
||||||
|
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
|
||||||
|
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||||
|
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
|
||||||
|
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
|
||||||
|
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
||||||
|
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
|
||||||
|
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
|
||||||
|
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
||||||
|
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
|
||||||
|
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
|
||||||
|
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||||
|
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
|
||||||
|
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||||
|
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
|
||||||
|
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||||
|
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
|
||||||
|
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||||
|
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
|
||||||
|
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
|
||||||
|
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
|
||||||
|
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
|
||||||
|
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
|
||||||
|
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
||||||
|
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
|
||||||
|
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string to a UTF8 array - faster than via buffer
|
||||||
|
* @param str
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
var toUTF8Array = function toUTF8Array(str) {
|
||||||
|
var char;
|
||||||
|
var i = 0;
|
||||||
|
var p = 0;
|
||||||
|
var utf8 = [];
|
||||||
|
var len = str.length;
|
||||||
|
|
||||||
|
for (; i < len; i++) {
|
||||||
|
char = str.charCodeAt(i);
|
||||||
|
if (char < 128) {
|
||||||
|
utf8[p++] = char;
|
||||||
|
} else if (char < 2048) {
|
||||||
|
utf8[p++] = (char >> 6) | 192;
|
||||||
|
utf8[p++] = (char & 63) | 128;
|
||||||
|
} else if (
|
||||||
|
((char & 0xFC00) === 0xD800) && (i + 1) < str.length &&
|
||||||
|
((str.charCodeAt(i + 1) & 0xFC00) === 0xDC00)) {
|
||||||
|
char = 0x10000 + ((char & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
|
||||||
|
utf8[p++] = (char >> 18) | 240;
|
||||||
|
utf8[p++] = ((char >> 12) & 63) | 128;
|
||||||
|
utf8[p++] = ((char >> 6) & 63) | 128;
|
||||||
|
utf8[p++] = (char & 63) | 128;
|
||||||
|
} else {
|
||||||
|
utf8[p++] = (char >> 12) | 224;
|
||||||
|
utf8[p++] = ((char >> 6) & 63) | 128;
|
||||||
|
utf8[p++] = (char & 63) | 128;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utf8;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string into a redis slot hash.
|
||||||
|
* @param str
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
var generate = module.exports = function generate(str) {
|
||||||
|
var char;
|
||||||
|
var i = 0;
|
||||||
|
var start = -1;
|
||||||
|
var result = 0;
|
||||||
|
var resultHash = 0;
|
||||||
|
var utf8 = typeof str === 'string' ? toUTF8Array(str) : str;
|
||||||
|
var len = utf8.length;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
char = utf8[i++];
|
||||||
|
if (start === -1) {
|
||||||
|
if (char === 0x7B) {
|
||||||
|
start = i;
|
||||||
|
}
|
||||||
|
} else if (char !== 0x7D) {
|
||||||
|
resultHash = lookup[(char ^ (resultHash >> 8)) & 0xFF] ^ (resultHash << 8);
|
||||||
|
} else if (i - 1 !== start) {
|
||||||
|
return resultHash & 0x3FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = lookup[(char ^ (result >> 8)) & 0xFF] ^ (result << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result & 0x3FFF;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array of multiple strings into a redis slot hash.
|
||||||
|
* Returns -1 if one of the keys is not for the same slot as the others
|
||||||
|
* @param keys
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
module.exports.generateMulti = function generateMulti(keys) {
|
||||||
|
var i = 1;
|
||||||
|
var len = keys.length;
|
||||||
|
var base = generate(keys[0]);
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
if (generate(keys[i++]) !== base) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
};
|
||||||
56
backend/node_modules/cluster-key-slot/package.json
generated
vendored
Normal file
56
backend/node_modules/cluster-key-slot/package.json
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"name": "cluster-key-slot",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"description": "Generates CRC hashes for strings - for use by node redis clients to determine key slots.",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"benchmark": "node ./benchmark",
|
||||||
|
"posttest": "eslint ./lib && npm run coveralls",
|
||||||
|
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
||||||
|
"test": "node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -R spec",
|
||||||
|
"coverage:check": "node ./node_modules/istanbul/lib/cli.js check-coverage --branch 100 --statement 100"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/Salakar/cluster-key-slot.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"redis",
|
||||||
|
"hash",
|
||||||
|
"crc",
|
||||||
|
"slot",
|
||||||
|
"calc",
|
||||||
|
"javascript",
|
||||||
|
"node",
|
||||||
|
"node_redis",
|
||||||
|
"ioredis"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"benchmark": "^2.1.0",
|
||||||
|
"codeclimate-test-reporter": "^0.3.1",
|
||||||
|
"coveralls": "^2.11.9",
|
||||||
|
"eslint": "^3.5.0",
|
||||||
|
"eslint-config-airbnb-base": "^7.1.0",
|
||||||
|
"eslint-plugin-import": "^1.8.0",
|
||||||
|
"istanbul": "^0.4.0",
|
||||||
|
"mocha": "^3.0.2"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Mike Diarmid",
|
||||||
|
"email": "mike.diarmid@gmail.com",
|
||||||
|
"url": "http://github.com/Salakar/"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Salakar/cluster-key-slot/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Salakar/cluster-key-slot#readme",
|
||||||
|
"directories": {
|
||||||
|
"test": "test",
|
||||||
|
"lib": "lib"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
backend/node_modules/denque/CHANGELOG.md
generated
vendored
Normal file
29
backend/node_modules/denque/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
- fix: issue where `clear()` is still keeping references to the elements (#47)
|
||||||
|
- refactor: performance optimizations for growth and array copy (#43)
|
||||||
|
- refactor: performance optimizations for toArray and fromArray (#46)
|
||||||
|
- test: add additional benchmarks for queue growth and `toArray` (#45)
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
- fix(types): incorrect return type on `size()`
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
- fix!: `push` & `unshift` now accept `undefined` values to match behaviour of `Array` (fixes #25) (#35)
|
||||||
|
- This is only a **BREAKING** change if you are currently expecting `push(undefined)` and `unshift(undefined)` to do
|
||||||
|
nothing - the new behaviour now correctly adds undefined values to the queue.
|
||||||
|
- **Note**: behaviour of `push()` & `unshift()` (no arguments) remains unchanged (nothing gets added to the queue).
|
||||||
|
- **Note**: If you need to differentiate between `undefined` values in the queue and the return value of `pop()` then
|
||||||
|
check the queue `.length` before popping.
|
||||||
|
- fix: incorrect methods in types definition file
|
||||||
|
|
||||||
|
## 1.5.1
|
||||||
|
|
||||||
|
- perf: minor performance tweak when growing queue size (#29)
|
||||||
|
|
||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
- feat: adds capacity option for circular buffers (#27)
|
||||||
|
|
||||||
201
backend/node_modules/denque/LICENSE
generated
vendored
Normal file
201
backend/node_modules/denque/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2018-present Invertase Limited
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
77
backend/node_modules/denque/README.md
generated
vendored
Normal file
77
backend/node_modules/denque/README.md
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<p align="center">
|
||||||
|
<h1 align="center">Denque</h1>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.npmjs.com/package/denque"><img src="https://img.shields.io/npm/dm/denque.svg?style=flat-square" alt="NPM downloads"></a>
|
||||||
|
<a href="https://www.npmjs.com/package/denque"><img src="https://img.shields.io/npm/v/denque.svg?style=flat-square" alt="NPM version"></a>
|
||||||
|
<a href="https://github.com/invertase/denque/actions/workflows/testing.yam"><img src="https://github.com/invertase/denque/actions/workflows/testing.yaml/badge.svg" alt="Tests status"></a>
|
||||||
|
<a href="https://codecov.io/gh/invertase/denque"><img src="https://codecov.io/gh/invertase/denque/branch/master/graph/badge.svg?token=rn91iI4bSe" alt="Coverage"></a>
|
||||||
|
<a href="/LICENSE"><img src="https://img.shields.io/npm/l/denque.svg?style=flat-square" alt="License"></a>
|
||||||
|
<a href="https://twitter.com/invertaseio"><img src="https://img.shields.io/twitter/follow/invertaseio.svg?style=social&label=Follow" alt="Follow on Twitter"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Denque is a well tested, extremely fast and lightweight [double-ended queue](http://en.wikipedia.org/wiki/Double-ended_queue)
|
||||||
|
implementation with zero dependencies and includes TypeScript types.
|
||||||
|
|
||||||
|
Double-ended queues can also be used as a:
|
||||||
|
|
||||||
|
- [Stack](http://en.wikipedia.org/wiki/Stack_\(abstract_data_type\))
|
||||||
|
- [Queue](http://en.wikipedia.org/wiki/Queue_\(data_structure\))
|
||||||
|
|
||||||
|
This implementation is currently the fastest available, even faster than `double-ended-queue`, see the [benchmarks](https://docs.page/invertase/denque/benchmarks).
|
||||||
|
|
||||||
|
Every queue operation is done at a constant `O(1)` - including random access from `.peekAt(index)`.
|
||||||
|
|
||||||
|
**Works on all node versions >= v0.10**
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Install the package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install denque
|
||||||
|
```
|
||||||
|
|
||||||
|
Create and consume a queue:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const Denque = require("denque");
|
||||||
|
|
||||||
|
const denque = new Denque([1,2,3,4]);
|
||||||
|
denque.shift(); // 1
|
||||||
|
denque.pop(); // 4
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
See the [API reference documentation](https://docs.page/invertase/denque/api) for more examples.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Who's using it?
|
||||||
|
|
||||||
|
- [Kafka Node.js client](https://www.npmjs.com/package/kafka-node)
|
||||||
|
- [MariaDB Node.js client](https://www.npmjs.com/package/mariadb)
|
||||||
|
- [MongoDB Node.js client](https://www.npmjs.com/package/mongodb)
|
||||||
|
- [MySQL Node.js client](https://www.npmjs.com/package/mysql2)
|
||||||
|
- [Redis Node.js clients](https://www.npmjs.com/package/redis)
|
||||||
|
|
||||||
|
... and [many more](https://www.npmjs.com/browse/depended/denque).
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
- See [LICENSE](/LICENSE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://invertase.io/?utm_source=readme&utm_medium=footer&utm_campaign=denque">
|
||||||
|
<img width="75px" src="https://static.invertase.io/assets/invertase/invertase-rounded-avatar.png">
|
||||||
|
</a>
|
||||||
|
<p align="center">
|
||||||
|
Built and maintained by <a href="https://invertase.io/?utm_source=readme&utm_medium=footer&utm_campaign=denque">Invertase</a>.
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
47
backend/node_modules/denque/index.d.ts
generated
vendored
Normal file
47
backend/node_modules/denque/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
declare class Denque<T = any> {
|
||||||
|
length: number;
|
||||||
|
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
constructor(array: T[]);
|
||||||
|
|
||||||
|
constructor(array: T[], options: IDenqueOptions);
|
||||||
|
|
||||||
|
push(item: T): number;
|
||||||
|
|
||||||
|
unshift(item: T): number;
|
||||||
|
|
||||||
|
pop(): T | undefined;
|
||||||
|
|
||||||
|
shift(): T | undefined;
|
||||||
|
|
||||||
|
peekBack(): T | undefined;
|
||||||
|
|
||||||
|
peekFront(): T | undefined;
|
||||||
|
|
||||||
|
peekAt(index: number): T | undefined;
|
||||||
|
|
||||||
|
get(index: number): T | undefined;
|
||||||
|
|
||||||
|
remove(index: number, count: number): T[];
|
||||||
|
|
||||||
|
removeOne(index: number): T | undefined;
|
||||||
|
|
||||||
|
splice(index: number, count: number, ...item: T[]): T[] | undefined;
|
||||||
|
|
||||||
|
isEmpty(): boolean;
|
||||||
|
|
||||||
|
clear(): void;
|
||||||
|
|
||||||
|
size(): number;
|
||||||
|
|
||||||
|
toString(): string;
|
||||||
|
|
||||||
|
toArray(): T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDenqueOptions {
|
||||||
|
capacity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export = Denque;
|
||||||
481
backend/node_modules/denque/index.js
generated
vendored
Normal file
481
backend/node_modules/denque/index.js
generated
vendored
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom implementation of a double ended queue.
|
||||||
|
*/
|
||||||
|
function Denque(array, options) {
|
||||||
|
var options = options || {};
|
||||||
|
this._capacity = options.capacity;
|
||||||
|
|
||||||
|
this._head = 0;
|
||||||
|
this._tail = 0;
|
||||||
|
|
||||||
|
if (Array.isArray(array)) {
|
||||||
|
this._fromArray(array);
|
||||||
|
} else {
|
||||||
|
this._capacityMask = 0x3;
|
||||||
|
this._list = new Array(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --------------
|
||||||
|
* PUBLIC API
|
||||||
|
* -------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item at the specified index from the list.
|
||||||
|
* 0 is the first element, 1 is the second, and so on...
|
||||||
|
* Elements at negative values are that many from the end: -1 is one before the end
|
||||||
|
* (the last element), -2 is two before the end (one before last), etc.
|
||||||
|
* @param index
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Denque.prototype.peekAt = function peekAt(index) {
|
||||||
|
var i = index;
|
||||||
|
// expect a number or return undefined
|
||||||
|
if ((i !== (i | 0))) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
var len = this.size();
|
||||||
|
if (i >= len || i < -len) return undefined;
|
||||||
|
if (i < 0) i += len;
|
||||||
|
i = (this._head + i) & this._capacityMask;
|
||||||
|
return this._list[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for peekAt()
|
||||||
|
* @param i
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Denque.prototype.get = function get(i) {
|
||||||
|
return this.peekAt(i);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first item in the list without removing it.
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Denque.prototype.peek = function peek() {
|
||||||
|
if (this._head === this._tail) return undefined;
|
||||||
|
return this._list[this._head];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for peek()
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Denque.prototype.peekFront = function peekFront() {
|
||||||
|
return this.peek();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item that is at the back of the queue without removing it.
|
||||||
|
* Uses peekAt(-1)
|
||||||
|
*/
|
||||||
|
Denque.prototype.peekBack = function peekBack() {
|
||||||
|
return this.peekAt(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current length of the queue
|
||||||
|
* @return {Number}
|
||||||
|
*/
|
||||||
|
Object.defineProperty(Denque.prototype, 'length', {
|
||||||
|
get: function length() {
|
||||||
|
return this.size();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of items on the list, or 0 if empty.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
Denque.prototype.size = function size() {
|
||||||
|
if (this._head === this._tail) return 0;
|
||||||
|
if (this._head < this._tail) return this._tail - this._head;
|
||||||
|
else return this._capacityMask + 1 - (this._head - this._tail);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an item at the beginning of the list.
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
|
Denque.prototype.unshift = function unshift(item) {
|
||||||
|
if (arguments.length === 0) return this.size();
|
||||||
|
var len = this._list.length;
|
||||||
|
this._head = (this._head - 1 + len) & this._capacityMask;
|
||||||
|
this._list[this._head] = item;
|
||||||
|
if (this._tail === this._head) this._growArray();
|
||||||
|
if (this._capacity && this.size() > this._capacity) this.pop();
|
||||||
|
if (this._head < this._tail) return this._tail - this._head;
|
||||||
|
else return this._capacityMask + 1 - (this._head - this._tail);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove and return the first item on the list,
|
||||||
|
* Returns undefined if the list is empty.
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Denque.prototype.shift = function shift() {
|
||||||
|
var head = this._head;
|
||||||
|
if (head === this._tail) return undefined;
|
||||||
|
var item = this._list[head];
|
||||||
|
this._list[head] = undefined;
|
||||||
|
this._head = (head + 1) & this._capacityMask;
|
||||||
|
if (head < 2 && this._tail > 10000 && this._tail <= this._list.length >>> 2) this._shrinkArray();
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an item to the bottom of the list.
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
|
Denque.prototype.push = function push(item) {
|
||||||
|
if (arguments.length === 0) return this.size();
|
||||||
|
var tail = this._tail;
|
||||||
|
this._list[tail] = item;
|
||||||
|
this._tail = (tail + 1) & this._capacityMask;
|
||||||
|
if (this._tail === this._head) {
|
||||||
|
this._growArray();
|
||||||
|
}
|
||||||
|
if (this._capacity && this.size() > this._capacity) {
|
||||||
|
this.shift();
|
||||||
|
}
|
||||||
|
if (this._head < this._tail) return this._tail - this._head;
|
||||||
|
else return this._capacityMask + 1 - (this._head - this._tail);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove and return the last item on the list.
|
||||||
|
* Returns undefined if the list is empty.
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Denque.prototype.pop = function pop() {
|
||||||
|
var tail = this._tail;
|
||||||
|
if (tail === this._head) return undefined;
|
||||||
|
var len = this._list.length;
|
||||||
|
this._tail = (tail - 1 + len) & this._capacityMask;
|
||||||
|
var item = this._list[this._tail];
|
||||||
|
this._list[this._tail] = undefined;
|
||||||
|
if (this._head < 2 && tail > 10000 && tail <= len >>> 2) this._shrinkArray();
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove and return the item at the specified index from the list.
|
||||||
|
* Returns undefined if the list is empty.
|
||||||
|
* @param index
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Denque.prototype.removeOne = function removeOne(index) {
|
||||||
|
var i = index;
|
||||||
|
// expect a number or return undefined
|
||||||
|
if ((i !== (i | 0))) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
if (this._head === this._tail) return void 0;
|
||||||
|
var size = this.size();
|
||||||
|
var len = this._list.length;
|
||||||
|
if (i >= size || i < -size) return void 0;
|
||||||
|
if (i < 0) i += size;
|
||||||
|
i = (this._head + i) & this._capacityMask;
|
||||||
|
var item = this._list[i];
|
||||||
|
var k;
|
||||||
|
if (index < size / 2) {
|
||||||
|
for (k = index; k > 0; k--) {
|
||||||
|
this._list[i] = this._list[i = (i - 1 + len) & this._capacityMask];
|
||||||
|
}
|
||||||
|
this._list[i] = void 0;
|
||||||
|
this._head = (this._head + 1 + len) & this._capacityMask;
|
||||||
|
} else {
|
||||||
|
for (k = size - 1 - index; k > 0; k--) {
|
||||||
|
this._list[i] = this._list[i = (i + 1 + len) & this._capacityMask];
|
||||||
|
}
|
||||||
|
this._list[i] = void 0;
|
||||||
|
this._tail = (this._tail - 1 + len) & this._capacityMask;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove number of items from the specified index from the list.
|
||||||
|
* Returns array of removed items.
|
||||||
|
* Returns undefined if the list is empty.
|
||||||
|
* @param index
|
||||||
|
* @param count
|
||||||
|
* @returns {array}
|
||||||
|
*/
|
||||||
|
Denque.prototype.remove = function remove(index, count) {
|
||||||
|
var i = index;
|
||||||
|
var removed;
|
||||||
|
var del_count = count;
|
||||||
|
// expect a number or return undefined
|
||||||
|
if ((i !== (i | 0))) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
if (this._head === this._tail) return void 0;
|
||||||
|
var size = this.size();
|
||||||
|
var len = this._list.length;
|
||||||
|
if (i >= size || i < -size || count < 1) return void 0;
|
||||||
|
if (i < 0) i += size;
|
||||||
|
if (count === 1 || !count) {
|
||||||
|
removed = new Array(1);
|
||||||
|
removed[0] = this.removeOne(i);
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
if (i === 0 && i + count >= size) {
|
||||||
|
removed = this.toArray();
|
||||||
|
this.clear();
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
if (i + count > size) count = size - i;
|
||||||
|
var k;
|
||||||
|
removed = new Array(count);
|
||||||
|
for (k = 0; k < count; k++) {
|
||||||
|
removed[k] = this._list[(this._head + i + k) & this._capacityMask];
|
||||||
|
}
|
||||||
|
i = (this._head + i) & this._capacityMask;
|
||||||
|
if (index + count === size) {
|
||||||
|
this._tail = (this._tail - count + len) & this._capacityMask;
|
||||||
|
for (k = count; k > 0; k--) {
|
||||||
|
this._list[i = (i + 1 + len) & this._capacityMask] = void 0;
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
if (index === 0) {
|
||||||
|
this._head = (this._head + count + len) & this._capacityMask;
|
||||||
|
for (k = count - 1; k > 0; k--) {
|
||||||
|
this._list[i = (i + 1 + len) & this._capacityMask] = void 0;
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
if (i < size / 2) {
|
||||||
|
this._head = (this._head + index + count + len) & this._capacityMask;
|
||||||
|
for (k = index; k > 0; k--) {
|
||||||
|
this.unshift(this._list[i = (i - 1 + len) & this._capacityMask]);
|
||||||
|
}
|
||||||
|
i = (this._head - 1 + len) & this._capacityMask;
|
||||||
|
while (del_count > 0) {
|
||||||
|
this._list[i = (i - 1 + len) & this._capacityMask] = void 0;
|
||||||
|
del_count--;
|
||||||
|
}
|
||||||
|
if (index < 0) this._tail = i;
|
||||||
|
} else {
|
||||||
|
this._tail = i;
|
||||||
|
i = (i + count + len) & this._capacityMask;
|
||||||
|
for (k = size - (count + index); k > 0; k--) {
|
||||||
|
this.push(this._list[i++]);
|
||||||
|
}
|
||||||
|
i = this._tail;
|
||||||
|
while (del_count > 0) {
|
||||||
|
this._list[i = (i + 1 + len) & this._capacityMask] = void 0;
|
||||||
|
del_count--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._head < 2 && this._tail > 10000 && this._tail <= len >>> 2) this._shrinkArray();
|
||||||
|
return removed;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native splice implementation.
|
||||||
|
* Remove number of items from the specified index from the list and/or add new elements.
|
||||||
|
* Returns array of removed items or empty array if count == 0.
|
||||||
|
* Returns undefined if the list is empty.
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
* @param count
|
||||||
|
* @param {...*} [elements]
|
||||||
|
* @returns {array}
|
||||||
|
*/
|
||||||
|
Denque.prototype.splice = function splice(index, count) {
|
||||||
|
var i = index;
|
||||||
|
// expect a number or return undefined
|
||||||
|
if ((i !== (i | 0))) {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
var size = this.size();
|
||||||
|
if (i < 0) i += size;
|
||||||
|
if (i > size) return void 0;
|
||||||
|
if (arguments.length > 2) {
|
||||||
|
var k;
|
||||||
|
var temp;
|
||||||
|
var removed;
|
||||||
|
var arg_len = arguments.length;
|
||||||
|
var len = this._list.length;
|
||||||
|
var arguments_index = 2;
|
||||||
|
if (!size || i < size / 2) {
|
||||||
|
temp = new Array(i);
|
||||||
|
for (k = 0; k < i; k++) {
|
||||||
|
temp[k] = this._list[(this._head + k) & this._capacityMask];
|
||||||
|
}
|
||||||
|
if (count === 0) {
|
||||||
|
removed = [];
|
||||||
|
if (i > 0) {
|
||||||
|
this._head = (this._head + i + len) & this._capacityMask;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removed = this.remove(i, count);
|
||||||
|
this._head = (this._head + i + len) & this._capacityMask;
|
||||||
|
}
|
||||||
|
while (arg_len > arguments_index) {
|
||||||
|
this.unshift(arguments[--arg_len]);
|
||||||
|
}
|
||||||
|
for (k = i; k > 0; k--) {
|
||||||
|
this.unshift(temp[k - 1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
temp = new Array(size - (i + count));
|
||||||
|
var leng = temp.length;
|
||||||
|
for (k = 0; k < leng; k++) {
|
||||||
|
temp[k] = this._list[(this._head + i + count + k) & this._capacityMask];
|
||||||
|
}
|
||||||
|
if (count === 0) {
|
||||||
|
removed = [];
|
||||||
|
if (i != size) {
|
||||||
|
this._tail = (this._head + i + len) & this._capacityMask;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removed = this.remove(i, count);
|
||||||
|
this._tail = (this._tail - leng + len) & this._capacityMask;
|
||||||
|
}
|
||||||
|
while (arguments_index < arg_len) {
|
||||||
|
this.push(arguments[arguments_index++]);
|
||||||
|
}
|
||||||
|
for (k = 0; k < leng; k++) {
|
||||||
|
this.push(temp[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
} else {
|
||||||
|
return this.remove(i, count);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft clear - does not reset capacity.
|
||||||
|
*/
|
||||||
|
Denque.prototype.clear = function clear() {
|
||||||
|
this._list = new Array(this._list.length);
|
||||||
|
this._head = 0;
|
||||||
|
this._tail = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true or false whether the list is empty.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
Denque.prototype.isEmpty = function isEmpty() {
|
||||||
|
return this._head === this._tail;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all queue items.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
Denque.prototype.toArray = function toArray() {
|
||||||
|
return this._copyArray(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* -------------
|
||||||
|
* INTERNALS
|
||||||
|
* -------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills the queue with items from an array
|
||||||
|
* For use in the constructor
|
||||||
|
* @param array
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Denque.prototype._fromArray = function _fromArray(array) {
|
||||||
|
var length = array.length;
|
||||||
|
var capacity = this._nextPowerOf2(length);
|
||||||
|
|
||||||
|
this._list = new Array(capacity);
|
||||||
|
this._capacityMask = capacity - 1;
|
||||||
|
this._tail = length;
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) this._list[i] = array[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param fullCopy
|
||||||
|
* @param size Initialize the array with a specific size. Will default to the current list size
|
||||||
|
* @returns {Array}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Denque.prototype._copyArray = function _copyArray(fullCopy, size) {
|
||||||
|
var src = this._list;
|
||||||
|
var capacity = src.length;
|
||||||
|
var length = this.length;
|
||||||
|
size = size | length;
|
||||||
|
|
||||||
|
// No prealloc requested and the buffer is contiguous
|
||||||
|
if (size == length && this._head < this._tail) {
|
||||||
|
// Simply do a fast slice copy
|
||||||
|
return this._list.slice(this._head, this._tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dest = new Array(size);
|
||||||
|
|
||||||
|
var k = 0;
|
||||||
|
var i;
|
||||||
|
if (fullCopy || this._head > this._tail) {
|
||||||
|
for (i = this._head; i < capacity; i++) dest[k++] = src[i];
|
||||||
|
for (i = 0; i < this._tail; i++) dest[k++] = src[i];
|
||||||
|
} else {
|
||||||
|
for (i = this._head; i < this._tail; i++) dest[k++] = src[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grows the internal list array.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Denque.prototype._growArray = function _growArray() {
|
||||||
|
if (this._head != 0) {
|
||||||
|
// double array size and copy existing data, head to end, then beginning to tail.
|
||||||
|
var newList = this._copyArray(true, this._list.length << 1);
|
||||||
|
|
||||||
|
this._tail = this._list.length;
|
||||||
|
this._head = 0;
|
||||||
|
|
||||||
|
this._list = newList;
|
||||||
|
} else {
|
||||||
|
this._tail = this._list.length;
|
||||||
|
this._list.length <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._capacityMask = (this._capacityMask << 1) | 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shrinks the internal list array.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Denque.prototype._shrinkArray = function _shrinkArray() {
|
||||||
|
this._list.length >>>= 1;
|
||||||
|
this._capacityMask >>>= 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the next power of 2, at least 4
|
||||||
|
* @private
|
||||||
|
* @param {number} num
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
Denque.prototype._nextPowerOf2 = function _nextPowerOf2(num) {
|
||||||
|
var log2 = Math.log(num) / Math.log(2);
|
||||||
|
var nextPow2 = 1 << (log2 + 1);
|
||||||
|
|
||||||
|
return Math.max(nextPow2, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Denque;
|
||||||
58
backend/node_modules/denque/package.json
generated
vendored
Normal file
58
backend/node_modules/denque/package.json
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "denque",
|
||||||
|
"version": "2.1.0",
|
||||||
|
"description": "The fastest javascript implementation of a double-ended queue. Used by the official Redis, MongoDB, MariaDB & MySQL libraries for Node.js and many other libraries. Maintains compatability with deque.",
|
||||||
|
"main": "index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"data-structure",
|
||||||
|
"data-structures",
|
||||||
|
"queue",
|
||||||
|
"double",
|
||||||
|
"end",
|
||||||
|
"ended",
|
||||||
|
"deque",
|
||||||
|
"denque",
|
||||||
|
"double-ended-queue"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "istanbul cover --report lcov _mocha && npm run typescript",
|
||||||
|
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
||||||
|
"typescript": "tsc --project ./test/type/tsconfig.json",
|
||||||
|
"benchmark_thousand": "node benchmark/thousand",
|
||||||
|
"benchmark_2mil": "node benchmark/two_million",
|
||||||
|
"benchmark_splice": "node benchmark/splice",
|
||||||
|
"benchmark_remove": "node benchmark/remove",
|
||||||
|
"benchmark_removeOne": "node benchmark/removeOne",
|
||||||
|
"benchmark_growth": "node benchmark/growth",
|
||||||
|
"benchmark_toArray": "node benchmark/toArray",
|
||||||
|
"benchmark_fromArray": "node benchmark/fromArray"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/invertase/denque.git"
|
||||||
|
},
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Invertase",
|
||||||
|
"email": "oss@invertase.io",
|
||||||
|
"url": "http://github.com/invertase/"
|
||||||
|
},
|
||||||
|
"contributors": [
|
||||||
|
"Mike Diarmid (Salakar) <mike@invertase.io>"
|
||||||
|
],
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/invertase/denque/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://docs.page/invertase/denque",
|
||||||
|
"devDependencies": {
|
||||||
|
"benchmark": "^2.1.4",
|
||||||
|
"codecov": "^3.8.3",
|
||||||
|
"double-ended-queue": "^2.1.0-0",
|
||||||
|
"istanbul": "^0.4.5",
|
||||||
|
"mocha": "^3.5.3",
|
||||||
|
"typescript": "^3.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
backend/node_modules/ioredis/LICENSE
generated
vendored
Normal file
21
backend/node_modules/ioredis/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2022 Zihua Li
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
1498
backend/node_modules/ioredis/README.md
generated
vendored
Normal file
1498
backend/node_modules/ioredis/README.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
165
backend/node_modules/ioredis/built/Command.d.ts
generated
vendored
Normal file
165
backend/node_modules/ioredis/built/Command.d.ts
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { Callback, Respondable, CommandParameter } from "./types";
|
||||||
|
export declare type ArgumentType = string | Buffer | number | (string | Buffer | number | any[])[];
|
||||||
|
interface CommandOptions {
|
||||||
|
/**
|
||||||
|
* Set the encoding of the reply, by default buffer will be returned.
|
||||||
|
*/
|
||||||
|
replyEncoding?: BufferEncoding | null;
|
||||||
|
errorStack?: Error;
|
||||||
|
keyPrefix?: string;
|
||||||
|
/**
|
||||||
|
* Force the command to be readOnly so it will also execute on slaves
|
||||||
|
*/
|
||||||
|
readOnly?: boolean;
|
||||||
|
}
|
||||||
|
declare type ArgumentTransformer = (args: any[]) => any[];
|
||||||
|
declare type ReplyTransformer = (reply: any) => any;
|
||||||
|
export interface CommandNameFlags {
|
||||||
|
VALID_IN_SUBSCRIBER_MODE: [
|
||||||
|
"subscribe",
|
||||||
|
"psubscribe",
|
||||||
|
"unsubscribe",
|
||||||
|
"punsubscribe",
|
||||||
|
"ssubscribe",
|
||||||
|
"sunsubscribe",
|
||||||
|
"ping",
|
||||||
|
"quit"
|
||||||
|
];
|
||||||
|
VALID_IN_MONITOR_MODE: ["monitor", "auth"];
|
||||||
|
ENTER_SUBSCRIBER_MODE: ["subscribe", "psubscribe", "ssubscribe"];
|
||||||
|
EXIT_SUBSCRIBER_MODE: ["unsubscribe", "punsubscribe", "sunsubscribe"];
|
||||||
|
WILL_DISCONNECT: ["quit"];
|
||||||
|
HANDSHAKE_COMMANDS: ["auth", "select", "client", "readonly", "info"];
|
||||||
|
IGNORE_RECONNECT_ON_ERROR: ["client"];
|
||||||
|
BLOCKING_COMMANDS: [
|
||||||
|
"blpop",
|
||||||
|
"brpop",
|
||||||
|
"brpoplpush",
|
||||||
|
"blmove",
|
||||||
|
"bzpopmin",
|
||||||
|
"bzpopmax",
|
||||||
|
"bzmpop",
|
||||||
|
"blmpop",
|
||||||
|
"xread",
|
||||||
|
"xreadgroup"
|
||||||
|
];
|
||||||
|
LAST_ARG_TIMEOUT_COMMANDS: [
|
||||||
|
"blpop",
|
||||||
|
"brpop",
|
||||||
|
"brpoplpush",
|
||||||
|
"blmove",
|
||||||
|
"bzpopmin",
|
||||||
|
"bzpopmax"
|
||||||
|
];
|
||||||
|
FIRST_ARG_TIMEOUT_COMMANDS: ["bzmpop", "blmpop"];
|
||||||
|
BLOCK_OPTION_COMMANDS: ["xread", "xreadgroup"];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Command instance
|
||||||
|
*
|
||||||
|
* It's rare that you need to create a Command instance yourself.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* var infoCommand = new Command('info', null, function (err, result) {
|
||||||
|
* console.log('result', result);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* redis.sendCommand(infoCommand);
|
||||||
|
*
|
||||||
|
* // When no callback provided, Command instance will have a `promise` property,
|
||||||
|
* // which will resolve/reject with the result of the command.
|
||||||
|
* var getCommand = new Command('get', ['foo']);
|
||||||
|
* getCommand.promise.then(function (result) {
|
||||||
|
* console.log('result', result);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default class Command implements Respondable {
|
||||||
|
name: string;
|
||||||
|
static FLAGS: {
|
||||||
|
[key in keyof CommandNameFlags]: CommandNameFlags[key];
|
||||||
|
};
|
||||||
|
private static flagMap?;
|
||||||
|
private static _transformer;
|
||||||
|
/**
|
||||||
|
* Check whether the command has the flag
|
||||||
|
*/
|
||||||
|
static checkFlag<T extends keyof CommandNameFlags>(flagName: T, commandName: string): commandName is CommandNameFlags[T][number];
|
||||||
|
static setArgumentTransformer(name: string, func: ArgumentTransformer): void;
|
||||||
|
static setReplyTransformer(name: string, func: ReplyTransformer): void;
|
||||||
|
private static getFlagMap;
|
||||||
|
ignore?: boolean;
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
args: CommandParameter[];
|
||||||
|
inTransaction: boolean;
|
||||||
|
pipelineIndex?: number;
|
||||||
|
isResolved: boolean;
|
||||||
|
reject: (err: Error) => void;
|
||||||
|
resolve: (result: any) => void;
|
||||||
|
promise: Promise<any>;
|
||||||
|
private replyEncoding;
|
||||||
|
private errorStack;
|
||||||
|
private bufferMode;
|
||||||
|
private callback;
|
||||||
|
private transformed;
|
||||||
|
private _commandTimeoutTimer?;
|
||||||
|
private _blockingTimeoutTimer?;
|
||||||
|
private _blockingDeadline?;
|
||||||
|
private slot?;
|
||||||
|
private keys?;
|
||||||
|
/**
|
||||||
|
* Creates an instance of Command.
|
||||||
|
* @param name Command name
|
||||||
|
* @param args An array of command arguments
|
||||||
|
* @param options
|
||||||
|
* @param callback The callback that handles the response.
|
||||||
|
* If omit, the response will be handled via Promise
|
||||||
|
*/
|
||||||
|
constructor(name: string, args?: Array<ArgumentType>, options?: CommandOptions, callback?: Callback);
|
||||||
|
getSlot(): number;
|
||||||
|
getKeys(): Array<string | Buffer>;
|
||||||
|
/**
|
||||||
|
* Convert command to writable buffer or string
|
||||||
|
*/
|
||||||
|
toWritable(_socket: object): string | Buffer;
|
||||||
|
stringifyArguments(): void;
|
||||||
|
/**
|
||||||
|
* Convert buffer/buffer[] to string/string[],
|
||||||
|
* and apply reply transformer.
|
||||||
|
*/
|
||||||
|
transformReply(result: Buffer | Buffer[]): string | string[] | Buffer | Buffer[];
|
||||||
|
/**
|
||||||
|
* Set the wait time before terminating the attempt to execute a command
|
||||||
|
* and generating an error.
|
||||||
|
*/
|
||||||
|
setTimeout(ms: number): void;
|
||||||
|
/**
|
||||||
|
* Set a timeout for blocking commands.
|
||||||
|
* When the timeout expires, the command resolves with null (matching Redis behavior).
|
||||||
|
* This handles the case of undetectable network failures (e.g., docker network disconnect)
|
||||||
|
* where the TCP connection becomes a zombie and no close event fires.
|
||||||
|
*/
|
||||||
|
setBlockingTimeout(ms: number): void;
|
||||||
|
/**
|
||||||
|
* Extract the blocking timeout from the command arguments.
|
||||||
|
*
|
||||||
|
* @returns The timeout in seconds, null for indefinite blocking (timeout of 0),
|
||||||
|
* or undefined if this is not a blocking command
|
||||||
|
*/
|
||||||
|
extractBlockingTimeout(): number | null | undefined;
|
||||||
|
/**
|
||||||
|
* Clear the command and blocking timers
|
||||||
|
*/
|
||||||
|
private _clearTimers;
|
||||||
|
private initPromise;
|
||||||
|
/**
|
||||||
|
* Iterate through the command arguments that are considered keys.
|
||||||
|
*/
|
||||||
|
private _iterateKeys;
|
||||||
|
/**
|
||||||
|
* Convert the value from buffer to the target encoding.
|
||||||
|
*/
|
||||||
|
private _convertValue;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
449
backend/node_modules/ioredis/built/Command.js
generated
vendored
Normal file
449
backend/node_modules/ioredis/built/Command.js
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const commands_1 = require("@ioredis/commands");
|
||||||
|
const calculateSlot = require("cluster-key-slot");
|
||||||
|
const standard_as_callback_1 = require("standard-as-callback");
|
||||||
|
const utils_1 = require("./utils");
|
||||||
|
const argumentParsers_1 = require("./utils/argumentParsers");
|
||||||
|
/**
|
||||||
|
* Command instance
|
||||||
|
*
|
||||||
|
* It's rare that you need to create a Command instance yourself.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* var infoCommand = new Command('info', null, function (err, result) {
|
||||||
|
* console.log('result', result);
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* redis.sendCommand(infoCommand);
|
||||||
|
*
|
||||||
|
* // When no callback provided, Command instance will have a `promise` property,
|
||||||
|
* // which will resolve/reject with the result of the command.
|
||||||
|
* var getCommand = new Command('get', ['foo']);
|
||||||
|
* getCommand.promise.then(function (result) {
|
||||||
|
* console.log('result', result);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class Command {
|
||||||
|
/**
|
||||||
|
* Creates an instance of Command.
|
||||||
|
* @param name Command name
|
||||||
|
* @param args An array of command arguments
|
||||||
|
* @param options
|
||||||
|
* @param callback The callback that handles the response.
|
||||||
|
* If omit, the response will be handled via Promise
|
||||||
|
*/
|
||||||
|
constructor(name, args = [], options = {}, callback) {
|
||||||
|
this.name = name;
|
||||||
|
this.inTransaction = false;
|
||||||
|
this.isResolved = false;
|
||||||
|
this.transformed = false;
|
||||||
|
this.replyEncoding = options.replyEncoding;
|
||||||
|
this.errorStack = options.errorStack;
|
||||||
|
this.args = args.flat();
|
||||||
|
this.callback = callback;
|
||||||
|
this.initPromise();
|
||||||
|
if (options.keyPrefix) {
|
||||||
|
// @ts-expect-error
|
||||||
|
const isBufferKeyPrefix = options.keyPrefix instanceof Buffer;
|
||||||
|
// @ts-expect-error
|
||||||
|
let keyPrefixBuffer = isBufferKeyPrefix
|
||||||
|
? options.keyPrefix
|
||||||
|
: null;
|
||||||
|
this._iterateKeys((key) => {
|
||||||
|
if (key instanceof Buffer) {
|
||||||
|
if (keyPrefixBuffer === null) {
|
||||||
|
keyPrefixBuffer = Buffer.from(options.keyPrefix);
|
||||||
|
}
|
||||||
|
return Buffer.concat([keyPrefixBuffer, key]);
|
||||||
|
}
|
||||||
|
else if (isBufferKeyPrefix) {
|
||||||
|
// @ts-expect-error
|
||||||
|
return Buffer.concat([options.keyPrefix, Buffer.from(String(key))]);
|
||||||
|
}
|
||||||
|
return options.keyPrefix + key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (options.readOnly) {
|
||||||
|
this.isReadOnly = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check whether the command has the flag
|
||||||
|
*/
|
||||||
|
static checkFlag(flagName, commandName) {
|
||||||
|
commandName = commandName.toLowerCase();
|
||||||
|
return !!this.getFlagMap()[flagName][commandName];
|
||||||
|
}
|
||||||
|
static setArgumentTransformer(name, func) {
|
||||||
|
this._transformer.argument[name] = func;
|
||||||
|
}
|
||||||
|
static setReplyTransformer(name, func) {
|
||||||
|
this._transformer.reply[name] = func;
|
||||||
|
}
|
||||||
|
static getFlagMap() {
|
||||||
|
if (!this.flagMap) {
|
||||||
|
this.flagMap = Object.keys(Command.FLAGS).reduce((map, flagName) => {
|
||||||
|
map[flagName] = {};
|
||||||
|
Command.FLAGS[flagName].forEach((commandName) => {
|
||||||
|
map[flagName][commandName] = true;
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
return this.flagMap;
|
||||||
|
}
|
||||||
|
getSlot() {
|
||||||
|
if (typeof this.slot === "undefined") {
|
||||||
|
const key = this.getKeys()[0];
|
||||||
|
this.slot = key == null ? null : calculateSlot(key);
|
||||||
|
}
|
||||||
|
return this.slot;
|
||||||
|
}
|
||||||
|
getKeys() {
|
||||||
|
return this._iterateKeys();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert command to writable buffer or string
|
||||||
|
*/
|
||||||
|
toWritable(_socket) {
|
||||||
|
let result;
|
||||||
|
const commandStr = "*" +
|
||||||
|
(this.args.length + 1) +
|
||||||
|
"\r\n$" +
|
||||||
|
Buffer.byteLength(this.name) +
|
||||||
|
"\r\n" +
|
||||||
|
this.name +
|
||||||
|
"\r\n";
|
||||||
|
if (this.bufferMode) {
|
||||||
|
const buffers = new MixedBuffers();
|
||||||
|
buffers.push(commandStr);
|
||||||
|
for (let i = 0; i < this.args.length; ++i) {
|
||||||
|
const arg = this.args[i];
|
||||||
|
if (arg instanceof Buffer) {
|
||||||
|
if (arg.length === 0) {
|
||||||
|
buffers.push("$0\r\n\r\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffers.push("$" + arg.length + "\r\n");
|
||||||
|
buffers.push(arg);
|
||||||
|
buffers.push("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffers.push("$" +
|
||||||
|
Buffer.byteLength(arg) +
|
||||||
|
"\r\n" +
|
||||||
|
arg +
|
||||||
|
"\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = buffers.toBuffer();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = commandStr;
|
||||||
|
for (let i = 0; i < this.args.length; ++i) {
|
||||||
|
const arg = this.args[i];
|
||||||
|
result +=
|
||||||
|
"$" +
|
||||||
|
Buffer.byteLength(arg) +
|
||||||
|
"\r\n" +
|
||||||
|
arg +
|
||||||
|
"\r\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
stringifyArguments() {
|
||||||
|
for (let i = 0; i < this.args.length; ++i) {
|
||||||
|
const arg = this.args[i];
|
||||||
|
if (typeof arg === "string") {
|
||||||
|
// buffers and strings don't need any transformation
|
||||||
|
}
|
||||||
|
else if (arg instanceof Buffer) {
|
||||||
|
this.bufferMode = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.args[i] = (0, utils_1.toArg)(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert buffer/buffer[] to string/string[],
|
||||||
|
* and apply reply transformer.
|
||||||
|
*/
|
||||||
|
transformReply(result) {
|
||||||
|
if (this.replyEncoding) {
|
||||||
|
result = (0, utils_1.convertBufferToString)(result, this.replyEncoding);
|
||||||
|
}
|
||||||
|
const transformer = Command._transformer.reply[this.name];
|
||||||
|
if (transformer) {
|
||||||
|
result = transformer(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set the wait time before terminating the attempt to execute a command
|
||||||
|
* and generating an error.
|
||||||
|
*/
|
||||||
|
setTimeout(ms) {
|
||||||
|
if (!this._commandTimeoutTimer) {
|
||||||
|
this._commandTimeoutTimer = setTimeout(() => {
|
||||||
|
if (!this.isResolved) {
|
||||||
|
this.reject(new Error("Command timed out"));
|
||||||
|
}
|
||||||
|
}, ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set a timeout for blocking commands.
|
||||||
|
* When the timeout expires, the command resolves with null (matching Redis behavior).
|
||||||
|
* This handles the case of undetectable network failures (e.g., docker network disconnect)
|
||||||
|
* where the TCP connection becomes a zombie and no close event fires.
|
||||||
|
*/
|
||||||
|
setBlockingTimeout(ms) {
|
||||||
|
if (ms <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Clear existing timer if any (can happen when command moves from offline to command queue)
|
||||||
|
if (this._blockingTimeoutTimer) {
|
||||||
|
clearTimeout(this._blockingTimeoutTimer);
|
||||||
|
this._blockingTimeoutTimer = undefined;
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
// First call: establish absolute deadline
|
||||||
|
if (this._blockingDeadline === undefined) {
|
||||||
|
this._blockingDeadline = now + ms;
|
||||||
|
}
|
||||||
|
// Check if we've already exceeded the deadline
|
||||||
|
const remaining = this._blockingDeadline - now;
|
||||||
|
if (remaining <= 0) {
|
||||||
|
// Resolve with null to indicate timeout (same as Redis behavior)
|
||||||
|
this.resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._blockingTimeoutTimer = setTimeout(() => {
|
||||||
|
if (this.isResolved) {
|
||||||
|
this._blockingTimeoutTimer = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._blockingTimeoutTimer = undefined;
|
||||||
|
// Timeout expired - resolve with null (same as Redis behavior when blocking command times out)
|
||||||
|
this.resolve(null);
|
||||||
|
}, remaining);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Extract the blocking timeout from the command arguments.
|
||||||
|
*
|
||||||
|
* @returns The timeout in seconds, null for indefinite blocking (timeout of 0),
|
||||||
|
* or undefined if this is not a blocking command
|
||||||
|
*/
|
||||||
|
extractBlockingTimeout() {
|
||||||
|
const args = this.args;
|
||||||
|
if (!args || args.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const name = this.name.toLowerCase();
|
||||||
|
if (Command.checkFlag("LAST_ARG_TIMEOUT_COMMANDS", name)) {
|
||||||
|
return (0, argumentParsers_1.parseSecondsArgument)(args[args.length - 1]);
|
||||||
|
}
|
||||||
|
if (Command.checkFlag("FIRST_ARG_TIMEOUT_COMMANDS", name)) {
|
||||||
|
return (0, argumentParsers_1.parseSecondsArgument)(args[0]);
|
||||||
|
}
|
||||||
|
if (Command.checkFlag("BLOCK_OPTION_COMMANDS", name)) {
|
||||||
|
return (0, argumentParsers_1.parseBlockOption)(args);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Clear the command and blocking timers
|
||||||
|
*/
|
||||||
|
_clearTimers() {
|
||||||
|
const existingTimer = this._commandTimeoutTimer;
|
||||||
|
if (existingTimer) {
|
||||||
|
clearTimeout(existingTimer);
|
||||||
|
delete this._commandTimeoutTimer;
|
||||||
|
}
|
||||||
|
const blockingTimer = this._blockingTimeoutTimer;
|
||||||
|
if (blockingTimer) {
|
||||||
|
clearTimeout(blockingTimer);
|
||||||
|
delete this._blockingTimeoutTimer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initPromise() {
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
if (!this.transformed) {
|
||||||
|
this.transformed = true;
|
||||||
|
const transformer = Command._transformer.argument[this.name];
|
||||||
|
if (transformer) {
|
||||||
|
this.args = transformer(this.args);
|
||||||
|
}
|
||||||
|
this.stringifyArguments();
|
||||||
|
}
|
||||||
|
this.resolve = this._convertValue(resolve);
|
||||||
|
this.reject = (err) => {
|
||||||
|
this._clearTimers();
|
||||||
|
if (this.errorStack) {
|
||||||
|
reject((0, utils_1.optimizeErrorStack)(err, this.errorStack.stack, __dirname));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.promise = (0, standard_as_callback_1.default)(promise, this.callback);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Iterate through the command arguments that are considered keys.
|
||||||
|
*/
|
||||||
|
_iterateKeys(transform = (key) => key) {
|
||||||
|
if (typeof this.keys === "undefined") {
|
||||||
|
this.keys = [];
|
||||||
|
if ((0, commands_1.exists)(this.name, { caseInsensitive: true })) {
|
||||||
|
// @ts-expect-error
|
||||||
|
const keyIndexes = (0, commands_1.getKeyIndexes)(this.name, this.args, {
|
||||||
|
nameCaseInsensitive: true,
|
||||||
|
});
|
||||||
|
for (const index of keyIndexes) {
|
||||||
|
this.args[index] = transform(this.args[index]);
|
||||||
|
this.keys.push(this.args[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.keys;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convert the value from buffer to the target encoding.
|
||||||
|
*/
|
||||||
|
_convertValue(resolve) {
|
||||||
|
return (value) => {
|
||||||
|
try {
|
||||||
|
this._clearTimers();
|
||||||
|
resolve(this.transformReply(value));
|
||||||
|
this.isResolved = true;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.reject(err);
|
||||||
|
}
|
||||||
|
return this.promise;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = Command;
|
||||||
|
Command.FLAGS = {
|
||||||
|
VALID_IN_SUBSCRIBER_MODE: [
|
||||||
|
"subscribe",
|
||||||
|
"psubscribe",
|
||||||
|
"unsubscribe",
|
||||||
|
"punsubscribe",
|
||||||
|
"ssubscribe",
|
||||||
|
"sunsubscribe",
|
||||||
|
"ping",
|
||||||
|
"quit",
|
||||||
|
],
|
||||||
|
VALID_IN_MONITOR_MODE: ["monitor", "auth"],
|
||||||
|
ENTER_SUBSCRIBER_MODE: ["subscribe", "psubscribe", "ssubscribe"],
|
||||||
|
EXIT_SUBSCRIBER_MODE: ["unsubscribe", "punsubscribe", "sunsubscribe"],
|
||||||
|
WILL_DISCONNECT: ["quit"],
|
||||||
|
HANDSHAKE_COMMANDS: ["auth", "select", "client", "readonly", "info"],
|
||||||
|
IGNORE_RECONNECT_ON_ERROR: ["client"],
|
||||||
|
BLOCKING_COMMANDS: [
|
||||||
|
"blpop",
|
||||||
|
"brpop",
|
||||||
|
"brpoplpush",
|
||||||
|
"blmove",
|
||||||
|
"bzpopmin",
|
||||||
|
"bzpopmax",
|
||||||
|
"bzmpop",
|
||||||
|
"blmpop",
|
||||||
|
"xread",
|
||||||
|
"xreadgroup",
|
||||||
|
],
|
||||||
|
LAST_ARG_TIMEOUT_COMMANDS: [
|
||||||
|
"blpop",
|
||||||
|
"brpop",
|
||||||
|
"brpoplpush",
|
||||||
|
"blmove",
|
||||||
|
"bzpopmin",
|
||||||
|
"bzpopmax",
|
||||||
|
],
|
||||||
|
FIRST_ARG_TIMEOUT_COMMANDS: ["bzmpop", "blmpop"],
|
||||||
|
BLOCK_OPTION_COMMANDS: ["xread", "xreadgroup"],
|
||||||
|
};
|
||||||
|
Command._transformer = {
|
||||||
|
argument: {},
|
||||||
|
reply: {},
|
||||||
|
};
|
||||||
|
const msetArgumentTransformer = function (args) {
|
||||||
|
if (args.length === 1) {
|
||||||
|
if (args[0] instanceof Map) {
|
||||||
|
return (0, utils_1.convertMapToArray)(args[0]);
|
||||||
|
}
|
||||||
|
if (typeof args[0] === "object" && args[0] !== null) {
|
||||||
|
return (0, utils_1.convertObjectToArray)(args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
const hsetArgumentTransformer = function (args) {
|
||||||
|
if (args.length === 2) {
|
||||||
|
if (args[1] instanceof Map) {
|
||||||
|
return [args[0]].concat((0, utils_1.convertMapToArray)(args[1]));
|
||||||
|
}
|
||||||
|
if (typeof args[1] === "object" && args[1] !== null) {
|
||||||
|
return [args[0]].concat((0, utils_1.convertObjectToArray)(args[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
Command.setArgumentTransformer("mset", msetArgumentTransformer);
|
||||||
|
Command.setArgumentTransformer("msetnx", msetArgumentTransformer);
|
||||||
|
Command.setArgumentTransformer("hset", hsetArgumentTransformer);
|
||||||
|
Command.setArgumentTransformer("hmset", hsetArgumentTransformer);
|
||||||
|
Command.setReplyTransformer("hgetall", function (result) {
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
const obj = {};
|
||||||
|
for (let i = 0; i < result.length; i += 2) {
|
||||||
|
const key = result[i];
|
||||||
|
const value = result[i + 1];
|
||||||
|
if (key in obj) {
|
||||||
|
// can only be truthy if the property is special somehow, like '__proto__' or 'constructor'
|
||||||
|
// https://github.com/luin/ioredis/issues/1267
|
||||||
|
Object.defineProperty(obj, key, {
|
||||||
|
value,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
obj[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
class MixedBuffers {
|
||||||
|
constructor() {
|
||||||
|
this.length = 0;
|
||||||
|
this.items = [];
|
||||||
|
}
|
||||||
|
push(x) {
|
||||||
|
this.length += Buffer.byteLength(x);
|
||||||
|
this.items.push(x);
|
||||||
|
}
|
||||||
|
toBuffer() {
|
||||||
|
const result = Buffer.allocUnsafe(this.length);
|
||||||
|
let offset = 0;
|
||||||
|
for (const item of this.items) {
|
||||||
|
const length = Buffer.byteLength(item);
|
||||||
|
Buffer.isBuffer(item)
|
||||||
|
? item.copy(result, offset)
|
||||||
|
: result.write(item, offset, length);
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
backend/node_modules/ioredis/built/DataHandler.d.ts
generated
vendored
Normal file
37
backend/node_modules/ioredis/built/DataHandler.d.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { NetStream, CommandItem } from "./types";
|
||||||
|
import Deque = require("denque");
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import SubscriptionSet from "./SubscriptionSet";
|
||||||
|
export interface Condition {
|
||||||
|
select: number;
|
||||||
|
auth?: string | [string, string];
|
||||||
|
subscriber: false | SubscriptionSet;
|
||||||
|
}
|
||||||
|
export declare type FlushQueueOptions = {
|
||||||
|
offlineQueue?: boolean;
|
||||||
|
commandQueue?: boolean;
|
||||||
|
};
|
||||||
|
export interface DataHandledable extends EventEmitter {
|
||||||
|
stream: NetStream;
|
||||||
|
status: string;
|
||||||
|
condition: Condition | null;
|
||||||
|
commandQueue: Deque<CommandItem>;
|
||||||
|
disconnect(reconnect: boolean): void;
|
||||||
|
recoverFromFatalError(commandError: Error, err: Error, options: FlushQueueOptions): void;
|
||||||
|
handleReconnection(err: Error, item: CommandItem): void;
|
||||||
|
}
|
||||||
|
interface ParserOptions {
|
||||||
|
stringNumbers: boolean;
|
||||||
|
}
|
||||||
|
export default class DataHandler {
|
||||||
|
private redis;
|
||||||
|
constructor(redis: DataHandledable, parserOptions: ParserOptions);
|
||||||
|
private returnFatalError;
|
||||||
|
private returnError;
|
||||||
|
private returnReply;
|
||||||
|
private handleSubscriberReply;
|
||||||
|
private handleMonitorReply;
|
||||||
|
private shiftCommand;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
224
backend/node_modules/ioredis/built/DataHandler.js
generated
vendored
Normal file
224
backend/node_modules/ioredis/built/DataHandler.js
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const Command_1 = require("./Command");
|
||||||
|
const utils_1 = require("./utils");
|
||||||
|
const RedisParser = require("redis-parser");
|
||||||
|
const SubscriptionSet_1 = require("./SubscriptionSet");
|
||||||
|
const debug = (0, utils_1.Debug)("dataHandler");
|
||||||
|
class DataHandler {
|
||||||
|
constructor(redis, parserOptions) {
|
||||||
|
this.redis = redis;
|
||||||
|
const parser = new RedisParser({
|
||||||
|
stringNumbers: parserOptions.stringNumbers,
|
||||||
|
returnBuffers: true,
|
||||||
|
returnError: (err) => {
|
||||||
|
this.returnError(err);
|
||||||
|
},
|
||||||
|
returnFatalError: (err) => {
|
||||||
|
this.returnFatalError(err);
|
||||||
|
},
|
||||||
|
returnReply: (reply) => {
|
||||||
|
this.returnReply(reply);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// prependListener ensures the parser receives and processes data before socket timeout checks are performed
|
||||||
|
redis.stream.prependListener("data", (data) => {
|
||||||
|
parser.execute(data);
|
||||||
|
});
|
||||||
|
// prependListener() doesn't enable flowing mode automatically - we need to resume the stream manually
|
||||||
|
redis.stream.resume();
|
||||||
|
}
|
||||||
|
returnFatalError(err) {
|
||||||
|
err.message += ". Please report this.";
|
||||||
|
this.redis.recoverFromFatalError(err, err, { offlineQueue: false });
|
||||||
|
}
|
||||||
|
returnError(err) {
|
||||||
|
const item = this.shiftCommand(err);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err.command = {
|
||||||
|
name: item.command.name,
|
||||||
|
args: item.command.args,
|
||||||
|
};
|
||||||
|
if (item.command.name == "ssubscribe" && err.message.includes("MOVED")) {
|
||||||
|
this.redis.emit("moved");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.redis.handleReconnection(err, item);
|
||||||
|
}
|
||||||
|
returnReply(reply) {
|
||||||
|
if (this.handleMonitorReply(reply)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.handleSubscriberReply(reply)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const item = this.shiftCommand(reply);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Command_1.default.checkFlag("ENTER_SUBSCRIBER_MODE", item.command.name)) {
|
||||||
|
this.redis.condition.subscriber = new SubscriptionSet_1.default();
|
||||||
|
this.redis.condition.subscriber.add(item.command.name, reply[1].toString());
|
||||||
|
if (!fillSubCommand(item.command, reply[2])) {
|
||||||
|
this.redis.commandQueue.unshift(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Command_1.default.checkFlag("EXIT_SUBSCRIBER_MODE", item.command.name)) {
|
||||||
|
if (!fillUnsubCommand(item.command, reply[2])) {
|
||||||
|
this.redis.commandQueue.unshift(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item.command.resolve(reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleSubscriberReply(reply) {
|
||||||
|
if (!this.redis.condition.subscriber) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const replyType = Array.isArray(reply) ? reply[0].toString() : null;
|
||||||
|
debug('receive reply "%s" in subscriber mode', replyType);
|
||||||
|
switch (replyType) {
|
||||||
|
case "message":
|
||||||
|
if (this.redis.listeners("message").length > 0) {
|
||||||
|
// Check if there're listeners to avoid unnecessary `toString()`.
|
||||||
|
this.redis.emit("message", reply[1].toString(), reply[2] ? reply[2].toString() : "");
|
||||||
|
}
|
||||||
|
this.redis.emit("messageBuffer", reply[1], reply[2]);
|
||||||
|
break;
|
||||||
|
case "pmessage": {
|
||||||
|
const pattern = reply[1].toString();
|
||||||
|
if (this.redis.listeners("pmessage").length > 0) {
|
||||||
|
this.redis.emit("pmessage", pattern, reply[2].toString(), reply[3].toString());
|
||||||
|
}
|
||||||
|
this.redis.emit("pmessageBuffer", pattern, reply[2], reply[3]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "smessage": {
|
||||||
|
if (this.redis.listeners("smessage").length > 0) {
|
||||||
|
this.redis.emit("smessage", reply[1].toString(), reply[2] ? reply[2].toString() : "");
|
||||||
|
}
|
||||||
|
this.redis.emit("smessageBuffer", reply[1], reply[2]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "ssubscribe":
|
||||||
|
case "subscribe":
|
||||||
|
case "psubscribe": {
|
||||||
|
const channel = reply[1].toString();
|
||||||
|
this.redis.condition.subscriber.add(replyType, channel);
|
||||||
|
const item = this.shiftCommand(reply);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fillSubCommand(item.command, reply[2])) {
|
||||||
|
this.redis.commandQueue.unshift(item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "sunsubscribe":
|
||||||
|
case "unsubscribe":
|
||||||
|
case "punsubscribe": {
|
||||||
|
const channel = reply[1] ? reply[1].toString() : null;
|
||||||
|
if (channel) {
|
||||||
|
this.redis.condition.subscriber.del(replyType, channel);
|
||||||
|
}
|
||||||
|
const count = reply[2];
|
||||||
|
if (Number(count) === 0) {
|
||||||
|
this.redis.condition.subscriber = false;
|
||||||
|
}
|
||||||
|
const item = this.shiftCommand(reply);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fillUnsubCommand(item.command, count)) {
|
||||||
|
this.redis.commandQueue.unshift(item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const item = this.shiftCommand(reply);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item.command.resolve(reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
handleMonitorReply(reply) {
|
||||||
|
if (this.redis.status !== "monitoring") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const replyStr = reply.toString();
|
||||||
|
if (replyStr === "OK") {
|
||||||
|
// Valid commands in the monitoring mode are AUTH and MONITOR,
|
||||||
|
// both of which always reply with 'OK'.
|
||||||
|
// So if we got an 'OK', we can make certain that
|
||||||
|
// the reply is made to AUTH & MONITOR.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Since commands sent in the monitoring mode will trigger an exception,
|
||||||
|
// any replies we received in the monitoring mode should consider to be
|
||||||
|
// realtime monitor data instead of result of commands.
|
||||||
|
const len = replyStr.indexOf(" ");
|
||||||
|
const timestamp = replyStr.slice(0, len);
|
||||||
|
const argIndex = replyStr.indexOf('"');
|
||||||
|
const args = replyStr
|
||||||
|
.slice(argIndex + 1, -1)
|
||||||
|
.split('" "')
|
||||||
|
.map((elem) => elem.replace(/\\"/g, '"'));
|
||||||
|
const dbAndSource = replyStr.slice(len + 2, argIndex - 2).split(" ");
|
||||||
|
this.redis.emit("monitor", timestamp, args, dbAndSource[1], dbAndSource[0]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
shiftCommand(reply) {
|
||||||
|
const item = this.redis.commandQueue.shift();
|
||||||
|
if (!item) {
|
||||||
|
const message = "Command queue state error. If you can reproduce this, please report it.";
|
||||||
|
const error = new Error(message +
|
||||||
|
(reply instanceof Error
|
||||||
|
? ` Last error: ${reply.message}`
|
||||||
|
: ` Last reply: ${reply.toString()}`));
|
||||||
|
this.redis.emit("error", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = DataHandler;
|
||||||
|
const remainingRepliesMap = new WeakMap();
|
||||||
|
function fillSubCommand(command, count) {
|
||||||
|
let remainingReplies = remainingRepliesMap.has(command)
|
||||||
|
? remainingRepliesMap.get(command)
|
||||||
|
: command.args.length;
|
||||||
|
remainingReplies -= 1;
|
||||||
|
if (remainingReplies <= 0) {
|
||||||
|
command.resolve(count);
|
||||||
|
remainingRepliesMap.delete(command);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
remainingRepliesMap.set(command, remainingReplies);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function fillUnsubCommand(command, count) {
|
||||||
|
let remainingReplies = remainingRepliesMap.has(command)
|
||||||
|
? remainingRepliesMap.get(command)
|
||||||
|
: command.args.length;
|
||||||
|
if (remainingReplies === 0) {
|
||||||
|
if (Number(count) === 0) {
|
||||||
|
remainingRepliesMap.delete(command);
|
||||||
|
command.resolve(count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
remainingReplies -= 1;
|
||||||
|
if (remainingReplies <= 0) {
|
||||||
|
command.resolve(count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
remainingRepliesMap.set(command, remainingReplies);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
31
backend/node_modules/ioredis/built/Pipeline.d.ts
generated
vendored
Normal file
31
backend/node_modules/ioredis/built/Pipeline.d.ts
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import Redis from "./Redis";
|
||||||
|
import Cluster from "./cluster";
|
||||||
|
import Command from "./Command";
|
||||||
|
import Commander from "./utils/Commander";
|
||||||
|
declare class Pipeline extends Commander<{
|
||||||
|
type: "pipeline";
|
||||||
|
}> {
|
||||||
|
redis: Redis | Cluster;
|
||||||
|
isCluster: boolean;
|
||||||
|
isPipeline: boolean;
|
||||||
|
leftRedirections: {
|
||||||
|
value?: number;
|
||||||
|
};
|
||||||
|
promise: Promise<unknown>;
|
||||||
|
resolve: (result: unknown) => void;
|
||||||
|
reject: (error: Error) => void;
|
||||||
|
private replyPending;
|
||||||
|
private _queue;
|
||||||
|
private _result;
|
||||||
|
private _transactions;
|
||||||
|
private _shaToScript;
|
||||||
|
private preferKey;
|
||||||
|
constructor(redis: Redis | Cluster);
|
||||||
|
fillResult(value: unknown[], position: number): void;
|
||||||
|
sendCommand(command: Command): unknown;
|
||||||
|
addBatch(commands: any): this;
|
||||||
|
}
|
||||||
|
export default Pipeline;
|
||||||
|
interface Pipeline {
|
||||||
|
length: number;
|
||||||
|
}
|
||||||
342
backend/node_modules/ioredis/built/Pipeline.js
generated
vendored
Normal file
342
backend/node_modules/ioredis/built/Pipeline.js
generated
vendored
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const calculateSlot = require("cluster-key-slot");
|
||||||
|
const commands_1 = require("@ioredis/commands");
|
||||||
|
const standard_as_callback_1 = require("standard-as-callback");
|
||||||
|
const util_1 = require("util");
|
||||||
|
const Command_1 = require("./Command");
|
||||||
|
const utils_1 = require("./utils");
|
||||||
|
const Commander_1 = require("./utils/Commander");
|
||||||
|
/*
|
||||||
|
This function derives from the cluster-key-slot implementation.
|
||||||
|
Instead of checking that all keys have the same slot, it checks that all slots are served by the same set of nodes.
|
||||||
|
If this is satisfied, it returns the first key's slot.
|
||||||
|
*/
|
||||||
|
function generateMultiWithNodes(redis, keys) {
|
||||||
|
const slot = calculateSlot(keys[0]);
|
||||||
|
const target = redis._groupsBySlot[slot];
|
||||||
|
for (let i = 1; i < keys.length; i++) {
|
||||||
|
if (redis._groupsBySlot[calculateSlot(keys[i])] !== target) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
class Pipeline extends Commander_1.default {
|
||||||
|
constructor(redis) {
|
||||||
|
super();
|
||||||
|
this.redis = redis;
|
||||||
|
this.isPipeline = true;
|
||||||
|
this.replyPending = 0;
|
||||||
|
this._queue = [];
|
||||||
|
this._result = [];
|
||||||
|
this._transactions = 0;
|
||||||
|
this._shaToScript = {};
|
||||||
|
this.isCluster =
|
||||||
|
this.redis.constructor.name === "Cluster" || this.redis.isCluster;
|
||||||
|
this.options = redis.options;
|
||||||
|
Object.keys(redis.scriptsSet).forEach((name) => {
|
||||||
|
const script = redis.scriptsSet[name];
|
||||||
|
this._shaToScript[script.sha] = script;
|
||||||
|
this[name] = redis[name];
|
||||||
|
this[name + "Buffer"] = redis[name + "Buffer"];
|
||||||
|
});
|
||||||
|
redis.addedBuiltinSet.forEach((name) => {
|
||||||
|
this[name] = redis[name];
|
||||||
|
this[name + "Buffer"] = redis[name + "Buffer"];
|
||||||
|
});
|
||||||
|
this.promise = new Promise((resolve, reject) => {
|
||||||
|
this.resolve = resolve;
|
||||||
|
this.reject = reject;
|
||||||
|
});
|
||||||
|
const _this = this;
|
||||||
|
Object.defineProperty(this, "length", {
|
||||||
|
get: function () {
|
||||||
|
return _this._queue.length;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fillResult(value, position) {
|
||||||
|
if (this._queue[position].name === "exec" && Array.isArray(value[1])) {
|
||||||
|
const execLength = value[1].length;
|
||||||
|
for (let i = 0; i < execLength; i++) {
|
||||||
|
if (value[1][i] instanceof Error) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const cmd = this._queue[position - (execLength - i)];
|
||||||
|
try {
|
||||||
|
value[1][i] = cmd.transformReply(value[1][i]);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
value[1][i] = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._result[position] = value;
|
||||||
|
if (--this.replyPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isCluster) {
|
||||||
|
let retriable = true;
|
||||||
|
let commonError;
|
||||||
|
for (let i = 0; i < this._result.length; ++i) {
|
||||||
|
const error = this._result[i][0];
|
||||||
|
const command = this._queue[i];
|
||||||
|
if (error) {
|
||||||
|
if (command.name === "exec" &&
|
||||||
|
error.message ===
|
||||||
|
"EXECABORT Transaction discarded because of previous errors.") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!commonError) {
|
||||||
|
commonError = {
|
||||||
|
name: error.name,
|
||||||
|
message: error.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (commonError.name !== error.name ||
|
||||||
|
commonError.message !== error.message) {
|
||||||
|
retriable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!command.inTransaction) {
|
||||||
|
const isReadOnly = (0, commands_1.exists)(command.name, { caseInsensitive: true }) &&
|
||||||
|
(0, commands_1.hasFlag)(command.name, "readonly", { nameCaseInsensitive: true });
|
||||||
|
if (!isReadOnly) {
|
||||||
|
retriable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (commonError && retriable) {
|
||||||
|
const _this = this;
|
||||||
|
const errv = commonError.message.split(" ");
|
||||||
|
const queue = this._queue;
|
||||||
|
let inTransaction = false;
|
||||||
|
this._queue = [];
|
||||||
|
for (let i = 0; i < queue.length; ++i) {
|
||||||
|
if (errv[0] === "ASK" &&
|
||||||
|
!inTransaction &&
|
||||||
|
queue[i].name !== "asking" &&
|
||||||
|
(!queue[i - 1] || queue[i - 1].name !== "asking")) {
|
||||||
|
const asking = new Command_1.default("asking");
|
||||||
|
asking.ignore = true;
|
||||||
|
this.sendCommand(asking);
|
||||||
|
}
|
||||||
|
queue[i].initPromise();
|
||||||
|
this.sendCommand(queue[i]);
|
||||||
|
inTransaction = queue[i].inTransaction;
|
||||||
|
}
|
||||||
|
let matched = true;
|
||||||
|
if (typeof this.leftRedirections === "undefined") {
|
||||||
|
this.leftRedirections = {};
|
||||||
|
}
|
||||||
|
const exec = function () {
|
||||||
|
_this.exec();
|
||||||
|
};
|
||||||
|
const cluster = this.redis;
|
||||||
|
cluster.handleError(commonError, this.leftRedirections, {
|
||||||
|
moved: function (_slot, key) {
|
||||||
|
_this.preferKey = key;
|
||||||
|
if (cluster.slots[errv[1]]) {
|
||||||
|
if (cluster.slots[errv[1]][0] !== key) {
|
||||||
|
cluster.slots[errv[1]] = [key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cluster.slots[errv[1]] = [key];
|
||||||
|
}
|
||||||
|
cluster._groupsBySlot[errv[1]] =
|
||||||
|
cluster._groupsIds[cluster.slots[errv[1]].join(";")];
|
||||||
|
cluster.refreshSlotsCache();
|
||||||
|
_this.exec();
|
||||||
|
},
|
||||||
|
ask: function (_slot, key) {
|
||||||
|
_this.preferKey = key;
|
||||||
|
_this.exec();
|
||||||
|
},
|
||||||
|
tryagain: exec,
|
||||||
|
clusterDown: exec,
|
||||||
|
connectionClosed: exec,
|
||||||
|
maxRedirections: () => {
|
||||||
|
matched = false;
|
||||||
|
},
|
||||||
|
defaults: () => {
|
||||||
|
matched = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (matched) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ignoredCount = 0;
|
||||||
|
for (let i = 0; i < this._queue.length - ignoredCount; ++i) {
|
||||||
|
if (this._queue[i + ignoredCount].ignore) {
|
||||||
|
ignoredCount += 1;
|
||||||
|
}
|
||||||
|
this._result[i] = this._result[i + ignoredCount];
|
||||||
|
}
|
||||||
|
this.resolve(this._result.slice(0, this._result.length - ignoredCount));
|
||||||
|
}
|
||||||
|
sendCommand(command) {
|
||||||
|
if (this._transactions > 0) {
|
||||||
|
command.inTransaction = true;
|
||||||
|
}
|
||||||
|
const position = this._queue.length;
|
||||||
|
command.pipelineIndex = position;
|
||||||
|
command.promise
|
||||||
|
.then((result) => {
|
||||||
|
this.fillResult([null, result], position);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.fillResult([error], position);
|
||||||
|
});
|
||||||
|
this._queue.push(command);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
addBatch(commands) {
|
||||||
|
let command, commandName, args;
|
||||||
|
for (let i = 0; i < commands.length; ++i) {
|
||||||
|
command = commands[i];
|
||||||
|
commandName = command[0];
|
||||||
|
args = command.slice(1);
|
||||||
|
this[commandName].apply(this, args);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = Pipeline;
|
||||||
|
// @ts-expect-error
|
||||||
|
const multi = Pipeline.prototype.multi;
|
||||||
|
// @ts-expect-error
|
||||||
|
Pipeline.prototype.multi = function () {
|
||||||
|
this._transactions += 1;
|
||||||
|
return multi.apply(this, arguments);
|
||||||
|
};
|
||||||
|
// @ts-expect-error
|
||||||
|
const execBuffer = Pipeline.prototype.execBuffer;
|
||||||
|
// @ts-expect-error
|
||||||
|
Pipeline.prototype.execBuffer = (0, util_1.deprecate)(function () {
|
||||||
|
if (this._transactions > 0) {
|
||||||
|
this._transactions -= 1;
|
||||||
|
}
|
||||||
|
return execBuffer.apply(this, arguments);
|
||||||
|
}, "Pipeline#execBuffer: Use Pipeline#exec instead");
|
||||||
|
// NOTE: To avoid an unhandled promise rejection, this will unconditionally always return this.promise,
|
||||||
|
// which always has the rejection handled by standard-as-callback
|
||||||
|
// adding the provided rejection callback.
|
||||||
|
//
|
||||||
|
// If a different promise instance were returned, that promise would cause its own unhandled promise rejection
|
||||||
|
// errors, even if that promise unconditionally resolved to **the resolved value of** this.promise.
|
||||||
|
Pipeline.prototype.exec = function (callback) {
|
||||||
|
// Wait for the cluster to be connected, since we need nodes information before continuing
|
||||||
|
if (this.isCluster && !this.redis.slots.length) {
|
||||||
|
if (this.redis.status === "wait")
|
||||||
|
this.redis.connect().catch(utils_1.noop);
|
||||||
|
if (callback && !this.nodeifiedPromise) {
|
||||||
|
this.nodeifiedPromise = true;
|
||||||
|
(0, standard_as_callback_1.default)(this.promise, callback);
|
||||||
|
}
|
||||||
|
this.redis.delayUntilReady((err) => {
|
||||||
|
if (err) {
|
||||||
|
this.reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.exec(callback);
|
||||||
|
});
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
if (this._transactions > 0) {
|
||||||
|
this._transactions -= 1;
|
||||||
|
return execBuffer.apply(this, arguments);
|
||||||
|
}
|
||||||
|
if (!this.nodeifiedPromise) {
|
||||||
|
this.nodeifiedPromise = true;
|
||||||
|
(0, standard_as_callback_1.default)(this.promise, callback);
|
||||||
|
}
|
||||||
|
if (!this._queue.length) {
|
||||||
|
this.resolve([]);
|
||||||
|
}
|
||||||
|
let pipelineSlot;
|
||||||
|
if (this.isCluster) {
|
||||||
|
// List of the first key for each command
|
||||||
|
const sampleKeys = [];
|
||||||
|
for (let i = 0; i < this._queue.length; i++) {
|
||||||
|
const keys = this._queue[i].getKeys();
|
||||||
|
if (keys.length) {
|
||||||
|
sampleKeys.push(keys[0]);
|
||||||
|
}
|
||||||
|
// For each command, check that the keys belong to the same slot
|
||||||
|
if (keys.length && calculateSlot.generateMulti(keys) < 0) {
|
||||||
|
this.reject(new Error("All the keys in a pipeline command should belong to the same slot"));
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sampleKeys.length) {
|
||||||
|
pipelineSlot = generateMultiWithNodes(this.redis, sampleKeys);
|
||||||
|
if (pipelineSlot < 0) {
|
||||||
|
this.reject(new Error("All keys in the pipeline should belong to the same slots allocation group"));
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Send the pipeline to a random node
|
||||||
|
pipelineSlot = (Math.random() * 16384) | 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const _this = this;
|
||||||
|
execPipeline();
|
||||||
|
return this.promise;
|
||||||
|
function execPipeline() {
|
||||||
|
let writePending = (_this.replyPending = _this._queue.length);
|
||||||
|
let node;
|
||||||
|
if (_this.isCluster) {
|
||||||
|
node = {
|
||||||
|
slot: pipelineSlot,
|
||||||
|
redis: _this.redis.connectionPool.nodes.all[_this.preferKey],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let data = "";
|
||||||
|
let buffers;
|
||||||
|
const stream = {
|
||||||
|
isPipeline: true,
|
||||||
|
destination: _this.isCluster ? node : { redis: _this.redis },
|
||||||
|
write(writable) {
|
||||||
|
if (typeof writable !== "string") {
|
||||||
|
if (!buffers) {
|
||||||
|
buffers = [];
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
buffers.push(Buffer.from(data, "utf8"));
|
||||||
|
data = "";
|
||||||
|
}
|
||||||
|
buffers.push(writable);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data += writable;
|
||||||
|
}
|
||||||
|
if (!--writePending) {
|
||||||
|
if (buffers) {
|
||||||
|
if (data) {
|
||||||
|
buffers.push(Buffer.from(data, "utf8"));
|
||||||
|
}
|
||||||
|
stream.destination.redis.stream.write(Buffer.concat(buffers));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stream.destination.redis.stream.write(data);
|
||||||
|
}
|
||||||
|
// Reset writePending for resending
|
||||||
|
writePending = _this._queue.length;
|
||||||
|
data = "";
|
||||||
|
buffers = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (let i = 0; i < _this._queue.length; ++i) {
|
||||||
|
_this.redis.sendCommand(_this._queue[i], stream, node);
|
||||||
|
}
|
||||||
|
return _this.promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
232
backend/node_modules/ioredis/built/Redis.d.ts
generated
vendored
Normal file
232
backend/node_modules/ioredis/built/Redis.d.ts
generated
vendored
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import Cluster from "./cluster";
|
||||||
|
import Command from "./Command";
|
||||||
|
import { DataHandledable, FlushQueueOptions, Condition } from "./DataHandler";
|
||||||
|
import { RedisOptions } from "./redis/RedisOptions";
|
||||||
|
import ScanStream from "./ScanStream";
|
||||||
|
import { Transaction } from "./transaction";
|
||||||
|
import { Callback, CommandItem, NetStream, ScanStreamOptions, WriteableStream } from "./types";
|
||||||
|
import Commander from "./utils/Commander";
|
||||||
|
import Deque = require("denque");
|
||||||
|
declare type RedisStatus = "wait" | "reconnecting" | "connecting" | "connect" | "ready" | "close" | "end";
|
||||||
|
/**
|
||||||
|
* This is the major component of ioredis.
|
||||||
|
* Use it to connect to a standalone Redis server or Sentinels.
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* const redis = new Redis(); // Default port is 6379
|
||||||
|
* async function main() {
|
||||||
|
* redis.set("foo", "bar");
|
||||||
|
* redis.get("foo", (err, result) => {
|
||||||
|
* // `result` should be "bar"
|
||||||
|
* console.log(err, result);
|
||||||
|
* });
|
||||||
|
* // Or use Promise
|
||||||
|
* const result = await redis.get("foo");
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
declare class Redis extends Commander implements DataHandledable {
|
||||||
|
static Cluster: typeof Cluster;
|
||||||
|
static Command: typeof Command;
|
||||||
|
/**
|
||||||
|
* Default options
|
||||||
|
*/
|
||||||
|
private static defaultOptions;
|
||||||
|
/**
|
||||||
|
* Create a Redis instance.
|
||||||
|
* This is the same as `new Redis()` but is included for compatibility with node-redis.
|
||||||
|
*/
|
||||||
|
static createClient(...args: ConstructorParameters<typeof Redis>): Redis;
|
||||||
|
options: RedisOptions;
|
||||||
|
status: RedisStatus;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
stream: NetStream;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
isCluster: boolean;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
condition: Condition | null;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
commandQueue: Deque<CommandItem>;
|
||||||
|
private connector;
|
||||||
|
private reconnectTimeout;
|
||||||
|
private offlineQueue;
|
||||||
|
private connectionEpoch;
|
||||||
|
private retryAttempts;
|
||||||
|
private manuallyClosing;
|
||||||
|
private socketTimeoutTimer;
|
||||||
|
private _autoPipelines;
|
||||||
|
private _runningAutoPipelines;
|
||||||
|
constructor(port: number, host: string, options: RedisOptions);
|
||||||
|
constructor(path: string, options: RedisOptions);
|
||||||
|
constructor(port: number, options: RedisOptions);
|
||||||
|
constructor(port: number, host: string);
|
||||||
|
constructor(options: RedisOptions);
|
||||||
|
constructor(port: number);
|
||||||
|
constructor(path: string);
|
||||||
|
constructor();
|
||||||
|
get autoPipelineQueueSize(): number;
|
||||||
|
/**
|
||||||
|
* Create a connection to Redis.
|
||||||
|
* This method will be invoked automatically when creating a new Redis instance
|
||||||
|
* unless `lazyConnect: true` is passed.
|
||||||
|
*
|
||||||
|
* When calling this method manually, a Promise is returned, which will
|
||||||
|
* be resolved when the connection status is ready. The promise can reject
|
||||||
|
* if the connection fails, times out, or if Redis is already connecting/connected.
|
||||||
|
*/
|
||||||
|
connect(callback?: Callback<void>): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Disconnect from Redis.
|
||||||
|
*
|
||||||
|
* This method closes the connection immediately,
|
||||||
|
* and may lose some pending replies that haven't written to client.
|
||||||
|
* If you want to wait for the pending replies, use Redis#quit instead.
|
||||||
|
*/
|
||||||
|
disconnect(reconnect?: boolean): void;
|
||||||
|
/**
|
||||||
|
* Disconnect from Redis.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
end(): void;
|
||||||
|
/**
|
||||||
|
* Create a new instance with the same options as the current one.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* var redis = new Redis(6380);
|
||||||
|
* var anotherRedis = redis.duplicate();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
duplicate(override?: Partial<RedisOptions>): Redis;
|
||||||
|
/**
|
||||||
|
* Mode of the connection.
|
||||||
|
*
|
||||||
|
* One of `"normal"`, `"subscriber"`, or `"monitor"`. When the connection is
|
||||||
|
* not in `"normal"` mode, certain commands are not allowed.
|
||||||
|
*/
|
||||||
|
get mode(): "normal" | "subscriber" | "monitor";
|
||||||
|
/**
|
||||||
|
* Listen for all requests received by the server in real time.
|
||||||
|
*
|
||||||
|
* This command will create a new connection to Redis and send a
|
||||||
|
* MONITOR command via the new connection in order to avoid disturbing
|
||||||
|
* the current connection.
|
||||||
|
*
|
||||||
|
* @param callback The callback function. If omit, a promise will be returned.
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* var redis = new Redis();
|
||||||
|
* redis.monitor(function (err, monitor) {
|
||||||
|
* // Entering monitoring mode.
|
||||||
|
* monitor.on('monitor', function (time, args, source, database) {
|
||||||
|
* console.log(time + ": " + util.inspect(args));
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // supports promise as well as other commands
|
||||||
|
* redis.monitor().then(function (monitor) {
|
||||||
|
* monitor.on('monitor', function (time, args, source, database) {
|
||||||
|
* console.log(time + ": " + util.inspect(args));
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
monitor(callback?: Callback<Redis>): Promise<Redis>;
|
||||||
|
/**
|
||||||
|
* Send a command to Redis
|
||||||
|
*
|
||||||
|
* This method is used internally and in most cases you should not
|
||||||
|
* use it directly. If you need to send a command that is not supported
|
||||||
|
* by the library, you can use the `call` method:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* const redis = new Redis();
|
||||||
|
*
|
||||||
|
* redis.call('set', 'foo', 'bar');
|
||||||
|
* // or
|
||||||
|
* redis.call(['set', 'foo', 'bar']);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
sendCommand(command: Command, stream?: WriteableStream): unknown;
|
||||||
|
private getBlockingTimeoutInMs;
|
||||||
|
private getConfiguredBlockingTimeout;
|
||||||
|
private setSocketTimeout;
|
||||||
|
scanStream(options?: ScanStreamOptions): ScanStream;
|
||||||
|
scanBufferStream(options?: ScanStreamOptions): ScanStream;
|
||||||
|
sscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
sscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
hscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
hscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
zscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
zscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
/**
|
||||||
|
* Emit only when there's at least one listener.
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
silentEmit(eventName: string, arg?: unknown): boolean;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
recoverFromFatalError(_commandError: Error, err: Error, options: FlushQueueOptions): void;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
handleReconnection(err: Error, item: CommandItem): void;
|
||||||
|
/**
|
||||||
|
* Get description of the connection. Used for debugging.
|
||||||
|
*/
|
||||||
|
private _getDescription;
|
||||||
|
private resetCommandQueue;
|
||||||
|
private resetOfflineQueue;
|
||||||
|
private parseOptions;
|
||||||
|
/**
|
||||||
|
* Change instance's status
|
||||||
|
*/
|
||||||
|
private setStatus;
|
||||||
|
private createScanStream;
|
||||||
|
/**
|
||||||
|
* Flush offline queue and command queue with error.
|
||||||
|
*
|
||||||
|
* @param error The error object to send to the commands
|
||||||
|
* @param options options
|
||||||
|
*/
|
||||||
|
private flushQueue;
|
||||||
|
/**
|
||||||
|
* Check whether Redis has finished loading the persistent data and is able to
|
||||||
|
* process commands.
|
||||||
|
*/
|
||||||
|
private _readyCheck;
|
||||||
|
}
|
||||||
|
interface Redis extends EventEmitter {
|
||||||
|
on(event: "message", cb: (channel: string, message: string) => void): this;
|
||||||
|
once(event: "message", cb: (channel: string, message: string) => void): this;
|
||||||
|
on(event: "messageBuffer", cb: (channel: Buffer, message: Buffer) => void): this;
|
||||||
|
once(event: "messageBuffer", cb: (channel: Buffer, message: Buffer) => void): this;
|
||||||
|
on(event: "pmessage", cb: (pattern: string, channel: string, message: string) => void): this;
|
||||||
|
once(event: "pmessage", cb: (pattern: string, channel: string, message: string) => void): this;
|
||||||
|
on(event: "pmessageBuffer", cb: (pattern: string, channel: Buffer, message: Buffer) => void): this;
|
||||||
|
once(event: "pmessageBuffer", cb: (pattern: string, channel: Buffer, message: Buffer) => void): this;
|
||||||
|
on(event: "error", cb: (error: Error) => void): this;
|
||||||
|
once(event: "error", cb: (error: Error) => void): this;
|
||||||
|
on(event: RedisStatus, cb: () => void): this;
|
||||||
|
once(event: RedisStatus, cb: () => void): this;
|
||||||
|
on(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
once(event: string | symbol, listener: (...args: any[]) => void): this;
|
||||||
|
}
|
||||||
|
interface Redis extends Transaction {
|
||||||
|
}
|
||||||
|
export default Redis;
|
||||||
745
backend/node_modules/ioredis/built/Redis.js
generated
vendored
Normal file
745
backend/node_modules/ioredis/built/Redis.js
generated
vendored
Normal file
@@ -0,0 +1,745 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const commands_1 = require("@ioredis/commands");
|
||||||
|
const events_1 = require("events");
|
||||||
|
const standard_as_callback_1 = require("standard-as-callback");
|
||||||
|
const cluster_1 = require("./cluster");
|
||||||
|
const Command_1 = require("./Command");
|
||||||
|
const connectors_1 = require("./connectors");
|
||||||
|
const SentinelConnector_1 = require("./connectors/SentinelConnector");
|
||||||
|
const eventHandler = require("./redis/event_handler");
|
||||||
|
const RedisOptions_1 = require("./redis/RedisOptions");
|
||||||
|
const ScanStream_1 = require("./ScanStream");
|
||||||
|
const transaction_1 = require("./transaction");
|
||||||
|
const utils_1 = require("./utils");
|
||||||
|
const applyMixin_1 = require("./utils/applyMixin");
|
||||||
|
const Commander_1 = require("./utils/Commander");
|
||||||
|
const lodash_1 = require("./utils/lodash");
|
||||||
|
const Deque = require("denque");
|
||||||
|
const debug = (0, utils_1.Debug)("redis");
|
||||||
|
/**
|
||||||
|
* This is the major component of ioredis.
|
||||||
|
* Use it to connect to a standalone Redis server or Sentinels.
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* const redis = new Redis(); // Default port is 6379
|
||||||
|
* async function main() {
|
||||||
|
* redis.set("foo", "bar");
|
||||||
|
* redis.get("foo", (err, result) => {
|
||||||
|
* // `result` should be "bar"
|
||||||
|
* console.log(err, result);
|
||||||
|
* });
|
||||||
|
* // Or use Promise
|
||||||
|
* const result = await redis.get("foo");
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class Redis extends Commander_1.default {
|
||||||
|
constructor(arg1, arg2, arg3) {
|
||||||
|
super();
|
||||||
|
this.status = "wait";
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
this.isCluster = false;
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
this.connectionEpoch = 0;
|
||||||
|
this.retryAttempts = 0;
|
||||||
|
this.manuallyClosing = false;
|
||||||
|
// Prepare autopipelines structures
|
||||||
|
this._autoPipelines = new Map();
|
||||||
|
this._runningAutoPipelines = new Set();
|
||||||
|
this.parseOptions(arg1, arg2, arg3);
|
||||||
|
events_1.EventEmitter.call(this);
|
||||||
|
this.resetCommandQueue();
|
||||||
|
this.resetOfflineQueue();
|
||||||
|
if (this.options.Connector) {
|
||||||
|
this.connector = new this.options.Connector(this.options);
|
||||||
|
}
|
||||||
|
else if (this.options.sentinels) {
|
||||||
|
const sentinelConnector = new SentinelConnector_1.default(this.options);
|
||||||
|
sentinelConnector.emitter = this;
|
||||||
|
this.connector = sentinelConnector;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.connector = new connectors_1.StandaloneConnector(this.options);
|
||||||
|
}
|
||||||
|
if (this.options.scripts) {
|
||||||
|
Object.entries(this.options.scripts).forEach(([name, definition]) => {
|
||||||
|
this.defineCommand(name, definition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// end(or wait) -> connecting -> connect -> ready -> end
|
||||||
|
if (this.options.lazyConnect) {
|
||||||
|
this.setStatus("wait");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.connect().catch(lodash_1.noop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a Redis instance.
|
||||||
|
* This is the same as `new Redis()` but is included for compatibility with node-redis.
|
||||||
|
*/
|
||||||
|
static createClient(...args) {
|
||||||
|
return new Redis(...args);
|
||||||
|
}
|
||||||
|
get autoPipelineQueueSize() {
|
||||||
|
let queued = 0;
|
||||||
|
for (const pipeline of this._autoPipelines.values()) {
|
||||||
|
queued += pipeline.length;
|
||||||
|
}
|
||||||
|
return queued;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a connection to Redis.
|
||||||
|
* This method will be invoked automatically when creating a new Redis instance
|
||||||
|
* unless `lazyConnect: true` is passed.
|
||||||
|
*
|
||||||
|
* When calling this method manually, a Promise is returned, which will
|
||||||
|
* be resolved when the connection status is ready. The promise can reject
|
||||||
|
* if the connection fails, times out, or if Redis is already connecting/connected.
|
||||||
|
*/
|
||||||
|
connect(callback) {
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
if (this.status === "connecting" ||
|
||||||
|
this.status === "connect" ||
|
||||||
|
this.status === "ready") {
|
||||||
|
reject(new Error("Redis is already connecting/connected"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.connectionEpoch += 1;
|
||||||
|
this.setStatus("connecting");
|
||||||
|
const { options } = this;
|
||||||
|
this.condition = {
|
||||||
|
select: options.db,
|
||||||
|
auth: options.username
|
||||||
|
? [options.username, options.password]
|
||||||
|
: options.password,
|
||||||
|
subscriber: false,
|
||||||
|
};
|
||||||
|
const _this = this;
|
||||||
|
(0, standard_as_callback_1.default)(this.connector.connect(function (type, err) {
|
||||||
|
_this.silentEmit(type, err);
|
||||||
|
}), function (err, stream) {
|
||||||
|
if (err) {
|
||||||
|
_this.flushQueue(err);
|
||||||
|
_this.silentEmit("error", err);
|
||||||
|
reject(err);
|
||||||
|
_this.setStatus("end");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let CONNECT_EVENT = options.tls ? "secureConnect" : "connect";
|
||||||
|
if ("sentinels" in options &&
|
||||||
|
options.sentinels &&
|
||||||
|
!options.enableTLSForSentinelMode) {
|
||||||
|
CONNECT_EVENT = "connect";
|
||||||
|
}
|
||||||
|
_this.stream = stream;
|
||||||
|
if (options.noDelay) {
|
||||||
|
stream.setNoDelay(true);
|
||||||
|
}
|
||||||
|
// Node ignores setKeepAlive before connect, therefore we wait for the event:
|
||||||
|
// https://github.com/nodejs/node/issues/31663
|
||||||
|
if (typeof options.keepAlive === "number") {
|
||||||
|
if (stream.connecting) {
|
||||||
|
stream.once(CONNECT_EVENT, () => {
|
||||||
|
stream.setKeepAlive(true, options.keepAlive);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stream.setKeepAlive(true, options.keepAlive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stream.connecting) {
|
||||||
|
stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this));
|
||||||
|
if (options.connectTimeout) {
|
||||||
|
/*
|
||||||
|
* Typically, Socket#setTimeout(0) will clear the timer
|
||||||
|
* set before. However, in some platforms (Electron 3.x~4.x),
|
||||||
|
* the timer will not be cleared. So we introduce a variable here.
|
||||||
|
*
|
||||||
|
* See https://github.com/electron/electron/issues/14915
|
||||||
|
*/
|
||||||
|
let connectTimeoutCleared = false;
|
||||||
|
stream.setTimeout(options.connectTimeout, function () {
|
||||||
|
if (connectTimeoutCleared) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
stream.setTimeout(0);
|
||||||
|
stream.destroy();
|
||||||
|
const err = new Error("connect ETIMEDOUT");
|
||||||
|
// @ts-expect-error
|
||||||
|
err.errorno = "ETIMEDOUT";
|
||||||
|
// @ts-expect-error
|
||||||
|
err.code = "ETIMEDOUT";
|
||||||
|
// @ts-expect-error
|
||||||
|
err.syscall = "connect";
|
||||||
|
eventHandler.errorHandler(_this)(err);
|
||||||
|
});
|
||||||
|
stream.once(CONNECT_EVENT, function () {
|
||||||
|
connectTimeoutCleared = true;
|
||||||
|
stream.setTimeout(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (stream.destroyed) {
|
||||||
|
const firstError = _this.connector.firstError;
|
||||||
|
if (firstError) {
|
||||||
|
process.nextTick(() => {
|
||||||
|
eventHandler.errorHandler(_this)(firstError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
process.nextTick(eventHandler.closeHandler(_this));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
process.nextTick(eventHandler.connectHandler(_this));
|
||||||
|
}
|
||||||
|
if (!stream.destroyed) {
|
||||||
|
stream.once("error", eventHandler.errorHandler(_this));
|
||||||
|
stream.once("close", eventHandler.closeHandler(_this));
|
||||||
|
}
|
||||||
|
const connectionReadyHandler = function () {
|
||||||
|
_this.removeListener("close", connectionCloseHandler);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
var connectionCloseHandler = function () {
|
||||||
|
_this.removeListener("ready", connectionReadyHandler);
|
||||||
|
reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
|
||||||
|
};
|
||||||
|
_this.once("ready", connectionReadyHandler);
|
||||||
|
_this.once("close", connectionCloseHandler);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return (0, standard_as_callback_1.default)(promise, callback);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Disconnect from Redis.
|
||||||
|
*
|
||||||
|
* This method closes the connection immediately,
|
||||||
|
* and may lose some pending replies that haven't written to client.
|
||||||
|
* If you want to wait for the pending replies, use Redis#quit instead.
|
||||||
|
*/
|
||||||
|
disconnect(reconnect = false) {
|
||||||
|
if (!reconnect) {
|
||||||
|
this.manuallyClosing = true;
|
||||||
|
}
|
||||||
|
if (this.reconnectTimeout && !reconnect) {
|
||||||
|
clearTimeout(this.reconnectTimeout);
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
}
|
||||||
|
if (this.status === "wait") {
|
||||||
|
eventHandler.closeHandler(this)();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.connector.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Disconnect from Redis.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
end() {
|
||||||
|
this.disconnect();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a new instance with the same options as the current one.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* var redis = new Redis(6380);
|
||||||
|
* var anotherRedis = redis.duplicate();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
duplicate(override) {
|
||||||
|
return new Redis({ ...this.options, ...override });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Mode of the connection.
|
||||||
|
*
|
||||||
|
* One of `"normal"`, `"subscriber"`, or `"monitor"`. When the connection is
|
||||||
|
* not in `"normal"` mode, certain commands are not allowed.
|
||||||
|
*/
|
||||||
|
get mode() {
|
||||||
|
var _a;
|
||||||
|
return this.options.monitor
|
||||||
|
? "monitor"
|
||||||
|
: ((_a = this.condition) === null || _a === void 0 ? void 0 : _a.subscriber)
|
||||||
|
? "subscriber"
|
||||||
|
: "normal";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Listen for all requests received by the server in real time.
|
||||||
|
*
|
||||||
|
* This command will create a new connection to Redis and send a
|
||||||
|
* MONITOR command via the new connection in order to avoid disturbing
|
||||||
|
* the current connection.
|
||||||
|
*
|
||||||
|
* @param callback The callback function. If omit, a promise will be returned.
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* var redis = new Redis();
|
||||||
|
* redis.monitor(function (err, monitor) {
|
||||||
|
* // Entering monitoring mode.
|
||||||
|
* monitor.on('monitor', function (time, args, source, database) {
|
||||||
|
* console.log(time + ": " + util.inspect(args));
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // supports promise as well as other commands
|
||||||
|
* redis.monitor().then(function (monitor) {
|
||||||
|
* monitor.on('monitor', function (time, args, source, database) {
|
||||||
|
* console.log(time + ": " + util.inspect(args));
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
monitor(callback) {
|
||||||
|
const monitorInstance = this.duplicate({
|
||||||
|
monitor: true,
|
||||||
|
lazyConnect: false,
|
||||||
|
});
|
||||||
|
return (0, standard_as_callback_1.default)(new Promise(function (resolve, reject) {
|
||||||
|
monitorInstance.once("error", reject);
|
||||||
|
monitorInstance.once("monitoring", function () {
|
||||||
|
resolve(monitorInstance);
|
||||||
|
});
|
||||||
|
}), callback);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Send a command to Redis
|
||||||
|
*
|
||||||
|
* This method is used internally and in most cases you should not
|
||||||
|
* use it directly. If you need to send a command that is not supported
|
||||||
|
* by the library, you can use the `call` method:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* const redis = new Redis();
|
||||||
|
*
|
||||||
|
* redis.call('set', 'foo', 'bar');
|
||||||
|
* // or
|
||||||
|
* redis.call(['set', 'foo', 'bar']);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
sendCommand(command, stream) {
|
||||||
|
var _a, _b;
|
||||||
|
if (this.status === "wait") {
|
||||||
|
this.connect().catch(lodash_1.noop);
|
||||||
|
}
|
||||||
|
if (this.status === "end") {
|
||||||
|
command.reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
|
||||||
|
return command.promise;
|
||||||
|
}
|
||||||
|
if (((_a = this.condition) === null || _a === void 0 ? void 0 : _a.subscriber) &&
|
||||||
|
!Command_1.default.checkFlag("VALID_IN_SUBSCRIBER_MODE", command.name)) {
|
||||||
|
command.reject(new Error("Connection in subscriber mode, only subscriber commands may be used"));
|
||||||
|
return command.promise;
|
||||||
|
}
|
||||||
|
if (typeof this.options.commandTimeout === "number") {
|
||||||
|
command.setTimeout(this.options.commandTimeout);
|
||||||
|
}
|
||||||
|
const blockingTimeout = this.getBlockingTimeoutInMs(command);
|
||||||
|
let writable = this.status === "ready" ||
|
||||||
|
(!stream &&
|
||||||
|
this.status === "connect" &&
|
||||||
|
(0, commands_1.exists)(command.name, { caseInsensitive: true }) &&
|
||||||
|
((0, commands_1.hasFlag)(command.name, "loading", { nameCaseInsensitive: true }) ||
|
||||||
|
Command_1.default.checkFlag("HANDSHAKE_COMMANDS", command.name)));
|
||||||
|
if (!this.stream) {
|
||||||
|
writable = false;
|
||||||
|
}
|
||||||
|
else if (!this.stream.writable) {
|
||||||
|
writable = false;
|
||||||
|
// @ts-expect-error
|
||||||
|
}
|
||||||
|
else if (this.stream._writableState && this.stream._writableState.ended) {
|
||||||
|
// TODO: We should be able to remove this as the PR has already been merged.
|
||||||
|
// https://github.com/iojs/io.js/pull/1217
|
||||||
|
writable = false;
|
||||||
|
}
|
||||||
|
if (!writable) {
|
||||||
|
if (!this.options.enableOfflineQueue) {
|
||||||
|
command.reject(new Error("Stream isn't writeable and enableOfflineQueue options is false"));
|
||||||
|
return command.promise;
|
||||||
|
}
|
||||||
|
if (command.name === "quit" && this.offlineQueue.length === 0) {
|
||||||
|
this.disconnect();
|
||||||
|
command.resolve(Buffer.from("OK"));
|
||||||
|
return command.promise;
|
||||||
|
}
|
||||||
|
// @ts-expect-error
|
||||||
|
if (debug.enabled) {
|
||||||
|
debug("queue command[%s]: %d -> %s(%o)", this._getDescription(), this.condition.select, command.name, command.args);
|
||||||
|
}
|
||||||
|
this.offlineQueue.push({
|
||||||
|
command: command,
|
||||||
|
stream: stream,
|
||||||
|
select: this.condition.select,
|
||||||
|
});
|
||||||
|
// For blocking commands in the offline queue, arm a client-side timeout
|
||||||
|
// only when blockingTimeout is configured. Without this option, queued
|
||||||
|
// blocking commands may wait indefinitely on a dead connection.
|
||||||
|
if (Command_1.default.checkFlag("BLOCKING_COMMANDS", command.name)) {
|
||||||
|
const offlineTimeout = this.getConfiguredBlockingTimeout();
|
||||||
|
if (offlineTimeout !== undefined) {
|
||||||
|
command.setBlockingTimeout(offlineTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// @ts-expect-error
|
||||||
|
if (debug.enabled) {
|
||||||
|
debug("write command[%s]: %d -> %s(%o)", this._getDescription(), (_b = this.condition) === null || _b === void 0 ? void 0 : _b.select, command.name, command.args);
|
||||||
|
}
|
||||||
|
if (stream) {
|
||||||
|
if ("isPipeline" in stream && stream.isPipeline) {
|
||||||
|
stream.write(command.toWritable(stream.destination.redis.stream));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stream.write(command.toWritable(stream));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.stream.write(command.toWritable(this.stream));
|
||||||
|
}
|
||||||
|
this.commandQueue.push({
|
||||||
|
command: command,
|
||||||
|
stream: stream,
|
||||||
|
select: this.condition.select,
|
||||||
|
});
|
||||||
|
if (blockingTimeout !== undefined) {
|
||||||
|
command.setBlockingTimeout(blockingTimeout);
|
||||||
|
}
|
||||||
|
if (Command_1.default.checkFlag("WILL_DISCONNECT", command.name)) {
|
||||||
|
this.manuallyClosing = true;
|
||||||
|
}
|
||||||
|
if (this.options.socketTimeout !== undefined && this.socketTimeoutTimer === undefined) {
|
||||||
|
this.setSocketTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (command.name === "select" && (0, utils_1.isInt)(command.args[0])) {
|
||||||
|
const db = parseInt(command.args[0], 10);
|
||||||
|
if (this.condition.select !== db) {
|
||||||
|
this.condition.select = db;
|
||||||
|
this.emit("select", db);
|
||||||
|
debug("switch to db [%d]", this.condition.select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return command.promise;
|
||||||
|
}
|
||||||
|
getBlockingTimeoutInMs(command) {
|
||||||
|
var _a;
|
||||||
|
if (!Command_1.default.checkFlag("BLOCKING_COMMANDS", command.name)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// Feature is opt-in: only enabled when blockingTimeout is set to a positive number
|
||||||
|
const configuredTimeout = this.getConfiguredBlockingTimeout();
|
||||||
|
if (configuredTimeout === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const timeout = command.extractBlockingTimeout();
|
||||||
|
if (typeof timeout === "number") {
|
||||||
|
if (timeout > 0) {
|
||||||
|
// Finite timeout from command args - add grace period
|
||||||
|
return timeout + ((_a = this.options.blockingTimeoutGrace) !== null && _a !== void 0 ? _a : RedisOptions_1.DEFAULT_REDIS_OPTIONS.blockingTimeoutGrace);
|
||||||
|
}
|
||||||
|
// Command has timeout=0 (block forever), use blockingTimeout option as safety net
|
||||||
|
return configuredTimeout;
|
||||||
|
}
|
||||||
|
if (timeout === null) {
|
||||||
|
// No BLOCK option found (e.g., XREAD without BLOCK), use blockingTimeout as safety net
|
||||||
|
return configuredTimeout;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
getConfiguredBlockingTimeout() {
|
||||||
|
if (typeof this.options.blockingTimeout === "number" &&
|
||||||
|
this.options.blockingTimeout > 0) {
|
||||||
|
return this.options.blockingTimeout;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
setSocketTimeout() {
|
||||||
|
this.socketTimeoutTimer = setTimeout(() => {
|
||||||
|
this.stream.destroy(new Error(`Socket timeout. Expecting data, but didn't receive any in ${this.options.socketTimeout}ms.`));
|
||||||
|
this.socketTimeoutTimer = undefined;
|
||||||
|
}, this.options.socketTimeout);
|
||||||
|
// this handler must run after the "data" handler in "DataHandler"
|
||||||
|
// so that `this.commandQueue.length` will be updated
|
||||||
|
this.stream.once("data", () => {
|
||||||
|
clearTimeout(this.socketTimeoutTimer);
|
||||||
|
this.socketTimeoutTimer = undefined;
|
||||||
|
if (this.commandQueue.length === 0)
|
||||||
|
return;
|
||||||
|
this.setSocketTimeout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
scanStream(options) {
|
||||||
|
return this.createScanStream("scan", { options });
|
||||||
|
}
|
||||||
|
scanBufferStream(options) {
|
||||||
|
return this.createScanStream("scanBuffer", { options });
|
||||||
|
}
|
||||||
|
sscanStream(key, options) {
|
||||||
|
return this.createScanStream("sscan", { key, options });
|
||||||
|
}
|
||||||
|
sscanBufferStream(key, options) {
|
||||||
|
return this.createScanStream("sscanBuffer", { key, options });
|
||||||
|
}
|
||||||
|
hscanStream(key, options) {
|
||||||
|
return this.createScanStream("hscan", { key, options });
|
||||||
|
}
|
||||||
|
hscanBufferStream(key, options) {
|
||||||
|
return this.createScanStream("hscanBuffer", { key, options });
|
||||||
|
}
|
||||||
|
zscanStream(key, options) {
|
||||||
|
return this.createScanStream("zscan", { key, options });
|
||||||
|
}
|
||||||
|
zscanBufferStream(key, options) {
|
||||||
|
return this.createScanStream("zscanBuffer", { key, options });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Emit only when there's at least one listener.
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
silentEmit(eventName, arg) {
|
||||||
|
let error;
|
||||||
|
if (eventName === "error") {
|
||||||
|
error = arg;
|
||||||
|
if (this.status === "end") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.manuallyClosing) {
|
||||||
|
// ignore connection related errors when manually disconnecting
|
||||||
|
if (error instanceof Error &&
|
||||||
|
(error.message === utils_1.CONNECTION_CLOSED_ERROR_MSG ||
|
||||||
|
// @ts-expect-error
|
||||||
|
error.syscall === "connect" ||
|
||||||
|
// @ts-expect-error
|
||||||
|
error.syscall === "read")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.listeners(eventName).length > 0) {
|
||||||
|
return this.emit.apply(this, arguments);
|
||||||
|
}
|
||||||
|
if (error && error instanceof Error) {
|
||||||
|
console.error("[ioredis] Unhandled error event:", error.stack);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
recoverFromFatalError(_commandError, err, options) {
|
||||||
|
this.flushQueue(err, options);
|
||||||
|
this.silentEmit("error", err);
|
||||||
|
this.disconnect(true);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
handleReconnection(err, item) {
|
||||||
|
var _a;
|
||||||
|
let needReconnect = false;
|
||||||
|
if (this.options.reconnectOnError &&
|
||||||
|
!Command_1.default.checkFlag("IGNORE_RECONNECT_ON_ERROR", item.command.name)) {
|
||||||
|
needReconnect = this.options.reconnectOnError(err);
|
||||||
|
}
|
||||||
|
switch (needReconnect) {
|
||||||
|
case 1:
|
||||||
|
case true:
|
||||||
|
if (this.status !== "reconnecting") {
|
||||||
|
this.disconnect(true);
|
||||||
|
}
|
||||||
|
item.command.reject(err);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (this.status !== "reconnecting") {
|
||||||
|
this.disconnect(true);
|
||||||
|
}
|
||||||
|
if (((_a = this.condition) === null || _a === void 0 ? void 0 : _a.select) !== item.select &&
|
||||||
|
item.command.name !== "select") {
|
||||||
|
this.select(item.select);
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
// @ts-expect-error
|
||||||
|
this.sendCommand(item.command);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
item.command.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get description of the connection. Used for debugging.
|
||||||
|
*/
|
||||||
|
_getDescription() {
|
||||||
|
let description;
|
||||||
|
if ("path" in this.options && this.options.path) {
|
||||||
|
description = this.options.path;
|
||||||
|
}
|
||||||
|
else if (this.stream &&
|
||||||
|
this.stream.remoteAddress &&
|
||||||
|
this.stream.remotePort) {
|
||||||
|
description = this.stream.remoteAddress + ":" + this.stream.remotePort;
|
||||||
|
}
|
||||||
|
else if ("host" in this.options && this.options.host) {
|
||||||
|
description = this.options.host + ":" + this.options.port;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unexpected
|
||||||
|
description = "";
|
||||||
|
}
|
||||||
|
if (this.options.connectionName) {
|
||||||
|
description += ` (${this.options.connectionName})`;
|
||||||
|
}
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
resetCommandQueue() {
|
||||||
|
this.commandQueue = new Deque();
|
||||||
|
}
|
||||||
|
resetOfflineQueue() {
|
||||||
|
this.offlineQueue = new Deque();
|
||||||
|
}
|
||||||
|
parseOptions(...args) {
|
||||||
|
const options = {};
|
||||||
|
let isTls = false;
|
||||||
|
for (let i = 0; i < args.length; ++i) {
|
||||||
|
const arg = args[i];
|
||||||
|
if (arg === null || typeof arg === "undefined") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof arg === "object") {
|
||||||
|
(0, lodash_1.defaults)(options, arg);
|
||||||
|
}
|
||||||
|
else if (typeof arg === "string") {
|
||||||
|
(0, lodash_1.defaults)(options, (0, utils_1.parseURL)(arg));
|
||||||
|
if (arg.startsWith("rediss://")) {
|
||||||
|
isTls = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (typeof arg === "number") {
|
||||||
|
options.port = arg;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error("Invalid argument " + arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isTls) {
|
||||||
|
(0, lodash_1.defaults)(options, { tls: true });
|
||||||
|
}
|
||||||
|
(0, lodash_1.defaults)(options, Redis.defaultOptions);
|
||||||
|
if (typeof options.port === "string") {
|
||||||
|
options.port = parseInt(options.port, 10);
|
||||||
|
}
|
||||||
|
if (typeof options.db === "string") {
|
||||||
|
options.db = parseInt(options.db, 10);
|
||||||
|
}
|
||||||
|
// @ts-expect-error
|
||||||
|
this.options = (0, utils_1.resolveTLSProfile)(options);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Change instance's status
|
||||||
|
*/
|
||||||
|
setStatus(status, arg) {
|
||||||
|
// @ts-expect-error
|
||||||
|
if (debug.enabled) {
|
||||||
|
debug("status[%s]: %s -> %s", this._getDescription(), this.status || "[empty]", status);
|
||||||
|
}
|
||||||
|
this.status = status;
|
||||||
|
process.nextTick(this.emit.bind(this, status, arg));
|
||||||
|
}
|
||||||
|
createScanStream(command, { key, options = {} }) {
|
||||||
|
return new ScanStream_1.default({
|
||||||
|
objectMode: true,
|
||||||
|
key: key,
|
||||||
|
redis: this,
|
||||||
|
command: command,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Flush offline queue and command queue with error.
|
||||||
|
*
|
||||||
|
* @param error The error object to send to the commands
|
||||||
|
* @param options options
|
||||||
|
*/
|
||||||
|
flushQueue(error, options) {
|
||||||
|
options = (0, lodash_1.defaults)({}, options, {
|
||||||
|
offlineQueue: true,
|
||||||
|
commandQueue: true,
|
||||||
|
});
|
||||||
|
let item;
|
||||||
|
if (options.offlineQueue) {
|
||||||
|
while ((item = this.offlineQueue.shift())) {
|
||||||
|
item.command.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.commandQueue) {
|
||||||
|
if (this.commandQueue.length > 0) {
|
||||||
|
if (this.stream) {
|
||||||
|
this.stream.removeAllListeners("data");
|
||||||
|
}
|
||||||
|
while ((item = this.commandQueue.shift())) {
|
||||||
|
item.command.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check whether Redis has finished loading the persistent data and is able to
|
||||||
|
* process commands.
|
||||||
|
*/
|
||||||
|
_readyCheck(callback) {
|
||||||
|
const _this = this;
|
||||||
|
this.info(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
if (err.message && err.message.includes("NOPERM")) {
|
||||||
|
console.warn(`Skipping the ready check because INFO command fails: "${err.message}". You can disable ready check with "enableReadyCheck". More: https://github.com/luin/ioredis/wiki/Disable-ready-check.`);
|
||||||
|
return callback(null, {});
|
||||||
|
}
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
if (typeof res !== "string") {
|
||||||
|
return callback(null, res);
|
||||||
|
}
|
||||||
|
const info = {};
|
||||||
|
const lines = res.split("\r\n");
|
||||||
|
for (let i = 0; i < lines.length; ++i) {
|
||||||
|
const [fieldName, ...fieldValueParts] = lines[i].split(":");
|
||||||
|
const fieldValue = fieldValueParts.join(":");
|
||||||
|
if (fieldValue) {
|
||||||
|
info[fieldName] = fieldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!info.loading || info.loading === "0") {
|
||||||
|
callback(null, info);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const loadingEtaMs = (info.loading_eta_seconds || 1) * 1000;
|
||||||
|
const retryTime = _this.options.maxLoadingRetryTime &&
|
||||||
|
_this.options.maxLoadingRetryTime < loadingEtaMs
|
||||||
|
? _this.options.maxLoadingRetryTime
|
||||||
|
: loadingEtaMs;
|
||||||
|
debug("Redis server still loading, trying again in " + retryTime + "ms");
|
||||||
|
setTimeout(function () {
|
||||||
|
_this._readyCheck(callback);
|
||||||
|
}, retryTime);
|
||||||
|
}
|
||||||
|
}).catch(lodash_1.noop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Redis.Cluster = cluster_1.default;
|
||||||
|
Redis.Command = Command_1.default;
|
||||||
|
/**
|
||||||
|
* Default options
|
||||||
|
*/
|
||||||
|
Redis.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
|
||||||
|
(0, applyMixin_1.default)(Redis, events_1.EventEmitter);
|
||||||
|
(0, transaction_1.addTransactionSupport)(Redis.prototype);
|
||||||
|
exports.default = Redis;
|
||||||
23
backend/node_modules/ioredis/built/ScanStream.d.ts
generated
vendored
Normal file
23
backend/node_modules/ioredis/built/ScanStream.d.ts
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { Readable, ReadableOptions } from "stream";
|
||||||
|
interface Options extends ReadableOptions {
|
||||||
|
key?: string;
|
||||||
|
match?: string;
|
||||||
|
type?: string;
|
||||||
|
command: string;
|
||||||
|
redis: any;
|
||||||
|
count?: string | number;
|
||||||
|
noValues?: boolean;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convenient class to convert the process of scanning keys to a readable stream.
|
||||||
|
*/
|
||||||
|
export default class ScanStream extends Readable {
|
||||||
|
private opt;
|
||||||
|
private _redisCursor;
|
||||||
|
private _redisDrained;
|
||||||
|
constructor(opt: Options);
|
||||||
|
_read(): void;
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
51
backend/node_modules/ioredis/built/ScanStream.js
generated
vendored
Normal file
51
backend/node_modules/ioredis/built/ScanStream.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const stream_1 = require("stream");
|
||||||
|
/**
|
||||||
|
* Convenient class to convert the process of scanning keys to a readable stream.
|
||||||
|
*/
|
||||||
|
class ScanStream extends stream_1.Readable {
|
||||||
|
constructor(opt) {
|
||||||
|
super(opt);
|
||||||
|
this.opt = opt;
|
||||||
|
this._redisCursor = "0";
|
||||||
|
this._redisDrained = false;
|
||||||
|
}
|
||||||
|
_read() {
|
||||||
|
if (this._redisDrained) {
|
||||||
|
this.push(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const args = [this._redisCursor];
|
||||||
|
if (this.opt.key) {
|
||||||
|
args.unshift(this.opt.key);
|
||||||
|
}
|
||||||
|
if (this.opt.match) {
|
||||||
|
args.push("MATCH", this.opt.match);
|
||||||
|
}
|
||||||
|
if (this.opt.type) {
|
||||||
|
args.push("TYPE", this.opt.type);
|
||||||
|
}
|
||||||
|
if (this.opt.count) {
|
||||||
|
args.push("COUNT", String(this.opt.count));
|
||||||
|
}
|
||||||
|
if (this.opt.noValues) {
|
||||||
|
args.push("NOVALUES");
|
||||||
|
}
|
||||||
|
this.opt.redis[this.opt.command](args, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
this.emit("error", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._redisCursor = res[0] instanceof Buffer ? res[0].toString() : res[0];
|
||||||
|
if (this._redisCursor === "0") {
|
||||||
|
this._redisDrained = true;
|
||||||
|
}
|
||||||
|
this.push(res[1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
close() {
|
||||||
|
this._redisDrained = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = ScanStream;
|
||||||
11
backend/node_modules/ioredis/built/Script.d.ts
generated
vendored
Normal file
11
backend/node_modules/ioredis/built/Script.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Callback } from "./types";
|
||||||
|
export default class Script {
|
||||||
|
private lua;
|
||||||
|
private numberOfKeys;
|
||||||
|
private keyPrefix;
|
||||||
|
private readOnly;
|
||||||
|
private sha;
|
||||||
|
private Command;
|
||||||
|
constructor(lua: string, numberOfKeys?: number | null, keyPrefix?: string, readOnly?: boolean);
|
||||||
|
execute(container: any, args: any[], options: any, callback?: Callback): any;
|
||||||
|
}
|
||||||
62
backend/node_modules/ioredis/built/Script.js
generated
vendored
Normal file
62
backend/node_modules/ioredis/built/Script.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const crypto_1 = require("crypto");
|
||||||
|
const Command_1 = require("./Command");
|
||||||
|
const standard_as_callback_1 = require("standard-as-callback");
|
||||||
|
class Script {
|
||||||
|
constructor(lua, numberOfKeys = null, keyPrefix = "", readOnly = false) {
|
||||||
|
this.lua = lua;
|
||||||
|
this.numberOfKeys = numberOfKeys;
|
||||||
|
this.keyPrefix = keyPrefix;
|
||||||
|
this.readOnly = readOnly;
|
||||||
|
this.sha = (0, crypto_1.createHash)("sha1").update(lua).digest("hex");
|
||||||
|
const sha = this.sha;
|
||||||
|
const socketHasScriptLoaded = new WeakSet();
|
||||||
|
this.Command = class CustomScriptCommand extends Command_1.default {
|
||||||
|
toWritable(socket) {
|
||||||
|
const origReject = this.reject;
|
||||||
|
this.reject = (err) => {
|
||||||
|
if (err.message.indexOf("NOSCRIPT") !== -1) {
|
||||||
|
socketHasScriptLoaded.delete(socket);
|
||||||
|
}
|
||||||
|
origReject.call(this, err);
|
||||||
|
};
|
||||||
|
if (!socketHasScriptLoaded.has(socket)) {
|
||||||
|
socketHasScriptLoaded.add(socket);
|
||||||
|
this.name = "eval";
|
||||||
|
this.args[0] = lua;
|
||||||
|
}
|
||||||
|
else if (this.name === "eval") {
|
||||||
|
this.name = "evalsha";
|
||||||
|
this.args[0] = sha;
|
||||||
|
}
|
||||||
|
return super.toWritable(socket);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
execute(container, args, options, callback) {
|
||||||
|
if (typeof this.numberOfKeys === "number") {
|
||||||
|
args.unshift(this.numberOfKeys);
|
||||||
|
}
|
||||||
|
if (this.keyPrefix) {
|
||||||
|
options.keyPrefix = this.keyPrefix;
|
||||||
|
}
|
||||||
|
if (this.readOnly) {
|
||||||
|
options.readOnly = true;
|
||||||
|
}
|
||||||
|
const evalsha = new this.Command("evalsha", [this.sha, ...args], options);
|
||||||
|
evalsha.promise = evalsha.promise.catch((err) => {
|
||||||
|
if (err.message.indexOf("NOSCRIPT") === -1) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
// Resend the same custom evalsha command that gets transformed
|
||||||
|
// to an eval in case it's not loaded yet on the connection.
|
||||||
|
const resend = new this.Command("evalsha", [this.sha, ...args], options);
|
||||||
|
const client = container.isPipeline ? container.redis : container;
|
||||||
|
return client.sendCommand(resend);
|
||||||
|
});
|
||||||
|
(0, standard_as_callback_1.default)(evalsha.promise, callback);
|
||||||
|
return container.sendCommand(evalsha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = Script;
|
||||||
14
backend/node_modules/ioredis/built/SubscriptionSet.d.ts
generated
vendored
Normal file
14
backend/node_modules/ioredis/built/SubscriptionSet.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { CommandNameFlags } from "./Command";
|
||||||
|
declare type AddSet = CommandNameFlags["ENTER_SUBSCRIBER_MODE"][number];
|
||||||
|
declare type DelSet = CommandNameFlags["EXIT_SUBSCRIBER_MODE"][number];
|
||||||
|
/**
|
||||||
|
* Tiny class to simplify dealing with subscription set
|
||||||
|
*/
|
||||||
|
export default class SubscriptionSet {
|
||||||
|
private set;
|
||||||
|
add(set: AddSet, channel: string): void;
|
||||||
|
del(set: DelSet, channel: string): void;
|
||||||
|
channels(set: AddSet | DelSet): string[];
|
||||||
|
isEmpty(): boolean;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
41
backend/node_modules/ioredis/built/SubscriptionSet.js
generated
vendored
Normal file
41
backend/node_modules/ioredis/built/SubscriptionSet.js
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/**
|
||||||
|
* Tiny class to simplify dealing with subscription set
|
||||||
|
*/
|
||||||
|
class SubscriptionSet {
|
||||||
|
constructor() {
|
||||||
|
this.set = {
|
||||||
|
subscribe: {},
|
||||||
|
psubscribe: {},
|
||||||
|
ssubscribe: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
add(set, channel) {
|
||||||
|
this.set[mapSet(set)][channel] = true;
|
||||||
|
}
|
||||||
|
del(set, channel) {
|
||||||
|
delete this.set[mapSet(set)][channel];
|
||||||
|
}
|
||||||
|
channels(set) {
|
||||||
|
return Object.keys(this.set[mapSet(set)]);
|
||||||
|
}
|
||||||
|
isEmpty() {
|
||||||
|
return (this.channels("subscribe").length === 0 &&
|
||||||
|
this.channels("psubscribe").length === 0 &&
|
||||||
|
this.channels("ssubscribe").length === 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = SubscriptionSet;
|
||||||
|
function mapSet(set) {
|
||||||
|
if (set === "unsubscribe") {
|
||||||
|
return "subscribe";
|
||||||
|
}
|
||||||
|
if (set === "punsubscribe") {
|
||||||
|
return "psubscribe";
|
||||||
|
}
|
||||||
|
if (set === "sunsubscribe") {
|
||||||
|
return "ssubscribe";
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
8
backend/node_modules/ioredis/built/autoPipelining.d.ts
generated
vendored
Normal file
8
backend/node_modules/ioredis/built/autoPipelining.d.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { ArgumentType } from "./Command";
|
||||||
|
export declare const kExec: unique symbol;
|
||||||
|
export declare const kCallbacks: unique symbol;
|
||||||
|
export declare const notAllowedAutoPipelineCommands: string[];
|
||||||
|
export declare function shouldUseAutoPipelining(client: any, functionName: string, commandName: string): boolean;
|
||||||
|
export declare function getFirstValueInFlattenedArray(args: ArgumentType[]): string | Buffer | number | null | undefined;
|
||||||
|
export declare function executeWithAutoPipelining(client: any, functionName: string, commandName: string, args: ArgumentType[], callback: any): Promise<unknown>;
|
||||||
167
backend/node_modules/ioredis/built/autoPipelining.js
generated
vendored
Normal file
167
backend/node_modules/ioredis/built/autoPipelining.js
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.executeWithAutoPipelining = exports.getFirstValueInFlattenedArray = exports.shouldUseAutoPipelining = exports.notAllowedAutoPipelineCommands = exports.kCallbacks = exports.kExec = void 0;
|
||||||
|
const lodash_1 = require("./utils/lodash");
|
||||||
|
const calculateSlot = require("cluster-key-slot");
|
||||||
|
const standard_as_callback_1 = require("standard-as-callback");
|
||||||
|
const commands_1 = require("@ioredis/commands");
|
||||||
|
exports.kExec = Symbol("exec");
|
||||||
|
exports.kCallbacks = Symbol("callbacks");
|
||||||
|
exports.notAllowedAutoPipelineCommands = [
|
||||||
|
"auth",
|
||||||
|
"info",
|
||||||
|
"script",
|
||||||
|
"quit",
|
||||||
|
"cluster",
|
||||||
|
"pipeline",
|
||||||
|
"multi",
|
||||||
|
"subscribe",
|
||||||
|
"psubscribe",
|
||||||
|
"unsubscribe",
|
||||||
|
"unpsubscribe",
|
||||||
|
"select",
|
||||||
|
"client",
|
||||||
|
];
|
||||||
|
function executeAutoPipeline(client, slotKey) {
|
||||||
|
/*
|
||||||
|
If a pipeline is already executing, keep queueing up commands
|
||||||
|
since ioredis won't serve two pipelines at the same time
|
||||||
|
*/
|
||||||
|
if (client._runningAutoPipelines.has(slotKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!client._autoPipelines.has(slotKey)) {
|
||||||
|
/*
|
||||||
|
Rare edge case. Somehow, something has deleted this running autopipeline in an immediate
|
||||||
|
call to executeAutoPipeline.
|
||||||
|
|
||||||
|
Maybe the callback in the pipeline.exec is sometimes called in the same tick,
|
||||||
|
e.g. if redis is disconnected?
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client._runningAutoPipelines.add(slotKey);
|
||||||
|
// Get the pipeline and immediately delete it so that new commands are queued on a new pipeline
|
||||||
|
const pipeline = client._autoPipelines.get(slotKey);
|
||||||
|
client._autoPipelines.delete(slotKey);
|
||||||
|
const callbacks = pipeline[exports.kCallbacks];
|
||||||
|
// Stop keeping a reference to callbacks immediately after the callbacks stop being used.
|
||||||
|
// This allows the GC to reclaim objects referenced by callbacks, especially with 16384 slots
|
||||||
|
// in Redis.Cluster
|
||||||
|
pipeline[exports.kCallbacks] = null;
|
||||||
|
// Perform the call
|
||||||
|
pipeline.exec(function (err, results) {
|
||||||
|
client._runningAutoPipelines.delete(slotKey);
|
||||||
|
/*
|
||||||
|
Invoke all callback in nextTick so the stack is cleared
|
||||||
|
and callbacks can throw errors without affecting other callbacks.
|
||||||
|
*/
|
||||||
|
if (err) {
|
||||||
|
for (let i = 0; i < callbacks.length; i++) {
|
||||||
|
process.nextTick(callbacks[i], err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (let i = 0; i < callbacks.length; i++) {
|
||||||
|
process.nextTick(callbacks[i], ...results[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there is another pipeline on the same node, immediately execute it without waiting for nextTick
|
||||||
|
if (client._autoPipelines.has(slotKey)) {
|
||||||
|
executeAutoPipeline(client, slotKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function shouldUseAutoPipelining(client, functionName, commandName) {
|
||||||
|
return (functionName &&
|
||||||
|
client.options.enableAutoPipelining &&
|
||||||
|
!client.isPipeline &&
|
||||||
|
!exports.notAllowedAutoPipelineCommands.includes(commandName) &&
|
||||||
|
!client.options.autoPipeliningIgnoredCommands.includes(commandName));
|
||||||
|
}
|
||||||
|
exports.shouldUseAutoPipelining = shouldUseAutoPipelining;
|
||||||
|
function getFirstValueInFlattenedArray(args) {
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
if (typeof arg === "string") {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
else if (Array.isArray(arg) || (0, lodash_1.isArguments)(arg)) {
|
||||||
|
if (arg.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return arg[0];
|
||||||
|
}
|
||||||
|
const flattened = [arg].flat();
|
||||||
|
if (flattened.length > 0) {
|
||||||
|
return flattened[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
exports.getFirstValueInFlattenedArray = getFirstValueInFlattenedArray;
|
||||||
|
function executeWithAutoPipelining(client, functionName, commandName, args, callback) {
|
||||||
|
// On cluster mode let's wait for slots to be available
|
||||||
|
if (client.isCluster && !client.slots.length) {
|
||||||
|
if (client.status === "wait")
|
||||||
|
client.connect().catch(lodash_1.noop);
|
||||||
|
return (0, standard_as_callback_1.default)(new Promise(function (resolve, reject) {
|
||||||
|
client.delayUntilReady((err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
executeWithAutoPipelining(client, functionName, commandName, args, null).then(resolve, reject);
|
||||||
|
});
|
||||||
|
}), callback);
|
||||||
|
}
|
||||||
|
// If we have slot information, we can improve routing by grouping slots served by the same subset of nodes
|
||||||
|
// Note that the first value in args may be a (possibly empty) array.
|
||||||
|
// ioredis will only flatten one level of the array, in the Command constructor.
|
||||||
|
const prefix = client.options.keyPrefix || "";
|
||||||
|
let slotKey = client.isCluster
|
||||||
|
? client.slots[calculateSlot(`${prefix}${getFirstValueInFlattenedArray(args)}`)].join(",")
|
||||||
|
: "main";
|
||||||
|
// When scaleReads is enabled, separate read and write commands into different pipelines
|
||||||
|
// so they can be routed to replicas and masters respectively
|
||||||
|
if (client.isCluster && client.options.scaleReads !== "master") {
|
||||||
|
const isReadOnly = (0, commands_1.exists)(commandName) && (0, commands_1.hasFlag)(commandName, "readonly");
|
||||||
|
slotKey += isReadOnly ? ":read" : ":write";
|
||||||
|
}
|
||||||
|
if (!client._autoPipelines.has(slotKey)) {
|
||||||
|
const pipeline = client.pipeline();
|
||||||
|
pipeline[exports.kExec] = false;
|
||||||
|
pipeline[exports.kCallbacks] = [];
|
||||||
|
client._autoPipelines.set(slotKey, pipeline);
|
||||||
|
}
|
||||||
|
const pipeline = client._autoPipelines.get(slotKey);
|
||||||
|
/*
|
||||||
|
Mark the pipeline as scheduled.
|
||||||
|
The symbol will make sure that the pipeline is only scheduled once per tick.
|
||||||
|
New commands are appended to an already scheduled pipeline.
|
||||||
|
*/
|
||||||
|
if (!pipeline[exports.kExec]) {
|
||||||
|
pipeline[exports.kExec] = true;
|
||||||
|
/*
|
||||||
|
Deferring with setImmediate so we have a chance to capture multiple
|
||||||
|
commands that can be scheduled by I/O events already in the event loop queue.
|
||||||
|
*/
|
||||||
|
setImmediate(executeAutoPipeline, client, slotKey);
|
||||||
|
}
|
||||||
|
// Create the promise which will execute the command in the pipeline.
|
||||||
|
const autoPipelinePromise = new Promise(function (resolve, reject) {
|
||||||
|
pipeline[exports.kCallbacks].push(function (err, value) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(value);
|
||||||
|
});
|
||||||
|
if (functionName === "call") {
|
||||||
|
args.unshift(commandName);
|
||||||
|
}
|
||||||
|
pipeline[functionName](...args);
|
||||||
|
});
|
||||||
|
return (0, standard_as_callback_1.default)(autoPipelinePromise, callback);
|
||||||
|
}
|
||||||
|
exports.executeWithAutoPipelining = executeWithAutoPipelining;
|
||||||
172
backend/node_modules/ioredis/built/cluster/ClusterOptions.d.ts
generated
vendored
Normal file
172
backend/node_modules/ioredis/built/cluster/ClusterOptions.d.ts
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { SrvRecord } from "dns";
|
||||||
|
import { RedisOptions } from "../redis/RedisOptions";
|
||||||
|
import { CommanderOptions } from "../utils/Commander";
|
||||||
|
import { NodeRole } from "./util";
|
||||||
|
export declare type DNSResolveSrvFunction = (hostname: string, callback: (err: NodeJS.ErrnoException | null | undefined, records?: SrvRecord[]) => void) => void;
|
||||||
|
export declare type DNSLookupFunction = (hostname: string, callback: (err: NodeJS.ErrnoException | null | undefined, address: string, family?: number) => void) => void;
|
||||||
|
export declare type NatMapFunction = (key: string) => {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
} | null;
|
||||||
|
export declare type NatMap = {
|
||||||
|
[key: string]: {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
};
|
||||||
|
} | NatMapFunction;
|
||||||
|
/**
|
||||||
|
* Options for Cluster constructor
|
||||||
|
*/
|
||||||
|
export interface ClusterOptions extends CommanderOptions {
|
||||||
|
/**
|
||||||
|
* See "Quick Start" section.
|
||||||
|
*
|
||||||
|
* @default (times) => Math.min(100 + times * 2, 2000)
|
||||||
|
*/
|
||||||
|
clusterRetryStrategy?: ((times: number, reason?: Error) => number | void | null) | undefined;
|
||||||
|
/**
|
||||||
|
* See Redis class.
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enableOfflineQueue?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* When enabled, ioredis only emits "ready" event when `CLUSTER INFO`
|
||||||
|
* command reporting the cluster is ready for handling commands.
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enableReadyCheck?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Scale reads to the node with the specified role.
|
||||||
|
*
|
||||||
|
* @default "master"
|
||||||
|
*/
|
||||||
|
scaleReads?: NodeRole | Function | undefined;
|
||||||
|
/**
|
||||||
|
* When a MOVED or ASK error is received, client will redirect the
|
||||||
|
* command to another node.
|
||||||
|
* This option limits the max redirections allowed to send a command.
|
||||||
|
*
|
||||||
|
* @default 16
|
||||||
|
*/
|
||||||
|
maxRedirections?: number | undefined;
|
||||||
|
/**
|
||||||
|
* When an error is received when sending a command (e.g.
|
||||||
|
* "Connection is closed." when the target Redis node is down), client will retry
|
||||||
|
* if `retryDelayOnFailover` is valid delay time (in ms).
|
||||||
|
*
|
||||||
|
* @default 100
|
||||||
|
*/
|
||||||
|
retryDelayOnFailover?: number | undefined;
|
||||||
|
/**
|
||||||
|
* When a CLUSTERDOWN error is received, client will retry
|
||||||
|
* if `retryDelayOnClusterDown` is valid delay time (in ms).
|
||||||
|
*
|
||||||
|
* @default 100
|
||||||
|
*/
|
||||||
|
retryDelayOnClusterDown?: number | undefined;
|
||||||
|
/**
|
||||||
|
* When a TRYAGAIN error is received, client will retry
|
||||||
|
* if `retryDelayOnTryAgain` is valid delay time (in ms).
|
||||||
|
*
|
||||||
|
* @default 100
|
||||||
|
*/
|
||||||
|
retryDelayOnTryAgain?: number | undefined;
|
||||||
|
/**
|
||||||
|
* By default, this value is 0, which means when a `MOVED` error is received,
|
||||||
|
* the client will resend the command instantly to the node returned together with
|
||||||
|
* the `MOVED` error. However, sometimes it takes time for a cluster to become
|
||||||
|
* state stabilized after a failover, so adding a delay before resending can
|
||||||
|
* prevent a ping pong effect.
|
||||||
|
*
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
retryDelayOnMoved?: number | undefined;
|
||||||
|
/**
|
||||||
|
* The milliseconds before a timeout occurs while refreshing
|
||||||
|
* slots from the cluster.
|
||||||
|
*
|
||||||
|
* @default 1000
|
||||||
|
*/
|
||||||
|
slotsRefreshTimeout?: number | undefined;
|
||||||
|
/**
|
||||||
|
* The milliseconds between every automatic slots refresh.
|
||||||
|
*
|
||||||
|
* @default 5000
|
||||||
|
*/
|
||||||
|
slotsRefreshInterval?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Use sharded subscribers instead of a single subscriber.
|
||||||
|
*
|
||||||
|
* If sharded subscribers are used, then one additional subscriber connection per master node
|
||||||
|
* is established. If you don't plan to use SPUBLISH/SSUBSCRIBE, then this should be disabled.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
shardedSubscribers?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Passed to the constructor of `Redis`
|
||||||
|
*
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
redisOptions?: Omit<RedisOptions, "port" | "host" | "path" | "sentinels" | "retryStrategy" | "enableOfflineQueue" | "readOnly"> | undefined;
|
||||||
|
/**
|
||||||
|
* By default, When a new Cluster instance is created,
|
||||||
|
* it will connect to the Redis cluster automatically.
|
||||||
|
* If you want to keep the instance disconnected until the first command is called,
|
||||||
|
* set this option to `true`.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
lazyConnect?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Discover nodes using SRV records
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
useSRVRecords?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* SRV records will be resolved via this function.
|
||||||
|
*
|
||||||
|
* You may provide a custom `resolveSrv` function when you want to customize
|
||||||
|
* the cache behavior of the default function.
|
||||||
|
*
|
||||||
|
* @default require('dns').resolveSrv
|
||||||
|
*/
|
||||||
|
resolveSrv?: DNSResolveSrvFunction | undefined;
|
||||||
|
/**
|
||||||
|
* Hostnames will be resolved to IP addresses via this function.
|
||||||
|
* This is needed when the addresses of startup nodes are hostnames instead
|
||||||
|
* of IPs.
|
||||||
|
*
|
||||||
|
* You may provide a custom `lookup` function when you want to customize
|
||||||
|
* the cache behavior of the default function.
|
||||||
|
*
|
||||||
|
* @default require('dns').lookup
|
||||||
|
*/
|
||||||
|
dnsLookup?: DNSLookupFunction | undefined;
|
||||||
|
natMap?: NatMap | undefined;
|
||||||
|
/**
|
||||||
|
* See Redis class.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
enableAutoPipelining?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* See Redis class.
|
||||||
|
*
|
||||||
|
* @default []
|
||||||
|
*/
|
||||||
|
autoPipeliningIgnoredCommands?: string[] | undefined;
|
||||||
|
/**
|
||||||
|
* Custom LUA commands
|
||||||
|
*/
|
||||||
|
scripts?: Record<string, {
|
||||||
|
lua: string;
|
||||||
|
numberOfKeys?: number;
|
||||||
|
readOnly?: boolean;
|
||||||
|
}> | undefined;
|
||||||
|
}
|
||||||
|
export declare const DEFAULT_CLUSTER_OPTIONS: ClusterOptions;
|
||||||
22
backend/node_modules/ioredis/built/cluster/ClusterOptions.js
generated
vendored
Normal file
22
backend/node_modules/ioredis/built/cluster/ClusterOptions.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DEFAULT_CLUSTER_OPTIONS = void 0;
|
||||||
|
const dns_1 = require("dns");
|
||||||
|
exports.DEFAULT_CLUSTER_OPTIONS = {
|
||||||
|
clusterRetryStrategy: (times) => Math.min(100 + times * 2, 2000),
|
||||||
|
enableOfflineQueue: true,
|
||||||
|
enableReadyCheck: true,
|
||||||
|
scaleReads: "master",
|
||||||
|
maxRedirections: 16,
|
||||||
|
retryDelayOnMoved: 0,
|
||||||
|
retryDelayOnFailover: 100,
|
||||||
|
retryDelayOnClusterDown: 100,
|
||||||
|
retryDelayOnTryAgain: 100,
|
||||||
|
slotsRefreshTimeout: 1000,
|
||||||
|
useSRVRecords: false,
|
||||||
|
resolveSrv: dns_1.resolveSrv,
|
||||||
|
dnsLookup: dns_1.lookup,
|
||||||
|
enableAutoPipelining: false,
|
||||||
|
autoPipeliningIgnoredCommands: [],
|
||||||
|
shardedSubscribers: false,
|
||||||
|
};
|
||||||
29
backend/node_modules/ioredis/built/cluster/ClusterSubscriber.d.ts
generated
vendored
Normal file
29
backend/node_modules/ioredis/built/cluster/ClusterSubscriber.d.ts
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import ConnectionPool from "./ConnectionPool";
|
||||||
|
export default class ClusterSubscriber {
|
||||||
|
private connectionPool;
|
||||||
|
private emitter;
|
||||||
|
private isSharded;
|
||||||
|
private started;
|
||||||
|
private subscriber;
|
||||||
|
private lastActiveSubscriber;
|
||||||
|
private slotRange;
|
||||||
|
constructor(connectionPool: ConnectionPool, emitter: EventEmitter, isSharded?: boolean);
|
||||||
|
getInstance(): any;
|
||||||
|
/**
|
||||||
|
* Associate this subscriber to a specific slot range.
|
||||||
|
*
|
||||||
|
* Returns the range or an empty array if the slot range couldn't be associated.
|
||||||
|
*
|
||||||
|
* BTW: This is more for debugging and testing purposes.
|
||||||
|
*
|
||||||
|
* @param range
|
||||||
|
*/
|
||||||
|
associateSlotRange(range: number[]): number[];
|
||||||
|
start(): void;
|
||||||
|
stop(): void;
|
||||||
|
isStarted(): boolean;
|
||||||
|
private onSubscriberEnd;
|
||||||
|
private selectSubscriber;
|
||||||
|
}
|
||||||
223
backend/node_modules/ioredis/built/cluster/ClusterSubscriber.js
generated
vendored
Normal file
223
backend/node_modules/ioredis/built/cluster/ClusterSubscriber.js
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const util_1 = require("./util");
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const Redis_1 = require("../Redis");
|
||||||
|
const debug = (0, utils_1.Debug)("cluster:subscriber");
|
||||||
|
class ClusterSubscriber {
|
||||||
|
constructor(connectionPool, emitter, isSharded = false) {
|
||||||
|
this.connectionPool = connectionPool;
|
||||||
|
this.emitter = emitter;
|
||||||
|
this.isSharded = isSharded;
|
||||||
|
this.started = false;
|
||||||
|
//There is only one connection for the entire pool
|
||||||
|
this.subscriber = null;
|
||||||
|
//The slot range for which this subscriber is responsible
|
||||||
|
this.slotRange = [];
|
||||||
|
this.onSubscriberEnd = () => {
|
||||||
|
if (!this.started) {
|
||||||
|
debug("subscriber has disconnected, but ClusterSubscriber is not started, so not reconnecting.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If the subscriber closes whilst it's still the active connection,
|
||||||
|
// we might as well try to connecting to a new node if possible to
|
||||||
|
// minimise the number of missed publishes.
|
||||||
|
debug("subscriber has disconnected, selecting a new one...");
|
||||||
|
this.selectSubscriber();
|
||||||
|
};
|
||||||
|
// If the current node we're using as the subscriber disappears
|
||||||
|
// from the node pool for some reason, we will select a new one
|
||||||
|
// to connect to.
|
||||||
|
// Note that this event is only triggered if the connection to
|
||||||
|
// the node has been used; cluster subscriptions are setup with
|
||||||
|
// lazyConnect = true. It's possible for the subscriber node to
|
||||||
|
// disappear without this method being called!
|
||||||
|
// See https://github.com/luin/ioredis/pull/1589
|
||||||
|
this.connectionPool.on("-node", (_, key) => {
|
||||||
|
if (!this.started || !this.subscriber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((0, util_1.getNodeKey)(this.subscriber.options) === key) {
|
||||||
|
debug("subscriber has left, selecting a new one...");
|
||||||
|
this.selectSubscriber();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.connectionPool.on("+node", () => {
|
||||||
|
if (!this.started || this.subscriber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug("a new node is discovered and there is no subscriber, selecting a new one...");
|
||||||
|
this.selectSubscriber();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getInstance() {
|
||||||
|
return this.subscriber;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Associate this subscriber to a specific slot range.
|
||||||
|
*
|
||||||
|
* Returns the range or an empty array if the slot range couldn't be associated.
|
||||||
|
*
|
||||||
|
* BTW: This is more for debugging and testing purposes.
|
||||||
|
*
|
||||||
|
* @param range
|
||||||
|
*/
|
||||||
|
associateSlotRange(range) {
|
||||||
|
if (this.isSharded) {
|
||||||
|
this.slotRange = range;
|
||||||
|
}
|
||||||
|
return this.slotRange;
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
this.started = true;
|
||||||
|
this.selectSubscriber();
|
||||||
|
debug("started");
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
this.started = false;
|
||||||
|
if (this.subscriber) {
|
||||||
|
this.subscriber.disconnect();
|
||||||
|
this.subscriber = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isStarted() {
|
||||||
|
return this.started;
|
||||||
|
}
|
||||||
|
selectSubscriber() {
|
||||||
|
const lastActiveSubscriber = this.lastActiveSubscriber;
|
||||||
|
// Disconnect the previous subscriber even if there
|
||||||
|
// will not be a new one.
|
||||||
|
if (lastActiveSubscriber) {
|
||||||
|
lastActiveSubscriber.off("end", this.onSubscriberEnd);
|
||||||
|
lastActiveSubscriber.disconnect();
|
||||||
|
}
|
||||||
|
if (this.subscriber) {
|
||||||
|
this.subscriber.off("end", this.onSubscriberEnd);
|
||||||
|
this.subscriber.disconnect();
|
||||||
|
}
|
||||||
|
const sampleNode = (0, utils_1.sample)(this.connectionPool.getNodes());
|
||||||
|
if (!sampleNode) {
|
||||||
|
debug("selecting subscriber failed since there is no node discovered in the cluster yet");
|
||||||
|
this.subscriber = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { options } = sampleNode;
|
||||||
|
debug("selected a subscriber %s:%s", options.host, options.port);
|
||||||
|
/*
|
||||||
|
* Create a specialized Redis connection for the subscription.
|
||||||
|
* Note that auto reconnection is enabled here.
|
||||||
|
*
|
||||||
|
* `enableReadyCheck` is also enabled because although subscription is allowed
|
||||||
|
* while redis is loading data from the disk, we can check if the password
|
||||||
|
* provided for the subscriber is correct, and if not, the current subscriber
|
||||||
|
* will be disconnected and a new subscriber will be selected.
|
||||||
|
*/
|
||||||
|
let connectionPrefix = "subscriber";
|
||||||
|
if (this.isSharded)
|
||||||
|
connectionPrefix = "ssubscriber";
|
||||||
|
this.subscriber = new Redis_1.default({
|
||||||
|
port: options.port,
|
||||||
|
host: options.host,
|
||||||
|
username: options.username,
|
||||||
|
password: options.password,
|
||||||
|
enableReadyCheck: true,
|
||||||
|
connectionName: (0, util_1.getConnectionName)(connectionPrefix, options.connectionName),
|
||||||
|
lazyConnect: true,
|
||||||
|
tls: options.tls,
|
||||||
|
// Don't try to reconnect the subscriber connection. If the connection fails
|
||||||
|
// we will get an end event (handled below), at which point we'll pick a new
|
||||||
|
// node from the pool and try to connect to that as the subscriber connection.
|
||||||
|
retryStrategy: null,
|
||||||
|
});
|
||||||
|
// Ignore the errors since they're handled in the connection pool.
|
||||||
|
this.subscriber.on("error", utils_1.noop);
|
||||||
|
this.subscriber.on("moved", () => {
|
||||||
|
this.emitter.emit("forceRefresh");
|
||||||
|
});
|
||||||
|
// The node we lost connection to may not come back up in a
|
||||||
|
// reasonable amount of time (e.g. a slave that's taken down
|
||||||
|
// for maintainence), we could potentially miss many published
|
||||||
|
// messages so we should reconnect as quickly as possible, to
|
||||||
|
// a different node if needed.
|
||||||
|
this.subscriber.once("end", this.onSubscriberEnd);
|
||||||
|
// Re-subscribe previous channels
|
||||||
|
const previousChannels = { subscribe: [], psubscribe: [], ssubscribe: [] };
|
||||||
|
if (lastActiveSubscriber) {
|
||||||
|
const condition = lastActiveSubscriber.condition || lastActiveSubscriber.prevCondition;
|
||||||
|
if (condition && condition.subscriber) {
|
||||||
|
previousChannels.subscribe = condition.subscriber.channels("subscribe");
|
||||||
|
previousChannels.psubscribe =
|
||||||
|
condition.subscriber.channels("psubscribe");
|
||||||
|
previousChannels.ssubscribe =
|
||||||
|
condition.subscriber.channels("ssubscribe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (previousChannels.subscribe.length ||
|
||||||
|
previousChannels.psubscribe.length ||
|
||||||
|
previousChannels.ssubscribe.length) {
|
||||||
|
let pending = 0;
|
||||||
|
for (const type of ["subscribe", "psubscribe", "ssubscribe"]) {
|
||||||
|
const channels = previousChannels[type];
|
||||||
|
if (channels.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
debug("%s %d channels", type, channels.length);
|
||||||
|
if (type === "ssubscribe") {
|
||||||
|
for (const channel of channels) {
|
||||||
|
pending += 1;
|
||||||
|
this.subscriber[type](channel)
|
||||||
|
.then(() => {
|
||||||
|
if (!--pending) {
|
||||||
|
this.lastActiveSubscriber = this.subscriber;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// TODO: should probably disconnect the subscriber and try again.
|
||||||
|
debug("failed to ssubscribe to channel: %s", channel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pending += 1;
|
||||||
|
this.subscriber[type](channels)
|
||||||
|
.then(() => {
|
||||||
|
if (!--pending) {
|
||||||
|
this.lastActiveSubscriber = this.subscriber;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// TODO: should probably disconnect the subscriber and try again.
|
||||||
|
debug("failed to %s %d channels", type, channels.length);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.lastActiveSubscriber = this.subscriber;
|
||||||
|
}
|
||||||
|
for (const event of [
|
||||||
|
"message",
|
||||||
|
"messageBuffer",
|
||||||
|
]) {
|
||||||
|
this.subscriber.on(event, (arg1, arg2) => {
|
||||||
|
this.emitter.emit(event, arg1, arg2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const event of ["pmessage", "pmessageBuffer"]) {
|
||||||
|
this.subscriber.on(event, (arg1, arg2, arg3) => {
|
||||||
|
this.emitter.emit(event, arg1, arg2, arg3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.isSharded == true) {
|
||||||
|
for (const event of [
|
||||||
|
"smessage",
|
||||||
|
"smessageBuffer",
|
||||||
|
]) {
|
||||||
|
this.subscriber.on(event, (arg1, arg2) => {
|
||||||
|
this.emitter.emit(event, arg1, arg2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = ClusterSubscriber;
|
||||||
108
backend/node_modules/ioredis/built/cluster/ClusterSubscriberGroup.d.ts
generated
vendored
Normal file
108
backend/node_modules/ioredis/built/cluster/ClusterSubscriberGroup.d.ts
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import * as EventEmitter from "events";
|
||||||
|
import ShardedSubscriber from "./ShardedSubscriber";
|
||||||
|
import { ClusterOptions } from "./ClusterOptions";
|
||||||
|
/**
|
||||||
|
* Redis distinguishes between "normal" and sharded PubSub. When using the normal PubSub feature,
|
||||||
|
* exactly one subscriber exists per cluster instance because the Redis cluster bus forwards
|
||||||
|
* messages between shards. Sharded PubSub removes this limitation by making each shard
|
||||||
|
* responsible for its own messages.
|
||||||
|
*
|
||||||
|
* This class coordinates one ShardedSubscriber per master node in the cluster, providing
|
||||||
|
* sharded PubSub support while keeping the public API backward compatible.
|
||||||
|
*/
|
||||||
|
export default class ClusterSubscriberGroup {
|
||||||
|
private readonly subscriberGroupEmitter;
|
||||||
|
private readonly options;
|
||||||
|
private shardedSubscribers;
|
||||||
|
private clusterSlots;
|
||||||
|
private subscriberToSlotsIndex;
|
||||||
|
private channels;
|
||||||
|
private failedAttemptsByNode;
|
||||||
|
private isResetting;
|
||||||
|
private pendingReset;
|
||||||
|
private static readonly MAX_RETRY_ATTEMPTS;
|
||||||
|
private static readonly MAX_BACKOFF_MS;
|
||||||
|
private static readonly BASE_BACKOFF_MS;
|
||||||
|
/**
|
||||||
|
* Register callbacks
|
||||||
|
*
|
||||||
|
* @param cluster
|
||||||
|
*/
|
||||||
|
constructor(subscriberGroupEmitter: EventEmitter, options: ClusterOptions);
|
||||||
|
/**
|
||||||
|
* Get the responsible subscriber.
|
||||||
|
*
|
||||||
|
* @param slot
|
||||||
|
*/
|
||||||
|
getResponsibleSubscriber(slot: number): ShardedSubscriber | undefined;
|
||||||
|
/**
|
||||||
|
* Adds a channel for which this subscriber group is responsible
|
||||||
|
*
|
||||||
|
* @param channels
|
||||||
|
*/
|
||||||
|
addChannels(channels: (string | Buffer)[]): number;
|
||||||
|
/**
|
||||||
|
* Removes channels for which the subscriber group is responsible by optionally unsubscribing
|
||||||
|
* @param channels
|
||||||
|
*/
|
||||||
|
removeChannels(channels: (string | Buffer)[]): number;
|
||||||
|
/**
|
||||||
|
* Disconnect all subscribers and clear some of the internal state.
|
||||||
|
*/
|
||||||
|
stop(): void;
|
||||||
|
/**
|
||||||
|
* Start all not yet started subscribers
|
||||||
|
*/
|
||||||
|
start(): Promise<any[]>;
|
||||||
|
/**
|
||||||
|
* Resets the subscriber group by disconnecting all subscribers that are no longer needed and connecting new ones.
|
||||||
|
*/
|
||||||
|
reset(clusterSlots: string[][], clusterNodes: any[]): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Refreshes the subscriber-related slot ranges
|
||||||
|
*
|
||||||
|
* Returns false if no refresh was needed
|
||||||
|
*
|
||||||
|
* @param targetSlots
|
||||||
|
*/
|
||||||
|
private _refreshSlots;
|
||||||
|
/**
|
||||||
|
* Resubscribes to the previous channels
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _resubscribe;
|
||||||
|
/**
|
||||||
|
* Deep equality of the cluster slots objects
|
||||||
|
*
|
||||||
|
* @param other
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _slotsAreEqual;
|
||||||
|
/**
|
||||||
|
* Checks if any subscribers are in an unhealthy state.
|
||||||
|
*
|
||||||
|
* A subscriber is considered unhealthy if:
|
||||||
|
* - It exists but is not started (failed/disconnected)
|
||||||
|
* - It's missing entirely for a node that should have one
|
||||||
|
*
|
||||||
|
* @returns true if any subscribers need to be recreated
|
||||||
|
*/
|
||||||
|
private hasUnhealthySubscribers;
|
||||||
|
/**
|
||||||
|
* Handles failed subscriber connections by emitting an event to refresh the slots cache
|
||||||
|
* after a backoff period.
|
||||||
|
*
|
||||||
|
* @param error
|
||||||
|
* @param nodeKey
|
||||||
|
*/
|
||||||
|
private handleSubscriberConnectFailed;
|
||||||
|
/**
|
||||||
|
* Handles successful subscriber connections by resetting the failed attempts counter.
|
||||||
|
*
|
||||||
|
* @param nodeKey
|
||||||
|
*/
|
||||||
|
private handleSubscriberConnectSucceeded;
|
||||||
|
private shouldStartSubscriber;
|
||||||
|
}
|
||||||
373
backend/node_modules/ioredis/built/cluster/ClusterSubscriberGroup.js
generated
vendored
Normal file
373
backend/node_modules/ioredis/built/cluster/ClusterSubscriberGroup.js
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const util_1 = require("./util");
|
||||||
|
const calculateSlot = require("cluster-key-slot");
|
||||||
|
const ShardedSubscriber_1 = require("./ShardedSubscriber");
|
||||||
|
const debug = (0, utils_1.Debug)("cluster:subscriberGroup");
|
||||||
|
/**
|
||||||
|
* Redis distinguishes between "normal" and sharded PubSub. When using the normal PubSub feature,
|
||||||
|
* exactly one subscriber exists per cluster instance because the Redis cluster bus forwards
|
||||||
|
* messages between shards. Sharded PubSub removes this limitation by making each shard
|
||||||
|
* responsible for its own messages.
|
||||||
|
*
|
||||||
|
* This class coordinates one ShardedSubscriber per master node in the cluster, providing
|
||||||
|
* sharded PubSub support while keeping the public API backward compatible.
|
||||||
|
*/
|
||||||
|
class ClusterSubscriberGroup {
|
||||||
|
/**
|
||||||
|
* Register callbacks
|
||||||
|
*
|
||||||
|
* @param cluster
|
||||||
|
*/
|
||||||
|
constructor(subscriberGroupEmitter, options) {
|
||||||
|
this.subscriberGroupEmitter = subscriberGroupEmitter;
|
||||||
|
this.options = options;
|
||||||
|
this.shardedSubscribers = new Map();
|
||||||
|
this.clusterSlots = [];
|
||||||
|
// Simple [min, max] slot ranges aren't enough because you can migrate single slots
|
||||||
|
this.subscriberToSlotsIndex = new Map();
|
||||||
|
this.channels = new Map();
|
||||||
|
this.failedAttemptsByNode = new Map();
|
||||||
|
// Only latest pending reset kept; throttled by refreshSlotsCache's isRefreshing + backoff delay
|
||||||
|
this.isResetting = false;
|
||||||
|
this.pendingReset = null;
|
||||||
|
/**
|
||||||
|
* Handles failed subscriber connections by emitting an event to refresh the slots cache
|
||||||
|
* after a backoff period.
|
||||||
|
*
|
||||||
|
* @param error
|
||||||
|
* @param nodeKey
|
||||||
|
*/
|
||||||
|
this.handleSubscriberConnectFailed = (error, nodeKey) => {
|
||||||
|
const currentAttempts = this.failedAttemptsByNode.get(nodeKey) || 0;
|
||||||
|
const failedAttempts = currentAttempts + 1;
|
||||||
|
this.failedAttemptsByNode.set(nodeKey, failedAttempts);
|
||||||
|
const attempts = Math.min(failedAttempts, ClusterSubscriberGroup.MAX_RETRY_ATTEMPTS);
|
||||||
|
const backoff = Math.min(ClusterSubscriberGroup.BASE_BACKOFF_MS * 2 ** attempts, ClusterSubscriberGroup.MAX_BACKOFF_MS);
|
||||||
|
const jitter = Math.floor((Math.random() - 0.5) * (backoff * 0.5));
|
||||||
|
const delay = Math.max(0, backoff + jitter);
|
||||||
|
debug("Failed to connect subscriber for %s. Refreshing slots in %dms", nodeKey, delay);
|
||||||
|
this.subscriberGroupEmitter.emit("subscriberConnectFailed", {
|
||||||
|
delay,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Handles successful subscriber connections by resetting the failed attempts counter.
|
||||||
|
*
|
||||||
|
* @param nodeKey
|
||||||
|
*/
|
||||||
|
this.handleSubscriberConnectSucceeded = (nodeKey) => {
|
||||||
|
this.failedAttemptsByNode.delete(nodeKey);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the responsible subscriber.
|
||||||
|
*
|
||||||
|
* @param slot
|
||||||
|
*/
|
||||||
|
getResponsibleSubscriber(slot) {
|
||||||
|
const nodeKey = this.clusterSlots[slot][0];
|
||||||
|
const sub = this.shardedSubscribers.get(nodeKey);
|
||||||
|
if (sub && sub.subscriberStatus === "idle") {
|
||||||
|
sub
|
||||||
|
.start()
|
||||||
|
.then(() => {
|
||||||
|
this.handleSubscriberConnectSucceeded(sub.getNodeKey());
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.handleSubscriberConnectFailed(err, sub.getNodeKey());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sub;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Adds a channel for which this subscriber group is responsible
|
||||||
|
*
|
||||||
|
* @param channels
|
||||||
|
*/
|
||||||
|
addChannels(channels) {
|
||||||
|
const slot = calculateSlot(channels[0]);
|
||||||
|
// Check if the all channels belong to the same slot and otherwise reject the operation
|
||||||
|
for (const c of channels) {
|
||||||
|
if (calculateSlot(c) !== slot) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const currChannels = this.channels.get(slot);
|
||||||
|
if (!currChannels) {
|
||||||
|
this.channels.set(slot, channels);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.channels.set(slot, currChannels.concat(channels));
|
||||||
|
}
|
||||||
|
return Array.from(this.channels.values()).reduce((sum, array) => sum + array.length, 0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Removes channels for which the subscriber group is responsible by optionally unsubscribing
|
||||||
|
* @param channels
|
||||||
|
*/
|
||||||
|
removeChannels(channels) {
|
||||||
|
const slot = calculateSlot(channels[0]);
|
||||||
|
// Check if the all channels belong to the same slot and otherwise reject the operation
|
||||||
|
for (const c of channels) {
|
||||||
|
if (calculateSlot(c) !== slot) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const slotChannels = this.channels.get(slot);
|
||||||
|
if (slotChannels) {
|
||||||
|
const updatedChannels = slotChannels.filter((c) => !channels.includes(c));
|
||||||
|
this.channels.set(slot, updatedChannels);
|
||||||
|
}
|
||||||
|
return Array.from(this.channels.values()).reduce((sum, array) => sum + array.length, 0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Disconnect all subscribers and clear some of the internal state.
|
||||||
|
*/
|
||||||
|
stop() {
|
||||||
|
for (const s of this.shardedSubscribers.values()) {
|
||||||
|
s.stop();
|
||||||
|
}
|
||||||
|
// Clear subscriber instances and pending operations.
|
||||||
|
// Channels are preserved for resubscription on reconnect.
|
||||||
|
this.pendingReset = null;
|
||||||
|
this.shardedSubscribers.clear();
|
||||||
|
this.subscriberToSlotsIndex.clear();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Start all not yet started subscribers
|
||||||
|
*/
|
||||||
|
start() {
|
||||||
|
const startPromises = [];
|
||||||
|
for (const s of this.shardedSubscribers.values()) {
|
||||||
|
if (this.shouldStartSubscriber(s)) {
|
||||||
|
startPromises.push(s
|
||||||
|
.start()
|
||||||
|
.then(() => {
|
||||||
|
this.handleSubscriberConnectSucceeded(s.getNodeKey());
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.handleSubscriberConnectFailed(err, s.getNodeKey());
|
||||||
|
}));
|
||||||
|
this.subscriberGroupEmitter.emit("+subscriber");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.all(startPromises);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Resets the subscriber group by disconnecting all subscribers that are no longer needed and connecting new ones.
|
||||||
|
*/
|
||||||
|
async reset(clusterSlots, clusterNodes) {
|
||||||
|
if (this.isResetting) {
|
||||||
|
this.pendingReset = { slots: clusterSlots, nodes: clusterNodes };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isResetting = true;
|
||||||
|
try {
|
||||||
|
const hasTopologyChanged = this._refreshSlots(clusterSlots);
|
||||||
|
const hasFailedSubscribers = this.hasUnhealthySubscribers();
|
||||||
|
if (!hasTopologyChanged && !hasFailedSubscribers) {
|
||||||
|
debug("No topology change detected or failed subscribers. Skipping reset.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// For each of the sharded subscribers
|
||||||
|
for (const [nodeKey, shardedSubscriber] of this.shardedSubscribers) {
|
||||||
|
if (
|
||||||
|
// If the subscriber is still responsible for a slot range and is healthy then keep it
|
||||||
|
this.subscriberToSlotsIndex.has(nodeKey) &&
|
||||||
|
shardedSubscriber.isHealthy()) {
|
||||||
|
debug("Skipping deleting subscriber for %s", nodeKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
debug("Removing subscriber for %s", nodeKey);
|
||||||
|
// Otherwise stop the subscriber and remove it
|
||||||
|
shardedSubscriber.stop();
|
||||||
|
this.shardedSubscribers.delete(nodeKey);
|
||||||
|
this.subscriberGroupEmitter.emit("-subscriber");
|
||||||
|
}
|
||||||
|
const startPromises = [];
|
||||||
|
// For each node in slots cache
|
||||||
|
for (const [nodeKey, _] of this.subscriberToSlotsIndex) {
|
||||||
|
const existingSubscriber = this.shardedSubscribers.get(nodeKey);
|
||||||
|
// If we already have a subscriber for this node, only ensure it is healthy
|
||||||
|
// when it now owns slots with active channel subscriptions.
|
||||||
|
if (existingSubscriber && existingSubscriber.isHealthy()) {
|
||||||
|
debug("Skipping creating new subscriber for %s", nodeKey);
|
||||||
|
if (!existingSubscriber.isStarted() &&
|
||||||
|
this.shouldStartSubscriber(existingSubscriber)) {
|
||||||
|
startPromises.push(existingSubscriber
|
||||||
|
.start()
|
||||||
|
.then(() => {
|
||||||
|
this.handleSubscriberConnectSucceeded(nodeKey);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.handleSubscriberConnectFailed(error, nodeKey);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If we have an existing subscriber but it is not healthy, stop it
|
||||||
|
if (existingSubscriber && !existingSubscriber.isHealthy()) {
|
||||||
|
debug("Replacing subscriber for %s", nodeKey);
|
||||||
|
existingSubscriber.stop();
|
||||||
|
this.shardedSubscribers.delete(nodeKey);
|
||||||
|
this.subscriberGroupEmitter.emit("-subscriber");
|
||||||
|
}
|
||||||
|
debug("Creating new subscriber for %s", nodeKey);
|
||||||
|
// Otherwise create a new subscriber
|
||||||
|
const redis = clusterNodes.find((node) => {
|
||||||
|
return (0, util_1.getNodeKey)(node.options) === nodeKey;
|
||||||
|
});
|
||||||
|
if (!redis) {
|
||||||
|
debug("Failed to find node for key %s", nodeKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const sub = new ShardedSubscriber_1.default(this.subscriberGroupEmitter, redis.options, this.options.redisOptions);
|
||||||
|
this.shardedSubscribers.set(nodeKey, sub);
|
||||||
|
if (this.shouldStartSubscriber(sub)) {
|
||||||
|
startPromises.push(sub
|
||||||
|
.start()
|
||||||
|
.then(() => {
|
||||||
|
this.handleSubscriberConnectSucceeded(nodeKey);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.handleSubscriberConnectFailed(error, nodeKey);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
this.subscriberGroupEmitter.emit("+subscriber");
|
||||||
|
}
|
||||||
|
// It's vital to await the start promises before resubscribing
|
||||||
|
// Otherwise we might try to resubscribe to a subscriber that is not yet connected
|
||||||
|
// This can cause a race condition
|
||||||
|
await Promise.all(startPromises);
|
||||||
|
this._resubscribe();
|
||||||
|
this.subscriberGroupEmitter.emit("subscribersReady");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.isResetting = false;
|
||||||
|
if (this.pendingReset) {
|
||||||
|
const { slots, nodes } = this.pendingReset;
|
||||||
|
this.pendingReset = null;
|
||||||
|
await this.reset(slots, nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Refreshes the subscriber-related slot ranges
|
||||||
|
*
|
||||||
|
* Returns false if no refresh was needed
|
||||||
|
*
|
||||||
|
* @param targetSlots
|
||||||
|
*/
|
||||||
|
_refreshSlots(targetSlots) {
|
||||||
|
//If there was an actual change, then reassign the slot ranges
|
||||||
|
// Also rebuild if subscriberToSlotsIndex is empty (e.g., after stop() was called)
|
||||||
|
if (this._slotsAreEqual(targetSlots) && this.subscriberToSlotsIndex.size > 0) {
|
||||||
|
debug("Nothing to refresh because the new cluster map is equal to the previous one.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
debug("Refreshing the slots of the subscriber group.");
|
||||||
|
//Rebuild the slots index
|
||||||
|
this.subscriberToSlotsIndex = new Map();
|
||||||
|
for (let slot = 0; slot < targetSlots.length; slot++) {
|
||||||
|
const node = targetSlots[slot][0];
|
||||||
|
if (!this.subscriberToSlotsIndex.has(node)) {
|
||||||
|
this.subscriberToSlotsIndex.set(node, []);
|
||||||
|
}
|
||||||
|
this.subscriberToSlotsIndex.get(node).push(Number(slot));
|
||||||
|
}
|
||||||
|
//Update the cached slots map
|
||||||
|
this.clusterSlots = JSON.parse(JSON.stringify(targetSlots));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Resubscribes to the previous channels
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_resubscribe() {
|
||||||
|
if (this.shardedSubscribers) {
|
||||||
|
this.shardedSubscribers.forEach((s, nodeKey) => {
|
||||||
|
const subscriberSlots = this.subscriberToSlotsIndex.get(nodeKey);
|
||||||
|
if (subscriberSlots) {
|
||||||
|
//Resubscribe on the underlying connection
|
||||||
|
subscriberSlots.forEach((ss) => {
|
||||||
|
//Might return null if being disconnected
|
||||||
|
const redis = s.getInstance();
|
||||||
|
const channels = this.channels.get(ss);
|
||||||
|
if (channels && channels.length > 0) {
|
||||||
|
if (!redis || redis.status === "end") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (redis.status === "ready") {
|
||||||
|
redis.ssubscribe(...channels).catch((err) => {
|
||||||
|
// TODO: Should we emit an error event here?
|
||||||
|
debug("Failed to ssubscribe on node %s: %s", nodeKey, err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
redis.once("ready", () => {
|
||||||
|
redis.ssubscribe(...channels).catch((err) => {
|
||||||
|
// TODO: Should we emit an error event here?
|
||||||
|
debug("Failed to ssubscribe on node %s: %s", nodeKey, err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Deep equality of the cluster slots objects
|
||||||
|
*
|
||||||
|
* @param other
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_slotsAreEqual(other) {
|
||||||
|
if (this.clusterSlots === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return JSON.stringify(this.clusterSlots) === JSON.stringify(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Checks if any subscribers are in an unhealthy state.
|
||||||
|
*
|
||||||
|
* A subscriber is considered unhealthy if:
|
||||||
|
* - It exists but is not started (failed/disconnected)
|
||||||
|
* - It's missing entirely for a node that should have one
|
||||||
|
*
|
||||||
|
* @returns true if any subscribers need to be recreated
|
||||||
|
*/
|
||||||
|
hasUnhealthySubscribers() {
|
||||||
|
const hasFailedSubscribers = Array.from(this.shardedSubscribers.values()).some((sub) => !sub.isHealthy());
|
||||||
|
const hasMissingSubscribers = Array.from(this.subscriberToSlotsIndex.keys()).some((nodeKey) => !this.shardedSubscribers.has(nodeKey));
|
||||||
|
return hasFailedSubscribers || hasMissingSubscribers;
|
||||||
|
}
|
||||||
|
shouldStartSubscriber(sub) {
|
||||||
|
if (sub.isStarted()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!sub.isLazyConnect()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const subscriberSlots = this.subscriberToSlotsIndex.get(sub.getNodeKey());
|
||||||
|
if (!subscriberSlots) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return subscriberSlots.some((slot) => {
|
||||||
|
const channels = this.channels.get(slot);
|
||||||
|
return Boolean(channels && channels.length > 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = ClusterSubscriberGroup;
|
||||||
|
// Retry strategy
|
||||||
|
ClusterSubscriberGroup.MAX_RETRY_ATTEMPTS = 10;
|
||||||
|
ClusterSubscriberGroup.MAX_BACKOFF_MS = 2000;
|
||||||
|
ClusterSubscriberGroup.BASE_BACKOFF_MS = 100;
|
||||||
37
backend/node_modules/ioredis/built/cluster/ConnectionPool.d.ts
generated
vendored
Normal file
37
backend/node_modules/ioredis/built/cluster/ConnectionPool.d.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import { RedisOptions, NodeKey, NodeRole } from "./util";
|
||||||
|
import Redis from "../Redis";
|
||||||
|
export default class ConnectionPool extends EventEmitter {
|
||||||
|
private redisOptions;
|
||||||
|
private nodes;
|
||||||
|
private specifiedOptions;
|
||||||
|
constructor(redisOptions: any);
|
||||||
|
getNodes(role?: NodeRole): Redis[];
|
||||||
|
getInstanceByKey(key: NodeKey): Redis;
|
||||||
|
getSampleInstance(role: NodeRole): Redis;
|
||||||
|
/**
|
||||||
|
* Add a master node to the pool
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
|
addMasterNode(node: RedisOptions): boolean;
|
||||||
|
/**
|
||||||
|
* Creates a Redis connection instance from the node options
|
||||||
|
* @param node
|
||||||
|
* @param readOnly
|
||||||
|
*/
|
||||||
|
createRedisFromOptions(node: RedisOptions, readOnly: boolean): Redis;
|
||||||
|
/**
|
||||||
|
* Find or create a connection to the node
|
||||||
|
*/
|
||||||
|
findOrCreate(node: RedisOptions, readOnly?: boolean): Redis;
|
||||||
|
/**
|
||||||
|
* Reset the pool with a set of nodes.
|
||||||
|
* The old node will be removed.
|
||||||
|
*/
|
||||||
|
reset(nodes: RedisOptions[]): void;
|
||||||
|
/**
|
||||||
|
* Remove a node from the pool.
|
||||||
|
*/
|
||||||
|
private removeNode;
|
||||||
|
}
|
||||||
154
backend/node_modules/ioredis/built/cluster/ConnectionPool.js
generated
vendored
Normal file
154
backend/node_modules/ioredis/built/cluster/ConnectionPool.js
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const events_1 = require("events");
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const util_1 = require("./util");
|
||||||
|
const Redis_1 = require("../Redis");
|
||||||
|
const debug = (0, utils_1.Debug)("cluster:connectionPool");
|
||||||
|
class ConnectionPool extends events_1.EventEmitter {
|
||||||
|
constructor(redisOptions) {
|
||||||
|
super();
|
||||||
|
this.redisOptions = redisOptions;
|
||||||
|
// master + slave = all
|
||||||
|
this.nodes = {
|
||||||
|
all: {},
|
||||||
|
master: {},
|
||||||
|
slave: {},
|
||||||
|
};
|
||||||
|
this.specifiedOptions = {};
|
||||||
|
}
|
||||||
|
getNodes(role = "all") {
|
||||||
|
const nodes = this.nodes[role];
|
||||||
|
return Object.keys(nodes).map((key) => nodes[key]);
|
||||||
|
}
|
||||||
|
getInstanceByKey(key) {
|
||||||
|
return this.nodes.all[key];
|
||||||
|
}
|
||||||
|
getSampleInstance(role) {
|
||||||
|
const keys = Object.keys(this.nodes[role]);
|
||||||
|
const sampleKey = (0, utils_1.sample)(keys);
|
||||||
|
return this.nodes[role][sampleKey];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add a master node to the pool
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
|
addMasterNode(node) {
|
||||||
|
const key = (0, util_1.getNodeKey)(node.options);
|
||||||
|
const redis = this.createRedisFromOptions(node, node.options.readOnly);
|
||||||
|
//Master nodes aren't read-only
|
||||||
|
if (!node.options.readOnly) {
|
||||||
|
this.nodes.all[key] = redis;
|
||||||
|
this.nodes.master[key] = redis;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Creates a Redis connection instance from the node options
|
||||||
|
* @param node
|
||||||
|
* @param readOnly
|
||||||
|
*/
|
||||||
|
createRedisFromOptions(node, readOnly) {
|
||||||
|
const redis = new Redis_1.default((0, utils_1.defaults)({
|
||||||
|
// Never try to reconnect when a node is lose,
|
||||||
|
// instead, waiting for a `MOVED` error and
|
||||||
|
// fetch the slots again.
|
||||||
|
retryStrategy: null,
|
||||||
|
// Offline queue should be enabled so that
|
||||||
|
// we don't need to wait for the `ready` event
|
||||||
|
// before sending commands to the node.
|
||||||
|
enableOfflineQueue: true,
|
||||||
|
readOnly: readOnly,
|
||||||
|
}, node, this.redisOptions, { lazyConnect: true }));
|
||||||
|
return redis;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Find or create a connection to the node
|
||||||
|
*/
|
||||||
|
findOrCreate(node, readOnly = false) {
|
||||||
|
const key = (0, util_1.getNodeKey)(node);
|
||||||
|
readOnly = Boolean(readOnly);
|
||||||
|
if (this.specifiedOptions[key]) {
|
||||||
|
Object.assign(node, this.specifiedOptions[key]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.specifiedOptions[key] = node;
|
||||||
|
}
|
||||||
|
let redis;
|
||||||
|
if (this.nodes.all[key]) {
|
||||||
|
redis = this.nodes.all[key];
|
||||||
|
if (redis.options.readOnly !== readOnly) {
|
||||||
|
redis.options.readOnly = readOnly;
|
||||||
|
debug("Change role of %s to %s", key, readOnly ? "slave" : "master");
|
||||||
|
redis[readOnly ? "readonly" : "readwrite"]().catch(utils_1.noop);
|
||||||
|
if (readOnly) {
|
||||||
|
delete this.nodes.master[key];
|
||||||
|
this.nodes.slave[key] = redis;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
delete this.nodes.slave[key];
|
||||||
|
this.nodes.master[key] = redis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug("Connecting to %s as %s", key, readOnly ? "slave" : "master");
|
||||||
|
redis = this.createRedisFromOptions(node, readOnly);
|
||||||
|
this.nodes.all[key] = redis;
|
||||||
|
this.nodes[readOnly ? "slave" : "master"][key] = redis;
|
||||||
|
redis.once("end", () => {
|
||||||
|
this.removeNode(key);
|
||||||
|
this.emit("-node", redis, key);
|
||||||
|
if (!Object.keys(this.nodes.all).length) {
|
||||||
|
this.emit("drain");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.emit("+node", redis, key);
|
||||||
|
redis.on("error", function (error) {
|
||||||
|
this.emit("nodeError", error, key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return redis;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Reset the pool with a set of nodes.
|
||||||
|
* The old node will be removed.
|
||||||
|
*/
|
||||||
|
reset(nodes) {
|
||||||
|
debug("Reset with %O", nodes);
|
||||||
|
const newNodes = {};
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
const key = (0, util_1.getNodeKey)(node);
|
||||||
|
// Don't override the existing (master) node
|
||||||
|
// when the current one is slave.
|
||||||
|
if (!(node.readOnly && newNodes[key])) {
|
||||||
|
newNodes[key] = node;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(this.nodes.all).forEach((key) => {
|
||||||
|
if (!newNodes[key]) {
|
||||||
|
debug("Disconnect %s because the node does not hold any slot", key);
|
||||||
|
this.nodes.all[key].disconnect();
|
||||||
|
this.removeNode(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(newNodes).forEach((key) => {
|
||||||
|
const node = newNodes[key];
|
||||||
|
this.findOrCreate(node, node.readOnly);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Remove a node from the pool.
|
||||||
|
*/
|
||||||
|
removeNode(key) {
|
||||||
|
const { nodes } = this;
|
||||||
|
if (nodes.all[key]) {
|
||||||
|
debug("Remove %s from the pool", key);
|
||||||
|
delete nodes.all[key];
|
||||||
|
}
|
||||||
|
delete nodes.master[key];
|
||||||
|
delete nodes.slave[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = ConnectionPool;
|
||||||
20
backend/node_modules/ioredis/built/cluster/DelayQueue.d.ts
generated
vendored
Normal file
20
backend/node_modules/ioredis/built/cluster/DelayQueue.d.ts
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export interface DelayQueueOptions {
|
||||||
|
callback?: Function;
|
||||||
|
timeout: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Queue that runs items after specified duration
|
||||||
|
*/
|
||||||
|
export default class DelayQueue {
|
||||||
|
private queues;
|
||||||
|
private timeouts;
|
||||||
|
/**
|
||||||
|
* Add a new item to the queue
|
||||||
|
*
|
||||||
|
* @param bucket bucket name
|
||||||
|
* @param item function that will run later
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
push(bucket: string, item: Function, options: DelayQueueOptions): void;
|
||||||
|
private execute;
|
||||||
|
}
|
||||||
53
backend/node_modules/ioredis/built/cluster/DelayQueue.js
generated
vendored
Normal file
53
backend/node_modules/ioredis/built/cluster/DelayQueue.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const Deque = require("denque");
|
||||||
|
const debug = (0, utils_1.Debug)("delayqueue");
|
||||||
|
/**
|
||||||
|
* Queue that runs items after specified duration
|
||||||
|
*/
|
||||||
|
class DelayQueue {
|
||||||
|
constructor() {
|
||||||
|
this.queues = {};
|
||||||
|
this.timeouts = {};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add a new item to the queue
|
||||||
|
*
|
||||||
|
* @param bucket bucket name
|
||||||
|
* @param item function that will run later
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
push(bucket, item, options) {
|
||||||
|
const callback = options.callback || process.nextTick;
|
||||||
|
if (!this.queues[bucket]) {
|
||||||
|
this.queues[bucket] = new Deque();
|
||||||
|
}
|
||||||
|
const queue = this.queues[bucket];
|
||||||
|
queue.push(item);
|
||||||
|
if (!this.timeouts[bucket]) {
|
||||||
|
this.timeouts[bucket] = setTimeout(() => {
|
||||||
|
callback(() => {
|
||||||
|
this.timeouts[bucket] = null;
|
||||||
|
this.execute(bucket);
|
||||||
|
});
|
||||||
|
}, options.timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
execute(bucket) {
|
||||||
|
const queue = this.queues[bucket];
|
||||||
|
if (!queue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { length } = queue;
|
||||||
|
if (!length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug("send %d commands in %s queue", length, bucket);
|
||||||
|
this.queues[bucket] = null;
|
||||||
|
while (queue.length > 0) {
|
||||||
|
queue.shift()();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = DelayQueue;
|
||||||
36
backend/node_modules/ioredis/built/cluster/ShardedSubscriber.d.ts
generated
vendored
Normal file
36
backend/node_modules/ioredis/built/cluster/ShardedSubscriber.d.ts
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import EventEmitter = require("events");
|
||||||
|
import { RedisOptions } from "./util";
|
||||||
|
import Redis from "../Redis";
|
||||||
|
import { ClusterOptions } from "./ClusterOptions";
|
||||||
|
declare const SubscriberStatus: {
|
||||||
|
readonly IDLE: "idle";
|
||||||
|
readonly STARTING: "starting";
|
||||||
|
readonly CONNECTED: "connected";
|
||||||
|
readonly STOPPING: "stopping";
|
||||||
|
readonly ENDED: "ended";
|
||||||
|
};
|
||||||
|
declare type SubscriberStatus = typeof SubscriberStatus[keyof typeof SubscriberStatus];
|
||||||
|
export default class ShardedSubscriber {
|
||||||
|
private readonly emitter;
|
||||||
|
private readonly nodeKey;
|
||||||
|
private status;
|
||||||
|
private instance;
|
||||||
|
private connectPromise;
|
||||||
|
private lazyConnect;
|
||||||
|
private readonly messageListeners;
|
||||||
|
constructor(emitter: EventEmitter, options: RedisOptions, redisOptions?: ClusterOptions["redisOptions"]);
|
||||||
|
start(): Promise<void>;
|
||||||
|
stop(): void;
|
||||||
|
isStarted(): boolean;
|
||||||
|
get subscriberStatus(): SubscriberStatus;
|
||||||
|
isHealthy(): boolean;
|
||||||
|
getInstance(): Redis | null;
|
||||||
|
getNodeKey(): string;
|
||||||
|
isLazyConnect(): boolean;
|
||||||
|
private onEnd;
|
||||||
|
private onError;
|
||||||
|
private onMoved;
|
||||||
|
private updateStatus;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
147
backend/node_modules/ioredis/built/cluster/ShardedSubscriber.js
generated
vendored
Normal file
147
backend/node_modules/ioredis/built/cluster/ShardedSubscriber.js
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const util_1 = require("./util");
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const Redis_1 = require("../Redis");
|
||||||
|
const debug = (0, utils_1.Debug)("cluster:subscriberGroup:shardedSubscriber");
|
||||||
|
const SubscriberStatus = {
|
||||||
|
IDLE: "idle",
|
||||||
|
STARTING: "starting",
|
||||||
|
CONNECTED: "connected",
|
||||||
|
STOPPING: "stopping",
|
||||||
|
ENDED: "ended",
|
||||||
|
};
|
||||||
|
const ALLOWED_STATUS_UPDATES = {
|
||||||
|
[SubscriberStatus.IDLE]: [
|
||||||
|
SubscriberStatus.STARTING,
|
||||||
|
SubscriberStatus.STOPPING,
|
||||||
|
SubscriberStatus.ENDED,
|
||||||
|
],
|
||||||
|
[SubscriberStatus.STARTING]: [
|
||||||
|
SubscriberStatus.CONNECTED,
|
||||||
|
SubscriberStatus.STOPPING,
|
||||||
|
SubscriberStatus.ENDED,
|
||||||
|
],
|
||||||
|
[SubscriberStatus.CONNECTED]: [
|
||||||
|
SubscriberStatus.STOPPING,
|
||||||
|
SubscriberStatus.ENDED,
|
||||||
|
],
|
||||||
|
[SubscriberStatus.STOPPING]: [SubscriberStatus.ENDED],
|
||||||
|
[SubscriberStatus.ENDED]: [],
|
||||||
|
};
|
||||||
|
class ShardedSubscriber {
|
||||||
|
constructor(emitter, options, redisOptions) {
|
||||||
|
var _a;
|
||||||
|
this.emitter = emitter;
|
||||||
|
this.status = SubscriberStatus.IDLE;
|
||||||
|
this.instance = null;
|
||||||
|
this.connectPromise = null;
|
||||||
|
// Store listener references for cleanup
|
||||||
|
this.messageListeners = new Map();
|
||||||
|
this.onEnd = () => {
|
||||||
|
this.updateStatus(SubscriberStatus.ENDED);
|
||||||
|
this.emitter.emit("-node", this.instance, this.nodeKey);
|
||||||
|
};
|
||||||
|
this.onError = (error) => {
|
||||||
|
this.emitter.emit("nodeError", error, this.nodeKey);
|
||||||
|
};
|
||||||
|
this.onMoved = () => {
|
||||||
|
this.emitter.emit("moved");
|
||||||
|
};
|
||||||
|
this.instance = new Redis_1.default((0, utils_1.defaults)({
|
||||||
|
enableReadyCheck: false,
|
||||||
|
enableOfflineQueue: true,
|
||||||
|
connectionName: (0, util_1.getConnectionName)("ssubscriber", options.connectionName),
|
||||||
|
/**
|
||||||
|
* Disable auto reconnection for subscribers.
|
||||||
|
* The ClusterSubscriberGroup will handle the reconnection.
|
||||||
|
*/
|
||||||
|
retryStrategy: null,
|
||||||
|
lazyConnect: true,
|
||||||
|
}, options, redisOptions));
|
||||||
|
this.lazyConnect = (_a = redisOptions === null || redisOptions === void 0 ? void 0 : redisOptions.lazyConnect) !== null && _a !== void 0 ? _a : true;
|
||||||
|
this.nodeKey = (0, util_1.getNodeKey)(options);
|
||||||
|
// Register listeners
|
||||||
|
this.instance.on("end", this.onEnd);
|
||||||
|
this.instance.on("error", this.onError);
|
||||||
|
this.instance.on("moved", this.onMoved);
|
||||||
|
for (const event of ["smessage", "smessageBuffer"]) {
|
||||||
|
const listener = (...args) => {
|
||||||
|
this.emitter.emit(event, ...args);
|
||||||
|
};
|
||||||
|
this.messageListeners.set(event, listener);
|
||||||
|
this.instance.on(event, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async start() {
|
||||||
|
if (this.connectPromise) {
|
||||||
|
return this.connectPromise;
|
||||||
|
}
|
||||||
|
if (this.status === SubscriberStatus.STARTING ||
|
||||||
|
this.status === SubscriberStatus.CONNECTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.status === SubscriberStatus.ENDED || !this.instance) {
|
||||||
|
throw new Error(`Sharded subscriber ${this.nodeKey} cannot be restarted once ended.`);
|
||||||
|
}
|
||||||
|
this.updateStatus(SubscriberStatus.STARTING);
|
||||||
|
this.connectPromise = this.instance.connect();
|
||||||
|
try {
|
||||||
|
await this.connectPromise;
|
||||||
|
this.updateStatus(SubscriberStatus.CONNECTED);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.updateStatus(SubscriberStatus.ENDED);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.connectPromise = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
this.updateStatus(SubscriberStatus.STOPPING);
|
||||||
|
if (this.instance) {
|
||||||
|
this.instance.disconnect();
|
||||||
|
this.instance.removeAllListeners();
|
||||||
|
this.messageListeners.clear();
|
||||||
|
this.instance = null;
|
||||||
|
}
|
||||||
|
this.updateStatus(SubscriberStatus.ENDED);
|
||||||
|
debug("stopped %s", this.nodeKey);
|
||||||
|
}
|
||||||
|
isStarted() {
|
||||||
|
return [
|
||||||
|
SubscriberStatus.CONNECTED,
|
||||||
|
SubscriberStatus.STARTING,
|
||||||
|
].includes(this.status);
|
||||||
|
}
|
||||||
|
get subscriberStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
isHealthy() {
|
||||||
|
return ((this.status === SubscriberStatus.IDLE ||
|
||||||
|
this.status === SubscriberStatus.CONNECTED ||
|
||||||
|
this.status === SubscriberStatus.STARTING) &&
|
||||||
|
this.instance !== null);
|
||||||
|
}
|
||||||
|
getInstance() {
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
getNodeKey() {
|
||||||
|
return this.nodeKey;
|
||||||
|
}
|
||||||
|
isLazyConnect() {
|
||||||
|
return this.lazyConnect;
|
||||||
|
}
|
||||||
|
updateStatus(nextStatus) {
|
||||||
|
if (this.status === nextStatus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ALLOWED_STATUS_UPDATES[this.status].includes(nextStatus)) {
|
||||||
|
debug("Invalid status transition for %s: %s -> %s", this.nodeKey, this.status, nextStatus);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.status = nextStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = ShardedSubscriber;
|
||||||
163
backend/node_modules/ioredis/built/cluster/index.d.ts
generated
vendored
Normal file
163
backend/node_modules/ioredis/built/cluster/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import Command from "../Command";
|
||||||
|
import Redis from "../Redis";
|
||||||
|
import ScanStream from "../ScanStream";
|
||||||
|
import { Transaction } from "../transaction";
|
||||||
|
import { Callback, ScanStreamOptions, WriteableStream } from "../types";
|
||||||
|
import Commander from "../utils/Commander";
|
||||||
|
import { ClusterOptions } from "./ClusterOptions";
|
||||||
|
import { NodeKey, NodeRole } from "./util";
|
||||||
|
export declare type ClusterNode = string | number | {
|
||||||
|
host?: string | undefined;
|
||||||
|
port?: number | undefined;
|
||||||
|
};
|
||||||
|
declare type ClusterStatus = "end" | "close" | "wait" | "connecting" | "connect" | "ready" | "reconnecting" | "disconnecting";
|
||||||
|
/**
|
||||||
|
* Client for the official Redis Cluster
|
||||||
|
*/
|
||||||
|
declare class Cluster extends Commander {
|
||||||
|
options: ClusterOptions;
|
||||||
|
slots: NodeKey[][];
|
||||||
|
status: ClusterStatus;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
_groupsIds: {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
_groupsBySlot: number[];
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
isCluster: boolean;
|
||||||
|
private startupNodes;
|
||||||
|
private connectionPool;
|
||||||
|
private manuallyClosing;
|
||||||
|
private retryAttempts;
|
||||||
|
private delayQueue;
|
||||||
|
private offlineQueue;
|
||||||
|
private subscriber;
|
||||||
|
private shardedSubscribers;
|
||||||
|
private slotsTimer;
|
||||||
|
private reconnectTimeout;
|
||||||
|
private isRefreshing;
|
||||||
|
private _refreshSlotsCacheCallbacks;
|
||||||
|
private _autoPipelines;
|
||||||
|
private _runningAutoPipelines;
|
||||||
|
private _readyDelayedCallbacks;
|
||||||
|
private subscriberGroupEmitter;
|
||||||
|
/**
|
||||||
|
* Every time Cluster#connect() is called, this value will be
|
||||||
|
* auto-incrementing. The purpose of this value is used for
|
||||||
|
* discarding previous connect attampts when creating a new
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
private connectionEpoch;
|
||||||
|
/**
|
||||||
|
* Creates an instance of Cluster.
|
||||||
|
*/
|
||||||
|
constructor(startupNodes: ClusterNode[], options?: ClusterOptions);
|
||||||
|
/**
|
||||||
|
* Connect to a cluster
|
||||||
|
*/
|
||||||
|
connect(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Disconnect from every node in the cluster.
|
||||||
|
*/
|
||||||
|
disconnect(reconnect?: boolean): void;
|
||||||
|
/**
|
||||||
|
* Quit the cluster gracefully.
|
||||||
|
*/
|
||||||
|
quit(callback?: Callback<"OK">): Promise<"OK">;
|
||||||
|
/**
|
||||||
|
* Create a new instance with the same startup nodes and options as the current one.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* var cluster = new Redis.Cluster([{ host: "127.0.0.1", port: "30001" }]);
|
||||||
|
* var anotherCluster = cluster.duplicate();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
duplicate(overrideStartupNodes?: any[], overrideOptions?: {}): Cluster;
|
||||||
|
/**
|
||||||
|
* Get nodes with the specified role
|
||||||
|
*/
|
||||||
|
nodes(role?: NodeRole): Redis[];
|
||||||
|
/**
|
||||||
|
* This is needed in order not to install a listener for each auto pipeline
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
delayUntilReady(callback: Callback): void;
|
||||||
|
/**
|
||||||
|
* Get the number of commands queued in automatic pipelines.
|
||||||
|
*
|
||||||
|
* This is not available (and returns 0) until the cluster is connected and slots information have been received.
|
||||||
|
*/
|
||||||
|
get autoPipelineQueueSize(): number;
|
||||||
|
/**
|
||||||
|
* Refresh the slot cache
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
refreshSlotsCache(callback?: Callback<void>): void;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
sendCommand(command: Command, stream?: WriteableStream, node?: any): unknown;
|
||||||
|
sscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
sscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
hscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
hscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
zscanStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
zscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
handleError(error: Error, ttl: {
|
||||||
|
value?: any;
|
||||||
|
}, handlers: any): void;
|
||||||
|
private resetOfflineQueue;
|
||||||
|
private clearNodesRefreshInterval;
|
||||||
|
private resetNodesRefreshInterval;
|
||||||
|
/**
|
||||||
|
* Change cluster instance's status
|
||||||
|
*/
|
||||||
|
private setStatus;
|
||||||
|
/**
|
||||||
|
* Called when closed to check whether a reconnection should be made
|
||||||
|
*/
|
||||||
|
private handleCloseEvent;
|
||||||
|
/**
|
||||||
|
* Flush offline queue with error.
|
||||||
|
*/
|
||||||
|
private flushQueue;
|
||||||
|
private executeOfflineCommands;
|
||||||
|
private natMapper;
|
||||||
|
private getInfoFromNode;
|
||||||
|
private invokeReadyDelayedCallbacks;
|
||||||
|
/**
|
||||||
|
* Check whether Cluster is able to process commands
|
||||||
|
*/
|
||||||
|
private readyCheck;
|
||||||
|
private resolveSrv;
|
||||||
|
private dnsLookup;
|
||||||
|
/**
|
||||||
|
* Normalize startup nodes, and resolving hostnames to IPs.
|
||||||
|
*
|
||||||
|
* This process happens every time when #connect() is called since
|
||||||
|
* #startupNodes and DNS records may chanage.
|
||||||
|
*/
|
||||||
|
private resolveStartupNodeHostnames;
|
||||||
|
private createScanStream;
|
||||||
|
private createShardedSubscriberGroup;
|
||||||
|
}
|
||||||
|
interface Cluster extends EventEmitter {
|
||||||
|
}
|
||||||
|
interface Cluster extends Transaction {
|
||||||
|
}
|
||||||
|
export default Cluster;
|
||||||
937
backend/node_modules/ioredis/built/cluster/index.js
generated
vendored
Normal file
937
backend/node_modules/ioredis/built/cluster/index.js
generated
vendored
Normal file
@@ -0,0 +1,937 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const commands_1 = require("@ioredis/commands");
|
||||||
|
const events_1 = require("events");
|
||||||
|
const redis_errors_1 = require("redis-errors");
|
||||||
|
const standard_as_callback_1 = require("standard-as-callback");
|
||||||
|
const Command_1 = require("../Command");
|
||||||
|
const ClusterAllFailedError_1 = require("../errors/ClusterAllFailedError");
|
||||||
|
const Redis_1 = require("../Redis");
|
||||||
|
const ScanStream_1 = require("../ScanStream");
|
||||||
|
const transaction_1 = require("../transaction");
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const applyMixin_1 = require("../utils/applyMixin");
|
||||||
|
const Commander_1 = require("../utils/Commander");
|
||||||
|
const ClusterOptions_1 = require("./ClusterOptions");
|
||||||
|
const ClusterSubscriber_1 = require("./ClusterSubscriber");
|
||||||
|
const ConnectionPool_1 = require("./ConnectionPool");
|
||||||
|
const DelayQueue_1 = require("./DelayQueue");
|
||||||
|
const util_1 = require("./util");
|
||||||
|
const Deque = require("denque");
|
||||||
|
const ClusterSubscriberGroup_1 = require("./ClusterSubscriberGroup");
|
||||||
|
const debug = (0, utils_1.Debug)("cluster");
|
||||||
|
const REJECT_OVERWRITTEN_COMMANDS = new WeakSet();
|
||||||
|
/**
|
||||||
|
* Client for the official Redis Cluster
|
||||||
|
*/
|
||||||
|
class Cluster extends Commander_1.default {
|
||||||
|
/**
|
||||||
|
* Creates an instance of Cluster.
|
||||||
|
*/
|
||||||
|
//TODO: Add an option that enables or disables sharded PubSub
|
||||||
|
constructor(startupNodes, options = {}) {
|
||||||
|
super();
|
||||||
|
this.slots = [];
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
this._groupsIds = {};
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
this._groupsBySlot = Array(16384);
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
this.isCluster = true;
|
||||||
|
this.retryAttempts = 0;
|
||||||
|
this.delayQueue = new DelayQueue_1.default();
|
||||||
|
this.offlineQueue = new Deque();
|
||||||
|
this.isRefreshing = false;
|
||||||
|
this._refreshSlotsCacheCallbacks = [];
|
||||||
|
this._autoPipelines = new Map();
|
||||||
|
this._runningAutoPipelines = new Set();
|
||||||
|
this._readyDelayedCallbacks = [];
|
||||||
|
/**
|
||||||
|
* Every time Cluster#connect() is called, this value will be
|
||||||
|
* auto-incrementing. The purpose of this value is used for
|
||||||
|
* discarding previous connect attampts when creating a new
|
||||||
|
* connection.
|
||||||
|
*/
|
||||||
|
this.connectionEpoch = 0;
|
||||||
|
events_1.EventEmitter.call(this);
|
||||||
|
this.startupNodes = startupNodes;
|
||||||
|
this.options = (0, utils_1.defaults)({}, options, ClusterOptions_1.DEFAULT_CLUSTER_OPTIONS, this.options);
|
||||||
|
if (this.options.shardedSubscribers) {
|
||||||
|
this.createShardedSubscriberGroup();
|
||||||
|
}
|
||||||
|
if (this.options.redisOptions &&
|
||||||
|
this.options.redisOptions.keyPrefix &&
|
||||||
|
!this.options.keyPrefix) {
|
||||||
|
this.options.keyPrefix = this.options.redisOptions.keyPrefix;
|
||||||
|
}
|
||||||
|
// validate options
|
||||||
|
if (typeof this.options.scaleReads !== "function" &&
|
||||||
|
["all", "master", "slave"].indexOf(this.options.scaleReads) === -1) {
|
||||||
|
throw new Error('Invalid option scaleReads "' +
|
||||||
|
this.options.scaleReads +
|
||||||
|
'". Expected "all", "master", "slave" or a custom function');
|
||||||
|
}
|
||||||
|
this.connectionPool = new ConnectionPool_1.default(this.options.redisOptions);
|
||||||
|
this.connectionPool.on("-node", (redis, key) => {
|
||||||
|
this.emit("-node", redis);
|
||||||
|
});
|
||||||
|
this.connectionPool.on("+node", (redis) => {
|
||||||
|
this.emit("+node", redis);
|
||||||
|
});
|
||||||
|
this.connectionPool.on("drain", () => {
|
||||||
|
this.setStatus("close");
|
||||||
|
});
|
||||||
|
this.connectionPool.on("nodeError", (error, key) => {
|
||||||
|
this.emit("node error", error, key);
|
||||||
|
});
|
||||||
|
this.subscriber = new ClusterSubscriber_1.default(this.connectionPool, this);
|
||||||
|
if (this.options.scripts) {
|
||||||
|
Object.entries(this.options.scripts).forEach(([name, definition]) => {
|
||||||
|
this.defineCommand(name, definition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.options.lazyConnect) {
|
||||||
|
this.setStatus("wait");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.connect().catch((err) => {
|
||||||
|
debug("connecting failed: %s", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Connect to a cluster
|
||||||
|
*/
|
||||||
|
connect() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (this.status === "connecting" ||
|
||||||
|
this.status === "connect" ||
|
||||||
|
this.status === "ready") {
|
||||||
|
reject(new Error("Redis is already connecting/connected"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const epoch = ++this.connectionEpoch;
|
||||||
|
this.setStatus("connecting");
|
||||||
|
this.resolveStartupNodeHostnames()
|
||||||
|
.then((nodes) => {
|
||||||
|
if (this.connectionEpoch !== epoch) {
|
||||||
|
debug("discard connecting after resolving startup nodes because epoch not match: %d != %d", epoch, this.connectionEpoch);
|
||||||
|
reject(new redis_errors_1.RedisError("Connection is discarded because a new connection is made"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.status !== "connecting") {
|
||||||
|
debug("discard connecting after resolving startup nodes because the status changed to %s", this.status);
|
||||||
|
reject(new redis_errors_1.RedisError("Connection is aborted"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.connectionPool.reset(nodes);
|
||||||
|
if (this.options.shardedSubscribers) {
|
||||||
|
this.shardedSubscribers
|
||||||
|
.reset(this.slots, this.connectionPool.getNodes("all"))
|
||||||
|
.catch((err) => {
|
||||||
|
// TODO should we emit an error event here?
|
||||||
|
debug("Error while starting subscribers: %s", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const readyHandler = () => {
|
||||||
|
this.setStatus("ready");
|
||||||
|
this.retryAttempts = 0;
|
||||||
|
this.executeOfflineCommands();
|
||||||
|
this.resetNodesRefreshInterval();
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
let closeListener = undefined;
|
||||||
|
const refreshListener = () => {
|
||||||
|
this.invokeReadyDelayedCallbacks(undefined);
|
||||||
|
this.removeListener("close", closeListener);
|
||||||
|
this.manuallyClosing = false;
|
||||||
|
this.setStatus("connect");
|
||||||
|
if (this.options.enableReadyCheck) {
|
||||||
|
this.readyCheck((err, fail) => {
|
||||||
|
if (err || fail) {
|
||||||
|
debug("Ready check failed (%s). Reconnecting...", err || fail);
|
||||||
|
if (this.status === "connect") {
|
||||||
|
this.disconnect(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
readyHandler();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
readyHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
closeListener = () => {
|
||||||
|
const error = new Error("None of startup nodes is available");
|
||||||
|
this.removeListener("refresh", refreshListener);
|
||||||
|
this.invokeReadyDelayedCallbacks(error);
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
this.once("refresh", refreshListener);
|
||||||
|
this.once("close", closeListener);
|
||||||
|
this.once("close", this.handleCloseEvent.bind(this));
|
||||||
|
this.refreshSlotsCache((err) => {
|
||||||
|
if (err && err.message === ClusterAllFailedError_1.default.defaultMessage) {
|
||||||
|
Redis_1.default.prototype.silentEmit.call(this, "error", err);
|
||||||
|
this.connectionPool.reset([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.subscriber.start();
|
||||||
|
if (this.options.shardedSubscribers) {
|
||||||
|
this.shardedSubscribers.start().catch((err) => {
|
||||||
|
// TODO should we emit an error event here?
|
||||||
|
debug("Error while starting subscribers: %s", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.setStatus("close");
|
||||||
|
this.handleCloseEvent(err);
|
||||||
|
this.invokeReadyDelayedCallbacks(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Disconnect from every node in the cluster.
|
||||||
|
*/
|
||||||
|
disconnect(reconnect = false) {
|
||||||
|
const status = this.status;
|
||||||
|
this.setStatus("disconnecting");
|
||||||
|
if (!reconnect) {
|
||||||
|
this.manuallyClosing = true;
|
||||||
|
}
|
||||||
|
if (this.reconnectTimeout && !reconnect) {
|
||||||
|
clearTimeout(this.reconnectTimeout);
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
debug("Canceled reconnecting attempts");
|
||||||
|
}
|
||||||
|
this.clearNodesRefreshInterval();
|
||||||
|
this.subscriber.stop();
|
||||||
|
if (this.options.shardedSubscribers) {
|
||||||
|
this.shardedSubscribers.stop();
|
||||||
|
}
|
||||||
|
if (status === "wait") {
|
||||||
|
this.setStatus("close");
|
||||||
|
this.handleCloseEvent();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.connectionPool.reset([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Quit the cluster gracefully.
|
||||||
|
*/
|
||||||
|
quit(callback) {
|
||||||
|
const status = this.status;
|
||||||
|
this.setStatus("disconnecting");
|
||||||
|
this.manuallyClosing = true;
|
||||||
|
if (this.reconnectTimeout) {
|
||||||
|
clearTimeout(this.reconnectTimeout);
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
}
|
||||||
|
this.clearNodesRefreshInterval();
|
||||||
|
this.subscriber.stop();
|
||||||
|
if (this.options.shardedSubscribers) {
|
||||||
|
this.shardedSubscribers.stop();
|
||||||
|
}
|
||||||
|
if (status === "wait") {
|
||||||
|
const ret = (0, standard_as_callback_1.default)(Promise.resolve("OK"), callback);
|
||||||
|
// use setImmediate to make sure "close" event
|
||||||
|
// being emitted after quit() is returned
|
||||||
|
setImmediate(function () {
|
||||||
|
this.setStatus("close");
|
||||||
|
this.handleCloseEvent();
|
||||||
|
}.bind(this));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return (0, standard_as_callback_1.default)(Promise.all(this.nodes().map((node) => node.quit().catch((err) => {
|
||||||
|
// Ignore the error caused by disconnecting since
|
||||||
|
// we're disconnecting...
|
||||||
|
if (err.message === utils_1.CONNECTION_CLOSED_ERROR_MSG) {
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}))).then(() => "OK"), callback);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a new instance with the same startup nodes and options as the current one.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* var cluster = new Redis.Cluster([{ host: "127.0.0.1", port: "30001" }]);
|
||||||
|
* var anotherCluster = cluster.duplicate();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
duplicate(overrideStartupNodes = [], overrideOptions = {}) {
|
||||||
|
const startupNodes = overrideStartupNodes.length > 0
|
||||||
|
? overrideStartupNodes
|
||||||
|
: this.startupNodes.slice(0);
|
||||||
|
const options = Object.assign({}, this.options, overrideOptions);
|
||||||
|
return new Cluster(startupNodes, options);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get nodes with the specified role
|
||||||
|
*/
|
||||||
|
nodes(role = "all") {
|
||||||
|
if (role !== "all" && role !== "master" && role !== "slave") {
|
||||||
|
throw new Error('Invalid role "' + role + '". Expected "all", "master" or "slave"');
|
||||||
|
}
|
||||||
|
return this.connectionPool.getNodes(role);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This is needed in order not to install a listener for each auto pipeline
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
delayUntilReady(callback) {
|
||||||
|
this._readyDelayedCallbacks.push(callback);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the number of commands queued in automatic pipelines.
|
||||||
|
*
|
||||||
|
* This is not available (and returns 0) until the cluster is connected and slots information have been received.
|
||||||
|
*/
|
||||||
|
get autoPipelineQueueSize() {
|
||||||
|
let queued = 0;
|
||||||
|
for (const pipeline of this._autoPipelines.values()) {
|
||||||
|
queued += pipeline.length;
|
||||||
|
}
|
||||||
|
return queued;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Refresh the slot cache
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
refreshSlotsCache(callback) {
|
||||||
|
if (callback) {
|
||||||
|
this._refreshSlotsCacheCallbacks.push(callback);
|
||||||
|
}
|
||||||
|
if (this.isRefreshing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isRefreshing = true;
|
||||||
|
const _this = this;
|
||||||
|
const wrapper = (error) => {
|
||||||
|
this.isRefreshing = false;
|
||||||
|
for (const callback of this._refreshSlotsCacheCallbacks) {
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
this._refreshSlotsCacheCallbacks = [];
|
||||||
|
};
|
||||||
|
const nodes = (0, utils_1.shuffle)(this.connectionPool.getNodes());
|
||||||
|
let lastNodeError = null;
|
||||||
|
function tryNode(index) {
|
||||||
|
if (index === nodes.length) {
|
||||||
|
const error = new ClusterAllFailedError_1.default(ClusterAllFailedError_1.default.defaultMessage, lastNodeError);
|
||||||
|
return wrapper(error);
|
||||||
|
}
|
||||||
|
const node = nodes[index];
|
||||||
|
const key = `${node.options.host}:${node.options.port}`;
|
||||||
|
debug("getting slot cache from %s", key);
|
||||||
|
_this.getInfoFromNode(node, function (err) {
|
||||||
|
switch (_this.status) {
|
||||||
|
case "close":
|
||||||
|
case "end":
|
||||||
|
return wrapper(new Error("Cluster is disconnected."));
|
||||||
|
case "disconnecting":
|
||||||
|
return wrapper(new Error("Cluster is disconnecting."));
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
_this.emit("node error", err, key);
|
||||||
|
lastNodeError = err;
|
||||||
|
tryNode(index + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_this.emit("refresh");
|
||||||
|
wrapper();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tryNode(0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
sendCommand(command, stream, node) {
|
||||||
|
if (this.status === "wait") {
|
||||||
|
this.connect().catch(utils_1.noop);
|
||||||
|
}
|
||||||
|
if (this.status === "end") {
|
||||||
|
command.reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
|
||||||
|
return command.promise;
|
||||||
|
}
|
||||||
|
let to = this.options.scaleReads;
|
||||||
|
if (to !== "master") {
|
||||||
|
const isCommandReadOnly = command.isReadOnly ||
|
||||||
|
((0, commands_1.exists)(command.name) && (0, commands_1.hasFlag)(command.name, "readonly"));
|
||||||
|
if (!isCommandReadOnly) {
|
||||||
|
to = "master";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let targetSlot = node ? node.slot : command.getSlot();
|
||||||
|
const ttl = {};
|
||||||
|
const _this = this;
|
||||||
|
if (!node && !REJECT_OVERWRITTEN_COMMANDS.has(command)) {
|
||||||
|
REJECT_OVERWRITTEN_COMMANDS.add(command);
|
||||||
|
const reject = command.reject;
|
||||||
|
command.reject = function (err) {
|
||||||
|
const partialTry = tryConnection.bind(null, true);
|
||||||
|
_this.handleError(err, ttl, {
|
||||||
|
moved: function (slot, key) {
|
||||||
|
debug("command %s is moved to %s", command.name, key);
|
||||||
|
targetSlot = Number(slot);
|
||||||
|
if (_this.slots[slot]) {
|
||||||
|
_this.slots[slot][0] = key;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_this.slots[slot] = [key];
|
||||||
|
}
|
||||||
|
_this._groupsBySlot[slot] =
|
||||||
|
_this._groupsIds[_this.slots[slot].join(";")];
|
||||||
|
_this.connectionPool.findOrCreate(_this.natMapper(key));
|
||||||
|
tryConnection();
|
||||||
|
debug("refreshing slot caches... (triggered by MOVED error)");
|
||||||
|
_this.refreshSlotsCache();
|
||||||
|
},
|
||||||
|
ask: function (slot, key) {
|
||||||
|
debug("command %s is required to ask %s:%s", command.name, key);
|
||||||
|
const mapped = _this.natMapper(key);
|
||||||
|
_this.connectionPool.findOrCreate(mapped);
|
||||||
|
tryConnection(false, `${mapped.host}:${mapped.port}`);
|
||||||
|
},
|
||||||
|
tryagain: partialTry,
|
||||||
|
clusterDown: partialTry,
|
||||||
|
connectionClosed: partialTry,
|
||||||
|
maxRedirections: function (redirectionError) {
|
||||||
|
reject.call(command, redirectionError);
|
||||||
|
},
|
||||||
|
defaults: function () {
|
||||||
|
reject.call(command, err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
tryConnection();
|
||||||
|
function tryConnection(random, asking) {
|
||||||
|
if (_this.status === "end") {
|
||||||
|
command.reject(new redis_errors_1.AbortError("Cluster is ended."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let redis;
|
||||||
|
if (_this.status === "ready" || command.name === "cluster") {
|
||||||
|
if (node && node.redis) {
|
||||||
|
redis = node.redis;
|
||||||
|
}
|
||||||
|
else if (Command_1.default.checkFlag("ENTER_SUBSCRIBER_MODE", command.name) ||
|
||||||
|
Command_1.default.checkFlag("EXIT_SUBSCRIBER_MODE", command.name)) {
|
||||||
|
if (_this.options.shardedSubscribers &&
|
||||||
|
(command.name == "ssubscribe" || command.name == "sunsubscribe")) {
|
||||||
|
const sub = _this.shardedSubscribers.getResponsibleSubscriber(targetSlot);
|
||||||
|
if (!sub) {
|
||||||
|
command.reject(new redis_errors_1.AbortError(`No sharded subscriber for slot: ${targetSlot}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let status = -1;
|
||||||
|
if (command.name == "ssubscribe") {
|
||||||
|
status = _this.shardedSubscribers.addChannels(command.getKeys());
|
||||||
|
}
|
||||||
|
if (command.name == "sunsubscribe") {
|
||||||
|
status = _this.shardedSubscribers.removeChannels(command.getKeys());
|
||||||
|
}
|
||||||
|
if (status !== -1) {
|
||||||
|
redis = sub.getInstance();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
command.reject(new redis_errors_1.AbortError("Possible CROSSSLOT error: All channels must hash to the same slot"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
redis = _this.subscriber.getInstance();
|
||||||
|
}
|
||||||
|
if (!redis) {
|
||||||
|
command.reject(new redis_errors_1.AbortError("No subscriber for the cluster"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!random) {
|
||||||
|
if (typeof targetSlot === "number" && _this.slots[targetSlot]) {
|
||||||
|
const nodeKeys = _this.slots[targetSlot];
|
||||||
|
if (typeof to === "function") {
|
||||||
|
const nodes = nodeKeys.map(function (key) {
|
||||||
|
return _this.connectionPool.getInstanceByKey(key);
|
||||||
|
});
|
||||||
|
redis = to(nodes, command);
|
||||||
|
if (Array.isArray(redis)) {
|
||||||
|
redis = (0, utils_1.sample)(redis);
|
||||||
|
}
|
||||||
|
if (!redis) {
|
||||||
|
redis = nodes[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let key;
|
||||||
|
if (to === "all") {
|
||||||
|
key = (0, utils_1.sample)(nodeKeys);
|
||||||
|
}
|
||||||
|
else if (to === "slave" && nodeKeys.length > 1) {
|
||||||
|
key = (0, utils_1.sample)(nodeKeys, 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
key = nodeKeys[0];
|
||||||
|
}
|
||||||
|
redis = _this.connectionPool.getInstanceByKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (asking) {
|
||||||
|
redis = _this.connectionPool.getInstanceByKey(asking);
|
||||||
|
redis.asking();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!redis) {
|
||||||
|
redis =
|
||||||
|
(typeof to === "function"
|
||||||
|
? null
|
||||||
|
: _this.connectionPool.getSampleInstance(to)) ||
|
||||||
|
_this.connectionPool.getSampleInstance("all");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node && !node.redis) {
|
||||||
|
node.redis = redis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (redis) {
|
||||||
|
redis.sendCommand(command, stream);
|
||||||
|
}
|
||||||
|
else if (_this.options.enableOfflineQueue) {
|
||||||
|
_this.offlineQueue.push({
|
||||||
|
command: command,
|
||||||
|
stream: stream,
|
||||||
|
node: node,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
command.reject(new Error("Cluster isn't ready and enableOfflineQueue options is false"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return command.promise;
|
||||||
|
}
|
||||||
|
sscanStream(key, options) {
|
||||||
|
return this.createScanStream("sscan", { key, options });
|
||||||
|
}
|
||||||
|
sscanBufferStream(key, options) {
|
||||||
|
return this.createScanStream("sscanBuffer", { key, options });
|
||||||
|
}
|
||||||
|
hscanStream(key, options) {
|
||||||
|
return this.createScanStream("hscan", { key, options });
|
||||||
|
}
|
||||||
|
hscanBufferStream(key, options) {
|
||||||
|
return this.createScanStream("hscanBuffer", { key, options });
|
||||||
|
}
|
||||||
|
zscanStream(key, options) {
|
||||||
|
return this.createScanStream("zscan", { key, options });
|
||||||
|
}
|
||||||
|
zscanBufferStream(key, options) {
|
||||||
|
return this.createScanStream("zscanBuffer", { key, options });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
handleError(error, ttl, handlers) {
|
||||||
|
if (typeof ttl.value === "undefined") {
|
||||||
|
ttl.value = this.options.maxRedirections;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ttl.value -= 1;
|
||||||
|
}
|
||||||
|
if (ttl.value <= 0) {
|
||||||
|
handlers.maxRedirections(new Error("Too many Cluster redirections. Last error: " + error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const errv = error.message.split(" ");
|
||||||
|
if (errv[0] === "MOVED") {
|
||||||
|
const timeout = this.options.retryDelayOnMoved;
|
||||||
|
if (timeout && typeof timeout === "number") {
|
||||||
|
this.delayQueue.push("moved", handlers.moved.bind(null, errv[1], errv[2]), { timeout });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handlers.moved(errv[1], errv[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (errv[0] === "ASK") {
|
||||||
|
handlers.ask(errv[1], errv[2]);
|
||||||
|
}
|
||||||
|
else if (errv[0] === "TRYAGAIN") {
|
||||||
|
this.delayQueue.push("tryagain", handlers.tryagain, {
|
||||||
|
timeout: this.options.retryDelayOnTryAgain,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (errv[0] === "CLUSTERDOWN" &&
|
||||||
|
this.options.retryDelayOnClusterDown > 0) {
|
||||||
|
this.delayQueue.push("clusterdown", handlers.connectionClosed, {
|
||||||
|
timeout: this.options.retryDelayOnClusterDown,
|
||||||
|
callback: this.refreshSlotsCache.bind(this),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (error.message === utils_1.CONNECTION_CLOSED_ERROR_MSG &&
|
||||||
|
this.options.retryDelayOnFailover > 0 &&
|
||||||
|
this.status === "ready") {
|
||||||
|
this.delayQueue.push("failover", handlers.connectionClosed, {
|
||||||
|
timeout: this.options.retryDelayOnFailover,
|
||||||
|
callback: this.refreshSlotsCache.bind(this),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handlers.defaults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetOfflineQueue() {
|
||||||
|
this.offlineQueue = new Deque();
|
||||||
|
}
|
||||||
|
clearNodesRefreshInterval() {
|
||||||
|
if (this.slotsTimer) {
|
||||||
|
clearTimeout(this.slotsTimer);
|
||||||
|
this.slotsTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetNodesRefreshInterval() {
|
||||||
|
if (this.slotsTimer || !this.options.slotsRefreshInterval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nextRound = () => {
|
||||||
|
this.slotsTimer = setTimeout(() => {
|
||||||
|
debug('refreshing slot caches... (triggered by "slotsRefreshInterval" option)');
|
||||||
|
this.refreshSlotsCache(() => {
|
||||||
|
nextRound();
|
||||||
|
});
|
||||||
|
}, this.options.slotsRefreshInterval);
|
||||||
|
};
|
||||||
|
nextRound();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Change cluster instance's status
|
||||||
|
*/
|
||||||
|
setStatus(status) {
|
||||||
|
debug("status: %s -> %s", this.status || "[empty]", status);
|
||||||
|
this.status = status;
|
||||||
|
process.nextTick(() => {
|
||||||
|
this.emit(status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Called when closed to check whether a reconnection should be made
|
||||||
|
*/
|
||||||
|
handleCloseEvent(reason) {
|
||||||
|
var _a;
|
||||||
|
if (reason) {
|
||||||
|
debug("closed because %s", reason);
|
||||||
|
}
|
||||||
|
let retryDelay;
|
||||||
|
if (!this.manuallyClosing &&
|
||||||
|
typeof this.options.clusterRetryStrategy === "function") {
|
||||||
|
retryDelay = this.options.clusterRetryStrategy.call(this, ++this.retryAttempts, reason);
|
||||||
|
}
|
||||||
|
if (typeof retryDelay === "number") {
|
||||||
|
this.setStatus("reconnecting");
|
||||||
|
this.reconnectTimeout = setTimeout(() => {
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
debug("Cluster is disconnected. Retrying after %dms", retryDelay);
|
||||||
|
this.connect().catch(function (err) {
|
||||||
|
debug("Got error %s when reconnecting. Ignoring...", err);
|
||||||
|
});
|
||||||
|
}, retryDelay);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.options.shardedSubscribers) {
|
||||||
|
(_a = this.subscriberGroupEmitter) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
|
||||||
|
}
|
||||||
|
this.setStatus("end");
|
||||||
|
this.flushQueue(new Error("None of startup nodes is available"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Flush offline queue with error.
|
||||||
|
*/
|
||||||
|
flushQueue(error) {
|
||||||
|
let item;
|
||||||
|
while ((item = this.offlineQueue.shift())) {
|
||||||
|
item.command.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executeOfflineCommands() {
|
||||||
|
if (this.offlineQueue.length) {
|
||||||
|
debug("send %d commands in offline queue", this.offlineQueue.length);
|
||||||
|
const offlineQueue = this.offlineQueue;
|
||||||
|
this.resetOfflineQueue();
|
||||||
|
let item;
|
||||||
|
while ((item = offlineQueue.shift())) {
|
||||||
|
this.sendCommand(item.command, item.stream, item.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
natMapper(nodeKey) {
|
||||||
|
const key = typeof nodeKey === "string"
|
||||||
|
? nodeKey
|
||||||
|
: `${nodeKey.host}:${nodeKey.port}`;
|
||||||
|
let mapped = null;
|
||||||
|
if (this.options.natMap && typeof this.options.natMap === "function") {
|
||||||
|
mapped = this.options.natMap(key);
|
||||||
|
}
|
||||||
|
else if (this.options.natMap && typeof this.options.natMap === "object") {
|
||||||
|
mapped = this.options.natMap[key];
|
||||||
|
}
|
||||||
|
if (mapped) {
|
||||||
|
debug("NAT mapping %s -> %O", key, mapped);
|
||||||
|
return Object.assign({}, mapped);
|
||||||
|
}
|
||||||
|
return typeof nodeKey === "string"
|
||||||
|
? (0, util_1.nodeKeyToRedisOptions)(nodeKey)
|
||||||
|
: nodeKey;
|
||||||
|
}
|
||||||
|
getInfoFromNode(redis, callback) {
|
||||||
|
if (!redis) {
|
||||||
|
return callback(new Error("Node is disconnected"));
|
||||||
|
}
|
||||||
|
// Use a duplication of the connection to avoid
|
||||||
|
// timeouts when the connection is in the blocking
|
||||||
|
// mode (e.g. waiting for BLPOP).
|
||||||
|
const duplicatedConnection = redis.duplicate({
|
||||||
|
enableOfflineQueue: true,
|
||||||
|
enableReadyCheck: false,
|
||||||
|
retryStrategy: null,
|
||||||
|
connectionName: (0, util_1.getConnectionName)("refresher", this.options.redisOptions && this.options.redisOptions.connectionName),
|
||||||
|
});
|
||||||
|
// Ignore error events since we will handle
|
||||||
|
// exceptions for the CLUSTER SLOTS command.
|
||||||
|
duplicatedConnection.on("error", utils_1.noop);
|
||||||
|
duplicatedConnection.cluster("SLOTS", (0, utils_1.timeout)((err, result) => {
|
||||||
|
duplicatedConnection.disconnect();
|
||||||
|
if (err) {
|
||||||
|
debug("error encountered running CLUSTER.SLOTS: %s", err);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
if (this.status === "disconnecting" ||
|
||||||
|
this.status === "close" ||
|
||||||
|
this.status === "end") {
|
||||||
|
debug("ignore CLUSTER.SLOTS results (count: %d) since cluster status is %s", result.length, this.status);
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nodes = [];
|
||||||
|
debug("cluster slots result count: %d", result.length);
|
||||||
|
for (let i = 0; i < result.length; ++i) {
|
||||||
|
const items = result[i];
|
||||||
|
const slotRangeStart = items[0];
|
||||||
|
const slotRangeEnd = items[1];
|
||||||
|
const keys = [];
|
||||||
|
for (let j = 2; j < items.length; j++) {
|
||||||
|
if (!items[j][0]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const node = this.natMapper({
|
||||||
|
host: items[j][0],
|
||||||
|
port: items[j][1],
|
||||||
|
});
|
||||||
|
node.readOnly = j !== 2;
|
||||||
|
nodes.push(node);
|
||||||
|
keys.push(node.host + ":" + node.port);
|
||||||
|
}
|
||||||
|
debug("cluster slots result [%d]: slots %d~%d served by %s", i, slotRangeStart, slotRangeEnd, keys);
|
||||||
|
for (let slot = slotRangeStart; slot <= slotRangeEnd; slot++) {
|
||||||
|
this.slots[slot] = keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assign to each node keys a numeric value to make autopipeline comparison faster.
|
||||||
|
this._groupsIds = Object.create(null);
|
||||||
|
let j = 0;
|
||||||
|
for (let i = 0; i < 16384; i++) {
|
||||||
|
const target = (this.slots[i] || []).join(";");
|
||||||
|
if (!target.length) {
|
||||||
|
this._groupsBySlot[i] = undefined;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!this._groupsIds[target]) {
|
||||||
|
this._groupsIds[target] = ++j;
|
||||||
|
}
|
||||||
|
this._groupsBySlot[i] = this._groupsIds[target];
|
||||||
|
}
|
||||||
|
this.connectionPool.reset(nodes);
|
||||||
|
if (this.options.shardedSubscribers) {
|
||||||
|
this.shardedSubscribers
|
||||||
|
.reset(this.slots, this.connectionPool.getNodes("all"))
|
||||||
|
.catch((err) => {
|
||||||
|
// TODO should we emit an error event here?
|
||||||
|
debug("Error while starting subscribers: %s", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
}, this.options.slotsRefreshTimeout));
|
||||||
|
}
|
||||||
|
invokeReadyDelayedCallbacks(err) {
|
||||||
|
for (const c of this._readyDelayedCallbacks) {
|
||||||
|
process.nextTick(c, err);
|
||||||
|
}
|
||||||
|
this._readyDelayedCallbacks = [];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check whether Cluster is able to process commands
|
||||||
|
*/
|
||||||
|
readyCheck(callback) {
|
||||||
|
this.cluster("INFO", (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
if (typeof res !== "string") {
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
let state;
|
||||||
|
const lines = res.split("\r\n");
|
||||||
|
for (let i = 0; i < lines.length; ++i) {
|
||||||
|
const parts = lines[i].split(":");
|
||||||
|
if (parts[0] === "cluster_state") {
|
||||||
|
state = parts[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state === "fail") {
|
||||||
|
debug("cluster state not ok (%s)", state);
|
||||||
|
callback(null, state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resolveSrv(hostname) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.options.resolveSrv(hostname, (err, records) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
const self = this, groupedRecords = (0, util_1.groupSrvRecords)(records), sortedKeys = Object.keys(groupedRecords).sort((a, b) => parseInt(a) - parseInt(b));
|
||||||
|
function tryFirstOne(err) {
|
||||||
|
if (!sortedKeys.length) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
const key = sortedKeys[0], group = groupedRecords[key], record = (0, util_1.weightSrvRecords)(group);
|
||||||
|
if (!group.records.length) {
|
||||||
|
sortedKeys.shift();
|
||||||
|
}
|
||||||
|
self.dnsLookup(record.name).then((host) => resolve({
|
||||||
|
host,
|
||||||
|
port: record.port,
|
||||||
|
}), tryFirstOne);
|
||||||
|
}
|
||||||
|
tryFirstOne();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dnsLookup(hostname) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.options.dnsLookup(hostname, (err, address) => {
|
||||||
|
if (err) {
|
||||||
|
debug("failed to resolve hostname %s to IP: %s", hostname, err.message);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug("resolved hostname %s to IP %s", hostname, address);
|
||||||
|
resolve(address);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Normalize startup nodes, and resolving hostnames to IPs.
|
||||||
|
*
|
||||||
|
* This process happens every time when #connect() is called since
|
||||||
|
* #startupNodes and DNS records may chanage.
|
||||||
|
*/
|
||||||
|
async resolveStartupNodeHostnames() {
|
||||||
|
if (!Array.isArray(this.startupNodes) || this.startupNodes.length === 0) {
|
||||||
|
throw new Error("`startupNodes` should contain at least one node.");
|
||||||
|
}
|
||||||
|
const startupNodes = (0, util_1.normalizeNodeOptions)(this.startupNodes);
|
||||||
|
const hostnames = (0, util_1.getUniqueHostnamesFromOptions)(startupNodes);
|
||||||
|
if (hostnames.length === 0) {
|
||||||
|
return startupNodes;
|
||||||
|
}
|
||||||
|
const configs = await Promise.all(hostnames.map((this.options.useSRVRecords ? this.resolveSrv : this.dnsLookup).bind(this)));
|
||||||
|
const hostnameToConfig = (0, utils_1.zipMap)(hostnames, configs);
|
||||||
|
return startupNodes.map((node) => {
|
||||||
|
const config = hostnameToConfig.get(node.host);
|
||||||
|
if (!config) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (this.options.useSRVRecords) {
|
||||||
|
return Object.assign({}, node, config);
|
||||||
|
}
|
||||||
|
return Object.assign({}, node, { host: config });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
createScanStream(command, { key, options = {} }) {
|
||||||
|
return new ScanStream_1.default({
|
||||||
|
objectMode: true,
|
||||||
|
key: key,
|
||||||
|
redis: this,
|
||||||
|
command: command,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
createShardedSubscriberGroup() {
|
||||||
|
this.subscriberGroupEmitter = new events_1.EventEmitter();
|
||||||
|
this.shardedSubscribers = new ClusterSubscriberGroup_1.default(this.subscriberGroupEmitter, this.options);
|
||||||
|
// Error handler used only for sharded-subscriber-triggered slots cache refreshes.
|
||||||
|
// Normal (non-subscriber) connections are created with lazyConnect: true and can
|
||||||
|
// become zombied. For sharded subscribers, a ClusterAllFailedError means
|
||||||
|
// we have lost all nodes from the subscriber perspective and must tear down.
|
||||||
|
const refreshSlotsCacheCallback = (err) => {
|
||||||
|
// Disconnect only when refreshing the slots cache fails with ClusterAllFailedError
|
||||||
|
if (err instanceof ClusterAllFailedError_1.default) {
|
||||||
|
this.disconnect(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.subscriberGroupEmitter.on("-node", (redis, nodeKey) => {
|
||||||
|
this.emit("-node", redis, nodeKey);
|
||||||
|
this.refreshSlotsCache(refreshSlotsCacheCallback);
|
||||||
|
});
|
||||||
|
this.subscriberGroupEmitter.on("subscriberConnectFailed", ({ delay, error }) => {
|
||||||
|
this.emit("error", error);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshSlotsCache(refreshSlotsCacheCallback);
|
||||||
|
}, delay);
|
||||||
|
});
|
||||||
|
this.subscriberGroupEmitter.on("moved", () => {
|
||||||
|
this.refreshSlotsCache(refreshSlotsCacheCallback);
|
||||||
|
});
|
||||||
|
this.subscriberGroupEmitter.on("-subscriber", () => {
|
||||||
|
this.emit("-subscriber");
|
||||||
|
});
|
||||||
|
this.subscriberGroupEmitter.on("+subscriber", () => {
|
||||||
|
this.emit("+subscriber");
|
||||||
|
});
|
||||||
|
this.subscriberGroupEmitter.on("nodeError", (error, nodeKey) => {
|
||||||
|
this.emit("nodeError", error, nodeKey);
|
||||||
|
});
|
||||||
|
this.subscriberGroupEmitter.on("subscribersReady", () => {
|
||||||
|
this.emit("subscribersReady");
|
||||||
|
});
|
||||||
|
for (const event of ["smessage", "smessageBuffer"]) {
|
||||||
|
this.subscriberGroupEmitter.on(event, (arg1, arg2, arg3) => {
|
||||||
|
this.emit(event, arg1, arg2, arg3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(0, applyMixin_1.default)(Cluster, events_1.EventEmitter);
|
||||||
|
(0, transaction_1.addTransactionSupport)(Cluster.prototype);
|
||||||
|
exports.default = Cluster;
|
||||||
25
backend/node_modules/ioredis/built/cluster/util.d.ts
generated
vendored
Normal file
25
backend/node_modules/ioredis/built/cluster/util.d.ts
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { SrvRecord } from "dns";
|
||||||
|
export declare type NodeKey = string;
|
||||||
|
export declare type NodeRole = "master" | "slave" | "all";
|
||||||
|
export interface RedisOptions {
|
||||||
|
port: number;
|
||||||
|
host: string;
|
||||||
|
username?: string | undefined;
|
||||||
|
password?: string | undefined;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
export interface SrvRecordsGroup {
|
||||||
|
totalWeight: number;
|
||||||
|
records: SrvRecord[];
|
||||||
|
}
|
||||||
|
export interface GroupedSrvRecords {
|
||||||
|
[key: number]: SrvRecordsGroup;
|
||||||
|
}
|
||||||
|
export declare function getNodeKey(node: RedisOptions): NodeKey;
|
||||||
|
export declare function nodeKeyToRedisOptions(nodeKey: NodeKey): RedisOptions;
|
||||||
|
export declare function normalizeNodeOptions(nodes: Array<string | number | object>): RedisOptions[];
|
||||||
|
export declare function getUniqueHostnamesFromOptions(nodes: RedisOptions[]): string[];
|
||||||
|
export declare function groupSrvRecords(records: SrvRecord[]): GroupedSrvRecords;
|
||||||
|
export declare function weightSrvRecords(recordsGroup: SrvRecordsGroup): SrvRecord;
|
||||||
|
export declare function getConnectionName(component: any, nodeConnectionName: any): string;
|
||||||
100
backend/node_modules/ioredis/built/cluster/util.js
generated
vendored
Normal file
100
backend/node_modules/ioredis/built/cluster/util.js
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.getConnectionName = exports.weightSrvRecords = exports.groupSrvRecords = exports.getUniqueHostnamesFromOptions = exports.normalizeNodeOptions = exports.nodeKeyToRedisOptions = exports.getNodeKey = void 0;
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const net_1 = require("net");
|
||||||
|
function getNodeKey(node) {
|
||||||
|
node.port = node.port || 6379;
|
||||||
|
node.host = node.host || "127.0.0.1";
|
||||||
|
return node.host + ":" + node.port;
|
||||||
|
}
|
||||||
|
exports.getNodeKey = getNodeKey;
|
||||||
|
function nodeKeyToRedisOptions(nodeKey) {
|
||||||
|
const portIndex = nodeKey.lastIndexOf(":");
|
||||||
|
if (portIndex === -1) {
|
||||||
|
throw new Error(`Invalid node key ${nodeKey}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
host: nodeKey.slice(0, portIndex),
|
||||||
|
port: Number(nodeKey.slice(portIndex + 1)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.nodeKeyToRedisOptions = nodeKeyToRedisOptions;
|
||||||
|
function normalizeNodeOptions(nodes) {
|
||||||
|
return nodes.map((node) => {
|
||||||
|
const options = {};
|
||||||
|
if (typeof node === "object") {
|
||||||
|
Object.assign(options, node);
|
||||||
|
}
|
||||||
|
else if (typeof node === "string") {
|
||||||
|
Object.assign(options, (0, utils_1.parseURL)(node));
|
||||||
|
}
|
||||||
|
else if (typeof node === "number") {
|
||||||
|
options.port = node;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error("Invalid argument " + node);
|
||||||
|
}
|
||||||
|
if (typeof options.port === "string") {
|
||||||
|
options.port = parseInt(options.port, 10);
|
||||||
|
}
|
||||||
|
// Cluster mode only support db 0
|
||||||
|
delete options.db;
|
||||||
|
if (!options.port) {
|
||||||
|
options.port = 6379;
|
||||||
|
}
|
||||||
|
if (!options.host) {
|
||||||
|
options.host = "127.0.0.1";
|
||||||
|
}
|
||||||
|
return (0, utils_1.resolveTLSProfile)(options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.normalizeNodeOptions = normalizeNodeOptions;
|
||||||
|
function getUniqueHostnamesFromOptions(nodes) {
|
||||||
|
const uniqueHostsMap = {};
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
uniqueHostsMap[node.host] = true;
|
||||||
|
});
|
||||||
|
return Object.keys(uniqueHostsMap).filter((host) => !(0, net_1.isIP)(host));
|
||||||
|
}
|
||||||
|
exports.getUniqueHostnamesFromOptions = getUniqueHostnamesFromOptions;
|
||||||
|
function groupSrvRecords(records) {
|
||||||
|
const recordsByPriority = {};
|
||||||
|
for (const record of records) {
|
||||||
|
if (!recordsByPriority.hasOwnProperty(record.priority)) {
|
||||||
|
recordsByPriority[record.priority] = {
|
||||||
|
totalWeight: record.weight,
|
||||||
|
records: [record],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recordsByPriority[record.priority].totalWeight += record.weight;
|
||||||
|
recordsByPriority[record.priority].records.push(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return recordsByPriority;
|
||||||
|
}
|
||||||
|
exports.groupSrvRecords = groupSrvRecords;
|
||||||
|
function weightSrvRecords(recordsGroup) {
|
||||||
|
if (recordsGroup.records.length === 1) {
|
||||||
|
recordsGroup.totalWeight = 0;
|
||||||
|
return recordsGroup.records.shift();
|
||||||
|
}
|
||||||
|
// + `recordsGroup.records.length` to support `weight` 0
|
||||||
|
const random = Math.floor(Math.random() * (recordsGroup.totalWeight + recordsGroup.records.length));
|
||||||
|
let total = 0;
|
||||||
|
for (const [i, record] of recordsGroup.records.entries()) {
|
||||||
|
total += 1 + record.weight;
|
||||||
|
if (total > random) {
|
||||||
|
recordsGroup.totalWeight -= record.weight;
|
||||||
|
recordsGroup.records.splice(i, 1);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.weightSrvRecords = weightSrvRecords;
|
||||||
|
function getConnectionName(component, nodeConnectionName) {
|
||||||
|
const prefix = `ioredis-cluster(${component})`;
|
||||||
|
return nodeConnectionName ? `${prefix}:${nodeConnectionName}` : prefix;
|
||||||
|
}
|
||||||
|
exports.getConnectionName = getConnectionName;
|
||||||
12
backend/node_modules/ioredis/built/connectors/AbstractConnector.d.ts
generated
vendored
Normal file
12
backend/node_modules/ioredis/built/connectors/AbstractConnector.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { NetStream } from "../types";
|
||||||
|
export declare type ErrorEmitter = (type: string, err: Error) => void;
|
||||||
|
export default abstract class AbstractConnector {
|
||||||
|
firstError?: Error;
|
||||||
|
protected connecting: boolean;
|
||||||
|
protected stream: NetStream;
|
||||||
|
private disconnectTimeout;
|
||||||
|
constructor(disconnectTimeout: number);
|
||||||
|
check(info: any): boolean;
|
||||||
|
disconnect(): void;
|
||||||
|
abstract connect(_: ErrorEmitter): Promise<NetStream>;
|
||||||
|
}
|
||||||
26
backend/node_modules/ioredis/built/connectors/AbstractConnector.js
generated
vendored
Normal file
26
backend/node_modules/ioredis/built/connectors/AbstractConnector.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const debug = (0, utils_1.Debug)("AbstractConnector");
|
||||||
|
class AbstractConnector {
|
||||||
|
constructor(disconnectTimeout) {
|
||||||
|
this.connecting = false;
|
||||||
|
this.disconnectTimeout = disconnectTimeout;
|
||||||
|
}
|
||||||
|
check(info) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
disconnect() {
|
||||||
|
this.connecting = false;
|
||||||
|
if (this.stream) {
|
||||||
|
const stream = this.stream; // Make sure callbacks refer to the same instance
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
debug("stream %s:%s still open, destroying it", stream.remoteAddress, stream.remotePort);
|
||||||
|
stream.destroy();
|
||||||
|
}, this.disconnectTimeout);
|
||||||
|
stream.on("close", () => clearTimeout(timeout));
|
||||||
|
stream.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = AbstractConnector;
|
||||||
5
backend/node_modules/ioredis/built/connectors/ConnectorConstructor.d.ts
generated
vendored
Normal file
5
backend/node_modules/ioredis/built/connectors/ConnectorConstructor.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import AbstractConnector from "./AbstractConnector";
|
||||||
|
interface ConnectorConstructor {
|
||||||
|
new (options: unknown): AbstractConnector;
|
||||||
|
}
|
||||||
|
export default ConnectorConstructor;
|
||||||
2
backend/node_modules/ioredis/built/connectors/ConnectorConstructor.js
generated
vendored
Normal file
2
backend/node_modules/ioredis/built/connectors/ConnectorConstructor.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
11
backend/node_modules/ioredis/built/connectors/SentinelConnector/FailoverDetector.d.ts
generated
vendored
Normal file
11
backend/node_modules/ioredis/built/connectors/SentinelConnector/FailoverDetector.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import SentinelConnector from "./index";
|
||||||
|
import { Sentinel } from "./types";
|
||||||
|
export declare class FailoverDetector {
|
||||||
|
private connector;
|
||||||
|
private sentinels;
|
||||||
|
private isDisconnected;
|
||||||
|
constructor(connector: SentinelConnector, sentinels: Sentinel[]);
|
||||||
|
cleanup(): void;
|
||||||
|
subscribe(): Promise<void>;
|
||||||
|
private disconnect;
|
||||||
|
}
|
||||||
45
backend/node_modules/ioredis/built/connectors/SentinelConnector/FailoverDetector.js
generated
vendored
Normal file
45
backend/node_modules/ioredis/built/connectors/SentinelConnector/FailoverDetector.js
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.FailoverDetector = void 0;
|
||||||
|
const utils_1 = require("../../utils");
|
||||||
|
const debug = (0, utils_1.Debug)("FailoverDetector");
|
||||||
|
const CHANNEL_NAME = "+switch-master";
|
||||||
|
class FailoverDetector {
|
||||||
|
// sentinels can't be used for regular commands after this
|
||||||
|
constructor(connector, sentinels) {
|
||||||
|
this.isDisconnected = false;
|
||||||
|
this.connector = connector;
|
||||||
|
this.sentinels = sentinels;
|
||||||
|
}
|
||||||
|
cleanup() {
|
||||||
|
this.isDisconnected = true;
|
||||||
|
for (const sentinel of this.sentinels) {
|
||||||
|
sentinel.client.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async subscribe() {
|
||||||
|
debug("Starting FailoverDetector");
|
||||||
|
const promises = [];
|
||||||
|
for (const sentinel of this.sentinels) {
|
||||||
|
const promise = sentinel.client.subscribe(CHANNEL_NAME).catch((err) => {
|
||||||
|
debug("Failed to subscribe to failover messages on sentinel %s:%s (%s)", sentinel.address.host || "127.0.0.1", sentinel.address.port || 26739, err.message);
|
||||||
|
});
|
||||||
|
promises.push(promise);
|
||||||
|
sentinel.client.on("message", (channel) => {
|
||||||
|
if (!this.isDisconnected && channel === CHANNEL_NAME) {
|
||||||
|
this.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
disconnect() {
|
||||||
|
// Avoid disconnecting more than once per failover.
|
||||||
|
// A new FailoverDetector will be created after reconnecting.
|
||||||
|
this.isDisconnected = true;
|
||||||
|
debug("Failover detected, disconnecting");
|
||||||
|
// Will call this.cleanup()
|
||||||
|
this.connector.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.FailoverDetector = FailoverDetector;
|
||||||
13
backend/node_modules/ioredis/built/connectors/SentinelConnector/SentinelIterator.d.ts
generated
vendored
Normal file
13
backend/node_modules/ioredis/built/connectors/SentinelConnector/SentinelIterator.d.ts
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { SentinelAddress } from "./types";
|
||||||
|
export default class SentinelIterator implements Iterator<Partial<SentinelAddress>> {
|
||||||
|
private cursor;
|
||||||
|
private sentinels;
|
||||||
|
constructor(sentinels: Array<Partial<SentinelAddress>>);
|
||||||
|
next(): {
|
||||||
|
done: boolean;
|
||||||
|
value: Partial<SentinelAddress>;
|
||||||
|
};
|
||||||
|
reset(moveCurrentEndpointToFirst: boolean): void;
|
||||||
|
add(sentinel: SentinelAddress): boolean;
|
||||||
|
toString(): string;
|
||||||
|
}
|
||||||
37
backend/node_modules/ioredis/built/connectors/SentinelConnector/SentinelIterator.js
generated
vendored
Normal file
37
backend/node_modules/ioredis/built/connectors/SentinelConnector/SentinelIterator.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
function isSentinelEql(a, b) {
|
||||||
|
return ((a.host || "127.0.0.1") === (b.host || "127.0.0.1") &&
|
||||||
|
(a.port || 26379) === (b.port || 26379));
|
||||||
|
}
|
||||||
|
class SentinelIterator {
|
||||||
|
constructor(sentinels) {
|
||||||
|
this.cursor = 0;
|
||||||
|
this.sentinels = sentinels.slice(0);
|
||||||
|
}
|
||||||
|
next() {
|
||||||
|
const done = this.cursor >= this.sentinels.length;
|
||||||
|
return { done, value: done ? undefined : this.sentinels[this.cursor++] };
|
||||||
|
}
|
||||||
|
reset(moveCurrentEndpointToFirst) {
|
||||||
|
if (moveCurrentEndpointToFirst &&
|
||||||
|
this.sentinels.length > 1 &&
|
||||||
|
this.cursor !== 1) {
|
||||||
|
this.sentinels.unshift(...this.sentinels.splice(this.cursor - 1));
|
||||||
|
}
|
||||||
|
this.cursor = 0;
|
||||||
|
}
|
||||||
|
add(sentinel) {
|
||||||
|
for (let i = 0; i < this.sentinels.length; i++) {
|
||||||
|
if (isSentinelEql(sentinel, this.sentinels[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.sentinels.push(sentinel);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
toString() {
|
||||||
|
return `${JSON.stringify(this.sentinels)} @${this.cursor}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = SentinelIterator;
|
||||||
72
backend/node_modules/ioredis/built/connectors/SentinelConnector/index.d.ts
generated
vendored
Normal file
72
backend/node_modules/ioredis/built/connectors/SentinelConnector/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import { NatMap } from "../../cluster/ClusterOptions";
|
||||||
|
import { ConnectionOptions } from "tls";
|
||||||
|
import SentinelIterator from "./SentinelIterator";
|
||||||
|
import { SentinelAddress } from "./types";
|
||||||
|
import AbstractConnector, { ErrorEmitter } from "../AbstractConnector";
|
||||||
|
import { NetStream } from "../../types";
|
||||||
|
interface AddressFromResponse {
|
||||||
|
port: string;
|
||||||
|
ip: string;
|
||||||
|
flags?: string | undefined;
|
||||||
|
}
|
||||||
|
declare type PreferredSlaves = ((slaves: AddressFromResponse[]) => AddressFromResponse | null) | Array<{
|
||||||
|
port: string;
|
||||||
|
ip: string;
|
||||||
|
prio?: number | undefined;
|
||||||
|
}> | {
|
||||||
|
port: string;
|
||||||
|
ip: string;
|
||||||
|
prio?: number | undefined;
|
||||||
|
};
|
||||||
|
export { SentinelAddress, SentinelIterator };
|
||||||
|
export interface SentinelConnectionOptions {
|
||||||
|
/**
|
||||||
|
* Master group name of the Sentinel
|
||||||
|
*/
|
||||||
|
name?: string | undefined;
|
||||||
|
/**
|
||||||
|
* @default "master"
|
||||||
|
*/
|
||||||
|
role?: "master" | "slave" | undefined;
|
||||||
|
tls?: ConnectionOptions | undefined;
|
||||||
|
sentinelUsername?: string | undefined;
|
||||||
|
sentinelPassword?: string | undefined;
|
||||||
|
sentinels?: Array<Partial<SentinelAddress>> | undefined;
|
||||||
|
sentinelRetryStrategy?: ((retryAttempts: number) => number | void | null) | undefined;
|
||||||
|
sentinelReconnectStrategy?: ((retryAttempts: number) => number | void | null) | undefined;
|
||||||
|
preferredSlaves?: PreferredSlaves | undefined;
|
||||||
|
connectTimeout?: number | undefined;
|
||||||
|
disconnectTimeout?: number | undefined;
|
||||||
|
sentinelCommandTimeout?: number | undefined;
|
||||||
|
enableTLSForSentinelMode?: boolean | undefined;
|
||||||
|
sentinelTLS?: ConnectionOptions | undefined;
|
||||||
|
natMap?: NatMap | undefined;
|
||||||
|
updateSentinels?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* @default 10
|
||||||
|
*/
|
||||||
|
sentinelMaxConnections?: number | undefined;
|
||||||
|
failoverDetector?: boolean | undefined;
|
||||||
|
}
|
||||||
|
export default class SentinelConnector extends AbstractConnector {
|
||||||
|
protected options: SentinelConnectionOptions;
|
||||||
|
emitter: EventEmitter | null;
|
||||||
|
protected sentinelIterator: SentinelIterator;
|
||||||
|
private retryAttempts;
|
||||||
|
private failoverDetector;
|
||||||
|
constructor(options: SentinelConnectionOptions);
|
||||||
|
check(info: {
|
||||||
|
role?: string;
|
||||||
|
}): boolean;
|
||||||
|
disconnect(): void;
|
||||||
|
connect(eventEmitter: ErrorEmitter): Promise<NetStream>;
|
||||||
|
private updateSentinels;
|
||||||
|
private resolveMaster;
|
||||||
|
private resolveSlave;
|
||||||
|
private sentinelNatResolve;
|
||||||
|
private connectToSentinel;
|
||||||
|
private resolve;
|
||||||
|
private initFailoverDetector;
|
||||||
|
}
|
||||||
305
backend/node_modules/ioredis/built/connectors/SentinelConnector/index.js
generated
vendored
Normal file
305
backend/node_modules/ioredis/built/connectors/SentinelConnector/index.js
generated
vendored
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.SentinelIterator = void 0;
|
||||||
|
const net_1 = require("net");
|
||||||
|
const utils_1 = require("../../utils");
|
||||||
|
const tls_1 = require("tls");
|
||||||
|
const SentinelIterator_1 = require("./SentinelIterator");
|
||||||
|
exports.SentinelIterator = SentinelIterator_1.default;
|
||||||
|
const AbstractConnector_1 = require("../AbstractConnector");
|
||||||
|
const Redis_1 = require("../../Redis");
|
||||||
|
const FailoverDetector_1 = require("./FailoverDetector");
|
||||||
|
const debug = (0, utils_1.Debug)("SentinelConnector");
|
||||||
|
class SentinelConnector extends AbstractConnector_1.default {
|
||||||
|
constructor(options) {
|
||||||
|
super(options.disconnectTimeout);
|
||||||
|
this.options = options;
|
||||||
|
this.emitter = null;
|
||||||
|
this.failoverDetector = null;
|
||||||
|
if (!this.options.sentinels.length) {
|
||||||
|
throw new Error("Requires at least one sentinel to connect to.");
|
||||||
|
}
|
||||||
|
if (!this.options.name) {
|
||||||
|
throw new Error("Requires the name of master.");
|
||||||
|
}
|
||||||
|
this.sentinelIterator = new SentinelIterator_1.default(this.options.sentinels);
|
||||||
|
}
|
||||||
|
check(info) {
|
||||||
|
const roleMatches = !info.role || this.options.role === info.role;
|
||||||
|
if (!roleMatches) {
|
||||||
|
debug("role invalid, expected %s, but got %s", this.options.role, info.role);
|
||||||
|
// Start from the next item.
|
||||||
|
// Note that `reset` will move the cursor to the previous element,
|
||||||
|
// so we advance two steps here.
|
||||||
|
this.sentinelIterator.next();
|
||||||
|
this.sentinelIterator.next();
|
||||||
|
this.sentinelIterator.reset(true);
|
||||||
|
}
|
||||||
|
return roleMatches;
|
||||||
|
}
|
||||||
|
disconnect() {
|
||||||
|
super.disconnect();
|
||||||
|
if (this.failoverDetector) {
|
||||||
|
this.failoverDetector.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connect(eventEmitter) {
|
||||||
|
this.connecting = true;
|
||||||
|
this.retryAttempts = 0;
|
||||||
|
let lastError;
|
||||||
|
const connectToNext = async () => {
|
||||||
|
const endpoint = this.sentinelIterator.next();
|
||||||
|
if (endpoint.done) {
|
||||||
|
this.sentinelIterator.reset(false);
|
||||||
|
const retryDelay = typeof this.options.sentinelRetryStrategy === "function"
|
||||||
|
? this.options.sentinelRetryStrategy(++this.retryAttempts)
|
||||||
|
: null;
|
||||||
|
let errorMsg = typeof retryDelay !== "number"
|
||||||
|
? "All sentinels are unreachable and retry is disabled."
|
||||||
|
: `All sentinels are unreachable. Retrying from scratch after ${retryDelay}ms.`;
|
||||||
|
if (lastError) {
|
||||||
|
errorMsg += ` Last error: ${lastError.message}`;
|
||||||
|
}
|
||||||
|
debug(errorMsg);
|
||||||
|
const error = new Error(errorMsg);
|
||||||
|
if (typeof retryDelay === "number") {
|
||||||
|
eventEmitter("error", error);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||||
|
return connectToNext();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let resolved = null;
|
||||||
|
let err = null;
|
||||||
|
try {
|
||||||
|
resolved = await this.resolve(endpoint.value);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
err = error;
|
||||||
|
}
|
||||||
|
if (!this.connecting) {
|
||||||
|
throw new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG);
|
||||||
|
}
|
||||||
|
const endpointAddress = endpoint.value.host + ":" + endpoint.value.port;
|
||||||
|
if (resolved) {
|
||||||
|
debug("resolved: %s:%s from sentinel %s", resolved.host, resolved.port, endpointAddress);
|
||||||
|
if (this.options.enableTLSForSentinelMode && this.options.tls) {
|
||||||
|
Object.assign(resolved, this.options.tls);
|
||||||
|
this.stream = (0, tls_1.connect)(resolved);
|
||||||
|
this.stream.once("secureConnect", this.initFailoverDetector.bind(this));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.stream = (0, net_1.createConnection)(resolved);
|
||||||
|
this.stream.once("connect", this.initFailoverDetector.bind(this));
|
||||||
|
}
|
||||||
|
this.stream.once("error", (err) => {
|
||||||
|
this.firstError = err;
|
||||||
|
});
|
||||||
|
return this.stream;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const errorMsg = err
|
||||||
|
? "failed to connect to sentinel " +
|
||||||
|
endpointAddress +
|
||||||
|
" because " +
|
||||||
|
err.message
|
||||||
|
: "connected to sentinel " +
|
||||||
|
endpointAddress +
|
||||||
|
" successfully, but got an invalid reply: " +
|
||||||
|
resolved;
|
||||||
|
debug(errorMsg);
|
||||||
|
eventEmitter("sentinelError", new Error(errorMsg));
|
||||||
|
if (err) {
|
||||||
|
lastError = err;
|
||||||
|
}
|
||||||
|
return connectToNext();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return connectToNext();
|
||||||
|
}
|
||||||
|
async updateSentinels(client) {
|
||||||
|
if (!this.options.updateSentinels) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = await client.sentinel("sentinels", this.options.name);
|
||||||
|
if (!Array.isArray(result)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
.map(utils_1.packObject)
|
||||||
|
.forEach((sentinel) => {
|
||||||
|
const flags = sentinel.flags ? sentinel.flags.split(",") : [];
|
||||||
|
if (flags.indexOf("disconnected") === -1 &&
|
||||||
|
sentinel.ip &&
|
||||||
|
sentinel.port) {
|
||||||
|
const endpoint = this.sentinelNatResolve(addressResponseToAddress(sentinel));
|
||||||
|
if (this.sentinelIterator.add(endpoint)) {
|
||||||
|
debug("adding sentinel %s:%s", endpoint.host, endpoint.port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
debug("Updated internal sentinels: %s", this.sentinelIterator);
|
||||||
|
}
|
||||||
|
async resolveMaster(client) {
|
||||||
|
const result = await client.sentinel("get-master-addr-by-name", this.options.name);
|
||||||
|
await this.updateSentinels(client);
|
||||||
|
return this.sentinelNatResolve(Array.isArray(result)
|
||||||
|
? { host: result[0], port: Number(result[1]) }
|
||||||
|
: null);
|
||||||
|
}
|
||||||
|
async resolveSlave(client) {
|
||||||
|
const result = await client.sentinel("slaves", this.options.name);
|
||||||
|
if (!Array.isArray(result)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const availableSlaves = result
|
||||||
|
.map(utils_1.packObject)
|
||||||
|
.filter((slave) => slave.flags && !slave.flags.match(/(disconnected|s_down|o_down)/));
|
||||||
|
return this.sentinelNatResolve(selectPreferredSentinel(availableSlaves, this.options.preferredSlaves));
|
||||||
|
}
|
||||||
|
sentinelNatResolve(item) {
|
||||||
|
if (!item || !this.options.natMap)
|
||||||
|
return item;
|
||||||
|
const key = `${item.host}:${item.port}`;
|
||||||
|
let result = item;
|
||||||
|
if (typeof this.options.natMap === "function") {
|
||||||
|
result = this.options.natMap(key) || item;
|
||||||
|
}
|
||||||
|
else if (typeof this.options.natMap === "object") {
|
||||||
|
result = this.options.natMap[key] || item;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
connectToSentinel(endpoint, options) {
|
||||||
|
const redis = new Redis_1.default({
|
||||||
|
port: endpoint.port || 26379,
|
||||||
|
host: endpoint.host,
|
||||||
|
username: this.options.sentinelUsername || null,
|
||||||
|
password: this.options.sentinelPassword || null,
|
||||||
|
family: endpoint.family ||
|
||||||
|
// @ts-expect-error
|
||||||
|
("path" in this.options && this.options.path
|
||||||
|
? undefined
|
||||||
|
: // @ts-expect-error
|
||||||
|
this.options.family),
|
||||||
|
tls: this.options.sentinelTLS,
|
||||||
|
retryStrategy: null,
|
||||||
|
enableReadyCheck: false,
|
||||||
|
connectTimeout: this.options.connectTimeout,
|
||||||
|
commandTimeout: this.options.sentinelCommandTimeout,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
// @ts-expect-error
|
||||||
|
return redis;
|
||||||
|
}
|
||||||
|
async resolve(endpoint) {
|
||||||
|
const client = this.connectToSentinel(endpoint);
|
||||||
|
// ignore the errors since resolve* methods will handle them
|
||||||
|
client.on("error", noop);
|
||||||
|
try {
|
||||||
|
if (this.options.role === "slave") {
|
||||||
|
return await this.resolveSlave(client);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await this.resolveMaster(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
client.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async initFailoverDetector() {
|
||||||
|
var _a;
|
||||||
|
if (!this.options.failoverDetector) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Move the current sentinel to the first position
|
||||||
|
this.sentinelIterator.reset(true);
|
||||||
|
const sentinels = [];
|
||||||
|
// In case of a large amount of sentinels, limit the number of concurrent connections
|
||||||
|
while (sentinels.length < this.options.sentinelMaxConnections) {
|
||||||
|
const { done, value } = this.sentinelIterator.next();
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const client = this.connectToSentinel(value, {
|
||||||
|
lazyConnect: true,
|
||||||
|
retryStrategy: this.options.sentinelReconnectStrategy,
|
||||||
|
});
|
||||||
|
client.on("reconnecting", () => {
|
||||||
|
var _a;
|
||||||
|
// Tests listen to this event
|
||||||
|
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit("sentinelReconnecting");
|
||||||
|
});
|
||||||
|
sentinels.push({ address: value, client });
|
||||||
|
}
|
||||||
|
this.sentinelIterator.reset(false);
|
||||||
|
if (this.failoverDetector) {
|
||||||
|
// Clean up previous detector
|
||||||
|
this.failoverDetector.cleanup();
|
||||||
|
}
|
||||||
|
this.failoverDetector = new FailoverDetector_1.FailoverDetector(this, sentinels);
|
||||||
|
await this.failoverDetector.subscribe();
|
||||||
|
// Tests listen to this event
|
||||||
|
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit("failoverSubscribed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = SentinelConnector;
|
||||||
|
function selectPreferredSentinel(availableSlaves, preferredSlaves) {
|
||||||
|
if (availableSlaves.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let selectedSlave;
|
||||||
|
if (typeof preferredSlaves === "function") {
|
||||||
|
selectedSlave = preferredSlaves(availableSlaves);
|
||||||
|
}
|
||||||
|
else if (preferredSlaves !== null && typeof preferredSlaves === "object") {
|
||||||
|
const preferredSlavesArray = Array.isArray(preferredSlaves)
|
||||||
|
? preferredSlaves
|
||||||
|
: [preferredSlaves];
|
||||||
|
// sort by priority
|
||||||
|
preferredSlavesArray.sort((a, b) => {
|
||||||
|
// default the priority to 1
|
||||||
|
if (!a.prio) {
|
||||||
|
a.prio = 1;
|
||||||
|
}
|
||||||
|
if (!b.prio) {
|
||||||
|
b.prio = 1;
|
||||||
|
}
|
||||||
|
// lowest priority first
|
||||||
|
if (a.prio < b.prio) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.prio > b.prio) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
// loop over preferred slaves and return the first match
|
||||||
|
for (let p = 0; p < preferredSlavesArray.length; p++) {
|
||||||
|
for (let a = 0; a < availableSlaves.length; a++) {
|
||||||
|
const slave = availableSlaves[a];
|
||||||
|
if (slave.ip === preferredSlavesArray[p].ip) {
|
||||||
|
if (slave.port === preferredSlavesArray[p].port) {
|
||||||
|
selectedSlave = slave;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedSlave) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if none of the preferred slaves are available, a random available slave is returned
|
||||||
|
if (!selectedSlave) {
|
||||||
|
selectedSlave = (0, utils_1.sample)(availableSlaves);
|
||||||
|
}
|
||||||
|
return addressResponseToAddress(selectedSlave);
|
||||||
|
}
|
||||||
|
function addressResponseToAddress(input) {
|
||||||
|
return { host: input.ip, port: Number(input.port) };
|
||||||
|
}
|
||||||
|
function noop() { }
|
||||||
21
backend/node_modules/ioredis/built/connectors/SentinelConnector/types.d.ts
generated
vendored
Normal file
21
backend/node_modules/ioredis/built/connectors/SentinelConnector/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { RedisOptions } from "../../redis/RedisOptions";
|
||||||
|
export interface SentinelAddress {
|
||||||
|
port: number;
|
||||||
|
host: string;
|
||||||
|
family?: number;
|
||||||
|
}
|
||||||
|
export interface RedisClient {
|
||||||
|
options: RedisOptions;
|
||||||
|
sentinel(subcommand: "sentinels", name: string): Promise<string[]>;
|
||||||
|
sentinel(subcommand: "get-master-addr-by-name", name: string): Promise<string[]>;
|
||||||
|
sentinel(subcommand: "slaves", name: string): Promise<string[]>;
|
||||||
|
subscribe(...channelNames: string[]): Promise<number>;
|
||||||
|
on(event: "message", callback: (channel: string, message: string) => void): void;
|
||||||
|
on(event: "error", callback: (error: Error) => void): void;
|
||||||
|
on(event: "reconnecting", callback: () => void): void;
|
||||||
|
disconnect(): void;
|
||||||
|
}
|
||||||
|
export interface Sentinel {
|
||||||
|
address: Partial<SentinelAddress>;
|
||||||
|
client: RedisClient;
|
||||||
|
}
|
||||||
2
backend/node_modules/ioredis/built/connectors/SentinelConnector/types.js
generated
vendored
Normal file
2
backend/node_modules/ioredis/built/connectors/SentinelConnector/types.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
17
backend/node_modules/ioredis/built/connectors/StandaloneConnector.d.ts
generated
vendored
Normal file
17
backend/node_modules/ioredis/built/connectors/StandaloneConnector.d.ts
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { IpcNetConnectOpts, TcpNetConnectOpts } from "net";
|
||||||
|
import { ConnectionOptions } from "tls";
|
||||||
|
import { NetStream } from "../types";
|
||||||
|
import AbstractConnector, { ErrorEmitter } from "./AbstractConnector";
|
||||||
|
declare type TcpOptions = Pick<TcpNetConnectOpts, "port" | "host" | "family">;
|
||||||
|
declare type IpcOptions = Pick<IpcNetConnectOpts, "path">;
|
||||||
|
export declare type StandaloneConnectionOptions = Partial<TcpOptions & IpcOptions> & {
|
||||||
|
disconnectTimeout?: number | undefined;
|
||||||
|
tls?: ConnectionOptions | undefined;
|
||||||
|
};
|
||||||
|
export default class StandaloneConnector extends AbstractConnector {
|
||||||
|
protected options: StandaloneConnectionOptions;
|
||||||
|
constructor(options: StandaloneConnectionOptions);
|
||||||
|
connect(_: ErrorEmitter): Promise<NetStream>;
|
||||||
|
}
|
||||||
|
export {};
|
||||||
69
backend/node_modules/ioredis/built/connectors/StandaloneConnector.js
generated
vendored
Normal file
69
backend/node_modules/ioredis/built/connectors/StandaloneConnector.js
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const net_1 = require("net");
|
||||||
|
const tls_1 = require("tls");
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const AbstractConnector_1 = require("./AbstractConnector");
|
||||||
|
class StandaloneConnector extends AbstractConnector_1.default {
|
||||||
|
constructor(options) {
|
||||||
|
super(options.disconnectTimeout);
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
connect(_) {
|
||||||
|
const { options } = this;
|
||||||
|
this.connecting = true;
|
||||||
|
let connectionOptions;
|
||||||
|
if ("path" in options && options.path) {
|
||||||
|
connectionOptions = {
|
||||||
|
path: options.path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
connectionOptions = {};
|
||||||
|
if ("port" in options && options.port != null) {
|
||||||
|
connectionOptions.port = options.port;
|
||||||
|
}
|
||||||
|
if ("host" in options && options.host != null) {
|
||||||
|
connectionOptions.host = options.host;
|
||||||
|
}
|
||||||
|
if ("family" in options && options.family != null) {
|
||||||
|
connectionOptions.family = options.family;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.tls) {
|
||||||
|
Object.assign(connectionOptions, options.tls);
|
||||||
|
}
|
||||||
|
// TODO:
|
||||||
|
// We use native Promise here since other Promise
|
||||||
|
// implementation may use different schedulers that
|
||||||
|
// cause issue when the stream is resolved in the
|
||||||
|
// next tick.
|
||||||
|
// Should use the provided promise in the next major
|
||||||
|
// version and do not connect before resolved.
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
process.nextTick(() => {
|
||||||
|
if (!this.connecting) {
|
||||||
|
reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (options.tls) {
|
||||||
|
this.stream = (0, tls_1.connect)(connectionOptions);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.stream = (0, net_1.createConnection)(connectionOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.stream.once("error", (err) => {
|
||||||
|
this.firstError = err;
|
||||||
|
});
|
||||||
|
resolve(this.stream);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = StandaloneConnector;
|
||||||
3
backend/node_modules/ioredis/built/connectors/index.d.ts
generated
vendored
Normal file
3
backend/node_modules/ioredis/built/connectors/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import StandaloneConnector from "./StandaloneConnector";
|
||||||
|
import SentinelConnector from "./SentinelConnector";
|
||||||
|
export { StandaloneConnector, SentinelConnector };
|
||||||
7
backend/node_modules/ioredis/built/connectors/index.js
generated
vendored
Normal file
7
backend/node_modules/ioredis/built/connectors/index.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.SentinelConnector = exports.StandaloneConnector = void 0;
|
||||||
|
const StandaloneConnector_1 = require("./StandaloneConnector");
|
||||||
|
exports.StandaloneConnector = StandaloneConnector_1.default;
|
||||||
|
const SentinelConnector_1 = require("./SentinelConnector");
|
||||||
|
exports.SentinelConnector = SentinelConnector_1.default;
|
||||||
9
backend/node_modules/ioredis/built/constants/TLSProfiles.d.ts
generated
vendored
Normal file
9
backend/node_modules/ioredis/built/constants/TLSProfiles.d.ts
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
149
backend/node_modules/ioredis/built/constants/TLSProfiles.js
generated
vendored
Normal file
149
backend/node_modules/ioredis/built/constants/TLSProfiles.js
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/**
|
||||||
|
* TLS settings for Redis Cloud. Updated on 2022-08-19.
|
||||||
|
*/
|
||||||
|
const RedisCloudCA = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDTzCCAjegAwIBAgIJAKSVpiDswLcwMA0GCSqGSIb3DQEBBQUAMD4xFjAUBgNV
|
||||||
|
BAoMDUdhcmFudGlhIERhdGExJDAiBgNVBAMMG1NTTCBDZXJ0aWZpY2F0aW9uIEF1
|
||||||
|
dGhvcml0eTAeFw0xMzEwMDExMjE0NTVaFw0yMzA5MjkxMjE0NTVaMD4xFjAUBgNV
|
||||||
|
BAoMDUdhcmFudGlhIERhdGExJDAiBgNVBAMMG1NTTCBDZXJ0aWZpY2F0aW9uIEF1
|
||||||
|
dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZqkh/DczWP
|
||||||
|
JnxnHLQ7QL0T4B4CDKWBKCcisriGbA6ZePWVNo4hfKQC6JrzfR+081NeD6VcWUiz
|
||||||
|
rmd+jtPhIY4c+WVQYm5PKaN6DT1imYdxQw7aqO5j2KUCEh/cznpLxeSHoTxlR34E
|
||||||
|
QwF28Wl3eg2vc5ct8LjU3eozWVk3gb7alx9mSA2SgmuX5lEQawl++rSjsBStemY2
|
||||||
|
BDwOpAMXIrdEyP/cVn8mkvi/BDs5M5G+09j0gfhyCzRWMQ7Hn71u1eolRxwVxgi3
|
||||||
|
TMn+/vTaFSqxKjgck6zuAYjBRPaHe7qLxHNr1So/Mc9nPy+3wHebFwbIcnUojwbp
|
||||||
|
4nctkWbjb2cCAwEAAaNQME4wHQYDVR0OBBYEFP1whtcrydmW3ZJeuSoKZIKjze3w
|
||||||
|
MB8GA1UdIwQYMBaAFP1whtcrydmW3ZJeuSoKZIKjze3wMAwGA1UdEwQFMAMBAf8w
|
||||||
|
DQYJKoZIhvcNAQEFBQADggEBAG2erXhwRAa7+ZOBs0B6X57Hwyd1R4kfmXcs0rta
|
||||||
|
lbPpvgULSiB+TCbf3EbhJnHGyvdCY1tvlffLjdA7HJ0PCOn+YYLBA0pTU/dyvrN6
|
||||||
|
Su8NuS5yubnt9mb13nDGYo1rnt0YRfxN+8DM3fXIVr038A30UlPX2Ou1ExFJT0MZ
|
||||||
|
uFKY6ZvLdI6/1cbgmguMlAhM+DhKyV6Sr5699LM3zqeI816pZmlREETYkGr91q7k
|
||||||
|
BpXJu/dtHaGxg1ZGu6w/PCsYGUcECWENYD4VQPd8N32JjOfu6vEgoEAwfPP+3oGp
|
||||||
|
Z4m3ewACcWOAenqflb+cQYC4PsF7qbXDmRaWrbKntOlZ3n0=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIGMTCCBBmgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwajELMAkGA1UEBhMCVVMx
|
||||||
|
CzAJBgNVBAgMAkNBMQswCQYDVQQHDAJDQTESMBAGA1UECgwJUmVkaXNMYWJzMS0w
|
||||||
|
KwYDVQQDDCRSZWRpc0xhYnMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
|
||||||
|
MTgwMjI1MTUzNzM3WhcNMjgwMjIzMTUzNzM3WjBfMQswCQYDVQQGEwJVUzELMAkG
|
||||||
|
A1UECAwCQ0ExEjAQBgNVBAoMCVJlZGlzTGFiczEvMC0GA1UEAwwmUkNQIEludGVy
|
||||||
|
bWVkaWF0ZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
|
||||||
|
A4ICDwAwggIKAoICAQDf9dqbxc8Bq7Ctq9rWcxrGNKKHivqLAFpPq02yLPx6fsOv
|
||||||
|
Tq7GsDChAYBBc4v7Y2Ap9RD5Vs3dIhEANcnolf27QwrG9RMnnvzk8pCvp1o6zSU4
|
||||||
|
VuOE1W66/O1/7e2rVxyrnTcP7UgK43zNIXu7+tiAqWsO92uSnuMoGPGpeaUm1jym
|
||||||
|
hjWKtkAwDFSqvHY+XL5qDVBEjeUe+WHkYUg40cAXjusAqgm2hZt29c2wnVrxW25W
|
||||||
|
P0meNlzHGFdA2AC5z54iRiqj57dTfBTkHoBczQxcyw6hhzxZQ4e5I5zOKjXXEhZN
|
||||||
|
r0tA3YC14CTabKRus/JmZieyZzRgEy2oti64tmLYTqSlAD78pRL40VNoaSYetXLw
|
||||||
|
hhNsXCHgWaY6d5bLOc/aIQMAV5oLvZQKvuXAF1IDmhPA+bZbpWipp0zagf1P1H3s
|
||||||
|
UzsMdn2KM0ejzgotbtNlj5TcrVwpmvE3ktvUAuA+hi3FkVx1US+2Gsp5x4YOzJ7u
|
||||||
|
P1WPk6ShF0JgnJH2ILdj6kttTWwFzH17keSFICWDfH/+kM+k7Y1v3EXMQXE7y0T9
|
||||||
|
MjvJskz6d/nv+sQhY04xt64xFMGTnZjlJMzfQNi7zWFLTZnDD0lPowq7l3YiPoTT
|
||||||
|
t5Xky83lu0KZsZBo0WlWaDG00gLVdtRgVbcuSWxpi5BdLb1kRab66JptWjxwXQID
|
||||||
|
AQABo4HrMIHoMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHBzOi8vcmwtY2Etc2VydmVy
|
||||||
|
LnJlZGlzbGFicy5jb20vdjEvY3JsMEYGCCsGAQUFBwEBBDowODA2BggrBgEFBQcw
|
||||||
|
AYYqaHR0cHM6Ly9ybC1jYS1zZXJ2ZXIucmVkaXNsYWJzLmNvbS92MS9vY3NwMB0G
|
||||||
|
A1UdDgQWBBQHar5OKvQUpP2qWt6mckzToeCOHDAfBgNVHSMEGDAWgBQi42wH6hM4
|
||||||
|
L2sujEvLM0/u8lRXTzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIB
|
||||||
|
hjANBgkqhkiG9w0BAQsFAAOCAgEAirEn/iTsAKyhd+pu2W3Z5NjCko4NPU0EYUbr
|
||||||
|
AP7+POK2rzjIrJO3nFYQ/LLuC7KCXG+2qwan2SAOGmqWst13Y+WHp44Kae0kaChW
|
||||||
|
vcYLXXSoGQGC8QuFSNUdaeg3RbMDYFT04dOkqufeWVccoHVxyTSg9eD8LZuHn5jw
|
||||||
|
7QDLiEECBmIJHk5Eeo2TAZrx4Yx6ufSUX5HeVjlAzqwtAqdt99uCJ/EL8bgpWbe+
|
||||||
|
XoSpvUv0SEC1I1dCAhCKAvRlIOA6VBcmzg5Am12KzkqTul12/VEFIgzqu0Zy2Jbc
|
||||||
|
AUPrYVu/+tOGXQaijy7YgwH8P8n3s7ZeUa1VABJHcxrxYduDDJBLZi+MjheUDaZ1
|
||||||
|
jQRHYevI2tlqeSBqdPKG4zBY5lS0GiAlmuze5oENt0P3XboHoZPHiqcK3VECgTVh
|
||||||
|
/BkJcuudETSJcZDmQ8YfoKfBzRQNg2sv/hwvUv73Ss51Sco8GEt2lD8uEdib1Q6z
|
||||||
|
zDT5lXJowSzOD5ZA9OGDjnSRL+2riNtKWKEqvtEG3VBJoBzu9GoxbAc7wIZLxmli
|
||||||
|
iF5a/Zf5X+UXD3s4TMmy6C4QZJpAA2egsSQCnraWO2ULhh7iXMysSkF/nzVfZn43
|
||||||
|
iqpaB8++9a37hWq14ZmOv0TJIDz//b2+KC4VFXWQ5W5QC6whsjT+OlG4p5ZYG0jo
|
||||||
|
616pxqo=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFujCCA6KgAwIBAgIJAJ1aTT1lu2ScMA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV
|
||||||
|
BAYTAlVTMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCQ0ExEjAQBgNVBAoMCVJlZGlz
|
||||||
|
TGFiczEtMCsGA1UEAwwkUmVkaXNMYWJzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9y
|
||||||
|
aXR5MB4XDTE4MDIyNTE1MjA0MloXDTM4MDIyMDE1MjA0MlowajELMAkGA1UEBhMC
|
||||||
|
VVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJDQTESMBAGA1UECgwJUmVkaXNMYWJz
|
||||||
|
MS0wKwYDVQQDDCRSZWRpc0xhYnMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
|
||||||
|
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLEjXy7YrbN5Waau5cd6g1
|
||||||
|
G5C2tMmeTpZ0duFAPxNU4oE3RHS5gGiok346fUXuUxbZ6QkuzeN2/2Z+RmRcJhQY
|
||||||
|
Dm0ZgdG4x59An1TJfnzKKoWj8ISmoHS/TGNBdFzXV7FYNLBuqZouqePI6ReC6Qhl
|
||||||
|
pp45huV32Q3a6IDrrvx7Wo5ZczEQeFNbCeCOQYNDdTmCyEkHqc2AGo8eoIlSTutT
|
||||||
|
ULOC7R5gzJVTS0e1hesQ7jmqHjbO+VQS1NAL4/5K6cuTEqUl+XhVhPdLWBXJQ5ag
|
||||||
|
54qhX4v+ojLzeU1R/Vc6NjMvVtptWY6JihpgplprN0Yh2556ewcXMeturcKgXfGJ
|
||||||
|
xeYzsjzXerEjrVocX5V8BNrg64NlifzTMKNOOv4fVZszq1SIHR8F9ROrqiOdh8iC
|
||||||
|
JpUbLpXH9hWCSEO6VRMB2xJoKu3cgl63kF30s77x7wLFMEHiwsQRKxooE1UhgS9K
|
||||||
|
2sO4TlQ1eWUvFvHSTVDQDlGQ6zu4qjbOpb3Q8bQwoK+ai2alkXVR4Ltxe9QlgYK3
|
||||||
|
StsnPhruzZGA0wbXdpw0bnM+YdlEm5ffSTpNIfgHeaa7Dtb801FtA71ZlH7A6TaI
|
||||||
|
SIQuUST9EKmv7xrJyx0W1pGoPOLw5T029aTjnICSLdtV9bLwysrLhIYG5bnPq78B
|
||||||
|
cS+jZHFGzD7PUVGQD01nOQIDAQABo2MwYTAdBgNVHQ4EFgQUIuNsB+oTOC9rLoxL
|
||||||
|
yzNP7vJUV08wHwYDVR0jBBgwFoAUIuNsB+oTOC9rLoxLyzNP7vJUV08wDwYDVR0T
|
||||||
|
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAHfg
|
||||||
|
z5pMNUAKdMzK1aS1EDdK9yKz4qicILz5czSLj1mC7HKDRy8cVADUxEICis++CsCu
|
||||||
|
rYOvyCVergHQLREcxPq4rc5Nq1uj6J6649NEeh4WazOOjL4ZfQ1jVznMbGy+fJm3
|
||||||
|
3Hoelv6jWRG9iqeJZja7/1s6YC6bWymI/OY1e4wUKeNHAo+Vger7MlHV+RuabaX+
|
||||||
|
hSJ8bJAM59NCM7AgMTQpJCncrcdLeceYniGy5Q/qt2b5mJkQVkIdy4TPGGB+AXDJ
|
||||||
|
D0q3I/JDRkDUFNFdeW0js7fHdsvCR7O3tJy5zIgEV/o/BCkmJVtuwPYOrw/yOlKj
|
||||||
|
TY/U7ATAx9VFF6/vYEOMYSmrZlFX+98L6nJtwDqfLB5VTltqZ4H/KBxGE3IRSt9l
|
||||||
|
FXy40U+LnXzhhW+7VBAvyYX8GEXhHkKU8Gqk1xitrqfBXY74xKgyUSTolFSfFVgj
|
||||||
|
mcM/X4K45bka+qpkj7Kfv/8D4j6aZekwhN2ly6hhC1SmQ8qjMjpG/mrWOSSHZFmf
|
||||||
|
ybu9iD2AYHeIOkshIl6xYIa++Q/00/vs46IzAbQyriOi0XxlSMMVtPx0Q3isp+ji
|
||||||
|
n8Mq9eOuxYOEQ4of8twUkUDd528iwGtEdwf0Q01UyT84S62N8AySl1ZBKXJz6W4F
|
||||||
|
UhWfa/HQYOAPDdEjNgnVwLI23b8t0TozyCWw7q8h
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEjzCCA3egAwIBAgIQe55B/ALCKJDZtdNT8kD6hTANBgkqhkiG9w0BAQsFADBM
|
||||||
|
MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv
|
||||||
|
YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMjAxMjYxMjAwMDBaFw0y
|
||||||
|
NTAxMjYwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu
|
||||||
|
IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAy
|
||||||
|
MDIyIFEyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmGmg1LW9b7Lf
|
||||||
|
8zDD83yBDTEkt+FOxKJZqF4veWc5KZsQj9HfnUS2e5nj/E+JImlGPsQuoiosLuXD
|
||||||
|
BVBNAMcUFa11buFMGMeEMwiTmCXoXRrXQmH0qjpOfKgYc5gHG3BsRGaRrf7VR4eg
|
||||||
|
ofNMG9wUBw4/g/TT7+bQJdA4NfE7Y4d5gEryZiBGB/swaX6Jp/8MF4TgUmOWmalK
|
||||||
|
dZCKyb4sPGQFRTtElk67F7vU+wdGcrcOx1tDcIB0ncjLPMnaFicagl+daWGsKqTh
|
||||||
|
counQb6QJtYHa91KvCfKWocMxQ7OIbB5UARLPmC4CJ1/f8YFm35ebfzAeULYdGXu
|
||||||
|
jE9CLor0OwIDAQABo4IBXzCCAVswDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG
|
||||||
|
CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW
|
||||||
|
BBSH5Zq7a7B/t95GfJWkDBpA8HHqdjAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj
|
||||||
|
move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw
|
||||||
|
Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1
|
||||||
|
cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w
|
||||||
|
K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwIQYD
|
||||||
|
VR0gBBowGDAIBgZngQwBAgIwDAYKKwYBBAGgMgoBAjANBgkqhkiG9w0BAQsFAAOC
|
||||||
|
AQEAKRic9/f+nmhQU/wz04APZLjgG5OgsuUOyUEZjKVhNGDwxGTvKhyXGGAMW2B/
|
||||||
|
3bRi+aElpXwoxu3pL6fkElbX3B0BeS5LoDtxkyiVEBMZ8m+sXbocwlPyxrPbX6mY
|
||||||
|
0rVIvnuUeBH8X0L5IwfpNVvKnBIilTbcebfHyXkPezGwz7E1yhUULjJFm2bt0SdX
|
||||||
|
y+4X/WeiiYIv+fTVgZZgl+/2MKIsu/qdBJc3f3TvJ8nz+Eax1zgZmww+RSQWeOj3
|
||||||
|
15Iw6Z5FX+NwzY/Ab+9PosR5UosSeq+9HhtaxZttXG1nVh+avYPGYddWmiMT90J5
|
||||||
|
ZgKnO/Fx2hBgTxhOTMYaD312kg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
|
||||||
|
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
|
||||||
|
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
|
||||||
|
MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
|
||||||
|
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
|
||||||
|
RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
|
||||||
|
gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
|
||||||
|
KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
|
||||||
|
QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
|
||||||
|
XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
|
||||||
|
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
|
||||||
|
LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
|
||||||
|
RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
|
||||||
|
jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
|
||||||
|
6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
|
||||||
|
mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
|
||||||
|
Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
|
||||||
|
WD9f
|
||||||
|
-----END CERTIFICATE-----`;
|
||||||
|
const TLSProfiles = {
|
||||||
|
RedisCloudFixed: { ca: RedisCloudCA },
|
||||||
|
RedisCloudFlexible: { ca: RedisCloudCA },
|
||||||
|
};
|
||||||
|
exports.default = TLSProfiles;
|
||||||
7
backend/node_modules/ioredis/built/errors/ClusterAllFailedError.d.ts
generated
vendored
Normal file
7
backend/node_modules/ioredis/built/errors/ClusterAllFailedError.d.ts
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { RedisError } from "redis-errors";
|
||||||
|
export default class ClusterAllFailedError extends RedisError {
|
||||||
|
lastNodeError: RedisError;
|
||||||
|
static defaultMessage: string;
|
||||||
|
constructor(message: any, lastNodeError: RedisError);
|
||||||
|
get name(): string;
|
||||||
|
}
|
||||||
15
backend/node_modules/ioredis/built/errors/ClusterAllFailedError.js
generated
vendored
Normal file
15
backend/node_modules/ioredis/built/errors/ClusterAllFailedError.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const redis_errors_1 = require("redis-errors");
|
||||||
|
class ClusterAllFailedError extends redis_errors_1.RedisError {
|
||||||
|
constructor(message, lastNodeError) {
|
||||||
|
super(message);
|
||||||
|
this.lastNodeError = lastNodeError;
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
}
|
||||||
|
get name() {
|
||||||
|
return this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = ClusterAllFailedError;
|
||||||
|
ClusterAllFailedError.defaultMessage = "Failed to refresh slots cache.";
|
||||||
5
backend/node_modules/ioredis/built/errors/MaxRetriesPerRequestError.d.ts
generated
vendored
Normal file
5
backend/node_modules/ioredis/built/errors/MaxRetriesPerRequestError.d.ts
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { AbortError } from "redis-errors";
|
||||||
|
export default class MaxRetriesPerRequestError extends AbortError {
|
||||||
|
constructor(maxRetriesPerRequest: number);
|
||||||
|
get name(): string;
|
||||||
|
}
|
||||||
14
backend/node_modules/ioredis/built/errors/MaxRetriesPerRequestError.js
generated
vendored
Normal file
14
backend/node_modules/ioredis/built/errors/MaxRetriesPerRequestError.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const redis_errors_1 = require("redis-errors");
|
||||||
|
class MaxRetriesPerRequestError extends redis_errors_1.AbortError {
|
||||||
|
constructor(maxRetriesPerRequest) {
|
||||||
|
const message = `Reached the max retries per request limit (which is ${maxRetriesPerRequest}). Refer to "maxRetriesPerRequest" option for details.`;
|
||||||
|
super(message);
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
}
|
||||||
|
get name() {
|
||||||
|
return this.constructor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.default = MaxRetriesPerRequestError;
|
||||||
2
backend/node_modules/ioredis/built/errors/index.d.ts
generated
vendored
Normal file
2
backend/node_modules/ioredis/built/errors/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import MaxRetriesPerRequestError from "./MaxRetriesPerRequestError";
|
||||||
|
export { MaxRetriesPerRequestError };
|
||||||
5
backend/node_modules/ioredis/built/errors/index.js
generated
vendored
Normal file
5
backend/node_modules/ioredis/built/errors/index.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.MaxRetriesPerRequestError = void 0;
|
||||||
|
const MaxRetriesPerRequestError_1 = require("./MaxRetriesPerRequestError");
|
||||||
|
exports.MaxRetriesPerRequestError = MaxRetriesPerRequestError_1.default;
|
||||||
43
backend/node_modules/ioredis/built/index.d.ts
generated
vendored
Normal file
43
backend/node_modules/ioredis/built/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export { default } from "./Redis";
|
||||||
|
export { default as Redis } from "./Redis";
|
||||||
|
export { default as Cluster } from "./cluster";
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export { default as Command } from "./Command";
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export { default as RedisCommander, Result, ClientContext, } from "./utils/RedisCommander";
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export { default as ScanStream } from "./ScanStream";
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export { default as Pipeline } from "./Pipeline";
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export { default as AbstractConnector } from "./connectors/AbstractConnector";
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export { default as SentinelConnector, SentinelIterator, } from "./connectors/SentinelConnector";
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export { Callback } from "./types";
|
||||||
|
export { SentinelAddress, SentinelConnectionOptions, } from "./connectors/SentinelConnector";
|
||||||
|
export { StandaloneConnectionOptions } from "./connectors/StandaloneConnector";
|
||||||
|
export { RedisOptions, CommonRedisOptions } from "./redis/RedisOptions";
|
||||||
|
export { ClusterNode } from "./cluster";
|
||||||
|
export { ClusterOptions, DNSLookupFunction, DNSResolveSrvFunction, NatMap, } from "./cluster/ClusterOptions";
|
||||||
|
export { NodeRole } from "./cluster/util";
|
||||||
|
export type { RedisKey, RedisValue, ChainableCommander, } from "./utils/RedisCommander";
|
||||||
|
export declare const ReplyError: any;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export declare function print(err: Error | null, reply?: any): void;
|
||||||
62
backend/node_modules/ioredis/built/index.js
generated
vendored
Normal file
62
backend/node_modules/ioredis/built/index.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.print = exports.ReplyError = exports.SentinelIterator = exports.SentinelConnector = exports.AbstractConnector = exports.Pipeline = exports.ScanStream = exports.Command = exports.Cluster = exports.Redis = exports.default = void 0;
|
||||||
|
exports = module.exports = require("./Redis").default;
|
||||||
|
var Redis_1 = require("./Redis");
|
||||||
|
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return Redis_1.default; } });
|
||||||
|
var Redis_2 = require("./Redis");
|
||||||
|
Object.defineProperty(exports, "Redis", { enumerable: true, get: function () { return Redis_2.default; } });
|
||||||
|
var cluster_1 = require("./cluster");
|
||||||
|
Object.defineProperty(exports, "Cluster", { enumerable: true, get: function () { return cluster_1.default; } });
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
var Command_1 = require("./Command");
|
||||||
|
Object.defineProperty(exports, "Command", { enumerable: true, get: function () { return Command_1.default; } });
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
var ScanStream_1 = require("./ScanStream");
|
||||||
|
Object.defineProperty(exports, "ScanStream", { enumerable: true, get: function () { return ScanStream_1.default; } });
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
var Pipeline_1 = require("./Pipeline");
|
||||||
|
Object.defineProperty(exports, "Pipeline", { enumerable: true, get: function () { return Pipeline_1.default; } });
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
var AbstractConnector_1 = require("./connectors/AbstractConnector");
|
||||||
|
Object.defineProperty(exports, "AbstractConnector", { enumerable: true, get: function () { return AbstractConnector_1.default; } });
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
var SentinelConnector_1 = require("./connectors/SentinelConnector");
|
||||||
|
Object.defineProperty(exports, "SentinelConnector", { enumerable: true, get: function () { return SentinelConnector_1.default; } });
|
||||||
|
Object.defineProperty(exports, "SentinelIterator", { enumerable: true, get: function () { return SentinelConnector_1.SentinelIterator; } });
|
||||||
|
// No TS typings
|
||||||
|
exports.ReplyError = require("redis-errors").ReplyError;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
Object.defineProperty(exports, "Promise", {
|
||||||
|
get() {
|
||||||
|
console.warn("ioredis v5 does not support plugging third-party Promise library anymore. Native Promise will be used.");
|
||||||
|
return Promise;
|
||||||
|
},
|
||||||
|
set(_lib) {
|
||||||
|
console.warn("ioredis v5 does not support plugging third-party Promise library anymore. Native Promise will be used.");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
function print(err, reply) {
|
||||||
|
if (err) {
|
||||||
|
console.log("Error: " + err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Reply: " + reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.print = print;
|
||||||
197
backend/node_modules/ioredis/built/redis/RedisOptions.d.ts
generated
vendored
Normal file
197
backend/node_modules/ioredis/built/redis/RedisOptions.d.ts
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import { CommanderOptions } from "../utils/Commander";
|
||||||
|
import ConnectorConstructor from "../connectors/ConnectorConstructor";
|
||||||
|
import { SentinelConnectionOptions } from "../connectors/SentinelConnector";
|
||||||
|
import { StandaloneConnectionOptions } from "../connectors/StandaloneConnector";
|
||||||
|
export declare type ReconnectOnError = (err: Error) => boolean | 1 | 2;
|
||||||
|
export interface CommonRedisOptions extends CommanderOptions {
|
||||||
|
Connector?: ConnectorConstructor | undefined;
|
||||||
|
retryStrategy?: ((times: number) => number | void | null) | undefined;
|
||||||
|
/**
|
||||||
|
* If a command does not return a reply within a set number of milliseconds,
|
||||||
|
* a "Command timed out" error will be thrown.
|
||||||
|
*/
|
||||||
|
commandTimeout?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Enables client-side timeout protection for blocking commands when set
|
||||||
|
* to a positive number. If `blockingTimeout` is undefined, `0`, or
|
||||||
|
* negative (e.g. `-1`), the protection is disabled and no client-side
|
||||||
|
* timers are installed for blocking commands.
|
||||||
|
*/
|
||||||
|
blockingTimeout?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Grace period (ms) added to blocking command timeouts. Only used when
|
||||||
|
* `blockingTimeout` is a positive number. Defaults to 100ms.
|
||||||
|
*/
|
||||||
|
blockingTimeoutGrace?: number | undefined;
|
||||||
|
/**
|
||||||
|
* If the socket does not receive data within a set number of milliseconds:
|
||||||
|
* 1. the socket is considered "dead" and will be destroyed
|
||||||
|
* 2. the client will reject any running commands (altought they might have been processed by the server)
|
||||||
|
* 3. the reconnect strategy will kick in (depending on the configuration)
|
||||||
|
*/
|
||||||
|
socketTimeout?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Enable/disable keep-alive functionality.
|
||||||
|
* @link https://nodejs.org/api/net.html#socketsetkeepaliveenable-initialdelay
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
keepAlive?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Enable/disable the use of Nagle's algorithm.
|
||||||
|
* @link https://nodejs.org/api/net.html#socketsetnodelaynodelay
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
noDelay?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Set the name of the connection to make it easier to identity the connection
|
||||||
|
* in client list.
|
||||||
|
* @link https://redis.io/commands/client-setname
|
||||||
|
*/
|
||||||
|
connectionName?: string | undefined;
|
||||||
|
/**
|
||||||
|
* If true, skips setting library info via CLIENT SETINFO.
|
||||||
|
* @link https://redis.io/docs/latest/commands/client-setinfo/
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
disableClientInfo?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Tag to append to the library name in CLIENT SETINFO (ioredis(tag)).
|
||||||
|
* @link https://redis.io/docs/latest/commands/client-setinfo/
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
clientInfoTag?: string | undefined;
|
||||||
|
/**
|
||||||
|
* If set, client will send AUTH command with the value of this option as the first argument when connected.
|
||||||
|
* This is supported since Redis 6.
|
||||||
|
*/
|
||||||
|
username?: string | undefined;
|
||||||
|
/**
|
||||||
|
* If set, client will send AUTH command with the value of this option when connected.
|
||||||
|
*/
|
||||||
|
password?: string | undefined;
|
||||||
|
/**
|
||||||
|
* Database index to use.
|
||||||
|
*
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
db?: number | undefined;
|
||||||
|
/**
|
||||||
|
* When the client reconnects, channels subscribed in the previous connection will be
|
||||||
|
* resubscribed automatically if `autoResubscribe` is `true`.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
autoResubscribe?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Whether or not to resend unfulfilled commands on reconnect.
|
||||||
|
* Unfulfilled commands are most likely to be blocking commands such as `brpop` or `blpop`.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
autoResendUnfulfilledCommands?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* Whether or not to reconnect on certain Redis errors.
|
||||||
|
* This options by default is `null`, which means it should never reconnect on Redis errors.
|
||||||
|
* You can pass a function that accepts an Redis error, and returns:
|
||||||
|
* - `true` or `1` to trigger a reconnection.
|
||||||
|
* - `false` or `0` to not reconnect.
|
||||||
|
* - `2` to reconnect and resend the failed command (who triggered the error) after reconnection.
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* const redis = new Redis({
|
||||||
|
* reconnectOnError(err) {
|
||||||
|
* const targetError = "READONLY";
|
||||||
|
* if (err.message.includes(targetError)) {
|
||||||
|
* // Only reconnect when the error contains "READONLY"
|
||||||
|
* return true; // or `return 1;`
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* @default null
|
||||||
|
*/
|
||||||
|
reconnectOnError?: ReconnectOnError | null | undefined;
|
||||||
|
/**
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
readOnly?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* When enabled, numbers returned by Redis will be converted to JavaScript strings instead of numbers.
|
||||||
|
* This is necessary if you want to handle big numbers (above `Number.MAX_SAFE_INTEGER` === 2^53).
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
stringNumbers?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* How long the client will wait before killing a socket due to inactivity during initial connection.
|
||||||
|
* @default 10000
|
||||||
|
*/
|
||||||
|
connectTimeout?: number | undefined;
|
||||||
|
/**
|
||||||
|
* This option is used internally when you call `redis.monitor()` to tell Redis
|
||||||
|
* to enter the monitor mode when the connection is established.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
monitor?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* The commands that don't get a reply due to the connection to the server is lost are
|
||||||
|
* put into a queue and will be resent on reconnect (if allowed by the `retryStrategy` option).
|
||||||
|
* This option is used to configure how many reconnection attempts should be allowed before
|
||||||
|
* the queue is flushed with a `MaxRetriesPerRequestError` error.
|
||||||
|
* Set this options to `null` instead of a number to let commands wait forever
|
||||||
|
* until the connection is alive again.
|
||||||
|
*
|
||||||
|
* @default 20
|
||||||
|
*/
|
||||||
|
maxRetriesPerRequest?: number | null | undefined;
|
||||||
|
/**
|
||||||
|
* @default 10000
|
||||||
|
*/
|
||||||
|
maxLoadingRetryTime?: number | undefined;
|
||||||
|
/**
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
enableAutoPipelining?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* @default []
|
||||||
|
*/
|
||||||
|
autoPipeliningIgnoredCommands?: string[] | undefined;
|
||||||
|
offlineQueue?: boolean | undefined;
|
||||||
|
commandQueue?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* By default, if the connection to Redis server has not been established, commands are added to a queue
|
||||||
|
* and are executed once the connection is "ready" (when `enableReadyCheck` is true, "ready" means
|
||||||
|
* the Redis server has loaded the database from disk, otherwise means the connection to the Redis
|
||||||
|
* server has been established). If this option is false, when execute the command when the connection
|
||||||
|
* isn't ready, an error will be returned.
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enableOfflineQueue?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* The client will sent an INFO command to check whether the server is still loading data from the disk (
|
||||||
|
* which happens when the server is just launched) when the connection is established, and only wait until
|
||||||
|
* the loading process is finished before emitting the `ready` event.
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enableReadyCheck?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* When a Redis instance is initialized, a connection to the server is immediately established. Set this to
|
||||||
|
* true will delay the connection to the server until the first command is sent or `redis.connect()` is called
|
||||||
|
* explicitly. When `redis.connect()` is called explicitly, a Promise is returned, which will be resolved
|
||||||
|
* when the connection is ready or rejected when it fails. The rejection should be handled by the user.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
lazyConnect?: boolean | undefined;
|
||||||
|
/**
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
scripts?: Record<string, {
|
||||||
|
lua: string;
|
||||||
|
numberOfKeys?: number | undefined;
|
||||||
|
readOnly?: boolean | undefined;
|
||||||
|
}> | undefined;
|
||||||
|
}
|
||||||
|
export declare type RedisOptions = CommonRedisOptions & SentinelConnectionOptions & StandaloneConnectionOptions;
|
||||||
|
export declare const DEFAULT_REDIS_OPTIONS: RedisOptions;
|
||||||
58
backend/node_modules/ioredis/built/redis/RedisOptions.js
generated
vendored
Normal file
58
backend/node_modules/ioredis/built/redis/RedisOptions.js
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.DEFAULT_REDIS_OPTIONS = void 0;
|
||||||
|
exports.DEFAULT_REDIS_OPTIONS = {
|
||||||
|
// Connection
|
||||||
|
port: 6379,
|
||||||
|
host: "localhost",
|
||||||
|
family: 0,
|
||||||
|
connectTimeout: 10000,
|
||||||
|
disconnectTimeout: 2000,
|
||||||
|
retryStrategy: function (times) {
|
||||||
|
return Math.min(times * 50, 2000);
|
||||||
|
},
|
||||||
|
keepAlive: 0,
|
||||||
|
noDelay: true,
|
||||||
|
connectionName: null,
|
||||||
|
disableClientInfo: false,
|
||||||
|
clientInfoTag: undefined,
|
||||||
|
// Sentinel
|
||||||
|
sentinels: null,
|
||||||
|
name: null,
|
||||||
|
role: "master",
|
||||||
|
sentinelRetryStrategy: function (times) {
|
||||||
|
return Math.min(times * 10, 1000);
|
||||||
|
},
|
||||||
|
sentinelReconnectStrategy: function () {
|
||||||
|
// This strategy only applies when sentinels are used for detecting
|
||||||
|
// a failover, not during initial master resolution.
|
||||||
|
// The deployment can still function when some of the sentinels are down
|
||||||
|
// for a long period of time, so we may not want to attempt reconnection
|
||||||
|
// very often. Therefore the default interval is fairly long (1 minute).
|
||||||
|
return 60000;
|
||||||
|
},
|
||||||
|
natMap: null,
|
||||||
|
enableTLSForSentinelMode: false,
|
||||||
|
updateSentinels: true,
|
||||||
|
failoverDetector: false,
|
||||||
|
// Status
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
|
db: 0,
|
||||||
|
// Others
|
||||||
|
enableOfflineQueue: true,
|
||||||
|
enableReadyCheck: true,
|
||||||
|
autoResubscribe: true,
|
||||||
|
autoResendUnfulfilledCommands: true,
|
||||||
|
lazyConnect: false,
|
||||||
|
keyPrefix: "",
|
||||||
|
reconnectOnError: null,
|
||||||
|
readOnly: false,
|
||||||
|
stringNumbers: false,
|
||||||
|
maxRetriesPerRequest: 20,
|
||||||
|
maxLoadingRetryTime: 10000,
|
||||||
|
enableAutoPipelining: false,
|
||||||
|
autoPipeliningIgnoredCommands: [],
|
||||||
|
sentinelMaxConnections: 10,
|
||||||
|
blockingTimeoutGrace: 100,
|
||||||
|
};
|
||||||
4
backend/node_modules/ioredis/built/redis/event_handler.d.ts
generated
vendored
Normal file
4
backend/node_modules/ioredis/built/redis/event_handler.d.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export declare function connectHandler(self: any): () => void;
|
||||||
|
export declare function closeHandler(self: any): () => void;
|
||||||
|
export declare function errorHandler(self: any): (error: any) => void;
|
||||||
|
export declare function readyHandler(self: any): () => void;
|
||||||
315
backend/node_modules/ioredis/built/redis/event_handler.js
generated
vendored
Normal file
315
backend/node_modules/ioredis/built/redis/event_handler.js
generated
vendored
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.readyHandler = exports.errorHandler = exports.closeHandler = exports.connectHandler = void 0;
|
||||||
|
const redis_errors_1 = require("redis-errors");
|
||||||
|
const Command_1 = require("../Command");
|
||||||
|
const errors_1 = require("../errors");
|
||||||
|
const utils_1 = require("../utils");
|
||||||
|
const DataHandler_1 = require("../DataHandler");
|
||||||
|
const debug = (0, utils_1.Debug)("connection");
|
||||||
|
function connectHandler(self) {
|
||||||
|
return function () {
|
||||||
|
var _a;
|
||||||
|
self.setStatus("connect");
|
||||||
|
self.resetCommandQueue();
|
||||||
|
// AUTH command should be processed before any other commands
|
||||||
|
let flushed = false;
|
||||||
|
const { connectionEpoch } = self;
|
||||||
|
if (self.condition.auth) {
|
||||||
|
self.auth(self.condition.auth, function (err) {
|
||||||
|
if (connectionEpoch !== self.connectionEpoch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
if (err.message.indexOf("no password is set") !== -1) {
|
||||||
|
console.warn("[WARN] Redis server does not require a password, but a password was supplied.");
|
||||||
|
}
|
||||||
|
else if (err.message.indexOf("without any password configured for the default user") !== -1) {
|
||||||
|
console.warn("[WARN] This Redis server's `default` user does not require a password, but a password was supplied");
|
||||||
|
}
|
||||||
|
else if (err.message.indexOf("wrong number of arguments for 'auth' command") !== -1) {
|
||||||
|
console.warn(`[ERROR] The server returned "wrong number of arguments for 'auth' command". You are probably passing both username and password to Redis version 5 or below. You should only pass the 'password' option for Redis version 5 and under.`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
flushed = true;
|
||||||
|
self.recoverFromFatalError(err, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (self.condition.select) {
|
||||||
|
self.select(self.condition.select).catch((err) => {
|
||||||
|
// If the node is in cluster mode, select is disallowed.
|
||||||
|
// In this case, reconnect won't help.
|
||||||
|
self.silentEmit("error", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
No need to keep the reference of DataHandler here
|
||||||
|
because we don't need to do the cleanup.
|
||||||
|
`Stream#end()` will remove all listeners for us.
|
||||||
|
*/
|
||||||
|
new DataHandler_1.default(self, {
|
||||||
|
stringNumbers: self.options.stringNumbers,
|
||||||
|
});
|
||||||
|
const clientCommandPromises = [];
|
||||||
|
if (self.options.connectionName) {
|
||||||
|
debug("set the connection name [%s]", self.options.connectionName);
|
||||||
|
clientCommandPromises.push(self.client("setname", self.options.connectionName).catch(utils_1.noop));
|
||||||
|
}
|
||||||
|
if (!self.options.disableClientInfo) {
|
||||||
|
debug("set the client info");
|
||||||
|
clientCommandPromises.push((0, utils_1.getPackageMeta)()
|
||||||
|
.then((packageMeta) => {
|
||||||
|
return self
|
||||||
|
.client("SETINFO", "LIB-VER", packageMeta.version)
|
||||||
|
.catch(utils_1.noop);
|
||||||
|
})
|
||||||
|
.catch(utils_1.noop));
|
||||||
|
clientCommandPromises.push(self
|
||||||
|
.client("SETINFO", "LIB-NAME", ((_a = self.options) === null || _a === void 0 ? void 0 : _a.clientInfoTag)
|
||||||
|
? `ioredis(${self.options.clientInfoTag})`
|
||||||
|
: "ioredis")
|
||||||
|
.catch(utils_1.noop));
|
||||||
|
}
|
||||||
|
Promise.all(clientCommandPromises)
|
||||||
|
.catch(utils_1.noop)
|
||||||
|
.finally(() => {
|
||||||
|
if (!self.options.enableReadyCheck) {
|
||||||
|
exports.readyHandler(self)();
|
||||||
|
}
|
||||||
|
if (self.options.enableReadyCheck) {
|
||||||
|
self._readyCheck(function (err, info) {
|
||||||
|
if (connectionEpoch !== self.connectionEpoch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
if (!flushed) {
|
||||||
|
self.recoverFromFatalError(new Error("Ready check failed: " + err.message), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (self.connector.check(info)) {
|
||||||
|
exports.readyHandler(self)();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.disconnect(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.connectHandler = connectHandler;
|
||||||
|
function abortError(command) {
|
||||||
|
const err = new redis_errors_1.AbortError("Command aborted due to connection close");
|
||||||
|
err.command = {
|
||||||
|
name: command.name,
|
||||||
|
args: command.args,
|
||||||
|
};
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
// If a contiguous set of pipeline commands starts from index zero then they
|
||||||
|
// can be safely reattempted. If however we have a chain of pipelined commands
|
||||||
|
// starting at index 1 or more it means we received a partial response before
|
||||||
|
// the connection close and those pipelined commands must be aborted. For
|
||||||
|
// example, if the queue looks like this: [2, 3, 4, 0, 1, 2] then after
|
||||||
|
// aborting and purging we'll have a queue that looks like this: [0, 1, 2]
|
||||||
|
function abortIncompletePipelines(commandQueue) {
|
||||||
|
var _a;
|
||||||
|
let expectedIndex = 0;
|
||||||
|
for (let i = 0; i < commandQueue.length;) {
|
||||||
|
const command = (_a = commandQueue.peekAt(i)) === null || _a === void 0 ? void 0 : _a.command;
|
||||||
|
const pipelineIndex = command.pipelineIndex;
|
||||||
|
if (pipelineIndex === undefined || pipelineIndex === 0) {
|
||||||
|
expectedIndex = 0;
|
||||||
|
}
|
||||||
|
if (pipelineIndex !== undefined && pipelineIndex !== expectedIndex++) {
|
||||||
|
commandQueue.remove(i, 1);
|
||||||
|
command.reject(abortError(command));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If only a partial transaction result was received before connection close,
|
||||||
|
// we have to abort any transaction fragments that may have ended up in the
|
||||||
|
// offline queue
|
||||||
|
function abortTransactionFragments(commandQueue) {
|
||||||
|
var _a;
|
||||||
|
for (let i = 0; i < commandQueue.length;) {
|
||||||
|
const command = (_a = commandQueue.peekAt(i)) === null || _a === void 0 ? void 0 : _a.command;
|
||||||
|
if (command.name === "multi") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (command.name === "exec") {
|
||||||
|
commandQueue.remove(i, 1);
|
||||||
|
command.reject(abortError(command));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (command.inTransaction) {
|
||||||
|
commandQueue.remove(i, 1);
|
||||||
|
command.reject(abortError(command));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function closeHandler(self) {
|
||||||
|
return function () {
|
||||||
|
const prevStatus = self.status;
|
||||||
|
self.setStatus("close");
|
||||||
|
if (self.commandQueue.length) {
|
||||||
|
abortIncompletePipelines(self.commandQueue);
|
||||||
|
}
|
||||||
|
if (self.offlineQueue.length) {
|
||||||
|
abortTransactionFragments(self.offlineQueue);
|
||||||
|
}
|
||||||
|
if (prevStatus === "ready") {
|
||||||
|
if (!self.prevCondition) {
|
||||||
|
self.prevCondition = self.condition;
|
||||||
|
}
|
||||||
|
if (self.commandQueue.length) {
|
||||||
|
self.prevCommandQueue = self.commandQueue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.manuallyClosing) {
|
||||||
|
self.manuallyClosing = false;
|
||||||
|
debug("skip reconnecting since the connection is manually closed.");
|
||||||
|
return close();
|
||||||
|
}
|
||||||
|
if (typeof self.options.retryStrategy !== "function") {
|
||||||
|
debug("skip reconnecting because `retryStrategy` is not a function");
|
||||||
|
return close();
|
||||||
|
}
|
||||||
|
const retryDelay = self.options.retryStrategy(++self.retryAttempts);
|
||||||
|
if (typeof retryDelay !== "number") {
|
||||||
|
debug("skip reconnecting because `retryStrategy` doesn't return a number");
|
||||||
|
return close();
|
||||||
|
}
|
||||||
|
debug("reconnect in %sms", retryDelay);
|
||||||
|
self.setStatus("reconnecting", retryDelay);
|
||||||
|
self.reconnectTimeout = setTimeout(function () {
|
||||||
|
self.reconnectTimeout = null;
|
||||||
|
self.connect().catch(utils_1.noop);
|
||||||
|
}, retryDelay);
|
||||||
|
const { maxRetriesPerRequest } = self.options;
|
||||||
|
if (typeof maxRetriesPerRequest === "number") {
|
||||||
|
if (maxRetriesPerRequest < 0) {
|
||||||
|
debug("maxRetriesPerRequest is negative, ignoring...");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const remainder = self.retryAttempts % (maxRetriesPerRequest + 1);
|
||||||
|
if (remainder === 0) {
|
||||||
|
debug("reach maxRetriesPerRequest limitation, flushing command queue...");
|
||||||
|
self.flushQueue(new errors_1.MaxRetriesPerRequestError(maxRetriesPerRequest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function close() {
|
||||||
|
self.setStatus("end");
|
||||||
|
self.flushQueue(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.closeHandler = closeHandler;
|
||||||
|
function errorHandler(self) {
|
||||||
|
return function (error) {
|
||||||
|
debug("error: %s", error);
|
||||||
|
self.silentEmit("error", error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.errorHandler = errorHandler;
|
||||||
|
function readyHandler(self) {
|
||||||
|
return function () {
|
||||||
|
self.setStatus("ready");
|
||||||
|
self.retryAttempts = 0;
|
||||||
|
if (self.options.monitor) {
|
||||||
|
self.call("monitor").then(() => self.setStatus("monitoring"), (error) => self.emit("error", error));
|
||||||
|
const { sendCommand } = self;
|
||||||
|
self.sendCommand = function (command) {
|
||||||
|
if (Command_1.default.checkFlag("VALID_IN_MONITOR_MODE", command.name)) {
|
||||||
|
return sendCommand.call(self, command);
|
||||||
|
}
|
||||||
|
command.reject(new Error("Connection is in monitoring mode, can't process commands."));
|
||||||
|
return command.promise;
|
||||||
|
};
|
||||||
|
self.once("close", function () {
|
||||||
|
delete self.sendCommand;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const finalSelect = self.prevCondition
|
||||||
|
? self.prevCondition.select
|
||||||
|
: self.condition.select;
|
||||||
|
if (self.options.readOnly) {
|
||||||
|
debug("set the connection to readonly mode");
|
||||||
|
self.readonly().catch(utils_1.noop);
|
||||||
|
}
|
||||||
|
if (self.prevCondition) {
|
||||||
|
const condition = self.prevCondition;
|
||||||
|
self.prevCondition = null;
|
||||||
|
if (condition.subscriber && self.options.autoResubscribe) {
|
||||||
|
// We re-select the previous db first since
|
||||||
|
// `SELECT` command is not valid in sub mode.
|
||||||
|
if (self.condition.select !== finalSelect) {
|
||||||
|
debug("connect to db [%d]", finalSelect);
|
||||||
|
self.select(finalSelect);
|
||||||
|
}
|
||||||
|
const subscribeChannels = condition.subscriber.channels("subscribe");
|
||||||
|
if (subscribeChannels.length) {
|
||||||
|
debug("subscribe %d channels", subscribeChannels.length);
|
||||||
|
self.subscribe(subscribeChannels);
|
||||||
|
}
|
||||||
|
const psubscribeChannels = condition.subscriber.channels("psubscribe");
|
||||||
|
if (psubscribeChannels.length) {
|
||||||
|
debug("psubscribe %d channels", psubscribeChannels.length);
|
||||||
|
self.psubscribe(psubscribeChannels);
|
||||||
|
}
|
||||||
|
const ssubscribeChannels = condition.subscriber.channels("ssubscribe");
|
||||||
|
if (ssubscribeChannels.length) {
|
||||||
|
debug("ssubscribe %s", ssubscribeChannels.length);
|
||||||
|
for (const channel of ssubscribeChannels) {
|
||||||
|
self.ssubscribe(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.prevCommandQueue) {
|
||||||
|
if (self.options.autoResendUnfulfilledCommands) {
|
||||||
|
debug("resend %d unfulfilled commands", self.prevCommandQueue.length);
|
||||||
|
while (self.prevCommandQueue.length > 0) {
|
||||||
|
const item = self.prevCommandQueue.shift();
|
||||||
|
if (item.select !== self.condition.select &&
|
||||||
|
item.command.name !== "select") {
|
||||||
|
self.select(item.select);
|
||||||
|
}
|
||||||
|
self.sendCommand(item.command, item.stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.prevCommandQueue = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.offlineQueue.length) {
|
||||||
|
debug("send %d commands in offline queue", self.offlineQueue.length);
|
||||||
|
const offlineQueue = self.offlineQueue;
|
||||||
|
self.resetOfflineQueue();
|
||||||
|
while (offlineQueue.length > 0) {
|
||||||
|
const item = offlineQueue.shift();
|
||||||
|
if (item.select !== self.condition.select &&
|
||||||
|
item.command.name !== "select") {
|
||||||
|
self.select(item.select);
|
||||||
|
}
|
||||||
|
self.sendCommand(item.command, item.stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.condition.select !== finalSelect) {
|
||||||
|
debug("connect to db [%d]", finalSelect);
|
||||||
|
self.select(finalSelect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.readyHandler = readyHandler;
|
||||||
13
backend/node_modules/ioredis/built/transaction.d.ts
generated
vendored
Normal file
13
backend/node_modules/ioredis/built/transaction.d.ts
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { ChainableCommander } from "./utils/RedisCommander";
|
||||||
|
export interface Transaction {
|
||||||
|
pipeline(commands?: unknown[][]): ChainableCommander;
|
||||||
|
multi(options: {
|
||||||
|
pipeline: false;
|
||||||
|
}): Promise<"OK">;
|
||||||
|
multi(): ChainableCommander;
|
||||||
|
multi(options: {
|
||||||
|
pipeline: true;
|
||||||
|
}): ChainableCommander;
|
||||||
|
multi(commands?: unknown[][]): ChainableCommander;
|
||||||
|
}
|
||||||
|
export declare function addTransactionSupport(redis: any): void;
|
||||||
93
backend/node_modules/ioredis/built/transaction.js
generated
vendored
Normal file
93
backend/node_modules/ioredis/built/transaction.js
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.addTransactionSupport = void 0;
|
||||||
|
const utils_1 = require("./utils");
|
||||||
|
const standard_as_callback_1 = require("standard-as-callback");
|
||||||
|
const Pipeline_1 = require("./Pipeline");
|
||||||
|
function addTransactionSupport(redis) {
|
||||||
|
redis.pipeline = function (commands) {
|
||||||
|
const pipeline = new Pipeline_1.default(this);
|
||||||
|
if (Array.isArray(commands)) {
|
||||||
|
pipeline.addBatch(commands);
|
||||||
|
}
|
||||||
|
return pipeline;
|
||||||
|
};
|
||||||
|
const { multi } = redis;
|
||||||
|
redis.multi = function (commands, options) {
|
||||||
|
if (typeof options === "undefined" && !Array.isArray(commands)) {
|
||||||
|
options = commands;
|
||||||
|
commands = null;
|
||||||
|
}
|
||||||
|
if (options && options.pipeline === false) {
|
||||||
|
return multi.call(this);
|
||||||
|
}
|
||||||
|
const pipeline = new Pipeline_1.default(this);
|
||||||
|
// @ts-expect-error
|
||||||
|
pipeline.multi();
|
||||||
|
if (Array.isArray(commands)) {
|
||||||
|
pipeline.addBatch(commands);
|
||||||
|
}
|
||||||
|
const exec = pipeline.exec;
|
||||||
|
pipeline.exec = function (callback) {
|
||||||
|
// Wait for the cluster to be connected, since we need nodes information before continuing
|
||||||
|
if (this.isCluster && !this.redis.slots.length) {
|
||||||
|
if (this.redis.status === "wait")
|
||||||
|
this.redis.connect().catch(utils_1.noop);
|
||||||
|
return (0, standard_as_callback_1.default)(new Promise((resolve, reject) => {
|
||||||
|
this.redis.delayUntilReady((err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.exec(pipeline).then(resolve, reject);
|
||||||
|
});
|
||||||
|
}), callback);
|
||||||
|
}
|
||||||
|
if (this._transactions > 0) {
|
||||||
|
exec.call(pipeline);
|
||||||
|
}
|
||||||
|
// Returns directly when the pipeline
|
||||||
|
// has been called multiple times (retries).
|
||||||
|
if (this.nodeifiedPromise) {
|
||||||
|
return exec.call(pipeline);
|
||||||
|
}
|
||||||
|
const promise = exec.call(pipeline);
|
||||||
|
return (0, standard_as_callback_1.default)(promise.then(function (result) {
|
||||||
|
const execResult = result[result.length - 1];
|
||||||
|
if (typeof execResult === "undefined") {
|
||||||
|
throw new Error("Pipeline cannot be used to send any commands when the `exec()` has been called on it.");
|
||||||
|
}
|
||||||
|
if (execResult[0]) {
|
||||||
|
execResult[0].previousErrors = [];
|
||||||
|
for (let i = 0; i < result.length - 1; ++i) {
|
||||||
|
if (result[i][0]) {
|
||||||
|
execResult[0].previousErrors.push(result[i][0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw execResult[0];
|
||||||
|
}
|
||||||
|
return (0, utils_1.wrapMultiResult)(execResult[1]);
|
||||||
|
}), callback);
|
||||||
|
};
|
||||||
|
// @ts-expect-error
|
||||||
|
const { execBuffer } = pipeline;
|
||||||
|
// @ts-expect-error
|
||||||
|
pipeline.execBuffer = function (callback) {
|
||||||
|
if (this._transactions > 0) {
|
||||||
|
execBuffer.call(pipeline);
|
||||||
|
}
|
||||||
|
return pipeline.exec(callback);
|
||||||
|
};
|
||||||
|
return pipeline;
|
||||||
|
};
|
||||||
|
const { exec } = redis;
|
||||||
|
redis.exec = function (callback) {
|
||||||
|
return (0, standard_as_callback_1.default)(exec.call(this).then(function (results) {
|
||||||
|
if (Array.isArray(results)) {
|
||||||
|
results = (0, utils_1.wrapMultiResult)(results);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}), callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.addTransactionSupport = addTransactionSupport;
|
||||||
33
backend/node_modules/ioredis/built/types.d.ts
generated
vendored
Normal file
33
backend/node_modules/ioredis/built/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { Socket } from "net";
|
||||||
|
import { TLSSocket } from "tls";
|
||||||
|
export declare type Callback<T = any> = (err?: Error | null, result?: T) => void;
|
||||||
|
export declare type NetStream = Socket | TLSSocket;
|
||||||
|
export declare type CommandParameter = string | Buffer | number | any[];
|
||||||
|
export interface Respondable {
|
||||||
|
name: string;
|
||||||
|
args: CommandParameter[];
|
||||||
|
resolve(result: any): void;
|
||||||
|
reject(error: Error): void;
|
||||||
|
}
|
||||||
|
export interface PipelineWriteableStream {
|
||||||
|
isPipeline: true;
|
||||||
|
write(data: string | Buffer): unknown;
|
||||||
|
destination: {
|
||||||
|
redis: {
|
||||||
|
stream: NetStream;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export declare type WriteableStream = NetStream | PipelineWriteableStream;
|
||||||
|
export interface CommandItem {
|
||||||
|
command: Respondable;
|
||||||
|
stream: WriteableStream;
|
||||||
|
select: number;
|
||||||
|
}
|
||||||
|
export interface ScanStreamOptions {
|
||||||
|
match?: string;
|
||||||
|
type?: string;
|
||||||
|
count?: number;
|
||||||
|
noValues?: boolean;
|
||||||
|
}
|
||||||
2
backend/node_modules/ioredis/built/types.js
generated
vendored
Normal file
2
backend/node_modules/ioredis/built/types.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
50
backend/node_modules/ioredis/built/utils/Commander.d.ts
generated
vendored
Normal file
50
backend/node_modules/ioredis/built/utils/Commander.d.ts
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import Command from "../Command";
|
||||||
|
import { WriteableStream } from "../types";
|
||||||
|
import RedisCommander, { ClientContext } from "./RedisCommander";
|
||||||
|
export interface CommanderOptions {
|
||||||
|
keyPrefix?: string | undefined;
|
||||||
|
showFriendlyErrorStack?: boolean | undefined;
|
||||||
|
}
|
||||||
|
declare class Commander<Context extends ClientContext = {
|
||||||
|
type: "default";
|
||||||
|
}> {
|
||||||
|
options: CommanderOptions;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
scriptsSet: {};
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
addedBuiltinSet: Set<string>;
|
||||||
|
/**
|
||||||
|
* Return supported builtin commands
|
||||||
|
*/
|
||||||
|
getBuiltinCommands(): string[];
|
||||||
|
/**
|
||||||
|
* Create a builtin command
|
||||||
|
*/
|
||||||
|
createBuiltinCommand(commandName: string): {
|
||||||
|
string: any;
|
||||||
|
buffer: any;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Create add builtin command
|
||||||
|
*/
|
||||||
|
addBuiltinCommand(commandName: string): void;
|
||||||
|
/**
|
||||||
|
* Define a custom command using lua script
|
||||||
|
*/
|
||||||
|
defineCommand(name: string, definition: {
|
||||||
|
lua: string;
|
||||||
|
numberOfKeys?: number;
|
||||||
|
readOnly?: boolean;
|
||||||
|
}): void;
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
sendCommand(command: Command, stream?: WriteableStream, node?: unknown): unknown;
|
||||||
|
}
|
||||||
|
interface Commander<Context> extends RedisCommander<Context> {
|
||||||
|
}
|
||||||
|
export default Commander;
|
||||||
117
backend/node_modules/ioredis/built/utils/Commander.js
generated
vendored
Normal file
117
backend/node_modules/ioredis/built/utils/Commander.js
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const commands_1 = require("@ioredis/commands");
|
||||||
|
const autoPipelining_1 = require("../autoPipelining");
|
||||||
|
const Command_1 = require("../Command");
|
||||||
|
const Script_1 = require("../Script");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
class Commander {
|
||||||
|
constructor() {
|
||||||
|
this.options = {};
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
this.scriptsSet = {};
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
this.addedBuiltinSet = new Set();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return supported builtin commands
|
||||||
|
*/
|
||||||
|
getBuiltinCommands() {
|
||||||
|
return commands.slice(0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a builtin command
|
||||||
|
*/
|
||||||
|
createBuiltinCommand(commandName) {
|
||||||
|
return {
|
||||||
|
string: generateFunction(null, commandName, "utf8"),
|
||||||
|
buffer: generateFunction(null, commandName, null),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create add builtin command
|
||||||
|
*/
|
||||||
|
addBuiltinCommand(commandName) {
|
||||||
|
this.addedBuiltinSet.add(commandName);
|
||||||
|
this[commandName] = generateFunction(commandName, commandName, "utf8");
|
||||||
|
this[commandName + "Buffer"] = generateFunction(commandName + "Buffer", commandName, null);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Define a custom command using lua script
|
||||||
|
*/
|
||||||
|
defineCommand(name, definition) {
|
||||||
|
const script = new Script_1.default(definition.lua, definition.numberOfKeys, this.options.keyPrefix, definition.readOnly);
|
||||||
|
this.scriptsSet[name] = script;
|
||||||
|
this[name] = generateScriptingFunction(name, name, script, "utf8");
|
||||||
|
this[name + "Buffer"] = generateScriptingFunction(name + "Buffer", name, script, null);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
sendCommand(command, stream, node) {
|
||||||
|
throw new Error('"sendCommand" is not implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const commands = commands_1.list.filter((command) => command !== "monitor");
|
||||||
|
commands.push("sentinel");
|
||||||
|
commands.forEach(function (commandName) {
|
||||||
|
Commander.prototype[commandName] = generateFunction(commandName, commandName, "utf8");
|
||||||
|
Commander.prototype[commandName + "Buffer"] = generateFunction(commandName + "Buffer", commandName, null);
|
||||||
|
});
|
||||||
|
Commander.prototype.call = generateFunction("call", "utf8");
|
||||||
|
Commander.prototype.callBuffer = generateFunction("callBuffer", null);
|
||||||
|
// @ts-expect-error
|
||||||
|
Commander.prototype.send_command = Commander.prototype.call;
|
||||||
|
function generateFunction(functionName, _commandName, _encoding) {
|
||||||
|
if (typeof _encoding === "undefined") {
|
||||||
|
_encoding = _commandName;
|
||||||
|
_commandName = null;
|
||||||
|
}
|
||||||
|
return function (...args) {
|
||||||
|
const commandName = (_commandName || args.shift());
|
||||||
|
let callback = args[args.length - 1];
|
||||||
|
if (typeof callback === "function") {
|
||||||
|
args.pop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback = undefined;
|
||||||
|
}
|
||||||
|
const options = {
|
||||||
|
errorStack: this.options.showFriendlyErrorStack ? new Error() : undefined,
|
||||||
|
keyPrefix: this.options.keyPrefix,
|
||||||
|
replyEncoding: _encoding,
|
||||||
|
};
|
||||||
|
// No auto pipeline, use regular command sending
|
||||||
|
if (!(0, autoPipelining_1.shouldUseAutoPipelining)(this, functionName, commandName)) {
|
||||||
|
return this.sendCommand(
|
||||||
|
// @ts-expect-error
|
||||||
|
new Command_1.default(commandName, args, options, callback));
|
||||||
|
}
|
||||||
|
// Create a new pipeline and make sure it's scheduled
|
||||||
|
return (0, autoPipelining_1.executeWithAutoPipelining)(this, functionName, commandName,
|
||||||
|
// @ts-expect-error
|
||||||
|
args, callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function generateScriptingFunction(functionName, commandName, script, encoding) {
|
||||||
|
return function (...args) {
|
||||||
|
const callback = typeof args[args.length - 1] === "function" ? args.pop() : undefined;
|
||||||
|
const options = {
|
||||||
|
replyEncoding: encoding,
|
||||||
|
};
|
||||||
|
if (this.options.showFriendlyErrorStack) {
|
||||||
|
options.errorStack = new Error();
|
||||||
|
}
|
||||||
|
// No auto pipeline, use regular command sending
|
||||||
|
if (!(0, autoPipelining_1.shouldUseAutoPipelining)(this, functionName, commandName)) {
|
||||||
|
return script.execute(this, args, options, callback);
|
||||||
|
}
|
||||||
|
// Create a new pipeline and make sure it's scheduled
|
||||||
|
return (0, autoPipelining_1.executeWithAutoPipelining)(this, functionName, commandName, args, callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.default = Commander;
|
||||||
8950
backend/node_modules/ioredis/built/utils/RedisCommander.d.ts
generated
vendored
Normal file
8950
backend/node_modules/ioredis/built/utils/RedisCommander.d.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
backend/node_modules/ioredis/built/utils/RedisCommander.js
generated
vendored
Normal file
7
backend/node_modules/ioredis/built/utils/RedisCommander.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"use strict";
|
||||||
|
/**
|
||||||
|
* This file is generated by @ioredis/interface-generator.
|
||||||
|
* Don't edit it manually. Instead, run `npm run generate` to update
|
||||||
|
* this file.
|
||||||
|
*/
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
3
backend/node_modules/ioredis/built/utils/applyMixin.d.ts
generated
vendored
Normal file
3
backend/node_modules/ioredis/built/utils/applyMixin.d.ts
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare type Constructor = new (...args: any[]) => void;
|
||||||
|
declare function applyMixin(derivedConstructor: Constructor, mixinConstructor: Constructor): void;
|
||||||
|
export default applyMixin;
|
||||||
8
backend/node_modules/ioredis/built/utils/applyMixin.js
generated
vendored
Normal file
8
backend/node_modules/ioredis/built/utils/applyMixin.js
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
function applyMixin(derivedConstructor, mixinConstructor) {
|
||||||
|
Object.getOwnPropertyNames(mixinConstructor.prototype).forEach((name) => {
|
||||||
|
Object.defineProperty(derivedConstructor.prototype, name, Object.getOwnPropertyDescriptor(mixinConstructor.prototype, name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.default = applyMixin;
|
||||||
14
backend/node_modules/ioredis/built/utils/argumentParsers.d.ts
generated
vendored
Normal file
14
backend/node_modules/ioredis/built/utils/argumentParsers.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { CommandParameter } from "../types";
|
||||||
|
/**
|
||||||
|
* Parses a command parameter as seconds and converts to milliseconds.
|
||||||
|
* @param arg - The command parameter representing seconds
|
||||||
|
* @returns The value in milliseconds, 0 if value is <= 0, or undefined if parsing fails
|
||||||
|
*/
|
||||||
|
export declare const parseSecondsArgument: (arg: CommandParameter | undefined) => number | undefined;
|
||||||
|
/**
|
||||||
|
* Parses the BLOCK option from Redis command arguments (e.g., XREAD, XREADGROUP).
|
||||||
|
* @param args - Array of command parameters to search for the BLOCK option
|
||||||
|
* @returns The block duration in milliseconds, 0 if duration is <= 0,
|
||||||
|
* null if BLOCK option is not found, or undefined if BLOCK is found but duration is invalid
|
||||||
|
*/
|
||||||
|
export declare const parseBlockOption: (args: CommandParameter[]) => number | null | undefined;
|
||||||
74
backend/node_modules/ioredis/built/utils/argumentParsers.js
generated
vendored
Normal file
74
backend/node_modules/ioredis/built/utils/argumentParsers.js
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.parseBlockOption = exports.parseSecondsArgument = void 0;
|
||||||
|
/**
|
||||||
|
* Parses a command parameter to a number.
|
||||||
|
* @param arg - The command parameter to parse (number, string, or Buffer)
|
||||||
|
* @returns The parsed number, or undefined if parsing fails or arg is undefined
|
||||||
|
*/
|
||||||
|
const parseNumberArgument = (arg) => {
|
||||||
|
if (typeof arg === "number") {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
if (Buffer.isBuffer(arg)) {
|
||||||
|
return parseNumberArgument(arg.toString());
|
||||||
|
}
|
||||||
|
if (typeof arg === "string") {
|
||||||
|
const value = Number(arg);
|
||||||
|
return Number.isFinite(value) ? value : undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Parses a command parameter to a string.
|
||||||
|
* @param arg - The command parameter to parse (string or Buffer)
|
||||||
|
* @returns The parsed string, or undefined if arg is not a string/Buffer or is undefined
|
||||||
|
*/
|
||||||
|
const parseStringArgument = (arg) => {
|
||||||
|
if (typeof arg === "string") {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
if (Buffer.isBuffer(arg)) {
|
||||||
|
return arg.toString();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Parses a command parameter as seconds and converts to milliseconds.
|
||||||
|
* @param arg - The command parameter representing seconds
|
||||||
|
* @returns The value in milliseconds, 0 if value is <= 0, or undefined if parsing fails
|
||||||
|
*/
|
||||||
|
const parseSecondsArgument = (arg) => {
|
||||||
|
const value = parseNumberArgument(arg);
|
||||||
|
if (value === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (value <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return value * 1000;
|
||||||
|
};
|
||||||
|
exports.parseSecondsArgument = parseSecondsArgument;
|
||||||
|
/**
|
||||||
|
* Parses the BLOCK option from Redis command arguments (e.g., XREAD, XREADGROUP).
|
||||||
|
* @param args - Array of command parameters to search for the BLOCK option
|
||||||
|
* @returns The block duration in milliseconds, 0 if duration is <= 0,
|
||||||
|
* null if BLOCK option is not found, or undefined if BLOCK is found but duration is invalid
|
||||||
|
*/
|
||||||
|
const parseBlockOption = (args) => {
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const token = parseStringArgument(args[i]);
|
||||||
|
if (token && token.toLowerCase() === "block") {
|
||||||
|
const duration = parseNumberArgument(args[i + 1]);
|
||||||
|
if (duration === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (duration <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
exports.parseBlockOption = parseBlockOption;
|
||||||
16
backend/node_modules/ioredis/built/utils/debug.d.ts
generated
vendored
Normal file
16
backend/node_modules/ioredis/built/utils/debug.d.ts
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
declare const MAX_ARGUMENT_LENGTH = 200;
|
||||||
|
/**
|
||||||
|
* helper function that tried to get a string value for
|
||||||
|
* arbitrary "debug" arg
|
||||||
|
*/
|
||||||
|
declare function getStringValue(v: any): string | void;
|
||||||
|
/**
|
||||||
|
* helper function that redacts a string representation of a "debug" arg
|
||||||
|
*/
|
||||||
|
declare function genRedactedString(str: string, maxLen: number): string;
|
||||||
|
/**
|
||||||
|
* a wrapper for the `debug` module, used to generate
|
||||||
|
* "debug functions" that trim the values in their output
|
||||||
|
*/
|
||||||
|
export default function genDebugFunction(namespace: string): (...args: any[]) => void;
|
||||||
|
export { MAX_ARGUMENT_LENGTH, getStringValue, genRedactedString };
|
||||||
95
backend/node_modules/ioredis/built/utils/debug.js
generated
vendored
Normal file
95
backend/node_modules/ioredis/built/utils/debug.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.genRedactedString = exports.getStringValue = exports.MAX_ARGUMENT_LENGTH = void 0;
|
||||||
|
const debug_1 = require("debug");
|
||||||
|
const MAX_ARGUMENT_LENGTH = 200;
|
||||||
|
exports.MAX_ARGUMENT_LENGTH = MAX_ARGUMENT_LENGTH;
|
||||||
|
const NAMESPACE_PREFIX = "ioredis";
|
||||||
|
/**
|
||||||
|
* helper function that tried to get a string value for
|
||||||
|
* arbitrary "debug" arg
|
||||||
|
*/
|
||||||
|
function getStringValue(v) {
|
||||||
|
if (v === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (typeof v) {
|
||||||
|
case "boolean":
|
||||||
|
return;
|
||||||
|
case "number":
|
||||||
|
return;
|
||||||
|
case "object":
|
||||||
|
if (Buffer.isBuffer(v)) {
|
||||||
|
return v.toString("hex");
|
||||||
|
}
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return v.join(",");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(v);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "string":
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.getStringValue = getStringValue;
|
||||||
|
/**
|
||||||
|
* helper function that redacts a string representation of a "debug" arg
|
||||||
|
*/
|
||||||
|
function genRedactedString(str, maxLen) {
|
||||||
|
const { length } = str;
|
||||||
|
return length <= maxLen
|
||||||
|
? str
|
||||||
|
: str.slice(0, maxLen) + ' ... <REDACTED full-length="' + length + '">';
|
||||||
|
}
|
||||||
|
exports.genRedactedString = genRedactedString;
|
||||||
|
/**
|
||||||
|
* a wrapper for the `debug` module, used to generate
|
||||||
|
* "debug functions" that trim the values in their output
|
||||||
|
*/
|
||||||
|
function genDebugFunction(namespace) {
|
||||||
|
const fn = (0, debug_1.default)(`${NAMESPACE_PREFIX}:${namespace}`);
|
||||||
|
function wrappedDebug(...args) {
|
||||||
|
if (!fn.enabled) {
|
||||||
|
return; // no-op
|
||||||
|
}
|
||||||
|
// we skip the first arg because that is the message
|
||||||
|
for (let i = 1; i < args.length; i++) {
|
||||||
|
const str = getStringValue(args[i]);
|
||||||
|
if (typeof str === "string" && str.length > MAX_ARGUMENT_LENGTH) {
|
||||||
|
args[i] = genRedactedString(str, MAX_ARGUMENT_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fn.apply(null, args);
|
||||||
|
}
|
||||||
|
Object.defineProperties(wrappedDebug, {
|
||||||
|
namespace: {
|
||||||
|
get() {
|
||||||
|
return fn.namespace;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
get() {
|
||||||
|
return fn.enabled;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
destroy: {
|
||||||
|
get() {
|
||||||
|
return fn.destroy;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
log: {
|
||||||
|
get() {
|
||||||
|
return fn.log;
|
||||||
|
},
|
||||||
|
set(l) {
|
||||||
|
fn.log = l;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return wrappedDebug;
|
||||||
|
}
|
||||||
|
exports.default = genDebugFunction;
|
||||||
124
backend/node_modules/ioredis/built/utils/index.d.ts
generated
vendored
Normal file
124
backend/node_modules/ioredis/built/utils/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
import { defaults, noop } from "./lodash";
|
||||||
|
import { Callback } from "../types";
|
||||||
|
import Debug from "./debug";
|
||||||
|
/**
|
||||||
|
* Convert a buffer to string, supports buffer array
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* const input = [Buffer.from('foo'), [Buffer.from('bar')]]
|
||||||
|
* const res = convertBufferToString(input, 'utf8')
|
||||||
|
* expect(res).to.eql(['foo', ['bar']])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export declare function convertBufferToString(value: any, encoding?: BufferEncoding): any;
|
||||||
|
/**
|
||||||
|
* Convert a list of results to node-style
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* const input = ['a', 'b', new Error('c'), 'd']
|
||||||
|
* const output = exports.wrapMultiResult(input)
|
||||||
|
* expect(output).to.eql([[null, 'a'], [null, 'b'], [new Error('c')], [null, 'd'])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export declare function wrapMultiResult(arr: unknown[] | null): unknown[][] | null;
|
||||||
|
/**
|
||||||
|
* Detect if the argument is a int
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* > isInt('123')
|
||||||
|
* true
|
||||||
|
* > isInt('123.3')
|
||||||
|
* false
|
||||||
|
* > isInt('1x')
|
||||||
|
* false
|
||||||
|
* > isInt(123)
|
||||||
|
* true
|
||||||
|
* > isInt(true)
|
||||||
|
* false
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export declare function isInt(value: any): value is string;
|
||||||
|
/**
|
||||||
|
* Pack an array to an Object
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* > packObject(['a', 'b', 'c', 'd'])
|
||||||
|
* { a: 'b', c: 'd' }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export declare function packObject(array: any[]): Record<string, any>;
|
||||||
|
/**
|
||||||
|
* Return a callback with timeout
|
||||||
|
*/
|
||||||
|
export declare function timeout<T>(callback: Callback<T>, timeout: number): Callback<T>;
|
||||||
|
/**
|
||||||
|
* Convert an object to an array
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* > convertObjectToArray({ a: '1' })
|
||||||
|
* ['a', '1']
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export declare function convertObjectToArray<T>(obj: Record<string, T>): (string | T)[];
|
||||||
|
/**
|
||||||
|
* Convert a map to an array
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* > convertMapToArray(new Map([[1, '2']]))
|
||||||
|
* [1, '2']
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export declare function convertMapToArray<K, V>(map: Map<K, V>): (K | V)[];
|
||||||
|
/**
|
||||||
|
* Convert a non-string arg to a string
|
||||||
|
*/
|
||||||
|
export declare function toArg(arg: any): string;
|
||||||
|
/**
|
||||||
|
* Optimize error stack
|
||||||
|
*
|
||||||
|
* @param error actually error
|
||||||
|
* @param friendlyStack the stack that more meaningful
|
||||||
|
* @param filterPath only show stacks with the specified path
|
||||||
|
*/
|
||||||
|
export declare function optimizeErrorStack(error: Error, friendlyStack: string, filterPath: string): Error;
|
||||||
|
/**
|
||||||
|
* Parse the redis protocol url
|
||||||
|
*/
|
||||||
|
export declare function parseURL(url: string): Record<string, unknown>;
|
||||||
|
interface TLSOptions {
|
||||||
|
port: number;
|
||||||
|
host: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Resolve TLS profile shortcut in connection options
|
||||||
|
*/
|
||||||
|
export declare function resolveTLSProfile(options: TLSOptions): TLSOptions;
|
||||||
|
/**
|
||||||
|
* Get a random element from `array`
|
||||||
|
*/
|
||||||
|
export declare function sample<T>(array: T[], from?: number): T;
|
||||||
|
/**
|
||||||
|
* Shuffle the array using the Fisher-Yates Shuffle.
|
||||||
|
* This method will mutate the original array.
|
||||||
|
*/
|
||||||
|
export declare function shuffle<T>(array: T[]): T[];
|
||||||
|
/**
|
||||||
|
* Error message for connection being disconnected
|
||||||
|
*/
|
||||||
|
export declare const CONNECTION_CLOSED_ERROR_MSG = "Connection is closed.";
|
||||||
|
export declare function zipMap<K, V>(keys: K[], values: V[]): Map<K, V>;
|
||||||
|
/**
|
||||||
|
* Retrieves cached package metadata from package.json.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
* @returns {Promise<{version: string} | null>} Package metadata or null if unavailable
|
||||||
|
*/
|
||||||
|
export declare function getPackageMeta(): Promise<{
|
||||||
|
version: string;
|
||||||
|
}>;
|
||||||
|
export { Debug, defaults, noop };
|
||||||
332
backend/node_modules/ioredis/built/utils/index.js
generated
vendored
Normal file
332
backend/node_modules/ioredis/built/utils/index.js
generated
vendored
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.noop = exports.defaults = exports.Debug = exports.getPackageMeta = exports.zipMap = exports.CONNECTION_CLOSED_ERROR_MSG = exports.shuffle = exports.sample = exports.resolveTLSProfile = exports.parseURL = exports.optimizeErrorStack = exports.toArg = exports.convertMapToArray = exports.convertObjectToArray = exports.timeout = exports.packObject = exports.isInt = exports.wrapMultiResult = exports.convertBufferToString = void 0;
|
||||||
|
const fs_1 = require("fs");
|
||||||
|
const path_1 = require("path");
|
||||||
|
const url_1 = require("url");
|
||||||
|
const lodash_1 = require("./lodash");
|
||||||
|
Object.defineProperty(exports, "defaults", { enumerable: true, get: function () { return lodash_1.defaults; } });
|
||||||
|
Object.defineProperty(exports, "noop", { enumerable: true, get: function () { return lodash_1.noop; } });
|
||||||
|
const debug_1 = require("./debug");
|
||||||
|
exports.Debug = debug_1.default;
|
||||||
|
const TLSProfiles_1 = require("../constants/TLSProfiles");
|
||||||
|
/**
|
||||||
|
* Convert a buffer to string, supports buffer array
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* const input = [Buffer.from('foo'), [Buffer.from('bar')]]
|
||||||
|
* const res = convertBufferToString(input, 'utf8')
|
||||||
|
* expect(res).to.eql(['foo', ['bar']])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function convertBufferToString(value, encoding) {
|
||||||
|
if (value instanceof Buffer) {
|
||||||
|
return value.toString(encoding);
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const length = value.length;
|
||||||
|
const res = Array(length);
|
||||||
|
for (let i = 0; i < length; ++i) {
|
||||||
|
res[i] =
|
||||||
|
value[i] instanceof Buffer && encoding === "utf8"
|
||||||
|
? value[i].toString()
|
||||||
|
: convertBufferToString(value[i], encoding);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
exports.convertBufferToString = convertBufferToString;
|
||||||
|
/**
|
||||||
|
* Convert a list of results to node-style
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* const input = ['a', 'b', new Error('c'), 'd']
|
||||||
|
* const output = exports.wrapMultiResult(input)
|
||||||
|
* expect(output).to.eql([[null, 'a'], [null, 'b'], [new Error('c')], [null, 'd'])
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function wrapMultiResult(arr) {
|
||||||
|
// When using WATCH/EXEC transactions, the EXEC will return
|
||||||
|
// a null instead of an array
|
||||||
|
if (!arr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const result = [];
|
||||||
|
const length = arr.length;
|
||||||
|
for (let i = 0; i < length; ++i) {
|
||||||
|
const item = arr[i];
|
||||||
|
if (item instanceof Error) {
|
||||||
|
result.push([item]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.push([null, item]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
exports.wrapMultiResult = wrapMultiResult;
|
||||||
|
/**
|
||||||
|
* Detect if the argument is a int
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* > isInt('123')
|
||||||
|
* true
|
||||||
|
* > isInt('123.3')
|
||||||
|
* false
|
||||||
|
* > isInt('1x')
|
||||||
|
* false
|
||||||
|
* > isInt(123)
|
||||||
|
* true
|
||||||
|
* > isInt(true)
|
||||||
|
* false
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function isInt(value) {
|
||||||
|
const x = parseFloat(value);
|
||||||
|
return !isNaN(value) && (x | 0) === x;
|
||||||
|
}
|
||||||
|
exports.isInt = isInt;
|
||||||
|
/**
|
||||||
|
* Pack an array to an Object
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* > packObject(['a', 'b', 'c', 'd'])
|
||||||
|
* { a: 'b', c: 'd' }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function packObject(array) {
|
||||||
|
const result = {};
|
||||||
|
const length = array.length;
|
||||||
|
for (let i = 1; i < length; i += 2) {
|
||||||
|
result[array[i - 1]] = array[i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
exports.packObject = packObject;
|
||||||
|
/**
|
||||||
|
* Return a callback with timeout
|
||||||
|
*/
|
||||||
|
function timeout(callback, timeout) {
|
||||||
|
let timer = null;
|
||||||
|
const run = function () {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
callback.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
timer = setTimeout(run, timeout, new Error("timeout"));
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
exports.timeout = timeout;
|
||||||
|
/**
|
||||||
|
* Convert an object to an array
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* > convertObjectToArray({ a: '1' })
|
||||||
|
* ['a', '1']
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function convertObjectToArray(obj) {
|
||||||
|
const result = [];
|
||||||
|
const keys = Object.keys(obj); // Object.entries requires node 7+
|
||||||
|
for (let i = 0, l = keys.length; i < l; i++) {
|
||||||
|
result.push(keys[i], obj[keys[i]]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
exports.convertObjectToArray = convertObjectToArray;
|
||||||
|
/**
|
||||||
|
* Convert a map to an array
|
||||||
|
* @example
|
||||||
|
* ```js
|
||||||
|
* > convertMapToArray(new Map([[1, '2']]))
|
||||||
|
* [1, '2']
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function convertMapToArray(map) {
|
||||||
|
const result = [];
|
||||||
|
let pos = 0;
|
||||||
|
map.forEach(function (value, key) {
|
||||||
|
result[pos] = key;
|
||||||
|
result[pos + 1] = value;
|
||||||
|
pos += 2;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
exports.convertMapToArray = convertMapToArray;
|
||||||
|
/**
|
||||||
|
* Convert a non-string arg to a string
|
||||||
|
*/
|
||||||
|
function toArg(arg) {
|
||||||
|
if (arg === null || typeof arg === "undefined") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return String(arg);
|
||||||
|
}
|
||||||
|
exports.toArg = toArg;
|
||||||
|
/**
|
||||||
|
* Optimize error stack
|
||||||
|
*
|
||||||
|
* @param error actually error
|
||||||
|
* @param friendlyStack the stack that more meaningful
|
||||||
|
* @param filterPath only show stacks with the specified path
|
||||||
|
*/
|
||||||
|
function optimizeErrorStack(error, friendlyStack, filterPath) {
|
||||||
|
const stacks = friendlyStack.split("\n");
|
||||||
|
let lines = "";
|
||||||
|
let i;
|
||||||
|
for (i = 1; i < stacks.length; ++i) {
|
||||||
|
if (stacks[i].indexOf(filterPath) === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let j = i; j < stacks.length; ++j) {
|
||||||
|
lines += "\n" + stacks[j];
|
||||||
|
}
|
||||||
|
if (error.stack) {
|
||||||
|
const pos = error.stack.indexOf("\n");
|
||||||
|
error.stack = error.stack.slice(0, pos) + lines;
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
exports.optimizeErrorStack = optimizeErrorStack;
|
||||||
|
/**
|
||||||
|
* Parse the redis protocol url
|
||||||
|
*/
|
||||||
|
function parseURL(url) {
|
||||||
|
if (isInt(url)) {
|
||||||
|
return { port: url };
|
||||||
|
}
|
||||||
|
let parsed = (0, url_1.parse)(url, true, true);
|
||||||
|
if (!parsed.slashes && url[0] !== "/") {
|
||||||
|
url = "//" + url;
|
||||||
|
parsed = (0, url_1.parse)(url, true, true);
|
||||||
|
}
|
||||||
|
const options = parsed.query || {};
|
||||||
|
const result = {};
|
||||||
|
if (parsed.auth) {
|
||||||
|
const index = parsed.auth.indexOf(":");
|
||||||
|
result.username = index === -1 ? parsed.auth : parsed.auth.slice(0, index);
|
||||||
|
result.password = index === -1 ? "" : parsed.auth.slice(index + 1);
|
||||||
|
}
|
||||||
|
if (parsed.pathname) {
|
||||||
|
if (parsed.protocol === "redis:" || parsed.protocol === "rediss:") {
|
||||||
|
if (parsed.pathname.length > 1) {
|
||||||
|
result.db = parsed.pathname.slice(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.path = parsed.pathname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parsed.host) {
|
||||||
|
result.host = parsed.hostname;
|
||||||
|
}
|
||||||
|
if (parsed.port) {
|
||||||
|
result.port = parsed.port;
|
||||||
|
}
|
||||||
|
if (typeof options.family === "string") {
|
||||||
|
const intFamily = Number.parseInt(options.family, 10);
|
||||||
|
if (!Number.isNaN(intFamily)) {
|
||||||
|
result.family = intFamily;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(0, lodash_1.defaults)(result, options);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
exports.parseURL = parseURL;
|
||||||
|
/**
|
||||||
|
* Resolve TLS profile shortcut in connection options
|
||||||
|
*/
|
||||||
|
function resolveTLSProfile(options) {
|
||||||
|
let tls = options === null || options === void 0 ? void 0 : options.tls;
|
||||||
|
if (typeof tls === "string")
|
||||||
|
tls = { profile: tls };
|
||||||
|
const profile = TLSProfiles_1.default[tls === null || tls === void 0 ? void 0 : tls.profile];
|
||||||
|
if (profile) {
|
||||||
|
tls = Object.assign({}, profile, tls);
|
||||||
|
delete tls.profile;
|
||||||
|
options = Object.assign({}, options, { tls });
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
exports.resolveTLSProfile = resolveTLSProfile;
|
||||||
|
/**
|
||||||
|
* Get a random element from `array`
|
||||||
|
*/
|
||||||
|
function sample(array, from = 0) {
|
||||||
|
const length = array.length;
|
||||||
|
if (from >= length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return array[from + Math.floor(Math.random() * (length - from))];
|
||||||
|
}
|
||||||
|
exports.sample = sample;
|
||||||
|
/**
|
||||||
|
* Shuffle the array using the Fisher-Yates Shuffle.
|
||||||
|
* This method will mutate the original array.
|
||||||
|
*/
|
||||||
|
function shuffle(array) {
|
||||||
|
let counter = array.length;
|
||||||
|
// While there are elements in the array
|
||||||
|
while (counter > 0) {
|
||||||
|
// Pick a random index
|
||||||
|
const index = Math.floor(Math.random() * counter);
|
||||||
|
// Decrease counter by 1
|
||||||
|
counter--;
|
||||||
|
// And swap the last element with it
|
||||||
|
[array[counter], array[index]] = [array[index], array[counter]];
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
exports.shuffle = shuffle;
|
||||||
|
/**
|
||||||
|
* Error message for connection being disconnected
|
||||||
|
*/
|
||||||
|
exports.CONNECTION_CLOSED_ERROR_MSG = "Connection is closed.";
|
||||||
|
function zipMap(keys, values) {
|
||||||
|
const map = new Map();
|
||||||
|
keys.forEach((key, index) => {
|
||||||
|
map.set(key, values[index]);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
exports.zipMap = zipMap;
|
||||||
|
/**
|
||||||
|
* Memoized package metadata to avoid repeated file system reads.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
let cachedPackageMeta = null;
|
||||||
|
/**
|
||||||
|
* Retrieves cached package metadata from package.json.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
* @returns {Promise<{version: string} | null>} Package metadata or null if unavailable
|
||||||
|
*/
|
||||||
|
async function getPackageMeta() {
|
||||||
|
if (cachedPackageMeta) {
|
||||||
|
return cachedPackageMeta;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const filePath = (0, path_1.resolve)(__dirname, "..", "..", "package.json");
|
||||||
|
const data = await fs_1.promises.readFile(filePath, "utf8");
|
||||||
|
const parsed = JSON.parse(data);
|
||||||
|
cachedPackageMeta = {
|
||||||
|
version: parsed.version,
|
||||||
|
};
|
||||||
|
return cachedPackageMeta;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
cachedPackageMeta = {
|
||||||
|
version: "error-fetching-version",
|
||||||
|
};
|
||||||
|
return cachedPackageMeta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.getPackageMeta = getPackageMeta;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user