graphql-request
Version:
Minimal GraphQL client supporting Node and browsers for scripts or simple apps.
170 lines • 7.04 kB
JavaScript
import { ACCEPT_HEADER, CONTENT_TYPE_GQL, CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON } from '../../lib/http.js';
import { casesExhausted, uppercase, zip } from '../../lib/prelude.js';
import { ClientError } from '../classes/ClientError.js';
import { cleanQuery, isGraphQLContentType, isRequestResultHaveErrors, parseGraphQLExecutionResult, } from '../lib/graphql.js';
import { defaultJsonSerializer } from './defaultJsonSerializer.js';
// @ts-expect-error todo
export const runRequest = async (input) => {
// todo make a Config type
const config = {
...input,
method: input.request._tag === `Single`
? input.request.document.isMutation
? `POST`
: uppercase(input.method ?? `post`)
: input.request.hasMutations
? `POST`
: uppercase(input.method ?? `post`),
fetchOptions: {
...input.fetchOptions,
errorPolicy: input.fetchOptions.errorPolicy ?? `none`,
},
};
const fetcher = createFetcher(config.method);
const fetchResponse = await fetcher(config);
if (!fetchResponse.ok) {
return new ClientError({ status: fetchResponse.status, headers: fetchResponse.headers }, {
query: input.request._tag === `Single` ? input.request.document.expression : input.request.query,
variables: input.request.variables,
});
}
const result = await parseResultFromResponse(fetchResponse, input.fetchOptions.jsonSerializer ?? defaultJsonSerializer);
if (result instanceof Error)
throw result; // todo something better
const clientResponseBase = {
status: fetchResponse.status,
headers: fetchResponse.headers,
};
if (isRequestResultHaveErrors(result) && config.fetchOptions.errorPolicy === `none`) {
// todo this client response on error is not consistent with the data type for success
const clientResponse = result._tag === `Batch`
? { ...result.executionResults, ...clientResponseBase }
: {
...result.executionResult,
...clientResponseBase,
};
// @ts-expect-error todo
return new ClientError(clientResponse, {
query: input.request._tag === `Single` ? input.request.document.expression : input.request.query,
variables: input.request.variables,
});
}
switch (result._tag) {
case `Single`:
// @ts-expect-error todo
return {
...clientResponseBase,
...executionResultClientResponseFields(config)(result.executionResult),
};
case `Batch`:
return {
...clientResponseBase,
data: result.executionResults.map(executionResultClientResponseFields(config)),
};
default:
casesExhausted(result);
}
};
const executionResultClientResponseFields = ($params) => (executionResult) => {
return {
extensions: executionResult.extensions,
data: executionResult.data,
errors: $params.fetchOptions.errorPolicy === `all` ? executionResult.errors : undefined,
};
};
const parseResultFromResponse = async (response, jsonSerializer) => {
const contentType = response.headers.get(CONTENT_TYPE_HEADER);
const text = await response.text();
if (contentType && isGraphQLContentType(contentType)) {
return parseGraphQLExecutionResult(jsonSerializer.parse(text));
}
else {
// todo what is this good for...? Seems very random/undefined
return parseGraphQLExecutionResult(text);
}
};
const createFetcher = (method) => async (params) => {
const headers = new Headers(params.headers);
let searchParams = null;
let body = undefined;
if (!headers.has(ACCEPT_HEADER)) {
headers.set(ACCEPT_HEADER, [CONTENT_TYPE_GQL, CONTENT_TYPE_JSON].join(`, `));
}
if (method === `POST`) {
const $jsonSerializer = params.fetchOptions.jsonSerializer ?? defaultJsonSerializer;
body = $jsonSerializer.stringify(buildBody(params));
if (typeof body === `string` && !headers.has(CONTENT_TYPE_HEADER)) {
headers.set(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON);
}
}
else {
searchParams = buildQueryParams(params);
}
const init = { method, headers, body, ...params.fetchOptions };
let url = new URL(params.url);
let initResolved = init;
if (params.middleware) {
const result = await Promise.resolve(params.middleware({
...init,
url: params.url,
operationName: params.request._tag === `Single` ? params.request.document.operationName : undefined,
variables: params.request.variables,
}));
const { url: urlNew, ...initNew } = result;
url = new URL(urlNew);
initResolved = initNew;
}
if (searchParams) {
searchParams.forEach((value, name) => {
url.searchParams.append(name, value);
});
}
const $fetch = params.fetch ?? fetch;
return await $fetch(url, initResolved);
};
const buildBody = (params) => {
switch (params.request._tag) {
case `Single`:
return {
query: params.request.document.expression,
variables: params.request.variables,
operationName: params.request.document.operationName,
};
case `Batch`:
return zip(params.request.query, params.request.variables ?? []).map(([query, variables]) => ({
query,
variables,
}));
default:
throw casesExhausted(params.request);
}
};
const buildQueryParams = (params) => {
const $jsonSerializer = params.fetchOptions.jsonSerializer ?? defaultJsonSerializer;
const searchParams = new URLSearchParams();
switch (params.request._tag) {
case `Single`: {
searchParams.append(`query`, cleanQuery(params.request.document.expression));
if (params.request.variables) {
searchParams.append(`variables`, $jsonSerializer.stringify(params.request.variables));
}
if (params.request.document.operationName) {
searchParams.append(`operationName`, params.request.document.operationName);
}
return searchParams;
}
case `Batch`: {
const variablesSerialized = params.request.variables?.map((v) => $jsonSerializer.stringify(v)) ?? [];
const queriesCleaned = params.request.query.map(cleanQuery);
const payload = zip(queriesCleaned, variablesSerialized).map(([query, variables]) => ({
query,
variables,
}));
searchParams.append(`query`, $jsonSerializer.stringify(payload));
return searchParams;
}
default:
throw casesExhausted(params.request);
}
};
//# sourceMappingURL=runRequest.js.map