@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 +7 -3
- package/lib/index.js +57 -27
- 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
|
@@ -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
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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) ??
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
33
|
+
"gitHead": "3b68f317345f4e74459689d2a82fb0a5cc785d1f"
|
|
34
34
|
}
|