@nxtedition/nxt-undici 6.2.19 → 6.2.22
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/interceptor/cache.js +4 -1
- package/lib/request.js +8 -1
- package/lib/sqlite-cache-store.js +33 -117
- package/package.json +1 -1
package/lib/interceptor/cache.js
CHANGED
|
@@ -4,6 +4,7 @@ import { SqliteCacheStore } from '../sqlite-cache-store.js'
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_STORE = new SqliteCacheStore({ location: ':memory:' })
|
|
6
6
|
const DEFAULT_MAX_ENTRY_SIZE = 128 * 1024
|
|
7
|
+
const DEFAULT_MAX_ENTRY_TTL = 14 * 24 * 3600
|
|
7
8
|
const NOOP = () => {}
|
|
8
9
|
|
|
9
10
|
class CacheHandler extends DecoratorHandler {
|
|
@@ -12,6 +13,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
12
13
|
#store
|
|
13
14
|
#logger
|
|
14
15
|
#maxEntrySize
|
|
16
|
+
#maxEntryTTL
|
|
15
17
|
|
|
16
18
|
constructor(key, { store, logger, handler, maxEntrySize }) {
|
|
17
19
|
super(handler)
|
|
@@ -21,6 +23,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
21
23
|
this.#value = null
|
|
22
24
|
this.#store = store
|
|
23
25
|
this.#maxEntrySize = maxEntrySize ?? store.maxEntrySize ?? DEFAULT_MAX_ENTRY_SIZE
|
|
26
|
+
this.#maxEntryTTL = maxEntrySize ?? store.maxEntryTTL ?? DEFAULT_MAX_ENTRY_TTL
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
onConnect(abort) {
|
|
@@ -124,7 +127,7 @@ class CacheHandler extends DecoratorHandler {
|
|
|
124
127
|
body: [],
|
|
125
128
|
start,
|
|
126
129
|
end,
|
|
127
|
-
deleteAt: cachedAt + ttl * 1e3,
|
|
130
|
+
deleteAt: cachedAt + Math.min(ttl, this.#maxEntryTTL) * 1e3,
|
|
128
131
|
statusCode,
|
|
129
132
|
statusMessage: '',
|
|
130
133
|
headers,
|
package/lib/request.js
CHANGED
|
@@ -163,7 +163,14 @@ export function request(dispatch, url, opts) {
|
|
|
163
163
|
let origin = url.origin
|
|
164
164
|
if (!origin) {
|
|
165
165
|
const protocol = url.protocol ?? 'http:'
|
|
166
|
-
const host =
|
|
166
|
+
const host =
|
|
167
|
+
url.host ??
|
|
168
|
+
(url.hostname ? `${url.hostname}:${url.port ?? (protocol === 'https:' ? 443 : 80)}` : null)
|
|
169
|
+
|
|
170
|
+
if (!host || !protocol) {
|
|
171
|
+
throw new InvalidArgumentError('invalid url')
|
|
172
|
+
}
|
|
173
|
+
|
|
167
174
|
origin = `${protocol}//${host}`
|
|
168
175
|
}
|
|
169
176
|
|
|
@@ -4,9 +4,6 @@ import { parseRangeHeader } from './utils.js'
|
|
|
4
4
|
|
|
5
5
|
const VERSION = 6
|
|
6
6
|
|
|
7
|
-
// 2gb
|
|
8
|
-
const MAX_ENTRY_SIZE = 2 * 1000 * 1000 * 1000
|
|
9
|
-
|
|
10
7
|
/**
|
|
11
8
|
* @typedef {import('undici-types/cache-interceptor.d.ts').default.CacheStore} CacheStore
|
|
12
9
|
* @implements {CacheStore}
|
|
@@ -28,9 +25,6 @@ const MAX_ENTRY_SIZE = 2 * 1000 * 1000 * 1000
|
|
|
28
25
|
* }} SqliteStoreValue
|
|
29
26
|
*/
|
|
30
27
|
export class SqliteCacheStore {
|
|
31
|
-
#maxEntrySize = MAX_ENTRY_SIZE
|
|
32
|
-
#maxEntryCount = 16 * 1024
|
|
33
|
-
|
|
34
28
|
/**
|
|
35
29
|
* @type {import('node:sqlite').DatabaseSync}
|
|
36
30
|
*/
|
|
@@ -57,57 +51,14 @@ export class SqliteCacheStore {
|
|
|
57
51
|
#deleteByUrlQuery
|
|
58
52
|
|
|
59
53
|
/**
|
|
60
|
-
* @type {
|
|
61
|
-
*/
|
|
62
|
-
#countEntriesQuery
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* @type {import('node:sqlite').StatementSync | null}
|
|
54
|
+
* @type {NodeJS.Timeout}
|
|
66
55
|
*/
|
|
67
|
-
#
|
|
56
|
+
#pruneInterval
|
|
68
57
|
|
|
69
58
|
/**
|
|
70
59
|
* @param {import('undici-types/cache-interceptor.d.ts').default.SqliteCacheStoreOpts & { maxEntryCount?: number} | undefined} opts
|
|
71
60
|
*/
|
|
72
61
|
constructor(opts) {
|
|
73
|
-
if (opts) {
|
|
74
|
-
if (typeof opts !== 'object') {
|
|
75
|
-
throw new TypeError('SqliteCacheStore options must be an object')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (opts.maxEntrySize !== undefined) {
|
|
79
|
-
if (
|
|
80
|
-
typeof opts.maxEntrySize !== 'number' ||
|
|
81
|
-
!Number.isInteger(opts.maxEntrySize) ||
|
|
82
|
-
opts.maxEntrySize < 0
|
|
83
|
-
) {
|
|
84
|
-
throw new TypeError(
|
|
85
|
-
'SqliteCacheStore options.maxEntrySize must be a non-negative integer',
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (opts.maxEntrySize > MAX_ENTRY_SIZE) {
|
|
90
|
-
throw new TypeError('SqliteCacheStore options.maxEntrySize must be less than 2gb')
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.#maxEntrySize = opts.maxEntrySize
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const maxEntryCount = opts.maxEntryCount ?? opts.maxCount
|
|
97
|
-
if (maxEntryCount !== undefined) {
|
|
98
|
-
if (
|
|
99
|
-
typeof maxEntryCount !== 'number' ||
|
|
100
|
-
!Number.isInteger(maxEntryCount) ||
|
|
101
|
-
maxEntryCount < 0
|
|
102
|
-
) {
|
|
103
|
-
throw new TypeError(
|
|
104
|
-
'SqliteCacheStore options.maxEntryCount must be a non-negative integer',
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
this.#maxEntryCount = maxEntryCount
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
62
|
this.#db = new DatabaseSync(opts?.location ?? ':memory:')
|
|
112
63
|
|
|
113
64
|
this.#db.exec(`
|
|
@@ -180,30 +131,17 @@ export class SqliteCacheStore {
|
|
|
180
131
|
`DELETE FROM cacheInterceptorV${VERSION} WHERE url = ?`,
|
|
181
132
|
)
|
|
182
133
|
|
|
183
|
-
this.#countEntriesQuery = this.#db.prepare(
|
|
184
|
-
`SELECT COUNT(*) AS total FROM cacheInterceptorV${VERSION}`,
|
|
185
|
-
)
|
|
186
|
-
|
|
187
134
|
this.#deleteExpiredValuesQuery = this.#db.prepare(
|
|
188
135
|
`DELETE FROM cacheInterceptorV${VERSION} WHERE deleteAt <= ?`,
|
|
189
136
|
)
|
|
190
137
|
|
|
191
|
-
this.#
|
|
192
|
-
this.#
|
|
193
|
-
|
|
194
|
-
: this.#db.prepare(`
|
|
195
|
-
DELETE FROM cacheInterceptorV${VERSION}
|
|
196
|
-
WHERE id IN (
|
|
197
|
-
SELECT
|
|
198
|
-
id
|
|
199
|
-
FROM cacheInterceptorV${VERSION}
|
|
200
|
-
ORDER BY cachedAt DESC
|
|
201
|
-
LIMIT ?
|
|
202
|
-
)
|
|
203
|
-
`)
|
|
138
|
+
this.#pruneInterval = setInterval(() => {
|
|
139
|
+
this.#deleteExpiredValuesQuery.run(Date.now())
|
|
140
|
+
}, 60e3)
|
|
204
141
|
}
|
|
205
142
|
|
|
206
143
|
close() {
|
|
144
|
+
clearInterval(this.#pruneInterval)
|
|
207
145
|
this.#db.close()
|
|
208
146
|
}
|
|
209
147
|
|
|
@@ -227,16 +165,11 @@ export class SqliteCacheStore {
|
|
|
227
165
|
assertCacheValue(value)
|
|
228
166
|
|
|
229
167
|
const body = Array.isArray(value.body) ? Buffer.concat(value.body) : value.body
|
|
230
|
-
if ((body?.byteLength ?? 0) > this.#maxEntrySize) {
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
168
|
|
|
234
169
|
assert(Number.isFinite(value.start))
|
|
235
170
|
assert(Number.isFinite(value.end))
|
|
236
171
|
assert(!body || body?.byteLength === value.end - value.start)
|
|
237
172
|
|
|
238
|
-
this.#prune()
|
|
239
|
-
|
|
240
173
|
this.#insertValueQuery.run(
|
|
241
174
|
makeValueUrl(key),
|
|
242
175
|
key.method,
|
|
@@ -266,39 +199,6 @@ export class SqliteCacheStore {
|
|
|
266
199
|
this.#deleteByUrlQuery.run(makeValueUrl(key))
|
|
267
200
|
}
|
|
268
201
|
|
|
269
|
-
#prune() {
|
|
270
|
-
if (this.size <= this.#maxEntryCount) {
|
|
271
|
-
return 0
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
{
|
|
275
|
-
const removed = this.#deleteExpiredValuesQuery.run(Date.now()).changes
|
|
276
|
-
if (removed) {
|
|
277
|
-
return removed
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
{
|
|
282
|
-
const removed = this.#deleteOldValuesQuery?.run(
|
|
283
|
-
Math.max(Math.floor(this.#maxEntryCount * 0.1), 1),
|
|
284
|
-
).changes
|
|
285
|
-
if (removed) {
|
|
286
|
-
return removed
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return 0
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Counts the number of rows in the cache
|
|
295
|
-
* @returns {Number}
|
|
296
|
-
*/
|
|
297
|
-
get size() {
|
|
298
|
-
const { total } = this.#countEntriesQuery.get()
|
|
299
|
-
return total
|
|
300
|
-
}
|
|
301
|
-
|
|
302
202
|
/**
|
|
303
203
|
* @param {import('undici-types/cache-interceptor.d.ts').default.CacheKey} key
|
|
304
204
|
* @param {boolean} [canBeExpired=false]
|
|
@@ -407,22 +307,30 @@ function makeResult(value) {
|
|
|
407
307
|
}
|
|
408
308
|
}
|
|
409
309
|
|
|
310
|
+
function printType(val) {
|
|
311
|
+
return val == null ? 'null' : typeof val === 'object' ? val.constructor.name : typeof val
|
|
312
|
+
}
|
|
313
|
+
|
|
410
314
|
/**
|
|
411
315
|
* @param {any} key
|
|
412
316
|
*/
|
|
413
317
|
function assertCacheKey(key) {
|
|
414
|
-
if (typeof key !== 'object') {
|
|
415
|
-
throw new TypeError(`expected key to be object, got ${typeof key}`)
|
|
318
|
+
if (typeof key !== 'object' || key == null) {
|
|
319
|
+
throw new TypeError(`expected key to be object, got ${typeof printType(key)} [${key}]`)
|
|
416
320
|
}
|
|
417
321
|
|
|
418
322
|
for (const property of ['origin', 'method', 'path']) {
|
|
419
323
|
if (typeof key[property] !== 'string') {
|
|
420
|
-
throw new TypeError(
|
|
324
|
+
throw new TypeError(
|
|
325
|
+
`expected key.${property} to be string, got ${printType(key[property])} [${key[property]}]`,
|
|
326
|
+
)
|
|
421
327
|
}
|
|
422
328
|
}
|
|
423
329
|
|
|
424
330
|
if (key.headers !== undefined && typeof key.headers !== 'object') {
|
|
425
|
-
throw new TypeError(
|
|
331
|
+
throw new TypeError(
|
|
332
|
+
`expected headers to be object, got ${typeof printType(key)} [${key.headers}]`,
|
|
333
|
+
)
|
|
426
334
|
}
|
|
427
335
|
}
|
|
428
336
|
|
|
@@ -430,31 +338,39 @@ function assertCacheKey(key) {
|
|
|
430
338
|
* @param {any} value
|
|
431
339
|
*/
|
|
432
340
|
function assertCacheValue(value) {
|
|
433
|
-
if (typeof value !== 'object') {
|
|
434
|
-
throw new TypeError(`expected value to be object, got ${typeof value}`)
|
|
341
|
+
if (typeof value !== 'object' || value == null) {
|
|
342
|
+
throw new TypeError(`expected value to be object, got ${typeof printType(value)}`)
|
|
435
343
|
}
|
|
436
344
|
|
|
437
345
|
for (const property of ['statusCode', 'cachedAt', 'staleAt', 'deleteAt']) {
|
|
438
346
|
if (typeof value[property] !== 'number') {
|
|
439
|
-
throw new TypeError(
|
|
347
|
+
throw new TypeError(
|
|
348
|
+
`expected value.${property} to be number, got ${printType(value[property])} [${value[property]}]`,
|
|
349
|
+
)
|
|
440
350
|
}
|
|
441
351
|
}
|
|
442
352
|
|
|
443
353
|
if (typeof value.statusMessage !== 'string') {
|
|
444
354
|
throw new TypeError(
|
|
445
|
-
`expected value.statusMessage to be string, got ${
|
|
355
|
+
`expected value.statusMessage to be string, got ${printType(value.statusMessage)} [${value.statusMessage}]`,
|
|
446
356
|
)
|
|
447
357
|
}
|
|
448
358
|
|
|
449
359
|
if (value.headers != null && typeof value.headers !== 'object') {
|
|
450
|
-
throw new TypeError(
|
|
360
|
+
throw new TypeError(
|
|
361
|
+
`expected value.rawHeaders to be object, got ${printType(value.headers)} [${value.headers}]`,
|
|
362
|
+
)
|
|
451
363
|
}
|
|
452
364
|
|
|
453
365
|
if (value.vary !== undefined && typeof value.vary !== 'object') {
|
|
454
|
-
throw new TypeError(
|
|
366
|
+
throw new TypeError(
|
|
367
|
+
`expected value.vary to be object, got ${printType(value.vary)} [${value.vary}]`,
|
|
368
|
+
)
|
|
455
369
|
}
|
|
456
370
|
|
|
457
371
|
if (value.etag !== undefined && typeof value.etag !== 'string') {
|
|
458
|
-
throw new TypeError(
|
|
372
|
+
throw new TypeError(
|
|
373
|
+
`expected value.etag to be string, got ${printType(value.etag)} [${value.etag}]`,
|
|
374
|
+
)
|
|
459
375
|
}
|
|
460
376
|
}
|