From 7bdb57f13526cbc9a7636d69452f7623361e81c8 Mon Sep 17 00:00:00 2001 From: adamnoakes Date: Tue, 3 Nov 2020 13:13:06 +0000 Subject: [PATCH 1/7] add function name --- lib/autoPipelining.ts | 27 ++++++++++---- lib/commander.ts | 86 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/lib/autoPipelining.ts b/lib/autoPipelining.ts index 7855de7c..d1b2dba1 100644 --- a/lib/autoPipelining.ts +++ b/lib/autoPipelining.ts @@ -17,7 +17,11 @@ export const notAllowedAutoPipelineCommands = [ "unpsubscribe", ]; -function findAutoPipeline(client, _commandName, ...args: Array): string { +function findAutoPipeline( + client, + _commandName, + ...args: Array +): string { if (!client.isCluster) { return "main"; } @@ -68,8 +72,13 @@ function executeAutoPipeline(client, slotKey: string) { }); } -export function shouldUseAutoPipelining(client, commandName: string): boolean { +export function shouldUseAutoPipelining( + client, + functionName: string, + commandName: string +): boolean { return ( + functionName && client.options.enableAutoPipelining && !client.isPipeline && !notAllowedAutoPipelineCommands.includes(commandName) && @@ -79,6 +88,7 @@ export function shouldUseAutoPipelining(client, commandName: string): boolean { export function executeWithAutoPipelining( client, + functionName: string, commandName: string, args: string[], callback @@ -94,10 +104,13 @@ export function executeWithAutoPipelining( return; } - executeWithAutoPipelining(client, commandName, args, callback).then( - resolve, - reject - ); + executeWithAutoPipelining( + client, + functionName, + commandName, + args, + callback + ).then(resolve, reject); }); }); } @@ -138,7 +151,7 @@ export function executeWithAutoPipelining( resolve(value); }); - pipeline[commandName](...args); + pipeline[functionName](...args); }); return asCallback(autoPipelinePromise, callback); diff --git a/lib/commander.ts b/lib/commander.ts index 0df4c750..fc5e8f81 100644 --- a/lib/commander.ts +++ b/lib/commander.ts @@ -57,21 +57,46 @@ Commander.prototype.getBuiltinCommands = function () { */ Commander.prototype.createBuiltinCommand = function (commandName) { return { - string: generateFunction(commandName, "utf8"), - buffer: generateFunction(commandName, null), + string: generateFunction(null, commandName, "utf8"), + buffer: generateFunction(null, commandName, null), }; }; +/** + * Create add builtin command + * + * @param {string} commandName - command name + * @return {object} functions + * @public + */ +Commander.prototype.addBuiltinCommand = function (commandName) { + Commander.prototype[commandName] = generateFunction( + commandName, + commandName, + "utf8" + ); + Commander.prototype[commandName + "Buffer"] = generateFunction( + commandName + "Buffer", + commandName, + "utf8" + ); +}; + commands.forEach(function (commandName) { - Commander.prototype[commandName] = generateFunction(commandName, "utf8"); + Commander.prototype[commandName] = generateFunction( + commandName, + commandName, + "utf8" + ); Commander.prototype[commandName + "Buffer"] = generateFunction( + commandName + "Buffer", commandName, null ); }); -Commander.prototype.call = generateFunction("utf8"); -Commander.prototype.callBuffer = generateFunction(null); +Commander.prototype.call = generateFunction("call", "utf8"); +Commander.prototype.callBuffer = generateFunction("callBuffer", null); // eslint-disable-next-line @typescript-eslint/camelcase Commander.prototype.send_command = Commander.prototype.call; @@ -93,8 +118,13 @@ Commander.prototype.defineCommand = function (name, definition) { definition.readOnly ); this.scriptsSet[name] = script; - this[name] = generateScriptingFunction(name, script, "utf8"); - this[name + "Buffer"] = generateScriptingFunction(name, script, null); + this[name] = generateScriptingFunction(name, name, script, "utf8"); + this[name + "Buffer"] = generateScriptingFunction( + name + "Buffer", + name, + script, + null + ); }; /** @@ -105,13 +135,22 @@ Commander.prototype.defineCommand = function (name, definition) { */ Commander.prototype.sendCommand = function () {}; -function generateFunction(_encoding: string); -function generateFunction(_commandName: string | void, _encoding: string); -function generateFunction(_commandName?: string, _encoding?: string) { +function generateFunction(_functionName: string | null, _encoding: string); +function generateFunction( + _functionName: string | null, + _commandName: string | void, + _encoding: string +); +function generateFunction( + _functionName: string | null, + _commandName?: string, + _encoding?: string +) { if (typeof _encoding === "undefined") { _encoding = _commandName; _commandName = null; } + const functionName = _functionName; return function (...args) { const commandName = _commandName || args.shift(); @@ -139,18 +178,29 @@ function generateFunction(_commandName?: string, _encoding?: string) { } // No auto pipeline, use regular command sending - if (!shouldUseAutoPipelining(this, commandName)) { + if (!shouldUseAutoPipelining(this, functionName, commandName)) { return this.sendCommand( new Command(commandName, args, options, callback) ); } // Create a new pipeline and make sure it's scheduled - return executeWithAutoPipelining(this, commandName, args, callback); + return executeWithAutoPipelining( + this, + functionName, + commandName, + args, + callback + ); }; } -function generateScriptingFunction(name, script, encoding) { +function generateScriptingFunction( + functionName, + commandName, + script, + encoding +) { return function () { let length = arguments.length; const lastArgIndex = length - 1; @@ -183,11 +233,17 @@ function generateScriptingFunction(name, script, encoding) { } // No auto pipeline, use regular command sending - if (!shouldUseAutoPipelining(this, name)) { + if (!shouldUseAutoPipelining(this, functionName, commandName)) { return script.execute(this, args, options, callback); } // Create a new pipeline and make sure it's scheduled - return executeWithAutoPipelining(this, name, args, callback); + return executeWithAutoPipelining( + this, + functionName, + commandName, + args, + callback + ); }; } From 3b1f88404a19ccb2b8597517783f87a18fee0e68 Mon Sep 17 00:00:00 2001 From: adamnoakes Date: Tue, 3 Nov 2020 13:15:30 +0000 Subject: [PATCH 2/7] clean --- lib/commander.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/commander.ts b/lib/commander.ts index fc5e8f81..98fce4a1 100644 --- a/lib/commander.ts +++ b/lib/commander.ts @@ -135,14 +135,14 @@ Commander.prototype.defineCommand = function (name, definition) { */ Commander.prototype.sendCommand = function () {}; -function generateFunction(_functionName: string | null, _encoding: string); +function generateFunction(functionName: string | null, _encoding: string); function generateFunction( - _functionName: string | null, + functionName: string | null, _commandName: string | void, _encoding: string ); function generateFunction( - _functionName: string | null, + functionName: string | null, _commandName?: string, _encoding?: string ) { @@ -150,7 +150,6 @@ function generateFunction( _encoding = _commandName; _commandName = null; } - const functionName = _functionName; return function (...args) { const commandName = _commandName || args.shift(); From f5c51805e903d952961e68a7355429510d22e22b Mon Sep 17 00:00:00 2001 From: adamnoakes Date: Tue, 3 Nov 2020 13:18:47 +0000 Subject: [PATCH 3/7] fix add buffer fn --- lib/commander.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commander.ts b/lib/commander.ts index 98fce4a1..e9718f14 100644 --- a/lib/commander.ts +++ b/lib/commander.ts @@ -78,7 +78,7 @@ Commander.prototype.addBuiltinCommand = function (commandName) { Commander.prototype[commandName + "Buffer"] = generateFunction( commandName + "Buffer", commandName, - "utf8" + null ); }; From f5b6247f939d975f2cc24f91acc162e98ae8a7b3 Mon Sep 17 00:00:00 2001 From: adamnoakes Date: Wed, 11 Nov 2020 12:10:11 +0000 Subject: [PATCH 4/7] fixes and tests --- lib/commander.ts | 10 ++++------ lib/pipeline.ts | 5 +++++ test/functional/autopipelining.ts | 11 +++++++++++ test/functional/cluster/autopipelining.ts | 20 +++++++++---------- test/functional/pipeline.ts | 11 +++++++++++ test/unit/commander.ts | 24 +++++++++++++++++++++++ 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/lib/commander.ts b/lib/commander.ts index e9718f14..fb6a3556 100644 --- a/lib/commander.ts +++ b/lib/commander.ts @@ -31,6 +31,7 @@ export default function Commander() { showFriendlyErrorStack: false, }); this.scriptsSet = {}; + this.addedBuiltinSet = {}; } const commands = require("redis-commands").list.filter(function (command) { @@ -70,12 +71,9 @@ Commander.prototype.createBuiltinCommand = function (commandName) { * @public */ Commander.prototype.addBuiltinCommand = function (commandName) { - Commander.prototype[commandName] = generateFunction( - commandName, - commandName, - "utf8" - ); - Commander.prototype[commandName + "Buffer"] = generateFunction( + this.addedBuiltinSet[commandName] = commandName; + this[commandName] = generateFunction(commandName, commandName, "utf8"); + this[commandName + "Buffer"] = generateFunction( commandName + "Buffer", commandName, null diff --git a/lib/pipeline.ts b/lib/pipeline.ts index 44ac4a06..4da96cf8 100644 --- a/lib/pipeline.ts +++ b/lib/pipeline.ts @@ -47,6 +47,11 @@ export default function Pipeline(redis) { this[name + "Buffer"] = redis[name + "Buffer"]; }); + Object.keys(redis.addedBuiltinSet).forEach((name) => { + this[name] = redis[name]; + this[name + "Buffer"] = redis[name + "Buffer"]; + }); + const Promise = PromiseContainer.get(); this.promise = new Promise((resolve, reject) => { this.resolve = resolve; diff --git a/test/functional/autopipelining.ts b/test/functional/autopipelining.ts index c8d00984..11a4f036 100644 --- a/test/functional/autopipelining.ts +++ b/test/functional/autopipelining.ts @@ -1,5 +1,7 @@ import { expect, use } from "chai"; import Redis from "../../lib/redis"; +import { ReplyError } from "redis-errors"; +import * as sinon from "sinon"; use(require("chai-as-promised")); @@ -44,6 +46,15 @@ describe("autoPipelining for single node", function () { await promise; }); + it("should support buffer commands", async () => { + const redis = new Redis({ enableAutoPipelining: true }); + const buffer = Buffer.from("bar"); + await redis.setBuffer("foo", buffer); + const promise = redis.getBuffer("foo"); + expect(redis.autoPipelineQueueSize).to.eql(1); + expect(await promise).to.eql(buffer); + }); + it("should support custom commands", async () => { const redis = new Redis({ enableAutoPipelining: true }); diff --git a/test/functional/cluster/autopipelining.ts b/test/functional/cluster/autopipelining.ts index 91be1a69..4039c9bf 100644 --- a/test/functional/cluster/autopipelining.ts +++ b/test/functional/cluster/autopipelining.ts @@ -1,5 +1,5 @@ import { expect, use } from "chai"; -import * as calculateKeySlot from 'cluster-key-slot'; +import * as calculateKeySlot from "cluster-key-slot"; import { default as Cluster } from "../../../lib/cluster"; import MockServer from "../../helpers/mock_server"; @@ -400,11 +400,11 @@ describe("autoPipelining for cluster", function () { const promise2 = cluster.set("foo5", "bar"); const promise3 = cluster.set("foo2", "bar"); const promise4 = cluster.set("foo6", "bar"); - + // Override slots to induce a failure - const key1Slot = calculateKeySlot('foo1'); - const key2Slot = calculateKeySlot('foo2'); - const key5Slot = calculateKeySlot('foo5'); + const key1Slot = calculateKeySlot("foo1"); + const key2Slot = calculateKeySlot("foo2"); + const key5Slot = calculateKeySlot("foo5"); cluster.slots[key1Slot] = cluster.slots[key2Slot]; cluster.slots[key2Slot] = cluster.slots[key5Slot]; @@ -492,9 +492,9 @@ describe("autoPipelining for cluster", function () { expect(cluster.autoPipelineQueueSize).to.eql(4); // Override slots to induce a failure - const key1Slot = calculateKeySlot('foo1'); - const key2Slot = calculateKeySlot('foo2'); - const key5Slot = calculateKeySlot('foo5'); + const key1Slot = calculateKeySlot("foo1"); + const key2Slot = calculateKeySlot("foo2"); + const key5Slot = calculateKeySlot("foo5"); cluster.slots[key1Slot] = cluster.slots[key2Slot]; cluster.slots[key2Slot] = cluster.slots[key5Slot]; }); @@ -541,8 +541,8 @@ describe("autoPipelining for cluster", function () { expect(cluster.autoPipelineQueueSize).to.eql(3); - const key1Slot = calculateKeySlot('foo1'); - const key2Slot = calculateKeySlot('foo2'); + const key1Slot = calculateKeySlot("foo1"); + const key2Slot = calculateKeySlot("foo2"); cluster.slots[key1Slot] = cluster.slots[key2Slot]; }); }); diff --git a/test/functional/pipeline.ts b/test/functional/pipeline.ts index 8363dee2..e964350f 100644 --- a/test/functional/pipeline.ts +++ b/test/functional/pipeline.ts @@ -1,5 +1,6 @@ import Redis from "../../lib/redis"; import { expect } from "chai"; +import * as sinon from "sinon"; describe("pipeline", function () { it("should return correct result", function (done) { @@ -138,6 +139,16 @@ describe("pipeline", function () { }); }); + it("should include added built in commands", async () => { + const redis = new Redis({ keyPrefix: "foo:" }); + redis.addBuiltinCommand("someCommand"); + sinon.stub(redis, "sendCommand").callsFake((command) => { + command.resolve(Buffer.from("OK")); + }); + const result = await redis.pipeline().someCommand().exec(); + expect(result).to.eql([[null, "OK"]]); + }); + describe("custom commands", function () { let redis; diff --git a/test/unit/commander.ts b/test/unit/commander.ts index 1291f12b..7b7a4c51 100644 --- a/test/unit/commander.ts +++ b/test/unit/commander.ts @@ -13,6 +13,30 @@ describe("Commander", function () { }); }); + describe("#addBuiltinCommand()", () => { + beforeEach(() => sinon.spy(Commander.prototype, "sendCommand")); + afterEach(() => sinon.restore()); + it("adds string command", () => { + const c = new Commander(); + c.addBuiltinCommand("someCommand"); + c.someCommand(); + console.log(Commander.prototype.sendCommand.call); + const command = Commander.prototype.sendCommand.getCall(0).args[0]; + expect(command.name).to.eql("someCommand"); + expect(command.replyEncoding).to.eql("utf8"); + }); + + it("adds buffer command", () => { + const c = new Commander(); + c.addBuiltinCommand("someCommand"); + c.someCommandBuffer(); + console.log(Commander.prototype.sendCommand.call); + const command = Commander.prototype.sendCommand.getCall(0).args[0]; + expect(command.name).to.eql("someCommand"); + expect(command.replyEncoding).to.eql(null); + }); + }); + it("should pass the correct arguments", function () { sinon.stub(Commander.prototype, "sendCommand").callsFake((command) => { return command; From ee5d920e9b43ac8cc9998ff5a79bf1eedf05d377 Mon Sep 17 00:00:00 2001 From: adamnoakes Date: Wed, 11 Nov 2020 12:16:06 +0000 Subject: [PATCH 5/7] clean up --- test/unit/commander.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/unit/commander.ts b/test/unit/commander.ts index 7b7a4c51..a9ed3d6e 100644 --- a/test/unit/commander.ts +++ b/test/unit/commander.ts @@ -20,7 +20,6 @@ describe("Commander", function () { const c = new Commander(); c.addBuiltinCommand("someCommand"); c.someCommand(); - console.log(Commander.prototype.sendCommand.call); const command = Commander.prototype.sendCommand.getCall(0).args[0]; expect(command.name).to.eql("someCommand"); expect(command.replyEncoding).to.eql("utf8"); @@ -30,7 +29,6 @@ describe("Commander", function () { const c = new Commander(); c.addBuiltinCommand("someCommand"); c.someCommandBuffer(); - console.log(Commander.prototype.sendCommand.call); const command = Commander.prototype.sendCommand.getCall(0).args[0]; expect(command.name).to.eql("someCommand"); expect(command.replyEncoding).to.eql(null); From 0fc4fab65f9eb73ed9c169a127169b23489fa1e1 Mon Sep 17 00:00:00 2001 From: adamnoakes Date: Wed, 11 Nov 2020 12:21:53 +0000 Subject: [PATCH 6/7] use actual set --- lib/commander.ts | 4 ++-- lib/pipeline.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/commander.ts b/lib/commander.ts index fb6a3556..4031e830 100644 --- a/lib/commander.ts +++ b/lib/commander.ts @@ -31,7 +31,7 @@ export default function Commander() { showFriendlyErrorStack: false, }); this.scriptsSet = {}; - this.addedBuiltinSet = {}; + this.addedBuiltinSet = new Set(); } const commands = require("redis-commands").list.filter(function (command) { @@ -71,7 +71,7 @@ Commander.prototype.createBuiltinCommand = function (commandName) { * @public */ Commander.prototype.addBuiltinCommand = function (commandName) { - this.addedBuiltinSet[commandName] = commandName; + this.addedBuiltinSet.add(commandName); this[commandName] = generateFunction(commandName, commandName, "utf8"); this[commandName + "Buffer"] = generateFunction( commandName + "Buffer", diff --git a/lib/pipeline.ts b/lib/pipeline.ts index 4da96cf8..82096261 100644 --- a/lib/pipeline.ts +++ b/lib/pipeline.ts @@ -47,7 +47,7 @@ export default function Pipeline(redis) { this[name + "Buffer"] = redis[name + "Buffer"]; }); - Object.keys(redis.addedBuiltinSet).forEach((name) => { + redis.addedBuiltinSet.forEach((name) => { this[name] = redis[name]; this[name + "Buffer"] = redis[name + "Buffer"]; }); From 5ae5395933c6dde631582d56b58f3aec1a8a9620 Mon Sep 17 00:00:00 2001 From: adamnoakes Date: Wed, 11 Nov 2020 13:50:17 +0000 Subject: [PATCH 7/7] update api docs --- API.md | 370 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 223 insertions(+), 147 deletions(-) diff --git a/API.md b/API.md index 7805f226..cf0ce301 100644 --- a/API.md +++ b/API.md @@ -12,77 +12,83 @@ ## Redis ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) + **Kind**: global class -**Extends**: [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter), [Commander](#Commander) - -* [Redis](#Redis) ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) - * [new Redis([port], [host], [options])](#new_Redis_new) - * _instance_ - * [.connect([callback])](#Redis+connect) ⇒ Promise.<void> - * [.disconnect()](#Redis+disconnect) - * ~~[.end()](#Redis+end)~~ - * [.duplicate()](#Redis+duplicate) - * [.monitor([callback])](#Redis+monitor) - * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> - * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object - * [.defineCommand(name, definition)](#Commander+defineCommand) - * _static_ - * ~~[.createClient()](#Redis.createClient)~~ +**Extends**: [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter), [Commander](#Commander) + +- [Redis](#Redis) ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) + - [new Redis([port], [host], [options])](#new_Redis_new) + - _instance_ + - [.connect([callback])](#Redis+connect) ⇒ Promise.<void> + - [.disconnect()](#Redis+disconnect) + - ~~[.end()](#Redis+end)~~ + - [.duplicate()](#Redis+duplicate) + - [.monitor([callback])](#Redis+monitor) + - [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> + - [.addBuiltinCommand(commandName)](#Commander+createBuiltinCommand) + - [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object + - [.defineCommand(name, definition)](#Commander+defineCommand) + - _static_ + - ~~[.createClient()](#Redis.createClient)~~ ### new Redis([port], [host], [options]) + Creates a Redis instance +| Param | Type | Default | Description | +| --------------------------------------- | ----------------------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [port] | number \| string \| Object | 6379 | Port of the Redis server, or a URL string(see the examples below), or the `options` object(see the third argument). | +| [host] | string \| Object | "localhost" | Host of the Redis server, when the first argument is a URL string, this argument is an object represents the options. | +| [options] | Object | | Other options. | +| [options.port] | number | 6379 | Port of the Redis server. | +| [options.host] | string | "localhost" | Host of the Redis server. | +| [options.family] | string | 4 | Version of IP stack. Defaults to 4. | +| [options.path] | string | null | Local domain socket path. If set the `port`, `host` and `family` will be ignored. | +| [options.keepAlive] | number | 0 | TCP KeepAlive on the socket with a X ms delay before start. Set to a non-number value to disable keepAlive. | +| [options.noDelay] | boolean | true | Whether to disable the Nagle's Algorithm. By default we disable it to reduce the latency. | +| [options.connectionName] | string | null | Connection name. | +| [options.db] | number | 0 | Database index to use. | +| [options.password] | string | null | If set, client will send AUTH command with the value of this option when connected. | +| [options.dropBufferSupport] | boolean | false | Drop the buffer support for better performance. This option is recommended to be enabled when handling large array response and you don't need the buffer support. | +| [options.enableReadyCheck] | boolean | true | When a connection is established to the Redis server, the server might still be loading the database from disk. While loading, the server not respond to any commands. To work around this, when this option is `true`, ioredis will check the status of the Redis server, and when the Redis server is able to process commands, a `ready` event will be emitted. | +| [options.enableOfflineQueue] | boolean | true | By default, if there is no active connection to the Redis server, commands are added to a queue and are executed once the connection is "ready" (when `enableReadyCheck` is `true`, "ready" means the Redis server has loaded the database from disk, otherwise means the connection to the Redis server has been established). If this option is false, when execute the command when the connection isn't ready, an error will be returned. | +| [options.connectTimeout] | number | 10000 | The milliseconds before a timeout occurs during the initial connection to the Redis server. | +| [options.autoResubscribe] | boolean | true | After reconnected, if the previous connection was in the subscriber mode, client will auto re-subscribe these channels. | +| [options.autoResendUnfulfilledCommands] | boolean | true | If true, client will resend unfulfilled commands(e.g. block commands) in the previous connection when reconnected. | +| [options.lazyConnect] | boolean | false | By default, When a new `Redis` instance is created, it will connect to Redis server automatically. If you want to keep the instance disconnected until a command is called, you can pass the `lazyConnect` option to the constructor: `javascript var redis = new Redis({ lazyConnect: true }); // No attempting to connect to the Redis server here. // Now let's connect to the Redis server redis.get('foo', function () { });` | +| [options.tls] | Object | | TLS connection support. See https://github.com/luin/ioredis#tls-options | +| [options.keyPrefix] | string | "''" | The prefix to prepend to all keys in a command. | +| [options.retryStrategy] | function | | See "Quick Start" section | +| [options.maxRetriesPerRequest] | number | | See "Quick Start" section | +| [options.reconnectOnError] | function | | See "Quick Start" section | +| [options.readOnly] | boolean | false | Enable READONLY mode for the connection. Only available for cluster mode. | +| [options.stringNumbers] | boolean | false | Force numbers to be always returned as JavaScript strings. This option is necessary when dealing with big numbers (exceed the [-2^53, +2^53] range). | +| [options.enableAutoPipelining] | boolean | false | When enabled, all commands issued during an event loop iteration are automatically wrapped in a pipeline and sent to the server at the same time. This can improve performance by 30-50%. | +| [options.autoPipeliningIgnoredCommands] | string[] | [] | The list of commands which must not be automatically wrapped in pipelines. | +| [options.maxScriptsCachingTime] | number | 60000 | Default script definition caching time. | + +**Example** -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| [port] | number \| string \| Object | 6379 | Port of the Redis server, or a URL string(see the examples below), or the `options` object(see the third argument). | -| [host] | string \| Object | "localhost" | Host of the Redis server, when the first argument is a URL string, this argument is an object represents the options. | -| [options] | Object | | Other options. | -| [options.port] | number | 6379 | Port of the Redis server. | -| [options.host] | string | "localhost" | Host of the Redis server. | -| [options.family] | string | 4 | Version of IP stack. Defaults to 4. | -| [options.path] | string | null | Local domain socket path. If set the `port`, `host` and `family` will be ignored. | -| [options.keepAlive] | number | 0 | TCP KeepAlive on the socket with a X ms delay before start. Set to a non-number value to disable keepAlive. | -| [options.noDelay] | boolean | true | Whether to disable the Nagle's Algorithm. By default we disable it to reduce the latency. | -| [options.connectionName] | string | null | Connection name. | -| [options.db] | number | 0 | Database index to use. | -| [options.password] | string | null | If set, client will send AUTH command with the value of this option when connected. | -| [options.dropBufferSupport] | boolean | false | Drop the buffer support for better performance. This option is recommended to be enabled when handling large array response and you don't need the buffer support. | -| [options.enableReadyCheck] | boolean | true | When a connection is established to the Redis server, the server might still be loading the database from disk. While loading, the server not respond to any commands. To work around this, when this option is `true`, ioredis will check the status of the Redis server, and when the Redis server is able to process commands, a `ready` event will be emitted. | -| [options.enableOfflineQueue] | boolean | true | By default, if there is no active connection to the Redis server, commands are added to a queue and are executed once the connection is "ready" (when `enableReadyCheck` is `true`, "ready" means the Redis server has loaded the database from disk, otherwise means the connection to the Redis server has been established). If this option is false, when execute the command when the connection isn't ready, an error will be returned. | -| [options.connectTimeout] | number | 10000 | The milliseconds before a timeout occurs during the initial connection to the Redis server. | -| [options.autoResubscribe] | boolean | true | After reconnected, if the previous connection was in the subscriber mode, client will auto re-subscribe these channels. | -| [options.autoResendUnfulfilledCommands] | boolean | true | If true, client will resend unfulfilled commands(e.g. block commands) in the previous connection when reconnected. | -| [options.lazyConnect] | boolean | false | By default, When a new `Redis` instance is created, it will connect to Redis server automatically. If you want to keep the instance disconnected until a command is called, you can pass the `lazyConnect` option to the constructor: ```javascript var redis = new Redis({ lazyConnect: true }); // No attempting to connect to the Redis server here. // Now let's connect to the Redis server redis.get('foo', function () { }); ``` | -| [options.tls] | Object | | TLS connection support. See https://github.com/luin/ioredis#tls-options | -| [options.keyPrefix] | string | "''" | The prefix to prepend to all keys in a command. | -| [options.retryStrategy] | function | | See "Quick Start" section | -| [options.maxRetriesPerRequest] | number | | See "Quick Start" section | -| [options.reconnectOnError] | function | | See "Quick Start" section | -| [options.readOnly] | boolean | false | Enable READONLY mode for the connection. Only available for cluster mode. | -| [options.stringNumbers] | boolean | false | Force numbers to be always returned as JavaScript strings. This option is necessary when dealing with big numbers (exceed the [-2^53, +2^53] range). | -| [options.enableAutoPipelining] | boolean | false | When enabled, all commands issued during an event loop iteration are automatically wrapped in a pipeline and sent to the server at the same time. This can improve performance by 30-50%. | -| [options.autoPipeliningIgnoredCommands] | string[] | [] | The list of commands which must not be automatically wrapped in pipelines. | -| [options.maxScriptsCachingTime] | number | 60000 | Default script definition caching time. | -**Example** ```js -var Redis = require('ioredis'); +var Redis = require("ioredis"); var redis = new Redis(); var redisOnPort6380 = new Redis(6380); -var anotherRedis = new Redis(6380, '192.168.100.1'); -var unixSocketRedis = new Redis({ path: '/tmp/echo.sock' }); -var unixSocketRedis2 = new Redis('/tmp/echo.sock'); -var urlRedis = new Redis('redis://user:password@redis-service.com:6379/'); -var urlRedis2 = new Redis('//localhost:6379'); -var authedRedis = new Redis(6380, '192.168.100.1', { password: 'password' }); +var anotherRedis = new Redis(6380, "192.168.100.1"); +var unixSocketRedis = new Redis({ path: "/tmp/echo.sock" }); +var unixSocketRedis2 = new Redis("/tmp/echo.sock"); +var urlRedis = new Redis("redis://user:password@redis-service.com:6379/"); +var urlRedis2 = new Redis("//localhost:6379"); +var authedRedis = new Redis(6380, "192.168.100.1", { password: "password" }); ``` + ### redis.connect([callback]) ⇒ Promise.<void> + Create a connection to Redis. This method will be invoked automatically when creating a new Redis instance unless `lazyConnect: true` is passed. @@ -91,15 +97,16 @@ When calling this method manually, a Promise is returned, which will be resolved when the connection status is ready. **Kind**: instance method of [Redis](#Redis) -**Access**: public +**Access**: public -| Param | Type | -| --- | --- | -| [callback] | function | +| Param | Type | +| ---------- | --------------------- | +| [callback] | function | ### redis.disconnect() + Disconnect from Redis. This method closes the connection immediately, @@ -111,7 +118,8 @@ If you want to wait for the pending replies, use Redis#quit instead. ### ~~redis.end()~~ -***Deprecated*** + +**_Deprecated_** Disconnect from Redis. @@ -119,18 +127,22 @@ Disconnect from Redis. ### redis.duplicate() + Create a new instance with the same options as the current one. **Kind**: instance method of [Redis](#Redis) **Access**: public -**Example** +**Example** + ```js var redis = new Redis(6380); var anotherRedis = redis.duplicate(); ``` + ### redis.monitor([callback]) + Listen for all requests received by the server in real time. This command will create a new connection to Redis and send a @@ -138,68 +150,88 @@ MONITOR command via the new connection in order to avoid disturbing the current connection. **Kind**: instance method of [Redis](#Redis) -**Access**: public +**Access**: public -| Param | Type | Description | -| --- | --- | --- | +| Param | Type | Description | +| ---------- | --------------------- | ----------------------------------------------------------- | | [callback] | function | The callback function. If omit, a promise will be returned. | -**Example** +**Example** + ```js var redis = new Redis(); redis.monitor(function (err, monitor) { // Entering monitoring mode. - monitor.on('monitor', function (time, args, source, database) { + monitor.on("monitor", function (time, args, source, database) { console.log(time + ": " + util.inspect(args)); }); }); // supports promise as well as other commands redis.monitor().then(function (monitor) { - monitor.on('monitor', function (time, args, source, database) { + monitor.on("monitor", function (time, args, source, database) { console.log(time + ": " + util.inspect(args)); }); }); ``` + ### redis.getBuiltinCommands() ⇒ Array.<string> + Return supported builtin commands **Kind**: instance method of [Redis](#Redis) **Returns**: Array.<string> - command list **Access**: public + + +### redis.addBuiltinCommand(commandName) ⇒ object + +Adds a builtin command + +**Kind**: instance method of [Redis](#Redis) +**Returns**: void +**Access**: public + +| Param | Type | Description | +| ----------- | ------------------- | ------------ | +| commandName | string | command name | + ### redis.createBuiltinCommand(commandName) ⇒ object + Create a builtin command **Kind**: instance method of [Redis](#Redis) **Returns**: object - functions -**Access**: public +**Access**: public -| Param | Type | Description | -| --- | --- | --- | +| Param | Type | Description | +| ----------- | ------------------- | ------------ | | commandName | string | command name | ### redis.defineCommand(name, definition) + Define a custom command using lua script -**Kind**: instance method of [Redis](#Redis) +**Kind**: instance method of [Redis](#Redis) -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| name | string | | the command name | -| definition | object | | | -| definition.lua | string | | the lua code | +| Param | Type | Default | Description | +| ------------------------- | ------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ | +| name | string | | the command name | +| definition | object | | | +| definition.lua | string | | the lua code | | [definition.numberOfKeys] | number | | the number of keys. If omit, you have to pass the number of keys as the first argument every time you invoke the command | ### ~~Redis.createClient()~~ -***Deprecated*** + +**_Deprecated_** Create a Redis instance @@ -207,46 +239,49 @@ Create a Redis instance ## Cluster ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) + **Kind**: global class -**Extends**: [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter), [Commander](#Commander) - -* [Cluster](#Cluster) ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) - * [new Cluster(startupNodes, options)](#new_Cluster_new) - * [.connect()](#Cluster+connect) ⇒ Promise - * [.disconnect([reconnect])](#Cluster+disconnect) - * [.quit([callback])](#Cluster+quit) ⇒ Promise - * [.nodes([role])](#Cluster+nodes) ⇒ [Array.<Redis>](#Redis) - * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> - * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object - * [.defineCommand(name, definition)](#Commander+defineCommand) - * *[.sendCommand()](#Commander+sendCommand)* +**Extends**: [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter), [Commander](#Commander) + +- [Cluster](#Cluster) ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) + - [new Cluster(startupNodes, options)](#new_Cluster_new) + - [.connect()](#Cluster+connect) ⇒ Promise + - [.disconnect([reconnect])](#Cluster+disconnect) + - [.quit([callback])](#Cluster+quit) ⇒ Promise + - [.nodes([role])](#Cluster+nodes) ⇒ [Array.<Redis>](#Redis) + - [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> + - [.addBuiltinCommand(commandName)](#Commander+createBuiltinCommand) + - [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object + - [.defineCommand(name, definition)](#Commander+defineCommand) + - _[.sendCommand()](#Commander+sendCommand)_ ### new Cluster(startupNodes, options) -Creates a Redis Cluster instance +Creates a Redis Cluster instance -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| startupNodes | Array.<Object> | | An array of nodes in the cluster, [{ port: number, host: string }] | -| options | Object | | | -| [options.clusterRetryStrategy] | function | | See "Quick Start" section | -| [options.dnsLookup] | function(hostname, function(err, addr, family)) | [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) | Function used to resolve DNS hostnames of Redis cluster members. | -| [options.enableOfflineQueue] | boolean | true | See Redis class | -| [options.enableReadyCheck] | boolean | true | When enabled, ioredis only emits "ready" event when `CLUSTER INFO` command reporting the cluster is ready for handling commands. | -| [options.scaleReads] | string | "master" | Scale reads to the node with the specified role. Available values are "master", "slave" and "all". | -| [options.maxRedirections] | number | 16 | When a MOVED or ASK error is received, client will redirect the command to another node. This option limits the max redirections allowed to send a command. | -| [options.retryDelayOnFailover] | number | 100 | When an error is received when sending a command(e.g. "Connection is closed." when the target Redis node is down), | -| [options.retryDelayOnClusterDown] | number | 100 | When a CLUSTERDOWN error is received, client will retry if `retryDelayOnClusterDown` is valid delay time. | -| [options.retryDelayOnTryAgain] | number | 100 | When a TRYAGAIN error is received, client will retry if `retryDelayOnTryAgain` is valid delay time. | -| [options.slotsRefreshTimeout] | number | 1000 | The milliseconds before a timeout occurs while refreshing slots from the cluster. | -| [options.slotsRefreshInterval] | number | 5000 | The milliseconds between every automatic slots refresh. | -| [options.redisOptions] | Object | | Passed to the constructor of `Redis`. | +| Param | Type | Default | Description | +| --------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| startupNodes | Array.<Object> | | An array of nodes in the cluster, [{ port: number, host: string }] | +| options | Object | | | +| [options.clusterRetryStrategy] | function | | See "Quick Start" section | +| [options.dnsLookup] | function(hostname, function(err, addr, family)) | [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) | Function used to resolve DNS hostnames of Redis cluster members. | +| [options.enableOfflineQueue] | boolean | true | See Redis class | +| [options.enableReadyCheck] | boolean | true | When enabled, ioredis only emits "ready" event when `CLUSTER INFO` command reporting the cluster is ready for handling commands. | +| [options.scaleReads] | string | "master" | Scale reads to the node with the specified role. Available values are "master", "slave" and "all". | +| [options.maxRedirections] | number | 16 | When a MOVED or ASK error is received, client will redirect the command to another node. This option limits the max redirections allowed to send a command. | +| [options.retryDelayOnFailover] | number | 100 | When an error is received when sending a command(e.g. "Connection is closed." when the target Redis node is down), | +| [options.retryDelayOnClusterDown] | number | 100 | When a CLUSTERDOWN error is received, client will retry if `retryDelayOnClusterDown` is valid delay time. | +| [options.retryDelayOnTryAgain] | number | 100 | When a TRYAGAIN error is received, client will retry if `retryDelayOnTryAgain` is valid delay time. | +| [options.slotsRefreshTimeout] | number | 1000 | The milliseconds before a timeout occurs while refreshing slots from the cluster. | +| [options.slotsRefreshInterval] | number | 5000 | The milliseconds between every automatic slots refresh. | +| [options.redisOptions] | Object | | Passed to the constructor of `Redis`. | ### cluster.connect() ⇒ Promise + Connect to a cluster **Kind**: instance method of [Cluster](#Cluster) @@ -254,79 +289,100 @@ Connect to a cluster ### cluster.disconnect([reconnect]) + Disconnect from every node in the cluster. **Kind**: instance method of [Cluster](#Cluster) -**Access**: public +**Access**: public -| Param | Type | -| --- | --- | -| [reconnect] | boolean | +| Param | Type | +| ----------- | -------------------- | +| [reconnect] | boolean | ### cluster.quit([callback]) ⇒ Promise + Quit the cluster gracefully. **Kind**: instance method of [Cluster](#Cluster) **Returns**: Promise - return 'OK' if successfully -**Access**: public +**Access**: public -| Param | Type | -| --- | --- | -| [callback] | function | +| Param | Type | +| ---------- | --------------------- | +| [callback] | function | ### cluster.nodes([role]) ⇒ [Array.<Redis>](#Redis) + Get nodes with the specified role **Kind**: instance method of [Cluster](#Cluster) **Returns**: [Array.<Redis>](#Redis) - array of nodes -**Access**: public +**Access**: public -| Param | Type | Default | Description | -| --- | --- | --- | --- | +| Param | Type | Default | Description | +| ------ | ------------------- | ---------------------------- | -------------------------------- | | [role] | string | "all" | role, "master", "slave" or "all" | ### cluster.getBuiltinCommands() ⇒ Array.<string> + Return supported builtin commands **Kind**: instance method of [Cluster](#Cluster) **Returns**: Array.<string> - command list **Access**: public + + +### cluster.addBuiltinCommand(commandName) ⇒ object + +Adds a builtin command + +**Kind**: instance method of [Cluster](#Cluster) +**Returns**: void +**Access**: public + +| Param | Type | Description | +| ----------- | ------------------- | ------------ | +| commandName | string | command name | + ### cluster.createBuiltinCommand(commandName) ⇒ object + Create a builtin command **Kind**: instance method of [Cluster](#Cluster) **Returns**: object - functions -**Access**: public +**Access**: public -| Param | Type | Description | -| --- | --- | --- | +| Param | Type | Description | +| ----------- | ------------------- | ------------ | | commandName | string | command name | ### cluster.defineCommand(name, definition) + Define a custom command using lua script -**Kind**: instance method of [Cluster](#Cluster) +**Kind**: instance method of [Cluster](#Cluster) -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| name | string | | the command name | -| definition | object | | | -| definition.lua | string | | the lua code | +| Param | Type | Default | Description | +| ------------------------- | ------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ | +| name | string | | the command name | +| definition | object | | | +| definition.lua | string | | the lua code | | [definition.numberOfKeys] | number | | the number of keys. If omit, you have to pass the number of keys as the first argument every time you invoke the command | -### *cluster.sendCommand()* +### _cluster.sendCommand()_ + Send a command **Kind**: instance abstract method of [Cluster](#Cluster) @@ -335,66 +391,86 @@ Send a command ## Commander -**Kind**: global class -* [Commander](#Commander) - * [new Commander()](#new_Commander_new) - * [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> - * [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object - * [.defineCommand(name, definition)](#Commander+defineCommand) - * *[.sendCommand()](#Commander+sendCommand)* +**Kind**: global class + +- [Commander](#Commander) + - [new Commander()](#new_Commander_new) + - [.getBuiltinCommands()](#Commander+getBuiltinCommands) ⇒ Array.<string> + - [.addBuiltinCommand(commandName)](#Commander+createBuiltinCommand) + - [.createBuiltinCommand(commandName)](#Commander+createBuiltinCommand) ⇒ object + - [.defineCommand(name, definition)](#Commander+defineCommand) + - _[.sendCommand()](#Commander+sendCommand)_ ### new Commander() + Commander This is the base class of Redis, Redis.Cluster and Pipeline - -| Param | Type | Default | Description | -| --- | --- | --- | --- | +| Param | Type | Default | Description | +| -------------------------------- | -------------------- | ------------------ | ------------------------------------------------------------------------------------ | | [options.showFriendlyErrorStack] | boolean | false | Whether to show a friendly error stack. Will decrease the performance significantly. | ### commander.getBuiltinCommands() ⇒ Array.<string> + Return supported builtin commands **Kind**: instance method of [Commander](#Commander) **Returns**: Array.<string> - command list **Access**: public + + +### commander.addBuiltinCommand(commandName) ⇒ object + +Adds a builtin command + +**Kind**: instance method of [Commander](#Commander) +**Returns**: void +**Access**: public + +| Param | Type | Description | +| ----------- | ------------------- | ------------ | +| commandName | string | command name | + ### commander.createBuiltinCommand(commandName) ⇒ object + Create a builtin command **Kind**: instance method of [Commander](#Commander) **Returns**: object - functions -**Access**: public +**Access**: public -| Param | Type | Description | -| --- | --- | --- | +| Param | Type | Description | +| ----------- | ------------------- | ------------ | | commandName | string | command name | ### commander.defineCommand(name, definition) + Define a custom command using lua script -**Kind**: instance method of [Commander](#Commander) +**Kind**: instance method of [Commander](#Commander) -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| name | string | | the command name | -| definition | object | | | -| definition.lua | string | | the lua code | +| Param | Type | Default | Description | +| ------------------------- | ------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ | +| name | string | | the command name | +| definition | object | | | +| definition.lua | string | | the lua code | | [definition.numberOfKeys] | number | | the number of keys. If omit, you have to pass the number of keys as the first argument every time you invoke the command | -### *commander.sendCommand()* +### _commander.sendCommand()_ + Send a command **Kind**: instance abstract method of [Commander](#Commander) -**Access**: public +**Access**: public