数据库选项

本页内容

EmDash 支持多种数据库后端。根据你的部署目标选择。

概览

数据库最适用于部署
D1Cloudflare Workers边缘,全球分布
HyperdriveCloudflare Workers 上的 PostgreSQL边缘,现有 Postgres
PostgreSQL生产 Node.js有 Postgres 的任何平台
libSQL远程数据库边缘或 Node.js
SQLiteNode.js,本地开发单服务器

Cloudflare D1

D1 是 Cloudflare 的无服务器 SQLite 数据库。在部署到 Cloudflare Workers 时使用。

import { d1 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({ binding: "DB" }),
		}),
	],
});

配置

选项类型默认值描述
bindingstringwrangler.jsonc 中的 D1 绑定名称
sessionstring"disabled"只读复制模式(见下方)
bookmarkCookiestring"__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 确保下一个请求至少看到该状态。
  • 写入请求POSTPUTDELETE)始终从主数据库开始。
  • 构建时查询(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,
			}),
		}),
	],
});

配置

选项类型描述
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,
			}),
		}),
	],
});

配置

你可以使用连接字符串或单独的参数连接:

// 连接字符串
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,
});
选项类型描述
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
  • 活动模式的 USAGECREATE
  • 每个 EmDash 表和函数的所有权(直接或通过拥有角色的 INHERIT 成员关系);以及
  • 这些表的 SELECTINSERTUPDATEDELETE

不需要超级用户、CREATEDBCREATEROLE 或创建扩展。EmDash 不执行 SET ROLE,因此不带继承的成员关系不够用。

大多数安装可以使用数据库的现有模式(通常是 public)。使用管理连接授予访问权限:

GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;

EmDash 使用 PostgreSQL 的活动 current_schema()。它不创建模式或设置 search_path,因此请在部署前验证连接:

SELECT
  current_database(),
  session_user,
  current_user,
  current_schema(),
  current_setting('search_path');

可选:使用专用模式

当 EmDash 与另一个应用共享数据库时,使用专用模式来隔离其对象:

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;

连接池

适配器内部使用 pg.Pool。根据部署调整池大小:

database: postgres({
	connectionString: process.env.DATABASE_URL,
	pool: { min: 2, max: 20 },
});

Hyperdrive

使用 hyperdrive() 适配器在 Cloudflare Workers 上运行 EmDash,后端使用现有的 PostgreSQL 数据库。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.3pnpm 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>"

配置

选项类型默认值描述
bindingstring"HYPERDRIVE"主(缓存禁用)Hyperdrive 绑定名称
cachedBindingstring可选的缓存启用绑定用于匿名读取
preferUncachedAfterWriteMsnumber60000*内容发布后在匿名公开读取中优先使用 binding 的毫秒数
migrationConnectionStringEnvstringCLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>包含直接 PostgreSQL origin URL 的环境变量
maxnumber5Worker 内到 Hyperdrive 的连接池最大大小

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

从缓存提供匿名读取

默认完全禁用 Hyperdrive 缓存。但 使用 GETHEAD 的匿名公开请求可以容忍短暂的过时窗口。如果可以接受该折衷,在同一数据库上运行两个 Hyperdrive 配置:一个缓存关闭(主 binding),一个缓存开启(cachedBinding)。EmDash 将匿名公开请求路由到缓存启用绑定,其他所有请求路由到无缓存主绑定。

这是 Cloudflare 记录的双配置模式。EmDash 按请求决定使用哪个绑定。

修复 PostgreSQL 混合所有权

如果站点使用了多个 PostgreSQL 用户,首先选择主 EmDash 连接将继续使用的标准角色。备份并在修复所有权期间停止模式更改。

检查活动模式中的每个表:

SELECT
  n.nspname AS schema_name,
  c.relname AS table_name,
  pg_get_userbyid(c.relowner) AS owner
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE n.nspname = current_schema()
  AND c.relkind IN ('r', 'p')
ORDER BY c.relname;

使用模式限定名称转移不匹配的对象:

ALTER TABLE emdash.content_taxonomies OWNER TO emdash_app;
ALTER TABLE emdash.ec_posts OWNER TO emdash_app;
ALTER FUNCTION emdash.emdash_media_usage_capture_work() OWNER TO emdash_app;

SQLite

SQLite 使用 Node.js 的内置数据库驱动,是 Node.js 部署最简单的选项。

import { sqlite } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: sqlite({ url: "file:./data.db" }),
		}),
	],
});

配置

选项类型描述
urlstringfile: 前缀的文件路径

文件路径

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 默认为所有支持的方言自动运行核心迁移。Astro build 和 sync 还会输出经过验证的无密钥 .emdash/migrations.jsonemdash migrate 可以在部署前应用。SQLite、libSQL、PostgreSQL、D1 和 Hyperdrive 后面的直接 PostgreSQL origin 都有部署执行器。

参见管理核心数据库迁移了解目标凭证、CI 序列化、auto/check/manual 运行时策略和恢复指导。

如果数据库为空(无集合)且设置向导未完成,EmDash 还会在首次启动时应用种子文件。种子从 .emdash/seed.jsonpackage.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" });