EmDash 支持多种数据库后端。根据你的部署目标进行选择。
概览
| 数据库 | 适用场景 | 部署方式 |
|---|---|---|
| D1 | Cloudflare Workers | 边缘,全球分布 |
| Hyperdrive | Cloudflare Workers 上的 PostgreSQL | 边缘,现有 Postgres |
| PostgreSQL | Node.js 生产环境 | 任何支持 Postgres 的平台 |
| libSQL | 远程数据库 | 边缘或 Node.js |
| SQLite | Node.js,本地开发 | 单服务器 |
Cloudflare D1
D1 是 Cloudflare 的无服务器 SQLite 数据库。在部署到 Cloudflare Workers 时使用。
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({ binding: "DB" }),
}),
],
});
配置
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
binding | string | — | wrangler.jsonc 中的 D1 绑定名称 |
session | string | "disabled" | 读取副本模式(见下文) |
bookmarkCookie | string | "__em_d1_bookmark" | 会话书签的 Cookie 名称 |
设置
wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "emdash-db"
}
]
} wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "emdash-db" 读取副本
D1 支持读取复制以降低全球分布站点的读取延迟。启用后,读取查询会路由到附近的副本,而不是始终查询主数据库。
EmDash 使用 D1 Sessions API 透明地管理此功能。使用 session 选项启用:
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({
binding: "DB",
session: "auto",
}),
}),
],
});
会话模式
| 模式 | 行为 |
|---|---|
"disabled" | 无会话。所有查询都发送到主数据库。默认值。 |
"auto" | 匿名请求从最近的副本读取。认证用户通过书签 Cookie 获得 read-your-writes 一致性。 |
"primary-first" | 与 "auto" 相同,但第一个查询始终发送到主数据库。适用于写入非常频繁的站点。 |
工作原理
- 匿名访客获得
first-unconstrained— 读取从最近的副本获取以实现最低延迟。由于匿名用户从不写入,因此不需要一致性保证。 - 认证用户(编辑者、作者)获得基于书签的会话。写入后,书签 Cookie 确保下一个请求至少看到该状态。
- 写入请求(
POST、PUT、DELETE)始终从主数据库开始。 - 构建时查询(Astro 内容集合)完全绕过会话,直接使用主数据库。
libSQL
libSQL 是 SQLite 的一个分支,支持远程连接。当你需要远程数据库但不使用 Cloudflare D1 时使用。
import { libsql } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
}),
}),
],
});
配置
| 选项 | 类型 | 描述 |
|---|---|---|
url | string | 数据库 URL(libsql://... 或 file:...) |
authToken | string | 远程数据库的认证令牌(本地可选) |
本地开发
开发时使用本地 libSQL 文件:
database: libsql({ url: "file:./data.db" });
PostgreSQL
PostgreSQL 支持需要完整关系数据库的 Node.js 部署。
import { postgres } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: postgres({
connectionString: process.env.DATABASE_URL,
}),
}),
],
});
配置
你可以使用连接字符串或单独的参数进行连接:
// 连接字符串
database: postgres({
connectionString: "postgres://user:password@localhost:5432/emdash",
});
// 单独参数
database: postgres({
host: "localhost",
port: 5432,
database: "emdash",
user: "emdash",
password: process.env.DB_PASSWORD,
ssl: true,
});
| 选项 | 类型 | 描述 |
|---|---|---|
connectionString | string | PostgreSQL 连接 URL |
host | string | 数据库主机 |
port | number | 数据库端口 |
database | string | 数据库名称 |
user | string | 数据库用户 |
password | string | 数据库密码 |
ssl | boolean | 启用 SSL |
pool.min | number | 连接池最小连接数(默认 0) |
pool.max | number | 连接池最大连接数(默认 10) |
连接池
适配器内部使用 pg.Pool。根据你的部署调整池大小:
database: postgres({
connectionString: process.env.DATABASE_URL,
pool: { min: 2, max: 20 },
});
Hyperdrive
使用 hyperdrive() 适配器在 Cloudflare Workers 上运行 EmDash,连接现有的 PostgreSQL — 或 Postgres 兼容(如 PlanetScale Postgres)— 数据库。Hyperdrive 通过 Cloudflare 网络池化和加速连接;EmDash 的 PostgreSQL 方言执行查询。
import { hyperdrive, r2 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: hyperdrive({ binding: "HYPERDRIVE" }),
storage: r2({ binding: "MEDIA" }),
}),
],
});
要求
- 站点中安装
pg >= 8.16.3(pnpm add pg) compatibility_flags: ["nodejs_compat"]compatibility_date >= "2024-09-23"
设置
创建 Hyperdrive 配置并将绑定添加到你的 Wrangler 配置:
wrangler hyperdrive create emdash-db \
--connection-string "postgres://user:password@host/db?sslmode=verify-full" \
--caching-disabled
wrangler.jsonc
{
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<your-hyperdrive-id>"
}
]
} wrangler.toml
[[hyperdrive]]
binding = "HYPERDRIVE"
id = "<your-hyperdrive-id>" 配置
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
binding | string | "HYPERDRIVE" | 主(缓存禁用)Hyperdrive 绑定 |
cachedBinding | string | — | 用于匿名读取的可选缓存启用绑定 |
max | number | 5 | Worker 内到 Hyperdrive 的连接池最大大小 |
从缓存提供匿名读取
默认情况下,你完全禁用 Hyperdrive 缓存,因为管理后台和写入需要 read-after-write 一致性。但匿名公共读取 — 无会话、无写入 — 可以容忍短暂的过时数据窗口。如果这个权衡可以接受,运行同一数据库上的两个 Hyperdrive 配置:一个禁用缓存(主 binding),一个启用缓存(cachedBinding)。EmDash 将匿名读取请求通过缓存启用的绑定路由,而所有认证请求和写入保持在无缓存的主绑定上,保持 read-after-write 一致性。
# 主 — 缓存禁用(管理、认证请求、写入、迁移)
wrangler hyperdrive create emdash-db \
--connection-string "postgres://user:password@host/db?sslmode=verify-full" \
--caching-disabled
# 缓存启用 — 相同连接字符串,缓存启用(仅匿名读取)
wrangler hyperdrive create emdash-db-cached \
--connection-string "postgres://user:password@host/db?sslmode=verify-full"
{
"hyperdrive": [
{ "binding": "HYPERDRIVE", "id": "<caching-disabled-id>" },
{ "binding": "HYPERDRIVE_CACHED", "id": "<caching-enabled-id>" }
]
}
database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" });
这是 Cloudflare 为缓存文档化的双配置模式。EmDash 按请求决定使用哪个绑定:
- 公共站点路径的匿名读取(
GET/HEAD、无会话、不在/_emdash下)→ 缓存启用的cachedBinding。 - 认证请求(编辑者、作者)→ 无缓存
binding。 - 写入(
POST、PUT、DELETE,包括匿名)→ 无缓存binding。 /_emdash下的任何请求(管理、设置、认证、内部 API),即使是匿名GET→ 无缓存binding。- 迁移和冷启动 → 始终主
binding。
SQLite
使用 better-sqlite3 的 SQLite 是 Node.js 部署最简单的选项。
import { sqlite } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
}),
],
});
配置
| 选项 | 类型 | 描述 |
|---|---|---|
url | string | 带 file: 前缀的文件路径 |
文件路径
url 必须以 file: 开头:
// 相对路径
database: sqlite({ url: "file:./data/emdash.db" });
// 绝对路径
database: sqlite({ url: "file:/var/data/emdash.db" });
// 从环境变量
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });
迁移
EmDash 在每个支持的方言(D1、SQLite、libSQL、PostgreSQL)的第一次请求时自动运行迁移。迁移打包在 emdash 包中并嵌入到你的构建中。
如果数据库为空(没有集合)且设置向导未完成,EmDash 还会在首次启动时应用种子文件。种子从 .emdash/seed.json、package.json#emdash.seed 中的路径或 seed/seed.json 读取 — 取最先找到的 — 并在编译时嵌入构建。如果都不存在,则使用内置的默认种子。后续针对现有数据库的启动不会更改其内容。
基于环境的配置
按环境使用不同的数据库:
import { sqlite, libsql, postgres } from "emdash/db";
import { d1 } from "@emdash-cms/cloudflare";
const database = import.meta.env.PROD ? d1({ binding: "DB" }) : sqlite({ url: "file:./data.db" });
export default defineConfig({
integrations: [emdash({ database })],
});
选择也可以基于环境变量而非构建模式:
const database = process.env.DATABASE_URL
? postgres({ connectionString: process.env.DATABASE_URL })
: sqlite({ url: "file:./data.db" });