Skip to content

Commit c73d3c3

Browse files
committed
Add QUERY method support
1 parent 61d6d66 commit c73d3c3

10 files changed

Lines changed: 118 additions & 7 deletions

File tree

readme.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ console.log(bytes instanceof Uint8Array);
173173
### ky.patch(input, options?)
174174
### ky.head(input, options?)
175175
### ky.delete(input, options?)
176+
### ky.query(input, options?)
176177

177178
Sets `options.method` to the method name and makes a request.
178179

@@ -199,7 +200,7 @@ Default: `'get'`
199200

200201
HTTP method used to make the request.
201202

202-
Internally, the standard methods (`GET`, `POST`, `PUT`, `PATCH`, `HEAD` and `DELETE`) are uppercased in order to avoid server errors due to case sensitivity.
203+
Internally, the standard methods (`GET`, `POST`, `PUT`, `PATCH`, `HEAD`, `DELETE`, and `QUERY`) are uppercased in order to avoid server errors due to case sensitivity.
203204

204205
##### json
205206

@@ -277,7 +278,7 @@ Notes:
277278
Type: `object | number`\
278279
Default:
279280
- `limit`: `2`
280-
- `methods`: `get` `put` `head` `delete` `options` `trace`
281+
- `methods`: `get` `put` `head` `delete` `options` `trace` `query`
281282
- `statusCodes`: [`408`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) [`413`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413) [`429`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) [`500`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) [`502`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502) [`503`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) [`504`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504)
282283
- `afterStatusCodes`: [`413`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413), [`429`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429), [`503`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503)
283284
- `maxRetryAfter`: `Infinity`

source/core/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const supportsAbortSignal = typeof globalThis.AbortSignal === 'function'
3636
export const supportsResponseStreams = typeof globalThis.ReadableStream === 'function';
3737
export const supportsFormData = typeof globalThis.FormData === 'function';
3838

39-
export const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete'] as const;
39+
export const requestMethods = ['get', 'post', 'put', 'patch', 'head', 'delete', 'query'] as const;
4040

