sehawq.db 4.0.2 โ 4.0.5
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/.github/workflows/npm-publish.yml +30 -30
- package/LICENSE +21 -21
- package/index.js +1 -1
- package/package.json +36 -36
- package/readme.md +413 -413
- package/src/core/Database.js +294 -294
- package/src/core/Events.js +285 -285
- package/src/core/IndexManager.js +813 -813
- package/src/core/Persistence.js +375 -375
- package/src/core/QueryEngine.js +447 -447
- package/src/core/Storage.js +321 -321
- package/src/core/Validator.js +324 -324
- package/src/index.js +115 -115
- package/src/performance/Cache.js +338 -338
- package/src/performance/LazyLoader.js +354 -354
- package/src/performance/MemoryManager.js +495 -495
- package/src/server/api.js +687 -687
- package/src/server/websocket.js +527 -527
- package/src/utils/benchmark.js +51 -51
- package/src/utils/dot-notation.js +247 -247
- package/src/utils/helpers.js +275 -275
- package/src/utils/profiler.js +70 -70
- package/src/version.js +37 -37
package/src/performance/Cache.js
CHANGED
|
@@ -1,339 +1,339 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart Cache System - Makes everything faster with magic ๐ช
|
|
3
|
-
*
|
|
4
|
-
* Implements LRU cache with TTL and memory management
|
|
5
|
-
* Because waiting is not an option in 2024 โก
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
class Cache {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
this.options = {
|
|
11
|
-
maxSize: 1000,
|
|
12
|
-
ttl: 5 * 60 * 1000, // 5 minutes default
|
|
13
|
-
cleanupInterval: 60 * 1000, // Clean every minute
|
|
14
|
-
...options
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Double-linked list for LRU + HashMap for O(1) access
|
|
18
|
-
this.cache = new Map();
|
|
19
|
-
this.head = { key: null, value: null, next: null, prev: null, expires: 0 };
|
|
20
|
-
this.tail = { key: null, value: null, next: null, prev: this.head, expires: 0 };
|
|
21
|
-
this.head.next = this.tail;
|
|
22
|
-
|
|
23
|
-
this.stats = {
|
|
24
|
-
hits: 0,
|
|
25
|
-
misses: 0,
|
|
26
|
-
evictions: 0,
|
|
27
|
-
sets: 0,
|
|
28
|
-
gets: 0,
|
|
29
|
-
memoryUsage: 0
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
this._startCleanupInterval();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get value from cache - moves item to front (most recently used)
|
|
37
|
-
*/
|
|
38
|
-
get(key) {
|
|
39
|
-
this.stats.gets++;
|
|
40
|
-
|
|
41
|
-
const node = this.cache.get(key);
|
|
42
|
-
|
|
43
|
-
// Check if exists and not expired
|
|
44
|
-
if (!node || this._isExpired(node)) {
|
|
45
|
-
this.stats.misses++;
|
|
46
|
-
|
|
47
|
-
if (node) {
|
|
48
|
-
// Remove expired node
|
|
49
|
-
this._removeNode(node);
|
|
50
|
-
this.cache.delete(key);
|
|
51
|
-
this.stats.evictions++;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Move to front (most recently used)
|
|
58
|
-
this._moveToFront(node);
|
|
59
|
-
this.stats.hits++;
|
|
60
|
-
|
|
61
|
-
return node.value;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Set value in cache - handles LRU eviction if needed
|
|
66
|
-
*/
|
|
67
|
-
set(key, value, ttl = this.options.ttl) {
|
|
68
|
-
this.stats.sets++;
|
|
69
|
-
|
|
70
|
-
let node = this.cache.get(key);
|
|
71
|
-
const expires = Date.now() + ttl;
|
|
72
|
-
|
|
73
|
-
if (node) {
|
|
74
|
-
// Update existing node
|
|
75
|
-
node.value = value;
|
|
76
|
-
node.expires = expires;
|
|
77
|
-
this._moveToFront(node);
|
|
78
|
-
} else {
|
|
79
|
-
// Create new node
|
|
80
|
-
node = {
|
|
81
|
-
key,
|
|
82
|
-
value,
|
|
83
|
-
expires,
|
|
84
|
-
prev: this.head,
|
|
85
|
-
next: this.head.next
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Add to cache and linked list
|
|
89
|
-
this.cache.set(key, node);
|
|
90
|
-
this.head.next.prev = node;
|
|
91
|
-
this.head.next = node;
|
|
92
|
-
|
|
93
|
-
// Evict if over capacity
|
|
94
|
-
if (this.cache.size > this.options.maxSize) {
|
|
95
|
-
this._evictLRU();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Update memory usage stats
|
|
100
|
-
this._updateMemoryStats();
|
|
101
|
-
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Check if key exists in cache (without updating LRU)
|
|
107
|
-
*/
|
|
108
|
-
has(key) {
|
|
109
|
-
const node = this.cache.get(key);
|
|
110
|
-
return !!(node && !this._isExpired(node));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Delete key from cache
|
|
115
|
-
*/
|
|
116
|
-
delete(key) {
|
|
117
|
-
const node = this.cache.get(key);
|
|
118
|
-
if (node) {
|
|
119
|
-
this._removeNode(node);
|
|
120
|
-
this.cache.delete(key);
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Clear entire cache
|
|
128
|
-
*/
|
|
129
|
-
clear() {
|
|
130
|
-
this.cache.clear();
|
|
131
|
-
this.head.next = this.tail;
|
|
132
|
-
this.tail.prev = this.head;
|
|
133
|
-
this.stats.memoryUsage = 0;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Get cache size (number of items)
|
|
138
|
-
*/
|
|
139
|
-
size() {
|
|
140
|
-
return this.cache.size;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Get all keys in cache (for debugging)
|
|
145
|
-
*/
|
|
146
|
-
keys() {
|
|
147
|
-
const keys = [];
|
|
148
|
-
let node = this.head.next;
|
|
149
|
-
|
|
150
|
-
while (node !== this.tail) {
|
|
151
|
-
if (!this._isExpired(node)) {
|
|
152
|
-
keys.push(node.key);
|
|
153
|
-
}
|
|
154
|
-
node = node.next;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return keys;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Get all values in cache (for debugging)
|
|
162
|
-
*/
|
|
163
|
-
values() {
|
|
164
|
-
const values = [];
|
|
165
|
-
let node = this.head.next;
|
|
166
|
-
|
|
167
|
-
while (node !== this.tail) {
|
|
168
|
-
if (!this._isExpired(node)) {
|
|
169
|
-
values.push(node.value);
|
|
170
|
-
}
|
|
171
|
-
node = node.next;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return values;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get cache statistics
|
|
179
|
-
*/
|
|
180
|
-
getStats() {
|
|
181
|
-
const hitRate = this.stats.hits + this.stats.misses > 0
|
|
182
|
-
? (this.stats.hits / (this.stats.hits + this.stats.misses) * 100).toFixed(2)
|
|
183
|
-
: 0;
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
...this.stats,
|
|
187
|
-
hitRate: `${hitRate}%`,
|
|
188
|
-
size: this.cache.size,
|
|
189
|
-
maxSize: this.options.maxSize,
|
|
190
|
-
utilization: `${((this.cache.size / this.options.maxSize) * 100).toFixed(1)}%`
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Move node to front of LRU list
|
|
196
|
-
*/
|
|
197
|
-
_moveToFront(node) {
|
|
198
|
-
// Remove from current position
|
|
199
|
-
this._removeNode(node);
|
|
200
|
-
|
|
201
|
-
// Insert after head
|
|
202
|
-
node.prev = this.head;
|
|
203
|
-
node.next = this.head.next;
|
|
204
|
-
this.head.next.prev = node;
|
|
205
|
-
this.head.next = node;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Remove node from linked list
|
|
210
|
-
*/
|
|
211
|
-
_removeNode(node) {
|
|
212
|
-
node.prev.next = node.next;
|
|
213
|
-
node.next.prev = node.prev;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Evict least recently used item
|
|
218
|
-
*/
|
|
219
|
-
_evictLRU() {
|
|
220
|
-
const lruNode = this.tail.prev;
|
|
221
|
-
|
|
222
|
-
if (lruNode !== this.head) {
|
|
223
|
-
this._removeNode(lruNode);
|
|
224
|
-
this.cache.delete(lruNode.key);
|
|
225
|
-
this.stats.evictions++;
|
|
226
|
-
this._updateMemoryStats();
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Check if node has expired
|
|
232
|
-
*/
|
|
233
|
-
_isExpired(node) {
|
|
234
|
-
return Date.now() > node.expires;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Start periodic cleanup of expired items
|
|
239
|
-
*/
|
|
240
|
-
_startCleanupInterval() {
|
|
241
|
-
setInterval(() => {
|
|
242
|
-
this._cleanupExpired();
|
|
243
|
-
}, this.options.cleanupInterval);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Remove all expired items from cache
|
|
248
|
-
*/
|
|
249
|
-
_cleanupExpired() {
|
|
250
|
-
const now = Date.now();
|
|
251
|
-
let node = this.head.next;
|
|
252
|
-
let expiredCount = 0;
|
|
253
|
-
|
|
254
|
-
while (node !== this.tail) {
|
|
255
|
-
const nextNode = node.next;
|
|
256
|
-
|
|
257
|
-
if (now > node.expires) {
|
|
258
|
-
this._removeNode(node);
|
|
259
|
-
this.cache.delete(node.key);
|
|
260
|
-
expiredCount++;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
node = nextNode;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (expiredCount > 0 && this.options.debug) {
|
|
267
|
-
console.log(`๐งน Cache cleanup: removed ${expiredCount} expired items`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
this._updateMemoryStats();
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Estimate memory usage (rough calculation)
|
|
275
|
-
*/
|
|
276
|
-
_updateMemoryStats() {
|
|
277
|
-
let totalSize = 0;
|
|
278
|
-
|
|
279
|
-
for (const [key, node] of this.cache) {
|
|
280
|
-
// Rough estimation: key size + value size (stringify for simplicity)
|
|
281
|
-
totalSize += Buffer.byteLength(key, 'utf8');
|
|
282
|
-
totalSize += Buffer.byteLength(JSON.stringify(node.value), 'utf8');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
this.stats.memoryUsage = totalSize;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Pre-warm cache with data
|
|
290
|
-
*/
|
|
291
|
-
async warmup(dataMap, ttl = this.options.ttl) {
|
|
292
|
-
for (const [key, value] of Object.entries(dataMap)) {
|
|
293
|
-
this.set(key, value, ttl);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (this.options.debug) {
|
|
297
|
-
console.log(`๐ฅ Cache warmup complete: ${Object.keys(dataMap).length} items`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Get cache snapshot for debugging
|
|
303
|
-
*/
|
|
304
|
-
getSnapshot() {
|
|
305
|
-
const snapshot = {};
|
|
306
|
-
let node = this.head.next;
|
|
307
|
-
|
|
308
|
-
while (node !== this.tail) {
|
|
309
|
-
if (!this._isExpired(node)) {
|
|
310
|
-
snapshot[node.key] = {
|
|
311
|
-
value: node.value,
|
|
312
|
-
expiresIn: node.expires - Date.now(),
|
|
313
|
-
ttl: this.options.ttl
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
node = node.next;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return snapshot;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Resize cache (useful for dynamic memory management)
|
|
324
|
-
*/
|
|
325
|
-
resize(newSize) {
|
|
326
|
-
this.options.maxSize = newSize;
|
|
327
|
-
|
|
328
|
-
// Evict excess items if needed
|
|
329
|
-
while (this.cache.size > newSize) {
|
|
330
|
-
this._evictLRU();
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (this.options.debug) {
|
|
334
|
-
console.log(`๐ Cache resized: ${newSize} items`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Smart Cache System - Makes everything faster with magic ๐ช
|
|
3
|
+
*
|
|
4
|
+
* Implements LRU cache with TTL and memory management
|
|
5
|
+
* Because waiting is not an option in 2024 โก
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class Cache {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = {
|
|
11
|
+
maxSize: 1000,
|
|
12
|
+
ttl: 5 * 60 * 1000, // 5 minutes default
|
|
13
|
+
cleanupInterval: 60 * 1000, // Clean every minute
|
|
14
|
+
...options
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Double-linked list for LRU + HashMap for O(1) access
|
|
18
|
+
this.cache = new Map();
|
|
19
|
+
this.head = { key: null, value: null, next: null, prev: null, expires: 0 };
|
|
20
|
+
this.tail = { key: null, value: null, next: null, prev: this.head, expires: 0 };
|
|
21
|
+
this.head.next = this.tail;
|
|
22
|
+
|
|
23
|
+
this.stats = {
|
|
24
|
+
hits: 0,
|
|
25
|
+
misses: 0,
|
|
26
|
+
evictions: 0,
|
|
27
|
+
sets: 0,
|
|
28
|
+
gets: 0,
|
|
29
|
+
memoryUsage: 0
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
this._startCleanupInterval();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get value from cache - moves item to front (most recently used)
|
|
37
|
+
*/
|
|
38
|
+
get(key) {
|
|
39
|
+
this.stats.gets++;
|
|
40
|
+
|
|
41
|
+
const node = this.cache.get(key);
|
|
42
|
+
|
|
43
|
+
// Check if exists and not expired
|
|
44
|
+
if (!node || this._isExpired(node)) {
|
|
45
|
+
this.stats.misses++;
|
|
46
|
+
|
|
47
|
+
if (node) {
|
|
48
|
+
// Remove expired node
|
|
49
|
+
this._removeNode(node);
|
|
50
|
+
this.cache.delete(key);
|
|
51
|
+
this.stats.evictions++;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Move to front (most recently used)
|
|
58
|
+
this._moveToFront(node);
|
|
59
|
+
this.stats.hits++;
|
|
60
|
+
|
|
61
|
+
return node.value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Set value in cache - handles LRU eviction if needed
|
|
66
|
+
*/
|
|
67
|
+
set(key, value, ttl = this.options.ttl) {
|
|
68
|
+
this.stats.sets++;
|
|
69
|
+
|
|
70
|
+
let node = this.cache.get(key);
|
|
71
|
+
const expires = Date.now() + ttl;
|
|
72
|
+
|
|
73
|
+
if (node) {
|
|
74
|
+
// Update existing node
|
|
75
|
+
node.value = value;
|
|
76
|
+
node.expires = expires;
|
|
77
|
+
this._moveToFront(node);
|
|
78
|
+
} else {
|
|
79
|
+
// Create new node
|
|
80
|
+
node = {
|
|
81
|
+
key,
|
|
82
|
+
value,
|
|
83
|
+
expires,
|
|
84
|
+
prev: this.head,
|
|
85
|
+
next: this.head.next
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Add to cache and linked list
|
|
89
|
+
this.cache.set(key, node);
|
|
90
|
+
this.head.next.prev = node;
|
|
91
|
+
this.head.next = node;
|
|
92
|
+
|
|
93
|
+
// Evict if over capacity
|
|
94
|
+
if (this.cache.size > this.options.maxSize) {
|
|
95
|
+
this._evictLRU();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Update memory usage stats
|
|
100
|
+
this._updateMemoryStats();
|
|
101
|
+
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if key exists in cache (without updating LRU)
|
|
107
|
+
*/
|
|
108
|
+
has(key) {
|
|
109
|
+
const node = this.cache.get(key);
|
|
110
|
+
return !!(node && !this._isExpired(node));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Delete key from cache
|
|
115
|
+
*/
|
|
116
|
+
delete(key) {
|
|
117
|
+
const node = this.cache.get(key);
|
|
118
|
+
if (node) {
|
|
119
|
+
this._removeNode(node);
|
|
120
|
+
this.cache.delete(key);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Clear entire cache
|
|
128
|
+
*/
|
|
129
|
+
clear() {
|
|
130
|
+
this.cache.clear();
|
|
131
|
+
this.head.next = this.tail;
|
|
132
|
+
this.tail.prev = this.head;
|
|
133
|
+
this.stats.memoryUsage = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get cache size (number of items)
|
|
138
|
+
*/
|
|
139
|
+
size() {
|
|
140
|
+
return this.cache.size;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get all keys in cache (for debugging)
|
|
145
|
+
*/
|
|
146
|
+
keys() {
|
|
147
|
+
const keys = [];
|
|
148
|
+
let node = this.head.next;
|
|
149
|
+
|
|
150
|
+
while (node !== this.tail) {
|
|
151
|
+
if (!this._isExpired(node)) {
|
|
152
|
+
keys.push(node.key);
|
|
153
|
+
}
|
|
154
|
+
node = node.next;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return keys;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get all values in cache (for debugging)
|
|
162
|
+
*/
|
|
163
|
+
values() {
|
|
164
|
+
const values = [];
|
|
165
|
+
let node = this.head.next;
|
|
166
|
+
|
|
167
|
+
while (node !== this.tail) {
|
|
168
|
+
if (!this._isExpired(node)) {
|
|
169
|
+
values.push(node.value);
|
|
170
|
+
}
|
|
171
|
+
node = node.next;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return values;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get cache statistics
|
|
179
|
+
*/
|
|
180
|
+
getStats() {
|
|
181
|
+
const hitRate = this.stats.hits + this.stats.misses > 0
|
|
182
|
+
? (this.stats.hits / (this.stats.hits + this.stats.misses) * 100).toFixed(2)
|
|
183
|
+
: 0;
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
...this.stats,
|
|
187
|
+
hitRate: `${hitRate}%`,
|
|
188
|
+
size: this.cache.size,
|
|
189
|
+
maxSize: this.options.maxSize,
|
|
190
|
+
utilization: `${((this.cache.size / this.options.maxSize) * 100).toFixed(1)}%`
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Move node to front of LRU list
|
|
196
|
+
*/
|
|
197
|
+
_moveToFront(node) {
|
|
198
|
+
// Remove from current position
|
|
199
|
+
this._removeNode(node);
|
|
200
|
+
|
|
201
|
+
// Insert after head
|
|
202
|
+
node.prev = this.head;
|
|
203
|
+
node.next = this.head.next;
|
|
204
|
+
this.head.next.prev = node;
|
|
205
|
+
this.head.next = node;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Remove node from linked list
|
|
210
|
+
*/
|
|
211
|
+
_removeNode(node) {
|
|
212
|
+
node.prev.next = node.next;
|
|
213
|
+
node.next.prev = node.prev;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Evict least recently used item
|
|
218
|
+
*/
|
|
219
|
+
_evictLRU() {
|
|
220
|
+
const lruNode = this.tail.prev;
|
|
221
|
+
|
|
222
|
+
if (lruNode !== this.head) {
|
|
223
|
+
this._removeNode(lruNode);
|
|
224
|
+
this.cache.delete(lruNode.key);
|
|
225
|
+
this.stats.evictions++;
|
|
226
|
+
this._updateMemoryStats();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if node has expired
|
|
232
|
+
*/
|
|
233
|
+
_isExpired(node) {
|
|
234
|
+
return Date.now() > node.expires;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Start periodic cleanup of expired items
|
|
239
|
+
*/
|
|
240
|
+
_startCleanupInterval() {
|
|
241
|
+
setInterval(() => {
|
|
242
|
+
this._cleanupExpired();
|
|
243
|
+
}, this.options.cleanupInterval);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Remove all expired items from cache
|
|
248
|
+
*/
|
|
249
|
+
_cleanupExpired() {
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
let node = this.head.next;
|
|
252
|
+
let expiredCount = 0;
|
|
253
|
+
|
|
254
|
+
while (node !== this.tail) {
|
|
255
|
+
const nextNode = node.next;
|
|
256
|
+
|
|
257
|
+
if (now > node.expires) {
|
|
258
|
+
this._removeNode(node);
|
|
259
|
+
this.cache.delete(node.key);
|
|
260
|
+
expiredCount++;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
node = nextNode;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (expiredCount > 0 && this.options.debug) {
|
|
267
|
+
console.log(`๐งน Cache cleanup: removed ${expiredCount} expired items`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this._updateMemoryStats();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Estimate memory usage (rough calculation)
|
|
275
|
+
*/
|
|
276
|
+
_updateMemoryStats() {
|
|
277
|
+
let totalSize = 0;
|
|
278
|
+
|
|
279
|
+
for (const [key, node] of this.cache) {
|
|
280
|
+
// Rough estimation: key size + value size (stringify for simplicity)
|
|
281
|
+
totalSize += Buffer.byteLength(key, 'utf8');
|
|
282
|
+
totalSize += Buffer.byteLength(JSON.stringify(node.value), 'utf8');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.stats.memoryUsage = totalSize;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Pre-warm cache with data
|
|
290
|
+
*/
|
|
291
|
+
async warmup(dataMap, ttl = this.options.ttl) {
|
|
292
|
+
for (const [key, value] of Object.entries(dataMap)) {
|
|
293
|
+
this.set(key, value, ttl);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (this.options.debug) {
|
|
297
|
+
console.log(`๐ฅ Cache warmup complete: ${Object.keys(dataMap).length} items`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Get cache snapshot for debugging
|
|
303
|
+
*/
|
|
304
|
+
getSnapshot() {
|
|
305
|
+
const snapshot = {};
|
|
306
|
+
let node = this.head.next;
|
|
307
|
+
|
|
308
|
+
while (node !== this.tail) {
|
|
309
|
+
if (!this._isExpired(node)) {
|
|
310
|
+
snapshot[node.key] = {
|
|
311
|
+
value: node.value,
|
|
312
|
+
expiresIn: node.expires - Date.now(),
|
|
313
|
+
ttl: this.options.ttl
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
node = node.next;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return snapshot;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Resize cache (useful for dynamic memory management)
|
|
324
|
+
*/
|
|
325
|
+
resize(newSize) {
|
|
326
|
+
this.options.maxSize = newSize;
|
|
327
|
+
|
|
328
|
+
// Evict excess items if needed
|
|
329
|
+
while (this.cache.size > newSize) {
|
|
330
|
+
this._evictLRU();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (this.options.debug) {
|
|
334
|
+
console.log(`๐ Cache resized: ${newSize} items`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
339
|
module.exports = Cache;
|