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

7.3 KiB

Add @suaveplan/api-plugin-graphql

Package: @suaveplan/api-plugin-graphql Status: PROPOSED Type: new-package

Why

@suaveplan/api consumers need a typed, builder-driven way to declare GraphQL APIs without authoring raw SDL. The upstream @suaveplan/api-plugin-graphql (suaveplan main, v1.0.4) shipped a self-contained description + resolver-composition layer with SchemaBuilder, TypeBuilder, ResolverComposer, and the defineGraphQLPlugin plugin-definition factory. Genesis must reach functional parity with the upstream surface so existing consumers can swap in without code change while picking up enterprise hardening (typed errors via @suaveplan/error, Zod-validated boundaries, OTel instruments, real EventBus event channel via @suaveplan/events).

What changes

  • ADDED: defineGraphQLPlugin(options) — factory producing a GraphQLPluginDefinition (name + merged config + lifecycle hooks + getSchema() / getResolvers() accessors).
  • ADDED: SchemaBuilder — fluent builder for object / input / enum / interface / union types plus addQuery / addMutation / addSubscription / build.
  • ADDED: TypeBuilder — chainable field declarations consumed by SchemaBuilder.
  • ADDED: ResolverComposer — use / useForType / addResolver / addResolvers / hasResolver / build, composing global + per-type middleware around each resolver.
  • ADDED: requireRoles(...roles) — auth-check middleware. Throws GraphQLError(GRAPHQL.UNAUTHENTICATED) when context.user is absent and GraphQLError(GRAPHQL.INSUFFICIENT_ROLE) when no role intersects.
  • ADDED: validateArgs(validator) — argument-validation middleware. Throws GraphQLError(GRAPHQL.VALIDATION_FAILED) when the validator returns a non-null string.
  • ADDED: createQueryExecutedMiddleware(bus?) — wraps a resolver in start/end timing and emits a typed graphql:query:executed event onto the provided EventBus (or the package-default graphqlEvents bus).
  • ADDED: graphqlEvents — package-default EventBus<GraphQLEventMap> for graphql:query:executed listeners.
  • ADDED: GRAPHQL_CODES + GraphQLError — defineCodes("GRAPHQL", [...]) registry with 6 codes: UNAUTHENTICATED, INSUFFICIENT_ROLE, VALIDATION_FAILED, INVALID_INPUT, TYPE_NOT_FOUND, DUPLICATE_RESOLVER.
  • ADDED: Zod schemas at the public boundary — DefineGraphQLPluginOptionsSchema validates options passed to defineGraphQLPlugin.
  • ADDED: OTel instruments — graphql.resolvers_executed_total{outcome} counter and graphql.resolver_duration_ms histogram emitted from createQueryExecutedMiddleware.
  • ADDED: Type exports — SchemaDefinition, TypeDefinition, TypeKind, FieldDefinition, ScalarType, QueryContext, ResolverFn, ResolverInfo, ResolverMiddleware, ResolverMap, GraphQLPluginConfig, GraphQLPluginHooks, GraphQLQueryExecutedEvent, GraphQLEventMap, DefineGraphQLPluginOptions, GraphQLPluginDefinition.

Out of scope

  • Live GraphQL execution / HTTP mount. The upstream package is a description + composition layer and does not consume graphql-js or mount a Hono route; genesis preserves that contract. A future @suaveplan/api-plugin-graphql-executor package (or a follow-up change) will add graphql-js execution + a Hono route — that work is enumerated in enterprise/api-plugin-graphql.md §3 and is explicitly a separate enterprise upgrade, not migration parity.
  • SDL import/export, federation, persisted queries, subscriptions transport, schema-registry publishing — all enumerated as enterprise upgrades, none present in upstream.

Source mapping

This change derives from ../suaveplan/enterprise/api-plugin-graphql.md and the upstream sources under ../suaveplan/packages/server/api-plugin-graphql/src/.

Requirement Source lines / files Notes
defineGraphQLPlugin factory with default config enterprise/api-plugin-graphql.md L13, L22; src/plugin/plugin.ts L83-L116 verbatim — basePath=/graphql, enableIntrospection=true, enablePlayground=true, maxDepth=10
getSchema() / getResolvers() accessors src/plugin/plugin.ts L109-L114 verbatim
Lifecycle hooks (onInit / onReady / onClose) passthrough src/plugin/plugin.ts L99-L103 verbatim
SchemaBuilder add{Type,InputType,EnumType,InterfaceType,UnionType,Query,Mutation,Subscription} + hasType/getType/typeCount/queryCount/mutationCount/build src/schema-builder/schema-builder.ts L36-L221 verbatim
TypeBuilder.field + fieldCount + build src/schema-builder/type-builder.ts L26-L63 verbatim
ResolverComposer use / useForType / addResolver / addResolvers / hasResolver / typeCount / resolverCount / build src/resolver/resolver.ts L26-L149 verbatim
Middleware composition right-to-left src/resolver/resolver.ts L132-L148 verbatim
requireRoles / validateArgs semantics src/resolver/resolver.ts L155-L186 upgraded — replaces throw new Error(...) with GraphQLError (enterprise §3 "AuthN/AuthZ", enterprise/api-plugin-graphql.md L48)
createQueryExecutedMiddleware + graphql:query:executed event src/resolver/resolver.ts L208-L234 adapted — EventEmitter (upstream) → EventBus<GraphQLEventMap> (genesis events shape)
graphqlEvents shared bus src/resolver/resolver.ts L192 adapted — new EventEmitter() → new EventBus<GraphQLEventMap>()
GraphQLQueryExecutedEvent payload shape src/types/graphql.types.ts L160-L171 verbatim
All public type exports src/types/graphql.types.ts full + src/index.ts verbatim
defineGraphQLPlugin event shape: GraphQLEventMap["graphql:query:executed"] src/types/graphql.types.ts L176-L178 verbatim

Requirements with no source line

Requirement Justification
GRAPHQL_CODES registry + GraphQLError class Genesis-wide policy (CLAUDE.md rules + AGENTS.md §13): every package extends SuaveplanError via defineCodes. Upstream threw bare Error(...); flagged in enterprise/api-plugin-graphql.md L37, L48.
Zod validation at defineGraphQLPlugin boundary Genesis-wide policy (AGENTS.md §13 row 2). Upstream did not validate options.
OTel resolvers_executed_total + resolver_duration_ms Genesis-wide policy (AGENTS.md §13 row 5). createQueryExecutedMiddleware already emits the event; instruments are the OTel-side mirror.
100% Istanbul coverage threshold Genesis-wide policy.

Verification approach

Every requirement is verified by a concrete it(...) title:

  • src/plugin/plugin.test.ts — defineGraphQLPlugin defaults, overrides, hooks, accessors, Zod boundary.
  • src/schema-builder/schema-builder.test.ts — every builder method + counts.
  • src/type-builder/type-builder.test.ts — field with all options + defaults.
  • src/resolver/resolver.test.ts — ResolverComposer, middleware composition (global, per-type, combined), requireRoles, validateArgs, createQueryExecutedMiddleware event emission.
  • src/error-codes/error-codes.test.ts — every GRAPHQL_* code thrown.
  • src/api-plugin-graphql.spec-conformance.test.ts — type-level checks (expectTypeOf), every code thrown from a production path, OTel instruments emitted against a real MeterProvider.

Migration impact

None — additive only. Genesis has no prior @suaveplan/api-plugin-graphql package.