@omnigraph/odata
Version:
122 lines (121 loc) • 5.64 kB
JavaScript
import DataLoader from 'dataloader';
import { parseResponse } from 'http-string-parser';
import urljoin from 'url-join';
import { getHeadersObj } from '@graphql-mesh/utils';
import { createGraphQLError, memoize1 } from '@graphql-tools/utils';
import { Response } from '@whatwg-node/fetch';
export function getDataloaderFactory({ fetchFn, endpoint, headersFactory, batchMode, }) {
const DATALOADER_FACTORIES = {
multipart: (context) => new DataLoader(async (requests) => {
let requestBody = '';
const requestBoundary = 'batch_' + Date.now();
for (const requestIndex in requests) {
requestBody += `--${requestBoundary}\n`;
const request = requests[requestIndex];
requestBody += `Content-Type: application/http\n`;
requestBody += `Content-Transfer-Encoding:binary\n`;
requestBody += `Content-ID: ${requestIndex}\n\n`;
requestBody += `${request.method} ${request.url} HTTP/1.1\n`;
request.headers?.forEach((value, key) => {
requestBody += `${key}: ${value}\n`;
});
if (request.body) {
const bodyAsStr = await request.text();
requestBody += `Content-Length: ${bodyAsStr.length}`;
requestBody += `\n`;
requestBody += bodyAsStr;
}
requestBody += `\n`;
}
requestBody += `--${requestBoundary}--\n`;
const batchHeaders = headersFactory({
context,
env: process.env,
}, 'POST');
batchHeaders['content-type'] = `multipart/mixed;boundary=${requestBoundary}`;
const batchResponse = await fetchFn(urljoin(endpoint, '$batch'), {
method: 'POST',
body: requestBody,
headers: batchHeaders,
});
if (batchResponse.headers.get('content-type').includes('json')) {
const batchResponseJson = await batchResponse.json();
return handleBatchJsonResults(batchResponseJson, requests);
}
const batchResponseText = await batchResponse.text();
const responseLines = batchResponseText.split('\n');
const responseBoundary = responseLines[0];
const actualResponse = responseLines.slice(1, responseLines.length - 2).join('\n');
const responseTextArr = actualResponse.split(responseBoundary);
return responseTextArr.map(responseTextWithContentHeader => {
const responseText = responseTextWithContentHeader.split('\n').slice(4).join('\n');
const { body, headers, statusCode, statusMessage } = parseResponse(responseText);
return new Response(body, {
headers,
status: parseInt(statusCode),
statusText: statusMessage,
});
});
}),
json: (context) => new DataLoader(async (requests) => {
const batchHeaders = headersFactory({
context,
env: process.env,
}, 'POST');
batchHeaders['content-type'] = 'application/json';
const batchResponse = await fetchFn(urljoin(endpoint, '$batch'), {
method: 'POST',
body: JSON.stringify({
requests: await Promise.all(requests.map(async (request, index) => {
const id = index.toString();
const url = request.url.replace(endpoint, '');
const method = request.method;
const headers = {};
request.headers?.forEach((value, key) => {
headers[key] = value;
});
return {
id,
url,
method,
body: request.body && (await request.json()),
headers,
};
})),
}),
headers: batchHeaders,
});
const batchResponseJson = await batchResponse.json();
return handleBatchJsonResults(batchResponseJson, requests);
}),
none: () =>
// We should refactor here
new DataLoader(requests => Promise.all(requests.map(async (request) => fetchFn(request.url, {
method: request.method,
body: request.body && (await request.text()),
headers: getHeadersObj(request.headers),
})))),
};
return memoize1(DATALOADER_FACTORIES[batchMode]);
}
function handleBatchJsonResults(batchResponseJson, requests) {
if ('error' in batchResponseJson) {
throw createGraphQLError(batchResponseJson.error.message, {
extensions: batchResponseJson.error,
});
}
if (!('responses' in batchResponseJson)) {
throw createGraphQLError(batchResponseJson.ExceptionMessage ||
batchResponseJson.Message ||
`Batch Request didn't return a valid response.`, {
extensions: batchResponseJson,
});
}
return requests.map((_req, index) => {
const responseObj = batchResponseJson.responses.find((res) => res.id === index.toString());
return Response.json(responseObj.body, {
status: responseObj.status,
headers: responseObj.headers,
});
});
}