@nxtedition/cache 2.0.7 → 2.1.1

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.d.ts CHANGED
@@ -5,6 +5,9 @@ export interface DatabaseOptions {
5
5
  timeout?: number;
6
6
  maxSize?: number;
7
7
  }
8
+ export interface LockOptions {
9
+ minTimeout?: number;
10
+ }
8
11
  export interface Serializer<V> {
9
12
  serialize: (value: V) => Buffer | Uint8Array | string;
10
13
  deserialize: (data: Buffer | string) => V;
@@ -12,6 +15,7 @@ export interface Serializer<V> {
12
15
  export interface CacheOptions<V> {
13
16
  ttl?: number | ((value: V, key: string) => number);
14
17
  stale?: number | ((value: V, key: string) => number);
18
+ lock?: LockOptions | false | null;
15
19
  memory?: MemoryOptions | false | null;
16
20
  database?: DatabaseOptions | false | null;
17
21
  serializer?: Serializer<V>;
@@ -28,15 +32,15 @@ declare global {
28
32
  stats: Cache['stats'];
29
33
  }[];
30
34
  }
31
- export declare class Cache<V = unknown, A extends unknown[] = unknown[]> extends EventEmitter {
35
+ export declare class Cache<V = unknown, A extends unknown[] = [string]> extends EventEmitter {
32
36
  #private;
33
- constructor(location: string, valueSelector: (...args: A) => V | PromiseLike<V>, keySelector: (...args: A) => string, opts?: CacheOptions<V>);
37
+ constructor(location: string, valueSelector?: (...args: A) => V | PromiseLike<V>, keySelector?: (...args: A) => string, opts?: CacheOptions<V>);
34
38
  get stats(): {
35
39
  lock: {
36
40
  timeout: number;
37
41
  mean: number;
38
42
  stddev: number;
39
- };
43
+ } | undefined;
40
44
  dedupe: {
41
45
  size: number;
42
46
  };
package/lib/index.js CHANGED
@@ -36,6 +36,10 @@ const dbs = new Set ()
36
36
 
37
37
 
38
38
 
39
+
40
+
41
+
42
+
39
43
 
40
44
 
41
45
 
@@ -49,6 +53,7 @@ const defaultSerializer = {
49
53
  return ArrayBuffer.isView(value) ? (value ) : JSON.stringify(value)
50
54
  },
51
55
  deserialize(data) {
56
+ // eslint-disable-next-line typescript-eslint/no-unsafe-argument
52
57
  return ArrayBuffer.isView(data) ? data : JSON.parse(data)
53
58
  },
54
59
  }
@@ -56,6 +61,7 @@ const defaultSerializer = {
56
61
 
57
62
 
58
63
 
64
+
59
65
 
60
66
 
61
67
 
@@ -73,7 +79,7 @@ const defaultSerializer = {
73
79
  const VERSION = 5
74
80
  const MAX_DURATION = 365000000e3
75
81
 
76
- export class Cache extends EventEmitter {
82
+ export class Cache extends EventEmitter {
77
83
  #memory
78
84
  #dedupe = new Map ()
79
85
  #closed = false
@@ -85,10 +91,11 @@ export class Cache extends EventEm
85
91
  #stale
86
92
  #serializer
87
93
 
88
- #lockMean = 5
89
- #lockVar = 0
90
- #lockTimeout = 10
91
94
  #lockId = randomUUID()
95
+ #lockVar = 0
96
+ #lockMean = 5
97
+ #lockTimeout = 10
98
+ #lockMinTimeout = 1
92
99
 
93
100
  #location
94
101
  #database = null
@@ -114,8 +121,8 @@ export class Cache extends EventEm
114
121
 
115
122
  constructor(
116
123
  location ,
117
- valueSelector ,
118
- keySelector ,
124
+ valueSelector ,
125
+ keySelector ,
119
126
  opts ,
120
127
  ) {
121
128
  super()
@@ -124,15 +131,15 @@ export class Cache extends EventEm
124
131
  }
125
132
  this.#location = location
126
133
 
127
- if (typeof valueSelector !== 'function') {
134
+ if (valueSelector !== undefined && typeof valueSelector !== 'function') {
128
135
  throw new TypeError('valueSelector must be a function')
129
136
  }
130
- this.#valueSelector = valueSelector
137
+ this.#valueSelector = valueSelector ?? (() => undefined )
131
138
 
132
- if (typeof keySelector !== 'function') {
139
+ if (keySelector !== undefined && typeof keySelector !== 'function') {
133
140
  throw new TypeError('keySelector must be a function')
134
141
  }
135
- this.#keySelector = keySelector
142
+ this.#keySelector = keySelector ?? ((...args ) => JSON.stringify(args))
136
143
 
137
144
  if (typeof opts?.ttl === 'number' || opts?.ttl === undefined) {
138
145
  const ttl = opts?.ttl ?? Number.MAX_SAFE_INTEGER
@@ -152,6 +159,24 @@ export class Cache extends EventEm
152
159
  throw new TypeError('stale must be a undefined, number or a function')
153
160
  }
154
161
 
162
+ if (opts?.lock === false || opts?.lock === null) {
163
+ this.#lockMinTimeout = -1
164
+ this.#lockTimeout = -1
165
+ this.#lockMean = -1
166
+ } else if (opts?.lock !== undefined) {
167
+ if (typeof opts.lock !== 'object' || opts.lock === null) {
168
+ throw new TypeError('lock must be an object')
169
+ }
170
+ if (opts.lock.minTimeout !== undefined) {
171
+ if (typeof opts.lock.minTimeout !== 'number') {
172
+ throw new TypeError('lock.minTimeout must be a number')
173
+ }
174
+ this.#lockMinTimeout = Math.max(0, opts.lock.minTimeout)
175
+ this.#lockTimeout = this.#lockMinTimeout
176
+ this.#lockMean = this.#lockTimeout / 1.2
177
+ }
178
+ }
179
+
155
180
  if (opts?.serializer !== undefined) {
156
181
  if (typeof opts.serializer !== 'object' || opts.serializer === null) {
157
182
  throw new TypeError('serializer must be an object')
@@ -178,7 +203,6 @@ export class Cache extends EventEm
178
203
  this.#database.exec(`
179
204
  PRAGMA journal_mode = WAL;
180
205
  PRAGMA synchronous = NORMAL;
181
- PRAGMA temp_store = memory;
182
206
  PRAGMA cache_size = -${Math.ceil(maxSize / 1024 / 8)};
183
207
  PRAGMA mmap_size = ${maxSize};
184
208
  PRAGMA max_page_count = ${Math.ceil(maxSize / 4096)};
@@ -276,11 +300,14 @@ export class Cache extends EventEm
276
300
  }
277
301
 
278
302
  return {
279
- lock: {
280
- timeout: this.#lockTimeout,
281
- mean: this.#lockMean,
282
- stddev: Math.sqrt(this.#lockVar),
283
- },
303
+ lock:
304
+ this.#lockTimeout >= 0
305
+ ? {
306
+ timeout: this.#lockTimeout,
307
+ mean: this.#lockMean,
308
+ stddev: Math.sqrt(this.#lockVar),
309
+ }
310
+ : undefined,
284
311
  dedupe: { size: this.#dedupe.size },
285
312
  memory: this.#memory?.stats,
286
313
  database,
@@ -348,6 +375,7 @@ export class Cache extends EventEm
348
375
  this.#memory?.purgeStale(Date.now())
349
376
  this.#purgeStaleQuery?.run(Date.now())
350
377
  this.#lockPurgeQuery?.run(Date.now() - 3_600_000)
378
+ this.#database?.exec('PRAGMA optimize')
351
379
  } catch (err) {
352
380
  this.#emitError(err )
353
381
  }
@@ -428,15 +456,17 @@ export class Cache extends EventEm
428
456
  return { async: true, value: existing }
429
457
  }
430
458
 
431
- // Another process holds the lock — wait for the value instead of calling valueSelector.
432
- // Use deferred pattern so #waitForValue can check promise identity to detect delete+get races.
433
- const lockResult = this.#tryAcquireLock(key)
434
- if (typeof lockResult === 'number') {
435
- const { promise, resolve, reject } = Promise.withResolvers ()
436
- promise.catch(noop)
437
- this.#dedupe.set(key, promise)
438
- this.#waitForValue(args, key, lockResult, promise).then(resolve, reject)
439
- return { async: true, value: promise }
459
+ if (this.#lockTimeout >= 0) {
460
+ // Another process holds the lock wait for the value instead of calling valueSelector.
461
+ // Use deferred pattern so #waitForValue can check promise identity to detect delete+get races.
462
+ const lockResult = this.#tryAcquireLock(key)
463
+ if (typeof lockResult === 'number') {
464
+ const { promise, resolve, reject } = Promise.withResolvers ()
465
+ promise.catch(noop)
466
+ this.#dedupe.set(key, promise)
467
+ this.#waitForValue(args, key, lockResult, promise).then(resolve, reject)
468
+ return { async: true, value: promise }
469
+ }
440
470
  }
441
471
 
442
472
  const res = this.#fetch(args)
@@ -505,7 +535,7 @@ export class Cache extends EventEm
505
535
  throw new TypeError('ttl must be nully or a positive integer')
506
536
  }
507
537
 
508
- const staleValue = Math.min(MAX_DURATION, this.#stale(value, key) ?? Number.MAX_SAFE_INTEGER)
538
+ const staleValue = Math.min(MAX_DURATION, this.#stale(value, key) ?? Infinity)
509
539
  if (!Number.isFinite(staleValue) || staleValue < 0) {
510
540
  throw new TypeError('stale must be nully or a positive integer')
511
541
  }
@@ -629,7 +659,7 @@ export class Cache extends EventEm
629
659
  this.#lockMean += alpha * diff
630
660
  this.#lockVar = (1 - alpha) * (this.#lockVar + alpha * diff * diff)
631
661
  this.#lockTimeout = Math.max(
632
- 10,
662
+ this.#lockMinTimeout,
633
663
  Math.min(1_000, Math.ceil(this.#lockMean * 1.2 + Math.sqrt(this.#lockVar) * 3)),
634
664
  )
635
665
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/cache",
3
- "version": "2.0.7",
3
+ "version": "2.1.1",
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": "c9b449408262ca9431467193d624f1a043d669bb"
33
+ "gitHead": "3b68f317345f4e74459689d2a82fb0a5cc785d1f"
34
34
  }