Files
2026-05-27 23:29:53 +01:00

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/ui returns 200 AND the body contains the GraphiQL shell AND the embedded graphqlEndpoint reads /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 src or href attribute 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-Policy header is set AND script-src does 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/ui AND 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/ui returns 200
  • THEN an in-memory MeterProvider observes graphql.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"