每次部署只选择一个数据库适配器。数据库保存内容模型、条目、用户、设置和插件数据。媒体二进制文件属于单独的存储后端。
概览
| 数据库 | 适用场景 | 运行时 |
|---|---|---|
| SQLite | 单个 Node.js 进程拥有持久化磁盘 | Node.js 或本地开发 |
| D1 | 站点运行在 Cloudflare Workers 上,并且应当使用 Cloudflare SQL | Cloudflare Workers |
| Hyperdrive | 站点运行在 Workers 上,并且必须使用已有的 PostgreSQL 源 | Cloudflare Workers |
| PostgreSQL | 多个 Node.js 进程需要共用一个数据库 | Node.js |
| libSQL | Node.js 部署需要一个与 SQLite 兼容的远程数据库 | Node.js |
D1 是 Cloudflare 模板的默认选项。SQLite 是最简单的 Node.js 选项,但它需要一个可写的持久化卷,并且需要运维层面的数据库备份。
SQLite
SQLite 使用 Node.js 内置的数据库驱动,是 Node.js 部署中最简单的选项。
import { sqlite } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
}),
],
});
配置
| 选项 | 类型 | 说明 |
|---|---|---|
url | string | 带 file: 前缀的文件路径 |
文件路径
url 必须以 file: 开头:
// Relative path
database: sqlite({ url: "file:./data/emdash.db" });
// Absolute path
database: sqlite({ url: "file:/var/data/emdash.db" });
// From environment variable
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });
Write-ahead logging
EmDash 以 write-ahead logging(WAL)模式打开 SQLite 数据库。站点运行期间,SQLite 会在数据库文件旁保留两个额外的文件,例如 emdash.db-wal 和 emdash.db-shm。进程需要对数据库所在目录有写入权限才能创建它们。
-wal 文件中可能保存着已提交、但尚未写入主数据库文件的更改。请使用 SQLite 的备份命令进行备份,而不是只复制 .db 文件。参阅 SQLite 备份与恢复。
WAL 需要共享内存,因此请把数据库放在本地磁盘或块存储卷上,而不是 NFS、SMB 之类的网络文件系统上。
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 绑定
wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "emdash-db"
}
]
} wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "emdash-db" 部署期间,Wrangler 可以根据这个绑定创建缺失的 D1 数据库。EmDash 迁移是单独的一步。完整的绑定集合请参阅部署到 Cloudflare,迁移操作手册请参阅管理核心数据库迁移。
读副本
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 | 远程数据库的运行时认证令牌(本地可选) |
migrationAuthTokenEnv | string | 迁移令牌的变量名称(默认 TURSO_AUTH_TOKEN) |
本地开发
开发期间使用本地 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,
}),
}),
],
});
配置
你可以使用连接字符串或单独的参数进行连接:
// Connection string
database: postgres({
connectionString: "postgres://user:password@localhost:5432/emdash",
});
// Individual parameters
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) |
pool.connectionTimeoutMillis | number | 最长连接等待时间(pg 默认值:0,即不超时) |
pool.idleTimeoutMillis | number | 空闲客户端的存活时间(pg 默认值:10,000 毫秒) |
migrationConnectionStringEnv | string | 迁移连接字符串的变量名称(默认 DATABASE_URL) |
把 pool.connectionTimeoutMillis 设为非零值,可以限制 PostgreSQL 无法访问或连接池中没有可用连接时请求的最长等待时间。把 pool.idleTimeoutMillis 设为 0,可以让空闲客户端一直保持打开,直到连接池关闭。省略任意一个选项都会保留 pg 的默认值。
数据库角色要求
EmDash 会创建并更新自己的 PostgreSQL 表。核心迁移会创建并修改系统表和集合表,内容类型会创建 ec_* 表,添加或删除字段会修改它所属的集合表。因此,所配置的 PostgreSQL 角色在站点的整个生命周期中都需要 schema 权限,而不仅仅是在初始设置期间。
请为 EmDash 使用一个固定的角色。它需要:
- 对数据库的
CONNECT权限; - 对当前 schema 的
USAGE和CREATE权限; - 拥有每一个 EmDash 表和函数的所有权,可以是直接拥有,也可以通过以
INHERIT方式成为所属角色的成员;以及 - 对这些表的
SELECT、INSERT、UPDATE和DELETE权限。
它不需要是超级用户,不需要 CREATEDB 或 CREATEROLE,也不需要创建扩展。PostgreSQL 没有提供 ALTER 或 DROP 表的授权:这些操作属于对象的所有者以及继承其权限的角色。把表上的 ALL 授予另一个角色,并不会让该角色成为所有者。EmDash 不会运行 SET ROLE,因此不带继承配置的成员关系是不够的。
大多数安装可以使用数据库已有的 schema,通常是 public。当数据库专用于 EmDash 时,这是最简单的选项。在下面的示例中,emdash_app 是 EmDash 连接字符串中的登录角色;请使用现有的服务商角色,或创建一个专用登录角色。使用管理员连接授予它访问权限,并替换成你自己的数据库、schema 和角色名称:
GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;
这些授权让该角色可以创建新对象,但不会更改已有表的所有者;当已有站点存在混合所有者时,请使用 PostgreSQL 所有权修复手册。
EmDash 使用 PostgreSQL 当前生效的 current_schema()。它不会创建 schema,也不会设置 search_path,所以请在部署之前验证连接:
SELECT
current_database(),
session_user,
current_user,
current_schema(),
current_setting('search_path');
可选:使用专用 schema
当 EmDash 与其他应用共用一个数据库,或者你希望把它的对象与 public 隔离开时,请使用专用 schema。这是可选的,最好在首次 EmDash 设置之前配置。专用于 EmDash 的数据库不需要单独的 schema。
假设标准的 emdash_app 角色已经存在,使用管理员连接创建并选定它的 schema:
GRANT CONNECT ON DATABASE app TO emdash_app;
CREATE SCHEMA emdash AUTHORIZATION emdash_app;
ALTER ROLE emdash_app IN DATABASE app SET search_path = emdash;
这不会把已有安装从 public 迁走,也不会修复混合所有权。已有站点应保留当前的 schema,并改用 PostgreSQL 所有权修复手册。
连接池
适配器使用 pg.Pool。请根据你的部署情况调整连接池大小:
database: postgres({
connectionString: process.env.DATABASE_URL,
pool: { min: 2, max: 20 },
});
Hyperdrive
使用 hyperdrive() 适配器,可以让 EmDash 在 Cloudflare Workers 上运行,并由已有的 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"
设置
首先准备 PostgreSQL 角色。然后使用该角色的连接字符串创建 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 | — | 用于匿名读取的、可选的已启用缓存的绑定(见下文) |
preferUncachedAfterWriteMs | number | 60000* | 内容发布之后,在这么多毫秒内,匿名公开读取优先使用 binding(与 Hyperdrive 的 max_age 保持一致) |
migrationConnectionStringEnv | string | CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING> | 包含 emdash migrate 所用的 PostgreSQL 直连源 URL 的环境变量 |
max | number | 5 | Worker 内到 Hyperdrive 的连接池的最大大小 |
*默认值 60000 仅在设置了 cachedBinding 时适用;否则会被忽略。
从缓存提供匿名读取
默认情况下,你会完全禁用 Hyperdrive 缓存,因为管理后台和写入操作需要写后读一致性。但是,使用 GET 或 HEAD 的匿名公开请求可以容忍一个较短的过期窗口。如果这种取舍可以接受,请在同一个数据库上运行两个 Hyperdrive 配置:一个关闭缓存(主 binding),一个开启缓存(cachedBinding)。EmDash 会把这些匿名公开请求路由到启用缓存的绑定,并把其他所有请求路由到未缓存的主绑定。
# Primary — caching OFF (used by admin, auth'd requests, writes, migrations)
wrangler hyperdrive create emdash-db \
--connection-string "postgres://user:password@host/db?sslmode=verify-full" \
--caching-disabled
# Cached — SAME database role and connection string, caching ON
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,但内容发布之后的一小段时间窗口内除外(默认 60 秒;把preferUncachedAfterWriteMs设为你的 Hyperdrivemax_age),此时 EmDash 优先使用未缓存的binding,以免重新构建时用仍然过期的 Hyperdrive 结果重新填充边缘/对象缓存。 - 已认证请求(编辑、作者)→ 未缓存的
binding。 - 变更请求(
POST、PUT、PATCH、DELETE,包括匿名请求)→ 未缓存的binding。 /_emdash之下的任何请求(管理后台、设置、认证、内部 API),即使是匿名的GET→ 未缓存的binding。- 运行时迁移和冷启动 → 始终使用主
binding。 - 由部署管理的迁移 → 使用
migrationConnectionStringEnv直接连接 PostgreSQL 源;它们从不使用任何一个 Hyperdrive 绑定。
可选:使用单独的缓存角色
迁移、设置、已认证请求和明确的写入请求始终使用主 binding。cachedBinding 使用的单独角色不需要 schema 所有权或 CREATE 权限,但它需要 CONNECT、对 schema 的 USAGE,以及对公开站点所用每个表的 SELECT 权限。
匿名公开的 GET 和 HEAD 请求还可能记录重定向命中和 404。为了保留这些功能,缓存角色还需要对 _emdash_redirects 的 UPDATE 权限,以及对 _emdash_404_log 的 SELECT、INSERT、UPDATE 和 DELETE 权限。在公开的 GET 或 HEAD 期间执行写入的插件或应用代码可能需要更多权限。除非你已经在受限的缓存角色下测试过站点,否则两个绑定请使用同一个角色。
请在 EmDash 完成初始迁移之后再添加缓存角色。下面的示例使用可选的 emdash schema;请替换为你当前生效的 schema,例如 public。使用你的服务商提供的管理员角色创建登录角色和数据库设置:
CREATE ROLE emdash_cached LOGIN PASSWORD 'replace-with-a-secret';
GRANT CONNECT ON DATABASE app TO emdash_cached;
ALTER ROLE emdash_cached IN DATABASE app SET search_path = emdash;
然后以 schema 和表的所有者 emdash_app 身份连接,向已有和未来的表授予访问权限:
GRANT USAGE ON SCHEMA emdash TO emdash_cached;
GRANT SELECT ON ALL TABLES IN SCHEMA emdash TO emdash_cached;
GRANT UPDATE ON emdash._emdash_redirects TO emdash_cached;
GRANT SELECT, INSERT, UPDATE, DELETE ON emdash._emdash_404_log TO emdash_cached;
ALTER DEFAULT PRIVILEGES IN SCHEMA emdash
GRANT SELECT ON TABLES TO emdash_cached;
在启用 cachedBinding 之前,请用这两个角色分别连接,并确认它们报告的 current_database() 和 current_schema() 相同。在共享的 schema 上,GRANT SELECT ON ALL TABLES 还会暴露无关的表。请改为对各个 EmDash 表逐一授权,并在添加集合或其他 schema 对象时更新这些授权。
核心迁移
默认情况下,EmDash 会为每种受支持的方言自动运行核心迁移。Astro 的构建和同步还会生成一份经过校验、不含密钥的 .emdash/migrations.json,emdash migrate 可以在部署之前应用它。SQLite、libSQL、PostgreSQL、D1,以及 Hyperdrive 背后的 PostgreSQL 直连源都有部署执行器。
目标凭据、CI 串行化、auto/check/manual 运行时策略,以及从未知记录或含糊不清的 D1 写入中恢复,请参阅管理核心数据库迁移。
对于 PostgreSQL,运行时迁移通过所配置的连接运行;Hyperdrive 的运行时迁移始终使用它的主绑定。由部署管理的 Hyperdrive 迁移则直接连接 PostgreSQL 源。核心迁移可能创建表、索引和函数,修改或删除列和约束,并更新已有的行。一个能够连接并修改行、但并不拥有已有 EmDash 对象的角色是不够的。设置向导无法修复缺失的数据库权限,因为运行时迁移在设置之前就已经运行。
在设置向导完成之前,EmDash 还会在第一次请求时一次性应用种子文件的 schema 和结构。种子中的示例内容只有在管理员于设置向导中选择它时才会被应用。种子从 .emdash/seed.json、package.json#emdash.seed 中的路径或 seed/seed.json 中读取,以先找到的为准,并在编译时内联到构建中。如果都不存在,则使用内置的默认种子。之后针对已有数据库的启动不会改动其内容。
为不同环境使用单独的数据库
请为开发、预览、预发布和生产各自提供独立的数据库。指向生产的预览部署可能会对线上数据运行核心迁移或破坏性的内容模型命令。
对于 Cloudflare,请在对应的 Wrangler 环境下定义每个 D1 或 Hyperdrive 绑定,并在 Wrangler 命令中传入 --env。对于 Node.js,请向每个运行时环境注入不同的数据库 URL。请把凭据保存在运行时密钥中,而不要放在 astro.config.mjs 里。