@nxtedition/cache 2.1.2 → 2.1.3
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 +71 -18
- 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
|
}
|
|
@@ -521,7 +518,9 @@ export class Cache extends EventEmi
|
|
|
521
518
|
this.#dedupe.delete(key)
|
|
522
519
|
|
|
523
520
|
if (value === undefined) {
|
|
521
|
+
// Slow path... Should not be common...
|
|
524
522
|
this.#memory?.delete(key)
|
|
523
|
+
this.#setBatch = this.#setBatch.filter((item) => item.key !== key)
|
|
525
524
|
try {
|
|
526
525
|
this.#delQuery?.run(key)
|
|
527
526
|
} catch (err) {
|
|
@@ -549,7 +548,6 @@ export class Cache extends EventEmi
|
|
|
549
548
|
}
|
|
550
549
|
|
|
551
550
|
const data = this.#serializer.serialize(value)
|
|
552
|
-
|
|
553
551
|
const entry = this.#createEntry(
|
|
554
552
|
key,
|
|
555
553
|
value,
|
|
@@ -559,19 +557,74 @@ export class Cache extends EventEmi
|
|
|
559
557
|
)
|
|
560
558
|
this.#memory?.set(key, entry)
|
|
561
559
|
|
|
560
|
+
if (this.#setBatch.length === 0) {
|
|
561
|
+
this.#memory?.cork()
|
|
562
|
+
setImmediate(this.#flush)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
this.#setBatch.push({ key, data, ttl, stale })
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
#flush = () => {
|
|
569
|
+
if (this.#setBatch.length === 0 || this.#closed || this.#database == null) {
|
|
570
|
+
this.#setBatch.length = 0
|
|
571
|
+
this.#memory?.uncork()
|
|
572
|
+
return
|
|
573
|
+
}
|
|
574
|
+
|
|
562
575
|
try {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
576
|
+
const startTime = performance.now()
|
|
577
|
+
for (let retryCount = 0; true; retryCount++) {
|
|
578
|
+
let n = 0
|
|
566
579
|
try {
|
|
567
|
-
this.#
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
580
|
+
this.#database.exec('BEGIN')
|
|
581
|
+
while (n < this.#setBatch.length) {
|
|
582
|
+
const { key, data, ttl, stale } = this.#setBatch[n++]
|
|
583
|
+
if (data != null) {
|
|
584
|
+
this.#setQuery?.run(key, data, ttl, stale)
|
|
585
|
+
} else {
|
|
586
|
+
this.#delQuery?.run(key)
|
|
587
|
+
}
|
|
588
|
+
if ((n & 0xf) === 0 && performance.now() - startTime > 10) {
|
|
589
|
+
break
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
this.#database.exec('COMMIT')
|
|
593
|
+
this.#setBatch.splice(0, n)
|
|
594
|
+
break
|
|
595
|
+
} catch (err) {
|
|
596
|
+
// ROLLBACK is required: a failed statement leaves the connection with
|
|
597
|
+
// an open transaction; without it the next BEGIN would throw.
|
|
598
|
+
// On SQLITE_FULL, SQLite automatically rolls back the transaction, so
|
|
599
|
+
// the explicit ROLLBACK may fail with "no transaction is active" — ignore it.
|
|
600
|
+
try {
|
|
601
|
+
this.#database.exec('ROLLBACK')
|
|
602
|
+
} catch {
|
|
603
|
+
// already rolled back automatically
|
|
604
|
+
// TODO (fix): Check that the error is what we expect (something like "no transaction is active")...
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (
|
|
608
|
+
(err )?.errcode === 13 /* SQLITE_FULL */ &&
|
|
609
|
+
retryCount < 3 &&
|
|
610
|
+
this.#evictQuery != null
|
|
611
|
+
) {
|
|
612
|
+
this.#evictQuery.run(256)
|
|
613
|
+
} else {
|
|
614
|
+
this.#setBatch.splice(0, n)
|
|
615
|
+
throw err
|
|
616
|
+
}
|
|
571
617
|
}
|
|
572
|
-
} else {
|
|
573
|
-
this.#emitError(err )
|
|
574
618
|
}
|
|
619
|
+
} catch (err) {
|
|
620
|
+
this.#emitError(err )
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (this.#setBatch.length > 0) {
|
|
624
|
+
// If we weren't able to flush the entire batch within the time limit, schedule another flush.
|
|
625
|
+
setImmediate(this.#flush)
|
|
626
|
+
} else {
|
|
627
|
+
this.#memory?.uncork()
|
|
575
628
|
}
|
|
576
629
|
}
|
|
577
630
|
|
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.3",
|
|
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": "9eb1dad9a2389d494b2e7fba2e5c149fa0872d53"
|
|
34
34
|
}
|