Queries, mutations, typed arguments, and per-operation context in resolver classes.
A resolver is a class decorated with @GqlResolver , pointing at the object type it serves. Queries and mutations are just methods — injection, validation, and error handling all work exactly as they do in HTTP and RPC services.
Queries and mutations
import { Injectable } from '@system-inc/base-foundation/dependency-injection/decorators/Injectable' ;
import { HttpErrors } from '@system-inc/base-foundation/error/HttpErrors' ;
import { GqlArgument } from '@system-inc/base-foundation/graphql/decorators/GqlArgument' ;
import { GqlMutation } from '@system-inc/base-foundation/graphql/decorators/GqlMutation' ;
import { GqlQuery } from '@system-inc/base-foundation/graphql/decorators/GqlQuery' ;
import { GqlResolver } from '@system-inc/base-foundation/graphql/decorators/GqlResolver' ;
import { CreateNoteInput } from '../types/CreateNoteInput' ;
import { Note } from '../types/Note' ;
@Injectable ()
@GqlResolver (() => Note )
export class NoteResolver {
@GqlQuery (() => Note )
async note (@GqlArgument ('id' , () => String ) id : string ): Promise <Note > {
const note = await this .findNote (id);
if (!note) {
throw HttpErrors .notFound ({ message : 'Note not found.' });
}
return note;
}
@GqlMutation (() => Note )
async createNote (
@GqlArgument ('input' , () => CreateNoteInput ) input : CreateNoteInput ,
): Promise <Note > { ... }
}
@GqlQuery / @GqlMutation take the return type as a thunk, like every type reference.
@GqlArgument(name, () => Type) declares a named schema argument. Class-typed arguments are deserialized and validated before your method runs — invalid input becomes a GraphQL error, not a resolver crash.
Thrown HttpErrors surface as structured GraphQL errors.
Register the resolver in services like everything else.
Request context in resolvers
The same RequestContext flows through GraphQL:
import { InjectRequestContext } from '@system-inc/base-foundation/request/decorators/RequestContextDecorator' ;
import { RequestContext } from '@system-inc/base-foundation/request/RequestContext' ;
@GqlQuery (() => Viewer )
async viewer (
@InjectRequestContext () context : RequestContext ,
): Promise <Viewer > {
return { requestId : context.requestId };
}
Middleware-set context keys, cookies, headers — all identical to HTTP. One request pipeline, three dispatchers.
Per-operation context
For GraphQL-specific introspection — which operation is running, and which fields the client actually selected — inject the operation context:
import {
GqlOperationContext ,
} from '@system-inc/base-foundation/graphql/GqlOperationContext' ;
import { InjectGqlOperationContext } from '@system-inc/base-foundation/graphql/decorators/GqlOperationContext' ;
@GqlQuery (() => NoteStats )
async noteStats (
@InjectGqlOperationContext ()
operation : GqlOperationContext <NoteStats >,
): Promise <NoteStats > {
const selected = Object .keys (operation.selectionSet );
...
}
operation.type, operation.name, and the typed selectionSet let a resolver skip expensive work for fields nobody requested.