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;