Modificação em Tempo de Execução
Crie e edite tipos de conteúdo sem alterações de código ou rebuilds. Não-desenvolvedores podem projetar seu modelo de conteúdo através da interface administrativa.
O EmDash utiliza um modelo de conteúdo baseado em banco de dados onde as definições de esquema residem no banco de dados, não no código. Esta é uma escolha de design fundamental que permite modificação de esquema em tempo de execução e configuração amigável para não-desenvolvedores.
CMSs tradicionais como Strapi ou Keystatic exigem que você defina o esquema em código:
// Traditional approach - schema in codeconst posts = collection({ fields: { title: text({ required: true }), content: richText(), },});O EmDash armazena essa mesma informação em tabelas do banco de dados:
-- _emdash_collections tableINSERT INTO _emdash_collections (slug, label)VALUES ('posts', 'Blog Posts');
-- Tabela _emdash_fieldsINSERT INTO _emdash_fields (collection_id, slug, type, required)VALUES ('coll_abc', 'title', 'string', true), ('coll_abc', 'content', 'portableText', false);Ambas as abordagens definem a mesma estrutura de conteúdo. A diferença está em onde essa estrutura reside e como pode ser modificada.
Modificação em Tempo de Execução
Crie e edite tipos de conteúdo sem alterações de código ou rebuilds. Não-desenvolvedores podem projetar seu modelo de conteúdo através da interface administrativa.
Colunas SQL Reais
Diferente do modelo EAV (Entidade-Atributo-Valor) do WordPress, cada campo obtém uma coluna real. Indexação adequada, chaves estrangeiras e otimização de consultas.
Auto-documentado
Ferramentas de banco de dados podem inspecionar o esquema diretamente. Não é necessário analisar código para entender o modelo de conteúdo.
Caminho de Migração
Exporte o esquema como JSON para controle de versão. Importe o esquema em novos ambientes.
Duas tabelas do sistema definem sua estrutura de conteúdo:
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);O campo source rastreia como a coleção foi criada:
| Fonte | Descrição |
|---|---|
manual | Criada via interface administrativa |
template:blog | Criada por um arquivo de seed de template |
import:wordpress | Importada do WordPress |
discovered | Descoberta automaticamente a partir de dados 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 coleção obtém sua própria tabela com o prefixo ec_. Quando você cria uma coleção “products” com campos title e 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
-- Colunas de conteúdo (das definições de campo) title TEXT NOT NULL, price REAL);Quando você adiciona um campo via interface administrativa, o EmDash:
_emdash_fields 2. Executa ALTER TABLE ec_collection ADD COLUMN nome_da_coluna TIPO 3. Regenera o esquema Zod para validaçãoO SQLite suporta estas operações ALTER TABLE em tempo de execução:
| Operação | Suportada |
|---|---|
| Adicionar coluna | Sim |
| Renomear coluna | Sim |
| Remover coluna | Sim (SQLite 3.35+) |
| Alterar tipo de coluna | Não (requer reconstrução da tabela) |
Para alterações de tipo, o EmDash lida com a reconstrução da tabela de forma transparente: cria nova tabela → copia dados → remove tabela antiga → renomeia nova tabela.
O EmDash mantém uma separação clara:
| Preocupação | Localização | Tabelas |
|---|---|---|
| Esquema | Tabelas do sistema | _emdash_collections, _emdash_fields |
| Conteúdo | Tabelas por coleção | ec_posts, ec_products, etc. |
| Mídia | Tabela separada + armazenamento | Tabela media + R2/S3 |
| Configurações | Tabela de opções | options com prefixo site: |
Esta separação significa:
O EmDash constrói esquemas Zod a partir das definições de campo do banco de dados na inicialização:
// 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);}O conteúdo é validado contra esses esquemas de tempo de execução em cada operação de criação e atualização.
Gere tipos TypeScript a partir do esquema do seu banco de dados:
# Fetch schema from database, generate typesnpx emdash typesIsso gera .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 funções 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 }>;}Desenvolvedores podem usar a CLI:
# Fetch schema, generate typesnpx emdash types
# Exportar esquema como JSONnpx emdash export-seed > seed.jsonNão-desenvolvedores usam exclusivamente a interface administrativa:
Ambas as abordagens modificam as mesmas tabelas de banco de dados subjacentes.
Templates e exportações usam arquivos JSON de seed para definições de esquema portáteis:
{ "version": "1", "collections": [ { "slug": "posts", "label": "Posts do blog", "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": "Navegação principal" }]}Aplique arquivos de seed programaticamente:
import { applySeed, validateSeed } from "emdash/seed";import seedData from "../../concepts/.emdash/seed.json";
// Valide primeiroconst { valid, errors } = validateSeed(seedData);
// Aplique (idempotente - seguro para reexecutar)await applySeed(db, seedData, { includeContent: true, onConflict: "skip", // 'skip' | 'update' | 'error'});| Abordagem | Localização do Esquema | Modificação em Tempo de Execução | Segurança de Tipos |
|---|---|---|---|
| EmDash | Banco de Dados | Sim (completa) | Gerada a partir do BD |
| WordPress | Código PHP + EAV | Limitada (campos meta) | Nenhuma |
| Strapi | Arquivos de código | Não (rebuild necessário) | Gerada no build |
| Sanity | Arquivos de código | Não (esquema deve ser implantado) | Integrada |
| Directus | Banco de Dados | Sim (completa) | Gerada a partir do BD |
O EmDash segue o modelo do Directus: banco de dados primeiro com geração opcional de tipos. Isso oferece máxima flexibilidade, enquanto ainda suporta desenvolvimento com tipagem segura quando desejado.
Collections
Aprenda sobre tipos de campo e validação.
Painel administrativo
Explore a arquitetura do painel de administração.
Seeding
Configure sites com arquivos de seed.