@node-ts-cache/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +763 -0
  3. package/dist/decorator/cache.decorator.d.ts +7 -0
  4. package/dist/decorator/cache.decorator.js +63 -0
  5. package/dist/decorator/cache.decorator.js.map +1 -0
  6. package/dist/decorator/multicache.decorator.d.ts +8 -0
  7. package/dist/decorator/multicache.decorator.js +110 -0
  8. package/dist/decorator/multicache.decorator.js.map +1 -0
  9. package/dist/decorator/synccache.decorator.d.ts +3 -0
  10. package/dist/decorator/synccache.decorator.js +43 -0
  11. package/dist/decorator/synccache.decorator.js.map +1 -0
  12. package/dist/index.d.ts +8 -0
  13. package/dist/index.js +7 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/storage/fs/fs.json.storage.d.ts +11 -0
  16. package/dist/storage/fs/fs.json.storage.js +47 -0
  17. package/dist/storage/fs/fs.json.storage.js.map +1 -0
  18. package/dist/storage/fs/index.d.ts +1 -0
  19. package/dist/storage/fs/index.js +2 -0
  20. package/dist/storage/fs/index.js.map +1 -0
  21. package/dist/storage/memory/index.d.ts +1 -0
  22. package/dist/storage/memory/index.js +2 -0
  23. package/dist/storage/memory/index.js.map +1 -0
  24. package/dist/storage/memory/memory.storage.d.ts +7 -0
  25. package/dist/storage/memory/memory.storage.js +15 -0
  26. package/dist/storage/memory/memory.storage.js.map +1 -0
  27. package/dist/strategy/caching/abstract.base.strategy.d.ts +8 -0
  28. package/dist/strategy/caching/abstract.base.strategy.js +6 -0
  29. package/dist/strategy/caching/abstract.base.strategy.js.map +1 -0
  30. package/dist/strategy/caching/expiration.strategy.d.ts +11 -0
  31. package/dist/strategy/caching/expiration.strategy.js +43 -0
  32. package/dist/strategy/caching/expiration.strategy.js.map +1 -0
  33. package/dist/strategy/key/json.stringify.strategy.d.ts +4 -0
  34. package/dist/strategy/key/json.stringify.strategy.js +6 -0
  35. package/dist/strategy/key/json.stringify.strategy.js.map +1 -0
  36. package/dist/types/cache.types.d.ts +57 -0
  37. package/dist/types/cache.types.js +2 -0
  38. package/dist/types/cache.types.js.map +1 -0
  39. package/dist/types/key.strategy.types.d.ts +6 -0
  40. package/dist/types/key.strategy.types.js +2 -0
  41. package/dist/types/key.strategy.types.js.map +1 -0
  42. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,763 @@
