@wfp99/async-lru-cache 1.1.0 โ†’ 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,58 +4,17 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen.svg)](https://nodejs.org/)
6
6
 
7
- An asynchronous LRU (Least Recently Used) memory cache that supports asynchronous loading and saving operations while automatically handling concurrent requests.
7
+ An in-memory LRU (Least Recently Used) cache for Node.js and TypeScript, with support for asynchronous loading, saving, and automatic deduplication of concurrent requests.
8
8
 
9
9
  ## Features
10
10
 
11
- - ๐Ÿš€ **Async Support**: Supports asynchronous loader and saver functions
12
- - ๐Ÿ”„ **Automatic Merging**: Automatically merges concurrent GET requests for the same key
13
- - ๐Ÿ“ **Serialized Writes**: Serializes PUT operations for the same key to ensure execution order
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
16
- - ๐Ÿ›ก๏ธ **Error Handling**: Comprehensive error handling with automatic cache cleanup on failures
17
- - ๐Ÿงน **Complete Clearing**: Support for clearing all cache entries at once
18
-
19
- ## Why AsyncLRUCache?
20
-
21
- ### The Problem
22
- Traditional caching solutions often struggle with asynchronous operations:
23
- - **Race Conditions**: Multiple concurrent requests for the same resource can trigger duplicate expensive operations
24
- - **Memory Leaks**: Failed operations may leave stale entries in cache without proper cleanup
25
- - **Complex State Management**: Coordinating cache updates with external persistence layers becomes error-prone
26
- - **External Dependencies**: Many caching solutions require external services (Redis, Memcached) adding infrastructure complexity, network latency, and deployment overhead
27
-
28
- ### The Solution
29
- AsyncLRUCache addresses these challenges by providing:
30
- - **Smart Request Merging**: Multiple concurrent GET calls for the same key automatically share a single loader execution
31
- - **Automatic Error Recovery**: Failed operations are automatically cleaned up, preventing memory leaks and stale data
32
- - **Operation Serialization**: PUT operations for the same key are queued and executed sequentially, ensuring data consistency
33
- - **Zero External Dependencies**: Pure in-memory solution eliminates the need for external caching services, reducing infrastructure complexity, setup overhead, and network latency while increasing reliability and performance
34
-
35
- ### When to Use
36
- AsyncLRUCache is perfect for scenarios involving:
37
- - **API Response Caching**: Cache expensive HTTP requests with automatic deduplication
38
- - **Database Query Results**: Reduce database load while maintaining data consistency
39
- - **Computed Values**: Cache expensive calculations with built-in invalidation
40
- - **File System Operations**: Cache file reads/writes with persistence hooks
41
- - **Microservices Communication**: Reduce inter-service calls with intelligent caching
42
-
43
- ### Why Choose In-Memory Over External Cache Services?
44
-
45
- **Simplicity & Performance**
46
- - **No Setup Required**: Install and use immediately without configuring external services
47
- - **Zero Network Latency**: Direct memory access provides microsecond response times
48
- - **Reduced Infrastructure**: Eliminate cache servers, reducing operational complexity and costs
49
-
50
- **Reliability & Predictability**
51
- - **No Network Dependencies**: Cache operations never fail due to network issues
52
- - **Consistent Performance**: No variable network latency affecting cache performance
53
- - **Simplified Deployment**: Deploy as part of your application without managing separate cache infrastructure
54
-
55
- **Development Efficiency**
56
- - **Instant Development**: Start coding immediately without setting up Redis, Memcached, etc.
57
- - **Easy Testing**: Unit tests run without external service dependencies
58
- - **Simplified Debugging**: Cache behavior is predictable and traceable within your application process
11
+ - Asynchronous loader (`get`) and saver (`put`) functions
12
+ - Concurrent `get()` calls for the same key share a single loader execution
13
+ - `put()` calls for the same key are serialized and execute in order
14
+ - LRU eviction once the configured capacity is exceeded
15
+ - Optional per-entry TTL (time to live), with optional periodic background cleanup
16
+ - Configurable error handling via `onError`, defaulting to console logging
17
+ - `get()` can be called without a loader for a read-only lookup, and `peek()` for a lookup that never affects LRU order
59
18
 
60
19
  ## Installation
61
20
 
@@ -69,229 +28,122 @@ npm install @wfp99/async-lru-cache
69
28
 
70
29
  ## Usage
71
30
 
72
- ### Basic Usage
31
+ ### Basic usage
73
32
 
74
33
  ```typescript
75
34
  import { AsyncLRUCache } from '@wfp99/async-lru-cache';
76
35
 
77
- // Create a cache with maximum 100 items
78
- const cache = new AsyncLRUCache({
79
- capacity: 100
80
- });
36
+ const cache = new AsyncLRUCache<string, User>({ capacity: 100 });
81
37
 
82
- // Use get method to retrieve data
83
- const data = await cache.get('user:123', async () => {
84
- // Loader function executed on cache miss
85
- const response = await fetch('/api/users/123');
86
- return response.json();
38
+ // get() calls the loader on a cache miss and caches the resolved value.
39
+ const user = await cache.get('user:123', async () => {
40
+ const response = await fetch('/api/users/123');
41
+ return response.json();
87
42
  });
88
43
 
89
- // Use put method to store data
44
+ // put() stores a value directly and optionally persists it via a saver.
90
45
  await cache.put('user:456', userData, async (key, value) => {
91
- // Optional saver function for data persistence
92
- await saveToDatabase(key, value);
46
+ await saveToDatabase(key, value);
93
47
  });
94
48
  ```
95
49
 
96
- ### Error Handling
50
+ ### Read-only lookups
97
51
 
98
52
  ```typescript
99
- try {
100
- const data = await cache.get('problematic-key', async () => {
101
- throw new Error('Load failed');
102
- });
103
- } catch (error) {
104
- console.error('Cache load failed:', error);
105
- // Failed items are automatically removed from cache
106
- }
107
- ```
53
+ // get() without a loader performs a read-only lookup: on a hit it behaves like a
54
+ // normal get() (and updates LRU order); on a miss it resolves to undefined without
55
+ // adding anything to the cache or affecting LRU order.
56
+ const cached = await cache.get('user:123'); // User | undefined
108
57
 
109
- ### TTL (Time To Live) Support
58
+ // peek() never affects LRU order, whether it is a hit or a miss.
59
+ const peeked = await cache.peek('user:123'); // Promise<User> | undefined
60
+ ```
110
61
 
111
- AsyncLRUCache supports automatic expiration of cache entries using TTL (Time To Live):
62
+ ### TTL (time to live)
112
63
 
113
64
  ```typescript
114
- import { AsyncLRUCache } from '@wfp99/async-lru-cache';
115
-
116
- // Create cache with TTL support
117
65
  const cache = new AsyncLRUCache({
118
- capacity: 100,
119
- defaultTtlMs: 5000, // Default TTL of 5 seconds
120
- cleanupIntervalMs: 10000 // Cleanup expired entries every 10 seconds
121
- });
122
-
123
- // Use default TTL
124
- const data1 = await cache.get('key1', async () => {
125
- return await fetchData();
66
+ capacity: 100,
67
+ defaultTtlMs: 5000, // default TTL applied to every entry
68
+ cleanupIntervalMs: 10000, // periodic background cleanup of expired entries
126
69
  });
127
70
 
128
- // Override TTL for specific entries
129
- const data2 = await cache.get('key2', async () => {
130
- return await fetchCriticalData();
131
- }, 30000); // 30 seconds TTL
71
+ // Override the default TTL for a specific entry.
72
+ await cache.get('key1', async () => fetchData(), 30000);
132
73
 
133
- // Put with custom TTL
134
- await cache.put('key3', value, async (key, val) => {
135
- await saveToDb(key, val);
136
- }, 60000); // 1 minute TTL
137
-
138
- // Manual cleanup of expired entries
139
- cache.cleanupExpired();
140
-
141
- // Check if key exists and is not expired
142
- if (cache.has('key1')) {
143
- console.log('Key exists and is valid');
144
- }
145
-
146
- // Get cache size
147
- console.log('Current cache size:', cache.size());
148
-
149
- // Destroy cache (stops cleanup timer and clears all data)
150
- cache.destroy();
74
+ cache.has('key1'); // false once the entry has expired
75
+ cache.cleanupExpired(); // manually remove expired entries
76
+ cache.destroy(); // stop the cleanup timer and clear all data
151
77
  ```
152
78
 
153
- #### TTL Configuration Options
154
-
155
- - **`defaultTtlMs`**: Default TTL for all cache entries (optional)
156
- - **`cleanupIntervalMs`**: Automatic cleanup interval for expired entries (optional)
157
- - Individual `get()` and `put()` methods accept TTL overrides
158
- - Expired entries are automatically removed during normal operations
159
- - Call `cleanupExpired()` for manual cleanup
160
- - Call `destroy()` to stop timers and prevent memory leaks
161
-
162
- ### Advanced Usage
79
+ ### Concurrency handling
163
80
 
164
81
  ```typescript
165
- // Working with database
166
- const cache = new AsyncLRUCache<string, UserData>({ capacity: 500 });
167
-
168
- // Load from database with automatic caching
169
- const user = await cache.get(`user:${userId}`, async () => {
170
- return await database.users.findById(userId);
171
- });
82
+ // These concurrent get() calls share a single loader execution.
83
+ const [a, b, c] = await Promise.all([
84
+ cache.get('shared-key', loader),
85
+ cache.get('shared-key', loader),
86
+ cache.get('shared-key', loader),
87
+ ]);
172
88
 
173
- // Save to cache and database atomically
174
- await cache.put(`user:${userId}`, updatedUser, async (key, value) => {
175
- await database.users.update(userId, value);
176
- });
89
+ // These put() calls execute sequentially, even though they are started concurrently.
90
+ cache.put('key', 'value1', saver1);
91
+ cache.put('key', 'value2', saver2);
92
+ cache.put('key', 'value3', saver3);
177
93
  ```
178
94
 
179
- ### Cache Management
95
+ ### Error handling
180
96
 
181
- ```typescript
182
- // Manually remove specific cache entry
183
- cache.invalidate('user:123');
97
+ - If a loader (`get`) throws, the entry is removed from the cache and the rejection propagates to the caller.
98
+ - If a saver (`put`) throws, the entry is also removed to avoid caching an unpersisted value.
99
+ - A `put()` call ignores the failure of a previous, already-settled operation on the same key, so it can still proceed.
100
+ - By default, these failures are logged with `console.error`/`console.warn`. Provide `onError` to handle them yourself instead:
184
101
 
185
- // Clear all cache entries
186
- cache.clear();
102
+ ```typescript
103
+ const cache = new AsyncLRUCache({
104
+ capacity: 100,
105
+ onError(error, { key, source }) {
106
+ // source is 'loader' | 'saver' | 'put-chain'
107
+ myLogger.error(`cache ${source} failed for ${key}`, error);
108
+ },
109
+ });
187
110
  ```
188
111
 
189
112
  ## API Reference
190
113
 
191
114
  ### `AsyncLRUCache<K, V>`
192
115
 
193
- #### Constructor
194
-
195
116
  ```typescript
196
- constructor(option: AsyncLRUCacheOption)
117
+ constructor(option: AsyncLRUCacheOption<K>)
197
118
  ```
198
119
 
199
- #### `AsyncLRUCacheOption`
120
+ #### `AsyncLRUCacheOption<K>`
200
121
 
201
122
  | Property | Type | Required | Description |
202
- |----------|------|----------|-------------|
203
- | `capacity` | `number` | Yes | Maximum number of items allowed in cache. Must be a positive number. |
123
+ |---|---|---|---|
124
+ | `capacity` | `number` | Yes | Maximum number of items allowed in the cache. Must be at least 10. |
125
+ | `defaultTtlMs` | `number` | No | Default TTL applied to entries that don't specify their own. |
126
+ | `cleanupIntervalMs` | `number` | No | Interval for automatic background cleanup of expired entries. |
127
+ | `onError` | `(error: unknown, context: { key: K; source: 'loader' \| 'saver' \| 'put-chain' }) => void` | No | Callback for loader/saver failures. Replaces the default console logging when provided. |
204
128
 
205
129
  #### Methods
206
130
 
207
- ##### `get(key: K, loader: () => Promise<V>): Promise<V>`
208
-
209
- Retrieves data from cache. If not found, uses the loader to load data.
210
-
211
- - **key**: Cache key
212
- - **loader**: Asynchronous loader function executed on cache miss
213
- - **Returns**: Promise that resolves to the required data
214
-
215
- ##### `put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>): Promise<V>`
216
-
217
- Puts a value into cache and optionally executes a saver function for persistence.
218
-
219
- - **key**: Cache key
220
- - **value**: Value to cache
221
- - **saver**: Optional asynchronous saver function
222
- - **Returns**: Promise that resolves to the latest value after saver operation completes
223
-
224
- ##### `invalidate(key: K): void`
131
+ | Method | Description |
132
+ |---|---|
133
+ | `get(key, loader, ttlMs?)` | Returns the cached value, or calls `loader()` on a miss and caches the result. |
134
+ | `get(key)` | Read-only lookup without a loader. On a hit, behaves like the overload above (updates LRU order). On a miss, resolves to `undefined` without touching the cache. |
135
+ | `peek(key)` | Returns the cached value's `Promise<V>`, or `undefined` if missing/expired. Never affects LRU order, even on a hit. |
136
+ | `put(key, value, saver?, ttlMs?)` | Stores `value` and optionally awaits `saver(key, value)` to persist it. |
137
+ | `invalidate(key)` | Removes the entry for `key`, if present. |
138
+ | `clear()` | Removes all entries. |
139
+ | `has(key)` | Returns whether `key` exists and has not expired. Expired entries are evicted as a side effect of this check; it does not affect LRU order. |
140
+ | `size()` | Returns the current number of entries. |
141
+ | `cleanupExpired()` | Immediately removes all expired entries. |
142
+ | `destroy()` | Stops the cleanup timer (if any) and clears all data. |
225
143
 
226
- Invalidates and removes the cache entry for the specified key.
144
+ ## TypeScript
227
145
 
228
- - **key**: Cache key to invalidate
229
-
230
- ##### `clear(): void`
231
-
232
- Clears all cache entries. This method removes all items from cache and manually clears node links to prevent potential memory leaks.
233
-
234
- ## Concurrency Handling
235
-
236
- ### GET Request Merging
237
-
238
- When multiple concurrent requests fetch the same key, AsyncLRUCache automatically merges these requests, ensuring the loader function executes only once:
239
-
240
- ```typescript
241
- // These three concurrent requests will share the same loader execution
242
- const [data1, data2, data3] = await Promise.all([
243
- cache.get('shared-key', loader),
244
- cache.get('shared-key', loader),
245
- cache.get('shared-key', loader)
246
- ]);
247
- ```
248
-
249
- ### PUT Operation Serialization
250
-
251
- Multiple PUT operations for the same key are serialized to ensure they execute in order:
252
-
253
- ```typescript
254
- // These operations will execute sequentially, even if started concurrently
255
- cache.put('key', 'value1', saver1);
256
- cache.put('key', 'value2', saver2);
257
- cache.put('key', 'value3', saver3);
258
- ```
259
-
260
- ## TypeScript Support
261
-
262
- This package is fully written in TypeScript and provides complete type support:
263
-
264
- ```typescript
265
- interface User {
266
- id: string;
267
- name: string;
268
- email: string;
269
- }
270
-
271
- const userCache = new AsyncLRUCache<string, User>({
272
- capacity: 1000
273
- });
274
-
275
- const user: User = await userCache.get('user:123', async () => {
276
- // Loader must return User type
277
- return fetchUserFromAPI('123');
278
- });
279
- ```
280
-
281
- ## Error Handling
282
-
283
- - **Loader Failures**: If loader function throws an exception, corresponding cache entry is automatically removed
284
- - **Saver Failures**: If saver function fails, cache entry is also removed to ensure data consistency
285
- - **Operation Chain Errors**: PUT operations ignore previous operation errors, allowing new operations to proceed
286
- - **Error Logging**: All errors are automatically logged to console for debugging
287
-
288
- ## Memory Management
289
-
290
- AsyncLRUCache provides comprehensive memory management:
291
-
292
- - **Automatic Eviction**: Automatically removes least recently used items based on capacity
293
- - **Manual Cleanup**: `clear()` method thoroughly cleans all node links to prevent memory leaks
294
- - **Error Cleanup**: Automatically cleans related cache entries when operations fail
146
+ This package is written in TypeScript and ships with type declarations; no separate `@types` package is required.
295
147
 
296
148
  ## License
297
149
 
@@ -301,6 +153,3 @@ MIT
301
153
 
302
154
  Issues and pull requests are welcome on [GitHub](https://github.com/wfp99/async-lru-cache).
303
155
 
304
- ## Author
305
-
306
- Wang Feng Ping
package/README.zh-TW.md CHANGED
@@ -1,57 +1,20 @@
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๏ผ‰่จ˜ๆ†ถ้ซ”ๅฟซๅ–๏ผŒ้ฉ็”จๆ–ผ Node.js ่ˆ‡ TypeScriptใ€‚
4
8
 
5
9
  ## ็‰น่‰ฒ
6
10
 
7
- - ๐Ÿš€ **้žๅŒๆญฅๆ”ฏๆด**๏ผšๆ”ฏๆด้žๅŒๆญฅ็š„่ผ‰ๅ…ฅๅ™จ๏ผˆloader๏ผ‰ๅ’Œไฟๅญ˜ๅ™จ๏ผˆsaver๏ผ‰ๅ‡ฝๆ•ธ
8
- - ๐Ÿ”„ **่‡ชๅ‹•ๅˆไฝต**๏ผš่‡ชๅ‹•ๅˆไฝต็›ธๅŒ key ็š„ไธฆ็™ผ GET ่ซ‹ๆฑ‚
9
- - ๐Ÿ“ **ๅบๅˆ—ๅŒ–ๅฏซๅ…ฅ**๏ผšๅบๅˆ—ๅŒ–ๅŒไธ€ key ็š„ PUT ๆ“ไฝœ๏ผŒ็ขบไฟ้ †ๅบๅŸท่กŒ
10
- - ๐Ÿ—‘๏ธ **LRU ๆท˜ๆฑฐ็ญ–็•ฅ**๏ผšๅฏฆ็พ LRU ๆผ”็ฎ—ๆณ•๏ผŒ่‡ชๅ‹•็งป้™คๆœ€ไน…ๆœชไฝฟ็”จ็š„้ …็›ฎ
11
- - โฐ **TTL ๆ”ฏๆด**๏ผšๆ”ฏๆดๅฏ้…็ฝฎ็š„็”Ÿๅญ˜ๆ™‚้–“๏ผˆTime To Live๏ผ‰๏ผŒ่‡ชๅ‹•้ŽๆœŸๅฟซๅ–้ …็›ฎ
12
- - ๐Ÿ›ก๏ธ **้Œฏ่ชค่™•็†**๏ผšๅฎŒๅ–„็š„้Œฏ่ชค่™•็†ๆฉŸๅˆถ๏ผŒ่ผ‰ๅ…ฅๆˆ–ไฟๅญ˜ๅคฑๆ•—ๆ™‚่‡ชๅ‹•ๆธ…็†ๅฟซๅ–
13
- - ๐Ÿงน **ๅฎŒๆ•ดๆธ…้™ค**๏ผšๆ”ฏๆดไธ€ๆฌกๆ€งๆธ…้™คๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎ
14
-
15
- ## ็‚บไป€้บผ้ธๆ“‡ AsyncLRUCache๏ผŸ
16
-
17
- ### ๅ•้กŒๆ‰€ๅœจ
18
- ๅ‚ณ็ตฑ็š„ๅฟซๅ–่งฃๆฑบๆ–นๆกˆๅœจ่™•็†้žๅŒๆญฅๆ“ไฝœๆ™‚็ถ“ๅธธ้‡ๅˆฐๅ›ฐ้›ฃ๏ผš
19
- - **็ซถๆ…‹ๆขไปถ**๏ผšๅฐๅŒไธ€่ณ‡ๆบ็š„ๅคšๅ€‹ไธฆ็™ผ่ซ‹ๆฑ‚ๅฏ่ƒฝ่งธ็™ผ้‡่ค‡็š„ๆ˜‚่ฒดๆ“ไฝœ
20
- - **่จ˜ๆ†ถ้ซ”ๆดฉๆผ**๏ผšๅคฑๆ•—็š„ๆ“ไฝœๅฏ่ƒฝๅœจๅฟซๅ–ไธญ็•™ไธ‹้ŽๆœŸ้ …็›ฎ่€Œๆฒ’ๆœ‰้ฉ็•ถ็š„ๆธ…็†
21
- - **่ค‡้›œ็š„็‹€ๆ…‹็ฎก็†**๏ผšๅ”่ชฟๅฟซๅ–ๆ›ดๆ–ฐ่ˆ‡ๅค–้ƒจๆŒไน…ๅŒ–ๅฑค่ฎŠๅพ—ๅฎนๆ˜“ๅ‡บ้Œฏ
22
- - **ๅค–้ƒจไพ่ณด**๏ผš่จฑๅคšๅฟซๅ–่งฃๆฑบๆ–นๆกˆ้œ€่ฆๅค–้ƒจๆœๅ‹™๏ผˆRedisใ€Memcached๏ผ‰๏ผŒๅขžๅŠ ๅŸบ็คŽๆžถๆง‹่ค‡้›œๆ€งใ€็ถฒ่ทฏๅปถ้ฒๅ’Œ้ƒจ็ฝฒๆˆๆœฌ
23
-
24
- ### ่งฃๆฑบๆ–นๆกˆ
25
- AsyncLRUCache ้€้Žไปฅไธ‹ๆ–นๅผ่งฃๆฑบ้€™ไบ›ๆŒ‘ๆˆฐ๏ผš
26
- - **ๆ™บๆ…ง่ซ‹ๆฑ‚ๅˆไฝต**๏ผšๅฐๅŒไธ€ key ็š„ๅคšๅ€‹ไธฆ็™ผ GET ๅ‘ผๅซ่‡ชๅ‹•ๅ…ฑไบซๅ–ฎไธ€่ผ‰ๅ…ฅๅ™จๅŸท่กŒ
27
- - **่‡ชๅ‹•้Œฏ่ชคๆขๅพฉ**๏ผšๅคฑๆ•—็š„ๆ“ไฝœๆœƒ่‡ชๅ‹•ๆธ…็†๏ผŒ้˜ฒๆญข่จ˜ๆ†ถ้ซ”ๆดฉๆผๅ’Œ้ŽๆœŸ่ณ‡ๆ–™
28
- - **ๆ“ไฝœๅบๅˆ—ๅŒ–**๏ผšๅŒไธ€ key ็š„ PUT ๆ“ไฝœๆœƒๆŽ’้šŠไธฆไพๅบๅŸท่กŒ๏ผŒ็ขบไฟ่ณ‡ๆ–™ไธ€่‡ดๆ€ง
29
- - **้›ถๅค–้ƒจไพ่ณด**๏ผš็ด”่จ˜ๆ†ถ้ซ”ๅ…ง่งฃๆฑบๆ–นๆกˆ๏ผŒ็„ก้œ€ๅค–้ƒจๅฟซๅ–ๆœๅ‹™๏ผŒๆธ›ๅฐ‘ๅŸบ็คŽๆžถๆง‹่ค‡้›œๆ€งใ€่จญๅฎšๆˆๆœฌๅ’Œ็ถฒ่ทฏๅปถ้ฒ๏ผŒๅŒๆ™‚ๆ้ซ˜ๅฏ้ ๆ€งๅ’Œๆ•ˆ่ƒฝ
30
-
31
- ### ้ฉ็”จๅ ดๆ™ฏ
32
- AsyncLRUCache ้žๅธธ้ฉๅˆไปฅไธ‹ๆƒ…ๅขƒ๏ผš
33
- - **API ๅ›žๆ‡‰ๅฟซๅ–**๏ผšๅฟซๅ–ๆ˜‚่ฒด็š„ HTTP ่ซ‹ๆฑ‚ไธฆ่‡ชๅ‹•ๅŽป้‡่ค‡
34
- - **่ณ‡ๆ–™ๅบซๆŸฅ่ฉข็ตๆžœ**๏ผšๆธ›ๅฐ‘่ณ‡ๆ–™ๅบซ่ฒ ่ผ‰ๅŒๆ™‚็ถญๆŒ่ณ‡ๆ–™ไธ€่‡ดๆ€ง
35
- - **่จˆ็ฎ—ๅ€ผ**๏ผšๅฟซๅ–ๆ˜‚่ฒด็š„่จˆ็ฎ—็ตๆžœไธฆๅ…งๅปบๅคฑๆ•ˆๆฉŸๅˆถ
36
- - **ๆช”ๆกˆ็ณป็ตฑๆ“ไฝœ**๏ผšๅฟซๅ–ๆช”ๆกˆ่ฎ€ๅฏซไธฆๆไพ›ๆŒไน…ๅŒ–ไธฒๆŽฅ
37
- - **ๅพฎๆœๅ‹™้€š่จŠ**๏ผš้€้Žๅฟซๅ–ๆธ›ๅฐ‘ๆœๅ‹™้–“ๅ‘ผๅซ
38
-
39
- ### ็‚บไป€้บผ้ธๆ“‡่จ˜ๆ†ถ้ซ”ๅ…งๅฟซๅ–่€Œ้žๅค–้ƒจๅฟซๅ–ๆœๅ‹™๏ผŸ
40
-
41
- **็ฐกๅ–ฎๆ€ง่ˆ‡ๆ•ˆ่ƒฝ**
42
- - **็„ก้œ€่จญๅฎš**๏ผšๅฎ‰่ฃๅพŒ็ซ‹ๅณไฝฟ็”จ๏ผŒ็„ก้œ€้…็ฝฎๅค–้ƒจๆœๅ‹™
43
- - **้›ถ็ถฒ่ทฏๅปถ้ฒ**๏ผš็›ดๆŽฅ่จ˜ๆ†ถ้ซ”ๅญ˜ๅ–ๆไพ›ๅพฎ็ง’็ดšๅ›žๆ‡‰ๆ™‚้–“
44
- - **็ฐกๅŒ–ๅŸบ็คŽๆžถๆง‹**๏ผš็œๅŽปๅฟซๅ–ไผบๆœๅ™จ๏ผŒๆธ›ๅฐ‘็‡Ÿ้‹่ค‡้›œๆ€งๅ’Œๆˆๆœฌ
45
-
46
- **ๅฏ้ ๆ€ง่ˆ‡ๅฏ้ ๆธฌๆ€ง**
47
- - **็„ก็ถฒ่ทฏไพ่ณด**๏ผšๅฟซๅ–ๆ“ไฝœไธๆœƒๅ› ็ถฒ่ทฏๅ•้กŒ่€Œๅคฑๆ•—
48
- - **ไธ€่‡ดๆ€งๆ•ˆ่ƒฝ**๏ผšๆฒ’ๆœ‰ๅฏ่ฎŠ็š„็ถฒ่ทฏๅปถ้ฒๅฝฑ้Ÿฟๅฟซๅ–ๆ•ˆ่ƒฝ
49
- - **็ฐกๅŒ–้ƒจ็ฝฒ**๏ผšไฝœ็‚บๆ‡‰็”จ็จ‹ๅผ็š„ไธ€้ƒจๅˆ†้ƒจ็ฝฒ๏ผŒ็„ก้œ€็ฎก็†็จ็ซ‹็š„ๅฟซๅ–ๅŸบ็คŽๆžถๆง‹
50
-
51
- **้–‹็™ผๆ•ˆ็އ**
52
- - **ๅณๆ™‚้–‹็™ผ**๏ผš็„ก้œ€่จญๅฎš Redisใ€Memcached ็ญ‰ๅณๅฏ้–‹ๅง‹็ทจ็ขผ
53
- - **่ผ•้ฌ†ๆธฌ่ฉฆ**๏ผšๅ–ฎๅ…ƒๆธฌ่ฉฆ็„ก้œ€ๅค–้ƒจๆœๅ‹™ไพ่ณดๅณๅฏๅŸท่กŒ
54
- - **็ฐกๅŒ–้™ค้Œฏ**๏ผšๅฟซๅ–่กŒ็‚บๅœจๆ‡‰็”จ็จ‹ๅผๆต็จ‹ๅ…งๅฏ้ ๆธฌไธ”ๅฏ่ฟฝ่นค
11
+ - ๆ”ฏๆด้žๅŒๆญฅ็š„่ผ‰ๅ…ฅๅ™จ๏ผˆloader๏ผ‰่ˆ‡ไฟๅญ˜ๅ™จ๏ผˆsaver๏ผ‰ๅ‡ฝๆ•ธ
12
+ - ๅฐๅŒไธ€ key ็š„ไธฆ็™ผ `get()` ๅ‘ผๅซๅ…ฑ็”จๅ–ฎไธ€่ผ‰ๅ…ฅๅ™จๅŸท่กŒ
13
+ - ๅฐๅŒไธ€ key ็š„ๅคšๅ€‹ `put()` ๅ‘ผๅซๆœƒๅบๅˆ—ๅŒ–ไธฆไพๅบๅŸท่กŒ
14
+ - ่ถ…ๅ‡บ่จญๅฎšๅฎน้‡ๆ™‚่‡ชๅ‹•ๅŸท่กŒ LRU ๆท˜ๆฑฐ
15
+ - ๆ”ฏๆดๆฏ็ญ†้ …็›ฎ็š„ๅฏ้ธ TTL๏ผˆๅญ˜ๆดปๆ™‚้–“๏ผ‰๏ผŒไธฆๅฏ่จญๅฎš้€ฑๆœŸๆ€ง่ƒŒๆ™ฏๆธ…็†
16
+ - ๅฏ้€้Ž `onError` ่‡ช่จ‚้Œฏ่ชค่™•็†๏ผŒ้ ่จญ็‚บ่จ˜้Œ„ๅˆฐ console
17
+ - `get()` ๅฏ็œ็•ฅ loader ้€ฒ่กŒๅ”ฏ่ฎ€ๆŸฅ่ฉข๏ผ›`peek()` ๅ‰‡ๆไพ›ๅฎŒๅ…จไธๅฝฑ้Ÿฟ LRU ้ †ๅบ็š„ๆŸฅ่ฉขๆ–นๅผ
55
18
 
56
19
  ## ๅฎ‰่ฃ
57
20
 
@@ -70,224 +33,116 @@ npm install @wfp99/async-lru-cache
70
33
  ```typescript
71
34
  import { AsyncLRUCache } from '@wfp99/async-lru-cache';
72
35
 
73
- // ๅ‰ตๅปบไธ€ๅ€‹ๆœ€ๅคšๅฎน็ด 100 ๅ€‹้ …็›ฎ็š„ๅฟซๅ–
74
- const cache = new AsyncLRUCache({
75
- capacity: 100
76
- });
36
+ const cache = new AsyncLRUCache<string, User>({ capacity: 100 });
77
37
 
78
- // ไฝฟ็”จ get ๆ–นๆณ•็ฒๅ–่ณ‡ๆ–™
79
- const data = await cache.get('user:123', async () => {
80
- // ๅฟซๅ–ๆœชๅ‘ฝไธญๆ™‚ๅŸท่กŒ็š„่ผ‰ๅ…ฅๅ™จๅ‡ฝๆ•ธ
81
- const response = await fetch('/api/users/123');
82
- return response.json();
38
+ // get() ๅœจๅฟซๅ–ๆœชๅ‘ฝไธญๆ™‚ๅ‘ผๅซ loader๏ผŒไธฆๅฟซๅ–ๅ…ถๅ›žๅ‚ณๅ€ผใ€‚
39
+ const user = await cache.get('user:123', async () => {
40
+ const response = await fetch('/api/users/123');
41
+ return response.json();
83
42
  });
84
43
 
85
- // ไฝฟ็”จ put ๆ–นๆณ•ๅ„ฒๅญ˜่ณ‡ๆ–™
44
+ // put() ็›ดๆŽฅๅ„ฒๅญ˜ๅ€ผ๏ผŒไธฆๅฏ้ธๆ“‡้€้Ž saver ้€ฒ่กŒๆŒไน…ๅŒ–ใ€‚
86
45
  await cache.put('user:456', userData, async (key, value) => {
87
- // ๅฏ้ธ็š„ไฟๅญ˜ๅ™จๅ‡ฝๆ•ธ๏ผŒ็”จๆ–ผๆŒไน…ๅŒ–่ณ‡ๆ–™
88
- await saveToDatabase(key, value);
46
+ await saveToDatabase(key, value);
89
47
  });
90
48
  ```
91
49
 
92
- ### ้Œฏ่ชค่™•็†
50
+ ### ๅ”ฏ่ฎ€ๆŸฅ่ฉข
93
51
 
94
52
  ```typescript
95
- try {
96
- const data = await cache.get('problematic-key', async () => {
97
- throw new Error('่ผ‰ๅ…ฅๅคฑๆ•—');
98
- });
99
- } catch (error) {
100
- console.error('ๅฟซๅ–่ผ‰ๅ…ฅๅคฑๆ•—:', error);
101
- // ๅคฑๆ•—็š„้ …็›ฎๆœƒ่‡ชๅ‹•ๅพžๅฟซๅ–ไธญ็งป้™ค
102
- }
103
- ```
53
+ // ็œ็•ฅ loader ็š„ get() ๅฑฌๆ–ผๅ”ฏ่ฎ€ๆŸฅ่ฉข๏ผšๅ‘ฝไธญๆ™‚่กŒ็‚บ่ˆ‡ไธ€่ˆฌ get() ็›ธๅŒ๏ผˆๆœƒๆ›ดๆ–ฐ LRU ้ †ๅบ๏ผ‰๏ผ›
54
+ // ๆœชๅ‘ฝไธญๆ™‚ๅ›žๅ‚ณ undefined๏ผŒไธ”ไธๆœƒๆ–ฐๅขžไปปไฝ•้ …็›ฎๆˆ–ๅฝฑ้Ÿฟ LRU ้ †ๅบใ€‚
55
+ const cached = await cache.get('user:123'); // User | undefined
104
56
 
105
- ### ้€ฒ้šŽไฝฟ็”จ
106
-
107
- ```typescript
108
- // ่ˆ‡่ณ‡ๆ–™ๅบซ้…ๅˆไฝฟ็”จ
109
- const cache = new AsyncLRUCache<string, UserData>({ capacity: 500 });
110
-
111
- // ๅพž่ณ‡ๆ–™ๅบซ่ผ‰ๅ…ฅไธฆ่‡ชๅ‹•ๅฟซๅ–
112
- const user = await cache.get(`user:${userId}`, async () => {
113
- return await database.users.findById(userId);
114
- });
115
-
116
- // ๅŒๆ™‚ๅ„ฒๅญ˜ๅˆฐๅฟซๅ–ๅ’Œ่ณ‡ๆ–™ๅบซ
117
- await cache.put(`user:${userId}`, updatedUser, async (key, value) => {
118
- await database.users.update(userId, value);
119
- });
57
+ // peek() ็„ก่ซ–ๅ‘ฝไธญๆˆ–ๆœชๅ‘ฝไธญ้ƒฝไธๆœƒๅฝฑ้Ÿฟ LRU ้ †ๅบใ€‚
58
+ const peeked = await cache.peek('user:123'); // Promise<User> | undefined
120
59
  ```
121
60
 
122
- ### TTL๏ผˆ็”Ÿๅญ˜ๆ™‚้–“๏ผ‰ๆ”ฏๆด
123
-
124
- AsyncLRUCache ๆ”ฏๆดไฝฟ็”จ TTL๏ผˆTime To Live๏ผ‰่‡ชๅ‹•้ŽๆœŸๅฟซๅ–้ …็›ฎ๏ผš
61
+ ### TTL๏ผˆๅญ˜ๆดปๆ™‚้–“๏ผ‰
125
62
 
126
63
  ```typescript
127
- import { AsyncLRUCache } from '@wfp99/async-lru-cache';
128
-
129
- // ๅปบ็ซ‹ๆ”ฏๆด TTL ็š„ๅฟซๅ–
130
64
  const cache = new AsyncLRUCache({
131
- capacity: 100,
132
- defaultTtlMs: 5000, // ้ ่จญ TTL ็‚บ 5 ็ง’
133
- cleanupIntervalMs: 10000 // ๆฏ 10 ็ง’ๆธ…็†้ŽๆœŸ้ …็›ฎ
134
- });
135
-
136
- // ไฝฟ็”จ้ ่จญ TTL
137
- const data1 = await cache.get('key1', async () => {
138
- return await fetchData();
65
+ capacity: 100,
66
+ defaultTtlMs: 5000, // ๅฅ—็”จๅˆฐๆฏ็ญ†้ …็›ฎ็š„้ ่จญ TTL
67
+ cleanupIntervalMs: 10000, // ้€ฑๆœŸๆ€ง่ƒŒๆ™ฏๆธ…็†้ŽๆœŸ้ …็›ฎ
139
68
  });
140
69
 
141
- // ็‚บ็‰นๅฎš้ …็›ฎ่ฆ†ๅฏซ TTL
142
- const data2 = await cache.get('key2', async () => {
143
- return await fetchCriticalData();
144
- }, 30000); // 30 ็ง’ TTL
145
-
146
- // ไฝฟ็”จ่‡ช่จ‚ TTL ้€ฒ่กŒ Put ๆ“ไฝœ
147
- await cache.put('key3', value, async (key, val) => {
148
- await saveToDb(key, val);
149
- }, 60000); // 1 ๅˆ†้˜ TTL
70
+ // ็‚บ็‰นๅฎš้ …็›ฎ่ฆ†ๅฏซ้ ่จญ TTLใ€‚
71
+ await cache.get('key1', async () => fetchData(), 30000);
150
72
 
151
- // ๆ‰‹ๅ‹•ๆธ…็†้ŽๆœŸ้ …็›ฎ
152
- cache.cleanupExpired();
73
+ cache.has('key1'); // ้ …็›ฎ้ŽๆœŸๅพŒๅ›žๅ‚ณ false
74
+ cache.cleanupExpired(); // ๆ‰‹ๅ‹•็งป้™ค้ŽๆœŸ้ …็›ฎ
75
+ cache.destroy(); // ๅœๆญขๆธ…็†่จˆๆ™‚ๅ™จไธฆๆธ…้™คๆ‰€ๆœ‰่ณ‡ๆ–™
76
+ ```
153
77
 
154
- // ๆชขๆŸฅ key ๆ˜ฏๅฆๅญ˜ๅœจไธ”ๆœช้ŽๆœŸ
155
- if (cache.has('key1')) {
156
- console.log('Key ๅญ˜ๅœจไธ”ๆœ‰ๆ•ˆ');
157
- }
78
+ ### ไธฆ็™ผ่™•็†
158
79
 
159
- // ๅ–ๅพ—ๅฟซๅ–ๅคงๅฐ
160
- console.log('็›ฎๅ‰ๅฟซๅ–ๅคงๅฐ:', cache.size());
80
+ ```typescript
81
+ // ไปฅไธ‹ไธฆ็™ผ็š„ get() ๅ‘ผๅซๆœƒๅ…ฑ็”จๅŒไธ€ๅ€‹ loader ๅŸท่กŒใ€‚
82
+ const [a, b, c] = await Promise.all([
83
+ cache.get('shared-key', loader),
84
+ cache.get('shared-key', loader),
85
+ cache.get('shared-key', loader),
86
+ ]);
161
87
 
162
- // ้Šทๆฏ€ๅฟซๅ–๏ผˆๅœๆญขๆธ…็†่จˆๆ™‚ๅ™จไธฆๆธ…้™คๆ‰€ๆœ‰่ณ‡ๆ–™๏ผ‰
163
- cache.destroy();
88
+ // ไปฅไธ‹ put() ๅ‘ผๅซๅณไฝฟไธฆ็™ผๅ•Ÿๅ‹•๏ผŒไนŸๆœƒไพๅบๅบๅˆ—ๅŒ–ๅŸท่กŒใ€‚
89
+ cache.put('key', 'value1', saver1);
90
+ cache.put('key', 'value2', saver2);
91
+ cache.put('key', 'value3', saver3);
164
92
  ```
165
93
 
166
- #### TTL ้…็ฝฎ้ธ้ …
167
-
168
- - **`defaultTtlMs`**๏ผšๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎ็š„้ ่จญ TTL๏ผˆๅฏ้ธ๏ผ‰
169
- - **`cleanupIntervalMs`**๏ผš้ŽๆœŸ้ …็›ฎ็š„่‡ชๅ‹•ๆธ…็†้–“้š”๏ผˆๅฏ้ธ๏ผ‰
170
- - ๅ€‹ๅˆฅ็š„ `get()` ๅ’Œ `put()` ๆ–นๆณ•ๆŽฅๅ— TTL ่ฆ†ๅฏซ
171
- - ้ŽๆœŸ้ …็›ฎๅœจๆญฃๅธธๆ“ไฝœๆœŸ้–“ๆœƒ่‡ชๅ‹•็งป้™ค
172
- - ๅ‘ผๅซ `cleanupExpired()` ้€ฒ่กŒๆ‰‹ๅ‹•ๆธ…็†
173
- - ๅ‘ผๅซ `destroy()` ๅœๆญข่จˆๆ™‚ๅ™จไธฆ้˜ฒๆญข่จ˜ๆ†ถ้ซ”ๆดฉๆผ
94
+ ### ้Œฏ่ชค่™•็†
174
95
 
175
- ### ๅฟซๅ–็ฎก็†
96
+ - ่‹ฅ่ผ‰ๅ…ฅๅ™จ๏ผˆ`get`๏ผ‰ๆ‹‹ๅ‡บไพ‹ๅค–๏ผŒ่ฉฒ้ …็›ฎๆœƒๅพžๅฟซๅ–ไธญ็งป้™ค๏ผŒ้Œฏ่ชคๆœƒๅ‚ณ้ž็ตฆๅ‘ผๅซ็ซฏใ€‚
97
+ - ่‹ฅไฟๅญ˜ๅ™จ๏ผˆ`put`๏ผ‰ๆ‹‹ๅ‡บไพ‹ๅค–๏ผŒ่ฉฒ้ …็›ฎไนŸๆœƒ่ขซ็งป้™ค๏ผŒ้ฟๅ…ๅฟซๅ–ๅˆฐๅฐšๆœชๆŒไน…ๅŒ–็š„ๅ€ผใ€‚
98
+ - `put()` ๆœƒๅฟฝ็•ฅๅŒไธ€ key ๅ‰ไธ€ๅ€‹ๅทฒ็ตๆŸๆ“ไฝœ็š„ๅคฑๆ•—๏ผŒ่ฎ“ๆ–ฐ็š„ๆ“ไฝœๅฏไปฅ็นผ็บŒ้€ฒ่กŒใ€‚
99
+ - ้ ่จญๆƒ…ๆณไธ‹๏ผŒไธŠ่ฟฐๅคฑๆ•—ๆœƒ้€้Ž `console.error`/`console.warn` ่จ˜้Œ„ใ€‚่‹ฅ่ฆ่‡ช่กŒ่™•็†๏ผŒๅฏๆไพ› `onError`๏ผš
176
100
 
177
101
  ```typescript
178
- // ๆ‰‹ๅ‹•็งป้™ค็‰นๅฎšๅฟซๅ–้ …็›ฎ
179
- cache.invalidate('user:123');
180
-
181
- // ๆธ…้™คๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎ
182
- cache.clear();
102
+ const cache = new AsyncLRUCache({
103
+ capacity: 100,
104
+ onError(error, { key, source }) {
105
+ // source ็‚บ 'loader' | 'saver' | 'put-chain'
106
+ myLogger.error(`cache ${source} failed for ${key}`, error);
107
+ },
108
+ });
183
109
  ```
184
110
 
185
111
  ## API ๅƒ่€ƒ
186
112
 
187
113
  ### `AsyncLRUCache<K, V>`
188
114
 
189
- #### ๅปบๆง‹ๅ‡ฝๆ•ธ
190
-
191
115
  ```typescript
192
- constructor(option: AsyncLRUCacheOption)
116
+ constructor(option: AsyncLRUCacheOption<K>)
193
117
  ```
194
118
 
195
- #### `AsyncLRUCacheOption`
119
+ #### `AsyncLRUCacheOption<K>`
196
120
 
197
121
  | ๅฑฌๆ€ง | ้กžๅž‹ | ๅฟ…้œ€ | ๆ่ฟฐ |
198
- |-----|------|------|------|
199
- | `capacity` | `number` | ๆ˜ฏ | ๅฟซๅ–ไธญๅ…่จฑ็š„ๆœ€ๅคง้ …็›ฎๆ•ธ้‡๏ผŒๅฟ…้ ˆ็‚บๆญฃๆ•ธ |
122
+ |---|---|---|---|
123
+ | `capacity` | `number` | ๆ˜ฏ | ๅฟซๅ–ไธญๅ…่จฑ็š„ๆœ€ๅคง้ …็›ฎๆ•ธ้‡๏ผŒๆœ€ๅฐๅ€ผ็‚บ 10 |
124
+ | `defaultTtlMs` | `number` | ๅฆ | ๅฅ—็”จๅˆฐๆœชๆŒ‡ๅฎš TTL ไน‹้ …็›ฎ็š„้ ่จญ TTL |
125
+ | `cleanupIntervalMs` | `number` | ๅฆ | ่‡ชๅ‹•่ƒŒๆ™ฏๆธ…็†้ŽๆœŸ้ …็›ฎ็š„ๅŸท่กŒ้–“้š” |
126
+ | `onError` | `(error: unknown, context: { key: K; source: 'loader' \| 'saver' \| 'put-chain' }) => void` | ๅฆ | ่ผ‰ๅ…ฅๅ™จ/ไฟๅญ˜ๅ™จๅคฑๆ•—ๆ™‚็š„ๅ›žๅ‘ผๅ‡ฝๆ•ธใ€‚ๆไพ›ๅพŒๅฐ‡ๅ–ไปฃ้ ่จญ็š„ console ่จ˜้Œ„่กŒ็‚บ |
200
127
 
201
128
  #### ๆ–นๆณ•
202
129
 
203
- ##### `get(key: K, loader: () => Promise<V>): Promise<V>`
204
-
205
- ๅพžๅฟซๅ–ไธญ็ฒๅ–่ณ‡ๆ–™ใ€‚ๅฆ‚ๆžœไธๅญ˜ๅœจ๏ผŒไฝฟ็”จ่ผ‰ๅ…ฅๅ™จ่ผ‰ๅ…ฅ่ณ‡ๆ–™ใ€‚
206
-
207
- - **key**: ๅฟซๅ–้ต
208
- - **loader**: ๅฟซๅ–ๆœชๅ‘ฝไธญๆ™‚ๅŸท่กŒ็š„้žๅŒๆญฅ่ผ‰ๅ…ฅๅ‡ฝๆ•ธ
209
- - **ๅ›žๅ‚ณ**: Promise๏ผŒ่งฃๆž็‚บๆ‰€้œ€็š„่ณ‡ๆ–™
210
-
211
- ##### `put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>): Promise<V>`
212
-
213
- ๅฐ‡ๅ€ผๆ”พๅ…ฅๅฟซๅ–ไธญ๏ผŒไธฆๅฏ้ธๆ“‡ๆ€งๅœฐๅŸท่กŒไฟๅญ˜ๅ™จๅ‡ฝๆ•ธ้€ฒ่กŒๆŒไน…ๅŒ–ใ€‚
130
+ | ๆ–นๆณ• | ๆ่ฟฐ |
131
+ |---|---|
132
+ | `get(key, loader, ttlMs?)` | ๅ›žๅ‚ณๅฟซๅ–ๅ€ผ๏ผ›ๆœชๅ‘ฝไธญๆ™‚ๅ‘ผๅซ `loader()` ไธฆๅฟซๅ–ๅ…ถ็ตๆžœ |
133
+ | `get(key)` | ็œ็•ฅ loader ็š„ๅ”ฏ่ฎ€ๆŸฅ่ฉขใ€‚ๅ‘ฝไธญๆ™‚่กŒ็‚บ่ˆ‡ไธŠๆ–นๅคš่ผ‰็›ธๅŒ๏ผˆๆœƒๆ›ดๆ–ฐ LRU ้ †ๅบ๏ผ‰๏ผ›ๆœชๅ‘ฝไธญๆ™‚ๅ›žๅ‚ณ `undefined`๏ผŒไธๆœƒ็•ฐๅ‹•ๅฟซๅ– |
134
+ | `peek(key)` | ๅ›žๅ‚ณๅฟซๅ–ๅ€ผ็š„ `Promise<V>`๏ผ›่‹ฅไธๅญ˜ๅœจๆˆ–ๅทฒ้ŽๆœŸๅ‰‡ๅ›žๅ‚ณ `undefined`ใ€‚็„ก่ซ–ๅ‘ฝไธญ่ˆ‡ๅฆ้ƒฝไธๅฝฑ้Ÿฟ LRU ้ †ๅบ |
135
+ | `put(key, value, saver?, ttlMs?)` | ๅ„ฒๅญ˜ `value`๏ผŒไธฆๅฏ้ธๆ“‡็ญ‰ๅพ… `saver(key, value)` ้€ฒ่กŒๆŒไน…ๅŒ– |
136
+ | `invalidate(key)` | ่‹ฅๅญ˜ๅœจ๏ผŒ็งป้™ค `key` ๅฐๆ‡‰็š„้ …็›ฎ |
137
+ | `clear()` | ็งป้™คๆ‰€ๆœ‰้ …็›ฎ |
138
+ | `has(key)` | ๅ›žๅ‚ณ `key` ๆ˜ฏๅฆๅญ˜ๅœจไธ”ๆœช้ŽๆœŸใ€‚ๆญคๆชขๆŸฅๆœƒ้ †ๅธถๆธ…้™คๅทฒ้ŽๆœŸ็š„้ …็›ฎ๏ผŒไฝ†ไธๅฝฑ้Ÿฟ LRU ้ †ๅบ |
139
+ | `size()` | ๅ›žๅ‚ณ็›ฎๅ‰็š„้ …็›ฎๆ•ธ้‡ |
140
+ | `cleanupExpired()` | ็ซ‹ๅณ็งป้™คๆ‰€ๆœ‰้ŽๆœŸ้ …็›ฎ |
141
+ | `destroy()` | ๅœๆญขๆธ…็†่จˆๆ™‚ๅ™จ๏ผˆ่‹ฅๆœ‰๏ผ‰ไธฆๆธ…้™คๆ‰€ๆœ‰่ณ‡ๆ–™ |
214
142
 
215
- - **key**: ๅฟซๅ–้ต
216
- - **value**: ่ฆๅฟซๅ–็š„ๅ€ผ
217
- - **saver**: ๅฏ้ธ็š„้žๅŒๆญฅไฟๅญ˜ๅ‡ฝๆ•ธ
218
- - **ๅ›žๅ‚ณ**: Promise๏ผŒ่งฃๆž็‚บไฟๅญ˜ๆ“ไฝœๅฎŒๆˆๅพŒ็š„ๆœ€ๆ–ฐๅ€ผ
143
+ ## TypeScript
219
144
 
220
- ##### `invalidate(key: K): void`
221
-
222
- ไฝฟๆŒ‡ๅฎš้ต็š„ๅฟซๅ–้ …็›ฎๅคฑๆ•ˆไธฆ็งป้™คใ€‚
223
-
224
- - **key**: ่ฆๅคฑๆ•ˆ็š„ๅฟซๅ–้ต
225
-
226
- ##### `clear(): void`
227
-
228
- ๆธ…้™คๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎใ€‚ๆญคๆ–นๆณ•ๆœƒ็งป้™คๅฟซๅ–ไธญ็š„ๆ‰€ๆœ‰้ …็›ฎไธฆๆ‰‹ๅ‹•ๆธ…็†็ฏ€้ปž้€ฃ็ตไปฅ้ฟๅ…ๆฝ›ๅœจ็š„่จ˜ๆ†ถ้ซ”ๆดฉๆผใ€‚
229
-
230
- ## ไธฆ็™ผ่™•็†
231
-
232
- ### GET ่ซ‹ๆฑ‚ๅˆไฝต
233
-
234
- ็•ถๅคšๅ€‹ไธฆ็™ผ่ซ‹ๆฑ‚็ฒๅ–็›ธๅŒ็š„ key ๆ™‚๏ผŒAsyncLRUCache ๆœƒ่‡ชๅ‹•ๅˆไฝต้€™ไบ›่ซ‹ๆฑ‚๏ผŒ็ขบไฟ่ผ‰ๅ…ฅๅ™จๅ‡ฝๆ•ธๅชๅŸท่กŒไธ€ๆฌก๏ผš
235
-
236
- ```typescript
237
- // ้€™ไธ‰ๅ€‹ไธฆ็™ผ่ซ‹ๆฑ‚ๆœƒๅ…ฑไบซๅŒไธ€ๅ€‹่ผ‰ๅ…ฅๅ™จๅŸท่กŒ
238
- const [data1, data2, data3] = await Promise.all([
239
- cache.get('shared-key', loader),
240
- cache.get('shared-key', loader),
241
- cache.get('shared-key', loader)
242
- ]);
243
- ```
244
-
245
- ### PUT ๆ“ไฝœๅบๅˆ—ๅŒ–
246
-
247
- ๅฐๅŒไธ€ key ็š„ๅคšๅ€‹ PUT ๆ“ไฝœๆœƒ่ขซๅบๅˆ—ๅŒ–๏ผŒ็ขบไฟๅฎƒๅ€‘ๆŒ‰้ †ๅบๅŸท่กŒ๏ผš
248
-
249
- ```typescript
250
- // ้€™ไบ›ๆ“ไฝœๆœƒๆŒ‰้ †ๅบๅŸท่กŒ๏ผŒๅณไฝฟๅฎƒๅ€‘ๆ˜ฏไธฆ็™ผๅ•Ÿๅ‹•็š„
251
- cache.put('key', 'value1', saver1);
252
- cache.put('key', 'value2', saver2);
253
- cache.put('key', 'value3', saver3);
254
- ```
255
-
256
- ## TypeScript ๆ”ฏๆด
257
-
258
- ๆญคๅฅ—ไปถๅฎŒๅ…จไฝฟ็”จ TypeScript ็ทจๅฏซ๏ผŒๆไพ›ๅฎŒๆ•ด็š„ๅž‹ๅˆฅๆ”ฏๆด๏ผš
259
-
260
- ```typescript
261
- interface User {
262
- id: string;
263
- name: string;
264
- email: string;
265
- }
266
-
267
- const userCache = new AsyncLRUCache<string, User>({
268
- capacity: 1000
269
- });
270
-
271
- const user: User = await userCache.get('user:123', async () => {
272
- // ่ผ‰ๅ…ฅๅ™จๅฟ…้ ˆๅ›žๅ‚ณ User ๅž‹ๅˆฅ
273
- return fetchUserFromAPI('123');
274
- });
275
- ```
276
-
277
- ## ้Œฏ่ชค่™•็†ๆฉŸๅˆถ
278
-
279
- - **่ผ‰ๅ…ฅๅ™จๅคฑๆ•—**๏ผšๅฆ‚ๆžœ่ผ‰ๅ…ฅๅ™จๅ‡ฝๆ•ธๆ‹‹ๅ‡บ็•ฐๅธธ๏ผŒๅฐๆ‡‰็š„ๅฟซๅ–้ …็›ฎๆœƒ่ขซ่‡ชๅ‹•็งป้™ค
280
- - **ไฟๅญ˜ๅ™จๅคฑๆ•—**๏ผšๅฆ‚ๆžœไฟๅญ˜ๅ™จๅ‡ฝๆ•ธๅคฑๆ•—๏ผŒๅฟซๅ–้ …็›ฎไนŸๆœƒ่ขซ็งป้™ค๏ผŒ็ขบไฟ่ณ‡ๆ–™ไธ€่‡ดๆ€ง
281
- - **ๆ“ไฝœ้ˆ้Œฏ่ชค**๏ผšPUT ๆ“ไฝœๆœƒๅฟฝ็•ฅๅ‰ไธ€ๅ€‹ๆ“ไฝœ็š„้Œฏ่ชค๏ผŒ็ขบไฟๆ–ฐๆ“ไฝœ่ƒฝๅค ็นผ็บŒ้€ฒ่กŒ
282
- - **้Œฏ่ชคๆ—ฅ่ชŒ**๏ผšๆ‰€ๆœ‰้Œฏ่ชค้ƒฝๆœƒ่‡ชๅ‹•่จ˜้Œ„ๅˆฐๆŽงๅˆถๅฐ๏ผŒไพฟๆ–ผ้™ค้Œฏ
283
-
284
- ## ่จ˜ๆ†ถ้ซ”็ฎก็†
285
-
286
- AsyncLRUCache ๅ…ทๅ‚™ๅฎŒๅ–„็š„่จ˜ๆ†ถ้ซ”็ฎก็†ๆฉŸๅˆถ๏ผš
287
-
288
- - **่‡ชๅ‹•ๆท˜ๆฑฐ**๏ผš็•ถๅฟซๅ–้ …็›ฎๆ•ธ้‡่ถ…้Žๅฎน้‡้™ๅˆถๆ™‚๏ผŒ่‡ชๅ‹•็งป้™คๆœ€ไน…ๆœชไฝฟ็”จ็š„้ …็›ฎ
289
- - **ๆ‰‹ๅ‹•ๆธ…็†**๏ผš`clear()` ๆ–นๆณ•ๆœƒๅพนๅบ•ๆธ…็†ๆ‰€ๆœ‰็ฏ€้ปž้€ฃ็ต๏ผŒ้˜ฒๆญข่จ˜ๆ†ถ้ซ”ๆดฉๆผ
290
- - **้Œฏ่ชคๆธ…็†**๏ผšๆ“ไฝœๅคฑๆ•—ๆ™‚่‡ชๅ‹•ๆธ…็†็›ธ้—œๅฟซๅ–้ …็›ฎ
145
+ ๆญคๅฅ—ไปถไปฅ TypeScript ๆ’ฐๅฏซไธฆๅ…งๅปบๅž‹ๅˆฅๅฎฃๅ‘Šๆช”๏ผŒ็„ก้œ€ๅฆๅค–ๅฎ‰่ฃ `@types` ๅฅ—ไปถใ€‚
291
146
 
292
147
  ## ๆŽˆๆฌŠ
293
148
 
@@ -297,6 +152,3 @@ MIT
297
152
 
298
153
  ๆญก่ฟŽๅœจ [GitHub](https://github.com/wfp99/async-lru-cache) ไธŠๆๅ‡บๅ•้กŒๅ’Œ็™ผ้€ Pull Requestใ€‚
299
154
 
300
- ## ไฝœ่€…
301
-
302
- Wang Feng Ping
package/dist/index.d.ts CHANGED
@@ -5,10 +5,31 @@
5
5
  *
6
6
  * @module AsyncLRUCache
7
7
  */
8
+ /**
9
+ * Identifies which internal operation produced an error passed to `AsyncLRUCacheOption.onError`.
10
+ * - `loader`: The loader function passed to `get()` rejected.
11
+ * - `saver`: The saver function passed to `put()` rejected.
12
+ * - `put-chain`: A previous (already-settled) operation for the same key failed; this is a
13
+ * recoverable warning, since the new `put()` operation is still allowed to proceed.
14
+ */
15
+ export type AsyncLRUCacheErrorSource = 'loader' | 'saver' | 'put-chain';
16
+ /**
17
+ * Context information supplied alongside an error to `AsyncLRUCacheOption.onError`.
18
+ */
19
+ export interface AsyncLRUCacheErrorContext<K> {
20
+ /**
21
+ * The cache key associated with the failed operation.
22
+ */
23
+ key: K;
24
+ /**
25
+ * Which internal operation produced the error.
26
+ */
27
+ source: AsyncLRUCacheErrorSource;
28
+ }
8
29
  /**
9
30
  * Configuration options for the AsyncLRUCache.
10
31
  */
11
- export interface AsyncLRUCacheOption {
32
+ export interface AsyncLRUCacheOption<K = unknown> {
12
33
  /**
13
34
  * Maximum capacity of the cache, this is the maximum number of items allowed.
14
35
  * Must not be less than 10.
@@ -26,6 +47,14 @@ export interface AsyncLRUCacheOption {
26
47
  * Setting this enables periodic background cleanup.
27
48
  */
28
49
  cleanupIntervalMs?: number;
50
+ /**
51
+ * Optional callback invoked whenever a loader/saver operation fails.
52
+ * When provided, this callback *replaces* the built-in `console.error`/`console.warn`
53
+ * logging, so you can forward errors to your own logger or monitoring system.
54
+ * If not provided, errors are logged to the console as before (default, backward-compatible behavior).
55
+ * This callback must not throw; any error thrown from it is ignored.
56
+ */
57
+ onError?: (error: unknown, context: AsyncLRUCacheErrorContext<K>) => void;
29
58
  }
30
59
  /**
31
60
  * Asynchronous LRU Cache class.
@@ -60,12 +89,16 @@ export declare class AsyncLRUCache<K, V> {
60
89
  * Timer ID for periodic cleanup of expired entries.
61
90
  */
62
91
  private cleanupTimer?;
92
+ /**
93
+ * Optional user-supplied error callback. When absent, errors are logged to the console.
94
+ */
95
+ private readonly onError?;
63
96
  /**
64
97
  * Creates an instance of AsyncLRUCache with the specified options.
65
98
  * @param option - The configuration options for the cache.
66
99
  * @throws {Error} Throws an error if the provided capacity is less than 10.
67
100
  */
68
- constructor(option: AsyncLRUCacheOption);
101
+ constructor(option: AsyncLRUCacheOption<K>);
69
102
  /**
70
103
  * Cleans up expired entries from the cache.
71
104
  * This method removes all nodes that have exceeded their TTL.
@@ -100,6 +133,15 @@ export declare class AsyncLRUCache<K, V> {
100
133
  * @param node - The node to add to the head of the list.
101
134
  */
102
135
  private _addToHead;
136
+ /**
137
+ * Reports an error that occurred during a loader/saver operation.
138
+ * Delegates to the user-supplied `onError` callback if one was provided in the
139
+ * constructor options; otherwise falls back to logging via `console.error`/`console.warn`.
140
+ * @param error - The error that was thrown/rejected.
141
+ * @param key - The cache key associated with the failed operation.
142
+ * @param source - Which internal operation produced the error.
143
+ */
144
+ private _reportError;
103
145
  /**
104
146
  * Retrieves data from the cache. If it does not exist, uses the loader to load it.
105
147
  * Automatically merges concurrent requests for the same key.
@@ -109,6 +151,25 @@ export declare class AsyncLRUCache<K, V> {
109
151
  * @returns A Promise that resolves to the required data.
110
152
  */
111
153
  get(key: K, loader: () => Promise<V>, ttlMs?: number): Promise<V>;
154
+ /**
155
+ * Retrieves data from the cache without loading it on a miss.
156
+ * On a cache hit, this behaves like the loader-based overload: the entry is moved to
157
+ * the head of the LRU list, same as a normal `get()`/`has()` hit.
158
+ * On a cache miss (including an expired entry), it resolves to `undefined` without
159
+ * adding anything to the cache, so it does not affect the LRU order.
160
+ * @param key - Cache key.
161
+ * @returns A Promise that resolves to the cached value, or `undefined` on a miss.
162
+ */
163
+ get(key: K): Promise<V | undefined>;
164
+ /**
165
+ * Returns the cached value for a key without triggering a loader and without
166
+ * affecting the LRU order, regardless of whether the key is a hit or a miss.
167
+ * If the entry has expired, it is evicted as a side effect of this check, the same
168
+ * way `has()` behaves; this does not count as an LRU-order-affecting access.
169
+ * @param key - Cache key.
170
+ * @returns The cached value's Promise if the key is present and not expired, otherwise `undefined`.
171
+ */
172
+ peek(key: K): Promise<V> | undefined;
112
173
  /**
113
174
  * Puts a value into the cache, and optionally executes a saver function to persist it.
114
175
  * This method serializes multiple put operations for the same key, ensuring they execute in order.
@@ -145,8 +206,15 @@ export declare class AsyncLRUCache<K, V> {
145
206
  size(): number;
146
207
  /**
147
208
  * Checks if a key exists in the cache and is not expired.
209
+ *
210
+ * Note: this method is **not** side-effect-free. If the entry has expired, it is
211
+ * immediately evicted from the cache as part of this check (same as what would
212
+ * happen on the next `get()`/cleanup pass). It does **not** affect LRU order for
213
+ * entries that are still valid (unlike `get()`, calling `has()` does not move the
214
+ * entry to the head of the list).
148
215
  * @param key - The key to check.
149
216
  * @returns True if the key exists and is not expired, false otherwise.
150
217
  */
151
218
  has(key: K): boolean;
152
219
  }
220
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8DH;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,yBAAyB,CAAC,CAAC;IAE3C;;OAEG;IACH,GAAG,EAAE,CAAC,CAAC;IAEP;;OAEG;IACH,MAAM,EAAE,wBAAwB,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,OAAO;IAE/C;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,yBAAyB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CAC1E;AAED;;;;;;;GAOG;AACH,qBAAa,aAAa,CAAC,CAAC,EAAE,CAAC;IAE9B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAS;IAEvC;;OAEG;IACH,OAAO,CAAC,OAAO,CAA4B;IAE3C;;OAEG;IACH,OAAO,CAAC,IAAI,CAA2B;IAEvC;;OAEG;IACH,OAAO,CAAC,IAAI,CAA2B;IAEvC;;OAEG;IACH,OAAO,CAAC,YAAY,CAAC,CAAiB;IAEtC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkE;IAE3F;;;;OAIG;gBACS,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAkB1C;;;OAGG;IACH,OAAO,CAAC,eAAe;IAyBvB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAW9B;;;OAGG;IACH,OAAO,CAAC,cAAc;IAStB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAQnB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAanB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAYlB;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;IA6BpB;;;;;;;OAOG;IACI,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAExE;;;;;;;;OAQG;IACI,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAiD1C;;;;;;;OAOG;IACI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS;IAgB3C;;;;;;;;OAQG;IACI,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IA4DrG;;;OAGG;IACI,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI;IAW/B;;OAEG;IACI,KAAK,IAAI,IAAI;IA2BpB;;;OAGG;IACI,OAAO,IAAI,IAAI;IAatB;;;OAGG;IACI,cAAc,IAAI,IAAI;IAK7B;;;OAGG;IACI,IAAI,IAAI,MAAM;IAKrB;;;;;;;;;;OAUG;IACI,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;CAe3B"}
package/dist/index.js CHANGED
@@ -79,6 +79,7 @@ class AsyncLRUCache {
79
79
  throw new Error("Capacity must be at least 10.");
80
80
  this.capacity = option.capacity;
81
81
  this.defaultTtlMs = option.defaultTtlMs;
82
+ this.onError = option.onError;
82
83
  // Setup periodic cleanup if specified
83
84
  if (option.cleanupIntervalMs && option.cleanupIntervalMs > 0) {
84
85
  this.cleanupTimer = setInterval(() => {
@@ -171,33 +172,60 @@ class AsyncLRUCache {
171
172
  this.tail = node;
172
173
  }
173
174
  /**
174
- * Retrieves data from the cache. If it does not exist, uses the loader to load it.
175
- * Automatically merges concurrent requests for the same key.
176
- * @param key - Cache key.
177
- * @param loader - Asynchronous loader function to execute on cache miss.
178
- * @param ttlMs - Optional TTL override for this entry in milliseconds.
179
- * @returns A Promise that resolves to the required data.
175
+ * Reports an error that occurred during a loader/saver operation.
176
+ * Delegates to the user-supplied `onError` callback if one was provided in the
177
+ * constructor options; otherwise falls back to logging via `console.error`/`console.warn`.
178
+ * @param error - The error that was thrown/rejected.
179
+ * @param key - The cache key associated with the failed operation.
180
+ * @param source - Which internal operation produced the error.
180
181
  */
182
+ _reportError(error, key, source) {
183
+ if (this.onError) {
184
+ try {
185
+ this.onError(error, { key, source });
186
+ }
187
+ catch {
188
+ // Swallow errors thrown by the user-supplied callback; it must not break cache operations.
189
+ }
190
+ return;
191
+ }
192
+ switch (source) {
193
+ case 'loader':
194
+ console.error(`[AsyncLRUCache ERROR] Loader for key ${key} failed. Removing from cache.`, error);
195
+ break;
196
+ case 'saver':
197
+ console.error(`[AsyncLRUCache ERROR] Saver operation for key ${key} failed. Removing from cache.`, error);
198
+ break;
199
+ case 'put-chain':
200
+ console.warn(`[AsyncLRUCache WARN] Previous operation for key ${key} failed. Chaining new PUT operation.`, error?.message ?? error);
201
+ break;
202
+ }
203
+ }
181
204
  get(key, loader, ttlMs) {
182
205
  const node = this.dataMap.get(key);
183
206
  if (node) {
184
207
  // Check if the node has expired
185
208
  if (this._checkAndRemoveExpired(key, node)) {
186
- // Node was expired and removed, proceed to load fresh data
209
+ // Node was expired and removed, proceed to load fresh data (if a loader is provided)
187
210
  }
188
211
  else {
189
212
  this._moveToHead(node);
190
213
  return node.value;
191
214
  }
192
215
  }
216
+ if (!loader) {
217
+ // No loader was provided and there is no valid cached value.
218
+ // Resolve to undefined without touching the cache or its LRU order.
219
+ return Promise.resolve(undefined);
220
+ }
193
221
  const loadingPromise = loader();
194
- const effectiveTtlMs = ttlMs !== null && ttlMs !== void 0 ? ttlMs : this.defaultTtlMs;
222
+ const effectiveTtlMs = ttlMs ?? this.defaultTtlMs;
195
223
  const newNode = new Node(key, loadingPromise, effectiveTtlMs);
196
224
  this.dataMap.set(key, newNode);
197
225
  this._addToHead(newNode);
198
226
  this._evictIfNeeded();
199
227
  loadingPromise.catch(err => {
200
- console.error(`[AsyncLRUCache ERROR] Loader for key ${key} failed. Removing from cache.`, err);
228
+ this._reportError(err, key, 'loader');
201
229
  if (this.dataMap.get(key) === newNode) {
202
230
  this.dataMap.delete(key);
203
231
  this._removeNode(newNode);
@@ -205,6 +233,24 @@ class AsyncLRUCache {
205
233
  });
206
234
  return loadingPromise;
207
235
  }
236
+ /**
237
+ * Returns the cached value for a key without triggering a loader and without
238
+ * affecting the LRU order, regardless of whether the key is a hit or a miss.
239
+ * If the entry has expired, it is evicted as a side effect of this check, the same
240
+ * way `has()` behaves; this does not count as an LRU-order-affecting access.
241
+ * @param key - Cache key.
242
+ * @returns The cached value's Promise if the key is present and not expired, otherwise `undefined`.
243
+ */
244
+ peek(key) {
245
+ const node = this.dataMap.get(key);
246
+ if (!node) {
247
+ return undefined;
248
+ }
249
+ if (this._checkAndRemoveExpired(key, node)) {
250
+ return undefined;
251
+ }
252
+ return node.value;
253
+ }
208
254
  /**
209
255
  * Puts a value into the cache, and optionally executes a saver function to persist it.
210
256
  * This method serializes multiple put operations for the same key, ensuring they execute in order.
@@ -218,7 +264,7 @@ class AsyncLRUCache {
218
264
  const lastPromise = this.dataMap.has(key) ? this.dataMap.get(key).value : Promise.resolve(undefined);
219
265
  const saverPromise = lastPromise.catch((err) => {
220
266
  // Ignore previous operation errors, so the new operation can proceed.
221
- console.warn(`[AsyncLRUCache WARN] Previous operation for key ${key} failed. Chaining new PUT operation.`, err.message);
267
+ this._reportError(err, key, 'put-chain');
222
268
  })
223
269
  .then(() => {
224
270
  // After the previous operation (success or failure) completes, execute this saver.
@@ -230,7 +276,7 @@ class AsyncLRUCache {
230
276
  return value;
231
277
  });
232
278
  let node = this.dataMap.get(key);
233
- const effectiveTtlMs = ttlMs !== null && ttlMs !== void 0 ? ttlMs : this.defaultTtlMs;
279
+ const effectiveTtlMs = ttlMs ?? this.defaultTtlMs;
234
280
  if (!node) {
235
281
  node = new Node(key, saverPromise, effectiveTtlMs);
236
282
  this.dataMap.set(key, node);
@@ -249,9 +295,8 @@ class AsyncLRUCache {
249
295
  this._moveToHead(node);
250
296
  }
251
297
  saverPromise.catch(err => {
252
- var _a;
253
- console.error(`[AsyncLRUCache ERROR] Saver operation for key ${key} failed. Removing from cache.`, err);
254
- if (((_a = this.dataMap.get(key)) === null || _a === void 0 ? void 0 : _a.value) === saverPromise) {
298
+ this._reportError(err, key, 'saver');
299
+ if (this.dataMap.get(key)?.value === saverPromise) {
255
300
  this.dataMap.delete(key);
256
301
  this._removeNode(node);
257
302
  }
@@ -320,6 +365,12 @@ class AsyncLRUCache {
320
365
  }
321
366
  /**
322
367
  * Checks if a key exists in the cache and is not expired.
368
+ *
369
+ * Note: this method is **not** side-effect-free. If the entry has expired, it is
370
+ * immediately evicted from the cache as part of this check (same as what would
371
+ * happen on the next `get()`/cleanup pass). It does **not** affect LRU order for
372
+ * entries that are still valid (unlike `get()`, calling `has()` does not move the
373
+ * entry to the head of the list).
323
374
  * @param key - The key to check.
324
375
  * @returns True if the key exists and is not expired, false otherwise.
325
376
  */
@@ -335,3 +386,4 @@ class AsyncLRUCache {
335
386
  }
336
387
  }
337
388
  exports.AsyncLRUCache = AsyncLRUCache;
389
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wfp99/async-lru-cache",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A simple memory cache using the LRU algorithm and supporting asynchronous data access.",
5
5
  "keywords": [
6
6
  "cache",
@@ -14,6 +14,13 @@
14
14
  "type": "commonjs",
15
15
  "main": "dist/index.js",
16
16
  "types": "dist/index.d.ts",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "require": "./dist/index.js"
22
+ }
23
+ },
17
24
  "files": [
18
25
  "dist/",
19
26
  "README.md",
@@ -34,11 +41,13 @@
34
41
  "scripts": {
35
42
  "build": "tsc",
36
43
  "clean": "rm -rf dist",
37
- "prepublishOnly": "npm run clean && npm run build",
38
- "test": "echo \"Error: no test specified\" && exit 1"
44
+ "prepublishOnly": "npm run clean && npm run build && npm test",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest"
39
47
  },
40
48
  "devDependencies": {
41
49
  "@types/node": "^24.0.7",
42
- "typescript": "^5.8.3"
50
+ "typescript": "^5.8.3",
51
+ "vitest": "^3.2.4"
43
52
  }
44
53
  }