@naman_deep_singh/cache 1.1.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.
- package/README.md +637 -0
- package/dist/cjs/adapters/memcache/MemcacheCache.d.ts +71 -0
- package/dist/cjs/adapters/memcache/MemcacheCache.js +347 -0
- package/dist/cjs/adapters/memcache/index.d.ts +1 -0
- package/dist/cjs/adapters/memcache/index.js +5 -0
- package/dist/cjs/adapters/memory/MemoryCache.d.ts +63 -0
- package/dist/cjs/adapters/memory/MemoryCache.js +273 -0
- package/dist/cjs/adapters/memory/index.d.ts +1 -0
- package/dist/cjs/adapters/memory/index.js +5 -0
- package/dist/cjs/adapters/redis/RedisCache.d.ts +67 -0
- package/dist/cjs/adapters/redis/RedisCache.js +288 -0
- package/dist/cjs/adapters/redis/index.d.ts +1 -0
- package/dist/cjs/adapters/redis/index.js +5 -0
- package/dist/cjs/core/BaseCache.d.ts +78 -0
- package/dist/cjs/core/BaseCache.js +138 -0
- package/dist/cjs/core/factory.d.ts +16 -0
- package/dist/cjs/core/factory.js +51 -0
- package/dist/cjs/core/interfaces/ICache.d.ts +58 -0
- package/dist/cjs/core/interfaces/ICache.js +2 -0
- package/dist/cjs/core/interfaces/ISession.d.ts +30 -0
- package/dist/cjs/core/interfaces/ISession.js +2 -0
- package/dist/cjs/core/interfaces/index.d.ts +2 -0
- package/dist/cjs/core/interfaces/index.js +2 -0
- package/dist/cjs/errors/CacheError.d.ts +9 -0
- package/dist/cjs/errors/CacheError.js +17 -0
- package/dist/cjs/errors/index.d.ts +1 -0
- package/dist/cjs/errors/index.js +5 -0
- package/dist/cjs/index.d.ts +10 -0
- package/dist/cjs/index.js +27 -0
- package/dist/cjs/middleware/express/cacheMiddleware.d.ts +22 -0
- package/dist/cjs/middleware/express/cacheMiddleware.js +100 -0
- package/dist/cjs/middleware/express/index.d.ts +1 -0
- package/dist/cjs/middleware/express/index.js +7 -0
- package/dist/cjs/session/SessionStore.d.ts +51 -0
- package/dist/cjs/session/SessionStore.js +153 -0
- package/dist/cjs/session/index.d.ts +2 -0
- package/dist/cjs/session/index.js +5 -0
- package/dist/cjs/types.d.ts +83 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/adapters/memcache/MemcacheCache.d.ts +71 -0
- package/dist/esm/adapters/memcache/MemcacheCache.js +340 -0
- package/dist/esm/adapters/memcache/index.d.ts +1 -0
- package/dist/esm/adapters/memcache/index.js +1 -0
- package/dist/esm/adapters/memory/MemoryCache.d.ts +63 -0
- package/dist/esm/adapters/memory/MemoryCache.js +269 -0
- package/dist/esm/adapters/memory/index.d.ts +1 -0
- package/dist/esm/adapters/memory/index.js +1 -0
- package/dist/esm/adapters/redis/RedisCache.d.ts +67 -0
- package/dist/esm/adapters/redis/RedisCache.js +284 -0
- package/dist/esm/adapters/redis/index.d.ts +1 -0
- package/dist/esm/adapters/redis/index.js +1 -0
- package/dist/esm/core/BaseCache.d.ts +78 -0
- package/dist/esm/core/BaseCache.js +134 -0
- package/dist/esm/core/factory.d.ts +16 -0
- package/dist/esm/core/factory.js +47 -0
- package/dist/esm/core/interfaces/ICache.d.ts +58 -0
- package/dist/esm/core/interfaces/ICache.js +1 -0
- package/dist/esm/core/interfaces/ISession.d.ts +30 -0
- package/dist/esm/core/interfaces/ISession.js +1 -0
- package/dist/esm/core/interfaces/index.d.ts +2 -0
- package/dist/esm/core/interfaces/index.js +1 -0
- package/dist/esm/errors/CacheError.d.ts +9 -0
- package/dist/esm/errors/CacheError.js +13 -0
- package/dist/esm/errors/index.d.ts +1 -0
- package/dist/esm/errors/index.js +1 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/middleware/express/cacheMiddleware.d.ts +22 -0
- package/dist/esm/middleware/express/cacheMiddleware.js +95 -0
- package/dist/esm/middleware/express/index.d.ts +1 -0
- package/dist/esm/middleware/express/index.js +1 -0
- package/dist/esm/session/SessionStore.d.ts +51 -0
- package/dist/esm/session/SessionStore.js +149 -0
- package/dist/esm/session/index.d.ts +2 -0
- package/dist/esm/session/index.js +1 -0
- package/dist/esm/types.d.ts +83 -0
- package/dist/esm/types.js +1 -0
- package/dist/types/adapters/memcache/MemcacheCache.d.ts +71 -0
- package/dist/types/adapters/memcache/index.d.ts +1 -0
- package/dist/types/adapters/memory/MemoryCache.d.ts +63 -0
- package/dist/types/adapters/memory/index.d.ts +1 -0
- package/dist/types/adapters/redis/RedisCache.d.ts +67 -0
- package/dist/types/adapters/redis/index.d.ts +1 -0
- package/dist/types/core/BaseCache.d.ts +78 -0
- package/dist/types/core/factory.d.ts +16 -0
- package/dist/types/core/interfaces/ICache.d.ts +58 -0
- package/dist/types/core/interfaces/ISession.d.ts +30 -0
- package/dist/types/core/interfaces/index.d.ts +2 -0
- package/dist/types/errors/CacheError.d.ts +9 -0
- package/dist/types/errors/index.d.ts +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/middleware/express/cacheMiddleware.d.ts +22 -0
- package/dist/types/middleware/express/index.d.ts +1 -0
- package/dist/types/session/SessionStore.d.ts +51 -0
- package/dist/types/session/index.d.ts +2 -0
- package/dist/types/types.d.ts +83 -0
- package/package.json +48 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { BaseCache } from '../../core/BaseCache';
|
|
2
|
+
import { CacheError } from '../../errors';
|
|
3
|
+
/**
|
|
4
|
+
* In-memory cache adapter for development and testing
|
|
5
|
+
*/
|
|
6
|
+
export class MemoryCache extends BaseCache {
|
|
7
|
+
constructor(memoryCacheConfig) {
|
|
8
|
+
super(memoryCacheConfig);
|
|
9
|
+
this.memoryCacheConfig = memoryCacheConfig;
|
|
10
|
+
this.store = new Map();
|
|
11
|
+
this.cleanupInterval = null;
|
|
12
|
+
this.startCleanup();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Start periodic cleanup of expired items
|
|
16
|
+
*/
|
|
17
|
+
startCleanup() {
|
|
18
|
+
if (this.cleanupInterval)
|
|
19
|
+
return;
|
|
20
|
+
this.cleanupInterval = setInterval(() => {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
for (const [key, entry] of this.store.entries()) {
|
|
23
|
+
if (entry.expiresAt && entry.expiresAt < now) {
|
|
24
|
+
this.store.delete(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}, 30000); // Cleanup every 30 seconds
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get a value from memory
|
|
31
|
+
*/
|
|
32
|
+
async get(key) {
|
|
33
|
+
try {
|
|
34
|
+
const fullKey = this.buildKey(key);
|
|
35
|
+
const entry = this.store.get(fullKey);
|
|
36
|
+
if (!entry) {
|
|
37
|
+
this.recordMiss();
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
// Check if expired
|
|
41
|
+
if (entry.expiresAt && entry.expiresAt < Date.now()) {
|
|
42
|
+
this.store.delete(fullKey);
|
|
43
|
+
this.recordMiss();
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
this.recordHit();
|
|
47
|
+
return entry.value;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
throw new CacheError(`Failed to get key "${key}" from memory cache`, 'MEMORY_GET_ERROR', 'memory', err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Set a value in memory
|
|
55
|
+
*/
|
|
56
|
+
async set(key, value, ttl) {
|
|
57
|
+
try {
|
|
58
|
+
const fullKey = this.buildKey(key);
|
|
59
|
+
const expiry = ttl ?? this.ttl;
|
|
60
|
+
const expiresAt = expiry > 0 ? Date.now() + expiry * 1000 : undefined;
|
|
61
|
+
// Check max size
|
|
62
|
+
const maxSize = this.memoryCacheConfig.maxSize;
|
|
63
|
+
if (maxSize && this.store.size >= maxSize && !this.store.has(fullKey)) {
|
|
64
|
+
// Remove oldest entry
|
|
65
|
+
const firstKey = this.store.keys().next().value;
|
|
66
|
+
if (firstKey) {
|
|
67
|
+
this.store.delete(firstKey);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.store.set(fullKey, { value, expiresAt });
|
|
71
|
+
this.recordSet();
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
throw new CacheError(`Failed to set key "${key}" in memory cache`, 'MEMORY_SET_ERROR', 'memory', err);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Delete a key from memory
|
|
79
|
+
*/
|
|
80
|
+
async delete(key) {
|
|
81
|
+
try {
|
|
82
|
+
const fullKey = this.buildKey(key);
|
|
83
|
+
const deleted = this.store.delete(fullKey);
|
|
84
|
+
if (deleted) {
|
|
85
|
+
this.recordDelete();
|
|
86
|
+
}
|
|
87
|
+
return deleted;
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
throw new CacheError(`Failed to delete key "${key}" from memory cache`, 'MEMORY_DELETE_ERROR', 'memory', err);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if key exists
|
|
95
|
+
*/
|
|
96
|
+
async exists(key) {
|
|
97
|
+
try {
|
|
98
|
+
const fullKey = this.buildKey(key);
|
|
99
|
+
const entry = this.store.get(fullKey);
|
|
100
|
+
if (!entry) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
// Check if expired
|
|
104
|
+
if (entry.expiresAt && entry.expiresAt < Date.now()) {
|
|
105
|
+
this.store.delete(fullKey);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
throw new CacheError(`Failed to check existence of key "${key}" in memory cache`, 'MEMORY_EXISTS_ERROR', 'memory', err);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Clear all keys with current namespace
|
|
116
|
+
*/
|
|
117
|
+
async clear() {
|
|
118
|
+
try {
|
|
119
|
+
if (this.namespace) {
|
|
120
|
+
// Clear only keys with current namespace
|
|
121
|
+
for (const key of this.store.keys()) {
|
|
122
|
+
if (key.startsWith(this.namespace)) {
|
|
123
|
+
this.store.delete(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Clear all
|
|
129
|
+
this.store.clear();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
throw new CacheError('Failed to clear memory cache', 'MEMORY_CLEAR_ERROR', 'memory', err);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get multiple values at once
|
|
138
|
+
*/
|
|
139
|
+
async getMultiple(keys) {
|
|
140
|
+
try {
|
|
141
|
+
const result = {};
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
for (const key of keys) {
|
|
144
|
+
const fullKey = this.buildKey(key);
|
|
145
|
+
const entry = this.store.get(fullKey);
|
|
146
|
+
if (!entry) {
|
|
147
|
+
this.recordMiss();
|
|
148
|
+
result[key] = null;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// Check if expired
|
|
152
|
+
if (entry.expiresAt && entry.expiresAt < now) {
|
|
153
|
+
this.store.delete(fullKey);
|
|
154
|
+
this.recordMiss();
|
|
155
|
+
result[key] = null;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.recordHit();
|
|
159
|
+
result[key] = entry.value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
throw new CacheError('Failed to get multiple keys from memory cache', 'MEMORY_GET_MULTIPLE_ERROR', 'memory', err);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Set multiple values at once
|
|
170
|
+
*/
|
|
171
|
+
async setMultiple(data, ttl) {
|
|
172
|
+
try {
|
|
173
|
+
const expiry = ttl ?? this.ttl;
|
|
174
|
+
const expiresAt = expiry > 0 ? Date.now() + expiry * 1000 : undefined;
|
|
175
|
+
for (const [key, value] of Object.entries(data)) {
|
|
176
|
+
const fullKey = this.buildKey(key);
|
|
177
|
+
this.store.set(fullKey, { value, expiresAt });
|
|
178
|
+
}
|
|
179
|
+
this.stats.sets += Object.keys(data).length;
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
throw new CacheError('Failed to set multiple keys in memory cache', 'MEMORY_SET_MULTIPLE_ERROR', 'memory', err);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Delete multiple keys at once
|
|
187
|
+
*/
|
|
188
|
+
async deleteMultiple(keys) {
|
|
189
|
+
try {
|
|
190
|
+
let count = 0;
|
|
191
|
+
for (const key of keys) {
|
|
192
|
+
const fullKey = this.buildKey(key);
|
|
193
|
+
if (this.store.delete(fullKey)) {
|
|
194
|
+
count++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
this.stats.deletes += count;
|
|
198
|
+
return count;
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
throw new CacheError('Failed to delete multiple keys from memory cache', 'MEMORY_DELETE_MULTIPLE_ERROR', 'memory', err);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Increment a numeric value
|
|
206
|
+
*/
|
|
207
|
+
async increment(key, amount = 1) {
|
|
208
|
+
try {
|
|
209
|
+
const fullKey = this.buildKey(key);
|
|
210
|
+
const entry = this.store.get(fullKey);
|
|
211
|
+
const current = entry && (!entry.expiresAt || entry.expiresAt >= Date.now())
|
|
212
|
+
? (typeof entry.value === 'number' ? entry.value : 0)
|
|
213
|
+
: 0;
|
|
214
|
+
const value = current + amount;
|
|
215
|
+
const expiry = this.ttl;
|
|
216
|
+
const expiresAt = expiry > 0 ? Date.now() + expiry * 1000 : undefined;
|
|
217
|
+
this.store.set(fullKey, { value: value, expiresAt });
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
throw new CacheError(`Failed to increment key "${key}" in memory cache`, 'MEMORY_INCREMENT_ERROR', 'memory', err);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Decrement a numeric value
|
|
226
|
+
*/
|
|
227
|
+
async decrement(key, amount = 1) {
|
|
228
|
+
try {
|
|
229
|
+
const fullKey = this.buildKey(key);
|
|
230
|
+
const entry = this.store.get(fullKey);
|
|
231
|
+
const current = entry && (!entry.expiresAt || entry.expiresAt >= Date.now())
|
|
232
|
+
? (typeof entry.value === 'number' ? entry.value : 0)
|
|
233
|
+
: 0;
|
|
234
|
+
const value = current - amount;
|
|
235
|
+
const expiry = this.ttl;
|
|
236
|
+
const expiresAt = expiry > 0 ? Date.now() + expiry * 1000 : undefined;
|
|
237
|
+
this.store.set(fullKey, { value: value, expiresAt });
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
throw new CacheError(`Failed to decrement key "${key}" in memory cache`, 'MEMORY_DECREMENT_ERROR', 'memory', err);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Check if memory cache is alive
|
|
246
|
+
*/
|
|
247
|
+
async isAlive() {
|
|
248
|
+
return {
|
|
249
|
+
isAlive: true,
|
|
250
|
+
adapter: 'memory',
|
|
251
|
+
timestamp: new Date()
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Close memory cache
|
|
256
|
+
*/
|
|
257
|
+
async close() {
|
|
258
|
+
try {
|
|
259
|
+
if (this.cleanupInterval) {
|
|
260
|
+
clearInterval(this.cleanupInterval);
|
|
261
|
+
this.cleanupInterval = null;
|
|
262
|
+
}
|
|
263
|
+
this.store.clear();
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
throw new CacheError('Failed to close memory cache', 'MEMORY_CLOSE_ERROR', 'memory', err);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MemoryCache } from './MemoryCache';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MemoryCache } from './MemoryCache';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { RedisCacheConfig, HealthCheckResponse } from '../../types';
|
|
2
|
+
import { BaseCache } from '../../core/BaseCache';
|
|
3
|
+
/**
|
|
4
|
+
* Redis cache adapter
|
|
5
|
+
*/
|
|
6
|
+
export declare class RedisCache<T = unknown> extends BaseCache<T> {
|
|
7
|
+
private redisConfig;
|
|
8
|
+
private client;
|
|
9
|
+
private isConnected;
|
|
10
|
+
constructor(redisConfig: RedisCacheConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Connect to Redis
|
|
13
|
+
*/
|
|
14
|
+
connect(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Ensure client is connected
|
|
17
|
+
*/
|
|
18
|
+
private ensureConnected;
|
|
19
|
+
/**
|
|
20
|
+
* Get a value from Redis
|
|
21
|
+
*/
|
|
22
|
+
get(key: string): Promise<T | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Set a value in Redis
|
|
25
|
+
*/
|
|
26
|
+
set(key: string, value: T, ttl?: number): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Delete a key from Redis
|
|
29
|
+
*/
|
|
30
|
+
delete(key: string): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if key exists
|
|
33
|
+
*/
|
|
34
|
+
exists(key: string): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Clear all keys with current namespace
|
|
37
|
+
*/
|
|
38
|
+
clear(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Get multiple values at once
|
|
41
|
+
*/
|
|
42
|
+
getMultiple(keys: string[]): Promise<Record<string, T | null>>;
|
|
43
|
+
/**
|
|
44
|
+
* Set multiple values at once
|
|
45
|
+
*/
|
|
46
|
+
setMultiple(data: Record<string, T>, ttl?: number): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Delete multiple keys at once
|
|
49
|
+
*/
|
|
50
|
+
deleteMultiple(keys: string[]): Promise<number>;
|
|
51
|
+
/**
|
|
52
|
+
* Increment a numeric value
|
|
53
|
+
*/
|
|
54
|
+
increment(key: string, amount?: number): Promise<number>;
|
|
55
|
+
/**
|
|
56
|
+
* Decrement a numeric value
|
|
57
|
+
*/
|
|
58
|
+
decrement(key: string, amount?: number): Promise<number>;
|
|
59
|
+
/**
|
|
60
|
+
* Check if Redis is alive
|
|
61
|
+
*/
|
|
62
|
+
isAlive(): Promise<HealthCheckResponse>;
|
|
63
|
+
/**
|
|
64
|
+
* Close Redis connection
|
|
65
|
+
*/
|
|
66
|
+
close(): Promise<void>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { createClient } from 'redis';
|
|
2
|
+
import { BaseCache } from '../../core/BaseCache';
|
|
3
|
+
import { CacheError } from '../../errors';
|
|
4
|
+
/**
|
|
5
|
+
* Redis cache adapter
|
|
6
|
+
*/
|
|
7
|
+
export class RedisCache extends BaseCache {
|
|
8
|
+
constructor(redisConfig) {
|
|
9
|
+
super(redisConfig);
|
|
10
|
+
this.redisConfig = redisConfig;
|
|
11
|
+
this.client = null;
|
|
12
|
+
this.isConnected = false;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Connect to Redis
|
|
16
|
+
*/
|
|
17
|
+
async connect() {
|
|
18
|
+
try {
|
|
19
|
+
const options = {
|
|
20
|
+
host: this.redisConfig.host ?? 'localhost',
|
|
21
|
+
port: this.redisConfig.port ?? 6379,
|
|
22
|
+
db: this.redisConfig.db ?? 0
|
|
23
|
+
};
|
|
24
|
+
if (this.redisConfig.username) {
|
|
25
|
+
options.username = this.redisConfig.username;
|
|
26
|
+
}
|
|
27
|
+
if (this.redisConfig.password) {
|
|
28
|
+
options.password = this.redisConfig.password;
|
|
29
|
+
}
|
|
30
|
+
if (this.redisConfig.tls) {
|
|
31
|
+
options.tls = true;
|
|
32
|
+
}
|
|
33
|
+
this.client = createClient(options);
|
|
34
|
+
this.client.on('error', (err) => {
|
|
35
|
+
this.isConnected = false;
|
|
36
|
+
console.error('Redis connection error:', err);
|
|
37
|
+
});
|
|
38
|
+
this.client.on('connect', () => {
|
|
39
|
+
this.isConnected = true;
|
|
40
|
+
});
|
|
41
|
+
await this.client.connect();
|
|
42
|
+
this.isConnected = true;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw new CacheError('Failed to connect to Redis', 'REDIS_CONNECTION_ERROR', 'redis', err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Ensure client is connected
|
|
50
|
+
*/
|
|
51
|
+
async ensureConnected() {
|
|
52
|
+
if (!this.client) {
|
|
53
|
+
await this.connect();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get a value from Redis
|
|
58
|
+
*/
|
|
59
|
+
async get(key) {
|
|
60
|
+
try {
|
|
61
|
+
await this.ensureConnected();
|
|
62
|
+
const fullKey = this.buildKey(key);
|
|
63
|
+
const value = await this.client.get(fullKey);
|
|
64
|
+
if (value === null) {
|
|
65
|
+
this.recordMiss();
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
this.recordHit();
|
|
69
|
+
return this.deserialize(value);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
throw new CacheError(`Failed to get key "${key}" from Redis`, 'REDIS_GET_ERROR', 'redis', err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Set a value in Redis
|
|
77
|
+
*/
|
|
78
|
+
async set(key, value, ttl) {
|
|
79
|
+
try {
|
|
80
|
+
await this.ensureConnected();
|
|
81
|
+
const fullKey = this.buildKey(key);
|
|
82
|
+
const serialized = this.serialize(value);
|
|
83
|
+
const expiry = ttl ?? this.ttl;
|
|
84
|
+
if (expiry > 0) {
|
|
85
|
+
await this.client.setEx(fullKey, expiry, serialized);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
await this.client.set(fullKey, serialized);
|
|
89
|
+
}
|
|
90
|
+
this.recordSet();
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
throw new CacheError(`Failed to set key "${key}" in Redis`, 'REDIS_SET_ERROR', 'redis', err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Delete a key from Redis
|
|
98
|
+
*/
|
|
99
|
+
async delete(key) {
|
|
100
|
+
try {
|
|
101
|
+
await this.ensureConnected();
|
|
102
|
+
const fullKey = this.buildKey(key);
|
|
103
|
+
const result = await this.client.del(fullKey);
|
|
104
|
+
this.recordDelete();
|
|
105
|
+
return result > 0;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
throw new CacheError(`Failed to delete key "${key}" from Redis`, 'REDIS_DELETE_ERROR', 'redis', err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if key exists
|
|
113
|
+
*/
|
|
114
|
+
async exists(key) {
|
|
115
|
+
try {
|
|
116
|
+
await this.ensureConnected();
|
|
117
|
+
const fullKey = this.buildKey(key);
|
|
118
|
+
const result = await this.client.exists(fullKey);
|
|
119
|
+
return result > 0;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
throw new CacheError(`Failed to check existence of key "${key}" in Redis`, 'REDIS_EXISTS_ERROR', 'redis', err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Clear all keys with current namespace
|
|
127
|
+
*/
|
|
128
|
+
async clear() {
|
|
129
|
+
try {
|
|
130
|
+
await this.ensureConnected();
|
|
131
|
+
if (this.namespace) {
|
|
132
|
+
// Clear only keys with the current namespace
|
|
133
|
+
const pattern = `${this.namespace}*`;
|
|
134
|
+
const keys = await this.client.keys(pattern);
|
|
135
|
+
if (keys.length > 0) {
|
|
136
|
+
await this.client.del(keys);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Clear all keys
|
|
141
|
+
await this.client.flushDb();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
throw new CacheError('Failed to clear Redis cache', 'REDIS_CLEAR_ERROR', 'redis', err);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get multiple values at once
|
|
150
|
+
*/
|
|
151
|
+
async getMultiple(keys) {
|
|
152
|
+
try {
|
|
153
|
+
await this.ensureConnected();
|
|
154
|
+
const fullKeys = keys.map(k => this.buildKey(k));
|
|
155
|
+
const values = await this.client.mGet(fullKeys);
|
|
156
|
+
const result = {};
|
|
157
|
+
keys.forEach((key, index) => {
|
|
158
|
+
const value = values[index];
|
|
159
|
+
if (value === null) {
|
|
160
|
+
this.recordMiss();
|
|
161
|
+
result[key] = null;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
this.recordHit();
|
|
165
|
+
result[key] = this.deserialize(value);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
throw new CacheError('Failed to get multiple keys from Redis', 'REDIS_GET_MULTIPLE_ERROR', 'redis', err);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Set multiple values at once
|
|
176
|
+
*/
|
|
177
|
+
async setMultiple(data, ttl) {
|
|
178
|
+
try {
|
|
179
|
+
await this.ensureConnected();
|
|
180
|
+
const expiry = ttl ?? this.ttl;
|
|
181
|
+
if (expiry > 0) {
|
|
182
|
+
// Use pipeline for batch operations with TTL
|
|
183
|
+
const pipeline = this.client.multi();
|
|
184
|
+
for (const [key, value] of Object.entries(data)) {
|
|
185
|
+
const fullKey = this.buildKey(key);
|
|
186
|
+
const serialized = this.serialize(value);
|
|
187
|
+
pipeline.setEx(fullKey, expiry, serialized);
|
|
188
|
+
}
|
|
189
|
+
await pipeline.exec();
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Use mSet for batch operations without TTL
|
|
193
|
+
const flatData = {};
|
|
194
|
+
for (const [key, value] of Object.entries(data)) {
|
|
195
|
+
const fullKey = this.buildKey(key);
|
|
196
|
+
flatData[fullKey] = this.serialize(value);
|
|
197
|
+
}
|
|
198
|
+
await this.client.mSet(flatData);
|
|
199
|
+
}
|
|
200
|
+
this.stats.sets += Object.keys(data).length;
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
throw new CacheError('Failed to set multiple keys in Redis', 'REDIS_SET_MULTIPLE_ERROR', 'redis', err);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Delete multiple keys at once
|
|
208
|
+
*/
|
|
209
|
+
async deleteMultiple(keys) {
|
|
210
|
+
try {
|
|
211
|
+
await this.ensureConnected();
|
|
212
|
+
const fullKeys = keys.map(k => this.buildKey(k));
|
|
213
|
+
const result = await this.client.del(fullKeys);
|
|
214
|
+
this.stats.deletes += result;
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
throw new CacheError('Failed to delete multiple keys from Redis', 'REDIS_DELETE_MULTIPLE_ERROR', 'redis', err);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Increment a numeric value
|
|
223
|
+
*/
|
|
224
|
+
async increment(key, amount = 1) {
|
|
225
|
+
try {
|
|
226
|
+
await this.ensureConnected();
|
|
227
|
+
const fullKey = this.buildKey(key);
|
|
228
|
+
return await this.client.incrBy(fullKey, amount);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
throw new CacheError(`Failed to increment key "${key}" in Redis`, 'REDIS_INCREMENT_ERROR', 'redis', err);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Decrement a numeric value
|
|
236
|
+
*/
|
|
237
|
+
async decrement(key, amount = 1) {
|
|
238
|
+
try {
|
|
239
|
+
await this.ensureConnected();
|
|
240
|
+
const fullKey = this.buildKey(key);
|
|
241
|
+
return await this.client.decrBy(fullKey, amount);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
throw new CacheError(`Failed to decrement key "${key}" in Redis`, 'REDIS_DECREMENT_ERROR', 'redis', err);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Check if Redis is alive
|
|
249
|
+
*/
|
|
250
|
+
async isAlive() {
|
|
251
|
+
try {
|
|
252
|
+
await this.ensureConnected();
|
|
253
|
+
await this.client.ping();
|
|
254
|
+
return {
|
|
255
|
+
isAlive: true,
|
|
256
|
+
adapter: 'redis',
|
|
257
|
+
timestamp: new Date()
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
return {
|
|
262
|
+
isAlive: false,
|
|
263
|
+
adapter: 'redis',
|
|
264
|
+
timestamp: new Date(),
|
|
265
|
+
error: err.message
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Close Redis connection
|
|
271
|
+
*/
|
|
272
|
+
async close() {
|
|
273
|
+
try {
|
|
274
|
+
if (this.client && this.isConnected) {
|
|
275
|
+
await this.client.quit();
|
|
276
|
+
this.isConnected = false;
|
|
277
|
+
this.client = null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
throw new CacheError('Failed to close Redis connection', 'REDIS_CLOSE_ERROR', 'redis', err);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RedisCache } from './RedisCache';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RedisCache } from './RedisCache';
|