@abtnode/db-cache 1.16.45-beta-20250609-025419-7fd1f86c

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.mjs ADDED
@@ -0,0 +1,811 @@
1
+ import { createClient } from 'redis';
2
+ import sqlite3 from 'sqlite3';
3
+ import * as path from 'path';
4
+ import * as fs from 'fs';
5
+
6
+ var __defProp$4 = Object.defineProperty;
7
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __publicField$4 = (obj, key, value) => {
9
+ __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
10
+ return value;
11
+ };
12
+ const _RedisAdapter = class _RedisAdapter {
13
+ constructor() {
14
+ __publicField$4(this, "opts", null);
15
+ __publicField$4(this, "defaultTtl", 1e3 * 60 * 60);
16
+ __publicField$4(this, "url", "");
17
+ __publicField$4(this, "prefix", "");
18
+ __publicField$4(this, "prefixKey", (key) => `${this.prefix}:${key}`);
19
+ __publicField$4(this, "prefixKeyGroup", (key) => `${this.prefix}:group:${key}`);
20
+ }
21
+ clearAll() {
22
+ throw new Error("Method not implemented.");
23
+ }
24
+ async ensure() {
25
+ if (!this.url) {
26
+ this.url = this.opts.redisUrl || "";
27
+ this.prefix = this.opts.prefix;
28
+ this.defaultTtl = this.opts.ttl;
29
+ }
30
+ if (_RedisAdapter.clients.has(this.url)) {
31
+ return;
32
+ }
33
+ if (!_RedisAdapter.initPromises.has(this.url)) {
34
+ _RedisAdapter.initPromises.set(
35
+ this.url,
36
+ (async () => {
37
+ const cli = createClient({ url: this.url });
38
+ cli.on("error", console.error);
39
+ await cli.connect();
40
+ _RedisAdapter.clients.set(this.url, cli);
41
+ })()
42
+ );
43
+ }
44
+ await _RedisAdapter.initPromises.get(this.url);
45
+ }
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ getClient() {
48
+ if (!this.url || !_RedisAdapter.clients.has(this.url)) {
49
+ throw new Error("Redis not initialized");
50
+ }
51
+ return _RedisAdapter.clients.get(this.url);
52
+ }
53
+ serialize(value) {
54
+ return JSON.stringify({ v: value });
55
+ }
56
+ deserialize(raw) {
57
+ if (raw === null || raw === void 0)
58
+ return null;
59
+ try {
60
+ const obj = JSON.parse(raw);
61
+ return obj.v;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ async set(key, value, { ttl, nx }) {
67
+ const client = this.getClient();
68
+ const storageKey = this.prefixKey(key);
69
+ if (ttl && ttl > 0) {
70
+ await client.set(storageKey, this.serialize(value), { PX: ttl, NX: nx });
71
+ } else {
72
+ await client.set(storageKey, this.serialize(value), { NX: nx });
73
+ }
74
+ return true;
75
+ }
76
+ async get(key) {
77
+ const client = this.getClient();
78
+ const storageKey = this.prefixKey(key);
79
+ const raw = await client.get(storageKey);
80
+ return this.deserialize(raw);
81
+ }
82
+ async del(key) {
83
+ const client = this.getClient();
84
+ const storageKey = this.prefixKey(key);
85
+ const storageKeyGroup = this.prefixKeyGroup(key);
86
+ await client.del(storageKey);
87
+ await client.del(storageKeyGroup);
88
+ }
89
+ async has(key) {
90
+ const client = this.getClient();
91
+ const storageKey = this.prefixKey(key);
92
+ const exists = await client.exists(storageKey);
93
+ if (exists === 0)
94
+ return false;
95
+ const raw = await client.get(storageKey);
96
+ if (!raw)
97
+ return false;
98
+ const data = JSON.parse(raw);
99
+ if (data.e && Date.now() > data.e) {
100
+ await this.del(key);
101
+ return false;
102
+ }
103
+ return true;
104
+ }
105
+ async groupSet(key, subKey, value, { ttl }) {
106
+ const client = this.getClient();
107
+ const storageKey = this.prefixKeyGroup(key);
108
+ await client.hSet(storageKey, subKey, this.serialize(value));
109
+ const effectiveTtl = ttl !== void 0 ? ttl : this.defaultTtl;
110
+ if (effectiveTtl > 0) {
111
+ await client.expire(storageKey, Math.ceil(effectiveTtl / 1e3));
112
+ }
113
+ }
114
+ async groupGet(key, subKey) {
115
+ const client = this.getClient();
116
+ const storageKey = this.prefixKeyGroup(key);
117
+ const raw = await client.hGet(storageKey, subKey);
118
+ return this.deserialize(raw);
119
+ }
120
+ async groupHas(key, subKey) {
121
+ const client = this.getClient();
122
+ const storageKey = this.prefixKeyGroup(key);
123
+ return await client.hExists(storageKey, subKey) === 1;
124
+ }
125
+ async groupDel(key, subKey) {
126
+ const client = this.getClient();
127
+ const storageKey = this.prefixKeyGroup(key);
128
+ await client.hDel(storageKey, subKey);
129
+ }
130
+ async close() {
131
+ if (this.url && _RedisAdapter.clients.has(this.url)) {
132
+ const client = _RedisAdapter.clients.get(this.url);
133
+ await client.quit();
134
+ _RedisAdapter.clients.delete(this.url);
135
+ _RedisAdapter.initPromises.delete(this.url);
136
+ }
137
+ }
138
+ async flushAll() {
139
+ const client = this.getClient();
140
+ await client.flushAll();
141
+ }
142
+ };
143
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
144
+ __publicField$4(_RedisAdapter, "clients", /* @__PURE__ */ new Map());
145
+ __publicField$4(_RedisAdapter, "initPromises", /* @__PURE__ */ new Map());
146
+ let RedisAdapter = _RedisAdapter;
147
+
148
+ var __defProp$3 = Object.defineProperty;
149
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
150
+ var __publicField$3 = (obj, key, value) => {
151
+ __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
152
+ return value;
153
+ };
154
+ const _SqliteAdapter = class _SqliteAdapter {
155
+ constructor() {
156
+ __publicField$3(this, "opts", null);
157
+ __publicField$3(this, "prefix", "");
158
+ __publicField$3(this, "defaultTtl", 1e3 * 60 * 60);
159
+ __publicField$3(this, "cleanupInterval", 5 * 60 * 1e3);
160
+ __publicField$3(this, "dbPath", "");
161
+ }
162
+ async ensure() {
163
+ if (!this.dbPath) {
164
+ this.dbPath = path.resolve(this.opts.sqlitePath);
165
+ this.prefix = this.opts.prefix;
166
+ this.defaultTtl = this.opts.ttl;
167
+ this.cleanupInterval = this.opts.cleanupInterval ?? 30 * 60 * 1e3;
168
+ }
169
+ if (_SqliteAdapter.clients.has(this.dbPath))
170
+ return;
171
+ if (!_SqliteAdapter.initPromises.has(this.dbPath)) {
172
+ const dir = path.dirname(this.dbPath);
173
+ if (!fs.existsSync(dir)) {
174
+ fs.mkdirSync(dir, { recursive: true });
175
+ }
176
+ _SqliteAdapter.initPromises.set(
177
+ this.dbPath,
178
+ (async () => {
179
+ const dbDir = path.dirname(this.dbPath);
180
+ if (!fs.existsSync(dbDir)) {
181
+ fs.mkdirSync(dbDir, { recursive: true });
182
+ }
183
+ const db = new sqlite3.Database(this.dbPath);
184
+ await new Promise(
185
+ (res) => db.exec(
186
+ `
187
+ PRAGMA journal_mode = WAL;
188
+ PRAGMA synchronous = OFF;
189
+ PRAGMA busy_timeout = 20000;
190
+ PRAGMA wal_autocheckpoint = 2000;
191
+ `,
192
+ (err) => {
193
+ if (err)
194
+ console.error("SQLite init error:", err);
195
+ res();
196
+ }
197
+ )
198
+ );
199
+ await new Promise(
200
+ (res, rej) => db.run(
201
+ `
202
+ CREATE TABLE IF NOT EXISTS kvcache (
203
+ key TEXT NOT NULL,
204
+ subKey TEXT NOT NULL,
205
+ value TEXT NOT NULL,
206
+ expiresAt INTEGER,
207
+ PRIMARY KEY (key, subKey)
208
+ )
209
+ `,
210
+ (err) => err ? rej(err) : res()
211
+ )
212
+ );
213
+ await new Promise(
214
+ (res, rej) => db.run(
215
+ `
216
+ PRAGMA shrink_memory;
217
+ `,
218
+ (err) => err ? rej(err) : res()
219
+ )
220
+ );
221
+ _SqliteAdapter.clients.set(this.dbPath, db);
222
+ _SqliteAdapter.cleanupTimers.set(
223
+ this.dbPath,
224
+ setInterval(
225
+ () => {
226
+ this.cleanup().catch(console.error);
227
+ },
228
+ this.cleanupInterval + Math.random() * 3e4
229
+ )
230
+ );
231
+ })()
232
+ );
233
+ }
234
+ await _SqliteAdapter.initPromises.get(this.dbPath);
235
+ }
236
+ getClient() {
237
+ if (!this.dbPath || !_SqliteAdapter.clients.has(this.dbPath)) {
238
+ throw new Error("SQLite not initialized");
239
+ }
240
+ return _SqliteAdapter.clients.get(this.dbPath);
241
+ }
242
+ async cleanup() {
243
+ const client = this.getClient();
244
+ await new Promise((res, rej) => {
245
+ client.run(
246
+ "DELETE FROM kvcache WHERE expiresAt IS NOT NULL AND expiresAt <= ?",
247
+ [Date.now()],
248
+ (err) => err ? rej(err) : res()
249
+ );
250
+ });
251
+ }
252
+ prefixKey(key) {
253
+ return `${this.prefix}:${key}`;
254
+ }
255
+ serialize(value) {
256
+ return JSON.stringify({ v: value });
257
+ }
258
+ deserialize(raw) {
259
+ if (raw === null || raw === void 0)
260
+ return null;
261
+ try {
262
+ const obj = JSON.parse(raw);
263
+ return obj.v;
264
+ } catch {
265
+ return null;
266
+ }
267
+ }
268
+ async set(key, value, opts) {
269
+ const client = this.getClient();
270
+ const storageKey = this.prefixKey(key);
271
+ const effectiveTtl = opts.ttl !== void 0 ? opts.ttl : this.defaultTtl;
272
+ const expiresAt = effectiveTtl > 0 ? Date.now() + effectiveTtl : null;
273
+ const subKey = "";
274
+ if (opts.nx) {
275
+ const exists = await this.has(key);
276
+ if (exists)
277
+ return false;
278
+ }
279
+ await new Promise((res, rej) => {
280
+ client.run(
281
+ `
282
+ INSERT INTO kvcache (key, subKey, value, expiresAt)
283
+ VALUES (?, ?, ?, ?)
284
+ ON CONFLICT(key, subKey) DO UPDATE SET
285
+ value = excluded.value,
286
+ expiresAt = excluded.expiresAt
287
+ `,
288
+ [storageKey, subKey, this.serialize(value), expiresAt],
289
+ (err) => err ? rej(err) : res()
290
+ );
291
+ });
292
+ return true;
293
+ }
294
+ async get(key) {
295
+ const client = this.getClient();
296
+ const storageKey = this.prefixKey(key);
297
+ const subKey = "";
298
+ const row = await new Promise(
299
+ (res, rej) => {
300
+ client.get(
301
+ "SELECT value, expiresAt FROM kvcache WHERE key = ? AND subKey = ?",
302
+ [storageKey, subKey],
303
+ (err, result) => {
304
+ if (err)
305
+ return rej(err);
306
+ return res(result);
307
+ }
308
+ );
309
+ }
310
+ );
311
+ if (!row)
312
+ return null;
313
+ if (row.expiresAt !== null && Date.now() > row.expiresAt) {
314
+ await this.del(key);
315
+ return null;
316
+ }
317
+ return this.deserialize(row.value);
318
+ }
319
+ async del(key) {
320
+ const client = this.getClient();
321
+ const storageKey = this.prefixKey(key);
322
+ await new Promise((res, rej) => {
323
+ client.run(
324
+ "DELETE FROM kvcache WHERE key = ?",
325
+ [storageKey],
326
+ (err) => err ? rej(err) : res()
327
+ );
328
+ });
329
+ }
330
+ async has(key) {
331
+ const client = this.getClient();
332
+ const storageKey = this.prefixKey(key);
333
+ const subKey = "";
334
+ const row = await new Promise((res, rej) => {
335
+ client.get(
336
+ "SELECT expiresAt FROM kvcache WHERE key = ? AND subKey = ?",
337
+ [storageKey, subKey],
338
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
339
+ (err, result) => err ? rej(err) : res(result)
340
+ );
341
+ });
342
+ if (!row)
343
+ return false;
344
+ if (row.expiresAt !== null && Date.now() > row.expiresAt) {
345
+ await this.del(key);
346
+ return false;
347
+ }
348
+ return true;
349
+ }
350
+ async groupSet(key, subKey, value, opts) {
351
+ const client = this.getClient();
352
+ const storageKey = this.prefixKey(key);
353
+ const effectiveTtl = opts.ttl !== void 0 ? opts.ttl : this.defaultTtl;
354
+ const expiresAt = effectiveTtl > 0 ? Date.now() + effectiveTtl : null;
355
+ if (opts.nx) {
356
+ const exists = await this.groupHas(key, subKey);
357
+ if (exists)
358
+ return;
359
+ }
360
+ await new Promise((res, rej) => {
361
+ client.run(
362
+ `
363
+ INSERT INTO kvcache (key, subKey, value, expiresAt)
364
+ VALUES (?, ?, ?, ?)
365
+ ON CONFLICT(key, subKey) DO UPDATE SET
366
+ value = excluded.value,
367
+ expiresAt = excluded.expiresAt
368
+ `,
369
+ [storageKey, subKey, this.serialize(value), expiresAt],
370
+ (err) => err ? rej(err) : res()
371
+ );
372
+ });
373
+ }
374
+ async groupGet(key, subKey) {
375
+ const client = this.getClient();
376
+ const storageKey = this.prefixKey(key);
377
+ const row = await new Promise(
378
+ (res, rej) => {
379
+ client.get(
380
+ "SELECT value, expiresAt FROM kvcache WHERE key = ? AND subKey = ?",
381
+ [storageKey, subKey],
382
+ (err, result) => {
383
+ if (err)
384
+ return rej(err);
385
+ return res(result);
386
+ }
387
+ );
388
+ }
389
+ );
390
+ if (!row)
391
+ return null;
392
+ if (row.expiresAt !== null && Date.now() > row.expiresAt) {
393
+ await this.groupDel(key, subKey);
394
+ return null;
395
+ }
396
+ return this.deserialize(row.value);
397
+ }
398
+ async groupHas(key, subKey) {
399
+ const client = this.getClient();
400
+ const storageKey = this.prefixKey(key);
401
+ const row = await new Promise((res, rej) => {
402
+ client.get(
403
+ "SELECT expiresAt FROM kvcache WHERE key = ? AND subKey = ?",
404
+ [storageKey, subKey],
405
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
406
+ (err, result) => err ? rej(err) : res(result)
407
+ );
408
+ });
409
+ if (!row)
410
+ return false;
411
+ if (row.expiresAt !== null && Date.now() > row.expiresAt) {
412
+ await this.groupDel(key, subKey);
413
+ return false;
414
+ }
415
+ return true;
416
+ }
417
+ async groupDel(key, subKey) {
418
+ const client = this.getClient();
419
+ const storageKey = this.prefixKey(key);
420
+ await new Promise((res, rej) => {
421
+ client.run(
422
+ "DELETE FROM kvcache WHERE key = ? AND subKey = ?",
423
+ [storageKey, subKey],
424
+ (err) => err ? rej(err) : res()
425
+ );
426
+ });
427
+ }
428
+ async close() {
429
+ if (this.dbPath) {
430
+ const timer = _SqliteAdapter.cleanupTimers.get(this.dbPath);
431
+ if (timer) {
432
+ clearInterval(timer);
433
+ _SqliteAdapter.cleanupTimers.delete(this.dbPath);
434
+ }
435
+ const client = _SqliteAdapter.clients.get(this.dbPath);
436
+ if (client) {
437
+ client.close();
438
+ _SqliteAdapter.clients.delete(this.dbPath);
439
+ _SqliteAdapter.initPromises.delete(this.dbPath);
440
+ }
441
+ }
442
+ }
443
+ async flushAll() {
444
+ const client = this.getClient();
445
+ await client.run("DELETE FROM kvcache");
446
+ }
447
+ };
448
+ __publicField$3(_SqliteAdapter, "clients", /* @__PURE__ */ new Map());
449
+ __publicField$3(_SqliteAdapter, "initPromises", /* @__PURE__ */ new Map());
450
+ __publicField$3(_SqliteAdapter, "cleanupTimers", /* @__PURE__ */ new Map());
451
+ let SqliteAdapter = _SqliteAdapter;
452
+
453
+ var __defProp$2 = Object.defineProperty;
454
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
455
+ var __publicField$2 = (obj, key, value) => {
456
+ __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
457
+ return value;
458
+ };
459
+ class BaseDBCache {
460
+ constructor(opts) {
461
+ __publicField$2(this, "adapter", null);
462
+ __publicField$2(this, "prefix", "");
463
+ __publicField$2(this, "type", "");
464
+ __publicField$2(this, "defaultTtl", 0);
465
+ __publicField$2(this, "getOpts");
466
+ __publicField$2(this, "_initdAdapter", false);
467
+ this.getOpts = opts;
468
+ }
469
+ initAdapter() {
470
+ if (this._initdAdapter) {
471
+ return;
472
+ }
473
+ if (typeof this.getOpts !== "function") {
474
+ throw new Error("getOpts must be a function");
475
+ }
476
+ const opts = this.getOpts();
477
+ if (!opts.prefix || typeof opts.prefix !== "string" || opts.prefix.trim() === "") {
478
+ throw new Error("prefix is required");
479
+ }
480
+ if (!opts.sqlitePath || typeof opts.sqlitePath !== "string") {
481
+ throw new Error("sqlitePath is required");
482
+ }
483
+ if (typeof opts.ttl !== "number" || opts.ttl < 0) {
484
+ throw new Error("ttl must be a non-negative number");
485
+ }
486
+ if (opts.cleanupInterval && opts.cleanupInterval < 1e3 * 60 * 5) {
487
+ console.error("cleanupInterval must be at least 5 minutes");
488
+ throw new Error("cleanupInterval must be at least 5 minutes");
489
+ }
490
+ this.type = opts.forceType || (opts.redisUrl ? "redis" : "sqlite");
491
+ this.prefix = opts.prefix;
492
+ this.defaultTtl = opts.ttl;
493
+ switch (this.type) {
494
+ case "redis":
495
+ this.adapter = new RedisAdapter();
496
+ this.adapter.opts = opts;
497
+ break;
498
+ case "sqlite":
499
+ this.adapter = new SqliteAdapter();
500
+ this.adapter.opts = opts;
501
+ break;
502
+ default:
503
+ throw new Error(`Unsupported backend: ${this.type}`);
504
+ }
505
+ this._initdAdapter = true;
506
+ }
507
+ static randomKey() {
508
+ return Math.random().toString(36).substring(2, 15);
509
+ }
510
+ async set(key, value, opts = {}) {
511
+ this.initAdapter();
512
+ if (typeof key !== "string") {
513
+ console.error("key must be a string");
514
+ return Promise.resolve(false);
515
+ }
516
+ const effectiveTtl = opts.ttl !== void 0 ? opts.ttl : this.defaultTtl;
517
+ if (typeof effectiveTtl !== "number" || effectiveTtl < 0) {
518
+ console.error("ttl must be a non-negative number");
519
+ throw new Error("ttl must be a non-negative number");
520
+ }
521
+ await this.adapter.ensure();
522
+ return this.adapter.set(key, value, { ttl: effectiveTtl, nx: opts.nx });
523
+ }
524
+ async get(key) {
525
+ this.initAdapter();
526
+ if (typeof key !== "string") {
527
+ console.error("key must be a string");
528
+ return Promise.resolve();
529
+ }
530
+ await this.adapter.ensure();
531
+ return this.adapter.get(key);
532
+ }
533
+ async del(key) {
534
+ this.initAdapter();
535
+ if (typeof key !== "string") {
536
+ console.error("key must be a string");
537
+ return Promise.resolve();
538
+ }
539
+ await this.adapter.ensure();
540
+ return this.adapter.del(key);
541
+ }
542
+ async has(key) {
543
+ this.initAdapter();
544
+ if (typeof key !== "string") {
545
+ console.error("key must be a string");
546
+ return Promise.resolve(false);
547
+ }
548
+ await this.adapter.ensure();
549
+ return this.adapter.has(key);
550
+ }
551
+ async groupSet(key, subKey, value, opts = {}) {
552
+ this.initAdapter();
553
+ if (typeof key !== "string" || typeof subKey !== "string") {
554
+ return Promise.resolve();
555
+ }
556
+ const effectiveTtl = opts.ttl !== void 0 ? opts.ttl : this.defaultTtl;
557
+ if (typeof effectiveTtl !== "number" || effectiveTtl < 0) {
558
+ console.error("ttl must be a non-negative number");
559
+ throw new Error("ttl must be a non-negative number");
560
+ }
561
+ await this.adapter.ensure();
562
+ return this.adapter.groupSet(key, subKey, value, { ttl: effectiveTtl, nx: opts.nx });
563
+ }
564
+ async groupGet(key, subKey) {
565
+ this.initAdapter();
566
+ if (typeof key !== "string" || typeof subKey !== "string") {
567
+ console.error("key must be a string");
568
+ return null;
569
+ }
570
+ await this.adapter.ensure();
571
+ return this.adapter.groupGet(key, subKey);
572
+ }
573
+ async groupHas(key, subKey) {
574
+ this.initAdapter();
575
+ if (typeof key !== "string") {
576
+ throw new Error("key must be a string");
577
+ }
578
+ if (typeof subKey !== "string") {
579
+ throw new Error("subKey must be a string");
580
+ }
581
+ await this.adapter.ensure();
582
+ return this.adapter.groupHas(key, subKey);
583
+ }
584
+ async groupDel(key, subKey) {
585
+ this.initAdapter();
586
+ if (!key || !subKey) {
587
+ return Promise.resolve();
588
+ }
589
+ await this.adapter.ensure();
590
+ return this.adapter.groupDel(key, subKey);
591
+ }
592
+ async close() {
593
+ this.initAdapter();
594
+ await this.adapter.ensure();
595
+ return this.adapter.close();
596
+ }
597
+ async flushAll() {
598
+ this.initAdapter();
599
+ await this.adapter.ensure();
600
+ return this.adapter.flushAll();
601
+ }
602
+ }
603
+
604
+ var __defProp$1 = Object.defineProperty;
605
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
606
+ var __publicField$1 = (obj, key, value) => {
607
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
608
+ return value;
609
+ };
610
+ class SingleFlightDBCache extends BaseDBCache {
611
+ constructor(opts) {
612
+ super(opts);
613
+ __publicField$1(this, "_pending");
614
+ this._pending = /* @__PURE__ */ new Map();
615
+ }
616
+ /**
617
+ * If a request for the same key is already in flight, reuse it.
618
+ * Otherwise, fetch, cache, and return the value.
619
+ */
620
+ autoCache(key, fn, { ttl } = {}) {
621
+ this.initAdapter();
622
+ if (!key) {
623
+ return fn();
624
+ }
625
+ if (typeof key !== "string") {
626
+ throw new Error("key must be a string");
627
+ }
628
+ if (this._pending.has(key)) {
629
+ return this._pending.get(key);
630
+ }
631
+ const task = (async () => {
632
+ try {
633
+ const hit = await super.get(key);
634
+ if (hit !== null) {
635
+ return hit;
636
+ }
637
+ const result = await fn();
638
+ await super.set(key, result, { ttl });
639
+ return result;
640
+ } finally {
641
+ this._pending.delete(key);
642
+ }
643
+ })();
644
+ this._pending.set(key, task);
645
+ return task;
646
+ }
647
+ /**
648
+ * Same logic as autoCache, but for group fields (hash).
649
+ */
650
+ autoCacheGroup(key, subKey, fn, { ttl } = {}) {
651
+ this.initAdapter();
652
+ if (!key || !subKey) {
653
+ return fn();
654
+ }
655
+ if (typeof key !== "string") {
656
+ throw new Error("key must be a string");
657
+ }
658
+ if (typeof subKey !== "string") {
659
+ throw new Error("subKey must be a string");
660
+ }
661
+ const pendingKey = `${key}-${subKey}`;
662
+ if (this._pending.has(pendingKey)) {
663
+ return this._pending.get(pendingKey);
664
+ }
665
+ const task = (async () => {
666
+ try {
667
+ const hit = await super.groupGet(key, subKey);
668
+ if (hit !== null) {
669
+ return hit;
670
+ }
671
+ const result = await fn();
672
+ await super.groupSet(key, subKey, result, { ttl });
673
+ return result;
674
+ } finally {
675
+ this._pending.delete(pendingKey);
676
+ }
677
+ })();
678
+ this._pending.set(pendingKey, task);
679
+ return task;
680
+ }
681
+ }
682
+
683
+ var __defProp = Object.defineProperty;
684
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
685
+ var __publicField = (obj, key, value) => {
686
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
687
+ return value;
688
+ };
689
+ class LockDBCache extends SingleFlightDBCache {
690
+ constructor() {
691
+ super(...arguments);
692
+ __publicField(this, "_inFlight", /* @__PURE__ */ new Map());
693
+ }
694
+ async createLock(lockName) {
695
+ this.initAdapter();
696
+ if (!lockName) {
697
+ return false;
698
+ }
699
+ const key = this._formatLockKey(lockName);
700
+ const lastLockTime = this._inFlight.get(key);
701
+ if (lastLockTime && Date.now() - lastLockTime < this.defaultTtl) {
702
+ return false;
703
+ }
704
+ this._inFlight.set(key, Date.now());
705
+ try {
706
+ const exists = await this.has(key);
707
+ if (!exists) {
708
+ const result = await this.set(key, Date.now(), { ttl: this.defaultTtl, nx: true });
709
+ return result;
710
+ }
711
+ return false;
712
+ } finally {
713
+ this._inFlight.delete(key);
714
+ for (const [key2, value] of this._inFlight.entries()) {
715
+ if (Date.now() - value > this.defaultTtl) {
716
+ this._inFlight.delete(key2);
717
+ }
718
+ }
719
+ }
720
+ }
721
+ /**
722
+ * 检查锁是否已经过期:只要缓存层面已经没有该 key,就代表已过期或从未创建。
723
+ *
724
+ * @param lockName 锁的名称
725
+ * @returns 如果锁不存在或已过期,返回 true;否则返回 false
726
+ */
727
+ async isExpired(lockName) {
728
+ this.initAdapter();
729
+ if (!lockName) {
730
+ return true;
731
+ }
732
+ const key = this._formatLockKey(lockName);
733
+ const exists = await this.has(key);
734
+ return !exists;
735
+ }
736
+ /**
737
+ * 主动释放锁:删除对应的缓存 key
738
+ *
739
+ * @param lockName 锁的名称
740
+ */
741
+ async releaseLock(lockName) {
742
+ this.initAdapter();
743
+ if (!lockName) {
744
+ return;
745
+ }
746
+ const key = this._formatLockKey(lockName);
747
+ await this.del(key);
748
+ }
749
+ /**
750
+ * 等待锁释放或超时。每隔 100ms 检查一次缓存层面是否仍存在该锁 key。
751
+ * 如果在 timeoutMs 毫秒内 key 不再存在,则 resolve(true)。否则 resolve(false)。
752
+ *
753
+ * @param lockName 锁的名称
754
+ * @param timeoutMs 等待超时时间(毫秒),默认使用构造时传入的 this._timeout
755
+ * @returns 如果锁被释放或过期,resolve(true);等待到超时还没释放,则 resolve(false)
756
+ */
757
+ // eslint-disable-next-line require-await
758
+ async waitUnLock(lockName, timeoutMs = this.defaultTtl) {
759
+ this.initAdapter();
760
+ if (!lockName) {
761
+ return true;
762
+ }
763
+ const key = this._formatLockKey(lockName);
764
+ const start = Date.now();
765
+ return new Promise((resolve) => {
766
+ const interval = setInterval(async () => {
767
+ const exists = await this.has(key);
768
+ if (!exists) {
769
+ clearInterval(interval);
770
+ resolve(true);
771
+ } else if (Date.now() - start >= timeoutMs) {
772
+ clearInterval(interval);
773
+ resolve(false);
774
+ }
775
+ }, 100);
776
+ });
777
+ }
778
+ /**
779
+ * 获取锁的流程:
780
+ * 1. 先尝试 createLock(lockName):
781
+ * - 如果立刻拿到(createLock 返回 true),直接返回;
782
+ * - 如果没拿到,就等待 waitUnLock(lockName)。
783
+ * 2. 等待到释放/过期后,再次尝试 createLock(lockName)。
784
+ *
785
+ * @param lockName 锁的名称
786
+ */
787
+ async acquire(lockName) {
788
+ while (true) {
789
+ const acquired = await this.createLock(lockName);
790
+ if (acquired) {
791
+ return;
792
+ }
793
+ await this.waitUnLock(lockName);
794
+ }
795
+ }
796
+ _formatLockKey(lockName) {
797
+ return `lock:${this.prefix}:${lockName}`;
798
+ }
799
+ }
800
+
801
+ const isJestTest = () => {
802
+ return process.env.NODE_ENV === "test" || process.env.BABEL_ENV === "test" || typeof jest !== "undefined";
803
+ };
804
+ const getAbtNodeRedisAndSQLiteUrl = () => {
805
+ return {
806
+ redisUrl: process.env.ABT_NODE_CACHE_REDIS_URL,
807
+ sqlitePath: isJestTest() ? "test.db" : process.env.ABT_NODE_CACHE_SQLITE_PATH
808
+ };
809
+ };
810
+
811
+ export { LockDBCache as DBCache, getAbtNodeRedisAndSQLiteUrl };