选择数据库

本页内容

每次部署选择一个数据库适配器。数据库保存内容模型、条目、用户、设置和插件数据。媒体二进制文件属于单独的存储后端。

概览

DatabaseUse it whenRuntime
SQLite一个 Node.js 进程拥有持久磁盘Node.js 或本地开发
D1站点运行在 Cloudflare Workers 上且应使用 Cloudflare SQLCloudflare Workers
Hyperdrive站点运行在 Workers 上且必须使用现有 PostgreSQL 源Cloudflare Workers
PostgreSQL多个 Node.js 进程需要一个共享数据库Node.js
libSQLNode.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" }),
		}),
	],
});

配置

OptionTypeDescription
urlstring带 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" }),
		}),
	],
});

配置

OptionTypeDefaultDescription
bindingstring—来自 wrangler.jsonc 的 D1 绑定名称
sessionstring"disabled"读复制模式(见下文)
bookmarkCookiestring"__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",
			}),
		}),
	],
});

会话模式

ModeBehavior
"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,
			}),
		}),
	],
});

配置

OptionTypeDescription
urlstring数据库 URL(libsql://... 或 file:...)
authTokenstring远程数据库的运行时认证令牌(本地可选)
migrationAuthTokenEnvstring迁移令牌变量名(默认 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,
});
OptionTypeDescription
connectionStringstringPostgreSQL 连接 URL
hoststring数据库主机
portnumber数据库端口
databasestring数据库名
userstring数据库用户
passwordstring数据库密码
sslboolean启用 SSL
pool.minnumber最小池连接数(默认 0)
pool.maxnumber最大池连接数(默认 10)
pool.connectionTimeoutMillisnumber最大连接等待(pg 默认:0,无超时)
pool.idleTimeoutMillisnumber空闲客户端寿命(pg 默认:10,000 ms)
migrationConnectionStringEnvstring迁移连接字符串变量名(默认 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() 适配器,让 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>"

配置

OptionTypeDefaultDescription
bindingstring"HYPERDRIVE"主(禁用缓存)Hyperdrive 绑定名称
cachedBindingstring—用于匿名读取的可选启用缓存绑定(见下文)
preferUncachedAfterWriteMsnumber60000*内容发布后,在匿名公开读取上优先使用 binding 这么多毫秒(匹配 Hyperdrive max_age)
migrationConnectionStringEnvstringCLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>包含供 emdash migrate 使用的直接 PostgreSQL 源 URL 的环境变量
maxnumber5Worker 内到 Hyperdrive 的连接池最大大小

*默认 60000 仅在设置了 cachedBinding 时适用;否则忽略。

从缓存提供匿名读取

默认完全禁用 Hyperdrive 缓存,因为管理和写入需要 read-after-write 一致性。但使用 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 设为你的 Hyperdrive max_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 'replace-with-a-secret';
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;

用两个角色连接,并在启用 cachedBinding 之前确认它们报告相同的 current_database() 和 current_schema()。在共享模式上,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。