@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 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 = NORMAL;
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
- this.#setQuery?.run(key, data , ttl, stale)
564
- } catch (err) {
565
- if ((err )?.errcode === 13 /* SQLITE_FULL */) {
576
+ const startTime = performance.now()
577
+ for (let retryCount = 0; true; retryCount++) {
578
+ let n = 0
566
579
  try {
567
- this.#evictQuery?.run(256)
568
- this.#setQuery?.run(key, data , ttl, stale)
569
- } catch (evictErr) {
570
- this.#emitError(evictErr )
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.2",
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": "f0aff304c4a0a7246ea0cb34f8d8b3839dc11e81"
33
+ "gitHead": "9eb1dad9a2389d494b2e7fba2e5c149fa0872d53"
34
34
  }