EmDash는 콘텐츠 쿼리, 미리보기, 설정, 메뉴, 택소노미, 위젯 영역, 섹션, 검색 작업을 위한 함수를 내보냅니다.
콘텐츠 쿼리
EmDash의 쿼리 함수는 Astro의 라이브 콘텐츠 컬렉션 패턴을 따르며, 우아한 에러 처리를 위해 { entries, error } 또는 { entry, error }를 반환합니다.
getEmDashCollection()
컬렉션의 모든 항목을 가져옵니다. 다음 예제는 모든 게시물을 로드하고 에러를 확인합니다:
import { getEmDashCollection } from "emdash";
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) {
console.error("게시물 로드 실패:", error);
}
매개변수
| 매개변수 | 타입 | 설명 |
|---|---|---|
collection | string | 컬렉션 슬러그 |
options | CollectionFilter | 선택적 필터 옵션 |
옵션
options 매개변수는 다음 필터를 받습니다:
interface CollectionFilter {
status?: "draft" | "published" | "archived";
limit?: number;
cursor?: string; // 키셋 페이지네이션 — 이전 `nextCursor` 전달
offset?: number; // 오프셋 페이지네이션 — N개 건너뛰기 (`limit`와 함께 사용)
where?: Record<string, string | string[]>; // 필드 또는 택소노미로 필터
}
반환값
함수는 CollectionResult로 해결됩니다:
interface CollectionResult<T> {
entries: ContentEntry<T>[]; // 에러이거나 없으면 빈 배열
error?: Error; // 쿼리 실패 시 설정
nextCursor?: string; // 다음 키셋 페이지의 커서 (있는 경우)
hasMore?: boolean; // 이 페이지 이후 더 많은 항목이 있는지 (`limit` 설정 시)
}
예제
다음 예제는 상태와 택소노미로 필터링하고, 결과를 제한하며, 에러를 처리합니다:
// 모든 게시된 게시물 가져오기
const { entries: posts } = await getEmDashCollection("posts", {
status: "published",
});
// 최신 5개 게시물 가져오기
const { entries: latest } = await getEmDashCollection("posts", {
limit: 5,
status: "published",
});
// 택소노미로 필터
const { entries: newsPosts } = await getEmDashCollection("posts", {
status: "published",
where: { category: "news" },
});
// 번호 매긴 아카이브 페이지 (예: /page/3) 오프셋 페이지네이션
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" },
});
// 에러 처리
const { entries, error } = await getEmDashCollection("posts");
if (error) {
return new Response("서버 에러", { status: 500 });
}
getEmDashEntry()
슬러그 또는 ID로 단일 항목을 가져옵니다. 다음 예제는 게시물을 로드하고 없으면 리디렉션합니다:
import { getEmDashEntry } from "emdash";
const { entry: post, error } = await getEmDashEntry("posts", "my-post-slug");
if (!post) {
return Astro.redirect("/404");
}
매개변수
| 매개변수 | 타입 | 설명 |
|---|---|---|
collection | string | 컬렉션 슬러그 |
slugOrId | string | 항목 슬러그 또는 ID |
options | { locale?: string } | 선택 사항. 슬러그 해석용 로케일 |
미리보기 모드는 자동으로 처리됩니다 — 미들웨어가 _preview 토큰을 감지하고 AsyncLocalStorage를 통해 드래프트 콘텐츠를 제공합니다. 선택적 options 매개변수는 슬러그 해석용 locale만 받습니다. 미리보기 상태에는 매개변수가 필요 없습니다.
반환값
함수는 EntryResult로 해결됩니다:
interface EntryResult<T> {
entry: ContentEntry<T> | null; // 찾지 못하면 null
error?: Error; // 실제 에러에만 설정, "찾지 못함"에는 설정 안 됨
isPreview: boolean; // 드래프트 콘텐츠가 제공되면 true
}
예제
다음 예제는 슬러그와 ID로 가져오고, 미리보기 상태를 읽고, 에러와 찾지-못함을 구분합니다:
// 슬러그로 가져오기
const { entry: post } = await getEmDashEntry("posts", "hello-world");
// ID로 가져오기
const { entry: post } = await getEmDashEntry("posts", "01HXK5MZSN0FVXT2Q3KPRT9M7D");
// 미리보기는 자동 — 유효한 _preview 토큰이 있으면 isPreview는 true
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);
// 에러 vs 찾지-못함 처리
if (error) {
return new Response("서버 에러", { status: 500 });
}
if (!entry) {
return Astro.redirect("/404");
}
콘텐츠 타입
ContentEntry
쿼리 함수는 다음 형태로 항목을 반환합니다:
interface ContentEntry<T = Record<string, unknown>> {
id: string;
data: T;
edit: EditProxy; // 비주얼 편집 어노테이션
}
edit 프록시는 비주얼 편집 어노테이션을 제공합니다. 요소에 스프레드하여 인라인 편집을 활성화합니다: {...entry.edit.title}. 프로덕션에서는 출력을 생성하지 않습니다.
data 객체에는 모든 콘텐츠 필드와 시스템 필드가 포함됩니다:
id- 고유 식별자slug- URL 친화적 식별자status- “draft” | “published” | “archived”createdAt- ISO 타임스탬프updatedAt- ISO 타임스탬프publishedAt- ISO 타임스탬프 또는 null- 컬렉션 스키마에 정의된 모든 커스텀 필드
미리보기 시스템
generatePreviewToken()
드래프트 콘텐츠용 미리보기 토큰을 생성합니다. 다음 예제는 1시간 후 만료되는 토큰을 생성합니다:
import { generatePreviewToken } from "emdash";
const token = await generatePreviewToken({
contentId: "posts:01HXK5MZSN...",
secret: process.env.EMDASH_ADMIN_SECRET,
expiresIn: 3600, // 1시간
});
verifyPreviewToken()
미리보기 토큰을 검증하고 페이로드를 읽습니다:
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는 "collection:id" 형식, 예: "posts:my-draft-post"
}
isPreviewRequest()
요청에 미리보기 토큰이 포함되어 있는지 확인하고 읽습니다:
import { isPreviewRequest, getPreviewToken } from "emdash";
if (isPreviewRequest(Astro.url)) {
const token = getPreviewToken(Astro.url);
// 검증하고 미리보기 콘텐츠 표시
}
콘텐츠 변환기
Portable Text와 ProseMirror 형식 간 변환:
import { prosemirrorToPortableText, portableTextToProsemirror } from "emdash";
// ProseMirror (에디터)에서 Portable Text (저장)로
const portableText = prosemirrorToPortableText(prosemirrorDoc);
// Portable Text에서 ProseMirror로
const prosemirrorDoc = portableTextToProsemirror(portableText);
사이트 설정
getSiteSettings와 getSiteSetting으로 사이트 전체 설정을 읽습니다:
import { getSiteSettings, getSiteSetting } from "emdash";
// 모든 설정 가져오기
const settings = await getSiteSettings();
// 단일 설정 가져오기
const title = await getSiteSetting("title");
설정은 런타임 API에서 읽기 전용입니다. 업데이트하려면 Admin API를 사용하세요.
메뉴
네비게이션 메뉴를 가져오고 중첩된 자식을 포함한 항목을 반복합니다:
import { getMenu, getMenus } from "emdash";
// 모든 메뉴 가져오기
const menus = await getMenus();
// 특정 메뉴를 항목과 함께 가져오기
const primaryMenu = await getMenu("primary");
if (primaryMenu) {
primaryMenu.items.forEach(item => {
console.log(item.label, item.url);
// 드롭다운용 중첩 항목
item.children.forEach(child => console.log(" -", child.label));
});
}
택소노미
택소노미 용어, 단일 용어, 항목의 용어, 또는 용어별 항목을 가져옵니다:
import { getTaxonomyTerms, getTerm, getEntryTerms, getEntriesByTerm } from "emdash";
// 택소노미의 모든 용어 가져오기 (계층형은 트리 구조)
const categories = await getTaxonomyTerms("category");
// 단일 용어 가져오기
const news = await getTerm("category", "news");
// 콘텐츠 항목에 할당된 용어 가져오기
const postCategories = await getEntryTerms("posts", "post-123", "category");
// 특정 용어를 가진 항목 가져오기
const newsPosts = await getEntriesByTerm("posts", "category", "news");
위젯 영역
위젯 영역과 포함된 위젯을 가져옵니다:
import { getWidgetArea, getWidgetAreas } from "emdash";
// 모든 위젯 영역 가져오기
const areas = await getWidgetAreas();
// 특정 위젯 영역을 위젯과 함께 가져오기
const sidebar = await getWidgetArea("sidebar");
if (sidebar) {
sidebar.widgets.forEach(widget => {
console.log(widget.type, widget.title);
});
}
섹션
섹션을 가져오고 필터링합니다:
import { getSection, getSections } from "emdash";
// 모든 섹션 가져오기 (페이지네이션)
const { items, nextCursor } = await getSections();
// 섹션 필터링
const { items: themeSections } = await getSections({ source: "theme" });
const { items: results } = await getSections({ search: "newsletter" });
// 슬러그로 단일 섹션 가져오기
const cta = await getSection("newsletter-cta");
getSections(options?)는 { items: Section[]; nextCursor?: string }를 반환합니다. 옵션은 source ("theme" | "user" | "import"), search, limit (기본값 50, 최대 100), cursor입니다.
검색
컬렉션 전체에서 글로벌 검색을 실행합니다. 결과에는 하이라이트된 스니펫이 포함됩니다:
import { search } from "emdash";
const results = await search("hello world", {
collections: ["posts", "pages"],
status: "published",
limit: 20,
});
// search()는 { items, nextCursor? }로 해결됨
results.items.forEach(result => {
console.log(result.title);
console.log(result.snippet); // <mark> 태그 포함
console.log(result.score);
});
// 페이지네이션: 다음 페이지를 얻기 위해 이전 nextCursor를 `cursor`로 전달
// 더 이상 결과가 없으면 nextCursor는 undefined
if (results.nextCursor) {
const next = await search("hello world", {
collections: ["posts", "pages"],
limit: 20,
cursor: results.nextCursor,
});
}
에러 처리
EmDash는 특정 실패를 처리하기 위한 에러 클래스를 내보냅니다. 다음 예제는 유효성 검사 및 스키마 에러를 캐치합니다:
import {
EmDashDatabaseError,
EmDashValidationError,
EmDashStorageError,
SchemaError,
} from "emdash";
try {
await repo.create({ ... });
} catch (error) {
if (error instanceof EmDashValidationError) {
console.error("유효성 검사 실패:", error.message);
}
if (error instanceof SchemaError) {
console.error("스키마 에러:", error.code, error.details);
}
}