You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
WIP
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
name-template: 'Version $NEXT_PATCH_VERSION'
|
name-template: 'Version $NEXT_PATCH_VERSION'
|
||||||
tag-template: 'v$NEXT_PATCH_VERSION'
|
tag-template: 'v$NEXT_PATCH_VERSION'
|
||||||
|
include-path:
|
||||||
|
- {{ }}
|
||||||
autolabeler:
|
autolabeler:
|
||||||
- label: 'chore'
|
- label: 'chore'
|
||||||
files:
|
files:
|
20
benchmark/lib/ping/ioredis-auto-pipeline.js
Normal file
20
benchmark/lib/ping/ioredis-auto-pipeline.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import Redis from 'ioredis';
|
||||||
|
|
||||||
|
export default async (host) => {
|
||||||
|
const client = new Redis({
|
||||||
|
host,
|
||||||
|
lazyConnect: true,
|
||||||
|
enableAutoPipelining: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
benchmark() {
|
||||||
|
return client.ping();
|
||||||
|
},
|
||||||
|
teardown() {
|
||||||
|
return client.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
21
benchmark/lib/ping/local-resp2.js
Normal file
21
benchmark/lib/ping/local-resp2.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { createClient } from 'redis-local';
|
||||||
|
|
||||||
|
export default async (host) => {
|
||||||
|
const client = createClient({
|
||||||
|
socket: {
|
||||||
|
host
|
||||||
|
},
|
||||||
|
RESP: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
benchmark() {
|
||||||
|
return client.ping();
|
||||||
|
},
|
||||||
|
teardown() {
|
||||||
|
return client.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
27
benchmark/lib/ping/local-resp3-module-with-flags.js
Normal file
27
benchmark/lib/ping/local-resp3-module-with-flags.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { createClient } from 'redis-local';
|
||||||
|
import PING from 'redis-local/dist/lib/commands/PING.js';
|
||||||
|
|
||||||
|
export default async (host) => {
|
||||||
|
const client = createClient({
|
||||||
|
socket: {
|
||||||
|
host
|
||||||
|
},
|
||||||
|
RESP: 3,
|
||||||
|
modules: {
|
||||||
|
module: {
|
||||||
|
ping: PING.default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
benchmark() {
|
||||||
|
return client.withFlags({}).module.ping();
|
||||||
|
},
|
||||||
|
teardown() {
|
||||||
|
return client.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
27
benchmark/lib/ping/local-resp3-module.js
Normal file
27
benchmark/lib/ping/local-resp3-module.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { createClient } from 'redis-local';
|
||||||
|
import PING from 'redis-local/dist/lib/commands/PING.js';
|
||||||
|
|
||||||
|
export default async (host) => {
|
||||||
|
const client = createClient({
|
||||||
|
socket: {
|
||||||
|
host
|
||||||
|
},
|
||||||
|
RESP: 3,
|
||||||
|
modules: {
|
||||||
|
module: {
|
||||||
|
ping: PING.default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
benchmark() {
|
||||||
|
return client.module.ping();
|
||||||
|
},
|
||||||
|
teardown() {
|
||||||
|
return client.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
21
benchmark/lib/ping/local-resp3.js
Normal file
21
benchmark/lib/ping/local-resp3.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { createClient } from 'redis-local';
|
||||||
|
|
||||||
|
export default async (host) => {
|
||||||
|
const client = createClient({
|
||||||
|
socket: {
|
||||||
|
host
|
||||||
|
},
|
||||||
|
RESP: 3
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
benchmark() {
|
||||||
|
return client.ping();
|
||||||
|
},
|
||||||
|
teardown() {
|
||||||
|
return client.disconnect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
@@ -1,4 +1,4 @@
|
|||||||
import { createClient } from '@redis/client';
|
import { createClient } from 'redis-v4';
|
||||||
|
|
||||||
export default async (host) => {
|
export default async (host) => {
|
||||||
const client = createClient({
|
const client = createClient({
|
||||||
|
@@ -71,13 +71,13 @@ const benchmarkStart = process.hrtime.bigint(),
|
|||||||
histogram = await run(times),
|
histogram = await run(times),
|
||||||
benchmarkNanoseconds = process.hrtime.bigint() - benchmarkStart,
|
benchmarkNanoseconds = process.hrtime.bigint() - benchmarkStart,
|
||||||
json = {
|
json = {
|
||||||
timestamp,
|
// timestamp,
|
||||||
operationsPerSecond: times / Number(benchmarkNanoseconds) * 1_000_000_000,
|
operationsPerSecond: times / Number(benchmarkNanoseconds) * 1_000_000_000,
|
||||||
p0: histogram.getValueAtPercentile(0),
|
// p0: histogram.getValueAtPercentile(0),
|
||||||
p50: histogram.getValueAtPercentile(50),
|
// p50: histogram.getValueAtPercentile(50),
|
||||||
p95: histogram.getValueAtPercentile(95),
|
// p95: histogram.getValueAtPercentile(95),
|
||||||
p99: histogram.getValueAtPercentile(99),
|
// p99: histogram.getValueAtPercentile(99),
|
||||||
p100: histogram.getValueAtPercentile(100)
|
// p100: histogram.getValueAtPercentile(100)
|
||||||
};
|
};
|
||||||
console.log(`[${basename(path)}]:`);
|
console.log(`[${basename(path)}]:`);
|
||||||
console.table(json);
|
console.table(json);
|
||||||
|
448
benchmark/package-lock.json
generated
448
benchmark/package-lock.json
generated
@@ -1,31 +1,36 @@
|
|||||||
{
|
{
|
||||||
"name": "@redis/client-benchmark",
|
"name": "@redis/client-benchmark",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@redis/client-benchmark",
|
"name": "@redis/client-benchmark",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redis/client": "../packages/client",
|
|
||||||
"hdr-histogram-js": "3.0.0",
|
"hdr-histogram-js": "3.0.0",
|
||||||
"ioredis": "5.3.0",
|
"ioredis": "5",
|
||||||
"redis-v3": "npm:redis@3.1.2",
|
"redis-local": "file:../packages/client",
|
||||||
"yargs": "17.6.2"
|
"redis-v3": "npm:redis@3",
|
||||||
|
"redis-v4": "npm:redis@4",
|
||||||
|
"yargs": "17.7.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@assemblyscript/loader": {
|
"node_modules/@assemblyscript/loader": {
|
||||||
"version": "0.19.23",
|
"version": "0.19.23",
|
||||||
"resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.19.23.tgz",
|
"license": "Apache-2.0"
|
||||||
"integrity": "sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw=="
|
|
||||||
},
|
},
|
||||||
"node_modules/@ioredis/commands": {
|
"node_modules/@ioredis/commands": {
|
||||||
"version": "1.1.1",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.1.1.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-fsR4P/ROllzf/7lXYyElUJCheWdTJVJvOTps8v9IWKFATxR61ANOlnoPqhH099xYLrJGpc2ZQ28B3rMeUt5VQg=="
|
},
|
||||||
|
"node_modules/@redis/bloom": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@redis/client": {
|
"node_modules/@redis/client": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.6",
|
||||||
"resolved": "file:../packages/client",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cluster-key-slot": "1.1.2",
|
"cluster-key-slot": "1.1.2",
|
||||||
@@ -36,18 +41,44 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@redis/graph": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/json": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/search": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@redis/time-series": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@redis/client": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ansi-styles": {
|
"node_modules/ansi-styles": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
},
|
},
|
||||||
@@ -60,8 +91,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
|
||||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -75,12 +104,12 @@
|
|||||||
"type": "consulting",
|
"type": "consulting",
|
||||||
"url": "https://feross.org/support"
|
"url": "https://feross.org/support"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
"license": "ISC",
|
||||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"string-width": "^4.2.0",
|
"string-width": "^4.2.0",
|
||||||
"strip-ansi": "^6.0.1",
|
"strip-ansi": "^6.0.1",
|
||||||
@@ -92,16 +121,14 @@
|
|||||||
},
|
},
|
||||||
"node_modules/cluster-key-slot": {
|
"node_modules/cluster-key-slot": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
"license": "Apache-2.0",
|
||||||
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
},
|
},
|
||||||
@@ -111,13 +138,11 @@
|
|||||||
},
|
},
|
||||||
"node_modules/color-name": {
|
"node_modules/color-name": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
},
|
},
|
||||||
@@ -132,45 +157,39 @@
|
|||||||
},
|
},
|
||||||
"node_modules/denque": {
|
"node_modules/denque": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
"license": "Apache-2.0",
|
||||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
|
||||||
},
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/generic-pool": {
|
"node_modules/generic-pool": {
|
||||||
"version": "3.9.0",
|
"version": "3.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-caller-file": {
|
"node_modules/get-caller-file": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"license": "ISC",
|
||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hdr-histogram-js": {
|
"node_modules/hdr-histogram-js": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-3.0.0.tgz",
|
"license": "BSD",
|
||||||
"integrity": "sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@assemblyscript/loader": "^0.19.21",
|
"@assemblyscript/loader": "^0.19.21",
|
||||||
"base64-js": "^1.2.0",
|
"base64-js": "^1.2.0",
|
||||||
@@ -181,9 +200,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ioredis": {
|
"node_modules/ioredis": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-Id9jKHhsILuIZpHc61QkagfVdUj2Rag5GzG1TGEvRNeM7dtTOjICgjC+tvqYxi//PuX2wjQ+Xjva2ONBuf92Pw==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ioredis/commands": "^1.1.1",
|
"@ioredis/commands": "^1.1.1",
|
||||||
"cluster-key-slot": "^1.1.0",
|
"cluster-key-slot": "^1.1.0",
|
||||||
@@ -205,49 +223,55 @@
|
|||||||
},
|
},
|
||||||
"node_modules/is-fullwidth-code-point": {
|
"node_modules/is-fullwidth-code-point": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash.defaults": {
|
"node_modules/lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isarguments": {
|
"node_modules/lodash.isarguments": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo="
|
|
||||||
},
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
},
|
},
|
||||||
"node_modules/pako": {
|
"node_modules/pako": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
"license": "(MIT AND Zlib)"
|
||||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
|
||||||
},
|
},
|
||||||
"node_modules/redis-commands": {
|
"node_modules/redis-commands": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
|
|
||||||
},
|
},
|
||||||
"node_modules/redis-errors": {
|
"node_modules/redis-errors": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis-local": {
|
||||||
|
"name": "@redis/client",
|
||||||
|
"version": "1.5.6",
|
||||||
|
"resolved": "file:../packages/client",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cluster-key-slot": "1.1.2",
|
||||||
|
"generic-pool": "3.9.0",
|
||||||
|
"yallist": "4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/redis-parser": {
|
"node_modules/redis-parser": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"redis-errors": "^1.0.0"
|
"redis-errors": "^1.0.0"
|
||||||
},
|
},
|
||||||
@@ -258,8 +282,7 @@
|
|||||||
"node_modules/redis-v3": {
|
"node_modules/redis-v3": {
|
||||||
"name": "redis",
|
"name": "redis",
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"denque": "^1.5.0",
|
"denque": "^1.5.0",
|
||||||
"redis-commands": "^1.7.0",
|
"redis-commands": "^1.7.0",
|
||||||
@@ -276,29 +299,41 @@
|
|||||||
},
|
},
|
||||||
"node_modules/redis-v3/node_modules/denque": {
|
"node_modules/redis-v3/node_modules/denque": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
|
"license": "Apache-2.0",
|
||||||
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis-v4": {
|
||||||
|
"name": "redis",
|
||||||
|
"version": "4.6.5",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"./packages/*"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@redis/bloom": "1.2.0",
|
||||||
|
"@redis/client": "1.5.6",
|
||||||
|
"@redis/graph": "1.1.0",
|
||||||
|
"@redis/json": "1.0.4",
|
||||||
|
"@redis/search": "1.1.2",
|
||||||
|
"@redis/time-series": "1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/standard-as-callback": {
|
"node_modules/standard-as-callback": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
|
|
||||||
},
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
"is-fullwidth-code-point": "^3.0.0",
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
@@ -310,8 +345,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/strip-ansi": {
|
"node_modules/strip-ansi": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
},
|
},
|
||||||
@@ -321,8 +355,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
"string-width": "^4.1.0",
|
"string-width": "^4.1.0",
|
||||||
@@ -337,21 +370,18 @@
|
|||||||
},
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"license": "ISC",
|
||||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"license": "ISC"
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
|
||||||
},
|
},
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "17.6.2",
|
"version": "17.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cliui": "^8.0.1",
|
"cliui": "^8.0.1",
|
||||||
"escalade": "^3.1.1",
|
"escalade": "^3.1.1",
|
||||||
@@ -367,264 +397,10 @@
|
|||||||
},
|
},
|
||||||
"node_modules/yargs-parser": {
|
"node_modules/yargs-parser": {
|
||||||
"version": "21.1.1",
|
"version": "21.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
"license": "ISC",
|
||||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@assemblyscript/loader": {
|
|
||||||
"version": "0.19.23",
|
|
||||||
"resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.19.23.tgz",
|
|
||||||
"integrity": "sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw=="
|
|
||||||
},
|
|
||||||
"@ioredis/commands": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-fsR4P/ROllzf/7lXYyElUJCheWdTJVJvOTps8v9IWKFATxR61ANOlnoPqhH099xYLrJGpc2ZQ28B3rMeUt5VQg=="
|
|
||||||
},
|
|
||||||
"@redis/client": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"requires": {
|
|
||||||
"cluster-key-slot": "1.1.2",
|
|
||||||
"generic-pool": "3.9.0",
|
|
||||||
"yallist": "4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ansi-regex": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
|
||||||
},
|
|
||||||
"ansi-styles": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
|
||||||
"requires": {
|
|
||||||
"color-convert": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"base64-js": {
|
|
||||||
"version": "1.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
|
||||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
|
|
||||||
},
|
|
||||||
"cliui": {
|
|
||||||
"version": "8.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
|
||||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
|
||||||
"requires": {
|
|
||||||
"string-width": "^4.2.0",
|
|
||||||
"strip-ansi": "^6.0.1",
|
|
||||||
"wrap-ansi": "^7.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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=="
|
|
||||||
},
|
|
||||||
"color-convert": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
|
||||||
"requires": {
|
|
||||||
"color-name": "~1.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"color-name": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
|
||||||
},
|
|
||||||
"debug": {
|
|
||||||
"version": "4.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
|
||||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
|
||||||
"requires": {
|
|
||||||
"ms": "2.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"denque": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="
|
|
||||||
},
|
|
||||||
"emoji-regex": {
|
|
||||||
"version": "8.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
|
||||||
},
|
|
||||||
"escalade": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
|
|
||||||
},
|
|
||||||
"generic-pool": {
|
|
||||||
"version": "3.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
|
|
||||||
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="
|
|
||||||
},
|
|
||||||
"get-caller-file": {
|
|
||||||
"version": "2.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
|
||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
|
||||||
},
|
|
||||||
"hdr-histogram-js": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==",
|
|
||||||
"requires": {
|
|
||||||
"@assemblyscript/loader": "^0.19.21",
|
|
||||||
"base64-js": "^1.2.0",
|
|
||||||
"pako": "^1.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ioredis": {
|
|
||||||
"version": "5.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.0.tgz",
|
|
||||||
"integrity": "sha512-Id9jKHhsILuIZpHc61QkagfVdUj2Rag5GzG1TGEvRNeM7dtTOjICgjC+tvqYxi//PuX2wjQ+Xjva2ONBuf92Pw==",
|
|
||||||
"requires": {
|
|
||||||
"@ioredis/commands": "^1.1.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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-fullwidth-code-point": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
|
|
||||||
},
|
|
||||||
"lodash.defaults": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
|
||||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
|
||||||
},
|
|
||||||
"lodash.isarguments": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
|
||||||
"integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo="
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
|
||||||
},
|
|
||||||
"pako": {
|
|
||||||
"version": "1.0.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
|
||||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
|
||||||
},
|
|
||||||
"redis-commands": {
|
|
||||||
"version": "1.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
|
||||||
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
|
|
||||||
},
|
|
||||||
"redis-errors": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
|
||||||
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
|
|
||||||
},
|
|
||||||
"redis-parser": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
|
|
||||||
"requires": {
|
|
||||||
"redis-errors": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"redis-v3": {
|
|
||||||
"version": "npm:redis@3.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
|
|
||||||
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
|
|
||||||
"requires": {
|
|
||||||
"denque": "^1.5.0",
|
|
||||||
"redis-commands": "^1.7.0",
|
|
||||||
"redis-errors": "^1.2.0",
|
|
||||||
"redis-parser": "^3.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"denque": {
|
|
||||||
"version": "1.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
|
|
||||||
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"require-directory": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
|
||||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
|
||||||
},
|
|
||||||
"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=="
|
|
||||||
},
|
|
||||||
"string-width": {
|
|
||||||
"version": "4.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
|
||||||
"requires": {
|
|
||||||
"emoji-regex": "^8.0.0",
|
|
||||||
"is-fullwidth-code-point": "^3.0.0",
|
|
||||||
"strip-ansi": "^6.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"strip-ansi": {
|
|
||||||
"version": "6.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
|
||||||
"requires": {
|
|
||||||
"ansi-regex": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"wrap-ansi": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
|
||||||
"requires": {
|
|
||||||
"ansi-styles": "^4.0.0",
|
|
||||||
"string-width": "^4.1.0",
|
|
||||||
"strip-ansi": "^6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"y18n": {
|
|
||||||
"version": "5.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
|
||||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
|
||||||
},
|
|
||||||
"yallist": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
|
||||||
},
|
|
||||||
"yargs": {
|
|
||||||
"version": "17.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
|
|
||||||
"integrity": "sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==",
|
|
||||||
"requires": {
|
|
||||||
"cliui": "^8.0.1",
|
|
||||||
"escalade": "^3.1.1",
|
|
||||||
"get-caller-file": "^2.0.5",
|
|
||||||
"require-directory": "^2.1.1",
|
|
||||||
"string-width": "^4.2.3",
|
|
||||||
"y18n": "^5.0.5",
|
|
||||||
"yargs-parser": "^21.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"yargs-parser": {
|
|
||||||
"version": "21.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
|
||||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,10 +7,11 @@
|
|||||||
"start": "node ."
|
"start": "node ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redis/client": "../packages/client",
|
|
||||||
"hdr-histogram-js": "3.0.0",
|
"hdr-histogram-js": "3.0.0",
|
||||||
"ioredis": "5.3.0",
|
"ioredis": "5",
|
||||||
"redis-v3": "npm:redis@3.1.2",
|
"redis-v3": "npm:redis@3",
|
||||||
"yargs": "17.6.2"
|
"redis-v4": "npm:redis@4",
|
||||||
|
"redis-local": "file:../packages/client",
|
||||||
|
"yargs": "17.7.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
md/RESP2-to-RESP3.md
Normal file
1
md/RESP2-to-RESP3.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
https://docs.google.com/document/d/1Bg4jxEYiWnbCl4Oa2GCk5xaWde7HLa6GcNGg1sFwDAY/edit#
|
12
md/Redis-todo.md
Normal file
12
md/Redis-todo.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Missing functionality
|
||||||
|
|
||||||
|
- `HEXISTS`: accepts one field only, should be the same as `EXISTS`
|
||||||
|
|
||||||
|
# Replies
|
||||||
|
|
||||||
|
`String` -> `Double`:
|
||||||
|
- `INCRBYFLOAT`
|
||||||
|
- `HINCRBYFLOAT`
|
||||||
|
|
||||||
|
`Number` -> `Boolean`:
|
||||||
|
- `HSETNX` (deprecated)
|
4398
package-lock.json
generated
4398
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -33,8 +33,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node14": "^1.0.3",
|
"@tsconfig/node14": "^1.0.3",
|
||||||
"gh-pages": "^5.0.0",
|
"gh-pages": "^5.0.0",
|
||||||
"release-it": "^15.6.0",
|
"release-it": "^15.9.3",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^5.0.2"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
import { pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||||
import { RedisCommandArgument, RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
import { RedisCommandArgument, RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
@@ -39,7 +39,7 @@ export function transformArguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
args.push('ITEMS');
|
args.push('ITEMS');
|
||||||
return pushVerdictArguments(args, items);
|
return pushVariadicArguments(args, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { transformBooleanArrayReply as transformReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
export { transformBooleanArrayReply as transformReply } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
import { pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ export function transformArguments(
|
|||||||
key: string,
|
key: string,
|
||||||
items: string | Array<string>
|
items: string | Array<string>
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
return pushVerdictArguments(['CMS.QUERY', key], items);
|
return pushVariadicArguments(['CMS.QUERY', key], items);
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare function transformReply(): Array<number>;
|
export declare function transformReply(): Array<number>;
|
||||||
|
@@ -10,7 +10,7 @@ import * as INSERTNX from './INSERTNX';
|
|||||||
import * as LOADCHUNK from './LOADCHUNK';
|
import * as LOADCHUNK from './LOADCHUNK';
|
||||||
import * as RESERVE from './RESERVE';
|
import * as RESERVE from './RESERVE';
|
||||||
import * as SCANDUMP from './SCANDUMP';
|
import * as SCANDUMP from './SCANDUMP';
|
||||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
import { pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -58,5 +58,5 @@ export function pushInsertOptions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
args.push('ITEMS');
|
args.push('ITEMS');
|
||||||
return pushVerdictArguments(args, items);
|
return pushVariadicArguments(args, items);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
import { RedisCommandArgument, RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||||
import { pushVerdictArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
import { pushVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||||
import { CompressionOption, pushCompressionArgument } from '.';
|
import { CompressionOption, pushCompressionArgument } from '.';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
@@ -13,7 +13,7 @@ export function transformArguments(
|
|||||||
srcKeys: RedisCommandArgument | Array<RedisCommandArgument>,
|
srcKeys: RedisCommandArgument | Array<RedisCommandArgument>,
|
||||||
options?: MergeOptions
|
options?: MergeOptions
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
const args = pushVerdictArgument(
|
const args = pushVariadicArgument(
|
||||||
['TDIGEST.MERGE', destKey],
|
['TDIGEST.MERGE', destKey],
|
||||||
srcKeys
|
srcKeys
|
||||||
);
|
);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
import { pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ export function transformArguments(
|
|||||||
key: string,
|
key: string,
|
||||||
items: string | Array<string>
|
items: string | Array<string>
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
return pushVerdictArguments(['TOPK.ADD', key], items);
|
return pushVariadicArguments(['TOPK.ADD', key], items);
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare function transformReply(): Array<null | string>;
|
export declare function transformReply(): Array<null | string>;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
import { pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ export function transformArguments(
|
|||||||
key: string,
|
key: string,
|
||||||
items: string | Array<string>
|
items: string | Array<string>
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
return pushVerdictArguments(['TOPK.COUNT', key], items);
|
return pushVariadicArguments(['TOPK.COUNT', key], items);
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare function transformReply(): Array<number>;
|
export declare function transformReply(): Array<number>;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
import { RedisCommandArguments } from '@redis/client/dist/lib/commands';
|
||||||
import { pushVerdictArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
import { pushVariadicArguments } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ export function transformArguments(
|
|||||||
key: string,
|
key: string,
|
||||||
items: string | Array<string>
|
items: string | Array<string>
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
return pushVerdictArguments(['TOPK.QUERY', key], items);
|
return pushVariadicArguments(['TOPK.QUERY', key], items);
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare function transformReply(): Array<number>;
|
export declare function transformReply(): Array<number>;
|
||||||
|
@@ -18,12 +18,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||||
"@redis/test-utils": "*",
|
"@redis/test-utils": "*",
|
||||||
"@types/node": "^18.14.1",
|
"@types/node": "^18.15.10",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"release-it": "^15.6.0",
|
"release-it": "^15.9.3",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typedoc": "^0.23.25",
|
"typedoc": "^0.23.28",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^5.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"root": true,
|
"root": true,
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@typescript-eslint"
|
"@typescript-eslint"
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
"plugin:@typescript-eslint/recommended"
|
"plugin:@typescript-eslint/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"semi": [2, "always"]
|
"semi": [2, "always"]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@@ -1,24 +1,18 @@
|
|||||||
import RedisClient from './lib/client';
|
export { RedisModules, RedisFunctions, RedisScripts, RespVersions } from './lib/RESP/types';
|
||||||
import RedisCluster from './lib/cluster';
|
export { VerbatimString } from './lib/RESP/verbatim-string';
|
||||||
|
export { defineScript } from './lib/lua-script';
|
||||||
export { RedisClientType, RedisClientOptions } from './lib/client';
|
// export * from './lib/errors';
|
||||||
|
|
||||||
export { RedisModules, RedisFunctions, RedisScripts } from './lib/commands';
|
|
||||||
|
|
||||||
|
import RedisClient, { RedisClientType, RedisClientOptions } from './lib/client';
|
||||||
|
export { RedisClientType, RedisClientOptions };
|
||||||
export const createClient = RedisClient.create;
|
export const createClient = RedisClient.create;
|
||||||
|
|
||||||
export const commandOptions = RedisClient.commandOptions;
|
import RedisCluster, { RedisClusterType, RedisClusterOptions } from './lib/cluster';
|
||||||
|
export { RedisClusterType, RedisClusterOptions };
|
||||||
export { RedisClusterType, RedisClusterOptions } from './lib/cluster';
|
|
||||||
|
|
||||||
export const createCluster = RedisCluster.create;
|
export const createCluster = RedisCluster.create;
|
||||||
|
|
||||||
export { defineScript } from './lib/lua-script';
|
// export { GeoReplyWith } from './lib/commands/generic-transformers';
|
||||||
|
|
||||||
export * from './lib/errors';
|
// export { SetOptions } from './lib/commands/SET';
|
||||||
|
|
||||||
export { GeoReplyWith } from './lib/commands/generic-transformers';
|
// export { RedisFlushModes } from './lib/commands/FLUSHALL';
|
||||||
|
|
||||||
export { SetOptions } from './lib/commands/SET';
|
|
||||||
|
|
||||||
export { RedisFlushModes } from './lib/commands/FLUSHALL';
|
|
||||||
|
1043
packages/client/lib/RESP/decoder-ts.ts
Normal file
1043
packages/client/lib/RESP/decoder-ts.ts
Normal file
File diff suppressed because it is too large
Load Diff
1159
packages/client/lib/RESP/decoder.ts
Normal file
1159
packages/client/lib/RESP/decoder.ts
Normal file
File diff suppressed because it is too large
Load Diff
33
packages/client/lib/RESP/encoder.spec.ts
Normal file
33
packages/client/lib/RESP/encoder.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { strict as assert } from 'assert';
|
||||||
|
import { describe } from 'mocha';
|
||||||
|
import encodeCommand from './encoder';
|
||||||
|
|
||||||
|
describe('RESP Encoder', () => {
|
||||||
|
it('1 byte', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
encodeCommand(['a', 'z']),
|
||||||
|
['*2\r\n$1\r\na\r\n$1\r\nz\r\n']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('2 bytes', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
encodeCommand(['א', 'ת']),
|
||||||
|
['*2\r\n$2\r\nא\r\n$2\r\nת\r\n']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('4 bytes', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
[...encodeCommand(['🐣', '🐤'])],
|
||||||
|
['*2\r\n$4\r\n🐣\r\n$4\r\n🐤\r\n']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('buffer', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
encodeCommand([Buffer.from('string')]),
|
||||||
|
['*1\r\n$6\r\n', Buffer.from('string'), '\r\n']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
28
packages/client/lib/RESP/encoder.ts
Normal file
28
packages/client/lib/RESP/encoder.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { RedisArgument } from "./types";
|
||||||
|
|
||||||
|
const CRLF = '\r\n';
|
||||||
|
|
||||||
|
export default function encodeCommand(args: Array<RedisArgument>): Array<RedisArgument> {
|
||||||
|
const toWrite: Array<RedisArgument> = [];
|
||||||
|
|
||||||
|
let strings = '*' + args.length + CRLF;
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
if (typeof arg === 'string') {
|
||||||
|
strings += '$' + Buffer.byteLength(arg) + CRLF + arg + CRLF;
|
||||||
|
} else if (arg instanceof Buffer) {
|
||||||
|
toWrite.push(
|
||||||
|
strings + '$' + arg.length.toString() + CRLF,
|
||||||
|
arg
|
||||||
|
);
|
||||||
|
strings = CRLF;
|
||||||
|
} else {
|
||||||
|
throw new TypeError(`"arguments[${i}]" must be of type "string | Buffer", got ${typeof arg} instead.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toWrite.push(strings);
|
||||||
|
|
||||||
|
return toWrite;
|
||||||
|
}
|
417
packages/client/lib/RESP/types.ts
Normal file
417
packages/client/lib/RESP/types.ts
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
import { RedisScriptConfig, SHA1 } from '../lua-script';
|
||||||
|
import { TYPES } from './decoder';
|
||||||
|
import { VerbatimString } from './verbatim-string';
|
||||||
|
|
||||||
|
export type RespTypes = typeof TYPES;
|
||||||
|
|
||||||
|
export type RespTypesUnion = RespTypes[keyof RespTypes];
|
||||||
|
|
||||||
|
type RespType<
|
||||||
|
RESP_TYPE extends RespTypesUnion,
|
||||||
|
DEFAULT,
|
||||||
|
TYPES = never,
|
||||||
|
FLAG_TYPES = DEFAULT | TYPES
|
||||||
|
> = (DEFAULT | TYPES) & {
|
||||||
|
RESP_TYPE: RESP_TYPE;
|
||||||
|
DEFAULT: DEFAULT;
|
||||||
|
TYPES: TYPES;
|
||||||
|
FLAG: Flag<FLAG_TYPES>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NullReply = RespType<
|
||||||
|
RespTypes['NULL'],
|
||||||
|
null
|
||||||
|
>;
|
||||||
|
export type BooleanReply<
|
||||||
|
T extends boolean = boolean
|
||||||
|
> = RespType<
|
||||||
|
RespTypes['BOOLEAN'],
|
||||||
|
T
|
||||||
|
>;
|
||||||
|
export type NumberReply<
|
||||||
|
T extends number = number
|
||||||
|
> = RespType<
|
||||||
|
RespTypes['NUMBER'],
|
||||||
|
T,
|
||||||
|
`${T}`,
|
||||||
|
number | string
|
||||||
|
>;
|
||||||
|
export type BigNumberReply<
|
||||||
|
T extends bigint = bigint
|
||||||
|
> = RespType<
|
||||||
|
RespTypes['BIG_NUMBER'],
|
||||||
|
T,
|
||||||
|
number | `${T}`,
|
||||||
|
bigint | number | string
|
||||||
|
>;
|
||||||
|
export type DoubleReply<
|
||||||
|
T extends number = number
|
||||||
|
> = RespType<
|
||||||
|
RespTypes['DOUBLE'],
|
||||||
|
T,
|
||||||
|
`${T}`,
|
||||||
|
number | string
|
||||||
|
>;
|
||||||
|
export type SimpleStringReply<
|
||||||
|
T extends string = string
|
||||||
|
> = RespType<
|
||||||
|
RespTypes['SIMPLE_STRING'],
|
||||||
|
T,
|
||||||
|
Buffer,
|
||||||
|
string | Buffer
|
||||||
|
>;
|
||||||
|
export type BlobStringReply<
|
||||||
|
T extends string = string
|
||||||
|
> = RespType<
|
||||||
|
RespTypes['BLOB_STRING'],
|
||||||
|
T,
|
||||||
|
Buffer,
|
||||||
|
string | Buffer
|
||||||
|
>;
|
||||||
|
export type VerbatimStringReply<
|
||||||
|
T extends string = string
|
||||||
|
> = RespType<
|
||||||
|
RespTypes['VERBATIM_STRING'],
|
||||||
|
T,
|
||||||
|
Buffer | VerbatimString,
|
||||||
|
string | Buffer | VerbatimString
|
||||||
|
>;
|
||||||
|
export type SimpleErrorReply = RespType<
|
||||||
|
RespTypes['SIMPLE_ERROR'],
|
||||||
|
Buffer
|
||||||
|
>;
|
||||||
|
export type BlobErrorReply = RespType<
|
||||||
|
RespTypes['BLOB_ERROR'],
|
||||||
|
Buffer
|
||||||
|
>;
|
||||||
|
export type ArrayReply<T> = RespType<
|
||||||
|
RespTypes['ARRAY'],
|
||||||
|
Array<T>,
|
||||||
|
never,
|
||||||
|
Array<any>
|
||||||
|
>;
|
||||||
|
export type TuplesReply<T extends [...Array<unknown>]> = RespType<
|
||||||
|
RespTypes['ARRAY'],
|
||||||
|
T,
|
||||||
|
never,
|
||||||
|
Array<any>
|
||||||
|
>;
|
||||||
|
export type SetReply<T> = RespType<
|
||||||
|
RespTypes['SET'],
|
||||||
|
Array<T>,
|
||||||
|
Set<T>,
|
||||||
|
Array<any> | Set<any>
|
||||||
|
>;
|
||||||
|
export type MapReply<K, V> = RespType<
|
||||||
|
RespTypes['MAP'],
|
||||||
|
{ [key: string]: V },
|
||||||
|
Map<K, V> | Array<K | V>,
|
||||||
|
Map<any, any> | Array<any>
|
||||||
|
>;
|
||||||
|
|
||||||
|
type MapKeyValue = [key: BlobStringReply, value: unknown];
|
||||||
|
|
||||||
|
type MapTuples = Array<MapKeyValue>;
|
||||||
|
|
||||||
|
export type TuplesToMapReply<T extends MapTuples> = RespType<
|
||||||
|
RespTypes['MAP'],
|
||||||
|
{
|
||||||
|
[P in T[number] as P[0] extends BlobStringReply<infer S> ? S : never]: P[1];
|
||||||
|
},
|
||||||
|
Map<T[number][0], T[number][1]> | FlattenTuples<T>
|
||||||
|
>;
|
||||||
|
|
||||||
|
type FlattenTuples<T> = (
|
||||||
|
T extends [] ? [] :
|
||||||
|
T extends [MapKeyValue] ? T[0] :
|
||||||
|
T extends [MapKeyValue, ...infer R] ? [
|
||||||
|
...T[0],
|
||||||
|
...FlattenTuples<R>
|
||||||
|
] :
|
||||||
|
never
|
||||||
|
);
|
||||||
|
|
||||||
|
export type ReplyUnion = NullReply | BooleanReply | NumberReply | BigNumberReply | DoubleReply | SimpleStringReply | BlobStringReply | VerbatimStringReply | SimpleErrorReply | BlobErrorReply |
|
||||||
|
// cannot reuse ArrayReply, SetReply and MapReply because of circular reference
|
||||||
|
RespType<
|
||||||
|
RespTypes['ARRAY'],
|
||||||
|
Array<ReplyUnion>
|
||||||
|
> |
|
||||||
|
RespType<
|
||||||
|
RespTypes['SET'],
|
||||||
|
Array<ReplyUnion>,
|
||||||
|
Set<ReplyUnion>
|
||||||
|
> |
|
||||||
|
RespType<
|
||||||
|
RespTypes['MAP'],
|
||||||
|
{ [key: string]: ReplyUnion },
|
||||||
|
Map<ReplyUnion, ReplyUnion> | Array<ReplyUnion | ReplyUnion>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type Reply = ReplyWithFlags<ReplyUnion, {}>;
|
||||||
|
|
||||||
|
export type Flag<T> = ((...args: any) => T) | (new (...args: any) => T);
|
||||||
|
|
||||||
|
type RespTypeUnion<T> = T extends RespType<RespTypesUnion, unknown, unknown, infer FLAG_TYPES> ? FLAG_TYPES : never;
|
||||||
|
|
||||||
|
export type Flags = {
|
||||||
|
[P in RespTypesUnion]?: Flag<RespTypeUnion<Extract<ReplyUnion, RespType<P, any, any, any>>>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MapKey<
|
||||||
|
T,
|
||||||
|
FLAGS extends Flags
|
||||||
|
> = ReplyWithFlags<T, FLAGS & {
|
||||||
|
// simple and blob strings as map keys decoded as strings
|
||||||
|
[TYPES.SIMPLE_STRING]: StringConstructor;
|
||||||
|
[TYPES.BLOB_STRING]: StringConstructor;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ReplyWithFlags<
|
||||||
|
REPLY,
|
||||||
|
FLAGS extends Flags
|
||||||
|
> = (
|
||||||
|
// if REPLY is a type, extract the coresponding type from FLAGS or use the default type
|
||||||
|
REPLY extends RespType<infer RESP_TYPE, infer DEFAULT, infer TYPES, unknown> ?
|
||||||
|
FLAGS[RESP_TYPE] extends Flag<infer T> ?
|
||||||
|
ReplyWithFlags<Extract<DEFAULT | TYPES, T>, FLAGS> :
|
||||||
|
ReplyWithFlags<DEFAULT, FLAGS>
|
||||||
|
: (
|
||||||
|
// if REPLY is a known generic type, convert its generic arguments
|
||||||
|
// TODO: tuples?
|
||||||
|
REPLY extends Array<infer T> ? Array<ReplyWithFlags<T, FLAGS>> :
|
||||||
|
REPLY extends Set<infer T> ? Set<ReplyWithFlags<T, FLAGS>> :
|
||||||
|
REPLY extends Map<infer K, infer V> ? Map<MapKey<K, FLAGS>, ReplyWithFlags<V, FLAGS>> :
|
||||||
|
// `Date` & `Buffer` are supersets of `Record`, so they need to be checked first
|
||||||
|
REPLY extends Date ? REPLY :
|
||||||
|
REPLY extends Buffer ? REPLY :
|
||||||
|
REPLY extends Record<PropertyKey, any> ? {
|
||||||
|
[P in keyof REPLY]: ReplyWithFlags<REPLY[P], FLAGS>;
|
||||||
|
} :
|
||||||
|
// otherwise, just return the REPLY as is
|
||||||
|
REPLY
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export type TransformReply = (this: void, reply: any, preserve?: any) => any; // TODO;
|
||||||
|
|
||||||
|
export type RedisArgument = string | Buffer;
|
||||||
|
|
||||||
|
export type CommandArguments = Array<RedisArgument> & { preserve?: unknown };
|
||||||
|
|
||||||
|
export const REQUEST_POLICIES = {
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
ALL_NODES: 'all_nodes',
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
ALL_SHARDS: 'all_shards',
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
SPECIAL: 'special'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type REQUEST_POLICIES = typeof REQUEST_POLICIES;
|
||||||
|
|
||||||
|
export type RequestPolicies = REQUEST_POLICIES[keyof REQUEST_POLICIES];
|
||||||
|
|
||||||
|
export const RESPONSE_POLICIES = {
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
ONE_SUCCEEDED: 'one_succeeded',
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
ALL_SUCCEEDED: 'all_succeeded',
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
LOGICAL_AND: 'agg_logical_and',
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
SPECIAL: 'special'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type RESPONSE_POLICIES = typeof RESPONSE_POLICIES;
|
||||||
|
|
||||||
|
export type ResponsePolicies = RESPONSE_POLICIES[keyof RESPONSE_POLICIES];
|
||||||
|
|
||||||
|
export type CommandPolicies = {
|
||||||
|
request?: RequestPolicies | null;
|
||||||
|
response?: ResponsePolicies | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Command = {
|
||||||
|
FIRST_KEY_INDEX?: number | ((this: void, ...args: Array<any>) => RedisArgument | undefined);
|
||||||
|
IS_READ_ONLY?: boolean;
|
||||||
|
POLICIES?: CommandPolicies;
|
||||||
|
transformArguments(this: void, ...args: Array<any>): CommandArguments;
|
||||||
|
TRANSFORM_LEGACY_REPLY?: boolean;
|
||||||
|
transformReply: TransformReply | Record<RespVersions, TransformReply>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RedisCommands = Record<string, Command>;
|
||||||
|
|
||||||
|
export type RedisModules = Record<string, RedisCommands>;
|
||||||
|
|
||||||
|
export interface RedisFunction extends Command {
|
||||||
|
NUMBER_OF_KEYS?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RedisFunctions = Record<string, Record<string, RedisFunction>>;
|
||||||
|
|
||||||
|
export type RedisScript = RedisScriptConfig & SHA1;
|
||||||
|
|
||||||
|
export type RedisScripts = Record<string, RedisScript>;
|
||||||
|
|
||||||
|
// TODO: move to Commander?
|
||||||
|
export interface CommanderConfig<
|
||||||
|
M extends RedisModules,
|
||||||
|
F extends RedisFunctions,
|
||||||
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions
|
||||||
|
> {
|
||||||
|
modules?: M;
|
||||||
|
functions?: F;
|
||||||
|
scripts?: S;
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
RESP?: RESP;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resp2Array<T> = (
|
||||||
|
T extends [] ? [] :
|
||||||
|
T extends [infer ITEM] ? [Resp2Reply<ITEM>] :
|
||||||
|
T extends [infer ITEM, ...infer REST] ? [
|
||||||
|
Resp2Reply<ITEM>,
|
||||||
|
...Resp2Array<REST>
|
||||||
|
] :
|
||||||
|
T extends Array<infer ITEM> ? Array<Resp2Reply<ITEM>> :
|
||||||
|
never
|
||||||
|
);
|
||||||
|
|
||||||
|
export type Resp2Reply<RESP3REPLY> = (
|
||||||
|
RESP3REPLY extends RespType<infer RESP_TYPE, infer DEFAULT, infer TYPES, unknown> ?
|
||||||
|
// TODO: RESP3 only scalar types
|
||||||
|
RESP_TYPE extends RespTypes['DOUBLE'] ? BlobStringReply :
|
||||||
|
RESP_TYPE extends RespTypes['ARRAY'] | RespTypes['SET'] ? RespType<
|
||||||
|
RESP_TYPE,
|
||||||
|
Resp2Array<DEFAULT>
|
||||||
|
> :
|
||||||
|
RESP_TYPE extends RespTypes['MAP'] ? RespType<
|
||||||
|
RespTypes['ARRAY'],
|
||||||
|
Resp2Array<Extract<TYPES, Array<any>>>
|
||||||
|
> :
|
||||||
|
RespType<
|
||||||
|
RESP_TYPE,
|
||||||
|
DEFAULT,
|
||||||
|
TYPES
|
||||||
|
> :
|
||||||
|
RESP3REPLY
|
||||||
|
);
|
||||||
|
|
||||||
|
export type RespVersions = 2 | 3;
|
||||||
|
|
||||||
|
export type CommandReply<
|
||||||
|
COMMAND extends Command,
|
||||||
|
RESP extends RespVersions
|
||||||
|
> = (
|
||||||
|
// if transformReply is a function, use its return type
|
||||||
|
COMMAND['transformReply'] extends (...args: any) => infer T ? T :
|
||||||
|
// if transformReply[RESP] is a function, use its return type
|
||||||
|
COMMAND['transformReply'] extends Record<RESP, (...args: any) => infer T> ? T :
|
||||||
|
// otherwise use the generic reply type
|
||||||
|
Reply
|
||||||
|
);
|
||||||
|
|
||||||
|
export type CommandSignature<
|
||||||
|
COMMAND extends Command,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags
|
||||||
|
> = (...args: Parameters<COMMAND['transformArguments']>) => Promise<ReplyWithFlags<CommandReply<COMMAND, RESP>, FLAGS>>;
|
||||||
|
|
||||||
|
export type CommandWithPoliciesSignature<
|
||||||
|
COMMAND extends Command,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags,
|
||||||
|
POLICIES extends CommandPolicies
|
||||||
|
> = (...args: Parameters<COMMAND['transformArguments']>) => Promise<
|
||||||
|
ReplyWithPolicy<
|
||||||
|
ReplyWithFlags<CommandReply<COMMAND, RESP>, FLAGS>,
|
||||||
|
MergePolicies<COMMAND, POLICIES>
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type MergePolicies<
|
||||||
|
COMMAND extends Command,
|
||||||
|
POLICIES extends CommandPolicies
|
||||||
|
> = Omit<COMMAND['POLICIES'], keyof POLICIES> & POLICIES;
|
||||||
|
|
||||||
|
type ReplyWithPolicy<
|
||||||
|
REPLY,
|
||||||
|
POLICIES extends CommandPolicies,
|
||||||
|
> = (
|
||||||
|
POLICIES['request'] extends REQUEST_POLICIES['SPECIAL'] ? never :
|
||||||
|
POLICIES['request'] extends null | undefined ? REPLY :
|
||||||
|
unknown extends POLICIES['request'] ? REPLY :
|
||||||
|
POLICIES['response'] extends RESPONSE_POLICIES['SPECIAL'] ? never :
|
||||||
|
POLICIES['response'] extends RESPONSE_POLICIES['ALL_SUCCEEDED' | 'ONE_SUCCEEDED' | 'LOGICAL_AND'] ? REPLY :
|
||||||
|
// otherwise, return array of replies
|
||||||
|
Array<REPLY>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SAME = {
|
||||||
|
transformArguments(key: string): Array<string> {
|
||||||
|
return ['GET', key];
|
||||||
|
},
|
||||||
|
transformReply: () => 'default' as const
|
||||||
|
} satisfies Command;
|
||||||
|
|
||||||
|
type SAME_DEFAULT = CommandWithPoliciesSignature<
|
||||||
|
typeof SAME,
|
||||||
|
2,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
request: REQUEST_POLICIES['ALL_NODES'];
|
||||||
|
response: RESPONSE_POLICIES['SPECIAL'];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
// type SAME_RESP2 = CommandReply<typeof SAME, 2>;
|
||||||
|
// type SAME_COMMAND_RESP2 = CommandSignuture<typeof SAME, 2>;
|
||||||
|
// type SAME_RESP3 = CommandReply<typeof SAME, 3>;
|
||||||
|
// type SAME_COMMAND_RESP3 = CommandSignuture<typeof SAME, 3>;
|
||||||
|
|
||||||
|
// interface Test {
|
||||||
|
// /**
|
||||||
|
// * This is a test
|
||||||
|
// */
|
||||||
|
// a: 'a';
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const DIFFERENT = {
|
||||||
|
// transformArguments(key: string): Array<string> {
|
||||||
|
// return ['GET', key];
|
||||||
|
// },
|
||||||
|
// transformReply: {
|
||||||
|
// 2: () => null as any as Test,
|
||||||
|
// 3: () => '3' as const
|
||||||
|
// }
|
||||||
|
// } satisfies Command;
|
||||||
|
|
||||||
|
// type DIFFERENT_RESP2 = CommandReply<typeof DIFFERENT, 2>;
|
||||||
|
// type DIFFERENT_COMMAND_RESP2 = CommandSignuture<typeof DIFFERENT, 2>;
|
||||||
|
// type DIFFERENT_RESP3 = CommandReply<typeof DIFFERENT, 3>;
|
||||||
|
// type DIFFERENT_COMMAND_RESP3 = CommandSignuture<typeof DIFFERENT, 3>;
|
||||||
|
|
||||||
|
// const a = null as any as DIFFERENT_COMMAND_RESP2;
|
||||||
|
|
||||||
|
// const b = await a('a');
|
||||||
|
|
||||||
|
// b.a
|
8
packages/client/lib/RESP/verbatim-string.ts
Normal file
8
packages/client/lib/RESP/verbatim-string.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export class VerbatimString extends String {
|
||||||
|
constructor(
|
||||||
|
public format: string,
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,14 +0,0 @@
|
|||||||
import { strict as assert } from 'assert';
|
|
||||||
import BufferComposer from './buffer';
|
|
||||||
|
|
||||||
describe('Buffer Composer', () => {
|
|
||||||
const composer = new BufferComposer();
|
|
||||||
|
|
||||||
it('should compose two buffers', () => {
|
|
||||||
composer.write(Buffer.from([0]));
|
|
||||||
assert.deepEqual(
|
|
||||||
composer.end(Buffer.from([1])),
|
|
||||||
Buffer.from([0, 1])
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,18 +0,0 @@
|
|||||||
import { Composer } from './interface';
|
|
||||||
|
|
||||||
export default class BufferComposer implements Composer<Buffer> {
|
|
||||||
private chunks: Array<Buffer> = [];
|
|
||||||
|
|
||||||
write(buffer: Buffer): void {
|
|
||||||
this.chunks.push(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
end(buffer: Buffer): Buffer {
|
|
||||||
this.write(buffer);
|
|
||||||
return Buffer.concat(this.chunks.splice(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.chunks = [];
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
export interface Composer<T> {
|
|
||||||
write(buffer: Buffer): void;
|
|
||||||
|
|
||||||
end(buffer: Buffer): T;
|
|
||||||
|
|
||||||
reset(): void;
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
import { strict as assert } from 'assert';
|
|
||||||
import StringComposer from './string';
|
|
||||||
|
|
||||||
describe('String Composer', () => {
|
|
||||||
const composer = new StringComposer();
|
|
||||||
|
|
||||||
it('should compose two strings', () => {
|
|
||||||
composer.write(Buffer.from([0]));
|
|
||||||
assert.deepEqual(
|
|
||||||
composer.end(Buffer.from([1])),
|
|
||||||
Buffer.from([0, 1]).toString()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,22 +0,0 @@
|
|||||||
import { StringDecoder } from 'string_decoder';
|
|
||||||
import { Composer } from './interface';
|
|
||||||
|
|
||||||
export default class StringComposer implements Composer<string> {
|
|
||||||
private decoder = new StringDecoder();
|
|
||||||
|
|
||||||
private string = '';
|
|
||||||
|
|
||||||
write(buffer: Buffer): void {
|
|
||||||
this.string += this.decoder.write(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
end(buffer: Buffer): string {
|
|
||||||
const string = this.string + this.decoder.end(buffer);
|
|
||||||
this.string = '';
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.string = '';
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,257 +0,0 @@
|
|||||||
import { ErrorReply } from '../../errors';
|
|
||||||
import { Composer } from './composers/interface';
|
|
||||||
import BufferComposer from './composers/buffer';
|
|
||||||
import StringComposer from './composers/string';
|
|
||||||
|
|
||||||
// RESP2 specification
|
|
||||||
// https://redis.io/topics/protocol
|
|
||||||
|
|
||||||
enum Types {
|
|
||||||
SIMPLE_STRING = 43, // +
|
|
||||||
ERROR = 45, // -
|
|
||||||
INTEGER = 58, // :
|
|
||||||
BULK_STRING = 36, // $
|
|
||||||
ARRAY = 42 // *
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ASCII {
|
|
||||||
CR = 13, // \r
|
|
||||||
ZERO = 48,
|
|
||||||
MINUS = 45
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Reply = string | Buffer | ErrorReply | number | null | Array<Reply>;
|
|
||||||
|
|
||||||
type ArrayReply = Array<Reply> | null;
|
|
||||||
|
|
||||||
export type ReturnStringsAsBuffers = () => boolean;
|
|
||||||
|
|
||||||
interface RESP2Options {
|
|
||||||
returnStringsAsBuffers: ReturnStringsAsBuffers;
|
|
||||||
onReply(reply: Reply): unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArrayInProcess {
|
|
||||||
array: Array<Reply>;
|
|
||||||
pushCounter: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Using TypeScript `private` and not the build-in `#` to avoid __classPrivateFieldGet and __classPrivateFieldSet
|
|
||||||
|
|
||||||
export default class RESP2Decoder {
|
|
||||||
constructor(private options: RESP2Options) {}
|
|
||||||
|
|
||||||
private cursor = 0;
|
|
||||||
|
|
||||||
private type?: Types;
|
|
||||||
|
|
||||||
private bufferComposer = new BufferComposer();
|
|
||||||
|
|
||||||
private stringComposer = new StringComposer();
|
|
||||||
|
|
||||||
private currentStringComposer: BufferComposer | StringComposer = this.stringComposer;
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.cursor = 0;
|
|
||||||
this.type = undefined;
|
|
||||||
this.bufferComposer.reset();
|
|
||||||
this.stringComposer.reset();
|
|
||||||
this.currentStringComposer = this.stringComposer;
|
|
||||||
}
|
|
||||||
|
|
||||||
write(chunk: Buffer): void {
|
|
||||||
while (this.cursor < chunk.length) {
|
|
||||||
if (!this.type) {
|
|
||||||
this.currentStringComposer = this.options.returnStringsAsBuffers() ?
|
|
||||||
this.bufferComposer :
|
|
||||||
this.stringComposer;
|
|
||||||
|
|
||||||
this.type = chunk[this.cursor];
|
|
||||||
if (++this.cursor >= chunk.length) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reply = this.parseType(chunk, this.type);
|
|
||||||
if (reply === undefined) break;
|
|
||||||
|
|
||||||
this.type = undefined;
|
|
||||||
this.options.onReply(reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cursor -= chunk.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseType(chunk: Buffer, type: Types, arraysToKeep?: number): Reply | undefined {
|
|
||||||
switch (type) {
|
|
||||||
case Types.SIMPLE_STRING:
|
|
||||||
return this.parseSimpleString(chunk);
|
|
||||||
|
|
||||||
case Types.ERROR:
|
|
||||||
return this.parseError(chunk);
|
|
||||||
|
|
||||||
case Types.INTEGER:
|
|
||||||
return this.parseInteger(chunk);
|
|
||||||
|
|
||||||
case Types.BULK_STRING:
|
|
||||||
return this.parseBulkString(chunk);
|
|
||||||
|
|
||||||
case Types.ARRAY:
|
|
||||||
return this.parseArray(chunk, arraysToKeep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private compose<
|
|
||||||
C extends Composer<T>,
|
|
||||||
T = C extends Composer<infer TT> ? TT : never
|
|
||||||
>(
|
|
||||||
chunk: Buffer,
|
|
||||||
composer: C
|
|
||||||
): T | undefined {
|
|
||||||
for (let i = this.cursor; i < chunk.length; i++) {
|
|
||||||
if (chunk[i] === ASCII.CR) {
|
|
||||||
const reply = composer.end(
|
|
||||||
chunk.subarray(this.cursor, i)
|
|
||||||
);
|
|
||||||
this.cursor = i + 2;
|
|
||||||
return reply;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const toWrite = chunk.subarray(this.cursor);
|
|
||||||
composer.write(toWrite);
|
|
||||||
this.cursor = chunk.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseSimpleString(chunk: Buffer): string | Buffer | undefined {
|
|
||||||
return this.compose(chunk, this.currentStringComposer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseError(chunk: Buffer): ErrorReply | undefined {
|
|
||||||
const message = this.compose(chunk, this.stringComposer);
|
|
||||||
if (message !== undefined) {
|
|
||||||
return new ErrorReply(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private integer = 0;
|
|
||||||
|
|
||||||
private isNegativeInteger?: boolean;
|
|
||||||
|
|
||||||
private parseInteger(chunk: Buffer): number | undefined {
|
|
||||||
if (this.isNegativeInteger === undefined) {
|
|
||||||
this.isNegativeInteger = chunk[this.cursor] === ASCII.MINUS;
|
|
||||||
if (this.isNegativeInteger && ++this.cursor === chunk.length) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
const byte = chunk[this.cursor];
|
|
||||||
if (byte === ASCII.CR) {
|
|
||||||
const integer = this.isNegativeInteger ? -this.integer : this.integer;
|
|
||||||
this.integer = 0;
|
|
||||||
this.isNegativeInteger = undefined;
|
|
||||||
this.cursor += 2;
|
|
||||||
return integer;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.integer = this.integer * 10 + byte - ASCII.ZERO;
|
|
||||||
} while (++this.cursor < chunk.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bulkStringRemainingLength?: number;
|
|
||||||
|
|
||||||
private parseBulkString(chunk: Buffer): string | Buffer | null | undefined {
|
|
||||||
if (this.bulkStringRemainingLength === undefined) {
|
|
||||||
const length = this.parseInteger(chunk);
|
|
||||||
if (length === undefined) return;
|
|
||||||
if (length === -1) return null;
|
|
||||||
|
|
||||||
this.bulkStringRemainingLength = length;
|
|
||||||
|
|
||||||
if (this.cursor >= chunk.length) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = this.cursor + this.bulkStringRemainingLength;
|
|
||||||
if (chunk.length >= end) {
|
|
||||||
const reply = this.currentStringComposer.end(
|
|
||||||
chunk.subarray(this.cursor, end)
|
|
||||||
);
|
|
||||||
this.bulkStringRemainingLength = undefined;
|
|
||||||
this.cursor = end + 2;
|
|
||||||
return reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
const toWrite = chunk.subarray(this.cursor);
|
|
||||||
this.currentStringComposer.write(toWrite);
|
|
||||||
this.bulkStringRemainingLength -= toWrite.length;
|
|
||||||
this.cursor = chunk.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private arraysInProcess: Array<ArrayInProcess> = [];
|
|
||||||
|
|
||||||
private initializeArray = false;
|
|
||||||
|
|
||||||
private arrayItemType?: Types;
|
|
||||||
|
|
||||||
private parseArray(chunk: Buffer, arraysToKeep = 0): ArrayReply | undefined {
|
|
||||||
if (this.initializeArray || this.arraysInProcess.length === arraysToKeep) {
|
|
||||||
const length = this.parseInteger(chunk);
|
|
||||||
if (length === undefined) {
|
|
||||||
this.initializeArray = true;
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.initializeArray = false;
|
|
||||||
this.arrayItemType = undefined;
|
|
||||||
|
|
||||||
if (length === -1) {
|
|
||||||
return this.returnArrayReply(null, arraysToKeep, chunk);
|
|
||||||
} else if (length === 0) {
|
|
||||||
return this.returnArrayReply([], arraysToKeep, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.arraysInProcess.push({
|
|
||||||
array: new Array(length),
|
|
||||||
pushCounter: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
while (this.cursor < chunk.length) {
|
|
||||||
if (!this.arrayItemType) {
|
|
||||||
this.arrayItemType = chunk[this.cursor];
|
|
||||||
|
|
||||||
if (++this.cursor >= chunk.length) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = this.parseType(
|
|
||||||
chunk,
|
|
||||||
this.arrayItemType,
|
|
||||||
arraysToKeep + 1
|
|
||||||
);
|
|
||||||
if (item === undefined) break;
|
|
||||||
|
|
||||||
this.arrayItemType = undefined;
|
|
||||||
|
|
||||||
const reply = this.pushArrayItem(item, arraysToKeep);
|
|
||||||
if (reply !== undefined) return reply;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private returnArrayReply(reply: ArrayReply, arraysToKeep: number, chunk?: Buffer): ArrayReply | undefined {
|
|
||||||
if (this.arraysInProcess.length <= arraysToKeep) return reply;
|
|
||||||
|
|
||||||
return this.pushArrayItem(reply, arraysToKeep, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
private pushArrayItem(item: Reply, arraysToKeep: number, chunk?: Buffer): ArrayReply | undefined {
|
|
||||||
const to = this.arraysInProcess[this.arraysInProcess.length - 1]!;
|
|
||||||
to.array[to.pushCounter] = item;
|
|
||||||
if (++to.pushCounter === to.array.length) {
|
|
||||||
return this.returnArrayReply(
|
|
||||||
this.arraysInProcess.pop()!.array,
|
|
||||||
arraysToKeep,
|
|
||||||
chunk
|
|
||||||
);
|
|
||||||
} else if (chunk && chunk.length > this.cursor) {
|
|
||||||
return this.parseArray(chunk, arraysToKeep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,33 +0,0 @@
|
|||||||
import { strict as assert } from 'assert';
|
|
||||||
import { describe } from 'mocha';
|
|
||||||
import encodeCommand from './encoder';
|
|
||||||
|
|
||||||
describe('RESP2 Encoder', () => {
|
|
||||||
it('1 byte', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
encodeCommand(['a', 'z']),
|
|
||||||
['*2\r\n$1\r\na\r\n$1\r\nz\r\n']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('2 bytes', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
encodeCommand(['א', 'ת']),
|
|
||||||
['*2\r\n$2\r\nא\r\n$2\r\nת\r\n']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('4 bytes', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
[...encodeCommand(['🐣', '🐤'])],
|
|
||||||
['*2\r\n$4\r\n🐣\r\n$4\r\n🐤\r\n']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('buffer', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
encodeCommand([Buffer.from('string')]),
|
|
||||||
['*1\r\n$6\r\n', Buffer.from('string'), '\r\n']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,28 +0,0 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '../../commands';
|
|
||||||
|
|
||||||
const CRLF = '\r\n';
|
|
||||||
|
|
||||||
export default function encodeCommand(args: RedisCommandArguments): Array<RedisCommandArgument> {
|
|
||||||
const toWrite: Array<RedisCommandArgument> = [];
|
|
||||||
|
|
||||||
let strings = '*' + args.length + CRLF;
|
|
||||||
|
|
||||||
for (let i = 0; i < args.length; i++) {
|
|
||||||
const arg = args[i];
|
|
||||||
if (typeof arg === 'string') {
|
|
||||||
strings += '$' + Buffer.byteLength(arg) + CRLF + arg + CRLF;
|
|
||||||
} else if (arg instanceof Buffer) {
|
|
||||||
toWrite.push(
|
|
||||||
strings + '$' + arg.length.toString() + CRLF,
|
|
||||||
arg
|
|
||||||
);
|
|
||||||
strings = CRLF;
|
|
||||||
} else {
|
|
||||||
throw new TypeError('Invalid argument type');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toWrite.push(strings);
|
|
||||||
|
|
||||||
return toWrite;
|
|
||||||
}
|
|
@@ -1,263 +1,319 @@
|
|||||||
import * as LinkedList from 'yallist';
|
import * as LinkedList from 'yallist';
|
||||||
import { AbortError, ErrorReply } from '../errors';
|
import encodeCommand from '../RESP/encoder';
|
||||||
import { RedisCommandArguments, RedisCommandRawReply } from '../commands';
|
import { Decoder, PUSH_FLAGS, TYPES } from '../RESP/decoder';
|
||||||
import RESP2Decoder from './RESP2/decoder';
|
import { CommandArguments, Flags, ReplyUnion, RespVersions } from '../RESP/types';
|
||||||
import encodeCommand from './RESP2/encoder';
|
|
||||||
import { ChannelListeners, PubSub, PubSubCommand, PubSubListener, PubSubType, PubSubTypeListeners } from './pub-sub';
|
import { ChannelListeners, PubSub, PubSubCommand, PubSubListener, PubSubType, PubSubTypeListeners } from './pub-sub';
|
||||||
|
import { AbortError, ErrorReply } from '../errors';
|
||||||
|
import { EventEmitter } from 'stream';
|
||||||
|
|
||||||
export interface QueueCommandOptions {
|
export interface QueueCommandOptions {
|
||||||
asap?: boolean;
|
asap?: boolean;
|
||||||
chainId?: symbol;
|
chainId?: symbol;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
returnBuffers?: boolean;
|
flags?: Flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommandWaitingToBeSent extends CommandWaitingForReply {
|
export interface CommandWaitingToBeSent extends CommandWaitingForReply {
|
||||||
args: RedisCommandArguments;
|
args: CommandArguments;
|
||||||
chainId?: symbol;
|
chainId?: symbol;
|
||||||
abort?: {
|
removeAbortListener?(): void;
|
||||||
signal: AbortSignal;
|
|
||||||
listener(): void;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommandWaitingForReply {
|
interface CommandWaitingForReply {
|
||||||
resolve(reply?: unknown): void;
|
resolve(reply?: unknown): void;
|
||||||
reject(err: unknown): void;
|
reject(err: unknown): void;
|
||||||
channelsCounter?: number;
|
channelsCounter?: number;
|
||||||
returnBuffers?: boolean;
|
flags?: Flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PONG = Buffer.from('pong');
|
|
||||||
|
|
||||||
export type OnShardedChannelMoved = (channel: string, listeners: ChannelListeners) => void;
|
export type OnShardedChannelMoved = (channel: string, listeners: ChannelListeners) => void;
|
||||||
|
|
||||||
|
const PONG = Buffer.from('pong');
|
||||||
|
|
||||||
|
const RESP2_PUSH_FLAGS = {
|
||||||
|
...PUSH_FLAGS,
|
||||||
|
[TYPES.SIMPLE_STRING]: Buffer
|
||||||
|
};
|
||||||
|
|
||||||
export default class RedisCommandsQueue {
|
export default class RedisCommandsQueue {
|
||||||
static #flushQueue<T extends CommandWaitingForReply>(queue: LinkedList<T>, err: Error): void {
|
private readonly _maxLength: number | null | undefined;
|
||||||
while (queue.length) {
|
private readonly _waitingToBeSent = new LinkedList<CommandWaitingToBeSent>();
|
||||||
queue.shift()!.reject(err);
|
private readonly _waitingForReply = new LinkedList<CommandWaitingForReply>();
|
||||||
}
|
private readonly _onShardedChannelMoved: OnShardedChannelMoved;
|
||||||
|
|
||||||
|
private readonly _pubSub = new PubSub();
|
||||||
|
|
||||||
|
get isPubSubActive() {
|
||||||
|
return this._pubSub.isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _chainInExecution: symbol | undefined;
|
||||||
|
|
||||||
|
decoder: Decoder;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
respVersion: RespVersions | null | undefined,
|
||||||
|
maxLength: number | null | undefined,
|
||||||
|
onShardedChannelMoved: EventEmitter['emit']
|
||||||
|
) {
|
||||||
|
this.decoder = this._initiateDecoder(respVersion);
|
||||||
|
this._maxLength = maxLength;
|
||||||
|
this._onShardedChannelMoved = onShardedChannelMoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _initiateDecoder(respVersion: RespVersions | null | undefined) {
|
||||||
|
return respVersion === 3 ?
|
||||||
|
this._initiateResp3Decoder() :
|
||||||
|
this._initiateResp2Decoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onReply(reply: ReplyUnion) {
|
||||||
|
this._waitingForReply.shift()!.resolve(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onErrorReply(err: ErrorReply) {
|
||||||
|
this._waitingForReply.shift()!.reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onPush(push: Array<any>) {
|
||||||
|
// TODO: type
|
||||||
|
if (this._pubSub.handleMessageReply(push)) return true;
|
||||||
|
|
||||||
|
const isShardedUnsubscribe = PubSub.isShardedUnsubscribe(push);
|
||||||
|
if (isShardedUnsubscribe && !this._waitingForReply.length) {
|
||||||
|
const channel = push[1].toString();
|
||||||
|
this._onShardedChannelMoved(
|
||||||
|
channel,
|
||||||
|
this._pubSub.removeShardedListeners(channel)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} else if (isShardedUnsubscribe || PubSub.isStatusReply(push)) {
|
||||||
|
const head = this._waitingForReply.head!.value;
|
||||||
|
if (
|
||||||
|
(Number.isNaN(head.channelsCounter!) && push[2] === 0) ||
|
||||||
|
--head.channelsCounter! === 0
|
||||||
|
) {
|
||||||
|
this._waitingForReply.shift()!.resolve();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly #maxLength: number | null | undefined;
|
private _getFlags() {
|
||||||
readonly #waitingToBeSent = new LinkedList<CommandWaitingToBeSent>();
|
return this._waitingForReply.head!.value.flags ?? {};
|
||||||
readonly #waitingForReply = new LinkedList<CommandWaitingForReply>();
|
}
|
||||||
readonly #onShardedChannelMoved: OnShardedChannelMoved;
|
|
||||||
|
|
||||||
readonly #pubSub = new PubSub();
|
private _initiateResp3Decoder() {
|
||||||
|
return new Decoder({
|
||||||
|
onReply: reply => this._onReply(reply),
|
||||||
|
onErrorReply: err => this._onErrorReply(err),
|
||||||
|
onPush: push => {
|
||||||
|
if (!this._onPush(push)) {
|
||||||
|
|
||||||
get isPubSubActive() {
|
|
||||||
return this.#pubSub.isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
#chainInExecution: symbol | undefined;
|
|
||||||
|
|
||||||
#decoder = new RESP2Decoder({
|
|
||||||
returnStringsAsBuffers: () => {
|
|
||||||
return !!this.#waitingForReply.head?.value.returnBuffers ||
|
|
||||||
this.#pubSub.isActive;
|
|
||||||
},
|
|
||||||
onReply: reply => {
|
|
||||||
if (this.#pubSub.isActive && Array.isArray(reply)) {
|
|
||||||
if (this.#pubSub.handleMessageReply(reply as Array<Buffer>)) return;
|
|
||||||
|
|
||||||
const isShardedUnsubscribe = PubSub.isShardedUnsubscribe(reply as Array<Buffer>);
|
|
||||||
if (isShardedUnsubscribe && !this.#waitingForReply.length) {
|
|
||||||
const channel = (reply[1] as Buffer).toString();
|
|
||||||
this.#onShardedChannelMoved(
|
|
||||||
channel,
|
|
||||||
this.#pubSub.removeShardedListeners(channel)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else if (isShardedUnsubscribe || PubSub.isStatusReply(reply as Array<Buffer>)) {
|
|
||||||
const head = this.#waitingForReply.head!.value;
|
|
||||||
if (
|
|
||||||
(Number.isNaN(head.channelsCounter!) && reply[2] === 0) ||
|
|
||||||
--head.channelsCounter! === 0
|
|
||||||
) {
|
|
||||||
this.#waitingForReply.shift()!.resolve();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (PONG.equals(reply[0] as Buffer)) {
|
|
||||||
const { resolve, returnBuffers } = this.#waitingForReply.shift()!,
|
|
||||||
buffer = ((reply[1] as Buffer).length === 0 ? reply[0] : reply[1]) as Buffer;
|
|
||||||
resolve(returnBuffers ? buffer : buffer.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { resolve, reject } = this.#waitingForReply.shift()!;
|
|
||||||
if (reply instanceof ErrorReply) {
|
|
||||||
reject(reply);
|
|
||||||
} else {
|
|
||||||
resolve(reply);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
getFlags: () => this._getFlags()
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
private _initiateResp2Decoder() {
|
||||||
maxLength: number | null | undefined,
|
return new Decoder({
|
||||||
onShardedChannelMoved: OnShardedChannelMoved
|
onReply: reply => {
|
||||||
) {
|
if (this._pubSub.isActive && Array.isArray(reply)) {
|
||||||
this.#maxLength = maxLength;
|
if (this._onPush(reply)) return;
|
||||||
this.#onShardedChannelMoved = onShardedChannelMoved;
|
|
||||||
}
|
if (PONG.equals(reply[0] as Buffer)) {
|
||||||
|
const { resolve, flags } = this._waitingForReply.shift()!,
|
||||||
addCommand<T = RedisCommandRawReply>(args: RedisCommandArguments, options?: QueueCommandOptions): Promise<T> {
|
buffer = ((reply[1] as Buffer).length === 0 ? reply[0] : reply[1]) as Buffer;
|
||||||
if (this.#maxLength && this.#waitingToBeSent.length + this.#waitingForReply.length >= this.#maxLength) {
|
resolve(flags?.[TYPES.SIMPLE_STRING] === Buffer ? buffer : buffer.toString());
|
||||||
return Promise.reject(new Error('The queue is full'));
|
|
||||||
} else if (options?.signal?.aborted) {
|
|
||||||
return Promise.reject(new AbortError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const node = new LinkedList.Node<CommandWaitingToBeSent>({
|
|
||||||
args,
|
|
||||||
chainId: options?.chainId,
|
|
||||||
returnBuffers: options?.returnBuffers,
|
|
||||||
resolve,
|
|
||||||
reject
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options?.signal) {
|
|
||||||
const listener = () => {
|
|
||||||
this.#waitingToBeSent.removeNode(node);
|
|
||||||
node.value.reject(new AbortError());
|
|
||||||
};
|
|
||||||
node.value.abort = {
|
|
||||||
signal: options.signal,
|
|
||||||
listener
|
|
||||||
};
|
|
||||||
// AbortSignal type is incorrent
|
|
||||||
(options.signal as any).addEventListener('abort', listener, {
|
|
||||||
once: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.asap) {
|
|
||||||
this.#waitingToBeSent.unshiftNode(node);
|
|
||||||
} else {
|
|
||||||
this.#waitingToBeSent.pushNode(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe<T extends boolean>(
|
|
||||||
type: PubSubType,
|
|
||||||
channels: string | Array<string>,
|
|
||||||
listener: PubSubListener<T>,
|
|
||||||
returnBuffers?: T
|
|
||||||
) {
|
|
||||||
return this.#pushPubSubCommand(
|
|
||||||
this.#pubSub.subscribe(type, channels, listener, returnBuffers)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe<T extends boolean>(
|
|
||||||
type: PubSubType,
|
|
||||||
channels?: string | Array<string>,
|
|
||||||
listener?: PubSubListener<T>,
|
|
||||||
returnBuffers?: T
|
|
||||||
) {
|
|
||||||
return this.#pushPubSubCommand(
|
|
||||||
this.#pubSub.unsubscribe(type, channels, listener, returnBuffers)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
resubscribe(): Promise<any> | undefined {
|
|
||||||
const commands = this.#pubSub.resubscribe();
|
|
||||||
if (!commands.length) return;
|
|
||||||
|
|
||||||
return Promise.all(
|
|
||||||
commands.map(command => this.#pushPubSubCommand(command))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
extendPubSubChannelListeners(
|
|
||||||
type: PubSubType,
|
|
||||||
channel: string,
|
|
||||||
listeners: ChannelListeners
|
|
||||||
) {
|
|
||||||
return this.#pushPubSubCommand(
|
|
||||||
this.#pubSub.extendChannelListeners(type, channel, listeners)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
extendPubSubListeners(type: PubSubType, listeners: PubSubTypeListeners) {
|
|
||||||
return this.#pushPubSubCommand(
|
|
||||||
this.#pubSub.extendTypeListeners(type, listeners)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPubSubListeners(type: PubSubType) {
|
|
||||||
return this.#pubSub.getTypeListeners(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
#pushPubSubCommand(command: PubSubCommand) {
|
|
||||||
if (command === undefined) return;
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
this.#waitingToBeSent.push({
|
|
||||||
args: command.args,
|
|
||||||
channelsCounter: command.channelsCounter,
|
|
||||||
returnBuffers: true,
|
|
||||||
resolve: () => {
|
|
||||||
command.resolve();
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
reject: err => {
|
|
||||||
command.reject?.();
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getCommandToSend(): RedisCommandArguments | undefined {
|
|
||||||
const toSend = this.#waitingToBeSent.shift();
|
|
||||||
if (!toSend) return;
|
|
||||||
|
|
||||||
let encoded: RedisCommandArguments;
|
|
||||||
try {
|
|
||||||
encoded = encodeCommand(toSend.args);
|
|
||||||
} catch (err) {
|
|
||||||
toSend.reject(err);
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#waitingForReply.push({
|
this._onReply(reply);
|
||||||
resolve: toSend.resolve,
|
},
|
||||||
reject: toSend.reject,
|
onErrorReply: err => this._onErrorReply(err),
|
||||||
channelsCounter: toSend.channelsCounter,
|
// PUSH type does not exist in RESP2
|
||||||
returnBuffers: toSend.returnBuffers
|
// PubSub is handled in onReply
|
||||||
});
|
// @ts-expect-error
|
||||||
this.#chainInExecution = toSend.chainId;
|
onPush: undefined,
|
||||||
return encoded;
|
getFlags: () => {
|
||||||
|
// PubSub push is an Array in RESP2
|
||||||
|
return this._pubSub.isActive ?
|
||||||
|
RESP2_PUSH_FLAGS :
|
||||||
|
this._getFlags();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addCommand<T>(args: CommandArguments, options?: QueueCommandOptions): Promise<T> {
|
||||||
|
if (this._maxLength && this._waitingToBeSent.length + this._waitingForReply.length >= this._maxLength) {
|
||||||
|
return Promise.reject(new Error('The queue is full'));
|
||||||
|
} else if (options?.signal?.aborted) {
|
||||||
|
return Promise.reject(new AbortError());
|
||||||
}
|
}
|
||||||
|
|
||||||
onReplyChunk(chunk: Buffer): void {
|
return new Promise((resolve, reject) => {
|
||||||
this.#decoder.write(chunk);
|
const node = new LinkedList.Node<CommandWaitingToBeSent>({
|
||||||
}
|
args,
|
||||||
|
chainId: options?.chainId,
|
||||||
|
flags: options?.flags,
|
||||||
|
resolve,
|
||||||
|
reject
|
||||||
|
});
|
||||||
|
|
||||||
flushWaitingForReply(err: Error): void {
|
if (options?.signal) {
|
||||||
this.#decoder.reset();
|
const listener = () => {
|
||||||
this.#pubSub.reset();
|
this._waitingToBeSent.removeNode(node);
|
||||||
RedisCommandsQueue.#flushQueue(this.#waitingForReply, err);
|
node.value.reject(new AbortError());
|
||||||
|
};
|
||||||
|
|
||||||
if (!this.#chainInExecution) return;
|
node.value.removeAbortListener = () => options.signal?.removeEventListener('abort', listener);
|
||||||
|
|
||||||
while (this.#waitingToBeSent.head?.value.chainId === this.#chainInExecution) {
|
options.signal.addEventListener('abort', listener, { once: true });
|
||||||
this.#waitingToBeSent.shift();
|
}
|
||||||
|
|
||||||
|
if (options?.asap) {
|
||||||
|
this._waitingToBeSent.unshiftNode(node);
|
||||||
|
} else {
|
||||||
|
this._waitingToBeSent.pushNode(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe<T extends boolean>(
|
||||||
|
type: PubSubType,
|
||||||
|
channels: string | Array<string>,
|
||||||
|
listener: PubSubListener<T>,
|
||||||
|
returnBuffers?: T
|
||||||
|
) {
|
||||||
|
return this._pushPubSubCommand(
|
||||||
|
this._pubSub.subscribe(type, channels, listener, returnBuffers)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe<T extends boolean>(
|
||||||
|
type: PubSubType,
|
||||||
|
channels?: string | Array<string>,
|
||||||
|
listener?: PubSubListener<T>,
|
||||||
|
returnBuffers?: T
|
||||||
|
) {
|
||||||
|
return this._pushPubSubCommand(
|
||||||
|
this._pubSub.unsubscribe(type, channels, listener, returnBuffers)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resubscribe(): Promise<any> | undefined {
|
||||||
|
const commands = this._pubSub.resubscribe();
|
||||||
|
if (!commands.length) return;
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
commands.map(command => this._pushPubSubCommand(command))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extendPubSubChannelListeners(
|
||||||
|
type: PubSubType,
|
||||||
|
channel: string,
|
||||||
|
listeners: ChannelListeners
|
||||||
|
) {
|
||||||
|
return this._pushPubSubCommand(
|
||||||
|
this._pubSub.extendChannelListeners(type, channel, listeners)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extendPubSubListeners(type: PubSubType, listeners: PubSubTypeListeners) {
|
||||||
|
return this._pushPubSubCommand(
|
||||||
|
this._pubSub.extendTypeListeners(type, listeners)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPubSubListeners(type: PubSubType) {
|
||||||
|
return this._pubSub.getTypeListeners(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pushPubSubCommand(command: PubSubCommand) {
|
||||||
|
if (command === undefined) return;
|
||||||
|
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
this._waitingToBeSent.push({
|
||||||
|
args: command.args,
|
||||||
|
channelsCounter: command.channelsCounter,
|
||||||
|
flags: PUSH_FLAGS,
|
||||||
|
resolve: () => {
|
||||||
|
command.resolve();
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
reject: err => {
|
||||||
|
command.reject?.();
|
||||||
|
reject(err);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.#chainInExecution = undefined;
|
getCommandToSend(): CommandArguments | undefined {
|
||||||
|
const toSend = this._waitingToBeSent.shift();
|
||||||
|
if (!toSend) return;
|
||||||
|
|
||||||
|
let encoded: CommandArguments;
|
||||||
|
try {
|
||||||
|
encoded = encodeCommand(toSend.args);
|
||||||
|
} catch (err) {
|
||||||
|
toSend.reject(err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
flushAll(err: Error): void {
|
// TODO
|
||||||
this.#decoder.reset();
|
// reuse `toSend`
|
||||||
this.#pubSub.reset();
|
(toSend.args as any) = undefined;
|
||||||
RedisCommandsQueue.#flushQueue(this.#waitingForReply, err);
|
if (toSend.removeAbortListener) {
|
||||||
RedisCommandsQueue.#flushQueue(this.#waitingToBeSent, err);
|
toSend.removeAbortListener();
|
||||||
|
(toSend.removeAbortListener as any) = undefined;
|
||||||
}
|
}
|
||||||
|
this._waitingForReply.push(toSend);
|
||||||
|
this._chainInExecution = toSend.chainId;
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flushWaitingForReply(err: Error): void {
|
||||||
|
while (this._waitingForReply.head) {
|
||||||
|
this._waitingForReply.shift()!.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static #flushWaitingToBeSent(command: CommandWaitingToBeSent, err: Error) {
|
||||||
|
command.removeAbortListener?.();
|
||||||
|
command.reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
flushWaitingForReply(err: Error): void {
|
||||||
|
this.decoder.reset();
|
||||||
|
this._pubSub.reset();
|
||||||
|
|
||||||
|
this.#flushWaitingForReply(err);
|
||||||
|
|
||||||
|
if (!this._chainInExecution) return;
|
||||||
|
|
||||||
|
while (this._waitingToBeSent.head?.value.chainId === this._chainInExecution) {
|
||||||
|
RedisCommandsQueue.#flushWaitingToBeSent(
|
||||||
|
this._waitingToBeSent.shift()!,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._chainInExecution = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
flushAll(err: Error): void {
|
||||||
|
this.decoder.reset();
|
||||||
|
this._pubSub.reset();
|
||||||
|
this.#flushWaitingForReply(err);
|
||||||
|
while (this._waitingToBeSent.head) {
|
||||||
|
RedisCommandsQueue.#flushWaitingToBeSent(
|
||||||
|
this._waitingToBeSent.shift()!,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,359 +0,0 @@
|
|||||||
import CLUSTER_COMMANDS from '../cluster/commands';
|
|
||||||
import * as ACL_CAT from '../commands/ACL_CAT';
|
|
||||||
import * as ACL_DELUSER from '../commands/ACL_DELUSER';
|
|
||||||
import * as ACL_DRYRUN from '../commands/ACL_DRYRUN';
|
|
||||||
import * as ACL_GENPASS from '../commands/ACL_GENPASS';
|
|
||||||
import * as ACL_GETUSER from '../commands/ACL_GETUSER';
|
|
||||||
import * as ACL_LIST from '../commands/ACL_LIST';
|
|
||||||
import * as ACL_LOAD from '../commands/ACL_LOAD';
|
|
||||||
import * as ACL_LOG_RESET from '../commands/ACL_LOG_RESET';
|
|
||||||
import * as ACL_LOG from '../commands/ACL_LOG';
|
|
||||||
import * as ACL_SAVE from '../commands/ACL_SAVE';
|
|
||||||
import * as ACL_SETUSER from '../commands/ACL_SETUSER';
|
|
||||||
import * as ACL_USERS from '../commands/ACL_USERS';
|
|
||||||
import * as ACL_WHOAMI from '../commands/ACL_WHOAMI';
|
|
||||||
import * as ASKING from '../commands/ASKING';
|
|
||||||
import * as AUTH from '../commands/AUTH';
|
|
||||||
import * as BGREWRITEAOF from '../commands/BGREWRITEAOF';
|
|
||||||
import * as BGSAVE from '../commands/BGSAVE';
|
|
||||||
import * as CLIENT_CACHING from '../commands/CLIENT_CACHING';
|
|
||||||
import * as CLIENT_GETNAME from '../commands/CLIENT_GETNAME';
|
|
||||||
import * as CLIENT_GETREDIR from '../commands/CLIENT_GETREDIR';
|
|
||||||
import * as CLIENT_ID from '../commands/CLIENT_ID';
|
|
||||||
import * as CLIENT_KILL from '../commands/CLIENT_KILL';
|
|
||||||
import * as CLIENT_LIST from '../commands/CLIENT_LIST';
|
|
||||||
import * as CLIENT_NO_EVICT from '../commands/CLIENT_NO-EVICT';
|
|
||||||
import * as CLIENT_PAUSE from '../commands/CLIENT_PAUSE';
|
|
||||||
import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME';
|
|
||||||
import * as CLIENT_TRACKING from '../commands/CLIENT_TRACKING';
|
|
||||||
import * as CLIENT_TRACKINGINFO from '../commands/CLIENT_TRACKINGINFO';
|
|
||||||
import * as CLIENT_UNPAUSE from '../commands/CLIENT_UNPAUSE';
|
|
||||||
import * as CLIENT_INFO from '../commands/CLIENT_INFO';
|
|
||||||
import * as CLUSTER_ADDSLOTS from '../commands/CLUSTER_ADDSLOTS';
|
|
||||||
import * as CLUSTER_ADDSLOTSRANGE from '../commands/CLUSTER_ADDSLOTSRANGE';
|
|
||||||
import * as CLUSTER_BUMPEPOCH from '../commands/CLUSTER_BUMPEPOCH';
|
|
||||||
import * as CLUSTER_COUNT_FAILURE_REPORTS from '../commands/CLUSTER_COUNT-FAILURE-REPORTS';
|
|
||||||
import * as CLUSTER_COUNTKEYSINSLOT from '../commands/CLUSTER_COUNTKEYSINSLOT';
|
|
||||||
import * as CLUSTER_DELSLOTS from '../commands/CLUSTER_DELSLOTS';
|
|
||||||
import * as CLUSTER_DELSLOTSRANGE from '../commands/CLUSTER_DELSLOTSRANGE';
|
|
||||||
import * as CLUSTER_FAILOVER from '../commands/CLUSTER_FAILOVER';
|
|
||||||
import * as CLUSTER_FLUSHSLOTS from '../commands/CLUSTER_FLUSHSLOTS';
|
|
||||||
import * as CLUSTER_FORGET from '../commands/CLUSTER_FORGET';
|
|
||||||
import * as CLUSTER_GETKEYSINSLOT from '../commands/CLUSTER_GETKEYSINSLOT';
|
|
||||||
import * as CLUSTER_INFO from '../commands/CLUSTER_INFO';
|
|
||||||
import * as CLUSTER_KEYSLOT from '../commands/CLUSTER_KEYSLOT';
|
|
||||||
import * as CLUSTER_LINKS from '../commands/CLUSTER_LINKS';
|
|
||||||
import * as CLUSTER_MEET from '../commands/CLUSTER_MEET';
|
|
||||||
import * as CLUSTER_MYID from '../commands/CLUSTER_MYID';
|
|
||||||
import * as CLUSTER_NODES from '../commands/CLUSTER_NODES';
|
|
||||||
import * as CLUSTER_REPLICAS from '../commands/CLUSTER_REPLICAS';
|
|
||||||
import * as CLUSTER_REPLICATE from '../commands/CLUSTER_REPLICATE';
|
|
||||||
import * as CLUSTER_RESET from '../commands/CLUSTER_RESET';
|
|
||||||
import * as CLUSTER_SAVECONFIG from '../commands/CLUSTER_SAVECONFIG';
|
|
||||||
import * as CLUSTER_SET_CONFIG_EPOCH from '../commands/CLUSTER_SET-CONFIG-EPOCH';
|
|
||||||
import * as CLUSTER_SETSLOT from '../commands/CLUSTER_SETSLOT';
|
|
||||||
import * as CLUSTER_SLOTS from '../commands/CLUSTER_SLOTS';
|
|
||||||
import * as COMMAND_COUNT from '../commands/COMMAND_COUNT';
|
|
||||||
import * as COMMAND_GETKEYS from '../commands/COMMAND_GETKEYS';
|
|
||||||
import * as COMMAND_GETKEYSANDFLAGS from '../commands/COMMAND_GETKEYSANDFLAGS';
|
|
||||||
import * as COMMAND_INFO from '../commands/COMMAND_INFO';
|
|
||||||
import * as COMMAND_LIST from '../commands/COMMAND_LIST';
|
|
||||||
import * as COMMAND from '../commands/COMMAND';
|
|
||||||
import * as CONFIG_GET from '../commands/CONFIG_GET';
|
|
||||||
import * as CONFIG_RESETASTAT from '../commands/CONFIG_RESETSTAT';
|
|
||||||
import * as CONFIG_REWRITE from '../commands/CONFIG_REWRITE';
|
|
||||||
import * as CONFIG_SET from '../commands/CONFIG_SET';
|
|
||||||
import * as DBSIZE from '../commands/DBSIZE';
|
|
||||||
import * as DISCARD from '../commands/DISCARD';
|
|
||||||
import * as ECHO from '../commands/ECHO';
|
|
||||||
import * as FAILOVER from '../commands/FAILOVER';
|
|
||||||
import * as FLUSHALL from '../commands/FLUSHALL';
|
|
||||||
import * as FLUSHDB from '../commands/FLUSHDB';
|
|
||||||
import * as FUNCTION_DELETE from '../commands/FUNCTION_DELETE';
|
|
||||||
import * as FUNCTION_DUMP from '../commands/FUNCTION_DUMP';
|
|
||||||
import * as FUNCTION_FLUSH from '../commands/FUNCTION_FLUSH';
|
|
||||||
import * as FUNCTION_KILL from '../commands/FUNCTION_KILL';
|
|
||||||
import * as FUNCTION_LIST_WITHCODE from '../commands/FUNCTION_LIST_WITHCODE';
|
|
||||||
import * as FUNCTION_LIST from '../commands/FUNCTION_LIST';
|
|
||||||
import * as FUNCTION_LOAD from '../commands/FUNCTION_LOAD';
|
|
||||||
import * as FUNCTION_RESTORE from '../commands/FUNCTION_RESTORE';
|
|
||||||
import * as FUNCTION_STATS from '../commands/FUNCTION_STATS';
|
|
||||||
import * as HELLO from '../commands/HELLO';
|
|
||||||
import * as INFO from '../commands/INFO';
|
|
||||||
import * as KEYS from '../commands/KEYS';
|
|
||||||
import * as LASTSAVE from '../commands/LASTSAVE';
|
|
||||||
import * as LATENCY_DOCTOR from '../commands/LATENCY_DOCTOR';
|
|
||||||
import * as LATENCY_GRAPH from '../commands/LATENCY_GRAPH';
|
|
||||||
import * as LOLWUT from '../commands/LOLWUT';
|
|
||||||
import * as MEMORY_DOCTOR from '../commands/MEMORY_DOCTOR';
|
|
||||||
import * as MEMORY_MALLOC_STATS from '../commands/MEMORY_MALLOC-STATS';
|
|
||||||
import * as MEMORY_PURGE from '../commands/MEMORY_PURGE';
|
|
||||||
import * as MEMORY_STATS from '../commands/MEMORY_STATS';
|
|
||||||
import * as MEMORY_USAGE from '../commands/MEMORY_USAGE';
|
|
||||||
import * as MODULE_LIST from '../commands/MODULE_LIST';
|
|
||||||
import * as MODULE_LOAD from '../commands/MODULE_LOAD';
|
|
||||||
import * as MODULE_UNLOAD from '../commands/MODULE_UNLOAD';
|
|
||||||
import * as MOVE from '../commands/MOVE';
|
|
||||||
import * as PING from '../commands/PING';
|
|
||||||
import * as PUBSUB_CHANNELS from '../commands/PUBSUB_CHANNELS';
|
|
||||||
import * as PUBSUB_NUMPAT from '../commands/PUBSUB_NUMPAT';
|
|
||||||
import * as PUBSUB_NUMSUB from '../commands/PUBSUB_NUMSUB';
|
|
||||||
import * as PUBSUB_SHARDCHANNELS from '../commands/PUBSUB_SHARDCHANNELS';
|
|
||||||
import * as RANDOMKEY from '../commands/RANDOMKEY';
|
|
||||||
import * as READONLY from '../commands/READONLY';
|
|
||||||
import * as READWRITE from '../commands/READWRITE';
|
|
||||||
import * as REPLICAOF from '../commands/REPLICAOF';
|
|
||||||
import * as RESTORE_ASKING from '../commands/RESTORE-ASKING';
|
|
||||||
import * as ROLE from '../commands/ROLE';
|
|
||||||
import * as SAVE from '../commands/SAVE';
|
|
||||||
import * as SCAN from '../commands/SCAN';
|
|
||||||
import * as SCRIPT_DEBUG from '../commands/SCRIPT_DEBUG';
|
|
||||||
import * as SCRIPT_EXISTS from '../commands/SCRIPT_EXISTS';
|
|
||||||
import * as SCRIPT_FLUSH from '../commands/SCRIPT_FLUSH';
|
|
||||||
import * as SCRIPT_KILL from '../commands/SCRIPT_KILL';
|
|
||||||
import * as SCRIPT_LOAD from '../commands/SCRIPT_LOAD';
|
|
||||||
import * as SHUTDOWN from '../commands/SHUTDOWN';
|
|
||||||
import * as SWAPDB from '../commands/SWAPDB';
|
|
||||||
import * as TIME from '../commands/TIME';
|
|
||||||
import * as UNWATCH from '../commands/UNWATCH';
|
|
||||||
import * as WAIT from '../commands/WAIT';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
...CLUSTER_COMMANDS,
|
|
||||||
ACL_CAT,
|
|
||||||
aclCat: ACL_CAT,
|
|
||||||
ACL_DELUSER,
|
|
||||||
aclDelUser: ACL_DELUSER,
|
|
||||||
ACL_DRYRUN,
|
|
||||||
aclDryRun: ACL_DRYRUN,
|
|
||||||
ACL_GENPASS,
|
|
||||||
aclGenPass: ACL_GENPASS,
|
|
||||||
ACL_GETUSER,
|
|
||||||
aclGetUser: ACL_GETUSER,
|
|
||||||
ACL_LIST,
|
|
||||||
aclList: ACL_LIST,
|
|
||||||
ACL_LOAD,
|
|
||||||
aclLoad: ACL_LOAD,
|
|
||||||
ACL_LOG_RESET,
|
|
||||||
aclLogReset: ACL_LOG_RESET,
|
|
||||||
ACL_LOG,
|
|
||||||
aclLog: ACL_LOG,
|
|
||||||
ACL_SAVE,
|
|
||||||
aclSave: ACL_SAVE,
|
|
||||||
ACL_SETUSER,
|
|
||||||
aclSetUser: ACL_SETUSER,
|
|
||||||
ACL_USERS,
|
|
||||||
aclUsers: ACL_USERS,
|
|
||||||
ACL_WHOAMI,
|
|
||||||
aclWhoAmI: ACL_WHOAMI,
|
|
||||||
ASKING,
|
|
||||||
asking: ASKING,
|
|
||||||
AUTH,
|
|
||||||
auth: AUTH,
|
|
||||||
BGREWRITEAOF,
|
|
||||||
bgRewriteAof: BGREWRITEAOF,
|
|
||||||
BGSAVE,
|
|
||||||
bgSave: BGSAVE,
|
|
||||||
CLIENT_CACHING,
|
|
||||||
clientCaching: CLIENT_CACHING,
|
|
||||||
CLIENT_GETNAME,
|
|
||||||
clientGetName: CLIENT_GETNAME,
|
|
||||||
CLIENT_GETREDIR,
|
|
||||||
clientGetRedir: CLIENT_GETREDIR,
|
|
||||||
CLIENT_ID,
|
|
||||||
clientId: CLIENT_ID,
|
|
||||||
CLIENT_KILL,
|
|
||||||
clientKill: CLIENT_KILL,
|
|
||||||
'CLIENT_NO-EVICT': CLIENT_NO_EVICT,
|
|
||||||
clientNoEvict: CLIENT_NO_EVICT,
|
|
||||||
CLIENT_LIST,
|
|
||||||
clientList: CLIENT_LIST,
|
|
||||||
CLIENT_PAUSE,
|
|
||||||
clientPause: CLIENT_PAUSE,
|
|
||||||
CLIENT_SETNAME,
|
|
||||||
clientSetName: CLIENT_SETNAME,
|
|
||||||
CLIENT_TRACKING,
|
|
||||||
clientTracking: CLIENT_TRACKING,
|
|
||||||
CLIENT_TRACKINGINFO,
|
|
||||||
clientTrackingInfo: CLIENT_TRACKINGINFO,
|
|
||||||
CLIENT_UNPAUSE,
|
|
||||||
clientUnpause: CLIENT_UNPAUSE,
|
|
||||||
CLIENT_INFO,
|
|
||||||
clientInfo: CLIENT_INFO,
|
|
||||||
CLUSTER_ADDSLOTS,
|
|
||||||
clusterAddSlots: CLUSTER_ADDSLOTS,
|
|
||||||
CLUSTER_ADDSLOTSRANGE,
|
|
||||||
clusterAddSlotsRange: CLUSTER_ADDSLOTSRANGE,
|
|
||||||
CLUSTER_BUMPEPOCH,
|
|
||||||
clusterBumpEpoch: CLUSTER_BUMPEPOCH,
|
|
||||||
CLUSTER_COUNT_FAILURE_REPORTS,
|
|
||||||
clusterCountFailureReports: CLUSTER_COUNT_FAILURE_REPORTS,
|
|
||||||
CLUSTER_COUNTKEYSINSLOT,
|
|
||||||
clusterCountKeysInSlot: CLUSTER_COUNTKEYSINSLOT,
|
|
||||||
CLUSTER_DELSLOTS,
|
|
||||||
clusterDelSlots: CLUSTER_DELSLOTS,
|
|
||||||
CLUSTER_DELSLOTSRANGE,
|
|
||||||
clusterDelSlotsRange: CLUSTER_DELSLOTSRANGE,
|
|
||||||
CLUSTER_FAILOVER,
|
|
||||||
clusterFailover: CLUSTER_FAILOVER,
|
|
||||||
CLUSTER_FLUSHSLOTS,
|
|
||||||
clusterFlushSlots: CLUSTER_FLUSHSLOTS,
|
|
||||||
CLUSTER_FORGET,
|
|
||||||
clusterForget: CLUSTER_FORGET,
|
|
||||||
CLUSTER_GETKEYSINSLOT,
|
|
||||||
clusterGetKeysInSlot: CLUSTER_GETKEYSINSLOT,
|
|
||||||
CLUSTER_INFO,
|
|
||||||
clusterInfo: CLUSTER_INFO,
|
|
||||||
CLUSTER_KEYSLOT,
|
|
||||||
clusterKeySlot: CLUSTER_KEYSLOT,
|
|
||||||
CLUSTER_LINKS,
|
|
||||||
clusterLinks: CLUSTER_LINKS,
|
|
||||||
CLUSTER_MEET,
|
|
||||||
clusterMeet: CLUSTER_MEET,
|
|
||||||
CLUSTER_MYID,
|
|
||||||
clusterMyId: CLUSTER_MYID,
|
|
||||||
CLUSTER_NODES,
|
|
||||||
clusterNodes: CLUSTER_NODES,
|
|
||||||
CLUSTER_REPLICAS,
|
|
||||||
clusterReplicas: CLUSTER_REPLICAS,
|
|
||||||
CLUSTER_REPLICATE,
|
|
||||||
clusterReplicate: CLUSTER_REPLICATE,
|
|
||||||
CLUSTER_RESET,
|
|
||||||
clusterReset: CLUSTER_RESET,
|
|
||||||
CLUSTER_SAVECONFIG,
|
|
||||||
clusterSaveConfig: CLUSTER_SAVECONFIG,
|
|
||||||
CLUSTER_SET_CONFIG_EPOCH,
|
|
||||||
clusterSetConfigEpoch: CLUSTER_SET_CONFIG_EPOCH,
|
|
||||||
CLUSTER_SETSLOT,
|
|
||||||
clusterSetSlot: CLUSTER_SETSLOT,
|
|
||||||
CLUSTER_SLOTS,
|
|
||||||
clusterSlots: CLUSTER_SLOTS,
|
|
||||||
COMMAND_COUNT,
|
|
||||||
commandCount: COMMAND_COUNT,
|
|
||||||
COMMAND_GETKEYS,
|
|
||||||
commandGetKeys: COMMAND_GETKEYS,
|
|
||||||
COMMAND_GETKEYSANDFLAGS,
|
|
||||||
commandGetKeysAndFlags: COMMAND_GETKEYSANDFLAGS,
|
|
||||||
COMMAND_INFO,
|
|
||||||
commandInfo: COMMAND_INFO,
|
|
||||||
COMMAND_LIST,
|
|
||||||
commandList: COMMAND_LIST,
|
|
||||||
COMMAND,
|
|
||||||
command: COMMAND,
|
|
||||||
CONFIG_GET,
|
|
||||||
configGet: CONFIG_GET,
|
|
||||||
CONFIG_RESETASTAT,
|
|
||||||
configResetStat: CONFIG_RESETASTAT,
|
|
||||||
CONFIG_REWRITE,
|
|
||||||
configRewrite: CONFIG_REWRITE,
|
|
||||||
CONFIG_SET,
|
|
||||||
configSet: CONFIG_SET,
|
|
||||||
DBSIZE,
|
|
||||||
dbSize: DBSIZE,
|
|
||||||
DISCARD,
|
|
||||||
discard: DISCARD,
|
|
||||||
ECHO,
|
|
||||||
echo: ECHO,
|
|
||||||
FAILOVER,
|
|
||||||
failover: FAILOVER,
|
|
||||||
FLUSHALL,
|
|
||||||
flushAll: FLUSHALL,
|
|
||||||
FLUSHDB,
|
|
||||||
flushDb: FLUSHDB,
|
|
||||||
FUNCTION_DELETE,
|
|
||||||
functionDelete: FUNCTION_DELETE,
|
|
||||||
FUNCTION_DUMP,
|
|
||||||
functionDump: FUNCTION_DUMP,
|
|
||||||
FUNCTION_FLUSH,
|
|
||||||
functionFlush: FUNCTION_FLUSH,
|
|
||||||
FUNCTION_KILL,
|
|
||||||
functionKill: FUNCTION_KILL,
|
|
||||||
FUNCTION_LIST_WITHCODE,
|
|
||||||
functionListWithCode: FUNCTION_LIST_WITHCODE,
|
|
||||||
FUNCTION_LIST,
|
|
||||||
functionList: FUNCTION_LIST,
|
|
||||||
FUNCTION_LOAD,
|
|
||||||
functionLoad: FUNCTION_LOAD,
|
|
||||||
FUNCTION_RESTORE,
|
|
||||||
functionRestore: FUNCTION_RESTORE,
|
|
||||||
FUNCTION_STATS,
|
|
||||||
functionStats: FUNCTION_STATS,
|
|
||||||
HELLO,
|
|
||||||
hello: HELLO,
|
|
||||||
INFO,
|
|
||||||
info: INFO,
|
|
||||||
KEYS,
|
|
||||||
keys: KEYS,
|
|
||||||
LASTSAVE,
|
|
||||||
lastSave: LASTSAVE,
|
|
||||||
LATENCY_DOCTOR,
|
|
||||||
latencyDoctor: LATENCY_DOCTOR,
|
|
||||||
LATENCY_GRAPH,
|
|
||||||
latencyGraph: LATENCY_GRAPH,
|
|
||||||
LOLWUT,
|
|
||||||
lolwut: LOLWUT,
|
|
||||||
MEMORY_DOCTOR,
|
|
||||||
memoryDoctor: MEMORY_DOCTOR,
|
|
||||||
'MEMORY_MALLOC-STATS': MEMORY_MALLOC_STATS,
|
|
||||||
memoryMallocStats: MEMORY_MALLOC_STATS,
|
|
||||||
MEMORY_PURGE,
|
|
||||||
memoryPurge: MEMORY_PURGE,
|
|
||||||
MEMORY_STATS,
|
|
||||||
memoryStats: MEMORY_STATS,
|
|
||||||
MEMORY_USAGE,
|
|
||||||
memoryUsage: MEMORY_USAGE,
|
|
||||||
MODULE_LIST,
|
|
||||||
moduleList: MODULE_LIST,
|
|
||||||
MODULE_LOAD,
|
|
||||||
moduleLoad: MODULE_LOAD,
|
|
||||||
MODULE_UNLOAD,
|
|
||||||
moduleUnload: MODULE_UNLOAD,
|
|
||||||
MOVE,
|
|
||||||
move: MOVE,
|
|
||||||
PING,
|
|
||||||
ping: PING,
|
|
||||||
PUBSUB_CHANNELS,
|
|
||||||
pubSubChannels: PUBSUB_CHANNELS,
|
|
||||||
PUBSUB_NUMPAT,
|
|
||||||
pubSubNumPat: PUBSUB_NUMPAT,
|
|
||||||
PUBSUB_NUMSUB,
|
|
||||||
pubSubNumSub: PUBSUB_NUMSUB,
|
|
||||||
PUBSUB_SHARDCHANNELS,
|
|
||||||
pubSubShardChannels: PUBSUB_SHARDCHANNELS,
|
|
||||||
RANDOMKEY,
|
|
||||||
randomKey: RANDOMKEY,
|
|
||||||
READONLY,
|
|
||||||
readonly: READONLY,
|
|
||||||
READWRITE,
|
|
||||||
readwrite: READWRITE,
|
|
||||||
REPLICAOF,
|
|
||||||
replicaOf: REPLICAOF,
|
|
||||||
'RESTORE-ASKING': RESTORE_ASKING,
|
|
||||||
restoreAsking: RESTORE_ASKING,
|
|
||||||
ROLE,
|
|
||||||
role: ROLE,
|
|
||||||
SAVE,
|
|
||||||
save: SAVE,
|
|
||||||
SCAN,
|
|
||||||
scan: SCAN,
|
|
||||||
SCRIPT_DEBUG,
|
|
||||||
scriptDebug: SCRIPT_DEBUG,
|
|
||||||
SCRIPT_EXISTS,
|
|
||||||
scriptExists: SCRIPT_EXISTS,
|
|
||||||
SCRIPT_FLUSH,
|
|
||||||
scriptFlush: SCRIPT_FLUSH,
|
|
||||||
SCRIPT_KILL,
|
|
||||||
scriptKill: SCRIPT_KILL,
|
|
||||||
SCRIPT_LOAD,
|
|
||||||
scriptLoad: SCRIPT_LOAD,
|
|
||||||
SHUTDOWN,
|
|
||||||
shutdown: SHUTDOWN,
|
|
||||||
SWAPDB,
|
|
||||||
swapDb: SWAPDB,
|
|
||||||
TIME,
|
|
||||||
time: TIME,
|
|
||||||
UNWATCH,
|
|
||||||
unwatch: UNWATCH,
|
|
||||||
WAIT,
|
|
||||||
wait: WAIT
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load Diff
@@ -1,200 +1,243 @@
|
|||||||
import COMMANDS from './commands';
|
import COMMANDS from '../commands';
|
||||||
import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, ExcludeMappedString, RedisFunction, RedisCommands } from '../commands';
|
|
||||||
import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command';
|
import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command';
|
||||||
import { attachCommands, attachExtensions, transformLegacyCommandArguments } from '../commander';
|
import { ReplyWithFlags, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, Flags } from '../RESP/types';
|
||||||
|
import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander';
|
||||||
|
|
||||||
type CommandSignature<
|
type CommandSignature<
|
||||||
C extends RedisCommand,
|
REPLIES extends Array<unknown>,
|
||||||
M extends RedisModules,
|
C extends Command,
|
||||||
F extends RedisFunctions,
|
M extends RedisModules,
|
||||||
S extends RedisScripts
|
F extends RedisFunctions,
|
||||||
> = (...args: Parameters<C['transformArguments']>) => RedisClientMultiCommandType<M, F, S>;
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags
|
||||||
|
> = (...args: Parameters<C['transformArguments']>) => RedisClientMultiCommandType<
|
||||||
|
[...REPLIES, ReplyWithFlags<CommandReply<C, RESP>, FLAGS>],
|
||||||
|
M,
|
||||||
|
F,
|
||||||
|
S,
|
||||||
|
RESP,
|
||||||
|
FLAGS
|
||||||
|
>;
|
||||||
|
|
||||||
type WithCommands<
|
type WithCommands<
|
||||||
M extends RedisModules,
|
REPLIES extends Array<unknown>,
|
||||||
F extends RedisFunctions,
|
M extends RedisModules,
|
||||||
S extends RedisScripts
|
F extends RedisFunctions,
|
||||||
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags
|
||||||
> = {
|
> = {
|
||||||
[P in keyof typeof COMMANDS]: CommandSignature<(typeof COMMANDS)[P], M, F, S>;
|
[P in keyof typeof COMMANDS]: CommandSignature<REPLIES, (typeof COMMANDS)[P], M, F, S, RESP, FLAGS>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type WithModules<
|
type WithModules<
|
||||||
M extends RedisModules,
|
REPLIES extends Array<unknown>,
|
||||||
F extends RedisFunctions,
|
M extends RedisModules,
|
||||||
S extends RedisScripts
|
F extends RedisFunctions,
|
||||||
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags
|
||||||
> = {
|
> = {
|
||||||
[P in keyof M as ExcludeMappedString<P>]: {
|
[P in keyof M]: {
|
||||||
[C in keyof M[P] as ExcludeMappedString<C>]: CommandSignature<M[P][C], M, F, S>;
|
[C in keyof M[P]]: CommandSignature<REPLIES, M[P][C], M, F, S, RESP, FLAGS>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type WithFunctions<
|
type WithFunctions<
|
||||||
M extends RedisModules,
|
REPLIES extends Array<unknown>,
|
||||||
F extends RedisFunctions,
|
M extends RedisModules,
|
||||||
S extends RedisScripts
|
F extends RedisFunctions,
|
||||||
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags
|
||||||
> = {
|
> = {
|
||||||
[P in keyof F as ExcludeMappedString<P>]: {
|
[L in keyof F]: {
|
||||||
[FF in keyof F[P] as ExcludeMappedString<FF>]: CommandSignature<F[P][FF], M, F, S>;
|
[C in keyof F[L]]: CommandSignature<REPLIES, F[L][C], M, F, S, RESP, FLAGS>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type WithScripts<
|
type WithScripts<
|
||||||
M extends RedisModules,
|
REPLIES extends Array<unknown>,
|
||||||
F extends RedisFunctions,
|
M extends RedisModules,
|
||||||
S extends RedisScripts
|
F extends RedisFunctions,
|
||||||
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags
|
||||||
> = {
|
> = {
|
||||||
[P in keyof S as ExcludeMappedString<P>]: CommandSignature<S[P], M, F, S>;
|
[P in keyof S]: CommandSignature<REPLIES, S[P], M, F, S, RESP, FLAGS>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RedisClientMultiCommandType<
|
export type RedisClientMultiCommandType<
|
||||||
M extends RedisModules,
|
REPLIES extends Array<any>,
|
||||||
F extends RedisFunctions,
|
M extends RedisModules,
|
||||||
S extends RedisScripts
|
F extends RedisFunctions,
|
||||||
> = RedisClientMultiCommand & WithCommands<M, F, S> & WithModules<M, F, S> & WithFunctions<M, F, S> & WithScripts<M, F, S>;
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions,
|
||||||
type InstantiableRedisMultiCommand<
|
FLAGS extends Flags
|
||||||
M extends RedisModules,
|
> = (
|
||||||
F extends RedisFunctions,
|
RedisClientMultiCommand<REPLIES> &
|
||||||
S extends RedisScripts
|
WithCommands<REPLIES, M, F, S, RESP, FLAGS> &
|
||||||
> = new (...args: ConstructorParameters<typeof RedisClientMultiCommand>) => RedisClientMultiCommandType<M, F, S>;
|
WithModules<REPLIES, M, F, S, RESP, FLAGS> &
|
||||||
|
WithFunctions<REPLIES, M, F, S, RESP, FLAGS> &
|
||||||
|
WithScripts<REPLIES, M, F, S, RESP, FLAGS>
|
||||||
|
);
|
||||||
|
|
||||||
export type RedisClientMultiExecutor = (
|
export type RedisClientMultiExecutor = (
|
||||||
queue: Array<RedisMultiQueuedCommand>,
|
queue: Array<RedisMultiQueuedCommand>,
|
||||||
selectedDB?: number,
|
selectedDB?: number,
|
||||||
chainId?: symbol
|
chainId?: symbol
|
||||||
) => Promise<Array<RedisCommandRawReply>>;
|
) => Promise<Array<unknown>>;
|
||||||
|
|
||||||
export default class RedisClientMultiCommand {
|
export default class RedisClientMultiCommand<REPLIES = []> extends RedisMultiCommand {
|
||||||
static extend<
|
static #createCommand(command: Command, resp: RespVersions) {
|
||||||
M extends RedisModules,
|
const transformReply = getTransformReply(command, resp);
|
||||||
F extends RedisFunctions,
|
return function (this: RedisClientMultiCommand) {
|
||||||
S extends RedisScripts
|
return this.addCommand(
|
||||||
>(extensions?: RedisExtensions<M, F, S>): InstantiableRedisMultiCommand<M, F, S> {
|
command.transformArguments.apply(undefined, arguments as any),
|
||||||
return attachExtensions({
|
transformReply
|
||||||
BaseClass: RedisClientMultiCommand,
|
);
|
||||||
modulesExecutor: RedisClientMultiCommand.prototype.commandsExecutor,
|
};
|
||||||
modules: extensions?.modules,
|
}
|
||||||
functionsExecutor: RedisClientMultiCommand.prototype.functionsExecutor,
|
|
||||||
functions: extensions?.functions,
|
|
||||||
scriptsExecutor: RedisClientMultiCommand.prototype.scriptsExecutor,
|
|
||||||
scripts: extensions?.scripts
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly #multi = new RedisMultiCommand();
|
static #createModuleCommand(command: Command, resp: RespVersions) {
|
||||||
readonly #executor: RedisClientMultiExecutor;
|
const transformReply = getTransformReply(command, resp);
|
||||||
readonly v4: Record<string, any> = {};
|
return function (this: { self: RedisClientMultiCommand }) {
|
||||||
#selectedDB?: number;
|
return this.self.addCommand(
|
||||||
|
command.transformArguments.apply(undefined, arguments as any),
|
||||||
|
transformReply
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
constructor(executor: RedisClientMultiExecutor, legacyMode = false) {
|
static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) {
|
||||||
this.#executor = executor;
|
const prefix = functionArgumentsPrefix(name, fn),
|
||||||
if (legacyMode) {
|
transformReply = getTransformReply(fn, resp);
|
||||||
this.#legacyMode();
|
return function (this: { self: RedisClientMultiCommand }) {
|
||||||
}
|
const fnArgs = fn.transformArguments.apply(undefined, arguments as any),
|
||||||
}
|
args: CommandArguments = prefix.concat(fnArgs);
|
||||||
|
args.preserve = fnArgs.preserve;
|
||||||
|
return this.self.addCommand(
|
||||||
|
args,
|
||||||
|
transformReply
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#legacyMode(): void {
|
static #createScriptCommand(script: RedisScript, resp: RespVersions) {
|
||||||
this.v4.addCommand = this.addCommand.bind(this);
|
const transformReply = getTransformReply(script, resp);
|
||||||
(this as any).addCommand = (...args: Array<any>): this => {
|
return function (this: RedisClientMultiCommand) {
|
||||||
this.#multi.addCommand(transformLegacyCommandArguments(args));
|
return this.addScript(
|
||||||
return this;
|
script,
|
||||||
};
|
script.transformArguments.apply(undefined, arguments as any),
|
||||||
this.v4.exec = this.exec.bind(this);
|
transformReply
|
||||||
(this as any).exec = (callback?: (err: Error | null, replies?: Array<unknown>) => unknown): void => {
|
);
|
||||||
this.v4.exec()
|
};
|
||||||
.then((reply: Array<unknown>) => {
|
}
|
||||||
if (!callback) return;
|
|
||||||
|
|
||||||
callback(null, reply);
|
static extend<
|
||||||
})
|
M extends RedisModules = Record<string, never>,
|
||||||
.catch((err: Error) => {
|
F extends RedisFunctions = Record<string, never>,
|
||||||
if (!callback) {
|
S extends RedisScripts = Record<string, never>,
|
||||||
// this.emit('error', err);
|
RESP extends RespVersions = 2,
|
||||||
return;
|
FLAGS extends Flags = {}
|
||||||
}
|
>(config?: CommanderConfig<M, F, S, RESP>) {
|
||||||
|
return attachConfig({
|
||||||
|
BaseClass: RedisClientMultiCommand,
|
||||||
|
commands: COMMANDS,
|
||||||
|
createCommand: RedisClientMultiCommand.#createCommand,
|
||||||
|
createModuleCommand: RedisClientMultiCommand.#createModuleCommand,
|
||||||
|
createFunctionCommand: RedisClientMultiCommand.#createFunctionCommand,
|
||||||
|
createScriptCommand: RedisClientMultiCommand.#createScriptCommand,
|
||||||
|
config
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
callback(err);
|
// readonly #multi = new RedisMultiCommand();
|
||||||
});
|
readonly #executor: RedisClientMultiExecutor;
|
||||||
};
|
// readonly v4: Record<string, any> = {};
|
||||||
|
#selectedDB?: number;
|
||||||
|
|
||||||
for (const [ name, command ] of Object.entries(COMMANDS as RedisCommands)) {
|
constructor(executor: RedisClientMultiExecutor, legacyMode = false) {
|
||||||
this.#defineLegacyCommand(name, command);
|
super();
|
||||||
(this as any)[name.toLowerCase()] ??= (this as any)[name];
|
this.#executor = executor;
|
||||||
}
|
// if (legacyMode) {
|
||||||
}
|
// this.#legacyMode();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
#defineLegacyCommand(this: any, name: string, command?: RedisCommand): void {
|
// #legacyMode(): void {
|
||||||
this.v4[name] = this[name].bind(this.v4);
|
// this.v4.addCommand = this.addCommand.bind(this);
|
||||||
this[name] = command && command.TRANSFORM_LEGACY_REPLY && command.transformReply ?
|
// (this as any).addCommand = (...args: Array<any>): this => {
|
||||||
(...args: Array<unknown>) => {
|
// this.#multi.addCommand(transformLegacyCommandArguments(args));
|
||||||
this.#multi.addCommand(
|
// return this;
|
||||||
[name, ...transformLegacyCommandArguments(args)],
|
// };
|
||||||
command.transformReply
|
// this.v4.exec = this.exec.bind(this);
|
||||||
);
|
// (this as any).exec = (callback?: (err: Error | null, replies?: Array<unknown>) => unknown): void => {
|
||||||
return this;
|
// this.v4.exec()
|
||||||
} :
|
// .then((reply: Array<unknown>) => {
|
||||||
(...args: Array<unknown>) => this.addCommand(name, ...args);
|
// if (!callback) return;
|
||||||
}
|
|
||||||
|
|
||||||
commandsExecutor(command: RedisCommand, args: Array<unknown>): this {
|
// callback(null, reply);
|
||||||
return this.addCommand(
|
// })
|
||||||
command.transformArguments(...args),
|
// .catch((err: Error) => {
|
||||||
command.transformReply
|
// if (!callback) {
|
||||||
);
|
// // this.emit('error', err);
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
SELECT(db: number, transformReply?: RedisCommand['transformReply']): this {
|
// callback(err);
|
||||||
this.#selectedDB = db;
|
// });
|
||||||
return this.addCommand(['SELECT', db.toString()], transformReply);
|
// };
|
||||||
}
|
|
||||||
|
|
||||||
select = this.SELECT;
|
// for (const [name, command] of Object.entries(COMMANDS as RedisCommands)) {
|
||||||
|
// this.#defineLegacyCommand(name, command);
|
||||||
|
// (this as any)[name.toLowerCase()] ??= (this as any)[name];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
addCommand(args: RedisCommandArguments, transformReply?: RedisCommand['transformReply']): this {
|
// #defineLegacyCommand(this: any, name: string, command?: RedisCommand): void {
|
||||||
this.#multi.addCommand(args, transformReply);
|
// this.v4[name] = this[name].bind(this.v4);
|
||||||
return this;
|
// this[name] = command && command.TRANSFORM_LEGACY_REPLY && command.transformReply ?
|
||||||
}
|
// (...args: Array<unknown>) => {
|
||||||
|
// this.#multi.addCommand(
|
||||||
|
// [name, ...transformLegacyCommandArguments(args)],
|
||||||
|
// command.transformReply
|
||||||
|
// );
|
||||||
|
// return this;
|
||||||
|
// } :
|
||||||
|
// (...args: Array<unknown>) => this.addCommand(name, ...args);
|
||||||
|
// }
|
||||||
|
|
||||||
functionsExecutor(fn: RedisFunction, args: Array<unknown>, name: string): this {
|
SELECT(db: number, transformReply?: TransformReply): this {
|
||||||
this.#multi.addFunction(name, fn, args);
|
this.#selectedDB = db;
|
||||||
return this;
|
return this.addCommand(['SELECT', db.toString()], transformReply);
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptsExecutor(script: RedisScript, args: Array<unknown>): this {
|
select = this.SELECT;
|
||||||
this.#multi.addScript(script, args);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
async exec(execAsPipeline = false): Promise<Array<RedisCommandRawReply>> {
|
async exec(execAsPipeline = false): Promise<REPLIES> {
|
||||||
if (execAsPipeline) {
|
if (execAsPipeline) return this.execAsPipeline();
|
||||||
return this.execAsPipeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.#multi.handleExecReplies(
|
return this.handleExecReplies(
|
||||||
await this.#executor(
|
await this.#executor(
|
||||||
this.#multi.queue,
|
this.queue,
|
||||||
this.#selectedDB,
|
this.#selectedDB,
|
||||||
RedisMultiCommand.generateChainId()
|
RedisMultiCommand.generateChainId()
|
||||||
)
|
)
|
||||||
);
|
) as REPLIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXEC = this.exec;
|
EXEC = this.exec;
|
||||||
|
|
||||||
async execAsPipeline(): Promise<Array<RedisCommandRawReply>> {
|
async execAsPipeline(): Promise<REPLIES> {
|
||||||
if (this.#multi.queue.length === 0) return [];
|
if (this.queue.length === 0) return [] as REPLIES;
|
||||||
|
|
||||||
return this.#multi.transformReplies(
|
return this.transformReplies(
|
||||||
await this.#executor(
|
await this.#executor(
|
||||||
this.#multi.queue,
|
this.queue,
|
||||||
this.#selectedDB
|
this.#selectedDB
|
||||||
)
|
)
|
||||||
);
|
) as REPLIES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachCommands({
|
|
||||||
BaseClass: RedisClientMultiCommand,
|
|
||||||
commands: COMMANDS,
|
|
||||||
executor: RedisClientMultiCommand.prototype.commandsExecutor
|
|
||||||
});
|
|
||||||
|
@@ -1,37 +1,37 @@
|
|||||||
import { RedisCommandArgument } from "../commands";
|
import { RedisArgument } from "../RESP/types";
|
||||||
|
|
||||||
export enum PubSubType {
|
export enum PubSubType {
|
||||||
CHANNELS = 'CHANNELS',
|
CHANNELS = 'CHANNELS',
|
||||||
PATTERNS = 'PATTERNS',
|
PATTERNS = 'PATTERNS',
|
||||||
SHARDED = 'SHARDED'
|
SHARDED = 'SHARDED'
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMMANDS = {
|
const COMMANDS = {
|
||||||
[PubSubType.CHANNELS]: {
|
[PubSubType.CHANNELS]: {
|
||||||
subscribe: Buffer.from('subscribe'),
|
subscribe: Buffer.from('subscribe'),
|
||||||
unsubscribe: Buffer.from('unsubscribe'),
|
unsubscribe: Buffer.from('unsubscribe'),
|
||||||
message: Buffer.from('message')
|
message: Buffer.from('message')
|
||||||
},
|
},
|
||||||
[PubSubType.PATTERNS]: {
|
[PubSubType.PATTERNS]: {
|
||||||
subscribe: Buffer.from('psubscribe'),
|
subscribe: Buffer.from('psubscribe'),
|
||||||
unsubscribe: Buffer.from('punsubscribe'),
|
unsubscribe: Buffer.from('punsubscribe'),
|
||||||
message: Buffer.from('pmessage')
|
message: Buffer.from('pmessage')
|
||||||
},
|
},
|
||||||
[PubSubType.SHARDED]: {
|
[PubSubType.SHARDED]: {
|
||||||
subscribe: Buffer.from('ssubscribe'),
|
subscribe: Buffer.from('ssubscribe'),
|
||||||
unsubscribe: Buffer.from('sunsubscribe'),
|
unsubscribe: Buffer.from('sunsubscribe'),
|
||||||
message: Buffer.from('smessage')
|
message: Buffer.from('smessage')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PubSubListener<
|
export type PubSubListener<
|
||||||
RETURN_BUFFERS extends boolean = false
|
RETURN_BUFFERS extends boolean = false
|
||||||
> = <T extends RETURN_BUFFERS extends true ? Buffer : string>(message: T, channel: T) => unknown;
|
> = <T extends RETURN_BUFFERS extends true ? Buffer : string>(message: T, channel: T) => unknown;
|
||||||
|
|
||||||
export interface ChannelListeners {
|
export interface ChannelListeners {
|
||||||
unsubscribing: boolean;
|
unsubscribing: boolean;
|
||||||
buffers: Set<PubSubListener<true>>;
|
buffers: Set<PubSubListener<true>>;
|
||||||
strings: Set<PubSubListener<false>>;
|
strings: Set<PubSubListener<false>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PubSubTypeListeners = Map<string, ChannelListeners>;
|
export type PubSubTypeListeners = Map<string, ChannelListeners>;
|
||||||
@@ -39,370 +39,370 @@ export type PubSubTypeListeners = Map<string, ChannelListeners>;
|
|||||||
type Listeners = Record<PubSubType, PubSubTypeListeners>;
|
type Listeners = Record<PubSubType, PubSubTypeListeners>;
|
||||||
|
|
||||||
export type PubSubCommand = ReturnType<
|
export type PubSubCommand = ReturnType<
|
||||||
typeof PubSub.prototype.subscribe |
|
typeof PubSub.prototype.subscribe |
|
||||||
typeof PubSub.prototype.unsubscribe |
|
typeof PubSub.prototype.unsubscribe |
|
||||||
typeof PubSub.prototype.extendTypeListeners
|
typeof PubSub.prototype.extendTypeListeners
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export class PubSub {
|
export class PubSub {
|
||||||
static isStatusReply(reply: Array<Buffer>): boolean {
|
static isStatusReply(reply: Array<Buffer>): boolean {
|
||||||
return (
|
return (
|
||||||
COMMANDS[PubSubType.CHANNELS].subscribe.equals(reply[0]) ||
|
COMMANDS[PubSubType.CHANNELS].subscribe.equals(reply[0]) ||
|
||||||
COMMANDS[PubSubType.CHANNELS].unsubscribe.equals(reply[0]) ||
|
COMMANDS[PubSubType.CHANNELS].unsubscribe.equals(reply[0]) ||
|
||||||
COMMANDS[PubSubType.PATTERNS].subscribe.equals(reply[0]) ||
|
COMMANDS[PubSubType.PATTERNS].subscribe.equals(reply[0]) ||
|
||||||
COMMANDS[PubSubType.PATTERNS].unsubscribe.equals(reply[0]) ||
|
COMMANDS[PubSubType.PATTERNS].unsubscribe.equals(reply[0]) ||
|
||||||
COMMANDS[PubSubType.SHARDED].subscribe.equals(reply[0])
|
COMMANDS[PubSubType.SHARDED].subscribe.equals(reply[0])
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static isShardedUnsubscribe(reply: Array<Buffer>): boolean {
|
||||||
|
return COMMANDS[PubSubType.SHARDED].unsubscribe.equals(reply[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static #channelsArray(channels: string | Array<string>) {
|
||||||
|
return (Array.isArray(channels) ? channels : [channels]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static #listenersSet<T extends boolean>(
|
||||||
|
listeners: ChannelListeners,
|
||||||
|
returnBuffers?: T
|
||||||
|
) {
|
||||||
|
return (returnBuffers ? listeners.buffers : listeners.strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
#subscribing = 0;
|
||||||
|
|
||||||
|
#isActive = false;
|
||||||
|
|
||||||
|
get isActive() {
|
||||||
|
return this.#isActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
#listeners: Listeners = {
|
||||||
|
[PubSubType.CHANNELS]: new Map(),
|
||||||
|
[PubSubType.PATTERNS]: new Map(),
|
||||||
|
[PubSubType.SHARDED]: new Map()
|
||||||
|
};
|
||||||
|
|
||||||
|
subscribe<T extends boolean>(
|
||||||
|
type: PubSubType,
|
||||||
|
channels: string | Array<string>,
|
||||||
|
listener: PubSubListener<T>,
|
||||||
|
returnBuffers?: T
|
||||||
|
) {
|
||||||
|
const args: Array<RedisArgument> = [COMMANDS[type].subscribe],
|
||||||
|
channelsArray = PubSub.#channelsArray(channels);
|
||||||
|
for (const channel of channelsArray) {
|
||||||
|
let channelListeners = this.#listeners[type].get(channel);
|
||||||
|
if (!channelListeners || channelListeners.unsubscribing) {
|
||||||
|
args.push(channel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static isShardedUnsubscribe(reply: Array<Buffer>): boolean {
|
if (args.length === 1) {
|
||||||
return COMMANDS[PubSubType.SHARDED].unsubscribe.equals(reply[0]);
|
// all channels are already subscribed, add listeners without issuing a command
|
||||||
}
|
for (const channel of channelsArray) {
|
||||||
|
PubSub.#listenersSet(
|
||||||
static #channelsArray(channels: string | Array<string>) {
|
this.#listeners[type].get(channel)!,
|
||||||
return (Array.isArray(channels) ? channels : [channels]);
|
returnBuffers
|
||||||
|
).add(listener);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static #listenersSet<T extends boolean>(
|
this.#isActive = true;
|
||||||
listeners: ChannelListeners,
|
this.#subscribing++;
|
||||||
returnBuffers?: T
|
return {
|
||||||
) {
|
args,
|
||||||
return (returnBuffers ? listeners.buffers : listeners.strings);
|
channelsCounter: args.length - 1,
|
||||||
}
|
resolve: () => {
|
||||||
|
this.#subscribing--;
|
||||||
#subscribing = 0;
|
|
||||||
|
|
||||||
#isActive = false;
|
|
||||||
|
|
||||||
get isActive() {
|
|
||||||
return this.#isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
#listeners: Listeners = {
|
|
||||||
[PubSubType.CHANNELS]: new Map(),
|
|
||||||
[PubSubType.PATTERNS]: new Map(),
|
|
||||||
[PubSubType.SHARDED]: new Map()
|
|
||||||
};
|
|
||||||
|
|
||||||
subscribe<T extends boolean>(
|
|
||||||
type: PubSubType,
|
|
||||||
channels: string | Array<string>,
|
|
||||||
listener: PubSubListener<T>,
|
|
||||||
returnBuffers?: T
|
|
||||||
) {
|
|
||||||
const args: Array<RedisCommandArgument> = [COMMANDS[type].subscribe],
|
|
||||||
channelsArray = PubSub.#channelsArray(channels);
|
|
||||||
for (const channel of channelsArray) {
|
for (const channel of channelsArray) {
|
||||||
let channelListeners = this.#listeners[type].get(channel);
|
let listeners = this.#listeners[type].get(channel);
|
||||||
if (!channelListeners || channelListeners.unsubscribing) {
|
if (!listeners) {
|
||||||
args.push(channel);
|
listeners = {
|
||||||
}
|
unsubscribing: false,
|
||||||
}
|
buffers: new Set(),
|
||||||
|
strings: new Set()
|
||||||
if (args.length === 1) {
|
};
|
||||||
// all channels are already subscribed, add listeners without issuing a command
|
|
||||||
for (const channel of channelsArray) {
|
|
||||||
PubSub.#listenersSet(
|
|
||||||
this.#listeners[type].get(channel)!,
|
|
||||||
returnBuffers
|
|
||||||
).add(listener);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#isActive = true;
|
|
||||||
this.#subscribing++;
|
|
||||||
return {
|
|
||||||
args,
|
|
||||||
channelsCounter: args.length - 1,
|
|
||||||
resolve: () => {
|
|
||||||
this.#subscribing--;
|
|
||||||
for (const channel of channelsArray) {
|
|
||||||
let listeners = this.#listeners[type].get(channel);
|
|
||||||
if (!listeners) {
|
|
||||||
listeners = {
|
|
||||||
unsubscribing: false,
|
|
||||||
buffers: new Set(),
|
|
||||||
strings: new Set()
|
|
||||||
};
|
|
||||||
this.#listeners[type].set(channel, listeners);
|
|
||||||
}
|
|
||||||
|
|
||||||
PubSub.#listenersSet(listeners, returnBuffers).add(listener);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
reject: () => {
|
|
||||||
this.#subscribing--;
|
|
||||||
this.#updateIsActive();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extendChannelListeners(
|
|
||||||
type: PubSubType,
|
|
||||||
channel: string,
|
|
||||||
listeners: ChannelListeners
|
|
||||||
) {
|
|
||||||
if (!this.#extendChannelListeners(type, channel, listeners)) return;
|
|
||||||
|
|
||||||
this.#isActive = true;
|
|
||||||
this.#subscribing++;
|
|
||||||
return {
|
|
||||||
args: [
|
|
||||||
COMMANDS[type].subscribe,
|
|
||||||
channel
|
|
||||||
],
|
|
||||||
channelsCounter: 1,
|
|
||||||
resolve: () => this.#subscribing--,
|
|
||||||
reject: () => {
|
|
||||||
this.#subscribing--;
|
|
||||||
this.#updateIsActive();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#extendChannelListeners(
|
|
||||||
type: PubSubType,
|
|
||||||
channel: string,
|
|
||||||
listeners: ChannelListeners
|
|
||||||
) {
|
|
||||||
const existingListeners = this.#listeners[type].get(channel);
|
|
||||||
if (!existingListeners) {
|
|
||||||
this.#listeners[type].set(channel, listeners);
|
this.#listeners[type].set(channel, listeners);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
PubSub.#listenersSet(listeners, returnBuffers).add(listener);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
for (const listener of listeners.buffers) {
|
reject: () => {
|
||||||
existingListeners.buffers.add(listener);
|
this.#subscribing--;
|
||||||
}
|
|
||||||
|
|
||||||
for (const listener of listeners.strings) {
|
|
||||||
existingListeners.strings.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
extendTypeListeners(type: PubSubType, listeners: PubSubTypeListeners) {
|
|
||||||
const args: Array<RedisCommandArgument> = [COMMANDS[type].subscribe];
|
|
||||||
for (const [channel, channelListeners] of listeners) {
|
|
||||||
if (this.#extendChannelListeners(type, channel, channelListeners)) {
|
|
||||||
args.push(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length === 1) return;
|
|
||||||
|
|
||||||
this.#isActive = true;
|
|
||||||
this.#subscribing++;
|
|
||||||
return {
|
|
||||||
args,
|
|
||||||
channelsCounter: args.length - 1,
|
|
||||||
resolve: () => this.#subscribing--,
|
|
||||||
reject: () => {
|
|
||||||
this.#subscribing--;
|
|
||||||
this.#updateIsActive();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe<T extends boolean>(
|
|
||||||
type: PubSubType,
|
|
||||||
channels?: string | Array<string>,
|
|
||||||
listener?: PubSubListener<T>,
|
|
||||||
returnBuffers?: T
|
|
||||||
) {
|
|
||||||
const listeners = this.#listeners[type];
|
|
||||||
if (!channels) {
|
|
||||||
return this.#unsubscribeCommand(
|
|
||||||
[COMMANDS[type].unsubscribe],
|
|
||||||
// cannot use `this.#subscribed` because there might be some `SUBSCRIBE` commands in the queue
|
|
||||||
// cannot use `this.#subscribed + this.#subscribing` because some `SUBSCRIBE` commands might fail
|
|
||||||
NaN,
|
|
||||||
() => listeners.clear()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const channelsArray = PubSub.#channelsArray(channels);
|
|
||||||
if (!listener) {
|
|
||||||
return this.#unsubscribeCommand(
|
|
||||||
[COMMANDS[type].unsubscribe, ...channelsArray],
|
|
||||||
channelsArray.length,
|
|
||||||
() => {
|
|
||||||
for (const channel of channelsArray) {
|
|
||||||
listeners.delete(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const args: Array<RedisCommandArgument> = [COMMANDS[type].unsubscribe];
|
|
||||||
for (const channel of channelsArray) {
|
|
||||||
const sets = listeners.get(channel);
|
|
||||||
if (sets) {
|
|
||||||
let current,
|
|
||||||
other;
|
|
||||||
if (returnBuffers) {
|
|
||||||
current = sets.buffers;
|
|
||||||
other = sets.strings;
|
|
||||||
} else {
|
|
||||||
current = sets.strings;
|
|
||||||
other = sets.buffers;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentSize = current.has(listener) ? current.size - 1 : current.size;
|
|
||||||
if (currentSize !== 0 || other.size !== 0) continue;
|
|
||||||
sets.unsubscribing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
args.push(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length === 1) {
|
|
||||||
// all channels has other listeners,
|
|
||||||
// delete the listeners without issuing a command
|
|
||||||
for (const channel of channelsArray) {
|
|
||||||
PubSub.#listenersSet(
|
|
||||||
listeners.get(channel)!,
|
|
||||||
returnBuffers
|
|
||||||
).delete(listener);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.#unsubscribeCommand(
|
|
||||||
args,
|
|
||||||
args.length - 1,
|
|
||||||
() => {
|
|
||||||
for (const channel of channelsArray) {
|
|
||||||
const sets = listeners.get(channel);
|
|
||||||
if (!sets) continue;
|
|
||||||
|
|
||||||
(returnBuffers ? sets.buffers : sets.strings).delete(listener);
|
|
||||||
if (sets.buffers.size === 0 && sets.strings.size === 0) {
|
|
||||||
listeners.delete(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#unsubscribeCommand(
|
|
||||||
args: Array<RedisCommandArgument>,
|
|
||||||
channelsCounter: number,
|
|
||||||
removeListeners: () => void
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
args,
|
|
||||||
channelsCounter,
|
|
||||||
resolve: () => {
|
|
||||||
removeListeners();
|
|
||||||
this.#updateIsActive();
|
|
||||||
},
|
|
||||||
reject: undefined // use the same structure as `subscribe`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#updateIsActive() {
|
|
||||||
this.#isActive = (
|
|
||||||
this.#listeners[PubSubType.CHANNELS].size !== 0 ||
|
|
||||||
this.#listeners[PubSubType.PATTERNS].size !== 0 ||
|
|
||||||
this.#listeners[PubSubType.CHANNELS].size !== 0 ||
|
|
||||||
this.#subscribing !== 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.#isActive = false;
|
|
||||||
this.#subscribing = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
resubscribe(): Array<PubSubCommand> {
|
|
||||||
const commands = [];
|
|
||||||
for (const [type, listeners] of Object.entries(this.#listeners)) {
|
|
||||||
if (!listeners.size) continue;
|
|
||||||
|
|
||||||
this.#isActive = true;
|
|
||||||
this.#subscribing++;
|
|
||||||
const callback = () => this.#subscribing--;
|
|
||||||
commands.push({
|
|
||||||
args: [
|
|
||||||
COMMANDS[type as PubSubType].subscribe,
|
|
||||||
...listeners.keys()
|
|
||||||
],
|
|
||||||
channelsCounter: listeners.size,
|
|
||||||
resolve: callback,
|
|
||||||
reject: callback
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return commands;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMessageReply(reply: Array<Buffer>): boolean {
|
|
||||||
if (COMMANDS[PubSubType.CHANNELS].message.equals(reply[0])) {
|
|
||||||
this.#emitPubSubMessage(
|
|
||||||
PubSubType.CHANNELS,
|
|
||||||
reply[2],
|
|
||||||
reply[1]
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} else if (COMMANDS[PubSubType.PATTERNS].message.equals(reply[0])) {
|
|
||||||
this.#emitPubSubMessage(
|
|
||||||
PubSubType.PATTERNS,
|
|
||||||
reply[3],
|
|
||||||
reply[2],
|
|
||||||
reply[1]
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} else if (COMMANDS[PubSubType.SHARDED].message.equals(reply[0])) {
|
|
||||||
this.#emitPubSubMessage(
|
|
||||||
PubSubType.SHARDED,
|
|
||||||
reply[2],
|
|
||||||
reply[1]
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeShardedListeners(channel: string): ChannelListeners {
|
|
||||||
const listeners = this.#listeners[PubSubType.SHARDED].get(channel)!;
|
|
||||||
this.#listeners[PubSubType.SHARDED].delete(channel);
|
|
||||||
this.#updateIsActive();
|
this.#updateIsActive();
|
||||||
return listeners;
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extendChannelListeners(
|
||||||
|
type: PubSubType,
|
||||||
|
channel: string,
|
||||||
|
listeners: ChannelListeners
|
||||||
|
) {
|
||||||
|
if (!this.#extendChannelListeners(type, channel, listeners)) return;
|
||||||
|
|
||||||
|
this.#isActive = true;
|
||||||
|
this.#subscribing++;
|
||||||
|
return {
|
||||||
|
args: [
|
||||||
|
COMMANDS[type].subscribe,
|
||||||
|
channel
|
||||||
|
],
|
||||||
|
channelsCounter: 1,
|
||||||
|
resolve: () => this.#subscribing--,
|
||||||
|
reject: () => {
|
||||||
|
this.#subscribing--;
|
||||||
|
this.#updateIsActive();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#extendChannelListeners(
|
||||||
|
type: PubSubType,
|
||||||
|
channel: string,
|
||||||
|
listeners: ChannelListeners
|
||||||
|
) {
|
||||||
|
const existingListeners = this.#listeners[type].get(channel);
|
||||||
|
if (!existingListeners) {
|
||||||
|
this.#listeners[type].set(channel, listeners);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#emitPubSubMessage(
|
|
||||||
type: PubSubType,
|
|
||||||
message: Buffer,
|
|
||||||
channel: Buffer,
|
|
||||||
pattern?: Buffer
|
|
||||||
): void {
|
|
||||||
const keyString = (pattern ?? channel).toString(),
|
|
||||||
listeners = this.#listeners[type].get(keyString);
|
|
||||||
|
|
||||||
if (!listeners) return;
|
for (const listener of listeners.buffers) {
|
||||||
|
existingListeners.buffers.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
for (const listener of listeners.buffers) {
|
for (const listener of listeners.strings) {
|
||||||
listener(message, channel);
|
existingListeners.strings.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
extendTypeListeners(type: PubSubType, listeners: PubSubTypeListeners) {
|
||||||
|
const args: Array<RedisArgument> = [COMMANDS[type].subscribe];
|
||||||
|
for (const [channel, channelListeners] of listeners) {
|
||||||
|
if (this.#extendChannelListeners(type, channel, channelListeners)) {
|
||||||
|
args.push(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length === 1) return;
|
||||||
|
|
||||||
|
this.#isActive = true;
|
||||||
|
this.#subscribing++;
|
||||||
|
return {
|
||||||
|
args,
|
||||||
|
channelsCounter: args.length - 1,
|
||||||
|
resolve: () => this.#subscribing--,
|
||||||
|
reject: () => {
|
||||||
|
this.#subscribing--;
|
||||||
|
this.#updateIsActive();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe<T extends boolean>(
|
||||||
|
type: PubSubType,
|
||||||
|
channels?: string | Array<string>,
|
||||||
|
listener?: PubSubListener<T>,
|
||||||
|
returnBuffers?: T
|
||||||
|
) {
|
||||||
|
const listeners = this.#listeners[type];
|
||||||
|
if (!channels) {
|
||||||
|
return this.#unsubscribeCommand(
|
||||||
|
[COMMANDS[type].unsubscribe],
|
||||||
|
// cannot use `this.#subscribed` because there might be some `SUBSCRIBE` commands in the queue
|
||||||
|
// cannot use `this.#subscribed + this.#subscribing` because some `SUBSCRIBE` commands might fail
|
||||||
|
NaN,
|
||||||
|
() => listeners.clear()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelsArray = PubSub.#channelsArray(channels);
|
||||||
|
if (!listener) {
|
||||||
|
return this.#unsubscribeCommand(
|
||||||
|
[COMMANDS[type].unsubscribe, ...channelsArray],
|
||||||
|
channelsArray.length,
|
||||||
|
() => {
|
||||||
|
for (const channel of channelsArray) {
|
||||||
|
listeners.delete(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const args: Array<RedisArgument> = [COMMANDS[type].unsubscribe];
|
||||||
|
for (const channel of channelsArray) {
|
||||||
|
const sets = listeners.get(channel);
|
||||||
|
if (sets) {
|
||||||
|
let current,
|
||||||
|
other;
|
||||||
|
if (returnBuffers) {
|
||||||
|
current = sets.buffers;
|
||||||
|
other = sets.strings;
|
||||||
|
} else {
|
||||||
|
current = sets.strings;
|
||||||
|
other = sets.buffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!listeners.strings.size) return;
|
const currentSize = current.has(listener) ? current.size - 1 : current.size;
|
||||||
|
if (currentSize !== 0 || other.size !== 0) continue;
|
||||||
|
sets.unsubscribing = true;
|
||||||
|
}
|
||||||
|
|
||||||
const channelString = pattern ? channel.toString() : keyString,
|
args.push(channel);
|
||||||
messageString = channelString === '__redis__:invalidate' ?
|
}
|
||||||
// https://github.com/redis/redis/pull/7469
|
|
||||||
// https://github.com/redis/redis/issues/7463
|
if (args.length === 1) {
|
||||||
(message === null ? null : (message as any as Array<Buffer>).map(x => x.toString())) as any :
|
// all channels has other listeners,
|
||||||
message.toString();
|
// delete the listeners without issuing a command
|
||||||
for (const listener of listeners.strings) {
|
for (const channel of channelsArray) {
|
||||||
listener(messageString, channelString);
|
PubSub.#listenersSet(
|
||||||
|
listeners.get(channel)!,
|
||||||
|
returnBuffers
|
||||||
|
).delete(listener);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#unsubscribeCommand(
|
||||||
|
args,
|
||||||
|
args.length - 1,
|
||||||
|
() => {
|
||||||
|
for (const channel of channelsArray) {
|
||||||
|
const sets = listeners.get(channel);
|
||||||
|
if (!sets) continue;
|
||||||
|
|
||||||
|
(returnBuffers ? sets.buffers : sets.strings).delete(listener);
|
||||||
|
if (sets.buffers.size === 0 && sets.strings.size === 0) {
|
||||||
|
listeners.delete(channel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#unsubscribeCommand(
|
||||||
|
args: Array<RedisArgument>,
|
||||||
|
channelsCounter: number,
|
||||||
|
removeListeners: () => void
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
args,
|
||||||
|
channelsCounter,
|
||||||
|
resolve: () => {
|
||||||
|
removeListeners();
|
||||||
|
this.#updateIsActive();
|
||||||
|
},
|
||||||
|
reject: undefined // use the same structure as `subscribe`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateIsActive() {
|
||||||
|
this.#isActive = (
|
||||||
|
this.#listeners[PubSubType.CHANNELS].size !== 0 ||
|
||||||
|
this.#listeners[PubSubType.PATTERNS].size !== 0 ||
|
||||||
|
this.#listeners[PubSubType.CHANNELS].size !== 0 ||
|
||||||
|
this.#subscribing !== 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.#isActive = false;
|
||||||
|
this.#subscribing = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
resubscribe(): Array<PubSubCommand> {
|
||||||
|
const commands = [];
|
||||||
|
for (const [type, listeners] of Object.entries(this.#listeners)) {
|
||||||
|
if (!listeners.size) continue;
|
||||||
|
|
||||||
|
this.#isActive = true;
|
||||||
|
this.#subscribing++;
|
||||||
|
const callback = () => this.#subscribing--;
|
||||||
|
commands.push({
|
||||||
|
args: [
|
||||||
|
COMMANDS[type as PubSubType].subscribe,
|
||||||
|
...listeners.keys()
|
||||||
|
],
|
||||||
|
channelsCounter: listeners.size,
|
||||||
|
resolve: callback,
|
||||||
|
reject: callback
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTypeListeners(type: PubSubType): PubSubTypeListeners {
|
return commands;
|
||||||
return this.#listeners[type];
|
}
|
||||||
|
|
||||||
|
handleMessageReply(reply: Array<Buffer>): boolean {
|
||||||
|
if (COMMANDS[PubSubType.CHANNELS].message.equals(reply[0])) {
|
||||||
|
this.#emitPubSubMessage(
|
||||||
|
PubSubType.CHANNELS,
|
||||||
|
reply[2],
|
||||||
|
reply[1]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} else if (COMMANDS[PubSubType.PATTERNS].message.equals(reply[0])) {
|
||||||
|
this.#emitPubSubMessage(
|
||||||
|
PubSubType.PATTERNS,
|
||||||
|
reply[3],
|
||||||
|
reply[2],
|
||||||
|
reply[1]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} else if (COMMANDS[PubSubType.SHARDED].message.equals(reply[0])) {
|
||||||
|
this.#emitPubSubMessage(
|
||||||
|
PubSubType.SHARDED,
|
||||||
|
reply[2],
|
||||||
|
reply[1]
|
||||||
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeShardedListeners(channel: string): ChannelListeners {
|
||||||
|
const listeners = this.#listeners[PubSubType.SHARDED].get(channel)!;
|
||||||
|
this.#listeners[PubSubType.SHARDED].delete(channel);
|
||||||
|
this.#updateIsActive();
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
#emitPubSubMessage(
|
||||||
|
type: PubSubType,
|
||||||
|
message: Buffer,
|
||||||
|
channel: Buffer,
|
||||||
|
pattern?: Buffer
|
||||||
|
): void {
|
||||||
|
const keyString = (pattern ?? channel).toString(),
|
||||||
|
listeners = this.#listeners[type].get(keyString);
|
||||||
|
|
||||||
|
if (!listeners) return;
|
||||||
|
|
||||||
|
for (const listener of listeners.buffers) {
|
||||||
|
listener(message, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!listeners.strings.size) return;
|
||||||
|
|
||||||
|
const channelString = pattern ? channel.toString() : keyString,
|
||||||
|
messageString = channelString === '__redis__:invalidate' ?
|
||||||
|
// https://github.com/redis/redis/pull/7469
|
||||||
|
// https://github.com/redis/redis/issues/7463
|
||||||
|
(message === null ? null : (message as any as Array<Buffer>).map(x => x.toString())) as any :
|
||||||
|
message.toString();
|
||||||
|
for (const listener of listeners.strings) {
|
||||||
|
listener(messageString, channelString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTypeListeners(type: PubSubType): PubSubTypeListeners {
|
||||||
|
return this.#listeners[type];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,309 +1,309 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import * as net from 'net';
|
import * as net from 'net';
|
||||||
import * as tls from 'tls';
|
import * as tls from 'tls';
|
||||||
import { RedisCommandArguments } from '../commands';
|
|
||||||
import { ConnectionTimeoutError, ClientClosedError, SocketClosedUnexpectedlyError, ReconnectStrategyError } from '../errors';
|
import { ConnectionTimeoutError, ClientClosedError, SocketClosedUnexpectedlyError, ReconnectStrategyError } from '../errors';
|
||||||
import { promiseTimeout } from '../utils';
|
import { promiseTimeout } from '../utils';
|
||||||
|
import { RedisArgument } from '../RESP/types';
|
||||||
|
|
||||||
export interface RedisSocketCommonOptions {
|
export interface RedisSocketCommonOptions {
|
||||||
/**
|
/**
|
||||||
* Connection Timeout (in milliseconds)
|
* Connection Timeout (in milliseconds)
|
||||||
*/
|
*/
|
||||||
connectTimeout?: number;
|
connectTimeout?: number;
|
||||||
/**
|
/**
|
||||||
* Toggle [`Nagle's algorithm`](https://nodejs.org/api/net.html#net_socket_setnodelay_nodelay)
|
* Toggle [`Nagle's algorithm`](https://nodejs.org/api/net.html#net_socket_setnodelay_nodelay)
|
||||||
*/
|
*/
|
||||||
noDelay?: boolean;
|
noDelay?: boolean;
|
||||||
/**
|
/**
|
||||||
* Toggle [`keep-alive`](https://nodejs.org/api/net.html#net_socket_setkeepalive_enable_initialdelay)
|
* Toggle [`keep-alive`](https://nodejs.org/api/net.html#net_socket_setkeepalive_enable_initialdelay)
|
||||||
*/
|
*/
|
||||||
keepAlive?: number | false;
|
keepAlive?: number | false;
|
||||||
/**
|
/**
|
||||||
* When the socket closes unexpectedly (without calling `.quit()`/`.disconnect()`), the client uses `reconnectStrategy` to decide what to do. The following values are supported:
|
* When the socket closes unexpectedly (without calling `.quit()`/`.disconnect()`), the client uses `reconnectStrategy` to decide what to do. The following values are supported:
|
||||||
* 1. `false` -> do not reconnect, close the client and flush the command queue.
|
* 1. `false` -> do not reconnect, close the client and flush the command queue.
|
||||||
* 2. `number` -> wait for `X` milliseconds before reconnecting.
|
* 2. `number` -> wait for `X` milliseconds before reconnecting.
|
||||||
* 3. `(retries: number, cause: Error) => false | number | Error` -> `number` is the same as configuring a `number` directly, `Error` is the same as `false`, but with a custom error.
|
* 3. `(retries: number, cause: Error) => false | number | Error` -> `number` is the same as configuring a `number` directly, `Error` is the same as `false`, but with a custom error.
|
||||||
* Defaults to `retries => Math.min(retries * 50, 500)`
|
* Defaults to `retries => Math.min(retries * 50, 500)`
|
||||||
*/
|
*/
|
||||||
reconnectStrategy?: false | number | ((retries: number, cause: Error) => false | Error | number);
|
reconnectStrategy?: false | number | ((retries: number, cause: Error) => false | Error | number);
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedisNetSocketOptions = Partial<net.SocketConnectOpts> & {
|
type RedisNetSocketOptions = Partial<net.SocketConnectOpts> & {
|
||||||
tls?: false;
|
tls?: false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RedisTlsSocketOptions extends tls.ConnectionOptions {
|
export interface RedisTlsSocketOptions extends tls.ConnectionOptions {
|
||||||
tls: true;
|
tls: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RedisSocketOptions = RedisSocketCommonOptions & (RedisNetSocketOptions | RedisTlsSocketOptions);
|
export type RedisSocketOptions = RedisSocketCommonOptions & (RedisNetSocketOptions | RedisTlsSocketOptions);
|
||||||
|
|
||||||
interface CreateSocketReturn<T> {
|
interface CreateSocketReturn<T> {
|
||||||
connectEvent: string;
|
connectEvent: string;
|
||||||
socket: T;
|
socket: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RedisSocketInitiator = () => Promise<void>;
|
export type RedisSocketInitiator = () => Promise<void>;
|
||||||
|
|
||||||
export default class RedisSocket extends EventEmitter {
|
export default class RedisSocket extends EventEmitter {
|
||||||
static #initiateOptions(options?: RedisSocketOptions): RedisSocketOptions {
|
static #initiateOptions(options?: RedisSocketOptions): RedisSocketOptions {
|
||||||
options ??= {};
|
options ??= {};
|
||||||
if (!(options as net.IpcSocketConnectOpts).path) {
|
if (!(options as net.IpcSocketConnectOpts).path) {
|
||||||
(options as net.TcpSocketConnectOpts).port ??= 6379;
|
(options as net.TcpSocketConnectOpts).port ??= 6379;
|
||||||
(options as net.TcpSocketConnectOpts).host ??= 'localhost';
|
(options as net.TcpSocketConnectOpts).host ??= 'localhost';
|
||||||
}
|
|
||||||
|
|
||||||
options.connectTimeout ??= 5000;
|
|
||||||
options.keepAlive ??= 5000;
|
|
||||||
options.noDelay ??= true;
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static #isTlsSocket(options: RedisSocketOptions): options is RedisTlsSocketOptions {
|
options.connectTimeout ??= 5000;
|
||||||
return (options as RedisTlsSocketOptions).tls === true;
|
options.keepAlive ??= 5000;
|
||||||
}
|
options.noDelay ??= true;
|
||||||
|
|
||||||
readonly #initiator: RedisSocketInitiator;
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
readonly #options: RedisSocketOptions;
|
static #isTlsSocket(options: RedisSocketOptions): options is RedisTlsSocketOptions {
|
||||||
|
return (options as RedisTlsSocketOptions).tls === true;
|
||||||
|
}
|
||||||
|
|
||||||
#socket?: net.Socket | tls.TLSSocket;
|
readonly #initiator: RedisSocketInitiator;
|
||||||
|
|
||||||
#isOpen = false;
|
readonly #options: RedisSocketOptions;
|
||||||
|
|
||||||
get isOpen(): boolean {
|
#socket?: net.Socket | tls.TLSSocket;
|
||||||
return this.#isOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
#isReady = false;
|
#isOpen = false;
|
||||||
|
|
||||||
get isReady(): boolean {
|
get isOpen(): boolean {
|
||||||
return this.#isReady;
|
return this.#isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
// `writable.writableNeedDrain` was added in v15.2.0 and therefore can't be used
|
#isReady = false;
|
||||||
// https://nodejs.org/api/stream.html#stream_writable_writableneeddrain
|
|
||||||
#writableNeedDrain = false;
|
|
||||||
|
|
||||||
get writableNeedDrain(): boolean {
|
get isReady(): boolean {
|
||||||
return this.#writableNeedDrain;
|
return this.#isReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
#isSocketUnrefed = false;
|
// `writable.writableNeedDrain` was added in v15.2.0 and therefore can't be used
|
||||||
|
// https://nodejs.org/api/stream.html#stream_writable_writableneeddrain
|
||||||
|
#writableNeedDrain = false;
|
||||||
|
|
||||||
constructor(initiator: RedisSocketInitiator, options?: RedisSocketOptions) {
|
get writableNeedDrain(): boolean {
|
||||||
super();
|
return this.#writableNeedDrain;
|
||||||
|
}
|
||||||
|
|
||||||
this.#initiator = initiator;
|
#isSocketUnrefed = false;
|
||||||
this.#options = RedisSocket.#initiateOptions(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
#reconnectStrategy(retries: number, cause: Error) {
|
constructor(initiator: RedisSocketInitiator, options?: RedisSocketOptions) {
|
||||||
if (this.#options.reconnectStrategy === false) {
|
super();
|
||||||
return false;
|
|
||||||
} else if (typeof this.#options.reconnectStrategy === 'number') {
|
|
||||||
return this.#options.reconnectStrategy;
|
|
||||||
} else if (this.#options.reconnectStrategy) {
|
|
||||||
try {
|
|
||||||
const retryIn = this.#options.reconnectStrategy(retries, cause);
|
|
||||||
if (retryIn !== false && !(retryIn instanceof Error) && typeof retryIn !== 'number') {
|
|
||||||
throw new TypeError(`Reconnect strategy should return \`false | Error | number\`, got ${retryIn} instead`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return retryIn;
|
this.#initiator = initiator;
|
||||||
} catch (err) {
|
this.#options = RedisSocket.#initiateOptions(options);
|
||||||
this.emit('error', err);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.min(retries * 50, 500);
|
#reconnectStrategy(retries: number, cause: Error) {
|
||||||
}
|
if (this.#options.reconnectStrategy === false) {
|
||||||
|
return false;
|
||||||
#shouldReconnect(retries: number, cause: Error) {
|
} else if (typeof this.#options.reconnectStrategy === 'number') {
|
||||||
const retryIn = this.#reconnectStrategy(retries, cause);
|
return this.#options.reconnectStrategy;
|
||||||
if (retryIn === false) {
|
} else if (this.#options.reconnectStrategy) {
|
||||||
this.#isOpen = false;
|
try {
|
||||||
this.emit('error', cause);
|
const retryIn = this.#options.reconnectStrategy(retries, cause);
|
||||||
return cause;
|
if (retryIn !== false && !(retryIn instanceof Error) && typeof retryIn !== 'number') {
|
||||||
} else if (retryIn instanceof Error) {
|
throw new TypeError(`Reconnect strategy should return \`false | Error | number\`, got ${retryIn} instead`);
|
||||||
this.#isOpen = false;
|
|
||||||
this.emit('error', cause);
|
|
||||||
return new ReconnectStrategyError(retryIn, cause);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return retryIn;
|
return retryIn;
|
||||||
}
|
} catch (err) {
|
||||||
|
|
||||||
async connect(): Promise<void> {
|
|
||||||
if (this.#isOpen) {
|
|
||||||
throw new Error('Socket already opened');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#isOpen = true;
|
|
||||||
return this.#connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
async #connect(): Promise<void> {
|
|
||||||
let retries = 0;
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
this.#socket = await this.#createSocket();
|
|
||||||
this.#writableNeedDrain = false;
|
|
||||||
this.emit('connect');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.#initiator();
|
|
||||||
} catch (err) {
|
|
||||||
this.#socket.destroy();
|
|
||||||
this.#socket = undefined;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
this.#isReady = true;
|
|
||||||
this.emit('ready');
|
|
||||||
} catch (err) {
|
|
||||||
const retryIn = this.#shouldReconnect(retries++, err as Error);
|
|
||||||
if (typeof retryIn !== 'number') {
|
|
||||||
throw retryIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('error', err);
|
|
||||||
await promiseTimeout(retryIn);
|
|
||||||
this.emit('reconnecting');
|
|
||||||
}
|
|
||||||
} while (this.#isOpen && !this.#isReady);
|
|
||||||
}
|
|
||||||
|
|
||||||
#createSocket(): Promise<net.Socket | tls.TLSSocket> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const { connectEvent, socket } = RedisSocket.#isTlsSocket(this.#options) ?
|
|
||||||
this.#createTlsSocket() :
|
|
||||||
this.#createNetSocket();
|
|
||||||
|
|
||||||
if (this.#options.connectTimeout) {
|
|
||||||
socket.setTimeout(this.#options.connectTimeout, () => socket.destroy(new ConnectionTimeoutError()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.#isSocketUnrefed) {
|
|
||||||
socket.unref();
|
|
||||||
}
|
|
||||||
|
|
||||||
socket
|
|
||||||
.setNoDelay(this.#options.noDelay)
|
|
||||||
.once('error', reject)
|
|
||||||
.once(connectEvent, () => {
|
|
||||||
socket
|
|
||||||
.setTimeout(0)
|
|
||||||
// https://github.com/nodejs/node/issues/31663
|
|
||||||
.setKeepAlive(this.#options.keepAlive !== false, this.#options.keepAlive || 0)
|
|
||||||
.off('error', reject)
|
|
||||||
.once('error', (err: Error) => this.#onSocketError(err))
|
|
||||||
.once('close', hadError => {
|
|
||||||
if (!hadError && this.#isOpen && this.#socket === socket) {
|
|
||||||
this.#onSocketError(new SocketClosedUnexpectedlyError());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('drain', () => {
|
|
||||||
this.#writableNeedDrain = false;
|
|
||||||
this.emit('drain');
|
|
||||||
})
|
|
||||||
.on('data', data => this.emit('data', data));
|
|
||||||
|
|
||||||
resolve(socket);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#createNetSocket(): CreateSocketReturn<net.Socket> {
|
|
||||||
return {
|
|
||||||
connectEvent: 'connect',
|
|
||||||
socket: net.connect(this.#options as net.NetConnectOpts) // TODO
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#createTlsSocket(): CreateSocketReturn<tls.TLSSocket> {
|
|
||||||
return {
|
|
||||||
connectEvent: 'secureConnect',
|
|
||||||
socket: tls.connect(this.#options as tls.ConnectionOptions) // TODO
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#onSocketError(err: Error): void {
|
|
||||||
this.#isReady = false;
|
|
||||||
this.emit('error', err);
|
this.emit('error', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.#isOpen || typeof this.#shouldReconnect(0, err) !== 'number') return;
|
return Math.min(retries * 50, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
#shouldReconnect(retries: number, cause: Error) {
|
||||||
|
const retryIn = this.#reconnectStrategy(retries, cause);
|
||||||
|
if (retryIn === false) {
|
||||||
|
this.#isOpen = false;
|
||||||
|
this.emit('error', cause);
|
||||||
|
return cause;
|
||||||
|
} else if (retryIn instanceof Error) {
|
||||||
|
this.#isOpen = false;
|
||||||
|
this.emit('error', cause);
|
||||||
|
return new ReconnectStrategyError(retryIn, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retryIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(): Promise<void> {
|
||||||
|
if (this.#isOpen) {
|
||||||
|
throw new Error('Socket already opened');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#isOpen = true;
|
||||||
|
return this.#connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
async #connect(): Promise<void> {
|
||||||
|
let retries = 0;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
this.#socket = await this.#createSocket();
|
||||||
|
this.#writableNeedDrain = false;
|
||||||
|
this.emit('connect');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.#initiator();
|
||||||
|
} catch (err) {
|
||||||
|
this.#socket.destroy();
|
||||||
|
this.#socket = undefined;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
this.#isReady = true;
|
||||||
|
this.emit('ready');
|
||||||
|
} catch (err) {
|
||||||
|
const retryIn = this.#shouldReconnect(retries++, err as Error);
|
||||||
|
if (typeof retryIn !== 'number') {
|
||||||
|
throw retryIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('error', err);
|
||||||
|
await promiseTimeout(retryIn);
|
||||||
this.emit('reconnecting');
|
this.emit('reconnecting');
|
||||||
this.#connect().catch(() => {
|
}
|
||||||
// the error was already emitted, silently ignore it
|
} while (this.#isOpen && !this.#isReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
#createSocket(): Promise<net.Socket | tls.TLSSocket> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const { connectEvent, socket } = RedisSocket.#isTlsSocket(this.#options) ?
|
||||||
|
this.#createTlsSocket() :
|
||||||
|
this.#createNetSocket();
|
||||||
|
|
||||||
|
if (this.#options.connectTimeout) {
|
||||||
|
socket.setTimeout(this.#options.connectTimeout, () => socket.destroy(new ConnectionTimeoutError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#isSocketUnrefed) {
|
||||||
|
socket.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
socket
|
||||||
|
.setNoDelay(this.#options.noDelay)
|
||||||
|
.once('error', reject)
|
||||||
|
.once(connectEvent, () => {
|
||||||
|
socket
|
||||||
|
.setTimeout(0)
|
||||||
|
// https://github.com/nodejs/node/issues/31663
|
||||||
|
.setKeepAlive(this.#options.keepAlive !== false, this.#options.keepAlive || 0)
|
||||||
|
.off('error', reject)
|
||||||
|
.once('error', (err: Error) => this.#onSocketError(err))
|
||||||
|
.once('close', hadError => {
|
||||||
|
if (!hadError && this.#isReady && this.#socket === socket) {
|
||||||
|
this.#onSocketError(new SocketClosedUnexpectedlyError());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('drain', () => {
|
||||||
|
this.#writableNeedDrain = false;
|
||||||
|
this.emit('drain');
|
||||||
|
})
|
||||||
|
.on('data', data => this.emit('data', data));
|
||||||
|
|
||||||
|
resolve(socket);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#createNetSocket(): CreateSocketReturn<net.Socket> {
|
||||||
|
return {
|
||||||
|
connectEvent: 'connect',
|
||||||
|
socket: net.connect(this.#options as net.NetConnectOpts) // TODO
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#createTlsSocket(): CreateSocketReturn<tls.TLSSocket> {
|
||||||
|
return {
|
||||||
|
connectEvent: 'secureConnect',
|
||||||
|
socket: tls.connect(this.#options as tls.ConnectionOptions) // TODO
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#onSocketError(err: Error): void {
|
||||||
|
this.#isReady = false;
|
||||||
|
this.emit('error', err);
|
||||||
|
|
||||||
|
if (!this.#isOpen || typeof this.#shouldReconnect(0, err) !== 'number') return;
|
||||||
|
|
||||||
|
this.emit('reconnecting');
|
||||||
|
this.#connect().catch(() => {
|
||||||
|
// the error was already emitted, silently ignore it
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommand(args: Array<RedisArgument>): void {
|
||||||
|
if (!this.#socket) {
|
||||||
|
throw new ClientClosedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
writeCommand(args: RedisCommandArguments): void {
|
for (const toWrite of args) {
|
||||||
if (!this.#socket) {
|
this.#writableNeedDrain = !this.#socket.write(toWrite);
|
||||||
throw new ClientClosedError();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const toWrite of args) {
|
disconnect(): void {
|
||||||
this.#writableNeedDrain = !this.#socket.write(toWrite);
|
if (!this.#isOpen) {
|
||||||
}
|
throw new ClientClosedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect(): void {
|
this.#isOpen = false;
|
||||||
if (!this.#isOpen) {
|
this.#disconnect();
|
||||||
throw new ClientClosedError();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.#isOpen = false;
|
#disconnect(): void {
|
||||||
this.#disconnect();
|
this.#isReady = false;
|
||||||
|
|
||||||
|
if (this.#socket) {
|
||||||
|
this.#socket.destroy();
|
||||||
|
this.#socket = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
#disconnect(): void {
|
this.emit('end');
|
||||||
this.#isReady = false;
|
}
|
||||||
|
|
||||||
if (this.#socket) {
|
async quit<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
this.#socket.destroy();
|
if (!this.#isOpen) {
|
||||||
this.#socket = undefined;
|
throw new ClientClosedError();
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('end');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async quit<T>(fn: () => Promise<T>): Promise<T> {
|
this.#isOpen = false;
|
||||||
if (!this.#isOpen) {
|
const reply = await fn();
|
||||||
throw new ClientClosedError();
|
this.#disconnect();
|
||||||
}
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
this.#isOpen = false;
|
#isCorked = false;
|
||||||
const reply = await fn();
|
|
||||||
this.#disconnect();
|
cork(): void {
|
||||||
return reply;
|
if (!this.#socket || this.#isCorked) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#isCorked = false;
|
this.#socket.cork();
|
||||||
|
this.#isCorked = true;
|
||||||
|
|
||||||
cork(): void {
|
queueMicrotask(() => {
|
||||||
if (!this.#socket || this.#isCorked) {
|
this.#socket?.uncork();
|
||||||
return;
|
this.#isCorked = false;
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.#socket.cork();
|
ref(): void {
|
||||||
this.#isCorked = true;
|
this.#isSocketUnrefed = false;
|
||||||
|
this.#socket?.ref();
|
||||||
|
}
|
||||||
|
|
||||||
queueMicrotask(() => {
|
unref(): void {
|
||||||
this.#socket?.uncork();
|
this.#isSocketUnrefed = true;
|
||||||
this.#isCorked = false;
|
this.#socket?.unref();
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ref(): void {
|
|
||||||
this.#isSocketUnrefed = false;
|
|
||||||
this.#socket?.ref();
|
|
||||||
}
|
|
||||||
|
|
||||||
unref(): void {
|
|
||||||
this.#isSocketUnrefed = true;
|
|
||||||
this.#socket?.unref();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,637 +0,0 @@
|
|||||||
|
|
||||||
import * as APPEND from '../commands/APPEND';
|
|
||||||
import * as BITCOUNT from '../commands/BITCOUNT';
|
|
||||||
import * as BITFIELD_RO from '../commands/BITFIELD_RO';
|
|
||||||
import * as BITFIELD from '../commands/BITFIELD';
|
|
||||||
import * as BITOP from '../commands/BITOP';
|
|
||||||
import * as BITPOS from '../commands/BITPOS';
|
|
||||||
import * as BLMOVE from '../commands/BLMOVE';
|
|
||||||
import * as BLMPOP from '../commands/BLMPOP';
|
|
||||||
import * as BLPOP from '../commands/BLPOP';
|
|
||||||
import * as BRPOP from '../commands/BRPOP';
|
|
||||||
import * as BRPOPLPUSH from '../commands/BRPOPLPUSH';
|
|
||||||
import * as BZMPOP from '../commands/BZMPOP';
|
|
||||||
import * as BZPOPMAX from '../commands/BZPOPMAX';
|
|
||||||
import * as BZPOPMIN from '../commands/BZPOPMIN';
|
|
||||||
import * as COPY from '../commands/COPY';
|
|
||||||
import * as DECR from '../commands/DECR';
|
|
||||||
import * as DECRBY from '../commands/DECRBY';
|
|
||||||
import * as DEL from '../commands/DEL';
|
|
||||||
import * as DUMP from '../commands/DUMP';
|
|
||||||
import * as EVAL_RO from '../commands/EVAL_RO';
|
|
||||||
import * as EVAL from '../commands/EVAL';
|
|
||||||
import * as EVALSHA_RO from '../commands/EVALSHA_RO';
|
|
||||||
import * as EVALSHA from '../commands/EVALSHA';
|
|
||||||
import * as EXISTS from '../commands/EXISTS';
|
|
||||||
import * as EXPIRE from '../commands/EXPIRE';
|
|
||||||
import * as EXPIREAT from '../commands/EXPIREAT';
|
|
||||||
import * as EXPIRETIME from '../commands/EXPIRETIME';
|
|
||||||
import * as FCALL_RO from '../commands/FCALL_RO';
|
|
||||||
import * as FCALL from '../commands/FCALL';
|
|
||||||
import * as GEOADD from '../commands/GEOADD';
|
|
||||||
import * as GEODIST from '../commands/GEODIST';
|
|
||||||
import * as GEOHASH from '../commands/GEOHASH';
|
|
||||||
import * as GEOPOS from '../commands/GEOPOS';
|
|
||||||
import * as GEORADIUS_RO_WITH from '../commands/GEORADIUS_RO_WITH';
|
|
||||||
import * as GEORADIUS_RO from '../commands/GEORADIUS_RO';
|
|
||||||
import * as GEORADIUS_WITH from '../commands/GEORADIUS_WITH';
|
|
||||||
import * as GEORADIUS from '../commands/GEORADIUS';
|
|
||||||
import * as GEORADIUSBYMEMBER_RO_WITH from '../commands/GEORADIUSBYMEMBER_RO_WITH';
|
|
||||||
import * as GEORADIUSBYMEMBER_RO from '../commands/GEORADIUSBYMEMBER_RO';
|
|
||||||
import * as GEORADIUSBYMEMBER_WITH from '../commands/GEORADIUSBYMEMBER_WITH';
|
|
||||||
import * as GEORADIUSBYMEMBER from '../commands/GEORADIUSBYMEMBER';
|
|
||||||
import * as GEORADIUSBYMEMBERSTORE from '../commands/GEORADIUSBYMEMBERSTORE';
|
|
||||||
import * as GEORADIUSSTORE from '../commands/GEORADIUSSTORE';
|
|
||||||
import * as GEOSEARCH_WITH from '../commands/GEOSEARCH_WITH';
|
|
||||||
import * as GEOSEARCH from '../commands/GEOSEARCH';
|
|
||||||
import * as GEOSEARCHSTORE from '../commands/GEOSEARCHSTORE';
|
|
||||||
import * as GET from '../commands/GET';
|
|
||||||
import * as GETBIT from '../commands/GETBIT';
|
|
||||||
import * as GETDEL from '../commands/GETDEL';
|
|
||||||
import * as GETEX from '../commands/GETEX';
|
|
||||||
import * as GETRANGE from '../commands/GETRANGE';
|
|
||||||
import * as GETSET from '../commands/GETSET';
|
|
||||||
import * as HDEL from '../commands/HDEL';
|
|
||||||
import * as HEXISTS from '../commands/HEXISTS';
|
|
||||||
import * as HGET from '../commands/HGET';
|
|
||||||
import * as HGETALL from '../commands/HGETALL';
|
|
||||||
import * as HINCRBY from '../commands/HINCRBY';
|
|
||||||
import * as HINCRBYFLOAT from '../commands/HINCRBYFLOAT';
|
|
||||||
import * as HKEYS from '../commands/HKEYS';
|
|
||||||
import * as HLEN from '../commands/HLEN';
|
|
||||||
import * as HMGET from '../commands/HMGET';
|
|
||||||
import * as HRANDFIELD_COUNT_WITHVALUES from '../commands/HRANDFIELD_COUNT_WITHVALUES';
|
|
||||||
import * as HRANDFIELD_COUNT from '../commands/HRANDFIELD_COUNT';
|
|
||||||
import * as HRANDFIELD from '../commands/HRANDFIELD';
|
|
||||||
import * as HSCAN from '../commands/HSCAN';
|
|
||||||
import * as HSET from '../commands/HSET';
|
|
||||||
import * as HSETNX from '../commands/HSETNX';
|
|
||||||
import * as HSTRLEN from '../commands/HSTRLEN';
|
|
||||||
import * as HVALS from '../commands/HVALS';
|
|
||||||
import * as INCR from '../commands/INCR';
|
|
||||||
import * as INCRBY from '../commands/INCRBY';
|
|
||||||
import * as INCRBYFLOAT from '../commands/INCRBYFLOAT';
|
|
||||||
import * as LCS_IDX_WITHMATCHLEN from '../commands/LCS_IDX_WITHMATCHLEN';
|
|
||||||
import * as LCS_IDX from '../commands/LCS_IDX';
|
|
||||||
import * as LCS_LEN from '../commands/LCS_LEN';
|
|
||||||
import * as LCS from '../commands/LCS';
|
|
||||||
import * as LINDEX from '../commands/LINDEX';
|
|
||||||
import * as LINSERT from '../commands/LINSERT';
|
|
||||||
import * as LLEN from '../commands/LLEN';
|
|
||||||
import * as LMOVE from '../commands/LMOVE';
|
|
||||||
import * as LMPOP from '../commands/LMPOP';
|
|
||||||
import * as LPOP_COUNT from '../commands/LPOP_COUNT';
|
|
||||||
import * as LPOP from '../commands/LPOP';
|
|
||||||
import * as LPOS_COUNT from '../commands/LPOS_COUNT';
|
|
||||||
import * as LPOS from '../commands/LPOS';
|
|
||||||
import * as LPUSH from '../commands/LPUSH';
|
|
||||||
import * as LPUSHX from '../commands/LPUSHX';
|
|
||||||
import * as LRANGE from '../commands/LRANGE';
|
|
||||||
import * as LREM from '../commands/LREM';
|
|
||||||
import * as LSET from '../commands/LSET';
|
|
||||||
import * as LTRIM from '../commands/LTRIM';
|
|
||||||
import * as MGET from '../commands/MGET';
|
|
||||||
import * as MIGRATE from '../commands/MIGRATE';
|
|
||||||
import * as MSET from '../commands/MSET';
|
|
||||||
import * as MSETNX from '../commands/MSETNX';
|
|
||||||
import * as OBJECT_ENCODING from '../commands/OBJECT_ENCODING';
|
|
||||||
import * as OBJECT_FREQ from '../commands/OBJECT_FREQ';
|
|
||||||
import * as OBJECT_IDLETIME from '../commands/OBJECT_IDLETIME';
|
|
||||||
import * as OBJECT_REFCOUNT from '../commands/OBJECT_REFCOUNT';
|
|
||||||
import * as PERSIST from '../commands/PERSIST';
|
|
||||||
import * as PEXPIRE from '../commands/PEXPIRE';
|
|
||||||
import * as PEXPIREAT from '../commands/PEXPIREAT';
|
|
||||||
import * as PEXPIRETIME from '../commands/PEXPIRETIME';
|
|
||||||
import * as PFADD from '../commands/PFADD';
|
|
||||||
import * as PFCOUNT from '../commands/PFCOUNT';
|
|
||||||
import * as PFMERGE from '../commands/PFMERGE';
|
|
||||||
import * as PSETEX from '../commands/PSETEX';
|
|
||||||
import * as PTTL from '../commands/PTTL';
|
|
||||||
import * as PUBLISH from '../commands/PUBLISH';
|
|
||||||
import * as RENAME from '../commands/RENAME';
|
|
||||||
import * as RENAMENX from '../commands/RENAMENX';
|
|
||||||
import * as RPOP_COUNT from '../commands/RPOP_COUNT';
|
|
||||||
import * as RPOP from '../commands/RPOP';
|
|
||||||
import * as RPOPLPUSH from '../commands/RPOPLPUSH';
|
|
||||||
import * as RPUSH from '../commands/RPUSH';
|
|
||||||
import * as RPUSHX from '../commands/RPUSHX';
|
|
||||||
import * as SADD from '../commands/SADD';
|
|
||||||
import * as SCARD from '../commands/SCARD';
|
|
||||||
import * as SDIFF from '../commands/SDIFF';
|
|
||||||
import * as SDIFFSTORE from '../commands/SDIFFSTORE';
|
|
||||||
import * as SET from '../commands/SET';
|
|
||||||
import * as SETBIT from '../commands/SETBIT';
|
|
||||||
import * as SETEX from '../commands/SETEX';
|
|
||||||
import * as SETNX from '../commands/SETNX';
|
|
||||||
import * as SETRANGE from '../commands/SETRANGE';
|
|
||||||
import * as SINTER from '../commands/SINTER';
|
|
||||||
import * as SINTERCARD from '../commands/SINTERCARD';
|
|
||||||
import * as SINTERSTORE from '../commands/SINTERSTORE';
|
|
||||||
import * as SISMEMBER from '../commands/SISMEMBER';
|
|
||||||
import * as SMEMBERS from '../commands/SMEMBERS';
|
|
||||||
import * as SMISMEMBER from '../commands/SMISMEMBER';
|
|
||||||
import * as SMOVE from '../commands/SMOVE';
|
|
||||||
import * as SORT_RO from '../commands/SORT_RO';
|
|
||||||
import * as SORT_STORE from '../commands/SORT_STORE';
|
|
||||||
import * as SORT from '../commands/SORT';
|
|
||||||
import * as SPOP from '../commands/SPOP';
|
|
||||||
import * as SPUBLISH from '../commands/SPUBLISH';
|
|
||||||
import * as SRANDMEMBER_COUNT from '../commands/SRANDMEMBER_COUNT';
|
|
||||||
import * as SRANDMEMBER from '../commands/SRANDMEMBER';
|
|
||||||
import * as SREM from '../commands/SREM';
|
|
||||||
import * as SSCAN from '../commands/SSCAN';
|
|
||||||
import * as STRLEN from '../commands/STRLEN';
|
|
||||||
import * as SUNION from '../commands/SUNION';
|
|
||||||
import * as SUNIONSTORE from '../commands/SUNIONSTORE';
|
|
||||||
import * as TOUCH from '../commands/TOUCH';
|
|
||||||
import * as TTL from '../commands/TTL';
|
|
||||||
import * as TYPE from '../commands/TYPE';
|
|
||||||
import * as UNLINK from '../commands/UNLINK';
|
|
||||||
import * as WATCH from '../commands/WATCH';
|
|
||||||
import * as XACK from '../commands/XACK';
|
|
||||||
import * as XADD from '../commands/XADD';
|
|
||||||
import * as XAUTOCLAIM_JUSTID from '../commands/XAUTOCLAIM_JUSTID';
|
|
||||||
import * as XAUTOCLAIM from '../commands/XAUTOCLAIM';
|
|
||||||
import * as XCLAIM_JUSTID from '../commands/XCLAIM_JUSTID';
|
|
||||||
import * as XCLAIM from '../commands/XCLAIM';
|
|
||||||
import * as XDEL from '../commands/XDEL';
|
|
||||||
import * as XGROUP_CREATE from '../commands/XGROUP_CREATE';
|
|
||||||
import * as XGROUP_CREATECONSUMER from '../commands/XGROUP_CREATECONSUMER';
|
|
||||||
import * as XGROUP_DELCONSUMER from '../commands/XGROUP_DELCONSUMER';
|
|
||||||
import * as XGROUP_DESTROY from '../commands/XGROUP_DESTROY';
|
|
||||||
import * as XGROUP_SETID from '../commands/XGROUP_SETID';
|
|
||||||
import * as XINFO_CONSUMERS from '../commands/XINFO_CONSUMERS';
|
|
||||||
import * as XINFO_GROUPS from '../commands/XINFO_GROUPS';
|
|
||||||
import * as XINFO_STREAM from '../commands/XINFO_STREAM';
|
|
||||||
import * as XLEN from '../commands/XLEN';
|
|
||||||
import * as XPENDING_RANGE from '../commands/XPENDING_RANGE';
|
|
||||||
import * as XPENDING from '../commands/XPENDING';
|
|
||||||
import * as XRANGE from '../commands/XRANGE';
|
|
||||||
import * as XREAD from '../commands/XREAD';
|
|
||||||
import * as XREADGROUP from '../commands/XREADGROUP';
|
|
||||||
import * as XREVRANGE from '../commands/XREVRANGE';
|
|
||||||
import * as XSETID from '../commands/XSETID';
|
|
||||||
import * as XTRIM from '../commands/XTRIM';
|
|
||||||
import * as ZADD from '../commands/ZADD';
|
|
||||||
import * as ZCARD from '../commands/ZCARD';
|
|
||||||
import * as ZCOUNT from '../commands/ZCOUNT';
|
|
||||||
import * as ZDIFF_WITHSCORES from '../commands/ZDIFF_WITHSCORES';
|
|
||||||
import * as ZDIFF from '../commands/ZDIFF';
|
|
||||||
import * as ZDIFFSTORE from '../commands/ZDIFFSTORE';
|
|
||||||
import * as ZINCRBY from '../commands/ZINCRBY';
|
|
||||||
import * as ZINTER_WITHSCORES from '../commands/ZINTER_WITHSCORES';
|
|
||||||
import * as ZINTER from '../commands/ZINTER';
|
|
||||||
import * as ZINTERCARD from '../commands/ZINTERCARD';
|
|
||||||
import * as ZINTERSTORE from '../commands/ZINTERSTORE';
|
|
||||||
import * as ZLEXCOUNT from '../commands/ZLEXCOUNT';
|
|
||||||
import * as ZMPOP from '../commands/ZMPOP';
|
|
||||||
import * as ZMSCORE from '../commands/ZMSCORE';
|
|
||||||
import * as ZPOPMAX_COUNT from '../commands/ZPOPMAX_COUNT';
|
|
||||||
import * as ZPOPMAX from '../commands/ZPOPMAX';
|
|
||||||
import * as ZPOPMIN_COUNT from '../commands/ZPOPMIN_COUNT';
|
|
||||||
import * as ZPOPMIN from '../commands/ZPOPMIN';
|
|
||||||
import * as ZRANDMEMBER_COUNT_WITHSCORES from '../commands/ZRANDMEMBER_COUNT_WITHSCORES';
|
|
||||||
import * as ZRANDMEMBER_COUNT from '../commands/ZRANDMEMBER_COUNT';
|
|
||||||
import * as ZRANDMEMBER from '../commands/ZRANDMEMBER';
|
|
||||||
import * as ZRANGE_WITHSCORES from '../commands/ZRANGE_WITHSCORES';
|
|
||||||
import * as ZRANGE from '../commands/ZRANGE';
|
|
||||||
import * as ZRANGEBYLEX from '../commands/ZRANGEBYLEX';
|
|
||||||
import * as ZRANGEBYSCORE_WITHSCORES from '../commands/ZRANGEBYSCORE_WITHSCORES';
|
|
||||||
import * as ZRANGEBYSCORE from '../commands/ZRANGEBYSCORE';
|
|
||||||
import * as ZRANGESTORE from '../commands/ZRANGESTORE';
|
|
||||||
import * as ZRANK from '../commands/ZRANK';
|
|
||||||
import * as ZREM from '../commands/ZREM';
|
|
||||||
import * as ZREMRANGEBYLEX from '../commands/ZREMRANGEBYLEX';
|
|
||||||
import * as ZREMRANGEBYRANK from '../commands/ZREMRANGEBYRANK';
|
|
||||||
import * as ZREMRANGEBYSCORE from '../commands/ZREMRANGEBYSCORE';
|
|
||||||
import * as ZREVRANK from '../commands/ZREVRANK';
|
|
||||||
import * as ZSCAN from '../commands/ZSCAN';
|
|
||||||
import * as ZSCORE from '../commands/ZSCORE';
|
|
||||||
import * as ZUNION_WITHSCORES from '../commands/ZUNION_WITHSCORES';
|
|
||||||
import * as ZUNION from '../commands/ZUNION';
|
|
||||||
import * as ZUNIONSTORE from '../commands/ZUNIONSTORE';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
APPEND,
|
|
||||||
append: APPEND,
|
|
||||||
BITCOUNT,
|
|
||||||
bitCount: BITCOUNT,
|
|
||||||
BITFIELD_RO,
|
|
||||||
bitFieldRo: BITFIELD_RO,
|
|
||||||
BITFIELD,
|
|
||||||
bitField: BITFIELD,
|
|
||||||
BITOP,
|
|
||||||
bitOp: BITOP,
|
|
||||||
BITPOS,
|
|
||||||
bitPos: BITPOS,
|
|
||||||
BLMOVE,
|
|
||||||
blMove: BLMOVE,
|
|
||||||
BLMPOP,
|
|
||||||
blmPop: BLMPOP,
|
|
||||||
BLPOP,
|
|
||||||
blPop: BLPOP,
|
|
||||||
BRPOP,
|
|
||||||
brPop: BRPOP,
|
|
||||||
BRPOPLPUSH,
|
|
||||||
brPopLPush: BRPOPLPUSH,
|
|
||||||
BZMPOP,
|
|
||||||
bzmPop: BZMPOP,
|
|
||||||
BZPOPMAX,
|
|
||||||
bzPopMax: BZPOPMAX,
|
|
||||||
BZPOPMIN,
|
|
||||||
bzPopMin: BZPOPMIN,
|
|
||||||
COPY,
|
|
||||||
copy: COPY,
|
|
||||||
DECR,
|
|
||||||
decr: DECR,
|
|
||||||
DECRBY,
|
|
||||||
decrBy: DECRBY,
|
|
||||||
DEL,
|
|
||||||
del: DEL,
|
|
||||||
DUMP,
|
|
||||||
dump: DUMP,
|
|
||||||
EVAL_RO,
|
|
||||||
evalRo: EVAL_RO,
|
|
||||||
EVAL,
|
|
||||||
eval: EVAL,
|
|
||||||
EVALSHA,
|
|
||||||
evalSha: EVALSHA,
|
|
||||||
EVALSHA_RO,
|
|
||||||
evalShaRo: EVALSHA_RO,
|
|
||||||
EXISTS,
|
|
||||||
exists: EXISTS,
|
|
||||||
EXPIRE,
|
|
||||||
expire: EXPIRE,
|
|
||||||
EXPIREAT,
|
|
||||||
expireAt: EXPIREAT,
|
|
||||||
EXPIRETIME,
|
|
||||||
expireTime: EXPIRETIME,
|
|
||||||
FCALL_RO,
|
|
||||||
fCallRo: FCALL_RO,
|
|
||||||
FCALL,
|
|
||||||
fCall: FCALL,
|
|
||||||
GEOADD,
|
|
||||||
geoAdd: GEOADD,
|
|
||||||
GEODIST,
|
|
||||||
geoDist: GEODIST,
|
|
||||||
GEOHASH,
|
|
||||||
geoHash: GEOHASH,
|
|
||||||
GEOPOS,
|
|
||||||
geoPos: GEOPOS,
|
|
||||||
GEORADIUS_RO_WITH,
|
|
||||||
geoRadiusRoWith: GEORADIUS_RO_WITH,
|
|
||||||
GEORADIUS_RO,
|
|
||||||
geoRadiusRo: GEORADIUS_RO,
|
|
||||||
GEORADIUS_WITH,
|
|
||||||
geoRadiusWith: GEORADIUS_WITH,
|
|
||||||
GEORADIUS,
|
|
||||||
geoRadius: GEORADIUS,
|
|
||||||
GEORADIUSBYMEMBER_RO_WITH,
|
|
||||||
geoRadiusByMemberRoWith: GEORADIUSBYMEMBER_RO_WITH,
|
|
||||||
GEORADIUSBYMEMBER_RO,
|
|
||||||
geoRadiusByMemberRo: GEORADIUSBYMEMBER_RO,
|
|
||||||
GEORADIUSBYMEMBER_WITH,
|
|
||||||
geoRadiusByMemberWith: GEORADIUSBYMEMBER_WITH,
|
|
||||||
GEORADIUSBYMEMBER,
|
|
||||||
geoRadiusByMember: GEORADIUSBYMEMBER,
|
|
||||||
GEORADIUSBYMEMBERSTORE,
|
|
||||||
geoRadiusByMemberStore: GEORADIUSBYMEMBERSTORE,
|
|
||||||
GEORADIUSSTORE,
|
|
||||||
geoRadiusStore: GEORADIUSSTORE,
|
|
||||||
GEOSEARCH_WITH,
|
|
||||||
geoSearchWith: GEOSEARCH_WITH,
|
|
||||||
GEOSEARCH,
|
|
||||||
geoSearch: GEOSEARCH,
|
|
||||||
GEOSEARCHSTORE,
|
|
||||||
geoSearchStore: GEOSEARCHSTORE,
|
|
||||||
GET,
|
|
||||||
get: GET,
|
|
||||||
GETBIT,
|
|
||||||
getBit: GETBIT,
|
|
||||||
GETDEL,
|
|
||||||
getDel: GETDEL,
|
|
||||||
GETEX,
|
|
||||||
getEx: GETEX,
|
|
||||||
GETRANGE,
|
|
||||||
getRange: GETRANGE,
|
|
||||||
GETSET,
|
|
||||||
getSet: GETSET,
|
|
||||||
HDEL,
|
|
||||||
hDel: HDEL,
|
|
||||||
HEXISTS,
|
|
||||||
hExists: HEXISTS,
|
|
||||||
HGET,
|
|
||||||
hGet: HGET,
|
|
||||||
HGETALL,
|
|
||||||
hGetAll: HGETALL,
|
|
||||||
HINCRBY,
|
|
||||||
hIncrBy: HINCRBY,
|
|
||||||
HINCRBYFLOAT,
|
|
||||||
hIncrByFloat: HINCRBYFLOAT,
|
|
||||||
HKEYS,
|
|
||||||
hKeys: HKEYS,
|
|
||||||
HLEN,
|
|
||||||
hLen: HLEN,
|
|
||||||
HMGET,
|
|
||||||
hmGet: HMGET,
|
|
||||||
HRANDFIELD_COUNT_WITHVALUES,
|
|
||||||
hRandFieldCountWithValues: HRANDFIELD_COUNT_WITHVALUES,
|
|
||||||
HRANDFIELD_COUNT,
|
|
||||||
hRandFieldCount: HRANDFIELD_COUNT,
|
|
||||||
HRANDFIELD,
|
|
||||||
hRandField: HRANDFIELD,
|
|
||||||
HSCAN,
|
|
||||||
hScan: HSCAN,
|
|
||||||
HSET,
|
|
||||||
hSet: HSET,
|
|
||||||
HSETNX,
|
|
||||||
hSetNX: HSETNX,
|
|
||||||
HSTRLEN,
|
|
||||||
hStrLen: HSTRLEN,
|
|
||||||
HVALS,
|
|
||||||
hVals: HVALS,
|
|
||||||
INCR,
|
|
||||||
incr: INCR,
|
|
||||||
INCRBY,
|
|
||||||
incrBy: INCRBY,
|
|
||||||
INCRBYFLOAT,
|
|
||||||
incrByFloat: INCRBYFLOAT,
|
|
||||||
LCS_IDX_WITHMATCHLEN,
|
|
||||||
lcsIdxWithMatchLen: LCS_IDX_WITHMATCHLEN,
|
|
||||||
LCS_IDX,
|
|
||||||
lcsIdx: LCS_IDX,
|
|
||||||
LCS_LEN,
|
|
||||||
lcsLen: LCS_LEN,
|
|
||||||
LCS,
|
|
||||||
lcs: LCS,
|
|
||||||
LINDEX,
|
|
||||||
lIndex: LINDEX,
|
|
||||||
LINSERT,
|
|
||||||
lInsert: LINSERT,
|
|
||||||
LLEN,
|
|
||||||
lLen: LLEN,
|
|
||||||
LMOVE,
|
|
||||||
lMove: LMOVE,
|
|
||||||
LMPOP,
|
|
||||||
lmPop: LMPOP,
|
|
||||||
LPOP_COUNT,
|
|
||||||
lPopCount: LPOP_COUNT,
|
|
||||||
LPOP,
|
|
||||||
lPop: LPOP,
|
|
||||||
LPOS_COUNT,
|
|
||||||
lPosCount: LPOS_COUNT,
|
|
||||||
LPOS,
|
|
||||||
lPos: LPOS,
|
|
||||||
LPUSH,
|
|
||||||
lPush: LPUSH,
|
|
||||||
LPUSHX,
|
|
||||||
lPushX: LPUSHX,
|
|
||||||
LRANGE,
|
|
||||||
lRange: LRANGE,
|
|
||||||
LREM,
|
|
||||||
lRem: LREM,
|
|
||||||
LSET,
|
|
||||||
lSet: LSET,
|
|
||||||
LTRIM,
|
|
||||||
lTrim: LTRIM,
|
|
||||||
MGET,
|
|
||||||
mGet: MGET,
|
|
||||||
MIGRATE,
|
|
||||||
migrate: MIGRATE,
|
|
||||||
MSET,
|
|
||||||
mSet: MSET,
|
|
||||||
MSETNX,
|
|
||||||
mSetNX: MSETNX,
|
|
||||||
OBJECT_ENCODING,
|
|
||||||
objectEncoding: OBJECT_ENCODING,
|
|
||||||
OBJECT_FREQ,
|
|
||||||
objectFreq: OBJECT_FREQ,
|
|
||||||
OBJECT_IDLETIME,
|
|
||||||
objectIdleTime: OBJECT_IDLETIME,
|
|
||||||
OBJECT_REFCOUNT,
|
|
||||||
objectRefCount: OBJECT_REFCOUNT,
|
|
||||||
PERSIST,
|
|
||||||
persist: PERSIST,
|
|
||||||
PEXPIRE,
|
|
||||||
pExpire: PEXPIRE,
|
|
||||||
PEXPIREAT,
|
|
||||||
pExpireAt: PEXPIREAT,
|
|
||||||
PEXPIRETIME,
|
|
||||||
pExpireTime: PEXPIRETIME,
|
|
||||||
PFADD,
|
|
||||||
pfAdd: PFADD,
|
|
||||||
PFCOUNT,
|
|
||||||
pfCount: PFCOUNT,
|
|
||||||
PFMERGE,
|
|
||||||
pfMerge: PFMERGE,
|
|
||||||
PSETEX,
|
|
||||||
pSetEx: PSETEX,
|
|
||||||
PTTL,
|
|
||||||
pTTL: PTTL,
|
|
||||||
PUBLISH,
|
|
||||||
publish: PUBLISH,
|
|
||||||
RENAME,
|
|
||||||
rename: RENAME,
|
|
||||||
RENAMENX,
|
|
||||||
renameNX: RENAMENX,
|
|
||||||
RPOP_COUNT,
|
|
||||||
rPopCount: RPOP_COUNT,
|
|
||||||
RPOP,
|
|
||||||
rPop: RPOP,
|
|
||||||
RPOPLPUSH,
|
|
||||||
rPopLPush: RPOPLPUSH,
|
|
||||||
RPUSH,
|
|
||||||
rPush: RPUSH,
|
|
||||||
RPUSHX,
|
|
||||||
rPushX: RPUSHX,
|
|
||||||
SADD,
|
|
||||||
sAdd: SADD,
|
|
||||||
SCARD,
|
|
||||||
sCard: SCARD,
|
|
||||||
SDIFF,
|
|
||||||
sDiff: SDIFF,
|
|
||||||
SDIFFSTORE,
|
|
||||||
sDiffStore: SDIFFSTORE,
|
|
||||||
SINTER,
|
|
||||||
sInter: SINTER,
|
|
||||||
SINTERCARD,
|
|
||||||
sInterCard: SINTERCARD,
|
|
||||||
SINTERSTORE,
|
|
||||||
sInterStore: SINTERSTORE,
|
|
||||||
SET,
|
|
||||||
set: SET,
|
|
||||||
SETBIT,
|
|
||||||
setBit: SETBIT,
|
|
||||||
SETEX,
|
|
||||||
setEx: SETEX,
|
|
||||||
SETNX,
|
|
||||||
setNX: SETNX,
|
|
||||||
SETRANGE,
|
|
||||||
setRange: SETRANGE,
|
|
||||||
SISMEMBER,
|
|
||||||
sIsMember: SISMEMBER,
|
|
||||||
SMEMBERS,
|
|
||||||
sMembers: SMEMBERS,
|
|
||||||
SMISMEMBER,
|
|
||||||
smIsMember: SMISMEMBER,
|
|
||||||
SMOVE,
|
|
||||||
sMove: SMOVE,
|
|
||||||
SORT_RO,
|
|
||||||
sortRo: SORT_RO,
|
|
||||||
SORT_STORE,
|
|
||||||
sortStore: SORT_STORE,
|
|
||||||
SORT,
|
|
||||||
sort: SORT,
|
|
||||||
SPOP,
|
|
||||||
sPop: SPOP,
|
|
||||||
SPUBLISH,
|
|
||||||
sPublish: SPUBLISH,
|
|
||||||
SRANDMEMBER_COUNT,
|
|
||||||
sRandMemberCount: SRANDMEMBER_COUNT,
|
|
||||||
SRANDMEMBER,
|
|
||||||
sRandMember: SRANDMEMBER,
|
|
||||||
SREM,
|
|
||||||
sRem: SREM,
|
|
||||||
SSCAN,
|
|
||||||
sScan: SSCAN,
|
|
||||||
STRLEN,
|
|
||||||
strLen: STRLEN,
|
|
||||||
SUNION,
|
|
||||||
sUnion: SUNION,
|
|
||||||
SUNIONSTORE,
|
|
||||||
sUnionStore: SUNIONSTORE,
|
|
||||||
TOUCH,
|
|
||||||
touch: TOUCH,
|
|
||||||
TTL,
|
|
||||||
ttl: TTL,
|
|
||||||
TYPE,
|
|
||||||
type: TYPE,
|
|
||||||
UNLINK,
|
|
||||||
unlink: UNLINK,
|
|
||||||
WATCH,
|
|
||||||
watch: WATCH,
|
|
||||||
XACK,
|
|
||||||
xAck: XACK,
|
|
||||||
XADD,
|
|
||||||
xAdd: XADD,
|
|
||||||
XAUTOCLAIM_JUSTID,
|
|
||||||
xAutoClaimJustId: XAUTOCLAIM_JUSTID,
|
|
||||||
XAUTOCLAIM,
|
|
||||||
xAutoClaim: XAUTOCLAIM,
|
|
||||||
XCLAIM,
|
|
||||||
xClaim: XCLAIM,
|
|
||||||
XCLAIM_JUSTID,
|
|
||||||
xClaimJustId: XCLAIM_JUSTID,
|
|
||||||
XDEL,
|
|
||||||
xDel: XDEL,
|
|
||||||
XGROUP_CREATE,
|
|
||||||
xGroupCreate: XGROUP_CREATE,
|
|
||||||
XGROUP_CREATECONSUMER,
|
|
||||||
xGroupCreateConsumer: XGROUP_CREATECONSUMER,
|
|
||||||
XGROUP_DELCONSUMER,
|
|
||||||
xGroupDelConsumer: XGROUP_DELCONSUMER,
|
|
||||||
XGROUP_DESTROY,
|
|
||||||
xGroupDestroy: XGROUP_DESTROY,
|
|
||||||
XGROUP_SETID,
|
|
||||||
xGroupSetId: XGROUP_SETID,
|
|
||||||
XINFO_CONSUMERS,
|
|
||||||
xInfoConsumers: XINFO_CONSUMERS,
|
|
||||||
XINFO_GROUPS,
|
|
||||||
xInfoGroups: XINFO_GROUPS,
|
|
||||||
XINFO_STREAM,
|
|
||||||
xInfoStream: XINFO_STREAM,
|
|
||||||
XLEN,
|
|
||||||
xLen: XLEN,
|
|
||||||
XPENDING_RANGE,
|
|
||||||
xPendingRange: XPENDING_RANGE,
|
|
||||||
XPENDING,
|
|
||||||
xPending: XPENDING,
|
|
||||||
XRANGE,
|
|
||||||
xRange: XRANGE,
|
|
||||||
XREAD,
|
|
||||||
xRead: XREAD,
|
|
||||||
XREADGROUP,
|
|
||||||
xReadGroup: XREADGROUP,
|
|
||||||
XREVRANGE,
|
|
||||||
xRevRange: XREVRANGE,
|
|
||||||
XSETID,
|
|
||||||
xSetId: XSETID,
|
|
||||||
XTRIM,
|
|
||||||
xTrim: XTRIM,
|
|
||||||
ZADD,
|
|
||||||
zAdd: ZADD,
|
|
||||||
ZCARD,
|
|
||||||
zCard: ZCARD,
|
|
||||||
ZCOUNT,
|
|
||||||
zCount: ZCOUNT,
|
|
||||||
ZDIFF_WITHSCORES,
|
|
||||||
zDiffWithScores: ZDIFF_WITHSCORES,
|
|
||||||
ZDIFF,
|
|
||||||
zDiff: ZDIFF,
|
|
||||||
ZDIFFSTORE,
|
|
||||||
zDiffStore: ZDIFFSTORE,
|
|
||||||
ZINCRBY,
|
|
||||||
zIncrBy: ZINCRBY,
|
|
||||||
ZINTER_WITHSCORES,
|
|
||||||
zInterWithScores: ZINTER_WITHSCORES,
|
|
||||||
ZINTER,
|
|
||||||
zInter: ZINTER,
|
|
||||||
ZINTERCARD,
|
|
||||||
zInterCard: ZINTERCARD,
|
|
||||||
ZINTERSTORE,
|
|
||||||
zInterStore: ZINTERSTORE,
|
|
||||||
ZLEXCOUNT,
|
|
||||||
zLexCount: ZLEXCOUNT,
|
|
||||||
ZMPOP,
|
|
||||||
zmPop: ZMPOP,
|
|
||||||
ZMSCORE,
|
|
||||||
zmScore: ZMSCORE,
|
|
||||||
ZPOPMAX_COUNT,
|
|
||||||
zPopMaxCount: ZPOPMAX_COUNT,
|
|
||||||
ZPOPMAX,
|
|
||||||
zPopMax: ZPOPMAX,
|
|
||||||
ZPOPMIN_COUNT,
|
|
||||||
zPopMinCount: ZPOPMIN_COUNT,
|
|
||||||
ZPOPMIN,
|
|
||||||
zPopMin: ZPOPMIN,
|
|
||||||
ZRANDMEMBER_COUNT_WITHSCORES,
|
|
||||||
zRandMemberCountWithScores: ZRANDMEMBER_COUNT_WITHSCORES,
|
|
||||||
ZRANDMEMBER_COUNT,
|
|
||||||
zRandMemberCount: ZRANDMEMBER_COUNT,
|
|
||||||
ZRANDMEMBER,
|
|
||||||
zRandMember: ZRANDMEMBER,
|
|
||||||
ZRANGE_WITHSCORES,
|
|
||||||
zRangeWithScores: ZRANGE_WITHSCORES,
|
|
||||||
ZRANGE,
|
|
||||||
zRange: ZRANGE,
|
|
||||||
ZRANGEBYLEX,
|
|
||||||
zRangeByLex: ZRANGEBYLEX,
|
|
||||||
ZRANGEBYSCORE_WITHSCORES,
|
|
||||||
zRangeByScoreWithScores: ZRANGEBYSCORE_WITHSCORES,
|
|
||||||
ZRANGEBYSCORE,
|
|
||||||
zRangeByScore: ZRANGEBYSCORE,
|
|
||||||
ZRANGESTORE,
|
|
||||||
zRangeStore: ZRANGESTORE,
|
|
||||||
ZRANK,
|
|
||||||
zRank: ZRANK,
|
|
||||||
ZREM,
|
|
||||||
zRem: ZREM,
|
|
||||||
ZREMRANGEBYLEX,
|
|
||||||
zRemRangeByLex: ZREMRANGEBYLEX,
|
|
||||||
ZREMRANGEBYRANK,
|
|
||||||
zRemRangeByRank: ZREMRANGEBYRANK,
|
|
||||||
ZREMRANGEBYSCORE,
|
|
||||||
zRemRangeByScore: ZREMRANGEBYSCORE,
|
|
||||||
ZREVRANK,
|
|
||||||
zRevRank: ZREVRANK,
|
|
||||||
ZSCAN,
|
|
||||||
zScan: ZSCAN,
|
|
||||||
ZSCORE,
|
|
||||||
zScore: ZSCORE,
|
|
||||||
ZUNION_WITHSCORES,
|
|
||||||
zUnionWithScores: ZUNION_WITHSCORES,
|
|
||||||
ZUNION,
|
|
||||||
zUnion: ZUNION,
|
|
||||||
ZUNIONSTORE,
|
|
||||||
zUnionStore: ZUNIONSTORE
|
|
||||||
};
|
|
@@ -1,424 +1,549 @@
|
|||||||
import COMMANDS from './commands';
|
import { ClientCommandOptions, RedisClientOptions, RedisClientType } from '../client';
|
||||||
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, RedisCommandSignature, RedisFunction } from '../commands';
|
import { Command, CommandArguments, CommanderConfig, CommandPolicies, CommandSignature, CommandWithPoliciesSignature, Flags, RedisArgument, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, TransformReply } from '../RESP/types';
|
||||||
import { ClientCommandOptions, RedisClientOptions, RedisClientType, WithFunctions, WithModules, WithScripts } from '../client';
|
import COMMANDS from '../commands';
|
||||||
import RedisClusterSlots, { NodeAddressMap, ShardNode } from './cluster-slots';
|
|
||||||
import { attachExtensions, transformCommandReply, attachCommands, transformCommandArguments } from '../commander';
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import RedisClusterMultiCommand, { InstantiableRedisClusterMultiCommandType, RedisClusterMultiCommandType } from './multi-command';
|
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
|
||||||
import { RedisMultiQueuedCommand } from '../multi-command';
|
import RedisClusterSlots, { NodeAddressMap, ShardNode } from './cluster-slots';
|
||||||
|
// import RedisClusterMultiCommand, { InstantiableRedisClusterMultiCommandType, RedisClusterMultiCommandType } from './multi-command';
|
||||||
|
// import { RedisMultiQueuedCommand } from '../multi-command';
|
||||||
import { PubSubListener } from '../client/pub-sub';
|
import { PubSubListener } from '../client/pub-sub';
|
||||||
import { ErrorReply } from '../errors';
|
import { ErrorReply } from '../errors';
|
||||||
|
|
||||||
export type RedisClusterClientOptions = Omit<
|
export type RedisClusterClientOptions = Omit<
|
||||||
RedisClientOptions,
|
RedisClientOptions,
|
||||||
'modules' | 'functions' | 'scripts' | 'database'
|
'modules' | 'functions' | 'scripts' | 'database' | 'RESP'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface RedisClusterOptions<
|
export interface RedisClusterOptions<
|
||||||
M extends RedisModules = Record<string, never>,
|
M extends RedisModules = RedisModules,
|
||||||
F extends RedisFunctions = Record<string, never>,
|
F extends RedisFunctions = RedisFunctions,
|
||||||
S extends RedisScripts = Record<string, never>
|
S extends RedisScripts = RedisScripts,
|
||||||
> extends RedisExtensions<M, F, S> {
|
RESP extends RespVersions = RespVersions
|
||||||
/**
|
> extends CommanderConfig<M, F, S, RESP> {
|
||||||
* Should contain details for some of the cluster nodes that the client will use to discover
|
/**
|
||||||
* the "cluster topology". We recommend including details for at least 3 nodes here.
|
* Should contain details for some of the cluster nodes that the client will use to discover
|
||||||
*/
|
* the "cluster topology". We recommend including details for at least 3 nodes here.
|
||||||
rootNodes: Array<RedisClusterClientOptions>;
|
*/
|
||||||
/**
|
rootNodes: Array<RedisClusterClientOptions>;
|
||||||
* Default values used for every client in the cluster. Use this to specify global values,
|
/**
|
||||||
* for example: ACL credentials, timeouts, TLS configuration etc.
|
* Default values used for every client in the cluster. Use this to specify global values,
|
||||||
*/
|
* for example: ACL credentials, timeouts, TLS configuration etc.
|
||||||
defaults?: Partial<RedisClusterClientOptions>;
|
*/
|
||||||
/**
|
defaults?: Partial<RedisClusterClientOptions>;
|
||||||
* When `true`, `.connect()` will only discover the cluster topology, without actually connecting to all the nodes.
|
/**
|
||||||
* Useful for short-term or PubSub-only connections.
|
* When `true`, `.connect()` will only discover the cluster topology, without actually connecting to all the nodes.
|
||||||
*/
|
* Useful for short-term or PubSub-only connections.
|
||||||
minimizeConnections?: boolean;
|
*/
|
||||||
/**
|
minimizeConnections?: boolean;
|
||||||
* When `true`, distribute load by executing readonly commands (such as `GET`, `GEOSEARCH`, etc.) across all cluster nodes. When `false`, only use master nodes.
|
/**
|
||||||
*/
|
* When `true`, distribute load by executing readonly commands (such as `GET`, `GEOSEARCH`, etc.) across all cluster nodes. When `false`, only use master nodes.
|
||||||
useReplicas?: boolean;
|
*/
|
||||||
/**
|
// TODO: replicas only mode?
|
||||||
* The maximum number of times a command will be redirected due to `MOVED` or `ASK` errors.
|
useReplicas?: boolean;
|
||||||
*/
|
/**
|
||||||
maxCommandRedirections?: number;
|
* The maximum number of times a command will be redirected due to `MOVED` or `ASK` errors.
|
||||||
/**
|
*/
|
||||||
* Mapping between the addresses in the cluster (see `CLUSTER SHARDS`) and the addresses the client should connect to
|
maxCommandRedirections?: number;
|
||||||
* Useful when the cluster is running on another network
|
/**
|
||||||
*
|
* Mapping between the addresses in the cluster (see `CLUSTER SHARDS`) and the addresses the client should connect to
|
||||||
*/
|
* Useful when the cluster is running on another network
|
||||||
nodeAddressMap?: NodeAddressMap;
|
*/
|
||||||
|
nodeAddressMap?: NodeAddressMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
type WithCommands = {
|
type WithCommands<
|
||||||
[P in keyof typeof COMMANDS]: RedisCommandSignature<(typeof COMMANDS)[P]>;
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags,
|
||||||
|
POLICIES extends CommandPolicies
|
||||||
|
> = {
|
||||||
|
[P in keyof typeof COMMANDS]: CommandWithPoliciesSignature<(typeof COMMANDS)[P], RESP, FLAGS, POLICIES>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RedisClusterType<
|
export type RedisClusterType<
|
||||||
M extends RedisModules = Record<string, never>,
|
M extends RedisModules = {},
|
||||||
F extends RedisFunctions = Record<string, never>,
|
F extends RedisFunctions = {},
|
||||||
S extends RedisScripts = Record<string, never>
|
S extends RedisScripts = {},
|
||||||
> = RedisCluster<M, F, S> & WithCommands & WithModules<M> & WithFunctions<F> & WithScripts<S>;
|
RESP extends RespVersions = 2,
|
||||||
|
FLAGS extends Flags = {},
|
||||||
|
POLICIES extends CommandPolicies = {}
|
||||||
|
> = RedisCluster<M, F, S, RESP, FLAGS, POLICIES> & WithCommands<RESP, FLAGS, POLICIES>;
|
||||||
|
// & WithModules<M> & WithFunctions<F> & WithScripts<S>
|
||||||
|
|
||||||
export default class RedisCluster<
|
export interface ClusterCommandOptions extends ClientCommandOptions {
|
||||||
M extends RedisModules,
|
policies?: CommandPolicies;
|
||||||
F extends RedisFunctions,
|
|
||||||
S extends RedisScripts
|
|
||||||
> extends EventEmitter {
|
|
||||||
static extractFirstKey(
|
|
||||||
command: RedisCommand,
|
|
||||||
originalArgs: Array<unknown>,
|
|
||||||
redisArgs: RedisCommandArguments
|
|
||||||
): RedisCommandArgument | undefined {
|
|
||||||
if (command.FIRST_KEY_INDEX === undefined) {
|
|
||||||
return undefined;
|
|
||||||
} else if (typeof command.FIRST_KEY_INDEX === 'number') {
|
|
||||||
return redisArgs[command.FIRST_KEY_INDEX];
|
|
||||||
}
|
|
||||||
|
|
||||||
return command.FIRST_KEY_INDEX(...originalArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
static create<
|
|
||||||
M extends RedisModules,
|
|
||||||
F extends RedisFunctions,
|
|
||||||
S extends RedisScripts
|
|
||||||
>(options?: RedisClusterOptions<M, F, S>): RedisClusterType<M, F, S> {
|
|
||||||
return new (attachExtensions({
|
|
||||||
BaseClass: RedisCluster,
|
|
||||||
modulesExecutor: RedisCluster.prototype.commandsExecutor,
|
|
||||||
modules: options?.modules,
|
|
||||||
functionsExecutor: RedisCluster.prototype.functionsExecutor,
|
|
||||||
functions: options?.functions,
|
|
||||||
scriptsExecutor: RedisCluster.prototype.scriptsExecutor,
|
|
||||||
scripts: options?.scripts
|
|
||||||
}))(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly #options: RedisClusterOptions<M, F, S>;
|
|
||||||
|
|
||||||
readonly #slots: RedisClusterSlots<M, F, S>;
|
|
||||||
|
|
||||||
get slots() {
|
|
||||||
return this.#slots.slots;
|
|
||||||
}
|
|
||||||
|
|
||||||
get shards() {
|
|
||||||
return this.#slots.shards;
|
|
||||||
}
|
|
||||||
|
|
||||||
get masters() {
|
|
||||||
return this.#slots.masters;
|
|
||||||
}
|
|
||||||
|
|
||||||
get replicas() {
|
|
||||||
return this.#slots.replicas;
|
|
||||||
}
|
|
||||||
|
|
||||||
get nodeByAddress() {
|
|
||||||
return this.#slots.nodeByAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
get pubSubNode() {
|
|
||||||
return this.#slots.pubSubNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly #Multi: InstantiableRedisClusterMultiCommandType<M, F, S>;
|
|
||||||
|
|
||||||
get isOpen() {
|
|
||||||
return this.#slots.isOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(options: RedisClusterOptions<M, F, S>) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.#options = options;
|
|
||||||
this.#slots = new RedisClusterSlots(options, this.emit.bind(this));
|
|
||||||
this.#Multi = RedisClusterMultiCommand.extend(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
duplicate(overrides?: Partial<RedisClusterOptions<M, F, S>>): RedisClusterType<M, F, S> {
|
|
||||||
return new (Object.getPrototypeOf(this).constructor)({
|
|
||||||
...this.#options,
|
|
||||||
...overrides
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
connect() {
|
|
||||||
return this.#slots.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
async commandsExecutor<C extends RedisCommand>(
|
|
||||||
command: C,
|
|
||||||
args: Array<unknown>
|
|
||||||
): Promise<RedisCommandReply<C>> {
|
|
||||||
const { args: redisArgs, options } = transformCommandArguments(command, args);
|
|
||||||
return transformCommandReply(
|
|
||||||
command,
|
|
||||||
await this.sendCommand(
|
|
||||||
RedisCluster.extractFirstKey(command, args, redisArgs),
|
|
||||||
command.IS_READ_ONLY,
|
|
||||||
redisArgs,
|
|
||||||
options
|
|
||||||
),
|
|
||||||
redisArgs.preserve
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendCommand<T = RedisCommandRawReply>(
|
|
||||||
firstKey: RedisCommandArgument | undefined,
|
|
||||||
isReadonly: boolean | undefined,
|
|
||||||
args: RedisCommandArguments,
|
|
||||||
options?: ClientCommandOptions
|
|
||||||
): Promise<T> {
|
|
||||||
return this.#execute(
|
|
||||||
firstKey,
|
|
||||||
isReadonly,
|
|
||||||
client => client.sendCommand<T>(args, options)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async functionsExecutor<F extends RedisFunction>(
|
|
||||||
fn: F,
|
|
||||||
args: Array<unknown>,
|
|
||||||
name: string,
|
|
||||||
): Promise<RedisCommandReply<F>> {
|
|
||||||
const { args: redisArgs, options } = transformCommandArguments(fn, args);
|
|
||||||
return transformCommandReply(
|
|
||||||
fn,
|
|
||||||
await this.executeFunction(
|
|
||||||
name,
|
|
||||||
fn,
|
|
||||||
args,
|
|
||||||
redisArgs,
|
|
||||||
options
|
|
||||||
),
|
|
||||||
redisArgs.preserve
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeFunction(
|
|
||||||
name: string,
|
|
||||||
fn: RedisFunction,
|
|
||||||
originalArgs: Array<unknown>,
|
|
||||||
redisArgs: RedisCommandArguments,
|
|
||||||
options?: ClientCommandOptions
|
|
||||||
): Promise<RedisCommandRawReply> {
|
|
||||||
return this.#execute(
|
|
||||||
RedisCluster.extractFirstKey(fn, originalArgs, redisArgs),
|
|
||||||
fn.IS_READ_ONLY,
|
|
||||||
client => client.executeFunction(name, fn, redisArgs, options)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async scriptsExecutor<S extends RedisScript>(script: S, args: Array<unknown>): Promise<RedisCommandReply<S>> {
|
|
||||||
const { args: redisArgs, options } = transformCommandArguments(script, args);
|
|
||||||
return transformCommandReply(
|
|
||||||
script,
|
|
||||||
await this.executeScript(
|
|
||||||
script,
|
|
||||||
args,
|
|
||||||
redisArgs,
|
|
||||||
options
|
|
||||||
),
|
|
||||||
redisArgs.preserve
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeScript(
|
|
||||||
script: RedisScript,
|
|
||||||
originalArgs: Array<unknown>,
|
|
||||||
redisArgs: RedisCommandArguments,
|
|
||||||
options?: ClientCommandOptions
|
|
||||||
): Promise<RedisCommandRawReply> {
|
|
||||||
return this.#execute(
|
|
||||||
RedisCluster.extractFirstKey(script, originalArgs, redisArgs),
|
|
||||||
script.IS_READ_ONLY,
|
|
||||||
client => client.executeScript(script, redisArgs, options)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async #execute<Reply>(
|
|
||||||
firstKey: RedisCommandArgument | undefined,
|
|
||||||
isReadonly: boolean | undefined,
|
|
||||||
executor: (client: RedisClientType<M, F, S>) => Promise<Reply>
|
|
||||||
): Promise<Reply> {
|
|
||||||
const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16;
|
|
||||||
let client = await this.#slots.getClient(firstKey, isReadonly);
|
|
||||||
for (let i = 0;; i++) {
|
|
||||||
try {
|
|
||||||
return await executor(client);
|
|
||||||
} catch (err) {
|
|
||||||
if (++i > maxCommandRedirections || !(err instanceof ErrorReply)) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.message.startsWith('ASK')) {
|
|
||||||
const address = err.message.substring(err.message.lastIndexOf(' ') + 1);
|
|
||||||
let redirectTo = await this.#slots.getMasterByAddress(address);
|
|
||||||
if (!redirectTo) {
|
|
||||||
await this.#slots.rediscover(client);
|
|
||||||
redirectTo = await this.#slots.getMasterByAddress(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!redirectTo) {
|
|
||||||
throw new Error(`Cannot find node ${address}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await redirectTo.asking();
|
|
||||||
client = redirectTo;
|
|
||||||
continue;
|
|
||||||
} else if (err.message.startsWith('MOVED')) {
|
|
||||||
await this.#slots.rediscover(client);
|
|
||||||
client = await this.#slots.getClient(firstKey, isReadonly);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MULTI(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, F, S> {
|
|
||||||
return new this.#Multi(
|
|
||||||
(commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => {
|
|
||||||
return this.#execute(
|
|
||||||
firstKey,
|
|
||||||
false,
|
|
||||||
client => client.multiExecutor(commands, undefined, chainId)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
routing
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
multi = this.MULTI;
|
|
||||||
|
|
||||||
async SUBSCRIBE<T extends boolean = false>(
|
|
||||||
channels: string | Array<string>,
|
|
||||||
listener: PubSubListener<T>,
|
|
||||||
bufferMode?: T
|
|
||||||
) {
|
|
||||||
return (await this.#slots.getPubSubClient())
|
|
||||||
.SUBSCRIBE(channels, listener, bufferMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe = this.SUBSCRIBE;
|
|
||||||
|
|
||||||
async UNSUBSCRIBE<T extends boolean = false>(
|
|
||||||
channels?: string | Array<string>,
|
|
||||||
listener?: PubSubListener<boolean>,
|
|
||||||
bufferMode?: T
|
|
||||||
) {
|
|
||||||
return this.#slots.executeUnsubscribeCommand(client =>
|
|
||||||
client.UNSUBSCRIBE(channels, listener, bufferMode)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe = this.UNSUBSCRIBE;
|
|
||||||
|
|
||||||
async PSUBSCRIBE<T extends boolean = false>(
|
|
||||||
patterns: string | Array<string>,
|
|
||||||
listener: PubSubListener<T>,
|
|
||||||
bufferMode?: T
|
|
||||||
) {
|
|
||||||
return (await this.#slots.getPubSubClient())
|
|
||||||
.PSUBSCRIBE(patterns, listener, bufferMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
pSubscribe = this.PSUBSCRIBE;
|
|
||||||
|
|
||||||
async PUNSUBSCRIBE<T extends boolean = false>(
|
|
||||||
patterns?: string | Array<string>,
|
|
||||||
listener?: PubSubListener<T>,
|
|
||||||
bufferMode?: T
|
|
||||||
) {
|
|
||||||
return this.#slots.executeUnsubscribeCommand(client =>
|
|
||||||
client.PUNSUBSCRIBE(patterns, listener, bufferMode)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pUnsubscribe = this.PUNSUBSCRIBE;
|
|
||||||
|
|
||||||
async SSUBSCRIBE<T extends boolean = false>(
|
|
||||||
channels: string | Array<string>,
|
|
||||||
listener: PubSubListener<T>,
|
|
||||||
bufferMode?: T
|
|
||||||
) {
|
|
||||||
const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16,
|
|
||||||
firstChannel = Array.isArray(channels) ? channels[0] : channels;
|
|
||||||
let client = await this.#slots.getShardedPubSubClient(firstChannel);
|
|
||||||
for (let i = 0;; i++) {
|
|
||||||
try {
|
|
||||||
return await client.SSUBSCRIBE(channels, listener, bufferMode);
|
|
||||||
} catch (err) {
|
|
||||||
if (++i > maxCommandRedirections || !(err instanceof ErrorReply)) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.message.startsWith('MOVED')) {
|
|
||||||
await this.#slots.rediscover(client);
|
|
||||||
client = await this.#slots.getShardedPubSubClient(firstChannel);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sSubscribe = this.SSUBSCRIBE;
|
|
||||||
|
|
||||||
SUNSUBSCRIBE<T extends boolean = false>(
|
|
||||||
channels: string | Array<string>,
|
|
||||||
listener: PubSubListener<T>,
|
|
||||||
bufferMode?: T
|
|
||||||
) {
|
|
||||||
return this.#slots.executeShardedUnsubscribeCommand(
|
|
||||||
Array.isArray(channels) ? channels[0] : channels,
|
|
||||||
client => client.SUNSUBSCRIBE(channels, listener, bufferMode)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sUnsubscribe = this.SUNSUBSCRIBE;
|
|
||||||
|
|
||||||
quit(): Promise<void> {
|
|
||||||
return this.#slots.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect(): Promise<void> {
|
|
||||||
return this.#slots.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeClient(node: ShardNode<M, F, S>) {
|
|
||||||
return this.#slots.nodeClient(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRandomNode() {
|
|
||||||
return this.#slots.getRandomNode();
|
|
||||||
}
|
|
||||||
|
|
||||||
getSlotRandomNode(slot: number) {
|
|
||||||
return this.#slots.getSlotRandomNode(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use `.masters` instead
|
|
||||||
*/
|
|
||||||
getMasters() {
|
|
||||||
return this.masters;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use `.slots[<SLOT>]` instead
|
|
||||||
*/
|
|
||||||
getSlotMaster(slot: number) {
|
|
||||||
return this.slots[slot].master;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attachCommands({
|
type ProxyCluster = RedisCluster<RedisModules, RedisFunctions, RedisScripts, RespVersions, Flags, CommandPolicies> & { commandOptions?: ClusterCommandOptions };
|
||||||
BaseClass: RedisCluster,
|
|
||||||
commands: COMMANDS,
|
type NamespaceProxyCluster = { self: ProxyCluster };
|
||||||
executor: RedisCluster.prototype.commandsExecutor
|
|
||||||
});
|
export default class RedisCluster<
|
||||||
|
M extends RedisModules,
|
||||||
|
F extends RedisFunctions,
|
||||||
|
S extends RedisScripts,
|
||||||
|
RESP extends RespVersions,
|
||||||
|
FLAGS extends Flags,
|
||||||
|
POLICIES extends CommandPolicies
|
||||||
|
> extends EventEmitter {
|
||||||
|
private static _extractFirstKey<C extends Command>(
|
||||||
|
command: C,
|
||||||
|
args: Parameters<C['transformArguments']>,
|
||||||
|
redisArgs: Array<RedisArgument>
|
||||||
|
): RedisArgument | undefined {
|
||||||
|
if (command.FIRST_KEY_INDEX === undefined) {
|
||||||
|
return undefined;
|
||||||
|
} else if (typeof command.FIRST_KEY_INDEX === 'number') {
|
||||||
|
return redisArgs[command.FIRST_KEY_INDEX];
|
||||||
|
}
|
||||||
|
|
||||||
|
return command.FIRST_KEY_INDEX(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _createCommand(command: Command, resp: RespVersions) {
|
||||||
|
const transformReply = getTransformReply(command, resp);
|
||||||
|
return async function (this: ProxyCluster) {
|
||||||
|
const args = command.transformArguments.apply(undefined, arguments as any),
|
||||||
|
firstKey = RedisCluster._extractFirstKey(
|
||||||
|
command,
|
||||||
|
arguments as any,
|
||||||
|
args
|
||||||
|
),
|
||||||
|
reply = await this.sendCommand(
|
||||||
|
firstKey,
|
||||||
|
command.IS_READ_ONLY,
|
||||||
|
args,
|
||||||
|
this.commandOptions,
|
||||||
|
command.POLICIES
|
||||||
|
);
|
||||||
|
|
||||||
|
return transformReply ?
|
||||||
|
transformReply(reply, args.preserve) :
|
||||||
|
reply;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _createModuleCommand(command: Command, resp: RespVersions) {
|
||||||
|
const transformReply = getTransformReply(command, resp);
|
||||||
|
return async function (this: NamespaceProxyCluster) {
|
||||||
|
const args = command.transformArguments.apply(undefined, arguments as any),
|
||||||
|
firstKey = RedisCluster._extractFirstKey(
|
||||||
|
command,
|
||||||
|
arguments as any,
|
||||||
|
args
|
||||||
|
),
|
||||||
|
reply = await this.self.sendCommand(
|
||||||
|
firstKey,
|
||||||
|
command.IS_READ_ONLY,
|
||||||
|
args,
|
||||||
|
this.self.commandOptions,
|
||||||
|
command.POLICIES
|
||||||
|
);
|
||||||
|
|
||||||
|
return transformReply ?
|
||||||
|
transformReply(reply, args.preserve) :
|
||||||
|
reply;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) {
|
||||||
|
const prefix = functionArgumentsPrefix(name, fn),
|
||||||
|
transformReply = getTransformReply(fn, resp);
|
||||||
|
return async function (this: NamespaceProxyCluster) {
|
||||||
|
const fnArgs = fn.transformArguments.apply(undefined, arguments as any),
|
||||||
|
args = prefix.concat(fnArgs),
|
||||||
|
firstKey = RedisCluster._extractFirstKey(
|
||||||
|
fn,
|
||||||
|
arguments as any,
|
||||||
|
args
|
||||||
|
),
|
||||||
|
reply = await this.self.sendCommand(
|
||||||
|
firstKey,
|
||||||
|
fn.IS_READ_ONLY,
|
||||||
|
args,
|
||||||
|
this.self.commandOptions,
|
||||||
|
fn.POLICIES
|
||||||
|
);
|
||||||
|
|
||||||
|
return transformReply ?
|
||||||
|
transformReply(reply, fnArgs.preserve) :
|
||||||
|
reply;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _createScriptCommand(script: RedisScript, resp: RespVersions) {
|
||||||
|
const prefix = scriptArgumentsPrefix(script),
|
||||||
|
transformReply = getTransformReply(script, resp);
|
||||||
|
return async function (this: ProxyCluster) {
|
||||||
|
const scriptArgs = script.transformArguments.apply(undefined, arguments as any),
|
||||||
|
args = prefix.concat(scriptArgs),
|
||||||
|
firstKey = RedisCluster._extractFirstKey(
|
||||||
|
script,
|
||||||
|
arguments as any,
|
||||||
|
args
|
||||||
|
),
|
||||||
|
reply = await this.sendCommand(
|
||||||
|
firstKey,
|
||||||
|
script.IS_READ_ONLY,
|
||||||
|
args,
|
||||||
|
this.commandOptions,
|
||||||
|
script.POLICIES
|
||||||
|
);
|
||||||
|
|
||||||
|
return transformReply ?
|
||||||
|
transformReply(reply, scriptArgs.preserve) :
|
||||||
|
reply;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static factory<
|
||||||
|
M extends RedisModules = {},
|
||||||
|
F extends RedisFunctions = {},
|
||||||
|
S extends RedisScripts = {},
|
||||||
|
RESP extends RespVersions = 2
|
||||||
|
>(config?: CommanderConfig<M, F, S, RESP>) {
|
||||||
|
const Cluster = attachConfig({
|
||||||
|
BaseClass: RedisCluster,
|
||||||
|
commands: COMMANDS,
|
||||||
|
createCommand: RedisCluster._createCommand,
|
||||||
|
createFunctionCommand: RedisCluster._createFunctionCommand,
|
||||||
|
createModuleCommand: RedisCluster._createModuleCommand,
|
||||||
|
createScriptCommand: RedisCluster._createScriptCommand,
|
||||||
|
config
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client.prototype.Multi = RedisClientMultiCommand.extend(config);
|
||||||
|
|
||||||
|
return (options?: Omit<RedisClusterOptions, keyof Exclude<typeof config, undefined>>) => {
|
||||||
|
// returning a proxy of the client to prevent the namespaces.self to leak between proxies
|
||||||
|
// namespaces will be bootstraped on first access per proxy
|
||||||
|
return Object.create(new Cluster(options)) as RedisClusterType<M, F, S, RESP>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static create<
|
||||||
|
M extends RedisModules = {},
|
||||||
|
F extends RedisFunctions = {},
|
||||||
|
S extends RedisScripts = {},
|
||||||
|
RESP extends RespVersions = 2
|
||||||
|
>(options?: RedisClusterOptions<M, F, S, RESP>) {
|
||||||
|
return RedisCluster.factory(options)(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly _options: RedisClusterOptions<M, F, S, RESP>;
|
||||||
|
|
||||||
|
private readonly _slots: RedisClusterSlots<M, F, S, RESP>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of the cluster slots, each slot contain its `master` and `replicas`.
|
||||||
|
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific node (master or replica).
|
||||||
|
*/
|
||||||
|
get slots() {
|
||||||
|
return this._slots.slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of cluster shards, each shard contain its `master` and `replicas`.
|
||||||
|
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific node (master or replica).
|
||||||
|
*/
|
||||||
|
get shards() {
|
||||||
|
return this._slots.shards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of the cluster masters.
|
||||||
|
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific master node.
|
||||||
|
*/
|
||||||
|
get masters() {
|
||||||
|
return this._slots.masters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of the cluster replicas.
|
||||||
|
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific replica node.
|
||||||
|
*/
|
||||||
|
get replicas() {
|
||||||
|
return this._slots.replicas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map form a node address (`<host>:<port>`) to its shard, each shard contain its `master` and `replicas`.
|
||||||
|
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific node (master or replica).
|
||||||
|
*/
|
||||||
|
get nodeByAddress() {
|
||||||
|
return this._slots.nodeByAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current pub/sub node.
|
||||||
|
*/
|
||||||
|
get pubSubNode() {
|
||||||
|
return this._slots.pubSubNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// readonly #Multi: InstantiableRedisClusterMultiCommandType<M, F, S>;
|
||||||
|
|
||||||
|
get isOpen() {
|
||||||
|
return this._slots.isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(options: RedisClusterOptions<M, F, S, RESP>) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._options = options;
|
||||||
|
this._slots = new RedisClusterSlots(options, this.emit.bind(this));
|
||||||
|
// this.#Multi = RedisClusterMultiCommand.extend(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicate(overrides?: Partial<RedisClusterOptions<M, F, S>>): RedisClusterType<M, F, S> {
|
||||||
|
return new (Object.getPrototypeOf(this).constructor)({
|
||||||
|
...this._options,
|
||||||
|
...overrides
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
return this._slots.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
withCommandOptions<T extends ClusterCommandOptions>(options: T) {
|
||||||
|
const proxy = Object.create(this);
|
||||||
|
proxy.commandOptions = options;
|
||||||
|
return proxy as RedisClusterType<
|
||||||
|
M,
|
||||||
|
F,
|
||||||
|
S,
|
||||||
|
RESP,
|
||||||
|
T['flags'] extends Flags ? T['flags'] : {},
|
||||||
|
T['policies'] extends CommandPolicies ? T['policies'] : {}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _commandOptionsProxy<
|
||||||
|
K extends keyof ClusterCommandOptions,
|
||||||
|
V extends ClusterCommandOptions[K]
|
||||||
|
>(
|
||||||
|
key: K,
|
||||||
|
value: V
|
||||||
|
) {
|
||||||
|
const proxy = Object.create(this);
|
||||||
|
proxy.commandOptions = Object.create((this as ProxyCluster).commandOptions ?? null);
|
||||||
|
proxy.commandOptions[key] = value;
|
||||||
|
return proxy as RedisClusterType<
|
||||||
|
M,
|
||||||
|
F,
|
||||||
|
S,
|
||||||
|
RESP,
|
||||||
|
K extends 'flags' ? V extends Flags ? V : {} : FLAGS,
|
||||||
|
K extends 'policies' ? V extends CommandPolicies ? V : {} : POLICIES
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the `flags` command option
|
||||||
|
*/
|
||||||
|
withFlags<FLAGS extends Flags>(flags: FLAGS) {
|
||||||
|
return this._commandOptionsProxy('flags', flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the `policies` command option
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
withPolicies<POLICIES extends CommandPolicies> (policies: POLICIES) {
|
||||||
|
return this._commandOptionsProxy('policies', policies);
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendCommand<T = ReplyUnion>(
|
||||||
|
firstKey: RedisArgument | undefined,
|
||||||
|
isReadonly: boolean | undefined,
|
||||||
|
args: CommandArguments,
|
||||||
|
options?: ClusterCommandOptions,
|
||||||
|
deafultPolicies?: CommandPolicies
|
||||||
|
): Promise<T> {
|
||||||
|
// const requestPolicy = options?.policies?.request ?? deafultPolicies?.request,
|
||||||
|
// responsePolicy = options?.policies?.response ?? deafultPolicies?.response;
|
||||||
|
|
||||||
|
const maxCommandRedirections = this._options.maxCommandRedirections ?? 16;
|
||||||
|
let client = await this._slots.getClient(firstKey, isReadonly);
|
||||||
|
for (let i = 0; ; i++) {
|
||||||
|
try {
|
||||||
|
return await client.sendCommand<T>(args, options);
|
||||||
|
} catch (err) {
|
||||||
|
// TODO: error class
|
||||||
|
if (++i > maxCommandRedirections || !(err instanceof Error)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.message.startsWith('ASK')) {
|
||||||
|
const address = err.message.substring(err.message.lastIndexOf(' ') + 1);
|
||||||
|
let redirectTo = await this._slots.getMasterByAddress(address);
|
||||||
|
if (!redirectTo) {
|
||||||
|
await this._slots.rediscover(client);
|
||||||
|
redirectTo = await this._slots.getMasterByAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!redirectTo) {
|
||||||
|
throw new Error(`Cannot find node ${address}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await redirectTo.asking();
|
||||||
|
client = redirectTo;
|
||||||
|
continue;
|
||||||
|
} else if (err.message.startsWith('MOVED')) {
|
||||||
|
await this._slots.rediscover(client);
|
||||||
|
client = await this._slots.getClient(firstKey, isReadonly);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MULTI(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, F, S> {
|
||||||
|
// return new this.#Multi(
|
||||||
|
// (commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => {
|
||||||
|
// return this.#execute(
|
||||||
|
// firstKey,
|
||||||
|
// false,
|
||||||
|
// client => client.multiExecutor(commands, undefined, chainId)
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// routing
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// multi = this.MULTI;
|
||||||
|
|
||||||
|
async SUBSCRIBE<T extends boolean = false>(
|
||||||
|
channels: string | Array<string>,
|
||||||
|
listener: PubSubListener<T>,
|
||||||
|
bufferMode?: T
|
||||||
|
) {
|
||||||
|
return (await this._slots.getPubSubClient())
|
||||||
|
.SUBSCRIBE(channels, listener, bufferMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe = this.SUBSCRIBE;
|
||||||
|
|
||||||
|
async UNSUBSCRIBE<T extends boolean = false>(
|
||||||
|
channels?: string | Array<string>,
|
||||||
|
listener?: PubSubListener<boolean>,
|
||||||
|
bufferMode?: T
|
||||||
|
) {
|
||||||
|
return this._slots.executeUnsubscribeCommand(client =>
|
||||||
|
client.UNSUBSCRIBE(channels, listener, bufferMode)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe = this.UNSUBSCRIBE;
|
||||||
|
|
||||||
|
async PSUBSCRIBE<T extends boolean = false>(
|
||||||
|
patterns: string | Array<string>,
|
||||||
|
listener: PubSubListener<T>,
|
||||||
|
bufferMode?: T
|
||||||
|
) {
|
||||||
|
return (await this._slots.getPubSubClient())
|
||||||
|
.PSUBSCRIBE(patterns, listener, bufferMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
pSubscribe = this.PSUBSCRIBE;
|
||||||
|
|
||||||
|
async PUNSUBSCRIBE<T extends boolean = false>(
|
||||||
|
patterns?: string | Array<string>,
|
||||||
|
listener?: PubSubListener<T>,
|
||||||
|
bufferMode?: T
|
||||||
|
) {
|
||||||
|
return this._slots.executeUnsubscribeCommand(client =>
|
||||||
|
client.PUNSUBSCRIBE(patterns, listener, bufferMode)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pUnsubscribe = this.PUNSUBSCRIBE;
|
||||||
|
|
||||||
|
async SSUBSCRIBE<T extends boolean = false>(
|
||||||
|
channels: string | Array<string>,
|
||||||
|
listener: PubSubListener<T>,
|
||||||
|
bufferMode?: T
|
||||||
|
) {
|
||||||
|
const maxCommandRedirections = this._options.maxCommandRedirections ?? 16,
|
||||||
|
firstChannel = Array.isArray(channels) ? channels[0] : channels;
|
||||||
|
let client = await this._slots.getShardedPubSubClient(firstChannel);
|
||||||
|
for (let i = 0; ; i++) {
|
||||||
|
try {
|
||||||
|
return await client.SSUBSCRIBE(channels, listener, bufferMode);
|
||||||
|
} catch (err) {
|
||||||
|
if (++i > maxCommandRedirections || !(err instanceof ErrorReply)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.message.startsWith('MOVED')) {
|
||||||
|
await this._slots.rediscover(client);
|
||||||
|
client = await this._slots.getShardedPubSubClient(firstChannel);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sSubscribe = this.SSUBSCRIBE;
|
||||||
|
|
||||||
|
SUNSUBSCRIBE<T extends boolean = false>(
|
||||||
|
channels: string | Array<string>,
|
||||||
|
listener: PubSubListener<T>,
|
||||||
|
bufferMode?: T
|
||||||
|
) {
|
||||||
|
return this._slots.executeShardedUnsubscribeCommand(
|
||||||
|
Array.isArray(channels) ? channels[0] : channels,
|
||||||
|
client => client.SUNSUBSCRIBE(channels, listener, bufferMode)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sUnsubscribe = this.SUNSUBSCRIBE;
|
||||||
|
|
||||||
|
// quit(): Promise<void> {
|
||||||
|
// return this.#slots.quit();
|
||||||
|
// }
|
||||||
|
|
||||||
|
disconnect(): Promise<void> {
|
||||||
|
return this._slots.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeClient(node: ShardNode<M, F, S, RESP>) {
|
||||||
|
return this._slots.nodeClient(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a random node from the cluster.
|
||||||
|
* Userful for running "forward" commands (like PUBLISH) on a random node.
|
||||||
|
*/
|
||||||
|
getRandomNode() {
|
||||||
|
return this._slots.getRandomNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a random node from a slot.
|
||||||
|
* Useful for running readonly commands on a slot.
|
||||||
|
*/
|
||||||
|
getSlotRandomNode(slot: number) {
|
||||||
|
return this._slots.getSlotRandomNode(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use `.masters` instead
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
getMasters() {
|
||||||
|
return this.masters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use `.slots[<SLOT>]` instead
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
getSlotMaster(slot: number) {
|
||||||
|
return this.slots[slot].master;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,141 +1,141 @@
|
|||||||
import COMMANDS from './commands';
|
// import COMMANDS from './commands';
|
||||||
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, ExcludeMappedString, RedisFunction } from '../commands';
|
// import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, ExcludeMappedString, RedisFunction } from '../commands';
|
||||||
import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command';
|
// import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command';
|
||||||
import { attachCommands, attachExtensions } from '../commander';
|
// import { attachCommands, attachExtensions } from '../commander';
|
||||||
import RedisCluster from '.';
|
// import RedisCluster from '.';
|
||||||
|
|
||||||
type RedisClusterMultiCommandSignature<
|
// type RedisClusterMultiCommandSignature<
|
||||||
C extends RedisCommand,
|
// C extends RedisCommand,
|
||||||
M extends RedisModules,
|
// M extends RedisModules,
|
||||||
F extends RedisFunctions,
|
// F extends RedisFunctions,
|
||||||
S extends RedisScripts
|
// S extends RedisScripts
|
||||||
> = (...args: Parameters<C['transformArguments']>) => RedisClusterMultiCommandType<M, F, S>;
|
// > = (...args: Parameters<C['transformArguments']>) => RedisClusterMultiCommandType<M, F, S>;
|
||||||
|
|
||||||
type WithCommands<
|
// type WithCommands<
|
||||||
M extends RedisModules,
|
// M extends RedisModules,
|
||||||
F extends RedisFunctions,
|
// F extends RedisFunctions,
|
||||||
S extends RedisScripts
|
// S extends RedisScripts
|
||||||
> = {
|
// > = {
|
||||||
[P in keyof typeof COMMANDS]: RedisClusterMultiCommandSignature<(typeof COMMANDS)[P], M, F, S>;
|
// [P in keyof typeof COMMANDS]: RedisClusterMultiCommandSignature<(typeof COMMANDS)[P], M, F, S>;
|
||||||
};
|
// };
|
||||||
|
|
||||||
type WithModules<
|
// type WithModules<
|
||||||
M extends RedisModules,
|
// M extends RedisModules,
|
||||||
F extends RedisFunctions,
|
// F extends RedisFunctions,
|
||||||
S extends RedisScripts
|
// S extends RedisScripts
|
||||||
> = {
|
// > = {
|
||||||
[P in keyof M as ExcludeMappedString<P>]: {
|
// [P in keyof M as ExcludeMappedString<P>]: {
|
||||||
[C in keyof M[P] as ExcludeMappedString<C>]: RedisClusterMultiCommandSignature<M[P][C], M, F, S>;
|
// [C in keyof M[P] as ExcludeMappedString<C>]: RedisClusterMultiCommandSignature<M[P][C], M, F, S>;
|
||||||
};
|
// };
|
||||||
};
|
// };
|
||||||
|
|
||||||
type WithFunctions<
|
// type WithFunctions<
|
||||||
M extends RedisModules,
|
// M extends RedisModules,
|
||||||
F extends RedisFunctions,
|
// F extends RedisFunctions,
|
||||||
S extends RedisScripts
|
// S extends RedisScripts
|
||||||
> = {
|
// > = {
|
||||||
[P in keyof F as ExcludeMappedString<P>]: {
|
// [P in keyof F as ExcludeMappedString<P>]: {
|
||||||
[FF in keyof F[P] as ExcludeMappedString<FF>]: RedisClusterMultiCommandSignature<F[P][FF], M, F, S>;
|
// [FF in keyof F[P] as ExcludeMappedString<FF>]: RedisClusterMultiCommandSignature<F[P][FF], M, F, S>;
|
||||||
};
|
// };
|
||||||
};
|
// };
|
||||||
|
|
||||||
type WithScripts<
|
// type WithScripts<
|
||||||
M extends RedisModules,
|
// M extends RedisModules,
|
||||||
F extends RedisFunctions,
|
// F extends RedisFunctions,
|
||||||
S extends RedisScripts
|
// S extends RedisScripts
|
||||||
> = {
|
// > = {
|
||||||
[P in keyof S as ExcludeMappedString<P>]: RedisClusterMultiCommandSignature<S[P], M, F, S>;
|
// [P in keyof S as ExcludeMappedString<P>]: RedisClusterMultiCommandSignature<S[P], M, F, S>;
|
||||||
};
|
// };
|
||||||
|
|
||||||
export type RedisClusterMultiCommandType<
|
// export type RedisClusterMultiCommandType<
|
||||||
M extends RedisModules,
|
// M extends RedisModules,
|
||||||
F extends RedisFunctions,
|
// F extends RedisFunctions,
|
||||||
S extends RedisScripts
|
// S extends RedisScripts
|
||||||
> = RedisClusterMultiCommand & WithCommands<M, F, S> & WithModules<M, F, S> & WithFunctions<M, F, S> & WithScripts<M, F, S>;
|
// > = RedisClusterMultiCommand & WithCommands<M, F, S> & WithModules<M, F, S> & WithFunctions<M, F, S> & WithScripts<M, F, S>;
|
||||||
|
|
||||||
export type InstantiableRedisClusterMultiCommandType<
|
// export type InstantiableRedisClusterMultiCommandType<
|
||||||
M extends RedisModules,
|
// M extends RedisModules,
|
||||||
F extends RedisFunctions,
|
// F extends RedisFunctions,
|
||||||
S extends RedisScripts
|
// S extends RedisScripts
|
||||||
> = new (...args: ConstructorParameters<typeof RedisClusterMultiCommand>) => RedisClusterMultiCommandType<M, F, S>;
|
// > = new (...args: ConstructorParameters<typeof RedisClusterMultiCommand>) => RedisClusterMultiCommandType<M, F, S>;
|
||||||
|
|
||||||
export type RedisClusterMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>;
|
// export type RedisClusterMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>;
|
||||||
|
|
||||||
export default class RedisClusterMultiCommand {
|
// export default class RedisClusterMultiCommand {
|
||||||
readonly #multi = new RedisMultiCommand();
|
// readonly #multi = new RedisMultiCommand();
|
||||||
readonly #executor: RedisClusterMultiExecutor;
|
// readonly #executor: RedisClusterMultiExecutor;
|
||||||
#firstKey: RedisCommandArgument | undefined;
|
// #firstKey: RedisCommandArgument | undefined;
|
||||||
|
|
||||||
static extend<
|
// static extend<
|
||||||
M extends RedisModules,
|
// M extends RedisModules,
|
||||||
F extends RedisFunctions,
|
// F extends RedisFunctions,
|
||||||
S extends RedisScripts
|
// S extends RedisScripts
|
||||||
>(extensions?: RedisExtensions<M, F, S>): InstantiableRedisClusterMultiCommandType<M, F, S> {
|
// >(extensions?: RedisExtensions<M, F, S>): InstantiableRedisClusterMultiCommandType<M, F, S> {
|
||||||
return attachExtensions({
|
// return attachExtensions({
|
||||||
BaseClass: RedisClusterMultiCommand,
|
// BaseClass: RedisClusterMultiCommand,
|
||||||
modulesExecutor: RedisClusterMultiCommand.prototype.commandsExecutor,
|
// modulesExecutor: RedisClusterMultiCommand.prototype.commandsExecutor,
|
||||||
modules: extensions?.modules,
|
// modules: extensions?.modules,
|
||||||
functionsExecutor: RedisClusterMultiCommand.prototype.functionsExecutor,
|
// functionsExecutor: RedisClusterMultiCommand.prototype.functionsExecutor,
|
||||||
functions: extensions?.functions,
|
// functions: extensions?.functions,
|
||||||
scriptsExecutor: RedisClusterMultiCommand.prototype.scriptsExecutor,
|
// scriptsExecutor: RedisClusterMultiCommand.prototype.scriptsExecutor,
|
||||||
scripts: extensions?.scripts
|
// scripts: extensions?.scripts
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
constructor(executor: RedisClusterMultiExecutor, firstKey?: RedisCommandArgument) {
|
// constructor(executor: RedisClusterMultiExecutor, firstKey?: RedisCommandArgument) {
|
||||||
this.#executor = executor;
|
// this.#executor = executor;
|
||||||
this.#firstKey = firstKey;
|
// this.#firstKey = firstKey;
|
||||||
}
|
// }
|
||||||
|
|
||||||
commandsExecutor(command: RedisCommand, args: Array<unknown>): this {
|
// commandsExecutor(command: RedisCommand, args: Array<unknown>): this {
|
||||||
const transformedArguments = command.transformArguments(...args);
|
// const transformedArguments = command.transformArguments(...args);
|
||||||
this.#firstKey ??= RedisCluster.extractFirstKey(command, args, transformedArguments);
|
// this.#firstKey ??= RedisCluster.extractFirstKey(command, args, transformedArguments);
|
||||||
return this.addCommand(undefined, transformedArguments, command.transformReply);
|
// return this.addCommand(undefined, transformedArguments, command.transformReply);
|
||||||
}
|
// }
|
||||||
|
|
||||||
addCommand(
|
// addCommand(
|
||||||
firstKey: RedisCommandArgument | undefined,
|
// firstKey: RedisCommandArgument | undefined,
|
||||||
args: RedisCommandArguments,
|
// args: RedisCommandArguments,
|
||||||
transformReply?: RedisCommand['transformReply']
|
// transformReply?: RedisCommand['transformReply']
|
||||||
): this {
|
// ): this {
|
||||||
this.#firstKey ??= firstKey;
|
// this.#firstKey ??= firstKey;
|
||||||
this.#multi.addCommand(args, transformReply);
|
// this.#multi.addCommand(args, transformReply);
|
||||||
return this;
|
// return this;
|
||||||
}
|
// }
|
||||||
|
|
||||||
functionsExecutor(fn: RedisFunction, args: Array<unknown>, name: string): this {
|
// functionsExecutor(fn: RedisFunction, args: Array<unknown>, name: string): this {
|
||||||
const transformedArguments = this.#multi.addFunction(name, fn, args);
|
// const transformedArguments = this.#multi.addFunction(name, fn, args);
|
||||||
this.#firstKey ??= RedisCluster.extractFirstKey(fn, args, transformedArguments);
|
// this.#firstKey ??= RedisCluster.extractFirstKey(fn, args, transformedArguments);
|
||||||
return this;
|
// return this;
|
||||||
}
|
// }
|
||||||
|
|
||||||
scriptsExecutor(script: RedisScript, args: Array<unknown>): this {
|
// scriptsExecutor(script: RedisScript, args: Array<unknown>): this {
|
||||||
const transformedArguments = this.#multi.addScript(script, args);
|
// const transformedArguments = this.#multi.addScript(script, args);
|
||||||
this.#firstKey ??= RedisCluster.extractFirstKey(script, args, transformedArguments);
|
// this.#firstKey ??= RedisCluster.extractFirstKey(script, args, transformedArguments);
|
||||||
return this;
|
// return this;
|
||||||
}
|
// }
|
||||||
|
|
||||||
async exec(execAsPipeline = false): Promise<Array<RedisCommandRawReply>> {
|
// async exec(execAsPipeline = false): Promise<Array<RedisCommandRawReply>> {
|
||||||
if (execAsPipeline) {
|
// if (execAsPipeline) {
|
||||||
return this.execAsPipeline();
|
// return this.execAsPipeline();
|
||||||
}
|
// }
|
||||||
|
|
||||||
return this.#multi.handleExecReplies(
|
// return this.#multi.handleExecReplies(
|
||||||
await this.#executor(this.#multi.queue, this.#firstKey, RedisMultiCommand.generateChainId())
|
// await this.#executor(this.#multi.queue, this.#firstKey, RedisMultiCommand.generateChainId())
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
EXEC = this.exec;
|
// EXEC = this.exec;
|
||||||
|
|
||||||
async execAsPipeline(): Promise<Array<RedisCommandRawReply>> {
|
// async execAsPipeline(): Promise<Array<RedisCommandRawReply>> {
|
||||||
return this.#multi.transformReplies(
|
// return this.#multi.transformReplies(
|
||||||
await this.#executor(this.#multi.queue, this.#firstKey)
|
// await this.#executor(this.#multi.queue, this.#firstKey)
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
attachCommands({
|
// attachCommands({
|
||||||
BaseClass: RedisClusterMultiCommand,
|
// BaseClass: RedisClusterMultiCommand,
|
||||||
commands: COMMANDS,
|
// commands: COMMANDS,
|
||||||
executor: RedisClusterMultiCommand.prototype.commandsExecutor
|
// executor: RedisClusterMultiCommand.prototype.commandsExecutor
|
||||||
});
|
// });
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
const symbol = Symbol('Command Options');
|
|
||||||
|
|
||||||
export type CommandOptions<T> = T & {
|
|
||||||
readonly [symbol]: true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function commandOptions<T>(options: T): CommandOptions<T> {
|
|
||||||
(options as any)[symbol] = true;
|
|
||||||
return options as CommandOptions<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCommandOptions<T>(options: any): options is CommandOptions<T> {
|
|
||||||
return options?.[symbol] === true;
|
|
||||||
}
|
|
@@ -1,163 +1,115 @@
|
|||||||
|
import { Command, CommanderConfig, RedisCommands, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, RespVersions } from './RESP/types';
|
||||||
|
|
||||||
import { ClientCommandOptions } from './client';
|
interface AttachConfigOptions<
|
||||||
import { CommandOptions, isCommandOptions } from './command-options';
|
M extends RedisModules,
|
||||||
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandReply, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts } from './commands';
|
F extends RedisFunctions,
|
||||||
|
S extends RedisScripts,
|
||||||
type Instantiable<T = any> = new (...args: Array<any>) => T;
|
RESP extends RespVersions
|
||||||
|
> {
|
||||||
type CommandsExecutor<C extends RedisCommand = RedisCommand> =
|
BaseClass: new (...args: any) => any;
|
||||||
(command: C, args: Array<unknown>, name: string) => unknown;
|
commands: RedisCommands;
|
||||||
|
createCommand(command: Command, resp: RespVersions): (...args: any) => any;
|
||||||
interface AttachCommandsConfig<C extends RedisCommand> {
|
createModuleCommand(command: Command, resp: RespVersions): (...args: any) => any;
|
||||||
BaseClass: Instantiable;
|
createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions): (...args: any) => any;
|
||||||
commands: Record<string, C>;
|
createScriptCommand(script: RedisScript, resp: RespVersions): (...args: any) => any;
|
||||||
executor: CommandsExecutor<C>;
|
config?: CommanderConfig<M, F, S, RESP>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachCommands<C extends RedisCommand>({
|
export function attachConfig<
|
||||||
BaseClass,
|
M extends RedisModules,
|
||||||
commands,
|
F extends RedisFunctions,
|
||||||
executor
|
S extends RedisScripts,
|
||||||
}: AttachCommandsConfig<C>): void {
|
RESP extends RespVersions
|
||||||
for (const [name, command] of Object.entries(commands)) {
|
>({
|
||||||
BaseClass.prototype[name] = function (...args: Array<unknown>): unknown {
|
BaseClass,
|
||||||
return executor.call(this, command, args, name);
|
commands,
|
||||||
};
|
createCommand,
|
||||||
|
createModuleCommand,
|
||||||
|
createFunctionCommand,
|
||||||
|
createScriptCommand,
|
||||||
|
config
|
||||||
|
}: AttachConfigOptions<M, F, S, RESP>) {
|
||||||
|
const RESP = config?.RESP ?? 2,
|
||||||
|
Class: any = class extends BaseClass {};
|
||||||
|
|
||||||
|
for (const [name, command] of Object.entries(commands)) {
|
||||||
|
Class.prototype[name] = createCommand(command, RESP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config?.modules) {
|
||||||
|
for (const [moduleName, module] of Object.entries(config.modules)) {
|
||||||
|
const fns = Object.create(null);
|
||||||
|
for (const [name, command] of Object.entries(module)) {
|
||||||
|
fns[name] = createModuleCommand(command, RESP);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachNamespace(Class.prototype, moduleName, fns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AttachExtensionsConfig<T extends Instantiable = Instantiable> {
|
if (config?.functions) {
|
||||||
BaseClass: T;
|
for (const [library, commands] of Object.entries(config.functions)) {
|
||||||
modulesExecutor: CommandsExecutor;
|
const fns = Object.create(null);
|
||||||
modules?: RedisModules;
|
for (const [name, command] of Object.entries(commands)) {
|
||||||
functionsExecutor: CommandsExecutor<RedisFunction>;
|
fns[name] = createFunctionCommand(name, command, RESP);
|
||||||
functions?: RedisFunctions;
|
}
|
||||||
scriptsExecutor: CommandsExecutor<RedisScript>;
|
|
||||||
scripts?: RedisScripts;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function attachExtensions(config: AttachExtensionsConfig): any {
|
attachNamespace(Class.prototype, library, fns);
|
||||||
let Commander;
|
|
||||||
|
|
||||||
if (config.modules) {
|
|
||||||
Commander = attachWithNamespaces({
|
|
||||||
BaseClass: config.BaseClass,
|
|
||||||
namespaces: config.modules,
|
|
||||||
executor: config.modulesExecutor
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.functions) {
|
if (config?.scripts) {
|
||||||
Commander = attachWithNamespaces({
|
for (const [name, script] of Object.entries(config.scripts)) {
|
||||||
BaseClass: Commander ?? config.BaseClass,
|
Class.prototype[name] = createScriptCommand(script, RESP);
|
||||||
namespaces: config.functions,
|
|
||||||
executor: config.functionsExecutor
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.scripts) {
|
return Class;
|
||||||
Commander ??= class extends config.BaseClass {};
|
}
|
||||||
attachCommands({
|
|
||||||
BaseClass: Commander,
|
function attachNamespace(prototype: any, name: PropertyKey, fns: any) {
|
||||||
commands: config.scripts,
|
Object.defineProperty(prototype, name, {
|
||||||
executor: config.scriptsExecutor
|
get() {
|
||||||
});
|
const value = Object.create(fns);
|
||||||
|
value.self = this;
|
||||||
|
Object.defineProperty(this, name, { value });
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return Commander ?? config.BaseClass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AttachWithNamespacesConfig<C extends RedisCommand> {
|
export function getTransformReply(command: Command, resp: RespVersions) {
|
||||||
BaseClass: Instantiable;
|
switch (typeof command.transformReply) {
|
||||||
namespaces: Record<string, Record<string, C>>;
|
case 'function':
|
||||||
executor: CommandsExecutor<C>;
|
return command.transformReply;
|
||||||
|
|
||||||
|
case 'object':
|
||||||
|
return command.transformReply[resp];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachWithNamespaces<C extends RedisCommand>({
|
export function functionArgumentsPrefix(name: string, fn: RedisFunction) {
|
||||||
BaseClass,
|
const prefix: Array<string | Buffer> = [
|
||||||
namespaces,
|
fn.IS_READ_ONLY ? 'FCALL_RO' : 'FCALL',
|
||||||
executor
|
name
|
||||||
}: AttachWithNamespacesConfig<C>): any {
|
];
|
||||||
const Commander = class extends BaseClass {
|
|
||||||
constructor(...args: Array<any>) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
for (const namespace of Object.keys(namespaces)) {
|
if (fn.NUMBER_OF_KEYS !== undefined) {
|
||||||
this[namespace] = Object.create(this[namespace], {
|
prefix.push(fn.NUMBER_OF_KEYS.toString());
|
||||||
self: {
|
}
|
||||||
value: this
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [namespace, commands] of Object.entries(namespaces)) {
|
return prefix;
|
||||||
Commander.prototype[namespace] = {};
|
|
||||||
for (const [name, command] of Object.entries(commands)) {
|
|
||||||
Commander.prototype[namespace][name] = function (...args: Array<unknown>): unknown {
|
|
||||||
return executor.call(this.self, command, args, name);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Commander;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformCommandArguments<T = ClientCommandOptions>(
|
export function scriptArgumentsPrefix(script: RedisScript) {
|
||||||
command: RedisCommand,
|
const prefix: Array<string | Buffer> = [
|
||||||
args: Array<unknown>
|
script.IS_READ_ONLY ? 'EVALSHA_RO' : 'EVALSHA',
|
||||||
): {
|
script.SHA1
|
||||||
args: RedisCommandArguments;
|
];
|
||||||
options: CommandOptions<T> | undefined;
|
|
||||||
} {
|
|
||||||
let options;
|
|
||||||
if (isCommandOptions<T>(args[0])) {
|
|
||||||
options = args[0];
|
|
||||||
args = args.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
if (script.NUMBER_OF_KEYS !== undefined) {
|
||||||
args: command.transformArguments(...args),
|
prefix.push(script.NUMBER_OF_KEYS.toString());
|
||||||
options
|
}
|
||||||
};
|
|
||||||
}
|
return prefix;
|
||||||
|
|
||||||
export function transformLegacyCommandArguments(args: Array<any>): Array<any> {
|
|
||||||
return args.flat().map(arg => {
|
|
||||||
return typeof arg === 'number' || arg instanceof Date ?
|
|
||||||
arg.toString() :
|
|
||||||
arg;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformCommandReply<C extends RedisCommand>(
|
|
||||||
command: C,
|
|
||||||
rawReply: unknown,
|
|
||||||
preserved: unknown
|
|
||||||
): RedisCommandReply<C> {
|
|
||||||
if (!command.transformReply) {
|
|
||||||
return rawReply as RedisCommandReply<C>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return command.transformReply(rawReply, preserved);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fCallArguments(
|
|
||||||
name: RedisCommandArgument,
|
|
||||||
fn: RedisFunction,
|
|
||||||
args: RedisCommandArguments
|
|
||||||
): RedisCommandArguments {
|
|
||||||
const actualArgs: RedisCommandArguments = [
|
|
||||||
fn.IS_READ_ONLY ? 'FCALL_RO' : 'FCALL',
|
|
||||||
name
|
|
||||||
];
|
|
||||||
|
|
||||||
if (fn.NUMBER_OF_KEYS !== undefined) {
|
|
||||||
actualArgs.push(fn.NUMBER_OF_KEYS.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
actualArgs.push(...args);
|
|
||||||
|
|
||||||
return actualArgs;
|
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,31 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './ACL_CAT';
|
import ACL_CAT from './ACL_CAT';
|
||||||
|
|
||||||
describe('ACL CAT', () => {
|
describe('ACL CAT', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
describe('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
it('simple', () => {
|
it('simple', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
ACL_CAT.transformArguments(),
|
||||||
['ACL', 'CAT']
|
['ACL', 'CAT']
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it('with categoryName', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformArguments('dangerous'),
|
|
||||||
['ACL', 'CAT', 'dangerous']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('with categoryName', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
ACL_CAT.transformArguments('dangerous'),
|
||||||
|
['ACL', 'CAT', 'dangerous']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClient('client.aclCat', async client => {
|
||||||
|
const categories = await client.aclCat();
|
||||||
|
assert.ok(Array.isArray(categories));
|
||||||
|
for (const category of categories) {
|
||||||
|
assert.equal(typeof category, 'string');
|
||||||
|
}
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,16 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, ArrayReply, BlobStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(categoryName?: RedisCommandArgument): RedisCommandArguments {
|
export default {
|
||||||
const args: RedisCommandArguments = ['ACL', 'CAT'];
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments(categoryName?: RedisArgument) {
|
||||||
|
const args: Array<RedisArgument> = ['ACL', 'CAT'];
|
||||||
|
|
||||||
if (categoryName) {
|
if (categoryName) {
|
||||||
args.push(categoryName);
|
args.push(categoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>
|
||||||
export declare function transformReply(): Array<RedisCommandArgument>;
|
} as const satisfies Command;
|
||||||
|
@@ -1,30 +1,30 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils, { GLOBAL } from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './ACL_DELUSER';
|
import ACL_DELUSER from './ACL_DELUSER';
|
||||||
|
|
||||||
describe('ACL DELUSER', () => {
|
describe('ACL DELUSER', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
describe('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
it('string', () => {
|
it('string', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments('username'),
|
ACL_DELUSER.transformArguments('username'),
|
||||||
['ACL', 'DELUSER', 'username']
|
['ACL', 'DELUSER', 'username']
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it('array', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformArguments(['1', '2']),
|
|
||||||
['ACL', 'DELUSER', '1', '2']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.aclDelUser', async client => {
|
it('array', () => {
|
||||||
assert.equal(
|
assert.deepEqual(
|
||||||
await client.aclDelUser('dosenotexists'),
|
ACL_DELUSER.transformArguments(['1', '2']),
|
||||||
0
|
['ACL', 'DELUSER', '1', '2']
|
||||||
);
|
);
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClient('client.aclDelUser', async client => {
|
||||||
|
assert.equal(
|
||||||
|
typeof await client.aclDelUser('user'),
|
||||||
|
'number'
|
||||||
|
);
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { NumberReply, Command } from '../RESP/types';
|
||||||
import { pushVerdictArguments } from './generic-transformers';
|
import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
|
||||||
|
|
||||||
export function transformArguments(
|
export default {
|
||||||
username: RedisCommandArgument | Array<RedisCommandArgument>
|
FIRST_KEY_INDEX: undefined,
|
||||||
): RedisCommandArguments {
|
IS_READ_ONLY: true,
|
||||||
return pushVerdictArguments(['ACL', 'DELUSER'], username);
|
transformArguments(username: RedisVariadicArgument) {
|
||||||
}
|
return pushVariadicArguments(['ACL', 'DELUSER'], username);
|
||||||
|
},
|
||||||
export declare function transformReply(): number;
|
transformReply: undefined as unknown as () => NumberReply
|
||||||
|
} as const satisfies Command;
|
||||||
|
@@ -1,21 +1,21 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils, { GLOBAL } from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './ACL_DRYRUN';
|
import ACL_DRYRUN from './ACL_DRYRUN';
|
||||||
|
|
||||||
describe('ACL DRYRUN', () => {
|
describe('ACL DRYRUN', () => {
|
||||||
testUtils.isVersionGreaterThanHook([7]);
|
testUtils.isVersionGreaterThanHook([7]);
|
||||||
|
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments('default', ['GET', 'key']),
|
ACL_DRYRUN.transformArguments('default', ['GET', 'key']),
|
||||||
['ACL', 'DRYRUN', 'default', 'GET', 'key']
|
['ACL', 'DRYRUN', 'default', 'GET', 'key']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.aclDryRun', async client => {
|
testUtils.testWithClient('client.aclDryRun', async client => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await client.aclDryRun('default', ['GET', 'key']),
|
await client.aclDryRun('default', ['GET', 'key']),
|
||||||
'OK'
|
'OK'
|
||||||
);
|
);
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, SimpleStringReply, BlobStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export const IS_READ_ONLY = true;
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
export function transformArguments(
|
IS_READ_ONLY: true,
|
||||||
username: RedisCommandArgument,
|
transformArguments(username: RedisArgument, command: Array<RedisArgument>) {
|
||||||
command: Array<RedisCommandArgument>
|
|
||||||
): RedisCommandArguments {
|
|
||||||
return [
|
return [
|
||||||
'ACL',
|
'ACL',
|
||||||
'DRYRUN',
|
'DRYRUN',
|
||||||
username,
|
username,
|
||||||
...command
|
...command
|
||||||
];
|
];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply<'OK'> | BlobStringReply
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
} as const satisfies Command;
|
||||||
|
|
||||||
|
@@ -1,23 +1,30 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './ACL_GENPASS';
|
import ACL_GENPASS from './ACL_GENPASS';
|
||||||
|
|
||||||
describe('ACL GENPASS', () => {
|
describe('ACL GENPASS', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
describe('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
it('simple', () => {
|
it('simple', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
ACL_GENPASS.transformArguments(),
|
||||||
['ACL', 'GENPASS']
|
['ACL', 'GENPASS']
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it('with bits', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformArguments(128),
|
|
||||||
['ACL', 'GENPASS', '128']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('with bits', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
ACL_GENPASS.transformArguments(128),
|
||||||
|
['ACL', 'GENPASS', '128']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClient('client.aclGenPass', async client => {
|
||||||
|
assert.equal(
|
||||||
|
typeof await client.aclGenPass(),
|
||||||
|
'string'
|
||||||
|
);
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { BlobStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(bits?: number): RedisCommandArguments {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments(bits?: number) {
|
||||||
const args = ['ACL', 'GENPASS'];
|
const args = ['ACL', 'GENPASS'];
|
||||||
|
|
||||||
if (bits) {
|
if (bits) {
|
||||||
args.push(bits.toString());
|
args.push(bits.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => BlobStringReply
|
||||||
|
} as const satisfies Command;
|
||||||
|
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
|
||||||
|
@@ -1,44 +1,34 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils, { GLOBAL } from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './ACL_GETUSER';
|
import ACL_GETUSER from './ACL_GETUSER';
|
||||||
|
|
||||||
describe('ACL GETUSER', () => {
|
describe('ACL GETUSER', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments('username'),
|
ACL_GETUSER.transformArguments('username'),
|
||||||
['ACL', 'GETUSER', 'username']
|
['ACL', 'GETUSER', 'username']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.aclGetUser', async client => {
|
testUtils.testWithClient('client.aclGetUser', async client => {
|
||||||
const expectedReply: any = {
|
const reply = await client.aclGetUser('default');
|
||||||
passwords: [],
|
|
||||||
commands: '+@all',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (testUtils.isVersionGreaterThan([7])) {
|
assert.ok(Array.isArray(reply.passwords));
|
||||||
expectedReply.flags = ['on', 'nopass'];
|
assert.equal(typeof reply.commands, 'string');
|
||||||
expectedReply.keys = '~*';
|
assert.ok(Array.isArray(reply.flags));
|
||||||
expectedReply.channels = '&*';
|
|
||||||
expectedReply.selectors = [];
|
|
||||||
} else {
|
|
||||||
expectedReply.keys = ['*'];
|
|
||||||
expectedReply.selectors = undefined;
|
|
||||||
|
|
||||||
if (testUtils.isVersionGreaterThan([6, 2])) {
|
if (testUtils.isVersionGreaterThan([7])) {
|
||||||
expectedReply.flags = ['on', 'allkeys', 'allchannels', 'allcommands', 'nopass'];
|
assert.equal(typeof reply.keys, 'string');
|
||||||
expectedReply.channels = ['*'];
|
assert.equal(typeof reply.channels, 'string');
|
||||||
} else {
|
assert.ok(Array.isArray(reply.selectors));
|
||||||
expectedReply.flags = ['on', 'allkeys', 'allcommands', 'nopass'];
|
} else {
|
||||||
expectedReply.channels = undefined;
|
assert.ok(Array.isArray(reply.keys));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.deepEqual(
|
if (testUtils.isVersionGreaterThan([6, 2])) {
|
||||||
await client.aclGetUser('default'),
|
assert.ok(Array.isArray(reply.channels));
|
||||||
expectedReply
|
}
|
||||||
);
|
}
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,40 +1,40 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, TuplesToMapReply, BlobStringReply, ArrayReply, Resp2Reply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(username: RedisCommandArgument): RedisCommandArguments {
|
type AclUser = TuplesToMapReply<[
|
||||||
|
[BlobStringReply<'flags'>, ArrayReply<BlobStringReply>],
|
||||||
|
[BlobStringReply<'passwords'>, ArrayReply<BlobStringReply>],
|
||||||
|
[BlobStringReply<'commands'>, BlobStringReply],
|
||||||
|
/** changed to BlobStringReply in 7.0 */
|
||||||
|
[BlobStringReply<'keys'>, ArrayReply<BlobStringReply> | BlobStringReply],
|
||||||
|
/** added in 6.2, changed to BlobStringReply in 7.0 */
|
||||||
|
[BlobStringReply<'channels'>, ArrayReply<BlobStringReply> | BlobStringReply],
|
||||||
|
/** added in 7.0 */
|
||||||
|
[BlobStringReply<'selectors'>, ArrayReply<TuplesToMapReply<[
|
||||||
|
[BlobStringReply<'commands'>, BlobStringReply],
|
||||||
|
[BlobStringReply<'keys'>, BlobStringReply],
|
||||||
|
[BlobStringReply<'channels'>, BlobStringReply]
|
||||||
|
]>>],
|
||||||
|
]>;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments(username: RedisArgument) {
|
||||||
return ['ACL', 'GETUSER', username];
|
return ['ACL', 'GETUSER', username];
|
||||||
}
|
},
|
||||||
|
transformReply: {
|
||||||
type AclGetUserRawReply = [
|
2: (reply: Resp2Reply<AclUser>) => ({
|
||||||
'flags',
|
flags: reply[1],
|
||||||
Array<RedisCommandArgument>,
|
passwords: reply[3],
|
||||||
'passwords',
|
commands: reply[5],
|
||||||
Array<RedisCommandArgument>,
|
keys: reply[7],
|
||||||
'commands',
|
channels: reply[9],
|
||||||
RedisCommandArgument,
|
selectors: reply[11]?.map(selector => ({
|
||||||
'keys',
|
commands: selector[1],
|
||||||
Array<RedisCommandArgument> | RedisCommandArgument,
|
keys: selector[3],
|
||||||
'channels',
|
channels: selector[5]
|
||||||
Array<RedisCommandArgument> | RedisCommandArgument,
|
}))
|
||||||
'selectors' | undefined,
|
}),
|
||||||
Array<Array<string>> | undefined
|
3: undefined as unknown as () => AclUser
|
||||||
];
|
}
|
||||||
|
} as const satisfies Command;
|
||||||
interface AclUser {
|
|
||||||
flags: Array<RedisCommandArgument>;
|
|
||||||
passwords: Array<RedisCommandArgument>;
|
|
||||||
commands: RedisCommandArgument;
|
|
||||||
keys: Array<RedisCommandArgument> | RedisCommandArgument;
|
|
||||||
channels: Array<RedisCommandArgument> | RedisCommandArgument;
|
|
||||||
selectors?: Array<Array<string>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformReply(reply: AclGetUserRawReply): AclUser {
|
|
||||||
return {
|
|
||||||
flags: reply[1],
|
|
||||||
passwords: reply[3],
|
|
||||||
commands: reply[5],
|
|
||||||
keys: reply[7],
|
|
||||||
channels: reply[9],
|
|
||||||
selectors: reply[11]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@@ -1,14 +1,22 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './ACL_LIST';
|
import ACL_LIST from './ACL_LIST';
|
||||||
|
|
||||||
describe('ACL LIST', () => {
|
describe('ACL LIST', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
ACL_LIST.transformArguments(),
|
||||||
['ACL', 'LIST']
|
['ACL', 'LIST']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClient('client.aclList', async client => {
|
||||||
|
const users = await client.aclList();
|
||||||
|
assert.ok(Array.isArray(users));
|
||||||
|
for (const user of users) {
|
||||||
|
assert.equal(typeof user, 'string');
|
||||||
|
}
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { ArrayReply, BlobStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(): RedisCommandArguments {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
return ['ACL', 'LIST'];
|
return ['ACL', 'LIST'];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>
|
||||||
export declare function transformReply(): Array<RedisCommandArgument>;
|
} as const satisfies Command;
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils from '../test-utils';
|
import testUtils from '../test-utils';
|
||||||
import { transformArguments } from './ACL_SAVE';
|
import ACL_LOAD from './ACL_LOAD';
|
||||||
|
|
||||||
describe('ACL SAVE', () => {
|
describe('ACL LOAD', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
ACL_LOAD.transformArguments(),
|
||||||
['ACL', 'SAVE']
|
['ACL', 'LOAD']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { SimpleStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(): RedisCommandArguments {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
return ['ACL', 'LOAD'];
|
return ['ACL', 'LOAD'];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
} as const satisfies Command;
|
||||||
|
@@ -1,53 +1,53 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments, transformReply } from './ACL_LOG';
|
import ACL_LOG from './ACL_LOG';
|
||||||
|
|
||||||
describe('ACL LOG', () => {
|
describe('ACL LOG', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
describe('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
it('simple', () => {
|
it('simple', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
ACL_LOG.transformArguments(),
|
||||||
['ACL', 'LOG']
|
['ACL', 'LOG']
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it('with count', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformArguments(10),
|
|
||||||
['ACL', 'LOG', '10']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('transformReply', () => {
|
it('with count', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformReply([[
|
ACL_LOG.transformArguments(10),
|
||||||
'count',
|
['ACL', 'LOG', '10']
|
||||||
1,
|
);
|
||||||
'reason',
|
|
||||||
'auth',
|
|
||||||
'context',
|
|
||||||
'toplevel',
|
|
||||||
'object',
|
|
||||||
'AUTH',
|
|
||||||
'username',
|
|
||||||
'someuser',
|
|
||||||
'age-seconds',
|
|
||||||
'4.096',
|
|
||||||
'client-info',
|
|
||||||
'id=6 addr=127.0.0.1:63026 fd=8 name= age=9 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=48 qbuf-free=32720 obl=0 oll=0 omem=0 events=r cmd=auth user=default'
|
|
||||||
]]),
|
|
||||||
[{
|
|
||||||
count: 1,
|
|
||||||
reason: 'auth',
|
|
||||||
context: 'toplevel',
|
|
||||||
object: 'AUTH',
|
|
||||||
username: 'someuser',
|
|
||||||
ageSeconds: 4.096,
|
|
||||||
clientInfo: 'id=6 addr=127.0.0.1:63026 fd=8 name= age=9 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=48 qbuf-free=32720 obl=0 oll=0 omem=0 events=r cmd=auth user=default'
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClient('client.aclLog', async client => {
|
||||||
|
// make sure to create at least one log
|
||||||
|
await Promise.all([
|
||||||
|
client.aclSetUser('test', 'on >test'),
|
||||||
|
client.auth({
|
||||||
|
username: 'test',
|
||||||
|
password: 'test'
|
||||||
|
}),
|
||||||
|
client.auth({
|
||||||
|
username: 'default',
|
||||||
|
password: ''
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
const logs = await client.aclLog();
|
||||||
|
assert.ok(Array.isArray(logs));
|
||||||
|
for (const log of logs) {
|
||||||
|
|
||||||
|
assert.equal(typeof log.count, 'number');
|
||||||
|
assert.equal(typeof log.timestamp, 'number');
|
||||||
|
assert.equal(typeof log.username, 'string');
|
||||||
|
assert.equal(typeof log.clientId, 'string');
|
||||||
|
assert.equal(typeof log.command, 'string');
|
||||||
|
assert.equal(typeof log.args, 'string');
|
||||||
|
assert.equal(typeof log.key, 'string');
|
||||||
|
assert.equal(typeof log.result, 'number');
|
||||||
|
assert.equal(typeof log.duration, 'number');
|
||||||
|
}
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,50 +1,38 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { Resp2Reply } from '../RESP/types';
|
||||||
|
import { ArrayReply, BlobStringReply, Command, NumberReply, TuplesToMapReply } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(count?: number): RedisCommandArguments {
|
export type AclLogReply = ArrayReply<TuplesToMapReply<[
|
||||||
|
[BlobStringReply<'count'>, NumberReply],
|
||||||
|
[BlobStringReply<'reason'>, BlobStringReply],
|
||||||
|
[BlobStringReply<'context'>, BlobStringReply],
|
||||||
|
[BlobStringReply<'object'>, BlobStringReply],
|
||||||
|
[BlobStringReply<'username'>, BlobStringReply],
|
||||||
|
[BlobStringReply<'age-seconds'>, BlobStringReply],
|
||||||
|
[BlobStringReply<'client-info'>, BlobStringReply]
|
||||||
|
]>>;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments(count?: number) {
|
||||||
const args = ['ACL', 'LOG'];
|
const args = ['ACL', 'LOG'];
|
||||||
|
|
||||||
if (count) {
|
if (count !== undefined) {
|
||||||
args.push(count.toString());
|
args.push(count.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply: {
|
||||||
type AclLogRawReply = [
|
2: (reply: Resp2Reply<AclLogReply>) => ({
|
||||||
_: RedisCommandArgument,
|
count: Number(reply[1]),
|
||||||
count: number,
|
reason: reply[3],
|
||||||
_: RedisCommandArgument,
|
context: reply[5],
|
||||||
reason: RedisCommandArgument,
|
object: reply[7],
|
||||||
_: RedisCommandArgument,
|
username: reply[9],
|
||||||
context: RedisCommandArgument,
|
'age-seconds': Number(reply[11]),
|
||||||
_: RedisCommandArgument,
|
'client-info': reply[13]
|
||||||
object: RedisCommandArgument,
|
}),
|
||||||
_: RedisCommandArgument,
|
3: undefined as unknown as () => AclLogReply
|
||||||
username: RedisCommandArgument,
|
}
|
||||||
_: RedisCommandArgument,
|
} as const satisfies Command;
|
||||||
ageSeconds: RedisCommandArgument,
|
|
||||||
_: RedisCommandArgument,
|
|
||||||
clientInfo: RedisCommandArgument
|
|
||||||
];
|
|
||||||
|
|
||||||
interface AclLog {
|
|
||||||
count: number;
|
|
||||||
reason: RedisCommandArgument;
|
|
||||||
context: RedisCommandArgument;
|
|
||||||
object: RedisCommandArgument;
|
|
||||||
username: RedisCommandArgument;
|
|
||||||
ageSeconds: number;
|
|
||||||
clientInfo: RedisCommandArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformReply(reply: Array<AclLogRawReply>): Array<AclLog> {
|
|
||||||
return reply.map(log => ({
|
|
||||||
count: log[1],
|
|
||||||
reason: log[3],
|
|
||||||
context: log[5],
|
|
||||||
object: log[7],
|
|
||||||
username: log[9],
|
|
||||||
ageSeconds: Number(log[11]),
|
|
||||||
clientInfo: log[13]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
@@ -1,14 +1,21 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './ACL_LOG_RESET';
|
import ACL_LOG_RESET from './ACL_LOG_RESET';
|
||||||
|
|
||||||
describe('ACL LOG RESET', () => {
|
describe('ACL LOG RESET', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
ACL_LOG_RESET.transformArguments(),
|
||||||
['ACL', 'LOG', 'RESET']
|
['ACL', 'LOG', 'RESET']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClient('client.aclLogReset', async client => {
|
||||||
|
assert.equal(
|
||||||
|
await client.aclLogReset(),
|
||||||
|
'OK'
|
||||||
|
);
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { SimpleStringReply, Command } from '../RESP/types';
|
||||||
|
import ACL_LOG from './ACL_LOG';
|
||||||
|
|
||||||
export function transformArguments(): RedisCommandArguments {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: ACL_LOG.IS_READ_ONLY,
|
||||||
|
transformArguments() {
|
||||||
return ['ACL', 'LOG', 'RESET'];
|
return ['ACL', 'LOG', 'RESET'];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
} as const satisfies Command;
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils from '../test-utils';
|
import testUtils from '../test-utils';
|
||||||
import { transformArguments } from './ACL_LOAD';
|
import ACL_SAVE from './ACL_SAVE';
|
||||||
|
|
||||||
describe('ACL LOAD', () => {
|
describe('ACL SAVE', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6]);
|
testUtils.isVersionGreaterThanHook([6]);
|
||||||
|
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
ACL_SAVE.transformArguments(),
|
||||||
['ACL', 'LOAD']
|
['ACL', 'SAVE']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { SimpleStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(): RedisCommandArguments {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
return ['ACL', 'SAVE'];
|
return ['ACL', 'SAVE'];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
} as const satisfies Command;
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, SimpleStringReply, Command } from '../RESP/types';
|
||||||
import { pushVerdictArguments } from './generic-transformers';
|
import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
|
||||||
|
|
||||||
export function transformArguments(
|
export default {
|
||||||
username: RedisCommandArgument,
|
FIRST_KEY_INDEX: undefined,
|
||||||
rule: RedisCommandArgument | Array<RedisCommandArgument>
|
IS_READ_ONLY: true,
|
||||||
): RedisCommandArguments {
|
transformArguments(username: RedisArgument, rule: RedisVariadicArgument) {
|
||||||
return pushVerdictArguments(['ACL', 'SETUSER', username], rule);
|
return pushVariadicArguments(['ACL', 'SETUSER', username], rule);
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
} as const satisfies Command;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { ArrayReply, BlobStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(): RedisCommandArguments {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
return ['ACL', 'USERS'];
|
return ['ACL', 'USERS'];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => ArrayReply<BlobStringReply>
|
||||||
export declare function transformReply(): Array<RedisCommandArgument>;
|
} as const satisfies Command;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { BlobStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(): RedisCommandArguments {
|
export default {
|
||||||
return ['ACL', 'WHOAMI'];
|
FIRST_KEY_INDEX: undefined,
|
||||||
}
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
return ['ACL', 'USERS'];
|
||||||
|
},
|
||||||
|
transformReply: undefined as unknown as () => BlobStringReply
|
||||||
|
} as const satisfies Command;
|
||||||
|
@@ -1,11 +1,22 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import { transformArguments } from './APPEND';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
|
import APPEND from './APPEND';
|
||||||
|
|
||||||
describe('APPEND', () => {
|
describe.only('APPEND', () => {
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments('key', 'value'),
|
APPEND.transformArguments('key', 'value'),
|
||||||
['APPEND', 'key', 'value']
|
['APPEND', 'key', 'value']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testUtils.testAll('append', async client => {
|
||||||
|
assert.equal(
|
||||||
|
await client.append('key', 'value'),
|
||||||
|
5
|
||||||
|
);
|
||||||
|
}, {
|
||||||
|
client: GLOBAL.SERVERS.OPEN,
|
||||||
|
cluster: GLOBAL.CLUSTERS.OPEN
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, NumberReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 1,
|
||||||
export function transformArguments(
|
IS_READ_ONLY: false,
|
||||||
key: RedisCommandArgument,
|
transformArguments(key: RedisArgument, value: RedisArgument) {
|
||||||
value: RedisCommandArgument
|
|
||||||
): RedisCommandArguments {
|
|
||||||
return ['APPEND', key, value];
|
return ['APPEND', key, value];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => NumberReply
|
||||||
export declare function transformReply(): number;
|
} as const satisfies Command;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { RedisCommandArguments, RedisCommandArgument } from '.';
|
import { SimpleStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(): RedisCommandArguments {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
return ['ASKING'];
|
return ['ASKING'];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
} as const satisfies Command;
|
||||||
|
@@ -1,25 +1,25 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import { transformArguments } from './AUTH';
|
import AUTH from './AUTH';
|
||||||
|
|
||||||
describe('AUTH', () => {
|
describe('AUTH', () => {
|
||||||
describe('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
it('password only', () => {
|
it('password only', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments({
|
AUTH.transformArguments({
|
||||||
password: 'password'
|
password: 'password'
|
||||||
}),
|
}),
|
||||||
['AUTH', 'password']
|
['AUTH', 'password']
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it('username & password', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformArguments({
|
|
||||||
username: 'username',
|
|
||||||
password: 'password'
|
|
||||||
}),
|
|
||||||
['AUTH', 'username', 'password']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('username & password', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
AUTH.transformArguments({
|
||||||
|
username: 'username',
|
||||||
|
password: 'password'
|
||||||
|
}),
|
||||||
|
['AUTH', 'username', 'password']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,16 +1,23 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, SimpleStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export interface AuthOptions {
|
export interface AuthOptions {
|
||||||
username?: RedisCommandArgument;
|
username?: RedisArgument;
|
||||||
password: RedisCommandArgument;
|
password: RedisArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformArguments({ username, password }: AuthOptions): RedisCommandArguments {
|
export default {
|
||||||
if (!username) {
|
FIRST_KEY_INDEX: undefined,
|
||||||
return ['AUTH', password];
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments({ username, password }: AuthOptions) {
|
||||||
|
const args: Array<RedisArgument> = ['AUTH'];
|
||||||
|
|
||||||
|
if (username !== undefined) {
|
||||||
|
args.push(username);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['AUTH', username, password];
|
args.push(password);
|
||||||
}
|
|
||||||
|
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
return args;
|
||||||
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||||
|
} as const satisfies Command;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { SimpleStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(): RedisCommandArguments {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: undefined,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
return ['BGREWRITEAOF'];
|
return ['BGREWRITEAOF'];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
} as const satisfies Command;
|
||||||
|
@@ -1,17 +1,10 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { SimpleStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
interface BgSaveOptions {
|
export default {
|
||||||
SCHEDULE?: true;
|
FIRST_KEY_INDEX: undefined,
|
||||||
}
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments() {
|
||||||
export function transformArguments(options?: BgSaveOptions): RedisCommandArguments {
|
return ['BGSAVE'];
|
||||||
const args = ['BGSAVE'];
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply
|
||||||
if (options?.SCHEDULE) {
|
} as const satisfies Command;
|
||||||
args.push('SCHEDULE');
|
|
||||||
}
|
|
||||||
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
export declare function transformReply(): RedisCommandArgument;
|
|
||||||
|
@@ -1,33 +1,29 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, NumberReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export interface BitCountRange {
|
||||||
|
start: number;
|
||||||
export const IS_READ_ONLY = true;
|
end: number;
|
||||||
|
mode?: 'BYTE' | 'BIT';
|
||||||
interface BitCountRange {
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
mode?: 'BYTE' | 'BIT';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformArguments(
|
export default {
|
||||||
key: RedisCommandArgument,
|
FIRST_KEY_INDEX: 1,
|
||||||
range?: BitCountRange
|
IS_READ_ONLY: true,
|
||||||
): RedisCommandArguments {
|
transformArguments(key: RedisArgument, range?: BitCountRange) {
|
||||||
const args = ['BITCOUNT', key];
|
const args = ['BITCOUNT', key];
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
args.push(
|
args.push(
|
||||||
range.start.toString(),
|
range.start.toString(),
|
||||||
range.end.toString()
|
range.end.toString()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (range.mode) {
|
if (range.mode) {
|
||||||
args.push(range.mode);
|
args.push(range.mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => NumberReply
|
||||||
export declare function transformReply(): number;
|
} as const satisfies Command;
|
||||||
|
@@ -1,46 +1,49 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils, { GLOBAL } from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './BITFIELD';
|
import BITFIELD from './BITFIELD';
|
||||||
|
|
||||||
describe('BITFIELD', () => {
|
describe('BITFIELD', () => {
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments('key', [{
|
BITFIELD.transformArguments('key', [{
|
||||||
operation: 'OVERFLOW',
|
operation: 'OVERFLOW',
|
||||||
behavior: 'WRAP'
|
behavior: 'WRAP'
|
||||||
}, {
|
}, {
|
||||||
operation: 'GET',
|
operation: 'GET',
|
||||||
encoding: 'i8',
|
encoding: 'i8',
|
||||||
offset: 0
|
offset: 0
|
||||||
}, {
|
}, {
|
||||||
operation: 'OVERFLOW',
|
operation: 'OVERFLOW',
|
||||||
behavior: 'SAT'
|
behavior: 'SAT'
|
||||||
}, {
|
}, {
|
||||||
operation: 'SET',
|
operation: 'SET',
|
||||||
encoding: 'i16',
|
encoding: 'i16',
|
||||||
offset: 1,
|
offset: 1,
|
||||||
value: 0
|
value: 0
|
||||||
}, {
|
}, {
|
||||||
operation: 'OVERFLOW',
|
operation: 'OVERFLOW',
|
||||||
behavior: 'FAIL'
|
behavior: 'FAIL'
|
||||||
}, {
|
}, {
|
||||||
operation: 'INCRBY',
|
operation: 'INCRBY',
|
||||||
encoding: 'i32',
|
encoding: 'i32',
|
||||||
offset: 2,
|
offset: 2,
|
||||||
increment: 1
|
increment: 1
|
||||||
}]),
|
}]),
|
||||||
['BITFIELD', 'key', 'OVERFLOW', 'WRAP', 'GET', 'i8', '0', 'OVERFLOW', 'SAT', 'SET', 'i16', '1', '0', 'OVERFLOW', 'FAIL', 'INCRBY', 'i32', '2', '1']
|
['BITFIELD', 'key', 'OVERFLOW', 'WRAP', 'GET', 'i8', '0', 'OVERFLOW', 'SAT', 'SET', 'i16', '1', '0', 'OVERFLOW', 'FAIL', 'INCRBY', 'i32', '2', '1']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.bitField', async client => {
|
testUtils.testAll('bitField', async client => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
await client.bitField('key', [{
|
await client.bitField('key', [{
|
||||||
operation: 'GET',
|
operation: 'GET',
|
||||||
encoding: 'i8',
|
encoding: 'i8',
|
||||||
offset: 0
|
offset: 0
|
||||||
}]),
|
}]),
|
||||||
[0]
|
[0]
|
||||||
);
|
);
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, {
|
||||||
|
client: GLOBAL.SERVERS.OPEN,
|
||||||
|
cluster: GLOBAL.CLUSTERS.OPEN
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,80 +1,87 @@
|
|||||||
export const FIRST_KEY_INDEX = 1;
|
import { RedisArgument, ArrayReply, NumberReply, NullReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export type BitFieldEncoding = `${'i' | 'u'}${number}`;
|
export type BitFieldEncoding = `${'i' | 'u'}${number}`;
|
||||||
|
|
||||||
export interface BitFieldOperation<S extends string> {
|
export interface BitFieldOperation<S extends string> {
|
||||||
operation: S;
|
operation: S;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BitFieldGetOperation extends BitFieldOperation<'GET'> {
|
export interface BitFieldGetOperation extends BitFieldOperation<'GET'> {
|
||||||
encoding: BitFieldEncoding;
|
encoding: BitFieldEncoding;
|
||||||
offset: number | string;
|
offset: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BitFieldSetOperation extends BitFieldOperation<'SET'> {
|
export interface BitFieldSetOperation extends BitFieldOperation<'SET'> {
|
||||||
encoding: BitFieldEncoding;
|
encoding: BitFieldEncoding;
|
||||||
offset: number | string;
|
offset: number | string;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BitFieldIncrByOperation extends BitFieldOperation<'INCRBY'> {
|
export interface BitFieldIncrByOperation extends BitFieldOperation<'INCRBY'> {
|
||||||
encoding: BitFieldEncoding;
|
encoding: BitFieldEncoding;
|
||||||
offset: number | string;
|
offset: number | string;
|
||||||
increment: number;
|
increment: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BitFieldOverflowOperation extends BitFieldOperation<'OVERFLOW'> {
|
export interface BitFieldOverflowOperation extends BitFieldOperation<'OVERFLOW'> {
|
||||||
behavior: string;
|
behavior: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type BitFieldOperations = Array<
|
export type BitFieldOperations = Array<
|
||||||
BitFieldGetOperation |
|
BitFieldGetOperation |
|
||||||
BitFieldSetOperation |
|
BitFieldSetOperation |
|
||||||
BitFieldIncrByOperation |
|
BitFieldIncrByOperation |
|
||||||
BitFieldOverflowOperation
|
BitFieldOverflowOperation
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function transformArguments(key: string, operations: BitFieldOperations): Array<string> {
|
export type BitFieldRoOperations = Array<
|
||||||
|
Omit<BitFieldGetOperation, 'operation'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 1,
|
||||||
|
IS_READ_ONLY: false,
|
||||||
|
transformArguments(key: RedisArgument, operations: BitFieldOperations) {
|
||||||
const args = ['BITFIELD', key];
|
const args = ['BITFIELD', key];
|
||||||
|
|
||||||
for (const options of operations) {
|
for (const options of operations) {
|
||||||
switch (options.operation) {
|
switch (options.operation) {
|
||||||
case 'GET':
|
case 'GET':
|
||||||
args.push(
|
args.push(
|
||||||
'GET',
|
'GET',
|
||||||
options.encoding,
|
options.encoding,
|
||||||
options.offset.toString()
|
options.offset.toString()
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'SET':
|
case 'SET':
|
||||||
args.push(
|
args.push(
|
||||||
'SET',
|
'SET',
|
||||||
options.encoding,
|
options.encoding,
|
||||||
options.offset.toString(),
|
options.offset.toString(),
|
||||||
options.value.toString()
|
options.value.toString()
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'INCRBY':
|
case 'INCRBY':
|
||||||
args.push(
|
args.push(
|
||||||
'INCRBY',
|
'INCRBY',
|
||||||
options.encoding,
|
options.encoding,
|
||||||
options.offset.toString(),
|
options.offset.toString(),
|
||||||
options.increment.toString()
|
options.increment.toString()
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'OVERFLOW':
|
case 'OVERFLOW':
|
||||||
args.push(
|
args.push(
|
||||||
'OVERFLOW',
|
'OVERFLOW',
|
||||||
options.behavior
|
options.behavior
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => ArrayReply<NumberReply | NullReply>
|
||||||
export declare function transformReply(): Array<number | null>;
|
} as const satisfies Command;
|
||||||
|
@@ -1,27 +1,30 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils, { GLOBAL } from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './BITFIELD_RO';
|
import BITFIELD_RO from './BITFIELD_RO';
|
||||||
|
|
||||||
describe('BITFIELD RO', () => {
|
describe('BITFIELD_RO', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6, 2]);
|
testUtils.isVersionGreaterThanHook([6, 2]);
|
||||||
|
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments('key', [{
|
BITFIELD_RO.transformArguments('key', [{
|
||||||
encoding: 'i8',
|
encoding: 'i8',
|
||||||
offset: 0
|
offset: 0
|
||||||
}]),
|
}]),
|
||||||
['BITFIELD_RO', 'key', 'GET', 'i8', '0']
|
['BITFIELD_RO', 'key', 'GET', 'i8', '0']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.bitFieldRo', async client => {
|
testUtils.testAll('bitFieldRo', async client => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
await client.bitFieldRo('key', [{
|
await client.bitFieldRo('key', [{
|
||||||
encoding: 'i8',
|
encoding: 'i8',
|
||||||
offset: 0
|
offset: 0
|
||||||
}]),
|
}]),
|
||||||
[0]
|
[0]
|
||||||
);
|
);
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, {
|
||||||
|
client: GLOBAL.SERVERS.OPEN,
|
||||||
|
cluster: GLOBAL.CLUSTERS.OPEN
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,26 +1,25 @@
|
|||||||
|
import { RedisArgument, ArrayReply, NumberReply, Command } from '../RESP/types';
|
||||||
import { BitFieldGetOperation } from './BITFIELD';
|
import { BitFieldGetOperation } from './BITFIELD';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export type BitFieldRoOperations = Array<
|
||||||
|
Omit<BitFieldGetOperation, 'operation'>
|
||||||
export const IS_READ_ONLY = true;
|
|
||||||
|
|
||||||
type BitFieldRoOperations = Array<
|
|
||||||
Omit<BitFieldGetOperation, 'operation'> &
|
|
||||||
Partial<Pick<BitFieldGetOperation, 'operation'>>
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function transformArguments(key: string, operations: BitFieldRoOperations): Array<string> {
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 1,
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments(key: RedisArgument, operations: BitFieldRoOperations) {
|
||||||
const args = ['BITFIELD_RO', key];
|
const args = ['BITFIELD_RO', key];
|
||||||
|
|
||||||
for (const operation of operations) {
|
for (const operation of operations) {
|
||||||
args.push(
|
args.push(
|
||||||
'GET',
|
'GET',
|
||||||
operation.encoding,
|
operation.encoding,
|
||||||
operation.offset.toString()
|
operation.offset.toString()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => ArrayReply<NumberReply>
|
||||||
export declare function transformReply(): Array<number | null>;
|
} as const satisfies Command;
|
||||||
|
@@ -1,35 +1,31 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import testUtils, { GLOBAL } from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { transformArguments } from './BITOP';
|
import BITOP from './BITOP';
|
||||||
|
|
||||||
describe('BITOP', () => {
|
describe('BITOP', () => {
|
||||||
describe('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
it('single key', () => {
|
it('single key', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments('AND', 'destKey', 'key'),
|
BITOP.transformArguments('AND', 'destKey', 'key'),
|
||||||
['BITOP', 'AND', 'destKey', 'key']
|
['BITOP', 'AND', 'destKey', 'key']
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it('multiple keys', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformArguments('AND', 'destKey', ['1', '2']),
|
|
||||||
['BITOP', 'AND', 'destKey', '1', '2']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('client.bitOp', async client => {
|
it('multiple keys', () => {
|
||||||
assert.equal(
|
assert.deepEqual(
|
||||||
await client.bitOp('AND', 'destKey', 'key'),
|
BITOP.transformArguments('AND', 'destKey', ['1', '2']),
|
||||||
0
|
['BITOP', 'AND', 'destKey', '1', '2']
|
||||||
);
|
);
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
testUtils.testWithCluster('cluster.bitOp', async cluster => {
|
testUtils.testAll('client.bitOp', async client => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
await cluster.bitOp('AND', '{tag}destKey', '{tag}key'),
|
await client.bitOp('AND', '{tag}destKey', '{tag}key'),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
}, GLOBAL.CLUSTERS.OPEN);
|
}, {
|
||||||
|
client: GLOBAL.SERVERS.OPEN,
|
||||||
|
cluster: GLOBAL.CLUSTERS.OPEN
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,16 +1,17 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { NumberReply, Command, RedisArgument } from '../RESP/types';
|
||||||
import { pushVerdictArguments } from './generic-transformers';
|
import { RedisVariadicArgument, pushVariadicArguments } from './generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 2;
|
export type BitOperations = 'AND' | 'OR' | 'XOR' | 'NOT';
|
||||||
|
|
||||||
type BitOperations = 'AND' | 'OR' | 'XOR' | 'NOT';
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 2,
|
||||||
export function transformArguments(
|
IS_READ_ONLY: false,
|
||||||
|
transformArguments(
|
||||||
operation: BitOperations,
|
operation: BitOperations,
|
||||||
destKey: RedisCommandArgument,
|
destKey: RedisArgument,
|
||||||
key: RedisCommandArgument | Array<RedisCommandArgument>
|
key: RedisVariadicArgument
|
||||||
): RedisCommandArguments {
|
) {
|
||||||
return pushVerdictArguments(['BITOP', operation, destKey], key);
|
return pushVariadicArguments(['BITOP', operation, destKey], key);
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => NumberReply
|
||||||
export declare function transformReply(): number;
|
} as const satisfies Command;
|
||||||
|
@@ -1,32 +1,31 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, NumberReply, Command } from '../RESP/types';
|
||||||
import { BitValue } from './generic-transformers';
|
import { BitValue } from './generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 1,
|
||||||
export const IS_READ_ONLY = true;
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments(
|
||||||
export function transformArguments(
|
key: RedisArgument,
|
||||||
key: RedisCommandArgument,
|
|
||||||
bit: BitValue,
|
bit: BitValue,
|
||||||
start?: number,
|
start?: number,
|
||||||
end?: number,
|
end?: number,
|
||||||
mode?: 'BYTE' | 'BIT'
|
mode?: 'BYTE' | 'BIT'
|
||||||
): RedisCommandArguments {
|
) {
|
||||||
const args = ['BITPOS', key, bit.toString()];
|
const args = ['BITPOS', key, bit.toString()];
|
||||||
|
|
||||||
if (typeof start === 'number') {
|
if (typeof start === 'number') {
|
||||||
args.push(start.toString());
|
args.push(start.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof end === 'number') {
|
if (typeof end === 'number') {
|
||||||
args.push(end.toString());
|
args.push(end.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode) {
|
if (mode) {
|
||||||
args.push(mode);
|
args.push(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => NumberReply
|
||||||
export declare function transformReply(): number;
|
} as const satisfies Command;
|
||||||
|
@@ -1,23 +1,24 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';
|
||||||
import { ListSide } from './generic-transformers';
|
import { ListSide } from './generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 1,
|
||||||
export function transformArguments(
|
IS_READ_ONLY: false,
|
||||||
source: RedisCommandArgument,
|
transformArguments(
|
||||||
destination: RedisCommandArgument,
|
source: RedisArgument,
|
||||||
sourceDirection: ListSide,
|
destination: RedisArgument,
|
||||||
destinationDirection: ListSide,
|
sourceSide: ListSide,
|
||||||
|
destinationSide: ListSide,
|
||||||
timeout: number
|
timeout: number
|
||||||
): RedisCommandArguments {
|
) {
|
||||||
return [
|
return [
|
||||||
'BLMOVE',
|
'BLMOVE',
|
||||||
source,
|
source,
|
||||||
destination,
|
destination,
|
||||||
sourceDirection,
|
sourceSide,
|
||||||
destinationDirection,
|
destinationSide,
|
||||||
timeout.toString()
|
timeout.toString()
|
||||||
];
|
];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => BlobStringReply | NullReply
|
||||||
export declare function transformReply(): RedisCommandArgument | null;
|
} as const satisfies Command;
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, Command } from '../RESP/types';
|
||||||
import { transformLMPopArguments, LMPopOptions, ListSide } from './generic-transformers';
|
import { transformLMPopArguments, LMPopOptions, ListSide } from './generic-transformers';
|
||||||
|
import LMPOP from './LMPOP';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 3;
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 3,
|
||||||
export function transformArguments(
|
IS_READ_ONLY: false,
|
||||||
|
transformArguments(
|
||||||
timeout: number,
|
timeout: number,
|
||||||
keys: RedisCommandArgument | Array<RedisCommandArgument>,
|
keys: RedisArgument | Array<RedisArgument>,
|
||||||
side: ListSide,
|
side: ListSide,
|
||||||
options?: LMPopOptions
|
options?: LMPopOptions
|
||||||
): RedisCommandArguments {
|
) {
|
||||||
return transformLMPopArguments(
|
return transformLMPopArguments(
|
||||||
['BLMPOP', timeout.toString()],
|
['BLMPOP', timeout.toString()],
|
||||||
keys,
|
keys,
|
||||||
side,
|
side,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
transformReply: LMPOP.transformReply
|
||||||
export { transformReply } from './LMPOP';
|
} as const satisfies Command;
|
||||||
|
@@ -1,31 +1,23 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';
|
||||||
import { pushVerdictArguments } from './generic-transformers';
|
import { pushVariadicArguments } from './generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 1,
|
||||||
export function transformArguments(
|
IS_READ_ONLY: true,
|
||||||
keys: RedisCommandArgument | Array<RedisCommandArgument>,
|
transformArguments(
|
||||||
|
key: RedisArgument | Array<RedisArgument>,
|
||||||
timeout: number
|
timeout: number
|
||||||
): RedisCommandArguments {
|
) {
|
||||||
const args = pushVerdictArguments(['BLPOP'], keys);
|
const args = pushVariadicArguments(['BRPOP'], key);
|
||||||
|
|
||||||
args.push(timeout.toString());
|
args.push(timeout.toString());
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply(reply: NullReply | [BlobStringReply, BlobStringReply]) {
|
||||||
type BLPopRawReply = null | [RedisCommandArgument, RedisCommandArgument];
|
|
||||||
|
|
||||||
type BLPopReply = null | {
|
|
||||||
key: RedisCommandArgument;
|
|
||||||
element: RedisCommandArgument;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function transformReply(reply: BLPopRawReply): BLPopReply {
|
|
||||||
if (reply === null) return null;
|
if (reply === null) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: reply[0],
|
key: reply[0],
|
||||||
element: reply[1]
|
element: reply[1]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
} as const satisfies Command;
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';
|
||||||
import { pushVerdictArguments } from './generic-transformers';
|
import { pushVariadicArguments } from './generic-transformers';
|
||||||
|
import BLPOP from './BLPOP';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 1,
|
||||||
export function transformArguments(
|
IS_READ_ONLY: true,
|
||||||
key: RedisCommandArgument | Array<RedisCommandArgument>,
|
transformArguments(
|
||||||
|
key: RedisArgument | Array<RedisArgument>,
|
||||||
timeout: number
|
timeout: number
|
||||||
): RedisCommandArguments {
|
) {
|
||||||
const args = pushVerdictArguments(['BRPOP'], key);
|
const args = pushVariadicArguments(['BRPOP'], key);
|
||||||
|
|
||||||
args.push(timeout.toString());
|
args.push(timeout.toString());
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
},
|
||||||
|
transformReply: BLPOP.transformReply
|
||||||
export { transformReply } from './BLPOP';
|
} as const satisfies Command;
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export default {
|
||||||
|
FIRST_KEY_INDEX: 1,
|
||||||
export function transformArguments(
|
IS_READ_ONLY: false,
|
||||||
source: RedisCommandArgument,
|
transformArguments(
|
||||||
destination: RedisCommandArgument,
|
source: RedisArgument,
|
||||||
|
destination: RedisArgument,
|
||||||
timeout: number
|
timeout: number
|
||||||
): RedisCommandArguments {
|
) {
|
||||||
return ['BRPOPLPUSH', source, destination, timeout.toString()];
|
return ['BRPOPLPUSH', source, destination, timeout.toString()];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => BlobStringReply | NullReply
|
||||||
export declare function transformReply(): RedisCommandArgument | null;
|
} as const satisfies Command;
|
||||||
|
@@ -18,3 +18,17 @@ export function transformArguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export { transformReply } from './ZMPOP';
|
export { transformReply } from './ZMPOP';
|
||||||
|
|
||||||
|
|
||||||
|
import { Command } from '../RESP/types';
|
||||||
|
import ZMPOP from './ZMPOP';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
IS_READ_ONLY: false,
|
||||||
|
FIRST_KEY_INDEX: 3,
|
||||||
|
transformArguments() {
|
||||||
|
return ['BZMPOP'];
|
||||||
|
},
|
||||||
|
transformReply: ZMPOP.transformReply
|
||||||
|
} as const satisfies Command;
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
||||||
import { pushVerdictArguments, transformNumberInfinityReply, ZMember } from './generic-transformers';
|
import { pushVariadicArguments, transformNumberInfinityReply, ZMember } from './generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ export function transformArguments(
|
|||||||
key: RedisCommandArgument | Array<RedisCommandArgument>,
|
key: RedisCommandArgument | Array<RedisCommandArgument>,
|
||||||
timeout: number
|
timeout: number
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
const args = pushVerdictArguments(['BZPOPMAX'], key);
|
const args = pushVariadicArguments(['BZPOPMAX'], key);
|
||||||
|
|
||||||
args.push(timeout.toString());
|
args.push(timeout.toString());
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
||||||
import { pushVerdictArguments } from './generic-transformers';
|
import { pushVariadicArguments } from './generic-transformers';
|
||||||
|
|
||||||
export const FIRST_KEY_INDEX = 1;
|
export const FIRST_KEY_INDEX = 1;
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ export function transformArguments(
|
|||||||
key: RedisCommandArgument | Array<RedisCommandArgument>,
|
key: RedisCommandArgument | Array<RedisCommandArgument>,
|
||||||
timeout: number
|
timeout: number
|
||||||
): RedisCommandArguments {
|
): RedisCommandArguments {
|
||||||
const args = pushVerdictArguments(['BZPOPMIN'], key);
|
const args = pushVariadicArguments(['BZPOPMIN'], key);
|
||||||
|
|
||||||
args.push(timeout.toString());
|
args.push(timeout.toString());
|
||||||
|
|
||||||
|
@@ -1,20 +1,20 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import { transformArguments } from './CLIENT_CACHING';
|
import CLIENT_CACHING from './CLIENT_CACHING';
|
||||||
|
|
||||||
describe('CLIENT CACHING', () => {
|
describe('CLIENT CACHING', () => {
|
||||||
describe('transformArguments', () => {
|
describe('transformArguments', () => {
|
||||||
it('true', () => {
|
it('true', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(true),
|
CLIENT_CACHING.transformArguments(true),
|
||||||
['CLIENT', 'CACHING', 'YES']
|
['CLIENT', 'CACHING', 'YES']
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it('false', () => {
|
|
||||||
assert.deepEqual(
|
|
||||||
transformArguments(false),
|
|
||||||
['CLIENT', 'CACHING', 'NO']
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('false', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
CLIENT_CACHING.transformArguments(false),
|
||||||
|
['CLIENT', 'CACHING', 'NO']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
import { RedisCommandArguments } from '.';
|
import { SimpleStringReply, Command } from '../RESP/types';
|
||||||
|
|
||||||
export function transformArguments(value: boolean): RedisCommandArguments {
|
export default {
|
||||||
|
IS_READ_ONLY: true,
|
||||||
|
transformArguments(value: boolean) {
|
||||||
return [
|
return [
|
||||||
'CLIENT',
|
'CLIENT',
|
||||||
'CACHING',
|
'CACHING',
|
||||||
value ? 'YES' : 'NO'
|
value ? 'YES' : 'NO'
|
||||||
];
|
];
|
||||||
}
|
},
|
||||||
|
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
|
||||||
export declare function transformReply(): 'OK' | Buffer;
|
} as const satisfies Command;
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { strict as assert } from 'assert';
|
import { strict as assert } from 'assert';
|
||||||
import { transformArguments } from './CLIENT_GETNAME';
|
import CLIENT_GETNAME from './CLIENT_GETNAME';
|
||||||
|
|
||||||
describe('CLIENT GETNAME', () => {
|
describe('CLIENT GETNAME', () => {
|
||||||
it('transformArguments', () => {
|
it('transformArguments', () => {
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transformArguments(),
|
CLIENT_GETNAME.transformArguments(),
|
||||||
['CLIENT', 'GETNAME']
|
['CLIENT', 'GETNAME']
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user