Wählen Sie einen Datenbankadapter pro Deployment. Die Datenbank hält das Inhaltsmodell, Einträge, Benutzer, Einstellungen und Plugin-Daten. Medienbinärdateien gehören in ein separates Speicher-Backend.
Überblick
| Database | Use it when | Runtime |
|---|---|---|
| SQLite | Ein Node.js-Prozess hat eine persistente Festplatte | Node.js oder lokale Entwicklung |
| D1 | Die Site läuft auf Cloudflare Workers und soll Cloudflare SQL nutzen | Cloudflare Workers |
| Hyperdrive | Die Site läuft auf Workers und muss eine bestehende PostgreSQL-Origin nutzen | Cloudflare Workers |
| PostgreSQL | Mehrere Node.js-Prozesse brauchen eine gemeinsame Datenbank | Node.js |
| libSQL | Ein Node.js-Deployment braucht eine entfernte SQLite-kompatible Datenbank | Node.js |
D1 ist der Standard für die Cloudflare-Vorlagen. SQLite ist die einfachste Node.js-Option, erfordert aber ein beschreibbares persistentes Volume und operative Datenbank-Backups.
SQLite
SQLite nutzt den eingebauten Datenbanktreiber von Node.js und ist die einfachste Option für Node.js-Deployments.
import { sqlite } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
}),
],
});
Konfiguration
| Option | Type | Description |
|---|---|---|
url | string | Dateipfad mit file:-Präfix |
Dateipfad
Die url muss mit file: beginnen:
// 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 öffnet SQLite-Datenbanken im Write-Ahead-Logging-(WAL)-Modus. Während die Site läuft, hält SQLite zwei zusätzliche Dateien neben der Datenbank, z. B. emdash.db-wal und emdash.db-shm. Der Prozess braucht Schreibzugriff auf das Datenbankverzeichnis, um sie zu erstellen.
Die -wal-Datei kann festgeschriebene Änderungen enthalten, die noch nicht in der Hauptdatenbankdatei sind. Sichern Sie mit dem Backup-Befehl von SQLite statt nur die .db-Datei zu kopieren. Siehe SQLite-Backup und Wiederherstellung.
WAL erfordert Shared Memory, halten Sie die Datenbank daher auf einer lokalen Festplatte oder einem Block-Volume statt auf einem Netzwerkdateisystem wie NFS oder SMB.
Cloudflare D1
D1 ist Cloudflares serverlose SQLite-Datenbank. Nutzen Sie sie beim Deployment auf Cloudflare Workers.
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({ binding: "DB" }),
}),
],
});
Konfiguration
| Option | Type | Default | Description |
|---|---|---|---|
binding | string | — | D1-Binding-Name aus wrangler.jsonc |
session | string | "disabled" | Read-Replication-Modus (siehe unten) |
bookmarkCookie | string | "__em_d1_bookmark" | Cookie-Name für Session-Bookmarks |
Wrangler-Binding
wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "emdash-db"
}
]
} wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "emdash-db" Wrangler kann während des Deployments eine fehlende D1-Datenbank aus diesem Binding bereitstellen. EmDash-Migrationen sind ein separater Schritt. Folgen Sie Auf Cloudflare deployen für den vollständigen Binding-Satz und Core-Datenbankmigrationen verwalten für das Migrations-Runbook.
Read Replicas
D1 unterstützt Read Replication, um die Leselatenz für global verteilte Sites zu senken. Wenn aktiviert, werden Leseabfragen an nahe Replicas geleitet, statt immer die Primärdatenbank zu treffen.
EmDash nutzt die D1 Sessions API, um dies transparent zu verwalten. Aktivieren Sie sie mit der Option session:
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({
binding: "DB",
session: "auto",
}),
}),
],
});
Session-Modi
| Mode | Behavior |
|---|---|
"disabled" | Keine Sessions. Alle Abfragen gehen zur Primärdatenbank. Standard. |
"auto" | Anonyme Anfragen lesen von der nächsten Replica. Authentifizierte Benutzer erhalten Read-your-writes-Konsistenz über Bookmark-Cookies. |
"primary-first" | Wie "auto", aber die erste Abfrage geht immer zur Primärdatenbank. Für Sites mit sehr häufigen Schreibvorgängen. |
So funktioniert es
- Anonyme Besucher erhalten
first-unconstrained— Lesevorgänge gehen zur nächsten Replica für die niedrigste Latenz. Da anonyme Benutzer nie schreiben, brauchen sie keine Konsistenzgarantien. - Authentifizierte Benutzer (Editoren, Autoren) erhalten bookmark-basierte Sessions. Nach einem Schreibvorgang stellt ein Bookmark-Cookie sicher, dass die nächste Anfrage mindestens diesen Zustand sieht.
- Schreibanfragen (
POST,PUT,DELETE) starten immer bei der Primärdatenbank. - Build-Zeit-Abfragen (Astro Content Collections) umgehen Sessions vollständig und nutzen die Primärdatenbank direkt.
libSQL
libSQL ist ein Fork von SQLite, der Remote-Verbindungen unterstützt. Nutzen Sie ihn, wenn Sie eine Remote-Datenbank ohne Cloudflare D1 brauchen.
import { libsql } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
}),
}),
],
});
Konfiguration
| Option | Type | Description |
|---|---|---|
url | string | Datenbank-URL (libsql://... oder file:...) |
authToken | string | Runtime-Auth-Token für Remote-Datenbanken (optional für lokal) |
migrationAuthTokenEnv | string | Name der Migrations-Token-Variable (Standard TURSO_AUTH_TOKEN) |
Lokale Entwicklung
Nutzen Sie während der Entwicklung eine lokale libSQL-Datei:
database: libsql({ url: "file:./data.db" });
PostgreSQL
PostgreSQL wird für Node.js-Deployments unterstützt, die eine vollständige relationale Datenbank brauchen.
import { postgres } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: postgres({
connectionString: process.env.DATABASE_URL,
}),
}),
],
});
Konfiguration
Sie können mit einer Connection String oder einzelnen Parametern verbinden:
// 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 | PostgreSQL-Verbindungs-URL |
host | string | Datenbank-Host |
port | number | Datenbank-Port |
database | string | Datenbankname |
user | string | Datenbankbenutzer |
password | string | Datenbankpasswort |
ssl | boolean | SSL aktivieren |
pool.min | number | Minimale Pool-Verbindungen (Standard 0) |
pool.max | number | Maximale Pool-Verbindungen (Standard 10) |
pool.connectionTimeoutMillis | number | Maximale Verbindungswartezeit (pg-Standard: 0, kein Timeout) |
pool.idleTimeoutMillis | number | Idle-Client-Lebensdauer (pg-Standard: 10.000 ms) |
migrationConnectionStringEnv | string | Name der Migrations-Connection-String-Variable (Standard DATABASE_URL) |
Setzen Sie pool.connectionTimeoutMillis auf einen Nicht-Null-Wert, um zu begrenzen, wie lange eine Anfrage wartet, wenn PostgreSQL unerreichbar ist oder keine Pool-Verbindung verfügbar wird. Setzen Sie pool.idleTimeoutMillis auf 0, um Idle-Clients offen zu halten, bis der Pool schließt. Das Weglassen einer Option behält den pg-Standard.
Anforderungen an die Datenbankrolle
EmDash erstellt und aktualisiert seine eigenen PostgreSQL-Tabellen. Core-Migrationen erstellen und ändern System- und Collection-Tabellen, Inhaltstypen erstellen ec_*-Tabellen, und das Hinzufügen oder Entfernen eines Felds ändert seine Collection-Tabelle. Die konfigurierte PostgreSQL-Rolle braucht daher Schema-Autorität für die Lebensdauer der Site, nicht nur während der Ersteinrichtung.
Verwenden Sie eine kanonische Rolle für EmDash. Sie braucht:
CONNECTauf der Datenbank;USAGEundCREATEauf dem aktiven Schema;- Eigentümerschaft jeder EmDash-Tabelle und -Funktion, direkt oder durch Mitgliedschaft mit
INHERITin der besitzenden Rolle; und SELECT,INSERT,UPDATEundDELETEauf diesen Tabellen.
Sie muss kein Superuser sein, kein CREATEDB oder CREATEROLE haben und keine Extensions erstellen. PostgreSQL bietet kein ALTER- oder DROP-Tabellenrecht: Diese Operationen gehören dem Objektbesitzer und Rollen, die dessen Privilegien erben. ALL auf einer Tabelle an eine andere Rolle zu gewähren, macht diese Rolle nicht zum Besitzer. EmDash führt kein SET ROLE aus, daher reicht Mitgliedschaft ohne Vererbung nicht.
Die meisten Installationen können das bestehende Schema der Datenbank nutzen, üblicherweise public. Das ist die einfachste Option, wenn die Datenbank EmDash gewidmet ist. In den Beispielen unten ist emdash_app die Login-Rolle in EmDashs Connection String; verwenden Sie eine bestehende Provider-Rolle oder erstellen Sie einen dedizierten Login. Gewähren Sie Zugriff mit einer administrativen Verbindung und ersetzen Sie Datenbank-, Schema- und Rollennamen:
GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;
Diese Grants lassen die Rolle neue Objekte erstellen. Sie ändern nicht den Besitzer bestehender Tabellen; nutzen Sie das PostgreSQL-Ownership-Repair-Runbook, wenn eine bestehende Site gemischte Besitzer hat.
EmDash verwendet PostgreSQLs aktives current_schema(). Es erstellt kein Schema und setzt keinen search_path, prüfen Sie daher die Verbindung vor dem Deployment:
SELECT
current_database(),
session_user,
current_user,
current_schema(),
current_setting('search_path');
Optional: ein dediziertes Schema verwenden
Verwenden Sie ein dediziertes Schema, wenn EmDash eine Datenbank mit einer anderen Anwendung teilt oder wenn Sie seine Objekte von public isolieren wollen. Das ist optional und am einfachsten vor dem ersten EmDash-Setup zu konfigurieren. Eine EmDash gewidmete Datenbank braucht kein separates Schema.
Angenommen, die kanonische Rolle emdash_app existiert bereits, erstellen und wählen Sie ihr Schema mit einer administrativen Verbindung:
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;
Das verschiebt keine bestehende Installation aus public und repariert keine gemischte Ownership. Bestehende Sites sollten ihr aktuelles Schema behalten und stattdessen das PostgreSQL-Ownership-Repair-Runbook nutzen.
Connection Pooling
Der Adapter nutzt pg.Pool. Passen Sie die Pool-Größe an Ihr Deployment an:
database: postgres({
connectionString: process.env.DATABASE_URL,
pool: { min: 2, max: 20 },
});
Hyperdrive
Nutzen Sie den Adapter hyperdrive(), um EmDash auf Cloudflare Workers mit einer bestehenden PostgreSQL — oder Postgres-kompatiblen (z. B. PlanetScale Postgres) — Datenbank zu betreiben. Hyperdrive poolt und beschleunigt die Verbindung über Cloudflares Netzwerk; EmDashs PostgreSQL-Dialekt führt die Abfragen aus.
import { hyperdrive, r2 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: hyperdrive({ binding: "HYPERDRIVE" }),
storage: r2({ binding: "MEDIA" }),
}),
],
});
Anforderungen
pg >= 8.16.3in Ihrer Site installiert (pnpm add pg)compatibility_flags: ["nodejs_compat"]compatibility_date >= "2024-09-23"
Einrichtung
Bereiten Sie zuerst die PostgreSQL-Rolle vor. Erstellen Sie dann die Hyperdrive-Konfiguration mit der Connection String dieser Rolle und fügen Sie das Binding Ihrer Wrangler-Konfiguration hinzu:
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>" Konfiguration
| Option | Type | Default | Description |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Primäres (Caching deaktiviert) Hyperdrive-Binding-Name |
cachedBinding | string | — | Optionales Caching-aktiviertes Binding für anonyme Lesevorgänge (siehe unten) |
preferUncachedAfterWriteMs | number | 60000* | Nach einer Inhaltsveröffentlichung für so viele ms bei anonymen öffentlichen Lesevorgängen binding bevorzugen (an Hyperdrive max_age anpassen) |
migrationConnectionStringEnv | string | CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING> | Umgebungsvariable mit der direkten PostgreSQL-Origin-URL für emdash migrate |
max | number | 5 | Maximale Größe des In-Worker-Verbindungspools zu Hyperdrive |
*Standard 60000 gilt nur, wenn cachedBinding gesetzt ist; sonst ignoriert.
Anonyme Lesevorgänge aus dem Cache bedienen
Standardmäßig deaktivieren Sie Hyperdrive-Caching vollständig, weil Admin und Schreibvorgänge Read-after-write-Konsistenz brauchen. Aber anonyme öffentliche Anfragen mit GET oder HEAD können ein kurzes Veraltungsfenster tolerieren. Wenn dieser Kompromiss akzeptabel ist, betreiben Sie zwei Hyperdrive-Konfigurationen über dieselbe Datenbank: eine mit Caching aus (das primäre binding) und eine mit Caching an (cachedBinding). EmDash leitet diese anonymen öffentlichen Anfragen über das Cache-aktivierte Binding und jede andere Anfrage über die ungecachte Primärdatenbank.
# 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" });
Das ist das Zwei-Konfigurationen-Muster, das Cloudflare für Caching dokumentiert. EmDash entscheidet pro Anfrage, welches Binding zu verwenden ist:
- Anonyme Lesevorgänge öffentlicher Site-Pfade (
GET/HEAD, keine Session, nicht unter/_emdash) → Cache-aktiviertescachedBinding, außer für ein kurzes Fenster nach einer Inhaltsveröffentlichung (Standard 60 s; setzen SiepreferUncachedAfterWriteMsauf Ihren Hyperdrive-max_age), wenn EmDash das ungecachtebindingbevorzugt, damit ein Rebuild Edge-/Objektcaches nicht aus noch veralteten Hyperdrive-Ergebnissen neu befüllen kann. - Authentifizierte Anfragen (Editoren, Autoren) → ungecachtes
binding. - Mutationsanfragen (
POST,PUT,PATCH,DELETE, einschließlich anonymer) → ungecachtesbinding. - Jede Anfrage unter
/_emdash(Admin, Setup, Auth, interne APIs), selbst ein anonymesGET→ ungecachtesbinding. - Runtime-Migrationen und Cold-Start → immer das primäre
binding. - Deployment-verwaltete Migrationen → verbinden direkt mit der PostgreSQL-Origin über
migrationConnectionStringEnv; sie nutzen nie keines der Hyperdrive-Bindings.
Optional: eine separate gecachte Rolle verwenden
Migrationen, Setup, authentifizierte Anfragen und explizite Schreibanfragen nutzen immer das primäre binding. Eine separate Rolle für cachedBinding braucht keine Schema-Eigentümerschaft oder CREATE, aber sie braucht CONNECT, Schema-USAGE und SELECT auf jeder vom öffentlichen Site genutzten Tabelle.
Anonyme öffentliche GET- und HEAD-Anfragen können auch Redirect-Treffer und 404s aufzeichnen. Um diese Features zu erhalten, braucht die gecachte Rolle zusätzlich UPDATE auf _emdash_redirects und SELECT, INSERT, UPDATE und DELETE auf _emdash_404_log. Plugins oder Anwendungscode, die während eines öffentlichen GET oder HEAD schreiben, können mehr brauchen. Verwenden Sie dieselbe Rolle für beide Bindings, es sei denn, Sie haben die Site mit einer eingeschränkten gecachten Rolle getestet.
Fügen Sie die gecachte Rolle hinzu, nachdem EmDash seine initialen Migrationen abgeschlossen hat. Die Beispiele unten nutzen das optionale Schema emdash; ersetzen Sie Ihr aktives Schema, z. B. public. Erstellen Sie Login und Datenbankeinstellungen mit der administrativen Rolle Ihres Providers:
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;
Verbinden Sie sich dann als emdash_app, der Schema- und Tabellenbesitzer, um Zugriff auf bestehende und zukünftige Tabellen zu gewähren:
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;
Verbinden Sie sich mit beiden Rollen und prüfen Sie, dass sie dieselbe current_database() und current_schema() melden, bevor Sie cachedBinding aktivieren. Auf einem geteilten Schema legt GRANT SELECT ON ALL TABLES auch unrelated Tabellen offen. Gewähren Sie stattdessen Zugriff auf einzelne EmDash-Tabellen und aktualisieren Sie diese Grants, wenn Collections oder andere Schema-Objekte hinzugefügt werden.
Core-Migrationen
EmDash führt Core-Migrationen standardmäßig automatisch für jeden unterstützten Dialekt aus. Astro Build und Sync erzeugen auch eine validierte, geheimnisfreie .emdash/migrations.json, die emdash migrate vor dem Deployment anwenden kann. SQLite, libSQL, PostgreSQL, D1 und die direkte PostgreSQL-Origin hinter Hyperdrive haben Deployment-Executoren.
Siehe Core-Datenbankmigrationen verwalten für Ziel-Credentials, CI-Serialisierung, Runtime-Richtlinie auto/check/manual und Wiederherstellung von unbekannten Records oder mehrdeutigen D1-Schreibvorgängen.
Für PostgreSQL laufen Runtime-Migrationen über die konfigurierte Verbindung; Hyperdrive-Runtime-Migrationen nutzen immer sein primäres Binding. Deployment-verwaltete Hyperdrive-Migrationen verbinden direkt mit der PostgreSQL-Origin. Core-Migrationen können Tabellen, Indizes und Funktionen erstellen, Spalten und Constraints ändern oder löschen und bestehende Zeilen aktualisieren. Eine Rolle, die verbinden und Zeilen ändern kann, aber die bestehenden EmDash-Objekte nicht besitzt, reicht nicht. Der Setup-Assistent kann fehlende Datenbankprivilegien nicht reparieren, weil Runtime-Migrationen vor dem Setup laufen.
Wenn die Datenbank leer ist (keine Collections) und der Setup-Assistent nicht abgeschlossen wurde, wendet EmDash beim ersten Boot auch eine Seed-Datei an. Der Seed wird aus .emdash/seed.json, dem Pfad in package.json#emdash.seed oder seed/seed.json gelesen — je nachdem, was zuerst gefunden wird — und zur Compile-Zeit in den Build inline eingebettet. Ist keiner vorhanden, wird ein eingebauter Standard-Seed verwendet. Nachfolgende Boots gegen eine bestehende Datenbank lassen ihren Inhalt unverändert.
Separate Datenbanken für separate Umgebungen verwenden
Geben Sie Entwicklung, Preview, Staging und Produktion jeweils eine eigene Datenbank. Ein Preview-Deployment, das auf Produktion zeigt, kann Core-Migrationen oder destruktive Inhaltsmodell-Befehle gegen Live-Daten ausführen.
Für Cloudflare definieren Sie jedes D1- oder Hyperdrive-Binding unter der passenden Wrangler-Umgebung und übergeben --env an Wrangler-Befehle. Für Node.js injizieren Sie eine andere Datenbank-URL in jede Runtime-Umgebung. Halten Sie Credentials in Runtime-Secrets, nicht in astro.config.mjs.