# `factory/create-graphiql-ui` _(no submodules)_ ## Purpose Public entry point of the package. `createGraphiqlUi(opts)` returns a `RouterPlugin` — a plain value with `name`, `version`, parsed `options`, the OTel instrument bundle, and a `register(router)` method that mounts two routes on a host `Router`: - `GET ` — serves the GraphiQL HTML shell with the configured CSP header. - `GET /assets/:filename` — serves bundled static assets with an `immutable` Cache-Control header. The plugin is opt-in: the host's plugin wiring decides whether to call `register(router)`. Mount-time validation runs Zod options parsing AND checks the host router for an existing route at the same path; collision raises `GRAPHQL_UI_GRAPHIQL.ENDPOINT_COLLISION` so operators see it at boot. ## Features - Single-call factory — `createGraphiqlUi(input)` returns a frozen `RouterPlugin`. - Endpoint-collision detection at register time via `router.collidesWith` plus a defence-in-depth catch of `ROUTER.ROUTE_COLLISION` from the `router.get` call itself. - Stale-hash-tolerant asset routing — the same `:filename` param matches every served permutation of the asset name. - Counter increments only on HTML hits, never on asset hits. - Custom OTel `MeterProvider` accepted via the optional `meterProvider` field. ## Usage: Basic ```ts import { Router } from "@suaveplan/api-router"; import { createGraphiqlUi } from "@suaveplan/api-graphql-ui-graphiql"; const router = new Router({ plugin: "host" }); createGraphiqlUi({}).register(router); // GET /graphql/ui now serves the explorer. ``` ## Usage: Advanced ```ts import { MeterProvider } from "@opentelemetry/sdk-metrics"; import { Router } from "@suaveplan/api-router"; import { createGraphiqlUi } from "@suaveplan/api-graphql-ui-graphiql"; const meterProvider = new MeterProvider({ /* ... */ }); const plugin = createGraphiqlUi({ uiEndpoint: "/api/explorer", graphqlEndpoint: "/api/gql", subscriptionEndpoint: "/api/gql/ws", theme: "dark", defaultQuery: "{ viewer { id } }", defaultVariableEditorOpen: true, editorOptions: { lineWrapping: true }, meterProvider, }); const router = new Router({ plugin: "host" }); plugin.register(router); ``` ## API Reference - `interface RouterPlugin { name; version; options; instruments; register(router) }`. - `interface CreateGraphiqlUiInput extends GraphiqlUiOptionsInput { meterProvider?: MeterProvider }`. - `interface RouterResponseValue { status; headers; body }` — the shape returned by the route handlers. - `type GraphiqlRouteHandler` — the handler signature consumed by the in-memory `Router`. - `createGraphiqlUi(input?): RouterPlugin`. - `listGraphiqlAssetNames(): readonly string[]`. ## Implementation Notes The handler signature is intentionally adapter-agnostic — it accepts a plain `{ method, path }` request and returns `{ status, headers, body }`. Adapter routers (`api-router-hono`, worker bridges) wrap this with their native request context. The in-memory `Router` from `@suaveplan/api-router` uses the same shape directly, which is what the package's unit tests exercise. The `RouterError` -> `GraphqlUiGraphiqlError` translation in `register()` exists because `router.collidesWith` is best-effort (it only checks the UI endpoint, not the asset sub-route); the second `router.get` for assets may still throw `ROUTE_COLLISION` if some other plugin owns the asset prefix.