Created
June 30, 2019 14:04
-
-
Save richtera/ec539df29d129136f38f4365ae8a6895 to your computer and use it in GitHub Desktop.
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
/* eslint-disable @typescript-eslint/no-use-before-define */ | |
import { print } from 'graphql'; | |
import { filter, make, merge, mergeMap, pipe, share, takeUntil } from 'wonka'; | |
import { CombinedError } from 'urql'; | |
import 'isomorphic-fetch'; | |
/** A default exchange for fetching GraphQL requests. */ | |
export const fetchExchange = ({ forward }) => { | |
const isOperationFetchable = (operation) => { | |
const { operationName } = operation; | |
return operationName === 'query' || operationName === 'mutation'; | |
}; | |
return ops$ => { | |
const sharedOps$ = share(ops$); | |
const fetchResults$ = pipe( | |
sharedOps$, | |
filter(isOperationFetchable), | |
mergeMap(operation => { | |
const { key } = operation; | |
const teardown$ = pipe( | |
sharedOps$, | |
filter(op => op.operationName === 'teardown' && op.key === key) | |
); | |
return pipe( | |
createFetchSource(operation), | |
takeUntil(teardown$) | |
); | |
}) | |
); | |
const forward$ = pipe( | |
sharedOps$, | |
filter(op => !isOperationFetchable(op)), | |
forward | |
); | |
return merge([fetchResults$, forward$]); | |
}; | |
}; | |
const createFetchSource = (operation) => { | |
if (operation.operationName === 'subscription') { | |
throw new Error( | |
'Received a subscription operation in the httpExchange. You are probably trying to create a subscription. Have you added a subscriptionExchange?' | |
); | |
} | |
return make(([next, complete]) => { | |
const abortController = | |
typeof AbortController !== 'undefined' | |
? new AbortController() | |
: undefined; | |
const { context } = operation; | |
const extraOptions = | |
typeof context.fetchOptions === 'function' | |
? context.fetchOptions() | |
: context.fetchOptions || {}; | |
const extraPromise = typeof extraOptions.then === 'function' ? extraOptions : null; | |
const fetchOptions = { | |
body: JSON.stringify({ | |
query: print(operation.query), | |
variables: operation.variables, | |
}), | |
...extraOptions, | |
method: 'POST', | |
headers: { | |
'content-type': 'application/json', | |
...extraOptions.headers, | |
}, | |
signal: | |
abortController !== undefined ? abortController.signal : undefined, | |
extraPromise, | |
}; | |
executeFetch(operation, fetchOptions).then(result => { | |
if (result !== undefined) { | |
if (result !== undefined) { | |
next(result); | |
} | |
} | |
complete(); | |
}); | |
return () => { | |
if (abortController !== undefined) { | |
abortController.abort(); | |
} | |
}; | |
}); | |
}; | |
const executeFetch = (operation, opts) => { | |
let response; | |
const { url } = operation.context; | |
return (opts.extraPromise || Promise.resolve(null)) | |
.then((extra) => { | |
let { extraPromise, ...myOpts } = opts; | |
if (extra) { | |
const { headers, ...rest } = extra; | |
const { headers: optsHeaders, extraPromise, ...optsRest } = opts; | |
myOpts = { | |
...optsRest, | |
...rest, | |
headers: { | |
...optsHeaders, | |
...headers, | |
}, | |
}; | |
} | |
return fetch(url, myOpts); | |
}) | |
.then(res => { | |
response = res; | |
checkStatus(opts.redirect, response); | |
return response.json(); | |
}) | |
.then(result => ({ | |
operation, | |
data: result.data, | |
error: Array.isArray(result.errors) | |
? new CombinedError({ | |
graphQLErrors: result.errors, | |
response, | |
}) | |
: undefined, | |
})) | |
.catch(err => { | |
if (err.name === 'AbortError') { | |
return undefined; | |
} | |
return { | |
operation, | |
data: undefined, | |
error: new CombinedError({ | |
networkError: err, | |
response, | |
}), | |
}; | |
}); | |
}; | |
const checkStatus = (redirectMode = 'follow', response) => { | |
const statusRangeEnd = redirectMode === 'manual' ? 400 : 300; | |
if (response.status < 200 || response.status > statusRangeEnd) { | |
throw new Error(response.statusText); | |
} | |
}; | |
export default fetchExchange; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment