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

fix(core): Allow path-only URLs to be used with GET requests (#3514)

Changed files
+37 -4
.changeset
packages
+5
.changeset/curvy-sheep-drive.md
···
···
+
---
+
'@urql/core': patch
+
---
+
+
Allow `url` to be a plain, non-URL pathname (i.e. `/api/graphql`) to be used with `preferGetMethod`
+20
packages/core/src/internal/fetchOptions.test.ts
···
);
});
it('returns a query parameter URL when GET is preferred', () => {
const operation = makeOperation(queryOperation.kind, queryOperation, {
...queryOperation.context,
···
const body = makeFetchBody(operation);
expect(makeFetchURL(operation, body)).toMatchInlineSnapshot(
'"http://localhost:3000/graphql?query=query+getUser%28%24name%3A+String%29+%7B%0A++user%28name%3A+%24name%29+%7B%0A++++id%0A++++firstName%0A++++lastName%0A++%7D%0A%7D&operationName=getUser&variables=%7B%22name%22%3A%22Clara%22%7D"'
);
});
···
);
});
+
it('returns the URL by default when only a path is provided', () => {
+
const operation = makeOperation(queryOperation.kind, queryOperation, {
+
url: '/graphql',
+
});
+
const body = makeFetchBody(operation);
+
expect(makeFetchURL(operation, body)).toBe('/graphql');
+
});
+
it('returns a query parameter URL when GET is preferred', () => {
const operation = makeOperation(queryOperation.kind, queryOperation, {
...queryOperation.context,
···
const body = makeFetchBody(operation);
expect(makeFetchURL(operation, body)).toMatchInlineSnapshot(
'"http://localhost:3000/graphql?query=query+getUser%28%24name%3A+String%29+%7B%0A++user%28name%3A+%24name%29+%7B%0A++++id%0A++++firstName%0A++++lastName%0A++%7D%0A%7D&operationName=getUser&variables=%7B%22name%22%3A%22Clara%22%7D"'
+
);
+
});
+
+
it('returns a query parameter URL when GET is preferred and only a path is provided', () => {
+
const operation = makeOperation(queryOperation.kind, queryOperation, {
+
url: '/graphql',
+
preferGetMethod: true,
+
});
+
+
const body = makeFetchBody(operation);
+
expect(makeFetchURL(operation, body)).toMatchInlineSnapshot(
+
'"/graphql?query=query+getUser%28%24name%3A+String%29+%7B%0A++user%28name%3A+%24name%29+%7B%0A++++id%0A++++firstName%0A++++lastName%0A++%7D%0A%7D&operationName=getUser&variables=%7B%22name%22%3A%22Clara%22%7D"'
);
});
+12 -4
packages/core/src/internal/fetchOptions.ts
···
operation.kind === 'query' && operation.context.preferGetMethod;
if (!useGETMethod || !body) return operation.context.url;
-
const url = new URL(operation.context.url);
for (const key in body) {
const value = body[key];
if (value) {
-
url.searchParams.set(
key,
typeof value === 'object' ? stringifyVariables(value) : value
);
}
}
-
-
const finalUrl = url.toString();
if (finalUrl.length > 2047 && useGETMethod !== 'force') {
operation.context.preferGetMethod = false;
return operation.context.url;
}
return finalUrl;
};
/** Serializes a {@link FetchBody} into a {@link RequestInit.body} format. */
···
operation.kind === 'query' && operation.context.preferGetMethod;
if (!useGETMethod || !body) return operation.context.url;
+
const urlParts = splitOutSearchParams(operation.context.url);
for (const key in body) {
const value = body[key];
if (value) {
+
urlParts[1].set(
key,
typeof value === 'object' ? stringifyVariables(value) : value
);
}
}
+
const finalUrl = urlParts.join('?');
if (finalUrl.length > 2047 && useGETMethod !== 'force') {
operation.context.preferGetMethod = false;
return operation.context.url;
}
return finalUrl;
+
};
+
+
const splitOutSearchParams = (
+
url: string
+
): readonly [string, URLSearchParams] => {
+
const start = url.indexOf('?');
+
return start > -1
+
? [url.slice(0, start), new URLSearchParams(url.slice(start + 1))]
+
: [url, new URLSearchParams()];
};
/** Serializes a {@link FetchBody} into a {@link RequestInit.body} format. */