@nxtedition/lib 26.0.11 → 26.0.14

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/cache.js CHANGED
@@ -80,37 +80,43 @@ export class AsyncCache {
80
80
  this.#lru =
81
81
  opts?.lru === false || opts?.lru === null ? null : new LRUCache({ max: 4096, ...opts?.lru })
82
82
 
83
- try {
84
- this.#db = new DatabaseSync(location, { timeout: 20, ...opts?.db })
85
-
86
- this.#db.exec(`
87
- PRAGMA journal_mode = WAL;
88
- PRAGMA synchronous = NORMAL;
89
- PRAGMA temp_store = memory;
90
- PRAGMA optimize;
91
-
92
- CREATE TABLE IF NOT EXISTS cache (
93
- key TEXT PRIMARY KEY NOT NULL,
94
- val TEXT NOT NULL,
95
- ttl INTEGER NOT NULL,
96
- stale INTEGER NOT NULL
97
- );
98
- `)
99
-
100
- this.#getQuery = this.#db.prepare(`SELECT val, ttl, stale FROM cache WHERE key = ?`)
101
- this.#setQuery = this.#db.prepare(
102
- `INSERT OR REPLACE INTO cache (key, val, ttl, stale) VALUES (?, ?, ?, ?)`,
103
- )
104
- this.#delQuery = this.#db.prepare(`DELETE FROM cache WHERE key = ?`)
105
- } catch (err) {
106
- this.#db?.close()
107
- this.#db = null
108
-
109
- this.#getQuery = null
110
- this.#setQuery = null
111
- this.#delQuery = null
112
-
113
- process.emitWarning(err)
83
+ for (let n = 0; true; n++) {
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
+ break
107
+ } catch (err) {
108
+ if (n >= 8) {
109
+ this.#db?.close()
110
+ this.#db = null
111
+
112
+ this.#getQuery = null
113
+ this.#setQuery = null
114
+ this.#delQuery = null
115
+
116
+ process.emitWarning(err)
117
+ break
118
+ }
119
+ }
114
120
  }
115
121
  }
116
122
 
@@ -123,6 +129,31 @@ export class AsyncCache {
123
129
  * @returns {{ value: V|Promise<V>, async: boolean }}
124
130
  */
125
131
  get(...args) {
132
+ return this.#load(args, true)
133
+ }
134
+
135
+ /**
136
+ * @param {...any} args
137
+ * @returns {{ value: V|Promise<V>, async: boolean }}
138
+ */
139
+ peek(...args) {
140
+ return this.#load(args, false)
141
+ }
142
+
143
+ /**
144
+ * @param {...any} args
145
+ * @returns {Promise<V>}
146
+ */
147
+ refresh(...args) {
148
+ return this.#refresh(args)
149
+ }
150
+
151
+ /**
152
+ * @param {any[]} args
153
+ * @param {boolean} refresh
154
+ * @returns {{ value: V|Promise<V>, async: boolean }}
155
+ */
156
+ #load(args, refresh) {
126
157
  const key = this.#keySelector(...args)
127
158
 
128
159
  if (typeof key !== 'string' || key.length === 0) {
@@ -167,30 +198,37 @@ export class AsyncCache {
167
198
  }
168
199
  }
169
200
 
170
- let promise
171
- if (this.#valueSelector) {
172
- promise = this.#dedupe.get(key)
173
- if (promise === undefined) {
174
- promise = this.#valueSelector(...args).then(
175
- (value) => {
176
- this.set(key, value)
177
- return value
178
- },
179
- (err) => {
180
- this.delete(key)
181
- throw err
182
- },
183
- )
184
- promise.catch(noop)
185
- this.#dedupe.set(key, promise)
186
- }
187
- }
201
+ const promise = refresh ? this.#refresh(args, key) : null
188
202
 
189
203
  return cached !== undefined
190
204
  ? { value: cached.value, async: false }
191
205
  : { value: promise, async: true }
192
206
  }
193
207
 
208
+ #refresh(args, key = this.#keySelector(...args)) {
209
+ if (typeof key !== 'string' || key.length === 0) {
210
+ throw new TypeError('keySelector must return a non-empty string')
211
+ }
212
+
213
+ let promise = this.#dedupe.get(key)
214
+ if (promise === undefined && this.#valueSelector) {
215
+ promise = this.#valueSelector(...args).then(
216
+ (value) => {
217
+ this.set(key, value)
218
+ return value
219
+ },
220
+ (err) => {
221
+ this.delete(key)
222
+ throw err
223
+ },
224
+ )
225
+ promise.catch(noop)
226
+ this.#dedupe.set(key, promise)
227
+ }
228
+
229
+ return promise
230
+ }
231
+
194
232
  /**
195
233
  * @param {string} key
196
234
  * @param {V} value
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "26.0.11",
3
+ "version": "26.0.14",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -71,12 +71,15 @@ class FetchEntry {
71
71
  })
72
72
  .catch((err) => {
73
73
  if (this.refresh) {
74
- this.error = Object.assign(err, { data: resource })
74
+ this.error = Object.assign(new Error('fetch failed'), {
75
+ data: { resource },
76
+ cause: err,
77
+ })
75
78
  this.refresh()
76
79
  }
77
80
  })
78
81
  } catch (err) {
79
- this.error = Object.assign(err, { data: resource })
82
+ this.error = Object.assign(new Error('fetch failed'), { data: { resource }, cause: err })
80
83
  this.refresh()
81
84
  }
82
85
  }