@wfp99/async-lru-cache 1.0.0 โ†’ 1.1.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.
package/README.md CHANGED
@@ -12,9 +12,22 @@ An asynchronous LRU (Least Recently Used) memory cache that supports asynchronou
12
12
  - ๐Ÿ”„ **Automatic Merging**: Automatically merges concurrent GET requests for the same key
13
13
  - ๐Ÿ“ **Serialized Writes**: Serializes PUT operations for the same key to ensure execution order
14
14
  - ๐Ÿ—‘๏ธ **LRU Eviction**: Implements LRU algorithm to automatically remove least recently used items
15
+ - โฐ **TTL Support**: Automatic expiration of cache entries with configurable Time To Live
15
16
  - ๐Ÿ›ก๏ธ **Error Handling**: Comprehensive error handling with automatic cache cleanup on failures
16
17
  - ๐Ÿงน **Complete Clearing**: Support for clearing all cache entries at once
17
18
 
19
+ ## Why Choose In-Memory Over External Cache Services?
20
+
21
+ **Simplicity & Performance**
22
+ - No setup required - install and use immediately
23
+ - Zero network latency - direct memory access
24
+ - Reduced infrastructure complexity
25
+
26
+ **Reliability & Development**
27
+ - No external dependencies or network failures
28
+ - Simplified testing and debugging
29
+ - Works without Redis, Memcached, or other cache servers
30
+
18
31
  ## Installation
19
32
 
20
33
  ```bash
@@ -64,6 +77,59 @@ try {
64
77
  }
65
78
  ```
66
79
 
80
+ ### TTL (Time To Live) Support
81
+
82
+ AsyncLRUCache supports automatic expiration of cache entries using TTL (Time To Live):
83
+
84
+ ```typescript
85
+ import { AsyncLRUCache } from '@wfp99/async-lru-cache';
86
+
87
+ // Create cache with TTL support
88
+ const cache = new AsyncLRUCache({
89
+ capacity: 100,
90
+ defaultTtlMs: 5000, // Default TTL of 5 seconds
91
+ cleanupIntervalMs: 10000 // Cleanup expired entries every 10 seconds
92
+ });
93
+
94
+ // Use default TTL
95
+ const data1 = await cache.get('key1', async () => {
96
+ return await fetchData();
97
+ });
98
+
99
+ // Override TTL for specific entries
100
+ const data2 = await cache.get('key2', async () => {
101
+ return await fetchCriticalData();
102
+ }, 30000); // 30 seconds TTL
103
+
104
+ // Put with custom TTL
105
+ await cache.put('key3', value, async (key, val) => {
106
+ await saveToDb(key, val);
107
+ }, 60000); // 1 minute TTL
108
+
109
+ // Manual cleanup of expired entries
110
+ cache.cleanupExpired();
111
+
112
+ // Check if key exists and is not expired
113
+ if (cache.has('key1')) {
114
+ console.log('Key exists and is valid');
115
+ }
116
+
117
+ // Get cache size
118
+ console.log('Current cache size:', cache.size());
119
+
120
+ // Destroy cache (stops cleanup timer and clears all data)
121
+ cache.destroy();
122
+ ```
123
+
124
+ #### TTL Configuration Options
125
+
126
+ - **`defaultTtlMs`**: Default TTL for all cache entries (optional)
127
+ - **`cleanupIntervalMs`**: Automatic cleanup interval for expired entries (optional)
128
+ - Individual `get()` and `put()` methods accept TTL overrides
129
+ - Expired entries are automatically removed during normal operations
130
+ - Call `cleanupExpired()` for manual cleanup
131
+ - Call `destroy()` to stop timers and prevent memory leaks
132
+
67
133
  ### Advanced Usage
68
134
 
69
135
  ```typescript
@@ -106,24 +172,28 @@ constructor(option: AsyncLRUCacheOption)
106
172
  | Property | Type | Required | Description |
107
173
  |----------|------|----------|-------------|
108
174
  | `capacity` | `number` | Yes | Maximum number of items allowed in cache. Must be a positive number. |
175
+ | `defaultTtlMs` | `number` | No | Default TTL for all cache entries (optional) |
176
+ | `cleanupIntervalMs` | `number` | No | Automatic cleanup interval for expired entries (optional) |
109
177
 
110
178
  #### Methods
111
179
 
112
- ##### `get(key: K, loader: () => Promise<V>): Promise<V>`
180
+ ##### `get(key: K, loader: () => Promise<V>, ttlMs?: number): Promise<V>`
113
181
 
114
182
  Retrieves data from cache. If not found, uses the loader to load data.
115
183
 
116
184
  - **key**: Cache key
117
185
  - **loader**: Asynchronous loader function executed on cache miss
186
+ - **ttlMs**: Optional TTL override for this entry
118
187
  - **Returns**: Promise that resolves to the required data
119
188
 
120
- ##### `put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>): Promise<V>`
189
+ ##### `put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>, ttlMs?: number): Promise<V>`
121
190
 
122
191
  Puts a value into cache and optionally executes a saver function for persistence.
123
192
 
124
193
  - **key**: Cache key
125
194
  - **value**: Value to cache
126
195
  - **saver**: Optional asynchronous saver function
196
+ - **ttlMs**: Optional TTL override for this entry
127
197
  - **Returns**: Promise that resolves to the latest value after saver operation completes
128
198
 
129
199
  ##### `invalidate(key: K): void`
@@ -136,11 +206,33 @@ Invalidates and removes the cache entry for the specified key.
136
206
 
137
207
  Clears all cache entries. This method removes all items from cache and manually clears node links to prevent potential memory leaks.
138
208
 
209
+ ##### `has(key: K): boolean`
210
+
211
+ Checks if a key exists in cache and is not expired.
212
+
213
+ - **key**: Cache key to check
214
+ - **Returns**: True if key exists and is valid
215
+
216
+ ##### `size(): number`
217
+
218
+ Returns the current number of items in cache.
219
+
220
+ - **Returns**: Number of cached items
221
+
222
+ ##### `cleanupExpired(): void`
223
+
224
+ Manually removes expired entries from cache.
225
+
226
+ ##### `destroy(): void`
227
+
228
+ Destroys the cache, stops cleanup timers and clears all data.
229
+
139
230
  ## Concurrency Handling
140
231
 
141
- ### GET Request Merging
232
+ AsyncLRUCache automatically handles concurrent operations:
142
233
 
143
- When multiple concurrent requests fetch the same key, AsyncLRUCache automatically merges these requests, ensuring the loader function executes only once:
234
+ - **GET Request Merging**: Multiple concurrent requests for the same key share a single loader execution
235
+ - **PUT Operation Serialization**: Multiple PUT operations for the same key are serialized to ensure they execute in order
144
236
 
145
237
  ```typescript
