Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# @suaveplan/api-graphql-ui-altair — Altair GraphQL UI plugin for the Suaveplan API plugin system. Mounts the Altair shell at GET /graphql/ui via createAltairUi(opts), serves bundled (no-CDN) assets from a hashed static map, embeds graphqlEndpoint / subscriptionEndpoint / initialQuery / initialHeaders / initialEnvironments into the shell, attaches a strict CSP (no script 'unsafe-inline'), increments graphql.ui.requests_total{ ui: "altair" } via an injectable MeterProvider, and refuses endpoint collisions with the GRAPHQL_UI_ALTAIR_ENDPOINT_COLLISION code. Structural sibling of api-graphql-ui-graphiql.
|
||||
|
||||
Altair GraphQL UI plugin for the Suaveplan API plugin
|
||||
system. Structural sibling of `@suaveplan/api-graphql-ui-graphiql`.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [assets](./src/assets/assets.md) — `assets` ships the Altair shell as a static-asset map keyed by logical filename. Every entry carries a content-type, th…
|
||||
- [assets](./src/assets/assets.md) — `assets` ships the Altair shell as a static-asset map keyed by logical filename. Every entry carries a content-type, th…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — `error-codes` is the package's single source of truth for the two stable error codes the Altair UI plugin emits. Centra…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — `error-codes` is the package's single source of truth for the two stable error codes the Altair UI plugin emits. Centra…
|
||||
- [factory](./src/factory/create-altair-ui.md) — `factory/create-altair-ui` is the package's public entry point. `createAltairUi(opts)` validates the caller's options a…
|
||||
- [create-altair-ui](./src/factory/create-altair-ui.md) — `factory/create-altair-ui` is the package's public entry point. `createAltairUi(opts)` validates the caller's options a…
|
||||
- [headers](./src/headers/csp.md) — `headers/csp` produces the Content-Security-Policy and the two ancillary security headers (`X-Content-Type-Options`, `R…
|
||||
- [csp](./src/headers/csp.md) — `headers/csp` produces the Content-Security-Policy and the two ancillary security headers (`X-Content-Type-Options`, `R…
|
||||
- [html](./src/html/render-shell.md) — `html/render-shell` produces the single HTML document the Altair UI plugin serves at `<uiEndpoint>`. The shell mounts a…
|
||||
- [render-shell](./src/html/render-shell.md) — `html/render-shell` produces the single HTML document the Altair UI plugin serves at `<uiEndpoint>`. The shell mounts a…
|
||||
- [instruments](./src/instruments/instruments.md) — `instruments` owns the package's single OpenTelemetry counter, `graphql.ui.requests_total`, labelled with the constant…
|
||||
- [instruments](./src/instruments/instruments.md) — `instruments` owns the package's single OpenTelemetry counter, `graphql.ui.requests_total`, labelled with the constant…
|
||||
- [options](./src/options/options.md) — `options` owns the Zod-validated configuration surface every consumer of the Altair UI plugin passes through. The schem…
|
||||
- [options](./src/options/options.md) — `options` owns the Zod-validated configuration surface every consumer of the Altair UI plugin passes through. The schem…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## What this package is
|
||||
|
||||
- **Opt-in.** The host serves nothing at `/graphql/ui` unless this
|
||||
plugin (or its GraphiQL sibling) is wired against the router.
|
||||
- **Bundled assets only — no CDN.** Every asset the HTML shell loads
|
||||
ships inside this package's `dist/` output. The plugin works in
|
||||
air-gapped, offline-first environments.
|
||||
- **Alternative to GraphiQL, not co-existing.** Operators wire one or
|
||||
the other against `/graphql/ui`. Both at once is refused by host
|
||||
config (the engine adapters return 404 there when neither is wired).
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import { createAltairUi } from "@suaveplan/api-graphql-ui-altair";
|
||||
import { Router } from "@suaveplan/api-router";
|
||||
|
||||
const router = new Router();
|
||||
const altair = createAltairUi();
|
||||
altair.mount(router);
|
||||
|
||||
// router.match("GET", "/graphql/ui") resolves to the Altair HTML handler.
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```ts
|
||||
createAltairUi({
|
||||
uiEndpoint: "/graphql/ui", // default
|
||||
graphqlEndpoint: "/graphql", // default
|
||||
subscriptionEndpoint: "/graphql/ws", // default
|
||||
theme: "system", // "light" | "dark" | "system"
|
||||
initialQuery: "",
|
||||
initialHeaders: { "x-tenant": "acme" },
|
||||
initialEnvironments: {
|
||||
production: {
|
||||
url: "https://api.example.com/graphql",
|
||||
subscriptionsEndpoint: "wss://api.example.com/graphql",
|
||||
headers: { "x-env": "prod" },
|
||||
variables: { region: "us-east-1" },
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Security posture
|
||||
|
||||
- Strict Content-Security-Policy on every HTML response.
|
||||
- `script-src 'self'` — no inline scripts.
|
||||
- `style-src 'self' 'unsafe-inline'` — required for the Altair shell.
|
||||
- `frame-ancestors 'none'` — clickjacking defence.
|
||||
- `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`.
|
||||
- Static assets carry `Cache-Control: public, max-age=31536000, immutable`.
|
||||
|
||||
## Observability
|
||||
|
||||
Emits one OTel counter: `graphql.ui.requests_total` with label
|
||||
`ui="altair"`. The metric name is deliberately identical to the
|
||||
GraphiQL sibling so dashboards can sum across both UIs by dropping
|
||||
the `ui` label.
|
||||
|
||||
## Error codes
|
||||
|
||||
- `GRAPHQL_UI_ALTAIR.ASSET_MISSING` — request for a static asset the
|
||||
bundled registry doesn't recognise (404).
|
||||
- `GRAPHQL_UI_ALTAIR.ENDPOINT_COLLISION` — `mount()` called against a
|
||||
router that already serves `GET <uiEndpoint>` (typically the
|
||||
GraphiQL sibling is wired against the same path).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# `assets` — Bundled asset registry, resolver, CDN-clean check
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`assets` ships the Altair shell as a static-asset map keyed by logical
|
||||
filename. Every entry carries a content-type, the payload bytes, and a
|
||||
short SHA-256 hash derived once at module init so cache-bust URLs are
|
||||
stable across deploys with unchanged payloads. The shell is shipped
|
||||
inline (no FS reads at request time, no CDN fetches) which preserves
|
||||
the Altair plugin's hermetic-bundle guarantee — the package can boot in
|
||||
air-gapped, offline-first environments and still serve a usable UI.
|
||||
|
||||
## Features
|
||||
|
||||
- Default registry: `altair.js` + `altair.css`.
|
||||
- `defineAsset(name, contentType, body)` returns a frozen `BundledAsset`.
|
||||
- `resolveAsset(registry, uiEndpointBase, name)` produces a
|
||||
`ResolvedAsset` with a hash-busted URL.
|
||||
- Missing names throw `GraphqlUiAltairError` (`ASSET_MISSING`).
|
||||
- `findExternalHostReferences` is the CDN-clean conformance check.
|
||||
- Hash derivation is SHA-256, truncated to 16 hex chars — same
|
||||
cardinality as a `git rev-parse --short=16`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
buildDefaultAssetRegistry,
|
||||
resolveAsset,
|
||||
} from "@suaveplan/api-graphql-ui-altair/assets";
|
||||
|
||||
const registry = buildDefaultAssetRegistry();
|
||||
const jsAsset = resolveAsset(registry, "/graphql/ui", "altair.js");
|
||||
console.log(jsAsset.url); // /graphql/ui/assets/<hash>/altair.js
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
buildDefaultAssetRegistry,
|
||||
defineAsset,
|
||||
findExternalHostReferences,
|
||||
} from "@suaveplan/api-graphql-ui-altair/assets";
|
||||
|
||||
// Extend the registry with an operator-specific asset.
|
||||
const registry = new Map(buildDefaultAssetRegistry());
|
||||
registry.set(
|
||||
"logo.svg",
|
||||
defineAsset(
|
||||
"logo.svg",
|
||||
"image/svg+xml",
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" />'
|
||||
)
|
||||
);
|
||||
|
||||
const offenders = findExternalHostReferences(registry);
|
||||
if (offenders.length > 0) {
|
||||
throw new Error("CDN leak: " + offenders.join(", "));
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `BundledAsset` — `{ name, contentType, body, hash }`.
|
||||
- `ResolvedAsset extends BundledAsset` — adds `url`.
|
||||
- `defineAsset(name, contentType, body): BundledAsset`.
|
||||
- `buildDefaultAssetRegistry(): ReadonlyMap<string, BundledAsset>`.
|
||||
- `resolveAsset(registry, uiEndpointBase, name): ResolvedAsset` — throws
|
||||
`GraphqlUiAltairError` (`ASSET_MISSING`) on misses.
|
||||
- `findExternalHostReferences(registry): readonly string[]`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The asset bodies are held as JavaScript template literals so the build
|
||||
emits them as plain string constants — no FS access at boot, no
|
||||
`process.cwd()` dependence. The hash is computed via `node:crypto` to
|
||||
match the same hashing the rest of the project's static-asset
|
||||
pipeline uses; the truncation to 16 hex chars is intentional — a
|
||||
collision in a single deploy's asset set is vanishingly unlikely and
|
||||
the shorter URL is easier on the eyes in production logs.
|
||||
@@ -0,0 +1,83 @@
|
||||
# `error-codes` — `GRAPHQL_UI_ALTAIR.*` registry
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`error-codes` is the package's single source of truth for the two stable
|
||||
error codes the Altair UI plugin emits. Centralising the codes here lets
|
||||
the factory, the asset map, and any downstream observability tooling
|
||||
import the literal strings from one location — no string-typos, no
|
||||
divergence between the wire format and the registry. The registry is
|
||||
frozen at module init and validated against the umbrella `defineCodes`
|
||||
contract from `@suaveplan/error` so accidental drift is caught at type
|
||||
check time, not at runtime.
|
||||
|
||||
## Features
|
||||
|
||||
- Two codes: `GRAPHQL_UI_ALTAIR.ASSET_MISSING`,
|
||||
`GRAPHQL_UI_ALTAIR.ENDPOINT_COLLISION`.
|
||||
- Concrete `GraphqlUiAltairError` class extending `SuaveplanError`.
|
||||
- `altairCode` narrows to `GraphqlUiAltairErrorCode` for exhaustive
|
||||
switch statements.
|
||||
- Compatible with the umbrella redaction + i18n + OTel span-event
|
||||
adapters shipped by `@suaveplan/error`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
GRAPHQL_UI_ALTAIR_CODES,
|
||||
GraphqlUiAltairError,
|
||||
} from "@suaveplan/api-graphql-ui-altair/error-codes";
|
||||
|
||||
throw new GraphqlUiAltairError(
|
||||
GRAPHQL_UI_ALTAIR_CODES.ASSET_MISSING,
|
||||
"Asset not present in bundled map",
|
||||
{ context: { path: "/graphql/ui/assets/missing.js" } }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
GRAPHQL_UI_ALTAIR_CODES,
|
||||
GraphqlUiAltairError,
|
||||
} from "@suaveplan/api-graphql-ui-altair/error-codes";
|
||||
|
||||
function handle(err: unknown): number {
|
||||
if (err instanceof GraphqlUiAltairError) {
|
||||
switch (err.altairCode) {
|
||||
case GRAPHQL_UI_ALTAIR_CODES.ASSET_MISSING:
|
||||
return 404;
|
||||
case GRAPHQL_UI_ALTAIR_CODES.ENDPOINT_COLLISION:
|
||||
return 409;
|
||||
}
|
||||
}
|
||||
return 500;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `GRAPHQL_UI_ALTAIR_CODES` — frozen record `{ ASSET_MISSING, ENDPOINT_COLLISION }`.
|
||||
- `GraphqlUiAltairErrorCode` — discriminated union of the two literal
|
||||
strings.
|
||||
- `class GraphqlUiAltairError extends SuaveplanError<GraphqlUiAltairErrorCode>`
|
||||
— owns the `altairCode` accessor.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`defineCodes("GRAPHQL_UI_ALTAIR", ...)` produces the prefixed literal
|
||||
strings — agents must not hand-build the strings to avoid prefix drift.
|
||||
The error class delegates entirely to `SuaveplanError` for context
|
||||
serialisation, cause chains, and the wire-format adapter; the only
|
||||
local responsibility is preserving `altairCode` for `instanceof`
|
||||
narrowing in operator-facing error handlers. The two-code surface is
|
||||
intentionally minimal: the asset path is the only failure mode at HTTP
|
||||
boundary, and endpoint collision is the only failure mode at mount
|
||||
boundary. Adding a code requires an OpenSpec change so the registry
|
||||
shape never drifts silently between the spec and the runtime.
|
||||
@@ -0,0 +1,88 @@
|
||||
# `factory/create-altair-ui` — Altair UI plugin factory
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`factory/create-altair-ui` is the package's public entry point.
|
||||
`createAltairUi(opts)` validates the caller's options against the Zod
|
||||
schema, builds the asset registry and OTel instruments, and returns a
|
||||
`RouterPlugin` whose `mount(router)` method registers `GET <uiEndpoint>`
|
||||
plus the hashed-asset routes against any `Router` instance. Endpoint
|
||||
collisions are detected up-front via `router.collidesWith` and reject
|
||||
the mount with a typed `GRAPHQL_UI_ALTAIR.ENDPOINT_COLLISION` error
|
||||
before any route is written — partial mounts are impossible.
|
||||
|
||||
## Features
|
||||
|
||||
- Zero-config call: `createAltairUi()` works out of the box.
|
||||
- Returns a frozen `RouterPlugin` with `name`, `options`, `mount`,
|
||||
`renderHtml`, and `renderAsset` members.
|
||||
- HTML response always carries the strict CSP + ancillary security
|
||||
headers.
|
||||
- Asset response carries `Cache-Control: public, max-age=31536000, immutable`.
|
||||
- Asset misses (wrong hash, wrong name) throw `GraphqlUiAltairError`
|
||||
with code `ASSET_MISSING`.
|
||||
- Endpoint collisions throw `GraphqlUiAltairError` with code
|
||||
`ENDPOINT_COLLISION` before any route is registered.
|
||||
- Injectable internals (`assetRegistry`, `instruments`) for integration
|
||||
tests and operators wanting custom asset bundles.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createAltairUi } from "@suaveplan/api-graphql-ui-altair/factory";
|
||||
import { Router } from "@suaveplan/api-router";
|
||||
|
||||
const router = new Router();
|
||||
const altair = createAltairUi();
|
||||
altair.mount(router);
|
||||
|
||||
// router.match("GET", "/graphql/ui") now resolves to the HTML handler.
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { createAltairUi } from "@suaveplan/api-graphql-ui-altair/factory";
|
||||
import { createAltairInstruments } from "@suaveplan/api-graphql-ui-altair/instruments";
|
||||
import { MeterProvider } from "@opentelemetry/sdk-metrics";
|
||||
import { Router } from "@suaveplan/api-router";
|
||||
|
||||
const meterProvider = new MeterProvider();
|
||||
const instruments = createAltairInstruments({ meterProvider });
|
||||
|
||||
const router = new Router();
|
||||
const altair = createAltairUi(
|
||||
{
|
||||
uiEndpoint: "/dev/altair",
|
||||
graphqlEndpoint: "/api/graphql",
|
||||
theme: "dark",
|
||||
initialEnvironments: {
|
||||
production: {
|
||||
url: "https://api.example.com/graphql",
|
||||
headers: { "x-tenant": "acme" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{ instruments }
|
||||
);
|
||||
altair.mount(router);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createAltairUi(input?: AltairUiOptionsInput, internals?: CreateAltairUiInternals): RouterPlugin`.
|
||||
- `RouterPlugin` — `{ name, options, mount(router), renderHtml(), renderAsset(hash, name) }`.
|
||||
- `CreateAltairUiInternals` — `{ assetRegistry?, instruments? }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The factory composes five lower-level modules — options, assets, html,
|
||||
headers, instruments — and exposes their behaviour behind one
|
||||
ergonomic entry point. The `RouterPlugin.options` field is the parsed,
|
||||
defaulted bundle (not the caller's raw input) so downstream code reads
|
||||
the same value the renderer wrote. Frozen-everything posture means
|
||||
operators cannot accidentally mutate the plugin between `mount` calls.
|
||||
@@ -0,0 +1,78 @@
|
||||
# `headers/csp` — Strict CSP for the Altair shell
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`headers/csp` produces the Content-Security-Policy and the two ancillary
|
||||
security headers (`X-Content-Type-Options`, `Referrer-Policy`) the
|
||||
Altair plugin attaches to its HTML response. The policy is deliberately
|
||||
strict — no `'unsafe-inline'` on `script-src` — so a regression that
|
||||
sneaks an inline script into the shell template breaks loud at policy
|
||||
enforcement, not at audit time. The same posture is used by the
|
||||
GraphiQL sibling so dashboards and auditors can compare both packages
|
||||
on equal terms.
|
||||
|
||||
## Features
|
||||
|
||||
- Single `Content-Security-Policy` string with eight directives.
|
||||
- `'unsafe-inline'` is permitted ONLY on `style-src` — required for
|
||||
Altair's shell styling, never for scripts.
|
||||
- `frame-ancestors 'none'` denies embedding (clickjacking defence).
|
||||
- Ancillary headers: `X-Content-Type-Options: nosniff`,
|
||||
`Referrer-Policy: no-referrer`.
|
||||
- Header bundle returned frozen so downstream middleware can't mutate
|
||||
it by accident.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { buildAltairResponseHeaders } from "@suaveplan/api-graphql-ui-altair/headers";
|
||||
|
||||
const headers = buildAltairResponseHeaders();
|
||||
// headers["Content-Security-Policy"] === "default-src 'self'; ..."
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
ALTAIR_CSP_HEADER_NAME,
|
||||
ALTAIR_CSP_HEADER_VALUE,
|
||||
buildAltairResponseHeaders,
|
||||
} from "@suaveplan/api-graphql-ui-altair/headers";
|
||||
|
||||
// Drop onto a Fetch-style Response.
|
||||
function respond(body: string): Response {
|
||||
const headers = new Headers(buildAltairResponseHeaders());
|
||||
headers.set("Content-Type", "text/html; charset=utf-8");
|
||||
return new Response(body, { status: 200, headers });
|
||||
}
|
||||
|
||||
// Direct access for static header injection (e.g. CDN edge config).
|
||||
console.log(`${ALTAIR_CSP_HEADER_NAME}: ${ALTAIR_CSP_HEADER_VALUE}`);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `buildAltairResponseHeaders(): Readonly<Record<string,string>>`
|
||||
- `ALTAIR_CSP_HEADER_NAME: "Content-Security-Policy"` literal.
|
||||
- `ALTAIR_CSP_HEADER_VALUE: string` — the eight-directive policy.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The CSP value is assembled as a constant string at module init — there
|
||||
are no parameters today (a nonce'd variant would land here if the
|
||||
shell template were converted off inline `<style>` and would consume a
|
||||
caller-supplied nonce). The function return is wrapped in
|
||||
`Object.freeze` to surface accidental mutation during integration
|
||||
tests. Eight directives keep the policy short enough to read in a
|
||||
single browser-devtools row yet rich enough to defeat the standard
|
||||
opportunistic-injection vectors (mixed content, inline JS, dangling
|
||||
`<form>` hijack, third-party `<base>` retargeting). Operators wanting
|
||||
a stricter posture — e.g. `script-src 'none'` for a read-only audit
|
||||
demo — can wrap the function and substitute their own value before it
|
||||
hits the response builder; the package never reads the CSP string
|
||||
back, so there is no integrity-check loop to satisfy.
|
||||
@@ -0,0 +1,81 @@
|
||||
# `html/render-shell` — Altair HTML shell builder
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`html/render-shell` produces the single HTML document the Altair UI
|
||||
plugin serves at `<uiEndpoint>`. The shell mounts a `<div id="altair-root">`,
|
||||
embeds the resolved JS/CSS asset URLs (hash-busted, so CDN cache
|
||||
behaviour is correct), and serialises the parsed `AltairUiOptions` as a
|
||||
`<script type="application/json">` data block — never as executable
|
||||
inline JavaScript — so the strict CSP attached by `headers/csp` can
|
||||
forbid `script-src 'unsafe-inline'` outright.
|
||||
|
||||
## Features
|
||||
|
||||
- One self-contained HTML document — no external host references.
|
||||
- Configurable endpoints surfaced both as `<meta>` tags AND as `data-*`
|
||||
attributes on the root container, so consumers integrating Altair
|
||||
via custom JS can read either layer.
|
||||
- JSON config block escaped against `</script>` injection per the HTML5
|
||||
spec recommendation.
|
||||
- User-supplied endpoint values HTML-escaped before embedding.
|
||||
- Theme attribute (`data-altair-theme`) on `<html>` for CSS hooks.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { renderAltairShell } from "@suaveplan/api-graphql-ui-altair/html";
|
||||
import { buildDefaultAssetRegistry } from "@suaveplan/api-graphql-ui-altair/assets";
|
||||
import { parseAltairUiOptions } from "@suaveplan/api-graphql-ui-altair/options";
|
||||
|
||||
const html = renderAltairShell({
|
||||
options: parseAltairUiOptions(),
|
||||
assetRegistry: buildDefaultAssetRegistry(),
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { renderAltairShell } from "@suaveplan/api-graphql-ui-altair/html";
|
||||
import {
|
||||
buildDefaultAssetRegistry,
|
||||
defineAsset,
|
||||
} from "@suaveplan/api-graphql-ui-altair/assets";
|
||||
import { parseAltairUiOptions } from "@suaveplan/api-graphql-ui-altair/options";
|
||||
|
||||
const registry = new Map(buildDefaultAssetRegistry());
|
||||
registry.set(
|
||||
"logo.svg",
|
||||
defineAsset("logo.svg", "image/svg+xml", "<svg/>")
|
||||
);
|
||||
|
||||
const html = renderAltairShell({
|
||||
options: parseAltairUiOptions({
|
||||
theme: "dark",
|
||||
initialEnvironments: {
|
||||
production: { url: "https://api.example.com/graphql" },
|
||||
},
|
||||
}),
|
||||
assetRegistry: registry,
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `RenderAltairShellInput` — `{ options: AltairUiOptions; assetRegistry: ReadonlyMap<string, BundledAsset> }`.
|
||||
- `renderAltairShell(input): string` — returns the full HTML document
|
||||
including the `<!DOCTYPE html>` declaration.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `</script>` escape inside the JSON config block uses the HTML5
|
||||
spec's recommended sequence `<\/script` so the JSON itself remains
|
||||
parseable while the closing-tag-injection vector is closed. All
|
||||
user-supplied strings flowing into HTML attributes pass through a
|
||||
four-character HTML escape — the structural goal is "no inline script
|
||||
ever executes", which the strict CSP then enforces at the browser.
|
||||
@@ -0,0 +1,67 @@
|
||||
# `instruments` — OTel counter for Altair UI requests
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`instruments` owns the package's single OpenTelemetry counter,
|
||||
`graphql.ui.requests_total`, labelled with the constant
|
||||
`ui="altair"`. The counter name is deliberately identical to the
|
||||
GraphiQL sibling's so dashboards summing across both UIs can drop the
|
||||
`ui` label and add the two series. The `MeterProvider` is injectable —
|
||||
operators can hand in their own provider for in-process testing or rely
|
||||
on the OTel global the rest of the host has registered at boot.
|
||||
|
||||
## Features
|
||||
|
||||
- Single counter — `graphql.ui.requests_total`.
|
||||
- Constant label preset: `{ ui: "altair" }`.
|
||||
- `recordRequest()` convenience captures the label internally so call
|
||||
sites never repeat the label literal (no drift risk).
|
||||
- Meter name pinned to `@suaveplan/api-graphql-ui-altair` for trace
|
||||
correlation.
|
||||
- Falls back to the OTel global `MeterProvider` when none is supplied.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createAltairInstruments } from "@suaveplan/api-graphql-ui-altair/instruments";
|
||||
|
||||
const inst = createAltairInstruments();
|
||||
inst.recordRequest();
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { MeterProvider } from "@opentelemetry/sdk-metrics";
|
||||
import { createAltairInstruments } from "@suaveplan/api-graphql-ui-altair/instruments";
|
||||
|
||||
const meterProvider = new MeterProvider({
|
||||
/* readers / exporters configured in the host */
|
||||
});
|
||||
|
||||
const inst = createAltairInstruments({ meterProvider });
|
||||
|
||||
// Inside the HTML response handler:
|
||||
inst.recordRequest(); // graphql.ui.requests_total{ui="altair"} += 1
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createAltairInstruments(config?: AltairInstrumentsConfig): AltairInstruments`.
|
||||
- `AltairInstruments` — `{ meter, requestsTotal, recordRequest }`,
|
||||
frozen.
|
||||
- `GRAPHQL_UI_REQUESTS_COUNTER` — literal `"graphql.ui.requests_total"`.
|
||||
- `ALTAIR_UI_LABEL` — literal `"altair"`.
|
||||
- `ALTAIR_METER_NAME` — literal `"@suaveplan/api-graphql-ui-altair"`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The counter description block documents the cross-package label contract
|
||||
so operators reading the OTel discovery endpoint understand why two
|
||||
packages emit the same metric name. The `Object.freeze` on the return
|
||||
record prevents accidental mutation downstream — the bundle is meant to
|
||||
be a singleton per host instance.
|
||||
@@ -0,0 +1,74 @@
|
||||
# `options` — `AltairUiOptions` schema + parser
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`options` owns the Zod-validated configuration surface every consumer of
|
||||
the Altair UI plugin passes through. The schema enforces structural
|
||||
invariants — every endpoint string must start with `/`, theme is one of
|
||||
three literal values, top-level keys are strict so typos surface at the
|
||||
boundary — while still defaulting every field so the most common call
|
||||
site (`createAltairUi()`) is zero-config. Centralising defaulting in one
|
||||
schema means the factory, the HTML renderer, and the asset map all
|
||||
consume the same shape.
|
||||
|
||||
## Features
|
||||
|
||||
- Defaults: `uiEndpoint=/graphql/ui`, `graphqlEndpoint=/graphql`,
|
||||
`subscriptionEndpoint=/graphql/ws`, `theme=system`, `initialQuery=""`,
|
||||
empty `initialHeaders`, empty `initialEnvironments`.
|
||||
- Strict mode at every nesting level — unknown keys are rejected.
|
||||
- Rich per-environment block: `url`, `subscriptionsEndpoint`, `headers`,
|
||||
`variables`.
|
||||
- `parseAltairUiOptions(undefined)` is valid and produces the default
|
||||
bundle.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { parseAltairUiOptions } from "@suaveplan/api-graphql-ui-altair/options";
|
||||
|
||||
const opts = parseAltairUiOptions();
|
||||
// opts.uiEndpoint === "/graphql/ui"
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { parseAltairUiOptions } from "@suaveplan/api-graphql-ui-altair/options";
|
||||
|
||||
const opts = parseAltairUiOptions({
|
||||
uiEndpoint: "/dev/ui",
|
||||
graphqlEndpoint: "/api/graphql",
|
||||
theme: "dark",
|
||||
initialEnvironments: {
|
||||
production: {
|
||||
url: "https://api.example.com/graphql",
|
||||
subscriptionsEndpoint: "wss://api.example.com/graphql",
|
||||
headers: { "x-tenant": "acme" },
|
||||
variables: { region: "us-east-1" },
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `altairUiOptionsSchema: z.ZodObject` — direct Zod handle (composable
|
||||
via `.extend()` or `.partial()` for downstream wrappers).
|
||||
- `parseAltairUiOptions(input?: AltairUiOptionsInput): AltairUiOptions`
|
||||
— throws `ZodError` on shape violations.
|
||||
- `AltairUiOptions` — output type (every field defined).
|
||||
- `AltairUiOptionsInput` — input type (every field optional).
|
||||
- `AltairEnvironment` — per-environment record shape.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The Zod default record literals (`{}`) feed back into `z.input` as
|
||||
optional, so the input type stays ergonomic at the caller boundary
|
||||
while the output type guarantees every field is populated downstream.
|
||||
The schema uses the Zod 4 default-import (`import z from "zod"`) per
|
||||
the repo-wide Bun-safe convention.
|
||||
Reference in New Issue
Block a user