undici 7.7.0 → 7.9.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/README.md CHANGED
@@ -261,12 +261,22 @@ const readableWebStream = response.body
261
261
  const readableNodeStream = Readable.fromWeb(readableWebStream)
262
262
  ```
263
263
 
264
- #### Specification Compliance
264
+ ## Specification Compliance
265
265
 
266
- This section documents parts of the [Fetch Standard](https://fetch.spec.whatwg.org) that Undici does
266
+ This section documents parts of the [HTTP/1.1](https://www.rfc-editor.org/rfc/rfc9110.html) and [Fetch Standard](https://fetch.spec.whatwg.org) that Undici does
267
267
  not support or does not fully implement.
268
268
 
269
- ##### Garbage Collection
269
+ #### CORS
270
+
271
+ Unlike browsers, Undici does not implement CORS (Cross-Origin Resource Sharing) checks by default. This means:
272
+
273
+ - No preflight requests are automatically sent for cross-origin requests
274
+ - No validation of `Access-Control-Allow-Origin` headers is performed
275
+ - Requests to any origin are allowed regardless of the source
276
+
277
+ This behavior is intentional for server-side environments where CORS restrictions are typically unnecessary. If your application requires CORS-like protections, you will need to implement these checks manually.
278
+
279
+ #### Garbage Collection
270
280
 
271
281
  * https://fetch.spec.whatwg.org/#garbage-collection
272
282
 
@@ -307,7 +317,7 @@ const headers = await fetch(url, { method: 'HEAD' })
307
317
  .then(res => res.headers)
308
318
  ```
309
319
 
310
- ##### Forbidden and Safelisted Header Names
320
+ #### Forbidden and Safelisted Header Names
311
321
 
312
322
  * https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
313
323
  * https://fetch.spec.whatwg.org/#forbidden-header-name
@@ -316,7 +326,7 @@ const headers = await fetch(url, { method: 'HEAD' })
316
326
 
