Files
2026-05-27 23:29:53 +01:00

98 lines
5.5 KiB
Markdown

# @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`.
<!-- begin:auto-docs -->
## 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 `<uiEndpoint>`. 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 `<uiEndpoint>`. 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…
<!-- end:auto-docs -->
## 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 <uiEndpoint>` (typically the
GraphiQL sibling is wired against the same path).
## License
MIT