+131
| //#region src/types.d.ts | ||
| /** | ||
| * Extended `Request` interface with optional `waitUntil` for background tasks. | ||
| * | ||
| * Compatible with srvx `ServerRequest`. | ||
| */ | ||
| interface ServerRequest extends Request { | ||
| waitUntil?: (promise: Promise<any>) => void; | ||
| } | ||
| /** | ||
| * Minimal HTTP event object containing a request and an optional pre-parsed URL. | ||
| */ | ||
| interface HTTPEvent { | ||
| req: ServerRequest; | ||
| /** Pre-parsed URL. Falls back to `new URL(req.url)` when not provided. */ | ||
| url?: URL; | ||
| } | ||
| /** | ||
| * Handler function that receives an {@link HTTPEvent} and returns a response value. | ||
| */ | ||
| type EventHandler = (event: HTTPEvent) => unknown | Promise<unknown>; | ||
| /** | ||
| * Stored cache entry wrapping a cached value with metadata. | ||
| */ | ||
| interface CacheEntry<T = any> { | ||
| /** The cached value. */ | ||
| value?: T; | ||
| /** Absolute timestamp (ms) when this entry expires. */ | ||
| expires?: number; | ||
| /** Absolute timestamp (ms) when this entry was last resolved. */ | ||
| mtime?: number; | ||
| /** Hash used to detect when the cached function or options have changed. */ | ||
| integrity?: string; | ||
| } | ||
| /** | ||
| * Options for configuring cached functions created by `defineCachedFunction`. | ||
| */ | ||
| interface CacheOptions<T = any, ArgsT extends unknown[] = any[]> { | ||
| /** Name used as part of the cache key. Defaults to the function name or `"_"`. */ | ||
| name?: string; | ||
| /** Custom cache key generator. Receives the same arguments as the cached function. */ | ||
| getKey?: (...args: ArgsT) => string | Promise<string>; | ||
| /** Transform the cached entry before returning. Return value replaces the cached value. */ | ||
| transform?: (entry: CacheEntry<T>, ...args: ArgsT) => any; | ||
| /** Validate a cache entry. Return `false` to treat the entry as invalid and re-resolve. */ | ||
| validate?: (entry: CacheEntry<T>, ...args: ArgsT) => boolean; | ||
| /** When returns `true`, the cache is invalidated and the function is re-invoked. */ | ||
| shouldInvalidateCache?: (...args: ArgsT) => boolean | Promise<boolean>; | ||
| /** When returns `true`, the cache is bypassed entirely and the function is called directly. */ | ||
| shouldBypassCache?: (...args: ArgsT) => boolean | Promise<boolean>; | ||
| /** Cache key group prefix. Defaults to `"ocache/functions"`. */ | ||
| group?: string; | ||
| /** Custom integrity value. Auto-generated from the function and options by default. */ | ||
| integrity?: any; | ||
| /** Number of seconds to cache the response. Defaults to `1`. */ | ||
| maxAge?: number; | ||
| /** Enable stale-while-revalidate behavior. When `true`, returns stale cache while refreshing in the background. Defaults to `true`. */ | ||
| swr?: boolean; | ||
| /** Maximum number of seconds a stale entry can be served while revalidating. */ | ||
| staleMaxAge?: number; | ||
| /** Base path prefix for cache keys. Defaults to `"/cache"`. */ | ||
| base?: string; | ||
| /** Optional error handler called for all cache-related errors (read, write, SWR, malformed data). */ | ||
| onError?: (error: unknown) => void; | ||
| } | ||
| /** | ||
| * Serialized HTTP response stored in the cache by `defineCachedHandler`. | ||
| */ | ||
| interface ResponseCacheEntry { | ||
| /** HTTP status code. */ | ||
| status: number; | ||
| /** HTTP status text. */ | ||
| statusText: string | undefined; | ||
| /** Response headers as a flat key-value record. */ | ||
| headers: Record<string, string>; | ||
| /** Response body as a string. */ | ||
| body: string | undefined; | ||
| } | ||
| /** | ||
| * Options for configuring cached HTTP handlers created by `defineCachedHandler`. | ||
| * | ||
| * Extends {@link CacheOptions} (without `transform` and `validate`, which are set internally). | ||
| */ | ||
| interface CachedEventHandlerOptions extends Omit<CacheOptions<ResponseCacheEntry, [HTTPEvent]>, "transform" | "validate"> { | ||
| /** When `true`, only handles conditional headers (304 responses) without full response caching. */ | ||
| headersOnly?: boolean; | ||
| /** Request header names that should vary the cache key (e.g., `["accept-language"]`). */ | ||
| varies?: string[] | readonly string[]; | ||
| } | ||
| //#endregion | ||
| //#region src/cache.d.ts | ||
| /** | ||
| * Wraps a function with caching support including TTL, SWR, integrity checks, and request deduplication. | ||
| * | ||
| * @param fn - The function to cache. | ||
| * @param opts - Cache configuration options. | ||
| * @returns A new async function that returns cached results when available. | ||
| */ | ||
| declare function defineCachedFunction<T, ArgsT extends unknown[] = any[]>(fn: (...args: ArgsT) => T | Promise<T>, opts?: CacheOptions<T, ArgsT>): (...args: ArgsT) => Promise<T>; | ||
| /** Alias for {@link defineCachedFunction}. */ | ||
| declare const cachedFunction: typeof defineCachedFunction; | ||
| //#endregion | ||
| //#region src/http.d.ts | ||
| /** | ||
| * Wraps an HTTP event handler with response caching. | ||
| * | ||
| * Automatically generates cache keys from the URL path and variable headers, | ||
| * sets `cache-control`, `etag`, and `last-modified` headers, and handles | ||
| * `304 Not Modified` responses via conditional request headers. | ||
| * | ||
| * @param handler - The event handler to cache. | ||
| * @param opts - Cache and HTTP-specific configuration options. | ||
| * @returns A new event handler that serves cached responses when available. | ||
| */ | ||
| declare function defineCachedHandler(handler: EventHandler, opts?: CachedEventHandlerOptions): EventHandler; | ||
| //#endregion | ||
| //#region src/storage.d.ts | ||
| interface StorageInterface { | ||
| get<T = unknown>(key: string): T | null | Promise<T | null>; | ||
| set<T = unknown>(key: string, value: T, opts?: { | ||
| ttl?: number; | ||
| }): void | Promise<void>; | ||
| } | ||
| /** Creates an in-memory storage backed by a `Map` with optional TTL support (in seconds). */ | ||
| declare function createMemoryStorage(): StorageInterface; | ||
| /** Returns the current storage instance. If none has been set via `setStorage`, lazily initializes an in-memory storage. */ | ||
| declare function useStorage(): StorageInterface; | ||
| /** Sets a custom storage implementation to be used by all cached functions. */ | ||
| declare function setStorage(storage: StorageInterface): void; | ||
| //#endregion | ||
| export { type CacheEntry, type CacheOptions, type CachedEventHandlerOptions, type EventHandler, type HTTPEvent, type ResponseCacheEntry, type ServerRequest, type StorageInterface, cachedFunction, createMemoryStorage, defineCachedFunction, defineCachedHandler, setStorage, useStorage }; |
+252
| import { hash } from "ohash"; | ||
| //#region src/storage.ts | ||
| /** Creates an in-memory storage backed by a `Map` with optional TTL support (in seconds). */ | ||
| function createMemoryStorage() { | ||
| const map = /* @__PURE__ */ new Map(); | ||
| return { | ||
| get(key) { | ||
| const entry = map.get(key); | ||
| if (!entry) return null; | ||
| if (entry.expires && Date.now() > entry.expires) { | ||
| map.delete(key); | ||
| return null; | ||
| } | ||
| return entry.value; | ||
| }, | ||
| set(key, value, opts) { | ||
| map.set(key, { | ||
| value, | ||
| expires: opts?.ttl ? Date.now() + opts.ttl * 1e3 : void 0 | ||
| }); | ||
| } | ||
| }; | ||
| } | ||
| let _storage; | ||
| /** Returns the current storage instance. If none has been set via `setStorage`, lazily initializes an in-memory storage. */ | ||
| function useStorage() { | ||
| if (!_storage) _storage = createMemoryStorage(); | ||
| return _storage; | ||
| } | ||
| /** Sets a custom storage implementation to be used by all cached functions. */ | ||
| function setStorage(storage) { | ||
| _storage = storage; | ||
| } | ||
| //#endregion | ||
| //#region src/cache.ts | ||
| function defaultCacheOptions$1() { | ||
| return { | ||
| name: "_", | ||
| base: "/cache", | ||
| swr: true, | ||
| maxAge: 1 | ||
| }; | ||
| } | ||
| /** | ||
| * Wraps a function with caching support including TTL, SWR, integrity checks, and request deduplication. | ||
| * | ||
| * @param fn - The function to cache. | ||
| * @param opts - Cache configuration options. | ||
| * @returns A new async function that returns cached results when available. | ||
| */ | ||
| function defineCachedFunction(fn, opts = {}) { | ||
| opts = { | ||
| ...defaultCacheOptions$1(), | ||
| ...opts | ||
| }; | ||
| const pending = {}; | ||
| const group = opts.group || "ocache/functions"; | ||
| const name = opts.name || fn.name || "_"; | ||
| const integrity = opts.integrity || hash([fn, opts]); | ||
| const validate = opts.validate || ((entry) => entry.value !== void 0); | ||
| const _onError = (context, error) => { | ||
| if (opts.onError) opts.onError(error); | ||
| else console.error(context, error); | ||
| }; | ||
| async function get(key, resolver, shouldInvalidateCache, event) { | ||
| const cacheKey = [ | ||
| opts.base, | ||
| group, | ||
| name, | ||
| key + ".json" | ||
| ].filter(Boolean).join(":").replace(/:\/$/, ":index"); | ||
| let entry = await Promise.resolve(useStorage().get(cacheKey)).catch((error) => { | ||
| _onError("[cache] Cache read error.", error); | ||
| }) || {}; | ||
| if (typeof entry !== "object") { | ||
| entry = {}; | ||
| _onError("[cache]", /* @__PURE__ */ new Error("Malformed data read from cache.")); | ||
| } | ||
| const ttl = (opts.maxAge ?? 0) * 1e3; | ||
| if (ttl) entry.expires = Date.now() + ttl; | ||
| const expired = shouldInvalidateCache || entry.integrity !== integrity || ttl && Date.now() - (entry.mtime || 0) > ttl || validate(entry) === false; | ||
| const _resolve = async () => { | ||
| const isPending = pending[key]; | ||
| if (!isPending) { | ||
| if (entry.value !== void 0 && (opts.staleMaxAge || 0) >= 0 && opts.swr === false) { | ||
| entry.value = void 0; | ||
| entry.integrity = void 0; | ||
| entry.mtime = void 0; | ||
| entry.expires = void 0; | ||
| } | ||
| pending[key] = Promise.resolve(resolver()); | ||
| } | ||
| try { | ||
| entry.value = await pending[key]; | ||
| } catch (error) { | ||
| if (!isPending) delete pending[key]; | ||
| throw error; | ||
| } | ||
| if (!isPending) { | ||
| entry.mtime = Date.now(); | ||
| entry.integrity = integrity; | ||
| delete pending[key]; | ||
| if (validate(entry) !== false) { | ||
| let setOpts; | ||
| if (opts.maxAge && !opts.swr) setOpts = { ttl: opts.maxAge }; | ||
| const promise = Promise.resolve(useStorage().set(cacheKey, entry, setOpts)).catch((error) => { | ||
| _onError("[cache] Cache write error.", error); | ||
| }); | ||
| if ((event?.req)?.waitUntil) event.req.waitUntil(promise); | ||
| } | ||
| } | ||
| }; | ||
| const _resolvePromise = expired ? _resolve() : Promise.resolve(); | ||
| if (entry.value === void 0) await _resolvePromise; | ||
| else if (expired && (event?.req)?.waitUntil) event.req.waitUntil(_resolvePromise); | ||
| if (opts.swr && validate(entry) !== false) { | ||
| _resolvePromise.catch((error) => { | ||
| _onError("[cache] SWR handler error.", error); | ||
| }); | ||
| return entry; | ||
| } | ||
| return _resolvePromise.then(() => entry); | ||
| } | ||
| return async (...args) => { | ||
| if (await opts.shouldBypassCache?.(...args)) return fn(...args); | ||
| const entry = await get(await (opts.getKey || getKey)(...args), () => fn(...args), await opts.shouldInvalidateCache?.(...args), isHTTPEvent(args[0]) ? args[0] : void 0); | ||
| let value = entry.value; | ||
| if (opts.transform) value = await opts.transform(entry, ...args) || value; | ||
| return value; | ||
| }; | ||
| } | ||
| /** Alias for {@link defineCachedFunction}. */ | ||
| const cachedFunction = defineCachedFunction; | ||
| function isHTTPEvent(input) { | ||
| return input?.req instanceof Request; | ||
| } | ||
| function getKey(...args) { | ||
| return args.length > 0 ? hash(args) : ""; | ||
| } | ||
| //#endregion | ||
| //#region src/http.ts | ||
| function defaultCacheOptions() { | ||
| return { | ||
| name: "_", | ||
| base: "/cache", | ||
| swr: true, | ||
| maxAge: 1 | ||
| }; | ||
| } | ||
| /** | ||
| * Wraps an HTTP event handler with response caching. | ||
| * | ||
| * Automatically generates cache keys from the URL path and variable headers, | ||
| * sets `cache-control`, `etag`, and `last-modified` headers, and handles | ||
| * `304 Not Modified` responses via conditional request headers. | ||
| * | ||
| * @param handler - The event handler to cache. | ||
| * @param opts - Cache and HTTP-specific configuration options. | ||
| * @returns A new event handler that serves cached responses when available. | ||
| */ | ||
| function defineCachedHandler(handler, opts = defaultCacheOptions()) { | ||
| const variableHeaderNames = (opts.varies || []).filter(Boolean).map((h) => h.toLowerCase()).sort(); | ||
| const _cachedHandler = cachedFunction(async (event) => { | ||
| const filteredHeaders = [...event.req.headers.entries()].filter(([key]) => !variableHeaderNames.includes(key.toLowerCase())); | ||
| try { | ||
| const originalReq = event.req; | ||
| event.req = new Request(event.req.url, { | ||
| method: event.req.method, | ||
| headers: filteredHeaders | ||
| }); | ||
| if (originalReq.runtime) event.req.runtime = originalReq.runtime; | ||
| } catch (error) { | ||
| console.error("[cache] Failed to filter headers:", error); | ||
| } | ||
| const rawValue = await handler(event); | ||
| const res = rawValue instanceof Response ? rawValue : new Response(String(rawValue)); | ||
| const body = await res.text(); | ||
| if (!res.headers.has("etag")) res.headers.set("etag", `W/"${hash(body)}"`); | ||
| if (!res.headers.has("last-modified")) res.headers.set("last-modified", (/* @__PURE__ */ new Date()).toUTCString()); | ||
| const cacheControl = []; | ||
| if (opts.swr) { | ||
| if (opts.maxAge) cacheControl.push(`s-maxage=${opts.maxAge}`); | ||
| if (opts.staleMaxAge) cacheControl.push(`stale-while-revalidate=${opts.staleMaxAge}`); | ||
| else cacheControl.push("stale-while-revalidate"); | ||
| } else if (opts.maxAge) cacheControl.push(`max-age=${opts.maxAge}`); | ||
| if (cacheControl.length > 0) res.headers.set("cache-control", cacheControl.join(", ")); | ||
| return { | ||
| status: res.status, | ||
| statusText: res.statusText, | ||
| headers: Object.fromEntries(res.headers.entries()), | ||
| body | ||
| }; | ||
| }, { | ||
| ...opts, | ||
| shouldBypassCache: (event) => { | ||
| return event.req.method !== "GET" && event.req.method !== "HEAD"; | ||
| }, | ||
| getKey: async (event) => { | ||
| const customKey = await opts.getKey?.(event); | ||
| if (customKey) return escapeKey(customKey); | ||
| const _url = event.url ?? new URL(event.req.url); | ||
| const _path = _url.pathname + _url.search; | ||
| let _pathname; | ||
| try { | ||
| _pathname = escapeKey(decodeURI(new URL(_path, "http://localhost").pathname)).slice(0, 16) || "index"; | ||
| } catch { | ||
| _pathname = "-"; | ||
| } | ||
| return [`${_pathname}.${hash(_path)}`, ...variableHeaderNames.map((header) => [header, event.req.headers.get(header)]).map(([name, value]) => `${escapeKey(name)}.${hash(value)}`)].join(":"); | ||
| }, | ||
| validate: (entry) => { | ||
| if (!entry.value) return false; | ||
| if (entry.value.status >= 400) return false; | ||
| if (entry.value.body === void 0) return false; | ||
| if (entry.value.headers.etag === "undefined" || entry.value.headers["last-modified"] === "undefined") return false; | ||
| return true; | ||
| }, | ||
| group: opts.group || "cache/handlers", | ||
| integrity: opts.integrity || hash([handler, opts]) | ||
| }); | ||
| return async (event) => { | ||
| if (opts.headersOnly) { | ||
| if (handleCacheHeaders(event, { maxAge: opts.maxAge })) return new Response(null, { status: 304 }); | ||
| return handler(event); | ||
| } | ||
| const response = await _cachedHandler(event); | ||
| if (handleCacheHeaders(event, { | ||
| modifiedTime: new Date(response.headers["last-modified"]), | ||
| etag: response.headers.etag, | ||
| maxAge: opts.maxAge | ||
| })) return new Response(null, { status: 304 }); | ||
| return new Response(response.body, { | ||
| status: response.status, | ||
| statusText: response.statusText, | ||
| headers: response.headers | ||
| }); | ||
| }; | ||
| } | ||
| function escapeKey(key) { | ||
| return String(key).replace(/\W/g, ""); | ||
| } | ||
| function handleCacheHeaders(event, opts) { | ||
| const ifNoneMatch = event.req.headers.get("if-none-match"); | ||
| if (ifNoneMatch && opts.etag && ifNoneMatch === opts.etag) return true; | ||
| const ifModifiedSince = event.req.headers.get("if-modified-since"); | ||
| if (ifModifiedSince && opts.modifiedTime) { | ||
| if (new Date(ifModifiedSince) >= opts.modifiedTime) return true; | ||
| } | ||
| return false; | ||
| } | ||
| //#endregion | ||
| export { cachedFunction, createMemoryStorage, defineCachedFunction, defineCachedHandler, setStorage, useStorage }; |
+21
| MIT License | ||
| Copyright (c) Pooya Parsa <pooya@pi0.io> | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
+291
| # ocache | ||
| <!-- automd:badges color=yellow --> | ||
| [](https://npmjs.com/package/ocache) | ||
| [](https://npm.chart.dev/ocache) | ||
| <!-- /automd --> | ||
| ## Usage | ||
| ### Caching Functions | ||
| Wrap any function with `defineCachedFunction` to add caching with TTL, stale-while-revalidate, and request deduplication: | ||
| ```ts | ||
| import { defineCachedFunction } from "ocache"; | ||
| const cachedFetch = defineCachedFunction( | ||
| async (url: string) => { | ||
| const res = await fetch(url); | ||
| return res.json(); | ||
| }, | ||
| { | ||
| maxAge: 60, // Cache for 60 seconds | ||
| name: "api-fetch", | ||
| }, | ||
| ); | ||
| // First call hits the function, subsequent calls return cached result | ||
| const data = await cachedFetch("https://api.example.com/data"); | ||
| ``` | ||
| #### Options | ||
| ```ts | ||
| const cached = defineCachedFunction(fn, { | ||
| name: "my-fn", // Cache key name (defaults to function name) | ||
| maxAge: 10, // TTL in seconds (default: 1) | ||
| swr: true, // Stale-while-revalidate (default: true) | ||
| staleMaxAge: 60, // Max seconds to serve stale content | ||
| group: "my-group", // Cache key group (default: "ocache/functions") | ||
| getKey: (...args) => "custom-key", // Custom cache key generator | ||
| shouldBypassCache: (...args) => false, // Skip cache entirely when true | ||
| shouldInvalidateCache: (...args) => false, // Force refresh when true | ||
| validate: (entry) => entry.value !== undefined, // Custom validation | ||
| transform: (entry) => entry.value, // Transform before returning | ||
| onError: (error) => console.error(error), // Error handler | ||
| }); | ||
| ``` | ||
| ### Caching HTTP Handlers | ||
| Wrap HTTP handlers with `defineCachedHandler` for automatic response caching with `etag`, `last-modified`, and `304 Not Modified` support: | ||
| ```ts | ||
| import { defineCachedHandler } from "ocache"; | ||
| const handler = defineCachedHandler( | ||
| async (event) => { | ||
| // event.req is a standard Request object | ||
| const url = event.url ?? new URL(event.req.url); | ||
| const data = await getExpensiveData(url.pathname); | ||
| return new Response(JSON.stringify(data), { | ||
| headers: { "content-type": "application/json" }, | ||
| }); | ||
| }, | ||
| { | ||
| maxAge: 300, // Cache for 5 minutes | ||
| swr: true, | ||
| staleMaxAge: 600, | ||
| varies: ["accept-language"], // Vary cache by these headers | ||
| }, | ||
| ); | ||
| // Use with any server that provides Request/Response | ||
| // e.g., Bun, Deno, Cloudflare Workers, srvx, etc. | ||
| ``` | ||
| #### Headers-only Mode | ||
| Use `headersOnly` to handle conditional requests without caching the full response: | ||
| ```ts | ||
| const handler = defineCachedHandler(myHandler, { | ||
| headersOnly: true, | ||
| maxAge: 60, | ||
| }); | ||
| ``` | ||
| ### Custom Storage | ||
| By default, ocache uses an in-memory `Map`-based storage. You can provide a custom storage implementation: | ||
| ```ts | ||
| import { setStorage } from "ocache"; | ||
| import type { StorageInterface } from "ocache"; | ||
| const redisStorage: StorageInterface = { | ||
| get: async (key) => { | ||
| return JSON.parse(await redis.get(key)); | ||
| }, | ||
| set: async (key, value, opts) => { | ||
| await redis.set(key, JSON.stringify(value), opts?.ttl ? { EX: opts.ttl } : undefined); | ||
| }, | ||
| }; | ||
| setStorage(redisStorage); | ||
| ``` | ||
| ## API | ||
| <!-- automd:docs4ts --> | ||
| ### `defineCachedFunction` | ||
| ```ts | ||
| function defineCachedFunction<T, ArgsT extends unknown[] = any[]>( | ||
| fn: (...args: ArgsT) => T | Promise<T>, | ||
| opts: CacheOptions<T, ArgsT> = | ||
| ``` | ||
| Wraps a function with caching support including TTL, SWR, integrity checks, and request deduplication. | ||
| **Parameters:** | ||
| - **`fn`** — The function to cache. | ||
| - **`opts`** — Cache configuration options. | ||
| **Returns:** — A new async function that returns cached results when available. | ||
| --- | ||
| ### `cachedFunction` | ||
| ```ts | ||
| const cachedFunction = defineCachedFunction; | ||
| ``` | ||
| Alias for [`defineCachedFunction`](#definecachedfunction). | ||
| --- | ||
| ### `defineCachedHandler` | ||
| ```ts | ||
| function defineCachedHandler( | ||
| handler: EventHandler, | ||
| opts: CachedEventHandlerOptions = defaultCacheOptions(), | ||
| ): EventHandler; | ||
| ``` | ||
| Wraps an HTTP event handler with response caching. | ||
| Automatically generates cache keys from the URL path and variable headers, | ||
| sets `cache-control`, `etag`, and `last-modified` headers, and handles | ||
| `304 Not Modified` responses via conditional request headers. | ||
| **Parameters:** | ||
| - **`handler`** — The event handler to cache. | ||
| - **`opts`** — Cache and HTTP-specific configuration options. | ||
| **Returns:** — A new event handler that serves cached responses when available. | ||
| --- | ||
| ### `createMemoryStorage` | ||
| ```ts | ||
| function createMemoryStorage(): StorageInterface; | ||
| ``` | ||
| Creates an in-memory storage backed by a `Map` with optional TTL support (in seconds). | ||
| --- | ||
| ### `useStorage` | ||
| ```ts | ||
| function useStorage(): StorageInterface; | ||
| ``` | ||
| Returns the current storage instance. If none has been set via `setStorage`, lazily initializes an in-memory storage. | ||
| --- | ||
| ### `setStorage` | ||
| ```ts | ||
| function setStorage(storage: StorageInterface): void; | ||
| ``` | ||
| Sets a custom storage implementation to be used by all cached functions. | ||
| --- | ||
| ### `ServerRequest` | ||
| ```ts | ||
| interface ServerRequest extends Request | ||
| ``` | ||
| Extended `Request` interface with optional `waitUntil` for background tasks. | ||
| Compatible with srvx `ServerRequest`. | ||
| --- | ||
| ### `HTTPEvent` | ||
| ```ts | ||
| interface HTTPEvent | ||
| ``` | ||
| Minimal HTTP event object containing a request and an optional pre-parsed URL. | ||
| --- | ||
| ### `EventHandler` | ||
| ```ts | ||
| type EventHandler = (event: HTTPEvent) => unknown | Promise<unknown>; | ||
| ``` | ||
| Handler function that receives an [`HTTPEvent`](#httpevent) and returns a response value. | ||
| --- | ||
| ### `CacheEntry` | ||
| ```ts | ||
| interface CacheEntry<T = any> | ||
| ``` | ||
| Stored cache entry wrapping a cached value with metadata. | ||
| --- | ||
| ### `CacheOptions` | ||
| ```ts | ||
| interface CacheOptions<T = any, ArgsT extends unknown[] = any[]> | ||
| ``` | ||
| Options for configuring cached functions created by `defineCachedFunction`. | ||
| --- | ||
| ### `ResponseCacheEntry` | ||
| ```ts | ||
| interface ResponseCacheEntry | ||
| ``` | ||
| Serialized HTTP response stored in the cache by `defineCachedHandler`. | ||
| --- | ||
| ### `CachedEventHandlerOptions` | ||
| ```ts | ||
| interface CachedEventHandlerOptions extends Omit< | ||
| CacheOptions<ResponseCacheEntry, [HTTPEvent]>, | ||
| "transform" | "validate" | ||
| > | ||
| ``` | ||
| Options for configuring cached HTTP handlers created by `defineCachedHandler`. | ||
| Extends [`CacheOptions`](#cacheoptions) (without `transform` and `validate`, which are set internally). | ||
| <!-- /automd--> | ||
| ## Development | ||
| <details> | ||
| <summary>local development</summary> | ||
| - Clone this repository | ||
| - Install latest LTS version of [Node.js](https://nodejs.org/en/) | ||
| - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` | ||
| - Install dependencies using `pnpm install` | ||
| - Run interactive tests using `pnpm dev` | ||
| </details> | ||
| ## License | ||
| Published under the [MIT](https://github.com/unjs/ocache/blob/main/LICENSE) license 💛. |
+42
-4
| { | ||
| "name": "ocache", | ||
| "version": "0.0.0", | ||
| "license": "MIT" | ||
| } | ||
| "name": "ocache", | ||
| "version": "0.1.1", | ||
| "description": "Standalone caching utilities with TTL, SWR, and HTTP response caching", | ||
| "license": "MIT", | ||
| "repository": "unjs/ocache", | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "type": "module", | ||
| "sideEffects": false, | ||
| "types": "./dist/index.d.mts", | ||
| "exports": { | ||
| ".": "./dist/index.mjs" | ||
| }, | ||
| "scripts": { | ||
| "build": "obuild", | ||
| "dev": "vitest dev", | ||
| "fmt": "automd && oxlint . --fix && oxfmt .", | ||
| "lint": "oxlint . && oxfmt --check .", | ||
| "prepack": "pnpm build", | ||
| "release": "pnpm test && pnpm build && changelogen --release && npm publish && git push --follow-tags", | ||
| "test": "pnpm lint && pnpm typecheck && vitest run --coverage", | ||
| "typecheck": "tsgo --noEmit --skipLibCheck" | ||
| }, | ||
| "dependencies": { | ||
| "ohash": "^2.0.11" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "latest", | ||
| "@typescript/native-preview": "latest", | ||
| "@vitest/coverage-v8": "latest", | ||
| "automd": "latest", | ||
| "changelogen": "latest", | ||
| "docs4ts": "^0.0.3", | ||
| "obuild": "latest", | ||
| "oxfmt": "latest", | ||
| "oxlint": "latest", | ||
| "typescript": "latest", | ||
| "vitest": "latest" | ||
| }, | ||
| "packageManager": "pnpm@10.29.3" | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
23981
39213.11%5
400%252
Infinity%1
-50%292
Infinity%Yes
NaN1
Infinity%11
Infinity%+ Added
+ Added