@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.
@@ -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 = url.host ?? `${url.hostname}:${url.port ?? (protocol === 'https:' ? 443 : 80)}`
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 {import('node:sqlite').StatementSync}
61
- */
62
- #countEntriesQuery
63
-
64
- /**
65
- * @type {import('node:sqlite').StatementSync | null}
54
+ * @type {NodeJS.Timeout}
66
55
  */
67
- #deleteOldValuesQuery
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.#deleteOldValuesQuery =
192
- this.#maxEntryCount === Infinity
193
- ? null
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(`expected key.${property} to be string, got ${typeof key[property]}`)
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(`expected headers to be object, got ${typeof key}`)
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(`expected value.${property} to be number, got ${typeof value[property]}`)
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 ${typeof value.statusMessage}`,
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(`expected value.rawHeaders to be object, got ${typeof value.headers}`)
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(`expected value.vary to be object, got ${typeof value.vary}`)
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(`expected value.etag to be string, got ${typeof value.etag}`)
372
+ throw new TypeError(
373
+ `expected value.etag to be string, got ${printType(value.etag)} [${value.etag}]`,
374
+ )
459
375
  }
460
376
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "6.2.19",
3
+ "version": "6.2.22",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",