UNPKG

@fortedigital/nextjs-cache-handler

Version:
115 lines (114 loc) 4 kB
// src/functions/nesh-classic-cache.ts import assert from "assert/strict"; import { createHash } from "crypto"; import { workAsyncStorage } from "next/dist/server/app-render/work-async-storage.external.js"; import { CACHE_ONE_YEAR } from "next/dist/lib/constants"; function hashCacheKey(url) { const MAIN_KEY_PREFIX = "nesh-pages-cache-v1"; const cacheString = JSON.stringify([MAIN_KEY_PREFIX, url]); return createHash("sha256").update(cacheString).digest("hex"); } function serializeArguments(object) { return JSON.stringify(object); } function serializeResult(object) { return Buffer.from(JSON.stringify(object), "utf-8").toString("base64"); } function deserializeResult(string) { return JSON.parse(Buffer.from(string, "base64").toString("utf-8")); } function neshClassicCache(callback, commonOptions) { if (commonOptions?.resultSerializer && !commonOptions?.resultDeserializer) { throw new Error( "neshClassicCache: if you provide a resultSerializer, you must provide a resultDeserializer." ); } if (commonOptions?.resultDeserializer && !commonOptions?.resultSerializer) { throw new Error( "neshClassicCache: if you provide a resultDeserializer, you must provide a resultSerializer." ); } const commonRevalidate = commonOptions?.revalidate ?? false; const commonArgumentsSerializer = commonOptions?.argumentsSerializer ?? serializeArguments; const commonResultSerializer = commonOptions?.resultSerializer ?? serializeResult; const commonResultDeserializer = commonOptions?.resultDeserializer ?? deserializeResult; async function cachedCallback(options, ...args) { const store = workAsyncStorage.getStore(); assert( !store?.incrementalCache, "neshClassicCache must be used in a Next.js Pages directory." ); const cacheHandler = globalThis?.__incrementalCache?.cacheHandler; assert( cacheHandler, "neshClassicCache must be used in a Next.js Pages directory." ); const { responseContext, tags = [], revalidate = commonRevalidate, cacheKey, argumentsSerializer = commonArgumentsSerializer, resultDeserializer = commonResultDeserializer, resultSerializer = commonResultSerializer } = options ?? {}; assert( revalidate === false || revalidate > 0 && Number.isInteger(revalidate), "neshClassicCache: revalidate must be a positive integer or false." ); responseContext?.setHeader( "Cache-Control", `public, s-maxage=${revalidate}, stale-while-revalidate` ); const uniqueTags = /* @__PURE__ */ new Set(); for (const tag of tags) { if (typeof tag === "string") { uniqueTags.add(tag); } else { console.warn( `neshClassicCache: Invalid tag: ${tag}. Skipping it. Expected a string.` ); } } const allTags = Array.from(uniqueTags); const key = hashCacheKey( `nesh-classic-cache-${cacheKey ?? argumentsSerializer(args)}` ); const cacheData = await cacheHandler.get(key, { revalidate, tags: allTags, kind: "FETCH", fetchUrl: "neshClassicCache" }); if (cacheData?.value?.kind === "FETCH" && cacheData.lifespan && cacheData.lifespan.staleAt > Date.now() / 1e3) { return resultDeserializer(cacheData.value.data.body); } const data = await callback(...args); cacheHandler.set( key, { kind: "FETCH", data: { body: resultSerializer(data), headers: {}, url: "neshClassicCache" }, revalidate: revalidate || CACHE_ONE_YEAR }, { revalidate, tags, fetchCache: true, fetchUrl: "neshClassicCache" } ); if (cacheData?.value?.kind === "FETCH" && cacheData?.lifespan && cacheData.lifespan.expireAt > Date.now() / 1e3) { return resultDeserializer(cacheData.value.data.body); } return data; } return cachedCallback; } export { neshClassicCache };