4141
const validate = <T extends Array<true>>() => undefined as unknown as T;
4242
validate<[

source/types/ky.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ export type KyInstance = {
6969
*/
7070
head: (url: Input, options?: Options) => ResponsePromise;
7171

72+
/**
73+
Fetch the given `url` using the option `{method: 'query'}`.
74+
75+
@param url - `Request` object, `URL` object, or URL string.
76+
@returns A promise with `Body` methods added.
77+
*/
78+
query: <T>(url: Input, options?: Options) => ResponsePromise<T>;
79+
7280
/**
7381
Create a new Ky instance with complete new defaults, without inheriting from any parent instance.
7482

source/types/options.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export type SearchParamsInit = string | string[][] | Record<string, string> | UR
88
// eslint-disable-next-line unicorn/prevent-abbreviations
99
export type SearchParamsOption = SearchParamsInit | Record<string, string | number | boolean | undefined> | Array<Array<string | number | boolean>>;
1010

11-
export type RequestHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete';
11+
export type RequestHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'query';
1212
export type HttpMethod = LiteralUnion<RequestHttpMethod | 'options' | 'trace', string>;
1313

1414
export type Input = string | URL | Request;
@@ -400,7 +400,7 @@ export interface Options extends KyOptions, Omit<RequestInit, 'headers'> { // es
400400
/**
401401
HTTP method used to make the request.
402402
403-
Internally, the standard methods (`GET`, `POST`, `PUT`, `PATCH`, `HEAD` and `DELETE`) are uppercased in order to avoid server errors due to case sensitivity.
403+
Internally, the standard methods (`GET`, `POST`, `PUT`, `PATCH`, `HEAD`, `DELETE`, and `QUERY`) are uppercased in order to avoid server errors due to case sensitivity.
404404
*/
405405
method?: LiteralUnion<HttpMethod, string>;
406406

source/types/retry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export type RetryOptions = {
2323
/**
2424
The HTTP methods allowed to retry.
2525
26-
@default ['get', 'put', 'head', 'delete', 'options', 'trace']
26+
@default ['get', 'put', 'head', 'delete', 'options', 'trace', 'query']
2727
*/
2828
methods?: HttpMethod[];
2929

source/utils/normalize.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {HttpMethod, RequestHttpMethod} from '../types/options.js';
55
export const normalizeRequestMethod = (input: string): string =>
66
requestMethods.includes(input as RequestHttpMethod) ? input.toUpperCase() : input;
77

8-
const retryMethods: HttpMethod[] = ['get', 'put', 'head', 'delete', 'options', 'trace'];
8+
const retryMethods: HttpMethod[] = ['get', 'put', 'head', 'delete', 'options', 'trace', 'query'];
99

1010
const retryStatusCodes = [408, 413, 429, 500, 502, 503, 504];
1111

test/browser.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,33 @@ defaultBrowsersTest('prefix option', async (t: ExecutionContext, page: Page) =>
108108
t.deepEqual(results, ['rainbow', 'rainbow', 'rainbow', 'rainbow', 'rainbow', 'rainbow', 'rainbow']);
109109
});
110110

111+
defaultBrowsersTest('QUERY request', async (t: ExecutionContext, page: Page) => {
112+
t.plan(2);
113+
114+
server.get('/', (_request, response) => {
115+
response.end();
116+
});
117+
118+
server.all('/test', (request, response) => {
119+
t.is(request.method, 'QUERY');
120+
response.json(request.body);
121+
});
122+
123+
await page.goto(server.url);
124+
await addKyScriptToPage(page);
125+
126+
const json = {
127+
foo: true,
128+
};
129+
130+
const result = await page.evaluate(async ({url, json}) => globalThis.ky.query(`${url}/test`, {json}).json(), {
131+
url: server.url,
132+
json,
133+
});
134+
135+
t.deepEqual(result, json);
136+
});
137+
111138
defaultBrowsersTest('aborting a request', async (t: ExecutionContext, page: Page) => {
112139
server.get('/', (_request, response) => {
113140
response.end('meow');

test/main.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,26 @@ test('DELETE request', async t => {
113113
t.is(await ky.delete(server.url).text(), 'DELETE');
114114
});
115115

116+
test('QUERY request', async t => {
117+
t.plan(3);
118+
119+
const server = await createHttpTestServer(t);
120+
server.all('/', (request, response) => {
121+
t.is(request.method, 'QUERY');
122+
t.is(request.headers['content-type'], 'application/json');
123+
response.json(request.body);
124+
});
125+
126+
const json = {
127+
foo: true,
128+
};
129+
130+
const responseJson = await ky.query<typeof json>(server.url, {json}).json();
131+
132+
expectTypeOf(responseJson).toEqualTypeOf<typeof json>();
133+
t.deepEqual(responseJson, json);
134+
});
135+
116136
test('POST JSON', async t => {
117137
t.plan(2);
118138

test/methods.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,26 @@ test('method defaults to "GET"', async t => {
4343
);
4444
});
4545

46+
test('QUERY method is normalized', async t => {
47+
const server = await createHttpTestServer(t);
48+
server.all('/', (_request, response) => {
49+
response.end();
50+
});
51+
52+
t.plan(1);
53+
54+
await ky(server.url, {
55+
method: 'query',
56+
hooks: {
57+
beforeRequest: [
58+
({options}) => {
59+
t.is(options.method, 'QUERY');
60+
},
61+
],
62+
},
63+
});
64+
});
65+
4666
test.failing('custom method remains identical', async t => {
4767
const server = await createHttpTestServer(t);
4868
server.all('/', (_request, response) => {

test/retry.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,41 @@ test('not on POST', async t => {
8989
t.is(requestCount, 1);
9090
});
9191

92+
test('QUERY retries by default', async t => {
93+
let requestCount = 0;
94+
const receivedBodies: unknown[] = [];
95+
const receivedMethods: string[] = [];
96+
97+
const server = await createHttpTestServer(t);
98+
server.all('/', (request, response) => {
99+
requestCount++;
100+
receivedMethods.push(request.method);
101+
receivedBodies.push(request.body);
102+
103+
if (requestCount === defaultRetryCount + 1) {
104+
response.json(request.body);
105+
} else {
106+
response.sendStatus(500);
107+
}
108+
});
109+
110+
const json = {
111+
foo: true,
112+
};
113+
114+
const result = await ky.query(server.url, {
115+
json,
116+
retry: {
117+
delay: () => 0,
118+
},
119+
}).json();
120+
121+
t.deepEqual(result, json);
122+
t.is(requestCount, defaultRetryCount + 1);
123+
t.deepEqual(receivedMethods, ['QUERY', 'QUERY', 'QUERY']);
124+
t.deepEqual(receivedBodies, [json, json, json]);
125+
});
126+
92127
test('respect Retry-After: 0 and retry immediately', async t => {
93128
const retryCount = 4;
94129
let requestCount = 0;

0 commit comments

Comments
 (0)