@nxtedition/lib 26.0.8 → 26.0.10

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 +63 -23
  2. package/http.js +11 -8
  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...
@@ -57,26 +79,41 @@ export class AsyncCache {
57
79
 
58
80
  this.#lru =
59
81
  opts?.lru === false || opts?.lru === null ? null : new LRUCache({ max: 4096, ...opts?.lru })
60
- this.#db = new DatabaseSync(location, { timeout: 20, ...opts?.db })
61
- this.#db.exec(`
62
- PRAGMA journal_mode = WAL;
63
- PRAGMA synchronous = NORMAL;
64
- PRAGMA temp_store = memory;
65
- PRAGMA optimize;
66
-
67
- CREATE TABLE IF NOT EXISTS cache (
68
- key TEXT PRIMARY KEY NOT NULL,
69
- val TEXT NOT NULL,
70
- ttl INTEGER NOT NULL,
71
- stale INTEGER NOT NULL
72
- );
73
- `)
74
-
75
- this.#getQuery = this.#db.prepare(`SELECT val, ttl, stale FROM cache WHERE key = ?`)
76
- this.#setQuery = this.#db.prepare(
77
- `INSERT OR REPLACE INTO cache (key, val, ttl, stale) VALUES (?, ?, ?, ?)`,
78
- )
79
- this.#delQuery = this.#db.prepare(`DELETE FROM cache WHERE key = ?`)
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
+ this.#db?.close()
108
+ this.#db = null
109
+
110
+ this.#getQuery = null
111
+ this.#setQuery = null
112
+ this.#delQuery = null
113
+
114
+ process.emitWarning(err)
115
+ }
116
+ }
80
117
  }
81
118
 
82
119
  close() {
@@ -85,7 +122,7 @@ export class AsyncCache {
85
122
 
86
123
  /**
87
124
  * @param {...any} args
88
- * @returns {{ value: Promise<unknown>|unknown, async: boolean }}
125
+ * @returns {{ value: V|Promise<V>, async: boolean }}
89
126
  */
90
127
  get(...args) {
91
128
  const key = this.#keySelector(...args)
@@ -158,7 +195,7 @@ export class AsyncCache {
158
195
 
159
196
  /**
160
197
  * @param {string} key
161
- * @param {any} value
198
+ * @param {V} value
162
199
  */
163
200
  set(key, value) {
164
201
  if (typeof key !== 'string' || key.length === 0) {
@@ -182,6 +219,9 @@ export class AsyncCache {
182
219
  }
183
220
  }
184
221
 
222
+ /**
223
+ * @param {string} key
224
+ */
185
225
  delete(key) {
186
226
  if (typeof key !== 'string' || key.length === 0) {
187
227
  throw new TypeError('key must be a non-empty string')
package/http.js CHANGED
@@ -75,7 +75,7 @@ export class Context {
75
75
  return this.#req.target
76
76
  }
77
77
 
78
- this.#target ??= requestTarget(this.#req)
78
+ this.#target ??= Object.freeze(requestTarget(this.#req))
79
79
 
80
80
  if (!this.#target) {
81
81
  throw new createError.BadRequest('invalid url')
@@ -89,13 +89,10 @@ export class Context {
89
89
  return this.#req.query
90
90
  }
91
91
 
92
- this.#target ??= requestTarget(this.#req)
93
- this.#query ??=
94
- this.#target.search.length > 1
95
- ? Object.freeze(querystring.parse(this.#target.search.slice(1)))
96
- : {}
92
+ const search = this.target.search
97
93
 
98
- return this.#query
94
+ return (this.#query ??=
95
+ search.length > 1 ? Object.freeze(querystring.parse(search.slice(1))) : {})
99
96
  }
100
97
 
101
98
  get userAgent() {
@@ -373,7 +370,13 @@ export class IncomingMessage extends http.IncomingMessage {
373
370
  this.#target = undefined
374
371
  }
375
372
 
376
- return (this.#target ??= requestTarget(this))
373
+ this.#target ??= Object.freeze(requestTarget(this))
374
+
375
+ if (!this.#target) {
376
+ throw new createError.BadRequest('invalid url')
377
+ }
378
+
379
+ return this.#target
377
380
  }
378
381
 
379
382
  get query() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "26.0.8",
3
+ "version": "26.0.10",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",