@nxtedition/cache 2.1.2 → 2.1.4
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/lib/index.js +78 -23
- package/lib/memory.d.ts +2 -0
- package/lib/memory.js +14 -0
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -100,7 +100,6 @@ export class Cache extends EventEmi
|
|
|
100
100
|
#location
|
|
101
101
|
#database = null
|
|
102
102
|
#getQuery = null
|
|
103
|
-
#setQuery = null
|
|
104
103
|
#delQuery = null
|
|
105
104
|
#purgeStaleQuery = null
|
|
106
105
|
#evictQuery = null
|
|
@@ -110,6 +109,8 @@ export class Cache extends EventEmi
|
|
|
110
109
|
#lockPurgeQuery = null
|
|
111
110
|
#pageCountQuery = null
|
|
112
111
|
#pageSizeQuery = null
|
|
112
|
+
#setQuery = null
|
|
113
|
+
#setBatch = []
|
|
113
114
|
|
|
114
115
|
#emitError(err ) {
|
|
115
116
|
if (this.listenerCount('error') > 0) {
|
|
@@ -202,7 +203,8 @@ export class Cache extends EventEmi
|
|
|
202
203
|
|
|
203
204
|
this.#database.exec(`
|
|
204
205
|
PRAGMA journal_mode = WAL;
|
|
205
|
-
PRAGMA synchronous =
|
|
206
|
+
PRAGMA synchronous = OFF;
|
|
207
|
+
PRAGMA wal_autocheckpoint = 10000;
|
|
206
208
|
PRAGMA cache_size = -${Math.ceil(maxSize / 1024 / 8)};
|
|
207
209
|
PRAGMA mmap_size = ${maxSize};
|
|
208
210
|
PRAGMA max_page_count = ${Math.ceil(maxSize / 4096)};
|
|
@@ -375,6 +377,7 @@ export class Cache extends EventEmi
|
|
|
375
377
|
this.#memory?.purgeStale(Date.now())
|
|
376
378
|
this.#purgeStaleQuery?.run(Date.now())
|
|
377
379
|
this.#lockPurgeQuery?.run(Date.now() - 3_600_000)
|
|
380
|
+
this.#database?.exec('PRAGMA wal_checkpoint(TRUNCATE)')
|
|
378
381
|
this.#database?.exec('PRAGMA optimize')
|
|
379
382
|
} catch (err) {
|
|
380
383
|
this.#emitError(err )
|
|
@@ -413,12 +416,6 @@ export class Cache extends EventEmi
|
|
|
413
416
|
if (now >= cached.stale) {
|
|
414
417
|
// stale-while-revalidate has expired, purge cached value.
|
|
415
418
|
this.#memory?.delete(key)
|
|
416
|
-
try {
|
|
417
|
-
this.#delQuery?.run(key)
|
|
418
|
-
} catch {
|
|
419
|
-
// Do nothing...
|
|
420
|
-
}
|
|
421
|
-
|
|
422
419
|
cached = undefined
|
|
423
420
|
}
|
|
424
421
|
}
|
|
@@ -428,6 +425,13 @@ export class Cache extends EventEmi
|
|
|
428
425
|
return { value: cached?.value, async: false }
|
|
429
426
|
}
|
|
430
427
|
|
|
428
|
+
{
|
|
429
|
+
const pending = this.#dedupe.get(key)
|
|
430
|
+
if (pending !== undefined) {
|
|
431
|
+
return { async: true, value: pending }
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
431
435
|
let result
|
|
432
436
|
try {
|
|
433
437
|
result = this.#refresh(args, key)
|
|
@@ -451,11 +455,6 @@ export class Cache extends EventEmi
|
|
|
451
455
|
throw new TypeError('keySelector must return a non-empty string')
|
|
452
456
|
}
|
|
453
457
|
|
|
454
|
-
const existing = this.#dedupe.get(key)
|
|
455
|
-
if (existing !== undefined) {
|
|
456
|
-
return { async: true, value: existing }
|
|
457
|
-
}
|
|
458
|
-
|
|
459
458
|
if (this.#lockTimeout >= 0) {
|
|
460
459
|
// Another process holds the lock — wait for the value instead of calling valueSelector.
|
|
461
460
|
// Use deferred pattern so #waitForValue can check promise identity to detect delete+get races.
|
|
@@ -521,7 +520,9 @@ export class Cache extends EventEmi
|
|
|
521
520
|
this.#dedupe.delete(key)
|
|
522
521
|
|
|
523
522
|
if (value === undefined) {
|
|
523
|
+
// Slow path... Should not be common...
|
|
524
524
|
this.#memory?.delete(key)
|
|
525
|
+
this.#setBatch = this.#setBatch.filter((item) => item.key !== key)
|
|
525
526
|
try {
|
|
526
527
|
this.#delQuery?.run(key)
|
|
527
528
|
} catch (err) {
|
|
@@ -549,7 +550,6 @@ export class Cache extends EventEmi
|
|
|
549
550
|
}
|
|
550
551
|
|
|
551
552
|
const data = this.#serializer.serialize(value)
|
|
552
|
-
|
|
553
553
|
const entry = this.#createEntry(
|
|
554
554
|
key,
|
|
555
555
|
value,
|
|
@@ -559,19 +559,74 @@ export class Cache extends EventEmi
|
|
|
559
559
|
)
|
|
560
560
|
this.#memory?.set(key, entry)
|
|
561
561
|
|
|
562
|
+
if (this.#setBatch.length === 0) {
|
|
563
|
+
this.#memory?.cork()
|
|
564
|
+
setImmediate(this.#flush)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
this.#setBatch.push({ key, data, ttl, stale })
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
#flush = () => {
|
|
571
|
+
if (this.#setBatch.length === 0 || this.#closed || this.#database == null) {
|
|
572
|
+
this.#setBatch.length = 0
|
|
573
|
+
this.#memory?.uncork()
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
|
|
562
577
|
try {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
578
|
+
const startTime = performance.now()
|
|
579
|
+
for (let retryCount = 0; true; retryCount++) {
|
|
580
|
+
let n = 0
|
|
566
581
|
try {
|
|
567
|
-
this.#
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
582
|
+
this.#database.exec('BEGIN')
|
|
583
|
+
while (n < this.#setBatch.length) {
|
|
584
|
+
const { key, data, ttl, stale } = this.#setBatch[n++]
|
|
585
|
+
if (data != null) {
|
|
586
|
+
this.#setQuery?.run(key, data, ttl, stale)
|
|
587
|
+
} else {
|
|
588
|
+
this.#delQuery?.run(key)
|
|
589
|
+
}
|
|
590
|
+
if ((n & 0xf) === 0 && performance.now() - startTime > 10) {
|
|
591
|
+
break
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
this.#database.exec('COMMIT')
|
|
595
|
+
this.#setBatch.splice(0, n)
|
|
596
|
+
break
|
|
597
|
+
} catch (err) {
|
|
598
|
+
// ROLLBACK is required: a failed statement leaves the connection with
|
|
599
|
+
// an open transaction; without it the next BEGIN would throw.
|
|
600
|
+
// On SQLITE_FULL, SQLite automatically rolls back the transaction, so
|
|
601
|
+
// the explicit ROLLBACK may fail with "no transaction is active" — ignore it.
|
|
602
|
+
try {
|
|
603
|
+
this.#database.exec('ROLLBACK')
|
|
604
|
+
} catch {
|
|
605
|
+
// already rolled back automatically
|
|
606
|
+
// TODO (fix): Check that the error is what we expect (something like "no transaction is active")...
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (
|
|
610
|
+
(err )?.errcode === 13 /* SQLITE_FULL */ &&
|
|
611
|
+
retryCount < 3 &&
|
|
612
|
+
this.#evictQuery != null
|
|
613
|
+
) {
|
|
614
|
+
this.#evictQuery.run(256)
|
|
615
|
+
} else {
|
|
616
|
+
this.#setBatch.splice(0, n)
|
|
617
|
+
throw err
|
|
618
|
+
}
|
|
571
619
|
}
|
|
572
|
-
} else {
|
|
573
|
-
this.#emitError(err )
|
|
574
620
|
}
|
|
621
|
+
} catch (err) {
|
|
622
|
+
this.#emitError(err )
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (this.#setBatch.length > 0) {
|
|
626
|
+
// If we weren't able to flush the entire batch within the time limit, schedule another flush.
|
|
627
|
+
setImmediate(this.#flush)
|
|
628
|
+
} else {
|
|
629
|
+
this.#memory?.uncork()
|
|
575
630
|
}
|
|
576
631
|
}
|
|
577
632
|
|
package/lib/memory.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare class MemoryCache<V> {
|
|
|
15
15
|
#private;
|
|
16
16
|
constructor(opts?: MemoryOptions);
|
|
17
17
|
set(key: string, entry: MemoryCacheEntry<V>): void;
|
|
18
|
+
cork(): void;
|
|
19
|
+
uncork(): void;
|
|
18
20
|
get(key: string): MemoryCacheEntry<V> | undefined;
|
|
19
21
|
delete(key: string): void;
|
|
20
22
|
purgeStale(now: number): void;
|
package/lib/memory.js
CHANGED
|
@@ -26,6 +26,7 @@ export class MemoryCache {
|
|
|
26
26
|
#count = 0
|
|
27
27
|
|
|
28
28
|
#counter = 0
|
|
29
|
+
#cork = 0
|
|
29
30
|
|
|
30
31
|
constructor(opts ) {
|
|
31
32
|
if (opts?.maxSize != null && (!Number.isInteger(opts.maxSize) || opts.maxSize < 1)) {
|
|
@@ -59,6 +60,19 @@ export class MemoryCache {
|
|
|
59
60
|
this.#prune()
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
cork() {
|
|
64
|
+
this.#cork += 1
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
uncork() {
|
|
68
|
+
if (this.#cork > 0) {
|
|
69
|
+
this.#cork -= 1
|
|
70
|
+
if (this.#cork === 0) {
|
|
71
|
+
this.#prune()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
62
76
|
get(key ) {
|
|
63
77
|
const entry = this.#map.get(key)
|
|
64
78
|
if (entry != null) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/cache",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"tsd": "^0.33.0",
|
|
31
31
|
"typescript": "^5.9.3"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "f7c04358e008a7b0f28e2ca76a81ebdc07fbd734"
|
|
34
34
|
}
|