JavaScript API 레퍼런스

이 페이지

EmDash는 콘텐츠 쿼리, 미리보기, 설정, 메뉴, 택소노미, 위젯 영역, 섹션, 검색 작업을 위한 함수를 내보냅니다.

콘텐츠 쿼리

EmDash의 쿼리 함수는 Astro의 라이브 콘텐츠 컬렉션 패턴을 따르며, 우아한 에러 처리를 위해 { entries, error } 또는 { entry, error }를 반환합니다.

getEmDashCollection()

컬렉션의 모든 항목을 가져옵니다. 다음 예제는 모든 게시물을 로드하고 에러를 확인합니다:

import { getEmDashCollection } from "emdash";

const { entries: posts, error } = await getEmDashCollection("posts");

if (error) {
	console.error("게시물 로드 실패:", error);
}

매개변수

매개변수타입설명
collectionstring컬렉션 슬러그
optionsCollectionFilter선택적 필터 옵션

옵션

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");
}

매개변수

매개변수타입설명
collectionstring컬렉션 슬러그
slugOrIdstring항목 슬러그 또는 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);

사이트 설정

getSiteSettingsgetSiteSetting으로 사이트 전체 설정을 읽습니다:

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);
  }
}