Skip to content
This repository was archived by the owner on Aug 3, 2024. It is now read-only.

Commit fe32f67

Browse files
committed
Add ability to use KV for persistent cache on workers
1 parent 0225e8c commit fe32f67

7 files changed

+182
-8
lines changed

config/storages.ts

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import type { StorageMounts } from 'nitropack'
2+
3+
// https://nuxt.com/docs/api/configuration/nuxt-config
4+
declare global {
5+
namespace NodeJS {
6+
interface ProcessEnv {
7+
/** Preset used to build Nitro (provided manually). */
8+
NITRO_PRESET?: string
9+
10+
/** Cloudflare KV via binding: name of the binding. */
11+
CF_KV_BINDING_CACHE?: string
12+
13+
/** Vercel KV: token. */
14+
KV_REST_API_TOKEN?: string
15+
/** Vercel KV: API URL. */
16+
KV_REST_API_URL?: string
17+
/**
18+
* Vercel KV: base name for cache KV.
19+
* @default 'cache'
20+
*/
21+
VERCEL_KV_CACHE_BASE?: string
22+
23+
/** Cache storage option. */
24+
CACHE_STORAGE_OPTION?: string
25+
}
26+
}
27+
}
28+
29+
/**
30+
* Checks that all environment variables are defined.
31+
* @param vars Variables to check.
32+
* @returns All missing variables.
33+
*/
34+
function getMissingVars(vars: string[]) {
35+
return vars.filter((varName) => !process.env[varName])
36+
}
37+
38+
/**
39+
* Returns Nitro storage mounts or nothing.
40+
*/
41+
function getCacheStorageMount(): StorageMounts[string] | undefined {
42+
switch (process.env.CACHE_STORAGE_OPTION) {
43+
case 'cloudflare-kv': {
44+
if (process.env.CF_KV_BINDING_CACHE) {
45+
return {
46+
driver: '~/server/storage/cached-cloudflare-kv-binding',
47+
binding: process.env.CF_KV_BINDING_CACHE,
48+
}
49+
}
50+
51+
console.warn(
52+
'You wanted to use `cloudflare-kv` cache store option, however you have not provided `CF_KV_BINDING_CACHE` environment variable. The cache will use in-memory storage that is not persistent in workers.'
53+
)
54+
55+
break
56+
}
57+
case 'vercel-kv': {
58+
const missingVars = getMissingVars(['KV_REST_API_TOKEN', 'KV_REST_API_URL'])
59+
60+
if (!missingVars.length) {
61+
return {
62+
driver: '~/server/storage/cached-vercel-kv',
63+
base: process.env.VERCEL_KV_CACHE_BASE || 'cache',
64+
url: process.env.KV_REST_API_URL,
65+
token: process.env.KV_REST_API_TOKEN,
66+
env: false,
67+
}
68+
}
69+
70+
console.log(
71+
`You wanted to use \`vercel-kv\` cache store option, however you have not provided ${missingVars
72+
.map((varName) => `\`${varName}\``)
73+
.join(
74+
', '
75+
)} environment variable. The cache will use in-memory storage taht is not persistent in serverless functions.`
76+
)
77+
78+
break
79+
}
80+
}
81+
82+
return undefined
83+
}
84+
85+
export function getStorageMounts(): StorageMounts | undefined {
86+
let mounts: StorageMounts | undefined
87+
88+
const cacheMount = getCacheStorageMount()
89+
if (cacheMount != null) (mounts ??= {}).cache = cacheMount
90+
91+
return mounts
92+
}

nuxt.config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { resolve, basename } from 'pathe'
55
import { defineNuxtConfig } from 'nuxt/config'
66
import { globIterate } from 'glob'
77
import { match as matchLocale } from '@formatjs/intl-localematcher'
8+
import { getStorageMounts } from './config/storages.ts'
89

910
const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/'
1011

@@ -249,6 +250,7 @@ export default defineNuxtConfig({
249250
siteKey: '0x4AAAAAAAHWfmKCm7cUG869',
250251
},
251252
nitro: {
253+
storage: getStorageMounts(),
252254
moduleSideEffects: ['@vintl/compact-number/locale-data'],
253255
},
254256
devtools: {

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@types/node": "^20.1.0",
2020
"@typescript-eslint/eslint-plugin": "^5.59.8",
2121
"@typescript-eslint/parser": "^5.59.8",
22+
"@vercel/kv": "^0.2.2",
2223
"@vintl/compact-number": "^2.0.4",
2324
"@vintl/how-ago": "^2.0.1",
2425
"@vintl/nuxt": "^1.3.0",

pnpm-lock.yaml

+42-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import cloudflareKVStorage, { KVOptions } from 'unstorage/drivers/cloudflare-kv-binding'
2+
import { Driver } from 'unstorage'
3+
import cachedDriver from './cached.ts'
4+
5+
export default function cachedVercelKV(opts: KVOptions): Driver {
6+
return cachedDriver({ driver: cloudflareKVStorage(opts) })
7+
}

server/storage/cached-vercel-kv.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import vercelStorage, { VercelKVOptions } from 'unstorage/drivers/vercel-kv'
2+
import { Driver } from 'unstorage'
3+
import cachedDriver from './cached.ts'
4+
5+
export default function cachedVercelKV(opts: VercelKVOptions): Driver {
6+
return cachedDriver({ driver: vercelStorage(opts) })
7+
}

server/storage/cached.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Driver } from 'unstorage'
2+
import memoryDriver from 'unstorage/drivers/memory'
3+
4+
export interface CachedOptions {
5+
driver: Driver
6+
}
7+
8+
export default function cached(options: CachedOptions): Driver {
9+
const { driver } = options
10+
const memory = memoryDriver() as Driver
11+
return {
12+
...driver,
13+
name: driver.name ? `cached:${driver.name}` : `cached`,
14+
options,
15+
async hasItem(key) {
16+
return (await memory.hasItem(key, {})) || (await driver.hasItem(key, {}))
17+
},
18+
async getItem(key) {
19+
const memoryLookup = await memory.getItem(key)
20+
if (memoryLookup !== null) return memoryLookup
21+
22+
const lookup = await driver.getItem(key)
23+
memory.setItem!(key, lookup as any, {})
24+
return lookup
25+
},
26+
async setItem(key, value) {
27+
memory.setItem!(key, value, {})
28+
await driver.setItem?.(key, value, {})
29+
},
30+
}
31+
}

0 commit comments

Comments
 (0)