@nxtedition/nxt-undici 6.1.7 → 6.2.2

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.
@@ -95,7 +95,6 @@ class CacheHandler extends DecoratorHandler {
95
95
  }
96
96
 
97
97
  const cachedAt = Date.now()
98
-
99
98
  this.#value = {
100
99
  body: [],
101
100
  size: 0,
@@ -104,7 +103,7 @@ class CacheHandler extends DecoratorHandler {
104
103
  statusMessage: '',
105
104
  headers,
106
105
  cacheControlDirectives,
107
- etag: headers.etag,
106
+ etag: typeof headers.etag === 'string' && isEtagUsable(headers.etag) ? headers.etag : '',
108
107
  vary,
109
108
  cachedAt,
110
109
  staleAt: 0,
@@ -213,3 +212,37 @@ export default () => (dispatch) => (opts, handler) => {
213
212
  abort(err)
214
213
  }
215
214
  }
215
+
216
+ /**
217
+ * Note: this deviates from the spec a little. Empty etags ("", W/"") are valid,
218
+ * however, including them in cached resposnes serves little to no purpose.
219
+ *
220
+ * @see https://www.rfc-editor.org/rfc/rfc9110.html#name-etag
221
+ *
222
+ * @param {string} etag
223
+ * @returns {boolean}
224
+ */
225
+ function isEtagUsable(etag) {
226
+ if (etag.length <= 2) {
227
+ // Shortest an etag can be is two chars (just ""). This is where we deviate
228
+ // from the spec requiring a min of 3 chars however
229
+ return false
230
+ }
231
+
232
+ if (etag[0] === '"' && etag[etag.length - 1] === '"') {
233
+ // ETag: ""asd123"" or ETag: "W/"asd123"", kinda undefined behavior in the
234
+ // spec. Some servers will accept these while others don't.
235
+ // ETag: "asd123"
236
+ return !(etag[1] === '"' || etag.startsWith('"W/'))
237
+ }
238
+
239
+ if (etag.startsWith('W/"') && etag[etag.length - 1] === '"') {
240
+ // ETag: W/"", also where we deviate from the spec & require a min of 3
241
+ // chars
242
+ // ETag: for W/"", W/"asd123"
243
+ return etag.length !== 4
244
+ }
245
+
246
+ // Anything else
247
+ return false
248
+ }
@@ -28,16 +28,35 @@ class Handler extends DecoratorHandler {
28
28
  }
29
29
  }
30
30
 
31
+ class DefaultCache {
32
+ #map = new Map()
33
+
34
+ set(hostname, records) {
35
+ this.#map.set(hostname, records)
36
+ }
37
+
38
+ get(hostname) {
39
+ return this.#map.get(hostname)
40
+ }
41
+
42
+ delete(hostname) {
43
+ this.#map.delete(hostname)
44
+ }
45
+ }
46
+
47
+ const DEFAULT_CACHES = new Map()
48
+
31
49
  export default () => (dispatch) => {
32
- const cache = new Map()
33
50
  const promises = new Map()
34
51
 
35
- function resolve(hostname, { resolve4, logger }) {
52
+ function resolve(hostname, { cache, resolve4, logger }) {
36
53
  let promise = promises.get(hostname)
37
54
  if (!promise) {
38
55
  logger?.debug({ dns: { hostname } }, 'lookup started')
39
56
  promise = new Promise((resolve) =>
40
57
  resolve4(hostname, { ttl: true }, (err, records) => {
58
+ promises.delete(hostname)
59
+
41
60
  if (err) {
42
61
  logger?.error({ err, dns: { hostname } }, 'lookup failed')
43
62
  resolve([err, null])
@@ -51,14 +70,12 @@ export default () => (dispatch) => {
51
70
  counter: 0,
52
71
  }))
53
72
 
54
- logger?.debug({ err, dns: { records } }, 'lookup completed')
73
+ logger?.debug({ err, dns: { hostname, records } }, 'lookup completed')
55
74
 
56
75
  cache.set(hostname, val)
57
76
 
58
77
  resolve([null, val])
59
78
  }
60
-
61
- assert(promises.delete(hostname))
62
79
  }),
63
80
  )
64
81
  promises.set(hostname, promise)
@@ -82,13 +99,19 @@ export default () => (dispatch) => {
82
99
 
83
100
  const now = getFastNow()
84
101
 
85
- let records = cache.get(hostname)
86
-
87
- const resolve4 = opts.dns.resolve4 || dns.resolve4
102
+ const resolve4 = opts.dns.resolve || opts.dns.resolve4 || dns.resolve4
88
103
  const logger = opts.dns.logger ?? opts.logger
89
104
 
105
+ let cache = opts.dns.cache ?? DEFAULT_CACHES.get(resolve4)
106
+ if (!cache) {
107
+ cache = new DefaultCache()
108
+ DEFAULT_CACHES.set(resolve4, cache)
109
+ }
110
+
111
+ let records = cache.get(hostname)
112
+
90
113
  if (records == null || records.every((x) => x.expires < now)) {
91
- const [err, val] = await resolve(hostname, { resolve4, logger })
114
+ const [err, val] = await resolve(hostname, { cache, resolve4, logger })
92
115
 
93
116
  if (err) {
94
117
  throw err
@@ -98,7 +121,7 @@ export default () => (dispatch) => {
98
121
 
99
122
  records = val
100
123
  } else if (records.some((x) => x.expires < now + 1e3)) {
101
- resolve(hostname, { resolve4, logger })
124
+ resolve(hostname, { cache, resolve4, logger })
102
125
  }
103
126
 
104
127
  records.sort(
package/lib/utils.js CHANGED
@@ -248,6 +248,10 @@ export class DecoratorHandler {
248
248
 
249
249
  onComplete(trailers) {
250
250
  if (!this.#aborted && !this.#completed && !this.#errored) {
251
+ <<<<<<< HEAD
252
+ this.#abort = null
253
+ =======
254
+ >>>>>>> 23823e3 (fix: decoerator)
251
255
  this.#completed = true
252
256
  return this.#handler.onComplete?.(trailers)
253
257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "6.1.7",
3
+ "version": "6.2.2",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",