Las integraciones son plugins. Los conectores integrados de REST, GraphQL y CMS están escritos con el mismo @malla/plugin-sdk que usa cualquier plugin: una conexión (configuración, campos secretos, una prueba) y operaciones (consultas y mutaciones) validadas con Zod.
Definir un plugin
import { definePlugin, defineOperation } from '@malla/plugin-sdk'
import { z } from 'zod'
const config = z.object({
baseUrl: z.url().describe('API base URL'),
})
export default definePlugin({
id: 'weather',
name: 'Weather',
version: '1.0.0',
description: 'Current conditions by city.',
connection: {
config,
secrets: { apiKey: { label: 'API key' } },
secretBoundFields: ['baseUrl'], // changing it clears the stored key
hosts: (c) => [new URL(c.baseUrl).host], // the only hosts ctx.fetch may reach
test: async (ctx) => {
const res = await ctx.fetch(`${ctx.config.baseUrl}/ping`)
if (!res.ok) throw new Error('Weather API unreachable')
},
},
operations: {
'weather.current': defineOperation({
kind: 'query',
name: 'Current weather',
input: z.object({ city: z.string().min(1) }),
output: z.object({ tempC: z.number(), summary: z.string() }),
run: async (ctx, input) => {
const url = `${ctx.config.baseUrl}/current?city=${encodeURIComponent(input.city)}`
const res = await ctx.fetch(url, { headers: { 'x-api-key': ctx.secrets.apiKey ?? '' } })
return res.json()
},
}),
},
})
- Los ids de las operaciones son
<plugin id>.<name>;definePluginlo comprueba. - El esquema
inputse convierte en el formulario del editor (como JSON Schema) y valida las entradas resueltas;outputalimenta el autocompletado. connection: nullsignifica que no hace falta una fila de integración, como en el CMS integrado.icondefine el icono en el editor;fetchTimeoutMsamplía el tiempo de espera de fetch para servicios lentos, como las API de modelos (120 s como máximo).
El contexto del plugin
Política de salida
ctx.fetch solo llega a los hosts que declara la conexión, por HTTPS. Resuelve los nombres de host (DNS over HTTPS) antes de cada solicitud y cada redirección, y bloquea las respuestas privadas, de loopback y link-local; sigue como máximo 5 redirecciones en el mismo host, se agota a los 10 segundos y limita las respuestas a 2 MB. ALLOW_LOCAL_INTEGRATIONS=1 quita las comprobaciones de dirección, solo para desarrollo local.
Secretos
Los valores secretos se guardan cifrados y la API nunca los devuelve; el editor solo sabe cuáles están definidos. Cambiar una configuración de secretBoundFields o el conjunto de hosts borra los secretos guardados, salvo que lleguen valores nuevos en la misma actualización, así que un token nunca puede redirigirse en silencio a otro host.
El catálogo de plugins
Los plugins se publican con un manifiesto que declara sus permisos, pasan por una revisión y se instalan por sitio con una versión fijada. Las actualizaciones son opcionales.