@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 +96 -18
- package/README.zh-TW.md +105 -23
- package/dist/index.d.ts +58 -2
- package/dist/index.js +126 -7
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
232
|
+
AsyncLRUCache automatically handles concurrent operations:
|
|
142
233
|
|
|
143
|
-
|
|
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
|
-
|
|
3
|
+
[](https://badge.fury.io/js/@wfp99%2Fasync-lru-cache)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](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
|
|
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
|
|
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
|
-
|
|
232
|
+
AsyncLRUCache ๆ่ชๅ่็ไธฆ็ผๆไฝ๏ผ
|
|
138
233
|
|
|
139
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
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
|
|
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;
|