Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(cache): improve documentation and cachedFunction default options #1011

Merged
merged 2 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 181 additions & 49 deletions docs/content/1.guide/1.introduction/5.cache.md
Original file line number Diff line number Diff line change
@@ -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
pi0 marked this conversation as resolved.
Show resolved Hide resolved
});
```

**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/<owner>/<repo>.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`
4 changes: 2 additions & 2 deletions src/runtime/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ const defaultCacheOptions = {

export function defineCachedFunction<T = any>(
fn: (...args) => T | Promise<T>,
opts: CacheOptions<T>
opts: CacheOptions<T> = {}
) {
opts = { ...defaultCacheOptions, ...opts };

const pending: { [key: string]: Promise<T> } = {};

// Normalize cache params
const group = opts.group || "nitro";
const group = opts.group || "nitro/functions";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is breaking change as invalidates all current prod deployment namespaces πŸ˜…

const name = opts.name || fn.name || "_";
const integrity = hash([opts.integrity, fn, opts]);
const validate = opts.validate || (() => true);
Expand Down