Apariencia
Arquitectura
Contenido del paquete
plugin.json clave, título, versión (semver), apiVersion y, si hay admin, { entry, css }
checksums.json sha256 de cada fichero (todos menos plugin.sig y checksums.json)
plugin.sig firma Ed25519 (base64) de los bytes de checksums.json
api/index.mjs módulo de la API (export default ZModule), compilado con esbuild
migrations/*.sql migraciones propias, en orden alfabético, idempotentes
admin/ index.js (ESM) + chunks/ + style.css + i18n/{en,ca,fr}.json
assets/ recursos del paquete (p. ej. la portada cover.png)El instalador (api/src/plugins/package.ts) abre el zip, rechaza rutas absolutas o con .., verifica la firma de checksums.json contra las claves aceptadas, comprueba que cada fichero coincide con su huella y valida plugin.json y la apiVersion. Nada se escribe en disco hasta que todo cuadra.
Cómo se carga
API. Al arrancar, plugins/loader.ts lee plugin_installs, importa <PLUGINS_DIR>/<clave>/<versión>/api/index.mjs y lo añade a la lista de módulos. A partir de ahí es un módulo más: gate MODULE_DISABLED, permisos, jobs, schedules y eventos de dominio. Un plugin que no carga no tumba la API: se prueba la versión anterior y, si tampoco, se omite.
Panel. Tras iniciar sesión, admin/src/plugins/runtime.ts pide GET /plugins/runtime, carga el diccionario del idioma, la hoja de estilos y el módulo ESM, y registra las rutas (router.addRoute("shell", …)), la entrada de menú, el tutorial y los slots. Un plugin apagado no se importa.
Reinicio. Instalar, actualizar, volver atrás o quitar cambia plugin_installs. Un vigilante (cada 15 s) en la API y en el worker detecta la diferencia con lo cargado y reinicia el proceso; Docker lo levanta. El panel espera y se recarga.
El contrato con el host
Un plugin no empaqueta librerías del host. El compilador convierte sus importaciones en lecturas de objetos globales que rellena el host antes de cargar el plugin:
| En tu código | Se compila a | Lo rellena |
|---|---|---|
import … from "@host/<ruta>", kysely, zod, fastify, pdfkit, qrcode… | globalThis.__HURY_API__[<ruta>] | api/src/plugins/hostRegistry.ts |
import … from "@plugin/<clave>" | globalThis.__HURY_PLUGINS__[<clave>] | el cargador (el plugin debe estar en requires) |
import … from "vue", "vue-router" | window.__HURY_ADMIN__[…] | admin/src/plugins/runtime.ts |
import … from "@/…" | await window.__HURY_ADMIN__.load("@/…") | runtime.ts (solo lo que enumera su import.meta.glob) |
Consecuencias que conviene tener presentes:
- Solo existe lo que el host expone. Un
importde algo que no está falla al cargar con «Cannot read properties of undefined» (API) o «El panel no expone…» (panel). La lista real está en Módulos del host de la API y Módulos del admin. - No se admite
import * as x from "@host/…"ni"@/…": se importan los nombres. - Los imports de solo tipo (
import type …) se borran al compilar y no cuentan. - La versión del contrato es
apiVersion(actualmente 2). Un plugin con unaapiVersionmayor que la del host se rechaza. Añadir módulos o exportaciones es compatible; cambiar o quitar algo existente sube la versión.
Qué comparte un plugin con el núcleo
Núcleo (pos, crm, catalog, commerce, insights, posv3…)
│ emite eventos de dominio y llama ganchos; no importa plugins
▼
Registro de eventos y ganchos ◄── domainHandlers / hooks de cada ZModule
▲
Plugins instalados (rutas, jobs, schedules, permisos, migraciones)
│ piden al núcleo lo que necesitan con @host/…
▼
Host (db, http/*, lib/*, modules/*/…service)El núcleo nunca importa un plugin. Se comunica con ellos por eventos de dominio (síncronos, dentro de la transacción del emisor) y ganchos (puntos de extensión con valor de vuelta). Sin nadie enganchado, el núcleo sigue su camino por defecto. Ver Ganchos y eventos.
Qué no hace el sistema
- No hay sandbox. El código del plugin corre con los mismos permisos que la API, y el del panel en el navegador de quien lo abre. Solo se instala lo firmado por una clave en la que el dueño de la instalación confía.
- No deshace migraciones al volver a una versión anterior: tienen que ser compatibles hacia atrás.
- No cobra nada. Los precios de lista son orientativos; no hay contratación ni facturación conectadas.