interface StorageInterface { get(key: string): T | null | Promise; set(key: string, value: T, opts?: { ttl?: number; }): void | Promise; /** * Largest byte charge one entry may have, when the backend enforces a ceiling. * * The HTTP layer derives its response-body limit from this value so that a body the * backend could never store is refused before it is buffered. */ maxEntryBytes?: number; /** * Whether stored values return byte views unchanged, including views nested in an object. * * Cached handlers store a binary response body as a `Uint8Array` when a backend declares * this, and as base64 text otherwise. A backend that serializes entries must leave it unset: * JSON renders a byte view as `{"0":255,...}`, which no reader can undo. A value read back * in that shape is rejected as a miss rather than served. * * A declaring backend hands the same view to every hit, so nothing may mutate a stored body. */ binary?: boolean; } interface MemoryStorageOptions { /** * Maximum entry count before LRU eviction. * * Defaults to `10 000`. * Set `Infinity` or `0` to disable this limit. * Use {@link maxBytes} to limit attacker-influenced entry sizes. */ maxSize?: number; /** * Maximum estimated bytes, including keys, before LRU eviction. * * Defaults to `100 MB`. * Set `Infinity` or `0` to disable this limit. * An oversized entry replaces its old value with no stored value. * An entry whose size cannot be measured is refused the same way. */ maxBytes?: number; /** * Returns the complete byte charge for a value and its key. * * Memory storage calls this hook only when {@link maxBytes} is active. * The built-in estimate does not count values deeper than eight levels. * Provide `sizeOf` for custom deep shapes and for values whose properties throw, * which memory storage otherwise refuses to store. * Invalid results and errors use the built-in estimate. */ sizeOf?: (value: unknown, key: string) => number; } /** Creates Map-based memory storage with TTLs in seconds and LRU eviction. */ declare function createMemoryStorage(opts?: MemoryStorageOptions): StorageInterface; /** * A backend that stores one byte payload per key. * * This is the shape a raw store already has: unstorage's `getItemRaw`/`setItemRaw`, a * filesystem `readFile`/`writeFile`, a Redis `getBuffer`/`set`, an object-store `get`/`put`. * {@link createBlobStorage} adapts one to a {@link StorageInterface}. */ interface BlobBackend { /** Returns the stored bytes, or `null` when the key is absent. */ get(key: string): BlobValue | null | undefined | Promise; /** Stores the bytes, or removes the key when `value` is `null`. */ set(key: string, value: Uint8Array | null, opts?: { ttl?: number; }): void | Promise; /** Largest byte charge one entry may have. Passed through to {@link StorageInterface}. */ maxEntryBytes?: number; } /** What a byte backend may return: a view, or the buffer behind one. */ type BlobValue = ArrayBufferView | ArrayBufferLike; /** * Adapts a byte-only backend into a {@link StorageInterface}, storing each entry as one frame. * * The entry's metadata travels as JSON and its payload travels as itself, appended after it. * That keeps a response body — text or binary — out of the JSON document: text pays no * escaping in either direction, and bytes pay no base64 and no 4/3 expansion in the backend. * * The payload is the one named by {@link CacheEntry.payload}, which the producer of the entry * declares — `http/entry.ts` for a response body, `cache.ts` for a byte value. Nothing here * infers a payload from a value's shape. * * This declares `binary`, because the frame carries the declared payload as bytes. Every * other member of the entry is JSON, exactly as it would be on a serializing backend: a byte * view hidden somewhere ocache did not put one does not survive, on this backend or on any * other JSON-shaped one. * * A frame written by a different version is read as a miss, so a format change costs one * revalidation rather than a mangled entry. * * @example * ```ts * const storage = createBlobStorage({ * get: (key) => unstorage.getItemRaw(key), * set: (key, value, opts) => * value === null ? unstorage.removeItem(key) : unstorage.setItemRaw(key, value, opts), * }); * ``` */ declare function createBlobStorage(backend: BlobBackend): StorageInterface; /** One layer of a {@link composeStorage} stack. */ interface StorageLayer { /** The backend holding this layer's entries. */ storage: StorageInterface; /** * Largest storage TTL written to this layer, in seconds. * * Caps whatever lifetime the cache asks for, and is the only lifetime a promotion has. * A layer without one holds a promoted entry until its own backend evicts it. */ ttl?: number; } /** Options for {@link composeStorage}. */ interface ComposeStorageOptions { /** * Whether a hit from a later layer is written back to every earlier one. Defaults to `true`. * * Promotions run in the background. A later `set` for the same key waits for one, so a * purge cannot be undone by a promotion already in flight. */ promote?: boolean; /** * Called for each layer that throws, instead of `console.error`. * * A layer failure is never fatal: a read falls through to the next layer and a write * continues to the others. */ onError?: (error: unknown, key: string) => void; } /** * Combines several backends into one tiered {@link StorageInterface}. * * Reads try each layer in order and stop at the first hit, promoting it into every earlier * layer. Writes and deletes reach every layer. A layer that throws is skipped rather than * failing the operation, so a shared remote layer can be down while a local one still serves. * * This is a backend, not a cache option: the cache above it sees one store with one * declaration, so nothing in the key, the entry, or the purge path changes. * * @example * ```ts * const storage = composeStorage([ * { storage: createMemoryStorage({ maxBytes: 64 * 1024 * 1024 }), ttl: 60 }, * createBlobStorage(redis), * ]); * ``` */ declare function composeStorage(layers: ReadonlyArray, opts?: ComposeStorageOptions): StorageInterface; /** * A storage instance or a late-bound storage factory. * * The cache calls a factory once, on the first cache operation. */ type StorageOption = StorageInterface | (() => StorageInterface); /** Request with the srvx-compatible `waitUntil` background task hook. */ interface ServerRequest extends Request { waitUntil?: (promise: Promise) => void; } /** Minimal HTTP event accepted by cached handlers. */ interface HTTPEvent { req: ServerRequest; /** Parsed URL. Defaults to `new URL(req.url)`. */ url?: URL; } /** Handler that receives an HTTP event. */ type EventHandler = (event: E) => unknown | Promise; /** * Cached handler with resource-level cache management methods. * * Each method covers GET and HEAD variants in every base prefix. */ type CachedEventHandler = EventHandler & { /** Returns all resource keys, with the event's method first. */ resolveKeys: (event: E) => Promise; /** Removes all entries for the event's resource. */ invalidate: (event: E) => Promise; /** Marks all entries for the event's resource as stale. */ expire: (event: E) => Promise; }; /** * Result of one cache call. * * - `"hit"`: returned a fresh stored value. * - `"stale"`: returned stale data and started background revalidation. * - `"revalidated"`: replaced an old value before returning. * - `"miss"`: resolved a value when none existed. */ type CacheStatus = "hit" | "stale" | "revalidated" | "miss"; /** Cached value and its metadata. */ interface CacheEntry { value?: T; /** Expiry time in Unix milliseconds. */ expires?: number; /** Last resolution time in Unix milliseconds. */ mtime?: number; /** Hash of the cached function and computation options. */ integrity?: string; /** Forces revalidation on the next access. */ stale?: boolean; /** Per-entry fresh lifetime in seconds. */ maxAge?: number; /** Per-entry stale lifetime in seconds. */ staleMaxAge?: number; /** * Stored form of a binary {@link value}. * * Set only for a value that is a byte view: `"bytes"` when the backend declares * `StorageInterface.binary`, and `"base64"` when it does not. * The field exists only between the storage write and the read that decodes it; * hooks and callers always see a `Uint8Array`. */ encoding?: "bytes" | "base64"; /** * Where this entry's bulk payload sits. * * A storage codec reads this to move the payload as bytes, outside whatever document * holds the metadata: `"value"` for a cached function whose value is the payload, and * `"value.body"` for a cached handler's response body. See {@link createBlobStorage}. * * The producer of the stored shape declares it, so a codec never infers a payload from * the value. Like {@link encoding} it describes storage, not the value: it exists only * between the storage write and the read that consumes it, and is written again from the * options in use. */ payload?: "value" | "value.body"; /** * Status for the current call. * This field is available to `transform` and is not stored. */ status?: CacheStatus; } /** * Options for cached functions. * * Explicit `undefined` values use defaults. * `null` values remain unchanged. */ interface CacheOptions { /** * Cache-key name. * * Defaults to the function name or an anonymous function source hash. * Pass an explicit name for equal-source closures created by factories or loops. */ name?: string; /** Returns a cache key from the function arguments. */ getKey?: (...args: ArgsT) => string | Promise; /** Transforms an entry before return. The return value replaces the cached value unless it is `undefined`. */ transform?: (entry: CacheEntry, ...args: ArgsT) => any; /** * Converts a resolved value to its stored form. * * Runs once per resolution after `getMaxAge`. * It may safely consume a one-use source shared by deduplicated callers. * `validate` receives this stored form on writes and reads. * * @example * ```ts * serialize: async (entry) => ({ ...entry.value, body: await streamToString(entry.value.body) }), * ``` */ serialize?: (entry: CacheEntry, ctx: { args: ArgsT; }) => any; /** * Declares where the stored value's bulk payload sits, for a storage codec. * * `"value.body"` names the `body` member of an object value, as a `serialize` hook * returning `{ body, ... }` produces. A value that is a byte view is its own payload and * needs no declaration. Anything else stays inside the metadata document. * * Only {@link createBlobStorage} and other byte-payload backends read this; every other * backend ignores it. See {@link CacheEntry.payload}. */ payload?: "value.body"; /** * Validates an entry for the current arguments. * Return or resolve to `false` to revalidate it. */ validate?: (entry: CacheEntry, ctx: { args: ArgsT; }) => boolean | Promise; /** Return `true` to trigger revalidation. */ shouldInvalidateCache?: (...args: ArgsT) => boolean | Promise; /** Return `true` to call the function without cache processing. */ shouldBypassCache?: (...args: ArgsT) => boolean | Promise; /** Cache-key group. Defaults to `"functions"`. Escaped like `name`. */ group?: string; /** Integrity value. Defaults to a hash of the function and options. */ integrity?: any; /** * Fresh lifetime in seconds. Defaults to `1`. * A non-positive lifetime prevents storage. */ maxAge?: number; /** * Enables stale-while-revalidate. Defaults to `false`. * * Without {@link staleMaxAge}, stale reuse is limited only by backend eviction. */ swr?: boolean; /** * Stale lifetime in seconds. * * `0` requires foreground revalidation. * An unset value allows stale reuse until backend eviction. */ staleMaxAge?: number; /** * Returns per-entry lifetimes after resolution and before storage. * * A number sets `maxAge`. * An object can set `maxAge` and `staleMaxAge`. * Returned fields override static options for freshness, storage, and Cache-Control. * Non-positive `maxAge` values prevent storage. * * @example * ```ts * getMaxAge: (entry) => entry.value?.expires_in, * getMaxAge: () => ({ maxAge: 60, staleMaxAge: 300 }), * ``` */ getMaxAge?: (entry: CacheEntry) => number | { maxAge?: number; staleMaxAge?: number; } | undefined | Promise; /** * Deadline for one shared resolution and its hooks, in seconds. * * Defaults to `30`. * Set `Infinity` or `0` to disable the deadline. * On timeout, all waiters reject with `TimeoutError` and the old entry is evicted. * A handler's `event.req.signal` aborts with that error; a plain resolver receives no * signal, so it continues but cannot write its late result. */ maxResolveTime?: number; /** * Cache-key base prefixes. Defaults to `"/cache"`. * Reads stop at the first hit. Misses write every prefix. * Revalidation writes the hit prefix and every earlier prefix. */ base?: string | string[]; /** * Storage instance or late-bound factory. * * Defaults to one memory store per cached function or handler. * Pass the same instance to share entries. * A factory runs once on the first cache operation. * The cache writes the resolved instance back to this options object. * * @example * ```ts * const storage = createMemoryStorage(); * const a = cachedFunction(fnA, { storage }); * const b = cachedFunction(fnB, { storage }); * ``` */ storage?: StorageOption; /** * Registers background work with the host: cache writes, SWR refreshes, and evictions. * * A cached function has no event to read `req.waitUntil` from, so a serverless host may * freeze the process before an SWR refresh lands. Pass the platform hook here. * Takes precedence over `event.req.waitUntil` when both are present, so one promise is * never registered twice. * * @example * ```ts * const cached = cachedFunction(fn, { swr: true, waitUntil: (p) => ctx.waitUntil(p) }); * ``` */ waitUntil?: (promise: Promise) => void; /** Receives handled cache, hook, and background errors. */ onError?: (error: unknown) => void; } /** Stored HTTP response data. */ interface ResponseCacheEntry { status: number; statusText: string | undefined; /** Response headers as key-value pairs. */ headers: Record; /** * Stored response body. * Invalid UTF-8 bytes are stored as a `Uint8Array` when the backend declares * `StorageInterface.binary`, and as base64 with {@link base64} set otherwise. */ body: string | Uint8Array | undefined; /** Marks a base64-encoded binary {@link body}. */ base64?: boolean; } /** * Values passed to the conditional response hook. * * The request validators are captured before narrowing, because narrowing forwards * only headers the cache key covers and no key covers a validator. Read them here * rather than from `event.req`, which no longer carries them. */ interface CacheConditions { modifiedTime?: Date; maxAge?: number; etag?: string; /** The request's `If-None-Match`, captured before narrowing. */ ifNoneMatch?: string; /** The request's `If-Modified-Since`, captured before narrowing. */ ifModifiedSince?: string; } /** * Options for cached HTTP handlers. * * Internal hooks serialize and validate Response values. * `getMaxAge` may inspect response metadata but must not consume the body. */ interface CachedEventHandlerOptions extends Omit, "transform" | "validate" | "serialize" | "payload"> { /** * Answers conditional requests with 304 without storing responses. * * The handler always runs, and its own `etag` and `last-modified` are the conditions. */ headersOnly?: boolean; /** * Request headers that reach the handler, vary the key, and appear in `Vary`. * * A response with an undeclared Vary name is returned but not stored. * Listing Cookie or Authorization keys the complete raw header value. * Prefer {@link allowCookies} for a cookie subset. */ varies?: string[] | readonly string[]; /** * Case-sensitive query names that reach the handler and generated key. * * By default, no query parameters reach cacheable handlers or vary the key. * Set to `true` to opt the full query string back in. * A custom `getKey` replaces key generation but does not disable URL filtering. */ allowQuery?: boolean | string[] | readonly string[]; /** * Case-sensitive cookie names that reach the handler and vary the key. * * By default, no request cookies reach cacheable handlers. * Values select shared representations and must not contain per-user secrets. * This option emits `Vary: Cookie`, which can reduce downstream cache hits. * Use {@link sendCacheControl} set to `false` for server-only cookie caching. * Cacheable responses always remove `Set-Cookie`. * This option overrides `varies: ["cookie"]`. */ allowCookies?: string[] | readonly string[]; /** * Allows Authorization and Proxy-Authorization to reach the handler and vary the key. * * Defaults to `false`, which strips these credentials from cacheable requests. * Enabled responses are shared by callers with the same credential value. * Bypass caching when responses must not be shared. */ allowAuthorization?: boolean; /** * Enables generated Cache-Control headers. Defaults to `true`. * * Set to `false` to suppress ocache's synthesized downstream freshness lifetime. * This does not emit `no-store` or prevent downstream storage. * Explicit handler Cache-Control headers remain unchanged. */ sendCacheControl?: boolean; /** * Cache-status response header. * * `true` uses `X-Cache`, a string sets its name, and `false` disables it. * This option has no effect in `headersOnly` mode. */ cacheStatusHeader?: boolean | string; /** * Converts a handler value to Response. * * Defaults to passing a `Response` through, wrapping a primitive or a body type * (bytes, `Blob`, `ReadableStream`) in `new Response(value)`, and throwing on anything * else. Set this hook for a handler that returns objects. */ toResponse?: (value: unknown, event: E) => Response | Promise; /** * Creates a Response from stored data. * The body is text, binary bytes, `null`, or a `ReadableStream` under {@link stream}. * Defaults to `new Response(body, init)`. */ createResponse?: (body: string | Uint8Array | ReadableStream | null, init: ResponseInit) => Response; /** * Streams the first response while the cache entry fills. Defaults to `false`. * * Without this, the request that fills an entry waits for the handler's complete body, * because the body has to be buffered before it can be stored. With it, that one request * receives the body as it is read and the entry is written once the read completes. * Later requests are served from the stored entry as usual. * * This trades error recovery for time to first byte. The response starts before the * handler finishes, so a failure part-way through can only reach the client as a * truncated body, and the streamed response carries no synthesized `etag` — the * validator digests a body that does not exist yet. Nothing partial is ever stored. * * Deduplicated and later requests still wait for the complete entry. Raise * {@link maxResolveTime} for a body that takes longer than the deadline to produce. */ stream?: boolean; /** * Largest response body, in bytes, that may be buffered for storage. * * Defaults to the largest body the storage backend could store, derived from the * per-entry ceiling it declares. Memory storage declares its `maxBytes`. * A larger response streams through uncached, exactly as a bypassed request does. * Set `Infinity` or `0` to buffer every response the backend accepts. */ maxBodySize?: number; /** Returns `true` to answer matching conditional headers with 304. */ handleCacheHeaders?: (event: E, conditions: CacheConditions) => boolean; /** * Applies an additional cacheability check to serialized responses. * * This hook can reject but cannot override built-in validation. * A rejected fresh response is returned but not stored. * It runs on writes and reads, including stale reads. * Errors fail closed and reach `onError`. * * @example * ```ts * shouldCache: (response) => response.status < 300, * ``` */ shouldCache?: (entry: ResponseCacheEntry) => boolean | Promise; } type CachedFunction = { (...args: ArgsT): Promise; /** Returns one storage key per base prefix. */ resolveKeys: (...args: ArgsT) => Promise; /** Removes matching entries from all base prefixes. */ invalidate: (...args: ArgsT) => Promise; /** Marks matching entries as stale in all base prefixes. */ expire: (...args: ArgsT) => Promise; }; /** * Wraps a function with caching, SWR, integrity checks, and request deduplication. * * @param fn - Function to cache. * @param opts - Cache options. * @returns The cached function and its cache management methods. */ declare function defineCachedFunction(fn: (...args: ArgsT) => T | Promise, opts?: CacheOptions): CachedFunction; /** Alias for {@link defineCachedFunction}. */ declare const cachedFunction: typeof defineCachedFunction; /** * Returns one storage key per base prefix. * * Pass the same `getKey`, `name`, `group`, and `base` options as the cached function. * This helper computes keys without accessing storage. * * @param input - Cache options and function arguments. * @returns Storage keys in base-prefix order. * * @example * ```ts * const keys = await resolveCacheKeys({ * options: { name: "fetchUser", getKey: (id: string) => id }, * args: ["user-123"], * }); * ``` */ declare function resolveCacheKeys(input?: { options?: Pick, "base" | "group" | "name" | "getKey">; args?: ArgsT; }): Promise; /** * Removes matching entries from all base prefixes. * * Pass the original options object or the same explicit storage backend. * This function throws when `options.storage` is unset because no global store exists. * Prefer the cached function's `.invalidate()` method when available. * * @param input - Cache options and function arguments. * * @example * ```ts * await invalidateCache({ * options: { name: "fetchUser", getKey: (id: string) => id, storage }, * args: ["user-123"], * }); * ``` */ declare function invalidateCache(input?: { options?: Pick, "base" | "group" | "name" | "getKey" | "storage">; args?: ArgsT; }): Promise; /** * Marks matching entries as stale without removing them. * * SWR may serve the stale value within its original stale window. * Without SWR, the next call revalidates before returning. * Pass the original lifetime options to preserve the remaining storage TTL. * This function throws when `options.storage` is unset. * * @param input - Cache options and function arguments. * * @example * ```ts * await expireCache({ * options: { name: "fetchUser", getKey: (id: string) => id, maxAge: 60, swr: true, storage }, * args: ["user-123"], * }); * ``` */ declare function expireCache(input?: { options?: Pick, "base" | "group" | "name" | "getKey" | "maxAge" | "swr" | "staleMaxAge" | "storage">; args?: ArgsT; }): Promise; /** * Wraps an HTTP handler with response caching and conditional response support. * * Only GET and HEAD requests without Range are cacheable. * Only 200, 203, 301, and 308 responses are stored. * Response Cache-Control and Vary headers can prevent storage. * * @param handler - Handler to cache. * @param opts - Cache and HTTP options. * @returns A cached handler with resource-level cache management methods. */ declare function defineCachedHandler(handler: EventHandler, opts?: CachedEventHandlerOptions): CachedEventHandler; export { type BlobBackend, type BlobValue, type CacheConditions, type CacheEntry, type CacheOptions, type CacheStatus, type CachedEventHandler, type CachedEventHandlerOptions, type CachedFunction, type ComposeStorageOptions, type EventHandler, type HTTPEvent, type MemoryStorageOptions, type ResponseCacheEntry, type ServerRequest, type StorageInterface, type StorageLayer, type StorageOption, cachedFunction, composeStorage, createBlobStorage, createMemoryStorage, defineCachedFunction, defineCachedHandler, expireCache, invalidateCache, resolveCacheKeys };