131 lines
7.5 KiB
Markdown
131 lines
7.5 KiB
Markdown
# @suaveplan/api-openapi-ui-scalar — Scalar UI adapter for the Suaveplan OpenAPI plugin system. Exposes createScalarUiHandler() and createPerPluginScalarUiHandler() — factories returning HTTP handlers that serve a self-contained Scalar HTML shell with strict CSP. Default OpenAPI explorer when operators want Scalar; sibling api-openapi-ui-swagger and api-openapi-ui-redoc cover the alternatives.
|
|
|
|
Scalar UI adapter for the Suaveplan OpenAPI plugin system.
|
|
|
|
`createScalarUiHandler(opts)` returns a frozen handler bundle that
|
|
serves a self-contained Scalar HTML shell at `/docs/ui/scalar` (or any
|
|
configured endpoint). The bundle is router-agnostic — host adapters
|
|
(`api-router-hono`, `api-router-in-memory`, worker bridge) wrap the
|
|
handler with their native request context. `createPerPluginScalarUiHandler`
|
|
is the per-plugin variant mounted under `/docs/ui/scalar/<plugin>`.
|
|
|
|
<!-- begin:auto-docs -->
|
|
## Documentation tree
|
|
|
|
- [assets](./src/assets/assets.md) — The assets module owns the in-package bundled Scalar runtime that ships with the package — a small self-contained JS bo…
|
|
- [assets](./src/assets/assets.md) — The assets module owns the in-package bundled Scalar runtime that ships with the package — a small self-contained JS bo…
|
|
- [error-codes](./src/error-codes/error-codes.md) — This module owns the stable error-code registry for the `@suaveplan/api-openapi-ui-scalar` package. The registry is bui…
|
|
- [error-codes](./src/error-codes/error-codes.md) — This module owns the stable error-code registry for the `@suaveplan/api-openapi-ui-scalar` package. The registry is bui…
|
|
- [handler](./src/handler/handler.md) — The handler module owns the public `createScalarUiHandler` factory the host calls when `host.openapi.ui = "scalar"`. It…
|
|
- [handler](./src/handler/handler.md) — The handler module owns the public `createScalarUiHandler` factory the host calls when `host.openapi.ui = "scalar"`. It…
|
|
- [headers](./src/headers/csp.md) — The headers module builds the `Content-Security-Policy` header value emitted on every HTML response from the Scalar UI…
|
|
- [csp](./src/headers/csp.md) — The headers module builds the `Content-Security-Policy` header value emitted on every HTML response from the Scalar UI…
|
|
- [html](./src/html/render-shell.md) — The render-shell module emits the HTML body returned by the Scalar UI handler. It embeds the configured OpenAPI documen…
|
|
- [render-shell](./src/html/render-shell.md) — The render-shell module emits the HTML body returned by the Scalar UI handler. It embeds the configured OpenAPI documen…
|
|
- [instruments](./src/instruments/instruments.md) — The instruments module wires the Scalar UI handler's OTel instruments into a frozen bundle constructed once at handler-…
|
|
- [instruments](./src/instruments/instruments.md) — The instruments module wires the Scalar UI handler's OTel instruments into a frozen bundle constructed once at handler-…
|
|
- [options](./src/options/options.md) — The options module owns the single Zod schema consumed by every Scalar UI handler factory. It is exposed both directly…
|
|
- [options](./src/options/options.md) — The options module owns the single Zod schema consumed by every Scalar UI handler factory. It is exposed both directly…
|
|
- [per-plugin](./src/per-plugin/per-plugin.md) — The per-plugin module exposes `createPerPluginScalarUiHandler`, the variant the host mounts under `/docs/ui/scalar/<plu…
|
|
- [per-plugin](./src/per-plugin/per-plugin.md) — The per-plugin module exposes `createPerPluginScalarUiHandler`, the variant the host mounts under `/docs/ui/scalar/<plu…
|
|
<!-- end:auto-docs -->
|
|
|
|
## Why opt-in
|
|
|
|
Per the design doc (`docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`
|
|
§7) the API foundation reserves `/docs/ui/<vendor>` for an interactive
|
|
OpenAPI explorer but does not bundle one by default — UI choice is
|
|
operator-chosen so the runtime image stays minimal for production
|
|
deployments that don't need it.
|
|
|
|
Operators wire ONE of:
|
|
|
|
- `@suaveplan/api-openapi-ui-scalar` — canonical Scalar (this package).
|
|
- `@suaveplan/api-openapi-ui-swagger` — Swagger UI alternative.
|
|
- `@suaveplan/api-openapi-ui-redoc` — ReDoc alternative.
|
|
|
|
The OpenAPI emitter (`api-openapi`) does not require a UI plugin — the
|
|
JSON / YAML document is always available at `/openapi.json` regardless.
|
|
|
|
## Quick start
|
|
|
|
```ts
|
|
import { createScalarUiHandler } from "@suaveplan/api-openapi-ui-scalar";
|
|
|
|
const { handler, options } = createScalarUiHandler({
|
|
openapiUrl: "/openapi.json",
|
|
});
|
|
|
|
// GET /docs/ui/scalar -> the explorer
|
|
const response = handler({ method: "GET", path: options.uiEndpoint });
|
|
```
|
|
|
|
## Modules
|
|
|
|
- `handler` — `createScalarUiHandler` factory and HTTP handler shape.
|
|
- `per-plugin` — `createPerPluginScalarUiHandler` variant.
|
|
- `options` — `scalarUiOptionsSchema` + `parseScalarUiOptions`.
|
|
- `assets` — bundled JS / CSS registry and `resolveAssetUrl` resolver.
|
|
- `headers` — `buildCspHeader` strict CSP builder.
|
|
- `html` — `renderShell` HTML shell renderer.
|
|
- `instruments` — OTel counter wiring.
|
|
- `error-codes` — `OPENAPI_UI_SCALAR.*` registry.
|
|
|
|
## Configuration
|
|
|
|
| Option | Default | Notes |
|
|
|---|---|---|
|
|
| `openapiUrl` | `/openapi.json` | Embedded into the Scalar config block. |
|
|
| `uiEndpoint` | `/docs/ui/scalar` | Must start with `/`. |
|
|
| `title` | `"API Reference"` | HTML-escaped before embedding. |
|
|
| `theme` | `"default"` | One of 11 built-in Scalar themes. |
|
|
| `layout` | `"modern"` | `"modern"` / `"classic"`. |
|
|
| `assetMode` | `"cdn"` | `"cdn"` / `"bundled"`. |
|
|
| `cdnUrl` | JSDelivr URL | Whitelisted in CSP when `assetMode = "cdn"`. |
|
|
| `defaultOpenAllTags` | `false` | Expand every tag on load. |
|
|
| `hideSearch` | `false` | Hide the Scalar search box. |
|
|
| `darkMode` | `false` | Force dark mode. |
|
|
| `editorOptions` | `{}` | Open passthrough bag to Scalar. |
|
|
| `meterProvider` | OTel global | OTel `MeterProvider` for the counter. |
|
|
|
|
## Security posture
|
|
|
|
- **Strict CSP.** `default-src 'self'; script-src 'self' [<cdn>]; style-src 'self' 'unsafe-inline' [<cdn>]; connect-src 'self' [<api-origin>]`.
|
|
Inline scripts are NEVER permitted — the Scalar bootstrap is loaded as
|
|
an external `<script src>` so `'unsafe-inline'` would be a regression.
|
|
- **Per-request nonce passthrough.** Hosts running nonce-based CSP pass
|
|
`nonce` on each request and the renderer threads it onto the inline
|
|
config block.
|
|
- **Bundled-mode hardening.** `assetMode = "bundled"` keeps every fetch
|
|
inside the host origin. A missing bundled asset raises
|
|
`OPENAPI_UI_SCALAR.ASSETS_MISSING` at handler construction — no silent
|
|
fall-back to CDN.
|
|
- **Cache-immutable assets.** Bundled JS / CSS carries
|
|
`Cache-Control: public, max-age=31536000, immutable`.
|
|
- **Stale-hash tolerance.** Hashed filenames resolve any matching stem +
|
|
ext to the current asset bytes — rolling deploys don't 404 mid-flight.
|
|
|
|
## OTel
|
|
|
|
- `openapi.ui.requests_total { ui: "scalar" }` — counter incremented on
|
|
each successful HTML response.
|
|
- `openapi.ui.requests_total { ui: "scalar", plugin: "<name>" }` — per-
|
|
plugin fan-out emitted by the per-plugin handler in addition to the
|
|
baseline observation.
|
|
|
|
## Error codes
|
|
|
|
- `OPENAPI_UI_SCALAR.ASSETS_MISSING` — bundled mode without a bundle.
|
|
- `OPENAPI_UI_SCALAR.INVALID_OPTIONS` — Zod schema rejected the config.
|
|
- `OPENAPI_UI_SCALAR.ASSET_NOT_REGISTERED` — internal asset lookup miss.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-openapi-ui-scalar
|
|
```
|
|
|
|
100% statements / branches / functions / lines required on touched
|
|
modules (`provider: istanbul`).
|
|
|