Last active
October 12, 2024 23:11
-
-
Save bl42/881d85ee0dd92bbe68e93dcd0c99cfdb to your computer and use it in GitHub Desktop.
This file contains hidden or 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
import { RemoteGraphQLDataSource } from '@apollo/gateway'; | |
import { fetch, Request, Headers } from 'apollo-server-env'; | |
import { isObject } from '@apollo/gateway/dist/utilities/predicates'; | |
import FormData from 'form-data'; | |
import _ from 'lodash'; | |
export default class FileUploadDataSource extends RemoteGraphQLDataSource { | |
async process(args) { | |
const { request, context } = args; | |
const fileVariables = this.extract(request.variables); | |
if (fileVariables.length > 0) { | |
return this.processFileUpload(args, fileVariables); | |
} else { | |
return super.process(args); | |
} | |
} | |
async processFileUpload({ request, context }, fileVariables) { | |
// GraphQL multipart request spec: | |
// https://github.com/jaydenseric/graphql-multipart-request-spec | |
const form = new FormData(); | |
// cannot mutate the request object | |
const variables = _.cloneDeep(request.variables); | |
for (const [variableName] of fileVariables) { | |
_.set(variables, variableName, null); | |
} | |
const operations = JSON.stringify({ | |
query: request.query, | |
variables, | |
}); | |
form.append('operations', operations); | |
const resolvedFiles = await Promise.all( | |
fileVariables.map(async ([variableName, file]) => { | |
const contents = await file; | |
return [variableName, contents]; | |
}) | |
); | |
// e.g. { "0": ["variables.file"] } | |
const fileMap = resolvedFiles.reduce( | |
(map, [variableName], i) => ({ | |
...map, | |
[i]: [`variables.${variableName}`], | |
}), | |
{} | |
); | |
form.append('map', JSON.stringify(fileMap)); | |
await Promise.all( | |
resolvedFiles.map(async ([, contents], i) => { | |
const { filename, mimetype, createReadStream } = contents; | |
const readStream = await createReadStream(); | |
// TODO: Buffers performance issues? may be better solution. | |
const buffer = await this.onReadStream(readStream); | |
form.append(i, buffer, { filename, contentType: mimetype }); | |
}) | |
); | |
// Respect incoming http headers (eg, apollo-federation-include-trace). | |
const headers = (request.http && request.http.headers) || new Headers(); | |
form.getLength(function(err, length) { | |
headers.set('Content-Length', length); | |
}); | |
Object.entries(form.getHeaders() || {}).forEach(([k, value]) => { | |
headers.set(k, value); | |
}); | |
request.http = { | |
method: 'POST', | |
url: this.url, | |
headers, | |
}; | |
if (this.willSendRequest) { | |
await this.willSendRequest({ request, context }); | |
} | |
const options = { | |
...request.http, | |
body: form, | |
}; | |
const httpRequest = new Request(request.http.url, options); | |
try { | |
const httpResponse = await fetch(httpRequest); | |
const body = await this.didReceiveResponse(httpResponse, httpRequest); | |
if (!isObject(body)) { | |
throw new Error(`Expected JSON response body, but received: ${body}`); | |
} | |
const response = { | |
...body, | |
http: httpResponse, | |
}; | |
return response; | |
} catch (error) { | |
this.didEncounterError(error, httpRequest); | |
throw error; | |
} | |
} | |
extract(obj) { | |
const files = []; | |
const _extract = (obj, keys) => | |
Object.entries(obj || {}).forEach(([k, value]) => { | |
const key = keys ? `${keys}.${k}` : k; | |
if (value instanceof Promise) { | |
return files.push([key, value]); | |
} | |
// TODO: support arrays of files | |
if (value instanceof Object) { | |
return _extract(value, key); | |
} | |
}); | |
_extract(obj); | |
return files; | |
} | |
onReadStream = readStream => { | |
return new Promise((resolve, reject) => { | |
var buffers = []; | |
readStream.on('data', function(data) { | |
buffers.push(data); | |
}); | |
readStream.on('end', function() { | |
var actualContents = Buffer.concat(buffers); | |
resolve(actualContents); | |
}); | |
readStream.on('error', function(err) { | |
reject(err); | |
}); | |
}); | |
}; | |
} |
@ariusxi I got mine working by swapping out these lines:
from:
const body = await this.didReceiveResponse(httpResponse, httpRequest);
to:
const body = await this.parseBody(httpResponse);
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm trying to implement this code, but when i call the function didReceiveResponse he's undefined can you help me?
this is my code: