Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

fix(core): support Headers being passed in fetchOptions (#3505)

Changed files
+80 -3
.changeset
packages
+5
.changeset/fluffy-poets-protect.md
···
+
---
+
'@urql/core': patch
+
---
+
+
Correctly support the `Headers` class being used in `fetchOptions`
+46
packages/core/src/internal/fetchOptions.test.ts
···
`);
});
+
it('handles the Headers object', () => {
+
const headers = new Headers();
+
headers.append('x-test', 'true');
+
const operation = makeOperation(queryOperation.kind, queryOperation, {
+
...queryOperation.context,
+
fetchOptions: {
+
headers,
+
},
+
});
+
const body = makeFetchBody(operation);
+
+
expect(makeFetchOptions(operation, body)).toMatchInlineSnapshot(`
+
{
+
"body": "{\\"operationName\\":\\"getUser\\",\\"query\\":\\"query getUser($name: String) {\\\\n user(name: $name) {\\\\n id\\\\n firstName\\\\n lastName\\\\n }\\\\n}\\",\\"variables\\":{\\"name\\":\\"Clara\\"}}",
+
"headers": {
+
"accept": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed",
+
"content-type": "application/json",
+
"x-test": "true",
+
},
+
"method": "POST",
+
}
+
`);
+
});
+
+
it('handles an Array headers init', () => {
+
const operation = makeOperation(queryOperation.kind, queryOperation, {
+
...queryOperation.context,
+
fetchOptions: {
+
headers: [['x-test', 'true']],
+
},
+
});
+
const body = makeFetchBody(operation);
+
+
expect(makeFetchOptions(operation, body)).toMatchInlineSnapshot(`
+
{
+
"body": "{\\"operationName\\":\\"getUser\\",\\"query\\":\\"query getUser($name: String) {\\\\n user(name: $name) {\\\\n id\\\\n firstName\\\\n lastName\\\\n }\\\\n}\\",\\"variables\\":{\\"name\\":\\"Clara\\"}}",
+
"headers": {
+
"accept": "application/graphql-response+json, application/graphql+json, application/json, text/event-stream, multipart/mixed",
+
"content-type": "application/json",
+
"x-test": "true",
+
},
+
"method": "POST",
+
}
+
`);
+
});
+
it('creates a GET request when preferred for query operations', () => {
const operation = makeOperation(queryOperation.kind, queryOperation, {
...queryOperation.context,
+29 -3
packages/core/src/internal/fetchOptions.ts
···
}
};
+
const isHeaders = (headers: HeadersInit): headers is Headers =>
+
'has' in headers && !Object.keys(headers).length;
+
/** Creates a `RequestInit` object for a given `Operation`.
*
* @param operation - An {@link Operation} for which to make the request.
···
(typeof operation.context.fetchOptions === 'function'
? operation.context.fetchOptions()
: operation.context.fetchOptions) || {};
-
if (extraOptions.headers)
-
for (const key in extraOptions.headers)
-
headers[key.toLowerCase()] = extraOptions.headers[key];
+
if (extraOptions.headers) {
+
if (isHeaders(extraOptions.headers)) {
+
extraOptions.headers.forEach((value, key) => {
+
headers[key] = value;
+
});
+
} else if (Array.isArray(extraOptions.headers)) {
+
(extraOptions.headers as Array<[string, string]>).forEach(
+
(value, key) => {
+
if (Array.isArray(value)) {
+
if (headers[value[0]]) {
+
headers[value[0]] = `${headers[value[0]]},${value[1]}`;
+
} else {
+
headers[value[0]] = value[1];
+
}
+
} else {
+
headers[key] = value;
+
}
+
}
+
);
+
} else {
+
for (const key in extraOptions.headers) {
+
headers[key.toLowerCase()] = extraOptions.headers[key];
+
}
+
}
+
}
+
const serializedBody = serializeBody(operation, body);
if (typeof serializedBody === 'string' && !headers['content-type'])
headers['content-type'] = 'application/json';