EmDash unterstützt mehrere Datenbank-Backends. Wählen Sie basierend auf Ihrem Deployment-Ziel.
Überblick
| Datenbank | Am besten für | Deployment |
|---|---|---|
| D1 | Cloudflare Workers | Edge, global verteilt |
| Hyperdrive | PostgreSQL auf Cloudflare Workers | Edge, vorhandenes Postgres |
| PostgreSQL | Produktion Node.js | Jede Plattform mit Postgres |
| libSQL | Remote-Datenbanken | Edge oder Node.js |
| SQLite | Node.js, lokale Entwicklung | Einzelserver |
Cloudflare D1
D1 ist Cloudflares serverlose SQLite-Datenbank. Verwenden Sie sie beim Deployment auf Cloudflare Workers.
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({ binding: "DB" }),
}),
],
});
Konfiguration
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
binding | string | — | D1-Binding-Name aus wrangler.jsonc |
session | string | "disabled" | Lesereplikationsmodus (siehe unten) |
bookmarkCookie | string | "__em_d1_bookmark" | Cookie-Name für Session-Bookmarks |
Einrichtung
wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "emdash-db"
}
]
} wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "emdash-db" Lesereplikate
D1 unterstützt Lesereplikation, um die Leselatenz für global verteilte Websites zu reduzieren. Wenn aktiviert, werden Leseabfragen an nahe gelegene Replikate weitergeleitet, anstatt immer die primäre Datenbank zu kontaktieren.
EmDash verwendet die D1 Sessions API, um dies transparent zu verwalten. Aktivieren Sie es mit der Option session:
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({
binding: "DB",
session: "auto",
}),
}),
],
});
Session-Modi
| Modus | Verhalten |
|---|---|
"disabled" | Keine Sessions. Alle Abfragen gehen an die primäre Datenbank. Standard. |
"auto" | Anonyme Anfragen lesen von der nächsten Replike. Authentifizierte Benutzer erhalten Read-your-writes-Konsistenz über Bookmark-Cookies. |
"primary-first" | Wie "auto", aber die erste Abfrage geht immer an die primäre. Für Websites mit sehr häufigen Schreibvorgängen. |
Wie es funktioniert
- Anonyme Besucher erhalten
first-unconstrained— Lesevorgänge gehen zur nächsten Replike für die niedrigste Latenz. Da anonyme Benutzer nie schreiben, benötigen 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 an der primären Datenbank. - Build-Zeit-Abfragen (Astro Content Collections) umgehen Sessions vollständig und verwenden direkt die primäre.
libSQL
libSQL ist ein Fork von SQLite, der Remote-Verbindungen unterstützt. Verwenden Sie es, wenn Sie eine Remote-Datenbank ohne Cloudflare D1 benötigen.
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 | Typ | Beschreibung |
|---|---|---|
url | string | Datenbank-URL (libsql://... oder file:...) |
authToken | string | Runtime-Auth-Token für Remote-Datenbanken (optional für lokal) |
migrationAuthTokenEnv | string | Variablenname für Migrations-Token (Standard TURSO_AUTH_TOKEN) |
Lokale Entwicklung
Verwenden Sie eine lokale libSQL-Datei während der Entwicklung:
database: libsql({ url: "file:./data.db" });
PostgreSQL
PostgreSQL wird für Node.js-Deployments unterstützt, die eine vollständige relationale Datenbank benötigen.
import { postgres } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: postgres({
connectionString: process.env.DATABASE_URL,
}),
}),
],
});
Konfiguration
Sie können sich mit einem Verbindungsstring oder einzelnen Parametern verbinden:
// Verbindungsstring
database: postgres({
connectionString: "postgres://user:password@localhost:5432/emdash",
});
// Einzelne Parameter
database: postgres({
host: "localhost",
port: 5432,
database: "emdash",
user: "emdash",
password: process.env.DB_PASSWORD,
ssl: true,
});
| Option | Typ | Beschreibung |
|---|---|---|
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 | Leerlauf-Client-Lebensdauer (pg Standard: 10.000 ms) |
migrationConnectionStringEnv | string | Variablenname für Migrations-Verbindungsstring (Standard DATABASE_URL) |
Setzen Sie pool.connectionTimeoutMillis auf einen Wert ungleich Null, um zu begrenzen, wie lange eine Anfrage wartet, wenn PostgreSQL nicht erreichbar ist oder keine gepoolte Verbindung verfügbar wird. Setzen Sie pool.idleTimeoutMillis auf 0, um Leerlauf-Clients offen zu halten, bis der Pool geschlossen wird. Das Weglassen einer der Optionen behält den pg-Standard bei.
Anforderungen an die Datenbankrolle
EmDash erstellt und aktualisiert seine eigenen PostgreSQL-Tabellen. Kernmigrationen erstellen und ändern System- und Collection-Tabellen, Inhaltstypen erstellen ec_*-Tabellen, und das Hinzufügen oder Entfernen eines Feldes ändert seine Collection-Tabelle. Die konfigurierte PostgreSQL-Rolle benötigt daher Schema-Autorität für die gesamte Lebensdauer der Website, nicht nur während der Ersteinrichtung.
Verwenden Sie eine kanonische Rolle für EmDash. Sie benötigt:
CONNECTauf der Datenbank;USAGEundCREATEauf dem aktiven Schema;- Eigentum an jeder EmDash-Tabelle und -Funktion, entweder direkt oder durch Mitgliedschaft mit
INHERITin der besitzenden Rolle; und SELECT,INSERT,UPDATEundDELETEauf diesen Tabellen.
Sie muss kein Superuser sein, CREATEDB oder CREATEROLE haben oder Erweiterungen erstellen. PostgreSQL bietet kein ALTER- oder DROP-Tabellen-Grant: Diese Operationen gehören dem Objekteigentümer und Rollen, die seine Privilegien erben. Das Gewähren von ALL auf eine Tabelle an eine andere Rolle macht diese Rolle nicht zum Eigentümer. EmDash führt kein SET ROLE aus, daher reicht eine ohne Vererbung konfigurierte Mitgliedschaft nicht aus.
Die meisten Installationen können das bestehende Schema der Datenbank verwenden, üblicherweise public. Dies ist die einfachste Option, wenn die Datenbank EmDash gewidmet ist. In den Beispielen unten ist emdash_app die Login-Rolle in EmDashs Verbindungsstring; verwenden Sie eine bestehende Provider-Rolle oder erstellen Sie einen dedizierten Login. Gewähren Sie Zugriff mit einer administrativen Verbindung und ersetzen Sie Ihre 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 Eigentümer bestehender Tabellen; siehe Gemischtes PostgreSQL-Eigentum reparieren.
EmDash verwendet PostgreSQLs aktives current_schema(). Es erstellt kein Schema und setzt keinen search_path, daher überprüfen Sie die Verbindung vor dem Deployment:
SELECT
current_database(),
session_user,
current_user,
current_schema(),
current_setting('search_path');
Optional: 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 möchten. Dies ist optional und am einfachsten vor der ersten EmDash-Einrichtung zu konfigurieren. Eine EmDash gewidmete Datenbank benötigt kein separates Schema.
Vorausgesetzt, 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;
Dies verschiebt keine bestehende Installation von public und repariert kein gemischtes Eigentum. Bestehende Websites sollten ihr aktuelles Schema beibehalten und stattdessen Gemischtes PostgreSQL-Eigentum reparieren folgen.
Verbindungspooling
Der Adapter verwendet pg.Pool unter der Haube. Passen Sie die Pool-Größe basierend auf Ihrem Deployment an:
database: postgres({
connectionString: process.env.DATABASE_URL,
pool: { min: 2, max: 20 },
});
Hyperdrive
Verwenden Sie den hyperdrive()-Adapter, 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 Website 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 dem Verbindungsstring dieser Rolle und fügen Sie das Binding zu 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 | Typ | Standard | Beschreibung |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Name des primären (Caching-deaktivierten) Hyperdrive-Bindings |
cachedBinding | string | — | Optionales Caching-aktiviertes Binding für anonyme Lesevorgänge (siehe unten) |
preferUncachedAfterWriteMs | number | 60000* | Nach einer Veröffentlichung binding für diese ms bei anonymen öffentlichen Lesevorgängen bevorzugen (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; andernfalls 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 benötigen. Aber anonyme öffentliche Anfragen mit GET oder HEAD können ein kurzes Veraltetheitsfenster tolerieren. Wenn dieser Kompromiss akzeptabel ist, betreiben Sie zwei Hyperdrive-Konfigurationen über dieselbe Datenbank: eine mit deaktiviertem Caching (das primäre binding) und eine mit aktiviertem Caching (cachedBinding). EmDash leitet diese anonymen öffentlichen Anfragen über das Cache-aktivierte Binding und jede andere Anfrage über das nicht gecachte primäre.
# Primär — Caching AUS (verwendet von Admin, authentifizierten Anfragen, Schreibvorgängen, Migrationen)
wrangler hyperdrive create emdash-db \
--connection-string "postgres://user:password@host/db?sslmode=verify-full" \
--caching-disabled
# Gecacht — DIESELBE Datenbankrolle und Verbindungsstring, Caching AN
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" });
Dies ist das Zwei-Konfigurations-Muster, das Cloudflare für Caching dokumentiert. EmDash entscheidet pro Anfrage, welches Binding verwendet wird:
- Anonyme Lesevorgänge öffentlicher Website-Pfade (
GET/HEAD, keine Session, nicht unter/_emdash) → Cache-aktiviertescachedBinding, außer für ein kurzes Fenster nach einer Inhaltsveröffentlichung (Standard 60s; setzen SiepreferUncachedAfterWriteMsauf Ihr Hyperdrivemax_age), wenn EmDash das nicht gecachtebindingbevorzugt, damit ein Rebuild nicht Edge-/Objekt-Caches aus noch veralteten Hyperdrive-Ergebnissen neu befüllen kann. - Authentifizierte Anfragen (Editoren, Autoren) → nicht gecachtes
binding. - Mutations-Anfragen (
POST,PUT,PATCH,DELETE, einschließlich anonymer) → nicht gecachtesbinding. - Jede Anfrage unter
/_emdash(Admin, Setup, Auth, interne APIs), auch ein anonymesGET→ nicht gecachtesbinding. - Runtime-Migrationen und Kaltstart → immer das primäre
binding. - Deployment-verwaltete Migrationen → verbinden sich direkt mit dem PostgreSQL-Origin über
migrationConnectionStringEnv; sie verwenden nie ein Hyperdrive-Binding.
Optional: Separate gecachte Rolle verwenden
Migrationen, Setup, authentifizierte Anfragen und explizite Schreibanfragen verwenden immer das primäre binding. Eine separate Rolle für cachedBinding benötigt kein Schema-Eigentum oder CREATE, aber CONNECT, Schema-USAGE und SELECT auf jeder Tabelle, die von der öffentlichen Website verwendet wird.
Anonyme öffentliche GET- und HEAD-Anfragen können auch Weiterleitungstreffer und 404s aufzeichnen. Um diese Funktionen zu erhalten, benötigt die gecachte Rolle zusätzlich UPDATE auf _emdash_redirects und SELECT, INSERT, UPDATE und DELETE auf _emdash_404_log. Plugins oder Anwendungscode, der während eines öffentlichen GET oder HEAD schreibt, erfordert möglicherweise mehr. Verwenden Sie dieselbe Rolle für beide Bindings, es sei denn, Sie haben die Website mit einer eingeschränkten gecachten Rolle getestet.
Fügen Sie die gecachte Rolle hinzu, nachdem EmDash seine anfänglichen Migrationen abgeschlossen hat. Die Beispiele unten verwenden das optionale emdash-Schema; ersetzen Sie Ihr aktives Schema, wie z.B. public. Erstellen Sie den Login und die 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, dem Schema- und Tabelleneigentümer, 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 überprüfen Sie, dass sie dasselbe current_database() und current_schema() melden, bevor Sie cachedBinding aktivieren. Bei einem gemeinsamen Schema setzt GRANT SELECT ON ALL TABLES auch nicht verwandte Tabellen frei. Gewähren Sie stattdessen Zugriff auf einzelne EmDash-Tabellen und aktualisieren Sie diese Grants, wenn Collections oder andere Schema-Objekte hinzugefügt werden.
Gemischtes PostgreSQL-Eigentum reparieren
Wenn eine Website mehrere PostgreSQL-Benutzer verwendet hat, wählen Sie zuerst die kanonische Rolle, die die primäre EmDash-Verbindung weiterhin verwenden wird. Erstellen Sie ein Backup und stoppen Sie Schema-Änderungen während der Eigentum-Reparatur.
Inspizieren Sie jede Tabelle im aktiven Schema:
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;
EmDash-Objekte umfassen _emdash_*- und _plugin_*-Systemtabellen, ec_*-Collection-Tabellen und Tabellen ohne Präfix wie content_taxonomies, media, options, revisions und taxonomies. In einem dedizierten EmDash-Schema sollte jede Anwendungstabelle den kanonischen Eigentümer haben.
EmDash erstellt auch PostgreSQL-Funktionen, die von Media-Usage-Triggern verwendet werden. Inspizieren Sie das Funktionseigentum und behalten Sie die Argument-Signatur jeder Funktion für den Reparaturbefehl bei:
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;
Übertragen Sie jedes nicht übereinstimmende Objekt mit einem Superuser oder Provider-Rolle, die sein Eigentum ändern kann, immer mit Schema-qualifizierten Namen:
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;
Verwenden Sie die von der Inventarabfrage zurückgegebene Argumentliste in jeder ALTER FUNCTION-Anweisung. Das Ändern des Eigentümers einer Tabelle deckt auch ihre angehängten Indexe, Constraints und Trigger ab, aber nicht deren unabhängige Trigger-Funktionen. Wiederholen Sie beide Inventarabfragen, bis jede EmDash-Tabelle und -Funktion den kanonischen Eigentümer meldet, verbinden Sie sich dann als diese Rolle und überprüfen Sie current_schema(), bevor Sie die Anwendung starten.
Damit ein Nicht-Superuser das Eigentum übertragen kann, muss er das Objekt besitzen oder das Eigentum erben, in der Lage sein, SET ROLE zum neuen Eigentümer auszuführen, und der neue Eigentümer muss CREATE auf dem Schema haben. Managed PostgreSQL-Provider erfordern möglicherweise ihre administrative Rolle für die Übertragung.
SQLite
SQLite verwendet den integrierten 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 | Typ | Beschreibung |
|---|---|---|
url | string | Dateipfad mit file:-Präfix |
Dateipfad
Die url muss mit file: beginnen:
// Relativer Pfad
database: sqlite({ url: "file:./data/emdash.db" });
// Absoluter Pfad
database: sqlite({ url: "file:/var/data/emdash.db" });
// Aus Umgebungsvariable
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });
Migrationen
EmDash führt Kernmigrationen 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 der direkte PostgreSQL-Origin hinter Hyperdrive haben Deployment-Executoren.
Siehe Kernmigrationen verwalten für Ziel-Anmeldedaten, CI-Serialisierung, auto/check/manual-Runtime-Richtlinie und Wiederherstellung von unbekannten Einträgen oder mehrdeutigen D1-Schreibvorgängen.
Für PostgreSQL laufen Runtime-Migrationen über die konfigurierte Verbindung; Hyperdrive-Runtime-Migrationen verwenden immer sein primäres Binding. Deployment-verwaltete Hyperdrive-Migrationen verbinden sich direkt mit dem PostgreSQL-Origin. Kernmigrationen können Tabellen, Indexe und Funktionen erstellen, Spalten und Constraints ändern oder löschen und bestehende Zeilen aktualisieren. Eine Rolle, die sich verbinden und Zeilen ändern kann, aber die bestehenden EmDash-Objekte nicht besitzt, ist nicht ausreichend. Der Setup-Assistent kann fehlende Datenbankprivilegien nicht reparieren, da Runtime-Migrationen vor dem Setup ausgeführt werden.
Wenn die Datenbank leer ist (keine Collections) und der Setup-Assistent nicht abgeschlossen wurde, wendet EmDash beim ersten Start auch eine Seed-Datei an. Der Seed wird aus .emdash/seed.json, dem Pfad in package.json#emdash.seed oder seed/seed.json gelesen — was zuerst gefunden wird — und zur Kompilierzeit in den Build eingebunden. Wenn keiner vorhanden ist, wird ein eingebauter Standard-Seed verwendet. Nachfolgende Starts gegen eine bestehende Datenbank lassen deren Inhalt unberührt.
Umgebungsbasierte Konfiguration
Verwenden Sie verschiedene Datenbanken pro Umgebung:
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 })],
});
Die Wahl kann auch von einer Umgebungsvariable statt vom Build-Modus abhängen:
const database = process.env.DATABASE_URL
? postgres({ connectionString: process.env.DATABASE_URL })
: sqlite({ url: "file:./data.db" });