@nxtedition/lib 26.0.7 → 26.0.9

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.
Files changed (3) hide show
  1. package/cache.js +79 -29
  2. package/http.js +3 -1
  3. package/package.json +1 -1
package/cache.js CHANGED
@@ -4,13 +4,29 @@ import { fastNow } from './time.js'
4
4
 
5
5
  function noop() {}
6
6
 
7
+ /**
8
+ * @template [V=unknown]
9
+ * @typedef {object} AsyncCacheOptions
10
+ * @property {number | ((value: V, key: string) => number)} [ttl]
11
+ * @property {number | ((value: V, key: string) => number)} [stale]
12
+ * @property {import('lru-cache').LRUCache.Options<string, { ttl: number, stale: number, value: V }> | false | null} [lru]
13
+ * @property {{ timeout?: number }} [db]
14
+ */
15
+
16
+ /**
17
+ * @template [V=unknown]
18
+ */
7
19
  export class AsyncCache {
8
- /** @type LRUCache<string, { ttl: number, stale: number, value: any } **/
20
+ /** @type LRUCache<string, { ttl: number, stale: number, value: V }> */
9
21
  #lru
10
22
  #valueSelector
11
23
  #keySelector
12
24
  #dedupe = new Map()
25
+
26
+ /** @type {(val: V, key: string) => number} */
13
27
  #ttl
28
+
29
+ /** @type {(val: V, key: string) => number} */
14
30
  #stale
15
31
 
16
32
  #db
@@ -18,6 +34,12 @@ export class AsyncCache {
18
34
  #setQuery
19
35
  #delQuery
20
36
 
37
+ /**
38
+ * @param {string} location
39
+ * @param {((...args: any[]) => Promise<V>)|undefined} [valueSelector]
40
+ * @param {((...args: any[]) => string)|undefined} [keySelector]
41
+ * @param {AsyncCacheOptions<V>} [opts]
42
+ */
21
43
  constructor(location, valueSelector, keySelector, opts) {
22
44
  if (typeof location === 'string') {
23
45
  // Do nothing...
@@ -55,27 +77,38 @@ export class AsyncCache {
55
77
  throw new TypeError('stale must be a undefined, number or a function')
56
78
  }
57
79
 
58
- this.#lru = new LRUCache({ max: 4096, ...opts?.lru })
59
- this.#db = new DatabaseSync(location, { timeout: 20, ...opts?.db })
60
- this.#db.exec(`
61
- PRAGMA journal_mode = WAL;
62
- PRAGMA synchronous = NORMAL;
63
- PRAGMA temp_store = memory;
64
- PRAGMA optimize;
65
-
66
- CREATE TABLE IF NOT EXISTS cache (
67
- key TEXT PRIMARY KEY NOT NULL,
68
- val TEXT NOT NULL,
69
- ttl INTEGER NOT NULL,
70
- stale INTEGER NOT NULL
71
- );
72
- `)
73
-
74
- this.#getQuery = this.#db.prepare(`SELECT val, ttl, stale FROM cache WHERE key = ?`)
75
- this.#setQuery = this.#db.prepare(
76
- `INSERT OR REPLACE INTO cache (key, val, ttl, stale) VALUES (?, ?, ?, ?)`,
77
- )
78
- this.#delQuery = this.#db.prepare(`DELETE FROM cache WHERE key = ?`)
80
+ this.#lru =
81
+ opts?.lru === false || opts?.lru === null ? null : new LRUCache({ max: 4096, ...opts?.lru })
82
+
83
+ for (let n = 0; true; n += 1) {
84
+ try {
85
+ this.#db = new DatabaseSync(location, { timeout: 20, ...opts?.db })
86
+
87
+ this.#db.exec(`
88
+ PRAGMA journal_mode = WAL;
89
+ PRAGMA synchronous = NORMAL;
90
+ PRAGMA temp_store = memory;
91
+ PRAGMA optimize;
92
+
93
+ CREATE TABLE IF NOT EXISTS cache (
94
+ key TEXT PRIMARY KEY NOT NULL,
95
+ val TEXT NOT NULL,
96
+ ttl INTEGER NOT NULL,
97
+ stale INTEGER NOT NULL
98
+ );
99
+ `)
100
+
101
+ this.#getQuery = this.#db.prepare(`SELECT val, ttl, stale FROM cache WHERE key = ?`)
102
+ this.#setQuery = this.#db.prepare(
103
+ `INSERT OR REPLACE INTO cache (key, val, ttl, stale) VALUES (?, ?, ?, ?)`,
104
+ )
105
+ this.#delQuery = this.#db.prepare(`DELETE FROM cache WHERE key = ?`)
106
+ } catch (err) {
107
+ if (n > 128 || !/locked|busy/i.test(err?.message)) {
108
+ throw err
109
+ }
110
+ }
111
+ }
79
112
  }
80
113
 
81
114
  close() {
@@ -84,7 +117,7 @@ export class AsyncCache {
84
117
 
85
118
  /**
86
119
  * @param {...any} args
87
- * @returns {{ value: Promise<unknown>|unknown, async: boolean }}
120
+ * @returns {{ value: V|Promise<V>, async: boolean }}
88
121
  */
89
122
  get(...args) {
90
123
  const key = this.#keySelector(...args)
@@ -95,7 +128,7 @@ export class AsyncCache {
95
128
 
96
129
  const now = fastNow()
97
130
 
98
- let cached = this.#lru.get(key)
131
+ let cached = this.#lru?.get(key)
99
132
 
100
133
  if (cached === undefined) {
101
134
  try {
@@ -106,7 +139,7 @@ export class AsyncCache {
106
139
  stale: ret.stale,
107
140
  value: JSON.parse(ret.val),
108
141
  }
109
- this.#lru.set(key, cached)
142
+ this.#lru?.set(key, cached)
110
143
  }
111
144
  } catch {
112
145
  // Do nothing...
@@ -120,9 +153,9 @@ export class AsyncCache {
120
153
 
121
154
  if (now > cached.stale) {
122
155
  // stale-while-revalidate has ttld, purge cached value.
123
- this.#lru.delete(key)
156
+ this.#lru?.delete(key)
124
157
  try {
125
- this.#delQuery.run(key)
158
+ this.#delQuery?.run(key)
126
159
  } catch {
127
160
  // Do nothing...
128
161
  }
@@ -157,7 +190,7 @@ export class AsyncCache {
157
190
 
158
191
  /**
159
192
  * @param {string} key
160
- * @param {any} value
193
+ * @param {V} value
161
194
  */
162
195
  set(key, value) {
163
196
  if (typeof key !== 'string' || key.length === 0) {
@@ -171,7 +204,7 @@ export class AsyncCache {
171
204
  value,
172
205
  }
173
206
 
174
- this.#lru.set(key, cached)
207
+ this.#lru?.set(key, cached)
175
208
  this.#dedupe.delete(key)
176
209
 
177
210
  try {
@@ -180,4 +213,21 @@ export class AsyncCache {
180
213
  // Do nothing...
181
214
  }
182
215
  }
216
+
217
+ /**
218
+ * @param {string} key
219
+ */
220
+ delete(key) {
221
+ if (typeof key !== 'string' || key.length === 0) {
222
+ throw new TypeError('key must be a non-empty string')
223
+ }
224
+
225
+ this.#lru?.delete(key)
226
+
227
+ try {
228
+ this.#delQuery?.run(key)
229
+ } catch {
230
+ // Do nothing...
231
+ }
232
+ }
183
233
  }
package/http.js CHANGED
@@ -166,7 +166,9 @@ export async function upgradeMiddleware(ctx, next) {
166
166
  await thenable
167
167
  }
168
168
 
169
- assert(socket.destroyed || socket.writableEnded, 'stream not completed')
169
+ if (!socket.destroyed && !socket.writableEnded) {
170
+ throw new Error('Stream not completed')
171
+ }
170
172
 
171
173
  const elapsedTime = performance.now() - startTime
172
174
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "26.0.7",
3
+ "version": "26.0.9",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",