From 2f20d0d11f0798721c0160357bc418e28bd71f1f Mon Sep 17 00:00:00 2001 From: Jay Herron Date: Sun, 8 Feb 2026 21:51:03 -0700 Subject: [PATCH 1/3] feat!: Centralizes computeContext inputs into one struct This allows non-breaking expansion of supported inputs in the future. --- Sources/GraphQLVapor/GraphQLHandler.swift | 2 +- Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift | 10 ++++++++-- Sources/GraphQLVapor/RoutesBuilder+graphql.swift | 6 +++++- .../WebSocket/GraphQLHandler+handleWebSocket.swift | 5 ++++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Sources/GraphQLVapor/GraphQLHandler.swift b/Sources/GraphQLVapor/GraphQLHandler.swift index 3655ed7..16b28d6 100644 --- a/Sources/GraphQLVapor/GraphQLHandler.swift +++ b/Sources/GraphQLVapor/GraphQLHandler.swift @@ -8,5 +8,5 @@ struct GraphQLHandler< let schema: GraphQLSchema let rootValue: any Sendable let config: GraphQLConfig - let computeContext: @Sendable (Request) async throws -> Context + let computeContext: @Sendable (GraphQLContextComputationInputs) async throws -> Context } diff --git a/Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift b/Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift index 0b3dc53..acae18c 100644 --- a/Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift +++ b/Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift @@ -15,7 +15,10 @@ extension GraphQLHandler { guard operationType != .mutation else { throw Abort(.methodNotAllowed, reason: "Mutations using GET are disallowed") } - let context = try await computeContext(request) + let graphQLContextComputationInputs = GraphQLContextComputationInputs( + vaporRequest: request + ) + let context = try await computeContext(graphQLContextComputationInputs) let result = await execute( graphQLRequest: graphQLRequest, context: context, @@ -30,7 +33,10 @@ extension GraphQLHandler { throw Abort(.unsupportedMediaType, reason: "Missing `Content-Type` header") } let graphQLRequest = try request.content.decode(GraphQLRequest.self) - let context = try await computeContext(request) + let graphQLContextComputationInputs = GraphQLContextComputationInputs( + vaporRequest: request + ) + let context = try await computeContext(graphQLContextComputationInputs) let result = await execute( graphQLRequest: graphQLRequest, context: context, diff --git a/Sources/GraphQLVapor/RoutesBuilder+graphql.swift b/Sources/GraphQLVapor/RoutesBuilder+graphql.swift index 3033a48..aea124a 100644 --- a/Sources/GraphQLVapor/RoutesBuilder+graphql.swift +++ b/Sources/GraphQLVapor/RoutesBuilder+graphql.swift @@ -26,7 +26,7 @@ public extension RoutesBuilder { schema: GraphQLSchema, rootValue: any Sendable = (), config: GraphQLConfig = GraphQLConfig(), - computeContext: @Sendable @escaping (Request) async throws -> Context + computeContext: @Sendable @escaping (GraphQLContextComputationInputs) async throws -> Context ) { ContentConfiguration.global.use(encoder: GraphQLJSONEncoder(), for: .jsonGraphQL) ContentConfiguration.global.use(decoder: JSONDecoder(), for: .jsonGraphQL) @@ -67,3 +67,7 @@ public extension RoutesBuilder { } } } + +public struct GraphQLContextComputationInputs: Sendable { + public let vaporRequest: Request +} diff --git a/Sources/GraphQLVapor/WebSocket/GraphQLHandler+handleWebSocket.swift b/Sources/GraphQLVapor/WebSocket/GraphQLHandler+handleWebSocket.swift index 20a4499..6a52ed0 100644 --- a/Sources/GraphQLVapor/WebSocket/GraphQLHandler+handleWebSocket.swift +++ b/Sources/GraphQLVapor/WebSocket/GraphQLHandler+handleWebSocket.swift @@ -9,7 +9,10 @@ extension GraphQLHandler { request: Request ) async throws -> Response { let subProtocol = try negotiateSubProtocol(request: request) - let context = try await computeContext(request) + let graphQLContextComputationInputs = GraphQLContextComputationInputs( + vaporRequest: request + ) + let context = try await computeContext(graphQLContextComputationInputs) let response = Response(status: .switchingProtocols) response.upgrader = WebSocketUpgrader( maxFrameSize: .default, From 9777be5bab4987765eb2a5c011cba467bf3a2e20 Mon Sep 17 00:00:00 2001 From: Jay Herron Date: Sun, 8 Feb 2026 21:54:00 -0700 Subject: [PATCH 2/3] feat: Adds GraphQLRequest to GraphQLContextComputationInputs fields --- .../HTTP/GraphQLHandler+HTTP.swift | 6 ++-- .../GraphQLVapor/RoutesBuilder+graphql.swift | 1 + .../GraphQLHandler+handleWebSocket.swift | 32 ++++++++++++++----- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift b/Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift index acae18c..fa0262b 100644 --- a/Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift +++ b/Sources/GraphQLVapor/HTTP/GraphQLHandler+HTTP.swift @@ -16,7 +16,8 @@ extension GraphQLHandler { throw Abort(.methodNotAllowed, reason: "Mutations using GET are disallowed") } let graphQLContextComputationInputs = GraphQLContextComputationInputs( - vaporRequest: request + vaporRequest: request, + graphQLRequest: graphQLRequest ) let context = try await computeContext(graphQLContextComputationInputs) let result = await execute( @@ -34,7 +35,8 @@ extension GraphQLHandler { } let graphQLRequest = try request.content.decode(GraphQLRequest.self) let graphQLContextComputationInputs = GraphQLContextComputationInputs( - vaporRequest: request + vaporRequest: request, + graphQLRequest: graphQLRequest ) let context = try await computeContext(graphQLContextComputationInputs) let result = await execute( diff --git a/Sources/GraphQLVapor/RoutesBuilder+graphql.swift b/Sources/GraphQLVapor/RoutesBuilder+graphql.swift index aea124a..43dcd8d 100644 --- a/Sources/GraphQLVapor/RoutesBuilder+graphql.swift +++ b/Sources/GraphQLVapor/RoutesBuilder+graphql.swift @@ -70,4 +70,5 @@ public extension RoutesBuilder { public struct GraphQLContextComputationInputs: Sendable { public let vaporRequest: Request + public let graphQLRequest: GraphQLRequest } diff --git a/Sources/GraphQLVapor/WebSocket/GraphQLHandler+handleWebSocket.swift b/Sources/GraphQLVapor/WebSocket/GraphQLHandler+handleWebSocket.swift index 6a52ed0..583c56c 100644 --- a/Sources/GraphQLVapor/WebSocket/GraphQLHandler+handleWebSocket.swift +++ b/Sources/GraphQLVapor/WebSocket/GraphQLHandler+handleWebSocket.swift @@ -9,10 +9,6 @@ extension GraphQLHandler { request: Request ) async throws -> Response { let subProtocol = try negotiateSubProtocol(request: request) - let graphQLContextComputationInputs = GraphQLContextComputationInputs( - vaporRequest: request - ) - let context = try await computeContext(graphQLContextComputationInputs) let response = Response(status: .switchingProtocols) response.upgrader = WebSocketUpgrader( maxFrameSize: .default, @@ -29,7 +25,12 @@ extension GraphQLHandler { let server = GraphQLTransportWS.Server>( messenger: messenger, onExecute: { graphQLRequest in - try await graphql( + let graphQLContextComputationInputs = GraphQLContextComputationInputs( + vaporRequest: request, + graphQLRequest: graphQLRequest + ) + let context = try await computeContext(graphQLContextComputationInputs) + return try await graphql( schema: schema, request: graphQLRequest.query, rootValue: rootValue, @@ -39,7 +40,12 @@ extension GraphQLHandler { ) }, onSubscribe: { graphQLRequest in - try await graphqlSubscribe( + let graphQLContextComputationInputs = GraphQLContextComputationInputs( + vaporRequest: request, + graphQLRequest: graphQLRequest + ) + let context = try await computeContext(graphQLContextComputationInputs) + return try await graphqlSubscribe( schema: schema, request: graphQLRequest.query, rootValue: rootValue, @@ -55,7 +61,12 @@ extension GraphQLHandler { let server = GraphQLWS.Server>( messenger: messenger, onExecute: { graphQLRequest in - try await graphql( + let graphQLContextComputationInputs = GraphQLContextComputationInputs( + vaporRequest: request, + graphQLRequest: graphQLRequest + ) + let context = try await computeContext(graphQLContextComputationInputs) + return try await graphql( schema: schema, request: graphQLRequest.query, rootValue: rootValue, @@ -65,7 +76,12 @@ extension GraphQLHandler { ) }, onSubscribe: { graphQLRequest in - try await graphqlSubscribe( + let graphQLContextComputationInputs = GraphQLContextComputationInputs( + vaporRequest: request, + graphQLRequest: graphQLRequest + ) + let context = try await computeContext(graphQLContextComputationInputs) + return try await graphqlSubscribe( schema: schema, request: graphQLRequest.query, rootValue: rootValue, From ec789d5968149f3678c0d01dde6b5ed2b70332e7 Mon Sep 17 00:00:00 2001 From: Jay Herron Date: Sun, 8 Feb 2026 22:02:42 -0700 Subject: [PATCH 3/3] docs: Adds context computation documentation --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index ffdf3da..3b4fb18 100644 --- a/README.md +++ b/README.md @@ -87,3 +87,18 @@ Response: ``` See the `RouteBuilder.graphql` function documentation for advanced configuration options. + +### Computing GraphQL Context + +The required closure in the `graphql` function is used to compute the `GraphQLContext` object, which is injected into each GraphQL resolver. The `inputs` argument passes in data from the request so that the Context can be created dynamically: + +```swift +app.graphql(schema: schema) { inputs in + return GraphQLContext( + userID: inputs.vaporRequest.auth.userID, + logger: inputs.vaporRequest.logger, + debug: inputs.vaporRequest.headers[.init("debug")!] != nil, + operationName: inputs.graphQLRequest.operationName + ) +} +```