Elija un adaptador de base de datos para cada despliegue. La base de datos almacena el modelo de contenido, entradas, usuarios, configuraciones y datos de plugins. Los archivos multimedia pertenecen a un backend de almacenamiento separado.
Resumen
| Base de datos | Úsela cuando | Entorno de ejecución |
|---|---|---|
| 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 | Un despliegue Node.js necesita una base de datos remota compatible con SQLite | Node.js |
D1 es el predeterminado para las plantillas de Cloudflare. SQLite es la opción más simple de Node.js, pero requiere un volumen persistente con escritura 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 simple para despliegues Node.js.
import { sqlite } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
}),
],
});
Configuración
| Opción | Tipo | Descripción |
|---|---|---|
url | string | Ruta de archivo con prefijo file: |
Ruta de archivo
La url debe comenzar con file::
// Ruta relativa
database: sqlite({ url: "file:./data/emdash.db" });
// Ruta absoluta
database: sqlite({ url: "file:/var/data/emdash.db" });
// Desde variable de entorno
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });
Cloudflare D1
D1 es la base de datos SQLite serverless de Cloudflare. Úsela cuando despliegue en Cloudflare Workers.
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({ binding: "DB" }),
}),
],
});
Configuración
| Opción | Tipo | Predeterminado | Descripción |
|---|---|---|---|
binding | string | — | Nombre del binding D1 de wrangler.jsonc |
session | string | "disabled" | Modo de replicación de lectura (ver 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 desde este binding durante el despliegue. Las migraciones de EmDash son un paso separado. Siga Desplegar en Cloudflare para el conjunto completo de bindings y Gestionar migraciones de base de datos core para el manual de migraciones.
Réplicas de lectura
D1 soporta 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 siempre acceder a la base de datos primaria.
EmDash usa la API de Sessions de D1 para gestionar esto de forma transparente. Habilítelo 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
| Modo | Comportamiento |
|---|---|
"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 de lectura-después-de-escritura mediante cookies de marcadores. |
"primary-first" | Como "auto", pero la primera consulta siempre va a la primaria. Use para 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. Después de 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 compilación (Astro content collections) omiten las sesiones por completo y usan la primaria directamente.
libSQL
libSQL es un fork de SQLite que soporta 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
| Opción | Tipo | Descripción |
|---|---|---|
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 para local) |
migrationAuthTokenEnv | string | Nombre de 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 está soportado para despliegues 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 conectar con una cadena de conexión o parámetros individuales:
// Cadena de conexión
database: postgres({
connectionString: "postgres://user:password@localhost:5432/emdash",
});
// Parámetros individuales
database: postgres({
host: "localhost",
port: 5432,
database: "emdash",
user: "emdash",
password: process.env.DB_PASSWORD,
ssl: true,
});
| Opción | Tipo | Descripción |
|---|---|---|
connectionString | string | URL de conexión 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 timeout) |
pool.idleTimeoutMillis | number | Vida útil de cliente inactivo (predeterminado pg: 10.000 ms) |
migrationConnectionStringEnv | string | Nombre de variable de cadena de conexión de migración (predeterminado DATABASE_URL) |
Establezca pool.connectionTimeoutMillis a un valor distinto de cero para limitar cuánto espera una solicitud cuando PostgreSQL no es accesible o no hay conexión disponible en el pool. Establezca pool.idleTimeoutMillis a 0 para mantener los clientes inactivos abiertos hasta que el pool se cierre. Omitir cualquier opción preserva el valor predeterminado de pg.
Requisitos del rol de base de datos
EmDash crea y actualiza sus propias tablas PostgreSQL. Las migraciones core crean y alteran tablas del sistema y de colecciones, los tipos de contenido crean tablas ec_*, y agregar o eliminar un campo altera la tabla de su colección. El rol PostgreSQL configurado necesita por tanto autoridad sobre el esquema durante toda la vida del sitio, no solo durante 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, ya sea directamente o mediante 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 grant ALTER o DROP de tabla: esas operaciones pertenecen al propietario del objeto y a los roles que heredan sus privilegios. Otorgar ALL en una tabla a un rol diferente no convierte a ese rol en propietario. EmDash no ejecuta SET ROLE, por lo 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. Esta es la opción más simple cuando la base de datos está dedicada a EmDash. En los ejemplos a continuación, 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. Otorgue acceso con una conexión administrativa, sustituyendo los nombres de su 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 nuevos objetos. No cambian el propietario de las tablas existentes; use el manual de reparación de propiedad PostgreSQL cuando un sitio existente tiene 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 del despliegue:
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 sus objetos aislados de public. Esto 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 de public ni repara la propiedad mixta. Los sitios existentes deben mantener su esquema actual y usar el manual de reparación de propiedad PostgreSQL en su lugar.
Pool de conexiones
El adaptador usa pg.Pool. Ajuste el tamaño del pool según su despliegue:
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 — o compatible con Postgres (p. ej. PlanetScale Postgres) — existente. 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 inicial
Primero prepare el rol PostgreSQL. Luego cree la configuración de Hyperdrive con la cadena de conexión de ese rol y agregue 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": "<su-id-de-hyperdrive>"
}
]
} wrangler.toml
[[hyperdrive]]
binding = "HYPERDRIVE"
id = "<su-id-de-hyperdrive>" Configuración
| Opción | Tipo | Predeterminado | Descripción |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Nombre del binding primario de Hyperdrive (caché deshabilitado) |
cachedBinding | string | — | Binding opcional con caché habilitado para lecturas anónimas (ver abajo) |
preferUncachedAfterWriteMs | number | 60000* | Después de publicar contenido, preferir binding durante estos ms en lecturas públicas anónimas (igualar al 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 en el Worker hacia Hyperdrive |
*El predeterminado 60000 aplica solo cuando cachedBinding está configurado; ignorado en caso contrario.
Servir lecturas anónimas desde caché
Por defecto se deshabilita completamente el caché de Hyperdrive, porque el admin y las escrituras necesitan consistencia de lectura-después-de-escritura. Pero las solicitudes públicas anónimas con GET o HEAD pueden tolerar una breve ventana de desactualización. Si ese compromiso es aceptable, ejecute dos configuraciones de Hyperdrive sobre la misma base de datos: una con caché desactivado (el binding primario) y una con caché activado (cachedBinding). EmDash enruta esas solicitudes públicas anónimas a través del binding con caché habilitado y cualquier otra solicitud a través de la primaria sin caché.
# Primario — caché DESACTIVADO (usado por admin, solicitudes autenticadas, escrituras, migraciones)
wrangler hyperdrive create emdash-db \
--connection-string "postgres://user:password@host/db?sslmode=verify-full" \
--caching-disabled
# Con caché — MISMA base de datos y cadena de conexión, caché ACTIVADO
wrangler hyperdrive create emdash-db-cached \
--connection-string "postgres://user:password@host/db?sslmode=verify-full"
{
"hyperdrive": [
{ "binding": "HYPERDRIVE", "id": "<id-con-caché-desactivado>" },
{ "binding": "HYPERDRIVE_CACHED", "id": "<id-con-caché-activado>" }
]
}
database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" });
Este es el patrón de dos configuraciones que Cloudflare documenta para caché. EmDash decide qué binding usar por solicitud:
- Lecturas anónimas de rutas públicas (
GET/HEAD, sin sesión, no bajo/_emdash) →cachedBindingcon caché habilitado, excepto por una breve ventana después de publicar contenido (predeterminado 60s; establezcapreferUncachedAfterWriteMsa sumax_agede Hyperdrive) cuando EmDash prefiere elbindingsin caché para que una reconstrucción no pueda llenar cachés edge/objeto con resultados aún desactualizados de Hyperdrive. - Solicitudes autenticadas (editores, autores) →
bindingsin caché. - Solicitudes de mutación (
POST,PUT,PATCH,DELETE, incluidas anónimas) →bindingsin caché. - Cualquier solicitud bajo
/_emdash(admin, configuración, auth, APIs internas), incluso unGETanónimo →bindingsin caché. - Migraciones en tiempo de ejecución y arranque en frío → siempre el
bindingprimario. - Migraciones gestionadas por despliegue → se conectan directamente al origen PostgreSQL usando
migrationConnectionStringEnv; nunca usan ningún binding de Hyperdrive.
Opcional: usar un rol con caché separado
Las migraciones, configuración, solicitudes autenticadas y 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 404s. Para preservar esas características, el rol con caché necesita adicionalmente UPDATE en _emdash_redirects y SELECT, INSERT, UPDATE y DELETE en _emdash_404_log. Los plugins o código de aplicación que escriben 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 con caché restringido.
Agregue el rol con caché después de que EmDash haya completado sus migraciones iniciales. Los ejemplos a continuación usan el esquema opcional emdash; sustituya su esquema activo, como public. Cree el inicio de sesión y la configuración de base de datos con el rol administrativo de su proveedor:
CREATE ROLE emdash_cached LOGIN PASSWORD 'reemplazar-con-un-secreto';
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 otorgar 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. Otorgue acceso a tablas individuales de EmDash en su lugar, y actualice esos grants cuando se agreguen colecciones u otros objetos del esquema.
Migraciones core
EmDash ejecuta migraciones core automáticamente por defecto para cada dialecto soportado. Astro build y sync también emiten un .emdash/migrations.json validado y sin secretos, que emdash migrate puede aplicar antes del despliegue. SQLite, libSQL, PostgreSQL, D1 y el origen PostgreSQL directo detrás de Hyperdrive tienen ejecutores de despliegue.
Vea Gestionar migraciones de base de datos core para credenciales de destino, serialización CI, política de tiempo de ejecución auto/check/manual y recuperación de registros desconocidos o escrituras ambiguas de D1.
Para PostgreSQL, las migraciones en tiempo de ejecución se ejecutan a través de la conexión configurada; las migraciones en tiempo de ejecución de Hyperdrive siempre usan el binding primario. Las migraciones gestionadas por despliegue de Hyperdrive se conectan directamente al origen PostgreSQL. Las migraciones core pueden crear tablas, índices y funciones, alterar o eliminar columnas y restricciones, y actualizar filas existentes. Un rol que puede conectar y modificar filas pero que no es propietario de los objetos existentes de EmDash 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 inicio. El seed se lee de .emdash/seed.json, la ruta en package.json#emdash.seed, o seed/seed.json — lo que se encuentre primero — y se integra en la compilación en tiempo de compilación. Si no hay ninguno presente, se usa un seed predeterminado integrado. Los inicios posteriores contra una base de datos existente no modifican su contenido.
Usar bases de datos separadas para entornos separados
Dé a desarrollo, vista previa, staging y producción su propia base de datos. Un despliegue de vista previa apuntando a producción puede ejecutar migraciones core 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 ejecución. Mantenga las credenciales en secretos de tiempo de ejecución, no en astro.config.mjs.