command.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const commands = require("redis-commands");
  4. const calculateSlot = require("cluster-key-slot");
  5. const standard_as_callback_1 = require("standard-as-callback");
  6. const utils_1 = require("./utils");
  7. const lodash_1 = require("./utils/lodash");
  8. const promiseContainer_1 = require("./promiseContainer");
  9. /**
  10. * Command instance
  11. *
  12. * It's rare that you need to create a Command instance yourself.
  13. *
  14. * @export
  15. * @class Command
  16. *
  17. * @example
  18. * ```js
  19. * var infoCommand = new Command('info', null, function (err, result) {
  20. * console.log('result', result);
  21. * });
  22. *
  23. * redis.sendCommand(infoCommand);
  24. *
  25. * // When no callback provided, Command instance will have a `promise` property,
  26. * // which will resolve/reject with the result of the command.
  27. * var getCommand = new Command('get', ['foo']);
  28. * getCommand.promise.then(function (result) {
  29. * console.log('result', result);
  30. * });
  31. * ```
  32. * @see {@link Redis#sendCommand} which can send a Command instance to Redis
  33. */
  34. class Command {
  35. /**
  36. * Creates an instance of Command.
  37. * @param {string} name Command name
  38. * @param {(Array<string | Buffer | number>)} [args=[]] An array of command arguments
  39. * @param {ICommandOptions} [options={}]
  40. * @param {CallbackFunction} [callback] The callback that handles the response.
  41. * If omit, the response will be handled via Promise
  42. * @memberof Command
  43. */
  44. constructor(name, args = [], options = {}, callback) {
  45. this.name = name;
  46. this.transformed = false;
  47. this.isCustomCommand = false;
  48. this.replyEncoding = options.replyEncoding;
  49. this.errorStack = options.errorStack;
  50. this.args = lodash_1.flatten(args);
  51. this.callback = callback;
  52. this.initPromise();
  53. if (options.keyPrefix) {
  54. this._iterateKeys(key => options.keyPrefix + key);
  55. }
  56. }
  57. static getFlagMap() {
  58. if (!this.flagMap) {
  59. this.flagMap = Object.keys(Command.FLAGS).reduce((map, flagName) => {
  60. map[flagName] = {};
  61. Command.FLAGS[flagName].forEach(commandName => {
  62. map[flagName][commandName] = true;
  63. });
  64. return map;
  65. }, {});
  66. }
  67. return this.flagMap;
  68. }
  69. /**
  70. * Check whether the command has the flag
  71. *
  72. * @param {string} flagName
  73. * @param {string} commandName
  74. * @return {boolean}
  75. */
  76. static checkFlag(flagName, commandName) {
  77. return !!this.getFlagMap()[flagName][commandName];
  78. }
  79. static setArgumentTransformer(name, func) {
  80. this._transformer.argument[name] = func;
  81. }
  82. static setReplyTransformer(name, func) {
  83. this._transformer.reply[name] = func;
  84. }
  85. initPromise() {
  86. const Promise = promiseContainer_1.get();
  87. const promise = new Promise((resolve, reject) => {
  88. if (!this.transformed) {
  89. this.transformed = true;
  90. const transformer = Command._transformer.argument[this.name];
  91. if (transformer) {
  92. this.args = transformer(this.args);
  93. }
  94. this.stringifyArguments();
  95. }
  96. this.resolve = this._convertValue(resolve);
  97. if (this.errorStack) {
  98. this.reject = err => {
  99. reject(utils_1.optimizeErrorStack(err, this.errorStack, __dirname));
  100. };
  101. }
  102. else {
  103. this.reject = reject;
  104. }
  105. });
  106. this.promise = standard_as_callback_1.default(promise, this.callback);
  107. }
  108. getSlot() {
  109. if (typeof this.slot === "undefined") {
  110. const key = this.getKeys()[0];
  111. this.slot = key == null ? null : calculateSlot(key);
  112. }
  113. return this.slot;
  114. }
  115. getKeys() {
  116. return this._iterateKeys();
  117. }
  118. /**
  119. * Iterate through the command arguments that are considered keys.
  120. *
  121. * @param {Function} [transform=(key) => key] The transformation that should be applied to
  122. * each key. The transformations will persist.
  123. * @returns {string[]} The keys of the command.
  124. * @memberof Command
  125. */
  126. _iterateKeys(transform = key => key) {
  127. if (typeof this.keys === "undefined") {
  128. this.keys = [];
  129. if (commands.exists(this.name)) {
  130. const keyIndexes = commands.getKeyIndexes(this.name, this.args);
  131. for (const index of keyIndexes) {
  132. this.args[index] = transform(this.args[index]);
  133. this.keys.push(this.args[index]);
  134. }
  135. }
  136. }
  137. return this.keys;
  138. }
  139. /**
  140. * Convert command to writable buffer or string
  141. *
  142. * @return {string|Buffer}
  143. * @see {@link Redis#sendCommand}
  144. * @public
  145. */
  146. toWritable() {
  147. let bufferMode = false;
  148. for (const arg of this.args) {
  149. if (arg instanceof Buffer) {
  150. bufferMode = true;
  151. break;
  152. }
  153. }
  154. let result;
  155. let commandStr = "*" +
  156. (this.args.length + 1) +
  157. "\r\n$" +
  158. Buffer.byteLength(this.name) +
  159. "\r\n" +
  160. this.name +
  161. "\r\n";
  162. if (bufferMode) {
  163. const buffers = new MixedBuffers();
  164. buffers.push(commandStr);
  165. for (const arg of this.args) {
  166. if (arg instanceof Buffer) {
  167. if (arg.length === 0) {
  168. buffers.push("$0\r\n\r\n");
  169. }
  170. else {
  171. buffers.push("$" + arg.length + "\r\n");
  172. buffers.push(arg);
  173. buffers.push("\r\n");
  174. }
  175. }
  176. else {
  177. buffers.push("$" +
  178. Buffer.byteLength(arg) +
  179. "\r\n" +
  180. arg +
  181. "\r\n");
  182. }
  183. }
  184. result = buffers.toBuffer();
  185. }
  186. else {
  187. result = commandStr;
  188. for (const arg of this.args) {
  189. result +=
  190. "$" +
  191. Buffer.byteLength(arg) +
  192. "\r\n" +
  193. arg +
  194. "\r\n";
  195. }
  196. }
  197. return result;
  198. }
  199. stringifyArguments() {
  200. for (let i = 0; i < this.args.length; ++i) {
  201. const arg = this.args[i];
  202. if (!(arg instanceof Buffer) && typeof arg !== "string") {
  203. this.args[i] = utils_1.toArg(arg);
  204. }
  205. }
  206. }
  207. /**
  208. * Convert the value from buffer to the target encoding.
  209. *
  210. * @private
  211. * @param {Function} resolve The resolve function of the Promise
  212. * @returns {Function} A funtion to transform and resolve a value
  213. * @memberof Command
  214. */
  215. _convertValue(resolve) {
  216. return value => {
  217. try {
  218. resolve(this.transformReply(value));
  219. }
  220. catch (err) {
  221. this.reject(err);
  222. }
  223. return this.promise;
  224. };
  225. }
  226. /**
  227. * Convert buffer/buffer[] to string/string[],
  228. * and apply reply transformer.
  229. *
  230. * @memberof Command
  231. */
  232. transformReply(result) {
  233. if (this.replyEncoding) {
  234. result = utils_1.convertBufferToString(result, this.replyEncoding);
  235. }
  236. const transformer = Command._transformer.reply[this.name];
  237. if (transformer) {
  238. result = transformer(result);
  239. }
  240. return result;
  241. }
  242. }
  243. Command.FLAGS = {
  244. VALID_IN_SUBSCRIBER_MODE: [
  245. "subscribe",
  246. "psubscribe",
  247. "unsubscribe",
  248. "punsubscribe",
  249. "ping",
  250. "quit"
  251. ],
  252. VALID_IN_MONITOR_MODE: ["monitor", "auth"],
  253. ENTER_SUBSCRIBER_MODE: ["subscribe", "psubscribe"],
  254. EXIT_SUBSCRIBER_MODE: ["unsubscribe", "punsubscribe"],
  255. WILL_DISCONNECT: ["quit"]
  256. };
  257. Command._transformer = {
  258. argument: {},
  259. reply: {}
  260. };
  261. exports.default = Command;
  262. const msetArgumentTransformer = function (args) {
  263. if (args.length === 1) {
  264. if (typeof Map !== "undefined" && args[0] instanceof Map) {
  265. return utils_1.convertMapToArray(args[0]);
  266. }
  267. if (typeof args[0] === "object" && args[0] !== null) {
  268. return utils_1.convertObjectToArray(args[0]);
  269. }
  270. }
  271. return args;
  272. };
  273. Command.setArgumentTransformer("mset", msetArgumentTransformer);
  274. Command.setArgumentTransformer("msetnx", msetArgumentTransformer);
  275. Command.setArgumentTransformer("hmset", function (args) {
  276. if (args.length === 2) {
  277. if (typeof Map !== "undefined" && args[1] instanceof Map) {
  278. return [args[0]].concat(utils_1.convertMapToArray(args[1]));
  279. }
  280. if (typeof args[1] === "object" && args[1] !== null) {
  281. return [args[0]].concat(utils_1.convertObjectToArray(args[1]));
  282. }
  283. }
  284. return args;
  285. });
  286. Command.setReplyTransformer("hgetall", function (result) {
  287. if (Array.isArray(result)) {
  288. var obj = {};
  289. for (var i = 0; i < result.length; i += 2) {
  290. obj[result[i]] = result[i + 1];
  291. }
  292. return obj;
  293. }
  294. return result;
  295. });
  296. class MixedBuffers {
  297. constructor() {
  298. this.length = 0;
  299. this.items = [];
  300. }
  301. push(x) {
  302. this.length += Buffer.byteLength(x);
  303. this.items.push(x);
  304. }
  305. toBuffer() {
  306. const result = Buffer.allocUnsafe(this.length);
  307. let offset = 0;
  308. for (const item of this.items) {
  309. const length = Buffer.byteLength(item);
  310. Buffer.isBuffer(item)
  311. ? item.copy(result, offset)
  312. : result.write(item, offset, length);
  313. offset += length;
  314. }
  315. return result;
  316. }
  317. }