@epic-web/cachified
Version:
neat wrapper for various caches
198 lines (197 loc) • 7 kB
TypeScript
import type { CreateReporter, Reporter } from './reporter';
import { StandardSchemaV1 } from './StandardSchemaV1';
export interface CacheMetadata {
createdTime: number;
ttl?: number | null;
swr?: number | null;
/** @deprecated use swr instead */
readonly swv?: number | null;
}
export interface CacheEntry<Value = unknown> {
metadata: CacheMetadata;
value: Value;
}
export type Eventually<Value> = Value | null | undefined | Promise<Value | null | undefined>;
export interface Cache<Value = any> {
name?: string;
get: (key: string) => Eventually<CacheEntry<Value>>;
set: (key: string, value: CacheEntry<Value>) => unknown | Promise<unknown>;
delete: (key: string) => unknown | Promise<unknown>;
}
export interface GetFreshValueContext {
readonly metadata: CacheMetadata;
readonly background: boolean;
}
export declare const HANDLE: unique symbol;
export type GetFreshValue<Value> = {
(context: GetFreshValueContext): Promise<Value> | Value;
[HANDLE]?: () => void;
};
export declare const MIGRATED: unique symbol;
export type MigratedValue<Value> = {
[MIGRATED]: boolean;
value: Value;
};
export type ValueCheckResultOk<Value> = true | undefined | null | void | MigratedValue<Value>;
export type ValueCheckResultInvalid = false | string;
export type ValueCheckResult<Value> = ValueCheckResultOk<Value> | ValueCheckResultInvalid;
export type CheckValue<Value> = (value: unknown, migrate: (value: Value, updateCache?: boolean) => MigratedValue<Value>) => ValueCheckResult<Value> | Promise<ValueCheckResult<Value>>;
/**
* @deprecated use a library supporting Standard Schema
* @see https://github.com/standard-schema/standard-schema?tab=readme-ov-file#what-schema-libraries-implement-the-spec
* @todo remove in next major version
*/
export interface Schema<Value, InputValue> {
_input: InputValue;
parseAsync(value: unknown): Promise<Value>;
}
export interface CachifiedOptions<Value> {
/**
* Required
*
* The key this value is cached by
* Must be unique for each value
*/
key: string;
/**
* Required
*
* Cache implementation to use
*
* Must conform with signature
* - set(key: string, value: object): void | Promise<void>
* - get(key: string): object | Promise<object>
* - delete(key: string): void | Promise<void>
*/
cache: Cache;
/**
* Required
*
* Function that is called when no valid value is in cache for given key
* Basically what we would do if we wouldn't use a cache
*
* Can be async and must return fresh value or throw
*
* receives context object as argument
* - context.metadata.ttl?: number
* - context.metadata.swr?: number
* - context.metadata.createdTime: number
* - context.background: boolean
*/
getFreshValue: GetFreshValue<Value>;
/**
* Time To Live; often also referred to as max age
*
* Amount of milliseconds the value should stay in cache
* before we get a fresh one
*
* Setting any negative value will disable caching
* Can be infinite
*
* Default: `Infinity`
*/
ttl?: number;
/**
* Amount of milliseconds that a value with exceeded ttl is still returned
* while a fresh value is refreshed in the background
*
* Should be positive, can be infinite
*
* Default: `0`
*/
staleWhileRevalidate?: number;
/**
* Alias for staleWhileRevalidate
*/
swr?: number;
/**
* Validator that checks every cached and fresh value to ensure type safety
*
* Can be a standard schema validator or a custom validator function
* @see https://github.com/standard-schema/standard-schema?tab=readme-ov-file#what-schema-libraries-implement-the-spec
*
* Value considered ok when:
* - schema succeeds
* - validator returns
* - true
* - migrate(newValue)
* - undefined
* - null
*
* Value considered bad when:
* - schema throws
* - validator:
* - returns false
* - returns reason as string
* - throws
*
* A validator function receives two arguments:
* 1. the value
* 2. a migrate callback, see https://github.com/epicweb-dev/cachified#migrating-values
*
* Default: `undefined` - no validation
*/
checkValue?: CheckValue<Value> | StandardSchemaV1<unknown, Value> | Schema<Value, unknown>;
/**
* Set true to not even try reading the currently cached value
*
* Will write new value to cache even when cached value is
* still valid.
*
* Default: `false`
*/
forceFresh?: boolean;
/**
* Whether or not to fall back to cache when getting a forced fresh value
* fails
*
* Can also be a positive number as the maximum age in milliseconds that a
* fallback value might have
*
* Default: `Infinity`
*/
fallbackToCache?: boolean | number;
/**
* Amount of time in milliseconds before revalidation of a stale
* cache entry is started
*
* Must be positive and finite
*
* Default: `0`
* @deprecated manually delay background refreshes in getFreshValue instead
* @see https://github.com/epicweb-dev/cachified/issues/132
*/
staleRefreshTimeout?: number;
/**
* @deprecated pass reporter as second argument to cachified
*/
reporter?: never;
/**
* Promises passed to `waitUntil` represent background tasks which must be
* completed before the server can shutdown. e.g. swr cache revalidation
*
* Useful for serverless environments such as Cloudflare Workers.
*
* Default: `undefined`
*/
waitUntil?: (promise: Promise<unknown>) => void;
}
export type CachifiedOptionsWithSchema<Value, InternalValue> = Omit<CachifiedOptions<Value>, 'checkValue' | 'getFreshValue'> & {
checkValue: StandardSchemaV1<unknown, Value> | Schema<Value, InternalValue>;
getFreshValue: GetFreshValue<InternalValue>;
};
export interface Context<Value> extends Omit<Required<CachifiedOptions<Value>>, 'fallbackToCache' | 'reporter' | 'checkValue' | 'swr'> {
checkValue: CheckValue<Value>;
report: Reporter<Value>;
fallbackToCache: number;
metadata: CacheMetadata;
}
export declare function createContext<Value>({ fallbackToCache, checkValue, ...options }: CachifiedOptions<Value>, reporter?: CreateReporter<Value>): Context<Value>;
export declare function staleWhileRevalidate(metadata: CacheMetadata): number | null;
export declare function totalTtl(metadata?: CacheMetadata): number;
export declare function createCacheMetaData({ ttl, swr, createdTime, }?: Partial<Omit<CacheMetadata, 'swv'>>): {
ttl: number | null;
swr: number | null;
createdTime: number;
};
export declare function createCacheEntry<Value>(value: Value, metadata?: Partial<Omit<CacheMetadata, 'swv'>>): CacheEntry<Value>;