Buckets:
tahamajs/Other_Lessons / AP /TA_1 /AP-ECE-UT.github.io /node_modules /cacheable-request /dist /index.js
| import EventEmitter from 'node:events'; | |
| import urlLib from 'node:url'; | |
| import crypto from 'node:crypto'; | |
| import stream, { PassThrough as PassThroughStream } from 'node:stream'; | |
| import normalizeUrl from 'normalize-url'; | |
| import getStream from 'get-stream'; | |
| import CachePolicy from 'http-cache-semantics'; | |
| import Response from 'responselike'; | |
| import Keyv from 'keyv'; | |
| import mimicResponse from 'mimic-response'; | |
| import { CacheError, RequestError } from './types.js'; | |
| class CacheableRequest { | |
| constructor(cacheRequest, cacheAdapter) { | |
| this.hooks = new Map(); | |
| this.request = () => (options, cb) => { | |
| let url; | |
| if (typeof options === 'string') { | |
| url = normalizeUrlObject(urlLib.parse(options)); | |
| options = {}; | |
| } | |
| else if (options instanceof urlLib.URL) { | |
| url = normalizeUrlObject(urlLib.parse(options.toString())); | |
| options = {}; | |
| } | |
| else { | |
| const [pathname, ...searchParts] = (options.path ?? '').split('?'); | |
| const search = searchParts.length > 0 | |
| ? `?${searchParts.join('?')}` | |
| : ''; | |
| url = normalizeUrlObject({ ...options, pathname, search }); | |
| } | |
| options = { | |
| headers: {}, | |
| method: 'GET', | |
| cache: true, | |
| strictTtl: false, | |
| automaticFailover: false, | |
| ...options, | |
| ...urlObjectToRequestOptions(url), | |
| }; | |
| options.headers = Object.fromEntries(entries(options.headers).map(([key, value]) => [key.toLowerCase(), value])); | |
| const ee = new EventEmitter(); | |
| const normalizedUrlString = normalizeUrl(urlLib.format(url), { | |
| stripWWW: false, | |
| removeTrailingSlash: false, | |
| stripAuthentication: false, | |
| }); | |
| let key = `${options.method}:${normalizedUrlString}`; | |
| // POST, PATCH, and PUT requests may be cached, depending on the response | |
| // cache-control headers. As a result, the body of the request should be | |
| // added to the cache key in order to avoid collisions. | |
| if (options.body && options.method !== undefined && ['POST', 'PATCH', 'PUT'].includes(options.method)) { | |
| if (options.body instanceof stream.Readable) { | |
| // Streamed bodies should completely skip the cache because they may | |
| // or may not be hashable and in either case the stream would need to | |
| // close before the cache key could be generated. | |
| options.cache = false; | |
| } | |
| else { | |
| key += `:${crypto.createHash('md5').update(options.body).digest('hex')}`; | |
| } | |
| } | |
| let revalidate = false; | |
| let madeRequest = false; | |
| const makeRequest = (options_) => { | |
| madeRequest = true; | |
| let requestErrored = false; | |
| let requestErrorCallback = () => { }; | |
| const requestErrorPromise = new Promise(resolve => { | |
| requestErrorCallback = () => { | |
| if (!requestErrored) { | |
| requestErrored = true; | |
| resolve(); | |
| } | |
| }; | |
| }); | |
| const handler = async (response) => { | |
| if (revalidate) { | |
| response.status = response.statusCode; | |
| const revalidatedPolicy = CachePolicy.fromObject(revalidate.cachePolicy).revalidatedPolicy(options_, response); | |
| if (!revalidatedPolicy.modified) { | |
| response.resume(); | |
| await new Promise(resolve => { | |
| // Skipping 'error' handler cause 'error' event should't be emitted for 304 response | |
| response | |
| .once('end', resolve); | |
| }); | |
| const headers = convertHeaders(revalidatedPolicy.policy.responseHeaders()); | |
| response = new Response({ statusCode: revalidate.statusCode, headers, body: revalidate.body, url: revalidate.url }); | |
| response.cachePolicy = revalidatedPolicy.policy; | |
| response.fromCache = true; | |
| } | |
| } | |
| if (!response.fromCache) { | |
| response.cachePolicy = new CachePolicy(options_, response, options_); | |
| response.fromCache = false; | |
| } | |
| let clonedResponse; | |
| if (options_.cache && response.cachePolicy.storable()) { | |
| clonedResponse = cloneResponse(response); | |
| (async () => { | |
| try { | |
| const bodyPromise = getStream.buffer(response); | |
| await Promise.race([ | |
| requestErrorPromise, | |
| new Promise(resolve => response.once('end', resolve)), | |
| new Promise(resolve => response.once('close', resolve)), // eslint-disable-line no-promise-executor-return | |
| ]); | |
| const body = await bodyPromise; | |
| let value = { | |
| url: response.url, | |
| statusCode: response.fromCache ? revalidate.statusCode : response.statusCode, | |
| body, | |
| cachePolicy: response.cachePolicy.toObject(), | |
| }; | |
| let ttl = options_.strictTtl ? response.cachePolicy.timeToLive() : undefined; | |
| if (options_.maxTtl) { | |
| ttl = ttl ? Math.min(ttl, options_.maxTtl) : options_.maxTtl; | |
| } | |
| if (this.hooks.size > 0) { | |
| /* eslint-disable no-await-in-loop */ | |
| for (const key_ of this.hooks.keys()) { | |
| value = await this.runHook(key_, value, response); | |
| } | |
| /* eslint-enable no-await-in-loop */ | |
| } | |
| await this.cache.set(key, value, ttl); | |
| } | |
| catch (error) { | |
| ee.emit('error', new CacheError(error)); | |
| } | |
| })(); | |
| } | |
| else if (options_.cache && revalidate) { | |
| (async () => { | |
| try { | |
| await this.cache.delete(key); | |
| } | |
| catch (error) { | |
| ee.emit('error', new CacheError(error)); | |
| } | |
| })(); | |
| } | |
| ee.emit('response', clonedResponse ?? response); | |
| if (typeof cb === 'function') { | |
| cb(clonedResponse ?? response); | |
| } | |
| }; | |
| try { | |
| const request_ = this.cacheRequest(options_, handler); | |
| request_.once('error', requestErrorCallback); | |
| request_.once('abort', requestErrorCallback); | |
| request_.once('destroy', requestErrorCallback); | |
| ee.emit('request', request_); | |
| } | |
| catch (error) { | |
| ee.emit('error', new RequestError(error)); | |
| } | |
| }; | |
| (async () => { | |
| const get = async (options_) => { | |
| await Promise.resolve(); | |
| const cacheEntry = options_.cache ? await this.cache.get(key) : undefined; | |
| if (cacheEntry === undefined && !options_.forceRefresh) { | |
| makeRequest(options_); | |
| return; | |
| } | |
| const policy = CachePolicy.fromObject(cacheEntry.cachePolicy); | |
| if (policy.satisfiesWithoutRevalidation(options_) && !options_.forceRefresh) { | |
| const headers = convertHeaders(policy.responseHeaders()); | |
| const response = new Response({ statusCode: cacheEntry.statusCode, headers, body: cacheEntry.body, url: cacheEntry.url }); | |
| response.cachePolicy = policy; | |
| response.fromCache = true; | |
| ee.emit('response', response); | |
| if (typeof cb === 'function') { | |
| cb(response); | |
| } | |
| } | |
| else if (policy.satisfiesWithoutRevalidation(options_) && Date.now() >= policy.timeToLive() && options_.forceRefresh) { | |
| await this.cache.delete(key); | |
| options_.headers = policy.revalidationHeaders(options_); | |
| makeRequest(options_); | |
| } | |
| else { | |
| revalidate = cacheEntry; | |
| options_.headers = policy.revalidationHeaders(options_); | |
| makeRequest(options_); | |
| } | |
| }; | |
| const errorHandler = (error) => ee.emit('error', new CacheError(error)); | |
| if (this.cache instanceof Keyv) { | |
| const cachek = this.cache; | |
| cachek.once('error', errorHandler); | |
| ee.on('error', () => cachek.removeListener('error', errorHandler)); | |
| ee.on('response', () => cachek.removeListener('error', errorHandler)); | |
| } | |
| try { | |
| await get(options); | |
| } | |
| catch (error) { | |
| if (options.automaticFailover && !madeRequest) { | |
| makeRequest(options); | |
| } | |
| ee.emit('error', new CacheError(error)); | |
| } | |
| })(); | |
| return ee; | |
| }; | |
| this.addHook = (name, fn) => { | |
| if (!this.hooks.has(name)) { | |
| this.hooks.set(name, fn); | |
| } | |
| }; | |
| this.removeHook = (name) => this.hooks.delete(name); | |
| this.getHook = (name) => this.hooks.get(name); | |
| this.runHook = async (name, ...args) => this.hooks.get(name)?.(...args); | |
| if (cacheAdapter instanceof Keyv) { | |
| this.cache = cacheAdapter; | |
| } | |
| else if (typeof cacheAdapter === 'string') { | |
| this.cache = new Keyv({ | |
| uri: cacheAdapter, | |
| namespace: 'cacheable-request', | |
| }); | |
| } | |
| else { | |
| this.cache = new Keyv({ | |
| store: cacheAdapter, | |
| namespace: 'cacheable-request', | |
| }); | |
| } | |
| this.request = this.request.bind(this); | |
| this.cacheRequest = cacheRequest; | |
| } | |
| } | |
| const entries = Object.entries; | |
| const cloneResponse = (response) => { | |
| const clone = new PassThroughStream({ autoDestroy: false }); | |
| mimicResponse(response, clone); | |
| return response.pipe(clone); | |
| }; | |
| const urlObjectToRequestOptions = (url) => { | |
| const options = { ...url }; | |
| options.path = `${url.pathname || '/'}${url.search || ''}`; | |
| delete options.pathname; | |
| delete options.search; | |
| return options; | |
| }; | |
| const normalizeUrlObject = (url) => | |
| // If url was parsed by url.parse or new URL: | |
| // - hostname will be set | |
| // - host will be hostname[:port] | |
| // - port will be set if it was explicit in the parsed string | |
| // Otherwise, url was from request options: | |
| // - hostname or host may be set | |
| // - host shall not have port encoded | |
| ({ | |
| protocol: url.protocol, | |
| auth: url.auth, | |
| hostname: url.hostname || url.host || 'localhost', | |
| port: url.port, | |
| pathname: url.pathname, | |
| search: url.search, | |
| }); | |
| const convertHeaders = (headers) => { | |
| const result = []; | |
| for (const name of Object.keys(headers)) { | |
| result[name.toLowerCase()] = headers[name]; | |
| } | |
| return result; | |
| }; | |
| export default CacheableRequest; | |
| export * from './types.js'; | |
| export const onResponse = 'onResponse'; | |
| //# sourceMappingURL=index.js.map |
Xet Storage Details
- Size:
- 12.8 kB
- Xet hash:
- 98ba972e7580f37d5638ec8654cac4f270ea028a165eef47449b165eb3f24e24
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.