Choisissez un adaptateur de base de données par 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 de plugins. Les binaires multimédias appartiennent à un backend de stockage séparé.
Aperçu
| Database | Use it when | Runtime |
|---|---|---|
| SQLite | Un processus Node.js dispose d’un disque persistant | Node.js ou développement local |
| D1 | Le site tourne sur Cloudflare Workers et doit utiliser Cloudflare SQL | Cloudflare Workers |
| Hyperdrive | Le site tourne sur Workers et doit utiliser une origine PostgreSQL existante | Cloudflare Workers |
| PostgreSQL | Plusieurs processus Node.js ont besoin d’une base partagée | Node.js |
| libSQL | Un déploiement Node.js a besoin d’une base distante compatible SQLite | Node.js |
D1 est la valeur par défaut pour les 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.
SQLite
SQLite utilise le pilote de base de données intégré de Node.js et est 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 SQLite en mode write-ahead logging (WAL). Pendant que le site tourne, SQLite conserve deux fichiers supplémentaires à côté de la base, comme emdash.db-wal et emdash.db-shm. Le processus a besoin d’un accès en écriture au répertoire de la base pour les créer.
Le fichier -wal peut contenir des modifications validées qui ne sont pas encore dans le fichier principal de la base. Sauvegardez avec la commande de sauvegarde de SQLite plutôt que de copier uniquement le fichier .db. Voir Sauvegarde et récupération SQLite.
WAL nécessite de la mémoire partagée, donc gardez la base 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 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 | Default | Description |
|---|---|---|---|
binding | string | — | Nom du binding D1 depuis wrangler.jsonc |
session | string | "disabled" | Mode de réplication en lecture (voir ci-dessous) |
bookmarkCookie | string | "__em_d1_bookmark" | Nom du cookie pour les 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 D1 manquante à partir de ce binding pendant le déploiement. Les migrations EmDash sont une étape séparée. Suivez Déployer sur Cloudflare pour l’ensemble complet des bindings et Gérer les migrations de la base principale pour le runbook de migration.
Réplicas en lecture
D1 prend en charge la réplication en lecture pour réduire la latence de lecture des sites distribués mondialement. Lorsqu’elle est activée, les requêtes de lecture sont acheminées vers des réplicas proches au lieu de toujours toucher la base primaire.
EmDash utilise l’API Sessions de D1 pour le gérer 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 | Behavior |
|---|---|
"disabled" | Pas de sessions. Toutes les requêtes vont à la primaire. Par défaut. |
"auto" | Les requêtes anonymes lisent depuis la réplica la plus proche. Les utilisateurs authentifiés obtiennent une cohérence read-your-writes via des cookies de signet. |
"primary-first" | Comme "auto", mais la première requête va toujours à la primaire. Pour les sites avec des écritures très fréquentes. |
Fonctionnement
- Visiteurs anonymes obtiennent
first-unconstrained— les lectures vont à la réplica la 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. - Utilisateurs authentifiés (éditeurs, auteurs) obtiennent des sessions basées sur des signets. Après une écriture, un cookie de signet garantit que la requête suivante voit au moins cet état.
- Requêtes d’écriture (
POST,PUT,DELETE) démarrent toujours sur la base primaire. - Requêtes au moment du build (collections de contenu Astro) contournent entièrement les sessions et utilisent la primaire directement.
libSQL
libSQL est un fork de SQLite qui prend en charge les connexions distantes. Utilisez-le lorsque vous avez besoin d’une base 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 (libsql://... ou file:...) |
authToken | string | Jeton d’auth runtime pour les bases distantes (optionnel en local) |
migrationAuthTokenEnv | string | Nom de la variable du jeton de migration (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 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 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 |
port | number | Port de la base |
database | string | Nom de la base |
user | string | Utilisateur de la base |
password | string | Mot de passe de la base |
ssl | boolean | Activer SSL |
pool.min | number | Connexions minimales du pool (défaut 0) |
pool.max | number | Connexions maximales du pool (défaut 10) |
pool.connectionTimeoutMillis | number | Attente maximale de connexion (défaut pg : 0, pas de délai) |
pool.idleTimeoutMillis | number | Durée de vie du client inactif (défaut pg : 10 000 ms) |
migrationConnectionStringEnv | string | Nom de la variable de chaîne de connexion de migration (défaut DATABASE_URL) |
Définissez pool.connectionTimeoutMillis à une valeur non nulle pour limiter le temps d’attente d’une requête lorsque PostgreSQL est injoignable ou qu’aucune connexion du pool n’est disponible. Définissez pool.idleTimeoutMillis à 0 pour garder les clients inactifs ouverts jusqu’à la fermeture du pool. Omettre l’une ou l’autre option conserve le 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 principales créent et altèrent les tables système et de collections, les types de contenu créent des tables ec_*, et l’ajout ou la suppression d’un champ altère sa table de collection. Le rôle PostgreSQL configuré a donc besoin d’autorité de schéma pour la durée de vie du site, pas seulement pendant la configuration initiale.
Utilisez un rôle canonique pour EmDash. Il a besoin de :
CONNECTsur la base ;USAGEetCREATEsur le schéma actif ;- la propriété de chaque table et fonction EmDash, directement ou 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 droit de table ALTER ou DROP : 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 n’en fait pas un propriétaire. EmDash n’exécute pas SET ROLE, donc une appartenance configurée sans héritage ne suffit pas.
La plupart des installations peuvent utiliser le schéma existant de la base, couramment public. C’est l’option la plus simple lorsque la base 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 fournisseur existant ou créez une connexion dédiée. Accordez-lui l’accès avec une connexion administrative, en substituant vos noms de base, schéma et rôle :
GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;
Ces grants 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 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 avec une autre application ou lorsque vous voulez isoler ses objets de public. C’est optionnel et plus facile à configurer avant la première configuration EmDash. Une base dédiée à EmDash n’a pas besoin d’un schéma séparé.
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 le runbook de réparation de propriété PostgreSQL à la place.
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 PostgreSQL existante — ou compatible Postgres (p. ex. PlanetScale Postgres). Hyperdrive met en pool et accélère la connexion sur le réseau 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
D’abord préparez le rôle PostgreSQL. Puis créez 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 | Default | Description |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Nom du binding Hyperdrive primaire (cache désactivé) |
cachedBinding | string | — | Binding optionnel avec cache activé pour les lectures anonymes (voir ci-dessous) |
preferUncachedAfterWriteMs | number | 60000* | Après une publication de contenu, préférer binding pendant ces ms sur les lectures publiques anonymes (correspondre à max_age 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 max du pool de connexions in-Worker vers Hyperdrive |
*Le défaut 60000 s’applique uniquement lorsque cachedBinding est défini ; ignoré sinon.
Servir les lectures anonymes depuis le cache
Par défaut vous désactivez entièrement le cache Hyperdrive, car l’admin et les écritures ont besoin d’une cohérence read-after-write. Mais les requêtes publiques anonymes en GET ou HEAD peuvent tolérer une courte fenêtre de fraîcheur. Si ce compromis est acceptable, exécutez deux configurations Hyperdrive sur la même base : une avec le cache désactivé (le binding primaire) et une avec le cache activé (cachedBinding). EmDash achemine ces requêtes publiques anonymes via le binding avec cache et toutes les autres via la primaire non mise en 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 le cache. EmDash décide quel binding utiliser par requête :
- Lectures anonymes des chemins du site public (
GET/HEAD, pas de session, pas sous/_emdash) →cachedBindingavec cache, sauf pendant une courte fenêtre après une publication de contenu (défaut 60 s ; définissezpreferUncachedAfterWriteMssur votremax_ageHyperdrive) lorsque EmDash préfère lebindingnon mis en cache pour qu’une reconstruction ne puisse pas réalimenter les caches edge/objet à partir de résultats Hyperdrive encore périmés. - Requêtes authentifiées (éditeurs, auteurs) →
bindingnon mis en cache. - Requêtes de mutation (
POST,PUT,PATCH,DELETE, y compris anonymes) →bindingnon mis en cache. - Toute requête sous
/_emdash(admin, configuration, auth, APIs internes), même unGETanonyme →bindingnon mis en cache. - Migrations runtime et démarrage à froid → toujours le
bindingprimaire. - Migrations gérées par le déploiement → se connectent directement à l’origine PostgreSQL via
migrationConnectionStringEnv; elles n’utilisent jamais aucun des bindings Hyperdrive.
Optionnel : utiliser un rôle mis en cache séparé
Les migrations, la configuration, les requêtes authentifiées et les écritures explicites utilisent toujours le binding primaire. Un rôle séparé pour cachedBinding n’a pas besoin de propriété de schéma ni de CREATE, mais il a besoin de CONNECT, de USAGE sur le schéma et de SELECT sur chaque table utilisée par le site public.
Les requêtes publiques anonymes GET et HEAD peuvent aussi enregistrer des hits de redirection et des 404. Pour préserver 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 d’application qui écrivent pendant un GET ou HEAD public peuvent en 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 après qu’EmDash ait terminé ses migrations initiales. Les exemples ci-dessous utilisent le schéma optionnel emdash ; substituez votre schéma actif, comme public. Créez la connexion et les paramètres de base 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;
Puis connectez-vous 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 rapportent le même current_database() et current_schema() avant d’activer cachedBinding. Sur un schéma partagé, GRANT SELECT ON ALL TABLES expose aussi des tables non liées. Accordez plutôt l’accès à des tables EmDash individuelles, et mettez à jour ces grants lorsque des collections ou d’autres objets de schéma sont ajoutés.
Migrations principales
EmDash exécute les migrations principales automatiquement par défaut pour chaque dialecte pris en charge. Le build et le sync Astro émettent aussi un .emdash/migrations.json validé et sans secrets, que emdash migrate peut appliquer avant le déploiement. SQLite, libSQL, PostgreSQL, D1 et l’origine PostgreSQL directe derrière Hyperdrive ont des exécuteurs de déploiement.
Voir Gérer les migrations de la base principale pour les identifiants cibles, la sérialisation CI, la politique runtime auto/check/manual, et la récupération des enregistrements inconnus ou des écritures D1 ambiguës.
Pour PostgreSQL, les migrations runtime passent par la connexion configurée ; les migrations runtime Hyperdrive utilisent toujours son binding primaire. Les migrations Hyperdrive gérées par le déploiement se connectent directement à l’origine PostgreSQL. Les migrations principales peuvent créer des tables, index et fonctions, altérer ou supprimer des colonnes et 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 manquants car les migrations runtime s’exécutent avant la configuration.
Si la base est vide (aucune collection) et que l’assistant de configuration n’a pas été terminé, EmDash applique aussi un fichier seed au premier démarrage. Le seed est lu depuis .emdash/seed.json, le chemin dans package.json#emdash.seed, ou seed/seed.json — le premier trouvé — et intégré au build à la compilation. Si aucun n’est présent, un seed par défaut intégré est utilisé. Les démarrages suivants contre une base existante laissent son contenu intact.
Utiliser des bases séparées pour des environnements séparés
Donnez à développement, preview, staging et production chacun sa propre base. Un déploiement preview pointé vers la production peut exécuter des migrations principales ou des commandes destructives du modèle de contenu contre des données en direct.
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 différente dans chaque environnement runtime. Gardez les identifiants dans des secrets runtime, pas dans astro.config.mjs.