3.8 KiB
yoga-engine.ts
Purpose
Implements the abstract GraphqlEngine contract from
@suaveplan/api-graphql over the Yoga / graphql-js stack. The factory
createYogaGraphqlEngine(config) returns an engine that hosts can wire
into api-host's adapter slot — it is the default GraphQL engine per
design doc §10.5.
The engine handles the full pipeline for every inbound request: persisted-
query resolution, document parse, introspection guard, depth+complexity
guards, validate, execute (with wall-clock timeout), and OTel publish.
buildSchema federates plugin contributions via
@graphql-tools/schema's makeExecutableSchema, increments
graphql.schema_rebuilds_total, and caches the executable schema.
executeSubscription parses + validates and returns the GraphQL
subscription's AsyncIterable, tracking the live count via
activeSubscriptions() so the observable gauge can sample it.
Features
- Schema federation across multiple plugin registries, with
GRAPHQL.SCHEMA_COLLISIONraised at the abstract layer. - 10
GRAPHQL_YOGA.*codes wired into the production paths. - Masked-errors and introspection-toggle behaviour controllable per host.
- Per-request OTel counter + histogram emission with masked / unmasked
outcomelabels. - Persisted-query lookup via injectable
Map<string, string>so the host can hot-update the registry without rebuilding the engine. - Observable
subscriptions_activegauge that reflects the live count ofexecuteSubscriptioniterators.
Usage: Basic
import { createGraphqlRegistry } from "@suaveplan/api-graphql";
import { createYogaGraphqlEngine } from "@suaveplan/api-graphql-yoga";
const engine = createYogaGraphqlEngine();
const registry = createGraphqlRegistry();
registry.addTypes("type Query { ping: String! }");
registry.addResolvers({ Query: { ping: () => "pong" } });
engine.buildSchema(registry);
const result = await engine.executeQuery(
{ query: "{ ping }" },
{} as never
);
// result.data.ping === "pong"
await engine.dispose();
Usage: Advanced
import {
createYogaGraphqlEngine,
federatePlugins,
} from "@suaveplan/api-graphql-yoga";
import { createGraphqlRegistry } from "@suaveplan/api-graphql";
import { createInMemoryWsBroker } from "@suaveplan/api-ws-broker-in-memory";
const broker = createInMemoryWsBroker();
const billing = createGraphqlRegistry();
billing.addTypes("extend type Query { invoices: [String!]! }");
billing.addResolvers({ Query: { invoices: () => ["i_1"] } });
const engine = createYogaGraphqlEngine({
options: { maxQueryDepth: 8, maxQueryComplexity: 250 },
entries: [{ plugin: "billing", registry: billing as never }],
wsBroker: broker,
persistedQueries: new Map([["pq_1", "{ invoices }"]]),
});
const inline = createGraphqlRegistry();
inline.addTypes("type Query { ping: String! }");
inline.addResolvers({ Query: { ping: () => "pong" } });
engine.buildSchema(inline);
API Reference
YogaEngineConfig—{ options?, entries?, wsBroker?, persistedQueries? }.YogaGraphqlEngine<Caps>— engine handle extendingGraphqlEngine<Caps>.createYogaGraphqlEngine<Caps>(config?)— factory.
Implementation Notes
buildSchema accepts a runtime-supplied registry alongside the pre-
registered config.entries; the runtime registry is appended as a final
__inline__ plugin so callers that don't manage entries up-front (e.g.
the conformance harness) still drive the engine through the same path.
The introspection guard runs against the raw query text rather than the
parsed AST so that pre-parse, pre-guard rejection paths still produce a
deterministic GRAPHQL_YOGA.INTROSPECTION_DENIED instead of leaking
parse errors. The depth/complexity guard ALWAYS runs before validate()
so denial-of-service queries cost a parse + a walk, not a parse +
validate + execute.