5.6 KiB
@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 likeapi-graphql-ui-graphiqlis 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-wsprotocol over theapi-wsengine.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/schemamakeExecutableSchema. Collisions raiseGRAPHQL.SCHEMA_COLLISION(PluginScope error code on@suaveplan/api-graphql's namespace). - ADDED: Subscriptions — bridges
graphql-wsover theapi-wsengine. Subscription resolvers returnAsyncIterabledriven by the event bus. - ADDED: Per-request
DataLoaderinstances scoped toRequestContext, 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_activegauge,graphql.schema_rebuilds_total. - ADDED:
./testingsubpath — re-export ofrunGraphqlEngineConformancefromapi-graphql/testingbound tocreateYogaGraphqlEngine.
Out of scope
- The abstract
api-graphqlcontract, schema-collision error code namespace, Registry / Resolver / LoaderFactory types — those live inapi-graphql(Stage 1). - The GraphiQL or Altair UI — separate packages
api-graphql-ui-graphiqlandapi-graphql-ui-altair. This adapter only proxies/graphql/uito 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— runsrunGraphqlEngineConformance("yoga", createYogaGraphqlEngine); pins identical invariants to those required ofapi-graphql-apollo.src/federation/federation.test.ts— two plugins eachextend type Query; assert the merged schema serves both fields; assert collision raisesGRAPHQL.SCHEMA_COLLISION.src/subscriptions/subscriptions.test.ts(integration, 30000ms) — subscribes viagraphql-wsoverapi-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.