I plugin richiedono configurazione: chiavi API, feature flag, preferenze di visualizzazione. EmDash offre due meccanismi: uno schema di impostazioni per opzioni configurabili dall’admin e uno store KV per l’accesso programmatico.
Schema delle impostazioni
Dichiara uno schema in admin.settingsSchema per generare automaticamente l’interfaccia di amministrazione:
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 genera un modulo di impostazioni nella sezione admin del plugin. Gli utenti modificano la configurazione senza toccare il codice.
Tipi di campo
String
Campo di testo per stringhe su una o più righe.
siteTitle: {
type: "string",
label: "Site Title",
description: "Optional help text",
default: "My Site",
multiline: false // Set true for textarea
}
Number
Input numerico con vincoli min/max opzionali.
maxItems: {
type: "number",
label: "Maximum Items",
default: 100,
min: 1,
max: 1000
}
Boolean
Interruttore per valori vero/falso.
enabled: {
type: "boolean",
label: "Enabled",
description: "Turn this feature on or off",
default: true
}
Select
Menu a tendina con opzioni predefinite.
theme: {
type: "select",
label: "Theme",
options: [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
{ value: "auto", label: "System" }
],
default: "auto"
}
Secret
Campo crittografato per valori sensibili come chiavi API. Dopo il salvataggio non viene mai inviato al client.
apiKey: {
type: "secret",
label: "API Key",
description: "Stored encrypted"
}
Accedere alle impostazioni
Leggi le impostazioni in hook e route tramite ctx.kv:
"content:beforeSave": async (event, ctx) => {
// Read a setting
const maxLength = await ctx.kv.get<number>("settings:maxTitleLength");
const apiKey = await ctx.kv.get<string>("settings:apiKey");
// Use defaults if not set
const limit = maxLength ?? 60;
ctx.log.info("Using max length", { limit });
return event.content;
}
Le impostazioni sono memorizzate con il prefisso settings: per convenzione. Così i valori configurabili dall’utente si distinguono dallo stato interno del plugin.
API dello store KV
Lo store KV (ctx.kv) è un archivio chiave-valore generico per i dati del 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 }>>;
}
Leggere valori
// Get a single value
const enabled = await ctx.kv.get<boolean>("settings:enabled");
// Get with type
const config = await ctx.kv.get<{ url: string; timeout: number }>("state:config");
Scrivere valori
// Set a value
await ctx.kv.set("settings:lastSync", new Date().toISOString());
// Set complex values
await ctx.kv.set("state:cache", {
data: items,
expiry: Date.now() + 3600000,
});
Elencare valori
// List all settings
const settings = await ctx.kv.list("settings:");
// Returns: [{ key: "settings:enabled", value: true }, ...]
// List all plugin keys
const all = await ctx.kv.list();
Eliminare valori
const deleted = await ctx.kv.delete("state:tempData");
// Returns true if key existed
Convenzioni sui nomi delle chiavi
Usa prefissi per organizzare i dati KV:
| Prefisso | Scopo | Esempio |
|---|---|---|
settings: | Preferenze configurabili dall’utente | settings:apiKey |
state: | Stato interno del plugin | state:lastSync |
cache: | Dati in cache | cache:results |
// Good: clear prefixes
await ctx.kv.set("settings:webhookUrl", url);
await ctx.kv.set("state:lastRun", timestamp);
await ctx.kv.set("cache:feed", feedData);
// Avoid: no prefix, unclear purpose
await ctx.kv.set("url", url);
Impostazioni vs Storage vs KV
Scegli il meccanismo di storage corretto:
| Caso d’uso | Meccanismo |
|---|---|
| Preferenze modificabili dall’admin | admin.settingsSchema + ctx.kv con settings: |
| Stato interno del plugin | ctx.kv con state: |
| Raccolte di documenti | ctx.storage |
Le impostazioni sono valori configurabili dall’utente, che un amministratore potrebbe cambiare. Hanno un’interfaccia generata automaticamente.
Il KV è per lo stato interno: timestamp, cursori di sincronizzazione, calcoli in cache. Nessuna UI, solo codice.
Storage è per raccolte di documenti con query indicizzate — invii di moduli, log di audit, ecc.
Caricare le impostazioni nelle route
Le route API possono esporre le impostazioni ai componenti dell’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 };
}
}
}
Valori predefiniti
Le impostazioni da settingsSchema non vengono persistite automaticamente. Sono predefiniti nell’interfaccia admin. Il codice deve gestire valori mancanti:
"content:afterSave": async (event, ctx) => {
// Always provide a fallback
const enabled = await ctx.kv.get<boolean>("settings:enabled") ?? true;
const maxItems = await ctx.kv.get<number>("settings:maxItems") ?? 100;
if (!enabled) return;
// ...
}
In alternativa, persiste i predefiniti in plugin:install:
hooks: {
"plugin:install": async (_event, ctx) => {
// Persist schema defaults
await ctx.kv.set("settings:enabled", true);
await ctx.kv.set("settings:maxItems", 100);
}
}
Implementazione dello storage
I valori KV sono memorizzati nella tabella _options con chiavi con namespace del plugin:
INSERT INTO _options (name, value) VALUES
('plugin:seo:settings:siteTitle', '"My Site"'),
('plugin:seo:settings:maxTitleLength', '60');
Il prefisso plugin:seo: viene aggiunto automaticamente. Il codice usa settings:siteTitle e EmDash salva plugin:seo:settings:siteTitle.
Così i plugin non sovrascrivono accidentalmente i dati degli altri.