Skip to content

Commit 705a89a

Browse files
committed
this could actually work
1 parent de9d194 commit 705a89a

File tree

3 files changed

+187
-25
lines changed

3 files changed

+187
-25
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"@types/graphql": "0.13.3",
2727
"ava": "0.25.0",
2828
"graphql": "0.13.2",
29-
"graphql-middleware": "1.3.3",
29+
"graphql-middleware": "^1.4.2",
3030
"graphql-tools": "3.0.5",
3131
"prettier": "1.13.7",
3232
"prettier-check": "2.0.0",
@@ -38,7 +38,7 @@
3838
},
3939
"peerDependencies": {
4040
"graphql": "^0.13.2",
41-
"graphql-middleware": "^1.3.0"
41+
"graphql-middleware": "^1.4.2"
4242
},
4343
"release": {
4444
"branch": "master"

src/index.ts

Lines changed: 182 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
import { IMiddleware } from 'graphql-middleware'
1+
import {
2+
GraphQLResolveInfo,
3+
GraphQLArgument,
4+
GraphQLField,
5+
getNamedType,
6+
} from 'graphql'
27
import { GraphQLUpload } from 'apollo-upload-server'
3-
import { GraphQLResolveInfo, GraphQLArgument, GraphQLField } from 'graphql'
8+
import { IMiddlewareFunction } from 'graphql-middleware'
49

5-
// GraphQL
10+
// GraphQL -------------------------------------------------------------------
611

712
type Maybe<T> = T | null
813

14+
/**
15+
*
16+
* @param info
17+
*
18+
* Returns GraphQLField type of the current resolver.
19+
*
20+
*/
921
function getResolverField(
1022
info: GraphQLResolveInfo,
1123
): GraphQLField<any, any, { [key: string]: any }> {
@@ -15,12 +27,28 @@ function getResolverField(
1527
return typeFields[fieldName]
1628
}
1729

30+
/**
31+
*
32+
* @param field
33+
*
34+
* Returns arguments that certain field accepts.
35+
*
36+
*/
1837
function getFieldArguments<TSource, TContext, TArgs>(
1938
field: GraphQLField<TSource, TContext, TArgs>,
2039
): GraphQLArgument[] {
2140
return field.args
2241
}
2342

43+
/**
44+
*
45+
* @param f
46+
* @param xs
47+
*
48+
* Maps an array of functions and filters out the values
49+
* which converted to null.
50+
*
51+
*/
2452
function filterMap<T, U>(f: (x: T) => Maybe<U>, xs: T[]): U[] {
2553
return xs.reduce((acc, x) => {
2654
const res = f(x)
@@ -32,11 +60,30 @@ function filterMap<T, U>(f: (x: T) => Maybe<U>, xs: T[]): U[] {
3260
}, [])
3361
}
3462

63+
/**
64+
*
65+
* @param args
66+
* @param arg
67+
*
68+
* Finds the value of argument from provided argument values and
69+
* argument definition.
70+
*
71+
*/
3572
function getArgumentValue(args: { [key: string]: any }, arg: GraphQLArgument) {
3673
return args.get(arg.name)
3774
}
3875

39-
export function executeWithArgumentType<T>(
76+
/**
77+
*
78+
* @param f
79+
* @param info
80+
* @param args
81+
*
82+
* Executes a funcition on all arguments of a particular field
83+
* and filters out the results which returned null.
84+
*
85+
*/
86+
export function filterMapFieldArguments<T>(
4087
f: (definition: GraphQLArgument, arg: any) => Maybe<T>,
4188
info: GraphQLResolveInfo,
4289
args: { [key: string]: any },
@@ -49,7 +96,23 @@ export function executeWithArgumentType<T>(
4996
return filterMap(fWithArguments, fieldArguments)
5097
}
5198

52-
// Upload
99+
/**
100+
*
101+
* @param type
102+
* @param x
103+
*
104+
* Checks whether a certain non-nullable, list or regular type
105+
* is of predicted type.
106+
*
107+
*/
108+
export function isGraphQLArgumentType(
109+
type,
110+
argument: GraphQLArgument,
111+
): boolean {
112+
return getNamedType(argument.type) instanceof type
113+
}
114+
115+
// Upload --------------------------------------------------------------------
53116

54117
export interface IUpload {
55118
stream: string
@@ -58,34 +121,133 @@ export interface IUpload {
58121
encoding: string
59122
}
60123

61-
declare type IUploadHandler<T> = (upload: Promise<IUpload>) => Promise<T>
124+
interface IUploadArgument {
125+
argumentName: string
126+
upload: Promise<IUpload> | Promise<IUpload>[]
127+
}
128+
129+
interface IProcessedUploadArgument<T> {
130+
argumentName: string
131+
upload: T | T[]
132+
}
133+
134+
declare type IUploadHandler<T> = (upload: IUpload) => Promise<T>
62135

63136
interface IConfig<T> {
64137
uploadHandler: IUploadHandler<T>
65138
}
66139

67-
function isGraphQLUploadArgument(
68-
x: GraphQLArgument,
69-
): x is GraphQLArgument & { type: GraphQLUpload } {
70-
return x.type instanceof GraphQLUpload
140+
/**
141+
*
142+
* @param def
143+
* @param value
144+
*
145+
* Funciton used to identify GraphQLUpload arguments.
146+
*
147+
*/
148+
function uploadTypeIdentifier(
149+
def: GraphQLArgument,
150+
value: any,
151+
): IUploadArgument {
152+
if (isGraphQLArgumentType(GraphQLUpload, def)) {
153+
return {
154+
argumentName: def.name,
155+
upload: value,
156+
}
157+
} else {
158+
return null
159+
}
71160
}
72161

73-
// function processor<T>
162+
/**
163+
*
164+
* @param args
165+
* @param info
166+
*
167+
* Function used to extract GraphQLUpload argumetns from a field.
168+
*
169+
*/
170+
function extractUploadArguments(
171+
args: { [key: string]: any },
172+
info: GraphQLResolveInfo,
173+
): IUploadArgument[] {
174+
return filterMapFieldArguments(uploadTypeIdentifier, info, args)
175+
}
74176

75-
async function processor<T>(uploadFunction: IUploadHandler<T>) {
76-
return async (def, value) => {
77-
if (isGraphQLUploadArgument(def)) {
78-
return (value as Promise<IUpload>).then(uploadFunction)
177+
/**
178+
*
179+
* @param arr
180+
*
181+
* Converts an array of processed uploads to one object which can
182+
* be later used as arguments definition.
183+
*
184+
*/
185+
function normaliseArguments<T>(
186+
args: IProcessedUploadArgument<T>[],
187+
): { [key: string]: T } {
188+
return args.reduce((acc, val) => {
189+
return {
190+
...acc,
191+
[val.argumentName]: val.upload,
192+
}
193+
}, {})
194+
}
195+
196+
/**
197+
*
198+
* @param uploadHandler
199+
*
200+
* Function used to process file uploads.
201+
*
202+
*/
203+
function processor<T>(uploadHandler: IUploadHandler<T>) {
204+
return function({
205+
argumentName,
206+
upload,
207+
}: IUploadArgument): Maybe<Promise<IProcessedUploadArgument<T>>> {
208+
if (Array.isArray(upload)) {
209+
const uploads = upload.map(file => file.then(uploadHandler))
210+
211+
return Promise.all(uploads).then(res => ({
212+
argumentName: argumentName,
213+
upload: res,
214+
}))
215+
} else if (upload !== undefined) {
216+
return upload.then(uploadHandler).then(res => ({
217+
argumentName: argumentName,
218+
upload: res,
219+
}))
79220
} else {
80221
return null
81222
}
82223
}
83224
}
84225

85-
export function upload<T>({ uploadHandler }: IConfig<T>): IMiddleware {
86-
return (resolve, parent, args, ctx, info) => {
87-
const executed = executeWithArgumentType(uploadHandler, info)
88-
89-
return resolve()
226+
/**
227+
*
228+
* @param config
229+
*
230+
* Exposed upload function which handles file upload in resolvers.
231+
* Internally, it returns a middleware function which is later processed
232+
* by GraphQL Middleware.
233+
* The first step is to extract upload arguments using identifier
234+
* which can be found above.
235+
* Once we found all the GraphQLUpload arguments we check whether they
236+
* carry a value or not and return a Promise to resolve them.
237+
* Once Promises get processed we normalise outputs and merge them
238+
* with old arguments to replace the old values with the new ones.
239+
*
240+
*/
241+
export function upload<T>({ uploadHandler }: IConfig<T>): IMiddlewareFunction {
242+
return async (resolve, parent, args, ctx, info) => {
243+
const files = extractUploadArguments(args, info)
244+
const uploads = filterMap(processor(uploadHandler), files)
245+
246+
const uploaded = await Promise.all(uploads)
247+
const argsUploaded = normaliseArguments(uploaded)
248+
249+
const argsWithUploads = { ...args, ...argsUploaded }
250+
251+
return resolve(parent, argsWithUploads, ctx, info)
90252
}
91253
}

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,9 +1935,9 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
19351935
version "4.1.11"
19361936
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
19371937

1938-
graphql-middleware@1.3.3:
1939-
version "1.3.3"
1940-
resolved "https://registry.yarnpkg.com/graphql-middleware/-/graphql-middleware-1.3.3.tgz#c6898df07c8d58a2f49c6ba3b0a49ec946f3d300"
1938+
graphql-middleware@^1.4.2:
1939+
version "1.4.2"
1940+
resolved "https://registry.yarnpkg.com/graphql-middleware/-/graphql-middleware-1.4.2.tgz#db1738587c14e87317ce9d8ae5787750db3b4680"
19411941
dependencies:
19421942
graphql-tools "^3.0.2"
19431943

0 commit comments

Comments
 (0)