@nxtedition/cache 2.1.0 → 2.1.2
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 +7 -3
- package/lib/index.js +56 -28
- package/lib/memory.js +2 -2
- package/package.json +2 -2
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[] =
|
|
35
|
+
export declare class Cache<V = unknown, A extends unknown[] = [string]> extends EventEmitter {
|
|
32
36
|
#private;
|
|
33
|
-
constructor(location: string, valueSelector
|
|
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
|
@@ -8,7 +8,6 @@ import { MemoryCache, } from "./memory
|
|
|
8
8
|
|
|
9
9
|
function noop() {}
|
|
10
10
|
|
|
11
|
-
// eslint-disable-next-line typescript-eslint/no-redundant-type-constituents
|
|
12
11
|
function maybeToBuffer(value ) {
|
|
13
12
|
return ArrayBuffer.isView(value)
|
|
14
13
|
? Buffer.from(value.buffer, value.byteOffset, value.byteLength)
|
|
@@ -37,6 +36,10 @@ const dbs = new Set ()
|
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
|
|
@@ -58,6 +61,7 @@ const defaultSerializer = {
|
|
|
58
61
|
|
|
59
62
|
|
|
60
63
|
|
|
64
|
+
|
|
61
65
|
|
|
62
66
|
|
|
63
67
|
|
|
@@ -75,7 +79,7 @@ const defaultSerializer = {
|
|
|
75
79
|
const VERSION = 5
|
|
76
80
|
const MAX_DURATION = 365000000e3
|
|
77
81
|
|
|
78
|
-
export class Cache
|
|
82
|
+
export class Cache extends EventEmitter {
|
|
79
83
|
#memory
|
|
80
84
|
#dedupe = new Map ()
|
|
81
85
|
#closed = false
|
|
@@ -87,10 +91,11 @@ export class Cache extends EventEm
|
|
|
87
91
|
#stale
|
|
88
92
|
#serializer
|
|
89
93
|
|
|
90
|
-
#lockMean = 5
|
|
91
|
-
#lockVar = 0
|
|
92
|
-
#lockTimeout = 10
|
|
93
94
|
#lockId = randomUUID()
|
|
95
|
+
#lockVar = 0
|
|
96
|
+
#lockMean = 5
|
|
97
|
+
#lockTimeout = 10
|
|
98
|
+
#lockMinTimeout = 1
|
|
94
99
|
|
|
95
100
|
#location
|
|
96
101
|
#database = null
|
|
@@ -116,8 +121,8 @@ export class Cache extends EventEm
|
|
|
116
121
|
|
|
117
122
|
constructor(
|
|
118
123
|
location ,
|
|
119
|
-
valueSelector
|
|
120
|
-
keySelector
|
|
124
|
+
valueSelector ,
|
|
125
|
+
keySelector ,
|
|
121
126
|
opts ,
|
|
122
127
|
) {
|
|
123
128
|
super()
|
|
@@ -126,15 +131,15 @@ export class Cache extends EventEm
|
|
|
126
131
|
}
|
|
127
132
|
this.#location = location
|
|
128
133
|
|
|
129
|
-
if (typeof valueSelector !== 'function') {
|
|
134
|
+
if (valueSelector !== undefined && typeof valueSelector !== 'function') {
|
|
130
135
|
throw new TypeError('valueSelector must be a function')
|
|
131
136
|
}
|
|
132
|
-
this.#valueSelector = valueSelector
|
|
137
|
+
this.#valueSelector = valueSelector ?? (() => undefined )
|
|
133
138
|
|
|
134
|
-
if (typeof keySelector !== 'function') {
|
|
139
|
+
if (keySelector !== undefined && typeof keySelector !== 'function') {
|
|
135
140
|
throw new TypeError('keySelector must be a function')
|
|
136
141
|
}
|
|
137
|
-
this.#keySelector = keySelector
|
|
142
|
+
this.#keySelector = keySelector ?? ((...args ) => JSON.stringify(args))
|
|
138
143
|
|
|
139
144
|
if (typeof opts?.ttl === 'number' || opts?.ttl === undefined) {
|
|
140
145
|
const ttl = opts?.ttl ?? Number.MAX_SAFE_INTEGER
|
|
@@ -154,6 +159,24 @@ export class Cache extends EventEm
|
|
|
154
159
|
throw new TypeError('stale must be a undefined, number or a function')
|
|
155
160
|
}
|
|
156
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
|
+
|
|
157
180
|
if (opts?.serializer !== undefined) {
|
|
158
181
|
if (typeof opts.serializer !== 'object' || opts.serializer === null) {
|
|
159
182
|
throw new TypeError('serializer must be an object')
|
|
@@ -180,7 +203,6 @@ export class Cache extends EventEm
|
|
|
180
203
|
this.#database.exec(`
|
|
181
204
|
PRAGMA journal_mode = WAL;
|
|
182
205
|
PRAGMA synchronous = NORMAL;
|
|
183
|
-
PRAGMA temp_store = memory;
|
|
184
206
|
PRAGMA cache_size = -${Math.ceil(maxSize / 1024 / 8)};
|
|
185
207
|
PRAGMA mmap_size = ${maxSize};
|
|
186
208
|
PRAGMA max_page_count = ${Math.ceil(maxSize / 4096)};
|
|
@@ -278,11 +300,14 @@ export class Cache extends EventEm
|
|
|
278
300
|
}
|
|
279
301
|
|
|
280
302
|
return {
|
|
281
|
-
lock:
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
303
|
+
lock:
|
|
304
|
+
this.#lockTimeout >= 0
|
|
305
|
+
? {
|
|
306
|
+
timeout: this.#lockTimeout,
|
|
307
|
+
mean: this.#lockMean,
|
|
308
|
+
stddev: Math.sqrt(this.#lockVar),
|
|
309
|
+
}
|
|
310
|
+
: undefined,
|
|
286
311
|
dedupe: { size: this.#dedupe.size },
|
|
287
312
|
memory: this.#memory?.stats,
|
|
288
313
|
database,
|
|
@@ -350,6 +375,7 @@ export class Cache extends EventEm
|
|
|
350
375
|
this.#memory?.purgeStale(Date.now())
|
|
351
376
|
this.#purgeStaleQuery?.run(Date.now())
|
|
352
377
|
this.#lockPurgeQuery?.run(Date.now() - 3_600_000)
|
|
378
|
+
this.#database?.exec('PRAGMA optimize')
|
|
353
379
|
} catch (err) {
|
|
354
380
|
this.#emitError(err )
|
|
355
381
|
}
|
|
@@ -430,15 +456,17 @@ export class Cache extends EventEm
|
|
|
430
456
|
return { async: true, value: existing }
|
|
431
457
|
}
|
|
432
458
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
+
}
|
|
442
470
|
}
|
|
443
471
|
|
|
444
472
|
const res = this.#fetch(args)
|
|
@@ -507,7 +535,7 @@ export class Cache extends EventEm
|
|
|
507
535
|
throw new TypeError('ttl must be nully or a positive integer')
|
|
508
536
|
}
|
|
509
537
|
|
|
510
|
-
const staleValue = Math.min(MAX_DURATION, this.#stale(value, key) ??
|
|
538
|
+
const staleValue = Math.min(MAX_DURATION, this.#stale(value, key) ?? Infinity)
|
|
511
539
|
if (!Number.isFinite(staleValue) || staleValue < 0) {
|
|
512
540
|
throw new TypeError('stale must be nully or a positive integer')
|
|
513
541
|
}
|
|
@@ -631,7 +659,7 @@ export class Cache extends EventEm
|
|
|
631
659
|
this.#lockMean += alpha * diff
|
|
632
660
|
this.#lockVar = (1 - alpha) * (this.#lockVar + alpha * diff * diff)
|
|
633
661
|
this.#lockTimeout = Math.max(
|
|
634
|
-
|
|
662
|
+
this.#lockMinTimeout,
|
|
635
663
|
Math.min(1_000, Math.ceil(this.#lockMean * 1.2 + Math.sqrt(this.#lockVar) * 3)),
|
|
636
664
|
)
|
|
637
665
|
}
|
package/lib/memory.js
CHANGED
|
@@ -78,7 +78,7 @@ export class MemoryCache {
|
|
|
78
78
|
#delete(entry ) {
|
|
79
79
|
this.#map.delete(entry.key)
|
|
80
80
|
|
|
81
|
-
this.#size -= entry.size
|
|
81
|
+
this.#size -= entry.size ?? 0
|
|
82
82
|
this.#count -= 1
|
|
83
83
|
|
|
84
84
|
const tmp = this.#arr.pop()
|
|
@@ -107,7 +107,7 @@ export class MemoryCache {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
#prune() {
|
|
110
|
-
while (this.#size > this.#maxSize || this.#count > this.#maxCount) {
|
|
110
|
+
while (this.#arr.length > 0 && (this.#size > this.#maxSize || this.#count > this.#maxCount)) {
|
|
111
111
|
const e1 = this.#arr[(Math.random() * this.#arr.length) | 0]
|
|
112
112
|
const e2 = this.#arr[(Math.random() * this.#arr.length) | 0]
|
|
113
113
|
const e = e2.counter > e1.counter ? e1 : e2
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/cache",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
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": "f0aff304c4a0a7246ea0cb34f8d8b3839dc11e81"
|
|
34
34
|
}
|