Opzioni database

In questa pagina

EmDash supporta più backend di database. Scegli in base al tuo obiettivo di deployment.

Panoramica

DatabaseIdeale perDeployment
D1Cloudflare WorkersEdge, distribuito globalmente
HyperdrivePostgreSQL su Cloudflare WorkersEdge, Postgres esistente
PostgreSQLProduzione Node.jsQualsiasi piattaforma con Postgres
libSQLDatabase remotiEdge o Node.js
SQLiteNode.js, sviluppo localeServer singolo

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 replica di lettura (vedi sotto)
bookmarkCookiestring"__em_d1_bookmark"Nome del cookie per i bookmark di sessione

Configurazione

wrangler.jsonc

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

wrangler.toml

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

Repliche di lettura

D1 supporta la replica di lettura per ridurre la latenza di lettura per i siti distribuiti globalmente. Quando abilitata, le query di lettura vengono instradate verso repliche vicine invece di colpire sempre il database primario.

EmDash usa l’API Sessions 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à di 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 consistenza read-your-writes tramite cookie bookmark.
"primary-first"Come "auto", ma la prima query va sempre al primario. Per siti con scritture molto frequenti.

Come funziona

  • 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 consistenza.
  • Utenti autenticati (editor, autori) ottengono sessioni basate su bookmark. Dopo una scrittura, un cookie bookmark assicura che la richiesta successiva veda almeno quello stato.
  • Richieste di scrittura (POST, PUT, DELETE) iniziano sempre al database primario.
  • 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 per 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 deployment 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
sslbooleanAbilita SSL
pool.minnumberConnessioni pool minime (predefinito 0)
pool.maxnumberConnessioni pool massime (predefinito 10)
pool.connectionTimeoutMillisnumberAttesa massima connessione (pg predefinito: 0, nessun timeout)
pool.idleTimeoutMillisnumberDurata vita client inattivo (pg predefinito: 10.000 ms)
migrationConnectionStringEnvstringNome variabile stringa connessione migrazione (predefinito DATABASE_URL)

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

Requisiti del ruolo database

EmDash crea e aggiorna le proprie tabelle PostgreSQL. Le migrazioni core creano e alterano tabelle di sistema e di collezione, i tipi di contenuto creano tabelle ec_*, e aggiungere o rimuovere un campo altera la sua tabella di 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, sia direttamente che attraverso appartenenza con INHERIT nel 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 tabella: quelle operazioni appartengono al proprietario dell’oggetto e ai ruoli che ereditano i suoi privilegi. Concedere ALL su una tabella a un ruolo diverso non rende quel ruolo proprietario. EmDash non esegue SET ROLE, quindi un’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 sotto, emdash_app è il ruolo di login nella stringa di connessione di EmDash; usa un ruolo provider esistente o crea un login dedicato. Concedi accesso con una connessione amministrativa, sostituendo i tuoi nomi di 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; vedi Riparare proprietà mista PostgreSQL.

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

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 necessita 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 seguire Riparare proprietà mista PostgreSQL invece.

Pool di connessioni

L’adattatore usa pg.Pool internamente. Regola la dimensione del pool in base al tuo deployment:

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 esistente — o compatibile con Postgres (es. PlanetScale Postgres). Hyperdrive raggruppa e accelera la connessione sulla rete di 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

Prima prepara 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": "<your-hyperdrive-id>"
    }
  ]
}

wrangler.toml

[[hyperdrive]]
binding = "HYPERDRIVE"
id = "<your-hyperdrive-id>"

Configurazione

OpzioneTipoPredefinitoDescrizione
bindingstring"HYPERDRIVE"Nome del binding primario (cache disabilitato) Hyperdrive
cachedBindingstringBinding opzionale con cache abilitato per letture anonime (vedi sotto)
preferUncachedAfterWriteMsnumber60000*Dopo una pubblicazione, preferire binding per questi ms sulle letture pubbliche anonime (corrispondere a Hyperdrive max_age)
migrationConnectionStringEnvstringCLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>Variabile d’ambiente contenente l’URL diretto PostgreSQL origin per emdash migrate
maxnumber5Dimensione max del pool di connessioni nel Worker verso Hyperdrive

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

Servire letture anonime dal cache

Per impostazione predefinita disabiliti completamente il cache Hyperdrive, perché l’admin e le scritture necessitano di consistenza read-after-write. Ma le richieste pubbliche anonime usando 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 abilitato e ogni altra richiesta attraverso il primario senza cache.

