Skip to content

Commit

Permalink
Response Cache plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Jul 27, 2022
1 parent c971794 commit fe50675
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 149 deletions.
9 changes: 9 additions & 0 deletions .changeset/chilly-rats-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@graphql-yoga/common': minor
---

New `setResult` helper is available in `onRequestParseDone` hook to set `ExecutionResult` before any GraphQL specific process.

You can check `@graphql-yoga/plugin-response-cache`'s implementation to see how it can be useful.

Also now `onResultProcess` and `useResultProcessor` hooks use generics to get more type-safety.
18 changes: 18 additions & 0 deletions .changeset/twenty-poets-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@graphql-yoga/plugin-response-cache': major
---

New Response Cache Plugin!!!

On top of [`@envelop/response-cache`](https://www.envelop.dev/plugins/use-response-cache), this new plugin allows you to skip execution phase even before all the GraphQL execution phases immediately after the GraphQL request parameters is parsed by Yoga.

Also it doesn't need to have `documentString` stored in somewhere in order to get it back during the execution to generate the cache key.

All the features of the same except for the following:

- `session` factory function takes `GraphQLParams` and `Request` objects instead of GraphQL context as arguments.

- `type SessionIdFactory = (params: GraphQLParams, request: Request) => Maybe<string>`

- `enabled` function takes `GraphQLParams` and `Request` objects instead of GraphQL context as arguments.
- `type EnabledFn = (params: GraphQLParams, request: Request) => boolean`
12 changes: 7 additions & 5 deletions packages/graphql-yoga/src/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export type OnRequestParseDoneHook = (
export interface OnRequestParseDoneEventPayload {
params: GraphQLParams
setParams: (params: GraphQLParams) => void
setResult: (result: ResultProcessorInput) => void
}

export type OnResultProcess = (
Expand All @@ -73,16 +74,17 @@ export type ResultProcessorInput = PromiseOrValue<
ExecutionResult | AsyncIterable<ExecutionResult | ExecutionPatchResult>
>

export type ResultProcessor = (
result: ResultProcessorInput,
fetchAPI: FetchAPI,
) => PromiseOrValue<Response>
export type ResultProcessor<
TResult extends ResultProcessorInput = ResultProcessorInput,
> = (result: TResult, fetchAPI: FetchAPI) => PromiseOrValue<Response>

export interface OnResultProcessEventPayload {
request: Request
result: ResultProcessorInput
resultProcessor?: ResultProcessor
setResultProcessor(resultProcessor: ResultProcessor): void
setResultProcessor<TResult extends ResultProcessorInput>(
resultProcessor: ResultProcessor<TResult>,
): void
}

export type OnResponseHook<TServerContext> = (
Expand Down
18 changes: 10 additions & 8 deletions packages/graphql-yoga/src/plugins/useResultProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { Plugin, ResultProcessor, ResultProcessorInput } from './types.js'

export interface ResultProcessorPluginOptions {
processResult: ResultProcessor
match?(request: Request, result: ResultProcessorInput): boolean
export interface ResultProcessorPluginOptions<
TResult extends ResultProcessorInput,
> {
processResult: ResultProcessor<TResult>
match?(request: Request, result: ResultProcessorInput): result is TResult
}

export function useResultProcessor(
options: ResultProcessorPluginOptions,
): Plugin {
const isMatch = options.match || (() => true)
export function useResultProcessor<
TResult extends ResultProcessorInput = ResultProcessorInput,
>(options: ResultProcessorPluginOptions<TResult>): Plugin {
const matchFn = options.match || (() => true)
return {
onResultProcess({ request, result, setResultProcessor }) {
if (isMatch(request, result)) {
if (matchFn(request, result)) {
setResultProcessor(options.processResult)
}
},
Expand Down
78 changes: 50 additions & 28 deletions packages/graphql-yoga/src/processRequest.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,57 @@
import { getOperationAST, ExecutionArgs } from 'graphql'
import { RequestProcessContext } from './types.js'
import { ResultProcessor } from './plugins/types.js'
import { FetchAPI, GraphQLParams } from './types.js'
import {
OnResultProcess,
ResultProcessor,
ResultProcessorInput,
} from './plugins/types.js'
import { GetEnvelopedFn } from '@envelop/core'

export async function processRequest<TContext>({
export async function processResult({
request,
params,
enveloped,
result,
fetchAPI,
onResultProcessHooks,
}: RequestProcessContext<TContext>): Promise<Response> {
}: {
request: Request
result: ResultProcessorInput
fetchAPI: FetchAPI
/**
* Response Hooks
*/
onResultProcessHooks: OnResultProcess[]
}) {
let resultProcessor: ResultProcessor<any> | undefined

for (const onResultProcessHook of onResultProcessHooks) {
await onResultProcessHook({
request,
result,
resultProcessor,
setResultProcessor(newResultProcessor) {
resultProcessor = newResultProcessor
},
})
}

// If no result processor found for this result, return an error
if (!resultProcessor) {
return new fetchAPI.Response(null, {
status: 406,
statusText: 'Not Acceptable',
})
}

return resultProcessor(result, fetchAPI)
}

export async function processRequest<TContext>({
params,
enveloped,
}: {
params: GraphQLParams
enveloped: ReturnType<GetEnvelopedFn<TContext>>
}): Promise<ResultProcessorInput> {
// Parse GraphQLParams
const document = enveloped.parse(params.query!)

Expand Down Expand Up @@ -38,26 +81,5 @@ export async function processRequest<TContext>({
// Get the result to be processed
const result = await executeFn(executionArgs)

let resultProcessor: ResultProcessor | undefined

for (const onResultProcessHook of onResultProcessHooks) {
await onResultProcessHook({
request,
result,
resultProcessor,
setResultProcessor(newResultProcessor) {
resultProcessor = newResultProcessor
},
})
}

// If no result processor found for this result, return an error
if (!resultProcessor) {
return new fetchAPI.Response(null, {
status: 406,
statusText: 'Not Acceptable',
})
}

return resultProcessor(result, fetchAPI)
return result
}
Loading

0 comments on commit fe50675

Please sign in to comment.