cachified-redis-adapter
Version:
A redis adapter for usage with @epic-web/cachified
8 lines (7 loc) • 35.5 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/common.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/reporter.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/createBatch.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/assertCacheEntry.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/shouldRefresh.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/checkValue.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/getCachedValue.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/getFreshValue.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/cachified.ts", "../node_modules/.pnpm/@epic-web+cachified@5.1.2/node_modules/@epic-web/cachified/src/softPurge.ts", "../src/index.ts"],
"sourcesContent": ["import type { CreateReporter, Reporter } from './reporter';\n\nexport interface CacheMetadata {\n createdTime: number;\n ttl?: number | null;\n swr?: number | null;\n /** @deprecated use swr instead */\n readonly swv?: number | null;\n}\n\nexport interface CacheEntry<Value = unknown> {\n metadata: CacheMetadata;\n value: Value;\n}\n\nexport type Eventually<Value> =\n | Value\n | null\n | undefined\n | Promise<Value | null | undefined>;\n\nexport interface Cache<Value = any> {\n name?: string;\n get: (key: string) => Eventually<CacheEntry<Value>>;\n set: (key: string, value: CacheEntry<Value>) => unknown | Promise<unknown>;\n delete: (key: string) => unknown | Promise<unknown>;\n}\n\nexport interface GetFreshValueContext {\n readonly metadata: CacheMetadata;\n readonly background: boolean;\n}\nexport const HANDLE = Symbol();\nexport type GetFreshValue<Value> = {\n (context: GetFreshValueContext): Promise<Value> | Value;\n [HANDLE]?: () => void;\n};\nexport const MIGRATED = Symbol();\nexport type MigratedValue<Value> = {\n [MIGRATED]: boolean;\n value: Value;\n};\n\nexport type ValueCheckResultOk<Value> =\n | true\n | undefined\n | null\n | void\n | MigratedValue<Value>;\nexport type ValueCheckResultInvalid = false | string;\nexport type ValueCheckResult<Value> =\n | ValueCheckResultOk<Value>\n | ValueCheckResultInvalid;\nexport type CheckValue<Value> = (\n value: unknown,\n migrate: (value: Value, updateCache?: boolean) => MigratedValue<Value>,\n) => ValueCheckResult<Value> | Promise<ValueCheckResult<Value>>;\nexport interface Schema<Value, InputValue> {\n _input: InputValue;\n parseAsync(value: unknown): Promise<Value>;\n}\n\nexport interface CachifiedOptions<Value> {\n /**\n * Required\n *\n * The key this value is cached by\n * Must be unique for each value\n */\n key: string;\n /**\n * Required\n *\n * Cache implementation to use\n *\n * Must conform with signature\n * - set(key: string, value: object): void | Promise<void>\n * - get(key: string): object | Promise<object>\n * - delete(key: string): void | Promise<void>\n */\n cache: Cache;\n /**\n * Required\n *\n * Function that is called when no valid value is in cache for given key\n * Basically what we would do if we wouldn't use a cache\n *\n * Can be async and must return fresh value or throw\n *\n * receives context object as argument\n * - context.metadata.ttl?: number\n * - context.metadata.swr?: number\n * - context.metadata.createdTime: number\n * - context.background: boolean\n */\n getFreshValue: GetFreshValue<Value>;\n /**\n * Time To Live; often also referred to as max age\n *\n * Amount of milliseconds the value should stay in cache\n * before we get a fresh one\n *\n * Setting any negative value will disable caching\n * Can be infinite\n *\n * Default: `Infinity`\n */\n ttl?: number;\n /**\n * Amount of milliseconds that a value with exceeded ttl is still returned\n * while a fresh value is refreshed in the background\n *\n * Should be positive, can be infinite\n *\n * Default: `0`\n */\n staleWhileRevalidate?: number;\n /**\n * Alias for staleWhileRevalidate\n */\n swr?: number;\n /**\n * Validator that checks every cached and fresh value to ensure type safety\n *\n * Can be a zod schema or a custom validator function\n *\n * Value considered ok when:\n * - zod schema.parseAsync succeeds\n * - validator returns\n * - true\n * - migrate(newValue)\n * - undefined\n * - null\n *\n * Value considered bad when:\n * - zod schema.parseAsync throws\n * - validator:\n * - returns false\n * - returns reason as string\n * - throws\n *\n * A validator function receives two arguments:\n * 1. the value\n * 2. a migrate callback, see https://github.com/Xiphe/cachified#migrating-values\n *\n * Default: `undefined` - no validation\n */\n checkValue?: CheckValue<Value> | Schema<Value, unknown>;\n /**\n * Set true to not even try reading the currently cached value\n *\n * Will write new value to cache even when cached value is\n * still valid.\n *\n * Default: `false`\n */\n forceFresh?: boolean;\n /**\n * Whether or not to fall back to cache when getting a forced fresh value\n * fails\n *\n * Can also be a positive number as the maximum age in milliseconds that a\n * fallback value might have\n *\n * Default: `Infinity`\n */\n fallbackToCache?: boolean | number;\n /**\n * Amount of time in milliseconds before revalidation of a stale\n * cache entry is started\n *\n * Must be positive and finite\n *\n * Default: `0`\n */\n staleRefreshTimeout?: number;\n /**\n * @deprecated pass reporter as second argument to cachified\n */\n reporter?: never;\n}\n\n/* When using a schema validator, a strongly typed getFreshValue is not required\n and sometimes even sub-optimal */\nexport type CachifiedOptionsWithSchema<Value, InternalValue> = Omit<\n CachifiedOptions<Value>,\n 'checkValue' | 'getFreshValue'\n> & {\n checkValue: Schema<Value, InternalValue>;\n getFreshValue: GetFreshValue<InternalValue>;\n};\n\nexport interface Context<Value>\n extends Omit<\n Required<CachifiedOptions<Value>>,\n 'fallbackToCache' | 'reporter' | 'checkValue' | 'swr'\n > {\n checkValue: CheckValue<Value>;\n report: Reporter<Value>;\n fallbackToCache: number;\n metadata: CacheMetadata;\n}\n\nexport function createContext<Value>(\n { fallbackToCache, checkValue, ...options }: CachifiedOptions<Value>,\n reporter?: CreateReporter<Value>,\n): Context<Value> {\n const ttl = options.ttl ?? Infinity;\n const staleWhileRevalidate = options.swr ?? options.staleWhileRevalidate ?? 0;\n const checkValueCompat: CheckValue<Value> =\n typeof checkValue === 'function'\n ? checkValue\n : typeof checkValue === 'object'\n ? (value, migrate) =>\n checkValue.parseAsync(value).then((v) => migrate(v, false))\n : () => true;\n\n const contextWithoutReport = {\n checkValue: checkValueCompat,\n ttl,\n staleWhileRevalidate,\n fallbackToCache:\n fallbackToCache === false\n ? 0\n : fallbackToCache === true || fallbackToCache === undefined\n ? Infinity\n : fallbackToCache,\n staleRefreshTimeout: 0,\n forceFresh: false,\n ...options,\n metadata: createCacheMetaData({ ttl, swr: staleWhileRevalidate }),\n };\n\n const report =\n reporter?.(contextWithoutReport) ||\n (() => {\n /* \u00AF\\_(\u30C4)_/\u00AF */\n });\n\n return {\n ...contextWithoutReport,\n report,\n };\n}\n\nexport function staleWhileRevalidate(metadata: CacheMetadata): number | null {\n return (\n (typeof metadata.swr === 'undefined' ? metadata.swv : metadata.swr) || null\n );\n}\n\nexport function totalTtl(metadata?: CacheMetadata): number {\n if (!metadata) {\n return 0;\n }\n if (metadata.ttl === null) {\n return Infinity;\n }\n return (metadata.ttl || 0) + (staleWhileRevalidate(metadata) || 0);\n}\n\nexport function createCacheMetaData({\n ttl = null,\n swr = 0,\n createdTime = Date.now(),\n}: Partial<Omit<CacheMetadata, 'swv'>> = {}) {\n return {\n ttl: ttl === Infinity ? null : ttl,\n swr: swr === Infinity ? null : swr,\n createdTime,\n };\n}\n\nexport function createCacheEntry<Value>(\n value: Value,\n metadata?: Partial<Omit<CacheMetadata, 'swv'>>,\n): CacheEntry<Value> {\n return {\n value,\n metadata: createCacheMetaData(metadata),\n };\n}\n", "import { CacheMetadata, Context, staleWhileRevalidate } from './common';\n\nexport type GetFreshValueStartEvent = {\n name: 'getFreshValueStart';\n};\nexport type GetFreshValueHookPendingEvent = {\n name: 'getFreshValueHookPending';\n};\nexport type GetFreshValueSuccessEvent<Value> = {\n name: 'getFreshValueSuccess';\n value: Value;\n};\nexport type GetFreshValueErrorEvent = {\n name: 'getFreshValueError';\n error: unknown;\n};\nexport type GetFreshValueCacheFallbackEvent = {\n name: 'getFreshValueCacheFallback';\n value: unknown;\n};\n/** @deprecated this event will be removed in favour of `CheckFreshValueErrorObjEvent` */\nexport type CheckFreshValueErrorEvent<Value> = {\n name: 'checkFreshValueError';\n reason: string;\n};\nexport type CheckFreshValueErrorObjEvent = {\n name: 'checkFreshValueErrorObj';\n reason: unknown;\n};\nexport type WriteFreshValueSuccessEvent<Value> = {\n name: 'writeFreshValueSuccess';\n metadata: CacheMetadata;\n /**\n * Value might not actually be written to cache in case getting fresh\n * value took longer then ttl */\n written: boolean;\n migrated: boolean;\n};\nexport type WriteFreshValueErrorEvent = {\n name: 'writeFreshValueError';\n error: unknown;\n};\n\nexport type GetCachedValueStartEvent = {\n name: 'getCachedValueStart';\n};\nexport type GetCachedValueReadEvent = {\n name: 'getCachedValueRead';\n entry: unknown;\n};\nexport type GetCachedValueEmptyEvent = {\n name: 'getCachedValueEmpty';\n};\nexport type GetCachedValueOutdatedEvent = {\n name: 'getCachedValueOutdated';\n value: unknown;\n metadata: CacheMetadata;\n};\nexport type GetCachedValueSuccessEvent<Value> = {\n name: 'getCachedValueSuccess';\n value: Value;\n migrated: boolean;\n};\n/** @deprecated this event will be removed in favour of `CheckCachedValueErrorObjEvent` */\nexport type CheckCachedValueErrorEvent = {\n name: 'checkCachedValueError';\n reason: string;\n};\nexport type CheckCachedValueErrorObjEvent = {\n name: 'checkCachedValueErrorObj';\n reason: unknown;\n};\nexport type GetCachedValueErrorEvent = {\n name: 'getCachedValueError';\n error: unknown;\n};\n\nexport type RefreshValueStartEvent = {\n name: 'refreshValueStart';\n};\nexport type RefreshValueSuccessEvent<Value> = {\n name: 'refreshValueSuccess';\n value: Value;\n};\nexport type RefreshValueErrorEvent = {\n name: 'refreshValueError';\n error: unknown;\n};\nexport type DoneEvent<Value> = {\n name: 'done';\n value: Value;\n};\n\nexport type CacheEvent<Value> =\n | GetFreshValueStartEvent\n | GetFreshValueHookPendingEvent\n | GetFreshValueSuccessEvent<Value>\n | GetFreshValueErrorEvent\n | GetFreshValueCacheFallbackEvent\n | CheckFreshValueErrorEvent<Value>\n | CheckFreshValueErrorObjEvent\n | WriteFreshValueSuccessEvent<Value>\n | WriteFreshValueErrorEvent\n | GetCachedValueStartEvent\n | GetCachedValueReadEvent\n | GetCachedValueEmptyEvent\n | GetCachedValueOutdatedEvent\n | GetCachedValueSuccessEvent<Value>\n | CheckCachedValueErrorEvent\n | CheckCachedValueErrorObjEvent\n | GetCachedValueErrorEvent\n | RefreshValueStartEvent\n | RefreshValueSuccessEvent<Value>\n | RefreshValueErrorEvent\n | DoneEvent<Value>;\n\nexport type Reporter<Value> = (event: CacheEvent<Value>) => void;\n\nexport type CreateReporter<Value> = (\n context: Omit<Context<Value>, 'report'>,\n) => Reporter<Value>;\n\nconst defaultFormatDuration = (ms: number) => `${Math.round(ms)}ms`;\nfunction formatCacheTime(\n metadata: CacheMetadata,\n formatDuration: (duration: number) => string,\n) {\n const swr = staleWhileRevalidate(metadata);\n if (metadata.ttl == null || swr == null) {\n return `forever${\n metadata.ttl != null\n ? ` (revalidation after ${formatDuration(metadata.ttl)})`\n : ''\n }`;\n }\n\n return `${formatDuration(metadata.ttl)} + ${formatDuration(swr)} stale`;\n}\n\nexport type NoInfer<T> = [T][T extends any ? 0 : never];\ninterface ReporterOpts {\n formatDuration?: (ms: number) => string;\n logger?: Pick<typeof console, 'log' | 'warn' | 'error'>;\n performance?: Pick<typeof Date, 'now'>;\n}\nexport function verboseReporter<Value>({\n formatDuration = defaultFormatDuration,\n logger = console,\n performance = global.performance || Date,\n}: ReporterOpts = {}): CreateReporter<Value> {\n return ({ key, fallbackToCache, forceFresh, metadata, cache }) => {\n const cacheName =\n cache.name ||\n cache\n .toString()\n .toString()\n .replace(/^\\[object (.*?)]$/, '$1');\n let cached: unknown;\n let freshValue: unknown;\n let getFreshValueStartTs: number;\n let refreshValueStartTS: number;\n\n return (event) => {\n switch (event.name) {\n case 'getCachedValueRead':\n cached = event.entry;\n break;\n case 'checkCachedValueError':\n logger.warn(\n `check failed for cached value of ${key}\\nReason: ${event.reason}.\\nDeleting the cache key and trying to get a fresh value.`,\n cached,\n );\n break;\n case 'getCachedValueError':\n logger.error(\n `error with cache at ${key}. Deleting the cache key and trying to get a fresh value.`,\n event.error,\n );\n break;\n case 'getFreshValueError':\n logger.error(\n `getting a fresh value for ${key} failed`,\n { fallbackToCache, forceFresh },\n event.error,\n );\n break;\n case 'getFreshValueStart':\n getFreshValueStartTs = performance.now();\n break;\n case 'writeFreshValueSuccess': {\n const totalTime = performance.now() - getFreshValueStartTs;\n if (event.written) {\n logger.log(\n `Updated the cache value for ${key}.`,\n `Getting a fresh value for this took ${formatDuration(\n totalTime,\n )}.`,\n `Caching for ${formatCacheTime(\n metadata,\n formatDuration,\n )} in ${cacheName}.`,\n );\n } else {\n logger.log(\n `Not updating the cache value for ${key}.`,\n `Getting a fresh value for this took ${formatDuration(\n totalTime,\n )}.`,\n `Thereby exceeding caching time of ${formatCacheTime(\n metadata,\n formatDuration,\n )}`,\n );\n }\n break;\n }\n case 'writeFreshValueError':\n logger.error(`error setting cache: ${key}`, event.error);\n break;\n case 'getFreshValueSuccess':\n freshValue = event.value;\n break;\n case 'checkFreshValueError':\n logger.error(\n `check failed for fresh value of ${key}\\nReason: ${event.reason}.`,\n freshValue,\n );\n break;\n case 'refreshValueStart':\n refreshValueStartTS = performance.now();\n break;\n case 'refreshValueSuccess':\n logger.log(\n `Background refresh for ${key} successful.`,\n `Getting a fresh value for this took ${formatDuration(\n performance.now() - refreshValueStartTS,\n )}.`,\n `Caching for ${formatCacheTime(\n metadata,\n formatDuration,\n )} in ${cacheName}.`,\n );\n break;\n case 'refreshValueError':\n logger.log(`Background refresh for ${key} failed.`, event.error);\n break;\n }\n };\n };\n}\n\nexport function mergeReporters<Value = unknown>(\n ...reporters: (CreateReporter<Value> | null | undefined)[]\n): CreateReporter<Value> {\n return (context) => {\n const reporter = reporters.map((r) => r?.(context));\n return (event) => {\n reporter.forEach((r) => r?.(event));\n };\n };\n}\n", "import type { GetFreshValue, GetFreshValueContext } from './common';\nimport { HANDLE } from './common';\n\ntype OnValueCallback<Value> = (\n context: GetFreshValueContext & {\n value: Value;\n },\n) => void;\n\nexport type AddFn<Value, Param> = (\n param: Param,\n onValue?: OnValueCallback<Value>,\n) => GetFreshValue<Value>;\n\nexport function createBatch<Value, Param>(\n getFreshValues: (params: Param[]) => Value[] | Promise<Value[]>,\n autoSubmit: false,\n): {\n submit: () => Promise<void>;\n add: AddFn<Value, Param>;\n};\nexport function createBatch<Value, Param>(\n getFreshValues: (params: Param[]) => Value[] | Promise<Value[]>,\n): {\n add: AddFn<Value, Param>;\n};\nexport function createBatch<Value, Param>(\n getFreshValues: (params: Param[]) => Value[] | Promise<Value[]>,\n autoSubmit: boolean = true,\n): {\n submit?: () => Promise<void>;\n add: AddFn<Value, Param>;\n} {\n const requests: [\n param: Param,\n res: (value: Value) => void,\n rej: (reason: unknown) => void,\n ][] = [];\n\n let count = 0;\n let submitted = false;\n const submission = new Deferred<void>();\n\n const checkSubmission = () => {\n if (submitted) {\n throw new Error('Can not add to batch after submission');\n }\n };\n\n const submit = async () => {\n if (count !== 0) {\n autoSubmit = true;\n return submission.promise;\n }\n checkSubmission();\n submitted = true;\n\n if (requests.length === 0) {\n submission.resolve();\n return;\n }\n\n try {\n const results = await Promise.resolve(\n getFreshValues(requests.map(([param]) => param)),\n );\n results.forEach((value, index) => requests[index][1](value));\n submission.resolve();\n } catch (err) {\n requests.forEach(([_, __, rej]) => rej(err));\n submission.resolve();\n }\n };\n\n const trySubmitting = () => {\n count--;\n if (autoSubmit === false) {\n return;\n }\n submit();\n };\n\n return {\n ...(autoSubmit === false ? { submit } : {}),\n add(param, onValue) {\n checkSubmission();\n count++;\n let handled = false;\n\n return Object.assign(\n (context: GetFreshValueContext) => {\n return new Promise<Value>((res, rej) => {\n requests.push([\n param,\n (value) => {\n onValue?.({ ...context, value });\n res(value);\n },\n rej,\n ]);\n if (!handled) {\n handled = true;\n trySubmitting();\n }\n });\n },\n {\n [HANDLE]: () => {\n if (!handled) {\n handled = true;\n trySubmitting();\n }\n },\n },\n );\n },\n };\n}\n\nexport class Deferred<Value> {\n readonly promise: Promise<Value>;\n // @ts-ignore\n readonly resolve: (value: Value | Promise<Value>) => void;\n // @ts-ignore\n readonly reject: (reason: unknown) => void;\n constructor() {\n this.promise = new Promise((res, rej) => {\n // @ts-ignore\n this.resolve = res;\n // @ts-ignore\n this.reject = rej;\n });\n }\n}\n", "import type { CacheMetadata } from './common';\n\nexport function logKey(key?: string) {\n return key ? `for ${key} ` : '';\n}\n\nexport function assertCacheEntry(\n entry: unknown,\n key?: string,\n): asserts entry is {\n metadata: CacheMetadata;\n value: unknown;\n} {\n if (!isRecord(entry)) {\n throw new Error(\n `Cache entry ${logKey(\n key,\n )}is not a cache entry object, it's a ${typeof entry}`,\n );\n }\n if (\n !isRecord(entry.metadata) ||\n typeof entry.metadata.createdTime !== 'number' ||\n (entry.metadata.ttl != null && typeof entry.metadata.ttl !== 'number') ||\n (entry.metadata.swr != null && typeof entry.metadata.swr !== 'number')\n ) {\n throw new Error(\n `Cache entry ${logKey(key)}does not have valid metadata property`,\n );\n }\n\n if (!('value' in entry)) {\n throw new Error(\n `Cache entry for ${logKey(key)}does not have a value property`,\n );\n }\n}\n\nfunction isRecord(entry: unknown): entry is Record<string, unknown> {\n return typeof entry === 'object' && entry !== null && !Array.isArray(entry);\n}\n", "import { CacheMetadata, staleWhileRevalidate } from './common';\n\nexport function shouldRefresh(\n metadata: CacheMetadata,\n): 'now' | 'stale' | false {\n if (metadata.ttl !== null) {\n const valid = metadata.createdTime + (metadata.ttl || 0);\n const stale = valid + (staleWhileRevalidate(metadata) || 0);\n const now = Date.now();\n if (now <= valid) {\n return false;\n }\n if (now <= stale) {\n return 'stale';\n }\n\n return 'now';\n }\n return false;\n}\n", "import type { Context } from './common';\nimport { MIGRATED } from './common';\n\nexport async function checkValue<Value>(\n context: Context<Value>,\n value: unknown,\n): Promise<\n | { success: true; value: Value; migrated: boolean }\n | { success: false; reason: unknown }\n> {\n try {\n const checkResponse = await context.checkValue(\n value,\n (value, updateCache = true) => ({\n [MIGRATED]: updateCache,\n value,\n }),\n );\n\n if (typeof checkResponse === 'string') {\n return { success: false, reason: checkResponse };\n }\n\n if (checkResponse == null || checkResponse === true) {\n return {\n success: true,\n value: value as Value,\n migrated: false,\n };\n }\n\n if (checkResponse && typeof checkResponse[MIGRATED] === 'boolean') {\n return {\n success: true,\n migrated: checkResponse[MIGRATED],\n value: checkResponse.value,\n };\n }\n\n return { success: false, reason: 'unknown' };\n } catch (err) {\n return {\n success: false,\n reason: err,\n };\n }\n}\n", "import { Context, CacheEntry } from './common';\nimport { assertCacheEntry } from './assertCacheEntry';\nimport { HANDLE } from './common';\nimport { shouldRefresh } from './shouldRefresh';\nimport { cachified } from './cachified';\nimport { Reporter } from './reporter';\nimport { checkValue } from './checkValue';\n\nexport const CACHE_EMPTY = Symbol();\nexport async function getCacheEntry<Value>(\n { key, cache }: Pick<Context<Value>, 'key' | 'cache'>,\n report: Reporter<Value>,\n): Promise<CacheEntry<unknown> | typeof CACHE_EMPTY> {\n report({ name: 'getCachedValueStart' });\n const cached = await cache.get(key);\n report({ name: 'getCachedValueRead', entry: cached });\n if (cached) {\n assertCacheEntry(cached, key);\n return cached;\n }\n return CACHE_EMPTY;\n}\n\nexport async function getCachedValue<Value>(\n context: Context<Value>,\n report: Reporter<Value>,\n hasPendingValue: () => boolean,\n): Promise<Value | typeof CACHE_EMPTY> {\n const {\n key,\n cache,\n staleWhileRevalidate,\n staleRefreshTimeout,\n metadata,\n getFreshValue: { [HANDLE]: handle },\n } = context;\n try {\n const cached = await getCacheEntry(context, report);\n\n if (cached === CACHE_EMPTY) {\n report({ name: 'getCachedValueEmpty' });\n return CACHE_EMPTY;\n }\n\n const refresh = shouldRefresh(cached.metadata);\n const staleRefresh =\n refresh === 'stale' ||\n (refresh === 'now' && staleWhileRevalidate === Infinity);\n\n if (refresh === 'now') {\n report({ name: 'getCachedValueOutdated', ...cached });\n }\n\n if (staleRefresh) {\n // refresh cache in background so future requests are faster\n setTimeout(() => {\n report({ name: 'refreshValueStart' });\n void cachified({\n ...context,\n getFreshValue({ metadata }) {\n return context.getFreshValue({ metadata, background: true });\n },\n forceFresh: true,\n fallbackToCache: false,\n })\n .then((value) => {\n report({ name: 'refreshValueSuccess', value });\n })\n .catch((error) => {\n report({ name: 'refreshValueError', error });\n });\n }, staleRefreshTimeout);\n }\n\n if (!refresh || staleRefresh) {\n const valueCheck = await checkValue(context, cached.value);\n if (valueCheck.success) {\n report({\n name: 'getCachedValueSuccess',\n value: valueCheck.value,\n migrated: valueCheck.migrated,\n });\n if (!staleRefresh) {\n // Notify batch that we handled this call using cached value\n handle?.();\n }\n\n if (valueCheck.migrated) {\n setTimeout(async () => {\n try {\n const cached = await context.cache.get(context.key);\n\n // Unless cached value was changed in the meantime or is about to\n // change\n if (\n cached &&\n cached.metadata.createdTime === metadata.createdTime &&\n !hasPendingValue()\n ) {\n // update with migrated value\n await context.cache.set(context.key, {\n ...cached,\n value: valueCheck.value,\n });\n }\n } catch (err) {\n /* \u00AF\\_(\u30C4)_/\u00AF */\n }\n }, 0);\n }\n\n return valueCheck.value;\n } else {\n report({ name: 'checkCachedValueErrorObj', reason: valueCheck.reason });\n report({\n name: 'checkCachedValueError',\n reason:\n valueCheck.reason instanceof Error\n ? valueCheck.reason.message\n : String(valueCheck.reason),\n });\n\n await cache.delete(key);\n }\n }\n } catch (error: unknown) {\n report({ name: 'getCachedValueError', error });\n\n await cache.delete(key);\n }\n\n return CACHE_EMPTY;\n}\n", "import { Context, CacheMetadata, createCacheEntry } from './common';\nimport { getCacheEntry, CACHE_EMPTY } from './getCachedValue';\nimport { shouldRefresh } from './shouldRefresh';\nimport { Reporter } from './reporter';\nimport { checkValue } from './checkValue';\n\nexport async function getFreshValue<Value>(\n context: Context<Value>,\n metadata: CacheMetadata,\n report: Reporter<Value>,\n): Promise<Value> {\n const { fallbackToCache, key, getFreshValue, forceFresh, cache } = context;\n\n let value: unknown;\n try {\n report({ name: 'getFreshValueStart' });\n const freshValue = await getFreshValue({\n metadata: context.metadata,\n background: false,\n });\n value = freshValue;\n report({ name: 'getFreshValueSuccess', value: freshValue });\n } catch (error) {\n report({ name: 'getFreshValueError', error });\n\n // in case a fresh value was forced (and errored) we might be able to\n // still get one from cache\n if (forceFresh && fallbackToCache > 0) {\n const entry = await getCacheEntry(context, report);\n if (\n entry === CACHE_EMPTY ||\n entry.metadata.createdTime + fallbackToCache < Date.now()\n ) {\n throw error;\n }\n value = entry.value;\n report({ name: 'getFreshValueCacheFallback', value });\n } else {\n // we are either not allowed to check the cache or already checked it\n // nothing we can do anymore\n throw error;\n }\n }\n\n const valueCheck = await checkValue(context, value);\n if (!valueCheck.success) {\n report({ name: 'checkFreshValueErrorObj', reason: valueCheck.reason });\n report({\n name: 'checkFreshValueError',\n reason:\n valueCheck.reason instanceof Error\n ? valueCheck.reason.message\n : String(valueCheck.reason),\n });\n\n throw new Error(`check failed for fresh value of ${key}`, {\n cause: valueCheck.reason,\n });\n }\n\n try {\n const write = shouldRefresh(metadata) !== 'now';\n if (write) {\n await cache.set(key, createCacheEntry(value, metadata));\n }\n report({\n name: 'writeFreshValueSuccess',\n metadata,\n migrated: valueCheck.migrated,\n written: write,\n });\n } catch (error: unknown) {\n report({ name: 'writeFreshValueError', error });\n }\n\n return valueCheck.value;\n}\n", "import {\n CachifiedOptions,\n CachifiedOptionsWithSchema,\n Cache,\n CacheEntry,\n createContext,\n} from './common';\nimport { CACHE_EMPTY, getCachedValue } from './getCachedValue';\nimport { getFreshValue } from './getFreshValue';\nimport { CreateReporter } from './reporter';\nimport { shouldRefresh } from './shouldRefresh';\n\n// This is to prevent requesting multiple fresh values in parallel\n// while revalidating or getting first value\n// Keys are unique per cache but may be used by multiple caches\nconst pendingValuesByCache = new WeakMap<Cache, Map<string, any>>();\n\nexport async function cachified<Value, InternalValue>(\n options: CachifiedOptionsWithSchema<Value, InternalValue>,\n reporter?: CreateReporter<Value>,\n): Promise<Value>;\nexport async function cachified<Value>(\n options: CachifiedOptions<Value>,\n reporter?: CreateReporter<Value>,\n): Promise<Value>;\nexport async function cachified<Value>(\n options: CachifiedOptions<Value>,\n reporter?: CreateReporter<Value>,\n): Promise<Value> {\n const context = createContext(options, reporter);\n const { key, cache, forceFresh, report, metadata } = context;\n\n // Register this cache\n if (!pendingValuesByCache.has(cache)) {\n pendingValuesByCache.set(cache, new Map());\n }\n const pendingValues: Map<\n string,\n CacheEntry<Promise<Value>> & { resolve: (value: Value) => void }\n > = pendingValuesByCache.get(cache)!;\n\n const hasPendingValue = () => {\n return pendingValues.has(key);\n };\n const cachedValue = !forceFresh\n ? await getCachedValue(context, report, hasPendingValue)\n : CACHE_EMPTY;\n if (cachedValue !== CACHE_EMPTY) {\n report({ name: 'done', value: cachedValue });\n return cachedValue;\n }\n\n if (pendingValues.has(key)) {\n const { value: pendingRefreshValue, metadata } = pendingValues.get(key)!;\n if (!shouldRefresh(metadata)) {\n report({ name: 'getFreshValueHookPending' });\n const value = await pendingRefreshValue;\n report({ name: 'done', value });\n return value;\n }\n }\n\n let resolveFromFuture: (value: Value) => void;\n const freshValue = Promise.race([\n // try to get a fresh value\n getFreshValue(context, metadata, report),\n // or when a future call is faster, we'll take it's value\n // this happens when getting value of first call takes longer then ttl + second response\n new Promise<Value>((r) => {\n resolveFromFuture = r;\n }),\n ]).finally(() => {\n pendingValues.delete(key);\n });\n\n // here we inform past calls that we got a response\n if (pendingValues.has(key)) {\n const { resolve } = pendingValues.get(key)!;\n freshValue.then((value) => resolve(value));\n }\n\n pendingValues.set(key, {\n metadata,\n value: freshValue,\n // here we receive a fresh value from a future call\n resolve: resolveFromFuture!,\n });\n\n const value = await freshValue;\n report({ name: 'done', value });\n return value;\n}\n", "import { Cache, createCacheEntry, staleWhileRevalidate } from './common';\nimport { CACHE_EMPTY, getCacheEntry } from './getCachedValue';\nimport { shouldRefresh } from './shouldRefresh';\n\ninterface SoftPurgeOpts {\n cache: Cache;\n key: string;\n /**\n * Force the entry to outdate after ms\n */\n staleWhileRevalidate?: number;\n /**\n * Force the entry to outdate after ms\n */\n swr?: number;\n}\n\nexport async function softPurge({\n cache,\n key,\n ...swrOverwrites\n}: SoftPurgeOpts) {\n const swrOverwrite = swrOverwrites.swr ?? swrOverwrites.staleWhileRevalidate;\n const entry = await getCacheEntry({ cache, key }, () => {});\n\n if (entry === CACHE_EMPTY || shouldRefresh(entry.metadata)) {\n return;\n }\n\n const ttl = entry.metadata.ttl || Infinity;\n const swr = staleWhileRevalidate(entry.metadata) || 0;\n const lt = Date.now() - entry.metadata.createdTime;\n\n await cache.set(\n key,\n createCacheEntry(entry.value, {\n ttl: 0,\n swr: swrOverwrite === undefined ? ttl + swr : swrOverwrite + lt,\n createdTime: entry.metadata.createdTime,\n }),\n );\n}\n", "import type {Cache} from '@epic-web/cachified'\nimport {totalTtl} from '@epic-web/cachified'\n\n// This is actually imported from @epic-web/cachified\n// but it will most likely be moved out of the package\n// and into some other adapter/package\nexport interface RedisLikeCache {\n name?: string\n set(\n key: string,\n value: string,\n options?: {\n EXAT: number\n },\n ): Promise<string | null>\n get(key: string): Promise<string | null>\n del(key: string): Promise<unknown>\n}\n\nexport function redisCacheAdapter(redisCache: RedisLikeCache): Cache {\n return {\n name: redisCache.name || 'Redis',\n set(key, value) {\n const ttl = totalTtl(value?.metadata)\n const createdTime = value?.metadata?.createdTime\n\n return redisCache.set(\n key,\n JSON.stringify(value),\n ttl > 0 && ttl < Infinity && typeof createdTime === 'number'\n ? {\n // convert the exat to seconds by dividing by 1000\n EXAT: Math.ceil((ttl + createdTime) / 1000),\n }\n : undefined,\n )\n },\n async get(key) {\n const value = await redisCache.get(key)\n if (value == null) {\n return null\n }\n // Note that parse can potentially throw an error here and the expectation is that the user of the adapter catches it\n return JSON.parse(value)\n },\n delete(key) {\n return redisCache.del(key)\n },\n }\n}\n"],
"mappings": "AAgCO,IAAMA,EAAS,OAAO,EAKhBC,EAAW,OAAO,EAgNxB,SAASC,EAAqBC,EAAwC,CAC3E,OACG,OAAOA,EAAS,IAAQ,IAAcA,EAAS,IAAMA,EAAS,MAAQ,IAE3E,CAEO,SAASC,EAASD,EAAkC,CACzD,OAAKA,EAGDA,EAAS,MAAQ,KACZ,KAEDA,EAAS,KAAO,IAAMD,EAAqBC,CAAQ,GAAK,GALvD,CAMX,CM3PO,IAAME,EAAc,OAAO,EIW3B,SAASC,EAAkBC,EAAmC,CACnE,MAAO,CACL,KAAMA,EAAW,MAAQ,QACzB,IAAIC,EAAKC,EAAO,CACd,IAAMC,EAAMC,EAASF,GAAO,QAAQ,EAC9BG,EAAcH,GAAO,UAAU,YAErC,OAAOF,EAAW,IAChBC,EACA,KAAK,UAAUC,CAAK,EACpBC,EAAM,GAAKA,EAAM,KAAY,OAAOE,GAAgB,SAChD,CAEE,KAAM,KAAK,MAAMF,EAAME,GAAe,GAAI,CAC5C,EACA,MACN,CACF,EACA,MAAM,IAAIJ,EAAK,CACb,IAAMC,EAAQ,MAAMF,EAAW,IAAIC,CAAG,EACtC,OAAIC,GAAS,KACJ,KAGF,KAAK,MAAMA,CAAK,CACzB,EACA,OAAOD,EAAK,CACV,OAAOD,EAAW,IAAIC,CAAG,CAC3B,CACF,CACF",
"names": ["HANDLE", "MIGRATED", "staleWhileRevalidate", "metadata", "totalTtl", "CACHE_EMPTY", "redisCacheAdapter", "redisCache", "key", "value", "ttl", "totalTtl", "createdTime"]
}