79 lines
5.3 KiB
Markdown
79 lines
5.3 KiB
Markdown
# `@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)
|