# @suaveplan/api-graphql-ui-altair — Altair GraphQL UI plugin for the Suaveplan API plugin system. Mounts the Altair shell at GET /graphql/ui via createAltairUi(opts), serves bundled (no-CDN) assets from a hashed static map, embeds graphqlEndpoint / subscriptionEndpoint / initialQuery / initialHeaders / initialEnvironments into the shell, attaches a strict CSP (no script 'unsafe-inline'), increments graphql.ui.requests_total{ ui: "altair" } via an injectable MeterProvider, and refuses endpoint collisions with the GRAPHQL_UI_ALTAIR_ENDPOINT_COLLISION code. Structural sibling of api-graphql-ui-graphiql. Altair GraphQL UI plugin for the Suaveplan API plugin system. Structural sibling of `@suaveplan/api-graphql-ui-graphiql`. ## Documentation tree - [assets](./src/assets/assets.md) — `assets` ships the Altair shell as a static-asset map keyed by logical filename. Every entry carries a content-type, th… - [assets](./src/assets/assets.md) — `assets` ships the Altair shell as a static-asset map keyed by logical filename. Every entry carries a content-type, th… - [error-codes](./src/error-codes/error-codes.md) — `error-codes` is the package's single source of truth for the two stable error codes the Altair UI plugin emits. Centra… - [error-codes](./src/error-codes/error-codes.md) — `error-codes` is the package's single source of truth for the two stable error codes the Altair UI plugin emits. Centra… - [factory](./src/factory/create-altair-ui.md) — `factory/create-altair-ui` is the package's public entry point. `createAltairUi(opts)` validates the caller's options a… - [create-altair-ui](./src/factory/create-altair-ui.md) — `factory/create-altair-ui` is the package's public entry point. `createAltairUi(opts)` validates the caller's options a… - [headers](./src/headers/csp.md) — `headers/csp` produces the Content-Security-Policy and the two ancillary security headers (`X-Content-Type-Options`, `R… - [csp](./src/headers/csp.md) — `headers/csp` produces the Content-Security-Policy and the two ancillary security headers (`X-Content-Type-Options`, `R… - [html](./src/html/render-shell.md) — `html/render-shell` produces the single HTML document the Altair UI plugin serves at ``. The shell mounts a… - [render-shell](./src/html/render-shell.md) — `html/render-shell` produces the single HTML document the Altair UI plugin serves at ``. The shell mounts a… - [instruments](./src/instruments/instruments.md) — `instruments` owns the package's single OpenTelemetry counter, `graphql.ui.requests_total`, labelled with the constant… - [instruments](./src/instruments/instruments.md) — `instruments` owns the package's single OpenTelemetry counter, `graphql.ui.requests_total`, labelled with the constant… - [options](./src/options/options.md) — `options` owns the Zod-validated configuration surface every consumer of the Altair UI plugin passes through. The schem… - [options](./src/options/options.md) — `options` owns the Zod-validated configuration surface every consumer of the Altair UI plugin passes through. The schem… ## What this package is - **Opt-in.** The host serves nothing at `/graphql/ui` unless this plugin (or its GraphiQL sibling) is wired against the router. - **Bundled assets only — no CDN.** Every asset the HTML shell loads ships inside this package's `dist/` output. The plugin works in air-gapped, offline-first environments. - **Alternative to GraphiQL, not co-existing.** Operators wire one or the other against `/graphql/ui`. Both at once is refused by host config (the engine adapters return 404 there when neither is wired). ## Quick start ```ts import { createAltairUi } from "@suaveplan/api-graphql-ui-altair"; import { Router } from "@suaveplan/api-router"; const router = new Router(); const altair = createAltairUi(); altair.mount(router); // router.match("GET", "/graphql/ui") resolves to the Altair HTML handler. ``` ## Configuration ```ts createAltairUi({ uiEndpoint: "/graphql/ui", // default graphqlEndpoint: "/graphql", // default subscriptionEndpoint: "/graphql/ws", // default theme: "system", // "light" | "dark" | "system" initialQuery: "", initialHeaders: { "x-tenant": "acme" }, initialEnvironments: { production: { url: "https://api.example.com/graphql", subscriptionsEndpoint: "wss://api.example.com/graphql", headers: { "x-env": "prod" }, variables: { region: "us-east-1" }, }, }, }); ``` ## Security posture - Strict Content-Security-Policy on every HTML response. - `script-src 'self'` — no inline scripts. - `style-src 'self' 'unsafe-inline'` — required for the Altair shell. - `frame-ancestors 'none'` — clickjacking defence. - `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`. - Static assets carry `Cache-Control: public, max-age=31536000, immutable`. ## Observability Emits one OTel counter: `graphql.ui.requests_total` with label `ui="altair"`. The metric name is deliberately identical to the GraphiQL sibling so dashboards can sum across both UIs by dropping the `ui` label. ## Error codes - `GRAPHQL_UI_ALTAIR.ASSET_MISSING` — request for a static asset the bundled registry doesn't recognise (404). - `GRAPHQL_UI_ALTAIR.ENDPOINT_COLLISION` — `mount()` called against a router that already serves `GET ` (typically the GraphiQL sibling is wired against the same path). ## License MIT