-
Notifications
You must be signed in to change notification settings - Fork 565
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: add jsdoc and do minor changes in utils.js #3550
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,10 @@ class BodyAsyncIterable { | |
} | ||
} | ||
|
||
/** | ||
* @param {*} body | ||
* @returns {*} | ||
*/ | ||
function wrapRequestBody (body) { | ||
if (isStream(body)) { | ||
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp | ||
|
@@ -67,11 +71,19 @@ function wrapRequestBody (body) { | |
} | ||
} | ||
|
||
/** | ||
* @param {*} obj | ||
* @returns {obj is import('node:stream').Stream} | ||
*/ | ||
function isStream (obj) { | ||
return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function' | ||
} | ||
|
||
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License) | ||
/** | ||
* @param {*} object | ||
* @returns {object is Blob} | ||
* based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License) | ||
*/ | ||
function isBlobLike (object) { | ||
if (object === null) { | ||
return false | ||
|
@@ -108,6 +120,10 @@ function serializePathWithQuery (url, queryParams) { | |
return url | ||
} | ||
|
||
/** | ||
* @param {number|string|undefined} port | ||
* @returns {boolean} | ||
*/ | ||
function isValidPort (port) { | ||
const value = parseInt(port, 10) | ||
return ( | ||
|
@@ -117,6 +133,12 @@ function isValidPort (port) { | |
) | ||
} | ||
|
||
/** | ||
* Check if the value is a valid http or https prefixed string. | ||
* | ||
* @param {string} value | ||
* @returns {boolean} | ||
*/ | ||
function isHttpOrHttpsPrefixed (value) { | ||
return ( | ||
value != null && | ||
|
@@ -134,8 +156,15 @@ function isHttpOrHttpsPrefixed (value) { | |
) | ||
} | ||
|
||
/** | ||
* @param {string|URL|Record<string,string>} url | ||
* @returns {URL} | ||
*/ | ||
function parseURL (url) { | ||
if (typeof url === 'string') { | ||
/** | ||
* @type {URL} | ||
*/ | ||
url = new URL(url) | ||
|
||
if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { | ||
|
@@ -205,6 +234,10 @@ function parseURL (url) { | |
return url | ||
} | ||
|
||
/** | ||
* @param {string|URL|Record<string, string>} url | ||
* @returns {URL} | ||
*/ | ||
function parseOrigin (url) { | ||
url = parseURL(url) | ||
|
||
|
@@ -215,6 +248,10 @@ function parseOrigin (url) { | |
return url | ||
} | ||
|
||
/** | ||
* @param {string} host | ||
* @returns {string} | ||
*/ | ||
function getHostname (host) { | ||
if (host[0] === '[') { | ||
const idx = host.indexOf(']') | ||
|
@@ -229,8 +266,12 @@ function getHostname (host) { | |
return host.substring(0, idx) | ||
} | ||
|
||
// IP addresses are not valid server names per RFC6066 | ||
// > Currently, the only server names supported are DNS hostnames | ||
/** | ||
* IP addresses are not valid server names per RFC6066 | ||
* Currently, the only server names supported are DNS hostnames | ||
* @param {string|null} host | ||
* @returns {string|null} | ||
*/ | ||
function getServerName (host) { | ||
if (!host) { | ||
return null | ||
|
@@ -246,18 +287,34 @@ function getServerName (host) { | |
return servername | ||
} | ||
|
||
/** | ||
* @param {*} obj | ||
* @returns {*} | ||
*/ | ||
function deepClone (obj) { | ||
return JSON.parse(JSON.stringify(obj)) | ||
} | ||
|
||
/** | ||
* @param {*} obj | ||
* @returns {obj is AsyncIterable} | ||
*/ | ||
function isAsyncIterable (obj) { | ||
return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function') | ||
} | ||
|
||
/** | ||
* @param {*} obj | ||
* @returns {obj is Iterable} | ||
*/ | ||
function isIterable (obj) { | ||
return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function')) | ||
} | ||
|
||
/** | ||
* @param {Blob|Buffer|import ('stream').Stream} body | ||
* @returns {number|null} | ||
*/ | ||
function bodyLength (body) { | ||
if (body == null) { | ||
return 0 | ||
|
@@ -275,10 +332,19 @@ function bodyLength (body) { | |
return null | ||
} | ||
|
||
/** | ||
* @param {import ('stream').Stream} body | ||
* @returns {boolean} | ||
*/ | ||
function isDestroyed (body) { | ||
return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body))) | ||
} | ||
|
||
/** | ||
* @param {import ('stream').Stream} stream | ||
* @param {Error} [err] | ||
* @returns | ||
*/ | ||
function destroy (stream, err) { | ||
if (stream == null || !isStream(stream) || isDestroyed(stream)) { | ||
return | ||
|
@@ -303,8 +369,12 @@ function destroy (stream, err) { | |
} | ||
|
||
const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/ | ||
/** | ||
* @param {string} val | ||
* @returns {number | null} | ||
*/ | ||
function parseKeepAliveTimeout (val) { | ||
const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR) | ||
const m = val.match(KEEPALIVE_TIMEOUT_EXPR) | ||
return m ? parseInt(m[1], 10) * 1000 : null | ||
} | ||
|
||
|
@@ -329,12 +399,13 @@ function bufferToLowerCasedHeaderName (value) { | |
} | ||
|
||
/** | ||
* @param {Record<string, string | string[]> | (Buffer | string | (Buffer | string)[])[]} headers | ||
* @param {(Buffer | string)[]} headers | ||
* @param {Record<string, string | string[]>} [obj] | ||
* @returns {Record<string, string | string[]>} | ||
*/ | ||
function parseHeaders (headers, obj) { | ||
if (obj === undefined) obj = {} | ||
|
||
for (let i = 0; i < headers.length; i += 2) { | ||
const key = headerNameToString(headers[i]) | ||
let val = obj[key] | ||
|
@@ -363,17 +434,24 @@ function parseHeaders (headers, obj) { | |
return obj | ||
} | ||
|
||
/** | ||
* @param {Buffer[]} headers | ||
* @returns {string[]} | ||
*/ | ||
function parseRawHeaders (headers) { | ||
const len = headers.length | ||
const ret = new Array(len) | ||
const headersLength = headers.length | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can reuse the headersLength variable for the for loop |
||
/** | ||
* @type {string[]} | ||
*/ | ||
const ret = new Array(headersLength) | ||
|
||
let hasContentLength = false | ||
let contentDispositionIdx = -1 | ||
let key | ||
let val | ||
let kLen = 0 | ||
|
||
for (let n = 0; n < headers.length; n += 2) { | ||
for (let n = 0; n < headersLength; n += 2) { | ||
key = headers[n] | ||
val = headers[n + 1] | ||
|
||
|
@@ -439,13 +517,33 @@ function validateHandler (handler, method, upgrade) { | |
} | ||
} | ||
|
||
// A body is disturbed if it has been read from and it cannot | ||
// be re-used without losing state or data. | ||
/** | ||
* A body is disturbed if it has been read from and it cannot be re-used without | ||
* losing state or data. | ||
* @param {import('node:stream').Readable} body | ||
* @returns {boolean} | ||
*/ | ||
function isDisturbed (body) { | ||
// TODO (fix): Why is body[kBodyUsed] needed? | ||
return !!(body && (stream.isDisturbed(body) || body[kBodyUsed])) | ||
} | ||
|
||
/** | ||
* @typedef {object} SocketInfo | ||
* @property {string} [localAddress] | ||
* @property {number} [localPort] | ||
* @property {string} [remoteAddress] | ||
* @property {number} [remotePort] | ||
* @property {string} [remoteFamily] | ||
* @property {number} [timeout] | ||
* @property {number} bytesWritten | ||
* @property {number} bytesRead | ||
*/ | ||
|
||
/** | ||
* @param {import('net').Socket} socket | ||
* @returns {SocketInfo} | ||
*/ | ||
function getSocketInfo (socket) { | ||
return { | ||
localAddress: socket.localAddress, | ||
|
@@ -459,7 +557,9 @@ function getSocketInfo (socket) { | |
} | ||
} | ||
|
||
/** @type {globalThis['ReadableStream']} */ | ||
/** | ||
* @returns {globalThis['ReadableStream']} | ||
*/ | ||
function ReadableStreamFrom (iterable) { | ||
// We cannot use ReadableStream.from here because it does not return a byte stream. | ||
|
||
|
@@ -484,16 +584,20 @@ function ReadableStreamFrom (iterable) { | |
} | ||
return controller.desiredSize > 0 | ||
}, | ||
async cancel (reason) { | ||
async cancel () { | ||
await iterator.return() | ||
}, | ||
type: 'bytes' | ||
} | ||
) | ||
} | ||
|
||
// The chunk should be a FormData instance and contains | ||
// all the required methods. | ||
/** | ||
* The object should be a FormData instance and contains all the required | ||
* methods. | ||
* @param {*} object | ||
* @returns {object is FormData} | ||
*/ | ||
function isFormDataLike (object) { | ||
return ( | ||
object && | ||
|
@@ -538,6 +642,7 @@ function isUSVString (val) { | |
/** | ||
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6 | ||
* @param {number} c | ||
* @returns {boolean} | ||
*/ | ||
function isTokenCharCode (c) { | ||
switch (c) { | ||
|
@@ -568,6 +673,7 @@ function isTokenCharCode (c) { | |
|
||
/** | ||
* @param {string} characters | ||
* @returns {boolean} | ||
*/ | ||
function isValidHTTPToken (characters) { | ||
if (characters.length === 0) { | ||
|
@@ -594,17 +700,24 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/ | |
|
||
/** | ||
* @param {string} characters | ||
* @returns {boolean} | ||
*/ | ||
function isValidHeaderValue (characters) { | ||
return !headerCharRegex.test(characters) | ||
} | ||
|
||
// Parsed accordingly to RFC 9110 | ||
// https://www.rfc-editor.org/rfc/rfc9110#field.content-range | ||
const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+)?$/ | ||
|
||
/** | ||
* Parse accordingly to RFC 9110 | ||
* @see https://www.rfc-editor.org/rfc/rfc9110#field.content-range | ||
* @param {string} [range] | ||
* @returns | ||
*/ | ||
function parseRangeHeader (range) { | ||
if (range == null || range === '') return { start: 0, end: null, size: null } | ||
|
||
const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null | ||
const m = range ? range.match(rangeHeaderRegex) : null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. extracted the range regex for better perf |
||
return m | ||
? { | ||
start: parseInt(m[1]), | ||
|
@@ -614,20 +727,35 @@ function parseRangeHeader (range) { | |
: null | ||
} | ||
|
||
/** | ||
* @param {Record<string|symbol, any>} obj | ||
* @param {string} name | ||
* @param {Function} listener | ||
*/ | ||
function addListener (obj, name, listener) { | ||
const listeners = (obj[kListeners] ??= []) | ||
listeners.push([name, listener]) | ||
obj.on(name, listener) | ||
return obj | ||
} | ||
|
||
/** | ||
* @param {Record<string|symbol, any>} obj | ||
*/ | ||
function removeAllListeners (obj) { | ||
for (const [name, listener] of obj[kListeners] ?? []) { | ||
obj.removeListener(name, listener) | ||
if (obj[kListeners] != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if obj[kListeners] is not null or undefined, then we know it is an array, so only do the steps if we have somthing set. |
||
for (const [name, listener] of obj[kListeners]) { | ||
obj.removeListener(name, listener) | ||
} | ||
obj[kListeners] = null | ||
} | ||
obj[kListeners] = null | ||
} | ||
|
||
/** | ||
* @param {import ('../dispatcher/client')} client | ||
* @param {import ('../core/request')} request | ||
* @param {Error} err | ||
*/ | ||
function errorRequest (client, request, err) { | ||
try { | ||
request.onError(err) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parseKeepAliveTimeout gets according to my analysis always a string, so we can avoid the toString() call