first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
# @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`).