Scegliere un database

In questa pagina

Scegli un adattatore di database per ogni distribuzione. Il database contiene il modello di contenuto, le voci, gli utenti, le impostazioni e i dati dei plugin. I file multimediali appartengono a un backend di archiviazione separato.

Panoramica

DatabaseQuando usarloRuntime
SQLiteUn processo Node.js ha un disco persistenteNode.js o sviluppo locale
D1Il sito gira su Cloudflare Workers e deve usare Cloudflare SQLCloudflare Workers
HyperdriveIl sito gira su Workers e deve usare un’origine PostgreSQL esistenteCloudflare Workers
PostgreSQLPiù processi Node.js necessitano di un database condivisoNode.js
libSQLUna distribuzione Node.js necessita di un database remoto compatibile con SQLiteNode.js

D1 è il predefinito per i template Cloudflare. SQLite è l’opzione Node.js più semplice, ma richiede un volume persistente scrivibile e backup operativi del database.

SQLite

SQLite usa il driver di database integrato di Node.js ed è l’opzione più semplice per le distribuzioni Node.js.

import { sqlite } from "emdash/db";

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

Configurazione

OpzioneTipoDescrizione
urlstringPercorso file con prefisso file:

Percorso file

L’url deve iniziare con file::

// Percorso relativo
database: sqlite({ url: "file:./data/emdash.db" });

// Percorso assoluto
database: sqlite({ url: "file:/var/data/emdash.db" });

// Da variabile d'ambiente
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });

Cloudflare D1

D1 è il database SQLite serverless di Cloudflare. Usalo quando distribuisci su Cloudflare Workers.

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

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

Configurazione

OpzioneTipoPredefinitoDescrizione
bindingstringNome del binding D1 da wrangler.jsonc
sessionstring"disabled"Modalità di replicazione in lettura (vedi sotto)
bookmarkCookiestring"__em_d1_bookmark"Nome del cookie per i segnalibri di sessione

Binding Wrangler

wrangler.jsonc

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "emdash-db"
    }
  ]
}

wrangler.toml

[[d1_databases]]
binding = "DB"
database_name = "emdash-db"

Wrangler può provisionare un database D1 mancante da questo binding durante la distribuzione. Le migrazioni EmDash sono un passaggio separato. Segui Distribuire su Cloudflare per l’insieme completo dei binding e Gestire le migrazioni core del database per il runbook delle migrazioni.

Repliche di lettura

D1 supporta la replicazione di lettura per ridurre la latenza di lettura per siti distribuiti globalmente. Quando abilitata, le query di lettura vengono instradate verso repliche vicine invece di accedere sempre al database primario.

EmDash usa l’API Sessions di D1 per gestire questo in modo trasparente. Abilitalo con l’opzione session:

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

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

Modalità sessione

ModalitàComportamento
"disabled"Nessuna sessione. Tutte le query vanno al primario. Predefinito.
"auto"Le richieste anonime leggono dalla replica più vicina. Gli utenti autenticati ottengono coerenza lettura-dopo-scrittura tramite cookie segnalibro.
"primary-first"Come "auto", ma la prima query va sempre al primario. Usa per siti con scritture molto frequenti.

Come funziona

  • I visitatori anonimi ottengono first-unconstrained — le letture vanno alla replica più vicina per la latenza più bassa. Poiché gli utenti anonimi non scrivono mai, non hanno bisogno di garanzie di coerenza.
  • Gli utenti autenticati (editor, autori) ottengono sessioni basate su segnalibri. Dopo una scrittura, un cookie segnalibro garantisce che la richiesta successiva veda almeno quello stato.
  • Le richieste di scrittura (POST, PUT, DELETE) iniziano sempre dal database primario.
  • Le query in fase di build (Astro content collections) bypassano completamente le sessioni e usano direttamente il primario.

libSQL

libSQL è un fork di SQLite che supporta connessioni remote. Usalo quando hai bisogno di un database remoto senza 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,
			}),
		}),
	],
});

Configurazione

OpzioneTipoDescrizione
urlstringURL del database (libsql://... o file:...)
authTokenstringToken di autenticazione runtime per database remoti (opzionale in locale)
migrationAuthTokenEnvstringNome variabile del token di migrazione (predefinito TURSO_AUTH_TOKEN)

Sviluppo locale

Usa un file libSQL locale durante lo sviluppo:

database: libsql({ url: "file:./data.db" });

PostgreSQL

PostgreSQL è supportato per le distribuzioni Node.js che necessitano di un database relazionale completo.

import { postgres } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: postgres({
				connectionString: process.env.DATABASE_URL,
			}),
		}),
	],
});

