@nxtedition/nxt-undici 7.3.10 → 7.3.12
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/sqlite-cache-store.js +134 -33
- package/package.json +1 -1
|
@@ -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
|
*/
|
|
@@ -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,117 @@ 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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
269
|
+
const startTime = performance.now()
|
|
270
|
+
for (let retryCount = 0; true; retryCount++) {
|
|
271
|
+
let n = 0
|
|
227
272
|
try {
|
|
228
|
-
this.#
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
273
|
+
this.#db.exec('BEGIN')
|
|
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
|
+
// On SQLITE_FULL, SQLite automatically rolls back the transaction, so
|
|
318
|
+
// the explicit ROLLBACK may fail with "no transaction is active" — ignore it.
|
|
319
|
+
try {
|
|
320
|
+
this.#db.exec('ROLLBACK')
|
|
321
|
+
} catch {
|
|
322
|
+
// already rolled back automatically
|
|
323
|
+
// TODO (fix): Check that the error is what we expect (something like "no transaction is active")...
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (err?.errcode === 13 /* SQLITE_FULL */ && retryCount < 3) {
|
|
327
|
+
this.#evictQuery.run(256)
|
|
328
|
+
} else {
|
|
329
|
+
// If BEGIN failed (n=0) clear the whole batch to avoid an infinite
|
|
330
|
+
// retry loop. If an INSERT/COMMIT failed, only clear the entries
|
|
331
|
+
// that were part of the attempted transaction.
|
|
332
|
+
this.#insertBatch.splice(0, n || this.#insertBatch.length)
|
|
333
|
+
throw err
|
|
334
|
+
}
|
|
232
335
|
}
|
|
233
|
-
} else {
|
|
234
|
-
process.emitWarning(err)
|
|
235
336
|
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
process.emitWarning(err)
|
|
236
339
|
}
|
|
237
|
-
}
|
|
238
340
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
)
|
|
341
|
+
if (this.#insertBatch.length > 0) {
|
|
342
|
+
// If we weren't able to flush the entire batch within the time limit, schedule another flush.
|
|
343
|
+
setImmediate(this.#flush)
|
|
344
|
+
}
|
|
256
345
|
}
|
|
257
346
|
|
|
258
347
|
/**
|
|
@@ -272,20 +361,32 @@ export class SqliteCacheStore {
|
|
|
272
361
|
return undefined
|
|
273
362
|
}
|
|
274
363
|
|
|
364
|
+
const url = makeValueUrl(key)
|
|
365
|
+
const now = getFastNow()
|
|
366
|
+
const requestedStart = range?.start ?? 0
|
|
367
|
+
|
|
275
368
|
/**
|
|
276
369
|
* @type {SqliteStoreValue[]}
|
|
277
370
|
*/
|
|
278
|
-
const values = this.#getValuesQuery.all(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
371
|
+
const values = this.#getValuesQuery.all(url, method, requestedStart, now)
|
|
372
|
+
|
|
373
|
+
for (const entry of this.#insertBatch) {
|
|
374
|
+
if (
|
|
375
|
+
entry.url === url &&
|
|
376
|
+
entry.method === method &&
|
|
377
|
+
entry.start <= requestedStart &&
|
|
378
|
+
entry.deleteAt > now
|
|
379
|
+
) {
|
|
380
|
+
values.push(entry)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
284
383
|
|
|
285
384
|
if (values.length === 0) {
|
|
286
385
|
return undefined
|
|
287
386
|
}
|
|
288
387
|
|
|
388
|
+
values.sort((a, b) => b.cachedAt - a.cachedAt)
|
|
389
|
+
|
|
289
390
|
for (const value of values) {
|
|
290
391
|
// TODO (fix): Allow full and partial match?
|
|
291
392
|
if (range && (range.start !== value.start || range.end !== value.end)) {
|