From 85c7c061f26dedbd796f4b8369b0fd95ce939858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Chopin?= Date: Fri, 3 Mar 2023 14:15:11 +0100 Subject: [PATCH 1/2] chore(cache): improve documentation and cachedFunction default options --- .../content/1.guide/1.introduction/5.cache.md | 230 ++++++++++++++---- src/runtime/cache.ts | 4 +- 2 files changed, 183 insertions(+), 51 deletions(-) diff --git a/docs/content/1.guide/1.introduction/5.cache.md b/docs/content/1.guide/1.introduction/5.cache.md index 554468401b..99043a854f 100644 --- a/docs/content/1.guide/1.introduction/5.cache.md +++ b/docs/content/1.guide/1.introduction/5.cache.md @@ -1,93 +1,225 @@ # Cache API -Nitro provides a powerful caching system built on top of the storage layer. +Nitro provides a powerful caching system built on top of the [storage layer](/guide/introduction/storage). -## Usage +It stores the data in the `cache` mountpoint. +- In development, it will use the [FS driver](https://unstorage.unjs.io/drivers/fs) writting to `.nitro/cache` or `.nuxt/cache` if using [Nuxt](https://nuxt.com). +- In production, it will use the [memory driver](https://unstorage.unjs.io/drivers/memory) by default. + +To overwrite the production storage, set the `cache` mountpoint using the `storage` option: + +::code-group +```ts [nitro.config.ts] +import { defineNitroConfig } from "nitropack"; -```js -const cachedFn = cachedEventHandler(fn, options); +export default defineNitroConfig({ + storage: { + cache: { + driver: 'redis', + /* redis connector options */ + } + } +}) +``` +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + nitro: { + storage: { + cache: { + driver: 'redis', + /* redis connector options */ + } + } + } +}) ``` +:: -### Options +To overwrite the `cache` mountpoint in development, use the `devStorage` option to add the `cache` mountpoint. -- `name`: Handler name. It will be guessed from function name if not provided and fallback to `_` otherwise. -- `group`: Part of cache name. Useful to organize cache storage. -- `getKey`: A function that accepts same arguments of normal function and should generate cache key. If not provided, a built-in hash function will be used. -- `integrity`: A value that changing it, will invalidate all caches for function. By default will be computed from **function code**. -- `maxAge`: Maximum age that cache is valid in seconds. Default is `1` second. -- `staleMaxAge`: Maximum age that a stale cache is valid in seconds. If set to `-1` a stale value will still be sent to the client, while updating the cache in the background. -- `swr`: Enable Stale-While-Revalidate behavior. Enabled by default. -- `base`: Name of the storage mointpoint to use for caching (`/cache` by default) -- `shouldInvalidateCache`: A function that returns a boolean to invalidate the current cache and create a new one. -- `shouldBypassCache`: A function that returns a boolean to bypass the current cache without invalidating the existing entry. +## Usage + +::code-group +```ts [Router Handler] +// Cache an API handler +export default cachedEventHandler((event) => { + // My event handler +}, options); +``` +```ts [Function] +const myFn = cachedFunction(() => { + // My function +}, options); +``` +:: ## Examples -**Example:** Cache an API handler +If you come from [Nuxt](https://nuxt.com), all the examples below should be placed inside the `server/` directory. -```js -// routes/cached.ts -const myFn = cachedEventHandler( - async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - return `Response generated at ${new Date().toISOString()}`; - }, - { - swr: true, - } -); +### Route Handler + +Cache a route with [stale-while-revalidate](https://www.rfc-editor.org/rfc/rfc5861#section-3) behavior for 10 second: + +```ts [routes/cached.ts] +export default cachedEventHandler(async () => { + return `Response generated at ${new Date().toISOString()}`; +}, { + maxAge: 10 +}); ``` -**Example:** Cache a utility function +The response will be cached for 10 second and a stale value will be sent to the client while the cache is being updated in the background. -```js -// utils/index.ts -const myFn = cachedFunction( - async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - return Math.random(); - }, - { - swr: true, - } -); +The cached answer will be store in development inside `.nitro/cache/handlers/_/*.json`. + +### Function + +Cache for 1 hour the result of a function fetching the GitHub stars for a repository: + +::code-group +```ts [utils/github.ts] +export const cachedGHStars = cachedFunction(async (repo: string) => { + const data: any = await $fetch(`https://api.github.com/repos/${repo}`) + + return data.stargazers_count +}, { + maxAge: 60 * 60, + name: 'ghStars', + getKey: (repo: string) => repo +}) ``` +```ts [api/stars/[...repo].ts] +export default eventHandler(async (event) => { + const repo = event.context.params.repo + const stars = await cachedGHStars(repo).catch(() => 0) -**Example:** Enable Cache on a group of routes (**🧪 Experimental!**) + return { repo, stars } +}) +``` +:: + +The stars will be cached in development inside `.nitro/cache/functions/ghStars//.json` with `value` being the number of stars. + +```json +{"expires":1677851092249,"value":43991,"mtime":1677847492540,"integrity":"ZUHcsxCWEH"} +``` -```js -// nitro.config.ts + +## Route Rules + +This feature enable to add caching routes based on a glob pattern directly in the main configuration file. + + +::alert{type="primary"} +This features is still experimental and may evolve in the future. +:: + +Cache all the blog routes for 1 hour with `stale-while-revalidate` behavior: + +::code-group +```ts [nitro.config.ts] import { defineNitroConfig } from "nitropack"; export default defineNitroConfig({ routeRules: { "/blog/**": { - swr: true, + swr: 60 * 60, + // or + cache: { + maxAge: 60 * 60 + } }, }, }); ``` +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + routeRules: { + "/blog/**": { + swr: 60 * 60, + // or + cache: { + maxAge: 60 * 60 + } + }, + } +}); +``` +:: -**Example:** Set cache storage mountpoint for a group of routes (**🧪 Experimental!**) +If we want to use a custom storage mountpoint, we can use the `base` option. Let's store our cache result for the blog routes in a Redis storage for production: -```js -// nitro.config.ts +::code-group +```ts [nitro.config.ts] import { defineNitroConfig } from "nitropack"; export default defineNitroConfig({ storage: { - "my-custom-storage": { + redis: { driver: "redis", url: "redis://localhost:6379", }, }, routeRules: { "/blog/**": { - swr: true, + swr: 60 * 60, cache: { - base: "/my-custom-storage", + base: "redis", }, }, }, }); ``` +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + nitro: { + storage: { + redis: { + driver: "redis", + url: "redis://localhost:6379", + }, + }, + }, + routeRules: { + "/blog/**": { + swr: 60 * 60, + cache: { + base: "redis", + }, + }, + }, +}); +``` +:: + + +## Options + +The `cachedEventHandler` and `cachedFunction` functions accept the following options: +- `name`: Handler name. + - Type: `String` + - Default: guessed from function name if not provided and fallback to `_` otherwise. +- `group`: Part of cache name. Useful to organize cache storage. + - Type: `String` + - Default: `'nitro/handlers'` for handlers and `'nitro/functions'` for functions. +- `getKey`: A function that accepts the same arguments of the function and returns a cache key (`String`). + - Type: `Function` + - Default: If not provided, a built-in hash function will be used. +- `integrity`: A value that invalidates the cache when changed. + - Type: `String` + - Default: computed from **function code**, used in development to invalidate the cache when the function code changes. +- `maxAge`: Maximum age that cache is valid in seconds. + - Type: `Number` + - Default: `1` (second). +- `staleMaxAge`: Maximum age that a stale cache is valid in seconds. If set to `-1` a stale value will still be sent to the client, while updating the cache in the background. + - Type: `Number` + - Default: `0` (disabled). +- `swr`: Enable `stale-while-revalidate` behavior. + - Default: `true` +- `base`: Name of the storage mointpoint to use for caching. + - Default: `cache`. +- `shouldInvalidateCache`: A function that returns a `Boolean` to invalidate the current cache and create a new one. + - Type: `Function` +- `shouldBypassCache`: A function that returns a boolean to bypass the current cache without invalidating the existing entry. + - Type: `Function` diff --git a/src/runtime/cache.ts b/src/runtime/cache.ts index 925aa17324..bc59b2756e 100644 --- a/src/runtime/cache.ts +++ b/src/runtime/cache.ts @@ -41,14 +41,14 @@ const defaultCacheOptions = { export function defineCachedFunction( fn: (...args) => T | Promise, - opts: CacheOptions + opts: CacheOptions = {} ) { opts = { ...defaultCacheOptions, ...opts }; const pending: { [key: string]: Promise } = {}; // Normalize cache params - const group = opts.group || "nitro"; + const group = opts.group || "nitro/functions"; const name = opts.name || fn.name || "_"; const integrity = hash([opts.integrity, fn, opts]); const validate = opts.validate || (() => true); From f1931c8f1974ee5dce8b12810bad88f38b7f6f50 Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Fri, 3 Mar 2023 15:47:14 +0100 Subject: [PATCH 2/2] Update docs/content/1.guide/1.introduction/5.cache.md --- docs/content/1.guide/1.introduction/5.cache.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/1.guide/1.introduction/5.cache.md b/docs/content/1.guide/1.introduction/5.cache.md index 99043a854f..f31fd17cd4 100644 --- a/docs/content/1.guide/1.introduction/5.cache.md +++ b/docs/content/1.guide/1.introduction/5.cache.md @@ -65,7 +65,7 @@ Cache a route with [stale-while-revalidate](https://www.rfc-editor.org/rfc/rfc58 export default cachedEventHandler(async () => { return `Response generated at ${new Date().toISOString()}`; }, { - maxAge: 10 + swr: true, maxAge: 10 }); ```