@wfp99/async-lru-cache 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Wang Feng Ping
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # Async LRU Cache
2
+
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
+ An asynchronous LRU (Least Recently Used) memory cache that supports asynchronous loading and saving operations while automatically handling concurrent requests.
8
+
9
+ ## Features
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
+ - ๐Ÿ›ก๏ธ **Error Handling**: Comprehensive error handling with automatic cache cleanup on failures
16
+ - ๐Ÿงน **Complete Clearing**: Support for clearing all cache entries at once
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @wfp99/async-lru-cache
22
+ ```
23
+
24
+ ## Requirements
25
+
26
+ - Node.js >= 14.0.0
27
+
28
+ ## Usage
29
+
30
+ ### Basic Usage
31
+
32
+ ```typescript
33
+ import { AsyncLRUCache } from '@wfp99/async-lru-cache';
34
+
35
+ // Create a cache with maximum 100 items
36
+ const cache = new AsyncLRUCache({
37
+ capacity: 100
38
+ });
39
+
40
+ // Use get method to retrieve data
41
+ const data = await cache.get('user:123', async () => {
42
+ // Loader function executed on cache miss
43
+ const response = await fetch('/api/users/123');
44
+ return response.json();
45
+ });
46
+
47
+ // Use put method to store data
48
+ await cache.put('user:456', userData, async (key, value) => {
49
+ // Optional saver function for data persistence
50
+ await saveToDatabase(key, value);
51
+ });
52
+ ```
53
+
54
+ ### Error Handling
55
+
56
+ ```typescript
57
+ try {
58
+ const data = await cache.get('problematic-key', async () => {
59
+ throw new Error('Load failed');
60
+ });
61
+ } catch (error) {
62
+ console.error('Cache load failed:', error);
63
+ // Failed items are automatically removed from cache
64
+ }
65
+ ```
66
+
67
+ ### Advanced Usage
68
+
69
+ ```typescript
70
+ // Working with database
71
+ const cache = new AsyncLRUCache<string, UserData>({ capacity: 500 });
72
+
73
+ // Load from database with automatic caching
74
+ const user = await cache.get(`user:${userId}`, async () => {
75
+ return await database.users.findById(userId);
76
+ });
77
+
78
+ // Save to cache and database atomically
79
+ await cache.put(`user:${userId}`, updatedUser, async (key, value) => {
80
+ await database.users.update(userId, value);
81
+ });
82
+ ```
83
+
84
+ ### Cache Management
85
+
86
+ ```typescript
87
+ // Manually remove specific cache entry
88
+ cache.invalidate('user:123');
89
+
90
+ // Clear all cache entries
91
+ cache.clear();
92
+ ```
93
+
94
+ ## API Reference
95
+
96
+ ### `AsyncLRUCache<K, V>`
97
+
98
+ #### Constructor
99
+
100
+ ```typescript
101
+ constructor(option: AsyncLRUCacheOption)
102
+ ```
103
+
104
+ #### `AsyncLRUCacheOption`
105
+
106
+ | Property | Type | Required | Description |
107
+ |----------|------|----------|-------------|
108
+ | `capacity` | `number` | Yes | Maximum number of items allowed in cache. Must be a positive number. |
109
+
110
+ #### Methods
111
+
112
+ ##### `get(key: K, loader: () => Promise<V>): Promise<V>`
113
+
114
+ Retrieves data from cache. If not found, uses the loader to load data.
115
+
116
+ - **key**: Cache key
117
+ - **loader**: Asynchronous loader function executed on cache miss
118
+ - **Returns**: Promise that resolves to the required data
119
+
120
+ ##### `put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>): Promise<V>`
121
+
122
+ Puts a value into cache and optionally executes a saver function for persistence.
123
+
124
+ - **key**: Cache key
125
+ - **value**: Value to cache
126
+ - **saver**: Optional asynchronous saver function
127
+ - **Returns**: Promise that resolves to the latest value after saver operation completes
128
+
129
+ ##### `invalidate(key: K): void`
130
+
131
+ Invalidates and removes the cache entry for the specified key.
132
+
133
+ - **key**: Cache key to invalidate
134
+
135
+ ##### `clear(): void`
136
+
137
+ Clears all cache entries. This method removes all items from cache and manually clears node links to prevent potential memory leaks.
138
+
139
+ ## Concurrency Handling
140
+
141
+ ### GET Request Merging
142
+
143
+ When multiple concurrent requests fetch the same key, AsyncLRUCache automatically merges these requests, ensuring the loader function executes only once:
144
+
145
+ ```typescript
146
+ // These three concurrent requests will share the same loader execution
147
+ const [data1, data2, data3] = await Promise.all([
148
+ cache.get('shared-key', loader),
149
+ cache.get('shared-key', loader),
150
+ cache.get('shared-key', loader)
151
+ ]);
152
+ ```
153
+
154
+ ### PUT Operation Serialization
155
+
156
+ Multiple PUT operations for the same key are serialized to ensure they execute in order:
157
+
158
+ ```typescript
159
+ // These operations will execute sequentially, even if started concurrently
160
+ cache.put('key', 'value1', saver1);
161
+ cache.put('key', 'value2', saver2);
162
+ cache.put('key', 'value3', saver3);
163
+ ```
164
+
165
+ ## TypeScript Support
166
+
167
+ This package is fully written in TypeScript and provides complete type support:
168
+
169
+ ```typescript
170
+ interface User {
171
+ id: string;
172
+ name: string;
173
+ email: string;
174
+ }
175
+
176
+ const userCache = new AsyncLRUCache<string, User>({
177
+ capacity: 1000
178
+ });
179
+
180
+ const user: User = await userCache.get('user:123', async () => {
181
+ // Loader must return User type
182
+ return fetchUserFromAPI('123');
183
+ });
184
+ ```
185
+
186
+ ## Error Handling
187
+
188
+ - **Loader Failures**: If loader function throws an exception, corresponding cache entry is automatically removed
189
+ - **Saver Failures**: If saver function fails, cache entry is also removed to ensure data consistency
190
+ - **Operation Chain Errors**: PUT operations ignore previous operation errors, allowing new operations to proceed
191
+ - **Error Logging**: All errors are automatically logged to console for debugging
192
+
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
+ ## License
202
+
203
+ MIT
204
+
205
+ ## Contributing
206
+
207
+ Issues and pull requests are welcome on [GitHub](https://github.com/wfp99/async-lru-cache).
208
+
209
+ ## Author
210
+
211
+ Wang Feng Ping
@@ -0,0 +1,207 @@
1
+ # Async LRU Cache
2
+
3
+ ไธ€ๅ€‹ๆ”ฏๆด้žๅŒๆญฅ่ผ‰ๅ…ฅๅ’Œไฟๅญ˜ๆ“ไฝœ็š„ LRU๏ผˆLeast Recently Used๏ผ‰่จ˜ๆ†ถ้ซ”ๅฟซๅ–้กžๅˆฅ๏ผŒ่‡ชๅ‹•่™•็†ไธฆ็™ผ่ซ‹ๆฑ‚ใ€‚
4
+
5
+ ## ็‰น่‰ฒ
6
+
7
+ - ๐Ÿš€ **้žๅŒๆญฅๆ”ฏๆด**๏ผšๆ”ฏๆด้žๅŒๆญฅ็š„่ผ‰ๅ…ฅๅ™จ๏ผˆloader๏ผ‰ๅ’Œไฟๅญ˜ๅ™จ๏ผˆsaver๏ผ‰ๅ‡ฝๆ•ธ
8
+ - ๐Ÿ”„ **่‡ชๅ‹•ๅˆไฝต**๏ผš่‡ชๅ‹•ๅˆไฝต็›ธๅŒ key ็š„ไธฆ็™ผ GET ่ซ‹ๆฑ‚
9
+ - ๐Ÿ“ **ๅบๅˆ—ๅŒ–ๅฏซๅ…ฅ**๏ผšๅบๅˆ—ๅŒ–ๅŒไธ€ key ็š„ PUT ๆ“ไฝœ๏ผŒ็ขบไฟ้ †ๅบๅŸท่กŒ
10
+ - ๐Ÿ—‘๏ธ **LRU ๆท˜ๆฑฐ็ญ–็•ฅ**๏ผšๅฏฆ็พ LRU ๆผ”็ฎ—ๆณ•๏ผŒ่‡ชๅ‹•็งป้™คๆœ€ไน…ๆœชไฝฟ็”จ็š„้ …็›ฎ
11
+ - ๐Ÿ›ก๏ธ **้Œฏ่ชค่™•็†**๏ผšๅฎŒๅ–„็š„้Œฏ่ชค่™•็†ๆฉŸๅˆถ๏ผŒ่ผ‰ๅ…ฅๆˆ–ไฟๅญ˜ๅคฑๆ•—ๆ™‚่‡ชๅ‹•ๆธ…็†ๅฟซๅ–
12
+ - ๐Ÿงน **ๅฎŒๆ•ดๆธ…้™ค**๏ผšๆ”ฏๆดไธ€ๆฌกๆ€งๆธ…้™คๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎ
13
+
14
+ ## ๅฎ‰่ฃ
15
+
16
+ ```bash
17
+ npm install @wfp99/async-lru-cache
18
+ ```
19
+
20
+ ## ็ณป็ตฑ้œ€ๆฑ‚
21
+
22
+ - Node.js >= 14.0.0
23
+
24
+ ## ไฝฟ็”จๆ–นๆณ•
25
+
26
+ ### ๅŸบๆœฌไฝฟ็”จ
27
+
28
+ ```typescript
29
+ import { AsyncLRUCache } from '@wfp99/async-lru-cache';
30
+
31
+ // ๅ‰ตๅปบไธ€ๅ€‹ๆœ€ๅคšๅฎน็ด 100 ๅ€‹้ …็›ฎ็š„ๅฟซๅ–
32
+ const cache = new AsyncLRUCache({
33
+ capacity: 100
34
+ });
35
+
36
+ // ไฝฟ็”จ get ๆ–นๆณ•็ฒๅ–่ณ‡ๆ–™
37
+ const data = await cache.get('user:123', async () => {
38
+ // ๅฟซๅ–ๆœชๅ‘ฝไธญๆ™‚ๅŸท่กŒ็š„่ผ‰ๅ…ฅๅ™จๅ‡ฝๆ•ธ
39
+ const response = await fetch('/api/users/123');
40
+ return response.json();
41
+ });
42
+
43
+ // ไฝฟ็”จ put ๆ–นๆณ•ๅ„ฒๅญ˜่ณ‡ๆ–™
44
+ await cache.put('user:456', userData, async (key, value) => {
45
+ // ๅฏ้ธ็š„ไฟๅญ˜ๅ™จๅ‡ฝๆ•ธ๏ผŒ็”จๆ–ผๆŒไน…ๅŒ–่ณ‡ๆ–™
46
+ await saveToDatabase(key, value);
47
+ });
48
+ ```
49
+
50
+ ### ้Œฏ่ชค่™•็†
51
+
52
+ ```typescript
53
+ try {
54
+ const data = await cache.get('problematic-key', async () => {
55
+ throw new Error('่ผ‰ๅ…ฅๅคฑๆ•—');
56
+ });
57
+ } catch (error) {
58
+ console.error('ๅฟซๅ–่ผ‰ๅ…ฅๅคฑๆ•—:', error);
59
+ // ๅคฑๆ•—็š„้ …็›ฎๆœƒ่‡ชๅ‹•ๅพžๅฟซๅ–ไธญ็งป้™ค
60
+ }
61
+ ```
62
+
63
+ ### ้€ฒ้šŽไฝฟ็”จ
64
+
65
+ ```typescript
66
+ // ่ˆ‡่ณ‡ๆ–™ๅบซ้…ๅˆไฝฟ็”จ
67
+ const cache = new AsyncLRUCache<string, UserData>({ capacity: 500 });
68
+
69
+ // ๅพž่ณ‡ๆ–™ๅบซ่ผ‰ๅ…ฅไธฆ่‡ชๅ‹•ๅฟซๅ–
70
+ const user = await cache.get(`user:${userId}`, async () => {
71
+ return await database.users.findById(userId);
72
+ });
73
+
74
+ // ๅŒๆ™‚ๅ„ฒๅญ˜ๅˆฐๅฟซๅ–ๅ’Œ่ณ‡ๆ–™ๅบซ
75
+ await cache.put(`user:${userId}`, updatedUser, async (key, value) => {
76
+ await database.users.update(userId, value);
77
+ });
78
+ ```
79
+
80
+ ### ๅฟซๅ–็ฎก็†
81
+
82
+ ```typescript
83
+ // ๆ‰‹ๅ‹•็งป้™ค็‰นๅฎšๅฟซๅ–้ …็›ฎ
84
+ cache.invalidate('user:123');
85
+
86
+ // ๆธ…้™คๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎ
87
+ cache.clear();
88
+ ```
89
+
90
+ ## API ๅƒ่€ƒ
91
+
92
+ ### `AsyncLRUCache<K, V>`
93
+
94
+ #### ๅปบๆง‹ๅ‡ฝๆ•ธ
95
+
96
+ ```typescript
97
+ constructor(option: AsyncLRUCacheOption)
98
+ ```
99
+
100
+ #### `AsyncLRUCacheOption`
101
+
102
+ | ๅฑฌๆ€ง | ้กžๅž‹ | ๅฟ…้œ€ | ๆ่ฟฐ |
103
+ |-----|------|------|------|
104
+ | `capacity` | `number` | ๆ˜ฏ | ๅฟซๅ–ไธญๅ…่จฑ็š„ๆœ€ๅคง้ …็›ฎๆ•ธ้‡๏ผŒๅฟ…้ ˆ็‚บๆญฃๆ•ธ |
105
+
106
+ #### ๆ–นๆณ•
107
+
108
+ ##### `get(key: K, loader: () => Promise<V>): Promise<V>`
109
+
110
+ ๅพžๅฟซๅ–ไธญ็ฒๅ–่ณ‡ๆ–™ใ€‚ๅฆ‚ๆžœไธๅญ˜ๅœจ๏ผŒไฝฟ็”จ่ผ‰ๅ…ฅๅ™จ่ผ‰ๅ…ฅ่ณ‡ๆ–™ใ€‚
111
+
112
+ - **key**: ๅฟซๅ–้ต
113
+ - **loader**: ๅฟซๅ–ๆœชๅ‘ฝไธญๆ™‚ๅŸท่กŒ็š„้žๅŒๆญฅ่ผ‰ๅ…ฅๅ‡ฝๆ•ธ
114
+ - **ๅ›žๅ‚ณ**: Promise๏ผŒ่งฃๆž็‚บๆ‰€้œ€็š„่ณ‡ๆ–™
115
+
116
+ ##### `put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>): Promise<V>`
117
+
118
+ ๅฐ‡ๅ€ผๆ”พๅ…ฅๅฟซๅ–ไธญ๏ผŒไธฆๅฏ้ธๆ“‡ๆ€งๅœฐๅŸท่กŒไฟๅญ˜ๅ™จๅ‡ฝๆ•ธ้€ฒ่กŒๆŒไน…ๅŒ–ใ€‚
119
+
120
+ - **key**: ๅฟซๅ–้ต
121
+ - **value**: ่ฆๅฟซๅ–็š„ๅ€ผ
122
+ - **saver**: ๅฏ้ธ็š„้žๅŒๆญฅไฟๅญ˜ๅ‡ฝๆ•ธ
123
+ - **ๅ›žๅ‚ณ**: Promise๏ผŒ่งฃๆž็‚บไฟๅญ˜ๆ“ไฝœๅฎŒๆˆๅพŒ็š„ๆœ€ๆ–ฐๅ€ผ
124
+
125
+ ##### `invalidate(key: K): void`
126
+
127
+ ไฝฟๆŒ‡ๅฎš้ต็š„ๅฟซๅ–้ …็›ฎๅคฑๆ•ˆไธฆ็งป้™คใ€‚
128
+
129
+ - **key**: ่ฆๅคฑๆ•ˆ็š„ๅฟซๅ–้ต
130
+
131
+ ##### `clear(): void`
132
+
133
+ ๆธ…้™คๆ‰€ๆœ‰ๅฟซๅ–้ …็›ฎใ€‚ๆญคๆ–นๆณ•ๆœƒ็งป้™คๅฟซๅ–ไธญ็š„ๆ‰€ๆœ‰้ …็›ฎไธฆๆ‰‹ๅ‹•ๆธ…็†็ฏ€้ปž้€ฃ็ตไปฅ้ฟๅ…ๆฝ›ๅœจ็š„่จ˜ๆ†ถ้ซ”ๆดฉๆผใ€‚
134
+
135
+ ## ไธฆ็™ผ่™•็†
136
+
137
+ ### GET ่ซ‹ๆฑ‚ๅˆไฝต
138
+
139
+ ็•ถๅคšๅ€‹ไธฆ็™ผ่ซ‹ๆฑ‚็ฒๅ–็›ธๅŒ็š„ key ๆ™‚๏ผŒAsyncLRUCache ๆœƒ่‡ชๅ‹•ๅˆไฝต้€™ไบ›่ซ‹ๆฑ‚๏ผŒ็ขบไฟ่ผ‰ๅ…ฅๅ™จๅ‡ฝๆ•ธๅชๅŸท่กŒไธ€ๆฌก๏ผš
140
+
141
+ ```typescript
142
+ // ้€™ไธ‰ๅ€‹ไธฆ็™ผ่ซ‹ๆฑ‚ๆœƒๅ…ฑไบซๅŒไธ€ๅ€‹่ผ‰ๅ…ฅๅ™จๅŸท่กŒ
143
+ const [data1, data2, data3] = await Promise.all([
144
+ cache.get('shared-key', loader),
145
+ cache.get('shared-key', loader),
146
+ cache.get('shared-key', loader)
147
+ ]);
148
+ ```
149
+
150
+ ### PUT ๆ“ไฝœๅบๅˆ—ๅŒ–
151
+
152
+ ๅฐๅŒไธ€ key ็š„ๅคšๅ€‹ PUT ๆ“ไฝœๆœƒ่ขซๅบๅˆ—ๅŒ–๏ผŒ็ขบไฟๅฎƒๅ€‘ๆŒ‰้ †ๅบๅŸท่กŒ๏ผš
153
+
154
+ ```typescript
155
+ // ้€™ไบ›ๆ“ไฝœๆœƒๆŒ‰้ †ๅบๅŸท่กŒ๏ผŒๅณไฝฟๅฎƒๅ€‘ๆ˜ฏไธฆ็™ผๅ•Ÿๅ‹•็š„
156
+ cache.put('key', 'value1', saver1);
157
+ cache.put('key', 'value2', saver2);
158
+ cache.put('key', 'value3', saver3);
159
+ ```
160
+
161
+ ## TypeScript ๆ”ฏๆด
162
+
163
+ ๆญคๅฅ—ไปถๅฎŒๅ…จไฝฟ็”จ TypeScript ็ทจๅฏซ๏ผŒๆไพ›ๅฎŒๆ•ด็š„ๅž‹ๅˆฅๆ”ฏๆด๏ผš
164
+
165
+ ```typescript
166
+ interface User {
167
+ id: string;
168
+ name: string;
169
+ email: string;
170
+ }
171
+
172
+ const userCache = new AsyncLRUCache<string, User>({
173
+ capacity: 1000
174
+ });
175
+
176
+ const user: User = await userCache.get('user:123', async () => {
177
+ // ่ผ‰ๅ…ฅๅ™จๅฟ…้ ˆๅ›žๅ‚ณ User ๅž‹ๅˆฅ
178
+ return fetchUserFromAPI('123');
179
+ });
180
+ ```
181
+
182
+ ## ้Œฏ่ชค่™•็†ๆฉŸๅˆถ
183
+
184
+ - **่ผ‰ๅ…ฅๅ™จๅคฑๆ•—**๏ผšๅฆ‚ๆžœ่ผ‰ๅ…ฅๅ™จๅ‡ฝๆ•ธๆ‹‹ๅ‡บ็•ฐๅธธ๏ผŒๅฐๆ‡‰็š„ๅฟซๅ–้ …็›ฎๆœƒ่ขซ่‡ชๅ‹•็งป้™ค
185
+ - **ไฟๅญ˜ๅ™จๅคฑๆ•—**๏ผšๅฆ‚ๆžœไฟๅญ˜ๅ™จๅ‡ฝๆ•ธๅคฑๆ•—๏ผŒๅฟซๅ–้ …็›ฎไนŸๆœƒ่ขซ็งป้™ค๏ผŒ็ขบไฟ่ณ‡ๆ–™ไธ€่‡ดๆ€ง
186
+ - **ๆ“ไฝœ้ˆ้Œฏ่ชค**๏ผšPUT ๆ“ไฝœๆœƒๅฟฝ็•ฅๅ‰ไธ€ๅ€‹ๆ“ไฝœ็š„้Œฏ่ชค๏ผŒ็ขบไฟๆ–ฐๆ“ไฝœ่ƒฝๅค ็นผ็บŒ้€ฒ่กŒ
187
+ - **้Œฏ่ชคๆ—ฅ่ชŒ**๏ผšๆ‰€ๆœ‰้Œฏ่ชค้ƒฝๆœƒ่‡ชๅ‹•่จ˜้Œ„ๅˆฐๆŽงๅˆถๅฐ๏ผŒไพฟๆ–ผ้™ค้Œฏ
188
+
189
+ ## ่จ˜ๆ†ถ้ซ”็ฎก็†
190
+
191
+ AsyncLRUCache ๅ…ทๅ‚™ๅฎŒๅ–„็š„่จ˜ๆ†ถ้ซ”็ฎก็†ๆฉŸๅˆถ๏ผš
192
+
193
+ - **่‡ชๅ‹•ๆท˜ๆฑฐ**๏ผš็•ถๅฟซๅ–้ …็›ฎๆ•ธ้‡่ถ…้Žๅฎน้‡้™ๅˆถๆ™‚๏ผŒ่‡ชๅ‹•็งป้™คๆœ€ไน…ๆœชไฝฟ็”จ็š„้ …็›ฎ
194
+ - **ๆ‰‹ๅ‹•ๆธ…็†**๏ผš`clear()` ๆ–นๆณ•ๆœƒๅพนๅบ•ๆธ…็†ๆ‰€ๆœ‰็ฏ€้ปž้€ฃ็ต๏ผŒ้˜ฒๆญข่จ˜ๆ†ถ้ซ”ๆดฉๆผ
195
+ - **้Œฏ่ชคๆธ…็†**๏ผšๆ“ไฝœๅคฑๆ•—ๆ™‚่‡ชๅ‹•ๆธ…็†็›ธ้—œๅฟซๅ–้ …็›ฎ
196
+
197
+ ## ๆŽˆๆฌŠ
198
+
199
+ MIT
200
+
201
+ ## ่ฒข็ป
202
+
203
+ ๆญก่ฟŽๅœจ [GitHub](https://github.com/wfp99/async-lru-cache) ไธŠๆๅ‡บๅ•้กŒๅ’Œ็™ผ้€ Pull Requestใ€‚
204
+
205
+ ## ไฝœ่€…
206
+
207
+ Wang Feng Ping
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Asynchronous LRU Cache.
3
+ * Supports asynchronous load and save operations, and automatically handles concurrent requests.
4
+ * Uses the LRU strategy to evict the oldest items.
5
+ *
6
+ * @module AsyncLRUCache
7
+ */
8
+ /**
9
+ * Configuration options for the AsyncLRUCache.
10
+ */
11
+ export interface AsyncLRUCacheOption {
12
+ /**
13
+ * Maximum capacity of the cache, this is the maximum number of items allowed.
14
+ * Must not be less than 10.
15
+ */
16
+ capacity: number;
17
+ }
18
+ /**
19
+ * Asynchronous LRU Cache class.
20
+ * - Supports asynchronous loaders for data fetching.
21
+ * - Supports asynchronous savers for data persistence.
22
+ * - Automatically merges concurrent requests (get) and serializes writes (put).
23
+ * - Implements LRU eviction strategy.
24
+ */
25
+ export declare class AsyncLRUCache<K, V> {
26
+ /**
27
+ * Maximum capacity of the cache.
28
+ */
29
+ private readonly capacity;
30
+ /**
31
+ * Map for storing cache entries. The key is the cache key, and the value is the Node.
32
+ */
33
+ private dataMap;
34
+ /**
35
+ * Head node of the linked list, pointing to the most recently used item.
36
+ */
37
+ private head;
38
+ /**
39
+ * Tail node of the linked list, pointing to the least recently used item.
40
+ */
41
+ private tail;
42
+ /**
43
+ * Creates an instance of AsyncLRUCache with the specified options.
44
+ * @param option - The configuration options for the cache.
45
+ * @throws {Error} Throws an error if the provided capacity is less than 10.
46
+ */
47
+ constructor(option: AsyncLRUCacheOption);
48
+ /**
49
+ * Checks if eviction is needed according to the cache strategy.
50
+ * When the cache exceeds its capacity, automatically removes the tail (least recently used) node.
51
+ */
52
+ private _evictIfNeeded;
53
+ /**
54
+ * Moves the specified node to the head of the linked list.
55
+ * This operation is used to mark a node as most recently used.
56
+ * @param node - The node to move to the head of the list.
57
+ */
58
+ private _moveToHead;
59
+ /**
60
+ * Removes a node from the doubly linked list used by the cache.
61
+ * Updates the previous and next pointers of adjacent nodes to unlink the specified node.
62
+ * @param node - The node to remove from the list.
63
+ */
64
+ private _removeNode;
65
+ /**
66
+ * Adds the given node to the head (front) of the doubly linked list.
67
+ * @param node - The node to add to the head of the list.
68
+ */
69
+ private _addToHead;
70
+ /**
71
+ * Retrieves data from the cache. If it does not exist, uses the loader to load it.
72
+ * Automatically merges concurrent requests for the same key.
73
+ * @param key - Cache key.
74
+ * @param loader - Asynchronous loader function to execute on cache miss.
75
+ * @returns A Promise that resolves to the required data.
76
+ */
77
+ get(key: K, loader: () => Promise<V>): Promise<V>;
78
+ /**
79
+ * Puts a value into the cache, and optionally executes a saver function to persist it.
80
+ * This method serializes multiple put operations for the same key, ensuring they execute in order.
81
+ * @param key - Cache key.
82
+ * @param value - Value to cache.
83
+ * @param saver - Optional asynchronous save function.
84
+ * @returns A Promise that resolves to the latest value after the saver operation completes.
85
+ */
86
+ put(key: K, value: V, saver?: (key: K, value: V) => Promise<void>): Promise<V>;
87
+ /**
88
+ * Invalidates and removes the cache entry associated with the specified key.
89
+ * @param key - The key of the cache entry to invalidate.
90
+ */
91
+ invalidate(key: K): void;
92
+ /**
93
+ * Clears the entire cache, removing all entries.
94
+ */
95
+ clear(): void;
96
+ }
package/dist/index.js ADDED
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ /**
3
+ * Asynchronous LRU Cache.
4
+ * Supports asynchronous load and save operations, and automatically handles concurrent requests.
5
+ * Uses the LRU strategy to evict the oldest items.
6
+ *
7
+ * @module AsyncLRUCache
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.AsyncLRUCache = void 0;
11
+ /**
12
+ * Node of the linked list, where value is a Promise representing the eventual data value.
13
+ */
14
+ class Node {
15
+ /**
16
+ * Creates a new Node instance.
17
+ * @param key - The key associated with this node.
18
+ * @param value - A Promise that resolves to the value of this node.
19
+ */
20
+ constructor(key, value) {
21
+ /**
22
+ * Reference to the next node in the linked list.
23
+ * This property is `null` if there is no next node.
24
+ */
25
+ this.next = null;
26
+ /**
27
+ * Reference to the previous node in the linked list.
28
+ * This property is `null` if there is no previous node.
29
+ */
30
+ this.prev = null;
31
+ this.key = key;
32
+ this.value = value;
33
+ }
34
+ }
35
+ /**
36
+ * Asynchronous LRU Cache class.
37
+ * - Supports asynchronous loaders for data fetching.
38
+ * - Supports asynchronous savers for data persistence.
39
+ * - Automatically merges concurrent requests (get) and serializes writes (put).
40
+ * - Implements LRU eviction strategy.
41
+ */
42
+ class AsyncLRUCache {
43
+ /**
44
+ * Creates an instance of AsyncLRUCache with the specified options.
45
+ * @param option - The configuration options for the cache.
46
+ * @throws {Error} Throws an error if the provided capacity is less than 10.
47
+ */
48
+ constructor(option) {
49
+ /**
50
+ * Map for storing cache entries. The key is the cache key, and the value is the Node.
51
+ */
52
+ this.dataMap = new Map();
53
+ /**
54
+ * Head node of the linked list, pointing to the most recently used item.
55
+ */
56
+ this.head = null;
57
+ /**
58
+ * Tail node of the linked list, pointing to the least recently used item.
59
+ */
60
+ this.tail = null;
61
+ if (option.capacity < 10)
62
+ throw new Error("Capacity must be at least 10.");
63
+ this.capacity = option.capacity;
64
+ }
65
+ /**
66
+ * Checks if eviction is needed according to the cache strategy.
67
+ * When the cache exceeds its capacity, automatically removes the tail (least recently used) node.
68
+ */
69
+ _evictIfNeeded() {
70
+ if (this.dataMap.size > this.capacity) {
71
+ this.dataMap.delete(this.tail.key);
72
+ this._removeNode(this.tail);
73
+ }
74
+ }
75
+ /**
76
+ * Moves the specified node to the head of the linked list.
77
+ * This operation is used to mark a node as most recently used.
78
+ * @param node - The node to move to the head of the list.
79
+ */
80
+ _moveToHead(node) {
81
+ if (node === this.head)
82
+ return;
83
+ this._removeNode(node);
84
+ this._addToHead(node);
85
+ }
86
+ /**
87
+ * Removes a node from the doubly linked list used by the cache.
88
+ * Updates the previous and next pointers of adjacent nodes to unlink the specified node.
89
+ * @param node - The node to remove from the list.
90
+ */
91
+ _removeNode(node) {
92
+ if (node.prev)
93
+ node.prev.next = node.next;
94
+ else
95
+ this.head = node.next;
96
+ if (node.next)
97
+ node.next.prev = node.prev;
98
+ else
99
+ this.tail = node.prev;
100
+ }
101
+ /**
102
+ * Adds the given node to the head (front) of the doubly linked list.
103
+ * @param node - The node to add to the head of the list.
104
+ */
105
+ _addToHead(node) {
106
+ node.prev = null;
107
+ node.next = this.head;
108
+ if (this.head)
109
+ this.head.prev = node;
110
+ this.head = node;
111
+ if (!this.tail)
112
+ this.tail = node;
113
+ }
114
+ /**
115
+ * Retrieves data from the cache. If it does not exist, uses the loader to load it.
116
+ * Automatically merges concurrent requests for the same key.
117
+ * @param key - Cache key.
118
+ * @param loader - Asynchronous loader function to execute on cache miss.
119
+ * @returns A Promise that resolves to the required data.
120
+ */
121
+ get(key, loader) {
122
+ const node = this.dataMap.get(key);
123
+ if (node) {
124
+ this._moveToHead(node);
125
+ return node.value;
126
+ }
127
+ const loadingPromise = loader();
128
+ const newNode = new Node(key, loadingPromise);
129
+ this.dataMap.set(key, newNode);
130
+ this._addToHead(newNode);
131
+ this._evictIfNeeded();
132
+ loadingPromise.catch(err => {
133
+ console.error(`[AsyncLRUCache ERROR] Loader for key ${key} failed. Removing from cache.`, err);
134
+ if (this.dataMap.get(key) === newNode) {
135
+ this.dataMap.delete(key);
136
+ this._removeNode(newNode);
137
+ }
138
+ });
139
+ return loadingPromise;
140
+ }
141
+ /**
142
+ * Puts a value into the cache, and optionally executes a saver function to persist it.
143
+ * This method serializes multiple put operations for the same key, ensuring they execute in order.
144
+ * @param key - Cache key.
145
+ * @param value - Value to cache.
146
+ * @param saver - Optional asynchronous save function.
147
+ * @returns A Promise that resolves to the latest value after the saver operation completes.
148
+ */
149
+ put(key, value, saver) {
150
+ const lastPromise = this.dataMap.has(key) ? this.dataMap.get(key).value : Promise.resolve(undefined);
151
+ const saverPromise = lastPromise.catch((err) => {
152
+ // Ignore previous operation errors, so the new operation can proceed.
153
+ console.warn(`[AsyncLRUCache WARN] Previous operation for key ${key} failed. Chaining new PUT operation.`, err.message);
154
+ })
155
+ .then(() => {
156
+ // After the previous operation (success or failure) completes, execute this saver.
157
+ if (saver)
158
+ return saver(key, value);
159
+ })
160
+ .then(() => {
161
+ // After saver completes, the final value of this Promise chain is the new value.
162
+ return value;
163
+ });
164
+ let node = this.dataMap.get(key);
165
+ if (!node) {
166
+ node = new Node(key, saverPromise);
167
+ this.dataMap.set(key, node);
168
+ this._addToHead(node);
169
+ this._evictIfNeeded();
170
+ }
171
+ else {
172
+ node.value = saverPromise;
173
+ this._moveToHead(node);
174
+ }
175
+ saverPromise.catch(err => {
176
+ var _a;
177
+ console.error(`[AsyncLRUCache ERROR] Saver operation for key ${key} failed. Removing from cache.`, err);
178
+ if (((_a = this.dataMap.get(key)) === null || _a === void 0 ? void 0 : _a.value) === saverPromise) {
179
+ this.dataMap.delete(key);
180
+ this._removeNode(node);
181
+ }
182
+ });
183
+ return saverPromise;
184
+ }
185
+ /**
186
+ * Invalidates and removes the cache entry associated with the specified key.
187
+ * @param key - The key of the cache entry to invalidate.
188
+ */
189
+ invalidate(key) {
190
+ const node = this.dataMap.get(key);
191
+ if (node) {
192
+ this._removeNode(node);
193
+ this.dataMap.delete(key);
194
+ }
195
+ }
196
+ /**
197
+ * Clears the entire cache, removing all entries.
198
+ */
199
+ clear() {
200
+ this.dataMap.clear();
201
+ // Manually clear all node links to avoid any potential memory leaks.
202
+ let node = this.head;
203
+ while (node !== null) {
204
+ // Save reference to the next node, as we are about to break the current node's next link.
205
+ const next = node.next;
206
+ // Break all internal and external links of the current node and clear data.
207
+ node.prev = null;
208
+ node.next = null;
209
+ node.key = null;
210
+ node.value = Promise.resolve(undefined);
211
+ // Move to the next node.
212
+ node = next;
213
+ }
214
+ this.head = null;
215
+ this.tail = null;
216
+ }
217
+ }
218
+ exports.AsyncLRUCache = AsyncLRUCache;
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@wfp99/async-lru-cache",
3
+ "version": "1.0.0",
4
+ "description": "A simple memory cache using the LRU algorithm and supporting asynchronous data access.",
5
+ "keywords": [
6
+ "cache",
7
+ "LRU",
8
+ "async",
9
+ "TypeScript",
10
+ "node.js"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Wang Feng Ping <wfp@nebuis.com>",
14
+ "type": "commonjs",
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "files": [
18
+ "dist/",
19
+ "README.md",
20
+ "README.zh-TW.md",
21
+ "LICENSE"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/wfp99/async-lru-cache.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/wfp99/async-lru-cache/issues"
29
+ },
30
+ "homepage": "https://github.com/wfp99/async-lru-cache#readme",
31
+ "engines": {
32
+ "node": ">=14.0.0"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "clean": "rm -rf dist",
37
+ "prepublishOnly": "npm run clean && npm run build",
38
+ "test": "echo \"Error: no test specified\" && exit 1"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^24.0.7",
42
+ "typescript": "^5.8.3"
43
+ }
44
+ }