import { createTenantMiddleware, type TenantResolver, } from "@suaveplan/api-tenant"; import type { Context, MiddlewareHandler, Next } from "hono"; import { getTenantPluginInstruments } from "../otel/instruments.js"; /** Options accepted by {@link buildTenantPluginMiddleware}. */ export interface BuildTenantPluginMiddlewareOptions { readonly resolver: TenantResolver; readonly required: boolean; readonly allowSuspended: boolean; readonly excludePaths?: readonly string[]; } /** * Build the Hono middleware installed by `createTenantPlugin`'s * `onInit`. Composes `createTenantMiddleware` from `@suaveplan/api-tenant` * with an outer wrapper that records OTel resolution timing + outcome * for every request — even the short-circuited / unresolved paths. * * The inner middleware is the source of truth for `c.set("tenant", * tenant)`, status-gating (deleted/suspended/pending), and the `required` * branch. The wrapper just observes. */ export function buildTenantPluginMiddleware( options: BuildTenantPluginMiddlewareOptions ): MiddlewareHandler { const { resolver, required, allowSuspended, excludePaths } = options; const inner = createTenantMiddleware({ resolver, required, allowSuspended, ...(excludePaths !== undefined ? { excludePaths } : {}), }); return async function tenantPluginMiddleware( c: Context, next: Next ): Promise { const instruments = getTenantPluginInstruments(); const start = performance.now(); try { const result = await inner(c, next); const elapsed = performance.now() - start; const tenant = c.get("tenant"); instruments.resolutionsTotal.add(1, { outcome: tenant !== undefined ? "resolved" : "missing", strategy: resolver.name, }); instruments.resolutionDurationMs.record(elapsed, { strategy: resolver.name, }); return result ?? undefined; } catch (err) { const elapsed = performance.now() - start; instruments.resolutionsTotal.add(1, { outcome: "error", strategy: resolver.name, }); instruments.resolutionDurationMs.record(elapsed, { strategy: resolver.name, }); throw err; } }; }