{"id":47786108,"url":"https://github.com/unjs/ocache","last_synced_at":"2026-06-10T19:00:24.070Z","repository":{"id":343336674,"uuid":"1177290530","full_name":"unjs/ocache","owner":"unjs","description":"Standalone caching utilities with TTL, SWR, and HTTP response caching","archived":false,"fork":false,"pushed_at":"2026-06-08T07:51:04.000Z","size":143,"stargazers_count":54,"open_issues_count":11,"forks_count":6,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-08T09:24:51.767Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/unjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-09T22:15:23.000Z","updated_at":"2026-05-18T01:12:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/unjs/ocache","commit_stats":null,"previous_names":["unjs/ocache"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/unjs/ocache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Focache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Focache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Focache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Focache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/unjs","download_url":"https://codeload.github.com/unjs/ocache/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unjs%2Focache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34165482,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-03T14:40:56.175Z","updated_at":"2026-06-10T19:00:24.058Z","avatar_url":"https://github.com/unjs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ocache\n\n\u003c!-- automd:badges color=yellow --\u003e\n\n[![npm version](https://img.shields.io/npm/v/ocache?color=yellow)](https://npmjs.com/package/ocache)\n[![npm downloads](https://img.shields.io/npm/dm/ocache?color=yellow)](https://npm.chart.dev/ocache)\n\n\u003c!-- /automd --\u003e\n\n## Usage\n\n### Caching Functions\n\nWrap any function with `defineCachedFunction` to add caching with TTL, stale-while-revalidate, and request deduplication:\n\n```ts\nimport { defineCachedFunction } from \"ocache\";\n\nconst cachedFetch = defineCachedFunction(\n  async (url: string) =\u003e {\n    const res = await fetch(url);\n    return res.json();\n  },\n  {\n    maxAge: 60, // Cache for 60 seconds\n    name: \"api-fetch\",\n  },\n);\n\n// First call hits the function, subsequent calls return cached result\nconst data = await cachedFetch(\"https://api.example.com/data\");\n```\n\n#### Options\n\n```ts\nconst cached = defineCachedFunction(fn, {\n  name: \"my-fn\", // Cache key name (defaults to function name)\n  maxAge: 10, // TTL in seconds (default: 1)\n  swr: true, // Stale-while-revalidate (default: true)\n  staleMaxAge: 60, // Max seconds to serve stale content\n  base: \"/cache\", // Base prefix for cache keys (string or string[] for multi-tier)\n  group: \"my-group\", // Cache key group (default: \"functions\")\n  getKey: (...args) =\u003e \"custom-key\", // Custom cache key generator\n  shouldBypassCache: (...args) =\u003e false, // Skip cache entirely when true\n  shouldInvalidateCache: (...args) =\u003e false, // Force refresh when true\n  validate: (entry) =\u003e entry.value !== undefined, // Custom validation\n  transform: (entry) =\u003e entry.value, // Transform before returning\n  onError: (error) =\u003e console.error(error), // Error handler\n});\n```\n\n### Caching HTTP Handlers\n\nWrap HTTP handlers with `defineCachedHandler` for automatic response caching with `etag`, `last-modified`, and `304 Not Modified` support:\n\n```ts\nimport { defineCachedHandler } from \"ocache\";\n\nconst handler = defineCachedHandler(\n  async (event) =\u003e {\n    // event.req is a standard Request object\n    const url = event.url ?? new URL(event.req.url);\n    const data = await getExpensiveData(url.pathname);\n    return new Response(JSON.stringify(data), {\n      headers: { \"content-type\": \"application/json\" },\n    });\n  },\n  {\n    maxAge: 300, // Cache for 5 minutes\n    swr: true,\n    staleMaxAge: 600,\n    varies: [\"accept-language\"], // Vary cache by these headers\n  },\n);\n```\n\n#### Headers-only Mode\n\nUse `headersOnly` to handle conditional requests without caching the full response:\n\n```ts\nconst handler = defineCachedHandler(myHandler, {\n  headersOnly: true,\n  maxAge: 60,\n});\n```\n\n### Cache Invalidation\n\nCached functions have an `.invalidate()` method that removes cached entries across all base prefixes:\n\n```ts\nimport { defineCachedFunction } from \"ocache\";\n\nconst getUser = defineCachedFunction(async (id: string) =\u003e db.users.find(id), {\n  name: \"getUser\",\n  maxAge: 60,\n  getKey: (id: string) =\u003e id,\n});\n\nconst user = await getUser(\"user-123\");\n\n// Invalidate a specific entry\nawait getUser.invalidate(\"user-123\");\n\n// Next call will re-invoke the function\nconst freshUser = await getUser(\"user-123\");\n```\n\nYou can also use the standalone `invalidateCache()` when you don't have a reference to the cached function — just pass the same options:\n\n```ts\nimport { invalidateCache } from \"ocache\";\n\nawait invalidateCache({\n  options: { name: \"getUser\", getKey: (id: string) =\u003e id },\n  args: [\"user-123\"],\n});\n```\n\nFor advanced use cases, `.resolveKeys()` returns the raw storage keys:\n\n```ts\nconst keys = await getUser.resolveKeys(\"user-123\");\n// [\"/cache:functions:getUser:user-123.json\"]\n```\n\n### Cache Expiration (SWR refresh)\n\nWhile `.invalidate()` removes an entry entirely (the next call must wait for a fresh value), `.expire()` only marks it as stale. With SWR enabled, stale values keep being served — still bounded by the originally configured `staleMaxAge` window — and the next access triggers a background refresh:\n\n```ts\n// Mark the entry stale: next call serves the stale value and refetches in the background\nawait getUser.expire(\"user-123\");\n```\n\nThe standalone `expireCache()` works like `invalidateCache()` — pass the same `maxAge` / `swr` / `staleMaxAge` options you cache with so the remaining storage TTL is preserved:\n\n```ts\nimport { expireCache } from \"ocache\";\n\nawait expireCache({\n  options: { name: \"getUser\", getKey: (id: string) =\u003e id, maxAge: 60, staleMaxAge: 300 },\n  args: [\"user-123\"],\n});\n```\n\n### Multi-tier Caching\n\nUse an array of `base` prefixes to enable multi-tier caching. On read, each prefix is tried in order and the first hit is used. On write, the entry is written to all prefixes:\n\n```ts\nconst cachedFetch = defineCachedFunction(\n  async (url: string) =\u003e {\n    const res = await fetch(url);\n    return res.json();\n  },\n  {\n    maxAge: 60,\n    base: [\"/tmp\", \"/cache\"],\n  },\n);\n```\n\nThis is useful for layered cache setups (e.g., fast local cache + shared remote cache) where you want reads to prefer the nearest tier while keeping all tiers populated on writes.\n\n### Custom Storage\n\nBy default, ocache uses an in-memory `Map`-based storage. You can provide a custom storage implementation:\n\n```ts\nimport { setStorage } from \"ocache\";\nimport type { StorageInterface } from \"ocache\";\n\nconst redisStorage: StorageInterface = {\n  get: async (key) =\u003e {\n    return JSON.parse(await redis.get(key));\n  },\n  set: async (key, value, opts) =\u003e {\n    // Setting null/undefined deletes the entry (used for cache invalidation)\n    if (value === null || value === undefined) {\n      await redis.del(key);\n      return;\n    }\n    await redis.set(key, JSON.stringify(value), opts?.ttl ? { EX: opts.ttl } : undefined);\n  },\n};\n\nsetStorage(redisStorage);\n```\n\n## API\n\n\u003c!-- automd:docs4ts --\u003e\n\n### `cachedFunction`\n\n```ts\nconst cachedFunction = defineCachedFunction;\n```\n\nAlias for [`defineCachedFunction`](#definecachedfunction).\n\n---\n\n### `createMemoryStorage`\n\n```ts\nfunction createMemoryStorage(): StorageInterface;\n```\n\nCreates an in-memory storage backed by a `Map` with optional TTL support (in seconds).\n\n---\n\n### `defineCachedFunction`\n\n```ts\nfunction defineCachedFunction\u003cT, ArgsT extends unknown[] = any[]\u003e(\n  fn: (...args: ArgsT) =\u003e T | Promise\u003cT\u003e,\n  opts: CacheOptions\u003cT, ArgsT\u003e =\n```\n\nWraps a function with caching support including TTL, SWR, integrity checks, and request deduplication.\n\n**Parameters:**\n\n- **`fn`** — The function to cache.\n- **`opts`** — Cache configuration options.\n\n**Returns:** — A cached function with a `.resolveKey(...args)` method for cache key resolution.\n\n---\n\n### `defineCachedHandler`\n\n```ts\nfunction defineCachedHandler\u003cE extends HTTPEvent = HTTPEvent\u003e(\n  handler: EventHandler\u003cE\u003e,\n  opts: CachedEventHandlerOptions\u003cE\u003e =\n```\n\nWraps an HTTP event handler with response caching.\n\nAutomatically generates cache keys from the URL path and variable headers,\nsets `cache-control`, `etag`, and `last-modified` headers, and handles\n`304 Not Modified` responses via conditional request headers.\n\n**Parameters:**\n\n- **`handler`** — The event handler to cache.\n- **`opts`** — Cache and HTTP-specific configuration options.\n\n**Returns:** — A new event handler that serves cached responses when available.\n\n---\n\n### `EventHandler`\n\n```ts\ntype EventHandler\u003cE extends HTTPEvent = HTTPEvent\u003e = (\n```\n\nHandler function that receives an [`HTTPEvent`](#httpevent) and returns a response value.\n\n---\n\n### `expireCache`\n\n```ts\nasync function expireCache\u003cArgsT extends unknown[] = any[]\u003e(\n  input:\n```\n\nExpires cached entries for given arguments and cache options across all base prefixes,\nwithout removing them.\n\nUnlike [`invalidateCache`](#invalidatecache) (which removes entries entirely), expired entries keep\nserving the stale value with SWR — still bounded by the originally configured\n`staleMaxAge` window — while the next access triggers a background refresh.\nWithout SWR, the next call re-resolves before returning.\n\nUses the same key derivation as `defineCachedFunction` / `resolveCacheKeys`.\nPass the same `maxAge` / `swr` / `staleMaxAge` options you cache with so the\nremaining storage TTL is preserved.\n\n**Parameters:**\n\n- **`input`** — Object with `options` (cache options) and optional `args` (function arguments).\n\n**Example:**\n\n```ts\n// Mark a cached entry for background refresh on next access\nawait expireCache({\n  options: { name: \"fetchUser\", getKey: (id: string) =\u003e id, maxAge: 60, staleMaxAge: 300 },\n  args: [\"user-123\"],\n});\n```\n\n---\n\n### `invalidateCache`\n\n```ts\nasync function invalidateCache\u003cArgsT extends unknown[] = any[]\u003e(\n  input:\n```\n\nInvalidates (removes) cached entries for given arguments and cache options across all base prefixes.\n\nUses the same key derivation as `defineCachedFunction` / `resolveCacheKeys`.\n\n**Parameters:**\n\n- **`input`** — Object with `options` (cache options) and optional `args` (function arguments).\n\n**Example:**\n\n```ts\n// Invalidate a specific cached entry\nawait invalidateCache({\n  options: { name: \"fetchUser\", getKey: (id: string) =\u003e id },\n  args: [\"user-123\"],\n});\n```\n\n---\n\n### `resolveCacheKeys`\n\n```ts\nasync function resolveCacheKeys\u003cArgsT extends unknown[] = any[]\u003e(\n  input:\n```\n\nResolves all cache storage keys (one per base prefix) for given arguments and cache options.\n\nUses the same key derivation as `defineCachedFunction` internally:\n\n- When `opts.getKey` is provided, it is called with `args` to produce the key segment.\n- Otherwise, `args` are hashed with `ohash` (same default as `defineCachedFunction`).\n\nPass the same `getKey`, `name`, `group`, and `base` options you use in\n`defineCachedFunction` / `defineCachedHandler` to get the exact storage keys.\n\n**Parameters:**\n\n- **`input`** — Object with `options` (cache options) and optional `args` (function arguments).\n\n**Returns:** — An array of storage key strings (one per base prefix).\n\n**Example:**\n\n```ts\nconst keys = await resolveCacheKeys({\n  options: { name: \"fetchUser\", getKey: (id: string) =\u003e id },\n  args: [\"user-123\"],\n});\nfor (const key of keys) {\n  await useStorage().set(key, null); // invalidate all tiers\n}\n```\n\n---\n\n### `setStorage`\n\n```ts\nfunction setStorage(storage: StorageInterface): void;\n```\n\nSets a custom storage implementation to be used by all cached functions.\n\n---\n\n### `useStorage`\n\n```ts\nfunction useStorage(): StorageInterface;\n```\n\nReturns the current storage instance. If none has been set via `setStorage`, lazily initializes an in-memory storage.\n\n\u003c!-- /automd--\u003e\n\n## Development\n\n\u003cdetails\u003e\n\n\u003csummary\u003elocal development\u003c/summary\u003e\n\n- Clone this repository\n- Install latest LTS version of [Node.js](https://nodejs.org/en/)\n- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`\n- Install dependencies using `pnpm install`\n- Run interactive tests using `pnpm dev`\n\n\u003c/details\u003e\n\n## License\n\nPublished under the [MIT](https://github.com/unjs/ocache/blob/main/LICENSE) license 💛.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funjs%2Focache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funjs%2Focache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funjs%2Focache/lists"}