@nxtedition/nxt-undici 7.3.9 → 7.3.11

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.
@@ -63,6 +63,9 @@ export class SqliteCacheStore {
63
63
  */
64
64
  #evictQuery
65
65
 
66
+ #insertBatch = []
67
+ #closed = false
68
+
66
69
  /**
67
70
  * @param {import('undici-types/cache-interceptor.d.ts').default.SqliteCacheStoreOpts & { maxSize?: number } | undefined} opts
68
71
  */
@@ -123,7 +126,7 @@ export class SqliteCacheStore {
123
126
  AND start <= ?
124
127
  AND deleteAt > ?
125
128
  ORDER BY
126
- deleteAt ASC
129
+ cachedAt DESC
127
130
  `)
128
131
 
129
132
  this.#insertValueQuery = this.#db.prepare(`
@@ -170,6 +173,10 @@ export class SqliteCacheStore {
170
173
 
171
174
  close() {
172
175
  stores.delete(this)
176
+ if (this.#insertBatch.length > 0) {
177
+ this.#flush()
178
+ }
179
+ this.#closed = true
173
180
  this.#db.close()
174
181
  }
175
182
 
@@ -180,6 +187,10 @@ export class SqliteCacheStore {
180
187
  get(key) {
181
188
  assertCacheKey(key)
182
189
 
190
+ if (this.#closed) {
191
+ return undefined
192
+ }
193
+
183
194
  const value = this.#findValue(key)
184
195
  return value ? makeResult(value) : undefined
185
196
  }
@@ -220,39 +231,106 @@ export class SqliteCacheStore {
220
231
  )
221
232
  }
222
233
 
234
+ if (this.#closed) {
235
+ return
236
+ }
237
+
238
+ if (this.#insertBatch.length === 0) {
239
+ setImmediate(this.#flush)
240
+ }
241
+
242
+ this.#insertBatch.push({
243
+ url: makeValueUrl(key),
244
+ method: key.method,
245
+ body,
246
+ start: value.start,
247
+ end: value.end,
248
+ deleteAt: value.deleteAt,
249
+ statusCode: value.statusCode,
250
+ statusMessage: value.statusMessage,
251
+ headers: value.headers ? JSON.stringify(value.headers) : null,
252
+ etag: value.etag != null ? value.etag : null,
253
+ cacheControlDirectives: value.cacheControlDirectives
254
+ ? JSON.stringify(value.cacheControlDirectives)
255
+ : null,
256
+ vary: value.vary ? JSON.stringify(value.vary) : null,
257
+ cachedAt: value.cachedAt,
258
+ staleAt: value.staleAt,
259
+ })
260
+ }
261
+
262
+ #flush = () => {
263
+ if (this.#insertBatch.length === 0) return
264
+ if (this.#closed) {
265
+ this.#insertBatch.length = 0
266
+ return
267
+ }
223
268
  try {
224
- this.#insert(key, value, body)
225
- } catch (err) {
226
- if (err?.errcode === 13 /* SQLITE_FULL */) {
269
+ const startTime = performance.now()
270
+ for (let retryCount = 0; true; retryCount++) {
271
+ this.#db.exec('BEGIN')
272
+ let n = 0
227
273
  try {
228
- this.#evictQuery.run(256)
229
- this.#insert(key, value, body)
230
- } catch (evictErr) {
231
- process.emitWarning(evictErr)
274
+ while (n < this.#insertBatch.length) {
275
+ const {
276
+ url,
277
+ method,
278
+ body,
279
+ start,
280
+ end,
281
+ deleteAt,
282
+ statusCode,
283
+ statusMessage,
284
+ headers,
285
+ etag,
286
+ cacheControlDirectives,
287
+ vary,
288
+ cachedAt,
289
+ staleAt,
290
+ } = this.#insertBatch[n++]
291
+ this.#insertValueQuery.run(
292
+ url,
293
+ method,
294
+ body,
295
+ start,
296
+ end,
297
+ deleteAt,
298
+ statusCode,
299
+ statusMessage,
300
+ headers,
301
+ etag,
302
+ cacheControlDirectives,
303
+ vary,
304
+ cachedAt,
305
+ staleAt,
306
+ )
307
+ if ((n & 0xf) === 0 && performance.now() - startTime > 10) {
308
+ break
309
+ }
310
+ }
311
+ this.#db.exec('COMMIT')
312
+ this.#insertBatch.splice(0, n)
313
+ break
314
+ } catch (err) {
315
+ // ROLLBACK is required: a failed statement leaves the connection with
316
+ // an open transaction; without it the next BEGIN would throw.
317
+ this.#db.exec('ROLLBACK')
318
+ if (err?.errcode === 13 /* SQLITE_FULL */ && retryCount < 3) {
319
+ this.#evictQuery.run(256)
320
+ } else {
321
+ this.#insertBatch.splice(0, n)
322
+ throw err
323
+ }
232
324
  }
233
- } else {
234
- process.emitWarning(err)
235
325
  }
326
+ } catch (err) {
327
+ process.emitWarning(err)
236
328
  }
237
- }
238
329
 
239
- #insert(key, value, body) {
240
- this.#insertValueQuery.run(
241
- makeValueUrl(key),
242
- key.method,
243
- body,
244
- value.start,
245
- value.end,
246
- value.deleteAt,
247
- value.statusCode,
248
- value.statusMessage,
249
- value.headers ? JSON.stringify(value.headers) : null,
250
- value.etag != null ? value.etag : null,
251
- value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null,
252
- value.vary ? JSON.stringify(value.vary) : null,
253
- value.cachedAt,
254
- value.staleAt,
255
- )
330
+ if (this.#insertBatch.length > 0) {
331
+ // If we weren't able to flush the entire batch within the time limit, schedule another flush.
332
+ setImmediate(this.#flush)
333
+ }
256
334
  }
257
335
 
258
336
  /**
@@ -272,20 +350,32 @@ export class SqliteCacheStore {
272
350
  return undefined
273
351
  }
274
352
 
353
+ const url = makeValueUrl(key)
354
+ const now = getFastNow()
355
+ const requestedStart = range?.start ?? 0
356
+
275
357
  /**
276
358
  * @type {SqliteStoreValue[]}
277
359
  */
278
- const values = this.#getValuesQuery.all(
279
- makeValueUrl(key),
280
- method,
281
- range?.start ?? 0,
282
- getFastNow(),
283
- )
360
+ const values = this.#getValuesQuery.all(url, method, requestedStart, now)
361
+
362
+ for (const entry of this.#insertBatch) {
363
+ if (
364
+ entry.url === url &&
365
+ entry.method === method &&
366
+ entry.start <= requestedStart &&
367
+ entry.deleteAt > now
368
+ ) {
369
+ values.push(entry)
370
+ }
371
+ }
284
372
 
285
373
  if (values.length === 0) {
286
374
  return undefined
287
375
  }
288
376
 
377
+ values.sort((a, b) => b.cachedAt - a.cachedAt)
378
+
289
379
  for (const value of values) {
290
380
  // TODO (fix): Allow full and partial match?
291
381
  if (range && (range.start !== value.start || range.end !== value.end)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "7.3.9",
3
+ "version": "7.3.11",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",