Os plugins precisam de configuração — chaves de API, flags de funcionalidades, preferências de exibição. O EmDash fornece dois mecanismos: um esquema de configuração para opções editáveis pelo admin e um armazenamento KV para acesso programático.
Esquema de configuração
Declare um esquema de configuração em admin.settingsSchema para gerar automaticamente uma UI de admin:
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",
},
},
},
});
O EmDash gera um formulário de configuração na secção admin do plugin. Os utilizadores editam as configurações sem tocar no código.
Tipos de campo
String
Campo de texto para strings de uma ou várias linhas.
siteTitle: {
type: "string",
label: "Site Title",
description: "Optional help text",
default: "My Site",
multiline: false // Defina true para textarea
}
Number
Campo numérico com restrições min/max opcionais.
maxItems: {
type: "number",
label: "Maximum Items",
default: 100,
min: 1,
max: 1000
}
Boolean
Interruptor para valores verdadeiro/falso.
enabled: {
type: "boolean",
label: "Enabled",
description: "Turn this feature on or off",
default: true
}
Select
Lista pendente para opções predefinidas.
theme: {
type: "select",
label: "Theme",
options: [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
{ value: "auto", label: "System" }
],
default: "auto"
}
Secret
Campo encriptado para valores sensíveis como chaves de API. Nunca enviado ao cliente após guardar.
apiKey: {
type: "secret",
label: "API Key",
description: "Stored encrypted"
}
Aceder às configurações
Leia configurações em hooks e rotas via ctx.kv:
"content:beforeSave": async (event, ctx) => {
// Ler uma configuração
const maxLength = await ctx.kv.get<number>("settings:maxTitleLength");
const apiKey = await ctx.kv.get<string>("settings:apiKey");
// Usar valores padrão se não definidos
const limit = maxLength ?? 60;
ctx.log.info("Using max length", { limit });
return event.content;
}
As configurações são guardadas com o prefixo settings: por convenção. Isto distingue valores configuráveis pelo utilizador do estado interno do plugin.
API do armazenamento KV
O armazenamento KV (ctx.kv) é um armazenamento chave-valor de uso geral para dados do plugin:
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 }>>;
}
Ler valores
// Obter um valor único
const enabled = await ctx.kv.get<boolean>("settings:enabled");
// Obter com tipo
const config = await ctx.kv.get<{ url: string; timeout: number }>("state:config");
Escrever valores
// Definir um valor
await ctx.kv.set("settings:lastSync", new Date().toISOString());
// Definir valores complexos
await ctx.kv.set("state:cache", {
data: items,
expiry: Date.now() + 3600000,
});
Listar valores
// Listar todas as configurações
const settings = await ctx.kv.list("settings:");
// Devolve: [{ key: "settings:enabled", value: true }, ...]
// Listar todas as chaves do plugin
const all = await ctx.kv.list();
Eliminar valores
const deleted = await ctx.kv.delete("state:tempData");
// Devolve true se a chave existia
Convenções de nomenclatura de chaves
Use prefixos para organizar os dados KV:
| Prefixo | Finalidade | Exemplo |
|---|---|---|
settings: | Preferências configuráveis | settings:apiKey |
state: | Estado interno do plugin | state:lastSync |
cache: | Dados em cache | cache:results |
// Bom: prefixos claros
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);
// Evitar: sem prefixo, finalidade pouco clara
await ctx.kv.set("url", url);
Configurações vs Storage vs KV
Escolha o mecanismo de armazenamento adequado:
| Caso de uso | Mecanismo |
|---|---|
| Preferências editáveis pelo admin | admin.settingsSchema + ctx.kv com settings: |
| Estado interno do plugin | ctx.kv com state: |
| Coleções de documentos | ctx.storage |
Configurações são para valores configuráveis pelo utilizador — coisas que um admin pode alterar. Têm uma UI gerada automaticamente.
KV é para estado interno como timestamps, cursores de sincronização ou cálculos em cache. Sem UI, apenas código.
Storage é para coleções de documentos com consultas indexadas — submissões de formulários, registos de auditoria, etc.
Carregar configurações em rotas
As rotas de API podem expor configurações a componentes de UI de admin:
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 };
}
}
}
Valores padrão
As configurações do settingsSchema não são persistidas automaticamente. São valores padrão na UI de admin. O código deve tratar valores em falta:
"content:afterSave": async (event, ctx) => {
// Sempre fornecer um valor alternativo
const enabled = await ctx.kv.get<boolean>("settings:enabled") ?? true;
const maxItems = await ctx.kv.get<number>("settings:maxItems") ?? 100;
if (!enabled) return;
// ...
}
Alternativamente, persista os valores padrão em plugin:install:
hooks: {
"plugin:install": async (_event, ctx) => {
// Persistir valores padrão do esquema
await ctx.kv.set("settings:enabled", true);
await ctx.kv.set("settings:maxItems", 100);
}
}
Implementação do armazenamento
Os valores KV são guardados na tabela _options com chaves com namespace do plugin:
INSERT INTO _options (name, value) VALUES
('plugin:seo:settings:siteTitle', '"My Site"'),
('plugin:seo:settings:maxTitleLength', '60');
O prefixo plugin:seo: é adicionado automaticamente. O código usa settings:siteTitle e o EmDash guarda como plugin:seo:settings:siteTitle.
Isto garante que os plugins não podem sobrescrever acidentalmente os dados uns dos outros.