-
-
Notifications
You must be signed in to change notification settings - Fork 443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support file uploads #14
Comments
That would be dope! Also, what would your ideal middleware setup look like? |
I suggest per-request config, overridable by the user: import { Client } from 'urql'
import getViewerToken from './helpers'
const client = new Client({
request: (request, operation) => {
// Can also be an async function if you want to await a few things to build the config.
// `request` properties match https://developer.mozilla.org/en-US/docs/Web/API/Request#Properties
// Populated with defaults the user can override. For example, a request may
// have a JSON or FormData body, depending if there are uploads.
// `operation` is the GraphQL operation object, in clase you want to
// manipulate it or sniff something about it for overriding config.
request.url = 'https://api.foo.com/graphql'
// Token is retrieved at the time of each request.
const token = getViewerToken()
if (token)
request.headers = {
authorization: `Bearer ${token}`
}
}
}) The manipulated request object will be used immediately afterwards by fetch, so the user has final say over fetch options. This allows a user to do almost anything you can imagine, without having to learn a system of links or middleware. By design it also prevents a lot of common noob mistakes such as setting credentials at client initialization instead of per-request. Easy to implement for a really lean bundle; There is no need for deep merging of request config set at client initialization and in middleware. |
I edited the example above a few times for polish. |
That would be awesome! |
can this be accomplished with the fetchOptions/functional fetchOptions settings on the Client instance currently? |
Yes most probably. |
Closing on the basis that this is achievable via fetchOptions |
@kenwheeler But we don't have access to query/mutation body in |
@kenwheeler I am in agreement with @morajabi. The current interface for |
@blorenz there’s potential to implement this as an “exchange” (our version of Apollo’s links) which is available on the “next” branch which will go out in the next release. I’ll reopen this and let’s see what we can achieve 👍 |
|
@kitten I'm game if you can provide some guidance |
Hello all! I needed to handle files, so I glued something together. It's really basic and needs cleaning up, but it's a start and it works for me. import { print } from 'graphql';
import { filter, pipe, tap } from 'wonka';
const fileExchange = ({ forward }) => {
return ops$ => {
const preFlight$ = pipe(
ops$,
filter(operation => {
if (operation.operationName !== 'mutation') {
return true;
}
if (!operation.variables.file) {
return true;
}
const { url } = operation.context;
const { file } = operation.variables;
const extraOptions =
typeof operation.context.fetchOptions === 'function'
? operation.context.fetchOptions()
: operation.context.fetchOptions || {};
const fetchOptions = {
method: 'POST',
headers: {
...extraOptions.headers,
},
};
fetchOptions.body = new FormData()
fetchOptions.body.append(
'operations',
JSON.stringify({
query: print(operation.query),
variables: Object.assign({}, operation.variables, { file: null }),
}),
)
fetchOptions.body.append(
'map',
JSON.stringify({
0: ['variables.file'],
})
)
fetchOptions.body.append(0, file, file.name)
fetch(url, fetchOptions)
.then(res => res.json())
.then(json => console.log(json));
return false;
})
);
return forward(preFlight$);
};
}
export default fileExchange; |
Is this still something people want? I could submit a PR with this code, but it really is thrown together. |
@engleek people want. people want. 😄 |
@engleek people should be able to use This is probably the best reference for how to prepare fetch options for regular and multipart requests:
|
Yes, people want this. I would love to have it |
I'm working on a project that needs this |
I looked into creating an exchange to perform file uploads today. I was hoping to avoid completely reimplementing the I thought I'd be able to do this by creating an exchange and assigning a Next, I realized that the request was being sent with It's important that the Ideally, I think URQL could expose a function called type CreateFetchExchange = (fn: (op: Operation) => RequestInit) => Exchange; That way, I could implement a custom fetcher without having to totally reinvent what it means to fetch. |
Hey, As a follow up I've made a guided exchange that will be added to the docs, here we take the PR: #370 |
@JoviDeCroock, I totally understand that I can copy and paste an entire exchange and hack on it. My intent was to try to avoid the duplication of that code. Someone is going to make a package that contains a wholesale copy of that fetch exchange, just to implement a tiny tweak. Then, when a change is made to URQL's fetch exchange, it'll be hard to remember to pull those changes back into the upload exchange. That's why I proposed #367. |
Hiya, we've recently reduced the boilerplate around the Usually these solutions very much depend on your own setup and servers, so we chose not provide a one-fits-all solution to make a For now I'll close this issue. Happy to answer any questions about this on Spectrum though :) https://spectrum.chat/urql |
* (chore) - add replacePlugin -60B * (chore) - avoid implicit return and needless var creation -10B
If there is interest I can raise a PR to add support for file uploads via GraphQL mutation variables according to the GraphQL multipart request spec pretty easily.
It would add less than 1kb to the client bundle and the API will be unchanged so it won't get in the way for users who don't need uploads.
The logic to add to the fetcher is only a few lines; you can see how it works in
apollo-upload-client
.I looked to see if there is some sort of middleware system that could be used as an alternative to baking it in, but there isn't one?
The text was updated successfully, but these errors were encountered: