import type { IncomingMessage, ServerResponse } from "node:http"; import type { JinnConfig } from "../shared/types.js"; import { type PluginHost } from "./host/index.js"; import { type PluginStorage } from "./storage.js"; /** One backend route. `req` and `res` are the gateway's own, unwrapped. */ export type PluginRouteHandler = (req: IncomingMessage, res: ServerResponse) => void | Promise; /** What a registrar returns: `"GET /ping"` → handler. A map rather than a router * object, because path parameters are not something a plugin needs yet and a * matcher nobody uses is a matcher nobody has tested. */ export type PluginRoutes = Record; /** The context a plugin's `server.js` receives. */ export interface PluginServerContext { id: string; log: (message: string) => void; storage: PluginStorage; /** The typed verb door: Todos, a scoped session spawn, the org, Workflows, * notes, a connector send, cron reads, knowledge search, and a dashboard * notice. The same object the plugin's registrar gets is the one its watcher * starts with, so a background task and a route act as one plugin rather * than as two with the same id. */ host: PluginHost; /** Append an event to this plugin's ring, readable at `/api/plugins//events` * by polling and over that path's socket. Bounded and in memory — the channel * a live UI watches, not a record to depend on. */ emit: (event: unknown) => void; /** This plugin's slice of `config.plugins.settings`. A getter, not a snapshot — * see {@link pluginSettings}. */ readonly settings: Record; } /** * A plugin's optional background task, named export `watcher` on `server.js`. * * The vocabulary is deliberately `Connector`'s (shared/types.ts) — `start`, * `stop`, and health readable by id — so a reader who knows connectors knows * this. The gateway owns when each is called: importing the module must never * start anything, which is why `start` is not module evaluation. * * The promise `start` returns is the watcher's lifetime, not merely its setup. * A task that fails long after starting rejects it, and that is how the * supervisor learns to restart it. */ export interface PluginWatcher { start(context: PluginServerContext): void | Promise; stop(): void | Promise; } export type PluginDispatch = { outcome: "handled"; } | { outcome: "no-route"; } /** The plugin failed. `message` is the gateway's own wording — a third party's * error text and stack stay in the log, not on the wire. */ | { outcome: "failed"; message: string; }; /** What it takes to load a plugin's server module, with nothing about a request * in it — the supervisor loads the same module the request path does. */ export interface PluginBackendRequest { id: string; /** Absolute path to the plugin's server entry. */ server: string; readSettings: () => Record; } export interface PluginDispatchRequest extends PluginBackendRequest { method: string; /** The path below `/api/plugins//`, without a leading slash. */ tail: string; } /** One live incarnation of one plugin's server module. The routes, the watcher * and the context all come from a single import, so the process never holds two * copies of a plugin disagreeing about its own state. */ export interface LoadedBackend { version: string; routes: PluginRoutes; watcher: PluginWatcher | null; context: PluginServerContext; } /** * The plugin's slice of `config.plugins.settings`, `{}` when the operator has * written none. Read through the context's getter rather than captured at import * time, so editing `config.yaml` reaches a registrar that loaded before the edit. */ export declare function pluginSettings(id: string, config: Pick): Record; /** This plugin's loaded module, or null when its server entry is absent or * broken. The supervisor reads it through here so that the watcher it starts and * the routes a request reaches are the same incarnation. */ export declare function loadPluginBackend(request: PluginBackendRequest): Promise; /** * Run one request against a plugin's own routes. * * Every call into third-party code — the registrar and the handler alike — is * wrapped, because a plugin failing is an expected event and the gateway serves * every other request through the same process. An escaping throw would answer * with the plugin's own error text; an unawaited rejection would reach the * process as an unhandled rejection. Both stop here, at this plugin's request. */ export declare function dispatchPluginRequest(req: IncomingMessage, res: ServerResponse, request: PluginDispatchRequest): Promise; /** Forget a plugin's registrar. Called when it stops being servable, so * re-enabling it imports the module again rather than resurrecting the * incarnation that was running when the operator turned it off. */ export declare function disposePluginBackend(id: string): void; /** Forget every loaded backend whose plugin is no longer servable. The request * path disposes lazily, on the first request that finds a plugin disabled — and * that request never arrives when the operator disables and re-enables in one * sitting, which would leave the plugin answering from the incarnation they * turned off. */ export declare function disposeUnservableBackends(isServable: (id: string) => boolean): void; //# sourceMappingURL=backend.d.ts.map