UNPKG

@composite-fetcher/with-caching

Version:

withCaching is a simple fetcher core plugin to manage request caching with different drivers

153 lines (150 loc) 4.41 kB
// src/drivers/InMemoryCacheDriver.ts var InMemoryCacheDriver = class { cache; constructor() { this.cache = /* @__PURE__ */ new Map(); } async has(key) { const item = this.cache.get(key); if (!item) return false; if (item.expiration && item.expiration <= /* @__PURE__ */ new Date()) { this.delete(key); return false; } return true; } async get(key) { const item = this.cache.get(key); if (!item) return null; if (item.expiration && item.expiration <= /* @__PURE__ */ new Date()) { this.delete(key); return null; } return this.recreateResponse(item.value); } async set(key, response, expiration) { const serializedResponse = await this.serializeResponse(response); this.cache.set(key, { value: serializedResponse, expiration }); } async delete(key) { this.cache.delete(key); } async clear() { this.cache.clear(); } async serializeResponse(response) { const body = await response.arrayBuffer(); return { url: response.url, status: response.status, statusText: response.statusText, headers: [...response.headers.entries()], body }; } recreateResponse(data) { return new Response(data.body, { status: data.status, statusText: data.statusText, headers: data.headers }); } }; // src/drivers/SessionStorageDriver.ts var SessionStorageDriver = class { async set(key, response, expirationDate) { const item = { url: response.url, value: await response.clone().text(), headers: Array.from(response.headers.entries()), status: response.status, statusText: response.statusText, expirationDate: expirationDate?.toISOString() }; sessionStorage.setItem(key, JSON.stringify(item)); } async get(key) { const itemStr = sessionStorage.getItem(key); if (!itemStr) return null; const item = JSON.parse(itemStr); if (item.expirationDate && new Date(item.expirationDate) < /* @__PURE__ */ new Date()) { await this.delete(key); return null; } const init = { status: item.status, statusText: item.statusText, headers: item.headers }; return new Response(item.value, init); } async has(key) { const value = await this.get(key); return value !== null; } async delete(key) { sessionStorage.removeItem(key); } async clear() { sessionStorage.clear(); } }; // src/withCaching.ts import { BasePlugin } from "@composite-fetcher/core"; var withCachingPlugin = class extends BasePlugin { cacheDriver; defaultTTL = 10 * 60 * 1e3; // 10 minutes constructor(options = {}) { super(); const { cacheDriver, defaultTTL } = options; this.cacheDriver = cacheDriver || new InMemoryCacheDriver(); if (defaultTTL) { this.defaultTTL = defaultTTL; } } async generateCacheKey(request) { const url = new URL(request.url); const sortedParams = Array.from(url.searchParams.entries()).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)).map(([key, val]) => `${key}=${val}`).join("&"); const bodyString = request.body ? JSON.stringify(request.body) : ""; const cacheKey = `${request.method}-${url.origin}${url.pathname}?${sortedParams}-${bodyString}`; return cacheKey; } getCacheTTL(milliseconds) { return new Date(Date.now() + milliseconds); } async onPreRequest(context) { if (context.request.headers.has("x-fetcher-no-cache")) { return; } const cacheKey = await this.generateCacheKey(context.request); if (await this.cacheDriver.has(cacheKey)) { const cachedResponse = await this.cacheDriver.get(cacheKey); if (cachedResponse !== null) { return cachedResponse.clone(); } } } async onPostRequest(context) { const { response, originalRequest } = context; if (!originalRequest.headers.has("x-fetcher-no-cache") && response.ok) { const cacheTTL = originalRequest.headers.has("x-fetcher-cache-ttl") ? Number(originalRequest.headers.get("x-fetcher-cache-ttl")) : this.defaultTTL; const cacheKey = await this.generateCacheKey(context.originalRequest); await this.cacheDriver.set( cacheKey, response, this.getCacheTTL(cacheTTL) ); } } }; export { InMemoryCacheDriver, SessionStorageDriver, withCachingPlugin as withCaching };