first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
# `factory/create-graphiql-ui`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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
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.