Architektur

Auf dieser Seite

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

Überblick auf hoher Ebene

┌──────────────────────────────────────────────────────────────────┐
│                         Ihre Astro-Site                           │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                   EmDash Integration                       │  │
│  │                                                            │  │
│  │  ┌──────────────┐   ┌──────────────┐   ┌───────────────┐   │  │
│  │  │   Content    │   │    Admin     │   │    Plugins    │   │  │
│  │  │    APIs      │   │    Panel     │   │               │   │  │
│  │  └──────────────┘   └──────────────┘   └───────────────┘   │  │
│  │                                                            │  │
│  │  ┌──────────────────────────────────────────────────────┐  │  │
│  │  │                    Datenschicht                      │  │  │
│  │  │   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-Panel und REST-APIs, bietet einen Content-Loader für Live Collections und verwaltet Datenbank-Migrationen und Speicherverbindungen.

Database-First-Schema

Anders als traditionelle CMS, die das Schema im Code definieren, speichert EmDash Schema-Definitionen in der Datenbank selbst. Zwei Systemtabellen verfolgen Ihre Content-Struktur:

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

Wenn Sie über die Admin-UI eine “products”-Collection mit title- und price-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-Modifikation — Erstellen und Bearbeiten von Content-Typen ohne Code-Änderungen oder Rebuilds
  • Entwicklerfreundliches Setup für Nicht-Entwickler — Content-Editoren können ihr Datenmodell über die UI gestalten
  • Echte SQL-Spalten — Ordnungsgemäße Indizierung, Fremdschlüssel und Query-Optimierung

Tabellen pro Collection

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

-- Erstellt, wenn "posts"-Collection 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,    -- Optimistic Locking

  -- Content-Spalten (aus Ihren Felddefinitionen)
  title TEXT NOT NULL,
  content JSON,                 -- Portable Text
  excerpt TEXT
);

Warum Tabellen pro Collection statt einer einzelnen Content-Tabelle mit JSON?

  • Echte SQL-Spalten ermöglichen ordnungsgemäße Indizierung und Queries
  • Fremdschlüssel funktionieren korrekt
  • Schema ist selbstdokumentierend in der Datenbank
  • Kein JSON-Parsing-Overhead für Feldzugriff
  • Datenbanktools können Schema direkt inspizieren

Live Collections Integration

EmDash verwendet Astro 6’s Live Collections, um Content zur Laufzeit bereitzustellen. Content-Änderungen sind sofort verfügbar ohne statische Rebuilds.

emdashLoader() implementiert Astros LiveLoader-Interface:

// src/live.config.ts
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";

export const collections = {
	_emdash: defineLiveCollection({ loader: emdashLoader() }),
};

Abfragen von Content mit den bereitgestellten Wrapper-Funktionen:

import { getEmDashCollection, getEmDashEntry } from "emdash";

// Alle veröffentlichten Posts abrufen
const { entries: posts } = await getEmDashCollection("posts");

// Entwürfe abrufen
const { entries: drafts } = await getEmDashCollection("posts", {
	status: "draft",
});

// Einzelnen Eintrag per Slug abrufen
const { entry: post } = await getEmDashEntry("posts", "my-post-slug");

Routen-Injektion

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

PfadmusterZweck
/_emdash/admin/[...path]Admin-Panel SPA
/_emdash/api/manifestAdmin-Manifest (Collections, Plugins)
/_emdash/api/content/[collection]CRUD für Content-Einträge
/_emdash/api/media/*Medienbibliothek-Operationen
/_emdash/api/schema/*Schema-Verwaltung
/_emdash/api/settingsSite-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.

Datenschicht

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

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 in astro.config.mjs an die Integration ü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",
			}),
		}),
	],
});

Speicher-Abstraktion

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

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

Uploads verwenden signierte URLs für direkte Client-zu-Speicher-Uploads und umgehen Workers Body-Size-Limits.

Plugin-Architektur

Plugins erweitern EmDash durch ein von WordPress inspiriertes Hook-System:

  • Content-Hookscontent:beforeSave, content:afterSave, content:beforeDelete, content:afterDelete
  • Media-Hooksmedia: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. Native — Voller Zugriff auf die Host-Umgebung (für First-Party-Plugins)
  2. Sandboxed — Laufen in V8 Isolates 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 })],
});

Request-Flow

Eine typische Content-Anfrage folgt diesem Pfad:

  1. Astro empfängt Request — Ihre Page-Komponente läuft
  2. Query ContentgetEmDashCollection() ruft Astros getLiveCollection() auf
  3. Loader führt ausemdashLoader fragt die entsprechende ec_*-Tabelle via Kysely ab
  4. Daten werden zurückgegeben — Einträge werden auf Astros Eintragsformat mit id, slug und data gemappt
  5. Seite rendert — Ihre Komponente erhält den Content und rendert HTML

Für Admin-Requests:

  1. Middleware authentifiziert — Validiert Session-Token
  2. API-Route behandelt Request — CRUD-Operationen via Repositories
  3. Hooks feuernbeforeCreate, afterUpdate, etc.
  4. Datenbank-Updates — Kysely führt SQL aus
  5. Response wird zurückgegeben — JSON-Response an Admin-SPA

Virtuelle Module

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

ModulZweck
virtual:emdash/configDatenbank- und Speicherkonfiguration
virtual:emdash/dialectDatenbank-Dialekt-Factory
virtual:emdash/plugin-adminsStatische Imports für Plugin-Admin-UIs

Dieser Ansatz stellt sicher, dass Bundler Plugin-Code ordnungsgemäß auflösen und tree-shaken können.

Nächste Schritte