redis-abstraction 0.0.2 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ A Redis client pool with abstraction to different Redis libraries.
4
4
  This package helps to create redis connections in a connection pool fashion, ideally redis is single threaded so multiple connection or a connection pool for simple commands may not make sense, but there are some conditions in which connection pool makes sense like blocking commands, pub-sub commands etc apart form these cases there are also some fringe cases where if you have a simple command with a lot of data to be serialized on wire/network here multiple connections make sense as redis execution is single threaded but its I/O stack is multi so another command which has relatively small data to be serialized on wire gets stuck behind this big network transfer command ahead of it, It can take advantage of another connection where there is not que before-hand and get executed first in such a case connection pools make sense. This package also supports cluster mode connections.
5
5
 
6
6
  Working of this package is simple it exposes an interface [i-redis-client-pool](https://github.com/LRagji/redis-abstraction/blob/main/source/i-redis-client-pool.ts) which has following methods
7
- 1. `acquire(token: string): Promise<void>` : Responsibile to acquire a connection to redis server in-reference to the unique token provided.
7
+ 1. `acquire(token: string): Promise<void>` : Responsible to acquire a connection to redis server in-reference to the unique token provided.
8
8
  2. `run(token: string, commandArgs: string[]): Promise<any>` : Responsible to run a redis command in-reference to the unique token acquired before.
9
9
  3. `release(token: string): Promise<void>` : Responsible to release the acquired connection back into connection pool in-reference to the unique token acquired before.
10
10
 
@@ -31,10 +31,19 @@ Currently this package only supports ioredis as underneath client library, but i
31
31
  ### Non Cluster Initialization
32
32
 
33
33
  ```javascript
34
+ //Function which can decompose the connection string into different components like hostname,password etc.
35
+ function parseRedisConnectionString(connectionString) {
36
+ //Used to parse the connection string and return components of the same
37
+ //Refer:ioredis/built/utils/index.js parseURL function for more details
38
+ //This is just a mock implementation, you can enhance it as per your needs.
39
+ return {
40
+ password: ""
41
+ };
42
+ }
34
43
  //Define the redis connection string
35
44
  const singleNodeRedisConnectionString = 'rediss://redis.my-service.com';
36
45
  //Create a injector function for creating redis connection instance.
37
- const connectionInjector = () => IORedisClientPool.IORedisClientClusterFactory([singleNodeRedisConnectionString]);
46
+ const connectionInjector = () => IORedisClientPool.IORedisClientClusterFactory([singleNodeRedisConnectionString], Redis, Cluster, parseRedisConnectionString);
38
47
  //Initialize the pool
39
48
  const pool = new IORedisClientPool(connectionInjector);
40
49
 
@@ -52,7 +61,8 @@ main(pool)
52
61
  const clusteredRedisConnectionStringPrimary = 'rediss://redis.my-service.com';
53
62
  const clusteredRedisConnectionStringSecondary = 'rediss://redis.my-service.com' || clusteredRedisConnectionStringPrimary; //Secondary is optional if not present pass in primary connection.
54
63
  //Create a injector function for creating redis connection instance.
55
- const connectionInjector = () => IORedisClientPool.IORedisClientClusterFactory([clusteredRedisConnectionStringPrimary,clusteredRedisConnectionStringSecondary]);//Passing more than one connection string indicates its a cluster setup.
64
+ //Passing more than one connection string indicates its a cluster setup.
65
+ const connectionInjector = () => IORedisClientPool.IORedisClientClusterFactory([clusteredRedisConnectionStringPrimary,clusteredRedisConnectionStringSecondary], Redis, Cluster, parseRedisConnectionString);
56
66
  //Initialize the pool
57
67
  const pool = new IORedisClientPool(connectionInjector);
58
68
 
@@ -93,4 +103,4 @@ main(pool)
93
103
 
94
104
  ## License
95
105
 
96
- This project is contrubution to public domain and completely free for use, view [LICENSE.md](/license.md) file for details.
106
+ This project is contribution to public domain and completely free for use, view [LICENSE.md](/license.md) file for details.
package/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  export { IRedisClientPool } from './i-redis-client-pool';
2
2
  export { IORedisClientPool } from './ioredis-client-pool';
3
+ export { TIORedisCommonCommands } from './t-ioredis-common-commands';
4
+ export { RedisClientPool } from './redis-client-pool';
5
+ export { TRedisCommonCommands } from './t-redis-common-commands';
package/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.IORedisClientPool = void 0;
3
+ exports.RedisClientPool = exports.IORedisClientPool = void 0;
4
+ //Implemented Class for IORedis
4
5
  var ioredis_client_pool_1 = require("./ioredis-client-pool");
5
6
  Object.defineProperty(exports, "IORedisClientPool", { enumerable: true, get: function () { return ioredis_client_pool_1.IORedisClientPool; } });
7
+ //Implemented Class for NodeRedis
8
+ var redis_client_pool_1 = require("./redis-client-pool");
9
+ Object.defineProperty(exports, "RedisClientPool", { enumerable: true, get: function () { return redis_client_pool_1.RedisClientPool; } });
@@ -1,11 +1,12 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import { IRedisClientPool } from "./i-redis-client-pool";
4
1
  import crypto from 'node:crypto';
5
2
  import fs from 'node:fs';
6
- import Redis, { Cluster } from 'ioredis';
7
- export declare type RedisConnection = Cluster | Redis;
8
- export declare class IORedisClientPool implements IRedisClientPool {
3
+ import { IRedisClientPool } from "./i-redis-client-pool";
4
+ import { TIORedisCommonCommands } from "./t-ioredis-common-commands";
5
+ /**
6
+ * A Redis Client Pool implementation using ioredis library
7
+ * @template redisConnectionType Type of the ioredis connection (Redis or Cluster)
8
+ */
9
+ export declare class IORedisClientPool<redisConnectionType extends TIORedisCommonCommands> implements IRedisClientPool {
9
10
  private readonly nodeFSModule;
10
11
  private readonly nodeCryptoModule;
11
12
  private poolRedisClients;
@@ -14,13 +15,13 @@ export declare class IORedisClientPool implements IRedisClientPool {
14
15
  private redisConnectionCreator;
15
16
  private idlePoolSize;
16
17
  private totalConnectionCounter;
17
- constructor(redisConnectionCreator: () => RedisConnection, idlePoolSize?: number, nodeFSModule?: typeof fs, nodeCryptoModule?: typeof crypto);
18
+ constructor(redisConnectionCreator: () => redisConnectionType, idlePoolSize?: number, nodeFSModule?: typeof fs, nodeCryptoModule?: typeof crypto);
18
19
  generateUniqueToken(prefix: string): string;
19
20
  shutdown(): Promise<void>;
20
21
  acquire(token: string): Promise<void>;
21
22
  release(token: string): Promise<void>;
22
- run(token: string, commandArgs: any): Promise<unknown>;
23
- pipeline(token: string, commands: string[][], transaction?: boolean): Promise<unknown[] | undefined>;
23
+ run(token: string, commandArgs: any): Promise<any>;
24
+ pipeline(token: string, commands: string[][], transaction?: boolean): Promise<any[]>;
24
25
  script(token: string, filePath: string, keys: string[], args: any[]): Promise<any>;
25
26
  info(): {
26
27
  "Idle Size": number;
@@ -29,5 +30,5 @@ export declare class IORedisClientPool implements IRedisClientPool {
29
30
  "Peak Connections": number;
30
31
  };
31
32
  private MD5Hash;
32
- static IORedisClientClusterFactory(connectionDetails: string[], instanceInjection?: <T>(c: new (...args: any) => T, args: any[]) => T): RedisConnection;
33
+ static IORedisClientClusterFactory(connectionDetails: string[], redisClass: new (...args: any) => TIORedisCommonCommands, clusterClass: new (...args: any) => TIORedisCommonCommands, parseURLFunction: (url: string) => Record<string, any>, instanceInjection?: <T>(c: new (...args: any) => T, args: any[]) => T): TIORedisCommonCommands;
33
34
  }
@@ -1,27 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -38,9 +15,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
38
15
  exports.IORedisClientPool = void 0;
39
16
  const node_crypto_1 = __importDefault(require("node:crypto"));
40
17
  const node_fs_1 = __importDefault(require("node:fs"));
41
- const ioredis_1 = __importStar(require("ioredis"));
42
- const utils_1 = require("ioredis/built/utils");
43
18
  function createInstance(c, args = []) { return new c(...args); }
19
+ /**
20
+ * A Redis Client Pool implementation using ioredis library
21
+ * @template redisConnectionType Type of the ioredis connection (Redis or Cluster)
22
+ */
44
23
  class IORedisClientPool {
45
24
  constructor(redisConnectionCreator, idlePoolSize = 6, nodeFSModule = node_fs_1.default, nodeCryptoModule = node_crypto_1.default) {
46
25
  this.nodeFSModule = nodeFSModule;
@@ -99,14 +78,15 @@ class IORedisClientPool {
99
78
  return yield redisClient.call(commandArgs.shift(), ...commandArgs);
100
79
  });
101
80
  }
102
- pipeline(token, commands, transaction = true) {
103
- return __awaiter(this, void 0, void 0, function* () {
81
+ pipeline(token_1, commands_1) {
82
+ return __awaiter(this, arguments, void 0, function* (token, commands, transaction = true) {
83
+ var _a;
104
84
  const redisClient = this.activeRedisClients.get(token);
105
85
  if (redisClient == undefined) {
106
86
  throw new Error("Please acquire a client with proper token");
107
87
  }
108
- const result = transaction === true ? yield redisClient.multi(commands).exec() : yield redisClient.pipeline(commands).exec();
109
- return result === null || result === void 0 ? void 0 : result.map(r => {
88
+ const result = (_a = (transaction === true ? yield redisClient.multi(commands).exec() : yield redisClient.pipeline(commands).exec())) !== null && _a !== void 0 ? _a : [];
89
+ return result.map(r => {
110
90
  let err = r[0];
111
91
  if (err != null) {
112
92
  throw err;
@@ -122,14 +102,12 @@ class IORedisClientPool {
122
102
  throw new Error("Please acquire a client with proper token");
123
103
  }
124
104
  let command = this.filenameToCommand.get(filePath);
125
- // @ts-ignore
126
105
  if (command == null || redisClient[command] == null) {
127
106
  const contents = yield this.nodeFSModule.promises.readFile(filePath, { encoding: "utf-8" });
128
107
  command = this.MD5Hash(contents);
129
108
  redisClient.defineCommand(command, { lua: contents });
130
109
  this.filenameToCommand.set(filePath, command);
131
110
  }
132
- // @ts-ignore
133
111
  return yield redisClient[command](keys.length, keys, args);
134
112
  });
135
113
  }
@@ -146,13 +124,13 @@ class IORedisClientPool {
146
124
  MD5Hash(value) {
147
125
  return this.nodeCryptoModule.createHash('md5').update(value).digest('hex');
148
126
  }
149
- static IORedisClientClusterFactory(connectionDetails, instanceInjection = createInstance) {
127
+ static IORedisClientClusterFactory(connectionDetails, redisClass, clusterClass, parseURLFunction, instanceInjection = createInstance) {
150
128
  const distinctConnections = new Set(connectionDetails);
151
129
  if (distinctConnections.size === 0) {
152
- throw new Error("Inncorrect or Invalid Connection details, cannot be empty");
130
+ throw new Error("Incorrect or Invalid Connection details, cannot be empty");
153
131
  }
154
132
  if (connectionDetails.length > distinctConnections.size || distinctConnections.size > 1) {
155
- const parsedRedisURl = (0, utils_1.parseURL)(connectionDetails[0]); //Assuming all have same password(they should have finally its a cluster)
133
+ const parsedRedisURl = parseURLFunction(connectionDetails[0]); //Assuming all have same password(they should have finally its a cluster)
156
134
  const awsElasticCacheOptions = {
157
135
  dnsLookup: (address, callback) => callback(null, address),
158
136
  redisOptions: {
@@ -161,10 +139,10 @@ class IORedisClientPool {
161
139
  maxRedirections: 32
162
140
  },
163
141
  };
164
- return instanceInjection(ioredis_1.Cluster, [Array.from(distinctConnections.values()), awsElasticCacheOptions]);
142
+ return instanceInjection(clusterClass, [Array.from(distinctConnections.values()), awsElasticCacheOptions]);
165
143
  }
166
144
  else {
167
- return instanceInjection(ioredis_1.default, [connectionDetails[0]]);
145
+ return instanceInjection(redisClass, [connectionDetails[0]]);
168
146
  }
169
147
  }
170
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redis-abstraction",
3
- "version": "0.0.2",
3
+ "version": "1.0.5",
4
4
  "description": "A Redis client pool with abstraction to different Redis libraries.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -39,18 +39,15 @@
39
39
  "Laukik"
40
40
  ],
41
41
  "devDependencies": {
42
- "@types/mocha": "^9.1.1",
43
- "@types/sinon": "^10.0.15",
44
- "cross-env": "^7.0.3",
45
- "mocha": "^9.2.2",
46
- "nyc": "^15.1.0",
42
+ "@types/mocha": "^10.0.10",
43
+ "@types/sinon": "^21.0.0",
44
+ "cross-env": "^10.1.0",
45
+ "mocha": "^11.3.0",
46
+ "nyc": "^17.1.0",
47
47
  "run-script-os": "^1.1.6",
48
- "sinon": "^15.2.0",
49
- "ts-node": "^10.7.0",
50
- "typedoc": "^0.22.14",
51
- "typescript": "^4.6.3"
52
- },
53
- "dependencies": {
54
- "ioredis": "^5.3.2"
48
+ "sinon": "^21.0.1",
49
+ "ts-node": "^10.9.2",
50
+ "typedoc": "^0.28.16",
51
+ "typescript": "^5.9.3"
55
52
  }
56
53
  }
@@ -0,0 +1,22 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import { IRedisClientPool } from "./i-redis-client-pool";
4
+ import { TRedisCommonCommands } from './t-redis-common-commands';
5
+ export declare class RedisClientPool<redisConnectionType extends TRedisCommonCommands> implements IRedisClientPool {
6
+ private readonly redisConnectionCreator;
7
+ private idlePoolSize;
8
+ private readonly nodeFSModule;
9
+ private readonly nodeCryptoModule;
10
+ private totalConnectionCounter;
11
+ private poolRedisClients;
12
+ private activeRedisClients;
13
+ constructor(redisConnectionCreator: () => redisConnectionType, idlePoolSize?: number, nodeFSModule?: typeof fs, nodeCryptoModule?: typeof crypto);
14
+ initialize(): Promise<void>;
15
+ acquire(token: string): Promise<void>;
16
+ release(token: string): Promise<void>;
17
+ shutdown(): Promise<void>;
18
+ run(token: string, commandArgs: string[]): Promise<any>;
19
+ pipeline(token: string, commands: string[][], transaction: boolean): Promise<any>;
20
+ script(token: string, filePath: string, keys: string[], args: string[]): Promise<any>;
21
+ generateUniqueToken(prefix: string): string;
22
+ }
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.RedisClientPool = void 0;
16
+ const node_crypto_1 = __importDefault(require("node:crypto"));
17
+ const node_fs_1 = __importDefault(require("node:fs"));
18
+ class RedisClientPool {
19
+ constructor(redisConnectionCreator, idlePoolSize = 6, nodeFSModule = node_fs_1.default, nodeCryptoModule = node_crypto_1.default) {
20
+ this.redisConnectionCreator = redisConnectionCreator;
21
+ this.idlePoolSize = idlePoolSize;
22
+ this.nodeFSModule = nodeFSModule;
23
+ this.nodeCryptoModule = nodeCryptoModule;
24
+ this.totalConnectionCounter = 0;
25
+ this.poolRedisClients = Array.from({ length: idlePoolSize }, (_) => redisConnectionCreator());
26
+ this.totalConnectionCounter += idlePoolSize;
27
+ this.activeRedisClients = new Map();
28
+ }
29
+ initialize() {
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ const initHandles = this.poolRedisClients.map((_) => __awaiter(this, void 0, void 0, function* () { yield _.connect(); }));
32
+ yield Promise.allSettled(initHandles);
33
+ });
34
+ }
35
+ acquire(token) {
36
+ return __awaiter(this, void 0, void 0, function* () {
37
+ if (!this.activeRedisClients.has(token)) {
38
+ const availableClient = this.poolRedisClients.pop() || (() => { this.totalConnectionCounter += 1; return this.redisConnectionCreator(); })();
39
+ this.activeRedisClients.set(token, availableClient);
40
+ }
41
+ });
42
+ }
43
+ release(token) {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ const releasedClient = this.activeRedisClients.get(token);
46
+ if (releasedClient == undefined) {
47
+ return;
48
+ }
49
+ this.activeRedisClients.delete(token);
50
+ if (this.poolRedisClients.length < this.idlePoolSize) {
51
+ this.poolRedisClients.push(releasedClient);
52
+ }
53
+ else {
54
+ yield releasedClient.close();
55
+ releasedClient.destroy();
56
+ }
57
+ });
58
+ }
59
+ shutdown() {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ const waitHandles = [...this.poolRedisClients, ...Array.from(this.activeRedisClients.values())]
62
+ .map((_) => __awaiter(this, void 0, void 0, function* () { yield _.close(); _.destroy(); }));
63
+ yield Promise.allSettled(waitHandles);
64
+ this.poolRedisClients = [];
65
+ this.activeRedisClients.clear();
66
+ this.totalConnectionCounter = 0;
67
+ });
68
+ }
69
+ run(token, commandArgs) {
70
+ return __awaiter(this, void 0, void 0, function* () {
71
+ const redisClient = this.activeRedisClients.get(token);
72
+ if (redisClient == undefined) {
73
+ throw new Error("Please acquire a client with proper token");
74
+ }
75
+ return yield redisClient.sendCommand(commandArgs);
76
+ });
77
+ }
78
+ pipeline(token, commands, transaction) {
79
+ return __awaiter(this, void 0, void 0, function* () {
80
+ var _a;
81
+ const redisClient = this.activeRedisClients.get(token);
82
+ if (redisClient == undefined) {
83
+ throw new Error("Please acquire a client with proper token");
84
+ }
85
+ const transactionContext = redisClient.multi();
86
+ for (const cmd of commands) {
87
+ const commandName = ((_a = cmd.shift()) !== null && _a !== void 0 ? _a : "").toLowerCase();
88
+ // @ts-ignore
89
+ transactionContext[commandName](...cmd);
90
+ }
91
+ return transaction === true ? yield transactionContext.exec() : yield transactionContext.execAsPipeline();
92
+ });
93
+ }
94
+ script(token, filePath, keys, args) {
95
+ throw new Error("Method not implemented.");
96
+ }
97
+ generateUniqueToken(prefix) {
98
+ return `${prefix}-${this.nodeCryptoModule.randomUUID()}`;
99
+ }
100
+ }
101
+ exports.RedisClientPool = RedisClientPool;
@@ -0,0 +1,20 @@
1
+ interface IIORedisRequiredCommands {
2
+ quit(): Promise<void>;
3
+ disconnect(): void;
4
+ multi(commands: string[][]): this;
5
+ pipeline(commands: string[][]): this;
6
+ exec(): Promise<(Error | null | any)[] | null>;
7
+ defineCommand(commandName: string, definition: {
8
+ lua: string;
9
+ }): void;
10
+ call(commandName: string, ...args: any[]): Promise<any>;
11
+ }
12
+ type TExclusion<T> = {
13
+ [key in Exclude<string, keyof T>]: (argsLength: number, ...args: any[]) => Promise<any>;
14
+ };
15
+ /**
16
+ * Type representing common commands for ioredis clients
17
+ * This interface abstracts the common methods used in both Redis and Cluster clients.
18
+ */
19
+ export type TIORedisCommonCommands = TExclusion<IIORedisRequiredCommands> & IIORedisRequiredCommands;
20
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ interface IRedisRequiredCommands {
2
+ close(): Promise<void>;
3
+ connect(): Promise<void>;
4
+ destroy(): void;
5
+ multi(): this;
6
+ exec(): Promise<(Error | null | any)[] | null>;
7
+ execAsPipeline(): Promise<(Error | null | any)[] | null>;
8
+ sendCommand(args: any[]): Promise<any>;
9
+ }
10
+ type TExclusion<T> = {
11
+ [key in Exclude<string, keyof T>]: (argsLength: number, ...args: any[]) => Promise<any>;
12
+ };
13
+ /**
14
+ * Type representing common commands for node-redis clients
15
+ * This interface abstracts the common methods used in both Redis and Cluster clients.
16
+ */
17
+ export type TRedisCommonCommands = TExclusion<IRedisRequiredCommands> & IRedisRequiredCommands;
18
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });