@jusbid/redis 1.0.0

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.
Files changed (2) hide show
  1. package/index.js +893 -0
  2. package/package.json +10 -0
package/index.js ADDED
@@ -0,0 +1,893 @@
1
+ const { createClient } = require('redis');
2
+ const { logger } = require('@jusbid/logger');
3
+
4
+ const redisOptions = {};
5
+
6
+ // Build Redis connection options dynamically
7
+ if (process.env.REDIS_URL) {
8
+ redisOptions.url = process.env.REDIS_URL;
9
+ } else {
10
+ const protocol = process.env.REDIS_TLS === 'true' ? 'rediss' : 'redis';
11
+ const host = process.env.REDIS_HOST || 'localhost';
12
+ const port = process.env.REDIS_PORT || 6379;
13
+ const username = process.env.REDIS_USERNAME || '';
14
+ const password = process.env.REDIS_PASSWORD || '';
15
+
16
+ if (password) {
17
+ redisOptions.url = `${protocol}://${username ? `${encodeURIComponent(username)}:` : ''}${encodeURIComponent(password)}@${host}:${port}`;
18
+ } else {
19
+ redisOptions.url = `${protocol}://${host}:${port}`;
20
+ }
21
+ }
22
+
23
+ // Enable secure TLS settings for enterprise Redis cloud deployments (e.g. AWS, Upstash)
24
+ redisOptions.socket = {
25
+ reconnectStrategy: (retries) => {
26
+ const delay = Math.min(retries * 100, 10000);
27
+ logger.warn(`Redis client disconnected. Retrying connection in ${delay}ms... (attempt ${retries})`);
28
+ return delay;
29
+ },
30
+ };
31
+
32
+ if (process.env.REDIS_TLS === 'true') {
33
+ redisOptions.socket.tls = true;
34
+ if (process.env.REDIS_REJECT_UNAUTHORIZED === 'false') {
35
+ redisOptions.socket.rejectUnauthorized = false;
36
+ }
37
+ }
38
+
39
+ const rawRedisClient = createClient(redisOptions);
40
+
41
+ rawRedisClient.on('error', (err) => {
42
+ logger.error('Redis client error occurred', { error: err.message, stack: err.stack });
43
+ });
44
+
45
+ rawRedisClient.on('connect', () => {
46
+ logger.info('Connecting to Redis server...');
47
+ });
48
+
49
+ rawRedisClient.on('ready', () => {
50
+ logger.info('Redis client connected and ready.');
51
+ });
52
+
53
+ // Lazy connection management
54
+ let isConnected = false;
55
+ let connectingPromise = null;
56
+
57
+ const ensureConnected = async () => {
58
+ if (rawRedisClient.isOpen && isConnected) {
59
+ return;
60
+ }
61
+
62
+ if (connectingPromise) {
63
+ return connectingPromise;
64
+ }
65
+
66
+ connectingPromise = (async () => {
67
+ try {
68
+ await rawRedisClient.connect();
69
+ isConnected = true;
70
+ } catch (err) {
71
+ logger.error('Failed to initialize Redis connection', { error: err.message });
72
+ isConnected = false;
73
+ throw err;
74
+ } finally {
75
+ connectingPromise = null;
76
+ }
77
+ })();
78
+
79
+ return connectingPromise;
80
+ };
81
+
82
+ // Create a proxy wrapper to dynamically connect on the first method invocation
83
+ const redisClient = new Proxy(rawRedisClient, {
84
+ get(target, prop) {
85
+ const origValue = target[prop];
86
+ if (typeof origValue === 'function') {
87
+ return async (...args) => {
88
+ // Exclude listener attachments, connection initiation, and closing from trigger
89
+ if (prop !== 'connect' && prop !== 'on' && prop !== 'off' && prop !== 'once' && prop !== 'quit') {
90
+ await ensureConnected();
91
+ }
92
+ return origValue.apply(target, args);
93
+ };
94
+ }
95
+ return origValue;
96
+ }
97
+ });
98
+
99
+ const get = async (key) => {
100
+ try {
101
+ const data = await redisClient.get(key);
102
+ return data ? JSON.parse(data) : null;
103
+ } catch (error) {
104
+ logger.error(`Redis GET error for key ${key}`, { error: error.message });
105
+ return null;
106
+ }
107
+ };
108
+
109
+ const sIsMember = async (key, action) => {
110
+ try {
111
+ const data = await redisClient.sIsMember(key, action);
112
+ return data ? JSON.parse(data) : null;
113
+ } catch (error) {
114
+ logger.error(`Redis sIsMember error for key ${key}`, { error: error.message });
115
+ return null;
116
+ }
117
+ };
118
+
119
+ const sAdd = async (key, action) => {
120
+ try {
121
+ await redisClient.sAdd(key, action);
122
+ return true;
123
+ }
124
+ catch (error) {
125
+ logger.error(`Redis sAdd error for key ${key}`, { error: error.message });
126
+ return false;
127
+ }
128
+ }
129
+
130
+ const sMembers = async (key) => {
131
+ try {
132
+ await redisClient.sMembers(key);
133
+ return true;
134
+ }
135
+ catch (error) {
136
+ logger.error(`Redis sAdd error for key ${key}`, { error: error.message });
137
+ return false;
138
+ }
139
+ }
140
+
141
+ const set = async (key, value, ttlSeconds = null) => {
142
+ try {
143
+ const stringified = JSON.stringify(value);
144
+ if (ttlSeconds) {
145
+ await redisClient.set(key, stringified, { EX: ttlSeconds });
146
+ } else {
147
+ await redisClient.set(key, stringified);
148
+ }
149
+ return true;
150
+ } catch (error) {
151
+ logger.error(`Redis SET error for key ${key}`, { error: error.message });
152
+ return false;
153
+ }
154
+ };
155
+
156
+ const del = async (key) => {
157
+ try {
158
+ await redisClient.del(key);
159
+ return true;
160
+ } catch (error) {
161
+ logger.error(`Redis DEL error for key ${key}`, { error: error.message });
162
+ return false;
163
+ }
164
+ };
165
+
166
+ const exists = async (key) => {
167
+ try {
168
+ return (await redisClient.exists(key)) === 1;
169
+ } catch (error) {
170
+ logger.error(`Redis EXISTS error for key ${key}`, { error: error.message });
171
+ return false;
172
+ }
173
+ };
174
+
175
+ const type = async (key) => {
176
+ try {
177
+ return await redisClient.type(key);
178
+ }
179
+ catch (error) {
180
+ logger.error(`Redis type error for key ${key}`, { error: error.message });
181
+ return null;
182
+ }
183
+ }
184
+
185
+ const hSet = async (key, value) => {
186
+ try {
187
+ await redisClient.hSet(key, value);
188
+ return true;
189
+ }
190
+ catch (error) {
191
+ logger.error(`Redis hSet error for key ${key}`, { error: error.message });
192
+ return false;
193
+ }
194
+ }
195
+
196
+ const hGet = async (key, field) => {
197
+ try {
198
+ const data = await redisClient.hGet(key, field);
199
+ return data ? JSON.parse(data) : null;
200
+ } catch (error) {
201
+ logger.error(`Redis hGet error for key ${key}`, { error: error.message });
202
+ return null;
203
+ }
204
+ }
205
+
206
+ const hGetAll = async (key) => {
207
+ try {
208
+ const data = await redisClient.hGetAll(key);
209
+ return data ? JSON.parse(data) : null;
210
+ } catch (error) {
211
+ logger.error(`Redis hGet error for key ${key}`, { error: error.message });
212
+ return null;
213
+ }
214
+ }
215
+ // ==============================
216
+ // Redis Streams
217
+ // ==============================
218
+
219
+ /**
220
+ * Add message to Redis Stream
221
+ *
222
+ * @param {string} stream
223
+ * @param {object} data
224
+ * @returns {Promise<string>}
225
+ */
226
+
227
+ // ==============================
228
+ // Redis Streams
229
+ // ==============================
230
+
231
+ /**
232
+ * Add message to Redis Stream
233
+ *
234
+ * @param {string} stream
235
+ * @param {object} data
236
+ * @returns {Promise<string>}
237
+ */
238
+ const xAdd = async (stream, data) => {
239
+ try {
240
+ const fields = {};
241
+
242
+ for (const [key, value] of Object.entries(data)) {
243
+ fields[key] =
244
+ typeof value === 'string'
245
+ ? value
246
+ : JSON.stringify(value);
247
+ }
248
+
249
+ return await redisClient.xAdd(
250
+ stream,
251
+ '*',
252
+ fields
253
+ );
254
+ } catch (error) {
255
+ logger.error('Redis XADD error', {
256
+ stream,
257
+ error: error.message,
258
+ stack: error.stack,
259
+ });
260
+
261
+ throw error;
262
+ }
263
+ };
264
+
265
+
266
+ /**
267
+ * Create consumer group
268
+ *
269
+ * If group already exists, that's fine.
270
+ *
271
+ * @param {string} stream
272
+ * @param {string} group
273
+ * @param {string} startId
274
+ */
275
+ const xGroupCreate = async (
276
+ stream,
277
+ group,
278
+ startId = '0'
279
+ ) => {
280
+ try {
281
+ await redisClient.xGroupCreate(
282
+ stream,
283
+ group,
284
+ startId,
285
+ {
286
+ MKSTREAM: true,
287
+ }
288
+ );
289
+
290
+ return true;
291
+ } catch (error) {
292
+
293
+ if (
294
+ error?.message?.includes('BUSYGROUP')
295
+ ) {
296
+ return true;
297
+ }
298
+
299
+ logger.error('Redis XGROUP CREATE error', {
300
+ stream,
301
+ group,
302
+ error: error.message,
303
+ stack: error.stack,
304
+ });
305
+
306
+ throw error;
307
+ }
308
+ };
309
+
310
+
311
+ /**
312
+ * Read messages from consumer group
313
+ *
314
+ * `>` = only new messages that have never
315
+ * been delivered to this consumer group.
316
+ *
317
+ * @param {string} stream
318
+ * @param {string} group
319
+ * @param {string} consumer
320
+ * @param {number} count
321
+ * @param {number} block
322
+ */
323
+ const xReadGroup = async (...args) => {
324
+ try {
325
+ let stream;
326
+ let group;
327
+ let consumer;
328
+ let count;
329
+ let block;
330
+
331
+ // OLD Redis command syntax:
332
+ // XREADGROUP GROUP group consumer COUNT count BLOCK block STREAMS stream >
333
+ if (args[0] === 'GROUP') {
334
+ group = args[1];
335
+ consumer = args[2];
336
+
337
+ const countIndex = args.indexOf('COUNT');
338
+ const blockIndex = args.indexOf('BLOCK');
339
+ const streamsIndex = args.indexOf('STREAMS');
340
+
341
+ count = Number(args[countIndex + 1]);
342
+ block = Number(args[blockIndex + 1]);
343
+ stream = args[streamsIndex + 1];
344
+
345
+ } else {
346
+ // New/internal syntax:
347
+ // stream, group, consumer, count, block
348
+ [
349
+ stream,
350
+ group,
351
+ consumer,
352
+ count,
353
+ block
354
+ ] = args;
355
+
356
+ count = Number(count);
357
+ block = Number(block);
358
+ }
359
+
360
+ if (!stream) {
361
+ throw new TypeError('Redis XREADGROUP: stream is required');
362
+ }
363
+
364
+ if (!group) {
365
+ throw new TypeError('Redis XREADGROUP: group is required');
366
+ }
367
+
368
+ if (!consumer) {
369
+ throw new TypeError('Redis XREADGROUP: consumer is required');
370
+ }
371
+
372
+ if (!Number.isInteger(count) || count <= 0) {
373
+ throw new TypeError(`Invalid count: ${count}`);
374
+ }
375
+
376
+ if (!Number.isInteger(block) || block < 0) {
377
+ throw new TypeError(`Invalid block: ${block}`);
378
+ }
379
+
380
+ return await redisClient.xReadGroup(
381
+ group,
382
+ consumer,
383
+ [
384
+ {
385
+ key: stream,
386
+ id: '>',
387
+ },
388
+ ],
389
+ {
390
+ COUNT: count,
391
+ BLOCK: block,
392
+ }
393
+ );
394
+
395
+ } catch (error) {
396
+ logger.error('Redis XREADGROUP error', {
397
+ args,
398
+ error: error.message,
399
+ stack: error.stack,
400
+ });
401
+
402
+ throw error;
403
+ }
404
+ };
405
+
406
+
407
+ /**
408
+ * Acknowledge messages
409
+ *
410
+ * @param {string} stream
411
+ * @param {string} group
412
+ * @param {string|string[]} ids
413
+ */
414
+ const xAck = async (stream, group, ...ids) => {
415
+ try {
416
+ const messageIds = ids.flat();
417
+
418
+ if (messageIds.length === 0) {
419
+ return 0;
420
+ }
421
+
422
+ return await redisClient.xAck(
423
+ stream,
424
+ group,
425
+ messageIds
426
+ );
427
+
428
+ } catch (error) {
429
+ logger.error('Redis XACK error', {
430
+ stream,
431
+ group,
432
+ ids,
433
+ error: error.message,
434
+ stack: error.stack,
435
+ });
436
+
437
+ throw error;
438
+ }
439
+ };
440
+
441
+ /**
442
+ * Get pending summary
443
+ *
444
+ * IMPORTANT:
445
+ * This does NOT return an array of messages.
446
+ *
447
+ * Example:
448
+ *
449
+ * {
450
+ * pending: 5,
451
+ * firstId: '123-0',
452
+ * lastId: '456-0',
453
+ * consumers: [...]
454
+ * }
455
+ */
456
+ const xPending = async (...args) => {
457
+ try {
458
+ let stream;
459
+ let group;
460
+ let minIdleTime;
461
+ let start = '-';
462
+ let end = '+';
463
+ let count = 10;
464
+
465
+ // Backward-compatible old syntax:
466
+ //
467
+ // xPending(
468
+ // stream,
469
+ // group,
470
+ // 'IDLE',
471
+ // minIdleTime,
472
+ // '-',
473
+ // '+',
474
+ // count
475
+ // )
476
+ if (args[2] === 'IDLE') {
477
+ stream = args[0];
478
+ group = args[1];
479
+ minIdleTime = Number(args[3]);
480
+ start = args[4] || '-';
481
+ end = args[5] || '+';
482
+ count = Number(args[6] ?? 10);
483
+ } else {
484
+ // New syntax:
485
+ //
486
+ // xPending(
487
+ // stream,
488
+ // group,
489
+ // minIdleTime,
490
+ // start,
491
+ // end,
492
+ // count
493
+ // )
494
+ stream = args[0];
495
+ group = args[1];
496
+ minIdleTime = Number(args[2]);
497
+ start = args[3] || '-';
498
+ end = args[4] || '+';
499
+ count = Number(args[5] ?? 10);
500
+ }
501
+
502
+ if (!stream) {
503
+ throw new TypeError('Redis XPENDING: stream is required');
504
+ }
505
+
506
+ if (!group) {
507
+ throw new TypeError('Redis XPENDING: group is required');
508
+ }
509
+
510
+ if (!Number.isInteger(minIdleTime) || minIdleTime < 0) {
511
+ throw new TypeError(`Invalid minIdleTime: ${minIdleTime}`);
512
+ }
513
+
514
+ if (!Number.isInteger(count) || count <= 0) {
515
+ throw new TypeError(`Invalid count: ${count}`);
516
+ }
517
+
518
+ const result = await redisClient.xPending(
519
+ stream,
520
+ group,
521
+ {
522
+ IDLE: minIdleTime,
523
+ START: start,
524
+ END: end,
525
+ COUNT: count,
526
+ }
527
+ );
528
+
529
+ /*
530
+ * node-redis may return:
531
+ *
532
+ * [
533
+ * {
534
+ * id: '123-0',
535
+ * owner: 'consumer-1',
536
+ * millisecondsSinceLastDelivery: 40000,
537
+ * deliveriesCounter: 1
538
+ * }
539
+ * ]
540
+ *
541
+ * Normalize it to an array for existing consumers.
542
+ */
543
+
544
+ if (Array.isArray(result)) {
545
+ return result;
546
+ }
547
+
548
+ // Some node-redis versions / command overloads can return
549
+ // an object-shaped result. Normalize it.
550
+ if (result && typeof result === 'object') {
551
+ if (Array.isArray(result.entries)) {
552
+ return result.entries;
553
+ }
554
+
555
+ if (Array.isArray(result.pending)) {
556
+ return result.pending;
557
+ }
558
+
559
+ // If it's a single pending entry, normalize it.
560
+ if (
561
+ result.id &&
562
+ (
563
+ result.owner ||
564
+ result.consumer
565
+ )
566
+ ) {
567
+ return [result];
568
+ }
569
+ }
570
+
571
+ // Nothing pending.
572
+ return [];
573
+
574
+ } catch (error) {
575
+ logger.error('Redis XPENDING RANGE error', {
576
+ args,
577
+ error: error.message,
578
+ stack: error.stack,
579
+ });
580
+
581
+ throw error;
582
+ }
583
+ };
584
+
585
+ /**
586
+ * Reclaim stale pending messages
587
+ *
588
+ * XAUTOCLAIM returns:
589
+ *
590
+ * [
591
+ * nextStartId,
592
+ * messages,
593
+ * deletedMessageIds
594
+ * ]
595
+ *
596
+ * depending on node-redis version.
597
+ */
598
+ const xAutoClaim = async (
599
+ stream,
600
+ group,
601
+ consumer,
602
+ minIdleTime = 30000,
603
+ startId = '0-0',
604
+ count = 10
605
+ ) => {
606
+ try {
607
+ minIdleTime = Number(minIdleTime);
608
+ count = Number(count);
609
+
610
+ if (!Number.isInteger(minIdleTime) || minIdleTime < 0) {
611
+ throw new TypeError(
612
+ `Invalid minIdleTime: ${minIdleTime}`
613
+ );
614
+ }
615
+
616
+ if (!Number.isInteger(count) || count <= 0) {
617
+ throw new TypeError(
618
+ `Invalid count: ${count}`
619
+ );
620
+ }
621
+
622
+ return await redisClient.xAutoClaim(
623
+ stream,
624
+ group,
625
+ consumer,
626
+ minIdleTime,
627
+ startId,
628
+ {
629
+ COUNT: count,
630
+ }
631
+ );
632
+
633
+ } catch (error) {
634
+ logger.error('Redis XAUTOCLAIM error', {
635
+ stream,
636
+ group,
637
+ consumer,
638
+ minIdleTime,
639
+ startId,
640
+ count,
641
+ error: error.message,
642
+ stack: error.stack,
643
+ });
644
+
645
+ throw error;
646
+ }
647
+ };
648
+
649
+
650
+ /**
651
+ * Delete messages from stream
652
+ *
653
+ * Usually DON'T call this after XACK.
654
+ */
655
+ const xDel = async (
656
+ stream,
657
+ ids
658
+ ) => {
659
+ try {
660
+
661
+ if (!ids) {
662
+ return 0;
663
+ }
664
+
665
+ const messageIds = Array.isArray(ids)
666
+ ? ids
667
+ : [ids];
668
+
669
+ if (messageIds.length === 0) {
670
+ return 0;
671
+ }
672
+
673
+ return await redisClient.xDel(
674
+ stream,
675
+ messageIds
676
+ );
677
+
678
+ } catch (error) {
679
+
680
+ logger.error('Redis XDEL error', {
681
+ stream,
682
+ ids,
683
+ error: error.message,
684
+ stack: error.stack,
685
+ });
686
+
687
+ throw error;
688
+ }
689
+ };
690
+
691
+
692
+ /**
693
+ * Trim stream
694
+ */
695
+ const xTrim = async (
696
+ stream,
697
+ maxLen
698
+ ) => {
699
+ try {
700
+
701
+ maxLen = Number(maxLen);
702
+
703
+ if (!Number.isInteger(maxLen) || maxLen <= 0) {
704
+ throw new TypeError(
705
+ `Invalid maxLen: ${maxLen}`
706
+ );
707
+ }
708
+
709
+ return await redisClient.xTrim(
710
+ stream,
711
+ 'MAXLEN',
712
+ maxLen,
713
+ {
714
+ strategyModifier: '~',
715
+ }
716
+ );
717
+
718
+ } catch (error) {
719
+
720
+ logger.error('Redis XTRIM error', {
721
+ stream,
722
+ maxLen,
723
+ error: error.message,
724
+ stack: error.stack,
725
+ });
726
+
727
+ throw error;
728
+ }
729
+ };
730
+
731
+
732
+ const xReadGroupNew = async (...args) => {
733
+ try {
734
+ let stream;
735
+ let group;
736
+ let consumer;
737
+ let count;
738
+ let block;
739
+
740
+ // Existing caller format:
741
+ //
742
+ // GROUP group consumer
743
+ // COUNT count
744
+ // BLOCK block
745
+ // STREAMS stream >
746
+ if (args[0] === 'GROUP') {
747
+ group = args[1];
748
+ consumer = args[2];
749
+
750
+ const countIndex = args.indexOf('COUNT');
751
+ const blockIndex = args.indexOf('BLOCK');
752
+ const streamsIndex = args.indexOf('STREAMS');
753
+
754
+ count = Number(args[countIndex + 1]);
755
+ block = Number(args[blockIndex + 1]);
756
+ stream = args[streamsIndex + 1];
757
+ } else {
758
+ stream = args[0];
759
+ group = args[1];
760
+ consumer = args[2];
761
+ count = Number(args[3]);
762
+ block = Number(args[4]);
763
+ }
764
+
765
+ if (!stream) {
766
+ throw new TypeError('XREADGROUP: stream is required');
767
+ }
768
+
769
+ if (!group) {
770
+ throw new TypeError('XREADGROUP: group is required');
771
+ }
772
+
773
+ if (!consumer) {
774
+ throw new TypeError('XREADGROUP: consumer is required');
775
+ }
776
+
777
+ if (!Number.isInteger(count) || count <= 0) {
778
+ throw new TypeError(`Invalid count: ${count}`);
779
+ }
780
+
781
+ if (!Number.isInteger(block) || block < 0) {
782
+ throw new TypeError(`Invalid block: ${block}`);
783
+ }
784
+
785
+ const result = await redisClient.xReadGroup(
786
+ group,
787
+ consumer,
788
+ [
789
+ {
790
+ key: stream,
791
+ id: '>',
792
+ },
793
+ ],
794
+ {
795
+ COUNT: count,
796
+ BLOCK: block,
797
+ }
798
+ );
799
+
800
+ if (!result || result.length === 0) {
801
+ return null;
802
+ }
803
+
804
+ /*
805
+ * node-redis:
806
+ *
807
+ * [
808
+ * {
809
+ * name: 'activity-log',
810
+ * messages: [
811
+ * {
812
+ * id: '123-0',
813
+ * message: {
814
+ * payload: '...',
815
+ * event_id: '...'
816
+ * }
817
+ * }
818
+ * ]
819
+ * }
820
+ * ]
821
+ *
822
+ * Existing consumer expects:
823
+ *
824
+ * [
825
+ * [
826
+ * 'activity-log',
827
+ * [
828
+ * [
829
+ * '123-0',
830
+ * ['payload', '...']
831
+ * ]
832
+ * ]
833
+ * ]
834
+ * ]
835
+ */
836
+
837
+ return result.map((streamResult) => {
838
+ const entries = (streamResult.messages || []).map((message) => {
839
+ const fields = [];
840
+
841
+ for (const [key, value] of Object.entries(message.message || {})) {
842
+ fields.push(key);
843
+ fields.push(value);
844
+ }
845
+
846
+ return [
847
+ message.id,
848
+ fields,
849
+ ];
850
+ });
851
+
852
+ return [
853
+ streamResult.name,
854
+ entries,
855
+ ];
856
+ });
857
+
858
+ } catch (error) {
859
+ logger.error('Redis XREADGROUP error', {
860
+ args,
861
+ error: error.message,
862
+ stack: error.stack,
863
+ });
864
+
865
+ throw error;
866
+ }
867
+ };
868
+
869
+ module.exports = {
870
+ redisClient,
871
+ get,
872
+ set,
873
+ del,
874
+ exists,
875
+ sIsMember,
876
+ sAdd,
877
+ sMembers,
878
+ type,
879
+ hSet,
880
+ hGet,
881
+ hGetAll,
882
+
883
+ // Redis Streams
884
+ xAdd,
885
+ xGroupCreate,
886
+ xReadGroup,
887
+ xAck,
888
+ xPending,
889
+ xAutoClaim,
890
+ xDel,
891
+ xTrim,
892
+ xReadGroupNew
893
+ };
package/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@jusbid/redis",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "private": false,
6
+ "dependencies": {
7
+ "redis": "^4.6.13",
8
+ "@jusbid/logger": "1.0.0"
9
+ }
10
+ }