forked from prisma-labs/yoga2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
William Luke
committed
Apr 23, 2019
1 parent
dc1c586
commit 89251fa
Showing
5 changed files
with
276 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import * as shield from "graphql-shield"; | ||
import { upload } from "./upload"; | ||
import { applyMiddleware } from "graphql-middleware"; | ||
|
||
const gqlMiddleware = { | ||
upload, | ||
shield, | ||
applyMiddleware | ||
} | ||
export default gqlMiddleware |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
// https://github.com/maticzav/graphql-middleware-apollo-upload-server/blob/master/src/index.ts | ||
import { | ||
GraphQLResolveInfo, | ||
GraphQLArgument, | ||
GraphQLField, | ||
getNamedType, | ||
GraphQLType, | ||
} from 'graphql' | ||
import { GraphQLUpload } from 'graphql-upload' | ||
import { IMiddlewareFunction } from 'graphql-middleware' | ||
|
||
// GraphQL ------------------------------------------------------------------- | ||
|
||
type Maybe<T> = T | null | ||
|
||
/** | ||
* | ||
* @param info | ||
* | ||
* Returns GraphQLField type of the current resolver. | ||
* | ||
*/ | ||
function getResolverField( | ||
info: GraphQLResolveInfo, | ||
): GraphQLField<any, any, { [key: string]: any }> { | ||
const { fieldName, parentType } = info | ||
const typeFields = parentType.getFields() | ||
|
||
return typeFields[fieldName] | ||
} | ||
|
||
/** | ||
* | ||
* @param field | ||
* | ||
* Returns arguments that certain field accepts. | ||
* | ||
*/ | ||
function getFieldArguments<TSource, TContext, TArgs>( | ||
field: GraphQLField<TSource, TContext, TArgs>, | ||
): GraphQLArgument[] { | ||
return field.args | ||
} | ||
|
||
/** | ||
* | ||
* @param f | ||
* @param xs | ||
* | ||
* Maps an array of functions and filters out the values | ||
* which converted to null. | ||
* | ||
*/ | ||
function filterMap<T, U>(f: (x: T) => Maybe<U>, xs: T[]): U[] { | ||
return xs.reduce((acc, x) => { | ||
const res = f(x) | ||
if (res !== null) { | ||
return [res, ...acc] | ||
} else { | ||
return acc | ||
} | ||
}, [] as any) | ||
} | ||
|
||
/** | ||
* | ||
* @param args | ||
* @param arg | ||
* | ||
* Finds the value of argument from provided argument values and | ||
* argument definition. | ||
* | ||
*/ | ||
function getArgumentValue(args: { [key: string]: any }, arg: GraphQLArgument) { | ||
return args[arg.name] | ||
} | ||
|
||
/** | ||
* | ||
* @param f | ||
* @param info | ||
* @param args | ||
* | ||
* Executes a funcition on all arguments of a particular field | ||
* and filters out the results which returned null. | ||
* | ||
*/ | ||
export function filterMapFieldArguments<T>( | ||
f: (definition: GraphQLArgument, arg: any) => Maybe<T>, | ||
info: GraphQLResolveInfo, | ||
args: { [key: string]: any }, | ||
): T[] { | ||
const field = getResolverField(info) | ||
const fieldArguments = getFieldArguments(field) | ||
|
||
const fWithArguments = (arg: any) => f(arg, getArgumentValue(args, arg)) | ||
|
||
return filterMap(fWithArguments, fieldArguments) | ||
} | ||
|
||
/** | ||
* | ||
* @param type | ||
* @param x | ||
* | ||
* Checks whether a certain non-nullable, list or regular type | ||
* is of predicted type. | ||
* | ||
*/ | ||
export function isGraphQLArgumentType( | ||
type: GraphQLType, | ||
argument: GraphQLArgument, | ||
): boolean { | ||
return getNamedType(argument.type).name === getNamedType(type).name | ||
} | ||
|
||
// Upload -------------------------------------------------------------------- | ||
|
||
export interface IUpload { | ||
stream: string | ||
filename: string | ||
mimetype: string | ||
encoding: string | ||
} | ||
|
||
interface IUploadArgument { | ||
argumentName: string | ||
upload: Promise<IUpload> | Promise<IUpload>[] | ||
} | ||
|
||
interface IProcessedUploadArgument<T> { | ||
argumentName: string | ||
upload: T | T[] | ||
} | ||
|
||
declare type IUploadHandler<T> = (upload: IUpload) => Promise<T> | ||
|
||
interface IConfig<T> { | ||
uploadHandler: IUploadHandler<T> | ||
} | ||
|
||
/** | ||
* | ||
* @param def | ||
* @param value | ||
* | ||
* Funciton used to identify GraphQLUpload arguments. | ||
* | ||
*/ | ||
export function uploadTypeIdentifier( | ||
def: GraphQLArgument, | ||
value: any, | ||
): IUploadArgument | null { | ||
if (isGraphQLArgumentType(GraphQLUpload, def)) { | ||
return { | ||
argumentName: def.name, | ||
upload: value, | ||
} | ||
} else { | ||
return null | ||
} | ||
} | ||
|
||
/** | ||
* | ||
* @param args | ||
* @param info | ||
* | ||
* Function used to extract GraphQLUpload argumetns from a field. | ||
* | ||
*/ | ||
function extractUploadArguments( | ||
args: { [key: string]: any }, | ||
info: GraphQLResolveInfo, | ||
): IUploadArgument[] { | ||
return filterMapFieldArguments(uploadTypeIdentifier, info, args) | ||
} | ||
|
||
/** | ||
* | ||
* @param arr | ||
* | ||
* Converts an array of processed uploads to one object which can | ||
* be later used as arguments definition. | ||
* | ||
*/ | ||
export function normaliseArguments<T>( | ||
args: IProcessedUploadArgument<T>[], | ||
): { [key: string]: T } { | ||
return args.reduce((acc, val) => { | ||
return { | ||
...acc, | ||
[val.argumentName]: val.upload, | ||
} | ||
}, {}) | ||
} | ||
|
||
/** | ||
* | ||
* @param uploadHandler | ||
* | ||
* Function used to process file uploads. | ||
* | ||
*/ | ||
export function processor<T>(uploadHandler: IUploadHandler<T>) { | ||
return function({ | ||
argumentName, | ||
upload, | ||
}: IUploadArgument): Maybe<Promise<IProcessedUploadArgument<T>>> | any { | ||
if (Array.isArray(upload)) { | ||
const uploads = upload.reduce((acc, file) => { | ||
if (file !== undefined && file !== null && file.then) { | ||
return [...acc, file.then(uploadHandler)] | ||
} else { | ||
return acc | ||
} | ||
}, [] as any) | ||
|
||
return Promise.all(uploads).then(res => ({ | ||
argumentName: argumentName, | ||
upload: res, | ||
})) | ||
} else if (upload !== undefined && upload !== null && upload.then) { | ||
return upload.then(uploadHandler).then(res => ({ | ||
argumentName: argumentName, | ||
upload: res, | ||
})) | ||
} else { | ||
return null | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* | ||
* @param config | ||
* | ||
* Exposed upload function which handles file upload in resolvers. | ||
* Internally, it returns a middleware function which is later processed | ||
* by GraphQL Middleware. | ||
* The first step is to extract upload arguments using identifier | ||
* which can be found above. | ||
* Once we found all the GraphQLUpload arguments we check whether they | ||
* carry a value or not and return a Promise to resolve them. | ||
* Once Promises get processed we normalise outputs and merge them | ||
* with old arguments to replace the old values with the new ones. | ||
* | ||
*/ | ||
export function upload<T>({ uploadHandler }: IConfig<T>): IMiddlewareFunction { | ||
return async (resolve, parent, args, ctx, info) => { | ||
const uploadArguments = extractUploadArguments(args, info) | ||
const uploads = filterMap(processor(uploadHandler), uploadArguments) | ||
|
||
const uploaded = await Promise.all(uploads) | ||
const argsUploaded = normaliseArguments(uploaded) | ||
|
||
const argsWithUploads = { ...args, ...argsUploaded } | ||
|
||
return resolve(parent, argsWithUploads, ctx, info) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters