eip-cloud-services 1.0.21 → 1.1.1

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/src/redis.js CHANGED
@@ -1,26 +1,111 @@
1
- const redis = require ( 'redis' );
2
- const config = require ( 'config' );
3
- let redisClient;
1
+ // Replace the redis require with ioredis
2
+ const Redis = require ( 'ioredis' );
3
+ const fs = require ( 'fs' );
4
+ let config = {};
5
+ const configDirPath = `${process.cwd ()}/config`;
6
+ if ( fs.existsSync ( configDirPath ) && fs.statSync ( configDirPath ).isDirectory () ) {
7
+ config = require ( 'config' ); // require the config directory if it exists
8
+ }
4
9
 
5
- const getClient = async () => {
10
+ const clients = {};
11
+
12
+ /**
13
+ * Creates or retrieves a Redis client instance based on a given client identifier.
14
+ * If the client does not exist, it creates a new one, either connecting to a Redis Cluster
15
+ * or a single Redis instance based on the configuration.
16
+ *
17
+ * @param {string} [clientId='main'] - The identifier for the Redis client. Defaults to 'main'.
18
+ * This identifier is used to manage multiple Redis client instances.
19
+ * @returns {Promise<Redis | Redis.Cluster>} A promise that resolves with the Redis client instance.
20
+ * @throws {Error} Throws an error if the Redis connection or configuration fails.
21
+ *
22
+ * @description
23
+ * This function manages a pool of Redis clients, identified by unique clientIds.
24
+ * It supports connecting to both standard Redis and Redis Cluster configurations.
25
+ * For a Redis Cluster, it expects an array of node details in the configuration.
26
+ * The function handles connection readiness and errors for both cluster and standard modes.
27
+ */
28
+ const getClient = async ( clientId = 'main' ) => {
6
29
  try {
7
- if ( !redisClient ) {
8
- redisClient = redis.createClient ( { url: `${config.redis.host}:${config.redis.port}` } );
9
- await redisClient.connect ();
10
- redisClient.on ( 'error', error => {
11
- console.error ( '\x1b[33mREDIS CONNECTION FAILED: Redis connection failed. If you\'re running locally, is a redis server actually active? Also, check your connection configuration.\x1b[0m' );
12
-
13
- throw error;
14
- }, { once: true } );
30
+ if ( !clients[ clientId ] ) {
31
+ let redisClient;
32
+
33
+ if ( config?.redis?.clusterEnabled && Array.isArray ( config.redis.cluster ) && config.redis.cluster.length > 0 ) {
34
+ const clusterNodes = config.redis.cluster.map ( node => ( {
35
+ host: node.host,
36
+ port: node.port
37
+ } ) );
38
+
39
+ redisClient = new Redis.Cluster ( clusterNodes );
40
+
41
+ // Await until the cluster is ready
42
+ await new Promise ( ( resolve, reject ) => {
43
+ redisClient.once ( 'ready', resolve );
44
+ redisClient.once ( 'error', reject );
45
+ } );
46
+
47
+ redisClient.on ( 'node error', err => {
48
+ console.error ( 'Redis cluster node error', err );
49
+ } );
50
+ }
51
+ else if ( config?.redis?.clusterEnabled && ( !Array.isArray ( config.redis.cluster ) || config.redis.cluster.length === 0 ) ) {
52
+ throw new Error ( 'Redis Cluster is enabled but there were no cluster nodes defined in config.redis.cluster' );
53
+ }
54
+ else {
55
+ redisClient = new Redis ( {
56
+ host: config?.redis?.host,
57
+ port: config?.redis?.port
58
+ } );
59
+
60
+ redisClient.on ( 'error', error => {
61
+ console.error ( '\x1b[33mREDIS CONNECTION FAILED: Redis connection failed. If you\'re running locally, is a redis server actually active? Also, check your connection configuration.\x1b[0m' );
62
+ throw error;
63
+ } );
64
+
65
+ await new Promise ( ( resolve, reject ) => {
66
+ redisClient.once ( 'ready', resolve );
67
+ redisClient.once ( 'error', reject );
68
+ } );
69
+ }
70
+
71
+ clients[ clientId ] = redisClient;
15
72
  }
16
73
 
17
- return redisClient;
74
+ return clients[ clientId ];
18
75
  }
19
- catch ( error ){
20
- throw new Error ( 'Redis connection failed. If running locally is redis actually running? Also check your connection configuration.' );
76
+ catch ( error ) {
77
+ throw error;
21
78
  }
22
79
  };
23
80
 
81
+ /**
82
+ * Retrieves a Redis client based on the provided identifier.
83
+ * This function is primarily used internally within the module.
84
+ * For standard operations, the module's functionality should be used directly.
85
+ *
86
+ * @param {string} [clientId='main'] - The identifier for the Redis client. Defaults to 'main'.
87
+ * @returns {Promise<Redis>} - A promise that resolves with the Redis client.
88
+ */
89
+ exports.getClient = getClient;
90
+
91
+ /**
92
+ * Retrieves a dedicated Redis client for subscription operations.
93
+ * This is a specialized client intended for internal use by the module,
94
+ * specifically for managing subscriptions to Redis channels.
95
+ *
96
+ * @returns {Promise<Redis>} - A promise that resolves with the Redis subscription client.
97
+ */
98
+ exports.getSubClient = () => getClient ( 'main_sub' );
99
+
100
+ /**
101
+ * Retrieves a dedicated Redis client for publishing operations.
102
+ * This client is used internally by the module for publishing messages to Redis channels.
103
+ * It's not intended for direct use; instead, use the module's publish functionality.
104
+ *
105
+ * @returns {Promise<Redis>} - A promise that resolves with the Redis publishing client.
106
+ */
107
+ exports.getPubClient = () => getClient ( 'main_pub' );
108
+
24
109
  /**
25
110
  * Retrieves all keys matching a pattern.
26
111
  * @param {string} pattern - The pattern to match.
@@ -34,11 +119,10 @@ exports.keys = async ( pattern ) => {
34
119
  if ( typeof pattern === 'string' ) {
35
120
  const client = await getClient ();
36
121
 
37
- const commandArgs = [
38
- pattern.startsWith ( config.redis.prefix ) ? pattern : config.redis.prefix + pattern,
39
- ];
122
+ const fullPattern = pattern.startsWith ( config?.redis?.prefix ) ? pattern : config?.redis?.prefix + pattern;
40
123
 
41
- return await client.sendCommand ( [ 'KEYS', ...commandArgs ] );
124
+ // Use the keys method directly instead of sendCommand
125
+ return await client.keys ( fullPattern );
42
126
  }
43
127
 
44
128
  throw new Error ( `redis.keys expects a string pattern, instead the type provided was: ${typeof pattern}` );
@@ -49,6 +133,36 @@ exports.keys = async ( pattern ) => {
49
133
  }
50
134
  };
51
135
 
136
+ /**
137
+ * Iteratively scans through keys in the Redis database.
138
+ * @param {number} cursor - The cursor to start scanning from. Use 0 to start a new scan.
139
+ * @param {string} [matchPattern='*'] - The pattern to match keys against. Defaults to '*' to match all keys.
140
+ * @param {number} [count=100] - The number of keys to return per call. Defaults to 100.
141
+ * @returns {Promise<Array>} - A promise that resolves with an array containing the next cursor and an array of keys.
142
+ *
143
+ * @description
144
+ * This method uses the SCAN command to iteratively scan through the keys in the Redis database.
145
+ * It allows for efficient scanning of large datasets without the risk of blocking the server for a long time.
146
+ * The method returns a promise that resolves with an array, where the first element is the next cursor to be used
147
+ * for subsequent calls, and the second element is an array of keys that matched the pattern.
148
+ */
149
+ exports.scan = async ( cursor, matchPattern = '*', count = 100 ) => {
150
+ try {
151
+ const client = await getClient ();
152
+
153
+ const fullMatchPattern = matchPattern.startsWith ( config?.redis?.prefix ) ? matchPattern : config?.redis?.prefix + matchPattern;
154
+
155
+ // Directly use the scan method provided by ioredis
156
+ const result = await client.scan ( cursor, 'MATCH', fullMatchPattern, 'COUNT', count );
157
+
158
+ return result;
159
+ }
160
+ catch ( error ) {
161
+ console.log ( 'REDIS LIB ERROR [scan]', error );
162
+ throw error;
163
+ }
164
+ };
165
+
52
166
  /**
53
167
  * Retrieves the value of a key.
54
168
  * @param {string} key - The key to retrieve.
@@ -63,11 +177,10 @@ exports.get = async ( key ) => {
63
177
  if ( typeof key === 'string' ) {
64
178
  const client = await getClient ();
65
179
 
66
- const commandArgs = [
67
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
68
- ];
180
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
69
181
 
70
- const data = await client.sendCommand ( [ 'GET', ...commandArgs ] );
182
+ // Use the get method directly
183
+ const data = await client.get ( fullKey );
71
184
 
72
185
  try {
73
186
  return JSON.parse ( data );
@@ -80,7 +193,44 @@ exports.get = async ( key ) => {
80
193
  throw new Error ( `redis.get expects a string key, instead the type provided was: ${typeof key}` );
81
194
  }
82
195
  catch ( error ) {
83
- console.log ( 'REDIS LIB ERROR [get]', error.stack );
196
+ console.log ( 'REDIS LIB ERROR [get]', error );
197
+ throw error;
198
+ }
199
+ };
200
+
201
+ /**
202
+ * Retrieves the values of multiple keys.
203
+ * @param {string[]} keys - The keys to retrieve.
204
+ * @returns {Promise} - A promise that resolves with the values of the keys.
205
+ *
206
+ * @description This method retrieves the values associated with the specified keys from Redis.
207
+ * If the value is a JSON string, it will be parsed and returned as a JavaScript object.
208
+ * If the value is not a valid JSON string, it will be returned as a string.
209
+ */
210
+ exports.multiGet = async ( keys ) => {
211
+ try {
212
+ if ( Array.isArray ( keys ) ) {
213
+ const client = await getClient ();
214
+
215
+ const fullKeys = keys.map ( key => key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key );
216
+
217
+ // Use the mget method directly
218
+ const data = await client.mget ( ...fullKeys );
219
+
220
+ return data.map ( val => {
221
+ try {
222
+ return JSON.parse ( val );
223
+ }
224
+ catch {
225
+ return val;
226
+ }
227
+ } );
228
+ }
229
+
230
+ throw new Error ( `redis.multiGet expects an array of keys, instead the type provided was: ${typeof keys}` );
231
+ }
232
+ catch ( error ) {
233
+ console.log ( 'REDIS LIB ERROR [multiGet]', error );
84
234
  throw error;
85
235
  }
86
236
  };
@@ -102,15 +252,12 @@ exports.set = async ( key, value, ex, px ) => {
102
252
  if ( typeof key === 'string' ) {
103
253
  const client = await getClient ();
104
254
 
105
- const commandArgs = [
106
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
107
- value,
108
- ];
109
-
110
- if ( ex ) commandArgs.push ( 'EX', String ( ex ) );
111
- if ( px ) commandArgs.push ( 'PX', String ( px ) );
255
+ // Automatically convert objects or arrays to JSON strings
256
+ const serializedValue = ( typeof value === 'object' && value !== null ) ? JSON.stringify ( value ) : value;
257
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
112
258
 
113
- return await client.sendCommand ( [ 'SET', ...commandArgs ] );
259
+ // Use the set method directly with optional EX and PX parameters
260
+ return await client.set ( fullKey, serializedValue, ...( ex ? [ 'EX', ex ] : [] ), ...( px ? [ 'PX', px ] : [] ) );
114
261
  }
115
262
 
116
263
  throw new Error ( `redis.set expects a string key, instead the type provided was: ${typeof key}` );
@@ -135,17 +282,47 @@ exports.del = async ( key ) => {
135
282
  if ( typeof key === 'string' ) {
136
283
  const client = await getClient ();
137
284
 
138
- const commandArgs = [
139
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
140
- ];
285
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
141
286
 
142
- return await client.sendCommand ( [ 'DEL', ...commandArgs ] );
287
+ // Use the del method directly
288
+ return await client.del ( fullKey );
143
289
  }
144
290
 
145
291
  throw new Error ( `redis.del expects a string key, instead the type provided was: ${typeof key}` );
146
292
  }
147
293
  catch ( error ) {
148
- console.log ( 'REDIS LIB ERROR [del]', error.stack );
294
+ console.log ( 'REDIS LIB ERROR [del]', error );
295
+ throw error;
296
+ }
297
+ };
298
+
299
+ /**
300
+ * Deletes multiple keys.
301
+ * @param {string[]} keys - The keys to delete.
302
+ * @returns {Promise<number>} - A promise that resolves with the count of keys deleted.
303
+ *
304
+ * @description
305
+ * This method deletes the specified keys from Redis.
306
+ * If a key does not exist, it is silently ignored. The function returns the count of keys actually deleted.
307
+ * If an array is empty or none of the keys exist, the function returns 0.
308
+ * Note: It's more efficient to delete multiple keys in one call than to use individual delete calls for each key.
309
+ */
310
+ exports.multiDel = async ( keys ) => {
311
+ try {
312
+ if ( Array.isArray ( keys ) && keys.every ( key => typeof key === 'string' ) ) {
313
+ const client = await getClient ();
314
+
315
+ // Prepend the Redis prefix to each key if necessary
316
+ const fullKeys = keys.map ( key => key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key );
317
+
318
+ // Use the del method with multiple keys
319
+ return await client.del ( ...fullKeys );
320
+ }
321
+
322
+ throw new Error ( `redis.multiDel expects an array of string keys, instead the type provided was: ${typeof keys}` );
323
+ }
324
+ catch ( error ) {
325
+ console.log ( 'REDIS LIB ERROR [multiDel]', error );
149
326
  throw error;
150
327
  }
151
328
  };
@@ -154,7 +331,7 @@ exports.del = async ( key ) => {
154
331
  * Adds one or more members to a set.
155
332
  * @param {string} key - The key of the set.
156
333
  * @param {string|string[]} members - The member(s) to add to the set.
157
- * @returns {Promise} - A promise that resolves with the result of the SADD command.
334
+ * @returns {Promise<number>} - A promise that resolves with the number of members that were added to the set.
158
335
  *
159
336
  * @description This method adds one or more members to the specified set in Redis.
160
337
  * If the set does not exist, it will be created.
@@ -166,12 +343,11 @@ exports.setAdd = async ( key, members ) => {
166
343
  if ( typeof key === 'string' ) {
167
344
  const client = await getClient ();
168
345
 
169
- const commandArgs = [
170
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
171
- ...( Array.isArray ( members ) ? members : [ members ] ),
172
- ];
346
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
347
+ const membersArray = Array.isArray ( members ) ? members : [ members ];
173
348
 
174
- return await client.sendCommand ( [ 'SADD', ...commandArgs ] );
349
+ // Use the sadd method directly
350
+ return await client.sadd ( fullKey, ...membersArray );
175
351
  }
176
352
 
177
353
  throw new Error ( `redis.setAdd expects a string key, instead the type provided was: ${typeof key}` );
@@ -186,7 +362,7 @@ exports.setAdd = async ( key, members ) => {
186
362
  * Removes one or more members from a set.
187
363
  * @param {string} key - The key of the set.
188
364
  * @param {string|string[]} members - The member(s) to remove from the set.
189
- * @returns {Promise} - A promise that resolves with the result of the SREM command.
365
+ * @returns {Promise<number>} - A promise that resolves with the number of members that were removed from the set.
190
366
  *
191
367
  * @description This method removes one or more members from the specified set in Redis.
192
368
  * If the set does not exist or if any member does not exist in the set, it will be ignored.
@@ -197,18 +373,17 @@ exports.setRemove = async ( key, members ) => {
197
373
  if ( typeof key === 'string' ) {
198
374
  const client = await getClient ();
199
375
 
200
- const commandArgs = [
201
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
202
- ...( Array.isArray ( members ) ? members : [ members ] ),
203
- ];
376
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
377
+ const membersArray = Array.isArray ( members ) ? members : [ members ];
204
378
 
205
- return await client.sendCommand ( [ 'SREM', ...commandArgs ] );
379
+ // Use the srem method directly
380
+ return await client.srem ( fullKey, ...membersArray );
206
381
  }
207
382
 
208
383
  throw new Error ( `redis.setRemove expects a string key, instead the type provided was: ${typeof key}` );
209
384
  }
210
385
  catch ( error ) {
211
- console.log ( 'REDIS LIB ERROR [setRemove]', error.stack );
386
+ console.log ( 'REDIS LIB ERROR [setRemove]', error );
212
387
  throw error;
213
388
  }
214
389
  };
@@ -216,7 +391,7 @@ exports.setRemove = async ( key, members ) => {
216
391
  /**
217
392
  * Retrieves all members of a set.
218
393
  * @param {string} key - The key of the set.
219
- * @returns {Promise} - A promise that resolves with the result of the SMEMBERS command.
394
+ * @returns {Promise<string[]>} - A promise that resolves with an array of members of the set.
220
395
  *
221
396
  * @description This method retrieves all members of the specified set in Redis.
222
397
  * If the set does not exist, it will return an empty array.
@@ -226,17 +401,16 @@ exports.setMembers = async ( key ) => {
226
401
  if ( typeof key === 'string' ) {
227
402
  const client = await getClient ();
228
403
 
229
- const commandArgs = [
230
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
231
- ];
404
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
232
405
 
233
- return await client.sendCommand ( [ 'SMEMBERS', ...commandArgs ] );
406
+ // Use the smembers method directly
407
+ return await client.smembers ( fullKey );
234
408
  }
235
409
 
236
410
  throw new Error ( `redis.setMembers expects a string key, instead the type provided was: ${typeof key}` );
237
411
  }
238
412
  catch ( error ) {
239
- console.log ( 'REDIS LIB ERROR [setMembers]', error.stack );
413
+ console.log ( 'REDIS LIB ERROR [setMembers]', error );
240
414
  throw error;
241
415
  }
242
416
  };
@@ -244,7 +418,7 @@ exports.setMembers = async ( key ) => {
244
418
  /**
245
419
  * Removes and returns a random member from a set.
246
420
  * @param {string} key - The key of the set.
247
- * @returns {Promise} - A promise that resolves with the removed member, or null if the set is empty.
421
+ * @returns {Promise<string|null>} - A promise that resolves with the removed member, or null if the set is empty.
248
422
  *
249
423
  * @description
250
424
  * This function removes and returns a random member from a set identified by the provided key.
@@ -256,25 +430,24 @@ exports.setPop = async ( key ) => {
256
430
  try {
257
431
  if ( typeof key === 'string' ) {
258
432
  const client = await getClient ();
259
-
260
- const commandArgs = [
261
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
262
- ];
263
-
264
- const member = await client.sendCommand ( [ 'SPOP', ...commandArgs ] );
265
-
433
+
434
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
435
+
436
+ // Use the spop method directly
437
+ const member = await client.spop ( fullKey );
438
+
266
439
  // If the member is null or an empty string, return null
267
440
  if ( member === null || member === '' ) {
268
441
  return null;
269
442
  }
270
-
443
+
271
444
  return member;
272
445
  }
273
-
446
+
274
447
  throw new Error ( `redis.setPop expects a string key, instead the type provided was: ${typeof key}` );
275
448
  }
276
449
  catch ( error ) {
277
- console.log ( 'REDIS LIB ERROR [setPop]', error.stack );
450
+ console.log ( 'REDIS LIB ERROR [setPop]', error );
278
451
  throw error;
279
452
  }
280
453
  };
@@ -283,7 +456,7 @@ exports.setPop = async ( key ) => {
283
456
  * Prepends multiple values to the head of a list.
284
457
  * @param {string} key - The key of the list.
285
458
  * @param {string|string[]} values - The values to prepend to the list.
286
- * @returns {Promise} - A promise that resolves with the result of the LPUSH command.
459
+ * @returns {Promise<number>} - A promise that resolves with the length of the list after the prepend operation.
287
460
  *
288
461
  * @description This method prepends one or more values to the head of the specified list in Redis.
289
462
  * If the list does not exist, it will be created.
@@ -295,27 +468,17 @@ exports.listUnshift = async ( key, values ) => {
295
468
  if ( typeof key === 'string' ) {
296
469
  const client = await getClient ();
297
470
 
298
- const commandArgs = [
299
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
300
- ];
301
-
302
- if ( typeof values === 'string' ) {
303
- commandArgs.push ( values );
304
- }
305
- else if ( Array.isArray ( values ) ) {
306
- commandArgs.push ( ...values );
307
- }
308
- else {
309
- throw new Error ( `redis.listUnshift expects a string or an array of strings as the values, instead the type provided was: ${typeof values}` );
310
- }
471
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
472
+ const valuesArray = Array.isArray ( values ) ? values : [ values ];
311
473
 
312
- return await client.sendCommand ( [ 'LPUSH', ...commandArgs ] );
474
+ // Use the lpush method directly
475
+ return await client.lpush ( fullKey, ...valuesArray );
313
476
  }
314
477
 
315
478
  throw new Error ( `redis.listUnshift expects a string key, instead the type provided was: ${typeof key}` );
316
479
  }
317
480
  catch ( error ) {
318
- console.log ( 'REDIS LIB ERROR [listUnshift]', error.stack );
481
+ console.log ( 'REDIS LIB ERROR [listUnshift]', error );
319
482
  throw error;
320
483
  }
321
484
  };
@@ -324,7 +487,7 @@ exports.listUnshift = async ( key, values ) => {
324
487
  * Pushes values to the tail of a list.
325
488
  * @param {string} key - The key of the list.
326
489
  * @param {string|string[]} values - The value(s) to push to the list.
327
- * @returns {Promise} - A promise that resolves with the result of the RPUSH command.
490
+ * @returns {Promise<number>} - A promise that resolves with the length of the list after the push operation.
328
491
  *
329
492
  * @description This method pushes one or more values to the tail of the specified list in Redis.
330
493
  * If the list does not exist, it will be created.
@@ -336,27 +499,17 @@ exports.listPush = async ( key, values ) => {
336
499
  if ( typeof key === 'string' ) {
337
500
  const client = await getClient ();
338
501
 
339
- const commandArgs = [
340
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
341
- ];
502
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
503
+ const valuesArray = Array.isArray ( values ) ? values : [ values ];
342
504
 
343
- if ( typeof values === 'string' ) {
344
- commandArgs.push ( values );
345
- }
346
- else if ( Array.isArray ( values ) ) {
347
- commandArgs.push ( ...values );
348
- }
349
- else {
350
- throw new Error ( `redis.listPush expects a string or an array of strings as the values, instead the type provided was: ${typeof values}` );
351
- }
352
-
353
- return await client.sendCommand ( [ 'RPUSH', ...commandArgs ] );
505
+ // Use the rpush method directly
506
+ return await client.rpush ( fullKey, ...valuesArray );
354
507
  }
355
508
 
356
509
  throw new Error ( `redis.listPush expects a string key, instead the type provided was: ${typeof key}` );
357
510
  }
358
511
  catch ( error ) {
359
- console.log ( 'REDIS LIB ERROR [listPush]', error.stack );
512
+ console.log ( 'REDIS LIB ERROR [listPush]', error );
360
513
  throw error;
361
514
  }
362
515
  };
@@ -364,8 +517,8 @@ exports.listPush = async ( key, values ) => {
364
517
  /**
365
518
  * Removes and returns the first element of a list.
366
519
  * @param {string} key - The key of the list.
367
- * @param {number} count - The number of items to remove (default: 0).
368
- * @returns {Promise} - A promise that resolves with the result of the LPOP command.
520
+ * @param {number} count - The number of items to remove (default: 1).
521
+ * @returns {Promise<string|string[]>} - A promise that resolves with the removed element(s).
369
522
  *
370
523
  * @description This method removes and returns the first element(s) from the specified list in Redis.
371
524
  * If the list is empty or does not exist, it will return null.
@@ -377,18 +530,41 @@ exports.listPop = async ( key, count = 1 ) => {
377
530
  if ( typeof key === 'string' ) {
378
531
  const client = await getClient ();
379
532
 
380
- const commandArgs = [
381
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
382
- String ( count ),
383
- ];
533
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
384
534
 
385
- return await client.sendCommand ( [ 'LPOP', ...commandArgs ] );
535
+ // Use the lpop method directly with the count parameter
536
+ return await client.lpop ( fullKey, count );
386
537
  }
387
538
 
388
539
  throw new Error ( `redis.listPop expects a string key, instead the type provided was: ${typeof key}` );
389
540
  }
390
541
  catch ( error ) {
391
- console.log ( 'REDIS LIB ERROR [listPop]', error.stack );
542
+ console.log ( 'REDIS LIB ERROR [listPop]', error );
543
+ throw error;
544
+ }
545
+ };
546
+
547
+ /**
548
+ * Removes and returns the last element of a list.
549
+ * @param {string} key - The key of the list.
550
+ * @returns {Promise<string|null>} - A promise that resolves with the removed element, or null if the list is empty.
551
+ *
552
+ * @description This method removes and returns the last element from the specified list in Redis.
553
+ */
554
+ exports.listPopEnd = async ( key ) => {
555
+ try {
556
+ if ( typeof key === 'string' ) {
557
+ const client = await getClient ();
558
+
559
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
560
+
561
+ return await client.rpop ( fullKey );
562
+ }
563
+
564
+ throw new Error ( 'redis.listPopEnd expects a string key' );
565
+ }
566
+ catch ( error ) {
567
+ console.error ( 'REDIS LIB ERROR [listPopEnd]', error );
392
568
  throw error;
393
569
  }
394
570
  };
@@ -398,7 +574,7 @@ exports.listPop = async ( key, count = 1 ) => {
398
574
  * @param {string} key - The key of the list.
399
575
  * @param {number} start - The starting index (default: 0).
400
576
  * @param {number} end - The ending index (default: -1).
401
- * @returns {Promise} - A promise that resolves with the result of the LRANGE command.
577
+ * @returns {Promise<string[]>} - A promise that resolves with an array of elements from the specified range.
402
578
  *
403
579
  * @description This method retrieves a range of elements from the specified list in Redis.
404
580
  * The range is specified by the start and end indices.
@@ -410,19 +586,16 @@ exports.listRange = async ( key, start = 0, end = -1 ) => {
410
586
  if ( typeof key === 'string' ) {
411
587
  const client = await getClient ();
412
588
 
413
- const commandArgs = [
414
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
415
- String ( start ),
416
- String ( end ),
417
- ];
589
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
418
590
 
419
- return await client.sendCommand ( [ 'LRANGE', ...commandArgs ] );
591
+ // Use the lrange method directly
592
+ return await client.lrange ( fullKey, start, end );
420
593
  }
421
594
 
422
595
  throw new Error ( `redis.listRange expects a string key, instead the type provided was: ${typeof key}` );
423
596
  }
424
597
  catch ( error ) {
425
- console.log ( 'REDIS LIB ERROR [listRange]', error.stack );
598
+ console.log ( 'REDIS LIB ERROR [listRange]', error );
426
599
  throw error;
427
600
  }
428
601
  };
@@ -430,7 +603,7 @@ exports.listRange = async ( key, start = 0, end = -1 ) => {
430
603
  /**
431
604
  * Retrieves the expiration time of a key in seconds.
432
605
  * @param {string} key - The key to get the expiration time of.
433
- * @returns {Promise} - A promise that resolves with the result of the TTL command.
606
+ * @returns {Promise<number>} - A promise that resolves with the number of seconds remaining until the key expires, or -1 if the key does not have an expiration.
434
607
  *
435
608
  * @description This method retrieves the expiration time (in seconds) of the specified key in Redis.
436
609
  * If the key does not exist or does not have an expiration set, it will return -1.
@@ -442,17 +615,16 @@ exports.getExpiryInSeconds = async ( key ) => {
442
615
  if ( typeof key === 'string' ) {
443
616
  const client = await getClient ();
444
617
 
445
- const commandArgs = [
446
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
447
- ];
618
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
448
619
 
449
- return await client.sendCommand ( [ 'TTL', ...commandArgs ] );
620
+ // Use the ttl method directly
621
+ return await client.ttl ( fullKey );
450
622
  }
451
623
 
452
624
  throw new Error ( `redis.getExpiryInSeconds expects a string key, instead the type provided was: ${typeof key}` );
453
625
  }
454
626
  catch ( error ) {
455
- console.log ( 'REDIS LIB ERROR [getExpiryInSeconds]', error.stack );
627
+ console.log ( 'REDIS LIB ERROR [getExpiryInSeconds]', error );
456
628
  throw error;
457
629
  }
458
630
  };
@@ -461,7 +633,7 @@ exports.getExpiryInSeconds = async ( key ) => {
461
633
  * Sets the expiration time for a key in seconds.
462
634
  * @param {string} key - The key to set the expiration time for.
463
635
  * @param {number} seconds - The expiration time in seconds (default: 0).
464
- * @returns {Promise} - A promise that resolves with the result of the EXPIRE command.
636
+ * @returns {Promise<number>} - A promise that resolves with 1 if the expiration time was set successfully, or 0 if the key does not exist.
465
637
  *
466
638
  * @description This method sets the expiration time (in seconds) for the specified key in Redis.
467
639
  * If the key does not exist, it will return 0 indicating no expiration was set.
@@ -472,25 +644,100 @@ exports.setExpiry = async ( key, seconds = 0 ) => {
472
644
  if ( typeof key === 'string' ) {
473
645
  const client = await getClient ();
474
646
 
475
- const commandArgs = [
476
- key.startsWith ( config.redis.prefix ) ? key : config.redis.prefix + key,
477
- String ( seconds ),
478
- ];
647
+ const fullKey = key.startsWith ( config?.redis?.prefix ) ? key : config?.redis?.prefix + key;
479
648
 
480
- return await client.sendCommand ( [ 'EXPIRE', ...commandArgs ] );
649
+ // Use the expire method directly
650
+ return await client.expire ( fullKey, seconds );
481
651
  }
482
652
 
483
653
  throw new Error ( `redis.setExpiry expects a string key, instead the type provided was: ${typeof key}` );
484
654
  }
485
655
  catch ( error ) {
486
- console.log ( 'REDIS LIB ERROR [setExpiry]', error.stack );
656
+ console.log ( 'REDIS LIB ERROR [setExpiry]', error );
487
657
  throw error;
488
658
  }
489
659
  };
490
660
 
491
- exports.kill = async () => {
492
- const client = await getClient ();
493
- client.quit ();
661
+ /**
662
+ * Publishes a message to a channel.
663
+ * @param {string} channel - The channel to publish the message to.
664
+ * @param {string} message - The message to publish.
665
+ * @returns {Promise<number>} - A promise that resolves with the number of subscribers that received the message.
666
+ *
667
+ * @description This method publishes a message to the specified channel in Redis.
668
+ */
669
+ exports.publish = async ( channel, message ) => {
670
+ try {
671
+ if ( typeof channel === 'string' && typeof message === 'string' ) {
672
+ const client = await getClient ( `${channel}_pub` );
494
673
 
495
- return;
674
+ return client.publish ( channel, message );
675
+ }
676
+
677
+ throw new Error ( 'redis.publish expects string types for both channel and message' );
678
+ }
679
+ catch ( error ) {
680
+ console.error ( 'REDIS LIB ERROR [publish]', error );
681
+ throw error;
682
+ }
683
+ };
684
+
685
+ /**
686
+ * Subscribes to a channel to listen for messages.
687
+ * @param {string} channel - The channel to subscribe to.
688
+ * @param {Function} messageHandler - The callback function to handle messages.
689
+ * @returns {Promise<void>} - A promise that resolves when the subscription is set up.
690
+ *
691
+ * @description This method subscribes to the specified channel in Redis and sets up a handler for incoming messages.
692
+ */
693
+ exports.subscribe = async ( channel, messageHandler ) => {
694
+ try {
695
+ if ( typeof channel === 'string' && typeof messageHandler === 'function' ) {
696
+ const client = await getClient ( `${channel}_sub` );
697
+
698
+ client.on ( 'message', ( receivedChannel, message ) => {
699
+ if ( receivedChannel === channel ) {
700
+ messageHandler ( message );
701
+ }
702
+ } );
703
+ await client.subscribe ( channel );
704
+
705
+ return;
706
+ }
707
+ else {
708
+ throw new Error ( 'redis.subscribe expects a string channel and a callback function' );
709
+ }
710
+ }
711
+ catch ( error ) {
712
+ console.error ( 'REDIS LIB ERROR [subscribe]', error );
713
+ throw error;
714
+ }
715
+ };
716
+
717
+ /**
718
+ * Retrieves information and statistics about the Redis server.
719
+ * @returns {Promise<string>} - A promise that resolves with server information.
720
+ *
721
+ * @description This method returns information and various statistics about the Redis server.
722
+ */
723
+ exports.info = async () => {
724
+ try {
725
+ const client = await getClient ();
726
+
727
+ return await client.info ();
728
+ }
729
+ catch ( error ) {
730
+ console.error ( 'REDIS LIB ERROR [info]', error );
731
+ throw error;
732
+ }
733
+ };
734
+
735
+ /**
736
+ * Gracefully closes the Redis connection.
737
+ * @returns {Promise<void>} - A promise that resolves once the connection is closed.
738
+ *
739
+ * @description This method closes the Redis connection using the 'quit' command, ensuring a graceful shutdown of the connection.
740
+ */
741
+ exports.kill = async () => {
742
+ await Promise.all ( Object.values ( clients ).map ( client => client.quit () ) );
496
743
  };