import type { OpenAPIHono } from "@hono/zod-openapi"; import type { MiddlewareHandler } from "hono"; import { createMiddleware } from "hono/factory"; import type { z } from "zod"; import { type ZodError, fromZodError } from "zod-validation-error"; import type { HonoEnv } from "~/types/api"; import type { ServerHooks } from "./hooks.ts"; export type HonoPluginEnv = HonoEnv & { Variables: { pluginConfig: z.infer; }; }; export class Plugin { private handlers: Partial = {}; private store: z.infer | null = null; private routes: { path: string; fn: (app: OpenAPIHono>) => void; }[] = []; public constructor(private configSchema: ConfigSchema) {} public get middleware(): MiddlewareHandler> { // Middleware that adds the plugin's configuration to the request object return createMiddleware>( async (context, next) => { context.set("pluginConfig", this.getConfig()); await next(); }, ); } public registerRoute( path: string, fn: (app: OpenAPIHono>) => void, ): void { this.routes.push({ path, fn, }); } /** * Loads the plugin's configuration from the Versia Server configuration file. * This will be called when the plugin is loaded. * @param config Values the user has set in the configuration file. */ protected async _loadConfig(config: z.input): Promise { try { this.store = await this.configSchema.parseAsync(config); } catch (error) { throw fromZodError(error as ZodError).message; } } protected _addToApp(app: OpenAPIHono): void { for (const route of this.routes) { app.use(route.path, this.middleware); route.fn( app as unknown as OpenAPIHono>, ); } } public registerHandler( hook: HookName, handler: ServerHooks[HookName], ): void { this.handlers[hook] = handler; } public static [Symbol.hasInstance](instance: unknown): boolean { return ( typeof instance === "object" && instance !== null && "registerHandler" in instance ); } /** * Returns the internal configuration object. */ private getConfig(): z.infer { if (!this.store) { throw new Error("Configuration has not been loaded yet."); } return this.store; } }