@abtnode/db-cache 1.17.3-beta-20251118-061144-335cd35d → 1.17.3-beta-20251119-034511-f26047c0
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 +211 -82
- package/dist/index.mjs +206 -81
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const path = require('path');
|
|
3
|
+
const path$1 = require('path');
|
|
4
4
|
const redis = require('redis');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const node_util = require('node:util');
|
|
5
9
|
const sqlite3 = require('sqlite3');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
10
|
+
const crypto = require('node:crypto');
|
|
11
|
+
const lockfile = require('proper-lockfile');
|
|
8
12
|
|
|
9
13
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
10
14
|
|
|
@@ -20,25 +24,44 @@ function _interopNamespaceCompat(e) {
|
|
|
20
24
|
return n;
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path$1);
|
|
28
|
+
const fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
|
|
29
|
+
const os__namespace = /*#__PURE__*/_interopNamespaceCompat(os);
|
|
23
30
|
const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
|
|
24
|
-
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
25
31
|
const sqlite3__default = /*#__PURE__*/_interopDefaultCompat(sqlite3);
|
|
26
|
-
const
|
|
32
|
+
const crypto__namespace = /*#__PURE__*/_interopNamespaceCompat(crypto);
|
|
33
|
+
const lockfile__default = /*#__PURE__*/_interopDefaultCompat(lockfile);
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
function ulid() {
|
|
36
|
+
const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
37
|
+
let t = Date.now();
|
|
38
|
+
let timeStr = "";
|
|
39
|
+
for (let i = 0; i < 10; i++) {
|
|
40
|
+
timeStr = alphabet[t % 32] + timeStr;
|
|
41
|
+
t = Math.floor(t / 32);
|
|
42
|
+
}
|
|
43
|
+
let rand = Math.random().toString(32).substring(2);
|
|
44
|
+
while (rand.length < 16) {
|
|
45
|
+
rand += Math.random().toString(32).substring(2);
|
|
46
|
+
}
|
|
47
|
+
rand = rand.substring(0, 16).toUpperCase();
|
|
48
|
+
return timeStr + rand;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var __defProp$5 = Object.defineProperty;
|
|
52
|
+
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
53
|
+
var __publicField$5 = (obj, key, value) => {
|
|
54
|
+
__defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
32
55
|
return value;
|
|
33
56
|
};
|
|
34
57
|
const _RedisAdapter = class _RedisAdapter {
|
|
35
58
|
constructor() {
|
|
36
|
-
__publicField$
|
|
37
|
-
__publicField$
|
|
38
|
-
__publicField$
|
|
39
|
-
__publicField$
|
|
40
|
-
__publicField$
|
|
41
|
-
__publicField$
|
|
59
|
+
__publicField$5(this, "opts", null);
|
|
60
|
+
__publicField$5(this, "defaultTtl", 1e3 * 60 * 60);
|
|
61
|
+
__publicField$5(this, "url", "");
|
|
62
|
+
__publicField$5(this, "prefix", "");
|
|
63
|
+
__publicField$5(this, "prefixKey", (key) => `${this.prefix}:${key}`);
|
|
64
|
+
__publicField$5(this, "prefixKeyGroup", (key) => `${this.prefix}:group:${key}`);
|
|
42
65
|
}
|
|
43
66
|
clearAll() {
|
|
44
67
|
throw new Error("Method not implemented.");
|
|
@@ -56,10 +79,15 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
56
79
|
_RedisAdapter.initPromises.set(
|
|
57
80
|
this.url,
|
|
58
81
|
(async () => {
|
|
59
|
-
const
|
|
82
|
+
const { url } = this;
|
|
83
|
+
if (!url) {
|
|
84
|
+
throw new Error("Redis URL is not set");
|
|
85
|
+
}
|
|
86
|
+
const cli = redis.createClient({ url });
|
|
60
87
|
cli.on("error", console.error);
|
|
61
88
|
await cli.connect();
|
|
62
|
-
|
|
89
|
+
await cli.ping();
|
|
90
|
+
_RedisAdapter.clients.set(url, cli);
|
|
63
91
|
})()
|
|
64
92
|
);
|
|
65
93
|
}
|
|
@@ -70,7 +98,11 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
70
98
|
if (!this.url || !_RedisAdapter.clients.has(this.url)) {
|
|
71
99
|
throw new Error("Redis not initialized");
|
|
72
100
|
}
|
|
73
|
-
|
|
101
|
+
const client = _RedisAdapter.clients.get(this.url);
|
|
102
|
+
if (!client) {
|
|
103
|
+
throw new Error("Redis client not found");
|
|
104
|
+
}
|
|
105
|
+
return client;
|
|
74
106
|
}
|
|
75
107
|
serialize(value) {
|
|
76
108
|
return JSON.stringify({ v: value });
|
|
@@ -88,12 +120,21 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
88
120
|
async set(key, value, { ttl, nx }) {
|
|
89
121
|
const client = this.getClient();
|
|
90
122
|
const storageKey = this.prefixKey(key);
|
|
91
|
-
|
|
92
|
-
|
|
123
|
+
const effectiveTtl = ttl !== void 0 ? ttl : this.defaultTtl;
|
|
124
|
+
let result;
|
|
125
|
+
if (effectiveTtl && effectiveTtl > 0) {
|
|
126
|
+
result = await client.set(storageKey, this.serialize(value), { PX: effectiveTtl, NX: nx });
|
|
93
127
|
} else {
|
|
94
|
-
await client.set(storageKey, this.serialize(value), { NX: nx });
|
|
128
|
+
result = await client.set(storageKey, this.serialize(value), { NX: nx });
|
|
95
129
|
}
|
|
96
|
-
|
|
130
|
+
if (result !== "OK") {
|
|
131
|
+
const storedValue = await client.get(storageKey);
|
|
132
|
+
if (storedValue === this.serialize(value)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return result === "OK";
|
|
97
138
|
}
|
|
98
139
|
async get(key) {
|
|
99
140
|
const client = this.getClient();
|
|
@@ -152,9 +193,11 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
152
193
|
async close() {
|
|
153
194
|
if (this.url && _RedisAdapter.clients.has(this.url)) {
|
|
154
195
|
const client = _RedisAdapter.clients.get(this.url);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
196
|
+
if (client) {
|
|
197
|
+
await client.quit();
|
|
198
|
+
_RedisAdapter.clients.delete(this.url);
|
|
199
|
+
_RedisAdapter.initPromises.delete(this.url);
|
|
200
|
+
}
|
|
158
201
|
}
|
|
159
202
|
}
|
|
160
203
|
async flushAll() {
|
|
@@ -163,8 +206,8 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
163
206
|
}
|
|
164
207
|
};
|
|
165
208
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
-
__publicField$
|
|
167
|
-
__publicField$
|
|
209
|
+
__publicField$5(_RedisAdapter, "clients", /* @__PURE__ */ new Map());
|
|
210
|
+
__publicField$5(_RedisAdapter, "initPromises", /* @__PURE__ */ new Map());
|
|
168
211
|
let RedisAdapter = _RedisAdapter;
|
|
169
212
|
|
|
170
213
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -193,6 +236,67 @@ async function withRetry(fn, {
|
|
|
193
236
|
}
|
|
194
237
|
}
|
|
195
238
|
|
|
239
|
+
var __defProp$4 = Object.defineProperty;
|
|
240
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
241
|
+
var __publicField$4 = (obj, key, value) => {
|
|
242
|
+
__defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
243
|
+
return value;
|
|
244
|
+
};
|
|
245
|
+
class FileLock {
|
|
246
|
+
// 默认 10 分钟
|
|
247
|
+
constructor(baseDir, defaultTtl) {
|
|
248
|
+
__publicField$4(this, "baseDir");
|
|
249
|
+
__publicField$4(this, "defaultTtl", 10 * 60 * 1e3);
|
|
250
|
+
this.baseDir = baseDir;
|
|
251
|
+
if (defaultTtl !== void 0) {
|
|
252
|
+
this.defaultTtl = defaultTtl;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
getLockFilePath(storageKey) {
|
|
256
|
+
const lockDir = path__namespace.join(this.baseDir, "locks");
|
|
257
|
+
if (!fs__namespace.existsSync(lockDir)) {
|
|
258
|
+
fs__namespace.mkdirSync(lockDir, { recursive: true });
|
|
259
|
+
}
|
|
260
|
+
const safeName = crypto__namespace.createHash("sha256").update(storageKey).digest("hex");
|
|
261
|
+
return path__namespace.join(lockDir, `${safeName}.lock`);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* acquire 返回 true 代表成功加锁
|
|
265
|
+
* 返回 false 代表有其他进程/线程持有锁
|
|
266
|
+
*/
|
|
267
|
+
async acquire(storageKey, ttl) {
|
|
268
|
+
const lockPath = this.getLockFilePath(storageKey);
|
|
269
|
+
const effectiveTtl = ttl !== void 0 ? ttl : this.defaultTtl;
|
|
270
|
+
try {
|
|
271
|
+
try {
|
|
272
|
+
fs__namespace.writeFileSync(lockPath, "", { flag: "wx" });
|
|
273
|
+
} catch (_createErr) {
|
|
274
|
+
}
|
|
275
|
+
await lockfile__default.lock(lockPath, {
|
|
276
|
+
realpath: true,
|
|
277
|
+
stale: effectiveTtl,
|
|
278
|
+
// 超时自动释放
|
|
279
|
+
update: Math.floor(effectiveTtl / 3),
|
|
280
|
+
// 定期续租,避免长任务过期
|
|
281
|
+
retries: 0
|
|
282
|
+
});
|
|
283
|
+
return true;
|
|
284
|
+
} catch (_err) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 释放锁
|
|
290
|
+
*/
|
|
291
|
+
async release(storageKey) {
|
|
292
|
+
const lockPath = this.getLockFilePath(storageKey);
|
|
293
|
+
try {
|
|
294
|
+
await lockfile__default.unlock(lockPath);
|
|
295
|
+
} catch (_err) {
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
196
300
|
var __defProp$3 = Object.defineProperty;
|
|
197
301
|
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
198
302
|
var __publicField$3 = (obj, key, value) => {
|
|
@@ -229,11 +333,11 @@ async function initSqliteWithRetry(db) {
|
|
|
229
333
|
await dbExec(
|
|
230
334
|
db,
|
|
231
335
|
`
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
`
|
|
336
|
+
PRAGMA journal_mode = WAL;
|
|
337
|
+
PRAGMA synchronous = normal;
|
|
338
|
+
PRAGMA wal_autocheckpoint = 5000;
|
|
339
|
+
PRAGMA busy_timeout = 5000;
|
|
340
|
+
`
|
|
237
341
|
);
|
|
238
342
|
await dbRun(
|
|
239
343
|
db,
|
|
@@ -245,7 +349,7 @@ async function initSqliteWithRetry(db) {
|
|
|
245
349
|
expiresAt INTEGER,
|
|
246
350
|
PRIMARY KEY (key, subKey)
|
|
247
351
|
)
|
|
248
|
-
|
|
352
|
+
`
|
|
249
353
|
);
|
|
250
354
|
} catch (err) {
|
|
251
355
|
throw new Error(`SQLite init failed: ${err}`);
|
|
@@ -258,28 +362,36 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
258
362
|
__publicField$3(this, "defaultTtl", 1e3 * 60 * 60);
|
|
259
363
|
__publicField$3(this, "cleanupInterval", 5 * 60 * 1e3);
|
|
260
364
|
__publicField$3(this, "dbPath", "");
|
|
365
|
+
__publicField$3(this, "fileLock", null);
|
|
261
366
|
}
|
|
367
|
+
/* ------------------------------- init ------------------------------- */
|
|
262
368
|
async ensure() {
|
|
263
369
|
if (!this.dbPath) {
|
|
264
370
|
this.dbPath = this.opts.sqlitePath === ":memory:" ? ":memory:" : path__namespace.resolve(this.opts.sqlitePath);
|
|
265
371
|
this.prefix = this.opts.prefix;
|
|
266
372
|
this.defaultTtl = this.opts.ttl;
|
|
267
373
|
this.cleanupInterval = this.opts.cleanupInterval ?? 30 * 60 * 1e3;
|
|
374
|
+
if (this.dbPath !== ":memory:") {
|
|
375
|
+
const baseDir = path__namespace.dirname(this.dbPath);
|
|
376
|
+
this.fileLock = new FileLock(baseDir);
|
|
377
|
+
} else {
|
|
378
|
+
const randomId = Math.random().toString(36).substring(2, 15);
|
|
379
|
+
const baseDir = path__namespace.join(os__namespace.tmpdir(), `db-cache-locks-${randomId}`);
|
|
380
|
+
this.fileLock = new FileLock(baseDir);
|
|
381
|
+
}
|
|
268
382
|
}
|
|
269
383
|
if (_SqliteAdapter.clients.has(this.dbPath))
|
|
270
384
|
return;
|
|
271
385
|
if (!_SqliteAdapter.initPromises.has(this.dbPath)) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
fs__namespace.
|
|
386
|
+
if (this.dbPath !== ":memory:") {
|
|
387
|
+
const dir = path__namespace.dirname(this.dbPath);
|
|
388
|
+
if (!fs__namespace.existsSync(dir)) {
|
|
389
|
+
fs__namespace.mkdirSync(dir, { recursive: true });
|
|
390
|
+
}
|
|
275
391
|
}
|
|
276
392
|
_SqliteAdapter.initPromises.set(
|
|
277
393
|
this.dbPath,
|
|
278
394
|
(async () => {
|
|
279
|
-
const dbDir = path__namespace.dirname(this.dbPath);
|
|
280
|
-
if (!fs__namespace.existsSync(dbDir)) {
|
|
281
|
-
fs__namespace.mkdirSync(dbDir, { recursive: true });
|
|
282
|
-
}
|
|
283
395
|
const db = new sqlite3__default.Database(this.dbPath);
|
|
284
396
|
await initSqliteWithRetry(db);
|
|
285
397
|
_SqliteAdapter.clients.set(this.dbPath, db);
|
|
@@ -303,16 +415,12 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
303
415
|
}
|
|
304
416
|
return _SqliteAdapter.clients.get(this.dbPath);
|
|
305
417
|
}
|
|
418
|
+
/* ------------------------------- cleanup ------------------------------- */
|
|
306
419
|
async cleanup() {
|
|
307
420
|
const client = this.getClient();
|
|
308
|
-
await
|
|
309
|
-
client.run(
|
|
310
|
-
"DELETE FROM kvcache WHERE expiresAt IS NOT NULL AND expiresAt <= ?",
|
|
311
|
-
[Date.now()],
|
|
312
|
-
(err) => err ? rej(err) : res()
|
|
313
|
-
);
|
|
314
|
-
});
|
|
421
|
+
await dbRun(client, "DELETE FROM kvcache WHERE expiresAt IS NOT NULL AND expiresAt <= ?", [Date.now()]);
|
|
315
422
|
}
|
|
423
|
+
/* ------------------------------- core ------------------------------- */
|
|
316
424
|
prefixKey(key) {
|
|
317
425
|
return `${this.prefix}:${key}`;
|
|
318
426
|
}
|
|
@@ -323,8 +431,7 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
323
431
|
if (raw === null || raw === void 0)
|
|
324
432
|
return null;
|
|
325
433
|
try {
|
|
326
|
-
|
|
327
|
-
return obj.v;
|
|
434
|
+
return JSON.parse(raw).v;
|
|
328
435
|
} catch {
|
|
329
436
|
return null;
|
|
330
437
|
}
|
|
@@ -334,33 +441,52 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
334
441
|
const storageKey = this.prefixKey(key);
|
|
335
442
|
const effectiveTtl = opts.ttl !== void 0 ? opts.ttl : this.defaultTtl;
|
|
336
443
|
const expiresAt = effectiveTtl > 0 ? Date.now() + effectiveTtl : null;
|
|
337
|
-
|
|
338
|
-
if (opts.nx) {
|
|
339
|
-
|
|
340
|
-
if (
|
|
444
|
+
let fileLocked = false;
|
|
445
|
+
if (opts.nx && this.fileLock) {
|
|
446
|
+
fileLocked = await this.fileLock.acquire(storageKey, effectiveTtl);
|
|
447
|
+
if (!fileLocked)
|
|
341
448
|
return false;
|
|
342
449
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
450
|
+
try {
|
|
451
|
+
if (opts.nx) {
|
|
452
|
+
const existing = await dbGet(
|
|
453
|
+
client,
|
|
454
|
+
"SELECT value FROM kvcache WHERE key = ? AND subKey = ?",
|
|
455
|
+
[storageKey, ""]
|
|
456
|
+
);
|
|
457
|
+
if (existing) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
await dbRun(
|
|
462
|
+
client,
|
|
463
|
+
`
|
|
464
|
+
INSERT INTO kvcache (key, subKey, value, expiresAt)
|
|
465
|
+
VALUES (?, ?, ?, ?)
|
|
466
|
+
ON CONFLICT(key, subKey) DO UPDATE SET
|
|
467
|
+
value = excluded.value,
|
|
468
|
+
expiresAt = excluded.expiresAt
|
|
469
|
+
`,
|
|
470
|
+
[storageKey, "", this.serialize(value), expiresAt]
|
|
471
|
+
);
|
|
472
|
+
if (fileLocked && this.fileLock) {
|
|
473
|
+
await this.fileLock.release(storageKey);
|
|
474
|
+
}
|
|
475
|
+
return true;
|
|
476
|
+
} catch (err) {
|
|
477
|
+
if (fileLocked && this.fileLock) {
|
|
478
|
+
await this.fileLock.release(storageKey);
|
|
479
|
+
}
|
|
480
|
+
throw err;
|
|
481
|
+
}
|
|
355
482
|
}
|
|
356
483
|
async get(key) {
|
|
357
484
|
const client = this.getClient();
|
|
358
485
|
const storageKey = this.prefixKey(key);
|
|
359
|
-
const subKey = "";
|
|
360
486
|
const row = await dbGet(
|
|
361
487
|
client,
|
|
362
488
|
'SELECT value, "expiresAt" FROM kvcache WHERE key = ? AND subKey = ?',
|
|
363
|
-
[storageKey,
|
|
489
|
+
[storageKey, ""]
|
|
364
490
|
);
|
|
365
491
|
if (!row)
|
|
366
492
|
return null;
|
|
@@ -374,15 +500,17 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
374
500
|
const client = this.getClient();
|
|
375
501
|
const storageKey = this.prefixKey(key);
|
|
376
502
|
await dbRun(client, "DELETE FROM kvcache WHERE key = ?", [storageKey]);
|
|
503
|
+
if (this.fileLock) {
|
|
504
|
+
await this.fileLock.release(storageKey);
|
|
505
|
+
}
|
|
377
506
|
}
|
|
378
507
|
async has(key) {
|
|
379
508
|
const client = this.getClient();
|
|
380
509
|
const storageKey = this.prefixKey(key);
|
|
381
|
-
const subKey = "";
|
|
382
510
|
const row = await dbGet(
|
|
383
511
|
client,
|
|
384
512
|
'SELECT "expiresAt" FROM kvcache WHERE key = ? AND subKey = ?',
|
|
385
|
-
[storageKey,
|
|
513
|
+
[storageKey, ""]
|
|
386
514
|
);
|
|
387
515
|
if (!row)
|
|
388
516
|
return false;
|
|
@@ -392,6 +520,7 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
392
520
|
}
|
|
393
521
|
return true;
|
|
394
522
|
}
|
|
523
|
+
/* ------------------------------- group APIs ------------------------------- */
|
|
395
524
|
async groupSet(key, subKey, value, opts) {
|
|
396
525
|
const client = this.getClient();
|
|
397
526
|
const storageKey = this.prefixKey(key);
|
|
@@ -405,11 +534,11 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
405
534
|
await dbRun(
|
|
406
535
|
client,
|
|
407
536
|
`
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
537
|
+
INSERT INTO kvcache (key, subKey, value, expiresAt)
|
|
538
|
+
VALUES (?, ?, ?, ?)
|
|
539
|
+
ON CONFLICT(key, subKey) DO UPDATE SET
|
|
540
|
+
value = excluded.value,
|
|
541
|
+
expiresAt = excluded.expiresAt
|
|
413
542
|
`,
|
|
414
543
|
[storageKey, subKey, this.serialize(value), expiresAt]
|
|
415
544
|
);
|
|
@@ -449,8 +578,12 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
449
578
|
async groupDel(key, subKey) {
|
|
450
579
|
const client = this.getClient();
|
|
451
580
|
const storageKey = this.prefixKey(key);
|
|
452
|
-
|
|
581
|
+
await dbRun(client, "DELETE FROM kvcache WHERE key = ? AND subKey = ?", [storageKey, subKey]);
|
|
582
|
+
if (this.fileLock) {
|
|
583
|
+
await this.fileLock.release(storageKey);
|
|
584
|
+
}
|
|
453
585
|
}
|
|
586
|
+
/* ------------------------------- misc ------------------------------- */
|
|
454
587
|
async close() {
|
|
455
588
|
if (this.dbPath) {
|
|
456
589
|
const timer = _SqliteAdapter.cleanupTimers.get(this.dbPath);
|
|
@@ -468,7 +601,7 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
468
601
|
}
|
|
469
602
|
async flushAll() {
|
|
470
603
|
const client = this.getClient();
|
|
471
|
-
const run =
|
|
604
|
+
const run = node_util.promisify(client.run.bind(client));
|
|
472
605
|
await run("DELETE FROM kvcache");
|
|
473
606
|
}
|
|
474
607
|
};
|
|
@@ -730,12 +863,8 @@ class LockDBCache extends SingleFlightDBCache {
|
|
|
730
863
|
}
|
|
731
864
|
this._inFlight.set(key, Date.now());
|
|
732
865
|
try {
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
const result = await this.set(key, Date.now(), { ttl: this.defaultTtl, nx: true });
|
|
736
|
-
return result;
|
|
737
|
-
}
|
|
738
|
-
return false;
|
|
866
|
+
const result = await this.set(key, ulid(), { ttl: this.defaultTtl, nx: true });
|
|
867
|
+
return result;
|
|
739
868
|
} finally {
|
|
740
869
|
this._inFlight.delete(key);
|
|
741
870
|
for (const [key2, value] of this._inFlight.entries()) {
|
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path__default from 'path';
|
|
1
|
+
import path$1 from 'path';
|
|
3
2
|
import { createClient } from 'redis';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
4
7
|
import sqlite3 from 'sqlite3';
|
|
5
|
-
import * as
|
|
6
|
-
import
|
|
8
|
+
import * as crypto from 'node:crypto';
|
|
9
|
+
import lockfile from 'proper-lockfile';
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
function ulid() {
|
|
12
|
+
const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
13
|
+
let t = Date.now();
|
|
14
|
+
let timeStr = "";
|
|
15
|
+
for (let i = 0; i < 10; i++) {
|
|
16
|
+
timeStr = alphabet[t % 32] + timeStr;
|
|
17
|
+
t = Math.floor(t / 32);
|
|
18
|
+
}
|
|
19
|
+
let rand = Math.random().toString(32).substring(2);
|
|
20
|
+
while (rand.length < 16) {
|
|
21
|
+
rand += Math.random().toString(32).substring(2);
|
|
22
|
+
}
|
|
23
|
+
rand = rand.substring(0, 16).toUpperCase();
|
|
24
|
+
return timeStr + rand;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var __defProp$5 = Object.defineProperty;
|
|
28
|
+
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
29
|
+
var __publicField$5 = (obj, key, value) => {
|
|
30
|
+
__defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
12
31
|
return value;
|
|
13
32
|
};
|
|
14
33
|
const _RedisAdapter = class _RedisAdapter {
|
|
15
34
|
constructor() {
|
|
16
|
-
__publicField$
|
|
17
|
-
__publicField$
|
|
18
|
-
__publicField$
|
|
19
|
-
__publicField$
|
|
20
|
-
__publicField$
|
|
21
|
-
__publicField$
|
|
35
|
+
__publicField$5(this, "opts", null);
|
|
36
|
+
__publicField$5(this, "defaultTtl", 1e3 * 60 * 60);
|
|
37
|
+
__publicField$5(this, "url", "");
|
|
38
|
+
__publicField$5(this, "prefix", "");
|
|
39
|
+
__publicField$5(this, "prefixKey", (key) => `${this.prefix}:${key}`);
|
|
40
|
+
__publicField$5(this, "prefixKeyGroup", (key) => `${this.prefix}:group:${key}`);
|
|
22
41
|
}
|
|
23
42
|
clearAll() {
|
|
24
43
|
throw new Error("Method not implemented.");
|
|
@@ -36,10 +55,15 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
36
55
|
_RedisAdapter.initPromises.set(
|
|
37
56
|
this.url,
|
|
38
57
|
(async () => {
|
|
39
|
-
const
|
|
58
|
+
const { url } = this;
|
|
59
|
+
if (!url) {
|
|
60
|
+
throw new Error("Redis URL is not set");
|
|
61
|
+
}
|
|
62
|
+
const cli = createClient({ url });
|
|
40
63
|
cli.on("error", console.error);
|
|
41
64
|
await cli.connect();
|
|
42
|
-
|
|
65
|
+
await cli.ping();
|
|
66
|
+
_RedisAdapter.clients.set(url, cli);
|
|
43
67
|
})()
|
|
44
68
|
);
|
|
45
69
|
}
|
|
@@ -50,7 +74,11 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
50
74
|
if (!this.url || !_RedisAdapter.clients.has(this.url)) {
|
|
51
75
|
throw new Error("Redis not initialized");
|
|
52
76
|
}
|
|
53
|
-
|
|
77
|
+
const client = _RedisAdapter.clients.get(this.url);
|
|
78
|
+
if (!client) {
|
|
79
|
+
throw new Error("Redis client not found");
|
|
80
|
+
}
|
|
81
|
+
return client;
|
|
54
82
|
}
|
|
55
83
|
serialize(value) {
|
|
56
84
|
return JSON.stringify({ v: value });
|
|
@@ -68,12 +96,21 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
68
96
|
async set(key, value, { ttl, nx }) {
|
|
69
97
|
const client = this.getClient();
|
|
70
98
|
const storageKey = this.prefixKey(key);
|
|
71
|
-
|
|
72
|
-
|
|
99
|
+
const effectiveTtl = ttl !== void 0 ? ttl : this.defaultTtl;
|
|
100
|
+
let result;
|
|
101
|
+
if (effectiveTtl && effectiveTtl > 0) {
|
|
102
|
+
result = await client.set(storageKey, this.serialize(value), { PX: effectiveTtl, NX: nx });
|
|
73
103
|
} else {
|
|
74
|
-
await client.set(storageKey, this.serialize(value), { NX: nx });
|
|
104
|
+
result = await client.set(storageKey, this.serialize(value), { NX: nx });
|
|
75
105
|
}
|
|
76
|
-
|
|
106
|
+
if (result !== "OK") {
|
|
107
|
+
const storedValue = await client.get(storageKey);
|
|
108
|
+
if (storedValue === this.serialize(value)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return result === "OK";
|
|
77
114
|
}
|
|
78
115
|
async get(key) {
|
|
79
116
|
const client = this.getClient();
|
|
@@ -132,9 +169,11 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
132
169
|
async close() {
|
|
133
170
|
if (this.url && _RedisAdapter.clients.has(this.url)) {
|
|
134
171
|
const client = _RedisAdapter.clients.get(this.url);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
172
|
+
if (client) {
|
|
173
|
+
await client.quit();
|
|
174
|
+
_RedisAdapter.clients.delete(this.url);
|
|
175
|
+
_RedisAdapter.initPromises.delete(this.url);
|
|
176
|
+
}
|
|
138
177
|
}
|
|
139
178
|
}
|
|
140
179
|
async flushAll() {
|
|
@@ -143,8 +182,8 @@ const _RedisAdapter = class _RedisAdapter {
|
|
|
143
182
|
}
|
|
144
183
|
};
|
|
145
184
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
146
|
-
__publicField$
|
|
147
|
-
__publicField$
|
|
185
|
+
__publicField$5(_RedisAdapter, "clients", /* @__PURE__ */ new Map());
|
|
186
|
+
__publicField$5(_RedisAdapter, "initPromises", /* @__PURE__ */ new Map());
|
|
148
187
|
let RedisAdapter = _RedisAdapter;
|
|
149
188
|
|
|
150
189
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -173,6 +212,67 @@ async function withRetry(fn, {
|
|
|
173
212
|
}
|
|
174
213
|
}
|
|
175
214
|
|
|
215
|
+
var __defProp$4 = Object.defineProperty;
|
|
216
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
217
|
+
var __publicField$4 = (obj, key, value) => {
|
|
218
|
+
__defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
219
|
+
return value;
|
|
220
|
+
};
|
|
221
|
+
class FileLock {
|
|
222
|
+
// 默认 10 分钟
|
|
223
|
+
constructor(baseDir, defaultTtl) {
|
|
224
|
+
__publicField$4(this, "baseDir");
|
|
225
|
+
__publicField$4(this, "defaultTtl", 10 * 60 * 1e3);
|
|
226
|
+
this.baseDir = baseDir;
|
|
227
|
+
if (defaultTtl !== void 0) {
|
|
228
|
+
this.defaultTtl = defaultTtl;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
getLockFilePath(storageKey) {
|
|
232
|
+
const lockDir = path.join(this.baseDir, "locks");
|
|
233
|
+
if (!fs.existsSync(lockDir)) {
|
|
234
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
235
|
+
}
|
|
236
|
+
const safeName = crypto.createHash("sha256").update(storageKey).digest("hex");
|
|
237
|
+
return path.join(lockDir, `${safeName}.lock`);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* acquire 返回 true 代表成功加锁
|
|
241
|
+
* 返回 false 代表有其他进程/线程持有锁
|
|
242
|
+
*/
|
|
243
|
+
async acquire(storageKey, ttl) {
|
|
244
|
+
const lockPath = this.getLockFilePath(storageKey);
|
|
245
|
+
const effectiveTtl = ttl !== void 0 ? ttl : this.defaultTtl;
|
|
246
|
+
try {
|
|
247
|
+
try {
|
|
248
|
+
fs.writeFileSync(lockPath, "", { flag: "wx" });
|
|
249
|
+
} catch (_createErr) {
|
|
250
|
+
}
|
|
251
|
+
await lockfile.lock(lockPath, {
|
|
252
|
+
realpath: true,
|
|
253
|
+
stale: effectiveTtl,
|
|
254
|
+
// 超时自动释放
|
|
255
|
+
update: Math.floor(effectiveTtl / 3),
|
|
256
|
+
// 定期续租,避免长任务过期
|
|
257
|
+
retries: 0
|
|
258
|
+
});
|
|
259
|
+
return true;
|
|
260
|
+
} catch (_err) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 释放锁
|
|
266
|
+
*/
|
|
267
|
+
async release(storageKey) {
|
|
268
|
+
const lockPath = this.getLockFilePath(storageKey);
|
|
269
|
+
try {
|
|
270
|
+
await lockfile.unlock(lockPath);
|
|
271
|
+
} catch (_err) {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
176
276
|
var __defProp$3 = Object.defineProperty;
|
|
177
277
|
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
178
278
|
var __publicField$3 = (obj, key, value) => {
|
|
@@ -209,11 +309,11 @@ async function initSqliteWithRetry(db) {
|
|
|
209
309
|
await dbExec(
|
|
210
310
|
db,
|
|
211
311
|
`
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
`
|
|
312
|
+
PRAGMA journal_mode = WAL;
|
|
313
|
+
PRAGMA synchronous = normal;
|
|
314
|
+
PRAGMA wal_autocheckpoint = 5000;
|
|
315
|
+
PRAGMA busy_timeout = 5000;
|
|
316
|
+
`
|
|
217
317
|
);
|
|
218
318
|
await dbRun(
|
|
219
319
|
db,
|
|
@@ -225,7 +325,7 @@ async function initSqliteWithRetry(db) {
|
|
|
225
325
|
expiresAt INTEGER,
|
|
226
326
|
PRIMARY KEY (key, subKey)
|
|
227
327
|
)
|
|
228
|
-
|
|
328
|
+
`
|
|
229
329
|
);
|
|
230
330
|
} catch (err) {
|
|
231
331
|
throw new Error(`SQLite init failed: ${err}`);
|
|
@@ -238,28 +338,36 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
238
338
|
__publicField$3(this, "defaultTtl", 1e3 * 60 * 60);
|
|
239
339
|
__publicField$3(this, "cleanupInterval", 5 * 60 * 1e3);
|
|
240
340
|
__publicField$3(this, "dbPath", "");
|
|
341
|
+
__publicField$3(this, "fileLock", null);
|
|
241
342
|
}
|
|
343
|
+
/* ------------------------------- init ------------------------------- */
|
|
242
344
|
async ensure() {
|
|
243
345
|
if (!this.dbPath) {
|
|
244
346
|
this.dbPath = this.opts.sqlitePath === ":memory:" ? ":memory:" : path.resolve(this.opts.sqlitePath);
|
|
245
347
|
this.prefix = this.opts.prefix;
|
|
246
348
|
this.defaultTtl = this.opts.ttl;
|
|
247
349
|
this.cleanupInterval = this.opts.cleanupInterval ?? 30 * 60 * 1e3;
|
|
350
|
+
if (this.dbPath !== ":memory:") {
|
|
351
|
+
const baseDir = path.dirname(this.dbPath);
|
|
352
|
+
this.fileLock = new FileLock(baseDir);
|
|
353
|
+
} else {
|
|
354
|
+
const randomId = Math.random().toString(36).substring(2, 15);
|
|
355
|
+
const baseDir = path.join(os.tmpdir(), `db-cache-locks-${randomId}`);
|
|
356
|
+
this.fileLock = new FileLock(baseDir);
|
|
357
|
+
}
|
|
248
358
|
}
|
|
249
359
|
if (_SqliteAdapter.clients.has(this.dbPath))
|
|
250
360
|
return;
|
|
251
361
|
if (!_SqliteAdapter.initPromises.has(this.dbPath)) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
fs.
|
|
362
|
+
if (this.dbPath !== ":memory:") {
|
|
363
|
+
const dir = path.dirname(this.dbPath);
|
|
364
|
+
if (!fs.existsSync(dir)) {
|
|
365
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
366
|
+
}
|
|
255
367
|
}
|
|
256
368
|
_SqliteAdapter.initPromises.set(
|
|
257
369
|
this.dbPath,
|
|
258
370
|
(async () => {
|
|
259
|
-
const dbDir = path.dirname(this.dbPath);
|
|
260
|
-
if (!fs.existsSync(dbDir)) {
|
|
261
|
-
fs.mkdirSync(dbDir, { recursive: true });
|
|
262
|
-
}
|
|
263
371
|
const db = new sqlite3.Database(this.dbPath);
|
|
264
372
|
await initSqliteWithRetry(db);
|
|
265
373
|
_SqliteAdapter.clients.set(this.dbPath, db);
|
|
@@ -283,16 +391,12 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
283
391
|
}
|
|
284
392
|
return _SqliteAdapter.clients.get(this.dbPath);
|
|
285
393
|
}
|
|
394
|
+
/* ------------------------------- cleanup ------------------------------- */
|
|
286
395
|
async cleanup() {
|
|
287
396
|
const client = this.getClient();
|
|
288
|
-
await
|
|
289
|
-
client.run(
|
|
290
|
-
"DELETE FROM kvcache WHERE expiresAt IS NOT NULL AND expiresAt <= ?",
|
|
291
|
-
[Date.now()],
|
|
292
|
-
(err) => err ? rej(err) : res()
|
|
293
|
-
);
|
|
294
|
-
});
|
|
397
|
+
await dbRun(client, "DELETE FROM kvcache WHERE expiresAt IS NOT NULL AND expiresAt <= ?", [Date.now()]);
|
|
295
398
|
}
|
|
399
|
+
/* ------------------------------- core ------------------------------- */
|
|
296
400
|
prefixKey(key) {
|
|
297
401
|
return `${this.prefix}:${key}`;
|
|
298
402
|
}
|
|
@@ -303,8 +407,7 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
303
407
|
if (raw === null || raw === void 0)
|
|
304
408
|
return null;
|
|
305
409
|
try {
|
|
306
|
-
|
|
307
|
-
return obj.v;
|
|
410
|
+
return JSON.parse(raw).v;
|
|
308
411
|
} catch {
|
|
309
412
|
return null;
|
|
310
413
|
}
|
|
@@ -314,33 +417,52 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
314
417
|
const storageKey = this.prefixKey(key);
|
|
315
418
|
const effectiveTtl = opts.ttl !== void 0 ? opts.ttl : this.defaultTtl;
|
|
316
419
|
const expiresAt = effectiveTtl > 0 ? Date.now() + effectiveTtl : null;
|
|
317
|
-
|
|
318
|
-
if (opts.nx) {
|
|
319
|
-
|
|
320
|
-
if (
|
|
420
|
+
let fileLocked = false;
|
|
421
|
+
if (opts.nx && this.fileLock) {
|
|
422
|
+
fileLocked = await this.fileLock.acquire(storageKey, effectiveTtl);
|
|
423
|
+
if (!fileLocked)
|
|
321
424
|
return false;
|
|
322
425
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
426
|
+
try {
|
|
427
|
+
if (opts.nx) {
|
|
428
|
+
const existing = await dbGet(
|
|
429
|
+
client,
|
|
430
|
+
"SELECT value FROM kvcache WHERE key = ? AND subKey = ?",
|
|
431
|
+
[storageKey, ""]
|
|
432
|
+
);
|
|
433
|
+
if (existing) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
await dbRun(
|
|
438
|
+
client,
|
|
439
|
+
`
|
|
440
|
+
INSERT INTO kvcache (key, subKey, value, expiresAt)
|
|
441
|
+
VALUES (?, ?, ?, ?)
|
|
442
|
+
ON CONFLICT(key, subKey) DO UPDATE SET
|
|
443
|
+
value = excluded.value,
|
|
444
|
+
expiresAt = excluded.expiresAt
|
|
445
|
+
`,
|
|
446
|
+
[storageKey, "", this.serialize(value), expiresAt]
|
|
447
|
+
);
|
|
448
|
+
if (fileLocked && this.fileLock) {
|
|
449
|
+
await this.fileLock.release(storageKey);
|
|
450
|
+
}
|
|
451
|
+
return true;
|
|
452
|
+
} catch (err) {
|
|
453
|
+
if (fileLocked && this.fileLock) {
|
|
454
|
+
await this.fileLock.release(storageKey);
|
|
455
|
+
}
|
|
456
|
+
throw err;
|
|
457
|
+
}
|
|
335
458
|
}
|
|
336
459
|
async get(key) {
|
|
337
460
|
const client = this.getClient();
|
|
338
461
|
const storageKey = this.prefixKey(key);
|
|
339
|
-
const subKey = "";
|
|
340
462
|
const row = await dbGet(
|
|
341
463
|
client,
|
|
342
464
|
'SELECT value, "expiresAt" FROM kvcache WHERE key = ? AND subKey = ?',
|
|
343
|
-
[storageKey,
|
|
465
|
+
[storageKey, ""]
|
|
344
466
|
);
|
|
345
467
|
if (!row)
|
|
346
468
|
return null;
|
|
@@ -354,15 +476,17 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
354
476
|
const client = this.getClient();
|
|
355
477
|
const storageKey = this.prefixKey(key);
|
|
356
478
|
await dbRun(client, "DELETE FROM kvcache WHERE key = ?", [storageKey]);
|
|
479
|
+
if (this.fileLock) {
|
|
480
|
+
await this.fileLock.release(storageKey);
|
|
481
|
+
}
|
|
357
482
|
}
|
|
358
483
|
async has(key) {
|
|
359
484
|
const client = this.getClient();
|
|
360
485
|
const storageKey = this.prefixKey(key);
|
|
361
|
-
const subKey = "";
|
|
362
486
|
const row = await dbGet(
|
|
363
487
|
client,
|
|
364
488
|
'SELECT "expiresAt" FROM kvcache WHERE key = ? AND subKey = ?',
|
|
365
|
-
[storageKey,
|
|
489
|
+
[storageKey, ""]
|
|
366
490
|
);
|
|
367
491
|
if (!row)
|
|
368
492
|
return false;
|
|
@@ -372,6 +496,7 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
372
496
|
}
|
|
373
497
|
return true;
|
|
374
498
|
}
|
|
499
|
+
/* ------------------------------- group APIs ------------------------------- */
|
|
375
500
|
async groupSet(key, subKey, value, opts) {
|
|
376
501
|
const client = this.getClient();
|
|
377
502
|
const storageKey = this.prefixKey(key);
|
|
@@ -385,11 +510,11 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
385
510
|
await dbRun(
|
|
386
511
|
client,
|
|
387
512
|
`
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
513
|
+
INSERT INTO kvcache (key, subKey, value, expiresAt)
|
|
514
|
+
VALUES (?, ?, ?, ?)
|
|
515
|
+
ON CONFLICT(key, subKey) DO UPDATE SET
|
|
516
|
+
value = excluded.value,
|
|
517
|
+
expiresAt = excluded.expiresAt
|
|
393
518
|
`,
|
|
394
519
|
[storageKey, subKey, this.serialize(value), expiresAt]
|
|
395
520
|
);
|
|
@@ -429,8 +554,12 @@ const _SqliteAdapter = class _SqliteAdapter {
|
|
|
429
554
|
async groupDel(key, subKey) {
|
|
430
555
|
const client = this.getClient();
|
|
431
556
|
const storageKey = this.prefixKey(key);
|
|
432
|
-
|
|
557
|
+
await dbRun(client, "DELETE FROM kvcache WHERE key = ? AND subKey = ?", [storageKey, subKey]);
|
|
558
|
+
if (this.fileLock) {
|
|
559
|
+
await this.fileLock.release(storageKey);
|
|
560
|
+
}
|
|
433
561
|
}
|
|
562
|
+
/* ------------------------------- misc ------------------------------- */
|
|
434
563
|
async close() {
|
|
435
564
|
if (this.dbPath) {
|
|
436
565
|
const timer = _SqliteAdapter.cleanupTimers.get(this.dbPath);
|
|
@@ -710,12 +839,8 @@ class LockDBCache extends SingleFlightDBCache {
|
|
|
710
839
|
}
|
|
711
840
|
this._inFlight.set(key, Date.now());
|
|
712
841
|
try {
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
const result = await this.set(key, Date.now(), { ttl: this.defaultTtl, nx: true });
|
|
716
|
-
return result;
|
|
717
|
-
}
|
|
718
|
-
return false;
|
|
842
|
+
const result = await this.set(key, ulid(), { ttl: this.defaultTtl, nx: true });
|
|
843
|
+
return result;
|
|
719
844
|
} finally {
|
|
720
845
|
this._inFlight.delete(key);
|
|
721
846
|
for (const [key2, value] of this._inFlight.entries()) {
|
|
@@ -809,7 +934,7 @@ const isJestTest = () => {
|
|
|
809
934
|
return process.env.NODE_ENV === "test" || process.env.BABEL_ENV === "test";
|
|
810
935
|
};
|
|
811
936
|
const getAbtNodeRedisAndSQLiteUrl = () => {
|
|
812
|
-
const blockletCacheDir = process.env.BLOCKLET_DATA_DIR ?
|
|
937
|
+
const blockletCacheDir = process.env.BLOCKLET_DATA_DIR ? path$1.join(process.env.BLOCKLET_DATA_DIR, "__default-cache-store.db") : void 0;
|
|
813
938
|
const params = {
|
|
814
939
|
redisUrl: process.env.ABT_NODE_CACHE_REDIS_URL,
|
|
815
940
|
sqlitePath: isJestTest() ? ":memory:" : blockletCacheDir || process.env.ABT_NODE_CACHE_SQLITE_PATH
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abtnode/db-cache",
|
|
3
|
-
"version": "1.17.3-beta-
|
|
3
|
+
"version": "1.17.3-beta-20251119-034511-f26047c0",
|
|
4
4
|
"description": "Db cache use redis or sqlite as backend",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"author": "",
|
|
28
28
|
"license": "ISC",
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
31
|
+
"proper-lockfile": "^4.1.2",
|
|
30
32
|
"redis": "^5.1.1",
|
|
31
33
|
"sqlite3": "^5.1.7"
|
|
32
34
|
},
|
|
@@ -40,5 +42,5 @@
|
|
|
40
42
|
"typescript": "^5.6.3",
|
|
41
43
|
"unbuild": "^2.0.0"
|
|
42
44
|
},
|
|
43
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "7ab331f3b29e171a1e02aca80e73f35b6a161b86"
|
|
44
46
|
}
|