为每个部署选择一个数据库适配器。数据库保存内容模型、条目、用户、设置和插件数据。媒体二进制文件属于单独的存储后端。
概述
| 数据库 | 使用场景 | 运行时 |
|---|---|---|
| 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: 开头:
// 相对路径
database: sqlite({ url: "file:./data/emdash.db" });
// 绝对路径
database: sqlite({ url: "file:/var/data/emdash.db" });
// 从环境变量
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });
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 获得写后读一致性。 |
"primary-first" | 类似 "auto",但第一个查询始终发送到主库。用于写入非常频繁的站点。 |
工作原理
- 匿名访客 获得
first-unconstrained— 读取从最近的副本进行以获得最低延迟。由于匿名用户从不写入,因此不需要一致性保证。 - 经过身份验证的用户(编辑者、作者)获得基于书签的会话。写入后,书签 Cookie 确保下一个请求至少看到该状态。
- 写入请求(
POST、PUT、DELETE)始终从主数据库开始。 - 构建时查询(Astro Content Collections)完全绕过会话并直接使用主库。
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,
}),
}),
],
});
配置
你可以使用连接字符串或单独的参数进行连接:
// 连接字符串
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) |
pool.connectionTimeoutMillis | number | 最大连接等待时间(pg 默认:0,无超时) |
pool.idleTimeoutMillis | number | 空闲客户端生存时间(pg 默认:10,000 ms) |
migrationConnectionStringEnv | string | 迁移连接字符串变量名(默认 DATABASE_URL) |
将 pool.connectionTimeoutMillis 设置为非零值以限制当 PostgreSQL 不可达或没有可用池化连接时请求的等待时间。将 pool.idleTimeoutMillis 设置为 0 以保持空闲客户端开放直到池关闭。省略任一选项将保留 pg 默认值。
数据库角色要求
EmDash 创建和更新自己的 PostgreSQL 表。核心迁移创建和修改系统表和集合表,内容类型创建 ec_* 表,添加或删除字段会修改其集合表。因此,配置的 PostgreSQL 角色需要在站点的整个生命周期内具有架构权限,而不仅仅是在初始设置期间。
为 EmDash 使用一个规范角色。它需要:
- 数据库的
CONNECT; - 活动架构的
USAGE和CREATE; - 每个 EmDash 表和函数的所有权,直接或通过对拥有角色的
INHERIT成员资格;以及 - 这些表上的
SELECT、INSERT、UPDATE和DELETE。
它不需要是超级用户,不需要 CREATEDB 或 CREATEROLE,也不需要创建扩展。PostgreSQL 不提供表的 ALTER 或 DROP 授权:这些操作属于对象所有者和继承其权限的角色。向不同角色授予表的 ALL 不会使该角色成为所有者。EmDash 不执行 SET ROLE,因此没有继承的成员资格是不够的。
大多数安装可以使用数据库的现有架构,通常是 public。当数据库专用于 EmDash 时,这是最简单的选项。在下面的示例中,emdash_app 是 EmDash 连接字符串中的登录角色;使用现有的提供商角色或创建专用登录。使用管理连接授予访问权限,替换你的数据库、架构和角色名称:
GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;
这些授权允许角色创建新对象。它们不会更改现有表的所有者;当现有站点有混合所有者时,使用 PostgreSQL 所有权修复操作手册。
EmDash 使用 PostgreSQL 的活动 current_schema()。它不创建架构也不设置 search_path,因此在部署前验证连接:
SELECT
current_database(),
session_user,
current_user,
current_schema(),
current_setting('search_path');
可选:使用专用架构
当 EmDash 与另一个应用程序共享数据库,或者你想将其对象从 public 中隔离时,使用专用架构。这是可选的,在 EmDash 首次设置之前配置最为简单。专用于 EmDash 的数据库不需要单独的架构。
假设规范角色 emdash_app 已存在,使用管理连接创建并选择其架构:
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 移动现有安装,也不会修复混合所有权。现有站点应保持其当前架构,改为使用 PostgreSQL 所有权修复操作手册。
连接池
适配器使用 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"
设置
首先准备 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 持续该时间(ms)(与 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 通过启用缓存的绑定路由这些匿名公共请求,通过未缓存的主绑定路由其他所有请求。
# 主 — 缓存关闭(用于管理、认证请求、写入、迁移)
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": "<缓存禁用 ID>" },
{ "binding": "HYPERDRIVE_CACHED", "id": "<缓存启用 ID>" }
]
}
database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" });
这是 Cloudflare 为缓存记录的双配置模式。EmDash 按请求决定使用哪个绑定:
- 公共路径的匿名读取(
GET/HEAD,无会话,不在/_emdash下)→ 缓存启用的cachedBinding,除了内容发布后的短暂窗口(默认 60s;将preferUncachedAfterWriteMs设置为 Hyperdrivemax_age),此时 EmDash 优先使用未缓存的binding,以防重建使用仍然过期的 Hyperdrive 结果重新填充边缘/对象缓存。 - 认证请求(编辑者、作者)→ 未缓存的
binding。 - 变更请求(
POST、PUT、PATCH、DELETE,包括匿名)→ 未缓存的binding。 /_emdash下的任何请求(管理、设置、认证、内部 API),即使是匿名GET→ 未缓存的binding。- 运行时迁移和冷启动 → 始终使用主
binding。 - 部署管理的迁移 → 使用
migrationConnectionStringEnv直接连接到 PostgreSQL 源;从不使用任何 Hyperdrive 绑定。
可选:使用单独的缓存角色
迁移、设置、认证请求和显式写入请求始终使用主 binding。cachedBinding 的单独角色不需要架构所有权或 CREATE,但需要 CONNECT、架构 USAGE 和公共站点使用的每个表上的 SELECT。
匿名公共 GET 和 HEAD 请求还可以记录重定向命中和 404。要保留这些功能,缓存角色还需要 _emdash_redirects 上的 UPDATE 和 _emdash_404_log 上的 SELECT、INSERT、UPDATE 和 DELETE。在公共 GET 或 HEAD 期间写入的插件或应用程序代码可能需要更多。除非你已使用受限缓存角色测试了站点,否则对两个绑定使用相同的角色。
在 EmDash 完成初始迁移后添加缓存角色。以下示例使用可选的 emdash 架构;替换你的活动架构,如 public。使用你提供商的管理角色创建登录和数据库设置:
CREATE ROLE emdash_cached LOGIN PASSWORD '替换为密钥';
GRANT CONNECT ON DATABASE app TO emdash_cached;
ALTER ROLE emdash_cached IN DATABASE app SET search_path = emdash;
然后以架构和表的所有者 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;
使用两个角色连接并验证它们报告相同的 current_database() 和 current_schema(),然后再启用 cachedBinding。在共享架构上,GRANT SELECT ON ALL TABLES 也会暴露不相关的表。改为授予对单个 EmDash 表的访问权限,并在添加集合或其他架构对象时更新这些授权。
核心迁移
EmDash 默认为每个支持的方言自动运行核心迁移。Astro 构建和同步还会输出经过验证的、不含密钥的 .emdash/migrations.json,emdash migrate 可以在部署前应用。SQLite、libSQL、PostgreSQL、D1 和 Hyperdrive 背后的直接 PostgreSQL 源都有部署执行器。
有关目标凭据、CI 序列化、auto/check/manual 运行时策略以及从未知记录或模糊 D1 写入中恢复,请参见管理核心数据库迁移。
对于 PostgreSQL,运行时迁移通过配置的连接运行;Hyperdrive 运行时迁移始终使用主绑定。部署管理的 Hyperdrive 迁移直接连接到 PostgreSQL 源。核心迁移可以创建表、索引和函数,修改或删除列和约束,以及更新现有行。能够连接和修改行但不拥有现有 EmDash 对象的角色是不够的。设置向导无法修复缺失的数据库权限,因为运行时迁移在设置之前执行。
如果数据库为空(无集合)且设置向导未完成,EmDash 还会在首次启动时应用种子文件。种子从 .emdash/seed.json、package.json#emdash.seed 中的路径或 seed/seed.json 读取 — 以最先找到的为准 — 并在编译时内联到构建中。如果都不存在,则使用内置的默认种子。后续针对现有数据库的启动不会修改其内容。
为不同环境使用不同的数据库
为开发、预览、暂存和生产分别提供自己的数据库。指向生产的预览部署可能会对生产数据执行核心迁移或破坏性内容模型命令。
对于 Cloudflare,在对应的 Wrangler 环境下定义每个 D1 或 Hyperdrive 绑定,并向 Wrangler 命令传递 --env。对于 Node.js,为每个运行时环境注入不同的数据库 URL。将凭据保存在运行时密钥中,而不是 astro.config.mjs 中。