Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 71 additions & 3 deletions src/core/handlers/GraphQLHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { DocumentNode, GraphQLError, OperationTypeNode } from 'graphql'
import {
AsyncResponseResolverReturnType,
DefaultBodyType,
MaybeAsyncResponseResolverReturnType,
RequestHandler,
RequestHandlerDefaultInfo,
RequestHandlerOptions,
Expand All @@ -19,7 +21,11 @@
} from '../utils/internal/parseGraphQLRequest'
import { toPublicUrl } from '../utils/request/toPublicUrl'
import { devUtils } from '../utils/internal/devUtils'
import { getAllRequestCookies } from '../utils/request/getRequestCookies'
import {
getAllAcceptedMimeTypes,
getAllRequestCookies,
} from '../utils/request/getRequestCookies'
import { isIterable } from '../utils/internal/isIterable'

export type ExpectedOperationTypeNode = OperationTypeNode | 'all'
export type GraphQLHandlerNameSelector = DocumentNode | RegExp | string
Expand All @@ -32,6 +38,12 @@
operationName: GraphQLHandlerNameSelector
}

export type GraphQLMimeType =
| 'application/graphql-response+json'
| 'application/json'
// eslint-disable-next-line @typescript-eslint/ban-types

Check failure on line 44 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / build (22)

Definition for rule '@typescript-eslint/ban-types' was not found

Check failure on line 44 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / build (20)

Definition for rule '@typescript-eslint/ban-types' was not found
| (string & {})

export type GraphQLRequestParsedResult = {
match: Match
cookies: Record<string, string>
Expand Down Expand Up @@ -86,6 +98,10 @@
return typeof value === 'object' && 'kind' in value && 'definitions' in value
}

export interface GraphQLHandlerOptions extends RequestHandlerOptions {
supportedMimeTypes?: GraphQLMimeType[]
}

export class GraphQLHandler extends RequestHandler<
GraphQLHandlerInfo,
GraphQLRequestParsedResult,
Expand All @@ -103,7 +119,7 @@
operationName: GraphQLHandlerNameSelector,
endpoint: Path,
resolver: ResponseResolver<GraphQLResolverExtras<any>, any, any>,
options?: RequestHandlerOptions,
options?: GraphQLHandlerOptions,
) {
let resolvedOperationName = operationName

Expand All @@ -130,13 +146,65 @@
? `${operationType} (origin: ${endpoint.toString()})`
: `${operationType} ${resolvedOperationName} (origin: ${endpoint.toString()})`

const supportedMimeTypes = options?.supportedMimeTypes || [
'application/graphql-response+json',
'application/json',
]

const wrappedResolver: typeof resolver = (info) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure there's a more elegant way to do this that I'm just not aware of....

function handleSingleResult(
singleResult: MaybeAsyncResponseResolverReturnType<any>,
): MaybeAsyncResponseResolverReturnType<any> {
if (!singleResult) return
if ('then' in singleResult) {
return singleResult.then(handleSingleResult)
}
if (!singleResult) return singleResult
const acceptedMimeTypes = getAllAcceptedMimeTypes(
info.request,
'application/json',
)
const mimeType = acceptedMimeTypes.find((type) =>
supportedMimeTypes.includes(type),
)
if (!mimeType) return singleResult
const clone = singleResult.clone()
clone.headers.set('Content-Type', mimeType)
return clone
}
function* handleGeneratorResult(
generatorResult: Extract<
AsyncResponseResolverReturnType<any>,
Generator
>,
): Extract<AsyncResponseResolverReturnType<any>, Generator> {
for (const result of generatorResult) {

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / exports

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.3)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.1)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.0)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.5)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.4)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.6)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.2)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.7)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.

Check failure on line 181 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.8)

Type 'Generator<any, any, unknown>' is not assignable to type 'never'.
yield handleSingleResult(result)

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / exports

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.3)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.1)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.0)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.5)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.4)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.6)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.2)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.7)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.

Check failure on line 182 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.8)

Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.
}
}
function handleResult(
originalResult: AsyncResponseResolverReturnType<any>,
): AsyncResponseResolverReturnType<any> {
if (!originalResult) return originalResult
if ('then' in originalResult) {
return originalResult.then(handleResult)
}
if (isIterable(originalResult)) {
return handleGeneratorResult(originalResult)
}

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / exports

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.3)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.1)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.0)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.5)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.4)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.6)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.2)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.7)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.

Check failure on line 194 in src/core/handlers/GraphQLHandler.ts

View workflow job for this annotation

GitHub Actions / typescript (5.8)

Argument of type 'Iterable<MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<any>, MaybeAsyncResponseResolverReturnType<...>> | AsyncIterable<...>' is not assignable to parameter of type 'never'.
return handleSingleResult(originalResult)
}

return handleResult(resolver(info))
}

super({
info: {
header,
operationType,
operationName: resolvedOperationName,
},
resolver,
resolver: wrappedResolver,
options,
})

Expand Down
34 changes: 34 additions & 0 deletions src/core/utils/request/getRequestCookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,40 @@ export function getRequestCookies(request: Request): Record<string, string> {
}
}

/**
* Returns all accepted mime types, ordered by priority
* So
* `"application/graphql-response+json, application/json;q=0.9"`
* would become
* ["application/graphql-response+json", "application/json"]
* and
* `"application/graphql-response+json, application/json;q=1.1"`
* would become
* ["application/json", "application/graphql-response+json"]
*
* Currently only takes into account quality weight, not other priority
* heuristics as described in [RFC7231 Sec 5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2)
*/
export function getAllAcceptedMimeTypes(
request: Request,
fallback: string,
): string[] {
const accepted: Array<{ type: string; weight: number }> = []
for (const part of (request.headers.get('accept') || '').split(',')) {
const [type, ...params] = part.split(';').map((v) => v.trim())
const entry: (typeof accepted)[number] = { type, weight: 1 }
for (const param of params) {
const [key, value] = param.split('=').map((v) => v.trim())
if (key === 'weight') {
entry.weight = Number(value)
}
}
accepted.push(entry)
}
if (!accepted.length) return [fallback]
return accepted.sort((a, b) => a.weight - b.weight).map((entry) => entry.type)
}

export function getAllRequestCookies(request: Request): Record<string, string> {
const requestCookiesString = request.headers.get('cookie')
const cookiesFromHeaders = requestCookiesString
Expand Down
Loading