Modificación en Tiempo de Ejecución
Crea y edita tipos de contenido sin cambios de código o reconstrucciones. Los no desarrolladores pueden diseñar su modelo de contenido a través de la interfaz de administración.
EmDash utiliza un modelo de contenido basado en base de datos donde las definiciones del esquema residen en la base de datos, no en el código. Esta es una decisión de diseño fundamental que permite la modificación del esquema en tiempo de ejecución y una configuración amigable para no desarrolladores.
Los CMS tradicionales como Strapi o Keystatic requieren que definas el esquema en el código:
// Traditional approach - schema in codeconst posts = collection({ fields: { title: text({ required: true }), content: richText(), },});EmDash almacena esta misma información en tablas de la base de datos:
-- _emdash_collections tableINSERT INTO _emdash_collections (slug, label)VALUES ('posts', 'Blog Posts');
-- Tabla _emdash_fieldsINSERT INTO _emdash_fields (collection_id, slug, type, required)VALUES ('coll_abc', 'title', 'string', true), ('coll_abc', 'content', 'portableText', false);Ambos enfoques definen la misma estructura de contenido. La diferencia está en dónde reside esa estructura y cómo puede modificarse.
Modificación en Tiempo de Ejecución
Crea y edita tipos de contenido sin cambios de código o reconstrucciones. Los no desarrolladores pueden diseñar su modelo de contenido a través de la interfaz de administración.
Columnas SQL Reales
A diferencia del modelo EAV (Entidad-Atributo-Valor) de WordPress, cada campo obtiene una columna real. Indexación adecuada, claves foráneas y optimización de consultas.
Auto-documentado
Las herramientas de base de datos pueden inspeccionar el esquema directamente. No es necesario analizar código para entender el modelo de contenido.
Ruta de Migración
Exporta el esquema como JSON para control de versiones. Importa el esquema en nuevos entornos.
Dos tablas del sistema definen tu estructura de contenido:
CREATE TABLE _emdash_collections ( id TEXT PRIMARY KEY, slug TEXT UNIQUE NOT NULL, -- "posts", "products" label TEXT NOT NULL, -- "Blog Posts" label_singular TEXT, -- "Post" description TEXT, icon TEXT, -- Lucide icon name supports JSON, -- ["drafts", "revisions", "preview"] source TEXT, -- How it was created created_at TEXT DEFAULT CURRENT_TIMESTAMP, updated_at TEXT);El campo source rastrea cómo se creó la colección:
| Origen | Descripción |
|---|---|
manual | Creada a través de la interfaz de administración |
template:blog | Creada por el archivo de semilla de una plantilla |
import:wordpress | Importada desde WordPress |
discovered | Descubierta automáticamente a partir de datos existentes |
CREATE TABLE _emdash_fields ( id TEXT PRIMARY KEY, collection_id TEXT REFERENCES _emdash_collections(id), slug TEXT NOT NULL, -- Column name: "title", "price" label TEXT NOT NULL, -- Display label type TEXT NOT NULL, -- Field type column_type TEXT NOT NULL, -- SQLite type: TEXT, REAL, INTEGER, JSON required INTEGER DEFAULT 0, unique_field INTEGER DEFAULT 0, default_value TEXT, -- JSON-encoded default validation JSON, -- Validation rules widget TEXT, -- Custom widget identifier options JSON, -- Widget options sort_order INTEGER, created_at TEXT DEFAULT CURRENT_TIMESTAMP, UNIQUE(collection_id, slug));Cada colección obtiene su propia tabla con el prefijo ec_. Cuando creas una colección “products” con campos title y price:
CREATE TABLE ec_products ( -- System columns (always present) id TEXT PRIMARY KEY, slug TEXT UNIQUE, status TEXT DEFAULT 'draft', 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
-- Columnas de contenido (de las definiciones de campo) title TEXT NOT NULL, price REAL);Cuando agregas un campo a través de la interfaz de administración, EmDash:
_emdash_fields 2. Ejecuta ALTER TABLE ec_collection ADD COLUMN nombre_columna TIPO 3. Regenera el esquema Zod para validaciónSQLite admite estas operaciones ALTER TABLE en tiempo de ejecución:
| Operación | Soportada |
|---|---|
| Agregar columna | Sí |
| Renombrar columna | Sí |
| Eliminar columna | Sí (SQLite 3.35+) |
| Cambiar tipo de columna | No (requiere reconstrucción de tabla) |
Para cambios de tipo, EmDash maneja la reconstrucción de la tabla de forma transparente: crear nueva tabla → copiar datos → eliminar tabla antigua → renombrar nueva tabla.
EmDash mantiene una separación clara:
| Ámbito | Ubicación | Tablas |
|---|---|---|
| Esquema | Tablas del sistema | _emdash_collections, _emdash_fields |
| Contenido | Tablas por colección | ec_posts, ec_products, etc. |
| Medios | Tabla separada + almacenamiento | Tabla media + R2/S3 |
| Configuraciones | Tabla de opciones | options con prefijo site: |
Esta separación significa:
EmDash construye esquemas Zod a partir de las definiciones de campo de la base de datos al iniciar:
// Simplified examplefunction buildSchema(fields: Field[]): ZodSchema { const shape: Record<string, ZodType> = {};
for (const field of fields) { let zodType = fieldTypeToZod(field.type);
if (field.required) { zodType = zodType.required(); }
if (field.validation?.min !== undefined) { zodType = zodType.min(field.validation.min); }
shape[field.slug] = zodType; }
return z.object(shape);}El contenido se valida contra estos esquemas en tiempo de ejecución en cada operación de creación y actualización.
Genera tipos de TypeScript desde el esquema de tu base de datos:
# Fetch schema from database, generate typesnpx emdash typesEsto genera .emdash/types.ts:
// .emdash/types.ts (generated)export interface Post { title: string; content: PortableTextBlock[]; excerpt?: string; featuredImage?: string;}
export interface Product { title: string; price: number; quantity: number;}
// Sobrecargas tipadas para funciones de consultadeclare module "emdash" { export function getEmDashCollection( type: "posts", ): Promise<{ entries: ContentEntry<Post>[]; error?: Error }>;
export function getEmDashEntry( type: "products", id: string, ): Promise<{ entry: ContentEntry<Product> | null; error?: Error; isPreview: boolean }>;}Los desarrolladores pueden usar la CLI:
# Fetch schema, generate typesnpx emdash types
# Exportar esquema como JSONnpx emdash export-seed > seed.jsonLos no desarrolladores usan exclusivamente la interfaz de administración:
Ambos enfoques modifican las mismas tablas de base de datos subyacentes.
Las plantillas y exportaciones usan archivos JSON de semilla para definiciones de esquema portátiles:
{ "version": "1", "collections": [ { "slug": "posts", "label": "Blog Posts", "labelSingular": "Post", "supports": ["drafts", "revisions", "preview"], "fields": [ { "slug": "title", "type": "string", "required": true }, { "slug": "content", "type": "portableText" }, { "slug": "featuredImage", "type": "image" } ] } ], "taxonomies": [{ "name": "category", "label": "Categorias", "hierarchical": true }], "menus": [{ "name": "primary", "label": "Navegacion principal" }]}Aplica archivos de semilla de forma programática:
import { applySeed, validateSeed } from "emdash/seed";import seedData from "../../concepts/.emdash/seed.json";
// Valida primeroconst { valid, errors } = validateSeed(seedData);
// Aplica (idempotente - seguro de volver a ejecutar)await applySeed(db, seedData, { includeContent: true, onConflict: "skip", // 'skip' | 'update' | 'error'});| Enfoque | Ubicación del Esquema | Modificación en Tiempo de Ejecución | Seguridad de Tipos |
|---|---|---|---|
| EmDash | Base de datos | Sí (completa) | Generada desde la BD |
| WordPress | Código PHP + EAV | Limitada (campos meta) | Ninguna |
| Strapi | Archivos de código | No (se requiere reconstrucción) | Generada en la compilación |
| Sanity | Archivos de código | No (el esquema debe desplegarse) | Integrada |
| Directus | Base de datos | Sí (completa) | Generada desde la BD |
EmDash sigue el modelo de Directus: primero la base de datos con generación opcional de tipos. Esto proporciona la máxima flexibilidad mientras aún admite el desarrollo con seguridad de tipos cuando se desea.
Collections
Aprende sobre tipos de campo y validación.
Admin Panel
Explora la arquitectura del panel de administración.
Seeding
Configura sitios con archivos de semilla.