Arquitectura

En esta página

EmDash se integra profundamente con Astro para proporcionar una experiencia CMS completa. Esta página explica las decisiones arquitectónicas clave y cómo encajan las piezas.

Descripción general de alto nivel

┌──────────────────────────────────────────────────────────────────┐
│                         Tu sitio Astro                            │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                   Integración EmDash                       │  │
│  │                                                            │  │
│  │  ┌──────────────┐   ┌──────────────┐   ┌───────────────┐   │  │
│  │  │   Contenido  │   │    Admin     │   │    Plugins    │   │  │
│  │  │    APIs      │   │    Panel     │   │               │   │  │
│  │  └──────────────┘   └──────────────┘   └───────────────┘   │  │
│  │                                                            │  │
│  │  ┌──────────────────────────────────────────────────────┐  │  │
│  │  │                    Capa de datos                     │  │  │
│  │  │   Base de datos (D1/SQLite)  +  Storage (R2/S3)      │  │  │
│  │  └──────────────────────────────────────────────────────┘  │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                    Framework Astro                         │  │
│  │         Live Collections · Middleware · Sesiones           │  │
│  └────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

EmDash funciona como una integración de Astro. Inyecta rutas para el panel de administración y las API REST, proporciona un cargador de contenido para Live Collections y gestiona migraciones de base de datos y conexiones de almacenamiento.

Esquema database-first

A diferencia de los CMS tradicionales que definen el esquema en código, EmDash almacena las definiciones del esquema en la propia base de datos. Dos tablas del sistema rastrean la estructura de tu contenido:

  • _emdash_collections — Metadatos de colección (slug, etiqueta, características)
  • _emdash_fields — Definiciones de campo para cada colección

Cuando creas una colección “products” con campos title y price a través de la interfaz de administración, EmDash:

  1. Inserta registros en _emdash_collections y _emdash_fields
  2. Ejecuta ALTER TABLE para crear ec_products con las columnas apropiadas

Este diseño permite:

  • Modificación del esquema en tiempo de ejecución — Crear y editar tipos de contenido sin cambios de código ni reconstrucciones
  • Configuración amigable para no desarrolladores — Los editores de contenido pueden diseñar su modelo de datos a través de la interfaz
  • Columnas SQL reales — Indexación adecuada, claves externas y optimización de consultas

Tablas por colección

Cada colección obtiene su propia tabla SQLite con prefijo ec_:

-- Creada cuando se añade la colección "posts"
CREATE TABLE ec_posts (
  -- Columnas del sistema (siempre presentes)
  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,              -- Eliminación suave
  version INTEGER DEFAULT 1,    -- Bloqueo optimista

  -- Columnas de contenido (de tus definiciones de campo)
  title TEXT NOT NULL,
  content JSON,                 -- Portable Text
  excerpt TEXT
);

¿Por qué tablas por colección en lugar de una sola tabla de contenido con JSON?

  • Las columnas SQL reales permiten indexación y consultas adecuadas
  • Las claves externas funcionan correctamente
  • El esquema se autodocumenta en la base de datos
  • Sin sobrecarga de análisis JSON para acceso a campos
  • Las herramientas de base de datos pueden inspeccionar el esquema directamente

Integración de Live Collections

EmDash utiliza las Live Collections de Astro 6 para servir contenido en tiempo de ejecución. Los cambios de contenido están disponibles inmediatamente sin reconstrucciones estáticas.

emdashLoader() implementa la interfaz LiveLoader de Astro:

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

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

Consulta contenido usando las funciones wrapper proporcionadas:

import { getEmDashCollection, getEmDashEntry } from "emdash";

// Obtener todos los posts publicados
const { entries: posts } = await getEmDashCollection("posts");

// Obtener borradores
const { entries: drafts } = await getEmDashCollection("posts", {
	status: "draft",
});

// Obtener una sola entrada por slug
const { entry: post } = await getEmDashEntry("posts", "my-post-slug");

Inyección de rutas

