Skip to content

Commit

Permalink
New Yoga-specific hooks for plugins: onRequestParse & onRequestParseD…
Browse files Browse the repository at this point in the history
…one (#1136)

* New Yoga-specific hooks for plugins: onRequestParse & onRequestParseDone

* Add server context to the hook params

* Small refactor

* Memoize headers

* ..

* No need to memoize things

* Try memoization

* Better

* Seperate packages

* Fix workspace

* Cleanup

* Type imports

* Update

* Maybe??

* ..

* ..
  • Loading branch information
ardatan committed May 4, 2022
1 parent 5a0f434 commit a7834d6
Show file tree
Hide file tree
Showing 12 changed files with 307 additions and 194 deletions.
5 changes: 5 additions & 0 deletions .changeset/neat-pumpkins-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-yoga/common': minor
---

New Yoga-specific hooks for plugins: onRequestParse & onRequestParseDone
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
},
"workspaces": [
"packages/*",
"packages/plugins/*",
"examples/**/*",
"benchmark/*",
"website",
Expand Down
82 changes: 0 additions & 82 deletions packages/common/src/getGraphQLParameters.ts

This file was deleted.

3 changes: 3 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ export * from './server'
export * from './graphiql'
export * from '@envelop/core'
export * from '@graphql-yoga/subscription'
export * from './plugins/types'
export * from './plugins/useRequestParser'
export { Plugin } from './plugins/types'
31 changes: 31 additions & 0 deletions packages/common/src/plugins/requestParser/GET.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { GraphQLParams } from '../../types'
import { Plugin } from '../types'

export function isGETRequest(request: Request) {
return request.method === 'GET'
}

export function parseGETRequest(request: Request): GraphQLParams {
const [, searchParamsStr] = request.url.split('?')
const searchParams = new URLSearchParams(searchParamsStr)
const operationName = searchParams.get('operationName') || undefined
const query = searchParams.get('query') || undefined
const variables = searchParams.get('variables') || undefined
const extensions = searchParams.get('extensions') || undefined
return {
operationName,
query,
variables: variables ? JSON.parse(variables) : undefined,
extensions: extensions ? JSON.parse(extensions) : undefined,
}
}

export function useGETRequestParser(): Plugin {
return {
onRequestParse: async ({ request, setRequestParser }) => {
if (isGETRequest(request)) {
setRequestParser(parseGETRequest)
}
},
}
}
26 changes: 26 additions & 0 deletions packages/common/src/plugins/requestParser/POST.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { GraphQLParams } from '../../types'
import { Plugin } from '../types'
import { useRequestParser } from '../useRequestParser'

export function isPOSTRequest(request: Request) {
return request.method === 'POST'
}

export async function parsePOSTRequest(
request: Request,
): Promise<GraphQLParams> {
const requestBody = await request.json()
return {
operationName: requestBody.operationName,
query: requestBody.query,
variables: requestBody.variables,
extensions: requestBody.extensions,
}
}

export function usePOSTRequestParser(): Plugin {
return useRequestParser({
match: isPOSTRequest,
parse: parsePOSTRequest,
})
}
42 changes: 42 additions & 0 deletions packages/common/src/plugins/requestParser/POSTMultipart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { dset } from 'dset'
import { GraphQLParams } from '../../types'
import { Plugin } from '../types'
import { useRequestParser } from '../useRequestParser'
import { isPOSTRequest } from './POST'

export function isPOSTMultipartRequest(request: Request): boolean {
return (
isPOSTRequest(request) &&
!!request.headers.get('content-type')?.startsWith('multipart/form-data')
)
}

export async function parsePOSTMultipartRequest(
request: Request,
): Promise<GraphQLParams> {
const requestBody = await request.formData()
const operationsStr = requestBody.get('operations')?.toString() || '{}'
const operations = JSON.parse(operationsStr)

const mapStr = requestBody.get('map')?.toString() || '{}'
const map = JSON.parse(mapStr)
for (const fileIndex in map) {
const file = requestBody.get(fileIndex)
const [path] = map[fileIndex]
dset(operations, path, file)
}

return {
operationName: operations.operationName,
query: operations.query,
variables: operations.variables,
extensions: operations.extensions,
}
}

export function usePOSTMultipartRequestParser(): Plugin {
return useRequestParser({
match: isPOSTMultipartRequest,
parse: parsePOSTMultipartRequest,
})
}
35 changes: 35 additions & 0 deletions packages/common/src/plugins/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Plugin as EnvelopPlugin, PromiseOrValue } from '@envelop/core'
import { GraphQLParams } from '../types'

export type Plugin<
PluginContext extends Record<string, any> = {},
TServerContext = {},
> = EnvelopPlugin<PluginContext> & {
onRequestParse?: OnRequestParseHook<TServerContext>
}

export type OnRequestParseHook<TServerContext> = (
payload: OnRequestParseEventPayload<TServerContext>,
) => PromiseOrValue<void | OnRequestParseHookResult>

export type RequestParser = (request: Request) => PromiseOrValue<GraphQLParams>

export interface OnRequestParseEventPayload<TServerContext> {
serverContext: TServerContext | undefined
request: Request
requestParser: RequestParser
setRequestParser: (parser: RequestParser) => void
}

export type OnRequestParseHookResult = {
onRequestParseDone?: OnRequestParseDoneHook
}

export type OnRequestParseDoneHook = (
payload: OnRequestParseDoneEventPayload,
) => PromiseOrValue<void>

export interface OnRequestParseDoneEventPayload {
params: GraphQLParams
setParams: (params: GraphQLParams) => void
}
23 changes: 23 additions & 0 deletions packages/common/src/plugins/useRequestParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Plugin } from './types'
import { PromiseOrValue } from '@envelop/core'
import { GraphQLParams } from '../types'

interface RequestParserPluginOptions {
match?(request: Request): boolean
parse(request: Request): PromiseOrValue<GraphQLParams>
}

const DEFAULT_MATCHER = () => true

export function useRequestParser(options: RequestParserPluginOptions): Plugin {
const matchFn = options.match || DEFAULT_MATCHER
return {
onRequestParse({ request, setRequestParser }) {
if (matchFn(request)) {
setRequestParser(function useRequestParserFn(request: Request) {
return options.parse(request)
})
}
},
}
}
Loading

0 comments on commit a7834d6

Please sign in to comment.