From edddf73e8c9f1692024e29956faa0975967bcc53 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Mon, 20 Jan 2025 20:05:42 +0100 Subject: [PATCH] feat: sqlite add set and minor cleanup --- lib/cache/sqlite-cache-store.js | 122 ++++++++++-------- .../sqlite-cache-store-tests.js | 45 ++++++- types/cache-interceptor.d.ts | 2 +- 3 files changed, 115 insertions(+), 54 deletions(-) diff --git a/lib/cache/sqlite-cache-store.js b/lib/cache/sqlite-cache-store.js index 24ab257d3b2..e42c6d68057 100644 --- a/lib/cache/sqlite-cache-store.js +++ b/lib/cache/sqlite-cache-store.js @@ -15,11 +15,18 @@ const MAX_ENTRY_SIZE = 2 * 1000 * 1000 * 1000 * @implements {CacheStore} * * @typedef {{ - * id: Readonly - * headers?: Record - * vary?: string | object - * body: string - * } & import('../../types/cache-interceptor.d.ts').default.CacheValue} SqliteStoreValue + * id: Readonly, + * body?: Buffer + * statusCode: number + * statusMessage: string + * headers?: string + * vary?: string + * etag?: string + * cacheControlDirectives?: string + * cachedAt: number + * staleAt: number + * deleteAt: number + * }} SqliteStoreValue */ module.exports = class SqliteCacheStore { #maxEntrySize = MAX_ENTRY_SIZE @@ -234,12 +241,12 @@ module.exports = class SqliteCacheStore { * @type {import('../../types/cache-interceptor.d.ts').default.GetResult} */ const result = { - body: Buffer.from(value.body), + body: value.body, statusCode: value.statusCode, statusMessage: value.statusMessage, headers: value.headers ? JSON.parse(value.headers) : undefined, etag: value.etag ? value.etag : undefined, - vary: value.vary ?? undefined, + vary: value.vary ? JSON.parse(value.vary) : undefined, cacheControlDirectives: value.cacheControlDirectives ? JSON.parse(value.cacheControlDirectives) : undefined, @@ -251,6 +258,56 @@ module.exports = class SqliteCacheStore { return result } + /** + * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key + * @param {import('../../types/cache-interceptor.d.ts').default.CacheValue & { body: null | Buffer | Array}} value + */ + set (key, value) { + const url = this.#makeValueUrl(key) + const body = Array.isArray(value.body) ? Buffer.concat(value.body) : value.body + const size = body?.byteLength + + if (size && size > this.#maxEntrySize) { + return + } + + const existingValue = this.#findValue(key, true) + if (existingValue) { + // Updating an existing response, let's overwrite it + this.#updateValueQuery.run( + body, + value.deleteAt, + value.statusCode, + value.statusMessage, + value.headers ? JSON.stringify(value.headers) : null, + value.etag ? value.etag : null, + value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null, + value.cachedAt, + value.staleAt, + value.deleteAt, + existingValue.id + ) + } else { + this.#prune() + // New response, let's insert it + this.#insertValueQuery.run( + url, + key.method, + body, + value.deleteAt, + value.statusCode, + value.statusMessage, + value.headers ? JSON.stringify(value.headers) : null, + value.etag ? value.etag : null, + value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null, + value.vary ? JSON.stringify(value.vary) : null, + value.cachedAt, + value.staleAt, + value.deleteAt + ) + } + } + /** * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key * @param {import('../../types/cache-interceptor.d.ts').default.CacheValue} value @@ -260,7 +317,6 @@ module.exports = class SqliteCacheStore { assertCacheKey(key) assertCacheValue(value) - const url = this.#makeValueUrl(key) let size = 0 /** * @type {Buffer[] | null} @@ -269,11 +325,8 @@ module.exports = class SqliteCacheStore { const store = this return new Writable({ + decodeStrings: true, write (chunk, encoding, callback) { - if (typeof chunk === 'string') { - chunk = Buffer.from(chunk, encoding) - } - size += chunk.byteLength if (size < store.#maxEntrySize) { @@ -285,42 +338,7 @@ module.exports = class SqliteCacheStore { callback() }, final (callback) { - const existingValue = store.#findValue(key, true) - if (existingValue) { - // Updating an existing response, let's overwrite it - store.#updateValueQuery.run( - Buffer.concat(body), - value.deleteAt, - value.statusCode, - value.statusMessage, - value.headers ? JSON.stringify(value.headers) : null, - value.etag ? value.etag : null, - value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null, - value.cachedAt, - value.staleAt, - value.deleteAt, - existingValue.id - ) - } else { - store.#prune() - // New response, let's insert it - store.#insertValueQuery.run( - url, - key.method, - Buffer.concat(body), - value.deleteAt, - value.statusCode, - value.statusMessage, - value.headers ? JSON.stringify(value.headers) : null, - value.etag ? value.etag : null, - value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null, - value.vary ? JSON.stringify(value.vary) : null, - value.cachedAt, - value.staleAt, - value.deleteAt - ) - } - + store.set(key, { ...value, body }) callback() } }) @@ -379,7 +397,7 @@ module.exports = class SqliteCacheStore { /** * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key * @param {boolean} [canBeExpired=false] - * @returns {(SqliteStoreValue & { vary?: Record }) | undefined} + * @returns {SqliteStoreValue | undefined} */ #findValue (key, canBeExpired = false) { const url = this.#makeValueUrl(key) @@ -407,10 +425,10 @@ module.exports = class SqliteCacheStore { return undefined } - value.vary = JSON.parse(value.vary) + const vary = JSON.parse(value.vary) - for (const header in value.vary) { - if (!headerValueEquals(headers[header], value.vary[header])) { + for (const header in vary) { + if (!headerValueEquals(headers[header], vary[header])) { matches = false break } diff --git a/test/cache-interceptor/sqlite-cache-store-tests.js b/test/cache-interceptor/sqlite-cache-store-tests.js index 34e4fd6257d..c1f49d3a4c3 100644 --- a/test/cache-interceptor/sqlite-cache-store-tests.js +++ b/test/cache-interceptor/sqlite-cache-store-tests.js @@ -1,7 +1,7 @@ 'use strict' const { test, skip } = require('node:test') -const { notEqual, strictEqual } = require('node:assert') +const { notEqual, strictEqual, deepStrictEqual } = require('node:assert') const { rm } = require('node:fs/promises') const { cacheStoreTests, writeBody, compareGetResults } = require('./cache-store-test-utils.js') @@ -179,3 +179,46 @@ test('SqliteCacheStore two writes', async (t) => { writeBody(writable, body) } }) + +test('SqliteCacheStore write & read', async (t) => { + if (!hasSqlite) { + t.skip() + return + } + + const SqliteCacheStore = require('../../lib/cache/sqlite-cache-store.js') + + const store = new SqliteCacheStore({ + maxCount: 10 + }) + + /** + * @type {import('../../types/cache-interceptor.d.ts').default.CacheKey} + */ + const key = { + origin: 'localhost', + path: '/', + method: 'GET', + headers: {} + } + + /** + * @type {import('../../types/cache-interceptor.d.ts').default.CacheValue & { body: Buffer }} + */ + const value = { + statusCode: 200, + statusMessage: '', + headers: { foo: 'bar' }, + cacheControlDirectives: { 'max-stale': 0 }, + cachedAt: Date.now(), + staleAt: Date.now() + 10000, + deleteAt: Date.now() + 20000, + body: Buffer.from('asd'), + etag: undefined, + vary: undefined + } + + store.set(key, value) + + deepStrictEqual(store.get(key), value) +}) diff --git a/types/cache-interceptor.d.ts b/types/cache-interceptor.d.ts index 0ff88261e59..1713ca7e747 100644 --- a/types/cache-interceptor.d.ts +++ b/types/cache-interceptor.d.ts @@ -90,7 +90,7 @@ declare namespace CacheHandler { headers: Record vary?: Record etag?: string - body: null | Readable | Iterable | AsyncIterable | Buffer | Iterable | AsyncIterable | string + body?: Readable | Iterable | AsyncIterable | Buffer | Iterable | AsyncIterable | string cacheControlDirectives: CacheControlDirectives, cachedAt: number staleAt: number