# Per-plugin Scalar UI handler _(no submodules)_ ## Purpose The per-plugin module exposes `createPerPluginScalarUiHandler`, the variant the host mounts under `/docs/ui/scalar/` so each plugin gets its own Scalar shell pointing at a filtered OpenAPI document (typically `/openapi.json?plugin=`). It composes the standard `createScalarUiHandler` factory but rewrites the OpenAPI URL passthrough and fans out the OTel observation with a `{ plugin }` attribute so dashboards summing per-plugin mounts get distinct buckets without losing the top-line UI total. ## Features - Defaults `uiEndpoint` to `/docs/ui/scalar/` when the caller does not override it — keeps the host's `/docs/ui/scalar` namespace collision-free across N plugins. - Embeds the host-computed filtered URL verbatim — the host is responsible for query-string composition so this package never has to re-implement the OpenAPI filter semantics. - Same CSP + asset + Zod validation guarantees as `createScalarUiHandler`. - Per-plugin OTel attribute fan-out — every render emits both the baseline `{ ui: "scalar" }` and `{ ui: "scalar", plugin }` observations so dashboards can sum either way without losing the top-line total. ## Usage: Basic ```ts import { createPerPluginScalarUiHandler } from "@suaveplan/api-openapi-ui-scalar/per-plugin"; const { handler } = createPerPluginScalarUiHandler({ plugin: "billing", openapiUrlForPlugin: "/openapi.json?plugin=billing", }); const response = handler({ method: "GET", path: "/docs/ui/scalar/billing", }); console.log(response.status); // 200 ``` ## Usage: Advanced ```ts import { createPerPluginScalarUiHandler } from "@suaveplan/api-openapi-ui-scalar/per-plugin"; const bundle = createPerPluginScalarUiHandler({ plugin: "billing", openapiUrlForPlugin: "/openapi.json?plugin=billing", theme: "deepSpace", assetMode: "bundled", uiEndpoint: "/docs/billing", // override the default mount path }); console.log(bundle.plugin); // "billing" console.log(bundle.options.uiEndpoint); // "/docs/billing" // The shell embeds the filtered URL exactly once. const html = bundle.handler({ method: "GET", path: "/docs/billing", }).body; console.assert(html.includes("/openapi.json?plugin=billing")); ``` ## API Reference - `CreatePerPluginScalarUiHandlerInput` — `Omit` plus `{ plugin, openapiUrlForPlugin, meterProvider?, bundledAssetProbe? }`. - `createPerPluginScalarUiHandler(input)` — `PerPluginScalarUiHandlerBundle`. - `PerPluginScalarUiHandlerBundle` extends `ScalarUiHandlerBundle` with the resolved `plugin` field. ## Implementation Notes The per-plugin factory deliberately delegates to the base `createScalarUiHandler` so the per-plugin path inherits every validation + CSP + asset-resolution guarantee. The only divergences are the rewritten OpenAPI URL, the default `uiEndpoint`, and the extra counter observation for the per-plugin attribute. Doubling the counter is intentional — the bare `{ ui: "scalar" }` total stays correct while the per-plugin breakdown remains queryable from the same series.