# Primario — cache DISABILITATO (usato da 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 database 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": "<caching-disabled-id>" },
		{ "binding": "HYPERDRIVE_CACHED", "id": "<caching-enabled-id>" }
	]
}
database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" });

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

  • Letture anonime dei percorsi del sito pubblico (GET/HEAD, nessuna sessione, non sotto /_emdash) → cachedBinding con cache abilitato, eccetto per una breve finestra dopo una pubblicazione di contenuto (predefinito 60s; imposta preferUncachedAfterWriteMs al tuo Hyperdrive max_age) quando EmDash preferisce il binding senza cache così un rebuild non può ri-alimentare i cache edge/oggetto da risultati Hyperdrive ancora obsoleti.
  • Richieste autenticate (editor, autori) → binding senza cache.
  • Richieste di mutazione (POST, PUT, PATCH, DELETE, incluse anonime) → binding senza cache.
  • Qualsiasi richiesta sotto /_emdash (admin, setup, auth, API interne), anche un GET anonimo → binding senza cache.
  • Migrazioni runtime e cold-start → sempre il binding primario.
  • Migrazioni gestite dal deployment → si connettono direttamente all’origin PostgreSQL usando migrationConnectionStringEnv; non usano mai alcun binding Hyperdrive.

Opzionale: usare un ruolo con cache separato

Le migrazioni, il setup, 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 reindirizzamento e 404. Per preservare queste funzionalità, il ruolo con cache necessita in aggiunta di UPDATE su _emdash_redirects e SELECT, INSERT, UPDATE e DELETE su _emdash_404_log. I plugin o il 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 sotto usano lo schema emdash opzionale; sostituisci il tuo schema attivo, come public. Crea il login e le impostazioni database con il ruolo amministrativo del tuo provider:

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;

Poi connettiti come emdash_app, il proprietario di schema e 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 gli stessi 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 accesso a tabelle EmDash individuali, e aggiorna quei grant quando collezioni o altri oggetti dello schema vengono aggiunti.

Riparare proprietà mista PostgreSQL

Se un sito ha usato più utenti PostgreSQL, prima scegli il ruolo canonico che la connessione principale EmDash continuerà a usare. Fai un backup e ferma le modifiche allo schema durante la riparazione della proprietà.

Ispeziona ogni tabella nello schema attivo:

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;

Gli oggetti EmDash includono tabelle di sistema _emdash_* e _plugin_*, tabelle di collezione ec_* e tabelle senza prefisso come content_taxonomies, media, options, revisions e taxonomies. In uno schema EmDash dedicato, ogni tabella applicativa dovrebbe avere il proprietario canonico.

EmDash crea anche funzioni PostgreSQL usate dai trigger di utilizzo media. Ispeziona la proprietà delle funzioni e conserva la firma degli argomenti di ogni funzione per il comando di riparazione:

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;

Trasferisci ogni oggetto non corrispondente con un superutente o ruolo provider che può cambiare la sua proprietà, usando sempre nomi qualificati dallo 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;

Usa la lista di argomenti restituita dalla query di inventario in ogni istruzione ALTER FUNCTION. Cambiare il proprietario di una tabella copre anche i suoi indici, vincoli e trigger allegati, ma non le loro funzioni trigger indipendenti. Ripeti entrambe le query di inventario fino a quando ogni tabella e funzione EmDash riporta il proprietario canonico, poi connettiti come quel ruolo e verifica current_schema() prima di avviare l’applicazione.

Perché un non-superutente trasferisca la proprietà, deve possedere o ereditare la proprietà dell’oggetto, essere in grado di eseguire SET ROLE al nuovo proprietario, e il nuovo proprietario deve avere CREATE sullo schema. I provider PostgreSQL gestiti possono richiedere il loro ruolo amministrativo per effettuare il trasferimento.

SQLite

SQLite usa il driver database integrato di Node.js ed è l’opzione più semplice per deployment 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}` });

Migrazioni

EmDash esegue migrazioni core automaticamente per impostazione predefinita per ogni dialetto supportato. Astro build e sync emettono anche un .emdash/migrations.json validato e privo di segreti, che emdash migrate può applicare prima del deployment. SQLite, libSQL, PostgreSQL, D1 e l’origin PostgreSQL diretto dietro Hyperdrive hanno esecutori di deployment.

Vedi Gestire le migrazioni del database core per credenziali target, serializzazione CI, politica runtime auto/check/manual e ripristino da record sconosciuti o scritture D1 ambigue.

Per PostgreSQL, le migrazioni runtime vengono eseguite attraverso la connessione configurata; le migrazioni runtime Hyperdrive usano sempre il suo binding primario. Le migrazioni Hyperdrive gestite dal deployment si connettono direttamente all’origin PostgreSQL. Le migrazioni core possono creare tabelle, indici e funzioni, alterare 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 setup non può riparare privilegi database mancanti perché le migrazioni runtime vengono eseguite prima del setup.

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

Configurazione basata sull’ambiente

Usa database diversi per 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 })],
});

La scelta può anche dipendere da una variabile d’ambiente invece che dalla modalità di build:

const database = process.env.DATABASE_URL
	? postgres({ connectionString: process.env.DATABASE_URL })
	: sqlite({ url: "file:./data.db" });