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:
- Inserta registros en
_emdash_collectionsy_emdash_fields - Ejecuta
ALTER TABLEpara crearec_productscon 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 ruta | Propósito |
|---|---|
/_emdash/admin/[...path] | SPA del panel de administración |
/_emdash/api/manifest | Manifiesto 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/settings | Configuració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 contenido —
content:beforeSave,content:afterSave,content:beforeDelete,content:afterDelete - Hooks multimedia —
media: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:
- Nativo — Acceso completo al entorno host (para plugins propios)
- 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:
- Astro recibe la solicitud — Tu componente de página se ejecuta
- Consultar contenido —
getEmDashCollection()llama agetLiveCollection()de Astro - El cargador se ejecuta —
emdashLoaderconsulta la tablaec_*apropiada a través de Kysely - Datos devueltos — Las entradas se mapean al formato de entrada de Astro con
id,slugydata - La página se renderiza — Tu componente recibe el contenido y renderiza HTML
Para solicitudes de administración:
- El middleware autentica — Valida el token de sesión
- La ruta API maneja la solicitud — Operaciones CRUD a través de repositorios
- Los hooks se disparan —
beforeCreate,afterUpdate, etc. - Actualizaciones de base de datos — Kysely ejecuta SQL
- 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ódulo | Propósito |
|---|---|
virtual:emdash/config | Configuración de base de datos y almacenamiento |
virtual:emdash/dialect | Factory de dialecto de base de datos |
virtual:emdash/plugin-admins | Importaciones 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
Colecciones
Aprende sobre colecciones de contenido y tipos de campo.
Modelo de contenido
Entiende el modelo de contenido database-first.
Panel de administración
Explora la arquitectura del panel de administración.