@nxtedition/nxt-undici 3.2.2 → 3.3.0

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/index.js CHANGED
@@ -20,6 +20,31 @@ export const interceptors = {
20
20
  export { parseHeaders } from './utils.js'
21
21
  export { Client, Pool, Agent, getGlobalDispatcher, setGlobalDispatcher } from '@nxtedition/undici'
22
22
 
23
+ function wrapdDispatcher(dispatcher) {
24
+ let wrappedDispatcher = dispatcherCache.get(dispatcher)
25
+ if (wrappedDispatcher == null) {
26
+ wrappedDispatcher = dispatcher.compose(
27
+ interceptors.responseError(),
28
+ interceptors.requestBodyFactory(),
29
+ interceptors.log(),
30
+ interceptors.dns(),
31
+ interceptors.lookup(),
32
+ interceptors.requestId(),
33
+ interceptors.responseRetry(),
34
+ interceptors.responseVerify(),
35
+ interceptors.redirect(),
36
+ interceptors.cache(),
37
+ interceptors.proxy(),
38
+ )
39
+ dispatcherCache.set(dispatcher, wrappedDispatcher)
40
+ }
41
+ return wrappedDispatcher
42
+ }
43
+
44
+ export function dispatch(dispatcher, opts, handler) {
45
+ return wrapdDispatcher(opts.dispatcher ?? undici.getGlobalDispatcher()).dispatch(opts, handler)
46
+ }
47
+
23
48
  export async function request(url, opts) {
24
49
  // TODO (fix): More argument validation...
25
50
 
@@ -55,30 +80,11 @@ export async function request(url, opts) {
55
80
  headers['user-agent'] = userAgent
56
81
  }
57
82
 
58
- const baseDispatcher = opts.dispatcher ?? undici.getGlobalDispatcher()
59
-
60
- let dispatcher = dispatcherCache.get(baseDispatcher)
61
- if (dispatcher == null) {
62
- dispatcher = baseDispatcher.compose(
63
- interceptors.responseError(),
64
- interceptors.requestBodyFactory(),
65
- interceptors.log(),
66
- interceptors.dns(),
67
- interceptors.lookup(),
68
- interceptors.requestId(),
69
- interceptors.responseRetry(),
70
- interceptors.responseVerify(),
71
- interceptors.redirect(),
72
- interceptors.cache(),
73
- interceptors.proxy(),
74
- )
75
- dispatcherCache.set(baseDispatcher, dispatcher)
76
- }
77
-
78
- return await undici.request(url, {
83
+ return await wrapdDispatcher(opts.dispatcher ?? undici.getGlobalDispatcher()).request({
79
84
  id: opts.id,
85
+ origin: url.origin,
86
+ path: url.path ?? (url.search ? `${url.pathname}${url.search}` : url.pathname),
80
87
  method,
81
- dispatcher,
82
88
  body: opts.body,
83
89
  query: opts.query,
84
90
  headers,
@@ -97,6 +103,6 @@ export async function request(url, opts) {
97
103
  verify: opts.verify ?? true,
98
104
  logger: opts.logger ?? null,
99
105
  lookup: opts.lookup ?? null,
100
- dns: opts.dns ?? null,
106
+ dns: opts.dns ?? true,
101
107
  })
102
108
  }
@@ -127,8 +127,7 @@ export default (opts) => (dispatch) => (opts, handler) => {
127
127
 
128
128
  // TODO (fix): Cache other methods?
129
129
  if (opts.method !== 'GET' && opts.method !== 'HEAD') {
130
- dispatch(opts, handler)
131
- return
130
+ return dispatch(opts, handler)
132
131
  }
133
132
 
134
133
  if (opts.headers?.['cache-control'] || opts.headers?.authorization) {
@@ -141,8 +140,7 @@ export default (opts) => (dispatch) => (opts, handler) => {
141
140
  // cacheControl['min-fresh']
142
141
  // cacheControl['no-transform']
143
142
  // cacheControl['only-if-cached']
144
- dispatch(opts, handler)
145
- return
143
+ return dispatch(opts, handler)
146
144
  }
147
145
 
148
146
  // TODO (fix): Support body...
@@ -1,19 +1,20 @@
1
1
  import assert from 'node:assert'
2
2
  import { DecoratorHandler } from '../utils.js'
3
3
  import CacheableLookup from 'cacheable-lookup'
4
+ import net from 'net'
4
5
 
5
- let DEFAULT_DNS
6
+ const DEFAULT_RESOLVER = new CacheableLookup()
6
7
 
7
8
  class Handler extends DecoratorHandler {
8
9
  #handler
9
- #store
10
+ #resolver
10
11
  #key
11
12
 
12
- constructor({ store, key }, { handler }) {
13
+ constructor({ resolver, key }, { handler }) {
13
14
  super(handler)
14
15
 
15
16
  this.#handler = handler
16
- this.#store = store
17
+ this.#resolver = resolver
17
18
  this.#key = key
18
19
  }
19
20
 
@@ -28,53 +29,66 @@ class Handler extends DecoratorHandler {
28
29
  'ENETUNREACH',
29
30
  'EHOSTDOWN',
30
31
  'EHOSTUNREACH',
32
+ 'ENODATA',
31
33
  'EPIPE',
32
34
  ].includes(err.code)
33
35
  ) {
34
- this.#store.clear(this.#key)
36
+ this.#resolver.clear(this.#key)
35
37
  }
36
38
 
37
39
  return this.#handler.onError(err)
38
40
  }
39
41
  }
40
42
 
41
- export default (opts) => (dispatch) => (opts, handler) => {
42
- const dns = opts.dns ?? (DEFAULT_DNS ??= new CacheableLookup())
43
+ export default (interceptorOpts) => (dispatch) => (opts, handler) => {
44
+ const dns = opts.dns
43
45
 
44
46
  if (!dns) {
45
- dispatch(opts, handler)
46
- return
47
+ return dispatch(opts, handler)
47
48
  }
48
49
 
49
- try {
50
- assert(typeof dns.lookup === 'function')
51
- assert(typeof dns.clear === 'function')
50
+ const {
51
+ resolver = interceptorOpts?.resolver ?? DEFAULT_RESOLVER,
52
+ family = interceptorOpts?.family,
53
+ hints = interceptorOpts?.hints,
54
+ order = interceptorOpts?.order ?? 'ipv4first',
55
+ all = interceptorOpts?.all ?? true,
56
+ } = dns
57
+
58
+ assert(typeof resolver.lookup === 'function')
52
59
 
53
- const { hostname } = new URL(opts.origin)
60
+ const { hostname } = new URL(opts.origin)
54
61
 
55
- const callback = (err, entries) => {
62
+ if (net.isIP(hostname)) {
63
+ dispatch(opts, handler)
64
+ } else {
65
+ const callback = (err, val) => {
56
66
  if (err) {
57
67
  handler.onConnect(() => {})
58
68
  handler.onError(err)
59
69
  } else {
60
70
  const url = new URL(opts.origin)
61
- url.hostname = entries[Math.floor(entries.length * Math.random())].address
71
+ url.hostname = Array.isArray(val)
72
+ ? val[Math.floor(val.length * Math.random())].address
73
+ : val?.address ?? val
62
74
  dispatch(
63
75
  { ...opts, origin: url.origin },
64
- new Handler({ store: dns, key: hostname }, { handler }),
76
+ resolver.clear ? new Handler({ resolver, key: hostname }, { handler }) : handler,
65
77
  )
66
78
  }
67
79
  }
68
80
 
69
- const thenable = dns.lookup(hostname, { all: true }, callback)
70
- if (typeof thenable?.then === 'function') {
71
- thenable.then(
72
- (val) => callback(null, val),
73
- (err) => callback(err),
74
- )
81
+ try {
82
+ const thenable = resolver.lookup(hostname, { family, hints, order, all }, callback)
83
+ if (typeof thenable?.then === 'function') {
84
+ thenable.then(
85
+ (val) => callback(null, val),
86
+ (err) => callback(err),
87
+ )
88
+ }
89
+ } catch (err) {
90
+ handler.onConnect(() => {})
91
+ handler.onError(err)
75
92
  }
76
- } catch (err) {
77
- handler.onConnect(() => {})
78
- handler.onError(err)
79
93
  }
80
94
  }
@@ -2,8 +2,7 @@ export default (opts) => (dispatch) => (opts, handler) => {
2
2
  const lookup = opts.lookup
3
3
 
4
4
  if (!lookup) {
5
- dispatch(opts, handler)
6
- return
5
+ return dispatch(opts, handler)
7
6
  }
8
7
 
9
8
  const callback = (err, origin) => {
@@ -24,6 +23,6 @@ export default (opts) => (dispatch) => (opts, handler) => {
24
23
  )
25
24
  }
26
25
  } catch (err) {
27
- queueMicrotask(() => callback(err))
26
+ callback(err)
28
27
  }
29
28
  }
@@ -55,45 +55,6 @@ class Handler extends DecoratorHandler {
55
55
  }
56
56
  }
57
57
 
58
- export default (opts) => (dispatch) => (opts, handler) => {
59
- if (!opts.proxy) {
60
- return dispatch(opts, handler)
61
- }
62
-
63
- const expectsPayload =
64
- opts.method === 'PUT' ||
65
- opts.method === 'POST' ||
66
- opts.method === 'PATCH' ||
67
- opts.method === 'QUERY'
68
-
69
- const headers = reduceHeaders(
70
- {
71
- headers: opts.headers ?? {},
72
- httpVersion: opts.proxy.httpVersion ?? opts.proxy.req?.httpVersion,
73
- socket: opts.proxy.socket ?? opts.proxy.req?.socket,
74
- proxyName: opts.proxy.name,
75
- },
76
- (obj, key, val) => {
77
- if (key === 'content-length' && !expectsPayload) {
78
- // https://tools.ietf.org/html/rfc7230#section-3.3.2
79
- // A user agent SHOULD NOT send a Content-Length header field when
80
- // the request message does not contain a payload body and the method
81
- // semantics do not anticipate such a body.
82
- // undici will error if provided an unexpected content-length: 0 header.
83
- }
84
- if (key === 'expect') {
85
- // undici doesn't support expect header.
86
- } else {
87
- obj[key] = val
88
- }
89
- return obj
90
- },
91
- {},
92
- )
93
-
94
- return dispatch({ ...opts, headers }, new Handler(opts.proxy, { handler }))
95
- }
96
-
97
58
  // This expression matches hop-by-hop headers.
98
59
  // These headers are meaningful only for a single transport-level connection,
99
60
  // and must not be retransmitted by proxies or cached.
@@ -201,3 +162,42 @@ function printIp(address, port) {
201
162
  }
202
163
  return str
203
164
  }
165
+
166
+ export default (opts) => (dispatch) => (opts, handler) => {
167
+ if (!opts.proxy) {
168
+ return dispatch(opts, handler)
169
+ }
170
+
171
+ const expectsPayload =
172
+ opts.method === 'PUT' ||
173
+ opts.method === 'POST' ||
174
+ opts.method === 'PATCH' ||
175
+ opts.method === 'QUERY'
176
+
177
+ const headers = reduceHeaders(
178
+ {
179
+ headers: opts.headers ?? {},
180
+ httpVersion: opts.proxy.httpVersion ?? opts.proxy.req?.httpVersion,
181
+ socket: opts.proxy.socket ?? opts.proxy.req?.socket,
182
+ proxyName: opts.proxy.name,
183
+ },
184
+ (obj, key, val) => {
185
+ if (key === 'content-length' && !expectsPayload) {
186
+ // https://tools.ietf.org/html/rfc7230#section-3.3.2
187
+ // A user agent SHOULD NOT send a Content-Length header field when
188
+ // the request message does not contain a payload body and the method
189
+ // semantics do not anticipate such a body.
190
+ // undici will error if provided an unexpected content-length: 0 header.
191
+ }
192
+ if (key === 'expect') {
193
+ // undici doesn't support expect header.
194
+ } else {
195
+ obj[key] = val
196
+ }
197
+ return obj
198
+ },
199
+ {},
200
+ )
201
+
202
+ return dispatch({ ...opts, headers }, new Handler(opts.proxy, { handler }))
203
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "3.2.2",
3
+ "version": "3.3.0",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",