설정

이 페이지

샌드박스 플러그인은 플러그인별 KV 스토어에 설정을 저장하고 편집 UI를 Block Kit 페이지로 렌더링합니다. 네이티브 플러그인이 사용할 수 있는 자동 생성된 admin.settingsSchema 양식은 샌드박스에서 사용할 수 없습니다. 대신 JSON으로 양식을 설명하고 라우트에서 제공해야 합니다.

이는 settingsSchema보다 약간 더 많은 작업이 필요하지만, 모든 것은 플러그인이 이미 훅과 라우트에 사용하는 동일한 메커니즘을 통해 이루어집니다. 추가로 배울 것이 없습니다.

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

KV는 플러그인별로 분리됩니다. 작성한 키는 플러그인 ID 아래에 저장되며 다른 플러그인에는 표시되지 않습니다.

읽기 및 쓰기

// 읽기
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 deleted = await ctx.kv.delete("state:tempData");

// 접두사로 나열
const allSettings = await ctx.kv.list("settings:");
// → [{ key: "settings:enabled", value: true }, ...]

키 명명 규칙

접두사를 사용하여 다른 종류의 값을 분리합니다. EmDash 플러그인의 규칙:

접두사목적예시
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);

Block Kit의 설정 UI

샌드박스 플러그인은 설정 페이지를 Block Kit로 설명합니다. 관리자는 플러그인의 라우트(일반적으로 routes.admin)에 page_load 상호 작용을 보내고 플러그인은 양식의 JSON 설명을 반환합니다. 사용자가 저장을 클릭하면 관리자는 block_action 또는 form_submit 상호 작용을 다시 보냅니다. 플러그인은 KV에 쓰고 업데이트된 블록을 반환합니다.

import { definePlugin } from "emdash";
import type { PluginContext } from "emdash";

interface BlockInteraction {
	type: "page_load" | "block_action" | "form_submit";
	page?: string;
	action_id?: string;
	values?: Record<string, unknown>;
}

export default definePlugin({
	routes: {
		admin: {
			handler: async (routeCtx, ctx: PluginContext) => {
				const interaction = routeCtx.input as BlockInteraction;

				if (interaction.type === "page_load" && interaction.page === "/settings") {
					return renderSettings(ctx);
				}

				if (interaction.type === "form_submit" && interaction.action_id === "save") {
					await saveSettings(ctx, interaction.values ?? {});
					return {
						...(await renderSettings(ctx)),
						toast: { message: "Settings saved", type: "success" },
					};
				}

				return { blocks: [] };
			},
		},
	},
});

async function renderSettings(ctx: PluginContext) {
	const apiKey = (await ctx.kv.get<string>("settings:apiKey")) ?? "";
	const enabled = (await ctx.kv.get<boolean>("settings:enabled")) ?? true;
	const maxItems = (await ctx.kv.get<number>("settings:maxItems")) ?? 100;

	return {
		blocks: [
			{ type: "header", text: "Plugin settings" },
			{
				type: "form",
				block_id: "settings",
				fields: [
					{
						type: "secret_input",
						action_id: "apiKey",
						label: "API key",
						initial_value: apiKey,
					},
					{
						type: "toggle",
						action_id: "enabled",
						label: "Enabled",
						initial_value: enabled,
					},
					{
						type: "number_input",
						action_id: "maxItems",
						label: "Max items",
						min: 1,
						max: 1000,
						initial_value: maxItems,
					},
				],
				submit: { label: "Save", action_id: "save" },
			},
		],
	};
}

async function saveSettings(ctx: PluginContext, values: Record<string, unknown>) {
	for (const [key, value] of Object.entries(values)) {
		if (value !== undefined) {
			await ctx.kv.set(`settings:${key}`, value);
		}
	}
}

설정 페이지를 관리 사이드바에 연결하려면 디스크립터에 선언합니다.

adminPages: [{ path: "/settings", label: "Settings", icon: "settings" }],

EmDash는 해당 경로에 대한 page_load 상호 작용을 자동으로 admin 라우트로 라우팅합니다.

전체 블록 유형, 양식 필드, 조건부 필드 및 @emdash-cms/blocks 빌더 도우미 세트는 Block Kit를 참조하십시오.

비밀 값

Block Kit의 secret_input 필드는 마스크된 입력으로 렌더링됩니다. 사용자가 거기에 입력하는 값은 주의해서 처리하십시오.

{
	type: "secret_input",
	action_id: "apiKey",
	label: "API key",
	// 실제 비밀로 initial_value를 시드하지 마십시오. 빈 문자열 또는 센티넬을 전달하고
	// 사용자가 비어 있지 않은 값을 입력할 때만 덮어씁니다.
	initial_value: "",
}

저장할 때 빈 문자열을 건너뛰어 매번 저장할 때마다 기존 비밀이 지워지지 않도록 합니다.

async function saveSettings(ctx: PluginContext, values: Record<string, unknown>) {
	if (typeof values.apiKey === "string" && values.apiKey.length > 0) {
		await ctx.kv.set("settings:apiKey", values.apiKey);
	}
	// ... 기타 필드
}

기본값

KV 읽기는 작성되지 않은 키에 대해 null을 반환합니다. 읽기 위치에서 기본값을 전달합니다.

const enabled = (await ctx.kv.get<boolean>("settings:enabled")) ?? true;
const maxItems = (await ctx.kv.get<number>("settings:maxItems")) ?? 100;

또는 설치 중에 기본값을 유지합니다.

hooks: {
	"plugin:install": async (_event, ctx) => {
		await ctx.kv.set("settings:enabled", true);
		await ctx.kv.set("settings:maxItems", 100);
	},
},

트레이드오프는 plugin:install이 설치당 한 번만 실행된다는 것입니다. 이후 버전에서 새 설정을 제공하는 경우 새로 설치된 경우에만 기본값이 표시됩니다. 기존 설치에는 plugin:activate의 마이그레이션(멱등성: 누락된 경우에만 작성) 또는 읽기 시간 폴백을 계속 사용해야 합니다.

설정 vs 스토리지 vs KV

사용 사례메커니즘
관리자 편집 가능한 기본 설정ctx.kvsettings: 접두사 + Block Kit 페이지
내부 플러그인 상태ctx.kvstate: 접두사
문서 컬렉션(쿼리)ctx.storage

KV는 문자열로 키가 지정된 작은 값용입니다. 설정, 동기화 커서, 캐시된 계산. 쿼리 없음, 인덱스 없음.

Storage는 인덱싱된 쿼리가 있는 문서 컬렉션용입니다. 양식 제출, 감사 로그, 필터링, 페이지 매김 또는 계산이 필요한 모든 것.

스토리지 레이아웃

KV 값은 플러그인 네임스페이스 키가 있는 _options 테이블에 저장됩니다. 코드는 settings:apiKey를 사용하지만 EmDash는 plugin:<your-plugin-id>:settings:apiKey로 저장합니다. 접두사는 자동으로 추가되며 한 플러그인이 다른 플러그인의 KV 데이터를 읽거나 덮어쓰는 것을 방지합니다.

네이티브 플러그인: settingsSchema

네이티브 플러그인을 작성하는 경우(React 관리 페이지 또는 PT 구성 요소가 필요하기 때문에) definePlugin() 내에서 직접 설정 스키마를 선언하고 EmDash가 양식을 자동 생성하도록 할 수 있습니다. 해당 경로는 네이티브 플러그인을 참조하십시오.