Skip to content

Commit

Permalink
feat: sqlite add set and minor cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ronag committed Jan 20, 2025
1 parent d76f5cf commit 83a1e4d
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 54 deletions.
122 changes: 70 additions & 52 deletions lib/cache/sqlite-cache-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ const MAX_ENTRY_SIZE = 2 * 1000 * 1000 * 1000
* @implements {CacheStore}
*
* @typedef {{
* id: Readonly<number>
* headers?: Record<string, string | string[]>
* vary?: string | object
* body: string
* } & import('../../types/cache-interceptor.d.ts').default.CacheValue} SqliteStoreValue
* id: Readonly<number>,
* 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
Expand Down Expand Up @@ -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,
Expand All @@ -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<Buffer>}} 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
Expand All @@ -260,7 +317,6 @@ module.exports = class SqliteCacheStore {
assertCacheKey(key)
assertCacheValue(value)

const url = this.#makeValueUrl(key)
let size = 0
/**
* @type {Buffer[] | null}
Expand All @@ -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) {
Expand All @@ -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()
}
})
Expand Down Expand Up @@ -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<string, string[]> }) | undefined}
* @returns {SqliteStoreValue | undefined}
*/
#findValue (key, canBeExpired = false) {
const url = this.#makeValueUrl(key)
Expand Down Expand Up @@ -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
}
Expand Down
45 changes: 44 additions & 1 deletion test/cache-interceptor/sqlite-cache-store-tests.js
Original file line number Diff line number Diff line change
@@ -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')

Expand Down Expand Up @@ -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)

Check failure on line 223 in test/cache-interceptor/sqlite-cache-store-tests.js

View workflow job for this annotation

GitHub Actions / Test with SQLite enabled

SqliteCacheStore write & read

