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

5.3 KiB

@suaveplan/api-graphql — abstract GraphQL registry, resolver, loader, subscription contract

Package: @suaveplan/api-graphql Status: PROPOSED Type: new-package Stage: 1 (Contracts) — see umbrella openspec/changes/api-plugin-foundation/

Why

GraphQL is a first-class engine alongside REST (design §10). Plugins mount GraphQL types + resolvers; the host federates a single schema across all plugins. Concrete engines (api-graphql-yoga default, api-graphql-apollo alternative) plug behind a stable contract so swapping engines is host-config only (design §10.5). This package owns that contract: the Registry (addTypes / addResolvers / addLoader), the Resolver shape, the LoaderFactory for DataLoaders, the SubscriptionResolver for GraphQL subscriptions over the WebSocket engine, and the federation rules (schema collision → GRAPHQL.SCHEMA_COLLISION, optional graphql.namespace wrapper).

graphql is added as a new CapabilityId — plugins request it via requiredCapabilities (design §10.1, §5.2).

What changes

Net-new package. Pure contract — no concrete engine code. Some runtime: the federation merger (engine-agnostic SDL union with collision detection), Zod schemas for the subscription protocol, and the conformance harness.

  • ADDED: ./registry — abstract GraphqlRegistry: addTypes(sdl: string), addResolvers(map: ResolverMap), addLoader<K, V>(name: string, batcher: BatchFn<K, V>).
  • ADDED: ./resolver — Resolver<Parent, Args, Result, Caps> typed against RequestContext<Caps>; ResolverMap keyed by GraphQL type name.
  • ADDED: ./loader — LoaderFactory: per-request DataLoader instance creation; lifecycle wired to step 7 (capability scoping) of the pipeline.
  • ADDED: ./subscription — SubscriptionResolver returning AsyncIterable; protocol marker for graphql-ws (subscriptions ride api-ws per §10.4).
  • ADDED: ./federation — mergeSdl(contributions: readonly SdlContribution[]): MergedSchema; collision detector → GRAPHQL.SCHEMA_COLLISION with the offending plugin pair + type/field path; graphql.namespace wrapper application.
  • ADDED: ./directives — @auth(scope) + @rateLimit(rpm) directive contracts (host-installed; engine adapters implement them).
  • ADDED: ./engine — abstract GraphqlEngine lifecycle interface (buildSchema, executeQuery, executeSubscription, dispose); concrete impls live in Stage 7 (api-graphql-yoga, api-graphql-apollo).
  • ADDED: ./capability-id — adds "graphql" to the CapabilityId union via the type-level merge with @suaveplan/api-types.
  • ADDED: ./error-codes — frozen GRAPHQL_CODES: SCHEMA_COLLISION, RESOLVER_MISSING, LOADER_NOT_REGISTERED, DIRECTIVE_UNKNOWN, SUBSCRIPTION_TRANSPORT_UNAVAILABLE, NAMESPACE_REQUIRED.
  • ADDED: ./testing — runGraphqlEngineConformance(factory) reusable harness.

CapabilityId additions

This package contributes "graphql" to the CapabilityId union. Pinned in the spec; documented here so reviewers can compare against the union maintained by api-types.

Out of scope

  • Concrete engines (api-graphql-yoga, api-graphql-apollo) — Stage 7.
  • GraphiQL / Altair UIs — Stage 7 (api-graphql-ui-graphiql, api-graphql-ui-altair).
  • Persisted queries, Apollo Studio integration — engine-specific, Stage 7.

Source mapping

Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.

Requirement Design doc section
Registry.addTypes / addResolvers / addLoader shape §10.1
Federation (extend type Query union; cross-plugin refs) §10.2
Pipeline integration (@auth, @rateLimit directives) §10.3
Subscriptions ride api-ws §10.4
Engine choice (Yoga default, Apollo alternative) §10.5
Collision → GRAPHQL.SCHEMA_COLLISION §10.2
graphql.namespace wrapper §10.2

Requirements with no source line

Requirement Justification
runGraphqlEngineConformance harness Stage-5 conformance suite (§13.4) needs a contract-side signature.

Verification approach

  • src/registry/registry.test.ts — addTypes/addResolvers/addLoader are accumulator-style; multiple calls compose; deduplication of identical contributions.
  • src/federation/merge-sdl.test.ts — union of extend type Query from multiple plugins; collision detection.
  • src/federation/namespace.test.ts — graphql.namespace: "Billing" wraps a plugin's contributions under BillingQuery/BillingMutation.
  • src/subscription/subscription.spec-conformance.test.ts — SubscriptionResolver returns AsyncIterable<T> and ties to api-ws SubscriptionBus.
  • src/directives/directives.test.ts — @auth and @rateLimit directive descriptors carry the contract.
  • src/engine/engine.spec-conformance.test.ts — GraphqlEngine lifecycle interface present.
  • src/capability-id/capability-id.spec-conformance.test.ts — "graphql" is in the CapabilityId union after augmentation.
  • tests/dep-graph.test.ts — pins deps: @suaveplan/api-types, @suaveplan/error, @suaveplan/types, zod. No GraphQL engine deps.

Migration impact

None — additive only.

Impact

Affected dependents

  • (none) — [x] no action needed (reason: net-new package; concrete engines land in Stage 7)