@nxtedition/cache 1.0.0 → 1.0.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 +6 -6
- package/lib/index.js +42 -71
- package/package.json +2 -3
package/lib/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface AsyncCacheOptions<V> {
|
|
|
12
12
|
ttl?: number | ((value: V, key: string) => number);
|
|
13
13
|
stale?: number | ((value: V, key: string) => number);
|
|
14
14
|
lru?: LRUCache.Options<string, CacheEntry<V>, unknown> | false | null;
|
|
15
|
-
db?: AsyncCacheDbOptions;
|
|
15
|
+
db?: AsyncCacheDbOptions | false | null;
|
|
16
16
|
}
|
|
17
17
|
export type CacheResult<V> = {
|
|
18
18
|
value: V;
|
|
@@ -21,13 +21,13 @@ export type CacheResult<V> = {
|
|
|
21
21
|
value: Promise<V> | null | undefined;
|
|
22
22
|
async: true;
|
|
23
23
|
};
|
|
24
|
-
export declare class AsyncCache<V = unknown> {
|
|
24
|
+
export declare class AsyncCache<V = unknown, A extends unknown[] = unknown[]> {
|
|
25
25
|
#private;
|
|
26
|
-
constructor(location: string, valueSelector?: (...args:
|
|
26
|
+
constructor(location: string, valueSelector?: (...args: A) => V | Promise<V>, keySelector?: (...args: A) => string, opts?: AsyncCacheOptions<V>);
|
|
27
27
|
close(): void;
|
|
28
|
-
get(...args:
|
|
29
|
-
peek(...args:
|
|
30
|
-
refresh(...args:
|
|
28
|
+
get(...args: A): CacheResult<V>;
|
|
29
|
+
peek(...args: A): CacheResult<V>;
|
|
30
|
+
refresh(...args: A): Promise<V> | undefined;
|
|
31
31
|
delete(key: string): void;
|
|
32
32
|
set(key: string, value: V): void;
|
|
33
33
|
purgeStale(): void;
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { DatabaseSync, } from 'node:sqlite'
|
|
2
2
|
import { LRUCache } from 'lru-cache'
|
|
3
|
-
import { doYield } from '@nxtedition/yield'
|
|
4
3
|
|
|
5
4
|
let fastNowTime = 0
|
|
6
5
|
|
|
@@ -44,13 +43,6 @@ const dbs = new Set ()
|
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
46
|
|
|
55
47
|
|
|
56
48
|
|
|
@@ -60,7 +52,7 @@ const dbs = new Set ()
|
|
|
60
52
|
|
|
61
53
|
|
|
62
54
|
|
|
63
|
-
|
|
55
|
+
|
|
64
56
|
|
|
65
57
|
|
|
66
58
|
|
|
@@ -70,10 +62,10 @@ const dbs = new Set ()
|
|
|
70
62
|
const VERSION = 2
|
|
71
63
|
const MAX_DURATION = 365000000e3
|
|
72
64
|
|
|
73
|
-
export class AsyncCache
|
|
65
|
+
export class AsyncCache {
|
|
74
66
|
#lru
|
|
75
|
-
#valueSelector
|
|
76
|
-
#keySelector
|
|
67
|
+
#valueSelector
|
|
68
|
+
#keySelector
|
|
77
69
|
#dedupe = new Map ()
|
|
78
70
|
|
|
79
71
|
#ttl
|
|
@@ -86,12 +78,10 @@ export class AsyncCache {
|
|
|
86
78
|
#purgeStaleQuery = null
|
|
87
79
|
#evictQuery = null
|
|
88
80
|
|
|
89
|
-
#setQueue = []
|
|
90
|
-
|
|
91
81
|
constructor(
|
|
92
82
|
location ,
|
|
93
|
-
valueSelector
|
|
94
|
-
keySelector
|
|
83
|
+
valueSelector ,
|
|
84
|
+
keySelector ,
|
|
95
85
|
opts ,
|
|
96
86
|
) {
|
|
97
87
|
if (typeof location === 'string') {
|
|
@@ -107,7 +97,7 @@ export class AsyncCache {
|
|
|
107
97
|
}
|
|
108
98
|
|
|
109
99
|
if (typeof keySelector === 'function' || keySelector === undefined) {
|
|
110
|
-
this.#keySelector = keySelector ?? ((...args
|
|
100
|
+
this.#keySelector = keySelector ?? ((...args ) => JSON.stringify(args))
|
|
111
101
|
} else {
|
|
112
102
|
throw new TypeError('keySelector must be a function')
|
|
113
103
|
}
|
|
@@ -131,13 +121,12 @@ export class AsyncCache {
|
|
|
131
121
|
}
|
|
132
122
|
|
|
133
123
|
this.#lru =
|
|
134
|
-
opts?.lru === false || opts?.lru ===
|
|
135
|
-
? null
|
|
136
|
-
: new LRUCache({ max: 4096, ...opts?.lru })
|
|
124
|
+
opts?.lru === false || opts?.lru === null ? null : new LRUCache({ max: 4096, ...opts?.lru })
|
|
137
125
|
|
|
138
|
-
for (let n = 0;
|
|
126
|
+
for (let n = 0; opts?.db !== null && opts?.db !== false; n++) {
|
|
139
127
|
try {
|
|
140
|
-
|
|
128
|
+
const { maxSize = 256 * 1024 * 1024, timeout = 20 } = opts?.db ?? {}
|
|
129
|
+
this.#db ??= new DatabaseSync(location, { timeout })
|
|
141
130
|
|
|
142
131
|
this.#db.exec(`
|
|
143
132
|
PRAGMA journal_mode = WAL;
|
|
@@ -154,7 +143,6 @@ export class AsyncCache {
|
|
|
154
143
|
`)
|
|
155
144
|
|
|
156
145
|
{
|
|
157
|
-
const maxSize = opts?.db?.maxSize ?? 256 * 1024 * 1024
|
|
158
146
|
const { page_size } = this.#db.prepare('PRAGMA page_size').get()
|
|
159
147
|
this.#db.exec(`PRAGMA max_page_count = ${Math.ceil(maxSize / page_size)}`)
|
|
160
148
|
}
|
|
@@ -191,7 +179,6 @@ export class AsyncCache {
|
|
|
191
179
|
|
|
192
180
|
close() {
|
|
193
181
|
dbs.delete(this)
|
|
194
|
-
this.#setQueue.length = 0
|
|
195
182
|
this.#getQuery = null
|
|
196
183
|
this.#setQuery = null
|
|
197
184
|
this.#delQuery = null
|
|
@@ -201,15 +188,15 @@ export class AsyncCache {
|
|
|
201
188
|
this.#db = null
|
|
202
189
|
}
|
|
203
190
|
|
|
204
|
-
get(...args
|
|
191
|
+
get(...args ) {
|
|
205
192
|
return this.#load(args, true)
|
|
206
193
|
}
|
|
207
194
|
|
|
208
|
-
peek(...args
|
|
195
|
+
peek(...args ) {
|
|
209
196
|
return this.#load(args, false)
|
|
210
197
|
}
|
|
211
198
|
|
|
212
|
-
refresh(...args
|
|
199
|
+
refresh(...args ) {
|
|
213
200
|
return this.#refresh(args)
|
|
214
201
|
}
|
|
215
202
|
|
|
@@ -228,7 +215,7 @@ export class AsyncCache {
|
|
|
228
215
|
} catch {}
|
|
229
216
|
}
|
|
230
217
|
|
|
231
|
-
#load(args
|
|
218
|
+
#load(args , refresh ) {
|
|
232
219
|
const key = this.#keySelector(...args)
|
|
233
220
|
|
|
234
221
|
if (typeof key !== 'string' || key.length === 0) {
|
|
@@ -246,16 +233,13 @@ export class AsyncCache {
|
|
|
246
233
|
cached = {
|
|
247
234
|
ttl: row.ttl,
|
|
248
235
|
stale: row.stale,
|
|
249
|
-
value: ArrayBuffer.isView(row.val)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
stale: entry.stale,
|
|
257
|
-
value: entry.value,
|
|
258
|
-
}
|
|
236
|
+
value: ArrayBuffer.isView(row.val)
|
|
237
|
+
? (Buffer.from(
|
|
238
|
+
row.val.buffer,
|
|
239
|
+
row.val.byteOffset,
|
|
240
|
+
row.val.byteLength,
|
|
241
|
+
) )
|
|
242
|
+
: JSON.parse(row.val),
|
|
259
243
|
}
|
|
260
244
|
}
|
|
261
245
|
|
|
@@ -292,12 +276,12 @@ export class AsyncCache {
|
|
|
292
276
|
: { value: promise, async: true }
|
|
293
277
|
}
|
|
294
278
|
|
|
295
|
-
|
|
296
|
-
#refresh(args , key = this.#keySelector(...args)) {
|
|
279
|
+
#refresh(args , key = this.#keySelector(...args)) {
|
|
297
280
|
if (typeof key !== 'string' || key.length === 0) {
|
|
298
281
|
throw new TypeError('keySelector must return a non-empty string')
|
|
299
282
|
}
|
|
300
283
|
|
|
284
|
+
// TODO (fix): cross process/thread dedupe...
|
|
301
285
|
let promise = this.#dedupe.get(key)
|
|
302
286
|
if (promise === undefined && this.#valueSelector) {
|
|
303
287
|
// eslint-disable-next-line: no-unsafe-argument
|
|
@@ -343,38 +327,30 @@ export class AsyncCache {
|
|
|
343
327
|
return
|
|
344
328
|
}
|
|
345
329
|
|
|
346
|
-
|
|
330
|
+
const storedValue = ArrayBuffer.isView(value)
|
|
331
|
+
? (Buffer.from(value.buffer, value.byteOffset, value.byteLength) )
|
|
332
|
+
: value
|
|
347
333
|
|
|
348
|
-
this.#
|
|
349
|
-
if (this.#setQueue.length === 1) {
|
|
350
|
-
doYield(this.#flushSetQueue)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
334
|
+
this.#lru?.set(key, { ttl, stale, value: storedValue })
|
|
353
335
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
} catch {
|
|
367
|
-
process.emitWarning(err )
|
|
368
|
-
}
|
|
369
|
-
} else {
|
|
336
|
+
const data = ArrayBuffer.isView(value)
|
|
337
|
+
? value
|
|
338
|
+
: JSON.stringify(value )
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
this.#setQuery?.run(key, data , ttl, stale)
|
|
342
|
+
} catch (err) {
|
|
343
|
+
if ((err )?.errcode === 13 /* SQLITE_FULL */) {
|
|
344
|
+
try {
|
|
345
|
+
this.#evictQuery?.run(256)
|
|
346
|
+
this.#setQuery?.run(key, data , ttl, stale)
|
|
347
|
+
} catch {
|
|
370
348
|
process.emitWarning(err )
|
|
371
349
|
}
|
|
350
|
+
} else {
|
|
351
|
+
process.emitWarning(err )
|
|
372
352
|
}
|
|
373
353
|
}
|
|
374
|
-
|
|
375
|
-
if (this.#setQueue.length > 0) {
|
|
376
|
-
doYield(this.#flushSetQueue)
|
|
377
|
-
}
|
|
378
354
|
}
|
|
379
355
|
|
|
380
356
|
#delete(key ) {
|
|
@@ -383,11 +359,6 @@ export class AsyncCache {
|
|
|
383
359
|
}
|
|
384
360
|
|
|
385
361
|
this.#lru?.delete(key)
|
|
386
|
-
|
|
387
|
-
if (this.#setQueue.some((x) => x.key === key)) {
|
|
388
|
-
this.#setQueue = this.#setQueue.filter((x) => x.key !== key)
|
|
389
|
-
}
|
|
390
|
-
|
|
391
362
|
try {
|
|
392
363
|
this.#delQuery?.run(key)
|
|
393
364
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/cache",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
"typescript": "^5.9.3"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@nxtedition/yield": "^1.0.7",
|
|
27
26
|
"lru-cache": "^11.2.6"
|
|
28
27
|
},
|
|
29
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "1bcd9e44bf750ac6fb478967c46e784e14ea63c5"
|
|
30
29
|
}
|