"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; // src/index.ts var src_exports = {}; __export(src_exports, { assertCacheEntry: () => assertCacheEntry, cachified: () => cachified, createBatch: () => createBatch, createCacheEntry: () => createCacheEntry, default: () => cachified, mergeReporters: () => mergeReporters, shouldRefresh: () => shouldRefresh, softPurge: () => softPurge, staleWhileRevalidate: () => staleWhileRevalidate, totalTtl: () => totalTtl, verboseReporter: () => verboseReporter }); module.exports = __toCommonJS(src_exports); // src/common.ts var HANDLE = Symbol(); var MIGRATED = Symbol(); function createContext({ fallbackToCache, checkValue: checkValue2, ...options }, reporter) { const ttl = options.ttl ?? Infinity; const staleWhileRevalidate2 = options.swr ?? options.staleWhileRevalidate ?? 0; const checkValueCompat = typeof checkValue2 === "function" ? checkValue2 : typeof checkValue2 === "object" ? (value, migrate) => checkValue2.parseAsync(value).then((v) => migrate(v, false)) : () => true; const contextWithoutReport = { checkValue: checkValueCompat, ttl, staleWhileRevalidate: staleWhileRevalidate2, fallbackToCache: fallbackToCache === false ? 0 : fallbackToCache === true || fallbackToCache === void 0 ? Infinity : fallbackToCache, staleRefreshTimeout: 0, forceFresh: false, ...options, metadata: createCacheMetaData({ ttl, swr: staleWhileRevalidate2 }), waitUntil: options.waitUntil ?? (() => { }) }; const report = reporter?.(contextWithoutReport) || (() => { }); return { ...contextWithoutReport, report }; } function staleWhileRevalidate(metadata) { return (typeof metadata.swr === "undefined" ? metadata.swv : metadata.swr) || null; } function totalTtl(metadata) { if (!metadata) { return 0; } if (metadata.ttl === null) { return Infinity; } return (metadata.ttl || 0) + (staleWhileRevalidate(metadata) || 0); } function createCacheMetaData({ ttl = null, swr = 0, createdTime = Date.now() } = {}) { return { ttl: ttl === Infinity ? null : ttl, swr: swr === Infinity ? null : swr, createdTime }; } function createCacheEntry(value, metadata) { return { value, metadata: createCacheMetaData(metadata) }; } // src/reporter.ts var defaultFormatDuration = (ms) => `${Math.round(ms)}ms`; function formatCacheTime(metadata, formatDuration) { const swr = staleWhileRevalidate(metadata); if (metadata.ttl == null || swr == null) { return `forever${metadata.ttl != null ? ` (revalidation after ${formatDuration(metadata.ttl)})` : ""}`; } return `${formatDuration(metadata.ttl)} + ${formatDuration(swr)} stale`; } function verboseReporter({ formatDuration = defaultFormatDuration, logger = console, performance = globalThis.performance || Date } = {}) { return ({ key, fallbackToCache, forceFresh, metadata, cache }) => { const cacheName = cache.name || cache.toString().toString().replace(/^\[object (.*?)]$/, "$1"); let cached; let freshValue; let getFreshValueStartTs; let refreshValueStartTS; return (event) => { switch (event.name) { case "getCachedValueRead": cached = event.entry; break; case "checkCachedValueError": logger.warn( `check failed for cached value of ${key} Reason: ${event.reason}. Deleting the cache key and trying to get a fresh value.`, cached ); break; case "getCachedValueError": logger.error( `error with cache at ${key}. Deleting the cache key and trying to get a fresh value.`, event.error ); break; case "getFreshValueError": logger.error( `getting a fresh value for ${key} failed`, { fallbackToCache, forceFresh }, event.error ); break; case "getFreshValueStart": getFreshValueStartTs = performance.now(); break; case "writeFreshValueSuccess": { const totalTime = performance.now() - getFreshValueStartTs; if (event.written) { logger.log( `Updated the cache value for ${key}.`, `Getting a fresh value for this took ${formatDuration( totalTime )}.`, `Caching for ${formatCacheTime( metadata, formatDuration )} in ${cacheName}.` ); } else { logger.log( `Not updating the cache value for ${key}.`, `Getting a fresh value for this took ${formatDuration( totalTime )}.`, `Thereby exceeding caching time of ${formatCacheTime( metadata, formatDuration )}` ); } break; } case "writeFreshValueError": logger.error(`error setting cache: ${key}`, event.error); break; case "getFreshValueSuccess": freshValue = event.value; break; case "checkFreshValueError": logger.error( `check failed for fresh value of ${key} Reason: ${event.reason}.`, freshValue ); break; case "refreshValueStart": refreshValueStartTS = performance.now(); break; case "refreshValueSuccess": logger.log( `Background refresh for ${key} successful.`, `Getting a fresh value for this took ${formatDuration( performance.now() - refreshValueStartTS )}.`, `Caching for ${formatCacheTime( metadata, formatDuration )} in ${cacheName}.` ); break; case "refreshValueError": logger.log(`Background refresh for ${key} failed.`, event.error); break; } }; }; } function mergeReporters(...reporters) { return (context) => { const reporter = reporters.map((r) => r?.(context)); return (event) => { reporter.forEach((r) => r?.(event)); }; }; } // src/createBatch.ts function createBatch(getFreshValues, autoSubmit = true) { const requests = []; let count = 0; let submitted = false; const submission = new Deferred(); const checkSubmission = () => { if (submitted) { throw new Error("Can not add to batch after submission"); } }; const submit = async () => { if (count !== 0) { autoSubmit = true; return submission.promise; } checkSubmission(); submitted = true; if (requests.length === 0) { submission.resolve(); return; } try { const results = await Promise.resolve( getFreshValues(requests.map(([param]) => param)) ); results.forEach((value, index) => requests[index][1](value)); submission.resolve(); } catch (err) { requests.forEach(([_, __, rej]) => rej(err)); submission.resolve(); } }; const trySubmitting = () => { count--; if (autoSubmit === false) { return; } submit(); }; return { ...autoSubmit === false ? { submit } : {}, add(param, onValue) { checkSubmission(); count++; let handled = false; return Object.assign( (context) => { return new Promise((res, rej) => { requests.push([ param, (value) => { onValue?.({ ...context, value }); res(value); }, rej ]); if (!handled) { handled = true; trySubmitting(); } }); }, { [HANDLE]: () => { if (!handled) { handled = true; trySubmitting(); } } } ); } }; } var Deferred = class { constructor() { __publicField(this, "promise"); // @ts-ignore __publicField(this, "resolve"); // @ts-ignore __publicField(this, "reject"); this.promise = new Promise((res, rej) => { this.resolve = res; this.reject = rej; }); } }; // src/assertCacheEntry.ts function logKey(key) { return key ? `for ${key} ` : ""; } function assertCacheEntry(entry, key) { if (!isRecord(entry)) { throw new Error( `Cache entry ${logKey( key )}is not a cache entry object, it's a ${typeof entry}` ); } if (!isRecord(entry.metadata) || typeof entry.metadata.createdTime !== "number" || entry.metadata.ttl != null && typeof entry.metadata.ttl !== "number" || entry.metadata.swr != null && typeof entry.metadata.swr !== "number") { throw new Error( `Cache entry ${logKey(key)}does not have valid metadata property` ); } if (!("value" in entry)) { throw new Error( `Cache entry for ${logKey(key)}does not have a value property` ); } } function isRecord(entry) { return typeof entry === "object" && entry !== null && !Array.isArray(entry); } // src/shouldRefresh.ts function shouldRefresh(metadata) { if (metadata.ttl !== null) { const valid = metadata.createdTime + (metadata.ttl || 0); const stale = valid + (staleWhileRevalidate(metadata) || 0); const now = Date.now(); if (now <= valid) { return false; } if (now <= stale) { return "stale"; } return "now"; } return false; } // src/checkValue.ts async function checkValue(context, value) { try { const checkResponse = await context.checkValue( value, (value2, updateCache = true) => ({ [MIGRATED]: updateCache, value: value2 }) ); if (typeof checkResponse === "string") { return { success: false, reason: checkResponse }; } if (checkResponse == null || checkResponse === true) { return { success: true, value, migrated: false }; } if (checkResponse && typeof checkResponse[MIGRATED] === "boolean") { return { success: true, migrated: checkResponse[MIGRATED], value: checkResponse.value }; } return { success: false, reason: "unknown" }; } catch (err) { return { success: false, reason: err }; } } // src/getCachedValue.ts var CACHE_EMPTY = Symbol(); async function getCacheEntry({ key, cache }, report) { report({ name: "getCachedValueStart" }); const cached = await cache.get(key); report({ name: "getCachedValueRead", entry: cached }); if (cached) { assertCacheEntry(cached, key); return cached; } return CACHE_EMPTY; } async function getCachedValue(context, report, hasPendingValue) { const { key, cache, staleWhileRevalidate: staleWhileRevalidate2, staleRefreshTimeout, metadata, getFreshValue: { [HANDLE]: handle } } = context; try { const cached = await getCacheEntry(context, report); if (cached === CACHE_EMPTY) { report({ name: "getCachedValueEmpty" }); return CACHE_EMPTY; } const refresh = shouldRefresh(cached.metadata); const staleRefresh = refresh === "stale" || refresh === "now" && staleWhileRevalidate2 === Infinity; if (refresh === "now") { report({ name: "getCachedValueOutdated", ...cached }); } if (staleRefresh) { context.waitUntil( Promise.resolve().then(async () => { await sleep(staleRefreshTimeout); report({ name: "refreshValueStart" }); await cachified({ ...context, getFreshValue({ metadata: metadata2 }) { return context.getFreshValue({ metadata: metadata2, background: true }); }, forceFresh: true, fallbackToCache: false }).then((value) => { report({ name: "refreshValueSuccess", value }); }).catch((error) => { report({ name: "refreshValueError", error }); }); }) ); } if (!refresh || staleRefresh) { const valueCheck = await checkValue(context, cached.value); if (valueCheck.success) { report({ name: "getCachedValueSuccess", value: valueCheck.value, migrated: valueCheck.migrated }); if (!staleRefresh) { handle?.(); } if (valueCheck.migrated) { context.waitUntil( Promise.resolve().then(async () => { try { await sleep(0); const cached2 = await context.cache.get(context.key); if (cached2 && cached2.metadata.createdTime === metadata.createdTime && !hasPendingValue()) { await context.cache.set(context.key, { ...cached2, value: valueCheck.value }); } } catch (err) { } }) ); } return valueCheck.value; } else { report({ name: "checkCachedValueErrorObj", reason: valueCheck.reason }); report({ name: "checkCachedValueError", reason: valueCheck.reason instanceof Error ? valueCheck.reason.message : String(valueCheck.reason) }); await cache.delete(key); } } } catch (error) { report({ name: "getCachedValueError", error }); await cache.delete(key); } return CACHE_EMPTY; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // src/getFreshValue.ts async function getFreshValue(context, metadata, report) { const { fallbackToCache, key, getFreshValue: getFreshValue2, forceFresh, cache } = context; let value; try { report({ name: "getFreshValueStart" }); const freshValue = await getFreshValue2({ metadata: context.metadata, background: false }); value = freshValue; report({ name: "getFreshValueSuccess", value: freshValue }); } catch (error) { report({ name: "getFreshValueError", error }); if (forceFresh && fallbackToCache > 0) { const entry = await getCacheEntry(context, report); if (entry === CACHE_EMPTY || entry.metadata.createdTime + fallbackToCache < Date.now()) { throw error; } value = entry.value; report({ name: "getFreshValueCacheFallback", value }); } else { throw error; } } const valueCheck = await checkValue(context, value); if (!valueCheck.success) { report({ name: "checkFreshValueErrorObj", reason: valueCheck.reason }); report({ name: "checkFreshValueError", reason: valueCheck.reason instanceof Error ? valueCheck.reason.message : String(valueCheck.reason) }); throw new Error(`check failed for fresh value of ${key}`, { cause: valueCheck.reason }); } try { const write = shouldRefresh(metadata) !== "now"; if (write) { await cache.set(key, createCacheEntry(value, metadata)); } report({ name: "writeFreshValueSuccess", metadata, migrated: valueCheck.migrated, written: write }); } catch (error) { report({ name: "writeFreshValueError", error }); } return valueCheck.value; } // src/cachified.ts var pendingValuesByCache = /* @__PURE__ */ new WeakMap(); async function cachified(options, reporter) { const context = createContext(options, reporter); const { key, cache, forceFresh, report, metadata } = context; if (!pendingValuesByCache.has(cache)) { pendingValuesByCache.set(cache, /* @__PURE__ */ new Map()); } const pendingValues = pendingValuesByCache.get(cache); const hasPendingValue = () => { return pendingValues.has(key); }; const cachedValue = !forceFresh ? await getCachedValue(context, report, hasPendingValue) : CACHE_EMPTY; if (cachedValue !== CACHE_EMPTY) { report({ name: "done", value: cachedValue }); return cachedValue; } if (pendingValues.has(key)) { const { value: pendingRefreshValue, metadata: metadata2 } = pendingValues.get(key); if (!shouldRefresh(metadata2)) { report({ name: "getFreshValueHookPending" }); const value2 = await pendingRefreshValue; report({ name: "done", value: value2 }); return value2; } } let resolveFromFuture; const freshValue = Promise.race([ // try to get a fresh value getFreshValue(context, metadata, report), // or when a future call is faster, we'll take it's value // this happens when getting value of first call takes longer then ttl + second response new Promise((r) => { resolveFromFuture = r; }) ]).finally(() => { pendingValues.delete(key); }); if (pendingValues.has(key)) { const { resolve } = pendingValues.get(key); freshValue.then((value2) => resolve(value2)); } pendingValues.set(key, { metadata, value: freshValue, // here we receive a fresh value from a future call resolve: resolveFromFuture }); const value = await freshValue; report({ name: "done", value }); return value; } // src/softPurge.ts async function softPurge({ cache, key, ...swrOverwrites }) { const swrOverwrite = swrOverwrites.swr ?? swrOverwrites.staleWhileRevalidate; const entry = await getCacheEntry({ cache, key }, () => { }); if (entry === CACHE_EMPTY || shouldRefresh(entry.metadata)) { return; } const ttl = entry.metadata.ttl || Infinity; const swr = staleWhileRevalidate(entry.metadata) || 0; const lt = Date.now() - entry.metadata.createdTime; await cache.set( key, createCacheEntry(entry.value, { ttl: 0, swr: swrOverwrite === void 0 ? ttl + swr : swrOverwrite + lt, createdTime: entry.metadata.createdTime }) ); } //# sourceMappingURL=index.cjs.map