Files
suaveplan/openspec/archive/2026-05-26-api-graphql-ui-graphiql/proposal.md
2026-05-27 23:29:53 +01:00

52 lines
4.1 KiB
Markdown

# `@suaveplan/api-graphql-ui-graphiql` — GraphiQL UI mount at `/graphql/ui`
**Package**: `@suaveplan/api-graphql-ui-graphiql`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 7 (GraphQL + WebSocket / SSE engines) — see umbrella `openspec/changes/api-plugin-foundation/`
## Why
Per design doc §10.6 the foundation reserves `GET /graphql/ui` for an interactive query explorer but does not bundle one by default — UI choice is opt-in via a separate package so the host's runtime image stays minimal for production deployments that don't need it. GraphiQL is the default option called out in §10.6 ("`GET /graphql/ui` — GraphiQL (or Altair via `api-graphql-ui-altair`)"). Operators wire this package when they want the canonical GraphQL UI; they wire `api-graphql-ui-altair` instead when they prefer Altair.
The GraphQL engine adapters (`api-graphql-yoga`, `api-graphql-apollo`) intentionally return 404 at `/graphql/ui` when no UI plugin is wired — so this package is the standard way to give developers an in-browser explorer.
## What changes
Net-new package depending on `@suaveplan/api-types`, `@suaveplan/api-router`, `@suaveplan/api-graphql`, `@suaveplan/error`, `@suaveplan/logger`, and `graphiql` (peer; static assets served from this package, not fetched from a CDN).
- ADDED: `createGraphiqlUi(opts)` — factory returning a `RouterPlugin` value that registers a `GET <uiEndpoint>` handler serving the GraphiQL HTML shell + assets.
- ADDED: `GraphiqlUiOptions` — Zod-validated config: `uiEndpoint` (default `/graphql/ui`), `graphqlEndpoint` (default `/graphql`), `subscriptionEndpoint` (default `/graphql/ws`), `theme` (`"light"` | `"dark"` | `"system"`, default `"system"`), `defaultQuery`, `defaultVariableEditorOpen`, `editorOptions` (codemirror passthroughs).
- ADDED: Static-asset serving — GraphiQL's bundle is served from the package's own `dist/assets/` (no CDN dependency). The HTML shell embeds versioned hashes for cache-busting.
- ADDED: CSP/security headers — `Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'` (GraphiQL needs inline styles) emitted on the HTML response.
- ADDED: `GRAPHQL_UI_GRAPHIQL_*` error codes: `GRAPHQL_UI_GRAPHIQL_ASSET_MISSING`, `GRAPHQL_UI_GRAPHIQL_ENDPOINT_COLLISION`.
- ADDED: OTel instruments: `graphql.ui.requests_total { ui: "graphiql" }` counter.
## Out of scope
- A CDN-fetched GraphiQL variant — bundled assets only, per the foundation's "no runtime CDN fetches" rule (security + offline-dev posture).
- The Altair alternative — separate package `api-graphql-ui-altair`.
- Schema introspection — that's the engine adapter's job (Yoga / Apollo configure introspection on `/graphql`). This package just renders an explorer that calls into whatever the engine exposes.
- Authentication on the UI route — host config decides whether `/graphql/ui` is behind auth; this package surfaces a `RouterPlugin` whose route options can be configured by the operator.
## Source mapping
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
| Requirement | Design doc section |
|---|---|
| `GET /graphql/ui` default to GraphiQL | §10.6 |
| UI as opt-in via separate package | §10.6 + cross-ref to engine adapters returning 404 when not wired |
| Theme / config passthroughs | §10.6 (implied — interactive explorer) |
## Verification approach
- `src/factory/create-graphiql-ui.test.ts` — factory returns a valid `RouterPlugin`; HTML response shape; CSP header presence; asset 200s.
- `src/factory/create-graphiql-ui.spec-conformance.test.ts` — pins that the mounted endpoint serves at `uiEndpoint` and that the response embeds the configured `graphqlEndpoint` + `subscriptionEndpoint`.
- `src/assets/assets.test.ts` — every bundled asset reachable; missing-asset path raises `GRAPHQL_UI_GRAPHIQL_ASSET_MISSING`.
- `tests/integration/graphiql-end-to-end.test.ts` (30000ms) — boots `api-graphql-yoga` + this UI plugin, opens a Playwright browser at `/graphql/ui`, runs a query through the live editor, asserts results render.
## Migration impact
None — additive only.