Configurazione

Puoi connetterti con una stringa di connessione o parametri individuali:

// Stringa di connessione
database: postgres({
	connectionString: "postgres://user:password@localhost:5432/emdash",
});

// Parametri individuali
database: postgres({
	host: "localhost",
	port: 5432,
	database: "emdash",
	user: "emdash",
	password: process.env.DB_PASSWORD,
	ssl: true,
});
OpzioneTipoDescrizione
connectionStringstringURL di connessione PostgreSQL
hoststringHost del database
portnumberPorta del database
databasestringNome del database
userstringUtente del database
passwordstringPassword del database
sslbooleanAbilitare SSL
pool.minnumberConnessioni minime del pool (predefinito 0)
pool.maxnumberConnessioni massime del pool (predefinito 10)
pool.connectionTimeoutMillisnumberAttesa massima di connessione (predefinito pg: 0, nessun timeout)
pool.idleTimeoutMillisnumberDurata client inattivo (predefinito pg: 10.000 ms)
migrationConnectionStringEnvstringNome variabile stringa di connessione migrazione (predefinito DATABASE_URL)

Imposta pool.connectionTimeoutMillis a un valore diverso da zero per limitare quanto una richiesta attende quando PostgreSQL non è raggiungibile o nessuna connessione del pool diventa disponibile. Imposta pool.idleTimeoutMillis a 0 per mantenere i client inattivi aperti fino alla chiusura del pool. Omettere entrambe le opzioni preserva il valore predefinito di pg.

Requisiti del ruolo database

EmDash crea e aggiorna le proprie tabelle PostgreSQL. Le migrazioni core creano e modificano tabelle di sistema e di collezioni, i tipi di contenuto creano tabelle ec_*, e l’aggiunta o rimozione di un campo modifica la tabella della sua collezione. Il ruolo PostgreSQL configurato necessita quindi di autorità sullo schema per l’intera durata del sito, non solo durante la configurazione iniziale.

Usa un ruolo canonico per EmDash. Necessita di:

  • CONNECT sul database;
  • USAGE e CREATE sullo schema attivo;
  • proprietà di ogni tabella e funzione EmDash, direttamente o tramite appartenenza con INHERIT al ruolo proprietario; e
  • SELECT, INSERT, UPDATE e DELETE su quelle tabelle.

Non deve essere superutente, avere CREATEDB o CREATEROLE, né creare estensioni. PostgreSQL non fornisce un grant ALTER o DROP sulle tabelle: queste operazioni appartengono al proprietario dell’oggetto e ai ruoli che ne ereditano i privilegi. Concedere ALL su una tabella a un ruolo diverso non rende quel ruolo proprietario. EmDash non esegue SET ROLE, quindi l’appartenenza configurata senza ereditarietà non è sufficiente.

La maggior parte delle installazioni può usare lo schema esistente del database, comunemente public. Questa è l’opzione più semplice quando il database è dedicato a EmDash. Negli esempi seguenti, emdash_app è il ruolo di login nella stringa di connessione di EmDash; usa un ruolo fornitore esistente o crea un login dedicato. Concedi l’accesso con una connessione amministrativa, sostituendo i nomi del tuo database, schema e ruolo:

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

Questi grant permettono al ruolo di creare nuovi oggetti. Non cambiano il proprietario delle tabelle esistenti; usa il runbook di riparazione proprietà PostgreSQL quando un sito esistente ha proprietari misti.

EmDash usa il current_schema() attivo di PostgreSQL. Non crea uno schema né imposta search_path, quindi verifica la connessione prima della distribuzione:

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

Opzionale: usare uno schema dedicato

Usa uno schema dedicato quando EmDash condivide un database con un’altra applicazione o quando vuoi i suoi oggetti isolati da public. Questo è opzionale ed è più facile da configurare prima della prima configurazione di EmDash. Un database dedicato a EmDash non ha bisogno di uno schema separato.

Assumendo che il ruolo canonico emdash_app esista già, crea e seleziona il suo schema con una connessione amministrativa:

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;

