@naman_deep_singh/cache 1.2.0 → 1.3.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 +7 -1
- package/dist/cjs/adapters/memcache/MemcacheCache.js +42 -12
- package/dist/cjs/adapters/redis/RedisCache.js +8 -2
- package/dist/cjs/middleware/express/cacheMiddleware.js +17 -4
- package/dist/cjs/session/SessionStore.d.ts +6 -0
- package/dist/cjs/session/SessionStore.js +8 -0
- package/dist/esm/adapters/memcache/MemcacheCache.js +42 -12
- package/dist/esm/adapters/redis/RedisCache.js +8 -2
- package/dist/esm/middleware/express/cacheMiddleware.js +17 -4
- package/dist/esm/session/SessionStore.d.ts +6 -0
- package/dist/esm/session/SessionStore.js +8 -0
- package/dist/types/session/SessionStore.d.ts +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @naman_deep_singh/cache
|
|
2
2
|
|
|
3
|
-
**Version:** 1.
|
|
3
|
+
**Version:** 1.3.0 (with Redis Clustering support)
|
|
4
4
|
|
|
5
5
|
A flexible, extensible caching layer with support for Redis, Memcache, and in-memory caches. Includes session management, health checks, and Express middleware.
|
|
6
6
|
|
|
@@ -687,3 +687,9 @@ ISC
|
|
|
687
687
|
## Author
|
|
688
688
|
|
|
689
689
|
Naman Deep Singh
|
|
690
|
+
|
|
691
|
+
## TypeScript Notes
|
|
692
|
+
|
|
693
|
+
- The package provides strong generics for `ICache<T>` and `SessionStore` so you can store typed values safely.
|
|
694
|
+
- Middleware in this package may attach runtime properties (for example session data) to `Request` objects in Express. If you're using TypeScript, import the package into your project so the included type augmentations are picked up automatically — no need to cast `req` to `any` in most cases.
|
|
695
|
+
|
|
@@ -86,19 +86,28 @@ class MemcacheCache extends BaseCache_1.BaseCache {
|
|
|
86
86
|
reject(err);
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
|
-
if (data === undefined) {
|
|
89
|
+
if (data === undefined || data === null) {
|
|
90
90
|
this.recordMiss();
|
|
91
91
|
resolve(null);
|
|
92
|
+
return;
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
this.recordHit();
|
|
95
|
+
try {
|
|
96
|
+
if (typeof data === 'string') {
|
|
96
97
|
resolve(this.deserialize(data));
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
else if (Buffer.isBuffer(data)) {
|
|
100
|
+
resolve(this.deserialize(data.toString()));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Unknown shape from memcached client - treat as miss
|
|
104
|
+
this.recordMiss();
|
|
105
|
+
resolve(null);
|
|
100
106
|
}
|
|
101
107
|
}
|
|
108
|
+
catch (parseErr) {
|
|
109
|
+
reject(parseErr);
|
|
110
|
+
}
|
|
102
111
|
});
|
|
103
112
|
});
|
|
104
113
|
}
|
|
@@ -201,21 +210,42 @@ class MemcacheCache extends BaseCache_1.BaseCache {
|
|
|
201
210
|
return;
|
|
202
211
|
}
|
|
203
212
|
const result = {};
|
|
213
|
+
if (!data || typeof data !== 'object') {
|
|
214
|
+
// Treat as all misses
|
|
215
|
+
for (const key of keys) {
|
|
216
|
+
this.recordMiss();
|
|
217
|
+
result[key] = null;
|
|
218
|
+
}
|
|
219
|
+
resolve(result);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const map = data;
|
|
204
223
|
keys.forEach((key) => {
|
|
205
224
|
const fullKey = this.buildKey(key);
|
|
206
|
-
|
|
225
|
+
const value = map[fullKey];
|
|
226
|
+
if (value === undefined || value === null) {
|
|
227
|
+
this.recordMiss();
|
|
228
|
+
result[key] = null;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
207
231
|
this.recordHit();
|
|
208
232
|
try {
|
|
209
|
-
|
|
233
|
+
if (typeof value === 'string') {
|
|
234
|
+
result[key] = this.deserialize(value);
|
|
235
|
+
}
|
|
236
|
+
else if (Buffer.isBuffer(value)) {
|
|
237
|
+
result[key] = this.deserialize(value.toString());
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Unknown, treat as miss
|
|
241
|
+
this.recordMiss();
|
|
242
|
+
result[key] = null;
|
|
243
|
+
}
|
|
210
244
|
}
|
|
211
245
|
catch (parseErr) {
|
|
212
246
|
reject(parseErr);
|
|
213
247
|
}
|
|
214
248
|
}
|
|
215
|
-
else {
|
|
216
|
-
this.recordMiss();
|
|
217
|
-
result[key] = null;
|
|
218
|
-
}
|
|
219
249
|
});
|
|
220
250
|
resolve(result);
|
|
221
251
|
});
|
|
@@ -159,7 +159,7 @@ class RedisCache extends BaseCache_1.BaseCache {
|
|
|
159
159
|
else {
|
|
160
160
|
// Clear all keys only in single-instance mode
|
|
161
161
|
const client = this.client;
|
|
162
|
-
if (client.flushDb) {
|
|
162
|
+
if (client && typeof client.flushDb === 'function') {
|
|
163
163
|
await client.flushDb();
|
|
164
164
|
}
|
|
165
165
|
else {
|
|
@@ -277,7 +277,13 @@ class RedisCache extends BaseCache_1.BaseCache {
|
|
|
277
277
|
try {
|
|
278
278
|
await this.ensureConnected();
|
|
279
279
|
// Use sendCommand which works for both single and cluster
|
|
280
|
-
|
|
280
|
+
// `sendCommand` exists on both single and cluster clients in runtime; cast narrowly for the call
|
|
281
|
+
if (this.client && typeof this.client.sendCommand === 'function') {
|
|
282
|
+
await this.client.sendCommand(['PING']);
|
|
283
|
+
}
|
|
284
|
+
else if (this.client && typeof this.client.ping === 'function') {
|
|
285
|
+
await this.client.ping();
|
|
286
|
+
}
|
|
281
287
|
return {
|
|
282
288
|
isAlive: true,
|
|
283
289
|
adapter: 'redis',
|
|
@@ -82,10 +82,23 @@ function cacheResponseMiddleware(cache, options) {
|
|
|
82
82
|
if (res.statusCode >= 200 &&
|
|
83
83
|
res.statusCode < 300 &&
|
|
84
84
|
!excludeStatusCodes.includes(res.statusCode)) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
85
|
+
let responseData = null;
|
|
86
|
+
if (typeof data === 'string') {
|
|
87
|
+
responseData = data;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
try {
|
|
91
|
+
responseData = JSON.stringify(data);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
responseData = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (responseData !== null) {
|
|
98
|
+
cache.set(cacheKey, responseData, ttl).catch((err) => {
|
|
99
|
+
console.error('Failed to cache response:', err);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
89
102
|
}
|
|
90
103
|
res.set('X-Cache', 'MISS');
|
|
91
104
|
return originalSend.call(this, data);
|
|
@@ -48,4 +48,10 @@ export declare class SessionStore {
|
|
|
48
48
|
* Get session data and extend expiry in one operation
|
|
49
49
|
*/
|
|
50
50
|
getAndExtend(sessionId: string, ttl?: number): Promise<SessionData | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Optional close hook for graceful shutdowns.
|
|
53
|
+
* Present to allow callers to call `close()` during shutdown without
|
|
54
|
+
* requiring every store implementation to provide one.
|
|
55
|
+
*/
|
|
56
|
+
close(): Promise<void>;
|
|
51
57
|
}
|
|
@@ -149,5 +149,13 @@ class SessionStore {
|
|
|
149
149
|
throw new errors_1.CacheError(`Failed to get and extend session "${sessionId}"`, 'SESSION_GET_EXTEND_ERROR', 'session', err);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Optional close hook for graceful shutdowns.
|
|
154
|
+
* Present to allow callers to call `close()` during shutdown without
|
|
155
|
+
* requiring every store implementation to provide one.
|
|
156
|
+
*/
|
|
157
|
+
async close() {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
152
160
|
}
|
|
153
161
|
exports.SessionStore = SessionStore;
|
|
@@ -80,19 +80,28 @@ export class MemcacheCache extends BaseCache {
|
|
|
80
80
|
reject(err);
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
-
if (data === undefined) {
|
|
83
|
+
if (data === undefined || data === null) {
|
|
84
84
|
this.recordMiss();
|
|
85
85
|
resolve(null);
|
|
86
|
+
return;
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
this.recordHit();
|
|
89
|
+
try {
|
|
90
|
+
if (typeof data === 'string') {
|
|
90
91
|
resolve(this.deserialize(data));
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
else if (Buffer.isBuffer(data)) {
|
|
94
|
+
resolve(this.deserialize(data.toString()));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Unknown shape from memcached client - treat as miss
|
|
98
|
+
this.recordMiss();
|
|
99
|
+
resolve(null);
|
|
94
100
|
}
|
|
95
101
|
}
|
|
102
|
+
catch (parseErr) {
|
|
103
|
+
reject(parseErr);
|
|
104
|
+
}
|
|
96
105
|
});
|
|
97
106
|
});
|
|
98
107
|
}
|
|
@@ -195,21 +204,42 @@ export class MemcacheCache extends BaseCache {
|
|
|
195
204
|
return;
|
|
196
205
|
}
|
|
197
206
|
const result = {};
|
|
207
|
+
if (!data || typeof data !== 'object') {
|
|
208
|
+
// Treat as all misses
|
|
209
|
+
for (const key of keys) {
|
|
210
|
+
this.recordMiss();
|
|
211
|
+
result[key] = null;
|
|
212
|
+
}
|
|
213
|
+
resolve(result);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const map = data;
|
|
198
217
|
keys.forEach((key) => {
|
|
199
218
|
const fullKey = this.buildKey(key);
|
|
200
|
-
|
|
219
|
+
const value = map[fullKey];
|
|
220
|
+
if (value === undefined || value === null) {
|
|
221
|
+
this.recordMiss();
|
|
222
|
+
result[key] = null;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
201
225
|
this.recordHit();
|
|
202
226
|
try {
|
|
203
|
-
|
|
227
|
+
if (typeof value === 'string') {
|
|
228
|
+
result[key] = this.deserialize(value);
|
|
229
|
+
}
|
|
230
|
+
else if (Buffer.isBuffer(value)) {
|
|
231
|
+
result[key] = this.deserialize(value.toString());
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Unknown, treat as miss
|
|
235
|
+
this.recordMiss();
|
|
236
|
+
result[key] = null;
|
|
237
|
+
}
|
|
204
238
|
}
|
|
205
239
|
catch (parseErr) {
|
|
206
240
|
reject(parseErr);
|
|
207
241
|
}
|
|
208
242
|
}
|
|
209
|
-
else {
|
|
210
|
-
this.recordMiss();
|
|
211
|
-
result[key] = null;
|
|
212
|
-
}
|
|
213
243
|
});
|
|
214
244
|
resolve(result);
|
|
215
245
|
});
|
|
@@ -156,7 +156,7 @@ export class RedisCache extends BaseCache {
|
|
|
156
156
|
else {
|
|
157
157
|
// Clear all keys only in single-instance mode
|
|
158
158
|
const client = this.client;
|
|
159
|
-
if (client.flushDb) {
|
|
159
|
+
if (client && typeof client.flushDb === 'function') {
|
|
160
160
|
await client.flushDb();
|
|
161
161
|
}
|
|
162
162
|
else {
|
|
@@ -274,7 +274,13 @@ export class RedisCache extends BaseCache {
|
|
|
274
274
|
try {
|
|
275
275
|
await this.ensureConnected();
|
|
276
276
|
// Use sendCommand which works for both single and cluster
|
|
277
|
-
|
|
277
|
+
// `sendCommand` exists on both single and cluster clients in runtime; cast narrowly for the call
|
|
278
|
+
if (this.client && typeof this.client.sendCommand === 'function') {
|
|
279
|
+
await this.client.sendCommand(['PING']);
|
|
280
|
+
}
|
|
281
|
+
else if (this.client && typeof this.client.ping === 'function') {
|
|
282
|
+
await this.client.ping();
|
|
283
|
+
}
|
|
278
284
|
return {
|
|
279
285
|
isAlive: true,
|
|
280
286
|
adapter: 'redis',
|
|
@@ -77,10 +77,23 @@ export function cacheResponseMiddleware(cache, options) {
|
|
|
77
77
|
if (res.statusCode >= 200 &&
|
|
78
78
|
res.statusCode < 300 &&
|
|
79
79
|
!excludeStatusCodes.includes(res.statusCode)) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
80
|
+
let responseData = null;
|
|
81
|
+
if (typeof data === 'string') {
|
|
82
|
+
responseData = data;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
try {
|
|
86
|
+
responseData = JSON.stringify(data);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
responseData = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (responseData !== null) {
|
|
93
|
+
cache.set(cacheKey, responseData, ttl).catch((err) => {
|
|
94
|
+
console.error('Failed to cache response:', err);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
84
97
|
}
|
|
85
98
|
res.set('X-Cache', 'MISS');
|
|
86
99
|
return originalSend.call(this, data);
|
|
@@ -48,4 +48,10 @@ export declare class SessionStore {
|
|
|
48
48
|
* Get session data and extend expiry in one operation
|
|
49
49
|
*/
|
|
50
50
|
getAndExtend(sessionId: string, ttl?: number): Promise<SessionData | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Optional close hook for graceful shutdowns.
|
|
53
|
+
* Present to allow callers to call `close()` during shutdown without
|
|
54
|
+
* requiring every store implementation to provide one.
|
|
55
|
+
*/
|
|
56
|
+
close(): Promise<void>;
|
|
51
57
|
}
|
|
@@ -146,4 +146,12 @@ export class SessionStore {
|
|
|
146
146
|
throw new CacheError(`Failed to get and extend session "${sessionId}"`, 'SESSION_GET_EXTEND_ERROR', 'session', err);
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Optional close hook for graceful shutdowns.
|
|
151
|
+
* Present to allow callers to call `close()` during shutdown without
|
|
152
|
+
* requiring every store implementation to provide one.
|
|
153
|
+
*/
|
|
154
|
+
async close() {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
149
157
|
}
|
|
@@ -48,4 +48,10 @@ export declare class SessionStore {
|
|
|
48
48
|
* Get session data and extend expiry in one operation
|
|
49
49
|
*/
|
|
50
50
|
getAndExtend(sessionId: string, ttl?: number): Promise<SessionData | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Optional close hook for graceful shutdowns.
|
|
53
|
+
* Present to allow callers to call `close()` during shutdown without
|
|
54
|
+
* requiring every store implementation to provide one.
|
|
55
|
+
*/
|
|
56
|
+
close(): Promise<void>;
|
|
51
57
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naman_deep_singh/cache",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Extensible caching layer supporting Redis, Memcache, and in-memory caches with automatic fallback, namespacing, session management, and Express middleware.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|