@@ -15,11 +15,18 @@ const MAX_ENTRY_SIZE = 2 * 1000 * 1000 * 1000 |
15 | 15 | * @implements {CacheStore} |
16 | 16 | * |
17 | 17 | * @typedef {{ |
18 | | - * id: Readonly<number> |
19 | | - * headers?: Record<string, string | string[]> |
20 | | - * vary?: string | object |
21 | | - * body: string |
22 | | - * } & import('../../types/cache-interceptor.d.ts').default.CacheValue} SqliteStoreValue |
| 18 | + * id: Readonly<number>, |
| 19 | + * body?: Uint8Array |
| 20 | + * statusCode: number |
| 21 | + * statusMessage: string |
| 22 | + * headers?: string |
| 23 | + * vary?: string |
| 24 | + * etag?: string |
| 25 | + * cacheControlDirectives?: string |
| 26 | + * cachedAt: number |
| 27 | + * staleAt: number |
| 28 | + * deleteAt: number |
| 29 | + * }} SqliteStoreValue |
23 | 30 | */ |
24 | 31 | module.exports = class SqliteCacheStore { |
25 | 32 | #maxEntrySize = MAX_ENTRY_SIZE |
@@ -61,7 +68,7 @@ module.exports = class SqliteCacheStore { |
61 | 68 | #countEntriesQuery |
62 | 69 |
|
63 | 70 | /** |
64 | | - * @type {import('node:sqlite').StatementSync} |
| 71 | + * @type {import('node:sqlite').StatementSync | null} |
65 | 72 | */ |
66 | 73 | #deleteOldValuesQuery |
67 | 74 |
|
@@ -163,8 +170,7 @@ module.exports = class SqliteCacheStore { |
163 | 170 | etag = ?, |
164 | 171 | cacheControlDirectives = ?, |
165 | 172 | cachedAt = ?, |
166 | | - staleAt = ?, |
167 | | - deleteAt = ? |
| 173 | + staleAt = ? |
168 | 174 | WHERE |
169 | 175 | id = ? |
170 | 176 | `) |
@@ -182,9 +188,8 @@ module.exports = class SqliteCacheStore { |
182 | 188 | cacheControlDirectives, |
183 | 189 | vary, |
184 | 190 | cachedAt, |
185 | | - staleAt, |
186 | | - deleteAt |
187 | | - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| 191 | + staleAt |
| 192 | + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
188 | 193 | `) |
189 | 194 |
|
190 | 195 | this.#deleteByUrlQuery = this.#db.prepare( |
@@ -219,36 +224,78 @@ module.exports = class SqliteCacheStore { |
219 | 224 |
|
220 | 225 | /** |
221 | 226 | * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key |
222 | | - * @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined} |
| 227 | + * @returns {(import('../../types/cache-interceptor.d.ts').default.GetResult & { body?: Buffer }) | undefined} |
223 | 228 | */ |
224 | 229 | get (key) { |
225 | 230 | assertCacheKey(key) |
226 | 231 |
|
227 | 232 | const value = this.#findValue(key) |
| 233 | + return value |
| 234 | + ? { |
| 235 | + body: value.body ? Buffer.from(value.body.buffer) : undefined, |
| 236 | + statusCode: value.statusCode, |
| 237 | + statusMessage: value.statusMessage, |
| 238 | + headers: value.headers ? JSON.parse(value.headers) : undefined, |
| 239 | + etag: value.etag ? value.etag : undefined, |
| 240 | + vary: value.vary ? JSON.parse(value.vary) : undefined, |
| 241 | + cacheControlDirectives: value.cacheControlDirectives |
| 242 | + ? JSON.parse(value.cacheControlDirectives) |
| 243 | + : undefined, |
| 244 | + cachedAt: value.cachedAt, |
| 245 | + staleAt: value.staleAt, |
| 246 | + deleteAt: value.deleteAt |
| 247 | + } |
| 248 | + : undefined |
| 249 | + } |
228 | 250 |
|
229 | | - if (!value) { |
230 | | - return undefined |
231 | | - } |
| 251 | + /** |
| 252 | + * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key |
| 253 | + * @param {import('../../types/cache-interceptor.d.ts').default.CacheValue & { body: null | Buffer | Array<Buffer>}} value |
| 254 | + */ |
| 255 | + set (key, value) { |
| 256 | + assertCacheKey(key) |
232 | 257 |
|
233 | | - /** |
234 | | - * @type {import('../../types/cache-interceptor.d.ts').default.GetResult} |
235 | | - */ |
236 | | - const result = { |
237 | | - body: Buffer.from(value.body), |
238 | | - statusCode: value.statusCode, |
239 | | - statusMessage: value.statusMessage, |
240 | | - headers: value.headers ? JSON.parse(value.headers) : undefined, |
241 | | - etag: value.etag ? value.etag : undefined, |
242 | | - vary: value.vary ?? undefined, |
243 | | - cacheControlDirectives: value.cacheControlDirectives |
244 | | - ? JSON.parse(value.cacheControlDirectives) |
245 | | - : undefined, |
246 | | - cachedAt: value.cachedAt, |
247 | | - staleAt: value.staleAt, |
248 | | - deleteAt: value.deleteAt |
| 258 | + const url = this.#makeValueUrl(key) |
| 259 | + const body = Array.isArray(value.body) ? Buffer.concat(value.body) : value.body |
| 260 | + const size = body?.byteLength |
| 261 | + |
| 262 | + if (size && size > this.#maxEntrySize) { |
| 263 | + return |
249 | 264 | } |
250 | 265 |
|
251 | | - return result |
| 266 | + const existingValue = this.#findValue(key, true) |
| 267 | + if (existingValue) { |
| 268 | + // Updating an existing response, let's overwrite it |
| 269 | + this.#updateValueQuery.run( |
| 270 | + body, |
| 271 | + value.deleteAt, |
| 272 | + value.statusCode, |
| 273 | + value.statusMessage, |
| 274 | + value.headers ? JSON.stringify(value.headers) : null, |
| 275 | + value.etag ? value.etag : null, |
| 276 | + value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null, |
| 277 | + value.cachedAt, |
| 278 | + value.staleAt, |
| 279 | + existingValue.id |
| 280 | + ) |
| 281 | + } else { |
| 282 | + this.#prune() |
| 283 | + // New response, let's insert it |
| 284 | + this.#insertValueQuery.run( |
| 285 | + url, |
| 286 | + key.method, |
| 287 | + body, |
| 288 | + value.deleteAt, |
| 289 | + value.statusCode, |
| 290 | + value.statusMessage, |
| 291 | + value.headers ? JSON.stringify(value.headers) : null, |
| 292 | + value.etag ? value.etag : null, |
| 293 | + value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null, |
| 294 | + value.vary ? JSON.stringify(value.vary) : null, |
| 295 | + value.cachedAt, |
| 296 | + value.staleAt |
| 297 | + ) |
| 298 | + } |
252 | 299 | } |
253 | 300 |
|
254 | 301 | /** |
@@ -260,7 +307,6 @@ module.exports = class SqliteCacheStore { |
260 | 307 | assertCacheKey(key) |
261 | 308 | assertCacheValue(value) |
262 | 309 |
|
263 | | - const url = this.#makeValueUrl(key) |
264 | 310 | let size = 0 |
265 | 311 | /** |
266 | 312 | * @type {Buffer[] | null} |
@@ -269,11 +315,8 @@ module.exports = class SqliteCacheStore { |
269 | 315 | const store = this |
270 | 316 |
|
271 | 317 | return new Writable({ |
| 318 | + decodeStrings: true, |
272 | 319 | write (chunk, encoding, callback) { |
273 | | - if (typeof chunk === 'string') { |
274 | | - chunk = Buffer.from(chunk, encoding) |
275 | | - } |
276 | | - |
277 | 320 | size += chunk.byteLength |
278 | 321 |
|
279 | 322 | if (size < store.#maxEntrySize) { |
@@ -285,42 +328,7 @@ module.exports = class SqliteCacheStore { |
285 | 328 | callback() |
286 | 329 | }, |
287 | 330 | final (callback) { |
288 | | - const existingValue = store.#findValue(key, true) |
289 | | - if (existingValue) { |
290 | | - // Updating an existing response, let's overwrite it |
291 | | - store.#updateValueQuery.run( |
292 | | - Buffer.concat(body), |
293 | | - value.deleteAt, |
294 | | - value.statusCode, |
295 | | - value.statusMessage, |
296 | | - value.headers ? JSON.stringify(value.headers) : null, |
297 | | - value.etag ? value.etag : null, |
298 | | - value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null, |
299 | | - value.cachedAt, |
300 | | - value.staleAt, |
301 | | - value.deleteAt, |
302 | | - existingValue.id |
303 | | - ) |
304 | | - } else { |
305 | | - store.#prune() |
306 | | - // New response, let's insert it |
307 | | - store.#insertValueQuery.run( |
308 | | - url, |
309 | | - key.method, |
310 | | - Buffer.concat(body), |
311 | | - value.deleteAt, |
312 | | - value.statusCode, |
313 | | - value.statusMessage, |
314 | | - value.headers ? JSON.stringify(value.headers) : null, |
315 | | - value.etag ? value.etag : null, |
316 | | - value.cacheControlDirectives ? JSON.stringify(value.cacheControlDirectives) : null, |
317 | | - value.vary ? JSON.stringify(value.vary) : null, |
318 | | - value.cachedAt, |
319 | | - value.staleAt, |
320 | | - value.deleteAt |
321 | | - ) |
322 | | - } |
323 | | - |
| 331 | + store.set(key, { ...value, body }) |
324 | 332 | callback() |
325 | 333 | } |
326 | 334 | }) |
@@ -344,14 +352,14 @@ module.exports = class SqliteCacheStore { |
344 | 352 |
|
345 | 353 | { |
346 | 354 | const removed = this.#deleteExpiredValuesQuery.run(Date.now()).changes |
347 | | - if (removed > 0) { |
| 355 | + if (removed) { |
348 | 356 | return removed |
349 | 357 | } |
350 | 358 | } |
351 | 359 |
|
352 | 360 | { |
353 | | - const removed = this.#deleteOldValuesQuery.run(Math.max(Math.floor(this.#maxCount * 0.1), 1)).changes |
354 | | - if (removed > 0) { |
| 361 | + const removed = this.#deleteOldValuesQuery?.run(Math.max(Math.floor(this.#maxCount * 0.1), 1)).changes |
| 362 | + if (removed) { |
355 | 363 | return removed |
356 | 364 | } |
357 | 365 | } |
@@ -379,7 +387,7 @@ module.exports = class SqliteCacheStore { |
379 | 387 | /** |
380 | 388 | * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} key |
381 | 389 | * @param {boolean} [canBeExpired=false] |
382 | | - * @returns {(SqliteStoreValue & { vary?: Record<string, string[]> }) | undefined} |
| 390 | + * @returns {SqliteStoreValue | undefined} |
383 | 391 | */ |
384 | 392 | #findValue (key, canBeExpired = false) { |
385 | 393 | const url = this.#makeValueUrl(key) |
@@ -407,10 +415,10 @@ module.exports = class SqliteCacheStore { |
407 | 415 | return undefined |
408 | 416 | } |
409 | 417 |
|
410 | | - value.vary = JSON.parse(value.vary) |
| 418 | + const vary = JSON.parse(value.vary) |
411 | 419 |
|
412 | | - for (const header in value.vary) { |
413 | | - if (!headerValueEquals(headers[header], value.vary[header])) { |
| 420 | + for (const header in vary) { |
| 421 | + if (!headerValueEquals(headers[header], vary[header])) { |
414 | 422 | matches = false |
415 | 423 | break |
416 | 424 | } |
|