4.3 KiB
Capability Spec Delta: api-graphql-ui-graphiql (api-graphql-ui-graphiql)
Package: @suaveplan/api-graphql-ui-graphiql
Status: PROPOSED
Every requirement carries a
**Verified by:**line.
ADDED Requirements
Requirement: Mounts GraphiQL at the configured UI endpoint
The package SHALL expose createGraphiqlUi(opts) returning a RouterPlugin that registers GET <uiEndpoint> (default /graphql/ui) on the host's api-router. The response MUST be the GraphiQL HTML shell embedding the configured graphqlEndpoint and subscriptionEndpoint.
Scenario: default mount serves GraphiQL at /graphql/ui
- WHEN the plugin is mounted with default
opts - THEN
GET /graphql/uireturns 200 AND the body contains the GraphiQL shell AND the embeddedgraphqlEndpointreads/graphql
Scenario: configured endpoints flow into the shell
- WHEN
createGraphiqlUi({ graphqlEndpoint: "/api/gql", subscriptionEndpoint: "/api/gql/ws" }) - THEN the rendered HTML embeds those endpoints (NOT the defaults)
Verified by: src/factory/create-graphiql-ui.test.ts::"default mount returns GraphiQL HTML at /graphql/ui" AND src/factory/create-graphiql-ui.spec-conformance.test.ts::"configured endpoints flow into the embedded shell"
Requirement: Bundled assets only — no CDN fetches
Every static asset referenced by the GraphiQL HTML shell SHALL be served from this package's own dist/assets/. No <script src="https://..."> or <link href="https://..."> MAY point at any CDN. Asset paths MUST embed a cache-busting hash so version upgrades invalidate browser caches. A request for a missing asset MUST return 404 with code GRAPHQL_UI_GRAPHIQL_ASSET_MISSING.
Scenario: no CDN references in the rendered shell
- WHEN the HTML body is inspected
- THEN no
srcorhrefattribute points at an external host
Scenario: missing asset returns the spec'd code
- WHEN a request arrives for
<uiEndpoint>/assets/does-not-exist.js - THEN the response is 404 with
code: "GRAPHQL_UI_GRAPHIQL_ASSET_MISSING"
Verified by: src/assets/assets.test.ts::"rendered shell references no external hosts" AND src/assets/assets.test.ts::"missing asset returns GRAPHQL_UI_GRAPHIQL_ASSET_MISSING"
Requirement: CSP header on the HTML response
The HTML response SHALL carry Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline' so the rendered page is safe to expose in production environments that enforce CSP. Inline styles are required by GraphiQL's bundle; inline scripts MUST NOT be permitted.
Scenario: CSP header is present and excludes 'unsafe-inline' for scripts
- WHEN the HTML response is returned
- THEN the
Content-Security-Policyheader is set ANDscript-srcdoes NOT include'unsafe-inline'
Verified by: src/headers/csp.test.ts::"CSP header set with 'unsafe-inline' only for style-src"
Requirement: Endpoint-collision detection
If the host already has a route registered at <uiEndpoint>, this package SHALL refuse to mount with GRAPHQL_UI_GRAPHIQL_ENDPOINT_COLLISION. Operators see the collision at boot rather than at first request.
Scenario: collision is refused
- WHEN another plugin has already mounted
GET /graphql/uiAND this package attempts to mount on top - THEN mount fails with
GRAPHQL_UI_GRAPHIQL_ENDPOINT_COLLISION
Verified by: src/factory/create-graphiql-ui.test.ts::"endpoint collision refused with GRAPHQL_UI_GRAPHIQL_ENDPOINT_COLLISION"
Requirement: OTel counter for UI traffic
The package SHALL emit graphql.ui.requests_total counter labelled { ui: "graphiql" } on each successful UI HTML request. Asset sub-requests MUST NOT increment this counter (they are observed via the router's standard request metrics).
Scenario: UI HTML request increments the counter
- WHEN
GET /graphql/uireturns 200 - THEN an in-memory
MeterProviderobservesgraphql.ui.requests_total{ui="graphiql"}increment by 1
Scenario: asset request does NOT increment the counter
- WHEN an asset under
<uiEndpoint>/assets/...is fetched - THEN the counter does NOT increment
Verified by: src/instruments/instruments.test.ts::"UI HTML increments graphql.ui.requests_total" AND src/instruments/instruments.test.ts::"asset requests do not increment the UI counter"