317
327
  The [Fetch Standard](https://fetch.spec.whatwg.org) requires implementations to exclude certain headers from requests and responses. In browser environments, some headers are forbidden so the user agent remains in full control over them. In Undici, these constraints are removed to give more control to the user.
318
328
 
319
- ### `undici.upgrade([url, options]): Promise`
329
+ #### `undici.upgrade([url, options]): Promise`
320
330
 
321
331
  Upgrade to a different protocol. See [MDN - HTTP - Protocol upgrade mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) for more details.
322
332
 
@@ -378,12 +388,7 @@ Returns: `URL`
378
388
  * **protocol** `string` (optional)
379
389
  * **search** `string` (optional)
380
390
 
381
- ## Specification Compliance
382
-
383
- This section documents parts of the HTTP/1.1 specification that Undici does
384
- not support or does not fully implement.
385
-
386
- ### Expect
391
+ #### Expect
387
392
 
388
393
  Undici does not support the `Expect` request header field. The request
389
394
  body is always immediately sent and the `100 Continue` response will be
@@ -391,7 +396,7 @@ ignored.
391
396
 
392
397
  Refs: https://tools.ietf.org/html/rfc7231#section-5.1.1
393
398
 
394
- ### Pipelining
399
+ #### Pipelining
395
400
 
396
401
  Undici will only use pipelining if configured with a `pipelining` factor
397
402
  greater than `1`. Also it is important to pass `blocking: false` to the
@@ -412,7 +417,7 @@ aborted.
412
417
  * Refs: https://tools.ietf.org/html/rfc2616#section-8.1.2.2
413
418
  * Refs: https://tools.ietf.org/html/rfc7230#section-6.3.2
414
419
 
415
- ### Manual Redirect
420
+ #### Manual Redirect
416
421
 
417
422
  Since it is not possible to manually follow an HTTP redirect on the server-side,
418
423
  Undici returns the actual response instead of an `opaqueredirect` filtered one
@@ -421,9 +426,9 @@ implementations in Deno and Cloudflare Workers.
421
426
 
422
427
  Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
423
428
 
424
- ## Workarounds
429
+ ### Workarounds
425
430
 
426
- ### Network address family autoselection.
431
+ #### Network address family autoselection.
427
432
 
428
433
  If you experience problem when connecting to a remote server that is resolved by your DNS servers to a IPv6 (AAAA record)
429
434
  first, there are chances that your local router or ISP might have problem connecting to IPv6 networks. In that case
@@ -75,3 +75,9 @@ See [`Dispatcher.stream(options, factory[, callback])`](/docs/docs/api/Dispatche
75
75
  ### `Agent.upgrade(options[, callback])`
76
76
 
77
77
  See [`Dispatcher.upgrade(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherupgradeoptions-callback).
78
+
79
+ ### `Agent.stats()`
80
+
81
+ Returns an object of stats by origin in the format of `Record<string, TClientStats | TPoolStats>`
82
+
83
+ See [`PoolStats`](/docs/docs/api/PoolStats.md) and [`ClientStats`](/docs/docs/api/ClientStats.md).
@@ -0,0 +1,27 @@
1
+ # Class: ClientStats
2
+
3
+ Stats for a [Client](/docs/docs/api/Client.md).
4
+
5
+ ## `new ClientStats(client)`
6
+
7
+ Arguments:
8
+
9
+ * **client** `Client` - Client from which to return stats.
10
+
11
+ ## Instance Properties
12
+
13
+ ### `ClientStats.connected`
14
+
15
+ Boolean if socket as open connection by this client.
16
+
17
+ ### `ClientStats.pending`
18
+
19
+ Number of pending requests of this client.
20
+
21
+ ### `ClientStats.running`
22
+
23
+ Number of currently active requests across this client.
24
+
25
+ ### `ClientStats.size`
26
+
27
+ Number of active, pending, or queued requests of this clients.
@@ -19,7 +19,7 @@ diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => {
19
19
  console.log('completed', request.completed)
20
20
  console.log('method', request.method)
21
21
  console.log('path', request.path)
22
- console.log('headers') // array of strings, e.g: ['foo', 'bar']
22
+ console.log('headers', request.headers) // array of strings, e.g: ['foo', 'bar']
23
23
  request.addHeader('hello', 'world')
24
24
  console.log('headers', request.headers) // e.g. ['foo', 'bar', 'hello', 'world']
25
25
  })
@@ -20,6 +20,8 @@ Extends: [`AgentOptions`](/docs/docs/api/Agent.md#parameter-agentoptions)
20
20
 
21
21
  * **ignoreTrailingSlash** `boolean` (optional) - Default: `false` - set the default value for `ignoreTrailingSlash` for interceptors.
22
22
 
23
+ * **acceptNonStandardSearchParameters** `boolean` (optional) - Default: `false` - set to `true` if the matcher should also accept non standard search parameters such as multi-value items specified with `[]` (e.g. `param[]=1&param[]=2&param[]=3`) and multi-value items which values are comma separated (e.g. `param=1,2,3`).
24
+
23
25
  ### Example - Basic MockAgent instantiation
24
26
 
25
27
  This will instantiate the MockAgent. It will not do anything until registered as the agent to use with requests and mock interceptions are added.
@@ -477,7 +479,7 @@ This method returns any pending interceptors registered on a mock agent. A pendi
477
479
 
478
480
  Returns: `PendingInterceptor[]` (where `PendingInterceptor` is a `MockDispatch` with an additional `origin: string`)
479
481
 
480
- #### Example - List all pending inteceptors
482
+ #### Example - List all pending interceptors
481
483
 
482
484
  ```js
483
485
  const agent = new MockAgent()
@@ -76,17 +76,9 @@ class MemoryCacheStore {
76
76
  const topLevelKey = `${key.origin}:${key.path}`
77
77
 
78
78
  const now = Date.now()
79
- const entry = this.#entries.get(topLevelKey)?.find((entry) => (
80
- entry.deleteAt > now &&
81
- entry.method === key.method &&
82
- (entry.vary == null || Object.keys(entry.vary).every(headerName => {
83
- if (entry.vary[headerName] === null) {
84
- return key.headers[headerName] === undefined
85
- }
79
+ const entries = this.#entries.get(topLevelKey)
86
80
 
87
- return entry.vary[headerName] === key.headers[headerName]
88
- }))
89
- ))
81
+ const entry = entries ? findEntry(key, entries, now) : null
90
82
 
91
83
  return entry == null
92
84
  ? undefined
@@ -140,10 +132,17 @@ class MemoryCacheStore {
140
132
  entries = []
141
133
  store.#entries.set(topLevelKey, entries)
142
134
  }
143
- entries.push(entry)
135
+ const previousEntry = findEntry(key, entries, Date.now())
136
+ if (previousEntry) {
137
+ const index = entries.indexOf(previousEntry)
138
+ entries.splice(index, 1, entry)
139
+ store.#size -= previousEntry.size
140
+ } else {
141
+ entries.push(entry)
142
+ store.#count += 1
143
+ }
144
144
 
145
145
  store.#size += entry.size
146
- store.#count += 1
147
146
 
148
147
  if (store.#size > store.#maxSize || store.#count > store.#maxCount) {
149
148
  for (const [key, entries] of store.#entries) {
@@ -180,4 +179,18 @@ class MemoryCacheStore {
180
179
  }
181
180
  }
182
181
 
182
+ function findEntry (key, entries, now) {
183
+ return entries.find((entry) => (
184
+ entry.deleteAt > now &&
185
+ entry.method === key.method &&
186
+ (entry.vary == null || Object.keys(entry.vary).every(headerName => {
187
+ if (entry.vary[headerName] === null) {
188
+ return key.headers[headerName] === undefined
189
+ }
190
+
191
+ return entry.vary[headerName] === key.headers[headerName]
192
+ }))
193
+ ))
194
+ }
195
+
183
196
  module.exports = MemoryCacheStore
@@ -115,6 +115,11 @@ module.exports = class SqliteCacheStore {
115
115
  this.#db = new DatabaseSync(opts?.location ?? ':memory:')
116
116
 
117
117
  this.#db.exec(`
118
+ PRAGMA journal_mode = WAL;
119
+ PRAGMA synchronous = NORMAL;
120
+ PRAGMA temp_store = memory;
121
+ PRAGMA optimize;
122
+
118
123
  CREATE TABLE IF NOT EXISTS cacheInterceptorV${VERSION} (
119
124
  -- Data specific to us
120
125
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -134,9 +139,8 @@ module.exports = class SqliteCacheStore {
134
139
  staleAt INTEGER NOT NULL
135
140
  );
136
141
 
137
- CREATE INDEX IF NOT EXISTS idx_cacheInterceptorV${VERSION}_url ON cacheInterceptorV${VERSION}(url);
138
- CREATE INDEX IF NOT EXISTS idx_cacheInterceptorV${VERSION}_method ON cacheInterceptorV${VERSION}(method);
139
- CREATE INDEX IF NOT EXISTS idx_cacheInterceptorV${VERSION}_deleteAt ON cacheInterceptorV${VERSION}(deleteAt);
142
+ CREATE INDEX IF NOT EXISTS idx_cacheInterceptorV${VERSION}_getValuesQuery ON cacheInterceptorV${VERSION}(url, method, deleteAt);
143
+ CREATE INDEX IF NOT EXISTS idx_cacheInterceptorV${VERSION}_deleteByUrlQuery ON cacheInterceptorV${VERSION}(deleteAt);
140
144
  `)
141
145
 
142
146
  this.#getValuesQuery = this.#db.prepare(`
@@ -346,7 +350,7 @@ module.exports = class SqliteCacheStore {
346
350
  }
347
351
 
348
352
  #prune () {
349
- if (this.size <= this.#maxCount) {
353
+ if (Number.isFinite(this.#maxCount) && this.size <= this.#maxCount) {
350
354
  return 0
351
355
  }
352
356
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { InvalidArgumentError } = require('../core/errors')
4
- const { kClients, kRunning, kClose, kDestroy, kDispatch } = require('../core/symbols')
4
+ const { kClients, kRunning, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
5
5
  const DispatcherBase = require('./dispatcher-base')
6
6
  const Pool = require('./pool')
7
7
  const Client = require('./client')
@@ -110,6 +110,16 @@ class Agent extends DispatcherBase {
110
110
 
111
111
  await Promise.all(destroyPromises)
112
112
  }
113
+
114
+ get stats () {
115
+ const allClientStats = {}
116
+ for (const client of this[kClients].values()) {
117
+ if (client.stats) {
118
+ allClientStats[client[kUrl].origin] = client.stats
119
+ }
120
+ }
121
+ return allClientStats
122
+ }
113
123
  }
114
124
 
115
125
  module.exports = Agent
@@ -295,11 +295,13 @@ function writeH2 (client, request) {
295
295
  if (Array.isArray(val)) {
296
296
  for (let i = 0; i < val.length; i++) {
297
297
  if (headers[key]) {
298
- headers[key] += `,${val[i]}`
298
+ headers[key] += `, ${val[i]}`
299
299
  } else {
300
300
  headers[key] = val[i]
301
301
  }
302
302
  }
303
+ } else if (headers[key]) {
304
+ headers[key] += `, ${val}`
303
305
  } else {
304
306
  headers[key] = val
305
307
  }
@@ -4,6 +4,7 @@ const assert = require('node:assert')
4
4
  const net = require('node:net')
5
5
  const http = require('node:http')
6
6
  const util = require('../core/util.js')
7
+ const { ClientStats } = require('../util/stats.js')
7
8
  const { channels } = require('../core/diagnostics.js')
8
9
  const Request = require('../core/request.js')
9
10
  const DispatcherBase = require('./dispatcher-base')
@@ -260,6 +261,10 @@ class Client extends DispatcherBase {
260
261
  this[kResume](true)
261
262
  }
262
263
 
264
+ get stats () {
265
+ return new ClientStats(this)
266
+ }
267
+
263
268
  get [kPending] () {
264
269
  return this[kQueue].length - this[kPendingIdx]
265
270
  }
@@ -1,9 +1,9 @@
1
1
  'use strict'
2
2
 
3
+ const { PoolStats } = require('../util/stats.js')
3
4
  const DispatcherBase = require('./dispatcher-base')
4
5
  const FixedQueue = require('./fixed-queue')
5
6
  const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = require('../core/symbols')
6
- const PoolStats = require('./pool-stats')
7
7
 
8
8
  const kClients = Symbol('clients')
9
9
  const kNeedDrain = Symbol('needDrain')
@@ -16,7 +16,6 @@ const kOnConnectionError = Symbol('onConnectionError')
16
16
  const kGetDispatcher = Symbol('get dispatcher')
17
17
  const kAddClient = Symbol('add client')
18
18
  const kRemoveClient = Symbol('remove client')
19
- const kStats = Symbol('stats')
20
19
 
21
20
  class PoolBase extends DispatcherBase {
22
21
  constructor () {
@@ -67,8 +66,6 @@ class PoolBase extends DispatcherBase {
67
66
  this[kOnConnectionError] = (origin, targets, err) => {
68
67
  pool.emit('connectionError', origin, [pool, ...targets], err)
69
68
  }
70
-
71
- this[kStats] = new PoolStats(this)
72
69
  }
73
70
 
74
71
  get [kBusy] () {
@@ -108,7 +105,7 @@ class PoolBase extends DispatcherBase {
108
105
  }
109
106
 
110
107
  get stats () {
111
- return this[kStats]
108
+ return new PoolStats(this)
112
109
  }
113
110
 
114
111
  async [kClose] () {
@@ -20,7 +20,12 @@ const { AbortError } = require('../core/errors.js')
20
20
  */
21
21
  function needsRevalidation (result, cacheControlDirectives) {
22
22
  if (cacheControlDirectives?.['no-cache']) {
23
- // Always revalidate requests with the no-cache directive
23
+ // Always revalidate requests with the no-cache request directive
24
+ return true
25
+ }
26
+
27
+ if (result.cacheControlDirectives?.['no-cache'] && !Array.isArray(result.cacheControlDirectives['no-cache'])) {
28
+ // Always revalidate requests with unqualified no-cache response directive
24
29
  return true
25
30
  }
26
31
 
@@ -233,7 +238,7 @@ function handleResult (
233
238
  }
234
239
 
235
240
  let headers = {
236
- ...normaliseHeaders(opts),
241
+ ...opts.headers,
237
242
  'if-modified-since': new Date(result.cachedAt).toUTCString()
238
243
  }
239
244
 
@@ -319,6 +324,11 @@ module.exports = (opts = {}) => {
319
324
  return dispatch(opts, handler)
320
325
  }
321
326
 
327
+ opts = {
328
+ ...opts,
329
+ headers: normaliseHeaders(opts)
330
+ }
331
+
322
332
  const reqCacheControl = opts.headers?.['cache-control']
323
333
  ? parseCacheControlHeader(opts.headers['cache-control'])
324
334
  : undefined
@@ -16,11 +16,12 @@ const {
16
16
  kMockAgentIsCallHistoryEnabled,
17
17
  kMockAgentAddCallHistoryLog,
18
18
  kMockAgentMockCallHistoryInstance,
19
+ kMockAgentAcceptsNonStandardSearchParameters,
19
20
  kMockCallHistoryAddLog
20
21
  } = require('./mock-symbols')
21
22
  const MockClient = require('./mock-client')
22
23
  const MockPool = require('./mock-pool')
23
- const { matchValue, buildAndValidateMockOptions } = require('./mock-utils')
24
+ const { matchValue, normalizeSearchParams, buildAndValidateMockOptions } = require('./mock-utils')
24
25
  const { InvalidArgumentError, UndiciError } = require('../core/errors')
25
26
  const Dispatcher = require('../dispatcher/dispatcher')
26
27
  const PendingInterceptorsFormatter = require('./pending-interceptors-formatter')
@@ -35,6 +36,7 @@ class MockAgent extends Dispatcher {
35
36
  this[kNetConnect] = true
36
37
  this[kIsMockActive] = true
37
38
  this[kMockAgentIsCallHistoryEnabled] = mockOptions?.enableCallHistory ?? false
39
+ this[kMockAgentAcceptsNonStandardSearchParameters] = mockOptions?.acceptNonStandardSearchParameters ?? false
38
40
 
39
41
  // Instantiate Agent and encapsulate
40
42
  if (opts?.agent && typeof opts.agent.dispatch !== 'function') {
@@ -67,7 +69,17 @@ class MockAgent extends Dispatcher {
67
69
 
68
70
  this[kMockAgentAddCallHistoryLog](opts)
69
71
 
70
- return this[kAgent].dispatch(opts, handler)
72
+ const acceptNonStandardSearchParameters = this[kMockAgentAcceptsNonStandardSearchParameters]
73
+
74
+ const dispatchOpts = { ...opts }
75
+
76
+ if (acceptNonStandardSearchParameters && dispatchOpts.path) {
77
+ const [path, searchParams] = dispatchOpts.path.split('?')
78
+ const normalizedSearchParams = normalizeSearchParams(searchParams, acceptNonStandardSearchParameters)
79
+ dispatchOpts.path = `${path}?${normalizedSearchParams}`
80
+ }
81
+
82
+ return this[kAgent].dispatch(dispatchOpts, handler)
71
83
  }
72
84
 
73
85
  async close () {
@@ -26,5 +26,6 @@ module.exports = {
26
26
  kMockAgentRegisterCallHistory: Symbol('mock agent register mock call history'),
27
27
  kMockAgentAddCallHistoryLog: Symbol('mock agent add call history log'),
28
28
  kMockAgentIsCallHistoryEnabled: Symbol('mock agent is call history enabled'),
29
+ kMockAgentAcceptsNonStandardSearchParameters: Symbol('mock agent accepts non standard search parameters'),
29
30
  kMockCallHistoryAddLog: Symbol('mock call history add log')
30
31
  }
@@ -92,13 +92,42 @@ function matchHeaders (mockDispatch, headers) {
92
92
  return true
93
93
  }
94
94
 
95
+ function normalizeSearchParams (query) {
96
+ if (typeof query !== 'string') {
97
+ return query
98
+ }
99
+
100
+ const originalQp = new URLSearchParams(query)
101
+ const normalizedQp = new URLSearchParams()
102
+
103
+ for (let [key, value] of originalQp.entries()) {
104
+ key = key.replace('[]', '')
105
+
106
+ const valueRepresentsString = /^(['"]).*\1$/.test(value)
107
+ if (valueRepresentsString) {
108
+ normalizedQp.append(key, value)
109
+ continue
110
+ }
111
+
112
+ if (value.includes(',')) {
113
+ const values = value.split(',')
114
+ for (const v of values) {
115
+ normalizedQp.append(key, v)
116
+ }
117
+ continue
118
+ }
119
+
120
+ normalizedQp.append(key, value)
121
+ }
122
+
123
+ return normalizedQp
124
+ }
125
+
95
126
  function safeUrl (path) {
96
127
  if (typeof path !== 'string') {
97
128
  return path
98
129
  }
99
-
100
130
  const pathSegments = path.split('?', 3)
101
-
102
131
  if (pathSegments.length !== 2) {
103
132
  return path
104
133
  }
@@ -376,6 +405,10 @@ function buildAndValidateMockOptions (opts) {
376
405
  throw new InvalidArgumentError('options.enableCallHistory must to be a boolean')
377
406
  }
378
407
 
408
+ if ('acceptNonStandardSearchParameters' in mockOptions && typeof mockOptions.acceptNonStandardSearchParameters !== 'boolean') {
409
+ throw new InvalidArgumentError('options.acceptNonStandardSearchParameters must to be a boolean')
410
+ }
411
+
379
412
  return mockOptions
380
413
  }
381
414
  }
@@ -395,5 +428,6 @@ module.exports = {
395
428
  checkNetConnect,
396
429
  buildAndValidateMockOptions,
397
430
  getHeaderByName,
398
- buildHeadersFromArray
431
+ buildHeadersFromArray,
432
+ normalizeSearchParams
399
433
  }
package/lib/util/cache.js CHANGED
@@ -12,13 +12,11 @@ function makeCacheKey (opts) {
12
12
  throw new Error('opts.origin is undefined')
13
13
  }
14
14
 
15
- const headers = normaliseHeaders(opts)
16
-
17
15
  return {
18
16
  origin: opts.origin.toString(),
19
17
  method: opts.method,
20
18
  path: opts.path,
21
- headers
19
+ headers: opts.headers
22
20
  }
23
21
  }
24
22
 
@@ -0,0 +1,32 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ kConnected,
5
+ kPending,
6
+ kRunning,
7
+ kSize,
8
+ kFree,
9
+ kQueued
10
+ } = require('../core/symbols')
11
+
12
+ class ClientStats {
13
+ constructor (client) {
14
+ this.connected = client[kConnected]
15
+ this.pending = client[kPending]
16
+ this.running = client[kRunning]
17
+ this.size = client[kSize]
18
+ }
19
+ }
20
+
21
+ class PoolStats {
22
+ constructor (pool) {
23
+ this.connected = pool[kConnected]
24
+ this.free = pool[kFree]
25
+ this.pending = pool[kPending]
26
+ this.queued = pool[kQueued]
27
+ this.running = pool[kRunning]
28
+ this.size = pool[kSize]
29
+ }
30
+ }
31
+
32
+ module.exports = { ClientStats, PoolStats }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.7.0",
3
+ "version": "7.9.0",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -116,7 +116,7 @@
116
116
  "c8": "^10.0.0",
117
117
  "cross-env": "^7.0.3",
118
118
  "dns-packet": "^5.4.0",
119
- "esbuild": "^0.24.0",
119
+ "esbuild": "^0.25.2",
120
120
  "eslint": "^9.9.0",
121
121
  "fast-check": "^3.17.1",
122
122
  "https-pem": "^3.0.0",
package/types/agent.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { URL } from 'url'
2
2
  import Pool from './pool'
3
3
  import Dispatcher from './dispatcher'
4
+ import TClientStats from './client-stats'
5
+ import TPoolStats from './pool-stats'
4
6
 
5
7
  export default Agent
6
8
 
@@ -12,6 +14,8 @@ declare class Agent extends Dispatcher {
12
14
  destroyed: boolean
13
15
  /** Dispatches a request. */
14
16
  dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
17
+ /** Aggregate stats for a Agent by origin. */
18
+ readonly stats: Record<string, TClientStats | TPoolStats>
15
19
  }
16
20
 
17
21
  declare namespace Agent {
@@ -0,0 +1,15 @@
1
+ import Client from './client'
2
+
3
+ export default ClientStats
4
+
5
+ declare class ClientStats {
6
+ constructor (pool: Client)
7
+ /** If socket has open connection. */
8
+ connected: boolean
9
+ /** Number of open socket connections in this client that do not have an active request. */
10
+ pending: number
11
+ /** Number of currently active requests of this client. */
12
+ running: number
13
+ /** Number of active, pending, or queued requests of this client. */
14
+ size: number
15
+ }
package/types/client.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { URL } from 'url'
2
2
  import Dispatcher from './dispatcher'
3
3
  import buildConnector from './connector'
4
+ import TClientStats from './client-stats'
4
5
 
5
6
  type ClientConnectOptions = Omit<Dispatcher.ConnectOptions, 'origin'>
6
7
 
@@ -15,6 +16,8 @@ export class Client extends Dispatcher {
15
16
  closed: boolean
16
17
  /** `true` after `client.destroyed()` has been called or `client.close()` has been called and the client shutdown has completed. */
17
18
  destroyed: boolean
19
+ /** Aggregate stats for a Client. */
20
+ readonly stats: TClientStats
18
21
 
19
22
  // Override dispatcher APIs.
20
23
  override connect (
@@ -84,13 +87,13 @@ export declare namespace Client {
84
87
  /**
85
88
  * @description Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
86
89
  * @default false
87
- */
90
+ */
88
91
  allowH2?: boolean;
89
92
  /**
90
93
  * @description Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
91
94
  * @default 100
92
- */
93
- maxConcurrentStreams?: number
95
+ */
96
+ maxConcurrentStreams?: number;
94
97
  }
95
98
  export interface SocketInfo {
96
99
  localAddress?: string
@@ -59,6 +59,9 @@ declare namespace MockAgent {
59
59
  /** Ignore trailing slashes in the path */
60
60
  ignoreTrailingSlash?: boolean;
61
61
 
62
+ /** Accept URLs with search parameters using non standard syntaxes. default false */
63
+ acceptNonStandardSearchParameters?: boolean;
64
+
62
65
  /** Enable call history. you can either call MockAgent.enableCallHistory(). default false */
63
66
  enableCallHistory?: boolean
64
67
  }
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
 
3
3
  import type { Blob } from 'buffer'
4
+ import type { ReadableStream, WritableStream } from 'stream/web'
4
5
  import type { MessagePort } from 'worker_threads'
5
6
  import {
6
7
  EventInit,
@@ -1,36 +0,0 @@
1
- 'use strict'
2
-
3
- const { kFree, kConnected, kPending, kQueued, kRunning, kSize } = require('../core/symbols')
4
- const kPool = Symbol('pool')
5
-
6
- class PoolStats {
7
- constructor (pool) {
8
- this[kPool] = pool
9
- }
10
-
11
- get connected () {
12
- return this[kPool][kConnected]
13
- }
14
-
15
- get free () {
16
- return this[kPool][kFree]
17
- }
18
-
19
- get pending () {
20
- return this[kPool][kPending]
21
- }
22
-
23
- get queued () {
24
- return this[kPool][kQueued]
25
- }
26
-
27
- get running () {
28
- return this[kPool][kRunning]
29
- }
30
-
31
- get size () {
32
- return this[kPool][kSize]
33
- }
34
- }
35
-
36
- module.exports = PoolStats