@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @naman_deep_singh/cache
2
2
 
3
- **Version:** 1.2.0 (with Redis Clustering support)
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
- else {
94
- this.recordHit();
95
- try {
94
+ this.recordHit();
95
+ try {
96
+ if (typeof data === 'string') {
96
97
  resolve(this.deserialize(data));
97
98
  }
98
- catch (parseErr) {
99
- reject(parseErr);
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
- if (fullKey in data) {
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
- result[key] = this.deserialize(data[fullKey]);
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
- await this.client.sendCommand(['PING']);
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
- const responseData = typeof data === 'string' ? data : JSON.stringify(data);
86
- cache.set(cacheKey, responseData, ttl).catch((err) => {
87
- console.error('Failed to cache response:', err);
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
- else {
88
- this.recordHit();
89
- try {
88
+ this.recordHit();
89
+ try {
90
+ if (typeof data === 'string') {
90
91
  resolve(this.deserialize(data));
91
92
  }
92
- catch (parseErr) {
93
- reject(parseErr);
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
- if (fullKey in data) {
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
- result[key] = this.deserialize(data[fullKey]);
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
- await this.client.sendCommand(['PING']);
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
- const responseData = typeof data === 'string' ? data : JSON.stringify(data);
81
- cache.set(cacheKey, responseData, ttl).catch((err) => {
82
- console.error('Failed to cache response:', err);
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.2.0",
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",