@naman_deep_singh/cache 1.2.0 → 1.3.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.
Files changed (45) hide show
  1. package/README.md +7 -1
  2. package/dist/cjs/adapters/memcache/MemcacheCache.d.ts +1 -1
  3. package/dist/cjs/adapters/memcache/MemcacheCache.js +50 -18
  4. package/dist/cjs/adapters/memory/MemoryCache.d.ts +1 -1
  5. package/dist/cjs/adapters/memory/MemoryCache.js +7 -3
  6. package/dist/cjs/adapters/redis/RedisCache.d.ts +1 -1
  7. package/dist/cjs/adapters/redis/RedisCache.js +22 -9
  8. package/dist/cjs/core/BaseCache.d.ts +1 -1
  9. package/dist/cjs/core/BaseCache.js +2 -2
  10. package/dist/cjs/core/factory.js +2 -2
  11. package/dist/cjs/core/interfaces/ICache.d.ts +1 -1
  12. package/dist/cjs/index.d.ts +2 -2
  13. package/dist/cjs/middleware/express/cacheMiddleware.d.ts +2 -2
  14. package/dist/cjs/middleware/express/cacheMiddleware.js +22 -6
  15. package/dist/cjs/middleware/express/index.d.ts +1 -1
  16. package/dist/cjs/session/SessionStore.d.ts +6 -0
  17. package/dist/cjs/session/SessionStore.js +9 -1
  18. package/dist/esm/adapters/memcache/MemcacheCache.d.ts +1 -1
  19. package/dist/esm/adapters/memcache/MemcacheCache.js +50 -18
  20. package/dist/esm/adapters/memory/MemoryCache.d.ts +1 -1
  21. package/dist/esm/adapters/memory/MemoryCache.js +7 -3
  22. package/dist/esm/adapters/redis/RedisCache.d.ts +1 -1
  23. package/dist/esm/adapters/redis/RedisCache.js +22 -9
  24. package/dist/esm/core/BaseCache.d.ts +1 -1
  25. package/dist/esm/core/BaseCache.js +2 -2
  26. package/dist/esm/core/factory.js +2 -2
  27. package/dist/esm/core/interfaces/ICache.d.ts +1 -1
  28. package/dist/esm/index.d.ts +2 -2
  29. package/dist/esm/index.js +1 -1
  30. package/dist/esm/middleware/express/cacheMiddleware.d.ts +2 -2
  31. package/dist/esm/middleware/express/cacheMiddleware.js +22 -6
  32. package/dist/esm/middleware/express/index.d.ts +1 -1
  33. package/dist/esm/middleware/express/index.js +1 -1
  34. package/dist/esm/session/SessionStore.d.ts +6 -0
  35. package/dist/esm/session/SessionStore.js +9 -1
  36. package/dist/types/adapters/memcache/MemcacheCache.d.ts +1 -1
  37. package/dist/types/adapters/memory/MemoryCache.d.ts +1 -1
  38. package/dist/types/adapters/redis/RedisCache.d.ts +1 -1
  39. package/dist/types/core/BaseCache.d.ts +1 -1
  40. package/dist/types/core/interfaces/ICache.d.ts +1 -1
  41. package/dist/types/index.d.ts +2 -2
  42. package/dist/types/middleware/express/cacheMiddleware.d.ts +2 -2
  43. package/dist/types/middleware/express/index.d.ts +1 -1
  44. package/dist/types/session/SessionStore.d.ts +6 -0
  45. package/package.json +1 -1
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.1 (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
+
@@ -1,5 +1,5 @@
1
- import type { MemcacheCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, MemcacheCacheConfig } from '../../types';
3
3
  /**
4
4
  * Memcache adapter
5
5
  */
@@ -31,12 +31,14 @@ class MemcacheCache extends BaseCache_1.BaseCache {
31
31
  remove: true,
32
32
  failOverServers: [],
33
33
  maxValue: 1048576, // 1MB default
34
- idle: 30000
34
+ idle: 30000,
35
35
  };
36
36
  if (this.memcacheConfig.username) {
37
+ ;
37
38
  options.username = this.memcacheConfig.username;
38
39
  }
39
40
  if (this.memcacheConfig.password) {
41
+ ;
40
42
  options.password = this.memcacheConfig.password;
41
43
  }
42
44
  this.client = new memcached_1.default(servers, options);
@@ -86,19 +88,28 @@ class MemcacheCache extends BaseCache_1.BaseCache {
86
88
  reject(err);
87
89
  return;
88
90
  }
89
- if (data === undefined) {
91
+ if (data === undefined || data === null) {
90
92
  this.recordMiss();
91
93
  resolve(null);
94
+ return;
92
95
  }
93
- else {
94
- this.recordHit();
95
- try {
96
+ this.recordHit();
97
+ try {
98
+ if (typeof data === 'string') {
96
99
  resolve(this.deserialize(data));
97
100
  }
98
- catch (parseErr) {
99
- reject(parseErr);
101
+ else if (Buffer.isBuffer(data)) {
102
+ resolve(this.deserialize(data.toString()));
103
+ }
104
+ else {
105
+ // Unknown shape from memcached client - treat as miss
106
+ this.recordMiss();
107
+ resolve(null);
100
108
  }
101
109
  }
110
+ catch (parseErr) {
111
+ reject(parseErr);
112
+ }
102
113
  });
103
114
  });
104
115
  }
