3.4 KiB
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 <uiEndpoint>— serves the GraphiQL HTML shell with the configured CSP header.GET <uiEndpoint>/assets/:filename— serves bundled static assets with animmutableCache-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 frozenRouterPlugin. - Endpoint-collision detection at register time via
router.collidesWithplus a defence-in-depth catch ofROUTER.ROUTE_COLLISIONfrom therouter.getcall itself. - Stale-hash-tolerant asset routing — the same
:filenameparam matches every served permutation of the asset name. - Counter increments only on HTML hits, never on asset hits.
- Custom OTel
MeterProvideraccepted via the optionalmeterProviderfield.
Usage: Basic
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
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-memoryRouter.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.