Files
suaveplan/openspec/archive/2026-05-26-api-graphql-apollo/proposal.md
2026-05-27 23:29:53 +01:00

64 lines
5.0 KiB
Markdown

# `@suaveplan/api-graphql-apollo` — Apollo Server GraphQL engine adapter
**Package**: `@suaveplan/api-graphql-apollo`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 7 (GraphQL + WebSocket / SSE engines) — see umbrella `openspec/changes/api-plugin-foundation/`
## Why
Per design doc §10.5 the foundation ships GraphQL Yoga as the default engine but offers Apollo Server as an alternative for shops standardised on Apollo's broader feature set — Apollo Studio integration, persisted-query manifest support, the @apollo/federation tooling, and a larger plugin ecosystem (including operation-cost analyzers and request-coalescing). Both engines MUST be host-config swappable, which is only meaningful if both implement the same `api-graphql` contract and pass the same conformance suite.
Without this adapter the foundation has only one GraphQL engine, undermining the §10.5 "engine choice" guarantee and forcing Apollo-standardised teams to fork the foundation.
## What changes
Net-new package depending on `@suaveplan/api-types`, `@suaveplan/api-graphql`, `@suaveplan/api-router`, `@suaveplan/api-ws`, `@suaveplan/error`, `@suaveplan/logger`, `@suaveplan/opentelemetry`, `@apollo/server` (peer), `graphql` (peer), `@apollo/server-plugin-landing-page-disabled` (peer), and `zod`.
- ADDED: `createApolloGraphqlEngine(opts)` — factory returning the engine implementing `@suaveplan/api-graphql`'s engine contract.
- ADDED: `ApolloGraphqlOptions` — Zod-validated config: `endpoint` (default `/graphql`), `sdlEndpoint` (default `/graphql/sdl`), `wsEndpoint` (default `/graphql/ws`), `uiEndpoint` (default `/graphql/ui`), `introspection` (default `false`), `persistedQueriesPath`, `studioReporting`, `maxQueryDepth`, `maxQueryComplexity`, `formatError`.
- ADDED: Endpoint mounts (identical to Yoga): `POST /graphql`, `GET /graphql/sdl`, `WS /graphql/ws`, `GET /graphql/ui` (proxied to wired UI plugin).
- ADDED: Apollo-specific features behind config flags:
- Persisted-query manifest (`persistedQueriesPath`).
- Apollo Studio usage reporting (`studioReporting`).
- Landing-page disabled by default (no GraphiQL fallback — same rule as Yoga: UI is opt-in via separate package).
- ADDED: Subscriptions — same `graphql-ws` over `api-ws` bridge as Yoga.
- ADDED: Per-request DataLoader scoping (identical to Yoga).
- ADDED: `GRAPHQL_APOLLO_*` error codes: `GRAPHQL_APOLLO_PARSE_FAILED`, `GRAPHQL_APOLLO_VALIDATION_FAILED`, `GRAPHQL_APOLLO_DEPTH_EXCEEDED`, `GRAPHQL_APOLLO_COMPLEXITY_EXCEEDED`, `GRAPHQL_APOLLO_SCHEMA_BUILD_FAILED`, `GRAPHQL_APOLLO_SUBSCRIPTION_TRANSPORT_UNAVAILABLE`, `GRAPHQL_APOLLO_TIMEOUT`, `GRAPHQL_APOLLO_INTROSPECTION_DENIED`, `GRAPHQL_APOLLO_PERSISTED_QUERY_NOT_FOUND`, `GRAPHQL_APOLLO_RESOLVER_THREW`.
- ADDED: OTel instruments: same surface as Yoga (`graphql.requests_total`, `graphql.request_duration_ms`, `graphql.resolver_duration_ms`, `graphql.subscriptions_active`, `graphql.schema_rebuilds_total`) — the metric names are deliberately engine-agnostic so dashboards survive an engine swap.
- ADDED: `./testing` subpath — re-export of `runGraphqlEngineConformance` bound to `createApolloGraphqlEngine`.
## Out of scope
- The abstract `api-graphql` contract.
- Apollo Federation (subgraph composition across services) — single-process schema federation across plugins is in scope; cross-service subgraph composition is a separate concern.
- The GraphiQL or Altair UI packages.
- Yoga-specific concerns.
## Source mapping
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
| Requirement | Design doc section |
|---|---|
| Alternative engine to Yoga | §10.5 |
| Same `api-graphql` contract | §10.5 |
| Same conformance suite passes | §10.5 ("Both implement the same `api-graphql` contract; switching is host-config only.") |
| Endpoint set identical to Yoga | §10.6 |
| Subscriptions over `api-ws` | §10.4 |
| Federation across plugins | §10.2 |
| Engine-agnostic OTel metric names | §12.3 (host emits for free) + §10.5 (swappable) |
## Verification approach
- `src/engine/apollo-engine.test.ts` — schema build, request execution, masked errors, depth/complexity guards.
- `src/engine/apollo-engine.spec-conformance.test.ts` — runs `runGraphqlEngineConformance("apollo", createApolloGraphqlEngine)`; MUST pass the exact same invariants as `api-graphql-yoga`.
- `src/federation/federation.test.ts` — same federation semantics; collision raises `GRAPHQL.SCHEMA_COLLISION`.
- `src/subscriptions/subscriptions.test.ts` (integration, 30000ms) — `graphql-ws` over `api-ws-broker-in-memory`.
- `src/persisted-queries/persisted-queries.test.ts` — Apollo-specific: persisted-query manifest lookup; unknown id raises `GRAPHQL_APOLLO_PERSISTED_QUERY_NOT_FOUND`.
- `tests/integration/engine-swap.test.ts` (30000ms) — boots the same plugin set under Yoga then under Apollo; asserts identical query results, identical SDL output, identical metric names (operator dashboards survive the swap).
## Migration impact
None — additive only.