@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 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: unknown[]) => V | Promise<V>, keySelector?: (...args: unknown[]) => string, opts?: AsyncCacheOptions<V>);
26
+ constructor(location: string, valueSelector?: (...args: A) => V | Promise<V>, keySelector?: (...args: A) => string, opts?: AsyncCacheOptions<V>);
27
27
  close(): void;
28
- get(...args: unknown[]): CacheResult<V>;
29
- peek(...args: unknown[]): CacheResult<V>;
30
- refresh(...args: unknown[]): Promise<V> | undefined;
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 ) => JSON.stringify(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 === undefined
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; true; n++) {
126
+ for (let n = 0; opts?.db !== null && opts?.db !== false; n++) {
139
127
  try {
140
- this.#db ??= new DatabaseSync(location, { timeout: 20, ...opts?.db })
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 , refresh ) {
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) ? (row.val ) : JSON.parse(row.val),
250
- }
251
- } else {
252
- const entry = this.#setQueue.findLast((x) => x.key === key)
253
- if (entry !== undefined) {
254
- cached = {
255
- ttl: entry.ttl,
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
- // eslint-disable-next-line: no-unsafe-argument
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
- this.#lru?.set(key, { ttl, stale, value })
330
+ const storedValue = ArrayBuffer.isView(value)
331
+ ? (Buffer.from(value.buffer, value.byteOffset, value.byteLength) )
332
+ : value
347
333
 
348
- this.#setQueue.push({ key, value, ttl, stale })
349
- if (this.#setQueue.length === 1) {
350
- doYield(this.#flushSetQueue)
351
- }
352
- }
334
+ this.#lru?.set(key, { ttl, stale, value: storedValue })
353
335
 
354
- #flushSetQueue = () => {
355
- for (const { key, value, ttl, stale } of this.#setQueue.splice(0, 64)) {
356
- const data = ArrayBuffer.isView(value)
357
- ? value
358
- : JSON.stringify(value )
359
- try {
360
- this.#setQuery?.run(key, data , ttl, stale)
361
- } catch (err) {
362
- if ((err )?.errcode === 13 /* SQLITE_FULL */) {
363
- try {
364
- this.#evictQuery?.run(256)
365
- this.#setQuery?.run(key, data , ttl, stale)
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.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": "e85766435987febc99140e523cd2d4f5af458892"
28
+ "gitHead": "1bcd9e44bf750ac6fb478967c46e784e14ea63c5"
30
29
  }