Elija un adaptador de base de datos por cada implementación. La base de datos contiene el modelo de contenido, las entradas, los usuarios, la configuración y los datos de plugins. Los binarios de medios pertenecen a un backend de almacenamiento separado.
Resumen
| Database | Use it when | Runtime |
|---|---|---|
| SQLite | Un proceso Node.js tiene un disco persistente | Node.js o desarrollo local |
| D1 | El sitio se ejecuta en Cloudflare Workers y debe usar Cloudflare SQL | Cloudflare Workers |
| Hyperdrive | El sitio se ejecuta en Workers y debe usar un origen PostgreSQL existente | Cloudflare Workers |
| PostgreSQL | Varios procesos Node.js necesitan una base de datos compartida | Node.js |
| libSQL | Una implementación Node.js necesita una base de datos remota compatible con SQLite | Node.js |
D1 es el valor predeterminado para las plantillas de Cloudflare. SQLite es la opción más sencilla de Node.js, pero requiere un volumen persistente escribible y copias de seguridad operativas de la base de datos.
SQLite
SQLite usa el controlador de base de datos integrado de Node.js y es la opción más sencilla para implementaciones Node.js.
import { sqlite } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
}),
],
});
Configuración
| Option | Type | Description |
|---|---|---|
url | string | Ruta de archivo con prefijo file: |
Ruta de archivo
La url debe empezar con 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 abre las bases de datos SQLite en modo write-ahead logging (WAL). Mientras el sitio se ejecuta, SQLite mantiene dos archivos adicionales junto a la base de datos, como emdash.db-wal y emdash.db-shm. El proceso necesita acceso de escritura al directorio de la base de datos para crearlos.
El archivo -wal puede contener cambios confirmados que aún no están en el archivo principal de la base de datos. Haga copias de seguridad con el comando de respaldo de SQLite en lugar de copiar solo el archivo .db. Consulte Copia de seguridad y recuperación de SQLite.
WAL requiere memoria compartida, así que mantenga la base de datos en un disco local o volumen de bloque en lugar de un sistema de archivos de red como NFS o SMB.
Cloudflare D1
D1 es la base de datos SQLite serverless de Cloudflare. Úsela al implementar en Cloudflare Workers.
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({ binding: "DB" }),
}),
],
});
Configuración
| Option | Type | Default | Description |
|---|---|---|---|
binding | string | — | Nombre del binding D1 de wrangler.jsonc |
session | string | "disabled" | Modo de replicación de lectura (véase abajo) |
bookmarkCookie | string | "__em_d1_bookmark" | Nombre de cookie para marcadores de sesión |
Binding de Wrangler
wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "emdash-db"
}
]
} wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "emdash-db" Wrangler puede aprovisionar una base de datos D1 faltante a partir de este binding durante la implementación. Las migraciones de EmDash son un paso separado. Siga Implementar en Cloudflare para el conjunto completo de bindings y Gestionar migraciones de la base de datos principal para el runbook de migraciones.
Réplicas de lectura
D1 admite replicación de lectura para reducir la latencia de lectura en sitios distribuidos globalmente. Cuando está habilitada, las consultas de lectura se enrutan a réplicas cercanas en lugar de ir siempre a la base de datos primaria.
EmDash usa la API de sesiones de D1 para gestionarlo de forma transparente. Habilítela con la opción session:
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({
binding: "DB",
session: "auto",
}),
}),
],
});
Modos de sesión
| Mode | Behavior |
|---|---|
"disabled" | Sin sesiones. Todas las consultas van a la primaria. Predeterminado. |
"auto" | Las solicitudes anónimas leen de la réplica más cercana. Los usuarios autenticados obtienen consistencia read-your-writes mediante cookies de marcador. |
"primary-first" | Como "auto", pero la primera consulta siempre va a la primaria. Úselo en sitios con escrituras muy frecuentes. |
Cómo funciona
- Visitantes anónimos obtienen
first-unconstrained: las lecturas van a la réplica más cercana para la menor latencia. Como los usuarios anónimos nunca escriben, no necesitan garantías de consistencia. - Usuarios autenticados (editores, autores) obtienen sesiones basadas en marcadores. Tras una escritura, una cookie de marcador asegura que la siguiente solicitud vea al menos ese estado.
- Solicitudes de escritura (
POST,PUT,DELETE) siempre comienzan en la base de datos primaria. - Consultas en tiempo de build (colecciones de contenido de Astro) omiten las sesiones por completo y usan la primaria directamente.
libSQL
libSQL es un fork de SQLite que admite conexiones remotas. Úselo cuando necesite una base de datos remota sin 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,
}),
}),
],
});
Configuración
| Option | Type | Description |
|---|---|---|
url | string | URL de la base de datos (libsql://... o file:...) |
authToken | string | Token de autenticación en tiempo de ejecución para bases remotas (opcional en local) |
migrationAuthTokenEnv | string | Nombre de la variable del token de migración (predeterminado TURSO_AUTH_TOKEN) |
Desarrollo local
Use un archivo libSQL local durante el desarrollo:
database: libsql({ url: "file:./data.db" });
PostgreSQL
PostgreSQL es compatible en implementaciones Node.js que necesitan una base de datos relacional completa.
import { postgres } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: postgres({
connectionString: process.env.DATABASE_URL,
}),
}),
],
});
Configuración
Puede conectarse con una cadena de conexión o parámetros individuales:
// 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,
});
| Option | Type | Description |
|---|---|---|
connectionString | string | URL de conexión de PostgreSQL |
host | string | Host de la base de datos |
port | number | Puerto de la base de datos |
database | string | Nombre de la base de datos |
user | string | Usuario de la base de datos |
password | string | Contraseña de la base de datos |
ssl | boolean | Habilitar SSL |
pool.min | number | Conexiones mínimas del pool (predeterminado 0) |
pool.max | number | Conexiones máximas del pool (predeterminado 10) |
pool.connectionTimeoutMillis | number | Espera máxima de conexión (predeterminado pg: 0, sin tiempo de espera) |
pool.idleTimeoutMillis | number | Vida útil del cliente inactivo (predeterminado pg: 10.000 ms) |
migrationConnectionStringEnv | string | Nombre de la variable de cadena de conexión de migración (predeterminado DATABASE_URL) |
Establezca pool.connectionTimeoutMillis en un valor distinto de cero para limitar cuánto espera una solicitud cuando PostgreSQL es inalcanzable o no hay conexión del pool disponible. Establezca pool.idleTimeoutMillis en 0 para mantener abiertos los clientes inactivos hasta que el pool se cierre. Omitir cualquiera de las opciones conserva el valor predeterminado de pg.
Requisitos del rol de base de datos
EmDash crea y actualiza sus propias tablas de PostgreSQL. Las migraciones principales crean y alteran tablas del sistema y de colecciones, los tipos de contenido crean tablas ec_*, y añadir o quitar un campo altera su tabla de colección. El rol de PostgreSQL configurado necesita por tanto autoridad de esquema durante la vida del sitio, no solo en la configuración inicial.
Use un rol canónico para EmDash. Necesita:
CONNECTen la base de datos;USAGEyCREATEen el esquema activo;- propiedad de cada tabla y función de EmDash, directamente o por membresía con
INHERITen el rol propietario; y SELECT,INSERT,UPDATEyDELETEen esas tablas.
No necesita ser superusuario, tener CREATEDB o CREATEROLE, ni crear extensiones. PostgreSQL no proporciona un permiso de tabla ALTER o DROP: esas operaciones pertenecen al propietario del objeto y a los roles que heredan sus privilegios. Conceder ALL en una tabla a un rol diferente no lo convierte en propietario. EmDash no ejecuta SET ROLE, así que la membresía configurada sin herencia no es suficiente.
La mayoría de las instalaciones pueden usar el esquema existente de la base de datos, comúnmente public. Es la opción más sencilla cuando la base de datos está dedicada a EmDash. En los ejemplos siguientes, emdash_app es el rol de inicio de sesión en la cadena de conexión de EmDash; use un rol de proveedor existente o cree un inicio de sesión dedicado. Concédele acceso con una conexión administrativa, sustituyendo los nombres de base de datos, esquema y rol:
GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;
Estos grants permiten al rol crear objetos nuevos. No cambian el propietario de tablas existentes; use el runbook de reparación de propiedad de PostgreSQL cuando un sitio existente tenga propietarios mixtos.
EmDash usa el current_schema() activo de PostgreSQL. No crea un esquema ni establece search_path, así que verifique la conexión antes de la implementación:
SELECT
current_database(),
session_user,
current_user,
current_schema(),
current_setting('search_path');
Opcional: usar un esquema dedicado
Use un esquema dedicado cuando EmDash comparte una base de datos con otra aplicación o cuando quiere aislar sus objetos de public. Es opcional y es más fácil de configurar antes de la primera configuración de EmDash. Una base de datos dedicada a EmDash no necesita un esquema separado.
Asumiendo que el rol canónico emdash_app ya existe, cree y seleccione su esquema con una conexión administrativa:
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;
Esto no mueve una instalación existente desde public ni repara propiedad mixta. Los sitios existentes deben conservar su esquema actual y usar el runbook de reparación de propiedad de PostgreSQL en su lugar.
Agrupación de conexiones
El adaptador usa pg.Pool. Ajuste el tamaño del pool según su implementación:
database: postgres({
connectionString: process.env.DATABASE_URL,
pool: { min: 2, max: 20 },
});
Hyperdrive
Use el adaptador hyperdrive() para ejecutar EmDash en Cloudflare Workers respaldado por una base de datos PostgreSQL existente — o compatible con Postgres (p. ej. PlanetScale Postgres). Hyperdrive agrupa y acelera la conexión sobre la red de Cloudflare; el dialecto PostgreSQL de EmDash ejecuta las consultas.
import { hyperdrive, r2 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: hyperdrive({ binding: "HYPERDRIVE" }),
storage: r2({ binding: "MEDIA" }),
}),
],
});
Requisitos
pg >= 8.16.3instalado en su sitio (pnpm add pg)compatibility_flags: ["nodejs_compat"]compatibility_date >= "2024-09-23"
Configuración
Primero prepare el rol de PostgreSQL. Luego cree la configuración de Hyperdrive con la cadena de conexión de ese rol y añada el binding a su configuración de 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>" Configuración
| Option | Type | Default | Description |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Nombre del binding Hyperdrive primario (caché deshabilitada) |
cachedBinding | string | — | Binding opcional con caché habilitada para lecturas anónimas (véase abajo) |
preferUncachedAfterWriteMs | number | 60000* | Tras publicar contenido, preferir binding durante estos ms en lecturas públicas anónimas (coincidir con max_age de Hyperdrive) |
migrationConnectionStringEnv | string | CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING> | Variable de entorno con la URL directa del origen PostgreSQL para emdash migrate |
max | number | 5 | Tamaño máximo del pool de conexiones in-Worker hacia Hyperdrive |
*El valor predeterminado 60000 solo se aplica cuando cachedBinding está establecido; se ignora en caso contrario.
Servir lecturas anónimas desde la caché
Por defecto deshabilita por completo la caché de Hyperdrive, porque la administración y las escrituras necesitan consistencia read-after-write. Pero las solicitudes públicas anónimas con GET o HEAD pueden tolerar una ventana breve de obsolescencia. Si ese compromiso es aceptable, ejecute dos configuraciones de Hyperdrive sobre la misma base de datos: una con caché desactivada (el binding primario) y otra con caché activada (cachedBinding). EmDash enruta esas solicitudes públicas anónimas por el binding con caché y el resto por la primaria sin caché.
# 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" });
Este es el patrón de dos configuraciones que Cloudflare documenta para la caché. EmDash decide qué binding usar por solicitud:
- Lecturas anónimas de rutas del sitio público (
GET/HEAD, sin sesión, no bajo/_emdash) →cachedBindingcon caché, excepto durante una ventana breve tras una publicación de contenido (predeterminado 60 s; establezcapreferUncachedAfterWriteMsa sumax_agede Hyperdrive) cuando EmDash prefiere elbindingsin caché para que una reconstrucción no pueda rellenar cachés de edge/objeto desde resultados aún obsoletos de Hyperdrive. - Solicitudes autenticadas (editores, autores) →
bindingsin caché. - Solicitudes de mutación (
POST,PUT,PATCH,DELETE, incluidas las anónimas) →bindingsin caché. - Cualquier solicitud bajo
/_emdash(administración, configuración, autenticación, APIs internas), incluso unGETanónimo →bindingsin caché. - Migraciones en tiempo de ejecución y arranque en frío → siempre el
bindingprimario. - Migraciones gestionadas por la implementación → se conectan directamente al origen PostgreSQL con
migrationConnectionStringEnv; nunca usan ninguno de los bindings de Hyperdrive.
Opcional: usar un rol en caché separado
Las migraciones, la configuración, las solicitudes autenticadas y las solicitudes de escritura explícitas siempre usan el binding primario. Un rol separado para cachedBinding no necesita propiedad del esquema ni CREATE, pero necesita CONNECT, USAGE del esquema y SELECT en cada tabla usada por el sitio público.
Las solicitudes públicas anónimas GET y HEAD también pueden registrar aciertos de redirección y 404. Para preservar esas funciones, el rol en caché necesita además UPDATE en _emdash_redirects y SELECT, INSERT, UPDATE y DELETE en _emdash_404_log. Plugins o código de aplicación que escriban durante un GET o HEAD público pueden requerir más. Use el mismo rol para ambos bindings a menos que haya probado el sitio con un rol en caché restringido.
Añada el rol en caché después de que EmDash haya completado sus migraciones iniciales. Los ejemplos siguientes usan el esquema opcional emdash; sustituya su esquema activo, como public. Cree el inicio de sesión y la configuración de la base de datos con el rol administrativo de su proveedor:
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;
Luego conéctese como emdash_app, el propietario del esquema y las tablas, para conceder acceso a tablas existentes y futuras:
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;
Conéctese con ambos roles y verifique que reporten el mismo current_database() y current_schema() antes de habilitar cachedBinding. En un esquema compartido, GRANT SELECT ON ALL TABLES también expone tablas no relacionadas. Conceda acceso a tablas individuales de EmDash en su lugar, y actualice esos grants cuando se añadan colecciones u otros objetos de esquema.
Migraciones principales
EmDash ejecuta migraciones principales automáticamente por defecto para cada dialecto admitido. El build y sync de Astro también emiten un .emdash/migrations.json validado y sin secretos, que emdash migrate puede aplicar antes de la implementación. SQLite, libSQL, PostgreSQL, D1 y el origen PostgreSQL directo detrás de Hyperdrive tienen ejecutores de implementación.
Consulte Gestionar migraciones de la base de datos principal para credenciales de destino, serialización de CI, política de tiempo de ejecución auto/check/manual y recuperación de registros desconocidos o escrituras D1 ambiguas.
Para PostgreSQL, las migraciones en tiempo de ejecución se ejecutan por la conexión configurada; las migraciones en tiempo de ejecución de Hyperdrive siempre usan su binding primario. Las migraciones de Hyperdrive gestionadas por la implementación se conectan directamente al origen PostgreSQL. Las migraciones principales pueden crear tablas, índices y funciones, alterar o eliminar columnas y restricciones, y actualizar filas existentes. Un rol que puede conectarse y modificar filas pero no posee los objetos EmDash existentes no es suficiente. El asistente de configuración no puede reparar privilegios de base de datos faltantes porque las migraciones en tiempo de ejecución se ejecutan antes de la configuración.
Si la base de datos está vacía (sin colecciones) y el asistente de configuración no se ha completado, EmDash también aplica un archivo seed en el primer arranque. El seed se lee de .emdash/seed.json, la ruta en package.json#emdash.seed o seed/seed.json — el que se encuentre primero — y se incrusta en el build en tiempo de compilación. Si no hay ninguno, se usa un seed predeterminado integrado. Los arranques posteriores contra una base de datos existente dejan su contenido intacto.
Usar bases de datos separadas para entornos separados
Dé a desarrollo, vista previa, staging y producción su propia base de datos. Una implementación de vista previa apuntada a producción puede ejecutar migraciones principales o comandos destructivos del modelo de contenido contra datos en vivo.
Para Cloudflare, defina cada binding D1 o Hyperdrive bajo el entorno Wrangler correspondiente y pase --env a los comandos de Wrangler. Para Node.js, inyecte una URL de base de datos diferente en cada entorno de tiempo de ejecución. Mantenga las credenciales en secretos de tiempo de ejecución, no en astro.config.mjs.