@voiceflow/fetch 1.5.5 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -10
- package/build/cjs/client-configuration.interface.d.ts +1 -1
- package/build/cjs/client-configuration.interface.d.ts.map +1 -1
- package/build/cjs/fetch.client.d.ts +3 -3
- package/build/cjs/fetch.client.d.ts.map +1 -1
- package/build/cjs/fetch.client.js +15 -12
- package/build/cjs/fetch.interface.d.ts +2 -2
- package/build/cjs/fetch.interface.d.ts.map +1 -1
- package/build/cjs/http-method.enum.js +1 -1
- package/build/cjs/request-options.interface.d.ts +4 -4
- package/build/cjs/request-options.interface.d.ts.map +1 -1
- package/build/esm/client-configuration.interface.d.ts +1 -1
- package/build/esm/client-configuration.interface.d.ts.map +1 -1
- package/build/esm/fetch.client.d.ts +3 -3
- package/build/esm/fetch.client.d.ts.map +1 -1
- package/build/esm/fetch.client.js +15 -12
- package/build/esm/fetch.interface.d.ts +2 -2
- package/build/esm/fetch.interface.d.ts.map +1 -1
- package/build/esm/request-options.interface.d.ts +4 -4
- package/build/esm/request-options.interface.d.ts.map +1 -1
- package/package.json +40 -41
- package/build/cjs/fetch.client.test.d.ts +0 -2
- package/build/cjs/fetch.client.test.d.ts.map +0 -1
- package/build/cjs/fetch.client.test.js +0 -245
- package/build/esm/fetch.client.test.d.ts +0 -2
- package/build/esm/fetch.client.test.d.ts.map +0 -1
- package/build/esm/fetch.client.test.js +0 -217
package/README.md
CHANGED
|
@@ -20,7 +20,9 @@ This is a universal library and can be used in the browser or in a Node.JS envir
|
|
|
20
20
|
```ts
|
|
21
21
|
import { FetchClient } from '@voiceflow/fetch';
|
|
22
22
|
|
|
23
|
-
const fetch = new FetchClient({
|
|
23
|
+
const fetch = new FetchClient({
|
|
24
|
+
/* config */
|
|
25
|
+
});
|
|
24
26
|
```
|
|
25
27
|
|
|
26
28
|
### Node Usage
|
|
@@ -29,12 +31,14 @@ const fetch = new FetchClient({ /* config */ });
|
|
|
29
31
|
import { FetchClient } from '@voiceflow/fetch';
|
|
30
32
|
import * as undici from 'undici';
|
|
31
33
|
|
|
32
|
-
const fetch = new FetchClient(undici.fetch, {
|
|
34
|
+
const fetch = new FetchClient(undici.fetch, {
|
|
35
|
+
/* config */
|
|
36
|
+
});
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
## Configuration
|
|
36
40
|
|
|
37
|
-
-
|
|
41
|
+
- **`baseURL`** (`string`): this will be added as a prefix to the URL of all requests
|
|
38
42
|
|
|
39
43
|
## Features
|
|
40
44
|
|
|
@@ -54,7 +58,7 @@ This will also automatically add the request header `Content-Type: application/j
|
|
|
54
58
|
const fetch = new FetchClient();
|
|
55
59
|
|
|
56
60
|
await fetch.put('http://example.com', {
|
|
57
|
-
json: { foo: 'bar' }
|
|
61
|
+
json: { foo: 'bar' },
|
|
58
62
|
});
|
|
59
63
|
```
|
|
60
64
|
|
|
@@ -66,7 +70,7 @@ You can also specify a type for the parsed result, by default the type will be `
|
|
|
66
70
|
```ts
|
|
67
71
|
const fetch = new FetchClient();
|
|
68
72
|
|
|
69
|
-
const result = await fetch.get('http://example.com').json<{ id: number
|
|
73
|
+
const result = await fetch.get('http://example.com').json<{ id: number; name: string }>();
|
|
70
74
|
```
|
|
71
75
|
|
|
72
76
|
### HTTP Methods
|
|
@@ -77,11 +81,11 @@ Use the appropriate method to set the HTTP method being used in the request.
|
|
|
77
81
|
const fetch = new FetchClient();
|
|
78
82
|
|
|
79
83
|
fetch.delete('/foo'); // DELETE /foo
|
|
80
|
-
fetch.get('/foo');
|
|
81
|
-
fetch.head('/foo');
|
|
82
|
-
fetch.patch('/foo');
|
|
83
|
-
fetch.post('/foo');
|
|
84
|
-
fetch.put('/foo');
|
|
84
|
+
fetch.get('/foo'); // GET /foo
|
|
85
|
+
fetch.head('/foo'); // HEAD /foo
|
|
86
|
+
fetch.patch('/foo'); // PATCH /foo
|
|
87
|
+
fetch.post('/foo'); // POST /foo
|
|
88
|
+
fetch.put('/foo'); // PUT /foo
|
|
85
89
|
```
|
|
86
90
|
|
|
87
91
|
### Base URL
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-configuration.interface.d.ts","sourceRoot":"","sources":["../../src/client-configuration.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"client-configuration.interface.d.ts","sourceRoot":"","sources":["../../src/client-configuration.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ClientConfiguration } from './client-configuration.interface';
|
|
2
|
-
import { FetchAPI, FetchOptions, FetchResponse } from './fetch.interface';
|
|
3
|
-
import { RequestOptions } from './request-options.interface';
|
|
1
|
+
import type { ClientConfiguration } from './client-configuration.interface';
|
|
2
|
+
import type { FetchAPI, FetchOptions, FetchResponse } from './fetch.interface';
|
|
3
|
+
import type { RequestOptions } from './request-options.interface';
|
|
4
4
|
export declare class FetchClient<Opts extends FetchOptions<any, any> = RequestInit, Req = URL | Request, Res extends FetchResponse = Response> {
|
|
5
5
|
private static extractHeaders;
|
|
6
6
|
private static extractQuery;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.client.d.ts","sourceRoot":"","sources":["../../src/fetch.client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"fetch.client.d.ts","sourceRoot":"","sources":["../../src/fetch.client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE/E,OAAO,KAAK,EAAkB,cAAc,EAAgB,MAAM,6BAA6B,CAAC;AAEhG,qBAAa,WAAW,CACtB,IAAI,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,WAAW,EACjD,GAAG,GAAG,GAAG,GAAG,OAAO,EACnB,GAAG,SAAS,aAAa,GAAG,QAAQ;IAEpC,OAAO,CAAC,MAAM,CAAC,cAAc;IAI7B,OAAO,CAAC,MAAM,CAAC,YAAY;IAI3B,OAAO,CAAC,MAAM,CAAC,SAAS;IAMxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAE7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;gBAGjD,MAAM,CAAC,EAAE,mBAAmB;gBAC5B,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB;YAW5D,IAAI;IA6BlB,OAAO,CAAC,YAAY;IAUb,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAIhE,MAAM,QAbE,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MASQ;IAE9C,GAAG,QAfK,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAWE;IAExC,IAAI,QAjBI,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAaI;IAE1C,KAAK,QAnBG,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAeM;IAE5C,IAAI,QArBI,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAiBI;IAE1C,GAAG,QAvBK,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAmBE;CAChD"}
|
|
@@ -4,6 +4,17 @@ exports.FetchClient = void 0;
|
|
|
4
4
|
const exception_1 = require("@voiceflow/exception");
|
|
5
5
|
const http_method_enum_1 = require("./http-method.enum");
|
|
6
6
|
class FetchClient {
|
|
7
|
+
static extractHeaders(headers) {
|
|
8
|
+
return new Map(headers instanceof Map ? headers : Object.entries(headers ?? {}));
|
|
9
|
+
}
|
|
10
|
+
static extractQuery(query) {
|
|
11
|
+
return new URLSearchParams(query instanceof Map ? Object.entries(query) : query);
|
|
12
|
+
}
|
|
13
|
+
static formatURL(baseURL, path, query) {
|
|
14
|
+
const url = new URL(path, baseURL);
|
|
15
|
+
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
16
|
+
return url.href;
|
|
17
|
+
}
|
|
7
18
|
constructor(fetchOrConfig, config) {
|
|
8
19
|
this.delete = this.createMethod(http_method_enum_1.HTTPMethod.DELETE);
|
|
9
20
|
this.get = this.createMethod(http_method_enum_1.HTTPMethod.GET);
|
|
@@ -19,22 +30,14 @@ class FetchClient {
|
|
|
19
30
|
this.config = fetchOrConfig ?? config ?? {};
|
|
20
31
|
}
|
|
21
32
|
}
|
|
22
|
-
static extractHeaders(headers) {
|
|
23
|
-
return new Map(headers instanceof Map ? headers : Object.entries(headers ?? {}));
|
|
24
|
-
}
|
|
25
|
-
static extractQuery(query) {
|
|
26
|
-
return new URLSearchParams(query instanceof Map ? Object.entries(query) : query);
|
|
27
|
-
}
|
|
28
|
-
static formatURL(baseURL, path, query) {
|
|
29
|
-
const url = new URL(path, baseURL);
|
|
30
|
-
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
31
|
-
return url.href;
|
|
32
|
-
}
|
|
33
33
|
/* eslint-enable lines-between-class-members */
|
|
34
34
|
async send(url, rawOptions) {
|
|
35
35
|
// eslint-disable-next-line prefer-const
|
|
36
36
|
let { json, headers, query, body, ...options } = rawOptions;
|
|
37
|
-
headers = new Map([
|
|
37
|
+
headers = new Map([
|
|
38
|
+
...FetchClient.extractHeaders(this.config.headers).entries(),
|
|
39
|
+
...FetchClient.extractHeaders(headers).entries(),
|
|
40
|
+
]);
|
|
38
41
|
query = FetchClient.extractQuery(query);
|
|
39
42
|
if (json != null) {
|
|
40
43
|
headers.set('content-type', 'application/json');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseResponse } from '@voiceflow/exception';
|
|
1
|
+
import type { BaseResponse } from '@voiceflow/exception';
|
|
2
2
|
export interface FetchOptions<Headers, Body> {
|
|
3
3
|
method?: string;
|
|
4
4
|
headers?: [string, string][] | Record<string, string> | Headers;
|
|
@@ -8,5 +8,5 @@ export interface FetchResponse extends BaseResponse {
|
|
|
8
8
|
ok: boolean;
|
|
9
9
|
json: () => Promise<any>;
|
|
10
10
|
}
|
|
11
|
-
export
|
|
11
|
+
export type FetchAPI<Opts extends FetchOptions<any, any>, Req, Res extends FetchResponse> = (input: string | Req, init?: Opts) => Promise<Res>;
|
|
12
12
|
//# sourceMappingURL=fetch.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.interface.d.ts","sourceRoot":"","sources":["../../src/fetch.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"fetch.interface.d.ts","sourceRoot":"","sources":["../../src/fetch.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,YAAY,CAAC,OAAO,EAAE,IAAI;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;IAChE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;CAC1B;AAED,MAAM,MAAM,QAAQ,CAAC,IAAI,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,SAAS,aAAa,IAAI,CAC1F,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,CAAC,EAAE,IAAI,KACR,OAAO,CAAC,GAAG,CAAC,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { FetchOptions } from './fetch.interface';
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
import type { FetchOptions } from './fetch.interface';
|
|
2
|
+
export type RequestHeaders = Record<string, string> | Map<string, string>;
|
|
3
|
+
export type RequestQuery = URLSearchParams | [string, string][] | Record<string, string> | Map<string, string>;
|
|
4
4
|
export interface ExtraOptions {
|
|
5
5
|
json?: any;
|
|
6
6
|
headers?: RequestHeaders;
|
|
7
7
|
query?: RequestQuery | undefined;
|
|
8
8
|
}
|
|
9
|
-
export
|
|
9
|
+
export type RequestOptions<Opts extends FetchOptions<any, any>> = Omit<Partial<Opts>, keyof ExtraOptions> & ExtraOptions;
|
|
10
10
|
//# sourceMappingURL=request-options.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-options.interface.d.ts","sourceRoot":"","sources":["../../src/request-options.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"request-options.interface.d.ts","sourceRoot":"","sources":["../../src/request-options.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1E,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/G,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,YAAY,CAAC,GACvG,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-configuration.interface.d.ts","sourceRoot":"","sources":["../../src/client-configuration.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"client-configuration.interface.d.ts","sourceRoot":"","sources":["../../src/client-configuration.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ClientConfiguration } from './client-configuration.interface.js';
|
|
2
|
-
import { FetchAPI, FetchOptions, FetchResponse } from './fetch.interface.js';
|
|
3
|
-
import { RequestOptions } from './request-options.interface.js';
|
|
1
|
+
import type { ClientConfiguration } from './client-configuration.interface.js';
|
|
2
|
+
import type { FetchAPI, FetchOptions, FetchResponse } from './fetch.interface.js';
|
|
3
|
+
import type { RequestOptions } from './request-options.interface.js';
|
|
4
4
|
export declare class FetchClient<Opts extends FetchOptions<any, any> = RequestInit, Req = URL | Request, Res extends FetchResponse = Response> {
|
|
5
5
|
private static extractHeaders;
|
|
6
6
|
private static extractQuery;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.client.d.ts","sourceRoot":"","sources":["../../src/fetch.client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"fetch.client.d.ts","sourceRoot":"","sources":["../../src/fetch.client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE/E,OAAO,KAAK,EAAkB,cAAc,EAAgB,MAAM,6BAA6B,CAAC;AAEhG,qBAAa,WAAW,CACtB,IAAI,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,WAAW,EACjD,GAAG,GAAG,GAAG,GAAG,OAAO,EACnB,GAAG,SAAS,aAAa,GAAG,QAAQ;IAEpC,OAAO,CAAC,MAAM,CAAC,cAAc;IAI7B,OAAO,CAAC,MAAM,CAAC,YAAY;IAI3B,OAAO,CAAC,MAAM,CAAC,SAAS;IAMxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAE7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;gBAGjD,MAAM,CAAC,EAAE,mBAAmB;gBAC5B,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB;YAW5D,IAAI;IA6BlB,OAAO,CAAC,YAAY;IAUb,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAIhE,MAAM,QAbE,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MASQ;IAE9C,GAAG,QAfK,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAWE;IAExC,IAAI,QAjBI,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAaI;IAE1C,KAAK,QAnBG,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAeM;IAE5C,IAAI,QArBI,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAiBI;IAE1C,GAAG,QAvBK,MAAM,GAAG,GAAG,YAAY,KAAK,eAAe,IAAI,CAAC,EAAE,QAAQ,CAAC;iCAIxC,QAAQ,CAAC,CAAC;MAmBE;CAChD"}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { ClientException } from '@voiceflow/exception';
|
|
2
2
|
import { HTTPMethod } from './http-method.enum.js';
|
|
3
3
|
export class FetchClient {
|
|
4
|
+
static extractHeaders(headers) {
|
|
5
|
+
return new Map(headers instanceof Map ? headers : Object.entries(headers ?? {}));
|
|
6
|
+
}
|
|
7
|
+
static extractQuery(query) {
|
|
8
|
+
return new URLSearchParams(query instanceof Map ? Object.entries(query) : query);
|
|
9
|
+
}
|
|
10
|
+
static formatURL(baseURL, path, query) {
|
|
11
|
+
const url = new URL(path, baseURL);
|
|
12
|
+
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
13
|
+
return url.href;
|
|
14
|
+
}
|
|
4
15
|
constructor(fetchOrConfig, config) {
|
|
5
16
|
this.delete = this.createMethod(HTTPMethod.DELETE);
|
|
6
17
|
this.get = this.createMethod(HTTPMethod.GET);
|
|
@@ -16,22 +27,14 @@ export class FetchClient {
|
|
|
16
27
|
this.config = fetchOrConfig ?? config ?? {};
|
|
17
28
|
}
|
|
18
29
|
}
|
|
19
|
-
static extractHeaders(headers) {
|
|
20
|
-
return new Map(headers instanceof Map ? headers : Object.entries(headers ?? {}));
|
|
21
|
-
}
|
|
22
|
-
static extractQuery(query) {
|
|
23
|
-
return new URLSearchParams(query instanceof Map ? Object.entries(query) : query);
|
|
24
|
-
}
|
|
25
|
-
static formatURL(baseURL, path, query) {
|
|
26
|
-
const url = new URL(path, baseURL);
|
|
27
|
-
query.forEach((value, key) => url.searchParams.append(key, value));
|
|
28
|
-
return url.href;
|
|
29
|
-
}
|
|
30
30
|
/* eslint-enable lines-between-class-members */
|
|
31
31
|
async send(url, rawOptions) {
|
|
32
32
|
// eslint-disable-next-line prefer-const
|
|
33
33
|
let { json, headers, query, body, ...options } = rawOptions;
|
|
34
|
-
headers = new Map([
|
|
34
|
+
headers = new Map([
|
|
35
|
+
...FetchClient.extractHeaders(this.config.headers).entries(),
|
|
36
|
+
...FetchClient.extractHeaders(headers).entries(),
|
|
37
|
+
]);
|
|
35
38
|
query = FetchClient.extractQuery(query);
|
|
36
39
|
if (json != null) {
|
|
37
40
|
headers.set('content-type', 'application/json');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseResponse } from '@voiceflow/exception';
|
|
1
|
+
import type { BaseResponse } from '@voiceflow/exception';
|
|
2
2
|
export interface FetchOptions<Headers, Body> {
|
|
3
3
|
method?: string;
|
|
4
4
|
headers?: [string, string][] | Record<string, string> | Headers;
|
|
@@ -8,5 +8,5 @@ export interface FetchResponse extends BaseResponse {
|
|
|
8
8
|
ok: boolean;
|
|
9
9
|
json: () => Promise<any>;
|
|
10
10
|
}
|
|
11
|
-
export
|
|
11
|
+
export type FetchAPI<Opts extends FetchOptions<any, any>, Req, Res extends FetchResponse> = (input: string | Req, init?: Opts) => Promise<Res>;
|
|
12
12
|
//# sourceMappingURL=fetch.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.interface.d.ts","sourceRoot":"","sources":["../../src/fetch.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"fetch.interface.d.ts","sourceRoot":"","sources":["../../src/fetch.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,YAAY,CAAC,OAAO,EAAE,IAAI;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;IAChE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;CAC1B;AAED,MAAM,MAAM,QAAQ,CAAC,IAAI,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,SAAS,aAAa,IAAI,CAC1F,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,CAAC,EAAE,IAAI,KACR,OAAO,CAAC,GAAG,CAAC,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { FetchOptions } from './fetch.interface.js';
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
import type { FetchOptions } from './fetch.interface.js';
|
|
2
|
+
export type RequestHeaders = Record<string, string> | Map<string, string>;
|
|
3
|
+
export type RequestQuery = URLSearchParams | [string, string][] | Record<string, string> | Map<string, string>;
|
|
4
4
|
export interface ExtraOptions {
|
|
5
5
|
json?: any;
|
|
6
6
|
headers?: RequestHeaders;
|
|
7
7
|
query?: RequestQuery | undefined;
|
|
8
8
|
}
|
|
9
|
-
export
|
|
9
|
+
export type RequestOptions<Opts extends FetchOptions<any, any>> = Omit<Partial<Opts>, keyof ExtraOptions> & ExtraOptions;
|
|
10
10
|
//# sourceMappingURL=request-options.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-options.interface.d.ts","sourceRoot":"","sources":["../../src/request-options.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"request-options.interface.d.ts","sourceRoot":"","sources":["../../src/request-options.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1E,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/G,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,YAAY,CAAC,GACvG,YAAY,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,59 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voiceflow/fetch",
|
|
3
|
+
"version": "1.6.1",
|
|
3
4
|
"description": "Voiceflow fetch wrapper and error handling for SDKs",
|
|
4
|
-
"version": "1.5.5",
|
|
5
|
-
"author": "Voiceflow",
|
|
6
|
-
"bugs": {
|
|
7
|
-
"url": "https://github.com/voiceflow/libs/issues"
|
|
8
|
-
},
|
|
9
|
-
"devDependencies": {
|
|
10
|
-
"@voiceflow-meta/jest-config": "1.0.2",
|
|
11
|
-
"@voiceflow-meta/typescript-config": "1.0.2",
|
|
12
|
-
"@voiceflow/exception": "1.5.4",
|
|
13
|
-
"fetch-mock": "^9.11.0",
|
|
14
|
-
"jest-environment-jsdom": "29.7.0",
|
|
15
|
-
"jest-fetch-mock": "3.0.3",
|
|
16
|
-
"undici": "5.12.0"
|
|
17
|
-
},
|
|
18
|
-
"files": [
|
|
19
|
-
"build"
|
|
20
|
-
],
|
|
21
|
-
"homepage": "https://github.com/voiceflow/libs#readme",
|
|
22
5
|
"keywords": [
|
|
23
6
|
"voiceflow"
|
|
24
7
|
],
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
"peerDependencies": {
|
|
29
|
-
"@voiceflow/exception": "*"
|
|
30
|
-
},
|
|
31
|
-
"publishConfig": {
|
|
32
|
-
"access": "public"
|
|
8
|
+
"homepage": "https://github.com/voiceflow/libs#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/voiceflow/libs/issues"
|
|
33
11
|
},
|
|
34
12
|
"repository": {
|
|
35
13
|
"type": "git",
|
|
36
14
|
"url": "git+https://github.com/voiceflow/libs.git"
|
|
37
15
|
},
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"author": "Voiceflow",
|
|
18
|
+
"main": "build/cjs/main.js",
|
|
19
|
+
"module": "build/esm/main.js",
|
|
20
|
+
"types": "build/cjs/main.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"build"
|
|
23
|
+
],
|
|
38
24
|
"scripts": {
|
|
39
|
-
"build": "yarn
|
|
40
|
-
"build:cjs": "yarn g:
|
|
41
|
-
"build:
|
|
25
|
+
"build": "yarn g:turbo run build:cmd --filter=@voiceflow/fetch...",
|
|
26
|
+
"build:cjs": "yarn g:build:pkg cjs",
|
|
27
|
+
"build:cmd": "yarn g:run-p build:cjs build:esm",
|
|
28
|
+
"build:esm": "yarn g:build:pkg esm",
|
|
42
29
|
"clean": "yarn g:rimraf build",
|
|
43
|
-
"
|
|
44
|
-
"lint": "yarn g:eslint
|
|
45
|
-
"lint:fix": "yarn lint --fix",
|
|
46
|
-
"lint:
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"test:
|
|
52
|
-
|
|
30
|
+
"lint": "yarn g:run-p -c lint:eslint lint:prettier",
|
|
31
|
+
"lint:eslint": "yarn g:eslint",
|
|
32
|
+
"lint:fix": "yarn g:run-p -c \"lint:eslint --fix\" \"lint:prettier --write\"",
|
|
33
|
+
"lint:prettier": "yarn g:prettier --check",
|
|
34
|
+
"tdd": "yarn g:vitest",
|
|
35
|
+
"test": "yarn g:run-p -c test:dependencies test:types test:unit",
|
|
36
|
+
"test:dependencies": "yarn g:depcruise",
|
|
37
|
+
"test:types": "yarn g:tsc --noEmit",
|
|
38
|
+
"test:unit": "yarn g:vitest run --coverage"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@voiceflow/exception": "1.6.1",
|
|
42
|
+
"fetch-mock": "9.11.0",
|
|
43
|
+
"jest-environment-jsdom": "29.7.0",
|
|
44
|
+
"jest-fetch-mock": "3.0.3",
|
|
45
|
+
"undici": "5.12.0",
|
|
46
|
+
"vitest-fetch-mock": "0.2.2"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@voiceflow/exception": "^"
|
|
53
50
|
},
|
|
54
|
-
"types": "build/cjs/main.d.ts",
|
|
55
51
|
"volta": {
|
|
56
52
|
"extends": "../../package.json"
|
|
57
53
|
},
|
|
58
|
-
"
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"gitHead": "123b2b6ed54ba2833fc56acc1b4258e8c2974223"
|
|
59
58
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.client.test.d.ts","sourceRoot":"","sources":["../../src/fetch.client.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
const exception_1 = require("@voiceflow/exception");
|
|
30
|
-
const fetch_mock_1 = __importDefault(require("fetch-mock"));
|
|
31
|
-
const jest_fetch_mock_1 = __importDefault(require("jest-fetch-mock"));
|
|
32
|
-
const node_url_1 = require("node:url");
|
|
33
|
-
const undici = __importStar(require("undici"));
|
|
34
|
-
const fetch_client_1 = require("./fetch.client");
|
|
35
|
-
const TARGET_URL = 'http://example.com/resource/123';
|
|
36
|
-
const JSON_HEADERS = { 'content-type': 'application/json' };
|
|
37
|
-
jest_fetch_mock_1.default.enableMocks();
|
|
38
|
-
describe('Fetch Client', () => {
|
|
39
|
-
let sandbox;
|
|
40
|
-
beforeAll(() => {
|
|
41
|
-
jest_fetch_mock_1.default.mockImplementation(async () => null);
|
|
42
|
-
});
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
sandbox = fetch_mock_1.default.sandbox();
|
|
45
|
-
});
|
|
46
|
-
describe('#raw()', () => {
|
|
47
|
-
const url = 'http://example.com';
|
|
48
|
-
describe('window.fetch', () => {
|
|
49
|
-
let fetchClient;
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
fetchClient = new fetch_client_1.FetchClient();
|
|
52
|
-
});
|
|
53
|
-
it('should pass through Request instance and options to window.fetch', async () => {
|
|
54
|
-
const request = new Request(new URL(url));
|
|
55
|
-
const options = { cache: 'only-if-cached' };
|
|
56
|
-
await fetchClient.raw(request, options);
|
|
57
|
-
expect(jest_fetch_mock_1.default).toHaveBeenCalledWith(request, options);
|
|
58
|
-
});
|
|
59
|
-
it('should pass through URL instance to window.fetch', async () => {
|
|
60
|
-
const request = new URL(url);
|
|
61
|
-
await fetchClient.raw(request);
|
|
62
|
-
expect(jest_fetch_mock_1.default).toHaveBeenCalledWith(request);
|
|
63
|
-
});
|
|
64
|
-
it('should pass through string to window.fetch', async () => {
|
|
65
|
-
await fetchClient.raw(url);
|
|
66
|
-
expect(jest_fetch_mock_1.default).toHaveBeenCalledWith(url);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
describe('undici.fetch', () => {
|
|
70
|
-
let fetchSpy;
|
|
71
|
-
let fetchClient;
|
|
72
|
-
beforeEach(() => {
|
|
73
|
-
fetchSpy = jest.fn();
|
|
74
|
-
fetchClient = new fetch_client_1.FetchClient(fetchSpy);
|
|
75
|
-
});
|
|
76
|
-
it('should pass through Request instance and options to undici.fetch', async () => {
|
|
77
|
-
const request = new undici.Request(new node_url_1.URL(url));
|
|
78
|
-
const options = { dispatcher: new undici.Dispatcher() };
|
|
79
|
-
await fetchClient.raw(request, options);
|
|
80
|
-
expect(fetchSpy).toHaveBeenCalledWith(request, options);
|
|
81
|
-
});
|
|
82
|
-
it('should pass through URL instance to undici.fetch', async () => {
|
|
83
|
-
const request = new node_url_1.URL(url);
|
|
84
|
-
await fetchClient.raw(request);
|
|
85
|
-
expect(fetchSpy).toHaveBeenCalledWith(request);
|
|
86
|
-
});
|
|
87
|
-
it('should pass through string to undici.fetch', async () => {
|
|
88
|
-
await fetchClient.raw(url);
|
|
89
|
-
expect(fetchSpy).toHaveBeenCalledWith(url);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
describe('#delete()', () => {
|
|
94
|
-
it('should send DELETE request', async () => {
|
|
95
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
96
|
-
sandbox.delete(TARGET_URL, 200);
|
|
97
|
-
await fetch.delete(TARGET_URL);
|
|
98
|
-
expect(sandbox.done()).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
describe('#get()', () => {
|
|
102
|
-
it('should support url search params', async () => {
|
|
103
|
-
const expectedURL = `${TARGET_URL}?test=encode+this%26`;
|
|
104
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
105
|
-
sandbox.get(expectedURL, { status: 200 });
|
|
106
|
-
await fetch.get(TARGET_URL, {
|
|
107
|
-
query: { test: 'encode this&' },
|
|
108
|
-
});
|
|
109
|
-
expect(sandbox.done()).toBe(true);
|
|
110
|
-
});
|
|
111
|
-
it('should send GET request', async () => {
|
|
112
|
-
const data = { foo: 'bar' };
|
|
113
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
114
|
-
sandbox.get(TARGET_URL, { status: 200, body: data });
|
|
115
|
-
const result = await fetch.get(TARGET_URL).json();
|
|
116
|
-
expect(result).toEqual(data);
|
|
117
|
-
expect(sandbox.done()).toBe(true);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe('#head()', () => {
|
|
121
|
-
it('should send HEAD request', async () => {
|
|
122
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
123
|
-
sandbox.head(TARGET_URL, 200);
|
|
124
|
-
await fetch.head(TARGET_URL);
|
|
125
|
-
expect(sandbox.done()).toBe(true);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
describe('#patch()', () => {
|
|
129
|
-
it('should send PATCH request', async () => {
|
|
130
|
-
const body = { foo: 'bar' };
|
|
131
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
132
|
-
sandbox.patch({ url: TARGET_URL, body, headers: JSON_HEADERS }, 200);
|
|
133
|
-
await fetch.patch(TARGET_URL, { json: body });
|
|
134
|
-
expect(sandbox.done()).toBe(true);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
describe('#post()', () => {
|
|
138
|
-
it('should send POST request', async () => {
|
|
139
|
-
const body = { foo: 'bar' };
|
|
140
|
-
const data = { fizz: 'buzz' };
|
|
141
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
142
|
-
sandbox.post({ url: TARGET_URL, body, headers: JSON_HEADERS }, { status: 200, body: data });
|
|
143
|
-
const result = await fetch.post(TARGET_URL, { json: body }).json();
|
|
144
|
-
expect(result).toEqual(data);
|
|
145
|
-
expect(sandbox.done()).toBe(true);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
describe('#put()', () => {
|
|
149
|
-
it('should send PUT request', async () => {
|
|
150
|
-
const body = { foo: 'bar' };
|
|
151
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
152
|
-
sandbox.put({ url: TARGET_URL, body, headers: JSON_HEADERS }, 200);
|
|
153
|
-
await fetch.put(TARGET_URL, { json: body });
|
|
154
|
-
expect(sandbox.done()).toBe(true);
|
|
155
|
-
});
|
|
156
|
-
});
|
|
157
|
-
describe('#config.baseURL', () => {
|
|
158
|
-
it('should prefix request URL with provided baseURL option', async () => {
|
|
159
|
-
const baseURL = 'http://example.com';
|
|
160
|
-
const path = 'foo/bar';
|
|
161
|
-
const fetch = new fetch_client_1.FetchClient(sandbox, { baseURL });
|
|
162
|
-
sandbox.get(`${baseURL}/${path}`, 200);
|
|
163
|
-
await fetch.get(path);
|
|
164
|
-
expect(sandbox.done()).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
it('should not prefix request using URL instance', async () => {
|
|
167
|
-
const url = new node_url_1.URL(TARGET_URL);
|
|
168
|
-
const fetchSpy = jest.fn().mockResolvedValue(new undici.Response());
|
|
169
|
-
const fetchClient = new fetch_client_1.FetchClient(fetchSpy, { baseURL: 'http://foo.com/' });
|
|
170
|
-
await fetchClient.get(url);
|
|
171
|
-
expect(fetchSpy).toHaveBeenCalledWith(url, { method: 'GET', headers: {}, body: undefined });
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
describe('#config.headers', () => {
|
|
175
|
-
const baseURL = 'http://example.com';
|
|
176
|
-
const path = 'foo/bar';
|
|
177
|
-
it('should join global headers with request headers', async () => {
|
|
178
|
-
const globalHeaders = { foo: 'one', bar: 'two' };
|
|
179
|
-
const headers = { bar: 'three', fizz: 'four' };
|
|
180
|
-
const fetch = new fetch_client_1.FetchClient(sandbox, { baseURL, headers: globalHeaders });
|
|
181
|
-
sandbox.get({ url: `${baseURL}/${path}`, headers: { ...globalHeaders, ...headers } }, 200);
|
|
182
|
-
await fetch.get(path, { headers });
|
|
183
|
-
expect(sandbox.done()).toBe(true);
|
|
184
|
-
});
|
|
185
|
-
it('should allow global headers to be updated asynchronously', async () => {
|
|
186
|
-
const globalHeaders = new Map([['foo', 'bar']]);
|
|
187
|
-
const fetch = new fetch_client_1.FetchClient(sandbox, { baseURL, headers: globalHeaders });
|
|
188
|
-
sandbox.get({ url: `${baseURL}/one`, headers: { foo: 'bar' } }, 200);
|
|
189
|
-
sandbox.get({ url: `${baseURL}/two`, headers: { foo: 'bar', fizz: 'buzz' } }, 200);
|
|
190
|
-
await fetch.get('one');
|
|
191
|
-
globalHeaders.set('fizz', 'buzz');
|
|
192
|
-
await fetch.get('two');
|
|
193
|
-
expect(sandbox.done()).toBe(true);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
describe('request options', () => {
|
|
197
|
-
it('should accept headers as a Map', async () => {
|
|
198
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
199
|
-
const headers = new Map([['foo', 'bar']]);
|
|
200
|
-
sandbox.get({ url: TARGET_URL, headers: { foo: 'bar' } }, 200);
|
|
201
|
-
await fetch.get(TARGET_URL, { headers });
|
|
202
|
-
expect(sandbox.done()).toBe(true);
|
|
203
|
-
});
|
|
204
|
-
it('should accept headers as an object', async () => {
|
|
205
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
206
|
-
const headers = { foo: 'bar' };
|
|
207
|
-
sandbox.get({ url: TARGET_URL, headers }, 200);
|
|
208
|
-
await fetch.get(TARGET_URL, { headers });
|
|
209
|
-
expect(sandbox.done()).toBe(true);
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
describe('error handling', () => {
|
|
213
|
-
it('should throw ClientException on non-2xx status code', async () => {
|
|
214
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
215
|
-
sandbox.head(TARGET_URL, 404);
|
|
216
|
-
await expect(fetch.head(TARGET_URL)).rejects.toBeInstanceOf(exception_1.ClientException);
|
|
217
|
-
expect(sandbox.done()).toBe(true);
|
|
218
|
-
});
|
|
219
|
-
it('should extract error details from response body', async () => {
|
|
220
|
-
const status = 500;
|
|
221
|
-
const message = 'something went wrong!';
|
|
222
|
-
const cause = 'the dog ate my homework';
|
|
223
|
-
const details = { traceID: '123' };
|
|
224
|
-
const fetch = new fetch_client_1.FetchClient(sandbox);
|
|
225
|
-
sandbox.get(TARGET_URL, { status, body: { message, cause, details } });
|
|
226
|
-
try {
|
|
227
|
-
await fetch.get(TARGET_URL);
|
|
228
|
-
fail('expected to throw ClientException');
|
|
229
|
-
}
|
|
230
|
-
catch (err) {
|
|
231
|
-
if (exception_1.ClientException.instanceOf(err)) {
|
|
232
|
-
expect(err.statusCode).toEqual(status);
|
|
233
|
-
expect(err.statusText).toEqual('Internal Server Error');
|
|
234
|
-
expect(err.message).toEqual(message);
|
|
235
|
-
expect(err.cause).toEqual(cause);
|
|
236
|
-
expect(err.details).toEqual(details);
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
fail('should be an instance of ClientException');
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
expect(sandbox.done()).toBe(true);
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.client.test.d.ts","sourceRoot":"","sources":["../../src/fetch.client.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { ClientException } from '@voiceflow/exception';
|
|
2
|
-
import fetchMock from 'fetch-mock';
|
|
3
|
-
import jestFetchMock from 'jest-fetch-mock';
|
|
4
|
-
import { URL as NodeURL } from 'node:url';
|
|
5
|
-
import * as undici from 'undici';
|
|
6
|
-
import { FetchClient } from './fetch.client.js';
|
|
7
|
-
const TARGET_URL = 'http://example.com/resource/123';
|
|
8
|
-
const JSON_HEADERS = { 'content-type': 'application/json' };
|
|
9
|
-
jestFetchMock.enableMocks();
|
|
10
|
-
describe('Fetch Client', () => {
|
|
11
|
-
let sandbox;
|
|
12
|
-
beforeAll(() => {
|
|
13
|
-
jestFetchMock.mockImplementation(async () => null);
|
|
14
|
-
});
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
sandbox = fetchMock.sandbox();
|
|
17
|
-
});
|
|
18
|
-
describe('#raw()', () => {
|
|
19
|
-
const url = 'http://example.com';
|
|
20
|
-
describe('window.fetch', () => {
|
|
21
|
-
let fetchClient;
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
fetchClient = new FetchClient();
|
|
24
|
-
});
|
|
25
|
-
it('should pass through Request instance and options to window.fetch', async () => {
|
|
26
|
-
const request = new Request(new URL(url));
|
|
27
|
-
const options = { cache: 'only-if-cached' };
|
|
28
|
-
await fetchClient.raw(request, options);
|
|
29
|
-
expect(jestFetchMock).toHaveBeenCalledWith(request, options);
|
|
30
|
-
});
|
|
31
|
-
it('should pass through URL instance to window.fetch', async () => {
|
|
32
|
-
const request = new URL(url);
|
|
33
|
-
await fetchClient.raw(request);
|
|
34
|
-
expect(jestFetchMock).toHaveBeenCalledWith(request);
|
|
35
|
-
});
|
|
36
|
-
it('should pass through string to window.fetch', async () => {
|
|
37
|
-
await fetchClient.raw(url);
|
|
38
|
-
expect(jestFetchMock).toHaveBeenCalledWith(url);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
describe('undici.fetch', () => {
|
|
42
|
-
let fetchSpy;
|
|
43
|
-
let fetchClient;
|
|
44
|
-
beforeEach(() => {
|
|
45
|
-
fetchSpy = jest.fn();
|
|
46
|
-
fetchClient = new FetchClient(fetchSpy);
|
|
47
|
-
});
|
|
48
|
-
it('should pass through Request instance and options to undici.fetch', async () => {
|
|
49
|
-
const request = new undici.Request(new NodeURL(url));
|
|
50
|
-
const options = { dispatcher: new undici.Dispatcher() };
|
|
51
|
-
await fetchClient.raw(request, options);
|
|
52
|
-
expect(fetchSpy).toHaveBeenCalledWith(request, options);
|
|
53
|
-
});
|
|
54
|
-
it('should pass through URL instance to undici.fetch', async () => {
|
|
55
|
-
const request = new NodeURL(url);
|
|
56
|
-
await fetchClient.raw(request);
|
|
57
|
-
expect(fetchSpy).toHaveBeenCalledWith(request);
|
|
58
|
-
});
|
|
59
|
-
it('should pass through string to undici.fetch', async () => {
|
|
60
|
-
await fetchClient.raw(url);
|
|
61
|
-
expect(fetchSpy).toHaveBeenCalledWith(url);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
describe('#delete()', () => {
|
|
66
|
-
it('should send DELETE request', async () => {
|
|
67
|
-
const fetch = new FetchClient(sandbox);
|
|
68
|
-
sandbox.delete(TARGET_URL, 200);
|
|
69
|
-
await fetch.delete(TARGET_URL);
|
|
70
|
-
expect(sandbox.done()).toBe(true);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
describe('#get()', () => {
|
|
74
|
-
it('should support url search params', async () => {
|
|
75
|
-
const expectedURL = `${TARGET_URL}?test=encode+this%26`;
|
|
76
|
-
const fetch = new FetchClient(sandbox);
|
|
77
|
-
sandbox.get(expectedURL, { status: 200 });
|
|
78
|
-
await fetch.get(TARGET_URL, {
|
|
79
|
-
query: { test: 'encode this&' },
|
|
80
|
-
});
|
|
81
|
-
expect(sandbox.done()).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
it('should send GET request', async () => {
|
|
84
|
-
const data = { foo: 'bar' };
|
|
85
|
-
const fetch = new FetchClient(sandbox);
|
|
86
|
-
sandbox.get(TARGET_URL, { status: 200, body: data });
|
|
87
|
-
const result = await fetch.get(TARGET_URL).json();
|
|
88
|
-
expect(result).toEqual(data);
|
|
89
|
-
expect(sandbox.done()).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
describe('#head()', () => {
|
|
93
|
-
it('should send HEAD request', async () => {
|
|
94
|
-
const fetch = new FetchClient(sandbox);
|
|
95
|
-
sandbox.head(TARGET_URL, 200);
|
|
96
|
-
await fetch.head(TARGET_URL);
|
|
97
|
-
expect(sandbox.done()).toBe(true);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
describe('#patch()', () => {
|
|
101
|
-
it('should send PATCH request', async () => {
|
|
102
|
-
const body = { foo: 'bar' };
|
|
103
|
-
const fetch = new FetchClient(sandbox);
|
|
104
|
-
sandbox.patch({ url: TARGET_URL, body, headers: JSON_HEADERS }, 200);
|
|
105
|
-
await fetch.patch(TARGET_URL, { json: body });
|
|
106
|
-
expect(sandbox.done()).toBe(true);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
describe('#post()', () => {
|
|
110
|
-
it('should send POST request', async () => {
|
|
111
|
-
const body = { foo: 'bar' };
|
|
112
|
-
const data = { fizz: 'buzz' };
|
|
113
|
-
const fetch = new FetchClient(sandbox);
|
|
114
|
-
sandbox.post({ url: TARGET_URL, body, headers: JSON_HEADERS }, { status: 200, body: data });
|
|
115
|
-
const result = await fetch.post(TARGET_URL, { json: body }).json();
|
|
116
|
-
expect(result).toEqual(data);
|
|
117
|
-
expect(sandbox.done()).toBe(true);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe('#put()', () => {
|
|
121
|
-
it('should send PUT request', async () => {
|
|
122
|
-
const body = { foo: 'bar' };
|
|
123
|
-
const fetch = new FetchClient(sandbox);
|
|
124
|
-
sandbox.put({ url: TARGET_URL, body, headers: JSON_HEADERS }, 200);
|
|
125
|
-
await fetch.put(TARGET_URL, { json: body });
|
|
126
|
-
expect(sandbox.done()).toBe(true);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
describe('#config.baseURL', () => {
|
|
130
|
-
it('should prefix request URL with provided baseURL option', async () => {
|
|
131
|
-
const baseURL = 'http://example.com';
|
|
132
|
-
const path = 'foo/bar';
|
|
133
|
-
const fetch = new FetchClient(sandbox, { baseURL });
|
|
134
|
-
sandbox.get(`${baseURL}/${path}`, 200);
|
|
135
|
-
await fetch.get(path);
|
|
136
|
-
expect(sandbox.done()).toBe(true);
|
|
137
|
-
});
|
|
138
|
-
it('should not prefix request using URL instance', async () => {
|
|
139
|
-
const url = new NodeURL(TARGET_URL);
|
|
140
|
-
const fetchSpy = jest.fn().mockResolvedValue(new undici.Response());
|
|
141
|
-
const fetchClient = new FetchClient(fetchSpy, { baseURL: 'http://foo.com/' });
|
|
142
|
-
await fetchClient.get(url);
|
|
143
|
-
expect(fetchSpy).toHaveBeenCalledWith(url, { method: 'GET', headers: {}, body: undefined });
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
describe('#config.headers', () => {
|
|
147
|
-
const baseURL = 'http://example.com';
|
|
148
|
-
const path = 'foo/bar';
|
|
149
|
-
it('should join global headers with request headers', async () => {
|
|
150
|
-
const globalHeaders = { foo: 'one', bar: 'two' };
|
|
151
|
-
const headers = { bar: 'three', fizz: 'four' };
|
|
152
|
-
const fetch = new FetchClient(sandbox, { baseURL, headers: globalHeaders });
|
|
153
|
-
sandbox.get({ url: `${baseURL}/${path}`, headers: { ...globalHeaders, ...headers } }, 200);
|
|
154
|
-
await fetch.get(path, { headers });
|
|
155
|
-
expect(sandbox.done()).toBe(true);
|
|
156
|
-
});
|
|
157
|
-
it('should allow global headers to be updated asynchronously', async () => {
|
|
158
|
-
const globalHeaders = new Map([['foo', 'bar']]);
|
|
159
|
-
const fetch = new FetchClient(sandbox, { baseURL, headers: globalHeaders });
|
|
160
|
-
sandbox.get({ url: `${baseURL}/one`, headers: { foo: 'bar' } }, 200);
|
|
161
|
-
sandbox.get({ url: `${baseURL}/two`, headers: { foo: 'bar', fizz: 'buzz' } }, 200);
|
|
162
|
-
await fetch.get('one');
|
|
163
|
-
globalHeaders.set('fizz', 'buzz');
|
|
164
|
-
await fetch.get('two');
|
|
165
|
-
expect(sandbox.done()).toBe(true);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
describe('request options', () => {
|
|
169
|
-
it('should accept headers as a Map', async () => {
|
|
170
|
-
const fetch = new FetchClient(sandbox);
|
|
171
|
-
const headers = new Map([['foo', 'bar']]);
|
|
172
|
-
sandbox.get({ url: TARGET_URL, headers: { foo: 'bar' } }, 200);
|
|
173
|
-
await fetch.get(TARGET_URL, { headers });
|
|
174
|
-
expect(sandbox.done()).toBe(true);
|
|
175
|
-
});
|
|
176
|
-
it('should accept headers as an object', async () => {
|
|
177
|
-
const fetch = new FetchClient(sandbox);
|
|
178
|
-
const headers = { foo: 'bar' };
|
|
179
|
-
sandbox.get({ url: TARGET_URL, headers }, 200);
|
|
180
|
-
await fetch.get(TARGET_URL, { headers });
|
|
181
|
-
expect(sandbox.done()).toBe(true);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
describe('error handling', () => {
|
|
185
|
-
it('should throw ClientException on non-2xx status code', async () => {
|
|
186
|
-
const fetch = new FetchClient(sandbox);
|
|
187
|
-
sandbox.head(TARGET_URL, 404);
|
|
188
|
-
await expect(fetch.head(TARGET_URL)).rejects.toBeInstanceOf(ClientException);
|
|
189
|
-
expect(sandbox.done()).toBe(true);
|
|
190
|
-
});
|
|
191
|
-
it('should extract error details from response body', async () => {
|
|
192
|
-
const status = 500;
|
|
193
|
-
const message = 'something went wrong!';
|
|
194
|
-
const cause = 'the dog ate my homework';
|
|
195
|
-
const details = { traceID: '123' };
|
|
196
|
-
const fetch = new FetchClient(sandbox);
|
|
197
|
-
sandbox.get(TARGET_URL, { status, body: { message, cause, details } });
|
|
198
|
-
try {
|
|
199
|
-
await fetch.get(TARGET_URL);
|
|
200
|
-
fail('expected to throw ClientException');
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
if (ClientException.instanceOf(err)) {
|
|
204
|
-
expect(err.statusCode).toEqual(status);
|
|
205
|
-
expect(err.statusText).toEqual('Internal Server Error');
|
|
206
|
-
expect(err.message).toEqual(message);
|
|
207
|
-
expect(err.cause).toEqual(cause);
|
|
208
|
-
expect(err.details).toEqual(details);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
fail('should be an instance of ClientException');
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
expect(sandbox.done()).toBe(true);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
});
|