api-def 0.8.4 → 0.9.0
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/LICENSE +20 -20
- package/README.md +34 -34
- package/cjs/Api.js +1 -0
- package/cjs/ApiTypes.d.ts +2 -0
- package/cjs/Endpoint.d.ts +5 -2
- package/cjs/Endpoint.js +1 -0
- package/cjs/EndpointBuilder.d.ts +6 -4
- package/cjs/EndpointBuilder.js +23 -4
- package/cjs/RequestContext.d.ts +12 -2
- package/cjs/RequestContext.js +32 -3
- package/cjs/RequestError.d.ts +5 -0
- package/cjs/RequestError.js +12 -1
- package/cjs/Requester.js +49 -6
- package/cjs/Validation.d.ts +8 -0
- package/cjs/Validation.js +2 -0
- package/cjs/backend/AxiosRequestBackend.js +1 -1
- package/cjs/backend/FetchRequestBackend.js +1 -1
- package/cjs/backend/MockRequestBackend.js +2 -2
- package/cjs/middleware/LoggingMiddleware.js +1 -2
- package/esm/Api.js +1 -0
- package/esm/ApiTypes.d.ts +2 -0
- package/esm/Endpoint.d.ts +5 -2
- package/esm/Endpoint.js +1 -0
- package/esm/EndpointBuilder.d.ts +6 -4
- package/esm/EndpointBuilder.js +12 -4
- package/esm/RequestContext.d.ts +12 -2
- package/esm/RequestContext.js +24 -3
- package/esm/RequestError.d.ts +5 -0
- package/esm/RequestError.js +12 -1
- package/esm/Requester.js +43 -0
- package/esm/Validation.d.ts +8 -0
- package/esm/Validation.js +1 -0
- package/esm/backend/AxiosRequestBackend.js +1 -1
- package/esm/backend/FetchRequestBackend.js +1 -1
- package/esm/backend/MockRequestBackend.js +2 -2
- package/esm/middleware/LoggingMiddleware.js +1 -2
- package/package.json +79 -78
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2019 James Waterhouse
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 James Waterhouse
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
# [api-def](https://github.com/Censkh/api-def/) · [](https://github.com/Censkh/api-def/blob/master/LICENSE) [](https://www.npmjs.com/package/api-def) [](https://github.com/Censkh/api-def/actions)
|
|
2
|
-
|
|
3
|
-
Typed APIs with middleware support
|
|
4
|
-
|
|
5
|
-
API def provides a unified way to type your endpoints allowing for compile time checking of query, body, response and even url parameters
|
|
6
|
-
|
|
7
|
-
``` npm i api-def ```
|
|
8
|
-
|
|
9
|
-
- [Documentation](https://censkh.github.io/api-def/)
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import {Api} from "api-def";
|
|
13
|
-
|
|
14
|
-
const api = new Api({
|
|
15
|
-
baseUrl: "https://my-api/",
|
|
16
|
-
name: "My API",
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const fetchData = api.endpoint()
|
|
20
|
-
.queryOf<{ includeAwesome: boolean; }>()
|
|
21
|
-
.responseOf<{ data: {awesome: boolean; } }>()
|
|
22
|
-
.build({
|
|
23
|
-
id: "fetch_data",
|
|
24
|
-
method: "get",
|
|
25
|
-
path: "/data"
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// calls GET https://my-api/data?includeAwesome=true
|
|
29
|
-
const res = await fetchData.submit({
|
|
30
|
-
query: {includeAwesome: true}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
console.log(res.data); // { data: { awesome: true } }
|
|
34
|
-
```
|
|
1
|
+
# [api-def](https://github.com/Censkh/api-def/) · [](https://github.com/Censkh/api-def/blob/master/LICENSE) [](https://www.npmjs.com/package/api-def) [](https://github.com/Censkh/api-def/actions)
|
|
2
|
+
|
|
3
|
+
Typed APIs with middleware support
|
|
4
|
+
|
|
5
|
+
API def provides a unified way to type your endpoints allowing for compile time checking of query, body, response and even url parameters
|
|
6
|
+
|
|
7
|
+
``` npm i api-def ```
|
|
8
|
+
|
|
9
|
+
- [Documentation](https://censkh.github.io/api-def/)
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import {Api} from "api-def";
|
|
13
|
+
|
|
14
|
+
const api = new Api({
|
|
15
|
+
baseUrl: "https://my-api/",
|
|
16
|
+
name: "My API",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const fetchData = api.endpoint()
|
|
20
|
+
.queryOf<{ includeAwesome: boolean; }>()
|
|
21
|
+
.responseOf<{ data: {awesome: boolean; } }>()
|
|
22
|
+
.build({
|
|
23
|
+
id: "fetch_data",
|
|
24
|
+
method: "get",
|
|
25
|
+
path: "/data"
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// calls GET https://my-api/data?includeAwesome=true
|
|
29
|
+
const res = await fetchData.submit({
|
|
30
|
+
query: {includeAwesome: true}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log(res.data); // { data: { awesome: true } }
|
|
34
|
+
```
|
package/cjs/Api.js
CHANGED
|
@@ -62,6 +62,7 @@ exports.setRequestBackend = setRequestBackend;
|
|
|
62
62
|
var HotRequestHost = /** @class */ (function () {
|
|
63
63
|
function HotRequestHost(api, path, method) {
|
|
64
64
|
this.responseType = undefined;
|
|
65
|
+
this.validation = {};
|
|
65
66
|
this.api = api;
|
|
66
67
|
this.method = method;
|
|
67
68
|
this.path = path;
|
package/cjs/ApiTypes.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import RequestContext from "./RequestContext";
|
|
|
2
2
|
import { Api } from "./Api";
|
|
3
3
|
import { CacheSource, EventResultType, RequestEvent, RequestMethod, ResponseType } from "./ApiConstants";
|
|
4
4
|
import RequestBackend from "./backend/RequestBackend";
|
|
5
|
+
import { Validation } from "./Validation";
|
|
5
6
|
export declare type AcceptableStatus = number | [min: number, max: number];
|
|
6
7
|
export declare type Headers = Record<string, string | number | boolean | null | undefined>;
|
|
7
8
|
export declare type Params = string;
|
|
@@ -87,6 +88,7 @@ export interface RequestHost {
|
|
|
87
88
|
readonly baseUrl: string;
|
|
88
89
|
readonly path: string;
|
|
89
90
|
readonly responseType: ResponseType | undefined;
|
|
91
|
+
readonly validation: Validation;
|
|
90
92
|
computeConfig<P extends Params | undefined, Q extends Query | undefined, B extends Body | undefined>(config: RequestConfig<P, Q, B>): ComputedRequestConfig<P, Q, B>;
|
|
91
93
|
computePath(path: string, config: RequestConfig): string;
|
|
92
94
|
getRequestBackend(): RequestBackend;
|
package/cjs/Endpoint.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { ApiResponse, BaseRequestConfig, Body, ComputedRequestConfig, Params, Qu
|
|
|
3
3
|
import * as Mocking from "./MockingTypes";
|
|
4
4
|
import { RequestMethod, ResponseType } from "./ApiConstants";
|
|
5
5
|
import RequestBackend from "./backend/RequestBackend";
|
|
6
|
-
|
|
6
|
+
import { Validation } from "./Validation";
|
|
7
|
+
export interface EndpointConfig<R, P extends Params | undefined, Q extends Query | undefined, B extends Body | undefined, Path extends string = string> {
|
|
7
8
|
readonly id: string;
|
|
8
9
|
readonly method: RequestMethod;
|
|
9
|
-
readonly path:
|
|
10
|
+
readonly path: Path;
|
|
10
11
|
/**
|
|
11
12
|
* Name your endpoint to help with debugging and documentation
|
|
12
13
|
* @default `id` is used as the name if no name is supplied
|
|
@@ -29,6 +30,7 @@ export interface EndpointConfig<R, P extends Params | undefined, Q extends Query
|
|
|
29
30
|
* Enable/disable mocked returns for all endpoints on your API object.
|
|
30
31
|
*/
|
|
31
32
|
readonly mocking?: Mocking.EndpointMockingConfig<R, P, Q, B>;
|
|
33
|
+
readonly validation?: Validation<R, P, Q, B>;
|
|
32
34
|
}
|
|
33
35
|
export default class Endpoint<R = any, P extends Params | undefined = Params | undefined, Q extends Query | undefined = Query | undefined, B extends Body | undefined = Body | undefined> implements EndpointConfig<R, P, Q, B>, RequestHost {
|
|
34
36
|
readonly api: Api;
|
|
@@ -40,6 +42,7 @@ export default class Endpoint<R = any, P extends Params | undefined = Params | u
|
|
|
40
42
|
readonly config?: BaseRequestConfig;
|
|
41
43
|
readonly responseType: ResponseType | undefined;
|
|
42
44
|
readonly mocking?: Mocking.EndpointMockingConfig<R, P, Q, B>;
|
|
45
|
+
readonly validation: Validation<R, P, Q, B>;
|
|
43
46
|
constructor(api: Api, info: EndpointConfig<R, P, Q, B>);
|
|
44
47
|
submit(config: RequestConfig<P, Q, B>): Promise<ApiResponse<R>>;
|
|
45
48
|
computePath(path: string, request: RequestConfig): string;
|
package/cjs/Endpoint.js
CHANGED
|
@@ -49,6 +49,7 @@ var Endpoint = /** @class */ (function () {
|
|
|
49
49
|
this.config = info.config;
|
|
50
50
|
this.responseType = info.responseType;
|
|
51
51
|
this.mocking = info.mocking;
|
|
52
|
+
this.validation = info.validation || {};
|
|
52
53
|
}
|
|
53
54
|
Endpoint.prototype.submit = function (config) {
|
|
54
55
|
var _a, _b;
|
package/cjs/EndpointBuilder.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Body, Params, Query } from "./ApiTypes";
|
|
2
2
|
import Endpoint, { EndpointConfig } from "./Endpoint";
|
|
3
3
|
import { Api } from "./Api";
|
|
4
|
+
import * as zod from "zod";
|
|
4
5
|
export default class EndpointBuilder<R = any, P extends Params | undefined = undefined, Q extends Query | undefined = undefined, B extends Body | undefined = undefined> {
|
|
5
6
|
private api;
|
|
7
|
+
private readonly validation;
|
|
6
8
|
constructor(api: Api);
|
|
7
|
-
queryOf<Q extends Query>(): EndpointBuilder<R, P, Q, B>;
|
|
9
|
+
queryOf<Q extends Query>(schema?: zod.Schema<Q>): EndpointBuilder<R, P, Q, B>;
|
|
8
10
|
paramsOf<P extends Params>(): EndpointBuilder<R, P, Q, B>;
|
|
9
|
-
bodyOf<B extends Body>(): EndpointBuilder<R, P, Q, B>;
|
|
10
|
-
responseOf<R>(): EndpointBuilder<R, P, Q, B>;
|
|
11
|
-
build(config: EndpointConfig<R, P, Q, B>): Endpoint<R, P, Q, B>;
|
|
11
|
+
bodyOf<B extends Body>(schema?: zod.Schema<B>): EndpointBuilder<R, P, Q, B>;
|
|
12
|
+
responseOf<R>(schema?: zod.Schema<R>): EndpointBuilder<R, P, Q, B>;
|
|
13
|
+
build<Path extends string>(config: Omit<EndpointConfig<R, P, Q, B, Path>, "validation">): Endpoint<R, P, Q, B>;
|
|
12
14
|
}
|
package/cjs/EndpointBuilder.js
CHANGED
|
@@ -1,24 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
2
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
14
|
var Endpoint_1 = require("./Endpoint");
|
|
15
|
+
/*type ExtractParams<Path> = Path extends `${infer Segment}/${infer Rest}`
|
|
16
|
+
? Segment extends `:${infer Param}` ? Param | ExtractParams<Rest> : ExtractParams<Rest>
|
|
17
|
+
: Path extends `:${infer Param}` ? Param : undefined;
|
|
18
|
+
*/
|
|
4
19
|
var EndpointBuilder = /** @class */ (function () {
|
|
5
20
|
function EndpointBuilder(api) {
|
|
21
|
+
this.validation = {};
|
|
6
22
|
this.api = api;
|
|
7
23
|
}
|
|
8
|
-
EndpointBuilder.prototype.queryOf = function () {
|
|
24
|
+
EndpointBuilder.prototype.queryOf = function (schema) {
|
|
25
|
+
this.validation.query = schema;
|
|
9
26
|
return this;
|
|
10
27
|
};
|
|
11
28
|
EndpointBuilder.prototype.paramsOf = function () {
|
|
12
29
|
return this;
|
|
13
30
|
};
|
|
14
|
-
EndpointBuilder.prototype.bodyOf = function () {
|
|
31
|
+
EndpointBuilder.prototype.bodyOf = function (schema) {
|
|
32
|
+
this.validation.body = schema;
|
|
15
33
|
return this;
|
|
16
34
|
};
|
|
17
|
-
EndpointBuilder.prototype.responseOf = function () {
|
|
35
|
+
EndpointBuilder.prototype.responseOf = function (schema) {
|
|
36
|
+
this.validation.response = schema;
|
|
18
37
|
return this;
|
|
19
38
|
};
|
|
20
39
|
EndpointBuilder.prototype.build = function (config) {
|
|
21
|
-
var endpoint = new Endpoint_1.default(this.api, config);
|
|
40
|
+
var endpoint = new Endpoint_1.default(this.api, __assign(__assign({}, config), { validation: this.validation }));
|
|
22
41
|
this.api.endpoints[endpoint.id] = endpoint;
|
|
23
42
|
return endpoint;
|
|
24
43
|
};
|
package/cjs/RequestContext.d.ts
CHANGED
|
@@ -4,10 +4,13 @@ import { RequestEvent, RequestMethod, ResponseType } from "./ApiConstants";
|
|
|
4
4
|
import { EndpointMockingConfig } from "./MockingTypes";
|
|
5
5
|
import { RequestError } from "./RequestError";
|
|
6
6
|
import RequestBackend from "./backend/RequestBackend";
|
|
7
|
+
import { Validation } from "./Validation";
|
|
7
8
|
export default class RequestContext<R = any, P extends Params | undefined = Params | undefined, Q extends Query | undefined = Query | undefined, B extends Body | undefined = Body | undefined> {
|
|
8
9
|
readonly id: number;
|
|
9
10
|
readonly key: string;
|
|
10
|
-
|
|
11
|
+
private computedPath;
|
|
12
|
+
private computedBaseUrl;
|
|
13
|
+
private computedMethod;
|
|
11
14
|
readonly stats: RequestContextStats;
|
|
12
15
|
private readonly host;
|
|
13
16
|
readonly eventHandlers: RequestEventHandlers<R>;
|
|
@@ -18,8 +21,10 @@ export default class RequestContext<R = any, P extends Params | undefined = Para
|
|
|
18
21
|
readonly cacheInfo: RequestCacheInfo;
|
|
19
22
|
cancelled: boolean;
|
|
20
23
|
readonly computedConfig: ComputedRequestConfig<P, Q, B>;
|
|
24
|
+
private computedRequestUrl;
|
|
21
25
|
readonly mocking: EndpointMockingConfig<R, P, Q, B> | null | undefined;
|
|
22
26
|
private parsedBody;
|
|
27
|
+
readonly validation: Validation<R, P, Q, B>;
|
|
23
28
|
constructor(backend: RequestBackend, host: RequestHost, config: ComputedRequestConfig<P, Q, B>, computedPath: string, mocking: EndpointMockingConfig<R, P, Q, B> | null | undefined);
|
|
24
29
|
get method(): RequestMethod;
|
|
25
30
|
get api(): Api;
|
|
@@ -34,5 +39,10 @@ export default class RequestContext<R = any, P extends Params | undefined = Para
|
|
|
34
39
|
triggerEvent(eventType: RequestEvent): Promise<EventResult<R> | undefined>;
|
|
35
40
|
addCanceller(canceler: () => void): void;
|
|
36
41
|
cancel(): void;
|
|
37
|
-
|
|
42
|
+
get requestUrl(): URL;
|
|
43
|
+
get path(): string;
|
|
44
|
+
updatePath(path: string): void;
|
|
45
|
+
updateBaseUrl(baseUrl: string): void;
|
|
46
|
+
updateMethod(method: RequestMethod): void;
|
|
47
|
+
private generateRequestUrl;
|
|
38
48
|
}
|
package/cjs/RequestContext.js
CHANGED
|
@@ -51,6 +51,8 @@ var RequestContext = /** @class */ (function () {
|
|
|
51
51
|
this.computedConfig = config;
|
|
52
52
|
Utils.assign({}, this.computedConfig.headers);
|
|
53
53
|
this.computedPath = computedPath;
|
|
54
|
+
this.computedBaseUrl = host.baseUrl;
|
|
55
|
+
this.computedMethod = host.method;
|
|
54
56
|
this.key = this.generateKey();
|
|
55
57
|
this.stats = {
|
|
56
58
|
attempt: 0,
|
|
@@ -58,12 +60,14 @@ var RequestContext = /** @class */ (function () {
|
|
|
58
60
|
};
|
|
59
61
|
this.eventHandlers = {};
|
|
60
62
|
this.mocking = mocking;
|
|
63
|
+
this.validation = host.validation;
|
|
61
64
|
this.initMiddleware();
|
|
62
65
|
this.parseRequestBody();
|
|
66
|
+
this.computedRequestUrl = this.generateRequestUrl();
|
|
63
67
|
}
|
|
64
68
|
Object.defineProperty(RequestContext.prototype, "method", {
|
|
65
69
|
get: function () {
|
|
66
|
-
return this.
|
|
70
|
+
return this.computedMethod;
|
|
67
71
|
},
|
|
68
72
|
enumerable: false,
|
|
69
73
|
configurable: true
|
|
@@ -77,7 +81,7 @@ var RequestContext = /** @class */ (function () {
|
|
|
77
81
|
});
|
|
78
82
|
Object.defineProperty(RequestContext.prototype, "baseUrl", {
|
|
79
83
|
get: function () {
|
|
80
|
-
return this.
|
|
84
|
+
return this.computedBaseUrl;
|
|
81
85
|
},
|
|
82
86
|
enumerable: false,
|
|
83
87
|
configurable: true
|
|
@@ -182,7 +186,32 @@ var RequestContext = /** @class */ (function () {
|
|
|
182
186
|
this.canceler();
|
|
183
187
|
}
|
|
184
188
|
};
|
|
185
|
-
RequestContext.prototype
|
|
189
|
+
Object.defineProperty(RequestContext.prototype, "requestUrl", {
|
|
190
|
+
get: function () {
|
|
191
|
+
return this.computedRequestUrl;
|
|
192
|
+
},
|
|
193
|
+
enumerable: false,
|
|
194
|
+
configurable: true
|
|
195
|
+
});
|
|
196
|
+
Object.defineProperty(RequestContext.prototype, "path", {
|
|
197
|
+
get: function () {
|
|
198
|
+
return this.computedPath;
|
|
199
|
+
},
|
|
200
|
+
enumerable: false,
|
|
201
|
+
configurable: true
|
|
202
|
+
});
|
|
203
|
+
RequestContext.prototype.updatePath = function (path) {
|
|
204
|
+
this.computedPath = path;
|
|
205
|
+
this.computedRequestUrl = this.generateRequestUrl();
|
|
206
|
+
};
|
|
207
|
+
RequestContext.prototype.updateBaseUrl = function (baseUrl) {
|
|
208
|
+
this.computedBaseUrl = baseUrl;
|
|
209
|
+
this.computedRequestUrl = this.generateRequestUrl();
|
|
210
|
+
};
|
|
211
|
+
RequestContext.prototype.updateMethod = function (method) {
|
|
212
|
+
this.computedMethod = method;
|
|
213
|
+
};
|
|
214
|
+
RequestContext.prototype.generateRequestUrl = function () {
|
|
186
215
|
var path = !this.baseUrl.endsWith("/")
|
|
187
216
|
? this.baseUrl + "/"
|
|
188
217
|
: this.baseUrl;
|
package/cjs/RequestError.d.ts
CHANGED
|
@@ -4,9 +4,14 @@ import RequestContext from "./RequestContext";
|
|
|
4
4
|
export declare const RequestErrorCode: {
|
|
5
5
|
readonly MISC_UNKNOWN_ERROR: "misc/unknown-error";
|
|
6
6
|
readonly REQUEST_NETWORK_ERROR: "request/network-error";
|
|
7
|
+
readonly REQUEST_HOST_NAME_NOT_FOUND: "request/host-name-not-found";
|
|
7
8
|
readonly REQUEST_INVALID_STATUS: "request/invalid-status";
|
|
8
9
|
readonly REQUEST_INVALID_CONFIG: "request/invalid-config";
|
|
9
10
|
readonly REQUEST_MISMATCH_RESPONSE_TYPE: "request/mismatch-response-type";
|
|
11
|
+
readonly VALIDATION_QUERY_VALIDATE_ERROR: "validation/query-validate-error";
|
|
12
|
+
readonly VALIDATION_PARAMS_VALIDATE_ERROR: "validation/params-validate-error";
|
|
13
|
+
readonly VALIDATION_BODY_VALIDATE_ERROR: "validation/body-validate-error";
|
|
14
|
+
readonly VALIDATION_RESPONSE_VALIDATE_ERROR: "validation/response-validate-error";
|
|
10
15
|
};
|
|
11
16
|
export declare type RequestErrorCode = EnumOf<typeof RequestErrorCode>;
|
|
12
17
|
export interface RequestError extends Error {
|
package/cjs/RequestError.js
CHANGED
|
@@ -4,9 +4,14 @@ exports.convertToRequestError = exports.isRequestError = exports.RequestErrorCod
|
|
|
4
4
|
exports.RequestErrorCode = {
|
|
5
5
|
MISC_UNKNOWN_ERROR: "misc/unknown-error",
|
|
6
6
|
REQUEST_NETWORK_ERROR: "request/network-error",
|
|
7
|
+
REQUEST_HOST_NAME_NOT_FOUND: "request/host-name-not-found",
|
|
7
8
|
REQUEST_INVALID_STATUS: "request/invalid-status",
|
|
8
9
|
REQUEST_INVALID_CONFIG: "request/invalid-config",
|
|
9
10
|
REQUEST_MISMATCH_RESPONSE_TYPE: "request/mismatch-response-type",
|
|
11
|
+
VALIDATION_QUERY_VALIDATE_ERROR: "validation/query-validate-error",
|
|
12
|
+
VALIDATION_PARAMS_VALIDATE_ERROR: "validation/params-validate-error",
|
|
13
|
+
VALIDATION_BODY_VALIDATE_ERROR: "validation/body-validate-error",
|
|
14
|
+
VALIDATION_RESPONSE_VALIDATE_ERROR: "validation/response-validate-error",
|
|
10
15
|
};
|
|
11
16
|
var isRequestError = function (error) {
|
|
12
17
|
return "isRequestError" in error;
|
|
@@ -22,12 +27,18 @@ var convertToRequestError = function (config) {
|
|
|
22
27
|
isRequestError: true,
|
|
23
28
|
attempts: context.stats.attempt,
|
|
24
29
|
request: {
|
|
25
|
-
url: context.
|
|
30
|
+
url: context.requestUrl.href,
|
|
26
31
|
query: context.computedConfig.queryObject,
|
|
27
32
|
headers: context.computedConfig.headers,
|
|
28
33
|
body: body,
|
|
29
34
|
},
|
|
30
35
|
});
|
|
36
|
+
try {
|
|
37
|
+
Object.defineProperty(resultError, "message", { value: "Request failed".concat((response === null || response === void 0 ? void 0 : response.status) ? " with status code ".concat(response.status) : "", " (").concat(code, ")") });
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
// ignore
|
|
41
|
+
}
|
|
31
42
|
delete resultError.config;
|
|
32
43
|
delete resultError.toJSON;
|
|
33
44
|
Object.setPrototypeOf(error, Error);
|
package/cjs/Requester.js
CHANGED
|
@@ -125,6 +125,31 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
|
|
|
125
125
|
beforeSendEventResult.type === ApiConstants_1.EventResultType.Respond) {
|
|
126
126
|
return [2 /*return*/, (context.response = beforeSendEventResult.response)];
|
|
127
127
|
}
|
|
128
|
+
// validation
|
|
129
|
+
if (context.validation.query) {
|
|
130
|
+
try {
|
|
131
|
+
context.computedConfig.queryObject = context.validation.query.parse(context.computedConfig.queryObject);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
throw (0, RequestError_1.convertToRequestError)({
|
|
135
|
+
error: error,
|
|
136
|
+
code: RequestError_1.RequestErrorCode.VALIDATION_QUERY_VALIDATE_ERROR,
|
|
137
|
+
context: context,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (context.validation.body) {
|
|
142
|
+
try {
|
|
143
|
+
context.computedConfig.body = context.validation.body.parse(context.computedConfig.body);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
throw (0, RequestError_1.convertToRequestError)({
|
|
147
|
+
error: error,
|
|
148
|
+
code: RequestError_1.RequestErrorCode.VALIDATION_BODY_VALIDATE_ERROR,
|
|
149
|
+
context: context,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
128
153
|
retryOptions = parseRetryOptions((_a = context.computedConfig) === null || _a === void 0 ? void 0 : _a.retry);
|
|
129
154
|
internalRetryOptions = {
|
|
130
155
|
retries: retryOptions.maxAttempts,
|
|
@@ -202,6 +227,18 @@ var makeRequest = function (context) { return __awaiter(void 0, void 0, void 0,
|
|
|
202
227
|
return [4 /*yield*/, (0, retry_1.default)(performRequest, internalRetryOptions)];
|
|
203
228
|
case 2:
|
|
204
229
|
response = _d.sent();
|
|
230
|
+
if (context.validation.response) {
|
|
231
|
+
try {
|
|
232
|
+
response.data = context.validation.response.parse(response.data);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
throw (0, RequestError_1.convertToRequestError)({
|
|
236
|
+
error: error,
|
|
237
|
+
code: RequestError_1.RequestErrorCode.VALIDATION_RESPONSE_VALIDATE_ERROR,
|
|
238
|
+
context: context,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
205
242
|
return [2 /*return*/, (response)];
|
|
206
243
|
}
|
|
207
244
|
});
|
|
@@ -262,21 +299,22 @@ var parseResponse = function (context, response, error) { return __awaiter(void
|
|
|
262
299
|
}); };
|
|
263
300
|
var parseError = function (context, rawError) { return __awaiter(void 0, void 0, void 0, function () {
|
|
264
301
|
var error, extractedResponse, errorResponse, code, errorInfo;
|
|
265
|
-
|
|
266
|
-
|
|
302
|
+
var _a;
|
|
303
|
+
return __generator(this, function (_b) {
|
|
304
|
+
switch (_b.label) {
|
|
267
305
|
case 0:
|
|
268
306
|
if (!(0, RequestError_1.isRequestError)(rawError)) return [3 /*break*/, 1];
|
|
269
307
|
error = rawError;
|
|
270
308
|
return [3 /*break*/, 5];
|
|
271
309
|
case 1: return [4 /*yield*/, context.backend.extractResponseFromError(rawError)];
|
|
272
310
|
case 2:
|
|
273
|
-
extractedResponse =
|
|
311
|
+
extractedResponse = _b.sent();
|
|
274
312
|
errorResponse = undefined;
|
|
275
313
|
if (!(extractedResponse !== undefined)) return [3 /*break*/, 4];
|
|
276
314
|
return [4 /*yield*/, parseResponse(context, extractedResponse, true)];
|
|
277
315
|
case 3:
|
|
278
|
-
errorResponse =
|
|
279
|
-
|
|
316
|
+
errorResponse = _b.sent();
|
|
317
|
+
_b.label = 4;
|
|
280
318
|
case 4:
|
|
281
319
|
code = (0, ApiUtils_1.isNetworkError)(rawError) ? RequestError_1.RequestErrorCode.REQUEST_NETWORK_ERROR : RequestError_1.RequestErrorCode.MISC_UNKNOWN_ERROR;
|
|
282
320
|
if (errorResponse) {
|
|
@@ -284,9 +322,14 @@ var parseError = function (context, rawError) { return __awaiter(void 0, void 0,
|
|
|
284
322
|
code = RequestError_1.RequestErrorCode.REQUEST_INVALID_STATUS;
|
|
285
323
|
}
|
|
286
324
|
}
|
|
325
|
+
else {
|
|
326
|
+
if (rawError.code === "ENOTFOUND" || ((_a = rawError.cause) === null || _a === void 0 ? void 0 : _a.code) === "ENOTFOUND") {
|
|
327
|
+
code = RequestError_1.RequestErrorCode.REQUEST_HOST_NAME_NOT_FOUND;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
287
330
|
errorInfo = context.backend.getErrorInfo(rawError, errorResponse);
|
|
288
331
|
error = (0, RequestError_1.convertToRequestError)(__assign({ error: rawError, response: errorResponse, code: code, context: context }, errorInfo));
|
|
289
|
-
|
|
332
|
+
_b.label = 5;
|
|
290
333
|
case 5: return [2 /*return*/, error];
|
|
291
334
|
}
|
|
292
335
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as zod from "zod";
|
|
2
|
+
import { Body, Params, Query } from "./ApiTypes";
|
|
3
|
+
export interface Validation<R = any, P extends Params | undefined = Params | undefined, Q extends Query | undefined = Query | undefined, B extends Body | undefined = Body | undefined> {
|
|
4
|
+
query?: zod.Schema<Q>;
|
|
5
|
+
params?: zod.Schema<P>;
|
|
6
|
+
body?: zod.Schema<B>;
|
|
7
|
+
response?: zod.Schema<R>;
|
|
8
|
+
}
|
|
@@ -74,7 +74,7 @@ var AxiosRequestBackend = /** @class */ (function () {
|
|
|
74
74
|
};
|
|
75
75
|
AxiosRequestBackend.prototype.makeRequest = function (context) {
|
|
76
76
|
var computedConfig = context.computedConfig;
|
|
77
|
-
var url = context.
|
|
77
|
+
var url = context.requestUrl;
|
|
78
78
|
var canceler = null;
|
|
79
79
|
var promise = axios({
|
|
80
80
|
method: context.method,
|
|
@@ -161,7 +161,7 @@ var FetchRequestBackend = /** @class */ (function () {
|
|
|
161
161
|
}
|
|
162
162
|
return parsedHeaders;
|
|
163
163
|
}, {});
|
|
164
|
-
var url = context.
|
|
164
|
+
var url = context.requestUrl;
|
|
165
165
|
var promise = this.fetch(url.href, {
|
|
166
166
|
method: context.method.toUpperCase(),
|
|
167
167
|
body: bodyJsonify ? JSON.stringify(body) : body,
|
|
@@ -82,7 +82,7 @@ var MockRequestBackend = /** @class */ (function () {
|
|
|
82
82
|
params: (_b = context.computedConfig.params) !== null && _b !== void 0 ? _b : {},
|
|
83
83
|
query: context.computedConfig.queryObject,
|
|
84
84
|
headers: (_c = context.computedConfig.headers) !== null && _c !== void 0 ? _c : {},
|
|
85
|
-
url: context.
|
|
85
|
+
url: context.requestUrl.toString(),
|
|
86
86
|
};
|
|
87
87
|
res = {
|
|
88
88
|
statusCode: -1,
|
|
@@ -140,7 +140,7 @@ var MockRequestBackend = /** @class */ (function () {
|
|
|
140
140
|
return parsedHeaders;
|
|
141
141
|
}, {});
|
|
142
142
|
return [2 /*return*/, {
|
|
143
|
-
url: context.
|
|
143
|
+
url: context.requestUrl.href,
|
|
144
144
|
method: context.method,
|
|
145
145
|
headers: parsedHeaders,
|
|
146
146
|
data: res.response,
|
|
@@ -41,11 +41,10 @@ var log = function (context, type, message, config, objects) {
|
|
|
41
41
|
if (typeof config.predicate === "function" && !config.predicate()) {
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
var computedPath = context.computedPath;
|
|
45
44
|
var color = COLOR_MAP[type];
|
|
46
45
|
var timestamp = formatTime(new Date());
|
|
47
46
|
var args = [
|
|
48
|
-
"%cnetwork %c[".concat(context.api.name, "] ").concat(context.method.toUpperCase(), " ").concat(
|
|
47
|
+
"%cnetwork %c[".concat(context.api.name, "] ").concat(context.method.toUpperCase(), " ").concat(context.path, " %c").concat(message, " %c@ ").concat(timestamp),
|
|
49
48
|
"color:gray",
|
|
50
49
|
"color:auto",
|
|
51
50
|
"color:".concat(color),
|
package/esm/Api.js
CHANGED
package/esm/ApiTypes.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import RequestContext from "./RequestContext";
|
|
|
2
2
|
import { Api } from "./Api";
|
|
3
3
|
import { CacheSource, EventResultType, RequestEvent, RequestMethod, ResponseType } from "./ApiConstants";
|
|
4
4
|
import RequestBackend from "./backend/RequestBackend";
|
|
5
|
+
import { Validation } from "./Validation";
|
|
5
6
|
export declare type AcceptableStatus = number | [min: number, max: number];
|
|
6
7
|
export declare type Headers = Record<string, string | number | boolean | null | undefined>;
|
|
7
8
|
export declare type Params = string;
|
|
@@ -87,6 +88,7 @@ export interface RequestHost {
|
|
|
87
88
|
readonly baseUrl: string;
|
|
88
89
|
readonly path: string;
|
|
89
90
|
readonly responseType: ResponseType | undefined;
|
|
91
|
+
readonly validation: Validation;
|
|
90
92
|
computeConfig<P extends Params | undefined, Q extends Query | undefined, B extends Body | undefined>(config: RequestConfig<P, Q, B>): ComputedRequestConfig<P, Q, B>;
|
|
91
93
|
computePath(path: string, config: RequestConfig): string;
|
|
92
94
|
getRequestBackend(): RequestBackend;
|
package/esm/Endpoint.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ import { ApiResponse, BaseRequestConfig, Body, ComputedRequestConfig, Params, Qu
|
|
|
3
3
|
import * as Mocking from "./MockingTypes";
|
|
4
4
|
import { RequestMethod, ResponseType } from "./ApiConstants";
|
|
5
5
|
import RequestBackend from "./backend/RequestBackend";
|
|
6
|
-
|
|
6
|
+
import { Validation } from "./Validation";
|
|
7
|
+
export interface EndpointConfig<R, P extends Params | undefined, Q extends Query | undefined, B extends Body | undefined, Path extends string = string> {
|
|
7
8
|
readonly id: string;
|
|
8
9
|
readonly method: RequestMethod;
|
|
9
|
-
readonly path:
|
|
10
|
+
readonly path: Path;
|
|
10
11
|
/**
|
|
11
12
|
* Name your endpoint to help with debugging and documentation
|
|
12
13
|
* @default `id` is used as the name if no name is supplied
|
|
@@ -29,6 +30,7 @@ export interface EndpointConfig<R, P extends Params | undefined, Q extends Query
|
|
|
29
30
|
* Enable/disable mocked returns for all endpoints on your API object.
|
|
30
31
|
*/
|
|
31
32
|
readonly mocking?: Mocking.EndpointMockingConfig<R, P, Q, B>;
|
|
33
|
+
readonly validation?: Validation<R, P, Q, B>;
|
|
32
34
|
}
|
|
33
35
|
export default class Endpoint<R = any, P extends Params | undefined = Params | undefined, Q extends Query | undefined = Query | undefined, B extends Body | undefined = Body | undefined> implements EndpointConfig<R, P, Q, B>, RequestHost {
|
|
34
36
|
readonly api: Api;
|
|
@@ -40,6 +42,7 @@ export default class Endpoint<R = any, P extends Params | undefined = Params | u
|
|
|
40
42
|
readonly config?: BaseRequestConfig;
|
|
41
43
|
readonly responseType: ResponseType | undefined;
|
|
42
44
|
readonly mocking?: Mocking.EndpointMockingConfig<R, P, Q, B>;
|
|
45
|
+
readonly validation: Validation<R, P, Q, B>;
|
|
43
46
|
constructor(api: Api, info: EndpointConfig<R, P, Q, B>);
|
|
44
47
|
submit(config: RequestConfig<P, Q, B>): Promise<ApiResponse<R>>;
|
|
45
48
|
computePath(path: string, request: RequestConfig): string;
|
package/esm/Endpoint.js
CHANGED
package/esm/EndpointBuilder.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Body, Params, Query } from "./ApiTypes";
|
|
2
2
|
import Endpoint, { EndpointConfig } from "./Endpoint";
|
|
3
3
|
import { Api } from "./Api";
|
|
4
|
+
import * as zod from "zod";
|
|
4
5
|
export default class EndpointBuilder<R = any, P extends Params | undefined = undefined, Q extends Query | undefined = undefined, B extends Body | undefined = undefined> {
|
|
5
6
|
private api;
|
|
7
|
+
private readonly validation;
|
|
6
8
|
constructor(api: Api);
|
|
7
|
-
queryOf<Q extends Query>(): EndpointBuilder<R, P, Q, B>;
|
|
9
|
+
queryOf<Q extends Query>(schema?: zod.Schema<Q>): EndpointBuilder<R, P, Q, B>;
|
|
8
10
|
paramsOf<P extends Params>(): EndpointBuilder<R, P, Q, B>;
|
|
9
|
-
bodyOf<B extends Body>(): EndpointBuilder<R, P, Q, B>;
|
|
10
|
-
responseOf<R>(): EndpointBuilder<R, P, Q, B>;
|
|
11
|
-
build(config: EndpointConfig<R, P, Q, B>): Endpoint<R, P, Q, B>;
|
|
11
|
+
bodyOf<B extends Body>(schema?: zod.Schema<B>): EndpointBuilder<R, P, Q, B>;
|
|
12
|
+
responseOf<R>(schema?: zod.Schema<R>): EndpointBuilder<R, P, Q, B>;
|
|
13
|
+
build<Path extends string>(config: Omit<EndpointConfig<R, P, Q, B, Path>, "validation">): Endpoint<R, P, Q, B>;
|
|
12
14
|
}
|
package/esm/EndpointBuilder.js
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
import Endpoint from "./Endpoint";
|
|
2
|
+
/*type ExtractParams<Path> = Path extends `${infer Segment}/${infer Rest}`
|
|
3
|
+
? Segment extends `:${infer Param}` ? Param | ExtractParams<Rest> : ExtractParams<Rest>
|
|
4
|
+
: Path extends `:${infer Param}` ? Param : undefined;
|
|
5
|
+
*/
|
|
2
6
|
export default class EndpointBuilder {
|
|
3
7
|
constructor(api) {
|
|
8
|
+
this.validation = {};
|
|
4
9
|
this.api = api;
|
|
5
10
|
}
|
|
6
|
-
queryOf() {
|
|
11
|
+
queryOf(schema) {
|
|
12
|
+
this.validation.query = schema;
|
|
7
13
|
return this;
|
|
8
14
|
}
|
|
9
15
|
paramsOf() {
|
|
10
16
|
return this;
|
|
11
17
|
}
|
|
12
|
-
bodyOf() {
|
|
18
|
+
bodyOf(schema) {
|
|
19
|
+
this.validation.body = schema;
|
|
13
20
|
return this;
|
|
14
21
|
}
|
|
15
|
-
responseOf() {
|
|
22
|
+
responseOf(schema) {
|
|
23
|
+
this.validation.response = schema;
|
|
16
24
|
return this;
|
|
17
25
|
}
|
|
18
26
|
build(config) {
|
|
19
|
-
const endpoint = new Endpoint(this.api, config);
|
|
27
|
+
const endpoint = new Endpoint(this.api, Object.assign(Object.assign({}, config), { validation: this.validation }));
|
|
20
28
|
this.api.endpoints[endpoint.id] = endpoint;
|
|
21
29
|
return endpoint;
|
|
22
30
|
}
|
package/esm/RequestContext.d.ts
CHANGED
|
@@ -4,10 +4,13 @@ import { RequestEvent, RequestMethod, ResponseType } from "./ApiConstants";
|
|
|
4
4
|
import { EndpointMockingConfig } from "./MockingTypes";
|
|
5
5
|
import { RequestError } from "./RequestError";
|
|
6
6
|
import RequestBackend from "./backend/RequestBackend";
|
|
7
|
+
import { Validation } from "./Validation";
|
|
7
8
|
export default class RequestContext<R = any, P extends Params | undefined = Params | undefined, Q extends Query | undefined = Query | undefined, B extends Body | undefined = Body | undefined> {
|
|
8
9
|
readonly id: number;
|
|
9
10
|
readonly key: string;
|
|
10
|
-
|
|
11
|
+
private computedPath;
|
|
12
|
+
private computedBaseUrl;
|
|
13
|
+
private computedMethod;
|
|
11
14
|
readonly stats: RequestContextStats;
|
|
12
15
|
private readonly host;
|
|
13
16
|
readonly eventHandlers: RequestEventHandlers<R>;
|
|
@@ -18,8 +21,10 @@ export default class RequestContext<R = any, P extends Params | undefined = Para
|
|
|
18
21
|
readonly cacheInfo: RequestCacheInfo;
|
|
19
22
|
cancelled: boolean;
|
|
20
23
|
readonly computedConfig: ComputedRequestConfig<P, Q, B>;
|
|
24
|
+
private computedRequestUrl;
|
|
21
25
|
readonly mocking: EndpointMockingConfig<R, P, Q, B> | null | undefined;
|
|
22
26
|
private parsedBody;
|
|
27
|
+
readonly validation: Validation<R, P, Q, B>;
|
|
23
28
|
constructor(backend: RequestBackend, host: RequestHost, config: ComputedRequestConfig<P, Q, B>, computedPath: string, mocking: EndpointMockingConfig<R, P, Q, B> | null | undefined);
|
|
24
29
|
get method(): RequestMethod;
|
|
25
30
|
get api(): Api;
|
|
@@ -34,5 +39,10 @@ export default class RequestContext<R = any, P extends Params | undefined = Para
|
|
|
34
39
|
triggerEvent(eventType: RequestEvent): Promise<EventResult<R> | undefined>;
|
|
35
40
|
addCanceller(canceler: () => void): void;
|
|
36
41
|
cancel(): void;
|
|
37
|
-
|
|
42
|
+
get requestUrl(): URL;
|
|
43
|
+
get path(): string;
|
|
44
|
+
updatePath(path: string): void;
|
|
45
|
+
updateBaseUrl(baseUrl: string): void;
|
|
46
|
+
updateMethod(method: RequestMethod): void;
|
|
47
|
+
private generateRequestUrl;
|
|
38
48
|
}
|
package/esm/RequestContext.js
CHANGED
|
@@ -22,6 +22,8 @@ export default class RequestContext {
|
|
|
22
22
|
this.computedConfig = config;
|
|
23
23
|
Utils.assign({}, this.computedConfig.headers);
|
|
24
24
|
this.computedPath = computedPath;
|
|
25
|
+
this.computedBaseUrl = host.baseUrl;
|
|
26
|
+
this.computedMethod = host.method;
|
|
25
27
|
this.key = this.generateKey();
|
|
26
28
|
this.stats = {
|
|
27
29
|
attempt: 0,
|
|
@@ -29,17 +31,19 @@ export default class RequestContext {
|
|
|
29
31
|
};
|
|
30
32
|
this.eventHandlers = {};
|
|
31
33
|
this.mocking = mocking;
|
|
34
|
+
this.validation = host.validation;
|
|
32
35
|
this.initMiddleware();
|
|
33
36
|
this.parseRequestBody();
|
|
37
|
+
this.computedRequestUrl = this.generateRequestUrl();
|
|
34
38
|
}
|
|
35
39
|
get method() {
|
|
36
|
-
return this.
|
|
40
|
+
return this.computedMethod;
|
|
37
41
|
}
|
|
38
42
|
get api() {
|
|
39
43
|
return this.host.api;
|
|
40
44
|
}
|
|
41
45
|
get baseUrl() {
|
|
42
|
-
return this.
|
|
46
|
+
return this.computedBaseUrl;
|
|
43
47
|
}
|
|
44
48
|
get responseType() {
|
|
45
49
|
return this.host.responseType;
|
|
@@ -124,7 +128,24 @@ export default class RequestContext {
|
|
|
124
128
|
this.canceler();
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
|
-
|
|
131
|
+
get requestUrl() {
|
|
132
|
+
return this.computedRequestUrl;
|
|
133
|
+
}
|
|
134
|
+
get path() {
|
|
135
|
+
return this.computedPath;
|
|
136
|
+
}
|
|
137
|
+
updatePath(path) {
|
|
138
|
+
this.computedPath = path;
|
|
139
|
+
this.computedRequestUrl = this.generateRequestUrl();
|
|
140
|
+
}
|
|
141
|
+
updateBaseUrl(baseUrl) {
|
|
142
|
+
this.computedBaseUrl = baseUrl;
|
|
143
|
+
this.computedRequestUrl = this.generateRequestUrl();
|
|
144
|
+
}
|
|
145
|
+
updateMethod(method) {
|
|
146
|
+
this.computedMethod = method;
|
|
147
|
+
}
|
|
148
|
+
generateRequestUrl() {
|
|
128
149
|
let path = !this.baseUrl.endsWith("/")
|
|
129
150
|
? this.baseUrl + "/"
|
|
130
151
|
: this.baseUrl;
|
package/esm/RequestError.d.ts
CHANGED
|
@@ -4,9 +4,14 @@ import RequestContext from "./RequestContext";
|
|
|
4
4
|
export declare const RequestErrorCode: {
|
|
5
5
|
readonly MISC_UNKNOWN_ERROR: "misc/unknown-error";
|
|
6
6
|
readonly REQUEST_NETWORK_ERROR: "request/network-error";
|
|
7
|
+
readonly REQUEST_HOST_NAME_NOT_FOUND: "request/host-name-not-found";
|
|
7
8
|
readonly REQUEST_INVALID_STATUS: "request/invalid-status";
|
|
8
9
|
readonly REQUEST_INVALID_CONFIG: "request/invalid-config";
|
|
9
10
|
readonly REQUEST_MISMATCH_RESPONSE_TYPE: "request/mismatch-response-type";
|
|
11
|
+
readonly VALIDATION_QUERY_VALIDATE_ERROR: "validation/query-validate-error";
|
|
12
|
+
readonly VALIDATION_PARAMS_VALIDATE_ERROR: "validation/params-validate-error";
|
|
13
|
+
readonly VALIDATION_BODY_VALIDATE_ERROR: "validation/body-validate-error";
|
|
14
|
+
readonly VALIDATION_RESPONSE_VALIDATE_ERROR: "validation/response-validate-error";
|
|
10
15
|
};
|
|
11
16
|
export declare type RequestErrorCode = EnumOf<typeof RequestErrorCode>;
|
|
12
17
|
export interface RequestError extends Error {
|
package/esm/RequestError.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
export const RequestErrorCode = {
|
|
2
2
|
MISC_UNKNOWN_ERROR: "misc/unknown-error",
|
|
3
3
|
REQUEST_NETWORK_ERROR: "request/network-error",
|
|
4
|
+
REQUEST_HOST_NAME_NOT_FOUND: "request/host-name-not-found",
|
|
4
5
|
REQUEST_INVALID_STATUS: "request/invalid-status",
|
|
5
6
|
REQUEST_INVALID_CONFIG: "request/invalid-config",
|
|
6
7
|
REQUEST_MISMATCH_RESPONSE_TYPE: "request/mismatch-response-type",
|
|
8
|
+
VALIDATION_QUERY_VALIDATE_ERROR: "validation/query-validate-error",
|
|
9
|
+
VALIDATION_PARAMS_VALIDATE_ERROR: "validation/params-validate-error",
|
|
10
|
+
VALIDATION_BODY_VALIDATE_ERROR: "validation/body-validate-error",
|
|
11
|
+
VALIDATION_RESPONSE_VALIDATE_ERROR: "validation/response-validate-error",
|
|
7
12
|
};
|
|
8
13
|
export const isRequestError = (error) => {
|
|
9
14
|
return "isRequestError" in error;
|
|
@@ -18,12 +23,18 @@ export const convertToRequestError = (config) => {
|
|
|
18
23
|
isRequestError: true,
|
|
19
24
|
attempts: context.stats.attempt,
|
|
20
25
|
request: {
|
|
21
|
-
url: context.
|
|
26
|
+
url: context.requestUrl.href,
|
|
22
27
|
query: context.computedConfig.queryObject,
|
|
23
28
|
headers: context.computedConfig.headers,
|
|
24
29
|
body: body,
|
|
25
30
|
},
|
|
26
31
|
});
|
|
32
|
+
try {
|
|
33
|
+
Object.defineProperty(resultError, "message", { value: `Request failed${(response === null || response === void 0 ? void 0 : response.status) ? ` with status code ${response.status}` : ""} (${code})` });
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
// ignore
|
|
37
|
+
}
|
|
27
38
|
delete resultError.config;
|
|
28
39
|
delete resultError.toJSON;
|
|
29
40
|
Object.setPrototypeOf(error, Error);
|
package/esm/Requester.js
CHANGED
|
@@ -74,6 +74,31 @@ const makeRequest = (context) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
74
74
|
beforeSendEventResult.type === EventResultType.Respond) {
|
|
75
75
|
return (context.response = beforeSendEventResult.response);
|
|
76
76
|
}
|
|
77
|
+
// validation
|
|
78
|
+
if (context.validation.query) {
|
|
79
|
+
try {
|
|
80
|
+
context.computedConfig.queryObject = context.validation.query.parse(context.computedConfig.queryObject);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw convertToRequestError({
|
|
84
|
+
error: error,
|
|
85
|
+
code: RequestErrorCode.VALIDATION_QUERY_VALIDATE_ERROR,
|
|
86
|
+
context: context,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (context.validation.body) {
|
|
91
|
+
try {
|
|
92
|
+
context.computedConfig.body = context.validation.body.parse(context.computedConfig.body);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw convertToRequestError({
|
|
96
|
+
error: error,
|
|
97
|
+
code: RequestErrorCode.VALIDATION_BODY_VALIDATE_ERROR,
|
|
98
|
+
context: context,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
77
102
|
const retryOptions = parseRetryOptions((_a = context.computedConfig) === null || _a === void 0 ? void 0 : _a.retry);
|
|
78
103
|
const internalRetryOptions = {
|
|
79
104
|
retries: retryOptions.maxAttempts,
|
|
@@ -137,6 +162,18 @@ const makeRequest = (context) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
137
162
|
}
|
|
138
163
|
});
|
|
139
164
|
const response = yield retry(performRequest, internalRetryOptions);
|
|
165
|
+
if (context.validation.response) {
|
|
166
|
+
try {
|
|
167
|
+
response.data = context.validation.response.parse(response.data);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
throw convertToRequestError({
|
|
171
|
+
error: error,
|
|
172
|
+
code: RequestErrorCode.VALIDATION_RESPONSE_VALIDATE_ERROR,
|
|
173
|
+
context: context,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
140
177
|
return (response);
|
|
141
178
|
});
|
|
142
179
|
const parseResponse = (context, response, error) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -187,6 +224,7 @@ const parseResponse = (context, response, error) => __awaiter(void 0, void 0, vo
|
|
|
187
224
|
return response;
|
|
188
225
|
});
|
|
189
226
|
const parseError = (context, rawError) => __awaiter(void 0, void 0, void 0, function* () {
|
|
227
|
+
var _e;
|
|
190
228
|
let error;
|
|
191
229
|
if (isRequestError(rawError)) {
|
|
192
230
|
error = rawError;
|
|
@@ -203,6 +241,11 @@ const parseError = (context, rawError) => __awaiter(void 0, void 0, void 0, func
|
|
|
203
241
|
code = RequestErrorCode.REQUEST_INVALID_STATUS;
|
|
204
242
|
}
|
|
205
243
|
}
|
|
244
|
+
else {
|
|
245
|
+
if (rawError.code === "ENOTFOUND" || ((_e = rawError.cause) === null || _e === void 0 ? void 0 : _e.code) === "ENOTFOUND") {
|
|
246
|
+
code = RequestErrorCode.REQUEST_HOST_NAME_NOT_FOUND;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
206
249
|
const errorInfo = context.backend.getErrorInfo(rawError, errorResponse);
|
|
207
250
|
error = convertToRequestError(Object.assign({ error: rawError, response: errorResponse, code: code, context: context }, errorInfo));
|
|
208
251
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as zod from "zod";
|
|
2
|
+
import { Body, Params, Query } from "./ApiTypes";
|
|
3
|
+
export interface Validation<R = any, P extends Params | undefined = Params | undefined, Q extends Query | undefined = Query | undefined, B extends Body | undefined = Body | undefined> {
|
|
4
|
+
query?: zod.Schema<Q>;
|
|
5
|
+
params?: zod.Schema<P>;
|
|
6
|
+
body?: zod.Schema<B>;
|
|
7
|
+
response?: zod.Schema<R>;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -39,7 +39,7 @@ export default class AxiosRequestBackend {
|
|
|
39
39
|
}
|
|
40
40
|
makeRequest(context) {
|
|
41
41
|
const { computedConfig } = context;
|
|
42
|
-
const url = context.
|
|
42
|
+
const url = context.requestUrl;
|
|
43
43
|
let canceler = null;
|
|
44
44
|
const promise = axios({
|
|
45
45
|
method: context.method,
|
|
@@ -92,7 +92,7 @@ export default class FetchRequestBackend {
|
|
|
92
92
|
}
|
|
93
93
|
return parsedHeaders;
|
|
94
94
|
}, {});
|
|
95
|
-
const url = context.
|
|
95
|
+
const url = context.requestUrl;
|
|
96
96
|
const promise = this.fetch(url.href, {
|
|
97
97
|
method: context.method.toUpperCase(),
|
|
98
98
|
body: bodyJsonify ? JSON.stringify(body) : body,
|
|
@@ -44,7 +44,7 @@ export default class MockRequestBackend {
|
|
|
44
44
|
params: (_b = context.computedConfig.params) !== null && _b !== void 0 ? _b : {},
|
|
45
45
|
query: context.computedConfig.queryObject,
|
|
46
46
|
headers: (_c = context.computedConfig.headers) !== null && _c !== void 0 ? _c : {},
|
|
47
|
-
url: context.
|
|
47
|
+
url: context.requestUrl.toString(),
|
|
48
48
|
};
|
|
49
49
|
const res = {
|
|
50
50
|
statusCode: -1,
|
|
@@ -96,7 +96,7 @@ export default class MockRequestBackend {
|
|
|
96
96
|
return parsedHeaders;
|
|
97
97
|
}, {});
|
|
98
98
|
return {
|
|
99
|
-
url: context.
|
|
99
|
+
url: context.requestUrl.href,
|
|
100
100
|
method: context.method,
|
|
101
101
|
headers: parsedHeaders,
|
|
102
102
|
data: res.response,
|
|
@@ -38,11 +38,10 @@ const log = (context, type, message, config, objects) => {
|
|
|
38
38
|
if (typeof config.predicate === "function" && !config.predicate()) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
const { computedPath } = context;
|
|
42
41
|
const color = COLOR_MAP[type];
|
|
43
42
|
const timestamp = formatTime(new Date());
|
|
44
43
|
const args = [
|
|
45
|
-
`%cnetwork %c[${context.api.name}] ${context.method.toUpperCase()} ${
|
|
44
|
+
`%cnetwork %c[${context.api.name}] ${context.method.toUpperCase()} ${context.path} %c${message} %c@ ${timestamp}`,
|
|
46
45
|
"color:gray",
|
|
47
46
|
"color:auto",
|
|
48
47
|
`color:${color}`,
|
package/package.json
CHANGED
|
@@ -1,78 +1,79 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "api-def",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Typed API definitions with middleware support",
|
|
5
|
-
"main": "cjs/index.js",
|
|
6
|
-
"types": "esm/index.d.ts",
|
|
7
|
-
"module": "esm/index.js",
|
|
8
|
-
"sideEffects": false,
|
|
9
|
-
"scripts": {
|
|
10
|
-
"prepublishOnly": "npm run test && npm run build",
|
|
11
|
-
"test": "npm run test:types && npm run test:lint && npm run test:unit",
|
|
12
|
-
"test:unit": "ava",
|
|
13
|
-
"test:lint": "npm run lint:strict",
|
|
14
|
-
"test:types": "tsc --noEmit -p .",
|
|
15
|
-
"example:start": "cd example && npm run start",
|
|
16
|
-
"lint": "esw --ext .tsx,.ts src",
|
|
17
|
-
"lint:watch": "npm run lint -- --watch",
|
|
18
|
-
"lint:fix": "npm run lint -- --fix",
|
|
19
|
-
"lint:strict": "npm run lint -- --max-warnings 0",
|
|
20
|
-
"cleanup": "rimraf esm && rimraf cjs",
|
|
21
|
-
"build": "npm run cleanup && npm run build:esm && npm run build:cjs",
|
|
22
|
-
"build:esm": "tsc --module es2015 --target es2016 --outDir esm --preserveWatchOutput",
|
|
23
|
-
"build:cjs": "tsc --module commonjs --target es5 --outDir cjs --preserveWatchOutput",
|
|
24
|
-
"build:watch": "npm-run-all -p \"build:esm -- -w\" \"build:cjs -- -w\" \"lint:watch\"",
|
|
25
|
-
"website:dev": "cd website && npm run start",
|
|
26
|
-
"website:deploy": "cd website && npm run deploy"
|
|
27
|
-
},
|
|
28
|
-
"keywords": [
|
|
29
|
-
"typescript",
|
|
30
|
-
"javascript",
|
|
31
|
-
"node",
|
|
32
|
-
"web",
|
|
33
|
-
"api",
|
|
34
|
-
"typed",
|
|
35
|
-
"cache",
|
|
36
|
-
"fetch",
|
|
37
|
-
"retry",
|
|
38
|
-
"middleware"
|
|
39
|
-
],
|
|
40
|
-
"author": "James Waterhouse <09jwater@gmail.com>",
|
|
41
|
-
"license": "MIT",
|
|
42
|
-
"files": [
|
|
43
|
-
"LICENSE",
|
|
44
|
-
"README.md",
|
|
45
|
-
"esm/",
|
|
46
|
-
"cjs/"
|
|
47
|
-
],
|
|
48
|
-
"repository": "https://github.com/Censkh/api-def",
|
|
49
|
-
"ava": {
|
|
50
|
-
"extensions": [
|
|
51
|
-
"ts"
|
|
52
|
-
],
|
|
53
|
-
"files": [
|
|
54
|
-
"src/tests/**/*.test.ts"
|
|
55
|
-
],
|
|
56
|
-
"nodeArguments": [
|
|
57
|
-
"--require=@esbuild-kit/cjs-loader"
|
|
58
|
-
]
|
|
59
|
-
},
|
|
60
|
-
"devDependencies": {
|
|
61
|
-
"@babel/preset-env": "7.14.8",
|
|
62
|
-
"@babel/preset-typescript": "7.14.5",
|
|
63
|
-
"@esbuild-kit/cjs-loader": "2.4.0",
|
|
64
|
-
"@types/axios": "0.14.0",
|
|
65
|
-
"@types/node": "16.4.6",
|
|
66
|
-
"@types/qs": "6.9.8",
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "4.28.5",
|
|
68
|
-
"@typescript-eslint/parser": "4.28.5",
|
|
69
|
-
"ava": "5.0.1",
|
|
70
|
-
"axios": "1.1.3",
|
|
71
|
-
"cross-env": "7.0.3",
|
|
72
|
-
"eslint": "7.31.0",
|
|
73
|
-
"eslint-watch": "7.0.0",
|
|
74
|
-
"npm-run-all": "4.1.5",
|
|
75
|
-
"qs": "6.11.2",
|
|
76
|
-
"typescript": "4.8.4"
|
|
77
|
-
|
|
78
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "api-def",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Typed API definitions with middleware support",
|
|
5
|
+
"main": "cjs/index.js",
|
|
6
|
+
"types": "esm/index.d.ts",
|
|
7
|
+
"module": "esm/index.js",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepublishOnly": "npm run test && npm run build",
|
|
11
|
+
"test": "npm run test:types && npm run test:lint && npm run test:unit",
|
|
12
|
+
"test:unit": "ava",
|
|
13
|
+
"test:lint": "npm run lint:strict",
|
|
14
|
+
"test:types": "tsc --noEmit -p .",
|
|
15
|
+
"example:start": "cd example && npm run start",
|
|
16
|
+
"lint": "esw --ext .tsx,.ts src",
|
|
17
|
+
"lint:watch": "npm run lint -- --watch",
|
|
18
|
+
"lint:fix": "npm run lint -- --fix",
|
|
19
|
+
"lint:strict": "npm run lint -- --max-warnings 0",
|
|
20
|
+
"cleanup": "rimraf esm && rimraf cjs",
|
|
21
|
+
"build": "npm run cleanup && npm run build:esm && npm run build:cjs",
|
|
22
|
+
"build:esm": "tsc --module es2015 --target es2016 --outDir esm --preserveWatchOutput",
|
|
23
|
+
"build:cjs": "tsc --module commonjs --target es5 --outDir cjs --preserveWatchOutput",
|
|
24
|
+
"build:watch": "npm-run-all -p \"build:esm -- -w\" \"build:cjs -- -w\" \"lint:watch\"",
|
|
25
|
+
"website:dev": "cd website && npm run start",
|
|
26
|
+
"website:deploy": "cd website && npm run deploy"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"typescript",
|
|
30
|
+
"javascript",
|
|
31
|
+
"node",
|
|
32
|
+
"web",
|
|
33
|
+
"api",
|
|
34
|
+
"typed",
|
|
35
|
+
"cache",
|
|
36
|
+
"fetch",
|
|
37
|
+
"retry",
|
|
38
|
+
"middleware"
|
|
39
|
+
],
|
|
40
|
+
"author": "James Waterhouse <09jwater@gmail.com>",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"files": [
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"README.md",
|
|
45
|
+
"esm/",
|
|
46
|
+
"cjs/"
|
|
47
|
+
],
|
|
48
|
+
"repository": "https://github.com/Censkh/api-def",
|
|
49
|
+
"ava": {
|
|
50
|
+
"extensions": [
|
|
51
|
+
"ts"
|
|
52
|
+
],
|
|
53
|
+
"files": [
|
|
54
|
+
"src/tests/**/*.test.ts"
|
|
55
|
+
],
|
|
56
|
+
"nodeArguments": [
|
|
57
|
+
"--require=@esbuild-kit/cjs-loader"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@babel/preset-env": "7.14.8",
|
|
62
|
+
"@babel/preset-typescript": "7.14.5",
|
|
63
|
+
"@esbuild-kit/cjs-loader": "2.4.0",
|
|
64
|
+
"@types/axios": "0.14.0",
|
|
65
|
+
"@types/node": "16.4.6",
|
|
66
|
+
"@types/qs": "6.9.8",
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "4.28.5",
|
|
68
|
+
"@typescript-eslint/parser": "4.28.5",
|
|
69
|
+
"ava": "5.0.1",
|
|
70
|
+
"axios": "1.1.3",
|
|
71
|
+
"cross-env": "7.0.3",
|
|
72
|
+
"eslint": "7.31.0",
|
|
73
|
+
"eslint-watch": "7.0.0",
|
|
74
|
+
"npm-run-all": "4.1.5",
|
|
75
|
+
"qs": "6.11.2",
|
|
76
|
+
"typescript": "4.8.4",
|
|
77
|
+
"zod": "3.22.4"
|
|
78
|
+
}
|
|
79
|
+
}
|