@abtnode/db-cache 1.17.8-beta-20260109-075740-5f484e08 → 1.17.8-beta-20260113-015027-32a1cec4
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/dist/index.cjs +478 -25
- package/dist/index.d.cts +132 -1
- package/dist/index.d.mts +132 -1
- package/dist/index.d.ts +132 -1
- package/dist/index.mjs +478 -26
- package/package.json +5 -3
package/dist/index.mjs
CHANGED
|
@@ -21,20 +21,20 @@ function ulid() {
|
|
|
21
21
|
return timeStr + rand;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
var __defProp$
|
|
25
|
-
var __defNormalProp$
|
|
26
|
-
var __publicField$
|
|
27
|
-
__defNormalProp$
|
|
24
|
+
var __defProp$5 = Object.defineProperty;
|
|
25
|
+
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
26
|
+
var __publicField$5 = (obj, key, value) => {
|
|
27
|
+
__defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
28
28
|
return value;
|
|
29
29
|
};
|
|
30
30
|
const _RedisAdapter = class _RedisAdapter {
|
|
31
31
|
constructor() {
|
|
32
|
-
__publicField$
|
|
33
|
-
__publicField$
|
|
34
|
-
__publicField$
|
|
35
|
-
__publicField$
|
|
36
|
-
__publicField$
|
|
37
|
-
__publicField$
|
|
32
|
+
__publicField$5(this, "opts", null);
|
|
33
|
+
__publicField$5(this, "defaultTtl", 1e3 * 60 * 60);
|
|
34
|
+
__publicField$5(this, "url", "");
|
|
35
|
+
__publicField$5(this, "prefix", "");
|
|
36
|
+
__publicField$5(this, "prefixKey", (key) => `${this.prefix}:${key}`);
|
|
37
|
+
__publicField$5(this, "prefixKeyGroup", (key) => `${this.prefix}:group:${key}`);
|
|
38
38
|
}
|
|
39
39
|
clearAll() {
|
|
40
40
|
throw new Error("Method not implemented.");
|
|
@@ -177,10 +177,16 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
177
177
|
const client = this.getClient();
|
|
178
178
|
await client.flushAll();
|
|
179
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* 获取 LRU 缓存统计信息(Redis 不使用 LRU 缓存)
|
|
182
|
+
*/
|
|
183
|
+
getLruCacheStats() {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
180
186
|
};
|
|
181
187
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
-
__publicField$
|
|
183
|
-
__publicField$
|
|
188
|
+
__publicField$5(_RedisAdapter, "clients", /* @__PURE__ */ new Map());
|
|
189
|
+
__publicField$5(_RedisAdapter, "initPromises", /* @__PURE__ */ new Map());
|
|
184
190
|
let RedisAdapter = _RedisAdapter;
|
|
185
191
|
|
|
186
192
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -209,6 +215,363 @@ async function withRetry(fn, {
|
|
|
209
215
|
}
|
|
210
216
|
}
|
|
211
217
|
|
|
218
|
+
var __defProp$4 = Object.defineProperty;
|
|
219
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
220
|
+
var __publicField$4 = (obj, key, value) => {
|
|
221
|
+
__defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
222
|
+
return value;
|
|
223
|
+
};
|
|
224
|
+
const lruCacheModule = require("lru-cache");
|
|
225
|
+
const LRUCacheLib = lruCacheModule?.LRUCache || lruCacheModule?.default || lruCacheModule;
|
|
226
|
+
const isTestEnv = process.env.NODE_ENV === "test";
|
|
227
|
+
const eventHub = isTestEnv ? require("@arcblock/event-hub/single") : require("@arcblock/event-hub");
|
|
228
|
+
const LRU_CACHE_SYNC_EVENT = "db-cache:lru:sync";
|
|
229
|
+
const LRU_CACHE_DELETE_EVENT = "db-cache:lru:delete";
|
|
230
|
+
const LRU_CACHE_CLEAR_EVENT = "db-cache:lru:clear";
|
|
231
|
+
const shouldEnableSync = () => {
|
|
232
|
+
if (isTestEnv) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return process.env.NODE_APP_INSTANCE !== void 0;
|
|
236
|
+
};
|
|
237
|
+
const _LruCache = class _LruCache {
|
|
238
|
+
constructor(options) {
|
|
239
|
+
__publicField$4(this, "cacheKey");
|
|
240
|
+
__publicField$4(this, "maxSize");
|
|
241
|
+
__publicField$4(this, "enableSync");
|
|
242
|
+
this.cacheKey = `lru:${options.prefix}`;
|
|
243
|
+
this.maxSize = options.maxSize ?? 1e4;
|
|
244
|
+
this.enableSync = (options.enableSync ?? true) && shouldEnableSync();
|
|
245
|
+
if (!_LruCache.caches.has(this.cacheKey)) {
|
|
246
|
+
_LruCache.caches.set(
|
|
247
|
+
this.cacheKey,
|
|
248
|
+
new LRUCacheLib({
|
|
249
|
+
max: this.maxSize,
|
|
250
|
+
ttl: 0
|
|
251
|
+
// 我们自己管理 TTL
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
_LruCache.groupCaches.set(this.cacheKey, /* @__PURE__ */ new Map());
|
|
255
|
+
if (this.enableSync && !_LruCache.listenerSetup.has(this.cacheKey)) {
|
|
256
|
+
this._setupSyncListener();
|
|
257
|
+
_LruCache.listenerSetup.set(this.cacheKey, true);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
getCache() {
|
|
262
|
+
const cache = _LruCache.caches.get(this.cacheKey);
|
|
263
|
+
if (!cache) {
|
|
264
|
+
throw new Error("LRU cache not initialized");
|
|
265
|
+
}
|
|
266
|
+
return cache;
|
|
267
|
+
}
|
|
268
|
+
getGroupCaches() {
|
|
269
|
+
const groupCaches = _LruCache.groupCaches.get(this.cacheKey);
|
|
270
|
+
if (!groupCaches) {
|
|
271
|
+
throw new Error("LRU group cache not initialized");
|
|
272
|
+
}
|
|
273
|
+
return groupCaches;
|
|
274
|
+
}
|
|
275
|
+
getOrCreateGroupCache(key) {
|
|
276
|
+
const groupCaches = this.getGroupCaches();
|
|
277
|
+
if (!groupCaches.has(key)) {
|
|
278
|
+
groupCaches.set(
|
|
279
|
+
key,
|
|
280
|
+
new LRUCacheLib({
|
|
281
|
+
max: this.maxSize,
|
|
282
|
+
ttl: 0
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return groupCaches.get(key);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 设置同步监听器,接收其他 worker 广播的缓存数据
|
|
290
|
+
*/
|
|
291
|
+
_setupSyncListener() {
|
|
292
|
+
const { cacheKey, maxSize } = this;
|
|
293
|
+
eventHub.on(LRU_CACHE_SYNC_EVENT, (payload) => {
|
|
294
|
+
const { channel, key, value, expiresAt, isGroup, subKey } = payload;
|
|
295
|
+
if (channel !== cacheKey)
|
|
296
|
+
return;
|
|
297
|
+
const entry = { value, expiresAt };
|
|
298
|
+
if (isGroup && subKey !== void 0) {
|
|
299
|
+
const groupCaches = _LruCache.groupCaches.get(cacheKey);
|
|
300
|
+
if (groupCaches) {
|
|
301
|
+
const groupCache = groupCaches.get(key) || new LRUCacheLib({ max: maxSize, ttl: 0 });
|
|
302
|
+
groupCache.set(subKey, entry);
|
|
303
|
+
groupCaches.set(key, groupCache);
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
const cache = _LruCache.caches.get(cacheKey);
|
|
307
|
+
if (cache) {
|
|
308
|
+
cache.set(key, entry);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
eventHub.on(LRU_CACHE_DELETE_EVENT, (payload) => {
|
|
313
|
+
const { channel, keys, isGroup, groupKey } = payload;
|
|
314
|
+
if (channel !== cacheKey)
|
|
315
|
+
return;
|
|
316
|
+
if (isGroup && groupKey !== void 0) {
|
|
317
|
+
const groupCaches = _LruCache.groupCaches.get(cacheKey);
|
|
318
|
+
if (groupCaches) {
|
|
319
|
+
const groupCache = groupCaches.get(groupKey);
|
|
320
|
+
if (groupCache) {
|
|
321
|
+
for (const key of keys) {
|
|
322
|
+
groupCache.delete(key);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
const cache = _LruCache.caches.get(cacheKey);
|
|
328
|
+
if (cache) {
|
|
329
|
+
for (const key of keys) {
|
|
330
|
+
cache.delete(key);
|
|
331
|
+
const groupCaches = _LruCache.groupCaches.get(cacheKey);
|
|
332
|
+
if (groupCaches) {
|
|
333
|
+
groupCaches.delete(key);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
eventHub.on(LRU_CACHE_CLEAR_EVENT, (payload) => {
|
|
340
|
+
const { channel } = payload;
|
|
341
|
+
if (channel !== cacheKey)
|
|
342
|
+
return;
|
|
343
|
+
const cache = _LruCache.caches.get(cacheKey);
|
|
344
|
+
if (cache) {
|
|
345
|
+
cache.clear();
|
|
346
|
+
}
|
|
347
|
+
const groupCaches = _LruCache.groupCaches.get(cacheKey);
|
|
348
|
+
if (groupCaches) {
|
|
349
|
+
groupCaches.clear();
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* 广播缓存数据给其他 worker
|
|
355
|
+
*/
|
|
356
|
+
_broadcastSync(key, value, expiresAt, isGroup = false, subKey) {
|
|
357
|
+
if (this.enableSync) {
|
|
358
|
+
eventHub.broadcast(LRU_CACHE_SYNC_EVENT, {
|
|
359
|
+
channel: this.cacheKey,
|
|
360
|
+
key,
|
|
361
|
+
value,
|
|
362
|
+
expiresAt,
|
|
363
|
+
isGroup,
|
|
364
|
+
subKey
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* 广播删除缓存给其他 worker
|
|
370
|
+
*/
|
|
371
|
+
_broadcastDelete(keys, isGroup = false, groupKey) {
|
|
372
|
+
if (this.enableSync && keys.length > 0) {
|
|
373
|
+
eventHub.broadcast(LRU_CACHE_DELETE_EVENT, {
|
|
374
|
+
channel: this.cacheKey,
|
|
375
|
+
keys,
|
|
376
|
+
isGroup,
|
|
377
|
+
groupKey
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* 广播清空缓存给其他 worker
|
|
383
|
+
*/
|
|
384
|
+
_broadcastClear() {
|
|
385
|
+
if (this.enableSync) {
|
|
386
|
+
eventHub.broadcast(LRU_CACHE_CLEAR_EVENT, {
|
|
387
|
+
channel: this.cacheKey
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
isExpired(entry) {
|
|
392
|
+
if (!entry)
|
|
393
|
+
return true;
|
|
394
|
+
if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
/* ---------------------- 基础 API ---------------------- */
|
|
400
|
+
/**
|
|
401
|
+
* 设置缓存
|
|
402
|
+
* @param key 缓存键
|
|
403
|
+
* @param value 序列化后的值
|
|
404
|
+
* @param expiresAt 过期时间戳,null 表示永不过期
|
|
405
|
+
*/
|
|
406
|
+
set(key, value, expiresAt) {
|
|
407
|
+
const cache = this.getCache();
|
|
408
|
+
const entry = { value, expiresAt };
|
|
409
|
+
cache.set(key, entry);
|
|
410
|
+
this._broadcastSync(key, value, expiresAt);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* 获取缓存
|
|
414
|
+
* @param key 缓存键
|
|
415
|
+
* @returns 序列化的值,未命中或过期返回 null
|
|
416
|
+
*/
|
|
417
|
+
get(key) {
|
|
418
|
+
const cache = this.getCache();
|
|
419
|
+
const entry = cache.get(key);
|
|
420
|
+
if (!entry)
|
|
421
|
+
return null;
|
|
422
|
+
if (this.isExpired(entry)) {
|
|
423
|
+
cache.delete(key);
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
return entry.value;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* 检查缓存是否存在且未过期
|
|
430
|
+
*/
|
|
431
|
+
has(key) {
|
|
432
|
+
const cache = this.getCache();
|
|
433
|
+
const entry = cache.get(key);
|
|
434
|
+
if (!entry)
|
|
435
|
+
return false;
|
|
436
|
+
if (this.isExpired(entry)) {
|
|
437
|
+
cache.delete(key);
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* 删除缓存
|
|
444
|
+
*/
|
|
445
|
+
del(key) {
|
|
446
|
+
const cache = this.getCache();
|
|
447
|
+
cache.delete(key);
|
|
448
|
+
const groupCaches = this.getGroupCaches();
|
|
449
|
+
groupCaches.delete(key);
|
|
450
|
+
this._broadcastDelete([key]);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* 按前缀删除缓存
|
|
454
|
+
* @returns 删除的数量
|
|
455
|
+
*/
|
|
456
|
+
delByPrefix(prefix) {
|
|
457
|
+
const cache = this.getCache();
|
|
458
|
+
const deletedKeys = [];
|
|
459
|
+
for (const key of cache.keys()) {
|
|
460
|
+
if (key.startsWith(prefix)) {
|
|
461
|
+
cache.delete(key);
|
|
462
|
+
deletedKeys.push(key);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const groupCaches = this.getGroupCaches();
|
|
466
|
+
for (const key of groupCaches.keys()) {
|
|
467
|
+
if (key.startsWith(prefix)) {
|
|
468
|
+
groupCaches.delete(key);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
this._broadcastDelete(deletedKeys);
|
|
472
|
+
return deletedKeys.length;
|
|
473
|
+
}
|
|
474
|
+
/* ---------------------- 分组 API ---------------------- */
|
|
475
|
+
/**
|
|
476
|
+
* 设置分组缓存
|
|
477
|
+
*/
|
|
478
|
+
groupSet(key, subKey, value, expiresAt) {
|
|
479
|
+
const groupCache = this.getOrCreateGroupCache(key);
|
|
480
|
+
const entry = { value, expiresAt };
|
|
481
|
+
groupCache.set(subKey, entry);
|
|
482
|
+
this._broadcastSync(key, value, expiresAt, true, subKey);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* 获取分组缓存
|
|
486
|
+
*/
|
|
487
|
+
groupGet(key, subKey) {
|
|
488
|
+
const groupCaches = this.getGroupCaches();
|
|
489
|
+
const groupCache = groupCaches.get(key);
|
|
490
|
+
if (!groupCache)
|
|
491
|
+
return null;
|
|
492
|
+
const entry = groupCache.get(subKey);
|
|
493
|
+
if (!entry)
|
|
494
|
+
return null;
|
|
495
|
+
if (this.isExpired(entry)) {
|
|
496
|
+
groupCache.delete(subKey);
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
return entry.value;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* 检查分组缓存是否存在
|
|
503
|
+
*/
|
|
504
|
+
groupHas(key, subKey) {
|
|
505
|
+
const groupCaches = this.getGroupCaches();
|
|
506
|
+
const groupCache = groupCaches.get(key);
|
|
507
|
+
if (!groupCache)
|
|
508
|
+
return false;
|
|
509
|
+
const entry = groupCache.get(subKey);
|
|
510
|
+
if (!entry)
|
|
511
|
+
return false;
|
|
512
|
+
if (this.isExpired(entry)) {
|
|
513
|
+
groupCache.delete(subKey);
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* 删除分组缓存
|
|
520
|
+
*/
|
|
521
|
+
groupDel(key, subKey) {
|
|
522
|
+
const groupCaches = this.getGroupCaches();
|
|
523
|
+
const groupCache = groupCaches.get(key);
|
|
524
|
+
if (groupCache) {
|
|
525
|
+
groupCache.delete(subKey);
|
|
526
|
+
this._broadcastDelete([subKey], true, key);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/* ---------------------- 管理 API ---------------------- */
|
|
530
|
+
/**
|
|
531
|
+
* 清空所有缓存
|
|
532
|
+
*/
|
|
533
|
+
clear() {
|
|
534
|
+
const cache = this.getCache();
|
|
535
|
+
cache.clear();
|
|
536
|
+
const groupCaches = this.getGroupCaches();
|
|
537
|
+
groupCaches.clear();
|
|
538
|
+
this._broadcastClear();
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* 获取缓存统计信息
|
|
542
|
+
*/
|
|
543
|
+
getStats() {
|
|
544
|
+
const cache = this.getCache();
|
|
545
|
+
const groupCaches = this.getGroupCaches();
|
|
546
|
+
let totalGroupSize = 0;
|
|
547
|
+
for (const groupCache of groupCaches.values()) {
|
|
548
|
+
totalGroupSize += groupCache.size;
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
size: cache.size,
|
|
552
|
+
// 普通缓存条目数
|
|
553
|
+
groupCount: totalGroupSize,
|
|
554
|
+
// 分组缓存条目数
|
|
555
|
+
maxSize: this.maxSize
|
|
556
|
+
// 最大容量
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* 关闭缓存(清理资源)
|
|
561
|
+
*
|
|
562
|
+
* 注意:不会广播 close 事件给其他 worker,因为每个 worker 有独立的生命周期
|
|
563
|
+
*/
|
|
564
|
+
close() {
|
|
565
|
+
_LruCache.caches.delete(this.cacheKey);
|
|
566
|
+
_LruCache.groupCaches.delete(this.cacheKey);
|
|
567
|
+
_LruCache.listenerSetup.delete(this.cacheKey);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
__publicField$4(_LruCache, "caches", /* @__PURE__ */ new Map());
|
|
571
|
+
__publicField$4(_LruCache, "groupCaches", /* @__PURE__ */ new Map());
|
|
572
|
+
__publicField$4(_LruCache, "listenerSetup", /* @__PURE__ */ new Map());
|
|
573
|
+
let LruCache = _LruCache;
|
|
574
|
+
|
|
212
575
|
var __defProp$3 = Object.defineProperty;
|
|
213
576
|
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
214
577
|
var __publicField$3 = (obj, key, value) => {
|
|
@@ -274,6 +637,8 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
274
637
|
__publicField$3(this, "defaultTtl", 1e3 * 60 * 60);
|
|
275
638
|
__publicField$3(this, "cleanupInterval", 5 * 60 * 1e3);
|
|
276
639
|
__publicField$3(this, "dbPath", "");
|
|
640
|
+
__publicField$3(this, "enableLruCache", false);
|
|
641
|
+
__publicField$3(this, "lruCache", null);
|
|
277
642
|
}
|
|
278
643
|
/* ------------------------------- init ------------------------------- */
|
|
279
644
|
async ensure() {
|
|
@@ -283,6 +648,28 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
283
648
|
this.prefix = this.opts.prefix;
|
|
284
649
|
this.defaultTtl = this.opts.ttl;
|
|
285
650
|
this.cleanupInterval = this.opts.cleanupInterval ?? 5 * 60 * 1e3;
|
|
651
|
+
this.enableLruCache = this.opts.enableLruCache ?? false;
|
|
652
|
+
}
|
|
653
|
+
if (this.enableLruCache && !this.lruCache) {
|
|
654
|
+
const lruCacheKey = `sqlite:${this.dbPath}:${this.prefix}`;
|
|
655
|
+
if (!_SqliteAdapter.lruCaches.has(lruCacheKey)) {
|
|
656
|
+
const requestedSize = this.opts.lruMaxSize ?? 100;
|
|
657
|
+
const actualSize = Math.min(requestedSize, _SqliteAdapter.MAX_LRU_CACHE_SIZE);
|
|
658
|
+
if (requestedSize > _SqliteAdapter.MAX_LRU_CACHE_SIZE) {
|
|
659
|
+
console.warn(
|
|
660
|
+
`[SqliteAdapter] Requested LRU cache size ${requestedSize} exceeds maximum ${_SqliteAdapter.MAX_LRU_CACHE_SIZE}, using ${actualSize} instead`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
_SqliteAdapter.lruCaches.set(
|
|
664
|
+
lruCacheKey,
|
|
665
|
+
new LruCache({
|
|
666
|
+
prefix: lruCacheKey,
|
|
667
|
+
maxSize: actualSize,
|
|
668
|
+
enableSync: this.opts.enableSync ?? true
|
|
669
|
+
})
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
this.lruCache = _SqliteAdapter.lruCaches.get(lruCacheKey);
|
|
286
673
|
}
|
|
287
674
|
if (_SqliteAdapter.clients.has(this.dbPath))
|
|
288
675
|
return;
|
|
@@ -345,7 +732,11 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
345
732
|
const storageKey = this.prefixKey(key);
|
|
346
733
|
const effectiveTtl = opts.ttl !== void 0 ? opts.ttl : this.defaultTtl;
|
|
347
734
|
const expiresAt = effectiveTtl > 0 ? Date.now() + effectiveTtl : null;
|
|
735
|
+
const serializedValue = this.serialize(value);
|
|
348
736
|
if (opts.nx) {
|
|
737
|
+
if (this.lruCache?.has(storageKey)) {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
349
740
|
const existing = await dbGet(
|
|
350
741
|
client,
|
|
351
742
|
"SELECT value FROM kvcache WHERE key = ? AND subKey = ?",
|
|
@@ -364,13 +755,22 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
364
755
|
value = excluded.value,
|
|
365
756
|
expiresAt = excluded.expiresAt
|
|
366
757
|
`,
|
|
367
|
-
[storageKey, "",
|
|
758
|
+
[storageKey, "", serializedValue, expiresAt]
|
|
368
759
|
);
|
|
760
|
+
if (this.lruCache) {
|
|
761
|
+
this.lruCache.set(storageKey, serializedValue, expiresAt);
|
|
762
|
+
}
|
|
369
763
|
return true;
|
|
370
764
|
}
|
|
371
765
|
async get(key) {
|
|
372
|
-
const client = this.getClient();
|
|
373
766
|
const storageKey = this.prefixKey(key);
|
|
767
|
+
if (this.lruCache) {
|
|
768
|
+
const cached = this.lruCache.get(storageKey);
|
|
769
|
+
if (cached !== null) {
|
|
770
|
+
return this.deserialize(cached);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
const client = this.getClient();
|
|
374
774
|
const row = await dbGet(
|
|
375
775
|
client,
|
|
376
776
|
'SELECT value, "expiresAt" FROM kvcache WHERE key = ? AND subKey = ?',
|
|
@@ -382,16 +782,25 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
382
782
|
await this.del(key);
|
|
383
783
|
return null;
|
|
384
784
|
}
|
|
785
|
+
if (this.lruCache) {
|
|
786
|
+
this.lruCache.set(storageKey, row.value, row.expiresAt);
|
|
787
|
+
}
|
|
385
788
|
return this.deserialize(row.value);
|
|
386
789
|
}
|
|
387
790
|
async del(key) {
|
|
388
|
-
const client = this.getClient();
|
|
389
791
|
const storageKey = this.prefixKey(key);
|
|
792
|
+
if (this.lruCache) {
|
|
793
|
+
this.lruCache.del(storageKey);
|
|
794
|
+
}
|
|
795
|
+
const client = this.getClient();
|
|
390
796
|
await dbRun(client, "DELETE FROM kvcache WHERE key = ?", [storageKey]);
|
|
391
797
|
}
|
|
392
798
|
async has(key) {
|
|
393
|
-
const client = this.getClient();
|
|
394
799
|
const storageKey = this.prefixKey(key);
|
|
800
|
+
if (this.lruCache?.has(storageKey)) {
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
const client = this.getClient();
|
|
395
804
|
const row = await dbGet(
|
|
396
805
|
client,
|
|
397
806
|
'SELECT "expiresAt" FROM kvcache WHERE key = ? AND subKey = ?',
|
|
@@ -411,11 +820,7 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
411
820
|
const storageKey = this.prefixKey(key);
|
|
412
821
|
const effectiveTtl = opts.ttl !== void 0 ? opts.ttl : this.defaultTtl;
|
|
413
822
|
const expiresAt = effectiveTtl > 0 ? Date.now() + effectiveTtl : null;
|
|
414
|
-
|
|
415
|
-
const exists = await this.groupHas(key, subKey);
|
|
416
|
-
if (exists)
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
823
|
+
const serializedValue = this.serialize(value);
|
|
419
824
|
await dbRun(
|
|
420
825
|
client,
|
|
421
826
|
`
|
|
@@ -425,12 +830,21 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
425
830
|
value = excluded.value,
|
|
426
831
|
expiresAt = excluded.expiresAt
|
|
427
832
|
`,
|
|
428
|
-
[storageKey, subKey,
|
|
833
|
+
[storageKey, subKey, serializedValue, expiresAt]
|
|
429
834
|
);
|
|
835
|
+
if (this.lruCache) {
|
|
836
|
+
this.lruCache.groupSet(storageKey, subKey, serializedValue, expiresAt);
|
|
837
|
+
}
|
|
430
838
|
}
|
|
431
839
|
async groupGet(key, subKey) {
|
|
432
|
-
const client = this.getClient();
|
|
433
840
|
const storageKey = this.prefixKey(key);
|
|
841
|
+
if (this.lruCache) {
|
|
842
|
+
const cached = this.lruCache.groupGet(storageKey, subKey);
|
|
843
|
+
if (cached !== null) {
|
|
844
|
+
return this.deserialize(cached);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const client = this.getClient();
|
|
434
848
|
const row = await dbGet(
|
|
435
849
|
client,
|
|
436
850
|
'SELECT value, "expiresAt" FROM kvcache WHERE key = ? AND subKey = ?',
|
|
@@ -442,11 +856,17 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
442
856
|
await this.groupDel(key, subKey);
|
|
443
857
|
return null;
|
|
444
858
|
}
|
|
859
|
+
if (this.lruCache) {
|
|
860
|
+
this.lruCache.groupSet(storageKey, subKey, row.value, row.expiresAt);
|
|
861
|
+
}
|
|
445
862
|
return this.deserialize(row.value);
|
|
446
863
|
}
|
|
447
864
|
async groupHas(key, subKey) {
|
|
448
|
-
const client = this.getClient();
|
|
449
865
|
const storageKey = this.prefixKey(key);
|
|
866
|
+
if (this.lruCache?.groupHas(storageKey, subKey)) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
const client = this.getClient();
|
|
450
870
|
const row = await dbGet(
|
|
451
871
|
client,
|
|
452
872
|
'SELECT "expiresAt" FROM kvcache WHERE key = ? AND subKey = ?',
|
|
@@ -461,12 +881,21 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
461
881
|
return true;
|
|
462
882
|
}
|
|
463
883
|
async groupDel(key, subKey) {
|
|
464
|
-
const client = this.getClient();
|
|
465
884
|
const storageKey = this.prefixKey(key);
|
|
885
|
+
if (this.lruCache) {
|
|
886
|
+
this.lruCache.groupDel(storageKey, subKey);
|
|
887
|
+
}
|
|
888
|
+
const client = this.getClient();
|
|
466
889
|
await dbRun(client, "DELETE FROM kvcache WHERE key = ? AND subKey = ?", [storageKey, subKey]);
|
|
467
890
|
}
|
|
468
891
|
/* ------------------------------- misc ------------------------------- */
|
|
469
892
|
async close() {
|
|
893
|
+
if (this.lruCache) {
|
|
894
|
+
this.lruCache.close();
|
|
895
|
+
const lruCacheKey = `sqlite:${this.dbPath}:${this.prefix}`;
|
|
896
|
+
_SqliteAdapter.lruCaches.delete(lruCacheKey);
|
|
897
|
+
this.lruCache = null;
|
|
898
|
+
}
|
|
470
899
|
if (this.dbPath) {
|
|
471
900
|
const timer = _SqliteAdapter.cleanupTimers.get(this.dbPath);
|
|
472
901
|
if (timer) {
|
|
@@ -482,14 +911,29 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
482
911
|
}
|
|
483
912
|
}
|
|
484
913
|
async flushAll() {
|
|
914
|
+
if (this.lruCache) {
|
|
915
|
+
this.lruCache.clear();
|
|
916
|
+
}
|
|
485
917
|
const client = this.getClient();
|
|
486
918
|
const run = promisify(client.run.bind(client));
|
|
487
919
|
await run("DELETE FROM kvcache");
|
|
488
920
|
}
|
|
921
|
+
/**
|
|
922
|
+
* 获取 LRU 缓存统计信息
|
|
923
|
+
*/
|
|
924
|
+
getLruCacheStats() {
|
|
925
|
+
if (!this.lruCache) {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
return this.lruCache.getStats();
|
|
929
|
+
}
|
|
489
930
|
};
|
|
490
931
|
__publicField$3(_SqliteAdapter, "clients", /* @__PURE__ */ new Map());
|
|
491
932
|
__publicField$3(_SqliteAdapter, "initPromises", /* @__PURE__ */ new Map());
|
|
492
933
|
__publicField$3(_SqliteAdapter, "cleanupTimers", /* @__PURE__ */ new Map());
|
|
934
|
+
__publicField$3(_SqliteAdapter, "lruCaches", /* @__PURE__ */ new Map());
|
|
935
|
+
/** LRU 缓存的最大条数上限 */
|
|
936
|
+
__publicField$3(_SqliteAdapter, "MAX_LRU_CACHE_SIZE", 100);
|
|
493
937
|
let SqliteAdapter = _SqliteAdapter;
|
|
494
938
|
|
|
495
939
|
var __defProp$2 = Object.defineProperty;
|
|
@@ -641,6 +1085,14 @@ class BaseDBCache {
|
|
|
641
1085
|
await this.adapter.ensure();
|
|
642
1086
|
return this.adapter.flushAll();
|
|
643
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* 获取 LRU 缓存统计信息
|
|
1090
|
+
* @returns 缓存统计信息,如果未启用 LRU 缓存则返回 null
|
|
1091
|
+
*/
|
|
1092
|
+
getLruCacheStats() {
|
|
1093
|
+
this.initAdapter();
|
|
1094
|
+
return this.adapter.getLruCacheStats();
|
|
1095
|
+
}
|
|
644
1096
|
}
|
|
645
1097
|
|
|
646
1098
|
var __defProp$1 = Object.defineProperty;
|
|
@@ -851,4 +1303,4 @@ const getAbtNodeRedisAndSQLiteUrl = () => {
|
|
|
851
1303
|
return params;
|
|
852
1304
|
};
|
|
853
1305
|
|
|
854
|
-
export { LockDBCache as DBCache, getAbtNodeRedisAndSQLiteUrl, withRetry };
|
|
1306
|
+
export { LockDBCache as DBCache, LruCache, getAbtNodeRedisAndSQLiteUrl, withRetry };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abtnode/db-cache",
|
|
3
|
-
"version": "1.17.8-beta-
|
|
4
|
-
"description": "Db cache use redis or
|
|
3
|
+
"version": "1.17.8-beta-20260113-015027-32a1cec4",
|
|
4
|
+
"description": "Db cache use redis, sqlite or memory as backend",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
"author": "",
|
|
28
28
|
"license": "ISC",
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@arcblock/event-hub": "^1.28.4",
|
|
30
31
|
"@types/proper-lockfile": "^4.1.4",
|
|
32
|
+
"lru-cache": "^11.1.0",
|
|
31
33
|
"proper-lockfile": "^4.1.2",
|
|
32
34
|
"redis": "^5.1.1",
|
|
33
35
|
"sqlite3": "^5.1.7"
|
|
@@ -42,5 +44,5 @@
|
|
|
42
44
|
"prettier": "^3.3.2",
|
|
43
45
|
"unbuild": "^2.0.0"
|
|
44
46
|
},
|
|
45
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "a2bdf6146c5c94d7348fc87d5ebd2e95e548df07"
|
|
46
48
|
}
|