1
+ # @node-ts-cache/core
2
+
3
+ [![npm](https://img.shields.io/npm/v/@node-ts-cache/core.svg)](https://www.npmjs.org/package/@node-ts-cache/core)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js CI](https://github.com/simllll/node-ts-cache/actions/workflows/test.yml/badge.svg)](https://github.com/simllll/node-ts-cache/actions/workflows/test.yml)
6
+
7
+ Simple and extensible caching module for TypeScript/Node.js with decorator support.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Quick Start](#quick-start)
13
+ - [Decorators](#decorators)
14
+ - [@Cache](#cache)
15
+ - [@SyncCache](#synccache)
16
+ - [@MultiCache](#multicache)
17
+ - [Direct API Usage](#direct-api-usage)
18
+ - [Strategies](#strategies)
19
+ - [ExpirationStrategy](#expirationstrategy)
20
+ - [Storages](#storages)
21
+ - [Built-in Storages](#built-in-storages)
22
+ - [Additional Storages](#additional-storages)
23
+ - [Custom Key Strategies](#custom-key-strategies)
24
+ - [Interface Definitions](#interface-definitions)
25
+ - [Advanced Usage](#advanced-usage)
26
+ - [Environment Variables](#environment-variables)
27
+ - [Testing](#testing)
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install @node-ts-cache/core
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```typescript
38
+ import { Cache, ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core';
39
+
40
+ // Create a caching strategy with in-memory storage
41
+ const cacheStrategy = new ExpirationStrategy(new MemoryStorage());
42
+
43
+ class UserService {
44
+ @Cache(cacheStrategy, { ttl: 60 })
45
+ async getUser(id: string): Promise<User> {
46
+ // Expensive operation - result will be cached for 60 seconds
47
+ return await database.findUser(id);
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## Decorators
53
+
54
+ ### @Cache
55
+
56
+ Caches async method responses. The cache key is generated from the class name, method name, and stringified arguments.
57
+
58
+ **Signature:**
59
+
60
+ ```typescript
61
+ @Cache(strategy: IAsynchronousCacheType | ISynchronousCacheType, options?: ExpirationOptions, keyStrategy?: IAsyncKeyStrategy)
62
+ ```
63
+
64
+ **Parameters:**
65
+
66
+ - `strategy` - A caching strategy instance (e.g., `ExpirationStrategy`)
67
+ - `options` - Options passed to the strategy (see [ExpirationStrategy](#expirationstrategy))
68
+ - `keyStrategy` - Optional custom key generation strategy
69
+
70
+ **Important:** `@Cache` always returns a Promise, even for synchronous methods, because cache operations may be asynchronous.
71
+
72
+ **Example:**
73
+
74
+ ```typescript
75
+ import { Cache, ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core';
76
+
77
+ const strategy = new ExpirationStrategy(new MemoryStorage());
78
+
79
+ class ProductService {
80
+ @Cache(strategy, { ttl: 300 })
81
+ async getProduct(id: string): Promise<Product> {
82
+ console.log('Fetching product from database...');
83
+ return await db.products.findById(id);
84
+ }
85
+
86
+ @Cache(strategy, { ttl: 3600, isCachedForever: false })
87
+ async getCategories(): Promise<Category[]> {
88
+ return await db.categories.findAll();
89
+ }
90
+ }
91
+
92
+ // Usage
93
+ const service = new ProductService();
94
+
95
+ // First call - hits database
96
+ const product1 = await service.getProduct('123');
97
+
98
+ // Second call with same args - returns cached result
99
+ const product2 = await service.getProduct('123');
100
+
101
+ // Different args - hits database again
102
+ const product3 = await service.getProduct('456');
103
+ ```
104
+
105
+ ### @SyncCache
106
+
107
+ Caches synchronous method responses without converting to Promises. Use this when your storage is synchronous (like `MemoryStorage` or `LRUStorage`).
108
+
109
+ **Signature:**
110
+
111
+ ```typescript
112
+ @SyncCache(strategy: ISynchronousCacheType, options?: ExpirationOptions, keyStrategy?: ISyncKeyStrategy)
113
+ ```
114
+
115
+ **Example:**
116
+
117
+ ```typescript
118
+ import { SyncCache, ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core';
119
+
120
+ const strategy = new ExpirationStrategy(new MemoryStorage());
121
+
122
+ class ConfigService {
123
+ @SyncCache(strategy, { ttl: 60 })
124
+ getConfig(key: string): ConfigValue {
125
+ // Expensive computation
126
+ return computeConfig(key);
127
+ }
128
+ }
129
+
130
+ // Usage - returns value directly, not a Promise
131
+ const config = new ConfigService().getConfig('theme');
132
+ ```
133
+
134
+ ### @MultiCache
135
+
136
+ Enables multi-tier caching with batch operations. Useful for:
137
+
138
+ - Caching array-based lookups efficiently
139
+ - Implementing local + remote cache tiers
140
+ - Reducing database queries for batch operations
141
+
142
+ **Signature:**
143
+
144
+ ```typescript
145
+ @MultiCache(
146
+ strategies: Array<IMultiSynchronousCacheType | IMultiIAsynchronousCacheType>,
147
+ parameterIndex: number,
148
+ cacheKeyFn?: (element: any) => string,
149
+ options?: ExpirationOptions
150
+ )
151
+ ```
152
+
153
+ **Parameters:**
154
+
155
+ - `strategies` - Array of cache strategies, checked in order (first = fastest, last = slowest)
156
+ - `parameterIndex` - Index of the array parameter in the method signature
157
+ - `cacheKeyFn` - Optional function to generate cache keys for each element
158
+ - `options` - Options passed to strategies
159
+
160
+ **Example:**
161
+
162
+ ```typescript
163
+ import { MultiCache, ExpirationStrategy } from '@node-ts-cache/core';
164
+ import NodeCacheStorage from '@node-ts-cache/node-cache-storage';
165
+ import RedisIOStorage from '@node-ts-cache/ioredis-storage';
166
+
167
+ // Local cache (fastest) -> Redis (shared) -> Database (slowest)
168
+ const localCache = new ExpirationStrategy(new NodeCacheStorage());
169
+ const redisCache = new RedisIOStorage(() => redisClient, { maxAge: 3600 });
170
+
171
+ class UserService {
172
+ @MultiCache([localCache, redisCache], 0, userId => `user:${userId}`, { ttl: 300 })
173
+ async getUsersByIds(userIds: string[]): Promise<User[]> {
174
+ // This only runs for IDs not found in any cache
175
+ // IMPORTANT: Return results in the same order as input IDs
176
+ return await db.users.findByIds(userIds);
177
+ }
178
+ }
179
+
180
+ // Usage
181
+ const service = new UserService();
182
+
183
+ // First call - checks local, then redis, then hits database
184
+ const users = await service.getUsersByIds(['1', '2', '3']);
185
+
186
+ // Second call - user 1 & 2 from local cache, user 4 from database
187
+ const moreUsers = await service.getUsersByIds(['1', '2', '4']);
188
+ ```
189
+
190
+ **Return Value Requirements:**
191
+
192
+ - Return an array with the same length and order as the input array
193
+ - Use `null` for entries that exist but are empty
194
+ - Use `undefined` for entries that should be re-queried next time
195
+
196
+ ## Direct API Usage
197
+
198
+ You can use the caching strategy directly without decorators:
199
+
200
+ ```typescript
201
+ import { ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core';
202
+
203
+ const cache = new ExpirationStrategy(new MemoryStorage());
204
+
205
+ class DataService {
206
+ async getData(key: string): Promise<Data> {
207
+ // Check cache first
208
+ const cached = await cache.getItem<Data>(key);
209
+ if (cached !== undefined) {
210
+ return cached;
211
+ }
212
+
213
+ // Fetch fresh data
214
+ const data = await fetchData(key);
215
+
216
+ // Store in cache
217
+ await cache.setItem(key, data, { ttl: 300 });
218
+
219
+ return data;
220
+ }
221
+
222
+ async invalidate(key: string): Promise<void> {
223
+ await cache.setItem(key, undefined);
224
+ }
225
+
226
+ async clearAll(): Promise<void> {
227
+ await cache.clear();
228
+ }
229
+ }
230
+ ```
231
+
232
+ ## Strategies
233
+
234
+ ### ExpirationStrategy
235
+
236
+ Time-based cache expiration strategy. Items are automatically invalidated after a specified TTL (Time To Live).
237
+
238
+ **Constructor:**
239
+
240
+ ```typescript
241
+ new ExpirationStrategy(storage: IAsynchronousCacheType | ISynchronousCacheType)
242
+ ```
243
+
244
+ **Options:**
245
+
246
+ | Option | Type | Default | Description |
247
+ | ----------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
248
+ | `ttl` | `number` | `60` | Time to live in **seconds** |
249
+ | `isLazy` | `boolean` | `true` | If `true`, items are deleted when accessed after expiration. If `false`, items are deleted automatically via `setTimeout` |
250
+ | `isCachedForever` | `boolean` | `false` | If `true`, items never expire (ignores `ttl`) |
251
+
252
+ **Example:**
253
+
254
+ ```typescript
255
+ import { ExpirationStrategy, MemoryStorage } from '@node-ts-cache/core';
256
+
257
+ const storage = new MemoryStorage();
258
+ const strategy = new ExpirationStrategy(storage);
259
+
260
+ // Cache for 5 minutes with lazy expiration
261
+ await strategy.setItem('key1', 'value', { ttl: 300, isLazy: true });
262
+
263
+ // Cache forever
264
+ await strategy.setItem('key2', 'value', { isCachedForever: true });
265
+
266
+ // Cache for 10 seconds with eager expiration (auto-delete)
267
+ await strategy.setItem('key3', 'value', { ttl: 10, isLazy: false });
268
+ ```
269
+
270
+ **Lazy vs Eager Expiration:**
271
+
272
+ - **Lazy (`isLazy: true`)**: Expired items remain in storage until accessed. Memory is freed on read. Better for large caches.
273
+ - **Eager (`isLazy: false`)**: Items are deleted via `setTimeout` after TTL. Frees memory automatically but uses timers.
274
+
275
+ ## Storages
276
+
277
+ ### Built-in Storages
278
+
279
+ These storages are included in the core package:
280
+
281
+ #### MemoryStorage
282
+
283
+ Simple in-memory storage using a JavaScript object. Best for development and simple use cases.
284
+
285
+ ```typescript
286
+ import { MemoryStorage, ExpirationStrategy } from '@node-ts-cache/core';
287
+
288
+ const storage = new MemoryStorage();
289
+ const strategy = new ExpirationStrategy(storage);
290
+ ```
291
+
292
+ **Characteristics:**
293
+
294
+ - Synchronous operations
295
+ - No external dependencies
296
+ - Data lost on process restart
297
+ - No size limits (can cause memory issues)
298
+
299
+ #### FsJsonStorage
300
+
301
+ File-based storage that persists cache to a JSON file. Useful for persistent local caching.
302
+
303
+ ```typescript
304
+ import { FsJsonStorage, ExpirationStrategy } from '@node-ts-cache/core';
305
+
306
+ const storage = new FsJsonStorage('/tmp/cache.json');
307
+ const strategy = new ExpirationStrategy(storage);
308
+ ```
309
+
310
+ **Characteristics:**
311
+
312
+ - Asynchronous operations
313
+ - Survives process restarts
314
+ - Slower than memory storage
315
+ - Good for development/single-instance deployments
316
+
317
+ ### Additional Storages
318
+
319
+ Install these separately based on your needs:
320
+
321
+ #### NodeCacheStorage
322
+
323
+ Wrapper for [node-cache](https://www.npmjs.com/package/node-cache) - a simple in-memory cache with TTL support.
324
+
325
+ ```bash
326
+ npm install @node-ts-cache/node-cache-storage
327
+ ```
328
+
329
+ ```typescript
330
+ import { ExpirationStrategy } from '@node-ts-cache/core';
331
+ import NodeCacheStorage from '@node-ts-cache/node-cache-storage';
332
+
333
+ const storage = new NodeCacheStorage({
334
+ stdTTL: 100, // Default TTL in seconds
335
+ checkperiod: 120, // Cleanup interval in seconds
336
+ maxKeys: 1000 // Maximum number of keys
337
+ });
338
+ const strategy = new ExpirationStrategy(storage);
339
+ ```
340
+
341
+ **Characteristics:**
342
+
343
+ - Synchronous operations
344
+ - Supports multi-get/set operations
345
+ - Built-in TTL and cleanup
346
+ - Good for production single-instance apps
347
+
348
+ #### LRUStorage
349
+
350
+ Wrapper for [lru-cache](https://www.npmjs.com/package/lru-cache) - Least Recently Used cache with automatic eviction.
351
+
352
+ ```bash
353
+ npm install @node-ts-cache/lru-storage
354
+ ```
355
+
356
+ ```typescript
357
+ import { ExpirationStrategy } from '@node-ts-cache/core';
358
+ import LRUStorage from '@node-ts-cache/lru-storage';
359
+
360
+ const storage = new LRUStorage({
361
+ max: 500, // Maximum number of items
362
+ ttl: 300 // TTL in seconds
363
+ });
364
+ const strategy = new ExpirationStrategy(storage);
365
+ ```
366
+
367
+ **Characteristics:**
368
+
369
+ - Synchronous operations
370
+ - Automatic eviction when max size reached
371
+ - Memory-safe with bounded size
372
+ - Supports multi-get/set operations
373
+
374
+ **Note:** LRU cache has its own TTL (`ttl` in seconds). When using with `ExpirationStrategy`, both TTLs apply. Set LRU `ttl` higher than your strategy TTL or use `isCachedForever` in the strategy.
375
+
376
+ #### RedisStorage
377
+
378
+ Redis storage using the legacy `redis` package (v3.x). For new projects, consider using `RedisIOStorage` instead.
379
+
380
+ ```bash
381
+ npm install @node-ts-cache/redis-storage
382
+ ```
383
+
384
+ ```typescript
385
+ import { ExpirationStrategy } from '@node-ts-cache/core';
386
+ import RedisStorage from '@node-ts-cache/redis-storage';
387
+
388
+ const storage = new RedisStorage({
389
+ host: 'localhost',
390
+ port: 6379,
391
+ password: 'optional'
392
+ });
393
+ const strategy = new ExpirationStrategy(storage);
394
+ ```
395
+
396
+ **Characteristics:**
397
+
398
+ - Asynchronous operations
399
+ - Uses legacy `redis` package with Bluebird promises
400
+ - Shared cache across multiple instances
401
+ - No compression support
402
+
403
+ #### RedisIOStorage
404
+
405
+ Modern Redis storage using [ioredis](https://github.com/redis/ioredis) with optional Snappy compression.
406
+
407
+ ```bash
408
+ npm install @node-ts-cache/ioredis-storage
409
+ ```
410
+
411
+ ```typescript
412
+ import { ExpirationStrategy } from '@node-ts-cache/core';
413
+ import RedisIOStorage from '@node-ts-cache/ioredis-storage';
414
+ import Redis from 'ioredis';
415
+
416
+ const redisClient = new Redis({
417
+ host: 'localhost',
418
+ port: 6379
419
+ });
420
+
421
+ // Basic usage
422
+ const storage = new RedisIOStorage(
423
+ () => redisClient,
424
+ { maxAge: 3600 } // TTL in seconds
425
+ );
426
+
427
+ // With compression (reduces bandwidth, increases CPU usage)
428
+ const compressedStorage = new RedisIOStorage(() => redisClient, { maxAge: 3600, compress: true });
429
+
430
+ // With error handler (non-blocking writes)
431
+ storage.onError(error => {
432
+ console.error('Redis error:', error);
433
+ });
434
+
435
+ const strategy = new ExpirationStrategy(storage);
436
+ ```
437
+
438
+ **Characteristics:**
439
+
440
+ - Asynchronous operations
441
+ - Supports multi-get/set operations
442
+ - Optional Snappy compression
443
+ - Modern ioredis client
444
+ - Custom error handler support
445
+ - Can bypass ExpirationStrategy TTL (uses Redis native TTL)
446
+
447
+ **Constructor Options:**
448
+ | Option | Type | Default | Description |
449
+ |--------|------|---------|-------------|
450
+ | `maxAge` | `number` | `86400` | TTL in seconds (used by Redis SETEX) |
451
+ | `compress` | `boolean` | `false` | Enable Snappy compression |
452
+
453
+ #### LRUWithRedisStorage
454
+
455
+ Two-tier caching: fast local LRU cache with Redis fallback. Provides the best of both worlds.
456
+
457
+ ```bash
458
+ npm install @node-ts-cache/lru-redis-storage
459
+ ```
460
+
461
+ ```typescript
462
+ import { ExpirationStrategy } from '@node-ts-cache/core';
463
+ import LRUWithRedisStorage from '@node-ts-cache/lru-redis-storage';
464
+ import Redis from 'ioredis';
465
+
466
+ const redisClient = new Redis();
467
+
468
+ const storage = new LRUWithRedisStorage(
469
+ { max: 1000 }, // LRU options
470
+ () => redisClient // Redis client factory
471
+ );
472
+ const strategy = new ExpirationStrategy(storage);
473
+ ```
474
+
475
+ **Characteristics:**
476
+
477
+ - Asynchronous operations
478
+ - Local LRU for hot data
479
+ - Redis fallback for cache misses
480
+ - Reduces Redis round-trips
481
+ - Good for high-traffic applications
482
+
483
+ ## Custom Key Strategies
484
+
485
+ By default, cache keys are generated as: `ClassName:methodName:JSON.stringify(args)`
486
+
487
+ You can implement custom key strategies for different needs:
488
+
489
+ ### Synchronous Key Strategy
490
+
491
+ ```typescript
492
+ import { Cache, ExpirationStrategy, MemoryStorage, ISyncKeyStrategy } from '@node-ts-cache/core';
493
+
494
+ class CustomKeyStrategy implements ISyncKeyStrategy {
495
+ getKey(className: string, methodName: string, args: any[]): string | undefined {
496
+ // Return undefined to skip caching for this call
497
+ if (args[0] === 'skip') {
498
+ return undefined;
499
+ }
500
+
501
+ // Custom key format
502
+ return `${className}::${methodName}::${args.join('-')}`;
503
+ }
504
+ }
505
+
506
+ const strategy = new ExpirationStrategy(new MemoryStorage());
507
+ const keyStrategy = new CustomKeyStrategy();
508
+
509
+ class MyService {
510
+ @Cache(strategy, { ttl: 60 }, keyStrategy)
511
+ async getData(id: string): Promise<Data> {
512
+ return fetchData(id);
513
+ }
514
+ }
515
+ ```
516
+
517
+ ### Asynchronous Key Strategy
518
+
519
+ For key generation that requires async operations (e.g., fetching user context):
520
+
521
+ ```typescript
522
+ import { Cache, ExpirationStrategy, MemoryStorage, IAsyncKeyStrategy } from '@node-ts-cache/core';
523
+
524
+ class AsyncKeyStrategy implements IAsyncKeyStrategy {
525
+ async getKey(className: string, methodName: string, args: any[]): Promise<string | undefined> {
526
+ // Async operation to build key
527
+ const userId = await getCurrentUserId();
528
+ return `${userId}:${className}:${methodName}:${JSON.stringify(args)}`;
529
+ }
530
+ }
531
+ ```
532
+
533
+ ## Interface Definitions
534
+
535
+ ### Storage Interfaces
536
+
537
+ ```typescript
538
+ /**
539
+ * Cache entry structure stored in backends
540
+ */
541
+ interface ICacheEntry {
542
+ content: any; // The cached value
543
+ meta: any; // Metadata (e.g., TTL, createdAt)
544
+ }
545
+
546
+ /**
547
+ * Asynchronous storage for single items
548
+ */
549
+ interface IAsynchronousCacheType<C = ICacheEntry> {
550
+ /** Retrieve an item by key. Returns undefined if not found. */
551
+ getItem<T>(key: string): Promise<T | undefined>;
552
+
553
+ /** Store an item. Pass undefined as content to delete. */
554
+ setItem(key: string, content: C | undefined, options?: any): Promise<void>;
555
+
556
+ /** Clear all items from the cache. */
557
+ clear(): Promise<void>;
558
+ }
559
+
560
+ /**
561
+ * Synchronous storage for single items
562
+ */
563
+ interface ISynchronousCacheType<C = ICacheEntry> {
564
+ /** Retrieve an item by key. Returns undefined if not found. */
565
+ getItem<T>(key: string): T | undefined;
566
+
567
+ /** Store an item. Pass undefined as content to delete. */
568
+ setItem(key: string, content: C | undefined, options?: any): void;
569
+
570
+ /** Clear all items from the cache. */
571
+ clear(): void;
572
+ }
573
+
574
+ /**
575
+ * Asynchronous storage with batch operations
576
+ */
577
+ interface IMultiIAsynchronousCacheType<C = ICacheEntry> {
578
+ /** Retrieve multiple items by keys. */
579
+ getItems<T>(keys: string[]): Promise<{ [key: string]: T | undefined }>;
580
+
581
+ /** Store multiple items at once. */
582
+ setItems(values: { key: string; content: C | undefined }[], options?: any): Promise<void>;
583
+
584
+ /** Clear all items from the cache. */
585
+ clear(): Promise<void>;
586
+ }
587
+
588
+ /**
589
+ * Synchronous storage with batch operations
590
+ */
591
+ interface IMultiSynchronousCacheType<C = ICacheEntry> {
592
+ /** Retrieve multiple items by keys. */
593
+ getItems<T>(keys: string[]): { [key: string]: T | undefined };
594
+
595
+ /** Store multiple items at once. */
596
+ setItems(values: { key: string; content: C | undefined }[], options?: any): void;
597
+
598
+ /** Clear all items from the cache. */
599
+ clear(): void;
600
+ }
601
+ ```
602
+
603
+ ### Key Strategy Interfaces
604
+
605
+ ```typescript
606
+ /**
607
+ * Synchronous key generation strategy
608
+ */
609
+ interface ISyncKeyStrategy {
610
+ /**
611
+ * Generate a cache key from method context
612
+ * @param className - Name of the class containing the method
613
+ * @param methodName - Name of the cached method
614
+ * @param args - Arguments passed to the method
615
+ * @returns Cache key string, or undefined to skip caching
616
+ */
617
+ getKey(className: string, methodName: string, args: any[]): string | undefined;
618
+ }
619
+
620
+ /**
621
+ * Asynchronous key generation strategy
622
+ */
623
+ interface IAsyncKeyStrategy {
624
+ /**
625
+ * Generate a cache key from method context (can be async)
626
+ * @param className - Name of the class containing the method
627
+ * @param methodName - Name of the cached method
628
+ * @param args - Arguments passed to the method
629
+ * @returns Cache key string, or undefined to skip caching
630
+ */
631
+ getKey(
632
+ className: string,
633
+ methodName: string,
634
+ args: any[]
635
+ ): Promise<string | undefined> | string | undefined;
636
+ }
637
+ ```
638
+
639
+ ### ExpirationStrategy Options
640
+
641
+ ```typescript
642
+ interface ExpirationOptions {
643
+ /** Time to live in seconds (default: 60) */
644
+ ttl?: number;
645
+
646
+ /** If true, delete on access after expiration. If false, delete via setTimeout (default: true) */
647
+ isLazy?: boolean;
648
+
649
+ /** If true, cache forever ignoring TTL (default: false) */
650
+ isCachedForever?: boolean;
651
+ }
652
+ ```
653
+
654
+ ## Advanced Usage
655
+
656
+ ### Call Deduplication
657
+
658
+ The `@Cache` decorator automatically deduplicates concurrent calls with the same cache key. If multiple calls are made before the first one completes, they all receive the same result:
659
+
660
+ ```typescript
661
+ class DataService {
662
+ @Cache(strategy, { ttl: 60 })
663
+ async fetchData(id: string): Promise<Data> {
664
+ console.log('Fetching...'); // Only logged once
665
+ return await slowApiCall(id);
666
+ }
667
+ }
668
+
669
+ const service = new DataService();
670
+
671
+ // All three calls share the same pending promise
672
+ const [a, b, c] = await Promise.all([
673
+ service.fetchData('123'),
674
+ service.fetchData('123'),
675
+ service.fetchData('123')
676
+ ]);
677
+ // "Fetching..." is logged only once, all three get the same result
678
+ ```
679
+
680
+ ### Handling Undefined vs Null
681
+
682
+ The cache distinguishes between:
683
+
684
+ - `undefined`: No value found in cache, or value should not be cached
685
+ - `null`: Explicit null value that is cached
686
+
687
+ ```typescript
688
+ class UserService {
689
+ @Cache(strategy, { ttl: 60 })
690
+ async findUser(id: string): Promise<User | null> {
691
+ const user = await db.findUser(id);
692
+ // Return null for non-existent users to cache the "not found" result
693
+ // Return undefined would cause re-fetching on every call
694
+ return user ?? null;
695
+ }
696
+ }
697
+ ```
698
+
699
+ ### Error Handling
700
+
701
+ Cache errors are logged but don't break the application flow. If caching fails, the method executes normally:
702
+
703
+ ```typescript
704
+ // Cache read/write failures are logged as warnings:
705
+ // "@node-ts-cache/core: reading cache failed [key] [error]"
706
+ // "@node-ts-cache/core: writing result to cache failed [key] [error]"
707
+
708
+ // For RedisIOStorage, you can add a custom error handler:
709
+ storage.onError(error => {
710
+ metrics.incrementCacheError();
711
+ logger.error('Cache error', error);
712
+ });
713
+ ```
714
+
715
+ ## Environment Variables
716
+
717
+ | Variable | Description |
718
+ | ------------------------- | -------------------------------------------------------------------------- |
719
+ | `DISABLE_CACHE_DECORATOR` | Set to any value to disable all `@Cache` decorators (useful for debugging) |
720
+
721
+ ## Testing
722
+
723
+ ```bash
724
+ # Run all tests
725
+ npm test
726
+
727
+ # Run tests in watch mode
728
+ npm run tdd
729
+
730
+ # Run tests with debugger
731
+ npm run tdd-debug-brk
732
+ ```
733
+
734
+ ## API Reference
735
+
736
+ ### Exports
737
+
738
+ ```typescript
739
+ // Decorators
740
+ export { Cache } from './decorator/cache.decorator';
741
+ export { SyncCache } from './decorator/synccache.decorator';
742
+ export { MultiCache } from './decorator/multicache.decorator';
743
+
744
+ // Strategies
745
+ export { ExpirationStrategy } from './strategy/caching/expiration.strategy';
746
+
747
+ // Built-in Storages
748
+ export { MemoryStorage } from './storage/memory';
749
+ export { FsJsonStorage } from './storage/fs';
750
+
751
+ // Interfaces
752
+ export {
753
+ IAsynchronousCacheType,
754
+ ISynchronousCacheType,
755
+ IMultiIAsynchronousCacheType,
756
+ IMultiSynchronousCacheType
757
+ } from './types/cache.types';
758
+ export { ISyncKeyStrategy, IAsyncKeyStrategy } from './types/key.strategy.types';
759
+ ```
760
+
761
+ ## License
762
+
763
+ MIT License