diff --git a/API.md b/API.md
index 7be90deb..abf4062c 100644
--- a/API.md
+++ b/API.md
@@ -13,7 +13,7 @@
## Redis ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)
-**Kind**: global class
+**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)
@@ -25,6 +25,7 @@
- [.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_
@@ -98,7 +99,8 @@ unless `lazyConnect: true` is passed.
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)
+**Kind**: instance method of [Redis
](#Redis)
+
**Access**: public
| Param | Type |
@@ -132,8 +134,9 @@ Disconnect from Redis.
Create a new instance with the same options as the current one.
-**Kind**: instance method of [Redis
](#Redis)
-**Access**: public
+**Kind**: instance method of [Redis
](#Redis)
+**Access**: public
+
**Example**
```js
@@ -151,7 +154,8 @@ This command will create a new connection to Redis and send a
MONITOR command via the new connection in order to avoid disturbing
the current connection.
-**Kind**: instance method of [Redis
](#Redis)
+**Kind**: instance method of [Redis
](#Redis)
+
**Access**: public
| Param | Type | Description |
@@ -183,17 +187,33 @@ redis.monitor().then(function (monitor) {
Return supported builtin commands
-**Kind**: instance method of [Redis
](#Redis)
-**Returns**: Array.<string>
- command list
+**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
+**Kind**: instance method of [Redis
](#Redis)
+**Returns**: object
- functions
+
**Access**: public
| Param | Type | Description |
@@ -228,7 +248,7 @@ Create a Redis instance
## Cluster ⇐ [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)
-**Kind**: global class
+**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)
@@ -238,6 +258,7 @@ Create a Redis instance
- [.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)_
@@ -279,7 +300,8 @@ Connect to a cluster
Disconnect from every node in the cluster.
-**Kind**: instance method of [Cluster
](#Cluster)
+**Kind**: instance method of [Cluster
](#Cluster)
+
**Access**: public
| Param | Type |
@@ -292,8 +314,9 @@ Disconnect from every node in the cluster.
Quit the cluster gracefully.
-**Kind**: instance method of [Cluster
](#Cluster)
-**Returns**: Promise
- return 'OK' if successfully
+**Kind**: instance method of [Cluster
](#Cluster)
+**Returns**: Promise
- return 'OK' if successfully
+
**Access**: public
| Param | Type |
@@ -306,8 +329,9 @@ Quit the cluster gracefully.
Get nodes with the specified role
-**Kind**: instance method of [Cluster
](#Cluster)
-**Returns**: [Array.<Redis>
](#Redis) - array of nodes
+**Kind**: instance method of [Cluster
](#Cluster)
+**Returns**: [Array.<Redis>
](#Redis) - array of nodes
+
**Access**: public
| Param | Type | Default | Description |
@@ -320,17 +344,33 @@ Get nodes with the specified role
Return supported builtin commands
-**Kind**: instance method of [Cluster
](#Cluster)
-**Returns**: Array.<string>
- command list
+**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
+**Kind**: instance method of [Cluster
](#Cluster)
+**Returns**: object
- functions
+
**Access**: public
| Param | Type | Description |
@@ -370,6 +410,7 @@ Send a command
- [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)_
@@ -392,17 +433,33 @@ This is the base class of Redis, Redis.Cluster and Pipeline
Return supported builtin commands
-**Kind**: instance method of [Commander
](#Commander)
-**Returns**: Array.<string>
- command list
+**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
+**Kind**: instance method of [Commander
](#Commander)
+**Returns**: object
- functions
+
**Access**: public
| Param | Type | Description |
@@ -430,5 +487,6 @@ Define a custom command using lua script
Send a command
-**Kind**: instance abstract method of [Commander
](#Commander)
+**Kind**: instance abstract method of [Commander
](#Commander)
+
**Access**: public
diff --git a/lib/autoPipelining.ts b/lib/autoPipelining.ts
index d75282b1..fe1948d5 100644
--- a/lib/autoPipelining.ts
+++ b/lib/autoPipelining.ts
@@ -73,8 +73,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) &&
@@ -84,6 +89,7 @@ export function shouldUseAutoPipelining(client, commandName: string): boolean {
export function executeWithAutoPipelining(
client,
+ functionName: string,
commandName: string,
args: string[],
callback
@@ -99,10 +105,13 @@ export function executeWithAutoPipelining(
return;
}
- executeWithAutoPipelining(client, commandName, args, callback).then(
- resolve,
- reject
- );
+ executeWithAutoPipelining(
+ client,
+ functionName,
+ commandName,
+ args,
+ callback
+ ).then(resolve, reject);
});
});
}
@@ -143,7 +152,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..4031e830 100644
--- a/lib/commander.ts
+++ b/lib/commander.ts
@@ -31,6 +31,7 @@ export default function Commander() {
showFriendlyErrorStack: false,
});
this.scriptsSet = {};
+ this.addedBuiltinSet = new Set();
}
const commands = require("redis-commands").list.filter(function (command) {
@@ -57,21 +58,43 @@ 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) {
+ this.addedBuiltinSet.add(commandName);
+ this[commandName] = generateFunction(commandName, commandName, "utf8");
+ this[commandName + "Buffer"] = generateFunction(
+ commandName + "Buffer",
+ commandName,
+ null
+ );
+};
+
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 +116,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,9 +133,17 @@ 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;
@@ -139,18 +175,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 +230,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
+ );
};
}
diff --git a/lib/pipeline.ts b/lib/pipeline.ts
index b85c9e00..11621ecd 100644
--- a/lib/pipeline.ts
+++ b/lib/pipeline.ts
@@ -48,6 +48,11 @@ export default function Pipeline(redis) {
this[name + "Buffer"] = redis[name + "Buffer"];
});
+ 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/pipeline.ts b/test/functional/pipeline.ts
index cc14ca51..ff5a854f 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..a9ed3d6e 100644
--- a/test/unit/commander.ts
+++ b/test/unit/commander.ts
@@ -13,6 +13,28 @@ 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();
+ 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();
+ 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;