La integración de EmDash utiliza la API injectRoute de Astro para añadir rutas de administración y API:

Patrón de rutaPropósito
/_emdash/admin/[...path]SPA del panel de administración
/_emdash/api/manifestManifiesto admin (colecciones, plugins)
/_emdash/api/content/[collection]CRUD para entradas de contenido
/_emdash/api/media/*Operaciones de biblioteca multimedia
/_emdash/api/schema/*Gestión de esquema
/_emdash/api/settingsConfiguración del sitio
/_emdash/api/menus/*Menús de navegación
/_emdash/api/taxonomies/*Categorías, etiquetas, taxonomías personalizadas

Las rutas se inyectan desde el paquete emdash — nada se copia en tu proyecto.

Capa de datos

EmDash utiliza Kysely para consultas SQL con seguridad de tipos en todas las bases de datos compatibles:

SQLite

Desarrollo local con sqlite({ url: "file:./data.db" })

D1

SQL sin servidor de Cloudflare con d1({ binding: "DB" })

libSQL

SQLite remoto con libsql({ url: "...", authToken: "..." })

La configuración de la base de datos se pasa a la integración en astro.config.mjs:

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",
			}),
		}),
	],
});

Abstracción de almacenamiento

Los archivos multimedia se almacenan por separado de la base de datos. EmDash admite:

  • Sistema de archivos local — Desarrollo y despliegues simples
  • Cloudflare R2 — Almacenamiento de objetos compatible con S3 en el edge
  • Compatible con S3 — Cualquier almacenamiento de objetos compatible con S3

Las cargas utilizan URLs firmadas para cargas directas de cliente a almacenamiento, evitando los límites de tamaño de cuerpo de Workers.

Arquitectura de plugins

Los plugins extienden EmDash a través de un sistema de hooks inspirado en WordPress:

  • Hooks de contenidocontent:beforeSave, content:afterSave, content:beforeDelete, content:afterDelete
  • Hooks multimediamedia:beforeUpload, media:afterUpload
  • Almacenamiento aislado — Cada plugin obtiene acceso KV con espacio de nombres
  • Extensiones de UI de administración — Widgets de panel, páginas de configuración, editores de campo personalizados

Los plugins pueden funcionar en dos modos:

  1. Nativo — Acceso completo al entorno host (para plugins propios)
  2. Sandboxed — Ejecutar en aislados V8 con permisos basados en capacidades (para plugins de terceros en Cloudflare)
// astro.config.mjs
import { seoPlugin } from "@emdash-cms/plugin-seo";

emdash({
	plugins: [seoPlugin({ maxTitleLength: 60 })],
});

Flujo de solicitud

Una solicitud de contenido típica sigue este camino:

  1. Astro recibe la solicitud — Tu componente de página se ejecuta
  2. Consultar contenidogetEmDashCollection() llama a getLiveCollection() de Astro
  3. El cargador se ejecutaemdashLoader consulta la tabla ec_* apropiada a través de Kysely
  4. Datos devueltos — Las entradas se mapean al formato de entrada de Astro con id, slug y data
  5. La página se renderiza — Tu componente recibe el contenido y renderiza HTML

Para solicitudes de administración:

  1. El middleware autentica — Valida el token de sesión
  2. La ruta API maneja la solicitud — Operaciones CRUD a través de repositorios
  3. Los hooks se disparanbeforeCreate, afterUpdate, etc.
  4. Actualizaciones de base de datos — Kysely ejecuta SQL
  5. Respuesta devuelta — Respuesta JSON a la SPA de administración

Módulos virtuales

EmDash genera módulos virtuales en tiempo de compilación para configurar el runtime:

MóduloPropósito
virtual:emdash/configConfiguración de base de datos y almacenamiento
virtual:emdash/dialectFactory de dialecto de base de datos
virtual:emdash/plugin-adminsImportaciones estáticas para UI admin de plugins

Este enfoque asegura que los bundlers puedan resolver y hacer tree-shake del código de plugins correctamente.

Próximos pasos