Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
export {
createGraphiqlInstruments,
type GraphiqlInstruments,
type GraphiqlInstrumentsConfig,
recordUiRequest,
UI_LABEL_VALUE,
} from "./instruments.js";

View File

@@ -0,0 +1,72 @@
# `instruments`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Owns the OpenTelemetry instrument bundle emitted by the GraphiQL UI
plugin. A single `Counter` (`graphql.ui.requests_total`) is constructed
at mount time and incremented on each successful HTML response. The
counter is always labelled `{ ui: "graphiql" }` so dashboards can sum
across sibling UI plugins (`api-graphql-ui-altair` etc.) without
collision.
Asset sub-requests (`<uiEndpoint>/assets/...`) do NOT increment this
counter — they are covered by the host router's standard request
metrics.
## Features
- Single `Counter` instrument — `graphql.ui.requests_total`.
- Optional `meterProvider` parameter — defaults to the OTel global
(which falls back to a no-op when no SDK is installed).
- Frozen instrument bundle — meter + counter created once at plugin
mount time.
- `recordUiRequest(instruments)` helper — applies the `{ ui: "graphiql" }`
label automatically so call sites never get it wrong.
## Usage: Basic
```ts
import {
createGraphiqlInstruments,
recordUiRequest,
} from "@suaveplan/api-graphql-ui-graphiql";
const instruments = createGraphiqlInstruments();
recordUiRequest(instruments);
```
## Usage: Advanced
```ts
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import { createGraphiqlInstruments } from "@suaveplan/api-graphql-ui-graphiql";
const meterProvider = new MeterProvider({ /* readers, views, ... */ });
const instruments = createGraphiqlInstruments({ meterProvider });
// instruments.meter / instruments.requestsTotal are now wired to the
// caller-controlled provider rather than the OTel global.
```
## API Reference
- `interface GraphiqlInstrumentsConfig { meterProvider?: MeterProvider }`.
- `interface GraphiqlInstruments { meter: Meter; requestsTotal: Counter }`
— frozen at construction.
- `createGraphiqlInstruments(config?)` — returns the frozen bundle.
- `recordUiRequest(instruments)` — increments the counter with the
canonical `{ ui: "graphiql" }` label.
- `UI_LABEL_VALUE: "graphiql"` — the literal used in every observation.
## Implementation Notes
`UI_LABEL_VALUE` is exported as a named constant so the sibling Altair
package can reference it via a `not-equal` predicate in shared tests
("the two UI plugins MUST emit distinct `ui` labels"). The counter name
is shared across UI plugins by design — operators get a single rollup of
"explorer requests" without per-UI configuration. The Stage-5
testing-kit's in-memory `MeterProvider` is used in tests; production
operators register their preferred SDK once at process boot.

View File

@@ -0,0 +1,95 @@
import type { Meter, MeterProvider } from "@opentelemetry/api";
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
createGraphiqlInstruments,
recordUiRequest,
UI_LABEL_VALUE,
} from "./instruments.js";
interface AddCall {
readonly value: number;
readonly attrs?: Readonly<Record<string, unknown>>;
}
interface FakeInstrument {
readonly name: string;
readonly addCalls: AddCall[];
}
interface CapturedMeter {
readonly name: string;
readonly instruments: FakeInstrument[];
}
function buildFakeMeterProvider(): {
provider: MeterProvider;
meters: CapturedMeter[];
} {
const meters: CapturedMeter[] = [];
const provider: MeterProvider = {
getMeter(name: string): Meter {
const captured: CapturedMeter = { name, instruments: [] };
meters.push(captured);
const meter: Meter = {
createCounter(n) {
const inst: FakeInstrument = { name: n, addCalls: [] };
captured.instruments.push(inst);
return {
add(v: number, a?: Readonly<Record<string, unknown>>) {
inst.addCalls.push({ value: v, attrs: a });
},
} as ReturnType<Meter["createCounter"]>;
},
} as unknown as Meter;
return meter;
},
} as MeterProvider;
return { provider, meters };
}
describe("createGraphiqlInstruments", () => {
it("creates the graphql.ui.requests_total counter on the package meter", {
timeout: 5000,
}, () => {
const { provider, meters } = buildFakeMeterProvider();
const instruments = createGraphiqlInstruments({
meterProvider: provider,
});
expect(meters[0]?.name).toBe("@suaveplan/api-graphql-ui-graphiql");
expect(meters[0]?.instruments.map((i) => i.name)).toEqual([
"graphql.ui.requests_total",
]);
expect(instruments.requestsTotal).toBeDefined();
expect(instruments.meter).toBeDefined();
});
it("falls back to OTel globals when no provider supplied", {
timeout: 5000,
}, () => {
const instruments = createGraphiqlInstruments();
expect(instruments.requestsTotal).toBeDefined();
});
it("UI HTML increments graphql.ui.requests_total", {
timeout: 5000,
}, () => {
const { provider, meters } = buildFakeMeterProvider();
const instruments = createGraphiqlInstruments({
meterProvider: provider,
});
recordUiRequest(instruments);
recordUiRequest(instruments);
const counter = meters[0]?.instruments.find(
(i) => i.name === "graphql.ui.requests_total"
);
expect(counter?.addCalls).toHaveLength(2);
expect(counter?.addCalls[0]?.value).toBe(1);
expect(counter?.addCalls[0]?.attrs).toEqual({ ui: UI_LABEL_VALUE });
});
it("UI_LABEL_VALUE is the literal 'graphiql' so dashboards can sum across UIs", {
timeout: 5000,
}, () => {
expect(UI_LABEL_VALUE).toBe("graphiql");
});
});

View File

@@ -0,0 +1,51 @@
import type { Counter, Meter, MeterProvider } from "@opentelemetry/api";
import { metrics } from "@opentelemetry/api";
/**
* Config consumed by {@link createGraphiqlInstruments}. The provider
* defaults to the OTel global, which falls back to a no-op
* implementation when no SDK is installed — so the plugin works in test
* envs without ceremony.
*/
export interface GraphiqlInstrumentsConfig {
readonly meterProvider?: MeterProvider;
}
/**
* Frozen bundle of every OTel instrument the GraphiQL UI plugin emits.
*
* - `graphql.ui.requests_total` — counter labelled `{ ui: "graphiql" }`
* incremented on each successful HTML response. Asset sub-requests do
* NOT increment this counter (router request metrics cover those).
*/
export interface GraphiqlInstruments {
readonly meter: Meter;
readonly requestsTotal: Counter;
}
const METER_NAME = "@suaveplan/api-graphql-ui-graphiql";
/** UI label attribute attached to every observation. */
export const UI_LABEL_VALUE = "graphiql";
/**
* Build the GraphiQL UI plugin's instrument registry. Returns a frozen
* bundle whose counter is constructed once at plugin-mount time.
*/
export function createGraphiqlInstruments(
config: GraphiqlInstrumentsConfig = {}
): GraphiqlInstruments {
const meterProvider = config.meterProvider ?? metrics.getMeterProvider();
const meter = meterProvider.getMeter(METER_NAME);
const requestsTotal = meter.createCounter("graphql.ui.requests_total");
return Object.freeze({ meter, requestsTotal });
}
/**
* Record a UI HTML request. Always labels the observation with
* `{ ui: "graphiql" }` so dashboards can sum across sibling UI plugins
* (e.g. `api-graphql-ui-altair`).
*/
export function recordUiRequest(instruments: GraphiqlInstruments): void {
instruments.requestsTotal.add(1, { ui: UI_LABEL_VALUE });
}