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

64 lines
5.6 KiB
Markdown

# `@suaveplan/api-graphql-yoga` — GraphQL Yoga engine adapter (default engine)
**Package**: `@suaveplan/api-graphql-yoga`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 7 (GraphQL + WebSocket / SSE engines) — see umbrella `openspec/changes/api-plugin-foundation/`
## Why
GraphQL is a first-class engine alongside REST in the foundation (design doc §10). Plugins contribute types and resolvers via the abstract `api-graphql` Registry; one of the host's adapters federates a single merged schema across all plugins and dispatches requests. Per §10.5 GraphQL Yoga is the default engine — Bun-friendly, fastest for our usage, simpler operational story than Apollo Server. This package binds the abstract `api-graphql` contract to Yoga.
Without this adapter the abstract `api-graphql` contract has no runnable implementation, and the umbrella's Stage-7 acceptance ("`api-plugin-example` mounts a federated GraphQL schema across two plugins AND publishes a tenant-scoped WebSocket broadcast") can't be reached.
## What changes
Net-new package depending on `@suaveplan/api-types`, `@suaveplan/api-graphql` (abstract contract), `@suaveplan/api-router` (mounts HTTP endpoints), `@suaveplan/api-ws` (subscription transport), `@suaveplan/error`, `@suaveplan/logger`, `@suaveplan/opentelemetry`, `graphql` (peer), `graphql-yoga` (peer), and `zod`.
- ADDED: `createYogaGraphqlEngine(opts)` — factory returning the engine implementing `@suaveplan/api-graphql`'s engine contract.
- ADDED: `YogaGraphqlOptions` — Zod-validated config: `endpoint` (default `/graphql`), `sdlEndpoint` (default `/graphql/sdl`), `wsEndpoint` (default `/graphql/ws`), `uiEndpoint` (default `/graphql/ui`, only mounted if a UI plugin like `api-graphql-ui-graphiql` is wired), `maskedErrors`, `landingPage`, `maxQueryDepth`, `maxQueryComplexity`, `parserCacheSize`.
- ADDED: Endpoint mounts:
- `POST /graphql` — main execution endpoint.
- `GET /graphql/sdl` — merged SDL across all federated plugin contributions.
- `WS /graphql/ws` — `graphql-ws` protocol over the `api-ws` engine.
- `GET /graphql/ui` — proxied to the wired UI plugin (this package does NOT mount GraphiQL/Altair itself; those are separate packages).
- ADDED: Schema federation — wraps multiple plugin contributions into one merged schema via `@graphql-tools/schema` `makeExecutableSchema`. Collisions raise `GRAPHQL.SCHEMA_COLLISION` (PluginScope error code on `@suaveplan/api-graphql`'s namespace).
- ADDED: Subscriptions — bridges `graphql-ws` over the `api-ws` engine. Subscription resolvers return `AsyncIterable` driven by the event bus.
- ADDED: Per-request `DataLoader` instances scoped to `RequestContext`, populated at pipeline step 7 and disposed at step 11 (design doc §10.3).
- ADDED: `GRAPHQL_YOGA_*` error codes: `GRAPHQL_YOGA_PARSE_FAILED`, `GRAPHQL_YOGA_VALIDATION_FAILED`, `GRAPHQL_YOGA_DEPTH_EXCEEDED`, `GRAPHQL_YOGA_COMPLEXITY_EXCEEDED`, `GRAPHQL_YOGA_SCHEMA_BUILD_FAILED`, `GRAPHQL_YOGA_SUBSCRIPTION_TRANSPORT_UNAVAILABLE`, `GRAPHQL_YOGA_TIMEOUT`, `GRAPHQL_YOGA_INTROSPECTION_DENIED`, `GRAPHQL_YOGA_PERSISTED_QUERY_NOT_FOUND`, `GRAPHQL_YOGA_RESOLVER_THREW`.
- ADDED: OTel instruments: `graphql.requests_total { operation_type, operation_name, outcome }`, `graphql.request_duration_ms`, `graphql.resolver_duration_ms { type, field }`, `graphql.subscriptions_active` gauge, `graphql.schema_rebuilds_total`.
- ADDED: `./testing` subpath — re-export of `runGraphqlEngineConformance` from `api-graphql/testing` bound to `createYogaGraphqlEngine`.
## Out of scope
- The abstract `api-graphql` contract, schema-collision error code namespace, Registry / Resolver / LoaderFactory types — those live in `api-graphql` (Stage 1).
- The GraphiQL or Altair UI — separate packages `api-graphql-ui-graphiql` and `api-graphql-ui-altair`. This adapter only proxies `/graphql/ui` to whichever is wired.
- Cross-plugin federation transport (the merge itself happens on the host); this adapter just consumes the already-merged schema descriptor.
- Apollo-specific features (persisted-query manifests, Apollo Studio) — that's `api-graphql-apollo`.
## Source mapping
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
| Requirement | Design doc section |
|---|---|
| Default engine for GraphQL | §10.5 |
| Endpoint set (POST /graphql, GET /graphql/sdl, WS /graphql/ws, GET /graphql/ui) | §10.6 |
| Federation via `extend type Query { ... }` across plugins | §10.2 |
| Subscriptions over `api-ws` via `graphql-ws` | §10.4 |
| Per-request DataLoader scoping | §10.3 |
| Same conformance suite as `api-graphql-apollo` | §10.5 (last sentence) |
## Verification approach
- `src/engine/yoga-engine.test.ts` — schema build, request execution, error masking, depth/complexity guards.
- `src/engine/yoga-engine.spec-conformance.test.ts` — runs `runGraphqlEngineConformance("yoga", createYogaGraphqlEngine)`; pins identical invariants to those required of `api-graphql-apollo`.
- `src/federation/federation.test.ts` — two plugins each `extend type Query`; assert the merged schema serves both fields; assert collision raises `GRAPHQL.SCHEMA_COLLISION`.
- `src/subscriptions/subscriptions.test.ts` (integration, 30000ms) — subscribes via `graphql-ws` over `api-ws-broker-in-memory`; broadcasts a tenant-scoped event; asserts delivery.
- `src/loaders/loaders.test.ts` — per-request DataLoader instances; assert disposal after step 11.
- `tests/integration/end-to-end.test.ts` (60000ms) — full pipeline: REST request → GraphQL request → subscription, all over Yoga, against an in-memory router + WS engine.
## Migration impact
None — additive only.