format: prettify entire project
This commit is contained in:
305
node_modules/undici/lib/handler/cache-handler.js
generated
vendored
305
node_modules/undici/lib/handler/cache-handler.js
generated
vendored
@ -1,21 +1,21 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const util = require('../core/util')
|
||||
const util = require('../core/util');
|
||||
const {
|
||||
parseCacheControlHeader,
|
||||
parseVaryHeader,
|
||||
isEtagUsable
|
||||
} = require('../util/cache')
|
||||
const { parseHttpDate } = require('../util/date.js')
|
||||
isEtagUsable,
|
||||
} = require('../util/cache');
|
||||
const { parseHttpDate } = require('../util/date.js');
|
||||
|
||||
function noop () {}
|
||||
function noop() {}
|
||||
|
||||
// Status codes that we can use some heuristics on to cache
|
||||
const HEURISTICALLY_CACHEABLE_STATUS_CODES = [
|
||||
200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501
|
||||
]
|
||||
200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501,
|
||||
];
|
||||
|
||||
const MAX_RESPONSE_AGE = 2147483647000
|
||||
const MAX_RESPONSE_AGE = 2147483647000;
|
||||
|
||||
/**
|
||||
* @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
|
||||
@ -26,54 +26,54 @@ class CacheHandler {
|
||||
/**
|
||||
* @type {import('../../types/cache-interceptor.d.ts').default.CacheKey}
|
||||
*/
|
||||
#cacheKey
|
||||
#cacheKey;
|
||||
|
||||
/**
|
||||
* @type {import('../../types/cache-interceptor.d.ts').default.CacheHandlerOptions['type']}
|
||||
*/
|
||||
#cacheType
|
||||
#cacheType;
|
||||
|
||||
/**
|
||||
* @type {number | undefined}
|
||||
*/
|
||||
#cacheByDefault
|
||||
#cacheByDefault;
|
||||
|
||||
/**
|
||||
* @type {import('../../types/cache-interceptor.d.ts').default.CacheStore}
|
||||
*/
|
||||
#store
|
||||
#store;
|
||||
|
||||
/**
|
||||
* @type {import('../../types/dispatcher.d.ts').default.DispatchHandler}
|
||||
*/
|
||||
#handler
|
||||
#handler;
|
||||
|
||||
/**
|
||||
* @type {import('node:stream').Writable | undefined}
|
||||
*/
|
||||
#writeStream
|
||||
#writeStream;
|
||||
|
||||
/**
|
||||
* @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.DispatchHandler} handler
|
||||
*/
|
||||
constructor ({ store, type, cacheByDefault }, cacheKey, handler) {
|
||||
this.#store = store
|
||||
this.#cacheType = type
|
||||
this.#cacheByDefault = cacheByDefault
|
||||
this.#cacheKey = cacheKey
|
||||
this.#handler = handler
|
||||
constructor({ store, type, cacheByDefault }, cacheKey, handler) {
|
||||
this.#store = store;
|
||||
this.#cacheType = type;
|
||||
this.#cacheByDefault = cacheByDefault;
|
||||
this.#cacheKey = cacheKey;
|
||||
this.#handler = handler;
|
||||
}
|
||||
|
||||
onRequestStart (controller, context) {
|
||||
this.#writeStream?.destroy()
|
||||
this.#writeStream = undefined
|
||||
this.#handler.onRequestStart?.(controller, context)
|
||||
onRequestStart(controller, context) {
|
||||
this.#writeStream?.destroy();
|
||||
this.#writeStream = undefined;
|
||||
this.#handler.onRequestStart?.(controller, context);
|
||||
}
|
||||
|
||||
onRequestUpgrade (controller, statusCode, headers, socket) {
|
||||
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
||||
onRequestUpgrade(controller, statusCode, headers, socket) {
|
||||
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,19 +82,14 @@ class CacheHandler {
|
||||
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
||||
* @param {string} statusMessage
|
||||
*/
|
||||
onResponseStart (
|
||||
controller,
|
||||
statusCode,
|
||||
resHeaders,
|
||||
statusMessage
|
||||
) {
|
||||
onResponseStart(controller, statusCode, resHeaders, statusMessage) {
|
||||
const downstreamOnHeaders = () =>
|
||||
this.#handler.onResponseStart?.(
|
||||
controller,
|
||||
statusCode,
|
||||
resHeaders,
|
||||
statusMessage
|
||||
)
|
||||
);
|
||||
|
||||
if (
|
||||
!util.safeHTTPMethods.includes(this.#cacheKey.method) &&
|
||||
@ -104,15 +99,17 @@ class CacheHandler {
|
||||
// Successful response to an unsafe method, delete it from cache
|
||||
// https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response
|
||||
try {
|
||||
this.#store.delete(this.#cacheKey)?.catch?.(noop)
|
||||
this.#store.delete(this.#cacheKey)?.catch?.(noop);
|
||||
} catch {
|
||||
// Fail silently
|
||||
}
|
||||
return downstreamOnHeaders()
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
|
||||
const cacheControlHeader = resHeaders['cache-control']
|
||||
const heuristicallyCacheable = resHeaders['last-modified'] && HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode)
|
||||
const cacheControlHeader = resHeaders['cache-control'];
|
||||
const heuristicallyCacheable =
|
||||
resHeaders['last-modified'] &&
|
||||
HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode);
|
||||
if (
|
||||
!cacheControlHeader &&
|
||||
!resHeaders['expires'] &&
|
||||
@ -121,50 +118,72 @@ class CacheHandler {
|
||||
) {
|
||||
// Don't have anything to tell us this response is cachable and we're not
|
||||
// caching by default
|
||||
return downstreamOnHeaders()
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
|
||||
const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
|
||||
if (!canCacheResponse(this.#cacheType, statusCode, resHeaders, cacheControlDirectives)) {
|
||||
return downstreamOnHeaders()
|
||||
const cacheControlDirectives =
|
||||
cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {};
|
||||
if (
|
||||
!canCacheResponse(
|
||||
this.#cacheType,
|
||||
statusCode,
|
||||
resHeaders,
|
||||
cacheControlDirectives
|
||||
)
|
||||
) {
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const resAge = resHeaders.age ? getAge(resHeaders.age) : undefined
|
||||
const now = Date.now();
|
||||
const resAge = resHeaders.age ? getAge(resHeaders.age) : undefined;
|
||||
if (resAge && resAge >= MAX_RESPONSE_AGE) {
|
||||
// Response considered stale
|
||||
return downstreamOnHeaders()
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
|
||||
const resDate = typeof resHeaders.date === 'string'
|
||||
? parseHttpDate(resHeaders.date)
|
||||
: undefined
|
||||
const resDate =
|
||||
typeof resHeaders.date === 'string' ?
|
||||
parseHttpDate(resHeaders.date)
|
||||
: undefined;
|
||||
|
||||
const staleAt =
|
||||
determineStaleAt(this.#cacheType, now, resAge, resHeaders, resDate, cacheControlDirectives) ??
|
||||
this.#cacheByDefault
|
||||
determineStaleAt(
|
||||
this.#cacheType,
|
||||
now,
|
||||
resAge,
|
||||
resHeaders,
|
||||
resDate,
|
||||
cacheControlDirectives
|
||||
) ?? this.#cacheByDefault;
|
||||
if (staleAt === undefined || (resAge && resAge > staleAt)) {
|
||||
return downstreamOnHeaders()
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
|
||||
const baseTime = resDate ? resDate.getTime() : now
|
||||
const absoluteStaleAt = staleAt + baseTime
|
||||
const baseTime = resDate ? resDate.getTime() : now;
|
||||
const absoluteStaleAt = staleAt + baseTime;
|
||||
if (now >= absoluteStaleAt) {
|
||||
// Response is already stale
|
||||
return downstreamOnHeaders()
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
|
||||
let varyDirectives
|
||||
let varyDirectives;
|
||||
if (this.#cacheKey.headers && resHeaders.vary) {
|
||||
varyDirectives = parseVaryHeader(resHeaders.vary, this.#cacheKey.headers)
|
||||
varyDirectives = parseVaryHeader(resHeaders.vary, this.#cacheKey.headers);
|
||||
if (!varyDirectives) {
|
||||
// Parse error
|
||||
return downstreamOnHeaders()
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAt = determineDeleteAt(baseTime, cacheControlDirectives, absoluteStaleAt)
|
||||
const strippedHeaders = stripNecessaryHeaders(resHeaders, cacheControlDirectives)
|
||||
const deleteAt = determineDeleteAt(
|
||||
baseTime,
|
||||
cacheControlDirectives,
|
||||
absoluteStaleAt
|
||||
);
|
||||
const strippedHeaders = stripNecessaryHeaders(
|
||||
resHeaders,
|
||||
cacheControlDirectives
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {import('../../types/cache-interceptor.d.ts').default.CacheValue}
|
||||
@ -177,58 +196,58 @@ class CacheHandler {
|
||||
cacheControlDirectives,
|
||||
cachedAt: resAge ? now - resAge : now,
|
||||
staleAt: absoluteStaleAt,
|
||||
deleteAt
|
||||
}
|
||||
deleteAt,
|
||||
};
|
||||
|
||||
if (typeof resHeaders.etag === 'string' && isEtagUsable(resHeaders.etag)) {
|
||||
value.etag = resHeaders.etag
|
||||
value.etag = resHeaders.etag;
|
||||
}
|
||||
|
||||
this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value)
|
||||
this.#writeStream = this.#store.createWriteStream(this.#cacheKey, value);
|
||||
if (!this.#writeStream) {
|
||||
return downstreamOnHeaders()
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
|
||||
const handler = this
|
||||
const handler = this;
|
||||
this.#writeStream
|
||||
.on('drain', () => controller.resume())
|
||||
.on('error', function () {
|
||||
// TODO (fix): Make error somehow observable?
|
||||
handler.#writeStream = undefined
|
||||
handler.#writeStream = undefined;
|
||||
|
||||
// Delete the value in case the cache store is holding onto state from
|
||||
// the call to createWriteStream
|
||||
handler.#store.delete(handler.#cacheKey)
|
||||
handler.#store.delete(handler.#cacheKey);
|
||||
})
|
||||
.on('close', function () {
|
||||
if (handler.#writeStream === this) {
|
||||
handler.#writeStream = undefined
|
||||
handler.#writeStream = undefined;
|
||||
}
|
||||
|
||||
// TODO (fix): Should we resume even if was paused downstream?
|
||||
controller.resume()
|
||||
})
|
||||
controller.resume();
|
||||
});
|
||||
|
||||
return downstreamOnHeaders()
|
||||
return downstreamOnHeaders();
|
||||
}
|
||||
|
||||
onResponseData (controller, chunk) {
|
||||
onResponseData(controller, chunk) {
|
||||
if (this.#writeStream?.write(chunk) === false) {
|
||||
controller.pause()
|
||||
controller.pause();
|
||||
}
|
||||
|
||||
this.#handler.onResponseData?.(controller, chunk)
|
||||
this.#handler.onResponseData?.(controller, chunk);
|
||||
}
|
||||
|
||||
onResponseEnd (controller, trailers) {
|
||||
this.#writeStream?.end()
|
||||
this.#handler.onResponseEnd?.(controller, trailers)
|
||||
onResponseEnd(controller, trailers) {
|
||||
this.#writeStream?.end();
|
||||
this.#handler.onResponseEnd?.(controller, trailers);
|
||||
}
|
||||
|
||||
onResponseError (controller, err) {
|
||||
this.#writeStream?.destroy(err)
|
||||
this.#writeStream = undefined
|
||||
this.#handler.onResponseError?.(controller, err)
|
||||
onResponseError(controller, err) {
|
||||
this.#writeStream?.destroy(err);
|
||||
this.#writeStream = undefined;
|
||||
this.#handler.onResponseError?.(controller, err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,56 +259,64 @@ class CacheHandler {
|
||||
* @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
|
||||
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
||||
*/
|
||||
function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives) {
|
||||
function canCacheResponse(
|
||||
cacheType,
|
||||
statusCode,
|
||||
resHeaders,
|
||||
cacheControlDirectives
|
||||
) {
|
||||
if (statusCode !== 200 && statusCode !== 307) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cacheControlDirectives['no-store']) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cacheType === 'shared' && cacheControlDirectives.private === true) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
|
||||
if (resHeaders.vary?.includes('*')) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
|
||||
if (resHeaders.authorization) {
|
||||
if (!cacheControlDirectives.public || typeof resHeaders.authorization !== 'string') {
|
||||
return false
|
||||
if (
|
||||
!cacheControlDirectives.public ||
|
||||
typeof resHeaders.authorization !== 'string'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(cacheControlDirectives['no-cache']) &&
|
||||
cacheControlDirectives['no-cache'].includes('authorization')
|
||||
) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(cacheControlDirectives['private']) &&
|
||||
cacheControlDirectives['private'].includes('authorization')
|
||||
) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | string[]} ageHeader
|
||||
* @returns {number | undefined}
|
||||
*/
|
||||
function getAge (ageHeader) {
|
||||
const age = parseInt(Array.isArray(ageHeader) ? ageHeader[0] : ageHeader)
|
||||
function getAge(ageHeader) {
|
||||
const age = parseInt(Array.isArray(ageHeader) ? ageHeader[0] : ageHeader);
|
||||
|
||||
return isNaN(age) ? undefined : age * 1000
|
||||
return isNaN(age) ? undefined : age * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,64 +329,71 @@ function getAge (ageHeader) {
|
||||
*
|
||||
* @returns {number | undefined} time that the value is stale at in seconds or undefined if it shouldn't be cached
|
||||
*/
|
||||
function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheControlDirectives) {
|
||||
function determineStaleAt(
|
||||
cacheType,
|
||||
now,
|
||||
age,
|
||||
resHeaders,
|
||||
responseDate,
|
||||
cacheControlDirectives
|
||||
) {
|
||||
if (cacheType === 'shared') {
|
||||
// Prioritize s-maxage since we're a shared cache
|
||||
// s-maxage > max-age > Expire
|
||||
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
|
||||
const sMaxAge = cacheControlDirectives['s-maxage']
|
||||
const sMaxAge = cacheControlDirectives['s-maxage'];
|
||||
if (sMaxAge !== undefined) {
|
||||
return sMaxAge > 0 ? sMaxAge * 1000 : undefined
|
||||
return sMaxAge > 0 ? sMaxAge * 1000 : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const maxAge = cacheControlDirectives['max-age']
|
||||
const maxAge = cacheControlDirectives['max-age'];
|
||||
if (maxAge !== undefined) {
|
||||
return maxAge > 0 ? maxAge * 1000 : undefined
|
||||
return maxAge > 0 ? maxAge * 1000 : undefined;
|
||||
}
|
||||
|
||||
if (typeof resHeaders.expires === 'string') {
|
||||
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
|
||||
const expiresDate = parseHttpDate(resHeaders.expires)
|
||||
const expiresDate = parseHttpDate(resHeaders.expires);
|
||||
if (expiresDate) {
|
||||
if (now >= expiresDate.getTime()) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (responseDate) {
|
||||
if (responseDate >= expiresDate) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (age !== undefined && age > (expiresDate - responseDate)) {
|
||||
return undefined
|
||||
if (age !== undefined && age > expiresDate - responseDate) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return expiresDate.getTime() - now
|
||||
return expiresDate.getTime() - now;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof resHeaders['last-modified'] === 'string') {
|
||||
// https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-heuristic-fresh
|
||||
const lastModified = new Date(resHeaders['last-modified'])
|
||||
const lastModified = new Date(resHeaders['last-modified']);
|
||||
if (isValidDate(lastModified)) {
|
||||
if (lastModified.getTime() >= now) {
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const responseAge = now - lastModified.getTime()
|
||||
const responseAge = now - lastModified.getTime();
|
||||
|
||||
return responseAge * 0.1
|
||||
return responseAge * 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheControlDirectives.immutable) {
|
||||
// https://www.rfc-editor.org/rfc/rfc8246.html#section-2.2
|
||||
return 31536000
|
||||
return 31536000;
|
||||
}
|
||||
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -367,24 +401,25 @@ function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheC
|
||||
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
||||
* @param {number} staleAt
|
||||
*/
|
||||
function determineDeleteAt (now, cacheControlDirectives, staleAt) {
|
||||
let staleWhileRevalidate = -Infinity
|
||||
let staleIfError = -Infinity
|
||||
let immutable = -Infinity
|
||||
function determineDeleteAt(now, cacheControlDirectives, staleAt) {
|
||||
let staleWhileRevalidate = -Infinity;
|
||||
let staleIfError = -Infinity;
|
||||
let immutable = -Infinity;
|
||||
|
||||
if (cacheControlDirectives['stale-while-revalidate']) {
|
||||
staleWhileRevalidate = staleAt + (cacheControlDirectives['stale-while-revalidate'] * 1000)
|
||||
staleWhileRevalidate =
|
||||
staleAt + cacheControlDirectives['stale-while-revalidate'] * 1000;
|
||||
}
|
||||
|
||||
if (cacheControlDirectives['stale-if-error']) {
|
||||
staleIfError = staleAt + (cacheControlDirectives['stale-if-error'] * 1000)
|
||||
staleIfError = staleAt + cacheControlDirectives['stale-if-error'] * 1000;
|
||||
}
|
||||
|
||||
if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity) {
|
||||
immutable = now + 31536000000
|
||||
immutable = now + 31536000000;
|
||||
}
|
||||
|
||||
return Math.max(staleAt, staleWhileRevalidate, staleIfError, immutable)
|
||||
return Math.max(staleAt, staleWhileRevalidate, staleIfError, immutable);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -393,7 +428,7 @@ function determineDeleteAt (now, cacheControlDirectives, staleAt) {
|
||||
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
||||
* @returns {Record<string, string | string []>}
|
||||
*/
|
||||
function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
|
||||
function stripNecessaryHeaders(resHeaders, cacheControlDirectives) {
|
||||
const headersToRemove = [
|
||||
'connection',
|
||||
'proxy-authenticate',
|
||||
@ -404,45 +439,49 @@ function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
|
||||
'transfer-encoding',
|
||||
'upgrade',
|
||||
// We'll add age back when serving it
|
||||
'age'
|
||||
]
|
||||
'age',
|
||||
];
|
||||
|
||||
if (resHeaders['connection']) {
|
||||
if (Array.isArray(resHeaders['connection'])) {
|
||||
// connection: a
|
||||
// connection: b
|
||||
headersToRemove.push(...resHeaders['connection'].map(header => header.trim()))
|
||||
headersToRemove.push(
|
||||
...resHeaders['connection'].map((header) => header.trim())
|
||||
);
|
||||
} else {
|
||||
// connection: a, b
|
||||
headersToRemove.push(...resHeaders['connection'].split(',').map(header => header.trim()))
|
||||
headersToRemove.push(
|
||||
...resHeaders['connection'].split(',').map((header) => header.trim())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(cacheControlDirectives['no-cache'])) {
|
||||
headersToRemove.push(...cacheControlDirectives['no-cache'])
|
||||
headersToRemove.push(...cacheControlDirectives['no-cache']);
|
||||
}
|
||||
|
||||
if (Array.isArray(cacheControlDirectives['private'])) {
|
||||
headersToRemove.push(...cacheControlDirectives['private'])
|
||||
headersToRemove.push(...cacheControlDirectives['private']);
|
||||
}
|
||||
|
||||
let strippedHeaders
|
||||
let strippedHeaders;
|
||||
for (const headerName of headersToRemove) {
|
||||
if (resHeaders[headerName]) {
|
||||
strippedHeaders ??= { ...resHeaders }
|
||||
delete strippedHeaders[headerName]
|
||||
strippedHeaders ??= { ...resHeaders };
|
||||
delete strippedHeaders[headerName];
|
||||
}
|
||||
}
|
||||
|
||||
return strippedHeaders ?? resHeaders
|
||||
return strippedHeaders ?? resHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} date
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidDate (date) {
|
||||
return date instanceof Date && Number.isFinite(date.valueOf())
|
||||
function isValidDate(date) {
|
||||
return date instanceof Date && Number.isFinite(date.valueOf());
|
||||
}
|
||||
|
||||
module.exports = CacheHandler
|
||||
module.exports = CacheHandler;
|
||||
|
Reference in New Issue
Block a user