O EmDash suporta múltiplos backends de banco de dados. Escolha com base no seu alvo de deploy.
Visão geral
| Banco de dados | Melhor para | Deploy |
|---|---|---|
| D1 | Cloudflare Workers | Edge, distribuído globalmente |
| Hyperdrive | PostgreSQL no Cloudflare Workers | Edge, Postgres existente |
| PostgreSQL | Produção Node.js | Qualquer plataforma com Postgres |
| libSQL | Bancos de dados remotos | Edge ou Node.js |
| SQLite | Node.js, desenvolvimento local | Servidor único |
Cloudflare D1
D1 é o banco de dados SQLite serverless da Cloudflare. Use ao fazer deploy no Cloudflare Workers.
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({ binding: "DB" }),
}),
],
});
Configuração
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
binding | string | — | Nome do binding D1 do wrangler.jsonc |
session | string | "disabled" | Modo de replicação de leitura (veja abaixo) |
bookmarkCookie | string | "__em_d1_bookmark" | Nome do cookie para bookmarks de sessão |
Setup
wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "emdash-db"
}
]
} wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "emdash-db" Réplicas de leitura
D1 suporta replicação de leitura para reduzir a latência de leitura para sites distribuídos globalmente. Quando habilitada, as consultas de leitura são roteadas para réplicas próximas em vez de sempre acessar o banco primário.
O EmDash usa a API D1 Sessions para gerenciar isso transparentemente. Habilite com a opção session:
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({
binding: "DB",
session: "auto",
}),
}),
],
});
Modos de sessão
| Modo | Comportamento |
|---|---|
"disabled" | Sem sessões. Todas as consultas vão para o primário. Padrão. |
"auto" | Requisições anônimas leem da réplica mais próxima. Usuários autenticados obtêm consistência read-your-writes via cookies bookmark. |
"primary-first" | Como "auto", mas a primeira consulta sempre vai para o primário. Para sites com escritas muito frequentes. |
Como funciona
- Visitantes anônimos obtêm
first-unconstrained— leituras vão para a réplica mais próxima para menor latência. Como usuários anônimos nunca escrevem, não precisam de garantias de consistência. - Usuários autenticados (editores, autores) obtêm sessões baseadas em bookmarks. Após uma escrita, um cookie bookmark garante que a próxima requisição veja pelo menos aquele estado.
- Requisições de escrita (
POST,PUT,DELETE) sempre começam no banco primário. - Consultas em tempo de build (Astro content collections) ignoram sessões completamente e usam o primário diretamente.
libSQL
libSQL é um fork do SQLite que suporta conexões remotas. Use quando precisar de um banco remoto sem 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,
}),
}),
],
});
Configuração
| Opção | Tipo | Descrição |
|---|---|---|
url | string | URL do banco (libsql://... ou file:...) |
authToken | string | Token de autenticação runtime para bancos remotos (opcional para local) |
migrationAuthTokenEnv | string | Nome da variável do token de migração (padrão TURSO_AUTH_TOKEN) |
Desenvolvimento local
Use um arquivo libSQL local durante o desenvolvimento:
database: libsql({ url: "file:./data.db" });
PostgreSQL
PostgreSQL é suportado para deploys Node.js que precisam de um banco relacional completo.
import { postgres } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: postgres({
connectionString: process.env.DATABASE_URL,
}),
}),
],
});
Configuração
Você pode conectar com uma string de conexão ou parâmetros individuais:
// String de conexão
database: postgres({
connectionString: "postgres://user:password@localhost:5432/emdash",
});
// Parâmetros individuais
database: postgres({
host: "localhost",
port: 5432,
database: "emdash",
user: "emdash",
password: process.env.DB_PASSWORD,
ssl: true,
});
| Opção | Tipo | Descrição |
|---|---|---|
connectionString | string | URL de conexão PostgreSQL |
host | string | Host do banco |
port | number | Porta do banco |
database | string | Nome do banco |
user | string | Usuário do banco |
password | string | Senha do banco |
ssl | boolean | Habilitar SSL |
pool.min | number | Conexões pool mínimas (padrão 0) |
pool.max | number | Conexões pool máximas (padrão 10) |
pool.connectionTimeoutMillis | number | Espera máxima de conexão (pg padrão: 0, sem timeout) |
pool.idleTimeoutMillis | number | Tempo de vida do cliente ocioso (pg padrão: 10.000 ms) |
migrationConnectionStringEnv | string | Nome da variável da string de conexão de migração (padrão DATABASE_URL) |
Defina pool.connectionTimeoutMillis para um valor diferente de zero para limitar quanto tempo uma requisição espera quando o PostgreSQL está inacessível ou nenhuma conexão do pool está disponível. Defina pool.idleTimeoutMillis para 0 para manter clientes ociosos abertos até o pool fechar. Omitir qualquer opção preserva o padrão do pg.
Requisitos do role do banco
O EmDash cria e atualiza suas próprias tabelas PostgreSQL. Migrações core criam e alteram tabelas do sistema e de coleções, tipos de conteúdo criam tabelas ec_*, e adicionar ou remover um campo altera sua tabela de coleção. O role PostgreSQL configurado precisa de autoridade sobre o schema durante toda a vida do site, não apenas durante a configuração inicial.
Use um role canônico para o EmDash. Ele precisa de:
CONNECTno banco;USAGEeCREATEno schema ativo;- propriedade de cada tabela e função do EmDash, diretamente ou através de membership com
INHERITno role proprietário; e SELECT,INSERT,UPDATEeDELETEnessas tabelas.
Não precisa ser superusuário, ter CREATEDB ou CREATEROLE, nem criar extensões. PostgreSQL não fornece um grant ALTER ou DROP em tabelas: essas operações pertencem ao proprietário do objeto e aos roles que herdam seus privilégios. Conceder ALL em uma tabela a um role diferente não torna esse role proprietário. O EmDash não executa SET ROLE, então membership configurada sem herança não é suficiente.
A maioria das instalações pode usar o schema existente do banco, comumente public. Esta é a opção mais simples quando o banco é dedicado ao EmDash. Nos exemplos abaixo, emdash_app é o role de login na string de conexão do EmDash; use um role de provedor existente ou crie um login dedicado. Conceda acesso com uma conexão administrativa, substituindo seus nomes de banco, schema e role:
GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;
Esses grants permitem que o role crie novos objetos. Não mudam o proprietário de tabelas existentes; veja Reparar propriedade mista PostgreSQL.
O EmDash usa o current_schema() ativo do PostgreSQL. Não cria um schema nem define search_path, então verifique a conexão antes do deploy:
SELECT
current_database(),
session_user,
current_user,
current_schema(),
current_setting('search_path');
Opcional: usar um schema dedicado
Use um schema dedicado quando o EmDash compartilha um banco com outra aplicação ou quando você quer seus objetos isolados de public. Isso é opcional e mais fácil de configurar antes da primeira instalação do EmDash. Um banco dedicado ao EmDash não precisa de schema separado.
Assumindo que o role canônico emdash_app já existe, crie e selecione seu schema com uma conexão 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;
Isso não move uma instalação existente de public nem repara propriedade mista. Sites existentes devem manter seu schema atual e seguir Reparar propriedade mista PostgreSQL.
Pool de conexões
O adaptador usa pg.Pool internamente. Ajuste o tamanho do pool baseado no seu deploy:
database: postgres({
connectionString: process.env.DATABASE_URL,
pool: { min: 2, max: 20 },
});
Hyperdrive
Use o adaptador hyperdrive() para rodar o EmDash no Cloudflare Workers com um banco PostgreSQL existente — ou compatível com Postgres (ex: PlanetScale Postgres). O Hyperdrive faz pool e acelera a conexão pela rede da Cloudflare; o dialeto PostgreSQL do EmDash executa as 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 no seu site (pnpm add pg)compatibility_flags: ["nodejs_compat"]compatibility_date >= "2024-09-23"
Setup
Primeiro prepare o role PostgreSQL. Depois crie a configuração Hyperdrive com a string de conexão desse role e adicione o binding à sua config 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>" Configuração
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Nome do binding primário (cache desabilitado) Hyperdrive |
cachedBinding | string | — | Binding opcional com cache habilitado para leituras anônimas (veja abaixo) |
preferUncachedAfterWriteMs | number | 60000* | Após publicação, preferir binding por estes ms em leituras públicas anônimas (corresponder ao Hyperdrive max_age) |
migrationConnectionStringEnv | string | CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING> | Variável de ambiente contendo a URL direta do PostgreSQL origin para emdash migrate |
max | number | 5 | Tamanho máximo do pool de conexões no Worker para o Hyperdrive |
*Padrão 60000 aplica-se apenas quando cachedBinding está definido; ignorado caso contrário.
Servindo leituras anônimas do cache
Por padrão você desabilita o cache do Hyperdrive completamente. Mas requisições públicas anônimas usando GET ou HEAD podem tolerar uma curta janela de desatualização. Se aceitável, rode duas configurações Hyperdrive sobre o mesmo banco: uma com cache desabilitado (o binding primário) e uma com cache habilitado (cachedBinding). O EmDash roteia requisições públicas anônimas pelo binding com cache e todas as outras pelo primário sem cache.
# Primário — cache OFF
wrangler hyperdrive create emdash-db \
--connection-string "postgres://user:password@host/db?sslmode=verify-full" \
--caching-disabled
# Com cache — MESMO role e string de conexão, cache 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 é o padrão de duas configurações que a Cloudflare documenta para cache. O EmDash decide qual binding usar por requisição:
- Leituras anônimas de caminhos do site público (
GET/HEAD, sem sessão, não sob/_emdash) →cachedBindingcom cache habilitado, exceto por uma curta janela após publicação de conteúdo (padrão 60s; definapreferUncachedAfterWriteMspara seu Hyperdrivemax_age). - Requisições autenticadas (editores, autores) →
bindingsem cache. - Requisições de mutação (
POST,PUT,PATCH,DELETE, incluindo anônimas) →bindingsem cache. - Qualquer requisição sob
/_emdash(admin, setup, auth, APIs internas), mesmo umGETanônimo →bindingsem cache. - Migrações runtime e cold-start → sempre o
bindingprimário. - Migrações gerenciadas por deploy → conectam diretamente ao origin PostgreSQL via
migrationConnectionStringEnv.
Opcional: usar um role com cache separado
Migrações, setup, requisições autenticadas e escritas explícitas sempre usam o binding primário. Um role separado para cachedBinding não precisa de propriedade do schema ou CREATE, mas precisa de CONNECT, USAGE do schema e SELECT em cada tabela usada pelo site público.
Requisições públicas anônimas GET e HEAD também podem registrar hits de redirecionamento e 404s. Para preservar essas funcionalidades, o role com cache precisa adicionalmente de UPDATE em _emdash_redirects e SELECT, INSERT, UPDATE, DELETE em _emdash_404_log.
Adicione o role com cache após o EmDash completar suas migrações iniciais:
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;
Depois conecte como emdash_app para conceder acesso:
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;
Conecte com ambos os roles e verifique que reportam o mesmo current_database() e current_schema() antes de habilitar cachedBinding.
Reparar propriedade mista PostgreSQL
Se um site usou múltiplos usuários PostgreSQL, primeiro escolha o role canônico que a conexão principal do EmDash continuará usando. Faça backup e pare mudanças de schema durante o reparo.
Inspecione cada tabela no schema ativo:
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;
Inspecione propriedade de funções:
SELECT
n.nspname AS schema_name,
p.proname AS function_name,
pg_get_function_identity_arguments(p.oid) AS arguments,
pg_get_userbyid(p.proowner) AS owner
FROM pg_proc AS p
JOIN pg_namespace AS n ON n.oid = p.pronamespace
WHERE n.nspname = current_schema()
ORDER BY p.proname, arguments;
Transfira cada objeto com nomes qualificados por schema:
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 usa o driver de banco integrado do Node.js e é a opção mais simples para deploys Node.js.
import { sqlite } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
}),
],
});
Configuração
| Opção | Tipo | Descrição |
|---|---|---|
url | string | Caminho de arquivo com prefixo file: |
Caminho do arquivo
A url deve começar com file::
// Caminho relativo
database: sqlite({ url: "file:./data/emdash.db" });
// Caminho absoluto
database: sqlite({ url: "file:/var/data/emdash.db" });
// De variável de ambiente
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });
Migrações
O EmDash executa migrações core automaticamente por padrão para cada dialeto suportado. Astro build e sync também emitem um .emdash/migrations.json validado e sem secrets, que emdash migrate pode aplicar antes do deploy. SQLite, libSQL, PostgreSQL, D1 e o origin PostgreSQL direto atrás do Hyperdrive têm executores de deploy.
Veja Gerenciar Migrações do Banco Core para credenciais de destino, serialização CI, política runtime auto/check/manual e recuperação de registros desconhecidos ou escritas D1 ambíguas.
Se o banco está vazio (sem coleções) e o assistente de setup não foi completado, o EmDash aplica um arquivo seed no primeiro boot. O seed é lido de .emdash/seed.json, do caminho em package.json#emdash.seed, ou seed/seed.json — o que for encontrado primeiro — e incorporado no build em tempo de compilação. Se nenhum estiver presente, um seed padrão embutido é usado.
Configuração baseada em ambiente
Use diferentes bancos por ambiente:
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 })],
});
A escolha também pode depender de uma variável de ambiente ao invés do modo de build:
const database = process.env.DATABASE_URL
? postgres({ connectionString: process.env.DATABASE_URL })
: sqlite({ url: "file:./data.db" });