codtracker-js/node_modules/undici/lib/cache/memory-cache-store.js
2025-03-29 16:05:52 -04:00

184 lines
5.0 KiB
JavaScript

'use strict'
const { Writable } = require('node:stream')
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}
*/
class MemoryCacheStore {
#maxCount = Infinity
#maxSize = Infinity
#maxEntrySize = Infinity
#size = 0
#count = 0
#entries = new Map()
/**
* @param {import('../../types/cache-interceptor.d.ts').default.MemoryCacheStoreOpts | undefined} [opts]
*/
constructor (opts) {
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
}
}
}
/**
* @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 entry = this.#entries.get(topLevelKey)?.find((entry) => (
entry.deleteAt > now &&
entry.method === key.method &&
(entry.vary == null || Object.keys(entry.vary).every(headerName => {
if (entry.vary[headerName] === null) {
return key.headers[headerName] === undefined
}
return entry.vary[headerName] === key.headers[headerName]
}))
))
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)
}
entries.push(entry)
store.#size += entry.size
store.#count += 1
if (store.#size > store.#maxSize || store.#count > store.#maxCount) {
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)
}
}
}
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)
}
}
module.exports = MemoryCacheStore