File Explorer

/var/runtime/node_modules/@aws-sdk/node_modules/undici/lib/cache

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

memory-cache-store.js7.3 KB · 280 lines
'use strict' const { Writable } = require('node:stream')const { EventEmitter } = require('node:events')const { assertCacheKey, assertCacheValue } = require('../util/cache.js') /** * @typedef {import('../../types/cache-interceptor.d.ts').default.CacheKey} CacheKey * @typedef {import('../../types/cache-interceptor.d.ts').default.CacheValue} CacheValue * @typedef {import('../../types/cache-interceptor.d.ts').default.CacheStore} CacheStore * @typedef {import('../../types/cache-interceptor.d.ts').default.GetResult} GetResult */ /** * @implements {CacheStore} * @extends {EventEmitter} */class MemoryCacheStore extends EventEmitter {  #maxCount = 1024  #maxSize = 104857600 // 100MB  #maxEntrySize = 5242880 // 5MB   #size = 0  #count = 0  #entries = new Map()  #hasEmittedMaxSizeEvent = false   /**   * @param {import('../../types/cache-interceptor.d.ts').default.MemoryCacheStoreOpts | undefined} [opts]   */  constructor (opts) {    super()    if (opts) {      if (typeof opts !== 'object') {        throw new TypeError('MemoryCacheStore options must be an object')      }       if (opts.maxCount !== undefined) {        if (          typeof opts.maxCount !== 'number' ||          !Number.isInteger(opts.maxCount) ||          opts.maxCount < 0        ) {          throw new TypeError('MemoryCacheStore options.maxCount must be a non-negative integer')        }        this.#maxCount = opts.maxCount      }       if (opts.maxSize !== undefined) {        if (          typeof opts.maxSize !== 'number' ||          !Number.isInteger(opts.maxSize) ||          opts.maxSize < 0        ) {          throw new TypeError('MemoryCacheStore options.maxSize must be a non-negative integer')        }        this.#maxSize = opts.maxSize      }       if (opts.maxEntrySize !== undefined) {        if (          typeof opts.maxEntrySize !== 'number' ||          !Number.isInteger(opts.maxEntrySize) ||          opts.maxEntrySize < 0        ) {          throw new TypeError('MemoryCacheStore options.maxEntrySize must be a non-negative integer')        }        this.#maxEntrySize = opts.maxEntrySize      }    }  }   /**   * Get the current size of the cache in bytes   * @returns {number} The current size of the cache in bytes   */  get size () {    return this.#size  }   /**   * Check if the cache is full (either max size or max count reached)   * @returns {boolean} True if the cache is full, false otherwise   */  isFull () {    return this.#size >= this.#maxSize || this.#count >= this.#maxCount  }   /**   * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req   * @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}   */  get (key) {    assertCacheKey(key)     const topLevelKey = `${key.origin}:${key.path}`     const now = Date.now()    const entries = this.#entries.get(topLevelKey)     const entry = entries ? findEntry(key, entries, now) : null     return entry == null      ? undefined      : {          statusMessage: entry.statusMessage,          statusCode: entry.statusCode,          headers: entry.headers,          body: entry.body,          vary: entry.vary ? entry.vary : undefined,          etag: entry.etag,          cacheControlDirectives: entry.cacheControlDirectives,          cachedAt: entry.cachedAt,          staleAt: entry.staleAt,          deleteAt: entry.deleteAt        }  }   /**   * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key   * @param {import('../../types/cache-interceptor.d.ts').default.CacheValue} val   * @returns {Writable | undefined}   */  createWriteStream (key, val) {    assertCacheKey(key)    assertCacheValue(val)     const topLevelKey = `${key.origin}:${key.path}`     const store = this    const entry = { ...key, ...val, body: [], size: 0 }     return new Writable({      write (chunk, encoding, callback) {        if (typeof chunk === 'string') {          chunk = Buffer.from(chunk, encoding)        }         entry.size += chunk.byteLength         if (entry.size >= store.#maxEntrySize) {          this.destroy()        } else {          entry.body.push(chunk)        }         callback(null)      },      final (callback) {        let entries = store.#entries.get(topLevelKey)        if (!entries) {          entries = []          store.#entries.set(topLevelKey, entries)        }        const previousEntry = findEntry(key, entries, Date.now())        if (previousEntry) {          const index = entries.indexOf(previousEntry)          entries.splice(index, 1, entry)          store.#size -= previousEntry.size        } else {          entries.push(entry)          store.#count += 1        }         store.#size += entry.size         // Check if cache is full and emit event if needed        if (store.#size > store.#maxSize || store.#count > store.#maxCount) {          // Emit maxSizeExceeded event if we haven't already          if (!store.#hasEmittedMaxSizeEvent) {            store.emit('maxSizeExceeded', {              size: store.#size,              maxSize: store.#maxSize,              count: store.#count,              maxCount: store.#maxCount            })            store.#hasEmittedMaxSizeEvent = true          }           // Perform eviction          for (const [key, entries] of store.#entries) {            for (const entry of entries.splice(0, entries.length / 2)) {              store.#size -= entry.size              store.#count -= 1            }            if (entries.length === 0) {              store.#entries.delete(key)            }          }           // Reset the event flag after eviction          if (store.#size < store.#maxSize && store.#count < store.#maxCount) {            store.#hasEmittedMaxSizeEvent = false          }        }         callback(null)      }    })  }   /**   * @param {CacheKey} key   */  delete (key) {    if (typeof key !== 'object') {      throw new TypeError(`expected key to be object, got ${typeof key}`)    }     const topLevelKey = `${key.origin}:${key.path}`     for (const entry of this.#entries.get(topLevelKey) ?? []) {      this.#size -= entry.size      this.#count -= 1    }    this.#entries.delete(topLevelKey)  }} function findEntry (key, entries, now) {  for (let i = 0; i < entries.length; i++) {    const entry = entries[i]    if (      entry.deleteAt > now &&      entry.method === key.method &&      varyMatches(key, entry)    ) {      return entry    }  }} function varyMatches (key, entry) {  if (entry.vary == null) {    return true  }   for (const headerName in entry.vary) {    if (Object.hasOwn(entry.vary, headerName) && !headerValueEquals(key.headers?.[headerName], entry.vary[headerName])) {      return false    }  }   return true} /** * @param {string|string[]|null|undefined} lhs * @param {string|string[]|null|undefined} rhs * @returns {boolean} */function headerValueEquals (lhs, rhs) {  if (lhs == null && rhs == null) {    return true  }   if ((lhs == null && rhs != null) ||      (lhs != null && rhs == null)) {    return false  }   if (Array.isArray(lhs) && Array.isArray(rhs)) {    if (lhs.length !== rhs.length) {      return false    }     for (let i = 0; i < lhs.length; i++) {      if (lhs[i] !== rhs[i]) {        return false      }    }     return true  }   return lhs === rhs} module.exports = MemoryCacheStore