Files
suaveplan/packages/api-stack/api/api-openapi-ui-scalar/src/per-plugin/per-plugin.md
2026-05-27 23:29:53 +01:00

3.1 KiB

Per-plugin Scalar UI handler

(no submodules)

Purpose

The per-plugin module exposes createPerPluginScalarUiHandler, the variant the host mounts under /docs/ui/scalar/<plugin> so each plugin gets its own Scalar shell pointing at a filtered OpenAPI document (typically /openapi.json?plugin=<name>). 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/<plugin> 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

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

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<ScalarUiOptionsInput, "openapiUrl"> 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.