146
238
  // These three concurrent requests will share the same loader execution
@@ -149,13 +241,7 @@ const [data1, data2, data3] = await Promise.all([
149
241
  cache.get('shared-key', loader),
150
242
  cache.get('shared-key', loader)
151
243
  ]);
152
- ```
153
-
154
- ### PUT Operation Serialization
155
244
 
156
- Multiple PUT operations for the same key are serialized to ensure they execute in order:
157
-
158
- ```typescript
159
245
  // These operations will execute sequentially, even if started concurrently
160
246
  cache.put('key', 'value1', saver1);
161
247
  cache.put('key', 'value2', saver2);
@@ -190,14 +276,6 @@ const user: User = await userCache.get('user:123', async () => {
190
276
  - **Operation Chain Errors**: PUT operations ignore previous operation errors, allowing new operations to proceed
191
277
  - **Error Logging**: All errors are automatically logged to console for debugging
192
278
 
193
- ## Memory Management
194
-
195
- AsyncLRUCache provides comprehensive memory management:
196
-
197
- - **Automatic Eviction**: Automatically removes least recently used items based on capacity
198
- - **Manual Cleanup**: `clear()` method thoroughly cleans all node links to prevent memory leaks
199
- - **Error Cleanup**: Automatically cleans related cache entries when operations fail
200
-
201
279
  ## License
202
280
 
203
281
  MIT
package/README.zh-TW.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Async LRU Cache
2
2
 
3
- ไธ€ๅ€‹ๆ”ฏๆด้žๅŒๆญฅ่ผ‰ๅ…ฅๅ’Œไฟๅญ˜ๆ“ไฝœ็š„ LRU๏ผˆLeast Recently Used๏ผ‰่จ˜ๆ†ถ้ซ”ๅฟซๅ–้กžๅˆฅ๏ผŒ่‡ชๅ‹•่™•็†ไธฆ็™ผ่ซ‹ๆฑ‚ใ€‚
3
+ [![npm version](https://badge.fury.io/js/@wfp99%2Fasync-lru-cache.svg)](https://badge.fury.io/js/@wfp99%2Fasync-lru-cache)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen.svg)](https://nodejs.org/)
6
+
7
+ ไธ€ๅ€‹ๆ”ฏๆด้žๅŒๆญฅ่ผ‰ๅ…ฅๅ’Œไฟๅญ˜ๆ“ไฝœ็š„ LRU๏ผˆLeast Recently Used๏ผ‰่จ˜ๆ†ถ้ซ”ๅฟซๅ–๏ผŒ่‡ชๅ‹•่™•็†ไธฆ็™ผ่ซ‹ๆฑ‚ใ€‚
4
8
 
5
9
  ## ็‰น่‰ฒ
6
10
 
@@ -8,9 +12,22 @@
8
12
  - ๐Ÿ”„ **่‡ชๅ‹•ๅˆไฝต**๏ผš่‡ชๅ‹•ๅˆไฝต็›ธๅŒ key ็š„ไธฆ็™ผ GET ่ซ‹ๆฑ‚
9
13
  - ๐Ÿ“ **ๅบๅˆ—ๅŒ–ๅฏซๅ…ฅ**๏ผšๅบๅˆ—ๅŒ–ๅŒไธ€ key ็š„ PUT ๆ“ไฝœ๏ผŒ็ขบไฟ้ †ๅบๅŸท่กŒ
10
14
  - ๐Ÿ—‘๏ธ **LRU ๆท˜ๆฑฐ็ญ–็•ฅ**๏ผšๅฏฆ็พ LRU ๆผ”็ฎ—ๆณ•๏ผŒ่‡ชๅ‹•็งป้™คๆœ€ไน…ๆœชไฝฟ็”จ็š„้ …็›ฎ
11
- - ๐Ÿ›ก๏ธ **้Œฏ่ชค่™•็†**๏ผšๅฎŒๅ–„็š„้Œฏ่ชค่™•็†ๆฉŸๅˆถ๏ผŒ่ผ‰ๅ…ฅๆˆ–ไฟๅญ˜ๅคฑๆ•—ๆ™‚่‡ชๅ‹•ๆธ…็†ๅฟซๅ–
15
+ - โฐ **TTL ๆ”ฏๆด**๏ผšๆ”ฏๆดๅฏ้…็ฝฎ็š„็”Ÿๅญ˜ๆ™‚้–“๏ผˆTime To Live๏ผ‰๏ผŒ่‡ชๅ‹•้ŽๆœŸๅฟซๅ–้ …็›ฎ
16
+ - ๐Ÿ›ก๏ธ **้Œฏ่ชค่™•็†**๏ผšๅฎŒๅ–„็š„้Œฏ่ชค่™•็†ๆฉŸๅˆถ๏ผŒๅคฑๆ•—ๆ™‚่‡ชๅ‹•ๆธ…็†ๅฟซๅ–
12
17
  - ๐Ÿงน **ๅฎŒๆ•ดๆธ…้™ค**๏ผšๆ”ฏๆดไธ€ๆฌกๆ€งๆธ…้™คๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎ
13
18
 
19
+ ## ็‚บไป€้บผ้ธๆ“‡่จ˜ๆ†ถ้ซ”ๅ…งๅฟซๅ–่€Œ้žๅค–้ƒจๅฟซๅ–ๆœๅ‹™๏ผŸ
20
+
21
+ **็ฐกๅ–ฎๆ€ง่ˆ‡ๆ•ˆ่ƒฝ**
22
+ - ็„ก้œ€่จญๅฎš - ๅฎ‰่ฃๅพŒ็ซ‹ๅณไฝฟ็”จ
23
+ - ้›ถ็ถฒ่ทฏๅปถ้ฒ - ็›ดๆŽฅ่จ˜ๆ†ถ้ซ”ๅญ˜ๅ–
24
+ - ๆธ›ๅฐ‘ๅŸบ็คŽๆžถๆง‹่ค‡้›œๆ€ง
25
+
26
+ **ๅฏ้ ๆ€ง่ˆ‡้–‹็™ผ**
27
+ - ็„กๅค–้ƒจไพ่ณดๆˆ–็ถฒ่ทฏๆ•…้šœ้ขจ้šช
28
+ - ็ฐกๅŒ–ๆธฌ่ฉฆๅ’Œ้™ค้Œฏ
29
+ - ็„ก้œ€ Redisใ€Memcached ๆˆ–ๅ…ถไป–ๅฟซๅ–ไผบๆœๅ™จ
30
+
14
31
  ## ๅฎ‰่ฃ
15
32
 
16
33
  ```bash
@@ -60,6 +77,59 @@ try {
60
77
  }
61
78
  ```
62
79
 
80
+ ### TTL๏ผˆ็”Ÿๅญ˜ๆ™‚้–“๏ผ‰ๆ”ฏๆด
81
+
82
+ AsyncLRUCache ๆ”ฏๆดไฝฟ็”จ TTL๏ผˆTime To Live๏ผ‰่‡ชๅ‹•้ŽๆœŸๅฟซๅ–้ …็›ฎ๏ผš
83
+
84
+ ```typescript
85
+ import { AsyncLRUCache } from '@wfp99/async-lru-cache';
86
+
87
+ // ๅปบ็ซ‹ๆ”ฏๆด TTL ็š„ๅฟซๅ–
88
+ const cache = new AsyncLRUCache({
89
+ capacity: 100,
90
+ defaultTtlMs: 5000, // ้ ่จญ TTL ็‚บ 5 ็ง’
91
+ cleanupIntervalMs: 10000 // ๆฏ 10 ็ง’ๆธ…็†้ŽๆœŸ้ …็›ฎ
92
+ });
93
+
94
+ // ไฝฟ็”จ้ ่จญ TTL
95
+ const data1 = await cache.get('key1', async () => {
96
+ return await fetchData();
97
+ });
98
+
99
+ // ็‚บ็‰นๅฎš้ …็›ฎ่ฆ†ๅฏซ TTL
100
+ const data2 = await cache.get('key2', async () => {
101
+ return await fetchCriticalData();
102
+ }, 30000); // 30 ็ง’ TTL
103
+
104
+ // ไฝฟ็”จ่‡ช่จ‚ TTL ้€ฒ่กŒ Put ๆ“ไฝœ
105
+ await cache.put('key3', value, async (key, val) => {
106
+ await saveToDb(key, val);
107
+ }, 60000); // 1 ๅˆ†้˜ TTL
108
+
109
+ // ๆ‰‹ๅ‹•ๆธ…็†้ŽๆœŸ้ …็›ฎ
110
+ cache.cleanupExpired();
111
+
112
+ // ๆชขๆŸฅ key ๆ˜ฏๅฆๅญ˜ๅœจไธ”ๆœช้ŽๆœŸ
113
+ if (cache.has('key1')) {
114
+ console.log('Key ๅญ˜ๅœจไธ”ๆœ‰ๆ•ˆ');
115
+ }
116
+
117
+ // ๅ–ๅพ—ๅฟซๅ–ๅคงๅฐ
118
+ console.log('็›ฎๅ‰ๅฟซๅ–ๅคงๅฐ:', cache.size());
119
+
120
+ // ้Šทๆฏ€ๅฟซๅ–๏ผˆๅœๆญขๆธ…็†่จˆๆ™‚ๅ™จไธฆๆธ…้™คๆ‰€ๆœ‰่ณ‡ๆ–™๏ผ‰
121
+ cache.destroy();
122
+ ```
123
+
124
+ #### TTL ้…็ฝฎ้ธ้ …
125
+
126
+ - **`defaultTtlMs`**๏ผšๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎ็š„้ ่จญ TTL๏ผˆๅฏ้ธ๏ผ‰
127
+ - **`cleanupIntervalMs`**๏ผš้ŽๆœŸ้ …็›ฎ็š„่‡ชๅ‹•ๆธ…็†้–“้š”๏ผˆๅฏ้ธ๏ผ‰
128
+ - ๅ€‹ๅˆฅ็š„ `get()` ๅ’Œ `put()` ๆ–นๆณ•ๆŽฅๅ— TTL ่ฆ†ๅฏซ
129
+ - ้ŽๆœŸ้ …็›ฎๅœจๆญฃๅธธๆ“ไฝœๆœŸ้–“ๆœƒ่‡ชๅ‹•็งป้™ค
130
+ - ๅ‘ผๅซ `cleanupExpired()` ้€ฒ่กŒๆ‰‹ๅ‹•ๆธ…็†
131
+ - ๅ‘ผๅซ `destroy()` ๅœๆญข่จˆๆ™‚ๅ™จไธฆ้˜ฒๆญข่จ˜ๆ†ถ้ซ”ๆดฉๆผ
132
+
63
133
  ### ้€ฒ้šŽไฝฟ็”จ
64
134
 
65
135
  ```typescript
@@ -71,7 +141,7 @@ const user = await cache.get(`user:${userId}`, async () => {
71
141
  return await database.users.findById(userId);
72
142
  });
73
143
 
74
- // ๅŒๆ™‚ๅ„ฒๅญ˜ๅˆฐๅฟซๅ–ๅ’Œ่ณ‡ๆ–™ๅบซ
144
+ // ๅŽŸๅญๆ€งๅœฐไฟๅญ˜ๅˆฐๅฟซๅ–ๅ’Œ่ณ‡ๆ–™ๅบซ
75
145
  await cache.put(`user:${userId}`, updatedUser, async (key, value) => {
76
146
  await database.users.update(userId, value);
77
147
  });
@@ -102,24 +172,28 @@ constructor(option: AsyncLRUCacheOption)
102
172
  | ๅฑฌๆ€ง | ้กžๅž‹ | ๅฟ…้œ€ | ๆ่ฟฐ |
103
173
  |-----|------|------|------|
104
174
  | `capacity` | `number` | ๆ˜ฏ | ๅฟซๅ–ไธญๅ…่จฑ็š„ๆœ€ๅคง้ …็›ฎๆ•ธ้‡๏ผŒๅฟ…้ ˆ็‚บๆญฃๆ•ธ |
175
+ | `defaultTtlMs` | `number` | ๅฆ | ๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎ็š„้ ่จญ TTL๏ผˆๅฏ้ธ๏ผ‰ |
176
+ | `cleanupIntervalMs` | `number` | ๅฆ | ้ŽๆœŸ้ …็›ฎ็š„่‡ชๅ‹•ๆธ…็†้–“้š”๏ผˆๅฏ้ธ๏ผ‰ |
105
177
 
106
178
  #### ๆ–นๆณ•
107
179
 
108
- ##### `get(key: K, loader: () => Promise<V>): Promise<V>`
180
+ ##### `get(key: K, loader: () => Promise<V>, ttlMs?: number): Promise<V>`
109
181
 
110
182
  ๅพžๅฟซๅ–ไธญ็ฒๅ–่ณ‡ๆ–™ใ€‚ๅฆ‚ๆžœไธๅญ˜ๅœจ๏ผŒไฝฟ็”จ่ผ‰ๅ…ฅๅ™จ่ผ‰ๅ…ฅ่ณ‡ๆ–™ใ€‚
111
183
 
112
184
  - **key**: ๅฟซๅ–้ต
113
185
  - **loader**: ๅฟซๅ–ๆœชๅ‘ฝไธญๆ™‚ๅŸท่กŒ็š„้žๅŒๆญฅ่ผ‰ๅ…ฅๅ‡ฝๆ•ธ
186
+ - **ttlMs**: ๆญค้ …็›ฎ็š„ๅฏ้ธ TTL ่ฆ†ๅฏซ
114
187
  - **ๅ›žๅ‚ณ**: Promise๏ผŒ่งฃๆž็‚บๆ‰€้œ€็š„่ณ‡ๆ–™
115
188
 
116
- ##### `put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>): Promise<V>`
189
+ ##### `put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>, ttlMs?: number): Promise<V>`
117
190
 
118
191
  ๅฐ‡ๅ€ผๆ”พๅ…ฅๅฟซๅ–ไธญ๏ผŒไธฆๅฏ้ธๆ“‡ๆ€งๅœฐๅŸท่กŒไฟๅญ˜ๅ™จๅ‡ฝๆ•ธ้€ฒ่กŒๆŒไน…ๅŒ–ใ€‚
119
192
 
120
193
  - **key**: ๅฟซๅ–้ต
121
194
  - **value**: ่ฆๅฟซๅ–็š„ๅ€ผ
122
195
  - **saver**: ๅฏ้ธ็š„้žๅŒๆญฅไฟๅญ˜ๅ‡ฝๆ•ธ
196
+ - **ttlMs**: ๆญค้ …็›ฎ็š„ๅฏ้ธ TTL ่ฆ†ๅฏซ
123
197
  - **ๅ›žๅ‚ณ**: Promise๏ผŒ่งฃๆž็‚บไฟๅญ˜ๆ“ไฝœๅฎŒๆˆๅพŒ็š„ๆœ€ๆ–ฐๅ€ผ
124
198
 
125
199
  ##### `invalidate(key: K): void`
@@ -132,11 +206,33 @@ constructor(option: AsyncLRUCacheOption)
132
206
 
133
207
  ๆธ…้™คๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎใ€‚ๆญคๆ–นๆณ•ๆœƒ็งป้™คๅฟซๅ–ไธญ็š„ๆ‰€ๆœ‰้ …็›ฎไธฆๆ‰‹ๅ‹•ๆธ…็†็ฏ€้ปž้€ฃ็ตไปฅ้ฟๅ…ๆฝ›ๅœจ็š„่จ˜ๆ†ถ้ซ”ๆดฉๆผใ€‚
134
208
 
209
+ ##### `has(key: K): boolean`
210
+
211
+ ๆชขๆŸฅๅฟซๅ–ไธญๆ˜ฏๅฆๅญ˜ๅœจๆŒ‡ๅฎš้ตไธ”ๆœช้ŽๆœŸใ€‚
212
+
213
+ - **key**: ่ฆๆชขๆŸฅ็š„ๅฟซๅ–้ต
214
+ - **ๅ›žๅ‚ณ**: ๅฆ‚ๆžœ้ตๅญ˜ๅœจไธ”ๆœ‰ๆ•ˆๅ‰‡ๅ›žๅ‚ณ true
215
+
216
+ ##### `size(): number`
217
+
218
+ ๅ›žๅ‚ณๅฟซๅ–ไธญ็›ฎๅ‰็š„้ …็›ฎๆ•ธ้‡ใ€‚
219
+
220
+ - **ๅ›žๅ‚ณ**: ๅฟซๅ–้ …็›ฎ็š„ๆ•ธ้‡
221
+
222
+ ##### `cleanupExpired(): void`
223
+
224
+ ๆ‰‹ๅ‹•ๅพžๅฟซๅ–ไธญ็งป้™ค้ŽๆœŸ้ …็›ฎใ€‚
225
+
226
+ ##### `destroy(): void`
227
+
228
+ ้Šทๆฏ€ๅฟซๅ–๏ผŒๅœๆญขๆธ…็†่จˆๆ™‚ๅ™จไธฆๆธ…้™คๆ‰€ๆœ‰่ณ‡ๆ–™ใ€‚
229
+
135
230
  ## ไธฆ็™ผ่™•็†
136
231
 
137
- ### GET ่ซ‹ๆฑ‚ๅˆไฝต
232
+ AsyncLRUCache ๆœƒ่‡ชๅ‹•่™•็†ไธฆ็™ผๆ“ไฝœ๏ผš
138
233
 
139
- ็•ถๅคšๅ€‹ไธฆ็™ผ่ซ‹ๆฑ‚็ฒๅ–็›ธๅŒ็š„ key ๆ™‚๏ผŒAsyncLRUCache ๆœƒ่‡ชๅ‹•ๅˆไฝต้€™ไบ›่ซ‹ๆฑ‚๏ผŒ็ขบไฟ่ผ‰ๅ…ฅๅ™จๅ‡ฝๆ•ธๅชๅŸท่กŒไธ€ๆฌก๏ผš
234
+ - **GET ่ซ‹ๆฑ‚ๅˆไฝต**๏ผšๅฐๅŒไธ€ key ็š„ๅคšๅ€‹ไธฆ็™ผ่ซ‹ๆฑ‚ๆœƒๅ…ฑไบซๅ–ฎไธ€่ผ‰ๅ…ฅๅ™จๅŸท่กŒ
235
+ - **PUT ๆ“ไฝœๅบๅˆ—ๅŒ–**๏ผšๅฐๅŒไธ€ key ็š„ๅคšๅ€‹ PUT ๆ“ไฝœๆœƒๅบๅˆ—ๅŒ–ๅŸท่กŒไปฅ็ขบไฟ้ †ๅบ
140
236
 
141
237
  ```typescript
142
238
  // ้€™ไธ‰ๅ€‹ไธฆ็™ผ่ซ‹ๆฑ‚ๆœƒๅ…ฑไบซๅŒไธ€ๅ€‹่ผ‰ๅ…ฅๅ™จๅŸท่กŒ
@@ -145,13 +241,7 @@ const [data1, data2, data3] = await Promise.all([
145
241
  cache.get('shared-key', loader),
146
242
  cache.get('shared-key', loader)
147
243
  ]);
148
- ```
149
-
150
- ### PUT ๆ“ไฝœๅบๅˆ—ๅŒ–
151
244
 
152
- ๅฐๅŒไธ€ key ็š„ๅคšๅ€‹ PUT ๆ“ไฝœๆœƒ่ขซๅบๅˆ—ๅŒ–๏ผŒ็ขบไฟๅฎƒๅ€‘ๆŒ‰้ †ๅบๅŸท่กŒ๏ผš
153
-
154
- ```typescript
155
245
  // ้€™ไบ›ๆ“ไฝœๆœƒๆŒ‰้ †ๅบๅŸท่กŒ๏ผŒๅณไฝฟๅฎƒๅ€‘ๆ˜ฏไธฆ็™ผๅ•Ÿๅ‹•็š„
156
246
  cache.put('key', 'value1', saver1);
157
247
  cache.put('key', 'value2', saver2);
@@ -179,21 +269,13 @@ const user: User = await userCache.get('user:123', async () => {
179
269
  });
180
270
  ```
181
271
 
182
- ## ้Œฏ่ชค่™•็†ๆฉŸๅˆถ
272
+ ## ้Œฏ่ชค่™•็†
183
273
 
184
274
  - **่ผ‰ๅ…ฅๅ™จๅคฑๆ•—**๏ผšๅฆ‚ๆžœ่ผ‰ๅ…ฅๅ™จๅ‡ฝๆ•ธๆ‹‹ๅ‡บ็•ฐๅธธ๏ผŒๅฐๆ‡‰็š„ๅฟซๅ–้ …็›ฎๆœƒ่ขซ่‡ชๅ‹•็งป้™ค
185
275
  - **ไฟๅญ˜ๅ™จๅคฑๆ•—**๏ผšๅฆ‚ๆžœไฟๅญ˜ๅ™จๅ‡ฝๆ•ธๅคฑๆ•—๏ผŒๅฟซๅ–้ …็›ฎไนŸๆœƒ่ขซ็งป้™ค๏ผŒ็ขบไฟ่ณ‡ๆ–™ไธ€่‡ดๆ€ง
186
- - **ๆ“ไฝœ้ˆ้Œฏ่ชค**๏ผšPUT ๆ“ไฝœๆœƒๅฟฝ็•ฅๅ‰ไธ€ๅ€‹ๆ“ไฝœ็š„้Œฏ่ชค๏ผŒ็ขบไฟๆ–ฐๆ“ไฝœ่ƒฝๅค ็นผ็บŒ้€ฒ่กŒ
276
+ - **ๆ“ไฝœ้ˆ้Œฏ่ชค**๏ผšPUT ๆ“ไฝœๆœƒๅฟฝ็•ฅๅ‰ไธ€ๅ€‹ๆ“ไฝœ็š„้Œฏ่ชค๏ผŒๅ…่จฑๆ–ฐๆ“ไฝœ็นผ็บŒ้€ฒ่กŒ
187
277
  - **้Œฏ่ชคๆ—ฅ่ชŒ**๏ผšๆ‰€ๆœ‰้Œฏ่ชค้ƒฝๆœƒ่‡ชๅ‹•่จ˜้Œ„ๅˆฐๆŽงๅˆถๅฐ๏ผŒไพฟๆ–ผ้™ค้Œฏ
188
278
 
189
- ## ่จ˜ๆ†ถ้ซ”็ฎก็†
190
-
191
- AsyncLRUCache ๅ…ทๅ‚™ๅฎŒๅ–„็š„่จ˜ๆ†ถ้ซ”็ฎก็†ๆฉŸๅˆถ๏ผš
192
-
193
- - **่‡ชๅ‹•ๆท˜ๆฑฐ**๏ผš็•ถๅฟซๅ–้ …็›ฎๆ•ธ้‡่ถ…้Žๅฎน้‡้™ๅˆถๆ™‚๏ผŒ่‡ชๅ‹•็งป้™คๆœ€ไน…ๆœชไฝฟ็”จ็š„้ …็›ฎ
194
- - **ๆ‰‹ๅ‹•ๆธ…็†**๏ผš`clear()` ๆ–นๆณ•ๆœƒๅพนๅบ•ๆธ…็†ๆ‰€ๆœ‰็ฏ€้ปž้€ฃ็ต๏ผŒ้˜ฒๆญข่จ˜ๆ†ถ้ซ”ๆดฉๆผ
195
- - **้Œฏ่ชคๆธ…็†**๏ผšๆ“ไฝœๅคฑๆ•—ๆ™‚่‡ชๅ‹•ๆธ…็†็›ธ้—œๅฟซๅ–้ …็›ฎ
196
-
197
279
  ## ๆŽˆๆฌŠ
198
280
 
199
281
  MIT
package/dist/index.d.ts CHANGED
@@ -14,6 +14,18 @@ export interface AsyncLRUCacheOption {
14
14
  * Must not be less than 10.
15
15
  */
16
16
  capacity: number;
17
+ /**
18
+ * Default time to live for cache entries in milliseconds.
19
+ * If not specified, entries will not expire automatically.
20
+ * Individual entries can override this value.
21
+ */
22
+ defaultTtlMs?: number;
23
+ /**
24
+ * Interval in milliseconds for automatic cleanup of expired entries.
25
+ * If not specified, cleanup will only happen during normal operations.
26
+ * Setting this enables periodic background cleanup.
27
+ */
28
+ cleanupIntervalMs?: number;
17
29
  }
18
30
  /**
19
31
  * Asynchronous LRU Cache class.
@@ -21,12 +33,17 @@ export interface AsyncLRUCacheOption {
21
33
  * - Supports asynchronous savers for data persistence.
22
34
  * - Automatically merges concurrent requests (get) and serializes writes (put).
23
35
  * - Implements LRU eviction strategy.
36
+ * - Supports TTL (Time To Live) for automatic expiration of entries.
24
37
  */
25
38
  export declare class AsyncLRUCache<K, V> {
26
39
  /**
27
40
  * Maximum capacity of the cache.
28
41
  */
29
42
  private readonly capacity;
43
+ /**
44
+ * Default TTL for cache entries in milliseconds.
45
+ */
46
+ private readonly defaultTtlMs?;
30
47
  /**
31
48
  * Map for storing cache entries. The key is the cache key, and the value is the Node.
32
49
  */
@@ -39,12 +56,28 @@ export declare class AsyncLRUCache<K, V> {
39
56
  * Tail node of the linked list, pointing to the least recently used item.
40
57
  */
41
58
  private tail;
59
+ /**
60
+ * Timer ID for periodic cleanup of expired entries.
61
+ */
62
+ private cleanupTimer?;
42
63
  /**
43
64
  * Creates an instance of AsyncLRUCache with the specified options.
44
65
  * @param option - The configuration options for the cache.
45
66
  * @throws {Error} Throws an error if the provided capacity is less than 10.
46
67
  */
47
68
  constructor(option: AsyncLRUCacheOption);
69
+ /**
70
+ * Cleans up expired entries from the cache.
71
+ * This method removes all nodes that have exceeded their TTL.
72
+ */
73
+ private _cleanupExpired;
74
+ /**
75
+ * Checks if a node is expired and removes it if so.
76
+ * @param key - The key to check.
77
+ * @param node - The node to check.
78
+ * @returns True if the node was expired and removed, false otherwise.
79
+ */
80
+ private _checkAndRemoveExpired;
48
81
  /**
49
82
  * Checks if eviction is needed according to the cache strategy.
50
83
  * When the cache exceeds its capacity, automatically removes the tail (least recently used) node.
@@ -72,18 +105,20 @@ export declare class AsyncLRUCache<K, V> {
72
105
  * Automatically merges concurrent requests for the same key.
73
106
  * @param key - Cache key.
74
107
  * @param loader - Asynchronous loader function to execute on cache miss.
108
+ * @param ttlMs - Optional TTL override for this entry in milliseconds.
75
109
  * @returns A Promise that resolves to the required data.
76
110
  */
77
- get(key: K, loader: () => Promise<V>): Promise<V>;
111
+ get(key: K, loader: () => Promise<V>, ttlMs?: number): Promise<V>;
78
112
  /**
79
113
  * Puts a value into the cache, and optionally executes a saver function to persist it.
80
114
  * This method serializes multiple put operations for the same key, ensuring they execute in order.
81
115
  * @param key - Cache key.
82
116
  * @param value - Value to cache.
83
117
  * @param saver - Optional asynchronous save function.
118
+ * @param ttlMs - Optional TTL override for this entry in milliseconds.
84
119
  * @returns A Promise that resolves to the latest value after the saver operation completes.
85
120
  */
86
- put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>): Promise<V>;
121
+ put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>, ttlMs?: number): Promise<V>;
87
122
  /**
88
123
  * Invalidates and removes the cache entry associated with the specified key.
89
124
  * @param key - The key of the cache entry to invalidate.
@@ -93,4 +128,25 @@ export declare class AsyncLRUCache<K, V> {
93
128
  * Clears the entire cache, removing all entries.
94
129
  */
95
130
  clear(): void;
131
+ /**
132
+ * Destroys the cache instance, clearing all data and stopping any background timers.
133
+ * Call this method when you no longer need the cache to prevent memory leaks.
134
+ */
135
+ destroy(): void;
136
+ /**
137
+ * Manually triggers cleanup of expired entries.
138
+ * This is useful if you want to force cleanup without waiting for the automatic interval.
139
+ */
140
+ cleanupExpired(): void;
141
+ /**
142
+ * Gets the current number of entries in the cache.
143
+ * @returns The number of entries currently in the cache.
144
+ */
145
+ size(): number;
146
+ /**
147
+ * Checks if a key exists in the cache and is not expired.
148
+ * @param key - The key to check.
149
+ * @returns True if the key exists and is not expired, false otherwise.
150
+ */
151
+ has(key: K): boolean;
96
152
  }
package/dist/index.js CHANGED
@@ -16,8 +16,9 @@ class Node {
16
16
  * Creates a new Node instance.
17
17
  * @param key - The key associated with this node.
18
18
  * @param value - A Promise that resolves to the value of this node.
19
+ * @param ttlMs - Time to live in milliseconds. If provided, sets the expiration time.
19
20
  */
20
- constructor(key, value) {
21
+ constructor(key, value, ttlMs) {
21
22
  /**
22
23
  * Reference to the next node in the linked list.
23
24
  * This property is `null` if there is no next node.
@@ -28,8 +29,23 @@ class Node {
28
29
  * This property is `null` if there is no previous node.
29
30
  */
30
31
  this.prev = null;
32
+ /**
33
+ * The timestamp when this node expires (in milliseconds).
34
+ * If null, the node never expires.
35
+ */
36
+ this.expiresAt = null;
31
37
  this.key = key;
32
38
  this.value = value;
39
+ if (ttlMs !== undefined && ttlMs > 0) {
40
+ this.expiresAt = Date.now() + ttlMs;
41
+ }
42
+ }
43
+ /**
44
+ * Checks if this node has expired.
45
+ * @returns True if the node has expired, false otherwise.
46
+ */
47
+ isExpired() {
48
+ return this.expiresAt !== null && Date.now() > this.expiresAt;
33
49
  }
34
50
  }
35
51
  /**
@@ -38,6 +54,7 @@ class Node {
38
54
  * - Supports asynchronous savers for data persistence.
39
55
  * - Automatically merges concurrent requests (get) and serializes writes (put).
40
56
  * - Implements LRU eviction strategy.
57
+ * - Supports TTL (Time To Live) for automatic expiration of entries.
41
58
  */
42
59
  class AsyncLRUCache {
43
60
  /**
@@ -61,6 +78,48 @@ class AsyncLRUCache {
61
78
  if (option.capacity < 10)
62
79
  throw new Error("Capacity must be at least 10.");
63
80
  this.capacity = option.capacity;
81
+ this.defaultTtlMs = option.defaultTtlMs;
82
+ // Setup periodic cleanup if specified
83
+ if (option.cleanupIntervalMs && option.cleanupIntervalMs > 0) {
84
+ this.cleanupTimer = setInterval(() => {
85
+ this._cleanupExpired();
86
+ }, option.cleanupIntervalMs);
87
+ }
88
+ }
89
+ /**
90
+ * Cleans up expired entries from the cache.
91
+ * This method removes all nodes that have exceeded their TTL.
92
+ */
93
+ _cleanupExpired() {
94
+ const expiredKeys = [];
95
+ // Collect expired keys
96
+ for (const [key, node] of this.dataMap) {
97
+ if (node.isExpired()) {
98
+ expiredKeys.push(key);
99
+ }
100
+ }
101
+ // Remove expired entries
102
+ for (const key of expiredKeys) {
103
+ const node = this.dataMap.get(key);
104
+ if (node) {
105
+ this.dataMap.delete(key);
106
+ this._removeNode(node);
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * Checks if a node is expired and removes it if so.
112
+ * @param key - The key to check.
113
+ * @param node - The node to check.
114
+ * @returns True if the node was expired and removed, false otherwise.
115
+ */
116
+ _checkAndRemoveExpired(key, node) {
117
+ if (node.isExpired()) {
118
+ this.dataMap.delete(key);
119
+ this._removeNode(node);
120
+ return true;
121
+ }
122
+ return false;
64
123
  }
65
124
  /**
66
125
  * Checks if eviction is needed according to the cache strategy.
@@ -116,16 +175,24 @@ class AsyncLRUCache {
116
175
  * Automatically merges concurrent requests for the same key.
117
176
  * @param key - Cache key.
118
177
  * @param loader - Asynchronous loader function to execute on cache miss.
178
+ * @param ttlMs - Optional TTL override for this entry in milliseconds.
119
179
  * @returns A Promise that resolves to the required data.
120
180
  */
121
- get(key, loader) {
181
+ get(key, loader, ttlMs) {
122
182
  const node = this.dataMap.get(key);
123
183
  if (node) {
124
- this._moveToHead(node);
125
- return node.value;
184
+ // Check if the node has expired
185
+ if (this._checkAndRemoveExpired(key, node)) {
186
+ // Node was expired and removed, proceed to load fresh data
187
+ }
188
+ else {
189
+ this._moveToHead(node);
190
+ return node.value;
191
+ }
126
192
  }
127
193
  const loadingPromise = loader();
128
- const newNode = new Node(key, loadingPromise);
194
+ const effectiveTtlMs = ttlMs !== null && ttlMs !== void 0 ? ttlMs : this.defaultTtlMs;
195
+ const newNode = new Node(key, loadingPromise, effectiveTtlMs);
129
196
  this.dataMap.set(key, newNode);
130
197
  this._addToHead(newNode);
131
198
  this._evictIfNeeded();
@@ -144,9 +211,10 @@ class AsyncLRUCache {
144
211
  * @param key - Cache key.
145
212
  * @param value - Value to cache.
146
213
  * @param saver - Optional asynchronous save function.
214
+ * @param ttlMs - Optional TTL override for this entry in milliseconds.
147
215
  * @returns A Promise that resolves to the latest value after the saver operation completes.
148
216
  */
149
- put(key, value, saver) {
217
+ put(key, value, saver, ttlMs) {
150
218
  const lastPromise = this.dataMap.has(key) ? this.dataMap.get(key).value : Promise.resolve(undefined);
151
219
  const saverPromise = lastPromise.catch((err) => {
152
220
  // Ignore previous operation errors, so the new operation can proceed.
@@ -162,14 +230,22 @@ class AsyncLRUCache {
162
230
  return value;
163
231
  });
164
232
  let node = this.dataMap.get(key);
233
+ const effectiveTtlMs = ttlMs !== null && ttlMs !== void 0 ? ttlMs : this.defaultTtlMs;
165
234
  if (!node) {
166
- node = new Node(key, saverPromise);
235
+ node = new Node(key, saverPromise, effectiveTtlMs);
167
236
  this.dataMap.set(key, node);
168
237
  this._addToHead(node);
169
238
  this._evictIfNeeded();
170
239
  }
171
240
  else {
241
+ // Update existing node with new value and TTL
172
242
  node.value = saverPromise;
243
+ if (effectiveTtlMs !== undefined && effectiveTtlMs > 0) {
244
+ node.expiresAt = Date.now() + effectiveTtlMs;
245
+ }
246
+ else {
247
+ node.expiresAt = null;
248
+ }
173
249
  this._moveToHead(node);
174
250
  }
175
251
  saverPromise.catch(err => {
@@ -208,11 +284,54 @@ class AsyncLRUCache {
208
284
  node.next = null;
209
285
  node.key = null;
210
286
  node.value = Promise.resolve(undefined);
287
+ node.expiresAt = null;
211
288
  // Move to the next node.
212
289
  node = next;
213
290
  }
214
291
  this.head = null;
215
292
  this.tail = null;
216
293
  }
294
+ /**
295
+ * Destroys the cache instance, clearing all data and stopping any background timers.
296
+ * Call this method when you no longer need the cache to prevent memory leaks.
297
+ */
298
+ destroy() {
299
+ // Stop the cleanup timer if it exists
300
+ if (this.cleanupTimer) {
301
+ clearInterval(this.cleanupTimer);
302
+ this.cleanupTimer = undefined;
303
+ }
304
+ // Clear all cache data
305
+ this.clear();
306
+ }
307
+ /**
308
+ * Manually triggers cleanup of expired entries.
309
+ * This is useful if you want to force cleanup without waiting for the automatic interval.
310
+ */
311
+ cleanupExpired() {
312
+ this._cleanupExpired();
313
+ }
314
+ /**
315
+ * Gets the current number of entries in the cache.
316
+ * @returns The number of entries currently in the cache.
317
+ */
318
+ size() {
319
+ return this.dataMap.size;
320
+ }
321
+ /**
322
+ * Checks if a key exists in the cache and is not expired.
323
+ * @param key - The key to check.
324
+ * @returns True if the key exists and is not expired, false otherwise.
325
+ */
326
+ has(key) {
327
+ const node = this.dataMap.get(key);
328
+ if (!node) {
329
+ return false;
330
+ }
331
+ if (this._checkAndRemoveExpired(key, node)) {
332
+ return false;
333
+ }
334
+ return true;
335
+ }
217
336
  }
218
337
  exports.AsyncLRUCache = AsyncLRUCache;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wfp99/async-lru-cache",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "A simple memory cache using the LRU algorithm and supporting asynchronous data access.",
5
5
  "keywords": [
6
6
  "cache",