EmDash exporta funciones para consultar contenido y trabajar con vista previa, configuraciones, menús, taxonomías, áreas de widgets, secciones y búsqueda.
Consultas de contenido
Las funciones de consulta de EmDash siguen el patrón de colecciones de contenido en vivo de Astro, devolviendo { entries, error } o { entry, error } para un manejo elegante de errores.
getEmDashCollection()
Obtener todas las entradas de una colección. El siguiente ejemplo carga todos los posts y verifica errores:
import { getEmDashCollection } from "emdash";
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) {
console.error("Error al cargar posts:", error);
}
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
collection | string | Slug de la colección |
options | CollectionFilter | Opciones de filtro opcionales |
Opciones
El parámetro options acepta el siguiente filtro:
interface CollectionFilter {
status?: "draft" | "published" | "archived";
limit?: number;
cursor?: string; // Paginación por keyset — pasa un `nextCursor` anterior
offset?: number; // Paginación por offset — saltar N entradas (usar con `limit`)
where?: Record<string, string | string[]>; // Filtrar por campo o taxonomía
}
Retorna
La función resuelve a un CollectionResult:
interface CollectionResult<T> {
entries: ContentEntry<T>[]; // Array vacío si hay error o no se encontraron
error?: Error; // Establecido si la consulta falló
nextCursor?: string; // Cursor para la siguiente página keyset, si existe
hasMore?: boolean; // Si existen más entradas más allá de esta página (cuando `limit` está establecido)
}
Ejemplos
Los siguientes ejemplos filtran por estado y taxonomía, limitan resultados y manejan errores:
// Obtener todos los posts publicados
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
// Obtener los últimos 5 posts
const { entries: latest } = await getEmDashCollection("posts", {
limit: 5,
status: "published",
});
// Filtrar por taxonomía
const { entries: newsPosts } = await getEmDashCollection("posts", {
status: "published",
where: { category: "news" },
});
// Página de archivo numerada (ej. /page/3) con paginación por offset
const perPage = 20;
const page = Number(Astro.params.page ?? 1);
const { entries: pagePosts, hasMore } = await getEmDashCollection("posts", {
status: "published",
limit: perPage,
offset: (page - 1) * perPage,
orderBy: { published_at: "desc" },
});
// Manejar errores
const { entries, error } = await getEmDashCollection("posts");
if (error) {
return new Response("Error del servidor", { status: 500 });
}
getEmDashEntry()
Obtener una entrada individual por slug o ID. El siguiente ejemplo carga un post y redirige cuando no existe:
import { getEmDashEntry } from "emdash";
const { entry: post, error } = await getEmDashEntry("posts", "my-post-slug");
if (!post) {
return Astro.redirect("/404");
}
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
collection | string | Slug de la colección |
slugOrId | string | Slug o ID de la entrada |
options | { locale?: string } | Opcional. Locale para resolución de slug |
El modo de vista previa se maneja automáticamente — el middleware detecta tokens _preview y sirve contenido borrador vía AsyncLocalStorage. El parámetro opcional options solo acepta un locale para la resolución de slug; el estado de vista previa no requiere parámetro.
Retorna
La función resuelve a un EntryResult:
interface EntryResult<T> {
entry: ContentEntry<T> | null; // null si no se encontró
error?: Error; // Solo para errores reales, no para "no encontrado"
isPreview: boolean; // true si se está sirviendo contenido borrador
}
Ejemplos
Los siguientes ejemplos obtienen por slug e ID, leen el estado de vista previa y distinguen errores de no-encontrado:
// Obtener por slug
const { entry: post } = await getEmDashEntry("posts", "hello-world");
// Obtener por ID
const { entry: post } = await getEmDashEntry("posts", "01HXK5MZSN0FVXT2Q3KPRT9M7D");
// La vista previa es automática — isPreview es true cuando hay un token _preview válido
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
// Manejar errores vs no-encontrado
if (error) {
return new Response("Error del servidor", { status: 500 });
}
if (!entry) {
return Astro.redirect("/404");
}
Tipos de contenido
ContentEntry
Las funciones de consulta devuelven entradas con la siguiente forma:
interface ContentEntry<T = Record<string, unknown>> {
id: string;
data: T;
edit: EditProxy; // Anotaciones de edición visual
}
El proxy edit proporciona anotaciones de edición visual. Distribúyelo en elementos para habilitar la edición en línea: {...entry.edit.title}. En producción, esto no produce salida.
El objeto data contiene todos los campos de contenido más campos del sistema:
id- Identificador únicoslug- Identificador amigable para URLstatus- “draft” | “published” | “archived”createdAt- Marca de tiempo ISOupdatedAt- Marca de tiempo ISOpublishedAt- Marca de tiempo ISO o null- Más todos los campos personalizados definidos en tu esquema de colección
Sistema de vista previa
generatePreviewToken()
Generar un token de vista previa para contenido borrador. El siguiente ejemplo crea un token que expira en una hora:
import { generatePreviewToken } from "emdash";
const token = await generatePreviewToken({
contentId: "posts:01HXK5MZSN...",
secret: process.env.EMDASH_ADMIN_SECRET,
expiresIn: 3600, // 1 hora
});
verifyPreviewToken()
Verificar un token de vista previa y leer su contenido:
import { verifyPreviewToken } from "emdash";
const result = await verifyPreviewToken({
token,
secret: process.env.EMDASH_ADMIN_SECRET,
});
if (result.valid) {
const { cid, exp, iat } = result.payload;
// cid está en formato "collection:id", ej. "posts:my-draft-post"
}
isPreviewRequest()
Verificar si una solicitud incluye un token de vista previa, luego leerlo:
import { isPreviewRequest, getPreviewToken } from "emdash";
if (isPreviewRequest(Astro.url)) {
const token = getPreviewToken(Astro.url);
// Verificar y mostrar contenido de vista previa
}
Conversores de contenido
Convertir entre formatos Portable Text y ProseMirror:
import { prosemirrorToPortableText, portableTextToProsemirror } from "emdash";
// De ProseMirror (editor) a Portable Text (almacenamiento)
const portableText = prosemirrorToPortableText(prosemirrorDoc);
// De Portable Text a ProseMirror
const prosemirrorDoc = portableTextToProsemirror(portableText);
Configuración del sitio
Leer la configuración global del sitio con getSiteSettings y getSiteSetting:
import { getSiteSettings, getSiteSetting } from "emdash";
// Obtener toda la configuración
const settings = await getSiteSettings();
// Obtener una configuración individual
const title = await getSiteSetting("title");
La configuración es de solo lectura desde la API de ejecución. Usa la API de administración para actualizarla.
Menús
Obtener menús de navegación e iterar sus elementos, incluidos hijos anidados:
import { getMenu, getMenus } from "emdash";
// Obtener todos los menús
const menus = await getMenus();
// Obtener menú específico con elementos
const primaryMenu = await getMenu("primary");
if (primaryMenu) {
primaryMenu.items.forEach(item => {
console.log(item.label, item.url);
// Elementos anidados para desplegables
item.children.forEach(child => console.log(" -", child.label));
});
}
Taxonomías
Obtener términos de taxonomía, un término individual, los términos de una entrada o entradas por término:
import { getTaxonomyTerms, getTerm, getEntryTerms, getEntriesByTerm } from "emdash";
// Obtener todos los términos de una taxonomía (estructura de árbol para jerárquicas)
const categories = await getTaxonomyTerms("category");
// Obtener un término individual
const news = await getTerm("category", "news");
// Obtener términos asignados a una entrada de contenido
const postCategories = await getEntryTerms("posts", "post-123", "category");
// Obtener entradas con un término específico
const newsPosts = await getEntriesByTerm("posts", "category", "news");
Áreas de widgets
Obtener áreas de widgets y los widgets que contienen:
import { getWidgetArea, getWidgetAreas } from "emdash";
// Obtener todas las áreas de widgets
const areas = await getWidgetAreas();
// Obtener área de widgets específica con widgets
const sidebar = await getWidgetArea("sidebar");
if (sidebar) {
sidebar.widgets.forEach(widget => {
console.log(widget.type, widget.title);
});
}
Secciones
Obtener secciones y filtrarlas:
import { getSection, getSections } from "emdash";
// Obtener todas las secciones (paginadas)
const { items, nextCursor } = await getSections();
// Filtrar secciones
const { items: themeSections } = await getSections({ source: "theme" });
const { items: results } = await getSections({ search: "newsletter" });
// Obtener una sección individual por slug
const cta = await getSection("newsletter-cta");
getSections(options?) devuelve { items: Section[]; nextCursor?: string }. Las opciones son source ("theme" | "user" | "import"), search, limit (por defecto 50, máx. 100) y cursor.
Búsqueda
Ejecutar una búsqueda global a través de colecciones. Los resultados incluyen fragmentos resaltados:
import { search } from "emdash";
const results = await search("hello world", {
collections: ["posts", "pages"],
status: "published",
limit: 20,
});
// search() resuelve a { items, nextCursor? }
results.items.forEach(result => {
console.log(result.title);
console.log(result.snippet); // Contiene etiquetas <mark>
console.log(result.score);
});
// Paginar: pasa el nextCursor anterior como `cursor` para obtener la siguiente página.
// nextCursor es undefined cuando no hay más resultados.
if (results.nextCursor) {
const next = await search("hello world", {
collections: ["posts", "pages"],
limit: 20,
cursor: results.nextCursor,
});
}
Manejo de errores
EmDash exporta clases de error para manejar fallos específicos. El siguiente ejemplo captura errores de validación y esquema:
import {
EmDashDatabaseError,
EmDashValidationError,
EmDashStorageError,
SchemaError,
} from "emdash";
try {
await repo.create({ ... });
} catch (error) {
if (error instanceof EmDashValidationError) {
console.error("Validación fallida:", error.message);
}
if (error instanceof SchemaError) {
console.error("Error de esquema:", error.code, error.details);
}
}