Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

undici

Package Overview
Dependencies
Maintainers
3
Versions
225
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.6 to 7.0.0-alpha.7

lib/cache/sqlite-cache-store.js

14

docs/docs/api/CacheStore.md

@@ -16,5 +16,17 @@ # Cache Store

- `maxEntries` - The maximum amount of responses to store. Default `Infinity`.
- `maxCount` - The maximum amount of responses to store. Default `Infinity`.
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached.
### `SqliteCacheStore`
The `SqliteCacheStore` stores the responses in a SQLite database.
Under the hood, it uses Node.js' [`node:sqlite`](https://nodejs.org/api/sqlite.html) api.
The `SqliteCacheStore` is only exposed if the `node:sqlite` api is present.
**Options**
- `location` - The location of the SQLite database to use. Default `:memory:`.
- `maxCount` - The maximum number of entries to store in the database. Default `Infinity`.
- `maxEntrySize` - The maximum size in bytes that a resposne's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`.
## Defining a Custom Cache Store

@@ -21,0 +33,0 @@

14

docs/docs/api/Dispatcher.md

@@ -208,10 +208,8 @@ # Dispatcher

* **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
* **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
* **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
* **onResponseStarted** `() => void` (optional) - Invoked when response is received, before headers have been read.
* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
* **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests.
* **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
* **onBodySent** `(chunk: string | Buffer | Uint8Array) => void` - Invoked when a body chunk is sent to the server. Not required. For a stream or iterable body this will be invoked for every chunk. For other body types, it will be invoked once after the body is sent.
* **onRequestStart** `(controller: DispatchController, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
* **onRequestUpgrade** `(controller: DispatchController, statusCode: number, headers: Record<string, string | string[]>, socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
* **onResponseStart** `(controller: DispatchController, statusCode: number, statusMessage?: string, headers: Record<string, string | string []>) => void` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
* **onResponseData** `(controller: DispatchController, chunk: Buffer) => void` - Invoked when response payload data is received. Not required for `upgrade` requests.
* **onResponseEnd** `(controller: DispatchController, trailers: Record<string, string | string[]>) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
* **onResponseError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.

@@ -218,0 +216,0 @@ #### Example 1 - Dispatch GET request

@@ -19,3 +19,3 @@ # Class: RedirectHandler

- **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandlers) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every redirection.
- **dispatch** `(options: Dispatch.DispatchOptions, handlers: Dispatch.DispatchHandler) => Promise<Dispatch.DispatchResponse>` (required) - Dispatch function to be called after every redirection.
- **maxRedirections** `number` (required) - Maximum number of redirections allowed.

@@ -22,0 +22,0 @@ - **opts** `object` (required) - Options for handling redirection.

@@ -46,4 +46,4 @@ # Class: RetryHandler

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

@@ -50,0 +50,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

@@ -51,2 +51,11 @@ 'use strict'

try {
const SqliteCacheStore = require('./lib/cache/sqlite-cache-store')
module.exports.cacheStores.SqliteCacheStore = SqliteCacheStore
} catch (err) {
if (err.code !== 'ERR_UNKNOWN_BUILTIN_MODULE') {
throw err
}
}
module.exports.buildConnector = buildConnector

@@ -53,0 +62,0 @@ module.exports.errors = errors

'use strict'
const { Writable } = require('node:stream')
const { assertCacheKey, assertCacheValue } = require('../util/cache.js')

@@ -73,5 +74,3 @@ /**

get (key) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}
assertCacheKey(key)

@@ -92,3 +91,3 @@ const topLevelKey = `${key.origin}:${key.path}`

statusCode: entry.statusCode,
rawHeaders: entry.rawHeaders,
headers: entry.headers,
body: entry.body,

@@ -108,8 +107,4 @@ etag: entry.etag,

createWriteStream (key, val) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}
if (typeof val !== 'object') {
throw new TypeError(`expected value to be object, got ${typeof val}`)
}
assertCacheKey(key)
assertCacheValue(val)

@@ -116,0 +111,0 @@ const topLevelKey = `${key.origin}:${key.path}`

@@ -514,2 +514,7 @@ 'use strict'

if (typeof handler.onRequestStart === 'function') {
// TODO (fix): More checks...
return
}
if (typeof handler.onConnect !== 'function') {

@@ -516,0 +521,0 @@ throw new InvalidArgumentError('invalid onConnect method')

@@ -772,6 +772,17 @@ 'use strict'

if (!llhttpInstance) {
const noop = () => {}
socket.on('error', noop)
llhttpInstance = await llhttpPromise
llhttpPromise = null
socket.off('error', noop)
}
if (socket.errored) {
throw socket.errored
}
if (socket.destroyed) {
throw new SocketError('destroyed')
}
socket[kNoRef] = false

@@ -778,0 +789,0 @@ socket[kWriting] = false

@@ -35,2 +35,4 @@ 'use strict'

let extractBody
// Experimental

@@ -205,8 +207,9 @@ let h2ExperimentalWarned = false

// Fail head of pipeline.
const request = client[kQueue][client[kRunningIdx]]
client[kQueue][client[kRunningIdx]++] = null
util.errorRequest(client, request, err)
if (client[kRunningIdx] < client[kQueue].length) {
const request = client[kQueue][client[kRunningIdx]]
client[kQueue][client[kRunningIdx]++] = null
util.errorRequest(client, request, err)
client[kPendingIdx] = client[kRunningIdx]
}
client[kPendingIdx] = client[kRunningIdx]
assert(client[kRunning] === 0)

@@ -284,3 +287,4 @@

const session = client[kHTTP2Session]
const { body, method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request
const { method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request
let { body } = request

@@ -413,2 +417,12 @@ if (upgrade) {

if (util.isFormDataLike(body)) {
extractBody ??= require('../web/fetch/body.js').extractBody
const [bodyStream, contentType] = extractBody(body)
headers['content-type'] = contentType
body = bodyStream.stream
contentLength = bodyStream.length
}
if (contentLength == null) {

@@ -415,0 +429,0 @@ contentLength = request.contentLength

'use strict'
const Dispatcher = require('./dispatcher')
const UnwrapHandler = require('../handler/unwrap-handler')
const {

@@ -145,3 +146,3 @@ ClientDestroyedError,

return this[kDispatch](opts, handler)
return this[kDispatch](opts, UnwrapHandler.unwrap(handler))
} catch (err) {

@@ -148,0 +149,0 @@ if (typeof handler.onError !== 'function') {

'use strict'
const EventEmitter = require('node:events')
const WrapHandler = require('../handler/wrap-handler')
const wrapInterceptor = (dispatch) => (opts, handler) => dispatch(opts, WrapHandler.wrap(handler))
class Dispatcher extends EventEmitter {

@@ -32,2 +35,3 @@ dispatch () {

dispatch = interceptor(dispatch)
dispatch = wrapInterceptor(dispatch)

@@ -34,0 +38,0 @@ if (dispatch == null || typeof dispatch !== 'function' || dispatch.length !== 2) {

'use strict'
const util = require('../core/util')
const DecoratorHandler = require('../handler/decorator-handler')
const {

@@ -14,5 +13,5 @@ parseCacheControlHeader,

/**
* Writes a response to a CacheStore and then passes it on to the next handler
* @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
*/
class CacheHandler extends DecoratorHandler {
class CacheHandler {
/**

@@ -29,3 +28,3 @@ * @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}

/**
* @type {import('../../types/dispatcher.d.ts').default.DispatchHandlers}
* @type {import('../../types/dispatcher.d.ts').default.DispatchHandler}
*/

@@ -40,5 +39,5 @@ #handler

/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions} opts
* @param {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions} opts
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
*/

@@ -48,4 +47,2 @@ constructor (opts, cacheKey, handler) {

super(handler)
this.#store = store

@@ -56,40 +53,25 @@ this.#cacheKey = cacheKey

onConnect (abort) {
if (this.#writeStream) {
this.#writeStream.destroy()
this.#writeStream = undefined
}
onRequestStart (controller, context) {
this.#writeStream?.destroy()
this.#writeStream = undefined
this.#handler.onRequestStart?.(controller, context)
}
if (typeof this.#handler.onConnect === 'function') {
this.#handler.onConnect(abort)
}
onRequestUpgrade (controller, statusCode, headers, socket) {
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
}
/**
* @see {DispatchHandlers.onHeaders}
*
* @param {number} statusCode
* @param {Buffer[]} rawHeaders
* @param {() => void} resume
* @param {string} statusMessage
* @returns {boolean}
*/
onHeaders (
onResponseStart (
controller,
statusCode,
rawHeaders,
resume,
statusMessage
statusMessage,
headers
) {
const downstreamOnHeaders = () => {
if (typeof this.#handler.onHeaders === 'function') {
return this.#handler.onHeaders(
statusCode,
rawHeaders,
resume,
statusMessage
)
} else {
return true
}
}
const downstreamOnHeaders = () =>
this.#handler.onResponseStart?.(
controller,
statusCode,
statusMessage,
headers
)

@@ -110,17 +92,8 @@ if (

const parsedRawHeaders = util.parseRawHeaders(rawHeaders)
const headers = util.parseHeaders(parsedRawHeaders)
const cacheControlHeader = headers['cache-control']
const isCacheFull = typeof this.#store.isFull !== 'undefined'
? this.#store.isFull
: false
if (
!cacheControlHeader ||
isCacheFull
) {
if (!cacheControlHeader) {
// Don't have the cache control header or the cache is full
return downstreamOnHeaders()
}
const cacheControlDirectives = parseCacheControlHeader(cacheControlHeader)

@@ -139,7 +112,3 @@ if (!canCacheResponse(statusCode, headers, cacheControlDirectives)) {

const strippedHeaders = stripNecessaryHeaders(
rawHeaders,
parsedRawHeaders,
cacheControlDirectives
)
const strippedHeaders = stripNecessaryHeaders(headers, cacheControlDirectives)

@@ -152,3 +121,3 @@ /**

statusMessage,
rawHeaders: strippedHeaders,
headers: strippedHeaders,
vary: varyDirectives,

@@ -169,3 +138,3 @@ cachedAt: now,

this.#writeStream
.on('drain', resume)
.on('drain', () => controller.resume())
.on('error', function () {

@@ -180,3 +149,3 @@ // TODO (fix): Make error somehow observable?

// TODO (fix): Should we resume even if was paused downstream?
resume()
controller.resume()
})

@@ -189,51 +158,19 @@ }

/**
* @see {DispatchHandlers.onData}
*
* @param {Buffer} chunk
* @returns {boolean}
*/
onData (chunk) {
let paused = false
if (this.#writeStream) {
paused ||= this.#writeStream.write(chunk) === false
onResponseData (controller, chunk) {
if (this.#writeStream?.write(chunk) === false) {
controller.pause()
}
if (typeof this.#handler.onData === 'function') {
paused ||= this.#handler.onData(chunk) === false
}
return !paused
this.#handler.onResponseData?.(controller, chunk)
}
/**
* @see {DispatchHandlers.onComplete}
*
* @param {string[] | null} rawTrailers
*/
onComplete (rawTrailers) {
if (this.#writeStream) {
this.#writeStream.end()
}
if (typeof this.#handler.onComplete === 'function') {
return this.#handler.onComplete(rawTrailers)
}
onResponseEnd (controller, trailers) {
this.#writeStream?.end()
this.#handler.onResponseEnd?.(controller, trailers)
}
/**
* @see {DispatchHandlers.onError}
*
* @param {Error} err
*/
onError (err) {
if (this.#writeStream) {
this.#writeStream.destroy(err)
this.#writeStream = undefined
}
if (typeof this.#handler.onError === 'function') {
this.#handler.onError(err)
}
onResponseError (controller, err) {
this.#writeStream?.destroy(err)
this.#writeStream = undefined
this.#handler.onResponseError?.(controller, err)
}

@@ -250,6 +187,3 @@ }

function canCacheResponse (statusCode, headers, cacheControlDirectives) {
if (
statusCode !== 200 &&
statusCode !== 307
) {
if (statusCode !== 200 && statusCode !== 307) {
return false

@@ -324,3 +258,3 @@ }

const expiresDate = new Date(headers.expire)
if (expiresDate instanceof Date && !isNaN(expiresDate)) {
if (expiresDate instanceof Date && Number.isFinite(expiresDate.valueOf())) {
return now + (Date.now() - expiresDate.getTime())

@@ -348,8 +282,7 @@ }

* Strips headers required to be removed in cached responses
* @param {Buffer[]} rawHeaders
* @param {string[]} parsedRawHeaders
* @param {Record<string, string | string[]>} headers
* @param {import('../util/cache.js').CacheControlDirectives} cacheControlDirectives
* @returns {Buffer[]}
* @returns {Record<string, string | string []>}
*/
function stripNecessaryHeaders (rawHeaders, parsedRawHeaders, cacheControlDirectives) {
function stripNecessaryHeaders (headers, cacheControlDirectives) {
const headersToRemove = ['connection']

@@ -366,49 +299,11 @@

let strippedHeaders
let offset = 0
for (let i = 0; i < parsedRawHeaders.length; i += 2) {
const headerName = parsedRawHeaders[i]
for (const headerName of Object.keys(headers)) {
if (headersToRemove.includes(headerName)) {
// We have at least one header we want to remove
if (!strippedHeaders) {
// This is the first header we want to remove, let's create the array
// Since we're stripping headers, this will over allocate. We'll trim
// it later.
strippedHeaders = new Array(parsedRawHeaders.length)
// Backfill the previous headers into it
for (let j = 0; j < i; j += 2) {
strippedHeaders[j] = parsedRawHeaders[j]
strippedHeaders[j + 1] = parsedRawHeaders[j + 1]
}
}
// We can't map indices 1:1 from stripped headers to rawHeaders without
// creating holes (if we skip a header, we now have two holes where at
// element should be). So, let's keep an offset to keep strippedHeaders
// flattened. We can also use this at the end for trimming the empty
// elements off of strippedHeaders.
offset += 2
continue
strippedHeaders ??= { ...headers }
delete headers[headerName]
}
// We want to keep this header. Let's add it to strippedHeaders if it exists
if (strippedHeaders) {
strippedHeaders[i - offset] = parsedRawHeaders[i]
strippedHeaders[i + 1 - offset] = parsedRawHeaders[i + 1]
}
}
if (strippedHeaders) {
// Trim off the empty values at the end
strippedHeaders.length -= offset
}
return strippedHeaders
? util.encodeRawHeaders(strippedHeaders)
: rawHeaders
return strippedHeaders ?? headers
}
module.exports = CacheHandler
'use strict'
const assert = require('node:assert')
const DecoratorHandler = require('../handler/decorator-handler')

@@ -17,21 +16,20 @@ /**

*
* @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandlers} DispatchHandlers
* @implements {DispatchHandlers}
* @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
*/
class CacheRevalidationHandler extends DecoratorHandler {
class CacheRevalidationHandler {
#successful = false
/**
* @type {((boolean) => void) | null}
* @type {((boolean, any) => void) | null}
*/
#callback
/**
* @type {(import('../../types/dispatcher.d.ts').default.DispatchHandlers)}
* @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
*/
#handler
#abort
#context
/**
* @param {(boolean) => void} callback Function to call if the cached value is valid
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
* @param {(boolean, any) => void} callback Function to call if the cached value is valid
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
*/

@@ -43,4 +41,2 @@ constructor (callback, handler) {

super(handler)
this.#callback = callback

@@ -50,21 +46,16 @@ this.#handler = handler

onConnect (abort) {
onRequestStart (controller, context) {
this.#successful = false
this.#abort = abort
this.#context = context
}
/**
* @see {DispatchHandlers.onHeaders}
*
* @param {number} statusCode
* @param {Buffer[]} rawHeaders
* @param {() => void} resume
* @param {string} statusMessage
* @returns {boolean}
*/
onHeaders (
onRequestUpgrade (controller, statusCode, headers, socket) {
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
}
onResponseStart (
controller,
statusCode,
rawHeaders,
resume,
statusMessage
statusMessage,
headers
) {

@@ -75,3 +66,3 @@ assert(this.#callback != null)

this.#successful = statusCode === 304
this.#callback(this.#successful)
this.#callback(this.#successful, this.#context)
this.#callback = null

@@ -83,42 +74,20 @@

if (typeof this.#handler.onConnect === 'function') {
this.#handler.onConnect(this.#abort)
}
if (typeof this.#handler.onHeaders === 'function') {
return this.#handler.onHeaders(
statusCode,
rawHeaders,
resume,
statusMessage
)
}
return true
this.#handler.onRequestStart?.(controller, this.#context)
this.#handler.onResponseStart?.(
controller,
statusCode,
statusMessage,
headers
)
}
/**
* @see {DispatchHandlers.onData}
*
* @param {Buffer} chunk
* @returns {boolean}
*/
onData (chunk) {
onResponseData (controller, chunk) {
if (this.#successful) {
return true
return
}
if (typeof this.#handler.onData === 'function') {
return this.#handler.onData(chunk)
}
return true
return this.#handler.onResponseData(controller, chunk)
}
/**
* @see {DispatchHandlers.onComplete}
*
* @param {string[] | null} rawTrailers
*/
onComplete (rawTrailers) {
onResponseEnd (controller, trailers) {
if (this.#successful) {

@@ -128,13 +97,6 @@ return

if (typeof this.#handler.onComplete === 'function') {
this.#handler.onComplete(rawTrailers)
}
this.#handler.onResponseEnd?.(controller, trailers)
}
/**
* @see {DispatchHandlers.onError}
*
* @param {Error} err
*/
onError (err) {
onResponseError (controller, err) {
if (this.#successful) {

@@ -149,4 +111,4 @@ return

if (typeof this.#handler.onError === 'function') {
this.#handler.onError(err)
if (typeof this.#handler.onResponseError === 'function') {
this.#handler.onResponseError(controller, err)
} else {

@@ -153,0 +115,0 @@ throw err

@@ -13,2 +13,4 @@ 'use strict'

const noop = () => {}
class BodyAsyncIterable {

@@ -42,4 +44,2 @@ constructor (body) {

util.assertRequestHandler(handler, opts.method, opts.upgrade)
this.dispatch = dispatch

@@ -52,3 +52,2 @@ this.location = null

this.history = []
this.redirectionLimitReached = false

@@ -103,17 +102,32 @@ if (util.isStream(this.opts.body)) {

onHeaders (statusCode, headers, resume, statusText) {
this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body)
? null
: parseLocation(statusCode, headers)
onHeaders (statusCode, rawHeaders, resume, statusText) {
if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
throw new Error('max redirects')
}
if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
if (this.request) {
this.request.abort(new Error('max redirects'))
// https://tools.ietf.org/html/rfc7231#section-6.4.2
// https://fetch.spec.whatwg.org/#http-redirect-fetch
// In case of HTTP 301 or 302 with POST, change the method to GET
if ((statusCode === 301 || statusCode === 302) && this.opts.method === 'POST') {
this.opts.method = 'GET'
if (util.isStream(this.opts.body)) {
util.destroy(this.opts.body.on('error', noop))
}
this.opts.body = null
}
this.redirectionLimitReached = true
this.abort(new Error('max redirects'))
return
// https://tools.ietf.org/html/rfc7231#section-6.4.4
// In case of HTTP 303, always replace method to be either HEAD or GET
if (statusCode === 303 && this.opts.method !== 'HEAD') {
this.opts.method = 'GET'
if (util.isStream(this.opts.body)) {
util.destroy(this.opts.body.on('error', noop))
}
this.opts.body = null
}
this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body)
? null
: parseLocation(statusCode, rawHeaders)
if (this.opts.origin) {

@@ -124,3 +138,3 @@ this.history.push(new URL(this.opts.path, this.opts.origin))

if (!this.location) {
return this.handler.onHeaders(statusCode, headers, resume, statusText)
return this.handler.onHeaders(statusCode, rawHeaders, resume, statusText)
}

@@ -139,9 +153,2 @@

this.opts.query = null
// https://tools.ietf.org/html/rfc7231#section-6.4.4
// In case of HTTP 303, always replace method to be either HEAD or GET
if (statusCode === 303 && this.opts.method !== 'HEAD') {
this.opts.method = 'GET'
this.opts.body = null
}
}

@@ -200,3 +207,3 @@

function parseLocation (statusCode, headers) {
function parseLocation (statusCode, rawHeaders) {
if (redirectableStatusCodes.indexOf(statusCode) === -1) {

@@ -206,5 +213,5 @@ return null

for (let i = 0; i < headers.length; i += 2) {
if (headers[i].length === 8 && util.headerNameToString(headers[i]) === 'location') {
return headers[i + 1]
for (let i = 0; i < rawHeaders.length; i += 2) {
if (rawHeaders[i].length === 8 && util.headerNameToString(rawHeaders[i]) === 'location') {
return rawHeaders[i + 1]
}

@@ -211,0 +218,0 @@ }

@@ -40,2 +40,3 @@ 'use strict'

this.aborted = false
this.connectCalled = false
this.retryOpts = {

@@ -72,12 +73,2 @@ retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],

this.resume = null
// Handle possible onConnect duplication
this.handler.onConnect(reason => {
this.aborted = true
if (this.abort) {
this.abort(reason)
} else {
this.reason = reason
}
})
}

@@ -97,7 +88,10 @@

onConnect (abort) {
if (this.aborted) {
abort(this.reason)
} else {
this.abort = abort
onConnect (abort, context) {
this.abort = abort
if (!this.connectCalled) {
this.connectCalled = true
this.handler.onConnect(reason => {
this.aborted = true
this.abort(reason)
}, context)
}

@@ -104,0 +98,0 @@ }

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

const { assertCacheStore, assertCacheMethods, makeCacheKey, parseCacheControlHeader } = require('../util/cache.js')
const { AbortError } = require('../core/errors.js')
const AGE_HEADER = Buffer.from('age')
/**
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
*/

@@ -152,3 +151,3 @@ function sendGatewayTimeout (handler) {

*/
const respondWithCachedValue = ({ rawHeaders, statusCode, statusMessage, body }, age) => {
const respondWithCachedValue = ({ headers, statusCode, statusMessage, body }, age, context) => {
const stream = util.isStream(body)

@@ -161,7 +160,28 @@ ? body

const controller = {
resume () {
stream.resume()
},
pause () {
stream.pause()
},
get paused () {
return stream.isPaused()
},
get aborted () {
return stream.destroyed
},
get reason () {
return stream.errored
},
abort (reason) {
stream.destroy(reason ?? new AbortError())
}
}
stream
.on('error', function (err) {
if (!this.readableEnded) {
if (typeof handler.onError === 'function') {
handler.onError(err)
if (typeof handler.onResponseError === 'function') {
handler.onResponseError(controller, err)
} else {

@@ -173,30 +193,20 @@ throw err

.on('close', function () {
if (!this.errored && typeof handler.onComplete === 'function') {
handler.onComplete([])
if (!this.errored) {
handler.onResponseEnd?.(controller, {})
}
})
if (typeof handler.onConnect === 'function') {
handler.onConnect((err) => {
stream.destroy(err)
})
handler.onRequestStart?.(controller, context)
if (stream.destroyed) {
return
}
if (stream.destroyed) {
return
}
if (typeof handler.onHeaders === 'function') {
// Add the age header
// https://www.rfc-editor.org/rfc/rfc9111.html#name-age
const age = Math.round((Date.now() - result.cachedAt) / 1000)
// Add the age header
// https://www.rfc-editor.org/rfc/rfc9111.html#name-age
// TODO (fix): What if headers.age already exists?
headers = age != null ? { ...headers, age: String(age) } : headers
// TODO (fix): What if rawHeaders already contains age header?
rawHeaders = [...rawHeaders, AGE_HEADER, Buffer.from(`${age}`)]
handler.onResponseStart?.(controller, statusCode, statusMessage, headers)
if (handler.onHeaders(statusCode, rawHeaders, () => stream?.resume(), statusMessage) === false) {
stream.pause()
}
}
if (opts.method === 'HEAD') {

@@ -206,5 +216,3 @@ stream.destroy()

stream.on('data', function (chunk) {
if (typeof handler.onData === 'function' && !handler.onData(chunk)) {
stream.pause()
}
handler.onResponseData?.(controller, chunk)
})

@@ -250,5 +258,5 @@ }

new CacheRevalidationHandler(
(success) => {
(success, context) => {
if (success) {
respondWithCachedValue(result, age)
respondWithCachedValue(result, age, context)
} else if (util.isStream(result.body)) {

@@ -267,3 +275,4 @@ result.body.on('error', () => {}).destroy()

}
respondWithCachedValue(result, age)
respondWithCachedValue(result, age, null)
}

@@ -270,0 +279,0 @@

@@ -16,16 +16,83 @@ 'use strict'

/**
* @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
*/
const cacheKey = {
/** @type {Record<string, string[] | string>} */
let headers
if (opts.headers == null) {
headers = {}
} else if (typeof opts.headers[Symbol.iterator] === 'function') {
headers = {}
for (const x of opts.headers) {
if (!Array.isArray(x)) {
throw new Error('opts.headers is not a valid header map')
}
const [key, val] = x
if (typeof key !== 'string' || typeof val !== 'string') {
throw new Error('opts.headers is not a valid header map')
}
headers[key] = val
}
} else if (typeof opts.headers === 'object') {
headers = opts.headers
} else {
throw new Error('opts.headers is not an object')
}
return {
origin: opts.origin.toString(),
method: opts.method,
path: opts.path,
headers: opts.headers
headers
}
}
return cacheKey
/**
* @param {any} key
*/
function assertCacheKey (key) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}
for (const property of ['origin', 'method', 'path']) {
if (typeof key[property] !== 'string') {
throw new TypeError(`expected key.${property} to be string, got ${typeof key[property]}`)
}
}
if (key.headers !== undefined && typeof key.headers !== 'object') {
throw new TypeError(`expected headers to be object, got ${typeof key}`)
}
}
/**
* @param {any} value
*/
function assertCacheValue (value) {
if (typeof value !== 'object') {
throw new TypeError(`expected value to be object, got ${typeof value}`)
}
for (const property of ['statusCode', 'cachedAt', 'staleAt', 'deleteAt']) {
if (typeof value[property] !== 'number') {
throw new TypeError(`expected value.${property} to be number, got ${typeof value[property]}`)
}
}
if (typeof value.statusMessage !== 'string') {
throw new TypeError(`expected value.statusMessage to be string, got ${typeof value.statusMessage}`)
}
if (value.headers != null && typeof value.headers !== 'object') {
throw new TypeError(`expected value.rawHeaders to be object, got ${typeof value.headers}`)
}
if (value.vary !== undefined && typeof value.vary !== 'object') {
throw new TypeError(`expected value.vary to be object, got ${typeof value.vary}`)
}
if (value.etag !== undefined && typeof value.etag !== 'string') {
throw new TypeError(`expected value.etag to be string, got ${typeof value.etag}`)
}
}
/**
* @see https://www.rfc-editor.org/rfc/rfc9111.html#name-cache-control

@@ -253,6 +320,2 @@ * @see https://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml

}
if (typeof store.isFull !== 'undefined' && typeof store.isFull !== 'boolean') {
throw new TypeError(`${name} needs a isFull getter with type boolean or undefined, current type: ${typeof store.isFull}`)
}
}

@@ -281,2 +344,4 @@ /**

makeCacheKey,
assertCacheKey,
assertCacheValue,
parseCacheControlHeader,

@@ -283,0 +348,0 @@ parseVaryHeader,

{
"name": "undici",
"version": "7.0.0-alpha.6",
"version": "7.0.0-alpha.7",
"description": "An HTTP/1.1 client, written from scratch for Node.js",

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

@@ -14,3 +14,3 @@ import { URL } from 'url'

/** Dispatches a request. */
dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandlers): boolean
dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
}

@@ -17,0 +17,0 @@

@@ -8,2 +8,6 @@ import { Readable, Writable } from 'node:stream'

export interface CacheHandlerOptions {
store: CacheStore
}
export interface CacheOptions {

@@ -32,3 +36,3 @@ store?: CacheStore

statusMessage: string
rawHeaders: Buffer[]
headers: Record<string, string | string[]>
vary?: Record<string, string | string[]>

@@ -50,3 +54,4 @@ etag?: string

statusMessage: string
rawHeaders: Buffer[]
headers: Record<string, string | string[]>
etag?: string
body: null | Readable | Iterable<Buffer> | AsyncIterable<Buffer> | Buffer | Iterable<string> | AsyncIterable<string> | string

@@ -76,9 +81,9 @@ cachedAt: number

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

@@ -98,2 +103,35 @@

}
export interface SqliteCacheStoreOpts {
/**
* Location of the database
* @default ':memory:'
*/
location?: string
/**
* @default Infinity
*/
maxCount?: number
/**
* @default Infinity
*/
maxEntrySize?: number
}
export class SqliteCacheStore implements CacheStore {
constructor (opts?: SqliteCacheStoreOpts)
/**
* Closes the connection to the database
*/
close (): void
get (key: CacheKey): GetResult | Promise<GetResult | undefined> | undefined
createWriteStream (key: CacheKey, value: CacheValue): Writable | undefined
delete (key: CacheKey): void | Promise<void>
}
}

@@ -18,3 +18,3 @@ import { URL } from 'url'

/** Dispatches a request. This API is expected to evolve through semver-major versions and is less stable than the preceding higher level APIs. It is primarily intended for library developers who implement higher level APIs on top of this. */
dispatch (options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandlers): boolean
dispatch (options: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
/** Starts two-way communications with the requested resource. */

@@ -107,3 +107,3 @@ connect<TOpaque = null>(options: Dispatcher.ConnectOptions<TOpaque>): Promise<Dispatcher.ConnectData<TOpaque>>

/** Default: `null` */
headers?: IncomingHttpHeaders | string[] | Iterable<[string, string | string[] | undefined]> | null;
headers?: Record<string, string | string[]> | IncomingHttpHeaders | string[] | Iterable<[string, string | string[] | undefined]> | null;
/** Query string params to be embedded in the request URL. Default: `null` */

@@ -218,18 +218,43 @@ query?: Record<string, any>;

export type StreamFactory<TOpaque = null> = (data: StreamFactoryData<TOpaque>) => Writable
export interface DispatchHandlers {
export interface DispatchController {
get aborted () : boolean
get paused () : boolean
get reason () : Error | null
abort (reason: Error): void
pause(): void
resume(): void
}
export interface DispatchHandler {
onRequestStart?(controller: DispatchController, context: any): void;
onRequestUpgrade?(controller: DispatchController, statusCode: number, headers: IncomingHttpHeaders, socket: Duplex): void;
onResponseStart?(controller: DispatchController, statusCode: number, statusMessage: string | null, headers: IncomingHttpHeaders): void;
onResponseData?(controller: DispatchController, chunk: Buffer): void;
onResponseEnd?(controller: DispatchController, trailers: IncomingHttpHeaders): void;
onResponseError?(controller: DispatchController, error: Error): void;
/** Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails. */
/** @deprecated */
onConnect?(abort: (err?: Error) => void): void;
/** Invoked when an error has occurred. */
/** @deprecated */
onError?(err: Error): void;
/** Invoked when request is upgraded either due to a `Upgrade` header or `CONNECT` method. */
/** @deprecated */
onUpgrade?(statusCode: number, headers: Buffer[] | string[] | null, socket: Duplex): void;
/** Invoked when response is received, before headers have been read. **/
/** @deprecated */
onResponseStarted?(): void;
/** Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. */
/** @deprecated */
onHeaders?(statusCode: number, headers: Buffer[], resume: () => void, statusText: string): boolean;
/** Invoked when response payload data is received. */
/** @deprecated */
onData?(chunk: Buffer): boolean;
/** Invoked when response payload and trailers have been received and the request has completed. */
/** @deprecated */
onComplete?(trailers: string[] | null): void;
/** Invoked when a body chunk is sent to the server. May be invoked multiple times for chunked requests */
/** @deprecated */
onBodySent?(chunkSize: number, totalBytesSent: number): void;

@@ -236,0 +261,0 @@ }

@@ -9,3 +9,3 @@ import Agent from './agent'

dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandlers): boolean
dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
}

@@ -12,0 +12,0 @@

import Dispatcher from './dispatcher'
export declare class RedirectHandler implements Dispatcher.DispatchHandlers {
export declare class RedirectHandler implements Dispatcher.DispatchHandler {
constructor (

@@ -8,3 +8,3 @@ dispatch: Dispatcher,

opts: Dispatcher.DispatchOptions,
handler: Dispatcher.DispatchHandlers,
handler: Dispatcher.DispatchHandler,
redirectionLimitReached: boolean

@@ -14,4 +14,4 @@ )

export declare class DecoratorHandler implements Dispatcher.DispatchHandlers {
constructor (handler: Dispatcher.DispatchHandlers)
export declare class DecoratorHandler implements Dispatcher.DispatchHandler {
constructor (handler: Dispatcher.DispatchHandler)
}

@@ -20,3 +20,3 @@ import Agent from './agent'

/** Dispatches a mocked request. */
dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandlers): boolean
dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
/** Closes the mock agent and waits for registered mock pools and clients to also close before resolving. */

@@ -23,0 +23,0 @@ close (): Promise<void>

@@ -14,3 +14,3 @@ import Client from './client'

/** Dispatches a mocked request. */
dispatch (options: Dispatcher.DispatchOptions, handlers: Dispatcher.DispatchHandlers): boolean
dispatch (options: Dispatcher.DispatchOptions, handlers: Dispatcher.DispatchHandler): boolean
/** Closes the mock client and gracefully waits for enqueued requests to complete. */

@@ -17,0 +17,0 @@ close (): Promise<void>

@@ -14,3 +14,3 @@ import Pool from './pool'

/** Dispatches a mocked request. */
dispatch (options: Dispatcher.DispatchOptions, handlers: Dispatcher.DispatchHandlers): boolean
dispatch (options: Dispatcher.DispatchOptions, handlers: Dispatcher.DispatchHandler): boolean
/** Closes the mock pool and gracefully waits for enqueued requests to complete. */

@@ -17,0 +17,0 @@ close (): Promise<void>

@@ -11,3 +11,3 @@ import Agent from './agent'

dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandlers): boolean
dispatch (options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean
close (): Promise<void>

@@ -14,0 +14,0 @@ }

@@ -5,3 +5,3 @@ import Dispatcher from './dispatcher'

declare class RetryHandler implements Dispatcher.DispatchHandlers {
declare class RetryHandler implements Dispatcher.DispatchHandler {
constructor (

@@ -36,3 +36,3 @@ options: Dispatcher.DispatchOptions & {

callback: OnRetryCallback
) => number | null
) => void

@@ -116,4 +116,4 @@ export interface RetryOptions {

dispatch: Dispatcher['dispatch'];
handler: Dispatcher.DispatchHandlers;
handler: Dispatcher.DispatchHandler;
}
}
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