플러그인에는 구성이 필요합니다—API 키, 기능 플래그, 표시 환경설정 등. EmDash는 두 가지 메커니즘을 제공합니다: 관리자가 구성 가능한 옵션을 위한 설정 스키마와 프로그래밍 방식 접근을 위한 KV 스토어.
설정 스키마
admin.settingsSchema에 설정 스키마를 선언하여 관리 UI를 자동 생성합니다:
import { definePlugin } from "emdash";
export default definePlugin({
id: "seo",
version: "1.0.0",
admin: {
settingsSchema: {
siteTitle: {
type: "string",
label: "Site Title",
description: "Used in title tags and meta",
default: "",
},
maxTitleLength: {
type: "number",
label: "Max Title Length",
description: "Characters before truncation",
default: 60,
min: 30,
max: 100,
},
generateSitemap: {
type: "boolean",
label: "Generate Sitemap",
description: "Automatically generate sitemap.xml",
default: true,
},
defaultRobots: {
type: "select",
label: "Default Robots",
options: [
{ value: "index,follow", label: "Index & Follow" },
{ value: "noindex,follow", label: "No Index, Follow" },
{ value: "noindex,nofollow", label: "No Index, No Follow" },
],
default: "index,follow",
},
apiKey: {
type: "secret",
label: "API Key",
description: "Encrypted at rest",
},
},
},
});
EmDash는 플러그인의 관리 섹션에 설정 폼을 생성합니다. 사용자는 코드를 수정하지 않고 설정을 편집합니다.
필드 타입
String
한 줄 또는 여러 줄 문자열을 위한 텍스트 입력.
siteTitle: {
type: "string",
label: "Site Title",
description: "Optional help text",
default: "My Site",
multiline: false // textarea를 위해 true로 설정
}
Number
선택적 최소/최대 제약이 있는 숫자 입력.
maxItems: {
type: "number",
label: "Maximum Items",
default: 100,
min: 1,
max: 1000
}
Boolean
참/거짓 값을 위한 토글 스위치.
enabled: {
type: "boolean",
label: "Enabled",
description: "Turn this feature on or off",
default: true
}
Select
미리 정의된 옵션을 위한 드롭다운.
theme: {
type: "select",
label: "Theme",
options: [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
{ value: "auto", label: "System" }
],
default: "auto"
}
Secret
API 키와 같은 민감한 값을 위한 암호화 필드. 저장 후 클라이언트로 전송되지 않습니다.
apiKey: {
type: "secret",
label: "API Key",
description: "Stored encrypted"
}
설정 접근
훅과 라우트에서 ctx.kv를 통해 설정을 읽습니다:
"content:beforeSave": async (event, ctx) => {
// 설정 읽기
const maxLength = await ctx.kv.get<number>("settings:maxTitleLength");
const apiKey = await ctx.kv.get<string>("settings:apiKey");
// 설정되지 않은 경우 기본값 사용
const limit = maxLength ?? 60;
ctx.log.info("Using max length", { limit });
return event.content;
}
설정은 관례적으로 settings: 접두사와 함께 저장됩니다. 이렇게 하면 사용자 구성 가능한 값과 내부 플러그인 상태를 구분합니다.
KV 스토어 API
KV 스토어(ctx.kv)는 플러그인 데이터를 위한 범용 키-값 저장소입니다:
interface KVAccess {
get<T>(key: string): Promise<T | null>;
set(key: string, value: unknown): Promise<void>;
delete(key: string): Promise<boolean>;
list(prefix?: string): Promise<Array<{ key: string; value: unknown }>>;
}
값 읽기
// 단일 값 가져오기
const enabled = await ctx.kv.get<boolean>("settings:enabled");
// 타입과 함께 가져오기
const config = await ctx.kv.get<{ url: string; timeout: number }>("state:config");
값 쓰기
// 값 설정
await ctx.kv.set("settings:lastSync", new Date().toISOString());
// 복합 값 설정
await ctx.kv.set("state:cache", {
data: items,
expiry: Date.now() + 3600000,
});
값 목록
// 모든 설정 목록
const settings = await ctx.kv.list("settings:");
// 반환: [{ key: "settings:enabled", value: true }, ...]
// 모든 플러그인 키 목록
const all = await ctx.kv.list();
값 삭제
const deleted = await ctx.kv.delete("state:tempData");
// 키가 존재했으면 true 반환
키 명명 규칙
접두사를 사용하여 KV 데이터를 정리합니다:
| 접두사 | 용도 | 예시 |
|---|---|---|
settings: | 사용자 구성 가능한 환경설정 | settings:apiKey |
state: | 내부 플러그인 상태 | state:lastSync |
cache: | 캐시된 데이터 | cache:results |
// 좋음: 명확한 접두사
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);
// 나쁨: 접두사 없음, 불명확한 용도
await ctx.kv.set("url", url);
설정 vs 스토리지 vs KV
적절한 저장 메커니즘을 선택합니다:
| 사용 사례 | 메커니즘 |
|---|---|
| 관리자가 편집 가능한 환경설정 | admin.settingsSchema + ctx.kv와 settings: |
| 내부 플러그인 상태 | ctx.kv와 state: |
| 문서 컬렉션 | ctx.storage |
설정은 사용자 구성 가능한 값—관리자가 변경할 수 있는 항목을 위한 것입니다. 자동 생성 UI를 제공합니다.
KV는 타임스탬프, 동기화 커서, 캐시된 계산과 같은 내부 상태를 위한 것입니다. UI 없이 코드만 사용합니다.
스토리지는 인덱싱된 쿼리가 있는 문서 컬렉션—폼 제출, 감사 로그 등을 위한 것입니다.
라우트에서 설정 로드
API 라우트는 관리 UI 컴포넌트에 설정을 노출할 수 있습니다:
routes: {
settings: {
handler: async (ctx) => {
const settings = await ctx.kv.list("settings:");
const result: Record<string, unknown> = {};
for (const entry of settings) {
const key = entry.key.replace("settings:", "");
result[key] = entry.value;
}
return result;
}
},
"settings/save": {
handler: async (ctx) => {
const input = ctx.input as Record<string, unknown>;
for (const [key, value] of Object.entries(input)) {
if (value !== undefined) {
await ctx.kv.set(`settings:${key}`, value);
}
}
return { success: true };
}
}
}
기본값
settingsSchema의 설정은 자동으로 지속되지 않습니다. 관리 UI의 기본값입니다. 코드에서 누락된 값을 처리해야 합니다:
"content:afterSave": async (event, ctx) => {
// 항상 폴백 제공
const enabled = await ctx.kv.get<boolean>("settings:enabled") ?? true;
const maxItems = await ctx.kv.get<number>("settings:maxItems") ?? 100;
if (!enabled) return;
// ...
}
또는 plugin:install에서 기본값을 지속합니다:
hooks: {
"plugin:install": async (_event, ctx) => {
// 스키마 기본값 지속
await ctx.kv.set("settings:enabled", true);
await ctx.kv.set("settings:maxItems", 100);
}
}
스토리지 구현
KV 값은 플러그인 네임스페이스 키와 함께 _options 테이블에 저장됩니다:
INSERT INTO _options (name, value) VALUES
('plugin:seo:settings:siteTitle', '"My Site"'),
('plugin:seo:settings:maxTitleLength', '60');
plugin:seo: 접두사는 자동으로 추가됩니다. 코드에서는 settings:siteTitle을 사용하고, EmDash는 plugin:seo:settings:siteTitle로 저장합니다.
이렇게 하면 플러그인이 실수로 서로의 데이터를 덮어쓰는 것을 방지합니다.