[Error [ERR_TEST_FAILURE]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { ] { code: 'ERR_TEST_FAILURE', failureType: 'testCodeFailure', cause: AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { at TestContext.<anonymous> (/home/runner/work/undici/undici/test/cache-interceptor/sqlite-cache-store-tests.js:223:3) at Test.runInAsyncScope (node:async_hooks:211:14) at Test.run (node:internal/test_runner/test:934:25) at Test.processPendingSubtests (node:internal/test_runner/test:633:18) at Test.postRun (node:internal/test_runner/test:1045:19) at Test.run (node:internal/test_runner/test:973:12) at async Test.processPendingSubtests (node:internal/test_runner/test:633:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: { body: [Uint8Array], statusCode: 200, statusMessage: '', headers: [Object], etag: undefined, vary: undefined, cacheControlDirectives: [Object], cachedAt: 1737400340985, staleAt: 1737400350985, deleteAt: 1737400360985 }, expected: { statusCode: 200, statusMessage: '', headers: [Object], cacheControlDirectives: [Object], cachedAt: 1737400340985, staleAt: 1737400350985, deleteAt: 1737400360985, body: <Buffer 61 73 64>, etag: undefined, vary: undefined }, operator: 'deepStrictEqual' } }

Check failure on line 223 in test/cache-interceptor/sqlite-cache-store-tests.js

View workflow job for this annotation

GitHub Actions / test (23, ubuntu-latest) / Test with Node.js 23 on ubuntu-latest

SqliteCacheStore write & read

[Error [ERR_TEST_FAILURE]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { ] { code: 'ERR_TEST_FAILURE', failureType: 'testCodeFailure', cause: AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { at TestContext.<anonymous> (/home/runner/work/undici/undici/test/cache-interceptor/sqlite-cache-store-tests.js:223:3) at Test.runInAsyncScope (node:async_hooks:211:14) at Test.run (node:internal/test_runner/test:931:25) at Test.processPendingSubtests (node:internal/test_runner/test:629:18) at Test.postRun (node:internal/test_runner/test:1042:19) at Test.run (node:internal/test_runner/test:970:12) at async Test.processPendingSubtests (node:internal/test_runner/test:629:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: { body: [Uint8Array], statusCode: 200, statusMessage: '', headers: [Object], etag: undefined, vary: undefined, cacheControlDirectives: [Object], cachedAt: 1737400379572, staleAt: 1737400389572, deleteAt: 1737400399572 }, expected: { statusCode: 200, statusMessage: '', headers: [Object], cacheControlDirectives: [Object], cachedAt: 1737400379572, staleAt: 1737400389572, deleteAt: 1737400399572, body: <Buffer 61 73 64>, etag: undefined, vary: undefined }, operator: 'deepStrictEqual' } }

Check failure on line 223 in test/cache-interceptor/sqlite-cache-store-tests.js

View workflow job for this annotation

GitHub Actions / test (22, macos-latest) / Test with Node.js 22 on macos-latest

SqliteCacheStore write & read

[Error [ERR_TEST_FAILURE]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { ] { code: 'ERR_TEST_FAILURE', failureType: 'testCodeFailure', cause: AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { at TestContext.<anonymous> (/Users/runner/work/undici/undici/test/cache-interceptor/sqlite-cache-store-tests.js:223:3) at Test.runInAsyncScope (node:async_hooks:211:14) at Test.run (node:internal/test_runner/test:931:25) at Test.processPendingSubtests (node:internal/test_runner/test:629:18) at Test.postRun (node:internal/test_runner/test:1042:19) at Test.run (node:internal/test_runner/test:970:12) at async Test.processPendingSubtests (node:internal/test_runner/test:629:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: { body: [Uint8Array], statusCode: 200, statusMessage: '', headers: [Object], etag: undefined, vary: undefined, cacheControlDirectives: [Object], cachedAt: 1737400402309, staleAt: 1737400412309, deleteAt: 1737400422309 }, expected: { statusCode: 200, statusMessage: '', headers: [Object], cacheControlDirectives: [Object], cachedAt: 1737400402309, staleAt: 1737400412309, deleteAt: 1737400422309, body: <Buffer 61 73 64>, etag: undefined, vary: undefined }, operator: 'deepStrictEqual' } }

Check failure on line 223 in test/cache-interceptor/sqlite-cache-store-tests.js

View workflow job for this annotation

GitHub Actions / test (23, macos-latest) / Test with Node.js 23 on macos-latest

SqliteCacheStore write & read

[Error [ERR_TEST_FAILURE]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { ] { code: 'ERR_TEST_FAILURE', failureType: 'testCodeFailure', cause: AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { at TestContext.<anonymous> (/Users/runner/work/undici/undici/test/cache-interceptor/sqlite-cache-store-tests.js:223:3) at Test.runInAsyncScope (node:async_hooks:211:14) at Test.run (node:internal/test_runner/test:931:25) at Test.processPendingSubtests (node:internal/test_runner/test:629:18) at Test.postRun (node:internal/test_runner/test:1042:19) at Test.run (node:internal/test_runner/test:970:12) at async Test.processPendingSubtests (node:internal/test_runner/test:629:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: { body: [Uint8Array], statusCode: 200, statusMessage: '', headers: [Object], etag: undefined, vary: undefined, cacheControlDirectives: [Object], cachedAt: 1737400403492, staleAt: 1737400413492, deleteAt: 1737400423492 }, expected: { statusCode: 200, statusMessage: '', headers: [Object], cacheControlDirectives: [Object], cachedAt: 1737400403492, staleAt: 1737400413492, deleteAt: 1737400423492, body: <Buffer 61 73 64>, etag: undefined, vary: undefined }, operator: 'deepStrictEqual' } }

Check failure on line 223 in test/cache-interceptor/sqlite-cache-store-tests.js

View workflow job for this annotation

GitHub Actions / Test with Node.js 22 compiled --without-intl

SqliteCacheStore write & read

[Error [ERR_TEST_FAILURE]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { ] { code: 'ERR_TEST_FAILURE', failureType: 'testCodeFailure', cause: AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { at TestContext.<anonymous> (/home/runner/work/undici/undici/test/cache-interceptor/sqlite-cache-store-tests.js:223:3) at Test.runInAsyncScope (node:async_hooks:211:14) at Test.run (node:internal/test_runner/test:931:25) at Test.processPendingSubtests (node:internal/test_runner/test:629:18) at Test.postRun (node:internal/test_runner/test:1042:19) at Test.run (node:internal/test_runner/test:970:12) at async Test.processPendingSubtests (node:internal/test_runner/test:629:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: { body: [Uint8Array], statusCode: 200, statusMessage: '', headers: [Object], etag: undefined, vary: undefined, cacheControlDirectives: [Object], cachedAt: 1737400429063, staleAt: 1737400439063, deleteAt: 1737400449063 }, expected: { statusCode: 200, statusMessage: '', headers: [Object], cacheControlDirectives: [Object], cachedAt: 1737400429063, staleAt: 1737400439063, deleteAt: 1737400449063, body: <Buffer 61 73 64>, etag: undefined, vary: undefined }, operator: 'deepStrictEqual' } }

Check failure on line 223 in test/cache-interceptor/sqlite-cache-store-tests.js

View workflow job for this annotation

GitHub Actions / Test with Node.js 23 compiled --without-intl

SqliteCacheStore write & read

[Error [ERR_TEST_FAILURE]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { ] { code: 'ERR_TEST_FAILURE', failureType: 'testCodeFailure', cause: AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal: + actual - expected { + body: Uint8Array(3) [ - body: Buffer(3) [Uint8Array] [ 97, 115, 100 ], cacheControlDirectives: { at TestContext.<anonymous> (/home/runner/work/undici/undici/test/cache-interceptor/sqlite-cache-store-tests.js:223:3) at Test.runInAsyncScope (node:async_hooks:211:14) at Test.run (node:internal/test_runner/test:931:25) at Test.processPendingSubtests (node:internal/test_runner/test:629:18) at Test.postRun (node:internal/test_runner/test:1042:19) at Test.run (node:internal/test_runner/test:970:12) at async Test.processPendingSubtests (node:internal/test_runner/test:629:7) { generatedMessage: true, code: 'ERR_ASSERTION', actual: { body: [Uint8Array], statusCode: 200, statusMessage: '', headers: [Object], etag: undefined, vary: undefined, cacheControlDirectives: [Object], cachedAt: 1737400441599, staleAt: 1737400451599, deleteAt: 1737400461599 }, expected: { statusCode: 200, statusMessage: '', headers: [Object], cacheControlDirectives: [Object], cachedAt: 1737400441599, staleAt: 1737400451599, deleteAt: 1737400461599, body: <Buffer 61 73 64>, etag: undefined, vary: undefined }, operator: 'deepStrictEqual' } }
})
2 changes: 1 addition & 1 deletion types/cache-interceptor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ declare namespace CacheHandler {
headers: Record<string, string | string[]>
vary?: Record<string, string | string[]>
etag?: string
body: null | Readable | Iterable<Buffer> | AsyncIterable<Buffer> | Buffer | Iterable<string> | AsyncIterable<string> | string
body?: null | Readable | Iterable<Buffer> | AsyncIterable<Buffer> | Buffer | Iterable<string> | AsyncIterable<string> | string
cacheControlDirectives: CacheControlDirectives,
cachedAt: number
staleAt: number
Expand Down

0 comments on commit 83a1e4d

Please sign in to comment.