Skip to content

Commit

Permalink
fixup
Browse files Browse the repository at this point in the history
  • Loading branch information
ronag committed Nov 15, 2024
1 parent e46c54c commit 977c314
Showing 1 changed file with 51 additions and 99 deletions.
150 changes: 51 additions & 99 deletions lib/cache/memory-cache-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
const { Writable } = require('node:stream')

/**
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheKey} CacheKey
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheStore} CacheStore
* @typedef {import('../../types/cache-interceptor.d.ts').default.CachedResponse} CachedResponse
* @typedef {import('../../types/cache-interceptor.d.ts').default.GetResult} GetResult
* @implements {CacheStore}
*
* @typedef {{
* locked: boolean
* opts: import('../../types/cache-interceptor.d.ts').default.CachedResponse
* body?: Buffer[]
* }} MemoryStoreValue
*/
class MemoryCacheStore {
#maxCount = Infinity
Expand All @@ -20,7 +17,7 @@ class MemoryCacheStore {
#entryCount = 0

/**
* @type {Map<string, Map<string, MemoryStoreValue[]>>}
* @type {Map<string, Map<string, GetResult[]>>}
*/
#data = new Map()

Expand Down Expand Up @@ -66,22 +63,8 @@ class MemoryCacheStore {
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}
*/
get (key) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}

const values = this.#getValuesForRequest(key, false)
if (!values) {
return undefined
}

const value = this.#findValue(key, values)

if (!value || value.locked) {
return undefined
}

return { ...value.opts, body: value.body }
const values = this.#getValuesForRequest(key)
return findValue(key, values)
}

/**
Expand All @@ -98,86 +81,52 @@ class MemoryCacheStore {
}

if (this.isFull) {
return undefined
this.#prune()
}

const values = this.#getValuesForRequest(key, true)

let value = this.#findValue(key, values)
if (!value) {
// The value doesn't already exist, meaning we haven't cached this
// response before. Let's assign it a value and insert it into our data
// property.
if (this.isFull) {
return undefined
}

if (this.isFull) {
// Or not, we don't have space to add another response
return undefined
}
const values = this.#getValuesForRequest(key)

if (this.#entryCount++ > this.#maxEntries) {
this.#prune()
}
let value = findValue(key, values)

value = { locked: true, opts }
if (!value) {
value = { ...opts, body: null }
values.push(value)
} else {
// Check if there's already another request writing to the value or
// a request reading from it
if (value.locked) {
return undefined
}

// Empty it so we can overwrite it
value.body = []
}

let currentSize = 0
/**
* @type {Buffer[] | null}
*/
let body = []

const body = []
const maxEntrySize = this.#maxEntrySize

const writable = new Writable({
return new Writable({
write (chunk, encoding, callback) {
if (key.method === 'HEAD') {
throw new Error('HEAD request shouldn\'t have a body')
}

if (!body) {
return callback()
}

if (typeof chunk === 'string') {
chunk = Buffer.from(chunk, encoding)
}

currentSize += chunk.byteLength

if (currentSize >= maxEntrySize) {
body = null
this.end()
return callback()
this.destroy()
} else {
body.push(chunk)
}

body.push(chunk)
callback()
},
final (callback) {
value.locked = false
if (body !== null) {
value.body = body
}

Object.assign(value, opts, { body })
callback()
}
})

return writable
}

/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @param {CacheKey} key
*/
delete (key) {
this.#data.delete(`${key.origin}:${key.path}`)
Expand All @@ -186,53 +135,36 @@ class MemoryCacheStore {
/**
* Gets all of the requests of the same origin, path, and method. Does not
* take the `vary` property into account.
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @param {boolean} [makeIfDoesntExist=false]
* @returns {MemoryStoreValue[] | undefined}
* @param {CacheKey} key
* @returns {GetResult[]}
*/
#getValuesForRequest (key, makeIfDoesntExist) {
#getValuesForRequest (key) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}

// https://www.rfc-editor.org/rfc/rfc9111.html#section-2-3
const topLevelKey = `${key.origin}:${key.path}`
let cachedPaths = this.#data.get(topLevelKey)
if (!cachedPaths) {
if (!makeIfDoesntExist) {
return undefined
}

cachedPaths = new Map()
this.#data.set(topLevelKey, cachedPaths)
}

let value = cachedPaths.get(key.method)
if (!value && makeIfDoesntExist) {
if (!value) {
value = []
cachedPaths.set(key.method, value)
}

return value
}

/**
* Given a list of values of a certain request, this decides the best value
* to respond with.
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
* @param {MemoryStoreValue[]} values
* @returns {(MemoryStoreValue) | undefined}
*/
#findValue (req, values) {
const now = Date.now()
return values.find(({ opts: { deleteAt, vary }, body }) => (
body != null &&
deleteAt > now &&
(!vary || Object.keys(vary).every(key => vary[key] === req.headers?.[key]))
))
}

#prune () {
const now = Date.now()
for (const [key, cachedPaths] of this.#data) {
for (const [method, prev] of cachedPaths) {
const next = prev.filter(({ opts, body }) => body == null || opts.deleteAt > now)
const next = prev.filter(({ deleteAt }) => deleteAt > now)
if (next.length === 0) {
cachedPaths.delete(method)
if (cachedPaths.size === 0) {
Expand All @@ -247,4 +179,24 @@ class MemoryCacheStore {
}
}

/**
* Given a list of values of a certain request, this decides the best value
* to respond with.
* @param {CacheKey} key
* @param {GetResult[] | undefined } values
* @returns {(GetResult) | undefined}
*/
function findValue (key, values) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}

const now = Date.now()
return values?.find(({ deleteAt, vary, body }) => (
body != null &&
deleteAt > now &&
(!vary || Object.keys(vary).every(headerName => vary[headerName] === key.headers?.[headerName]))
))
}

module.exports = MemoryCacheStore

0 comments on commit 977c314

Please # to comment.