@@ -193,7 +204,7 @@ class MemcacheCache extends BaseCache_1.BaseCache {
193
204
  async getMultiple(keys) {
194
205
  try {
195
206
  await this.ensureConnected();
196
- const fullKeys = keys.map(k => this.buildKey(k));
207
+ const fullKeys = keys.map((k) => this.buildKey(k));
197
208
  return new Promise((resolve, reject) => {
198
209
  this.client.getMulti(fullKeys, (err, data) => {
199
210
  if (err) {
@@ -201,21 +212,42 @@ class MemcacheCache extends BaseCache_1.BaseCache {
201
212
  return;
202
213
  }
203
214
  const result = {};
215
+ if (!data || typeof data !== 'object') {
216
+ // Treat as all misses
217
+ for (const key of keys) {
218
+ this.recordMiss();
219
+ result[key] = null;
220
+ }
221
+ resolve(result);
222
+ return;
223
+ }
224
+ const map = data;
204
225
  keys.forEach((key) => {
205
226
  const fullKey = this.buildKey(key);
206
- if (fullKey in data) {
227
+ const value = map[fullKey];
228
+ if (value === undefined || value === null) {
229
+ this.recordMiss();
230
+ result[key] = null;
231
+ }
232
+ else {
207
233
  this.recordHit();
208
234
  try {
209
- result[key] = this.deserialize(data[fullKey]);
235
+ if (typeof value === 'string') {
236
+ result[key] = this.deserialize(value);
237
+ }
238
+ else if (Buffer.isBuffer(value)) {
239
+ result[key] = this.deserialize(value.toString());
240
+ }
241
+ else {
242
+ // Unknown, treat as miss
243
+ this.recordMiss();
244
+ result[key] = null;
245
+ }
210
246
  }
211
247
  catch (parseErr) {
212
248
  reject(parseErr);
213
249
  }
214
250
  }
215
- else {
216
- this.recordMiss();
217
- result[key] = null;
218
- }
219
251
  });
220
252
  resolve(result);
221
253
  });
@@ -259,9 +291,9 @@ class MemcacheCache extends BaseCache_1.BaseCache {
259
291
  async deleteMultiple(keys) {
260
292
  try {
261
293
  await this.ensureConnected();
262
- const fullKeys = keys.map(k => this.buildKey(k));
294
+ const fullKeys = keys.map((k) => this.buildKey(k));
263
295
  let deletedCount = 0;
264
- await Promise.all(fullKeys.map(key => new Promise((resolve, reject) => {
296
+ await Promise.all(fullKeys.map((key) => new Promise((resolve, reject) => {
265
297
  this.client.del(key, (err) => {
266
298
  if (err) {
267
299
  reject(err);
@@ -316,7 +348,7 @@ class MemcacheCache extends BaseCache_1.BaseCache {
316
348
  return {
317
349
  isAlive: true,
318
350
  adapter: 'memcache',
319
- timestamp: new Date()
351
+ timestamp: new Date(),
320
352
  };
321
353
  }
322
354
  catch (err) {
@@ -324,7 +356,7 @@ class MemcacheCache extends BaseCache_1.BaseCache {
324
356
  isAlive: false,
325
357
  adapter: 'memcache',
326
358
  timestamp: new Date(),
327
- error: err.message
359
+ error: err.message,
328
360
  };
329
361
  }
330
362
  }
@@ -1,5 +1,5 @@
1
- import type { MemoryCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, MemoryCacheConfig } from '../../types';
3
3
  /**
4
4
  * In-memory cache adapter for development and testing
5
5
  */
@@ -212,7 +212,9 @@ class MemoryCache extends BaseCache_1.BaseCache {
212
212
  const fullKey = this.buildKey(key);
213
213
  const entry = this.store.get(fullKey);
214
214
  const current = entry && (!entry.expiresAt || entry.expiresAt >= Date.now())
215
- ? (typeof entry.value === 'number' ? entry.value : 0)
215
+ ? typeof entry.value === 'number'
216
+ ? entry.value
217
+ : 0
216
218
  : 0;
217
219
  const value = current + amount;
218
220
  const expiry = this.ttl;
@@ -232,7 +234,9 @@ class MemoryCache extends BaseCache_1.BaseCache {
232
234
  const fullKey = this.buildKey(key);
233
235
  const entry = this.store.get(fullKey);
234
236
  const current = entry && (!entry.expiresAt || entry.expiresAt >= Date.now())
235
- ? (typeof entry.value === 'number' ? entry.value : 0)
237
+ ? typeof entry.value === 'number'
238
+ ? entry.value
239
+ : 0
236
240
  : 0;
237
241
  const value = current - amount;
238
242
  const expiry = this.ttl;
@@ -251,7 +255,7 @@ class MemoryCache extends BaseCache_1.BaseCache {
251
255
  return {
252
256
  isAlive: true,
253
257
  adapter: 'memory',
254
- timestamp: new Date()
258
+ timestamp: new Date(),
255
259
  };
256
260
  }
257
261
  /**
@@ -1,5 +1,5 @@
1
- import type { RedisCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, RedisCacheConfig } from '../../types';
3
3
  /**
4
4
  * Redis cache adapter
5
5
  */
@@ -20,7 +20,10 @@ class RedisCache extends BaseCache_1.BaseCache {
20
20
  async connect() {
21
21
  try {
22
22
  const cluster = this.redisConfig.cluster;
23
- const hasCluster = cluster && (Array.isArray(cluster) ? cluster.length > 0 : cluster.nodes?.length > 0);
23
+ const hasCluster = cluster &&
24
+ (Array.isArray(cluster)
25
+ ? cluster.length > 0
26
+ : cluster.nodes?.length > 0);
24
27
  if (hasCluster && cluster) {
25
28
  // Cluster mode
26
29
  let nodes = [];
@@ -31,7 +34,9 @@ class RedisCache extends BaseCache_1.BaseCache {
31
34
  nodes = cluster.nodes;
32
35
  }
33
36
  this.client = (0, redis_1.createCluster)({
34
- rootNodes: nodes.map(node => ({ url: `redis://${node.host}:${node.port}` }))
37
+ rootNodes: nodes.map((node) => ({
38
+ url: `redis://${node.host}:${node.port}`,
39
+ })),
35
40
  });
36
41
  }
37
42
  else {
@@ -39,7 +44,7 @@ class RedisCache extends BaseCache_1.BaseCache {
39
44
  const options = {
40
45
  host: this.redisConfig.host ?? 'localhost',
41
46
  port: this.redisConfig.port ?? 6379,
42
- db: this.redisConfig.db ?? 0
47
+ db: this.redisConfig.db ?? 0,
43
48
  };
44
49
  if (this.redisConfig.username) {
45
50
  options.username = this.redisConfig.username;
@@ -159,7 +164,7 @@ class RedisCache extends BaseCache_1.BaseCache {
159
164
  else {
160
165
  // Clear all keys only in single-instance mode
161
166
  const client = this.client;
162
- if (client.flushDb) {
167
+ if (client && typeof client.flushDb === 'function') {
163
168
  await client.flushDb();
164
169
  }
165
170
  else {
@@ -177,7 +182,7 @@ class RedisCache extends BaseCache_1.BaseCache {
177
182
  async getMultiple(keys) {
178
183
  try {
179
184
  await this.ensureConnected();
180
- const fullKeys = keys.map(k => this.buildKey(k));
185
+ const fullKeys = keys.map((k) => this.buildKey(k));
181
186
  const values = await this.client.mGet(fullKeys);
182
187
  const result = {};
183
188
  keys.forEach((key, index) => {
@@ -235,7 +240,7 @@ class RedisCache extends BaseCache_1.BaseCache {
235
240
  async deleteMultiple(keys) {
236
241
  try {
237
242
  await this.ensureConnected();
238
- const fullKeys = keys.map(k => this.buildKey(k));
243
+ const fullKeys = keys.map((k) => this.buildKey(k));
239
244
  const result = await this.client.del(fullKeys);
240
245
  this.stats.deletes += result;
241
246
  return result;
@@ -277,11 +282,19 @@ class RedisCache extends BaseCache_1.BaseCache {
277
282
  try {
278
283
  await this.ensureConnected();
279
284
  // Use sendCommand which works for both single and cluster
280
- await this.client.sendCommand(['PING']);
285
+ // `sendCommand` exists on both single and cluster clients in runtime; cast narrowly for the call
286
+ if (this.client &&
287
+ typeof this.client.sendCommand === 'function') {
288
+ await this.client.sendCommand(['PING']);
289
+ }
290
+ else if (this.client &&
291
+ typeof this.client.ping === 'function') {
292
+ await this.client.ping();
293
+ }
281
294
  return {
282
295
  isAlive: true,
283
296
  adapter: 'redis',
284
- timestamp: new Date()
297
+ timestamp: new Date(),
285
298
  };
286
299
  }
287
300
  catch (err) {
@@ -289,7 +302,7 @@ class RedisCache extends BaseCache_1.BaseCache {
289
302
  isAlive: false,
290
303
  adapter: 'redis',
291
304
  timestamp: new Date(),
292
- error: err.message
305
+ error: err.message,
293
306
  };
294
307
  }
295
308
  }
@@ -1,4 +1,4 @@
1
- import type { CacheConfig, CacheStats, HealthCheckResponse, BatchResult } from '../types';
1
+ import type { BatchResult, CacheConfig, CacheStats, HealthCheckResponse } from '../types';
2
2
  import type { ICache } from './interfaces';
3
3
  /**
4
4
  * Abstract base class for all cache adapters
@@ -12,7 +12,7 @@ class BaseCache {
12
12
  hits: 0,
13
13
  misses: 0,
14
14
  sets: 0,
15
- deletes: 0
15
+ deletes: 0,
16
16
  };
17
17
  this.namespace = config.namespace ? `${config.namespace}:` : '';
18
18
  this.ttl = config.ttl ?? 3600; // 1 hour default
@@ -77,7 +77,7 @@ class BaseCache {
77
77
  hits: 0,
78
78
  misses: 0,
79
79
  sets: 0,
80
- deletes: 0
80
+ deletes: 0,
81
81
  };
82
82
  }
83
83
  /**
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CacheFactory = void 0;
4
- const redis_1 = require("../adapters/redis");
5
4
  const memcache_1 = require("../adapters/memcache");
6
5
  const memory_1 = require("../adapters/memory");
6
+ const redis_1 = require("../adapters/redis");
7
7
  const errors_1 = require("../errors");
8
8
  /**
9
9
  * Factory for creating cache instances
@@ -50,7 +50,7 @@ class CacheFactory {
50
50
  return new memory_1.MemoryCache({
51
51
  adapter: 'memory',
52
52
  namespace: config.namespace,
53
- ttl: config.ttl
53
+ ttl: config.ttl,
54
54
  });
55
55
  }
56
56
  // No fallback, throw error
@@ -1,4 +1,4 @@
1
- import type { CacheStats, HealthCheckResponse, BatchResult } from '../../types';
1
+ import type { BatchResult, CacheStats, HealthCheckResponse } from '../../types';
2
2
  /**
3
3
  * Main cache interface - defines all cache operations
4
4
  */
@@ -1,4 +1,4 @@
1
- export type { CacheConfig, RedisCacheConfig, MemcacheCacheConfig, MemoryCacheConfig, SessionData, SessionOptions, CacheStats, HealthCheckResponse, BatchResult, CacheEntry } from './types';
1
+ export type { CacheConfig, RedisCacheConfig, MemcacheCacheConfig, MemoryCacheConfig, SessionData, SessionOptions, CacheStats, HealthCheckResponse, BatchResult, CacheEntry, } from './types';
2
2
  export { CacheError } from './errors';
3
3
  export type { ICache, ISession } from './core/interfaces';
4
4
  export { BaseCache } from './core/BaseCache';
@@ -7,4 +7,4 @@ export { MemcacheCache } from './adapters/memcache';
7
7
  export { MemoryCache } from './adapters/memory';
8
8
  export { CacheFactory } from './core/factory';
9
9
  export { SessionStore } from './session';
10
- export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware } from './middleware/express';
10
+ export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware, } from './middleware/express';
@@ -1,6 +1,6 @@
1
- import type { Request, Response, NextFunction } from 'express';
1
+ import type { NextFunction, Request, Response } from 'express';
2
2
  import type { ICache } from '../../core/interfaces';
3
- import { SessionStore } from '../../session/SessionStore';
3
+ import type { SessionStore } from '../../session/SessionStore';
4
4
  /**
5
5
  * Express middleware for session management using cache
6
6
  */
@@ -17,6 +17,7 @@ function cacheSessionMiddleware(sessionStore, options) {
17
17
  // Fetch session data and extend expiry
18
18
  const sessionData = await sessionStore.getAndExtend(sessionId);
19
19
  if (sessionData) {
20
+ ;
20
21
  req[sessionDataKey] = sessionData;
21
22
  }
22
23
  }
@@ -44,7 +45,7 @@ function cacheHealthCheckMiddleware(cache, endpoint = '/.health/cache') {
44
45
  isAlive: false,
45
46
  adapter: 'unknown',
46
47
  timestamp: new Date(),
47
- error: err.message
48
+ error: err.message,
48
49
  });
49
50
  });
50
51
  return;
@@ -58,7 +59,9 @@ function cacheHealthCheckMiddleware(cache, endpoint = '/.health/cache') {
58
59
  function cacheResponseMiddleware(cache, options) {
59
60
  const ttl = options?.ttl ?? 300; // 5 minutes default
60
61
  const keyPrefix = options?.keyPrefix ?? 'response:';
61
- const excludeStatusCodes = options?.excludeStatusCodes ?? [300, 301, 302, 303, 304, 307, 308, 404, 500, 501, 502, 503];
62
+ const excludeStatusCodes = options?.excludeStatusCodes ?? [
63
+ 300, 301, 302, 303, 304, 307, 308, 404, 500, 501, 502, 503,
64
+ ];
62
65
  return (req, res, next) => {
63
66
  // Only cache GET requests
64
67
  if (req.method !== 'GET') {
@@ -82,10 +85,23 @@ function cacheResponseMiddleware(cache, options) {
82
85
  if (res.statusCode >= 200 &&
83
86
  res.statusCode < 300 &&
84
87
  !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
- });
88
+ let responseData = null;
89
+ if (typeof data === 'string') {
90
+ responseData = data;
91
+ }
92
+ else {
93
+ try {
94
+ responseData = JSON.stringify(data);
95
+ }
96
+ catch (e) {
97
+ responseData = null;
98
+ }
99
+ }
100
+ if (responseData !== null) {
101
+ cache.set(cacheKey, responseData, ttl).catch((err) => {
102
+ console.error('Failed to cache response:', err);
103
+ });
104
+ }
89
105
  }
90
106
  res.set('X-Cache', 'MISS');
91
107
  return originalSend.call(this, data);
@@ -1 +1 @@
1
- export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware } from './cacheMiddleware';
1
+ export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware, } from './cacheMiddleware';
@@ -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
  }
@@ -12,7 +12,7 @@ class SessionStore {
12
12
  this.options = {
13
13
  ttl: options.ttl ?? 3600, // 1 hour default
14
14
  serialize: options.serialize ?? ((data) => JSON.stringify(data)),
15
- deserialize: options.deserialize ?? ((data) => JSON.parse(data))
15
+ deserialize: options.deserialize ?? ((data) => JSON.parse(data)),
16
16
  };
17
17
  }
18
18
  /**
@@ -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;
@@ -1,5 +1,5 @@
1
- import type { MemcacheCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, MemcacheCacheConfig } from '../../types';
3
3
  /**
4
4
  * Memcache adapter
5
5
  */
@@ -25,12 +25,14 @@ export class MemcacheCache extends BaseCache {
25
25
  remove: true,
26
26
  failOverServers: [],
27
27
  maxValue: 1048576, // 1MB default
28
- idle: 30000
28
+ idle: 30000,
29
29
  };
30
30
  if (this.memcacheConfig.username) {
31
+ ;
31
32
  options.username = this.memcacheConfig.username;
32
33
  }
33
34
  if (this.memcacheConfig.password) {
35
+ ;
34
36
  options.password = this.memcacheConfig.password;
35
37
  }
36
38
  this.client = new Memcached(servers, options);
@@ -80,19 +82,28 @@ export class MemcacheCache extends BaseCache {
80
82
  reject(err);
81
83
  return;
82
84
  }
83
- if (data === undefined) {
85
+ if (data === undefined || data === null) {
84
86
  this.recordMiss();
85
87
  resolve(null);
88
+ return;
86
89
  }
87
- else {
88
- this.recordHit();
89
- try {
90
+ this.recordHit();
91
+ try {
92
+ if (typeof data === 'string') {
90
93
  resolve(this.deserialize(data));
91
94
  }
92
- catch (parseErr) {
93
- reject(parseErr);
95
+ else if (Buffer.isBuffer(data)) {
96
+ resolve(this.deserialize(data.toString()));
97
+ }
98
+ else {
99
+ // Unknown shape from memcached client - treat as miss
100
+ this.recordMiss();
101
+ resolve(null);
94
102
  }
95
103
  }
104
+ catch (parseErr) {
105
+ reject(parseErr);
106
+ }
96
107
  });
97
108
  });
98
109
  }
@@ -187,7 +198,7 @@ export class MemcacheCache extends BaseCache {
187
198
  async getMultiple(keys) {
188
199
  try {
189
200
  await this.ensureConnected();
190
- const fullKeys = keys.map(k => this.buildKey(k));
201
+ const fullKeys = keys.map((k) => this.buildKey(k));
191
202
  return new Promise((resolve, reject) => {
192
203
  this.client.getMulti(fullKeys, (err, data) => {
193
204
  if (err) {
@@ -195,21 +206,42 @@ export class MemcacheCache extends BaseCache {
195
206
  return;
196
207
  }
197
208
  const result = {};
209
+ if (!data || typeof data !== 'object') {
210
+ // Treat as all misses
211
+ for (const key of keys) {
212
+ this.recordMiss();
213
+ result[key] = null;
214
+ }
215
+ resolve(result);
216
+ return;
217
+ }
218
+ const map = data;
198
219
  keys.forEach((key) => {
199
220
  const fullKey = this.buildKey(key);
200
- if (fullKey in data) {
221
+ const value = map[fullKey];
222
+ if (value === undefined || value === null) {
223
+ this.recordMiss();
224
+ result[key] = null;
225
+ }
226
+ else {
201
227
  this.recordHit();
202
228
  try {
203
- result[key] = this.deserialize(data[fullKey]);
229
+ if (typeof value === 'string') {
230
+ result[key] = this.deserialize(value);
231
+ }
232
+ else if (Buffer.isBuffer(value)) {
233
+ result[key] = this.deserialize(value.toString());
234
+ }
235
+ else {
236
+ // Unknown, treat as miss
237
+ this.recordMiss();
238
+ result[key] = null;
239
+ }
204
240
  }
205
241
  catch (parseErr) {
206
242
  reject(parseErr);
207
243
  }
208
244
  }
209
- else {
210
- this.recordMiss();
211
- result[key] = null;
212
- }
213
245
  });
214
246
  resolve(result);
215
247
  });
@@ -253,9 +285,9 @@ export class MemcacheCache extends BaseCache {
253
285
  async deleteMultiple(keys) {
254
286
  try {
255
287
  await this.ensureConnected();
256
- const fullKeys = keys.map(k => this.buildKey(k));
288
+ const fullKeys = keys.map((k) => this.buildKey(k));
257
289
  let deletedCount = 0;
258
- await Promise.all(fullKeys.map(key => new Promise((resolve, reject) => {
290
+ await Promise.all(fullKeys.map((key) => new Promise((resolve, reject) => {
259
291
  this.client.del(key, (err) => {
260
292
  if (err) {
261
293
  reject(err);
@@ -310,7 +342,7 @@ export class MemcacheCache extends BaseCache {
310
342
  return {
311
343
  isAlive: true,
312
344
  adapter: 'memcache',
313
- timestamp: new Date()
345
+ timestamp: new Date(),
314
346
  };
315
347
  }
316
348
  catch (err) {
@@ -318,7 +350,7 @@ export class MemcacheCache extends BaseCache {
318
350
  isAlive: false,
319
351
  adapter: 'memcache',
320
352
  timestamp: new Date(),
321
- error: err.message
353
+ error: err.message,
322
354
  };
323
355
  }
324
356
  }
@@ -1,5 +1,5 @@
1
- import type { MemoryCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, MemoryCacheConfig } from '../../types';
3
3
  /**
4
4
  * In-memory cache adapter for development and testing
5
5
  */
@@ -209,7 +209,9 @@ export class MemoryCache extends BaseCache {
209
209
  const fullKey = this.buildKey(key);
210
210
  const entry = this.store.get(fullKey);
211
211
  const current = entry && (!entry.expiresAt || entry.expiresAt >= Date.now())
212
- ? (typeof entry.value === 'number' ? entry.value : 0)
212
+ ? typeof entry.value === 'number'
213
+ ? entry.value
214
+ : 0
213
215
  : 0;
214
216
  const value = current + amount;
215
217
  const expiry = this.ttl;
@@ -229,7 +231,9 @@ export class MemoryCache extends BaseCache {
229
231
  const fullKey = this.buildKey(key);
230
232
  const entry = this.store.get(fullKey);
231
233
  const current = entry && (!entry.expiresAt || entry.expiresAt >= Date.now())
232
- ? (typeof entry.value === 'number' ? entry.value : 0)
234
+ ? typeof entry.value === 'number'
235
+ ? entry.value
236
+ : 0
233
237
  : 0;
234
238
  const value = current - amount;
235
239
  const expiry = this.ttl;
@@ -248,7 +252,7 @@ export class MemoryCache extends BaseCache {
248
252
  return {
249
253
  isAlive: true,
250
254
  adapter: 'memory',
251
- timestamp: new Date()
255
+ timestamp: new Date(),
252
256
  };
253
257
  }
254
258
  /**
@@ -1,5 +1,5 @@
1
- import type { RedisCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, RedisCacheConfig } from '../../types';
3
3
  /**
4
4
  * Redis cache adapter
5
5
  */
@@ -17,7 +17,10 @@ export class RedisCache extends BaseCache {
17
17
  async connect() {
18
18
  try {
19
19
  const cluster = this.redisConfig.cluster;
20
- const hasCluster = cluster && (Array.isArray(cluster) ? cluster.length > 0 : cluster.nodes?.length > 0);
20
+ const hasCluster = cluster &&
21
+ (Array.isArray(cluster)
22
+ ? cluster.length > 0
23
+ : cluster.nodes?.length > 0);
21
24
  if (hasCluster && cluster) {
22
25
  // Cluster mode
23
26
  let nodes = [];
@@ -28,7 +31,9 @@ export class RedisCache extends BaseCache {
28
31
  nodes = cluster.nodes;
29
32
  }
30
33
  this.client = createCluster({
31
- rootNodes: nodes.map(node => ({ url: `redis://${node.host}:${node.port}` }))
34
+ rootNodes: nodes.map((node) => ({
35
+ url: `redis://${node.host}:${node.port}`,
36
+ })),
32
37
  });
33
38
  }
34
39
  else {
@@ -36,7 +41,7 @@ export class RedisCache extends BaseCache {
36
41
  const options = {
37
42
  host: this.redisConfig.host ?? 'localhost',
38
43
  port: this.redisConfig.port ?? 6379,
39
- db: this.redisConfig.db ?? 0
44
+ db: this.redisConfig.db ?? 0,
40
45
  };
41
46
  if (this.redisConfig.username) {
42
47
  options.username = this.redisConfig.username;
@@ -156,7 +161,7 @@ export class RedisCache extends BaseCache {
156
161
  else {
157
162
  // Clear all keys only in single-instance mode
158
163
  const client = this.client;
159
- if (client.flushDb) {
164
+ if (client && typeof client.flushDb === 'function') {
160
165
  await client.flushDb();
161
166
  }
162
167
  else {
@@ -174,7 +179,7 @@ export class RedisCache extends BaseCache {
174
179
  async getMultiple(keys) {
175
180
  try {
176
181
  await this.ensureConnected();
177
- const fullKeys = keys.map(k => this.buildKey(k));
182
+ const fullKeys = keys.map((k) => this.buildKey(k));
178
183
  const values = await this.client.mGet(fullKeys);
179
184
  const result = {};
180
185
  keys.forEach((key, index) => {
@@ -232,7 +237,7 @@ export class RedisCache extends BaseCache {
232
237
  async deleteMultiple(keys) {
233
238
  try {
234
239
  await this.ensureConnected();
235
- const fullKeys = keys.map(k => this.buildKey(k));
240
+ const fullKeys = keys.map((k) => this.buildKey(k));
236
241
  const result = await this.client.del(fullKeys);
237
242
  this.stats.deletes += result;
238
243
  return result;
@@ -274,11 +279,19 @@ export class RedisCache extends BaseCache {
274
279
  try {
275
280
  await this.ensureConnected();
276
281
  // Use sendCommand which works for both single and cluster
277
- await this.client.sendCommand(['PING']);
282
+ // `sendCommand` exists on both single and cluster clients in runtime; cast narrowly for the call
283
+ if (this.client &&
284
+ typeof this.client.sendCommand === 'function') {
285
+ await this.client.sendCommand(['PING']);
286
+ }
287
+ else if (this.client &&
288
+ typeof this.client.ping === 'function') {
289
+ await this.client.ping();
290
+ }
278
291
  return {
279
292
  isAlive: true,
280
293
  adapter: 'redis',
281
- timestamp: new Date()
294
+ timestamp: new Date(),
282
295
  };
283
296
  }
284
297
  catch (err) {
@@ -286,7 +299,7 @@ export class RedisCache extends BaseCache {
286
299
  isAlive: false,
287
300
  adapter: 'redis',
288
301
  timestamp: new Date(),
289
- error: err.message
302
+ error: err.message,
290
303
  };
291
304
  }
292
305
  }
@@ -1,4 +1,4 @@
1
- import type { CacheConfig, CacheStats, HealthCheckResponse, BatchResult } from '../types';
1
+ import type { BatchResult, CacheConfig, CacheStats, HealthCheckResponse } from '../types';
2
2
  import type { ICache } from './interfaces';
3
3
  /**
4
4
  * Abstract base class for all cache adapters
@@ -9,7 +9,7 @@ export class BaseCache {
9
9
  hits: 0,
10
10
  misses: 0,
11
11
  sets: 0,
12
- deletes: 0
12
+ deletes: 0,
13
13
  };
14
14
  this.namespace = config.namespace ? `${config.namespace}:` : '';
15
15
  this.ttl = config.ttl ?? 3600; // 1 hour default
@@ -74,7 +74,7 @@ export class BaseCache {
74
74
  hits: 0,
75
75
  misses: 0,
76
76
  sets: 0,
77
- deletes: 0
77
+ deletes: 0,
78
78
  };
79
79
  }
80
80
  /**
@@ -1,6 +1,6 @@
1
- import { RedisCache } from '../adapters/redis';
2
1
  import { MemcacheCache } from '../adapters/memcache';
3
2
  import { MemoryCache } from '../adapters/memory';
3
+ import { RedisCache } from '../adapters/redis';
4
4
  import { CacheError } from '../errors';
5
5
  /**
6
6
  * Factory for creating cache instances
@@ -47,7 +47,7 @@ export class CacheFactory {
47
47
  return new MemoryCache({
48
48
  adapter: 'memory',
49
49
  namespace: config.namespace,
50
- ttl: config.ttl
50
+ ttl: config.ttl,
51
51
  });
52
52
  }
53
53
  // No fallback, throw error
@@ -1,4 +1,4 @@
1
- import type { CacheStats, HealthCheckResponse, BatchResult } from '../../types';
1
+ import type { BatchResult, CacheStats, HealthCheckResponse } from '../../types';
2
2
  /**
3
3
  * Main cache interface - defines all cache operations
4
4
  */
@@ -1,4 +1,4 @@
1
- export type { CacheConfig, RedisCacheConfig, MemcacheCacheConfig, MemoryCacheConfig, SessionData, SessionOptions, CacheStats, HealthCheckResponse, BatchResult, CacheEntry } from './types';
1
+ export type { CacheConfig, RedisCacheConfig, MemcacheCacheConfig, MemoryCacheConfig, SessionData, SessionOptions, CacheStats, HealthCheckResponse, BatchResult, CacheEntry, } from './types';
2
2
  export { CacheError } from './errors';
3
3
  export type { ICache, ISession } from './core/interfaces';
4
4
  export { BaseCache } from './core/BaseCache';
@@ -7,4 +7,4 @@ export { MemcacheCache } from './adapters/memcache';
7
7
  export { MemoryCache } from './adapters/memory';
8
8
  export { CacheFactory } from './core/factory';
9
9
  export { SessionStore } from './session';
10
- export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware } from './middleware/express';
10
+ export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware, } from './middleware/express';
package/dist/esm/index.js CHANGED
@@ -11,4 +11,4 @@ export { CacheFactory } from './core/factory';
11
11
  // Session
12
12
  export { SessionStore } from './session';
13
13
  // Middleware
14
- export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware } from './middleware/express';
14
+ export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware, } from './middleware/express';
@@ -1,6 +1,6 @@
1
- import type { Request, Response, NextFunction } from 'express';
1
+ import type { NextFunction, Request, Response } from 'express';
2
2
  import type { ICache } from '../../core/interfaces';
3
- import { SessionStore } from '../../session/SessionStore';
3
+ import type { SessionStore } from '../../session/SessionStore';
4
4
  /**
5
5
  * Express middleware for session management using cache
6
6
  */
@@ -12,6 +12,7 @@ export function cacheSessionMiddleware(sessionStore, options) {
12
12
  // Fetch session data and extend expiry
13
13
  const sessionData = await sessionStore.getAndExtend(sessionId);
14
14
  if (sessionData) {
15
+ ;
15
16
  req[sessionDataKey] = sessionData;
16
17
  }
17
18
  }
@@ -39,7 +40,7 @@ export function cacheHealthCheckMiddleware(cache, endpoint = '/.health/cache') {
39
40
  isAlive: false,
40
41
  adapter: 'unknown',
41
42
  timestamp: new Date(),
42
- error: err.message
43
+ error: err.message,
43
44
  });
44
45
  });
45
46
  return;
@@ -53,7 +54,9 @@ export function cacheHealthCheckMiddleware(cache, endpoint = '/.health/cache') {
53
54
  export function cacheResponseMiddleware(cache, options) {
54
55
  const ttl = options?.ttl ?? 300; // 5 minutes default
55
56
  const keyPrefix = options?.keyPrefix ?? 'response:';
56
- const excludeStatusCodes = options?.excludeStatusCodes ?? [300, 301, 302, 303, 304, 307, 308, 404, 500, 501, 502, 503];
57
+ const excludeStatusCodes = options?.excludeStatusCodes ?? [
58
+ 300, 301, 302, 303, 304, 307, 308, 404, 500, 501, 502, 503,
59
+ ];
57
60
  return (req, res, next) => {
58
61
  // Only cache GET requests
59
62
  if (req.method !== 'GET') {
@@ -77,10 +80,23 @@ export function cacheResponseMiddleware(cache, options) {
77
80
  if (res.statusCode >= 200 &&
78
81
  res.statusCode < 300 &&
79
82
  !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
- });
83
+ let responseData = null;
84
+ if (typeof data === 'string') {
85
+ responseData = data;
86
+ }
87
+ else {
88
+ try {
89
+ responseData = JSON.stringify(data);
90
+ }
91
+ catch (e) {
92
+ responseData = null;
93
+ }
94
+ }
95
+ if (responseData !== null) {
96
+ cache.set(cacheKey, responseData, ttl).catch((err) => {
97
+ console.error('Failed to cache response:', err);
98
+ });
99
+ }
84
100
  }
85
101
  res.set('X-Cache', 'MISS');
86
102
  return originalSend.call(this, data);
@@ -1 +1 @@
1
- export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware } from './cacheMiddleware';
1
+ export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware, } from './cacheMiddleware';
@@ -1 +1 @@
1
- export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware } from './cacheMiddleware';
1
+ export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware, } from './cacheMiddleware';
@@ -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
  }
@@ -9,7 +9,7 @@ export class SessionStore {
9
9
  this.options = {
10
10
  ttl: options.ttl ?? 3600, // 1 hour default
11
11
  serialize: options.serialize ?? ((data) => JSON.stringify(data)),
12
- deserialize: options.deserialize ?? ((data) => JSON.parse(data))
12
+ deserialize: options.deserialize ?? ((data) => JSON.parse(data)),
13
13
  };
14
14
  }
15
15
  /**
@@ -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
  }
@@ -1,5 +1,5 @@
1
- import type { MemcacheCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, MemcacheCacheConfig } from '../../types';
3
3
  /**
4
4
  * Memcache adapter
5
5
  */
@@ -1,5 +1,5 @@
1
- import type { MemoryCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, MemoryCacheConfig } from '../../types';
3
3
  /**
4
4
  * In-memory cache adapter for development and testing
5
5
  */
@@ -1,5 +1,5 @@
1
- import type { RedisCacheConfig, HealthCheckResponse } from '../../types';
2
1
  import { BaseCache } from '../../core/BaseCache';
2
+ import type { HealthCheckResponse, RedisCacheConfig } from '../../types';
3
3
  /**
4
4
  * Redis cache adapter
5
5
  */
@@ -1,4 +1,4 @@
1
- import type { CacheConfig, CacheStats, HealthCheckResponse, BatchResult } from '../types';
1
+ import type { BatchResult, CacheConfig, CacheStats, HealthCheckResponse } from '../types';
2
2
  import type { ICache } from './interfaces';
3
3
  /**
4
4
  * Abstract base class for all cache adapters
@@ -1,4 +1,4 @@
1
- import type { CacheStats, HealthCheckResponse, BatchResult } from '../../types';
1
+ import type { BatchResult, CacheStats, HealthCheckResponse } from '../../types';
2
2
  /**
3
3
  * Main cache interface - defines all cache operations
4
4
  */
@@ -1,4 +1,4 @@
1
- export type { CacheConfig, RedisCacheConfig, MemcacheCacheConfig, MemoryCacheConfig, SessionData, SessionOptions, CacheStats, HealthCheckResponse, BatchResult, CacheEntry } from './types';
1
+ export type { CacheConfig, RedisCacheConfig, MemcacheCacheConfig, MemoryCacheConfig, SessionData, SessionOptions, CacheStats, HealthCheckResponse, BatchResult, CacheEntry, } from './types';
2
2
  export { CacheError } from './errors';
3
3
  export type { ICache, ISession } from './core/interfaces';
4
4
  export { BaseCache } from './core/BaseCache';
@@ -7,4 +7,4 @@ export { MemcacheCache } from './adapters/memcache';
7
7
  export { MemoryCache } from './adapters/memory';
8
8
  export { CacheFactory } from './core/factory';
9
9
  export { SessionStore } from './session';
10
- export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware } from './middleware/express';
10
+ export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware, } from './middleware/express';
@@ -1,6 +1,6 @@
1
- import type { Request, Response, NextFunction } from 'express';
1
+ import type { NextFunction, Request, Response } from 'express';
2
2
  import type { ICache } from '../../core/interfaces';
3
- import { SessionStore } from '../../session/SessionStore';
3
+ import type { SessionStore } from '../../session/SessionStore';
4
4
  /**
5
5
  * Express middleware for session management using cache
6
6
  */
@@ -1 +1 @@
1
- export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware } from './cacheMiddleware';
1
+ export { cacheSessionMiddleware, cacheHealthCheckMiddleware, cacheResponseMiddleware, } from './cacheMiddleware';
@@ -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.1",
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",