aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/undici/lib/util
diff options
context:
space:
mode:
Diffstat (limited to 'vanilla/node_modules/undici/lib/util')
-rw-r--r--vanilla/node_modules/undici/lib/util/cache.js405
-rw-r--r--vanilla/node_modules/undici/lib/util/date.js653
-rw-r--r--vanilla/node_modules/undici/lib/util/promise.js28
-rw-r--r--vanilla/node_modules/undici/lib/util/runtime-features.js124
-rw-r--r--vanilla/node_modules/undici/lib/util/stats.js32
-rw-r--r--vanilla/node_modules/undici/lib/util/timers.js425
6 files changed, 1667 insertions, 0 deletions
diff --git a/vanilla/node_modules/undici/lib/util/cache.js b/vanilla/node_modules/undici/lib/util/cache.js
new file mode 100644
index 0000000..b7627d0
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/util/cache.js
@@ -0,0 +1,405 @@
+'use strict'
+
+const {
+ safeHTTPMethods,
+ pathHasQueryOrFragment
+} = require('../core/util')
+
+const { serializePathWithQuery } = require('../core/util')
+
+/**
+ * @param {import('../../types/dispatcher.d.ts').default.DispatchOptions} opts
+ */
+function makeCacheKey (opts) {
+ if (!opts.origin) {
+ throw new Error('opts.origin is undefined')
+ }
+
+ let fullPath = opts.path || '/'
+
+ if (opts.query && !pathHasQueryOrFragment(opts.path)) {
+ fullPath = serializePathWithQuery(fullPath, opts.query)
+ }
+
+ return {
+ origin: opts.origin.toString(),
+ method: opts.method,
+ path: fullPath,
+ headers: opts.headers
+ }
+}
+
+/**
+ * @param {Record<string, string[] | string>}
+ * @returns {Record<string, string[] | string>}
+ */
+function normalizeHeaders (opts) {
+ 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.toLowerCase()] = val
+ }
+ } else if (typeof opts.headers === 'object') {
+ headers = {}
+
+ for (const key of Object.keys(opts.headers)) {
+ headers[key.toLowerCase()] = opts.headers[key]
+ }
+ } else {
+ throw new Error('opts.headers is not an object')
+ }
+
+ return headers
+}
+
+/**
+ * @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
+ * @see https://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml
+
+ * @param {string | string[]} header
+ * @returns {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
+ */
+function parseCacheControlHeader (header) {
+ /**
+ * @type {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives}
+ */
+ const output = {}
+
+ let directives
+ if (Array.isArray(header)) {
+ directives = []
+
+ for (const directive of header) {
+ directives.push(...directive.split(','))
+ }
+ } else {
+ directives = header.split(',')
+ }
+
+ for (let i = 0; i < directives.length; i++) {
+ const directive = directives[i].toLowerCase()
+ const keyValueDelimiter = directive.indexOf('=')
+
+ let key
+ let value
+ if (keyValueDelimiter !== -1) {
+ key = directive.substring(0, keyValueDelimiter).trimStart()
+ value = directive.substring(keyValueDelimiter + 1)
+ } else {
+ key = directive.trim()
+ }
+
+ switch (key) {
+ case 'min-fresh':
+ case 'max-stale':
+ case 'max-age':
+ case 's-maxage':
+ case 'stale-while-revalidate':
+ case 'stale-if-error': {
+ if (value === undefined || value[0] === ' ') {
+ continue
+ }
+
+ if (
+ value.length >= 2 &&
+ value[0] === '"' &&
+ value[value.length - 1] === '"'
+ ) {
+ value = value.substring(1, value.length - 1)
+ }
+
+ const parsedValue = parseInt(value, 10)
+ // eslint-disable-next-line no-self-compare
+ if (parsedValue !== parsedValue) {
+ continue
+ }
+
+ if (key === 'max-age' && key in output && output[key] >= parsedValue) {
+ continue
+ }
+
+ output[key] = parsedValue
+
+ break
+ }
+ case 'private':
+ case 'no-cache': {
+ if (value) {
+ // The private and no-cache directives can be unqualified (aka just
+ // `private` or `no-cache`) or qualified (w/ a value). When they're
+ // qualified, it's a list of headers like `no-cache=header1`,
+ // `no-cache="header1"`, or `no-cache="header1, header2"`
+ // If we're given multiple headers, the comma messes us up since
+ // we split the full header by commas. So, let's loop through the
+ // remaining parts in front of us until we find one that ends in a
+ // quote. We can then just splice all of the parts in between the
+ // starting quote and the ending quote out of the directives array
+ // and continue parsing like normal.
+ // https://www.rfc-editor.org/rfc/rfc9111.html#name-no-cache-2
+ if (value[0] === '"') {
+ // Something like `no-cache="some-header"` OR `no-cache="some-header, another-header"`.
+
+ // Add the first header on and cut off the leading quote
+ const headers = [value.substring(1)]
+
+ let foundEndingQuote = value[value.length - 1] === '"'
+ if (!foundEndingQuote) {
+ // Something like `no-cache="some-header, another-header"`
+ // This can still be something invalid, e.g. `no-cache="some-header, ...`
+ for (let j = i + 1; j < directives.length; j++) {
+ const nextPart = directives[j]
+ const nextPartLength = nextPart.length
+
+ headers.push(nextPart.trim())
+
+ if (nextPartLength !== 0 && nextPart[nextPartLength - 1] === '"') {
+ foundEndingQuote = true
+ break
+ }
+ }
+ }
+
+ if (foundEndingQuote) {
+ let lastHeader = headers[headers.length - 1]
+ if (lastHeader[lastHeader.length - 1] === '"') {
+ lastHeader = lastHeader.substring(0, lastHeader.length - 1)
+ headers[headers.length - 1] = lastHeader
+ }
+
+ if (key in output) {
+ output[key] = output[key].concat(headers)
+ } else {
+ output[key] = headers
+ }
+ }
+ } else {
+ // Something like `no-cache="some-header"`
+ if (key in output) {
+ output[key] = output[key].concat(value)
+ } else {
+ output[key] = [value]
+ }
+ }
+
+ break
+ }
+ }
+ // eslint-disable-next-line no-fallthrough
+ case 'public':
+ case 'no-store':
+ case 'must-revalidate':
+ case 'proxy-revalidate':
+ case 'immutable':
+ case 'no-transform':
+ case 'must-understand':
+ case 'only-if-cached':
+ if (value) {
+ // These are qualified (something like `public=...`) when they aren't
+ // allowed to be, skip
+ continue
+ }
+
+ output[key] = true
+ break
+ default:
+ // Ignore unknown directives as per https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.3-1
+ continue
+ }
+ }
+
+ return output
+}
+
+/**
+ * @param {string | string[]} varyHeader Vary header from the server
+ * @param {Record<string, string | string[]>} headers Request headers
+ * @returns {Record<string, string | string[]>}
+ */
+function parseVaryHeader (varyHeader, headers) {
+ if (typeof varyHeader === 'string' && varyHeader.includes('*')) {
+ return headers
+ }
+
+ const output = /** @type {Record<string, string | string[] | null>} */ ({})
+
+ const varyingHeaders = typeof varyHeader === 'string'
+ ? varyHeader.split(',')
+ : varyHeader
+
+ for (const header of varyingHeaders) {
+ const trimmedHeader = header.trim().toLowerCase()
+
+ output[trimmedHeader] = headers[trimmedHeader] ?? null
+ }
+
+ return output
+}
+
+/**
+ * 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
+ * @returns {asserts store is import('../../types/cache-interceptor.d.ts').default.CacheStore}
+ */
+function assertCacheStore (store, name = 'CacheStore') {
+ if (typeof store !== 'object' || store === null) {
+ throw new TypeError(`expected type of ${name} to be a CacheStore, got ${store === null ? 'null' : typeof store}`)
+ }
+
+ for (const fn of ['get', 'createWriteStream', 'delete']) {
+ if (typeof store[fn] !== 'function') {
+ throw new TypeError(`${name} needs to have a \`${fn}()\` function`)
+ }
+ }
+}
+/**
+ * @param {unknown} methods
+ * @returns {asserts methods is import('../../types/cache-interceptor.d.ts').default.CacheMethods[]}
+ */
+function assertCacheMethods (methods, name = 'CacheMethods') {
+ if (!Array.isArray(methods)) {
+ throw new TypeError(`expected type of ${name} needs to be an array, got ${methods === null ? 'null' : typeof methods}`)
+ }
+
+ if (methods.length === 0) {
+ throw new TypeError(`${name} needs to have at least one method`)
+ }
+
+ for (const method of methods) {
+ if (!safeHTTPMethods.includes(method)) {
+ throw new TypeError(`element of ${name}-array needs to be one of following values: ${safeHTTPMethods.join(', ')}, got ${method}`)
+ }
+ }
+}
+
+/**
+ * Creates a string key for request deduplication purposes.
+ * This key is used to identify in-flight requests that can be shared.
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
+ * @param {Set<string>} [excludeHeaders] Set of lowercase header names to exclude from the key
+ * @returns {string}
+ */
+function makeDeduplicationKey (cacheKey, excludeHeaders) {
+ // Create a deterministic string key from the cache key
+ // Include origin, method, path, and sorted headers
+ let key = `${cacheKey.origin}:${cacheKey.method}:${cacheKey.path}`
+
+ if (cacheKey.headers) {
+ const sortedHeaders = Object.keys(cacheKey.headers).sort()
+ for (const header of sortedHeaders) {
+ // Skip excluded headers
+ if (excludeHeaders?.has(header.toLowerCase())) {
+ continue
+ }
+ const value = cacheKey.headers[header]
+ key += `:${header}=${Array.isArray(value) ? value.join(',') : value}`
+ }
+ }
+
+ return key
+}
+
+module.exports = {
+ makeCacheKey,
+ normalizeHeaders,
+ assertCacheKey,
+ assertCacheValue,
+ parseCacheControlHeader,
+ parseVaryHeader,
+ isEtagUsable,
+ assertCacheMethods,
+ assertCacheStore,
+ makeDeduplicationKey
+}
diff --git a/vanilla/node_modules/undici/lib/util/date.js b/vanilla/node_modules/undici/lib/util/date.js
new file mode 100644
index 0000000..99dd150
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/util/date.js
@@ -0,0 +1,653 @@
+'use strict'
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9110.html#name-date-time-formats
+ *
+ * @param {string} date
+ * @returns {Date | undefined}
+ */
+function parseHttpDate (date) {
+ // Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
+ // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+ // Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
+
+ switch (date[3]) {
+ case ',': return parseImfDate(date)
+ case ' ': return parseAscTimeDate(date)
+ default: return parseRfc850Date(date)
+ }
+}
+
+/**
+ * @see https://httpwg.org/specs/rfc9110.html#preferred.date.format
+ *
+ * @param {string} date
+ * @returns {Date | undefined}
+ */
+function parseImfDate (date) {
+ if (
+ date.length !== 29 ||
+ date[4] !== ' ' ||
+ date[7] !== ' ' ||
+ date[11] !== ' ' ||
+ date[16] !== ' ' ||
+ date[19] !== ':' ||
+ date[22] !== ':' ||
+ date[25] !== ' ' ||
+ date[26] !== 'G' ||
+ date[27] !== 'M' ||
+ date[28] !== 'T'
+ ) {
+ return undefined
+ }
+
+ let weekday = -1
+ if (date[0] === 'S' && date[1] === 'u' && date[2] === 'n') { // Sunday
+ weekday = 0
+ } else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n') { // Monday
+ weekday = 1
+ } else if (date[0] === 'T' && date[1] === 'u' && date[2] === 'e') { // Tuesday
+ weekday = 2
+ } else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd') { // Wednesday
+ weekday = 3
+ } else if (date[0] === 'T' && date[1] === 'h' && date[2] === 'u') { // Thursday
+ weekday = 4
+ } else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i') { // Friday
+ weekday = 5
+ } else if (date[0] === 'S' && date[1] === 'a' && date[2] === 't') { // Saturday
+ weekday = 6
+ } else {
+ return undefined // Not a valid day of the week
+ }
+
+ let day = 0
+ if (date[5] === '0') {
+ // Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
+ const code = date.charCodeAt(6)
+ if (code < 49 || code > 57) {
+ return undefined // Not a digit
+ }
+ day = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(5)
+ if (code1 < 49 || code1 > 51) {
+ return undefined // Not a digit between 1 and 3
+ }
+ const code2 = date.charCodeAt(6)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let monthIdx = -1
+ if (
+ (date[8] === 'J' && date[9] === 'a' && date[10] === 'n')
+ ) {
+ monthIdx = 0 // Jan
+ } else if (
+ (date[8] === 'F' && date[9] === 'e' && date[10] === 'b')
+ ) {
+ monthIdx = 1 // Feb
+ } else if (
+ (date[8] === 'M' && date[9] === 'a')
+ ) {
+ if (date[10] === 'r') {
+ monthIdx = 2 // Mar
+ } else if (date[10] === 'y') {
+ monthIdx = 4 // May
+ } else {
+ return undefined // Invalid month
+ }
+ } else if (
+ (date[8] === 'J')
+ ) {
+ if (date[9] === 'a' && date[10] === 'n') {
+ monthIdx = 0 // Jan
+ } else if (date[9] === 'u') {
+ if (date[10] === 'n') {
+ monthIdx = 5 // Jun
+ } else if (date[10] === 'l') {
+ monthIdx = 6 // Jul
+ } else {
+ return undefined // Invalid month
+ }
+ } else {
+ return undefined // Invalid month
+ }
+ } else if (
+ (date[8] === 'A')
+ ) {
+ if (date[9] === 'p' && date[10] === 'r') {
+ monthIdx = 3 // Apr
+ } else if (date[9] === 'u' && date[10] === 'g') {
+ monthIdx = 7 // Aug
+ } else {
+ return undefined // Invalid month
+ }
+ } else if (
+ (date[8] === 'S' && date[9] === 'e' && date[10] === 'p')
+ ) {
+ monthIdx = 8 // Sep
+ } else if (
+ (date[8] === 'O' && date[9] === 'c' && date[10] === 't')
+ ) {
+ monthIdx = 9 // Oct
+ } else if (
+ (date[8] === 'N' && date[9] === 'o' && date[10] === 'v')
+ ) {
+ monthIdx = 10 // Nov
+ } else if (
+ (date[8] === 'D' && date[9] === 'e' && date[10] === 'c')
+ ) {
+ monthIdx = 11 // Dec
+ } else {
+ // Not a valid month
+ return undefined
+ }
+
+ const yearDigit1 = date.charCodeAt(12)
+ if (yearDigit1 < 48 || yearDigit1 > 57) {
+ return undefined // Not a digit
+ }
+ const yearDigit2 = date.charCodeAt(13)
+ if (yearDigit2 < 48 || yearDigit2 > 57) {
+ return undefined // Not a digit
+ }
+ const yearDigit3 = date.charCodeAt(14)
+ if (yearDigit3 < 48 || yearDigit3 > 57) {
+ return undefined // Not a digit
+ }
+ const yearDigit4 = date.charCodeAt(15)
+ if (yearDigit4 < 48 || yearDigit4 > 57) {
+ return undefined // Not a digit
+ }
+ const year = (yearDigit1 - 48) * 1000 + (yearDigit2 - 48) * 100 + (yearDigit3 - 48) * 10 + (yearDigit4 - 48)
+
+ let hour = 0
+ if (date[17] === '0') {
+ const code = date.charCodeAt(18)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ hour = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(17)
+ if (code1 < 48 || code1 > 50) {
+ return undefined // Not a digit between 0 and 2
+ }
+ const code2 = date.charCodeAt(18)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ if (code1 === 50 && code2 > 51) {
+ return undefined // Hour cannot be greater than 23
+ }
+ hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let minute = 0
+ if (date[20] === '0') {
+ const code = date.charCodeAt(21)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ minute = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(20)
+ if (code1 < 48 || code1 > 53) {
+ return undefined // Not a digit between 0 and 5
+ }
+ const code2 = date.charCodeAt(21)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let second = 0
+ if (date[23] === '0') {
+ const code = date.charCodeAt(24)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ second = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(23)
+ if (code1 < 48 || code1 > 53) {
+ return undefined // Not a digit between 0 and 5
+ }
+ const code2 = date.charCodeAt(24)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
+ return result.getUTCDay() === weekday ? result : undefined
+}
+
+/**
+ * @see https://httpwg.org/specs/rfc9110.html#obsolete.date.formats
+ *
+ * @param {string} date
+ * @returns {Date | undefined}
+ */
+function parseAscTimeDate (date) {
+ // This is assumed to be in UTC
+
+ if (
+ date.length !== 24 ||
+ date[7] !== ' ' ||
+ date[10] !== ' ' ||
+ date[19] !== ' '
+ ) {
+ return undefined
+ }
+
+ let weekday = -1
+ if (date[0] === 'S' && date[1] === 'u' && date[2] === 'n') { // Sunday
+ weekday = 0
+ } else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n') { // Monday
+ weekday = 1
+ } else if (date[0] === 'T' && date[1] === 'u' && date[2] === 'e') { // Tuesday
+ weekday = 2
+ } else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd') { // Wednesday
+ weekday = 3
+ } else if (date[0] === 'T' && date[1] === 'h' && date[2] === 'u') { // Thursday
+ weekday = 4
+ } else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i') { // Friday
+ weekday = 5
+ } else if (date[0] === 'S' && date[1] === 'a' && date[2] === 't') { // Saturday
+ weekday = 6
+ } else {
+ return undefined // Not a valid day of the week
+ }
+
+ let monthIdx = -1
+ if (
+ (date[4] === 'J' && date[5] === 'a' && date[6] === 'n')
+ ) {
+ monthIdx = 0 // Jan
+ } else if (
+ (date[4] === 'F' && date[5] === 'e' && date[6] === 'b')
+ ) {
+ monthIdx = 1 // Feb
+ } else if (
+ (date[4] === 'M' && date[5] === 'a')
+ ) {
+ if (date[6] === 'r') {
+ monthIdx = 2 // Mar
+ } else if (date[6] === 'y') {
+ monthIdx = 4 // May
+ } else {
+ return undefined // Invalid month
+ }
+ } else if (
+ (date[4] === 'J')
+ ) {
+ if (date[5] === 'a' && date[6] === 'n') {
+ monthIdx = 0 // Jan
+ } else if (date[5] === 'u') {
+ if (date[6] === 'n') {
+ monthIdx = 5 // Jun
+ } else if (date[6] === 'l') {
+ monthIdx = 6 // Jul
+ } else {
+ return undefined // Invalid month
+ }
+ } else {
+ return undefined // Invalid month
+ }
+ } else if (
+ (date[4] === 'A')
+ ) {
+ if (date[5] === 'p' && date[6] === 'r') {
+ monthIdx = 3 // Apr
+ } else if (date[5] === 'u' && date[6] === 'g') {
+ monthIdx = 7 // Aug
+ } else {
+ return undefined // Invalid month
+ }
+ } else if (
+ (date[4] === 'S' && date[5] === 'e' && date[6] === 'p')
+ ) {
+ monthIdx = 8 // Sep
+ } else if (
+ (date[4] === 'O' && date[5] === 'c' && date[6] === 't')
+ ) {
+ monthIdx = 9 // Oct
+ } else if (
+ (date[4] === 'N' && date[5] === 'o' && date[6] === 'v')
+ ) {
+ monthIdx = 10 // Nov
+ } else if (
+ (date[4] === 'D' && date[5] === 'e' && date[6] === 'c')
+ ) {
+ monthIdx = 11 // Dec
+ } else {
+ // Not a valid month
+ return undefined
+ }
+
+ let day = 0
+ if (date[8] === ' ') {
+ // Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
+ const code = date.charCodeAt(9)
+ if (code < 49 || code > 57) {
+ return undefined // Not a digit
+ }
+ day = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(8)
+ if (code1 < 49 || code1 > 51) {
+ return undefined // Not a digit between 1 and 3
+ }
+ const code2 = date.charCodeAt(9)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let hour = 0
+ if (date[11] === '0') {
+ const code = date.charCodeAt(12)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ hour = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(11)
+ if (code1 < 48 || code1 > 50) {
+ return undefined // Not a digit between 0 and 2
+ }
+ const code2 = date.charCodeAt(12)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ if (code1 === 50 && code2 > 51) {
+ return undefined // Hour cannot be greater than 23
+ }
+ hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let minute = 0
+ if (date[14] === '0') {
+ const code = date.charCodeAt(15)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ minute = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(14)
+ if (code1 < 48 || code1 > 53) {
+ return undefined // Not a digit between 0 and 5
+ }
+ const code2 = date.charCodeAt(15)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let second = 0
+ if (date[17] === '0') {
+ const code = date.charCodeAt(18)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ second = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(17)
+ if (code1 < 48 || code1 > 53) {
+ return undefined // Not a digit between 0 and 5
+ }
+ const code2 = date.charCodeAt(18)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ const yearDigit1 = date.charCodeAt(20)
+ if (yearDigit1 < 48 || yearDigit1 > 57) {
+ return undefined // Not a digit
+ }
+ const yearDigit2 = date.charCodeAt(21)
+ if (yearDigit2 < 48 || yearDigit2 > 57) {
+ return undefined // Not a digit
+ }
+ const yearDigit3 = date.charCodeAt(22)
+ if (yearDigit3 < 48 || yearDigit3 > 57) {
+ return undefined // Not a digit
+ }
+ const yearDigit4 = date.charCodeAt(23)
+ if (yearDigit4 < 48 || yearDigit4 > 57) {
+ return undefined // Not a digit
+ }
+ const year = (yearDigit1 - 48) * 1000 + (yearDigit2 - 48) * 100 + (yearDigit3 - 48) * 10 + (yearDigit4 - 48)
+
+ const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
+ return result.getUTCDay() === weekday ? result : undefined
+}
+
+/**
+ * @see https://httpwg.org/specs/rfc9110.html#obsolete.date.formats
+ *
+ * @param {string} date
+ * @returns {Date | undefined}
+ */
+function parseRfc850Date (date) {
+ let commaIndex = -1
+
+ let weekday = -1
+ if (date[0] === 'S') {
+ if (date[1] === 'u' && date[2] === 'n' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
+ weekday = 0 // Sunday
+ commaIndex = 6
+ } else if (date[1] === 'a' && date[2] === 't' && date[3] === 'u' && date[4] === 'r' && date[5] === 'd' && date[6] === 'a' && date[7] === 'y') {
+ weekday = 6 // Saturday
+ commaIndex = 8
+ }
+ } else if (date[0] === 'M' && date[1] === 'o' && date[2] === 'n' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
+ weekday = 1 // Monday
+ commaIndex = 6
+ } else if (date[0] === 'T') {
+ if (date[1] === 'u' && date[2] === 'e' && date[3] === 's' && date[4] === 'd' && date[5] === 'a' && date[6] === 'y') {
+ weekday = 2 // Tuesday
+ commaIndex = 7
+ } else if (date[1] === 'h' && date[2] === 'u' && date[3] === 'r' && date[4] === 's' && date[5] === 'd' && date[6] === 'a' && date[7] === 'y') {
+ weekday = 4 // Thursday
+ commaIndex = 8
+ }
+ } else if (date[0] === 'W' && date[1] === 'e' && date[2] === 'd' && date[3] === 'n' && date[4] === 'e' && date[5] === 's' && date[6] === 'd' && date[7] === 'a' && date[8] === 'y') {
+ weekday = 3 // Wednesday
+ commaIndex = 9
+ } else if (date[0] === 'F' && date[1] === 'r' && date[2] === 'i' && date[3] === 'd' && date[4] === 'a' && date[5] === 'y') {
+ weekday = 5 // Friday
+ commaIndex = 6
+ } else {
+ // Not a valid day name
+ return undefined
+ }
+
+ if (
+ date[commaIndex] !== ',' ||
+ (date.length - commaIndex - 1) !== 23 ||
+ date[commaIndex + 1] !== ' ' ||
+ date[commaIndex + 4] !== '-' ||
+ date[commaIndex + 8] !== '-' ||
+ date[commaIndex + 11] !== ' ' ||
+ date[commaIndex + 14] !== ':' ||
+ date[commaIndex + 17] !== ':' ||
+ date[commaIndex + 20] !== ' ' ||
+ date[commaIndex + 21] !== 'G' ||
+ date[commaIndex + 22] !== 'M' ||
+ date[commaIndex + 23] !== 'T'
+ ) {
+ return undefined
+ }
+
+ let day = 0
+ if (date[commaIndex + 2] === '0') {
+ // Single digit day, e.g. "Sun Nov 6 08:49:37 1994"
+ const code = date.charCodeAt(commaIndex + 3)
+ if (code < 49 || code > 57) {
+ return undefined // Not a digit
+ }
+ day = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(commaIndex + 2)
+ if (code1 < 49 || code1 > 51) {
+ return undefined // Not a digit between 1 and 3
+ }
+ const code2 = date.charCodeAt(commaIndex + 3)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ day = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let monthIdx = -1
+ if (
+ (date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'n')
+ ) {
+ monthIdx = 0 // Jan
+ } else if (
+ (date[commaIndex + 5] === 'F' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'b')
+ ) {
+ monthIdx = 1 // Feb
+ } else if (
+ (date[commaIndex + 5] === 'M' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'r')
+ ) {
+ monthIdx = 2 // Mar
+ } else if (
+ (date[commaIndex + 5] === 'A' && date[commaIndex + 6] === 'p' && date[commaIndex + 7] === 'r')
+ ) {
+ monthIdx = 3 // Apr
+ } else if (
+ (date[commaIndex + 5] === 'M' && date[commaIndex + 6] === 'a' && date[commaIndex + 7] === 'y')
+ ) {
+ monthIdx = 4 // May
+ } else if (
+ (date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'n')
+ ) {
+ monthIdx = 5 // Jun
+ } else if (
+ (date[commaIndex + 5] === 'J' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'l')
+ ) {
+ monthIdx = 6 // Jul
+ } else if (
+ (date[commaIndex + 5] === 'A' && date[commaIndex + 6] === 'u' && date[commaIndex + 7] === 'g')
+ ) {
+ monthIdx = 7 // Aug
+ } else if (
+ (date[commaIndex + 5] === 'S' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'p')
+ ) {
+ monthIdx = 8 // Sep
+ } else if (
+ (date[commaIndex + 5] === 'O' && date[commaIndex + 6] === 'c' && date[commaIndex + 7] === 't')
+ ) {
+ monthIdx = 9 // Oct
+ } else if (
+ (date[commaIndex + 5] === 'N' && date[commaIndex + 6] === 'o' && date[commaIndex + 7] === 'v')
+ ) {
+ monthIdx = 10 // Nov
+ } else if (
+ (date[commaIndex + 5] === 'D' && date[commaIndex + 6] === 'e' && date[commaIndex + 7] === 'c')
+ ) {
+ monthIdx = 11 // Dec
+ } else {
+ // Not a valid month
+ return undefined
+ }
+
+ const yearDigit1 = date.charCodeAt(commaIndex + 9)
+ if (yearDigit1 < 48 || yearDigit1 > 57) {
+ return undefined // Not a digit
+ }
+ const yearDigit2 = date.charCodeAt(commaIndex + 10)
+ if (yearDigit2 < 48 || yearDigit2 > 57) {
+ return undefined // Not a digit
+ }
+
+ let year = (yearDigit1 - 48) * 10 + (yearDigit2 - 48) // Convert ASCII codes to number
+
+ // RFC 6265 states that the year is in the range 1970-2069.
+ // @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1
+ //
+ // 3. If the year-value is greater than or equal to 70 and less than or
+ // equal to 99, increment the year-value by 1900.
+ // 4. If the year-value is greater than or equal to 0 and less than or
+ // equal to 69, increment the year-value by 2000.
+ year += year < 70 ? 2000 : 1900
+
+ let hour = 0
+ if (date[commaIndex + 12] === '0') {
+ const code = date.charCodeAt(commaIndex + 13)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ hour = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(commaIndex + 12)
+ if (code1 < 48 || code1 > 50) {
+ return undefined // Not a digit between 0 and 2
+ }
+ const code2 = date.charCodeAt(commaIndex + 13)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ if (code1 === 50 && code2 > 51) {
+ return undefined // Hour cannot be greater than 23
+ }
+ hour = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let minute = 0
+ if (date[commaIndex + 15] === '0') {
+ const code = date.charCodeAt(commaIndex + 16)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ minute = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(commaIndex + 15)
+ if (code1 < 48 || code1 > 53) {
+ return undefined // Not a digit between 0 and 5
+ }
+ const code2 = date.charCodeAt(commaIndex + 16)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ minute = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ let second = 0
+ if (date[commaIndex + 18] === '0') {
+ const code = date.charCodeAt(commaIndex + 19)
+ if (code < 48 || code > 57) {
+ return undefined // Not a digit
+ }
+ second = code - 48 // Convert ASCII code to number
+ } else {
+ const code1 = date.charCodeAt(commaIndex + 18)
+ if (code1 < 48 || code1 > 53) {
+ return undefined // Not a digit between 0 and 5
+ }
+ const code2 = date.charCodeAt(commaIndex + 19)
+ if (code2 < 48 || code2 > 57) {
+ return undefined // Not a digit
+ }
+ second = (code1 - 48) * 10 + (code2 - 48) // Convert ASCII codes to number
+ }
+
+ const result = new Date(Date.UTC(year, monthIdx, day, hour, minute, second))
+ return result.getUTCDay() === weekday ? result : undefined
+}
+
+module.exports = {
+ parseHttpDate
+}
diff --git a/vanilla/node_modules/undici/lib/util/promise.js b/vanilla/node_modules/undici/lib/util/promise.js
new file mode 100644
index 0000000..048f86e
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/util/promise.js
@@ -0,0 +1,28 @@
+'use strict'
+
+/**
+ * @template {*} T
+ * @typedef {Object} DeferredPromise
+ * @property {Promise<T>} promise
+ * @property {(value?: T) => void} resolve
+ * @property {(reason?: any) => void} reject
+ */
+
+/**
+ * @template {*} T
+ * @returns {DeferredPromise<T>} An object containing a promise and its resolve/reject methods.
+ */
+function createDeferredPromise () {
+ let res
+ let rej
+ const promise = new Promise((resolve, reject) => {
+ res = resolve
+ rej = reject
+ })
+
+ return { promise, resolve: res, reject: rej }
+}
+
+module.exports = {
+ createDeferredPromise
+}
diff --git a/vanilla/node_modules/undici/lib/util/runtime-features.js b/vanilla/node_modules/undici/lib/util/runtime-features.js
new file mode 100644
index 0000000..3e62dc0
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/util/runtime-features.js
@@ -0,0 +1,124 @@
+'use strict'
+
+/** @typedef {`node:${string}`} NodeModuleName */
+
+/** @type {Record<NodeModuleName, () => any>} */
+const lazyLoaders = {
+ __proto__: null,
+ 'node:crypto': () => require('node:crypto'),
+ 'node:sqlite': () => require('node:sqlite'),
+ 'node:worker_threads': () => require('node:worker_threads'),
+ 'node:zlib': () => require('node:zlib')
+}
+
+/**
+ * @param {NodeModuleName} moduleName
+ * @returns {boolean}
+ */
+function detectRuntimeFeatureByNodeModule (moduleName) {
+ try {
+ lazyLoaders[moduleName]()
+ return true
+ } catch (err) {
+ if (err.code !== 'ERR_UNKNOWN_BUILTIN_MODULE' && err.code !== 'ERR_NO_CRYPTO') {
+ throw err
+ }
+ return false
+ }
+}
+
+/**
+ * @param {NodeModuleName} moduleName
+ * @param {string} property
+ * @returns {boolean}
+ */
+function detectRuntimeFeatureByExportedProperty (moduleName, property) {
+ const module = lazyLoaders[moduleName]()
+ return typeof module[property] !== 'undefined'
+}
+
+const runtimeFeaturesByExportedProperty = /** @type {const} */ (['markAsUncloneable', 'zstd'])
+
+/** @type {Record<RuntimeFeatureByExportedProperty, [NodeModuleName, string]>} */
+const exportedPropertyLookup = {
+ markAsUncloneable: ['node:worker_threads', 'markAsUncloneable'],
+ zstd: ['node:zlib', 'createZstdDecompress']
+}
+
+/** @typedef {typeof runtimeFeaturesByExportedProperty[number]} RuntimeFeatureByExportedProperty */
+
+const runtimeFeaturesAsNodeModule = /** @type {const} */ (['crypto', 'sqlite'])
+/** @typedef {typeof runtimeFeaturesAsNodeModule[number]} RuntimeFeatureByNodeModule */
+
+const features = /** @type {const} */ ([
+ ...runtimeFeaturesAsNodeModule,
+ ...runtimeFeaturesByExportedProperty
+])
+
+/** @typedef {typeof features[number]} Feature */
+
+/**
+ * @param {Feature} feature
+ * @returns {boolean}
+ */
+function detectRuntimeFeature (feature) {
+ if (runtimeFeaturesAsNodeModule.includes(/** @type {RuntimeFeatureByNodeModule} */ (feature))) {
+ return detectRuntimeFeatureByNodeModule(`node:${feature}`)
+ } else if (runtimeFeaturesByExportedProperty.includes(/** @type {RuntimeFeatureByExportedProperty} */ (feature))) {
+ const [moduleName, property] = exportedPropertyLookup[feature]
+ return detectRuntimeFeatureByExportedProperty(moduleName, property)
+ }
+ throw new TypeError(`unknown feature: ${feature}`)
+}
+
+/**
+ * @class
+ * @name RuntimeFeatures
+ */
+class RuntimeFeatures {
+ /** @type {Map<Feature, boolean>} */
+ #map = new Map()
+
+ /**
+ * Clears all cached feature detections.
+ */
+ clear () {
+ this.#map.clear()
+ }
+
+ /**
+ * @param {Feature} feature
+ * @returns {boolean}
+ */
+ has (feature) {
+ return (
+ this.#map.get(feature) ?? this.#detectRuntimeFeature(feature)
+ )
+ }
+
+ /**
+ * @param {Feature} feature
+ * @param {boolean} value
+ */
+ set (feature, value) {
+ if (features.includes(feature) === false) {
+ throw new TypeError(`unknown feature: ${feature}`)
+ }
+ this.#map.set(feature, value)
+ }
+
+ /**
+ * @param {Feature} feature
+ * @returns {boolean}
+ */
+ #detectRuntimeFeature (feature) {
+ const result = detectRuntimeFeature(feature)
+ this.#map.set(feature, result)
+ return result
+ }
+}
+
+const instance = new RuntimeFeatures()
+
+module.exports.runtimeFeatures = instance
+module.exports.default = instance
diff --git a/vanilla/node_modules/undici/lib/util/stats.js b/vanilla/node_modules/undici/lib/util/stats.js
new file mode 100644
index 0000000..a13132e
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/util/stats.js
@@ -0,0 +1,32 @@
+'use strict'
+
+const {
+ kConnected,
+ kPending,
+ kRunning,
+ kSize,
+ kFree,
+ kQueued
+} = require('../core/symbols')
+
+class ClientStats {
+ constructor (client) {
+ this.connected = client[kConnected]
+ this.pending = client[kPending]
+ this.running = client[kRunning]
+ this.size = client[kSize]
+ }
+}
+
+class PoolStats {
+ constructor (pool) {
+ this.connected = pool[kConnected]
+ this.free = pool[kFree]
+ this.pending = pool[kPending]
+ this.queued = pool[kQueued]
+ this.running = pool[kRunning]
+ this.size = pool[kSize]
+ }
+}
+
+module.exports = { ClientStats, PoolStats }
diff --git a/vanilla/node_modules/undici/lib/util/timers.js b/vanilla/node_modules/undici/lib/util/timers.js
new file mode 100644
index 0000000..14984d4
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/util/timers.js
@@ -0,0 +1,425 @@
+'use strict'
+
+/**
+ * This module offers an optimized timer implementation designed for scenarios
+ * where high precision is not critical.
+ *
+ * The timer achieves faster performance by using a low-resolution approach,
+ * with an accuracy target of within 500ms. This makes it particularly useful
+ * for timers with delays of 1 second or more, where exact timing is less
+ * crucial.
+ *
+ * It's important to note that Node.js timers are inherently imprecise, as
+ * delays can occur due to the event loop being blocked by other operations.
+ * Consequently, timers may trigger later than their scheduled time.
+ */
+
+/**
+ * The fastNow variable contains the internal fast timer clock value.
+ *
+ * @type {number}
+ */
+let fastNow = 0
+
+/**
+ * RESOLUTION_MS represents the target resolution time in milliseconds.
+ *
+ * @type {number}
+ * @default 1000
+ */
+const RESOLUTION_MS = 1e3
+
+/**
+ * TICK_MS defines the desired interval in milliseconds between each tick.
+ * The target value is set to half the resolution time, minus 1 ms, to account
+ * for potential event loop overhead.
+ *
+ * @type {number}
+ * @default 499
+ */
+const TICK_MS = (RESOLUTION_MS >> 1) - 1
+
+/**
+ * fastNowTimeout is a Node.js timer used to manage and process
+ * the FastTimers stored in the `fastTimers` array.
+ *
+ * @type {NodeJS.Timeout}
+ */
+let fastNowTimeout
+
+/**
+ * The kFastTimer symbol is used to identify FastTimer instances.
+ *
+ * @type {Symbol}
+ */
+const kFastTimer = Symbol('kFastTimer')
+
+/**
+ * The fastTimers array contains all active FastTimers.
+ *
+ * @type {FastTimer[]}
+ */
+const fastTimers = []
+
+/**
+ * These constants represent the various states of a FastTimer.
+ */
+
+/**
+ * The `NOT_IN_LIST` constant indicates that the FastTimer is not included
+ * in the `fastTimers` array. Timers with this status will not be processed
+ * during the next tick by the `onTick` function.
+ *
+ * A FastTimer can be re-added to the `fastTimers` array by invoking the
+ * `refresh` method on the FastTimer instance.
+ *
+ * @type {-2}
+ */
+const NOT_IN_LIST = -2
+
+/**
+ * The `TO_BE_CLEARED` constant indicates that the FastTimer is scheduled
+ * for removal from the `fastTimers` array. A FastTimer in this state will
+ * be removed in the next tick by the `onTick` function and will no longer
+ * be processed.
+ *
+ * This status is also set when the `clear` method is called on the FastTimer instance.
+ *
+ * @type {-1}
+ */
+const TO_BE_CLEARED = -1
+
+/**
+ * The `PENDING` constant signifies that the FastTimer is awaiting processing
+ * in the next tick by the `onTick` function. Timers with this status will have
+ * their `_idleStart` value set and their status updated to `ACTIVE` in the next tick.
+ *
+ * @type {0}
+ */
+const PENDING = 0
+
+/**
+ * The `ACTIVE` constant indicates that the FastTimer is active and waiting
+ * for its timer to expire. During the next tick, the `onTick` function will
+ * check if the timer has expired, and if so, it will execute the associated callback.
+ *
+ * @type {1}
+ */
+const ACTIVE = 1
+
+/**
+ * The onTick function processes the fastTimers array.
+ *
+ * @returns {void}
+ */
+function onTick () {
+ /**
+ * Increment the fastNow value by the TICK_MS value, despite the actual time
+ * that has passed since the last tick. This approach ensures independence
+ * from the system clock and delays caused by a blocked event loop.
+ *
+ * @type {number}
+ */
+ fastNow += TICK_MS
+
+ /**
+ * The `idx` variable is used to iterate over the `fastTimers` array.
+ * Expired timers are removed by replacing them with the last element in the array.
+ * Consequently, `idx` is only incremented when the current element is not removed.
+ *
+ * @type {number}
+ */
+ let idx = 0
+
+ /**
+ * The len variable will contain the length of the fastTimers array
+ * and will be decremented when a FastTimer should be removed from the
+ * fastTimers array.
+ *
+ * @type {number}
+ */
+ let len = fastTimers.length
+
+ while (idx < len) {
+ /**
+ * @type {FastTimer}
+ */
+ const timer = fastTimers[idx]
+
+ // If the timer is in the ACTIVE state and the timer has expired, it will
+ // be processed in the next tick.
+ if (timer._state === PENDING) {
+ // Set the _idleStart value to the fastNow value minus the TICK_MS value
+ // to account for the time the timer was in the PENDING state.
+ timer._idleStart = fastNow - TICK_MS
+ timer._state = ACTIVE
+ } else if (
+ timer._state === ACTIVE &&
+ fastNow >= timer._idleStart + timer._idleTimeout
+ ) {
+ timer._state = TO_BE_CLEARED
+ timer._idleStart = -1
+ timer._onTimeout(timer._timerArg)
+ }
+
+ if (timer._state === TO_BE_CLEARED) {
+ timer._state = NOT_IN_LIST
+
+ // Move the last element to the current index and decrement len if it is
+ // not the only element in the array.
+ if (--len !== 0) {
+ fastTimers[idx] = fastTimers[len]
+ }
+ } else {
+ ++idx
+ }
+ }
+
+ // Set the length of the fastTimers array to the new length and thus
+ // removing the excess FastTimers elements from the array.
+ fastTimers.length = len
+
+ // If there are still active FastTimers in the array, refresh the Timer.
+ // If there are no active FastTimers, the timer will be refreshed again
+ // when a new FastTimer is instantiated.
+ if (fastTimers.length !== 0) {
+ refreshTimeout()
+ }
+}
+
+function refreshTimeout () {
+ // If the fastNowTimeout is already set and the Timer has the refresh()-
+ // method available, call it to refresh the timer.
+ // Some timer objects returned by setTimeout may not have a .refresh()
+ // method (e.g. mocked timers in tests).
+ if (fastNowTimeout?.refresh) {
+ fastNowTimeout.refresh()
+ // fastNowTimeout is not instantiated yet or refresh is not availabe,
+ // create a new Timer.
+ } else {
+ clearTimeout(fastNowTimeout)
+ fastNowTimeout = setTimeout(onTick, TICK_MS)
+ // If the Timer has an unref method, call it to allow the process to exit,
+ // if there are no other active handles. When using fake timers or mocked
+ // environments (like Jest), .unref() may not be defined,
+ fastNowTimeout?.unref()
+ }
+}
+
+/**
+ * The `FastTimer` class is a data structure designed to store and manage
+ * timer information.
+ */
+class FastTimer {
+ [kFastTimer] = true
+
+ /**
+ * The state of the timer, which can be one of the following:
+ * - NOT_IN_LIST (-2)
+ * - TO_BE_CLEARED (-1)
+ * - PENDING (0)
+ * - ACTIVE (1)
+ *
+ * @type {-2|-1|0|1}
+ * @private
+ */
+ _state = NOT_IN_LIST
+
+ /**
+ * The number of milliseconds to wait before calling the callback.
+ *
+ * @type {number}
+ * @private
+ */
+ _idleTimeout = -1
+
+ /**
+ * The time in milliseconds when the timer was started. This value is used to
+ * calculate when the timer should expire.
+ *
+ * @type {number}
+ * @default -1
+ * @private
+ */
+ _idleStart = -1
+
+ /**
+ * The function to be executed when the timer expires.
+ * @type {Function}
+ * @private
+ */
+ _onTimeout
+
+ /**
+ * The argument to be passed to the callback when the timer expires.
+ *
+ * @type {*}
+ * @private
+ */
+ _timerArg
+
+ /**
+ * @constructor
+ * @param {Function} callback A function to be executed after the timer
+ * expires.
+ * @param {number} delay The time, in milliseconds that the timer should wait
+ * before the specified function or code is executed.
+ * @param {*} arg
+ */
+ constructor (callback, delay, arg) {
+ this._onTimeout = callback
+ this._idleTimeout = delay
+ this._timerArg = arg
+
+ this.refresh()
+ }
+
+ /**
+ * Sets the timer's start time to the current time, and reschedules the timer
+ * to call its callback at the previously specified duration adjusted to the
+ * current time.
+ * Using this on a timer that has already called its callback will reactivate
+ * the timer.
+ *
+ * @returns {void}
+ */
+ refresh () {
+ // In the special case that the timer is not in the list of active timers,
+ // add it back to the array to be processed in the next tick by the onTick
+ // function.
+ if (this._state === NOT_IN_LIST) {
+ fastTimers.push(this)
+ }
+
+ // If the timer is the only active timer, refresh the fastNowTimeout for
+ // better resolution.
+ if (!fastNowTimeout || fastTimers.length === 1) {
+ refreshTimeout()
+ }
+
+ // Setting the state to PENDING will cause the timer to be reset in the
+ // next tick by the onTick function.
+ this._state = PENDING
+ }
+
+ /**
+ * The `clear` method cancels the timer, preventing it from executing.
+ *
+ * @returns {void}
+ * @private
+ */
+ clear () {
+ // Set the state to TO_BE_CLEARED to mark the timer for removal in the next
+ // tick by the onTick function.
+ this._state = TO_BE_CLEARED
+
+ // Reset the _idleStart value to -1 to indicate that the timer is no longer
+ // active.
+ this._idleStart = -1
+ }
+}
+
+/**
+ * This module exports a setTimeout and clearTimeout function that can be
+ * used as a drop-in replacement for the native functions.
+ */
+module.exports = {
+ /**
+ * The setTimeout() method sets a timer which executes a function once the
+ * timer expires.
+ * @param {Function} callback A function to be executed after the timer
+ * expires.
+ * @param {number} delay The time, in milliseconds that the timer should
+ * wait before the specified function or code is executed.
+ * @param {*} [arg] An optional argument to be passed to the callback function
+ * when the timer expires.
+ * @returns {NodeJS.Timeout|FastTimer}
+ */
+ setTimeout (callback, delay, arg) {
+ // If the delay is less than or equal to the RESOLUTION_MS value return a
+ // native Node.js Timer instance.
+ return delay <= RESOLUTION_MS
+ ? setTimeout(callback, delay, arg)
+ : new FastTimer(callback, delay, arg)
+ },
+ /**
+ * The clearTimeout method cancels an instantiated Timer previously created
+ * by calling setTimeout.
+ *
+ * @param {NodeJS.Timeout|FastTimer} timeout
+ */
+ clearTimeout (timeout) {
+ // If the timeout is a FastTimer, call its own clear method.
+ if (timeout[kFastTimer]) {
+ /**
+ * @type {FastTimer}
+ */
+ timeout.clear()
+ // Otherwise it is an instance of a native NodeJS.Timeout, so call the
+ // Node.js native clearTimeout function.
+ } else {
+ clearTimeout(timeout)
+ }
+ },
+ /**
+ * The setFastTimeout() method sets a fastTimer which executes a function once
+ * the timer expires.
+ * @param {Function} callback A function to be executed after the timer
+ * expires.
+ * @param {number} delay The time, in milliseconds that the timer should
+ * wait before the specified function or code is executed.
+ * @param {*} [arg] An optional argument to be passed to the callback function
+ * when the timer expires.
+ * @returns {FastTimer}
+ */
+ setFastTimeout (callback, delay, arg) {
+ return new FastTimer(callback, delay, arg)
+ },
+ /**
+ * The clearTimeout method cancels an instantiated FastTimer previously
+ * created by calling setFastTimeout.
+ *
+ * @param {FastTimer} timeout
+ */
+ clearFastTimeout (timeout) {
+ timeout.clear()
+ },
+ /**
+ * The now method returns the value of the internal fast timer clock.
+ *
+ * @returns {number}
+ */
+ now () {
+ return fastNow
+ },
+ /**
+ * Trigger the onTick function to process the fastTimers array.
+ * Exported for testing purposes only.
+ * Marking as deprecated to discourage any use outside of testing.
+ * @deprecated
+ * @param {number} [delay=0] The delay in milliseconds to add to the now value.
+ */
+ tick (delay = 0) {
+ fastNow += delay - RESOLUTION_MS + 1
+ onTick()
+ onTick()
+ },
+ /**
+ * Reset FastTimers.
+ * Exported for testing purposes only.
+ * Marking as deprecated to discourage any use outside of testing.
+ * @deprecated
+ */
+ reset () {
+ fastNow = 0
+ fastTimers.length = 0
+ clearTimeout(fastNowTimeout)
+ fastNowTimeout = null
+ },
+ /**
+ * Exporting for testing purposes only.
+ * Marking as deprecated to discourage any use outside of testing.
+ * @deprecated
+ */
+ kFastTimer
+}