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

Commit

Permalink
Add ability to use KV for persistent cache on workers
Browse files Browse the repository at this point in the history
  • Loading branch information
brawaru committed Jul 13, 2023
1 parent d0e9032 commit d3f688f
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 6 deletions.
92 changes: 92 additions & 0 deletions config/storages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { StorageMounts } from 'nitropack'

// https://nuxt.com/docs/api/configuration/nuxt-config
declare global {
namespace NodeJS {
interface ProcessEnv {
/** Preset used to build Nitro (provided manually). */
NITRO_PRESET?: string

/** Cloudflare KV via binding: name of the binding. */
CF_KV_BINDING_CACHE?: string

/** Vercel KV: token. */
KV_REST_API_TOKEN?: string
/** Vercel KV: API URL. */
KV_REST_API_URL?: string
/**
* Vercel KV: base name for cache KV.
* @default 'cache'
*/
VERCEL_KV_CACHE_BASE?: string

/** Cache storage option. */
CACHE_STORAGE_OPTION?: string
}
}
}

/**
* Checks that all environment variables are defined.
* @param vars Variables to check.
* @returns All missing variables.
*/
function getMissingVars(vars: string[]) {
return vars.filter((varName) => !process.env[varName])
}

/**
* Returns Nitro storage mounts or nothing.
*/
function getCacheStorageMount(): StorageMounts[string] | undefined {
switch (process.env.CACHE_STORAGE_OPTION) {
case 'cloudflare-kv': {
if (process.env.CF_KV_BINDING_CACHE) {
return {
driver: '~/server/storage/cached-cloudflare-kv-binding',
binding: process.env.CF_KV_BINDING_CACHE,
}
}

console.warn(
'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.'
)

break
}
case 'vercel-kv': {
const missingVars = getMissingVars(['KV_REST_API_TOKEN', 'KV_REST_API_URL'])

if (!missingVars.length) {
return {
driver: '~/server/storage/cached-vercel-kv',
base: process.env.VERCEL_KV_CACHE_BASE || 'cache',
url: process.env.KV_REST_API_URL,
token: process.env.KV_REST_API_TOKEN,
env: false,
}
}

console.log(
`You wanted to use \`vercel-kv\` cache store option, however you have not provided ${missingVars
.map((varName) => `\`${varName}\``)
.join(
', '
)} environment variable. The cache will use in-memory storage taht is not persistent in serverless functions.`
)

break
}
}

return undefined
}

export function getStorageMounts(): StorageMounts | undefined {
let mounts: StorageMounts | undefined

const cacheMount = getCacheStorageMount()
if (cacheMount != null) (mounts ??= {}).cache = cacheMount

return mounts
}
2 changes: 2 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { resolve, basename } from 'pathe'
import { defineNuxtConfig } from 'nuxt/config'
import { globIterate } from 'glob'
import { match as matchLocale } from '@formatjs/intl-localematcher'
import { getStorageMounts } from './config/storages.ts'

const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/'
const STAGING_ARIADNE_URL = 'https://staging-ariadne.modrinth.com/v1/'
Expand Down Expand Up @@ -232,6 +233,7 @@ export default defineNuxtConfig({
parserless: 'only-prod',
},
nitro: {
storage: getStorageMounts(),
moduleSideEffects: ['@vintl/compact-number/locale-data'],
},
})
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@types/node": "^20.1.0",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"@vercel/kv": "^0.2.2",
"@vintl/compact-number": "^2.0.4",
"@vintl/how-ago": "^2.0.1",
"@vintl/nuxt": "^1.2.3",
Expand Down
46 changes: 40 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions server/storage/cached-cloudflare-kv-binding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import cloudflareKVStorage, { KVOptions } from 'unstorage/drivers/cloudflare-kv-binding'
import { Driver } from 'unstorage'
import cachedDriver from './cached.ts'

export default function cachedVercelKV(opts: KVOptions): Driver {
return cachedDriver({ driver: cloudflareKVStorage(opts) })
}
7 changes: 7 additions & 0 deletions server/storage/cached-vercel-kv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import vercelStorage, { VercelKVOptions } from 'unstorage/drivers/vercel-kv'
import { Driver } from 'unstorage'
import cachedDriver from './cached.ts'

export default function cachedVercelKV(opts: VercelKVOptions): Driver {
return cachedDriver({ driver: vercelStorage(opts) })
}
31 changes: 31 additions & 0 deletions server/storage/cached.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Driver } from 'unstorage'
import memoryDriver from 'unstorage/drivers/memory'

export interface CachedOptions {
driver: Driver
}

export default function cached(options: CachedOptions): Driver {
const { driver } = options
const memory = memoryDriver() as Driver
return {
...driver,
name: driver.name ? `cached:${driver.name}` : `cached`,
options,
async hasItem(key) {
return (await memory.hasItem(key, {})) || (await driver.hasItem(key, {}))
},
async getItem(key) {
const memoryLookup = await memory.getItem(key)
if (memoryLookup !== null) return memoryLookup

const lookup = await driver.getItem(key)
memory.setItem!(key, lookup as any, {})
return lookup
},
async setItem(key, value) {
memory.setItem!(key, value, {})
await driver.setItem?.(key, value, {})
},
}
}

0 comments on commit d3f688f

Please sign in to comment.