KV 存储
Nitro 提供了一个内置存储层,可以抽象文件系统、数据库或任何其他数据源。
Nitro 内置了与 unstorage 的集成,以提供与运行时无关的持久化层:你的代码始终调用相同的键值 API,而底层驱动(内存、文件系统、Redis 等)则通过配置进行切换。
#用法
要使用存储层,可以使用 useKV() 工具函数访问存储实例。
import { useKV } from "nitro/kv";
// Default storage (in-memory)
await useKV().setItem("test:foo", { hello: "world" });
const value = await useKV().getItem("test:foo");
// You can specify a base prefix with useKV(base)
const testStorage = useKV("test");
await testStorage.setItem("foo", { hello: "world" });
await testStorage.getItem("foo"); // { hello: "world" }
// You can use generics to type the return value
await useKV<{ hello: string }>("test").getItem("foo");
await useKV("test").getItem<{ hello: string }>("foo");Note
useKV 之前名为 useStorage,并从 nitro/storage 导出;kv 配置选项之前名为 storage。旧名称仍可作为已弃用的别名使用。已弃用的 devStorage 选项已被 $development(以及 $prerender)中的 kv 替代。
#可用方法
useKV() 返回的存储实例提供以下方法:
| 方法 | 描述 |
|---|---|
getItem(key) | 获取键的值。如果键不存在则返回 null。 |
getItems(items) | 一次获取多个项目。接受键数组或 { key, options } 对象。 |
getItemRaw(key) | 获取键的原始值,不进行解析。适用于二进制数据。 |
setItem(key, value) | 设置键的值。 |
setItems(items) | 一次设置多个项目。接受 { key, value } 对象数组。 |
setItemRaw(key, value) | 设置键的原始值,不进行序列化。 |
hasItem(key) | 检查键是否存在。返回布尔值。 |
removeItem(key) | 从存储中移除键。 |
getKeys(base?) | 获取所有键,可选按基础前缀过滤。 |
clear(base?) | 清除所有键,可选按基础前缀过滤。 |
getMeta(key) | 获取键的元数据(例如 mtime、atime、ttl)。 |
setMeta(key, meta) | 设置键的元数据。 |
removeMeta(key) | 移除键的元数据。 |
mount(base, driver) | 在基础路径动态挂载存储驱动。 |
unmount(base) | 从基础路径卸载存储驱动。 |
watch(callback) | 监视变更。回调接收 (event, key),其中 event 为 "update" 或 "remove"。 |
unwatch() | 停止监视变更。 |
还提供了简写别名:get、set、has、del、remove、keys。
import { useKV } from "nitro/kv";
// Get all keys under a prefix
const keys = await useKV("test").getKeys();
// Check if a key exists
const exists = await useKV().hasItem("test:foo");
// Remove a key
await useKV().removeItem("test:foo");
// Get raw binary data
const raw = await useKV().getItemRaw("assets:server:image.png");
// Get metadata (type, etag, mtime, etc.)
const meta = await useKV("assets:server").getMeta("file.txt");#配置
你可以使用 kv 选项挂载一个或多个自定义存储驱动。
键是挂载点名称,值是驱动名称和配置。
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
redis: {
driver: "redis",
/* redis 连接器选项 */
}
}
})然后,你可以使用 useKV("redis") 函数来使用 Redis 存储。
Important
挂载选项会被序列化到构建输出中,因此必须是可进行 JSON 序列化的。如果挂载需要不可序列化的选项,请使用运行时配置。
#驱动依赖
某些驱动依赖第三方库(例如,redis 需要 ioredis)。
Nitro 会检测挂载驱动所需的库,并提示安装缺失的库(在 CI 中会自动安装)。安装的库随后会通过其 lib 选项显式传递给驱动,以便打包器能够静态解析它们。因此,lib 选项无法在 kv 挂载中配置(将其设置为 null 可选择不注入导入)。
#开发存储
你可以使用 $development 配置键在开发期间覆盖 kv 挂载。
当你的生产驱动在开发环境中不可用时(例如托管的 Redis 实例),这很有用。
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
db: {
driver: "redis",
host: "prod.example.com",
}
},
$development: {
kv: {
db: {
driver: "fs",
base: "./.data/db"
}
}
}
})在开发模式下运行时,$development.kv 挂载会合并到 kv 挂载之上,让你在开发时可以使用本地文件系统驱动或内存驱动。使用不同 driver 的挂载会替换整个挂载(上例中的 host 不会传递给 fs 驱动)。使用相同 driver 的挂载则会深度合并其选项。
$development 仅适用于开发服务器。在预渲染生产构建时,则会依次应用 $production 和 $prerender 覆盖项。使用 $prerender 仅在预渲染期间覆盖挂载:
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
db: { driver: "redis", host: "prod.example.com" }
},
$development: {
kv: { db: { driver: "fs", base: "./.data/db" } }
},
$prerender: {
kv: { db: { driver: "fs", base: "./.data/db" } }
}
})Tip
一种常见配置是在生产环境的 kv 中使用托管驱动(例如 redis),并在 $development.kv 中使用 fs 驱动,这样开发数据便于在本地 .data/ 中检查,也无需依赖外部服务。
#内置挂载点
Nitro 自动挂载以下存储路径:
#/assets
服务器资源挂载在 /assets 基础路径下(参见服务器资源)。在生产环境中,此挂载为只读,并且仅支持 getKeys()、hasItem()、getItem()、getItemRaw() 和 getMeta()。
import { useKV } from "nitro/kv";
// Access server assets via the /assets mount
const content = await useKV("assets:server").getItem("my-file.txt");#默认(内存中)
根存储(无基础路径)默认使用内存驱动。此处存储的数据在重启后不会持久化。
import { useKV } from "nitro/kv";
// In-memory by default, not persisted
await useKV().setItem("counter", 1);要持久化数据,请使用 kv 配置选项挂载具有持久化后端的驱动(例如 fs、redis 等)。
Note
缓存层也会将其条目存储在此根存储中(位于 cache: 前缀下),除非你配置了专用的 cache 挂载点,因此默认情况下,缓存数据在重启后也不会持久化。
#服务器资源
Nitro 允许你从 assets/ 目录中捆绑文件。这些文件可以在运行时通过 assets:server 存储挂载访问。
当设置了 serverDir 时,目录相对于该目录解析;否则相对于项目根目录解析:
my-project/
server/
assets/
data.json
templates/
welcome.html
routes/
index.tsimport { defineHandler } from "nitro";
import { useKV } from "nitro/kv";
export default defineHandler(async () => {
const serverAssets = useKV("assets:server");
const keys = await serverAssets.getKeys();
const data = await serverAssets.getItem("data.json");
const template = await serverAssets.getItem("templates/welcome.html");
return { keys, data, template };
});#自定义资源目录
你可以使用 serverAssets 配置选项注册其他资源目录。每个 dir 都相对于 rootDir(而不是 serverDir)解析:
import { defineConfig } from "nitro";
export default defineConfig({
serverAssets: [
{
baseName: "templates",
dir: "./templates",
}
]
})自定义资源目录可通过 assets:<baseName> 访问:
import { useKV } from "nitro/kv";
const templates = useKV("assets:templates");
const keys = await templates.getKeys();
const html = await templates.getItem("email.html");#资源元数据
服务器资源包含元数据,例如内容类型、ETag 和修改时间:
import { useKV } from "nitro/kv";
const serverAssets = useKV("assets:server");
const meta = await serverAssets.getMeta("image.png");
// { type: "image/png", etag: "\"...\"", mtime: "2024-01-01T00:00:00.000Z" }
// 适用于设置响应头
const raw = await serverAssets.getItemRaw("image.png");Note
在开发环境中(以及预渲染期间),服务器资源会直接使用 fs 驱动从文件系统读取,因此 getMeta() 返回的是文件系统统计信息(mtime、size 等),而不是 type 和 etag。在生产环境中,资源会被捆绑到构建输出中,其元数据会在构建时预先计算。
#运行时配置
在挂载点配置直到运行时才知道的场景中,Nitro 可以使用插件在启动期间动态添加挂载点。
import { useKV } from "nitro/kv";
import { definePlugin } from "nitro";
import redisDriver from "unstorage/drivers/redis";
export default definePlugin(() => {
const storage = useKV()
// Dynamically pass credentials from runtime config or elsewhere
const driver = redisDriver({
base: "redis",
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
// Pass the driver library explicitly so that it is bundled
lib: () => import("ioredis"),
/* other redis connector options */
})
// Mount the driver
storage.mount("redis", driver)
})Important
以这种方式挂载的驱动不在 Nitro 的驱动依赖检测范围内:请自行安装第三方库(上例中的 ioredis),并通过驱动的 lib 选项传入。如果不传入,驱动会回退到动态 import("ioredis"),而打包器无法静态解析该导入,导致挂载在运行时失败,并出现 Cannot import ioredis 错误。