Questo non sposta un’installazione esistente da public né ripara la proprietà mista. I siti esistenti dovrebbero mantenere il loro schema attuale e usare il runbook di riparazione proprietà PostgreSQL al suo posto.

Pool di connessioni

L’adattatore usa pg.Pool. Regola la dimensione del pool in base alla tua distribuzione:

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

Hyperdrive

Usa l’adattatore hyperdrive() per eseguire EmDash su Cloudflare Workers supportato da un database PostgreSQL — o compatibile con Postgres (es. PlanetScale Postgres) — esistente. Hyperdrive raggruppa e accelera la connessione sulla rete Cloudflare; il dialetto PostgreSQL di EmDash esegue le query.

import { hyperdrive, r2 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: hyperdrive({ binding: "HYPERDRIVE" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

Requisiti

  • pg >= 8.16.3 installato nel tuo sito (pnpm add pg)
  • compatibility_flags: ["nodejs_compat"]
  • compatibility_date >= "2024-09-23"

Configurazione iniziale

Prepara prima il ruolo PostgreSQL. Poi crea la configurazione Hyperdrive con la stringa di connessione di quel ruolo e aggiungi il binding alla tua configurazione Wrangler:

wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

wrangler.jsonc

{
  "hyperdrive": [
    {
      "binding": "HYPERDRIVE",
      "id": "<il-tuo-id-hyperdrive>"
    }
  ]
}

wrangler.toml

[[hyperdrive]]
binding = "HYPERDRIVE"
id = "<il-tuo-id-hyperdrive>"

Configurazione

OpzioneTipoPredefinitoDescrizione
bindingstring"HYPERDRIVE"Nome del binding Hyperdrive primario (cache disabilitato)
cachedBindingstringBinding opzionale con cache abilitato per letture anonime (vedi sotto)
preferUncachedAfterWriteMsnumber60000*Dopo una pubblicazione, preferire binding per questi ms su letture pubbliche anonime (allineare al max_age di Hyperdrive)
migrationConnectionStringEnvstringCLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>Variabile d’ambiente contenente l’URL diretto dell’origine PostgreSQL per emdash migrate
maxnumber5Dimensione massima del pool di connessioni in-Worker verso Hyperdrive

*Il predefinito 60000 si applica solo quando cachedBinding è impostato; ignorato altrimenti.

Servire letture anonime dalla cache

Per impostazione predefinita si disabilita completamente il cache di Hyperdrive, perché l’admin e le scritture necessitano di coerenza lettura-dopo-scrittura. Ma le richieste pubbliche anonime con GET o HEAD possono tollerare una breve finestra di obsolescenza. Se questo compromesso è accettabile, esegui due configurazioni Hyperdrive sullo stesso database: una con cache disabilitato (il binding primario) e una con cache abilitato (cachedBinding). EmDash instrada quelle richieste pubbliche anonime attraverso il binding con cache e ogni altra richiesta attraverso il primario senza cache.

# Primario — cache DISABILITATO (per admin, richieste autenticate, scritture, migrazioni)
wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

# Con cache — STESSO ruolo e stringa di connessione, cache ABILITATO
wrangler hyperdrive create emdash-db-cached \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full"
{
	"hyperdrive": [
		{ "binding": "HYPERDRIVE", "id": "<id-cache-disabilitato>" },
		{ "binding": "HYPERDRIVE_CACHED", "id": "<id-cache-abilitato>" }
	]
}
database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" });

Questo è il pattern a due configurazioni che Cloudflare documenta per il cache. EmDash decide quale binding usare per richiesta:

  • Letture anonime di percorsi pubblici (GET/HEAD, nessuna sessione, non sotto /_emdash) → cachedBinding con cache, eccetto per una breve finestra dopo una pubblicazione (predefinito 60s; imposta preferUncachedAfterWriteMs al tuo max_age di Hyperdrive) quando EmDash preferisce il binding senza cache affinché una ricostruzione non possa riempire le cache edge/oggetto con risultati Hyperdrive ancora obsoleti.
  • Richieste autenticate (editor, autori) → binding senza cache.
  • Richieste di mutazione (POST, PUT, PATCH, DELETE, incluse quelle anonime) → binding senza cache.
  • Qualsiasi richiesta sotto /_emdash (admin, configurazione, auth, API interne), anche un GET anonimo → binding senza cache.
  • Migrazioni runtime e avvio a freddo → sempre il binding primario.
  • Migrazioni gestite dalla distribuzione → si connettono direttamente all’origine PostgreSQL usando migrationConnectionStringEnv; non usano mai nessun binding Hyperdrive.

Opzionale: usare un ruolo con cache separato

Le migrazioni, la configurazione, le richieste autenticate e le richieste di scrittura esplicite usano sempre il binding primario. Un ruolo separato per cachedBinding non necessita di proprietà dello schema o CREATE, ma necessita di CONNECT, USAGE dello schema e SELECT su ogni tabella usata dal sito pubblico.

Le richieste pubbliche anonime GET e HEAD possono anche registrare hit di redirect e 404. Per preservare queste funzionalità, il ruolo con cache necessita inoltre di UPDATE su _emdash_redirects e SELECT, INSERT, UPDATE e DELETE su _emdash_404_log. Plugin o codice applicativo che scrivono durante un GET o HEAD pubblico possono richiedere di più. Usa lo stesso ruolo per entrambi i binding a meno che tu non abbia testato il sito con un ruolo con cache ristretto.

Aggiungi il ruolo con cache dopo che EmDash ha completato le sue migrazioni iniziali. Gli esempi seguenti usano lo schema opzionale emdash; sostituisci il tuo schema attivo, come public. Crea il login e le impostazioni del database con il ruolo amministrativo del tuo fornitore:

CREATE ROLE emdash_cached LOGIN PASSWORD 'sostituire-con-un-segreto';
GRANT CONNECT ON DATABASE app TO emdash_cached;
ALTER ROLE emdash_cached IN DATABASE app SET search_path = emdash;

Poi connettiti come emdash_app, il proprietario dello schema e delle tabelle, per concedere accesso a tabelle esistenti e future:

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;

Connettiti con entrambi i ruoli e verifica che riportino lo stesso current_database() e current_schema() prima di abilitare cachedBinding. Su uno schema condiviso, GRANT SELECT ON ALL TABLES espone anche tabelle non correlate. Concedi invece l’accesso a singole tabelle EmDash e aggiorna quei grant quando vengono aggiunte collezioni o altri oggetti dello schema.

Migrazioni core

EmDash esegue le migrazioni core automaticamente per impostazione predefinita per ogni dialetto supportato. Astro build e sync emettono anche un .emdash/migrations.json validato e senza segreti, che emdash migrate può applicare prima della distribuzione. SQLite, libSQL, PostgreSQL, D1 e l’origine PostgreSQL diretta dietro Hyperdrive hanno esecutori di distribuzione.

Vedi Gestire le migrazioni core del database per le credenziali di destinazione, la serializzazione CI, la policy runtime auto/check/manual e il recupero da record sconosciuti o scritture D1 ambigue.

Per PostgreSQL, le migrazioni runtime passano attraverso la connessione configurata; le migrazioni runtime di Hyperdrive usano sempre il binding primario. Le migrazioni Hyperdrive gestite dalla distribuzione si connettono direttamente all’origine PostgreSQL. Le migrazioni core possono creare tabelle, indici e funzioni, modificare o eliminare colonne e vincoli, e aggiornare righe esistenti. Un ruolo che può connettersi e modificare righe ma non possiede gli oggetti EmDash esistenti non è sufficiente. La procedura guidata di configurazione non può riparare i privilegi del database mancanti perché le migrazioni runtime vengono eseguite prima della configurazione.

Se il database è vuoto (nessuna collezione) e la procedura guidata di configurazione non è stata completata, EmDash applica anche un file seed al primo avvio. Il seed viene letto da .emdash/seed.json, dal percorso in package.json#emdash.seed, o da seed/seed.json — il primo trovato — e incorporato nel build in fase di compilazione. Se nessuno è presente, viene usato un seed predefinito integrato. Gli avvii successivi contro un database esistente lasciano il suo contenuto invariato.

Usare database separati per ambienti separati

Dai a sviluppo, anteprima, staging e produzione il proprio database. Una distribuzione di anteprima che punta alla produzione può eseguire migrazioni core o comandi distruttivi del modello di contenuto contro dati in produzione.

Per Cloudflare, definisci ogni binding D1 o Hyperdrive sotto l’ambiente Wrangler corrispondente e passa --env ai comandi Wrangler. Per Node.js, inietta un URL di database diverso in ogni ambiente di esecuzione. Mantieni le credenziali nei segreti di runtime, non in astro.config.mjs.