初始化
This commit is contained in:
234
node_modules/undici/lib/cache/memory-cache-store.js
generated
vendored
Normal file
234
node_modules/undici/lib/cache/memory-cache-store.js
generated
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
'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) {
|
||||
return entries.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]
|
||||
}))
|
||||
))
|
||||
}
|
||||
|
||||
module.exports = MemoryCacheStore
|
||||
Reference in New Issue
Block a user