diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 85f32757..16d37116 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -219,6 +219,10 @@ const sidebars = (): DefaultTheme.SidebarItem[] => [ text: '3rd-party Middleware', link: '/docs/middleware/third-party', }, + { + text: 'Universal Cache (3rd-party)', + link: '/docs/middleware/third-party/universal-cache', + }, ], }, { diff --git a/docs/middleware/third-party.md b/docs/middleware/third-party.md index aeed2a32..4fa63d08 100644 --- a/docs/middleware/third-party.md +++ b/docs/middleware/third-party.md @@ -82,4 +82,6 @@ Most of this middleware leverages external libraries. - [RONIN (Database)](https://github.com/ronin-co/hono-client) - [Session](https://github.com/honojs/middleware/tree/main/packages/session) - [tsyringe](https://github.com/honojs/middleware/tree/main/packages/tsyringe) +- [Universal Cache](https://github.com/honojs/middleware/tree/main/packages/universal-cache) +- [Universal Cache Guide](/docs/middleware/third-party/universal-cache) - [User Agent based Blocker](https://github.com/honojs/middleware/tree/main/packages/ua-blocker) diff --git a/docs/middleware/third-party/universal-cache.md b/docs/middleware/third-party/universal-cache.md new file mode 100644 index 00000000..02719c87 --- /dev/null +++ b/docs/middleware/third-party/universal-cache.md @@ -0,0 +1,223 @@ +# Universal Cache Middleware + +`@hono/universal-cache` is a third-party middleware package for response and function-level caching in Hono apps. + +It supports: + +- response caching with `cacheMiddleware()` +- request-scoped defaults with `cacheDefaults()` +- function result caching with `cacheFunction()` +- stale-while-revalidate (SWR) +- custom keying, serialization, and cache invalidation hooks + +## Install + +```txt +npm i @hono/universal-cache +``` + +## Import + +```ts +import { Hono } from 'hono' +import { + cacheDefaults, + cacheFunction, + cacheMiddleware, +} from '@hono/universal-cache' +``` + +## Default Behavior + +Universal Cache uses an in-memory `unstorage` driver by default, with these defaults: + +```ts +const baseDefaults = { + base: 'cache', + maxAge: 1, + staleMaxAge: 0, + swr: true, + keepPreviousOn5xx: true, + revalidateHeader: 'x-cache-revalidate', +} + +const middlewareDefaults = { + ...baseDefaults, + group: 'hono/handlers', + methods: ['GET', 'HEAD'], + varies: [], +} + +const functionDefaults = { + ...baseDefaults, + group: 'hono/functions', +} +``` + +Default key strategy: + +- `cacheMiddleware()`: hash of `pathname + search`, plus hashed `varies` header values +- `cacheFunction()`: hash of serialized function arguments +- middleware name default: normalized request path (e.g. `/api/items` -> `api:items`) +- function name default: `fn.name` (fallback: `_`) +- integrity default: auto-derived hash (middleware by cache namespace, function by function source) + +## Usage + +### Cache route responses + +```ts +const app = new Hono() + +app.get( + '/api/items', + cacheMiddleware({ + maxAge: 60, + staleMaxAge: 30, + swr: true, + }), + (c) => c.json({ items: ['a', 'b'] }) +) +``` + +### Set global cache defaults + +```ts +app.use( + '*', + cacheDefaults({ + maxAge: 60, + staleMaxAge: 30, + swr: true, + }) +) +``` + +### Use storage adapters (via unstorage) + +```ts +import { createStorage } from 'unstorage' +import redisDriver from 'unstorage/drivers/redis' + +app.use( + '*', + cacheDefaults({ + storage: createStorage({ + driver: redisDriver({ url: 'redis://localhost:6379' }), + }), + maxAge: 60, + staleMaxAge: 30, + swr: true, + }) +) +``` + +### Override global defaults per route + +```ts +app.get( + '/api/slow/items', + cacheMiddleware({ + config: { maxAge: 300, staleMaxAge: 120 }, + varies: ['accept-language'], + }), + (c) => c.json({ ok: true }) +) +``` + +### Cache function results + +```ts +const getStats = cacheFunction( + async (id: string) => { + return { id, ts: Date.now() } + }, + { + maxAge: 60, + getKey: (id) => id, + } +) +``` + +## Revalidation and Invalidation + +By default, route cache can be manually revalidated by sending: + +```txt +x-cache-revalidate: 1 +``` + +You can also use hooks such as: + +- `shouldBypassCache` +- `shouldInvalidateCache` + +for custom logic. + +## Options Summary + +Base options (`cacheMiddleware` + `cacheFunction`): + +- `storage`: custom unstorage instance +- `base`: storage prefix (default: `cache`) +- `group`: storage group segment (default middleware: `hono/handlers`, function: `hono/functions`) +- `name`: cache entry name (default inferred from route/function name) +- `hash`: custom hash function for default keys/integrity +- `integrity`: manual integrity value for cache busting +- `maxAge`: max age in seconds (default: `1`) +- `staleMaxAge`: stale max age in seconds, `-1` means unlimited stale (default: `0`) +- `swr`: enable stale-while-revalidate (default: `true`) +- `keepPreviousOn5xx`: keep previous entry when refresh fails in invalidation paths (default: `true`) +- `revalidateHeader`: revalidation header name (default: `x-cache-revalidate`) + +Middleware-only: + +- `config`: request-scoped defaults to apply before route options +- `methods`: cacheable methods (default: `GET`, `HEAD`) +- `varies`: request headers to include in key +- `getKey`: custom request key builder +- `serialize` / `deserialize`: custom response cache format +- `validate`: validate cached response entries +- `shouldBypassCache`: skip cache for matching requests +- `shouldInvalidateCache`: invalidate entry before refresh + +Function-only: + +- `getKey`: custom key builder from function args +- `serialize` / `deserialize`: custom function entry format +- `validate`: validate cached function entries +- `shouldBypassCache`: skip cache for matching calls +- `shouldInvalidateCache`: invalidate entry before refresh + +## Key APIs + +### `cacheMiddleware(options | maxAge)` + +Caches route responses (default methods: `GET`, `HEAD`). + +### `cacheDefaults(options)` + +Sets request-scoped default options for cache middleware. + +### `cacheFunction(fn, options | maxAge)` + +Wraps a function with caching behavior. + +### Storage/default helpers + +- `createCacheStorage()` +- `setCacheStorage()` / `getCacheStorage()` +- `setCacheDefaults()` / `getCacheDefaults()` + +## Notes + +- Cached route responses drop `set-cookie` and hop-by-hop headers. +- Responses with `cache-control: no-store` or `no-cache` are not cached. +- Cached function values should be serializable for your selected storage driver. + +## Links + +- Source package: + `https://github.com/honojs/middleware/tree/main/packages/universal-cache` +- Third-party middleware index: + `/docs/middleware/third-party`