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 8ed6901 commit d3d77b2
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 87 deletions.
114 changes: 35 additions & 79 deletions lib/cache/memory-cache-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,100 +64,64 @@ class MemoryCacheStore {
}
}

get isFull () {
return this.#arr.length >= this.#maxCount || this.#size >= this.#maxSize
}

/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}
*/
get ({ origin, path, method, headers }) {
const now = Date.now()

const value = this.#arr.find((value) => (
value.method === method &&
value.origin === origin &&
value.path === path &&
value.deleteAt > now &&
(!value.vary || Object.keys(value.vary).every(headerName => value.vary[headerName] === headers?.[headerName]))
return this.#arr.find((entry) => (
entry.deleteAt > now &&
entry.method === method &&
entry.origin === origin &&
entry.path === path &&
(!entry.vary || Object.keys(entry.vary).every(headerName => entry.vary[headerName] === headers?.[headerName]))
))

return value != null
? {
statusMessage: value.statusMessage,
statusCode: value.statusCode,
rawHeaders: value.rawHeaders,
cachedAt: value.cachedAt,
staleAt: value.staleAt,
body: value.body
}
: undefined
}

/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
* @param {import('../../types/cache-interceptor.d.ts').default.CacheValue} opts
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @param {import('../../types/cache-interceptor.d.ts').default.CacheValue} val
* @returns {Writable | undefined}
*/
createWriteStream (req, opts) {
if (typeof req !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof req}`)
createWriteStream (key, val) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}
if (typeof opts !== 'object') {
throw new TypeError(`expected value to be object, got ${typeof opts}`)
if (typeof val !== 'object') {
throw new TypeError(`expected value to be object, got ${typeof val}`)
}

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

if (this.isFull) {
return undefined
}

let currentSize = 0

const store = this

// TODO (fix): Deep clone...
// TODO (perf): Faster with explicit props...
const val = {
statusCode: opts.statusCode,
statusMessage: opts.statusMessage,
rawHeaders: opts.rawHeaders,
vary: opts.vary,
cachedAt: opts.cachedAt,
staleAt: opts.staleAt,
deleteAt: opts.deleteAt,
method: req.method,
origin: req.origin,
path: req.path,
/** @type {Buffer[]} */
body: []
}
const entry = { ...key, ...val, body: [], size: 0 }

return new Writable({
write (chunk, encoding, callback) {
if (typeof chunk === 'string') {
chunk = Buffer.from(chunk, encoding)
}

currentSize += chunk.byteLength
entry.size += chunk.byteLength

if (currentSize >= store.#maxEntrySize) {
if (entry.size >= store.#maxEntrySize) {
this.destroy()
} else {
val.body.push(chunk)
entry.body.push(chunk)
}

callback(null)
},
final (callback) {
store.#arr.push(val)
for (const buf of val.body) {
store.#size += buf.byteLength
store.#arr.push(entry)
store.#size += entry.size

while (store.#arr.length >= store.#maxCount || store.#size >= store.#maxSize) {
const count = Math.max(0, store.#arr.length - store.#maxCount / 2)
for (const entry of store.#arr.splice(0, count)) {
store.#size -= entry.size
}
}

callback(null)
}
})
Expand All @@ -166,29 +130,21 @@ class MemoryCacheStore {
/**
* @param {CacheKey} key
*/
delete ({ origin, path }) {
// https://www.rfc-editor.org/rfc/rfc9111.html#section-2-3
delete (key) {
if (typeof key !== 'object') {
throw new TypeError(`expected key to be object, got ${typeof key}`)
}

const arr = []
for (const value of this.#arr) {
if (value.path === path && value.origin === origin) {
for (const buf of value.body) {
this.#size -= buf.byteLength
}
for (const entry of this.#arr) {
if (entry.path === key.path && entry.origin === key.origin) {
this.#size -= entry.size
} else {
arr.push(value)
arr.push(entry)
}
}
this.#arr = arr
}

#prune () {
const count = Math.max(0, this.#arr.length - this.#maxCount / 2)
for (const value of this.#arr.splice(0, count)) {
for (const buf of value.body) {
this.#size -= buf.byteLength
}
}
}
}

module.exports = MemoryCacheStore
9 changes: 1 addition & 8 deletions types/cache-interceptor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ declare namespace CacheHandler {
statusCode: number
statusMessage: string
rawHeaders: Buffer[]
body: null | Readable | Iterable<Buffer> | Buffer | Iterable<string> | string
body: null | Readable | Iterable<Buffer> | AsyncIterable<Buffer> | Buffer | Iterable<string> | AsyncIterable<string> | string
cachedAt: number
staleAt: number
}
Expand All @@ -54,11 +54,6 @@ declare namespace CacheHandler {
* Underlying storage provider for cached responses
*/
export interface CacheStore {
/**
* Whether or not the cache is full and can not store any more responses
*/
get isFull(): boolean | undefined

get(key: CacheKey): GetResult | Promise<GetResult | undefined> | undefined

createWriteStream(key: CacheKey, val: CacheValue): Writable | undefined
Expand Down Expand Up @@ -88,8 +83,6 @@ declare namespace CacheHandler {
export class MemoryCacheStore implements CacheStore {
constructor (opts?: MemoryCacheStoreOpts)

get isFull (): boolean | undefined

get (key: CacheKey): GetResult | Promise<GetResult | undefined> | undefined

createWriteStream (key: CacheKey, value: CachedResponse): Writable | undefined
Expand Down

0 comments on commit d3d77b2

Please sign in to comment.