Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: nodejs/undici
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v7.2.3
Choose a base ref
...
head repository: nodejs/undici
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v7.3.0
Choose a head ref
  • 4 commits
  • 4 files changed
  • 2 contributors

Commits on Jan 20, 2025

  1. fix: sqlite null ref (#4016)

    ronag authored Jan 20, 2025

    Unverified

    No user is associated with the committer email.
    Copy the full SHA
    d76f5cf View commit details

Commits on Jan 21, 2025

  1. fix: sqlite remove unnecessary parameter (#4017)

    ronag authored Jan 21, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    atanmarko Marko Atanasievski
    Copy the full SHA
    a364e7c View commit details
  2. feat: sqlite add set and minor cleanup (#4018)

    * feat: sqlite add set and minor cleanup
    
    * fixup
    ronag authored Jan 21, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    atanmarko Marko Atanasievski
    Copy the full SHA
    fe21269 View commit details

Commits on Jan 22, 2025

  1. Bumped v7.3.0 (#4021)

    Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
    github-actions[bot] authored Jan 22, 2025
    Copy the full SHA
    ee02abe View commit details
Showing with 134 additions and 83 deletions.
  1. +88 −80 lib/cache/sqlite-cache-store.js
  2. +1 −1 package.json
  3. +44 −1 test/cache-interceptor/sqlite-cache-store-tests.js
  4. +1 −1 types/cache-interceptor.d.ts
168 changes: 88 additions & 80 deletions lib/cache/sqlite-cache-store.js
Original file line number Diff line number Diff line change
@@ -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?: Uint8Array
* 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
@@ -61,7 +68,7 @@ module.exports = class SqliteCacheStore {
#countEntriesQuery

/**
* @type {import('node:sqlite').StatementSync}
* @type {import('node:sqlite').StatementSync | null}
*/
#deleteOldValuesQuery

@@ -163,8 +170,7 @@ module.exports = class SqliteCacheStore {
etag = ?,
cacheControlDirectives = ?,
cachedAt = ?,
staleAt = ?,
deleteAt = ?
staleAt = ?
WHERE
id = ?
`)
@@ -182,9 +188,8 @@ module.exports = class SqliteCacheStore {
cacheControlDirectives,
vary,
cachedAt,
staleAt,
deleteAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
staleAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)

this.#deleteByUrlQuery = this.#db.prepare(
@@ -219,36 +224,78 @@ module.exports = class SqliteCacheStore {

/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}
* @returns {(import('../../types/cache-interceptor.d.ts').default.GetResult & { body?: Buffer }) | undefined}
*/
get (key) {
assertCacheKey(key)

const value = this.#findValue(key)
return value
? {
body: value.body ? Buffer.from(value.body.buffer) : undefined,
statusCode: value.statusCode,
statusMessage: value.statusMessage,
headers: value.headers ? JSON.parse(value.headers) : undefined,
etag: value.etag ? value.etag : undefined,
vary: value.vary ? JSON.parse(value.vary) : undefined,
cacheControlDirectives: value.cacheControlDirectives
? JSON.parse(value.cacheControlDirectives)
: undefined,
cachedAt: value.cachedAt,
staleAt: value.staleAt,
deleteAt: value.deleteAt
}
: undefined
}

if (!value) {
return undefined
}
/**
* @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) {
assertCacheKey(key)

/**
* @type {import('../../types/cache-interceptor.d.ts').default.GetResult}
*/
const result = {
body: Buffer.from(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,
cacheControlDirectives: value.cacheControlDirectives
? JSON.parse(value.cacheControlDirectives)
: undefined,
cachedAt: value.cachedAt,
staleAt: value.staleAt,
deleteAt: value.deleteAt
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
}

return result
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,
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
)
}
}

/**
@@ -260,7 +307,6 @@ module.exports = class SqliteCacheStore {
assertCacheKey(key)
assertCacheValue(value)

const url = this.#makeValueUrl(key)
let size = 0
/**
* @type {Buffer[] | null}
@@ -269,11 +315,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 +328,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()
}
})
@@ -344,14 +352,14 @@ module.exports = class SqliteCacheStore {

{
const removed = this.#deleteExpiredValuesQuery.run(Date.now()).changes
if (removed > 0) {
if (removed) {
return removed
}
}

{
const removed = this.#deleteOldValuesQuery.run(Math.max(Math.floor(this.#maxCount * 0.1), 1)).changes
if (removed > 0) {
const removed = this.#deleteOldValuesQuery?.run(Math.max(Math.floor(this.#maxCount * 0.1), 1)).changes
if (removed) {
return removed
}
}
@@ -379,7 +387,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)
@@ -407,10 +415,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
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "undici",
"version": "7.2.3",
"version": "7.3.0",
"description": "An HTTP/1.1 client, written from scratch for Node.js",
"homepage": "https://undici.nodejs.org",
"bugs": {
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')

@@ -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)
})
2 changes: 1 addition & 1 deletion types/cache-interceptor.d.ts
Original file line number Diff line number Diff line change
@@ -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?: Readable | Iterable<Buffer> | AsyncIterable<Buffer> | Buffer | Iterable<string> | AsyncIterable<string> | string
cacheControlDirectives: CacheControlDirectives,
cachedAt: number
staleAt: number