New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

undici

Package Overview
Dependencies
Maintainers
3
Versions
234
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

undici - npm Package Compare versions

Comparing version 7.0.0-alpha.4 to 7.0.0-alpha.5

28

docs/docs/api/Agent.md

@@ -19,3 +19,3 @@ # Agent

Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)
Extends: [`PoolOptions`](/docs/docs/api/Pool.md#parameter-pooloptions)

@@ -28,7 +28,7 @@ * **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`

Implements [Client.closed](Client.md#clientclosed)
Implements [Client.closed](/docs/docs/api/Client.md#clientclosed)
### `Agent.destroyed`
Implements [Client.destroyed](Client.md#clientdestroyed)
Implements [Client.destroyed](/docs/docs/api/Client.md#clientdestroyed)

@@ -39,42 +39,42 @@ ## Instance Methods

Implements [`Dispatcher.close([callback])`](Dispatcher.md#dispatcherclosecallback-promise).
Implements [`Dispatcher.close([callback])`](/docs/docs/api/Dispatcher.md#dispatcherclosecallback-promise).
### `Agent.destroy([error, callback])`
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).
### `Agent.dispatch(options, handler: AgentDispatchOptions)`
Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
#### Parameter: `AgentDispatchOptions`
Extends: [`DispatchOptions`](Dispatcher.md#parameter-dispatchoptions)
Extends: [`DispatchOptions`](/docs/docs/api/Dispatcher.md#parameter-dispatchoptions)
* **origin** `string | URL`
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).
### `Agent.connect(options[, callback])`
See [`Dispatcher.connect(options[, callback])`](Dispatcher.md#dispatcherconnectoptions-callback).
See [`Dispatcher.connect(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherconnectoptions-callback).
### `Agent.dispatch(options, handler)`
Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
### `Agent.pipeline(options, handler)`
See [`Dispatcher.pipeline(options, handler)`](Dispatcher.md#dispatcherpipelineoptions-handler).
See [`Dispatcher.pipeline(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherpipelineoptions-handler).
### `Agent.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
### `Agent.stream(options, factory[, callback])`
See [`Dispatcher.stream(options, factory[, callback])`](Dispatcher.md#dispatcherstreamoptions-factory-callback).
See [`Dispatcher.stream(options, factory[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherstreamoptions-factory-callback).
### `Agent.upgrade(options[, callback])`
See [`Dispatcher.upgrade(options[, callback])`](Dispatcher.md#dispatcherupgradeoptions-callback).
See [`Dispatcher.upgrade(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherupgradeoptions-callback).

@@ -61,29 +61,29 @@ # Client Lifecycle

The **idle** state is the initial state of a `Client` instance. While an `origin` is required for instantiating a `Client` instance, the underlying socket connection will not be established until a request is queued using [`Client.dispatch()`](Client.md#clientdispatchoptions-handlers). By calling `Client.dispatch()` directly or using one of the multiple implementations ([`Client.connect()`](Client.md#clientconnectoptions-callback), [`Client.pipeline()`](Client.md#clientpipelineoptions-handler), [`Client.request()`](Client.md#clientrequestoptions-callback), [`Client.stream()`](Client.md#clientstreamoptions-factory-callback), and [`Client.upgrade()`](Client.md#clientupgradeoptions-callback)), the `Client` instance will transition from **idle** to [**pending**](#pending) and then most likely directly to [**processing**](#processing).
The **idle** state is the initial state of a `Client` instance. While an `origin` is required for instantiating a `Client` instance, the underlying socket connection will not be established until a request is queued using [`Client.dispatch()`](/docs/docs/api/Client.md#clientdispatchoptions-handlers). By calling `Client.dispatch()` directly or using one of the multiple implementations ([`Client.connect()`](Client.md#clientconnectoptions-callback), [`Client.pipeline()`](Client.md#clientpipelineoptions-handler), [`Client.request()`](Client.md#clientrequestoptions-callback), [`Client.stream()`](Client.md#clientstreamoptions-factory-callback), and [`Client.upgrade()`](/docs/docs/api/Client.md#clientupgradeoptions-callback)), the `Client` instance will transition from **idle** to [**pending**](/docs/docs/api/Client.md#pending) and then most likely directly to [**processing**](/docs/docs/api/Client.md#processing).
Calling [`Client.close()`](Client.md#clientclosecallback) or [`Client.destroy()`](Client.md#clientdestroyerror-callback) transitions directly to the [**destroyed**](#destroyed) state since the `Client` instance will have no queued requests in this state.
Calling [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback) or [`Client.destroy()`](Client.md#clientdestroyerror-callback) transitions directly to the [**destroyed**](/docs/docs/api/Client.md#destroyed) state since the `Client` instance will have no queued requests in this state.
### pending
The **pending** state signifies a non-processing `Client`. Upon entering this state, the `Client` establishes a socket connection and emits the [`'connect'`](Client.md#event-connect) event signalling a connection was successfully established with the `origin` provided during `Client` instantiation. The internal queue is initially empty, and requests can start queueing.
The **pending** state signifies a non-processing `Client`. Upon entering this state, the `Client` establishes a socket connection and emits the [`'connect'`](/docs/docs/api/Client.md#event-connect) event signalling a connection was successfully established with the `origin` provided during `Client` instantiation. The internal queue is initially empty, and requests can start queueing.
Calling [`Client.close()`](Client.md#clientclosecallback) with queued requests, transitions the `Client` to the [**processing**](#processing) state. Without queued requests, it transitions to the [**destroyed**](#destroyed) state.
Calling [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback) with queued requests, transitions the `Client` to the [**processing**](/docs/docs/api/Client.md#processing) state. Without queued requests, it transitions to the [**destroyed**](/docs/docs/api/Client.md#destroyed) state.
Calling [`Client.destroy()`](Client.md#clientdestroyerror-callback) transitions directly to the [**destroyed**](#destroyed) state regardless of existing requests.
Calling [`Client.destroy()`](/docs/docs/api/Client.md#clientdestroyerror-callback) transitions directly to the [**destroyed**](/docs/docs/api/Client.md#destroyed) state regardless of existing requests.
### processing
The **processing** state is a state machine within itself. It initializes to the [**processing.running**](#running) state. The [`Client.dispatch()`](Client.md#clientdispatchoptions-handlers), [`Client.close()`](Client.md#clientclosecallback), and [`Client.destroy()`](Client.md#clientdestroyerror-callback) can be called at any time while the `Client` is in this state. `Client.dispatch()` will add more requests to the queue while existing requests continue to be processed. `Client.close()` will transition to the [**processing.closing**](#closing) state. And `Client.destroy()` will transition to [**destroyed**](#destroyed).
The **processing** state is a state machine within itself. It initializes to the [**processing.running**](/docs/docs/api/Client.md#running) state. The [`Client.dispatch()`](/docs/docs/api/Client.md#clientdispatchoptions-handlers), [`Client.close()`](Client.md#clientclosecallback), and [`Client.destroy()`](Client.md#clientdestroyerror-callback) can be called at any time while the `Client` is in this state. `Client.dispatch()` will add more requests to the queue while existing requests continue to be processed. `Client.close()` will transition to the [**processing.closing**](/docs/docs/api/Client.md#closing) state. And `Client.destroy()` will transition to [**destroyed**](/docs/docs/api/Client.md#destroyed).
#### running
In the **processing.running** sub-state, queued requests are being processed in a FIFO order. If a request body requires draining, the *needDrain* event transitions to the [**processing.busy**](#busy) sub-state. The *close* event transitions the Client to the [**process.closing**](#closing) sub-state. If all queued requests are processed and neither [`Client.close()`](Client.md#clientclosecallback) nor [`Client.destroy()`](Client.md#clientdestroyerror-callback) are called, then the [**processing**](#processing) machine will trigger a *keepalive* event transitioning the `Client` back to the [**pending**](#pending) state. During this time, the `Client` is waiting for the socket connection to timeout, and once it does, it triggers the *timeout* event and transitions to the [**idle**](#idle) state.
In the **processing.running** sub-state, queued requests are being processed in a FIFO order. If a request body requires draining, the *needDrain* event transitions to the [**processing.busy**](/docs/docs/api/Client.md#busy) sub-state. The *close* event transitions the Client to the [**process.closing**](/docs/docs/api/Client.md#closing) sub-state. If all queued requests are processed and neither [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback) nor [`Client.destroy()`](Client.md#clientdestroyerror-callback) are called, then the [**processing**](/docs/docs/api/Client.md#processing) machine will trigger a *keepalive* event transitioning the `Client` back to the [**pending**](/docs/docs/api/Client.md#pending) state. During this time, the `Client` is waiting for the socket connection to timeout, and once it does, it triggers the *timeout* event and transitions to the [**idle**](/docs/docs/api/Client.md#idle) state.
#### busy
This sub-state is only entered when a request body is an instance of [Stream](https://nodejs.org/api/stream.html) and requires draining. The `Client` cannot process additional requests while in this state and must wait until the currently processing request body is completely drained before transitioning back to [**processing.running**](#running).
This sub-state is only entered when a request body is an instance of [Stream](https://nodejs.org/api/stream.html) and requires draining. The `Client` cannot process additional requests while in this state and must wait until the currently processing request body is completely drained before transitioning back to [**processing.running**](/docs/docs/api/Client.md#running).
#### closing
This sub-state is only entered when a `Client` instance has queued requests and the [`Client.close()`](Client.md#clientclosecallback) method is called. In this state, the `Client` instance continues to process requests as usual, with the one exception that no additional requests can be queued. Once all of the queued requests are processed, the `Client` will trigger the *done* event gracefully entering the [**destroyed**](#destroyed) state without an error.
This sub-state is only entered when a `Client` instance has queued requests and the [`Client.close()`](/docs/docs/api/Client.md#clientclosecallback) method is called. In this state, the `Client` instance continues to process requests as usual, with the one exception that no additional requests can be queued. Once all of the queued requests are processed, the `Client` will trigger the *done* event gracefully entering the [**destroyed**](/docs/docs/api/Client.md#destroyed) state without an error.

@@ -90,0 +90,0 @@ ### destroyed

@@ -18,3 +18,3 @@ # Class: BalancedPool

Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)
Extends: [`PoolOptions`](/docs/docs/api/Pool.md#parameter-pooloptions)

@@ -32,11 +32,11 @@ * **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`

Implements [Client.closed](Client.md#clientclosed)
Implements [Client.closed](/docs/docs/api/Client.md#clientclosed)
### `BalancedPool.destroyed`
Implements [Client.destroyed](Client.md#clientdestroyed)
Implements [Client.destroyed](/docs/docs/api/Client.md#clientdestroyed)
### `Pool.stats`
Returns [`PoolStats`](PoolStats.md) instance for this pool.
Returns [`PoolStats`](/docs/docs/api/PoolStats.md) instance for this pool.

@@ -59,31 +59,31 @@ ## Instance Methods

Implements [`Dispatcher.close([callback])`](Dispatcher.md#dispatcherclosecallback-promise).
Implements [`Dispatcher.close([callback])`](/docs/docs/api/Dispatcher.md#dispatcherclosecallback-promise).
### `BalancedPool.destroy([error, callback])`
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).
### `BalancedPool.connect(options[, callback])`
See [`Dispatcher.connect(options[, callback])`](Dispatcher.md#dispatcherconnectoptions-callback).
See [`Dispatcher.connect(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherconnectoptions-callback).
### `BalancedPool.dispatch(options, handlers)`
Implements [`Dispatcher.dispatch(options, handlers)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handlers)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
### `BalancedPool.pipeline(options, handler)`
See [`Dispatcher.pipeline(options, handler)`](Dispatcher.md#dispatcherpipelineoptions-handler).
See [`Dispatcher.pipeline(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherpipelineoptions-handler).
### `BalancedPool.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
### `BalancedPool.stream(options, factory[, callback])`
See [`Dispatcher.stream(options, factory[, callback])`](Dispatcher.md#dispatcherstreamoptions-factory-callback).
See [`Dispatcher.stream(options, factory[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherstreamoptions-factory-callback).
### `BalancedPool.upgrade(options[, callback])`
See [`Dispatcher.upgrade(options[, callback])`](Dispatcher.md#dispatcherupgradeoptions-callback).
See [`Dispatcher.upgrade(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherupgradeoptions-callback).

@@ -94,10 +94,10 @@ ## Instance Events

See [Dispatcher Event: `'connect'`](Dispatcher.md#event-connect).
See [Dispatcher Event: `'connect'`](/docs/docs/api/Dispatcher.md#event-connect).
### Event: `'disconnect'`
See [Dispatcher Event: `'disconnect'`](Dispatcher.md#event-disconnect).
See [Dispatcher Event: `'disconnect'`](/docs/docs/api/Dispatcher.md#event-disconnect).
### Event: `'drain'`
See [Dispatcher Event: `'drain'`](Dispatcher.md#event-drain).
See [Dispatcher Event: `'drain'`](/docs/docs/api/Dispatcher.md#event-drain).

@@ -39,3 +39,3 @@ # Cache Store

* **response** `CachedResponse` - The cached response data.
* **response** `CacheValue` - The cached response data.
* **body** `Readable | undefined` - The response's body.

@@ -48,7 +48,7 @@

* **req** `Dispatcher.RequestOptions` - Incoming request
* **value** `CachedResponse` - Response to store
* **value** `CacheValue` - Response to store
Returns: `Writable | undefined` - If the store is full, return `undefined`. Otherwise, return a writable so that the cache interceptor can stream the body and trailers to the store.
## `CachedResponse`
## `CacheValue`

@@ -103,1 +103,20 @@ This is an interface containing the majority of a response's data (minus the body).

The store must not return a response after the time defined in this property.
## `CacheStoreReadable`
This extends Node's [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable)
and defines extra properties relevant to the cache interceptor.
### Getter: `value`
The response's [`CacheStoreValue`](/docs/docs/api/CacheStore.md#cachestorevalue)
## `CacheStoreWriteable`
This extends Node's [`Writable`](https://nodejs.org/api/stream.html#class-streamwritable)
and defines extra properties relevant to the cache interceptor.
### Setter: `rawTrailers`
If the response has trailers, the cache interceptor will pass them to the cache
interceptor through this method.

@@ -89,7 +89,7 @@ # Class: Client

Implements [`Dispatcher.close([callback])`](Dispatcher.md#dispatcherclosecallback-promise).
Implements [`Dispatcher.close([callback])`](/docs/docs/api/Dispatcher.md#dispatcherclosecallback-promise).
### `Client.destroy([error, callback])`
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).

@@ -100,23 +100,23 @@ Waits until socket is closed before invoking the callback (or returning a promise if no callback is provided).

See [`Dispatcher.connect(options[, callback])`](Dispatcher.md#dispatcherconnectoptions-callback).
See [`Dispatcher.connect(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherconnectoptions-callback).
### `Client.dispatch(options, handlers)`
Implements [`Dispatcher.dispatch(options, handlers)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handlers)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
### `Client.pipeline(options, handler)`
See [`Dispatcher.pipeline(options, handler)`](Dispatcher.md#dispatcherpipelineoptions-handler).
See [`Dispatcher.pipeline(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherpipelineoptions-handler).
### `Client.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
### `Client.stream(options, factory[, callback])`
See [`Dispatcher.stream(options, factory[, callback])`](Dispatcher.md#dispatcherstreamoptions-factory-callback).
See [`Dispatcher.stream(options, factory[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherstreamoptions-factory-callback).
### `Client.upgrade(options[, callback])`
See [`Dispatcher.upgrade(options[, callback])`](Dispatcher.md#dispatcherupgradeoptions-callback).
See [`Dispatcher.upgrade(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherupgradeoptions-callback).

@@ -147,3 +147,3 @@ ## Instance Properties

See [Dispatcher Event: `'connect'`](Dispatcher.md#event-connect).
See [Dispatcher Event: `'connect'`](/docs/docs/api/Dispatcher.md#event-connect).

@@ -194,3 +194,3 @@ Parameters:

See [Dispatcher Event: `'disconnect'`](Dispatcher.md#event-disconnect).
See [Dispatcher Event: `'disconnect'`](/docs/docs/api/Dispatcher.md#event-disconnect).

@@ -240,3 +240,3 @@ Parameters:

See [Dispatcher Event: `'drain'`](Dispatcher.md#event-drain).
See [Dispatcher Event: `'drain'`](/docs/docs/api/Dispatcher.md#event-drain).

@@ -243,0 +243,0 @@ #### Example - Client drain event

@@ -380,3 +380,3 @@ # Dispatcher

Extends: [`RequestOptions`](#parameter-requestoptions)
Extends: [`RequestOptions`](/docs/docs/api/Dispatcher.md#parameter-requestoptions)

@@ -471,3 +471,3 @@ * **objectMode** `boolean` (optional) - Default: `false` - Set to `true` if the `handler` will return an object stream.

Extends: [`DispatchOptions`](#parameter-dispatchoptions)
Extends: [`DispatchOptions`](/docs/docs/api/Dispatcher.md#parameter-dispatchoptions)

@@ -659,3 +659,3 @@ * **opaque** `unknown` (optional) - Default: `null` - Used for passing through context to `ResponseData`.

As demonstrated in [Example 1 - Basic GET stream request](#example-1---basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](#example-2---stream-to-fastify-response) for more details.
As demonstrated in [Example 1 - Basic GET stream request](/docs/docs/api/Dispatcher.md#example-1---basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](/docs/docs/api/Dispatch.md#example-2---stream-to-fastify-response) for more details.

@@ -942,3 +942,3 @@ Arguments:

It accepts the same arguments as the [`RedirectHandler` constructor](./RedirectHandler.md).
It accepts the same arguments as the [`RedirectHandler` constructor](/docs/docs/api/RedirectHandler.md).

@@ -961,3 +961,3 @@ **Example - Basic Redirect Interceptor**

It accepts the same arguments as the [`RetryHandler` constructor](./RetryHandler.md).
It accepts the same arguments as the [`RetryHandler` constructor](/docs/docs/api/RetryHandler.md).

@@ -1268,3 +1268,3 @@ **Example - Basic Redirect Interceptor**

- `store` - The [`CacheStore`](./CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](./CacheStore.md#memorycachestore).
- `store` - The [`CacheStore`](/docs/docs/api/CacheStore.md) to store and retrieve responses from. Default is [`MemoryCacheStore`](/docs/docs/api/CacheStore.md#memorycachestore).
- `methods` - The [**safe** HTTP methods](https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1) to cache the response of.

@@ -1318,3 +1318,3 @@

Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in three forms:
Header arguments such as `options.headers` in [`Client.dispatch`](/docs/docs/api/Client.md#clientdispatchoptions-handlers) can be specified in three forms:
* As an object specified by the `Record<string, string | string[] | undefined>` (`IncomingHttpHeaders`) type.

@@ -1325,3 +1325,3 @@ * As an array of strings. An array representation of a header list must have an even length, or an `InvalidArgumentError` will be thrown.

Response headers will derive a `host` from the `url` of the [Client](Client.md#class-client) instance if no `host` header was previously specified.
Response headers will derive a `host` from the `url` of the [Client](/docs/docs/api/Client.md#class-client) instance if no `host` header was previously specified.

@@ -1328,0 +1328,0 @@ ### Example 1 - Object

@@ -23,3 +23,3 @@ # Class: EnvHttpProxyAgent

Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
Extends: [`AgentOptions`](/docs/docs/api/Agent.md#parameter-agentoptions)

@@ -122,42 +122,42 @@ * **httpProxy** `string` (optional) - When set, it will override the `HTTP_PROXY` environment variable.

Implements [`Dispatcher.close([callback])`](Dispatcher.md#dispatcherclosecallback-promise).
Implements [`Dispatcher.close([callback])`](/docs/docs/api/Dispatcher.md#dispatcherclosecallback-promise).
### `EnvHttpProxyAgent.destroy([error, callback])`
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).
### `EnvHttpProxyAgent.dispatch(options, handler: AgentDispatchOptions)`
Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
#### Parameter: `AgentDispatchOptions`
Extends: [`DispatchOptions`](Dispatcher.md#parameter-dispatchoptions)
Extends: [`DispatchOptions`](/docs/docs/api/Dispatcher.md#parameter-dispatchoptions)
* **origin** `string | URL`
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).
### `EnvHttpProxyAgent.connect(options[, callback])`
See [`Dispatcher.connect(options[, callback])`](Dispatcher.md#dispatcherconnectoptions-callback).
See [`Dispatcher.connect(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherconnectoptions-callback).
### `EnvHttpProxyAgent.dispatch(options, handler)`
Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
### `EnvHttpProxyAgent.pipeline(options, handler)`
See [`Dispatcher.pipeline(options, handler)`](Dispatcher.md#dispatcherpipelineoptions-handler).
See [`Dispatcher.pipeline(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherpipelineoptions-handler).
### `EnvHttpProxyAgent.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
### `EnvHttpProxyAgent.stream(options, factory[, callback])`
See [`Dispatcher.stream(options, factory[, callback])`](Dispatcher.md#dispatcherstreamoptions-factory-callback).
See [`Dispatcher.stream(options, factory[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherstreamoptions-factory-callback).
### `EnvHttpProxyAgent.upgrade(options[, callback])`
See [`Dispatcher.upgrade(options[, callback])`](Dispatcher.md#dispatcherupgradeoptions-callback).
See [`Dispatcher.upgrade(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherupgradeoptions-callback).

@@ -17,3 +17,3 @@ # Class: MockAgent

Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
Extends: [`AgentOptions`](/docs/docs/api/Agent.md#parameter-agentoptions)

@@ -305,7 +305,7 @@ * **agent** `Agent` (optional) - Default: `new Agent([options])` - a custom agent encapsulated by the MockAgent.

Implements [`Agent.dispatch(options, handlers)`](Agent.md#parameter-agentdispatchoptions).
Implements [`Agent.dispatch(options, handlers)`](/docs/docs/api/Agent.md#parameter-agentdispatchoptions).
### `MockAgent.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).

@@ -312,0 +312,0 @@ #### Example - MockAgent request

@@ -39,15 +39,15 @@ # Class: MockClient

Implements: [`MockPool.intercept(options)`](MockPool.md#mockpoolinterceptoptions)
Implements: [`MockPool.intercept(options)`](/docs/docs/api/MockPool.md#mockpoolinterceptoptions)
### `MockClient.close()`
Implements: [`MockPool.close()`](MockPool.md#mockpoolclose)
Implements: [`MockPool.close()`](/docs/docs/api/MockPool.md#mockpoolclose)
### `MockClient.dispatch(options, handlers)`
Implements [`Dispatcher.dispatch(options, handlers)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handlers)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
### `MockClient.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).

@@ -54,0 +54,0 @@ #### Example - MockClient request

@@ -515,7 +515,7 @@ # Class: MockPool

Implements [`Dispatcher.dispatch(options, handlers)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handlers)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
### `MockPool.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).

@@ -522,0 +522,0 @@ #### Example - MockPool request

@@ -18,3 +18,3 @@ # Class: Pool

Extends: [`ClientOptions`](Client.md#parameter-clientoptions)
Extends: [`ClientOptions`](/docs/docs/api/Client.md#parameter-clientoptions)

@@ -28,7 +28,7 @@ * **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Client(origin, opts)`

Implements [Client.closed](Client.md#clientclosed)
Implements [Client.closed](/docs/docs/api/Client.md#clientclosed)
### `Pool.destroyed`
Implements [Client.destroyed](Client.md#clientdestroyed)
Implements [Client.destroyed](/docs/docs/api/Client.md#clientdestroyed)

@@ -43,31 +43,31 @@ ### `Pool.stats`

Implements [`Dispatcher.close([callback])`](Dispatcher.md#dispatcherclosecallback-promise).
Implements [`Dispatcher.close([callback])`](/docs/docs/api/Dispatcher.md#dispatcherclosecallback-promise).
### `Pool.destroy([error, callback])`
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
Implements [`Dispatcher.destroy([error, callback])`](/docs/docs/api/Dispatcher.md#dispatcherdestroyerror-callback-promise).
### `Pool.connect(options[, callback])`
See [`Dispatcher.connect(options[, callback])`](Dispatcher.md#dispatcherconnectoptions-callback).
See [`Dispatcher.connect(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherconnectoptions-callback).
### `Pool.dispatch(options, handler)`
Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
Implements [`Dispatcher.dispatch(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler).
### `Pool.pipeline(options, handler)`
See [`Dispatcher.pipeline(options, handler)`](Dispatcher.md#dispatcherpipelineoptions-handler).
See [`Dispatcher.pipeline(options, handler)`](/docs/docs/api/Dispatcher.md#dispatcherpipelineoptions-handler).
### `Pool.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).
### `Pool.stream(options, factory[, callback])`
See [`Dispatcher.stream(options, factory[, callback])`](Dispatcher.md#dispatcherstreamoptions-factory-callback).
See [`Dispatcher.stream(options, factory[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherstreamoptions-factory-callback).
### `Pool.upgrade(options[, callback])`
See [`Dispatcher.upgrade(options[, callback])`](Dispatcher.md#dispatcherupgradeoptions-callback).
See [`Dispatcher.upgrade(options[, callback])`](/docs/docs/api/Dispatcher.md#dispatcherupgradeoptions-callback).

@@ -78,10 +78,10 @@ ## Instance Events

See [Dispatcher Event: `'connect'`](Dispatcher.md#event-connect).
See [Dispatcher Event: `'connect'`](/docs/docs/api/Dispatcher.md#event-connect).
### Event: `'disconnect'`
See [Dispatcher Event: `'disconnect'`](Dispatcher.md#event-disconnect).
See [Dispatcher Event: `'disconnect'`](/docs/docs/api/Dispatcher.md#event-disconnect).
### Event: `'drain'`
See [Dispatcher Event: `'drain'`](Dispatcher.md#event-drain).
See [Dispatcher Event: `'drain'`](/docs/docs/api/Dispatcher.md#event-drain).
# Class: PoolStats
Aggregate stats for a [Pool](Pool.md) or [BalancedPool](BalancedPool.md).
Aggregate stats for a [Pool](/docs/docs/api/Pool.md) or [BalancedPool](/docs/docs/api/BalancedPool.md).

@@ -5,0 +5,0 @@ ## `new PoolStats(pool)`

@@ -17,3 +17,3 @@ # Class: ProxyAgent

Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
Extends: [`AgentOptions`](/docs/docs/api/Agent.md#parameter-agentoptions)

@@ -127,6 +127,6 @@ * **uri** `string | URL` (required) - The URI of the proxy server. This can be provided as a string, as an instance of the URL class, or as an object with a `uri` property of type string.

Implements [`Agent.dispatch(options, handlers)`](Agent.md#parameter-agentdispatchoptions).
Implements [`Agent.dispatch(options, handlers)`](/docs/docs/api/Agent.md#parameter-agentdispatchoptions).
### `ProxyAgent.request(options[, callback])`
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).

@@ -18,3 +18,3 @@ # Class: RetryHandler

Extends: [`Dispatch.DispatchOptions`](Dispatcher.md#parameter-dispatchoptions).
Extends: [`Dispatch.DispatchOptions`](/docs/docs/api/Dispatcher.md#parameter-dispatchoptions).

@@ -48,3 +48,3 @@ #### `RetryOptions`

- **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandlers) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every retry.
- **handler** Extends [`Dispatch.DispatchHandlers`](Dispatcher.md#dispatcherdispatchoptions-handler) (required) - Handler function to be called after the request is successful or the retries are exhausted.
- **handler** Extends [`Dispatch.DispatchHandlers`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handler) (required) - Handler function to be called after the request is successful or the retries are exhausted.

@@ -51,0 +51,0 @@ >__Note__: The `RetryHandler` does not retry over stateful bodies (e.g. streams, AsyncIterable) as those, once consumed, are left in a state that cannot be reutilized. For these situations the `RetryHandler` will identify

@@ -12,3 +12,3 @@ # Class: WebSocket

* **url** `URL | string`
* **protocol** `string | string[] | WebSocketInit` (optional) - Subprotocol(s) to request the server use, or a [`Dispatcher`](./Dispatcher.md).
* **protocol** `string | string[] | WebSocketInit` (optional) - Subprotocol(s) to request the server use, or a [`Dispatcher`](/docs/docs/api/Dispatcher.md).

@@ -15,0 +15,0 @@ ### Example:

'use strict'
const { Writable } = require('node:stream')
const { nowAbsolute } = require('../util/timers.js')
/**
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheKey} CacheKey
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheValue} CacheValue
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheStore} CacheStore
* @typedef {import('../../types/cache-interceptor.d.ts').default.GetResult} GetResult
*/
/**
* @implements {CacheStore}
*
* @typedef {{
* locked: boolean
* opts: import('../../types/cache-interceptor.d.ts').default.CachedResponse
* body?: Buffer[]
* }} MemoryStoreValue
*/
class MemoryCacheStore {
#maxCount = Infinity
#maxSize = Infinity
#maxEntrySize = Infinity
#entryCount = 0
#size = 0
#count = 0
#entries = new Map()
/**
* @type {Map<string, Map<string, MemoryStoreValue[]>>}
*/
#data = new Map()
/**
* @param {import('../../types/cache-interceptor.d.ts').default.MemoryCacheStoreOpts | undefined} [opts]

@@ -47,2 +45,13 @@ */

if (opts.maxSize !== undefined) {
if (
typeof opts.maxSize !== 'number' ||
!Number.isInteger(opts.maxSize) ||
opts.maxSize < 0
) {
throw new TypeError('MemoryCacheStore options.maxSize must be a non-negative integer')
}
this.#maxSize = opts.maxSize
}
if (opts.maxEntrySize !== undefined) {

@@ -61,8 +70,4 @@ if (

get isFull () {
return this.#entryCount >= this.#maxCount
}
/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}

@@ -75,14 +80,23 @@ */

const values = this.#getValuesForRequest(key, false)
if (!values) {
return undefined
}
const topLevelKey = `${key.origin}:${key.path}`
const value = this.#findValue(key, values)
const now = nowAbsolute()
const entry = this.#entries.get(topLevelKey)?.find((entry) => (
entry.deleteAt > now &&
entry.method === key.method &&
(entry.vary == null || Object.keys(entry.vary).every(headerName => entry.vary[headerName] === key.headers?.[headerName]))
))
if (!value || value.locked) {
return undefined
}
return { ...value.opts, body: value.body }
return entry == null
? undefined
: {
statusMessage: entry.statusMessage,
statusCode: entry.statusCode,
rawHeaders: entry.rawHeaders,
body: entry.body,
etag: entry.etag,
cachedAt: entry.cachedAt,
staleAt: entry.staleAt,
deleteAt: entry.deleteAt
}
}

@@ -92,109 +106,20 @@

* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @param {import('../../types/cache-interceptor.d.ts').default.CachedResponse} opts
* @param {import('../../types/cache-interceptor.d.ts').default.CacheValue} val
* @returns {Writable | undefined}
*/
createWriteStream (key, opts) {
createWriteStream (key, val) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}
if (typeof opts !== 'object') {
throw new TypeError(`expected value to be object, got ${typeof opts}`)
if (typeof val !== 'object') {
throw new TypeError(`expected value to be object, got ${typeof val}`)
}
if (this.isFull) {
return undefined
}
const topLevelKey = `${key.origin}:${key.path}`
const values = this.#getValuesForRequest(key, true)
const store = this
const entry = { ...key, ...val, body: [], size: 0 }
/**
* @type {(MemoryStoreValue & { index: number }) | undefined}
*/
let value = this.#findValue(key, values)
let valueIndex = value?.index
if (!value) {
// The value doesn't already exist, meaning we haven't cached this
// response before. Let's assign it a value and insert it into our data
// property.
if (this.isFull) {
// Or not, we don't have space to add another response
return undefined
}
this.#entryCount++
value = {
locked: true,
opts
}
// We want to sort our responses in decending order by their deleteAt
// timestamps so that deleting expired responses is faster
if (
values.length === 0 ||
opts.deleteAt < values[values.length - 1].deleteAt
) {
// Our value is either the only response for this path or our deleteAt
// time is sooner than all the other responses
values.push(value)
valueIndex = values.length - 1
} else if (opts.deleteAt >= values[0].deleteAt) {
// Our deleteAt is later than everyone elses
values.unshift(value)
valueIndex = 0
} else {
// We're neither in the front or the end, let's just binary search to
// find our stop we need to be in
let startIndex = 0
let endIndex = values.length
while (true) {
if (startIndex === endIndex) {
values.splice(startIndex, 0, value)
break
}
const middleIndex = Math.floor((startIndex + endIndex) / 2)
const middleValue = values[middleIndex]
if (opts.deleteAt === middleIndex) {
values.splice(middleIndex, 0, value)
valueIndex = middleIndex
break
} else if (opts.deleteAt > middleValue.opts.deleteAt) {
endIndex = middleIndex
continue
} else {
startIndex = middleIndex
continue
}
}
}
} else {
// Check if there's already another request writing to the value or
// a request reading from it
if (value.locked) {
return undefined
}
// Empty it so we can overwrite it
value.body = []
}
let currentSize = 0
/**
* @type {Buffer[] | null}
*/
let body = key.method !== 'HEAD' ? [] : null
const maxEntrySize = this.#maxEntrySize
const writable = new Writable({
return new Writable({
write (chunk, encoding, callback) {
if (key.method === 'HEAD') {
throw new Error('HEAD request shouldn\'t have a body')
}
if (!body) {
return callback()
}
if (typeof chunk === 'string') {

@@ -204,127 +129,58 @@ chunk = Buffer.from(chunk, encoding)

currentSize += chunk.byteLength
entry.size += chunk.byteLength
if (currentSize >= maxEntrySize) {
body = null
this.end()
shiftAtIndex(values, valueIndex)
return callback()
if (entry.size >= store.#maxEntrySize) {
this.destroy()
} else {
entry.body.push(chunk)
}
body.push(chunk)
callback()
callback(null)
},
final (callback) {
value.locked = false
if (body !== null) {
value.body = body
let entries = store.#entries.get(topLevelKey)
if (!entries) {
entries = []
store.#entries.set(topLevelKey, entries)
}
entries.push(entry)
callback()
store.#size += entry.size
store.#count += 1
if (store.#size > store.#maxSize || store.#count > store.#maxCount) {
for (const [key, entries] of store.#entries) {
for (const entry of entries.splice(0, entries.length / 2)) {
store.#size -= entry.size
store.#count -= 1
}
if (entries.length === 0) {
store.#entries.delete(key)
}
}
}
callback(null)
}
})
return writable
}
/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @param {CacheKey} key
*/
delete (key) {
this.#data.delete(`${key.origin}:${key.path}`)
}
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}
/**
* Gets all of the requests of the same origin, path, and method. Does not
* take the `vary` property into account.
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @param {boolean} [makeIfDoesntExist=false]
* @returns {MemoryStoreValue[] | undefined}
*/
#getValuesForRequest (key, makeIfDoesntExist) {
// https://www.rfc-editor.org/rfc/rfc9111.html#section-2-3
const topLevelKey = `${key.origin}:${key.path}`
let cachedPaths = this.#data.get(topLevelKey)
if (!cachedPaths) {
if (!makeIfDoesntExist) {
return undefined
}
cachedPaths = new Map()
this.#data.set(topLevelKey, cachedPaths)
for (const entry of this.#entries.get(topLevelKey) ?? []) {
this.#size -= entry.size
this.#count -= 1
}
let value = cachedPaths.get(key.method)
if (!value && makeIfDoesntExist) {
value = []
cachedPaths.set(key.method, value)
}
return value
this.#entries.delete(topLevelKey)
}
/**
* Given a list of values of a certain request, this decides the best value
* to respond with.
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
* @param {MemoryStoreValue[]} values
* @returns {(MemoryStoreValue & { index: number }) | undefined}
*/
#findValue (req, values) {
/**
* @type {MemoryStoreValue | undefined}
*/
let value
const now = Date.now()
for (let i = values.length - 1; i >= 0; i--) {
const current = values[i]
const currentCacheValue = current.opts
if (now >= currentCacheValue.deleteAt) {
// We've reached expired values, let's delete them
this.#entryCount -= values.length - i
values.length = i
break
}
let matches = true
if (currentCacheValue.vary) {
if (!req.headers) {
matches = false
break
}
for (const key in currentCacheValue.vary) {
if (currentCacheValue.vary[key] !== req.headers[key]) {
matches = false
break
}
}
}
if (matches) {
value = {
...current,
index: i
}
break
}
}
return value
}
}
/**
* @param {any[]} array Array to modify
* @param {number} idx Index to delete
*/
function shiftAtIndex (array, idx) {
for (let i = idx + 1; idx < array.length; i++) {
array[i - 1] = array[i]
}
array.length--
}
module.exports = MemoryCacheStore

@@ -7,4 +7,6 @@ 'use strict'

parseCacheControlHeader,
parseVaryHeader
parseVaryHeader,
isEtagUsable
} = require('../util/cache')
const { nowAbsolute } = require('../util/timers.js')

@@ -125,3 +127,3 @@ function noop () {}

const now = Date.now()
const now = nowAbsolute()
const staleAt = determineStaleAt(now, headers, cacheControlDirectives)

@@ -140,3 +142,6 @@ if (staleAt) {

this.#writeStream = this.#store.createWriteStream(this.#cacheKey, {
/**
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
*/
const value = {
statusCode,

@@ -149,4 +154,10 @@ statusMessage,

deleteAt
})
}
if (typeof headers.etag === 'string' && isEtagUsable(headers.etag)) {
value.etag = headers.etag
}
this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value)
if (this.#writeStream) {

@@ -307,3 +318,3 @@ const handler = this

if (expiresDate instanceof Date && !isNaN(expiresDate)) {
return now + (Date.now() - expiresDate.getTime())
return now + (nowAbsolute() - expiresDate.getTime())
}

@@ -310,0 +321,0 @@ }

@@ -9,3 +9,4 @@ 'use strict'

const CacheRevalidationHandler = require('../handler/cache-revalidation-handler')
const { assertCacheStore, assertCacheMethods, makeCacheKey } = require('../util/cache.js')
const { assertCacheStore, assertCacheMethods, makeCacheKey, parseCacheControlHeader } = require('../util/cache.js')
const { nowAbsolute } = require('../util/timers.js')

@@ -15,6 +16,74 @@ const AGE_HEADER = Buffer.from('age')

/**
* @typedef {import('../../types/cache-interceptor.d.ts').default.CachedResponse} CachedResponse
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
*/
function sendGatewayTimeout (handler) {
let aborted = false
try {
if (typeof handler.onConnect === 'function') {
handler.onConnect(() => {
aborted = true
})
if (aborted) {
return
}
}
if (typeof handler.onHeaders === 'function') {
handler.onHeaders(504, [], () => {}, 'Gateway Timeout')
if (aborted) {
return
}
}
if (typeof handler.onComplete === 'function') {
handler.onComplete([])
}
} catch (err) {
if (typeof handler.onError === 'function') {
handler.onError(err)
}
}
}
/**
* @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
* @param {number} age
* @param {import('../util/cache.js').CacheControlDirectives | undefined} cacheControlDirectives
* @returns {boolean}
*/
function needsRevalidation (result, age, cacheControlDirectives) {
if (cacheControlDirectives?.['no-cache']) {
// Always revalidate requests with the no-cache directive
return true
}
const now = nowAbsolute()
if (now > result.staleAt) {
// Response is stale
if (cacheControlDirectives?.['max-stale']) {
// There's a threshold where we can serve stale responses, let's see if
// we're in it
// https://www.rfc-editor.org/rfc/rfc9111.html#name-max-stale
const gracePeriod = result.staleAt + (cacheControlDirectives['max-stale'] * 1000)
return now > gracePeriod
}
return true
}
if (cacheControlDirectives?.['min-fresh']) {
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.3
// At this point, staleAt is always > now
const timeLeftTillStale = result.staleAt - now
const threshold = cacheControlDirectives['min-fresh'] * 1000
return timeLeftTillStale <= threshold
}
return false
}
/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions} [opts]

@@ -53,2 +122,10 @@ * @returns {import('../../types/dispatcher.d.ts').default.DispatcherComposeInterceptor}

const requestCacheControl = opts.headers?.['cache-control']
? parseCacheControlHeader(opts.headers['cache-control'])
: undefined
if (requestCacheControl?.['no-store']) {
return dispatch(opts, handler)
}
/**

@@ -64,2 +141,9 @@ * @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}

if (!result) {
if (requestCacheControl?.['only-if-cached']) {
// We only want cached responses
// https://www.rfc-editor.org/rfc/rfc9111.html#name-only-if-cached
sendGatewayTimeout(handler)
return true
}
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))

@@ -70,4 +154,5 @@ }

* @param {import('../../types/cache-interceptor.d.ts').default.GetResult} result
* @param {number} age
*/
const respondWithCachedValue = ({ cachedAt, rawHeaders, statusCode, statusMessage, body }) => {
const respondWithCachedValue = ({ rawHeaders, statusCode, statusMessage, body }, age) => {
const stream = util.isStream(body)

@@ -109,3 +194,2 @@ ? body

// https://www.rfc-editor.org/rfc/rfc9111.html#name-age
const age = Math.round((Date.now() - cachedAt) / 1000)

@@ -141,17 +225,19 @@ // TODO (fix): What if rawHeaders already contains age header?

const age = Math.round((nowAbsolute() - result.cachedAt) / 1000)
if (requestCacheControl?.['max-age'] && age >= requestCacheControl['max-age']) {
// Response is considered expired for this specific request
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.1
return dispatch(opts, handler)
}
// Check if the response is stale
const now = Date.now()
if (now < result.staleAt) {
// Dump request body.
if (util.isStream(opts.body)) {
opts.body.on('error', () => {}).destroy()
if (needsRevalidation(result, age, requestCacheControl)) {
if (util.isStream(opts.body) && util.bodyLength(opts.body) !== 0) {
// If body is is stream we can't revalidate...
// TODO (fix): This could be less strict...
return dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
}
respondWithCachedValue(result)
} else if (util.isStream(opts.body) && util.bodyLength(opts.body) !== 0) {
// If body is is stream we can't revalidate...
// TODO (fix): This could be less strict...
dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))
} else {
// Need to revalidate the response
dispatch(
// We need to revalidate the response
return dispatch(
{

@@ -161,3 +247,4 @@ ...opts,

...opts.headers,
'if-modified-since': new Date(result.cachedAt).toUTCString()
'if-modified-since': new Date(result.cachedAt).toUTCString(),
etag: result.etag
}

@@ -168,3 +255,3 @@ },

if (success) {
respondWithCachedValue(result)
respondWithCachedValue(result, age)
} else if (util.isStream(result.body)) {

@@ -178,2 +265,8 @@ result.body.on('error', () => {}).destroy()

}
// Dump request body.
if (util.isStream(opts.body)) {
opts.body.on('error', () => {}).destroy()
}
respondWithCachedValue(result, age)
}

@@ -184,2 +277,9 @@

if (!result) {
if (requestCacheControl?.['only-if-cached']) {
// We only want cached responses
// https://www.rfc-editor.org/rfc/rfc9111.html#name-only-if-cached
sendGatewayTimeout(handler)
return true
}
dispatch(opts, new CacheHandler(globalOpts, cacheKey, handler))

@@ -186,0 +286,0 @@ } else {

@@ -205,2 +205,36 @@ 'use strict'

/**
* Note: this deviates from the spec a little. Empty etags ("", W/"") are valid,
* however, including them in cached resposnes serves little to no purpose.
*
* @see https://www.rfc-editor.org/rfc/rfc9110.html#name-etag
*
* @param {string} etag
* @returns {boolean}
*/
function isEtagUsable (etag) {
if (etag.length <= 2) {
// Shortest an etag can be is two chars (just ""). This is where we deviate
// from the spec requiring a min of 3 chars however
return false
}
if (etag[0] === '"' && etag[etag.length - 1] === '"') {
// ETag: ""asd123"" or ETag: "W/"asd123"", kinda undefined behavior in the
// spec. Some servers will accept these while others don't.
// ETag: "asd123"
return !(etag[1] === '"' || etag.startsWith('"W/'))
}
if (etag.startsWith('W/"') && etag[etag.length - 1] === '"') {
// ETag: W/"", also where we deviate from the spec & require a min of 3
// chars
// ETag: for W/"", W/"asd123"
return etag.length !== 4
}
// Anything else
return false
}
/**
* @param {unknown} store

@@ -248,4 +282,5 @@ * @returns {asserts store is import('../../types/cache-interceptor.d.ts').default.CacheStore}

parseVaryHeader,
isEtagUsable,
assertCacheMethods,
assertCacheStore
}

@@ -25,2 +25,9 @@ 'use strict'

/**
* The fastNowAbsolute variable contains the rough result of Date.now()
*
* @type {number}
*/
let fastNowAbsolute = Date.now()
/**
* RESOLUTION_MS represents the target resolution time in milliseconds.

@@ -126,2 +133,4 @@ *

fastNowAbsolute = Date.now()
/**

@@ -395,2 +404,5 @@ * The `idx` variable is used to iterate over the `fastTimers` array.

},
nowAbsolute () {
return fastNowAbsolute
},
/**

@@ -425,3 +437,9 @@ * Trigger the onTick function to process the fastTimers array.

*/
kFastTimer
kFastTimer,
/**
* Exporting for testing purposes only.
* Marking as deprecated to discourage any use outside of testing.
* @deprecated
*/
RESOLUTION_MS
}
{
"name": "undici",
"version": "7.0.0-alpha.4",
"version": "7.0.0-alpha.5",
"description": "An HTTP/1.1 client, written from scratch for Node.js",

@@ -5,0 +5,0 @@ "homepage": "https://undici.nodejs.org",

@@ -28,2 +28,13 @@ import { Readable, Writable } from 'node:stream'

export interface CacheValue {
statusCode: number
statusMessage: string
rawHeaders: Buffer[]
vary?: Record<string, string | string[]>
etag?: string
cachedAt: number
staleAt: number
deleteAt: number
}
export interface DeleteByUri {

@@ -35,3 +46,11 @@ origin: string

type GetResult = CachedResponse & { body: null | Readable | Iterable<Buffer> | Buffer | Iterable<string> | string }
type GetResult = {
statusCode: number
statusMessage: string
rawHeaders: Buffer[]
body: null | Readable | Iterable<Buffer> | AsyncIterable<Buffer> | Buffer | Iterable<string> | AsyncIterable<string> | string
cachedAt: number
staleAt: number
deleteAt: number
}

@@ -42,10 +61,5 @@ /**

export interface CacheStore {
/**
* Whether or not the cache is full and can not store any more responses
*/
get isFull(): boolean | undefined
get(key: CacheKey): GetResult | Promise<GetResult | undefined> | undefined
createWriteStream(key: CacheKey, value: CachedResponse): Writable | undefined
createWriteStream(key: CacheKey, val: CacheValue): Writable | undefined

@@ -55,26 +69,2 @@ delete(key: CacheKey): void | Promise<void>

export interface CachedResponse {
statusCode: number;
statusMessage: string;
rawHeaders: Buffer[];
/**
* Headers defined by the Vary header and their respective values for
* later comparison
*/
vary?: Record<string, string | string[]>
/**
* Time in millis that this value was cached
*/
cachedAt: number
/**
* Time in millis that this value is considered stale
*/
staleAt: number
/**
* Time in millis that this value is to be deleted from the cache. This is
* either the same as staleAt or the `max-stale` caching directive.
*/
deleteAt: number
}
export interface MemoryCacheStoreOpts {

@@ -89,2 +79,7 @@ /**

*/
maxSize?: number
/**
* @default Infinity
*/
maxEntrySize?: number

@@ -98,7 +93,5 @@

get isFull (): boolean | undefined
get (key: CacheKey): GetResult | Promise<GetResult | undefined> | undefined
createWriteStream (key: CacheKey, value: CachedResponse): Writable | undefined
createWriteStream (key: CacheKey, value: CacheValue): Writable | undefined

@@ -105,0 +98,0 @@ delete (key: CacheKey): void | Promise<void>

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc