Zum Inhalt springen

Architektur

EmDash integriert sich tief mit Astro, um ein vollständiges CMS-Erlebnis zu bieten. Diese Seite erklärt die wichtigsten Architekturentscheidungen und wie die Teile zusammenpassen.

┌──────────────────────────────────────────────────────────────────┐
│ Deine Astro-Site │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ EmDash-Integration │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │ │
│ │ │ Inhalts-APIs │ │ Admin- │ │ Plugins │ │ │
│ │ │ │ │ Panel │ │ │ │ │
│ │ └──────────────┘ └──────────────┘ └───────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ Datenebene │ │ │
│ │ │ Datenbank (D1/SQLite) + Speicher (R2/S3) │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Astro-Framework │ │
│ │ Live Collections · Middleware · Sessions │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘

EmDash läuft als Astro-Integration. Es injiziert Routen für das Admin-Oberfläche und REST-APIs, stellt einen Content-Loader für dynamische Sammlungen bereit und verwaltet Datenbank-Migrationen und Speicherverbindungen.

Im Gegensatz zu traditionellen CMS, die das Schema im Code definieren, speichert EmDash Schemadefinitionen in der Datenbank selbst. Zwei Systemtabellen verfolgen Ihre Inhaltsstruktur:

  • _emdash_collections — Metadaten der Sammlung (Slug, Label, Features)
  • _emdash_fields — Felddefinitionen für jede Sammlung

Wenn Sie über die Admin-UI eine “products”-Sammlung mit Titel- und Preis-Feldern erstellen, führt EmDash folgendes aus:

  1. Fügt Datensätze in _emdash_collections und _emdash_fields ein
  2. Führt ALTER TABLE aus, um ec_products mit den entsprechenden Spalten zu erstellen

Dieses Design ermöglicht:

  • Laufzeit-Schemaänderung — Erstellen und Bearbeiten von Inhaltstypen ohne Codeänderungen oder Neubuilds
  • Nicht-Entwicklerfreundliches Setup — Redakteure können ihr Inhaltsmodell über die UI gestalten
  • Echte SQL-Spalten — Richtige Indizierung, Fremdschlüssel und Abfrageoptimierung

Jede Sammlung erhält ihre eigene SQLite-Tabelle mit einem ec_-Präfix:

-- Erstellt, wenn die Sammlung "posts" hinzugefügt wird
CREATE TABLE ec_posts (
-- Systemspalten (immer vorhanden)
id TEXT PRIMARY KEY,
slug TEXT UNIQUE,
status TEXT DEFAULT 'draft', -- draft, published, scheduled
author_id TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
published_at TEXT,
deleted_at TEXT, -- Soft Delete
version INTEGER DEFAULT 1, -- Optimistische Sperre
-- Inhalts-Spalten (aus Ihren Felddefinitionen)
title TEXT NOT NULL,
content JSON, -- Portable Text
excerpt TEXT
);

Warum Tabellen pro Sammlung anstelle einer einzelnen Inhalts-Tabelle mit JSON?

  • Echte SQL-Spalten ermöglichen richtige Indizierung und Abfragen
  • Fremdschlüssel funktionieren korrekt
  • Das Schema ist in der Datenbank selbsterklärend
  • Kein JSON-Parsing-Overhead für Feldzugriffe
  • Datenbank-Tools können das Schema direkt inspizieren

EmDash nutzt Astro 6’s dynamische Sammlungen, um Inhalte zur Laufzeit bereitzustellen. Inhaltsänderungen sind sofort verfügbar, ohne statische Neubuilds.

Der emdashLoader() implementiert Astros LiveLoader-Schnittstelle:

src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({ loader: emdashLoader() }),
};

Abfragen Sie Inhalte mit den bereitgestellten Wrapper-Funktionen:

import { getEmDashCollection, getEmDashEntry } from "emdash";
// Alle veröffentlichten Beiträge abrufen
const { entries: posts } = await getEmDashCollection("posts");
// Entwürfe abrufen
const { entries: drafts } = await getEmDashCollection("posts", {
status: "draft",
});
// Einen einzelnen Eintrag per Slug abrufen
const { entry: post } = await getEmDashEntry("posts", "my-post-slug");

Die EmDash-Integration verwendet Astros injectRoute-API, um Admin- und API-Routen hinzuzufügen:

PfadmusterZweck
/_emdash/admin/[...path]Admin-Oberfläche SPA
/_emdash/api/manifestAdmin-Manifest (Sammlungen, Plugins)
/_emdash/api/content/[collection]CRUD für Inhalts-Einträge
/_emdash/api/media/*Medienbibliothek-Operationen
/_emdash/api/schema/*Schemaverwaltung
/_emdash/api/settingsWebsite-Einstellungen
/_emdash/api/menus/*Navigationsmenüs
/_emdash/api/taxonomies/*Kategorien, Tags, benutzerdefinierte Taxonomien

Routen werden aus dem emdash-Paket injiziert – nichts wird in Ihr Projekt kopiert.

EmDash verwendet Kysely für typsichere SQL-Abfragen über alle unterstützten Datenbanken hinweg:

SQLite

Lokale Entwicklung mit sqlite({ url: "file:./data.db" })

D1

Cloudflares serverloses SQL mit d1({ binding: "DB" })

libSQL

Remote SQLite mit libsql({ url: "...", authToken: "..." })

Die Datenbankkonfiguration wird der Integration in astro.config.mjs übergeben:

import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { sqlite } from "emdash/db";
import { local } from "emdash/storage";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});

Mediendateien werden getrennt von der Datenbank gespeichert. EmDash unterstützt:

  • Lokales Dateisystem — Entwicklung und einfache Deployments
  • Cloudflare R2 — S3-kompatibler Objektspeicher am Edge
  • S3-kompatibel — Jeder S3-kompatible Objektspeicher

Uploads verwenden signierte URLs für direkte Client-zu-Speicher-Uploads und umgehen so Workers-Body-Größenlimits.

Plugins erweitern EmDash über ein WordPress-inspiriertes Hook-System:

  • Inhalts-Hooks — content:beforeSave, content:afterSave, content:beforeDelete, content:afterDelete
  • Medien-Hooks — media:beforeUpload, media:afterUpload
  • Isolierter Speicher — Jedes Plugin erhält namespaced KV-Zugriff
  • Admin-UI-Erweiterungen — Dashboard-Widgets, Einstellungsseiten, benutzerdefinierte Feld-Editoren

Plugins können in zwei Modi laufen:

  1. Vertrauenswürdig — Vollzugriff auf die Host-Umgebung (für First-Party-Plugins)
  2. Sandboxed — Laufen in V8-Isolaten mit capability-basierten Berechtigungen (für Third-Party-Plugins auf Cloudflare)
astro.config.mjs
import { seoPlugin } from "@emdash-cms/plugin-seo";
emdash({
plugins: [seoPlugin({ maxTitleLength: 60 })],
});

Eine typische Inhaltsanfrage folgt diesem Pfad:

  1. Astro empfängt die Anfrage

    Ihre Seitenkomponente wird ausgeführt.

  2. Inhalte werden abgefragt

    getEmDashCollection() ruft Astros getLiveCollection() auf.

  3. Der Loader läuft

    emdashLoader fragt die passende ec_*-Tabelle über Kysely ab.

  4. Daten werden zurückgegeben

    Die Einträge werden auf Astros Eintragsformat mit id, slug und data abgebildet.

  5. Die Seite rendert

    Ihre Komponente erhält den Inhalt und erzeugt HTML.

Für Admin-Anfragen:

  1. Die Middleware authentifiziert

    Das Session-Token wird validiert.

  2. Die API-Route verarbeitet die Anfrage

    CRUD-Operationen laufen über Repositories.

  3. Hooks werden ausgelöst

    beforeCreate, afterUpdate und weitere Hooks werden ausgeführt.

  4. Die Datenbank wird aktualisiert

    Kysely führt das benötigte SQL aus.

  5. Die Antwort wird zurückgegeben

    Die Admin-SPA erhält eine JSON-Antwort.

EmDash generiert zur Build-Zeit virtuelle Module, um die Laufzeit zu konfigurieren:

ModulZweck
virtual:emdash/configDatenbank- und Speicherkonfiguration
virtual:emdash/dialectDatenbankdialekt-Fabrik
virtual:emdash/plugin-adminsStatische Imports für Plugin-Admin-Oberflächen

Dieser Ansatz stellt sicher, dass Bundler den Plugin-Code korrekt auflösen und Tree-Shaking durchführen können.