Choisissez un adaptateur de base de données pour chaque déploiement. La base de données contient le modèle de contenu, les entrées, les utilisateurs, les paramètres et les données des plugins. Les binaires des médias vont dans un backend de stockage distinct.
Aperçu
| Base de données | À utiliser lorsque | Environnement d’exécution |
|---|---|---|
| SQLite | Un processus Node.js dispose d’un disque persistant | Node.js ou développement local |
| D1 | Le site s’exécute sur Cloudflare Workers et doit utiliser Cloudflare SQL | Cloudflare Workers |
| Hyperdrive | Le site s’exécute sur Workers et doit utiliser une origine PostgreSQL existante | Cloudflare Workers |
| PostgreSQL | Plusieurs processus Node.js ont besoin d’une base de données partagée | Node.js |
| libSQL | Un déploiement Node.js a besoin d’une base de données distante compatible SQLite | Node.js |
D1 est le choix par défaut des modèles Cloudflare. SQLite est l’option Node.js la plus simple, mais elle nécessite un volume persistant accessible en écriture et des sauvegardes opérationnelles de la base de données.
SQLite
SQLite utilise le pilote de base de données intégré à Node.js et constitue l’option la plus simple pour les déploiements Node.js.
import { sqlite } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
}),
],
});
Configuration
| Option | Type | Description |
|---|---|---|
url | string | Chemin de fichier avec le préfixe file: |
Chemin de fichier
L’url doit commencer par file: :
// 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 ouvre les bases de données SQLite en mode write-ahead logging (WAL). Pendant l’exécution du site, SQLite conserve deux fichiers supplémentaires à côté de la base de données, tels que emdash.db-wal et emdash.db-shm. Le processus a besoin d’un accès en écriture au répertoire de la base de données pour les créer.
Le fichier -wal peut contenir des modifications validées qui ne figurent pas encore dans le fichier principal de la base de données. Sauvegardez avec la commande de sauvegarde de SQLite plutôt qu’en copiant uniquement le fichier .db. Consultez Sauvegarde et récupération SQLite.
WAL nécessite de la mémoire partagée ; gardez donc la base de données sur un disque local ou un volume bloc plutôt que sur un système de fichiers réseau comme NFS ou SMB.
Cloudflare D1
D1 est la base de données SQLite serverless de Cloudflare. Utilisez-la lors d’un déploiement sur Cloudflare Workers.
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({ binding: "DB" }),
}),
],
});
Configuration
| Option | Type | Par défaut | Description |
|---|---|---|---|
binding | string | — | Nom du binding D1 dans wrangler.jsonc |
session | string | "disabled" | Mode de réplication en lecture (voir ci-dessous) |
bookmarkCookie | string | "__em_d1_bookmark" | Nom du cookie des signets de session |
Binding Wrangler
wrangler.jsonc
{
"d1_databases": [
{
"binding": "DB",
"database_name": "emdash-db"
}
]
} wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "emdash-db" Wrangler peut provisionner une base de données D1 manquante à partir de ce binding lors du déploiement. Les migrations d’EmDash constituent une étape distincte. Suivez Déployer sur Cloudflare pour l’ensemble complet des bindings et Gérer les migrations de base de données du noyau pour le runbook des migrations.
Réplicas en lecture
D1 prend en charge la réplication en lecture afin de réduire la latence de lecture des sites distribués dans le monde entier. Lorsqu’elle est activée, les requêtes de lecture sont acheminées vers des réplicas proches au lieu de toujours solliciter la base de données principale.
EmDash utilise l’API D1 Sessions pour gérer cela de façon transparente. Activez-la avec l’option session :
import { d1 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: d1({
binding: "DB",
session: "auto",
}),
}),
],
});
Modes de session
| Mode | Comportement |
|---|---|
"disabled" | Pas de sessions. Toutes les requêtes vont à la base principale. Par défaut. |
"auto" | Les requêtes anonymes lisent depuis le réplica le plus proche. Les utilisateurs authentifiés obtiennent une cohérence read-your-writes grâce aux cookies de signet. |
"primary-first" | Comme "auto", mais la première requête va toujours à la base principale. À utiliser pour les sites avec des écritures très fréquentes. |
Fonctionnement
- Les visiteurs anonymes obtiennent
first-unconstrained— les lectures vont au réplica le plus proche pour la latence la plus faible. Comme les utilisateurs anonymes n’écrivent jamais, ils n’ont pas besoin de garanties de cohérence. - Les utilisateurs authentifiés (éditeurs, auteurs) obtiennent des sessions fondées sur des signets. Après une écriture, un cookie de signet garantit que la requête suivante voit au moins cet état.
- Les requêtes d’écriture (
POST,PUT,DELETE) commencent toujours sur la base de données principale. - Les requêtes au moment du build (collections de contenu Astro) contournent entièrement les sessions et utilisent directement la base principale.
libSQL
libSQL est un fork de SQLite qui prend en charge les connexions distantes. Utilisez-le lorsque vous avez besoin d’une base de données distante sans 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,
}),
}),
],
});
Configuration
| Option | Type | Description |
|---|---|---|
url | string | URL de la base de données (libsql://... ou file:...) |
authToken | string | Jeton d’authentification à l’exécution pour les bases distantes (facultatif en local) |
migrationAuthTokenEnv | string | Nom de la variable du jeton de migration (par défaut TURSO_AUTH_TOKEN) |
Développement local
Utilisez un fichier libSQL local pendant le développement :
database: libsql({ url: "file:./data.db" });
PostgreSQL
PostgreSQL est pris en charge pour les déploiements Node.js qui ont besoin d’une base de données relationnelle complète.
import { postgres } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: postgres({
connectionString: process.env.DATABASE_URL,
}),
}),
],
});
Configuration
Vous pouvez vous connecter avec une chaîne de connexion ou avec des paramètres individuels :
// 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 | URL de connexion PostgreSQL |
host | string | Hôte de la base de données |
port | number | Port de la base de données |
database | string | Nom de la base de données |
user | string | Utilisateur de la base de données |
password | string | Mot de passe de la base de données |
ssl | boolean | Activer SSL |
pool.min | number | Connexions minimales du pool (par défaut 0) |
pool.max | number | Connexions maximales du pool (par défaut 10) |
pool.connectionTimeoutMillis | number | Attente maximale de connexion (par défaut de pg : 0, sans délai) |
pool.idleTimeoutMillis | number | Durée de vie des clients inactifs (par défaut de pg : 10 000 ms) |
migrationConnectionStringEnv | string | Nom de la variable de la chaîne de connexion de migration (par défaut DATABASE_URL) |
Définissez pool.connectionTimeoutMillis sur une valeur non nulle pour limiter la durée d’attente d’une requête lorsque PostgreSQL est inaccessible ou qu’aucune connexion du pool ne devient disponible. Définissez pool.idleTimeoutMillis sur 0 pour garder les clients inactifs ouverts jusqu’à la fermeture du pool. Omettre l’une ou l’autre option conserve la valeur par défaut de pg.
Exigences du rôle de base de données
EmDash crée et met à jour ses propres tables PostgreSQL. Les migrations du noyau créent et modifient les tables système et de collection, les types de contenu créent des tables ec_*, et l’ajout ou la suppression d’un champ modifie la table de sa collection. Le rôle PostgreSQL configuré a donc besoin d’une autorité sur le schéma pendant toute la durée de vie du site, et pas seulement lors de la configuration initiale.
Utilisez un seul rôle canonique pour EmDash. Il a besoin de :
CONNECTsur la base de données ;USAGEetCREATEsur le schéma actif ;- la propriété de chaque table et fonction d’EmDash, soit directement, soit par appartenance avec
INHERITau rôle propriétaire ; et SELECT,INSERT,UPDATEetDELETEsur ces tables.
Il n’a pas besoin d’être superutilisateur, d’avoir CREATEDB ou CREATEROLE, ni de créer des extensions. PostgreSQL ne fournit pas de privilège ALTER ou DROP sur les tables : ces opérations appartiennent au propriétaire de l’objet et aux rôles qui héritent de ses privilèges. Accorder ALL sur une table à un autre rôle ne fait pas de ce rôle un propriétaire. EmDash n’exécute pas SET ROLE ; une appartenance configurée sans héritage ne suffit donc pas.
La plupart des installations peuvent utiliser le schéma existant de la base de données, généralement public. C’est l’option la plus simple lorsque la base de données est dédiée à EmDash. Dans les exemples ci-dessous, emdash_app est le rôle de connexion dans la chaîne de connexion d’EmDash ; utilisez un rôle existant de votre fournisseur ou créez une connexion dédiée. Accordez-lui l’accès avec une connexion administrative, en remplaçant les noms de votre base de données, de votre schéma et de votre rôle :
GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;
Ces privilèges permettent au rôle de créer de nouveaux objets. Ils ne changent pas le propriétaire des tables existantes ; utilisez le runbook de réparation de la propriété PostgreSQL lorsqu’un site existant a des propriétaires mixtes.
EmDash utilise le current_schema() actif de PostgreSQL. Il ne crée pas de schéma et ne définit pas search_path ; vérifiez donc la connexion avant le déploiement :
SELECT
current_database(),
session_user,
current_user,
current_schema(),
current_setting('search_path');
Optionnel : utiliser un schéma dédié
Utilisez un schéma dédié lorsque EmDash partage une base de données avec une autre application ou lorsque vous voulez isoler ses objets de public. C’est facultatif et plus simple à configurer avant la première configuration d’EmDash. Une base de données dédiée à EmDash n’a pas besoin de schéma distinct.
En supposant que le rôle canonique emdash_app existe déjà, créez et sélectionnez son schéma avec une connexion administrative :
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;
Cela ne déplace pas une installation existante depuis public et ne répare pas une propriété mixte. Les sites existants doivent conserver leur schéma actuel et utiliser plutôt le runbook de réparation de la propriété PostgreSQL.
Pool de connexions
L’adaptateur utilise pg.Pool. Ajustez la taille du pool selon votre déploiement :
database: postgres({
connectionString: process.env.DATABASE_URL,
pool: { min: 2, max: 20 },
});
Hyperdrive
Utilisez l’adaptateur hyperdrive() pour exécuter EmDash sur Cloudflare Workers avec une base de données PostgreSQL existante — ou compatible Postgres (par exemple PlanetScale Postgres). Hyperdrive met en pool et accélère la connexion via le réseau de Cloudflare ; le dialecte PostgreSQL d’EmDash exécute les requêtes.
import { hyperdrive, r2 } from "@emdash-cms/cloudflare";
export default defineConfig({
integrations: [
emdash({
database: hyperdrive({ binding: "HYPERDRIVE" }),
storage: r2({ binding: "MEDIA" }),
}),
],
});
Exigences
pg >= 8.16.3installé dans votre site (pnpm add pg)compatibility_flags: ["nodejs_compat"]compatibility_date >= "2024-09-23"
Configuration
Préparez d’abord le rôle PostgreSQL. Créez ensuite la configuration Hyperdrive avec la chaîne de connexion de ce rôle et ajoutez le binding à votre configuration 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>" Configuration
| Option | Type | Par défaut | Description |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Nom du binding Hyperdrive principal (cache désactivé) |
cachedBinding | string | — | Binding facultatif avec cache activé pour les lectures anonymes (voir ci-dessous) |
preferUncachedAfterWriteMs | number | 60000* | Après une publication de contenu, préférer binding pendant ce nombre de ms pour les lectures publiques anonymes (à aligner sur le max_age d’Hyperdrive) |
migrationConnectionStringEnv | string | CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING> | Variable d’environnement contenant l’URL directe de l’origine PostgreSQL pour emdash migrate |
max | number | 5 | Taille maximale du pool de connexions vers Hyperdrive dans le Worker |
*La valeur par défaut 60000 ne s’applique que lorsque cachedBinding est défini ; elle est ignorée sinon.
Servir les lectures anonymes depuis le cache
Par défaut, vous désactivez entièrement le cache d’Hyperdrive, car l’administration et les écritures ont besoin de la cohérence lecture après écriture. Mais les requêtes publiques anonymes utilisant GET ou HEAD peuvent tolérer une courte fenêtre de péremption. Si ce compromis est acceptable, exécutez deux configurations Hyperdrive sur la même base de données : l’une avec le cache désactivé (le binding principal) et l’autre avec le cache activé (cachedBinding). EmDash achemine ces requêtes publiques anonymes via le binding avec cache et toutes les autres requêtes via le binding principal sans cache.
# 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" });
C’est le modèle à deux configurations que Cloudflare documente pour la mise en cache. EmDash décide du binding à utiliser pour chaque requête :
- Lectures anonymes de chemins du site public (
GET/HEAD, sans session, hors de/_emdash) →cachedBindingavec cache activé, sauf pendant une courte fenêtre après une publication de contenu (60 s par défaut ; définissezpreferUncachedAfterWriteMssur lemax_agede votre Hyperdrive) où EmDash préfère lebindingsans cache afin qu’une reconstruction ne puisse pas réalimenter les caches edge/objet avec des résultats Hyperdrive encore périmés. - Requêtes authentifiées (éditeurs, auteurs) →
bindingsans cache. - Requêtes de mutation (
POST,PUT,PATCH,DELETE, y compris anonymes) →bindingsans cache. - Toute requête sous
/_emdash(administration, configuration, authentification, API internes), même unGETanonyme →bindingsans cache. - Migrations à l’exécution et démarrage à froid → toujours le
bindingprincipal. - Migrations gérées par le déploiement → se connectent directement à l’origine PostgreSQL via
migrationConnectionStringEnv; elles n’utilisent jamais aucun des deux bindings Hyperdrive.
Optionnel : utiliser un rôle mis en cache séparé
Les migrations, la configuration, les requêtes authentifiées et les requêtes d’écriture explicites utilisent toujours le binding principal. Un rôle distinct pour cachedBinding n’a pas besoin de la propriété du schéma ni de CREATE, mais il lui faut CONNECT, USAGE sur le schéma et SELECT sur chaque table utilisée par le site public.
Les requêtes publiques anonymes GET et HEAD peuvent aussi enregistrer des accès aux redirections et des 404. Pour conserver ces fonctionnalités, le rôle mis en cache a en plus besoin de UPDATE sur _emdash_redirects et de SELECT, INSERT, UPDATE et DELETE sur _emdash_404_log. Les plugins ou le code applicatif qui écrivent pendant un GET ou un HEAD public peuvent exiger davantage. Utilisez le même rôle pour les deux bindings, sauf si vous avez testé le site avec un rôle mis en cache restreint.
Ajoutez le rôle mis en cache une fois qu’EmDash a terminé ses migrations initiales. Les exemples ci-dessous utilisent le schéma facultatif emdash ; remplacez-le par votre schéma actif, par exemple public. Créez la connexion et les paramètres de base de données avec le rôle administratif de votre fournisseur :
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;
Connectez-vous ensuite en tant que emdash_app, le propriétaire du schéma et des tables, pour accorder l’accès aux tables existantes et futures :
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;
Connectez-vous avec les deux rôles et vérifiez qu’ils renvoient les mêmes current_database() et current_schema() avant d’activer cachedBinding. Sur un schéma partagé, GRANT SELECT ON ALL TABLES expose aussi des tables sans rapport. Accordez plutôt l’accès aux tables EmDash individuellement, et mettez à jour ces privilèges lorsque des collections ou d’autres objets du schéma sont ajoutés.
Migrations principales
EmDash exécute par défaut les migrations principales automatiquement pour chaque dialecte pris en charge. Le build et la synchronisation d’Astro produisent aussi un .emdash/migrations.json validé et sans secret, que emdash migrate peut appliquer avant le déploiement. SQLite, libSQL, PostgreSQL, D1 et l’origine PostgreSQL directe derrière Hyperdrive disposent d’exécuteurs de déploiement.
Consultez Gérer les migrations de base de données du noyau pour les identifiants de la cible, la sérialisation en CI, la politique d’exécution auto/check/manual et la récupération après des enregistrements inconnus ou des écritures D1 ambiguës.
Pour PostgreSQL, les migrations à l’exécution passent par la connexion configurée ; les migrations à l’exécution d’Hyperdrive utilisent toujours son binding principal. Les migrations Hyperdrive gérées par le déploiement se connectent directement à l’origine PostgreSQL. Les migrations principales peuvent créer des tables, des index et des fonctions, modifier ou supprimer des colonnes et des contraintes, et mettre à jour des lignes existantes. Un rôle qui peut se connecter et modifier des lignes mais ne possède pas les objets EmDash existants ne suffit pas. L’assistant de configuration ne peut pas réparer les privilèges de base de données manquants, car les migrations à l’exécution s’exécutent avant la configuration.
Tant que l’assistant de configuration n’est pas terminé, EmDash applique aussi une seule fois, à la première requête, le schéma et la structure d’un fichier seed. Le contenu d’exemple du seed n’est appliqué que lorsqu’un administrateur le choisit dans l’assistant de configuration. Le seed est lu depuis .emdash/seed.json, le chemin indiqué dans package.json#emdash.seed ou seed/seed.json — le premier trouvé — et intégré au build au moment de la compilation. Si aucun n’est présent, un seed par défaut intégré est utilisé. Les démarrages suivants sur une base de données existante laissent son contenu intact.
Utiliser des bases séparées pour des environnements séparés
Donnez au développement, à la preview, au staging et à la production leur propre base de données. Un déploiement de preview pointant vers la production peut exécuter des migrations principales ou des commandes destructrices du modèle de contenu sur des données en production.
Pour Cloudflare, définissez chaque binding D1 ou Hyperdrive sous l’environnement Wrangler correspondant et passez --env aux commandes Wrangler. Pour Node.js, injectez une URL de base de données différente dans chaque environnement d’exécution. Conservez les identifiants dans des secrets d’exécution, pas dans astro.config.mjs.