@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
JavaScript
// 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
};