@odata2ts/http-client-base 0.1.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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/lib/BaseHttpClient.d.ts +56 -0
- package/lib/BaseHttpClient.js +106 -0
- package/lib/BaseHttpClient.js.map +1 -0
- package/lib/ErrorMessageRetriever.d.ts +13 -0
- package/lib/ErrorMessageRetriever.js +16 -0
- package/lib/ErrorMessageRetriever.js.map +1 -0
- package/lib/HttpMethods.d.ts +7 -0
- package/lib/HttpMethods.js +12 -0
- package/lib/HttpMethods.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +10 -0
- package/lib/index.js.map +1 -0
- package/package.json +46 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# 0.1.0 (2023-06-10)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **http-client-base:** promote feature version & add some more documentation ([3216cf3](https://github.com/odata2ts/http-client/commit/3216cf34750732e9e3f064270351f56dac49e581))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 odata2ts
|
|
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
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@odata2ts/http-client-base)
|
|
2
|
+
|
|
3
|
+
# OData HTTP Client Base
|
|
4
|
+
|
|
5
|
+
Base implementation for [odata2ts](https://github.com/odata2ts/odata2ts) compatible HTTP clients:
|
|
6
|
+
|
|
7
|
+
- implements automatic CSRF token handling
|
|
8
|
+
- allows user to set custom CSRF token header key (default: `x-csrf-token`)
|
|
9
|
+
- implements standard error message retrieval method for OData error responses
|
|
10
|
+
- works for V2 & V4
|
|
11
|
+
- allows user to set custom retrieval method
|
|
12
|
+
- streamlines all HTTP calls (POST, GET, ...) into one method
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Install package `@odata2ts/http-client-base` as dependency:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install --save @odata2ts/http-client-base
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Extend the class `BaseHttpClient` to simplify your HttpClient implementation.
|
|
25
|
+
You need to implement two abstract methods:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { BaseHttpClient, BaseHttpClientOptions } from "@odata2ts/http-client-base";
|
|
29
|
+
|
|
30
|
+
export interface MyRequestConfig {}
|
|
31
|
+
|
|
32
|
+
export class MyHttpClient extends BaseHttpClient<MyRequestConfig> {
|
|
33
|
+
constructor(clientOptions: BaseHttpClientOptions) {
|
|
34
|
+
super(clientOptions);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected addHeaderToRequestConfig(
|
|
38
|
+
headers: Record<string, string>,
|
|
39
|
+
config: BaseHttpClient | undefined
|
|
40
|
+
): MyRequestConfig {
|
|
41
|
+
// your implementation
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected async executeRequest<ResponseModel>(
|
|
45
|
+
method: HttpMethods,
|
|
46
|
+
url: string,
|
|
47
|
+
data: any,
|
|
48
|
+
requestConfig: AxiosRequestConfig | undefined = {}
|
|
49
|
+
): Promise<HttpResponseModel<ResponseModel>> {
|
|
50
|
+
// your implementation
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Compare any of the existing clients.
|
|
56
|
+
|
|
57
|
+
## Documentation
|
|
58
|
+
|
|
59
|
+
[HTTP Client Documentation](https://odata2ts.github.io/docs/http-client)
|
|
60
|
+
|
|
61
|
+
Main documentation for the odata2ts eco system:
|
|
62
|
+
[https://odata2ts.github.io](https://odata2ts.github.io/)
|
|
63
|
+
|
|
64
|
+
## Tests
|
|
65
|
+
|
|
66
|
+
See folder [test](https://github.com/odata2ts/http-client/tree/main/packages/core/test)
|
|
67
|
+
for unit tests.
|
|
68
|
+
|
|
69
|
+
## Support, Feedback, Contributing
|
|
70
|
+
|
|
71
|
+
This project is open to feature requests, suggestions, bug reports, usage questions etc.
|
|
72
|
+
via [GitHub issues](https://github.com/odata2ts/http-client/issues).
|
|
73
|
+
|
|
74
|
+
Contributions and feedback are encouraged and always welcome.
|
|
75
|
+
|
|
76
|
+
See the [contribution guidelines](https://github.com/odata2ts/http-client/blob/main/CONTRIBUTING.md) for further information.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT - see [License](./LICENSE).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { HttpResponseModel, ODataHttpClient } from "@odata2ts/http-client-api";
|
|
2
|
+
import { ErrorMessageRetriever } from "./ErrorMessageRetriever";
|
|
3
|
+
import { HttpMethods } from "./HttpMethods";
|
|
4
|
+
export interface BaseHttpClientOptions {
|
|
5
|
+
useCsrfProtection?: boolean;
|
|
6
|
+
csrfTokenFetchUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const DEFAULT_CSRF_TOKEN_KEY = "x-csrf-token";
|
|
9
|
+
export declare abstract class BaseHttpClient<RequestConfigType> implements ODataHttpClient<RequestConfigType> {
|
|
10
|
+
private baseOptions;
|
|
11
|
+
private csrfToken;
|
|
12
|
+
private csrfTokenKey;
|
|
13
|
+
protected retrieveErrorMessage: ErrorMessageRetriever;
|
|
14
|
+
protected constructor(baseOptions?: BaseHttpClientOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Use the given headers to either create an entire new request config or merge them into the given
|
|
17
|
+
* request config.
|
|
18
|
+
*
|
|
19
|
+
* @param headers
|
|
20
|
+
* @param config
|
|
21
|
+
* @returns request configuration
|
|
22
|
+
*/
|
|
23
|
+
protected abstract addHeaderToRequestConfig(headers: Record<string, string>, config?: RequestConfigType): RequestConfigType;
|
|
24
|
+
/**
|
|
25
|
+
* Main function to implement by any extending http client.
|
|
26
|
+
* As it name suggests, the request gets executed in this method.
|
|
27
|
+
* Additionally, failures should be handled and errors of type <code>HttpClientError</code> should be thrown.
|
|
28
|
+
*
|
|
29
|
+
* @param method
|
|
30
|
+
* @param url
|
|
31
|
+
* @param data
|
|
32
|
+
* @param config
|
|
33
|
+
*/
|
|
34
|
+
protected abstract executeRequest<ResponseModel>(method: HttpMethods, url: string, data: any, config?: RequestConfigType): Promise<HttpResponseModel<ResponseModel>>;
|
|
35
|
+
getCsrfTokenKey(): string;
|
|
36
|
+
setCsrfTokenKey(newKey: string): void;
|
|
37
|
+
setErrorMessageRetriever(getErrorMsg: ErrorMessageRetriever): void;
|
|
38
|
+
protected setupSecurityToken(): Promise<[string, string | undefined]>;
|
|
39
|
+
protected fetchSecurityToken(): Promise<string | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Follows the template pattern.
|
|
42
|
+
*
|
|
43
|
+
* @param method
|
|
44
|
+
* @param url
|
|
45
|
+
* @param data
|
|
46
|
+
* @param requestConfig
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
private sendRequest;
|
|
50
|
+
get<ResponseModel>(url: string, requestConfig?: RequestConfigType): Promise<HttpResponseModel<ResponseModel>>;
|
|
51
|
+
post<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType): Promise<HttpResponseModel<ResponseModel>>;
|
|
52
|
+
put<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType): Promise<HttpResponseModel<ResponseModel>>;
|
|
53
|
+
patch<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType): Promise<HttpResponseModel<ResponseModel>>;
|
|
54
|
+
merge<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType): Promise<HttpResponseModel<ResponseModel>>;
|
|
55
|
+
delete(url: string, requestConfig?: RequestConfigType): Promise<HttpResponseModel<void>>;
|
|
56
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseHttpClient = exports.DEFAULT_CSRF_TOKEN_KEY = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const ErrorMessageRetriever_1 = require("./ErrorMessageRetriever");
|
|
6
|
+
const HttpMethods_1 = require("./HttpMethods");
|
|
7
|
+
exports.DEFAULT_CSRF_TOKEN_KEY = "x-csrf-token";
|
|
8
|
+
const EDIT_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
|
|
9
|
+
const FAILURE_MISSING_CSRF_URL = "When automatic CSRF token handling is activated, the URL must be supplied via attribute [csrfTokenFetchUrl]!";
|
|
10
|
+
const FAILURE_MISSING_URL = "Value for URL must be provided!";
|
|
11
|
+
class BaseHttpClient {
|
|
12
|
+
constructor(baseOptions = { useCsrfProtection: false }) {
|
|
13
|
+
var _a;
|
|
14
|
+
this.baseOptions = baseOptions;
|
|
15
|
+
this.csrfTokenKey = exports.DEFAULT_CSRF_TOKEN_KEY;
|
|
16
|
+
this.retrieveErrorMessage = ErrorMessageRetriever_1.retrieveErrorMessage;
|
|
17
|
+
if (baseOptions.useCsrfProtection && !((_a = baseOptions.csrfTokenFetchUrl) === null || _a === void 0 ? void 0 : _a.trim())) {
|
|
18
|
+
throw new Error(FAILURE_MISSING_CSRF_URL);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
getCsrfTokenKey() {
|
|
22
|
+
return this.csrfTokenKey;
|
|
23
|
+
}
|
|
24
|
+
setCsrfTokenKey(newKey) {
|
|
25
|
+
this.csrfTokenKey = newKey || exports.DEFAULT_CSRF_TOKEN_KEY;
|
|
26
|
+
}
|
|
27
|
+
setErrorMessageRetriever(getErrorMsg) {
|
|
28
|
+
this.retrieveErrorMessage = getErrorMsg;
|
|
29
|
+
}
|
|
30
|
+
setupSecurityToken() {
|
|
31
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
if (!this.csrfToken) {
|
|
33
|
+
this.csrfToken = yield this.fetchSecurityToken();
|
|
34
|
+
}
|
|
35
|
+
return [this.csrfTokenKey, this.csrfToken];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
fetchSecurityToken() {
|
|
39
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
const fetchUrl = this.baseOptions.csrfTokenFetchUrl;
|
|
41
|
+
const response = yield this.get(fetchUrl, this.addHeaderToRequestConfig({ [this.csrfTokenKey]: "Fetch" }));
|
|
42
|
+
return response.headers[this.csrfTokenKey];
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Follows the template pattern.
|
|
47
|
+
*
|
|
48
|
+
* @param method
|
|
49
|
+
* @param url
|
|
50
|
+
* @param data
|
|
51
|
+
* @param requestConfig
|
|
52
|
+
* @private
|
|
53
|
+
*/
|
|
54
|
+
sendRequest(method, url, data, requestConfig) {
|
|
55
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
// noinspection SuspiciousTypeOfGuard
|
|
57
|
+
if (typeof url !== "string") {
|
|
58
|
+
throw new Error(FAILURE_MISSING_URL);
|
|
59
|
+
}
|
|
60
|
+
let config = requestConfig;
|
|
61
|
+
// setup automatic CSRF token handling
|
|
62
|
+
if (this.baseOptions.useCsrfProtection && EDIT_METHODS.includes(method)) {
|
|
63
|
+
const [tokenKey, tokenValue] = yield this.setupSecurityToken();
|
|
64
|
+
if (tokenValue) {
|
|
65
|
+
config = this.addHeaderToRequestConfig({ [tokenKey]: tokenValue }, requestConfig);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
return yield this.executeRequest(method, url, data, config);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
const clientError = e;
|
|
73
|
+
// automatic CSRF token handling
|
|
74
|
+
if (!!this.baseOptions.useCsrfProtection &&
|
|
75
|
+
clientError.status === 403 &&
|
|
76
|
+
!!clientError.headers &&
|
|
77
|
+
clientError.headers["x-csrf-token"] === "Required") {
|
|
78
|
+
// token has expired: reset csrf token & perform the original request again
|
|
79
|
+
this.csrfToken = undefined;
|
|
80
|
+
return this.sendRequest(method, url, data, requestConfig);
|
|
81
|
+
}
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
get(url, requestConfig) {
|
|
87
|
+
return this.sendRequest(HttpMethods_1.HttpMethods.Get, url, undefined, requestConfig);
|
|
88
|
+
}
|
|
89
|
+
post(url, data, requestConfig) {
|
|
90
|
+
return this.sendRequest(HttpMethods_1.HttpMethods.Post, url, data, requestConfig);
|
|
91
|
+
}
|
|
92
|
+
put(url, data, requestConfig) {
|
|
93
|
+
return this.sendRequest(HttpMethods_1.HttpMethods.Put, url, data, requestConfig);
|
|
94
|
+
}
|
|
95
|
+
patch(url, data, requestConfig) {
|
|
96
|
+
return this.sendRequest(HttpMethods_1.HttpMethods.Patch, url, data, requestConfig);
|
|
97
|
+
}
|
|
98
|
+
merge(url, data, requestConfig) {
|
|
99
|
+
return this.sendRequest(HttpMethods_1.HttpMethods.Post, url, data, this.addHeaderToRequestConfig({ "X-Http-Method": "MERGE" }, requestConfig));
|
|
100
|
+
}
|
|
101
|
+
delete(url, requestConfig) {
|
|
102
|
+
return this.sendRequest(HttpMethods_1.HttpMethods.Delete, url, undefined, requestConfig);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.BaseHttpClient = BaseHttpClient;
|
|
106
|
+
//# sourceMappingURL=BaseHttpClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseHttpClient.js","sourceRoot":"","sources":["../src/BaseHttpClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { HttpResponseModel, ODataClientError, ODataHttpClient } from \"@odata2ts/http-client-api\";\r\n\r\nimport { ErrorMessageRetriever, retrieveErrorMessage } from \"./ErrorMessageRetriever\";\r\nimport { HttpMethods } from \"./HttpMethods\";\r\n\r\nexport interface BaseHttpClientOptions {\r\n useCsrfProtection?: boolean;\r\n csrfTokenFetchUrl?: string;\r\n}\r\n\r\nexport const DEFAULT_CSRF_TOKEN_KEY = \"x-csrf-token\";\r\n\r\nconst EDIT_METHODS = [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\r\nconst FAILURE_MISSING_CSRF_URL =\r\n \"When automatic CSRF token handling is activated, the URL must be supplied via attribute [csrfTokenFetchUrl]!\";\r\nconst FAILURE_MISSING_URL = \"Value for URL must be provided!\";\r\n\r\nexport abstract class BaseHttpClient<RequestConfigType> implements ODataHttpClient<RequestConfigType> {\r\n private csrfToken: string | undefined;\r\n private csrfTokenKey = DEFAULT_CSRF_TOKEN_KEY;\r\n\r\n protected retrieveErrorMessage: ErrorMessageRetriever = retrieveErrorMessage;\r\n\r\n protected constructor(private baseOptions: BaseHttpClientOptions = { useCsrfProtection: false }) {\r\n if (baseOptions.useCsrfProtection && !baseOptions.csrfTokenFetchUrl?.trim()) {\r\n throw new Error(FAILURE_MISSING_CSRF_URL);\r\n }\r\n }\r\n\r\n /**\r\n * Use the given headers to either create an entire new request config or merge them into the given\r\n * request config.\r\n *\r\n * @param headers\r\n * @param config\r\n * @returns request configuration\r\n */\r\n protected abstract addHeaderToRequestConfig(\r\n headers: Record<string, string>,\r\n config?: RequestConfigType\r\n ): RequestConfigType;\r\n\r\n /**\r\n * Main function to implement by any extending http client.\r\n * As it name suggests, the request gets executed in this method.\r\n * Additionally, failures should be handled and errors of type <code>HttpClientError</code> should be thrown.\r\n *\r\n * @param method\r\n * @param url\r\n * @param data\r\n * @param config\r\n */\r\n protected abstract executeRequest<ResponseModel>(\r\n method: HttpMethods,\r\n url: string,\r\n data: any,\r\n config?: RequestConfigType\r\n ): Promise<HttpResponseModel<ResponseModel>>;\r\n\r\n public getCsrfTokenKey() {\r\n return this.csrfTokenKey;\r\n }\r\n\r\n public setCsrfTokenKey(newKey: string) {\r\n this.csrfTokenKey = newKey || DEFAULT_CSRF_TOKEN_KEY;\r\n }\r\n\r\n public setErrorMessageRetriever(getErrorMsg: ErrorMessageRetriever) {\r\n this.retrieveErrorMessage = getErrorMsg;\r\n }\r\n\r\n protected async setupSecurityToken(): Promise<[string, string | undefined]> {\r\n if (!this.csrfToken) {\r\n this.csrfToken = await this.fetchSecurityToken();\r\n }\r\n return [this.csrfTokenKey, this.csrfToken];\r\n }\r\n\r\n protected async fetchSecurityToken(): Promise<string | undefined> {\r\n const fetchUrl = this.baseOptions!.csrfTokenFetchUrl!;\r\n const response = await this.get(fetchUrl, this.addHeaderToRequestConfig({ [this.csrfTokenKey]: \"Fetch\" }));\r\n\r\n return response.headers[this.csrfTokenKey];\r\n }\r\n\r\n /**\r\n * Follows the template pattern.\r\n *\r\n * @param method\r\n * @param url\r\n * @param data\r\n * @param requestConfig\r\n * @private\r\n */\r\n private async sendRequest<ResponseModel>(\r\n method: HttpMethods,\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n // noinspection SuspiciousTypeOfGuard\r\n if (typeof url !== \"string\") {\r\n throw new Error(FAILURE_MISSING_URL);\r\n }\r\n\r\n let config = requestConfig;\r\n\r\n // setup automatic CSRF token handling\r\n if (this.baseOptions.useCsrfProtection && EDIT_METHODS.includes(method)) {\r\n const [tokenKey, tokenValue] = await this.setupSecurityToken();\r\n if (tokenValue) {\r\n config = this.addHeaderToRequestConfig({ [tokenKey]: tokenValue }, requestConfig);\r\n }\r\n }\r\n\r\n try {\r\n return await this.executeRequest<ResponseModel>(method, url, data, config);\r\n } catch (e) {\r\n const clientError = e as ODataClientError;\r\n\r\n // automatic CSRF token handling\r\n if (\r\n !!this.baseOptions.useCsrfProtection &&\r\n clientError.status === 403 &&\r\n !!clientError.headers &&\r\n clientError.headers[\"x-csrf-token\"] === \"Required\"\r\n ) {\r\n // token has expired: reset csrf token & perform the original request again\r\n this.csrfToken = undefined;\r\n return this.sendRequest<ResponseModel>(method, url, data, requestConfig);\r\n }\r\n\r\n throw e;\r\n }\r\n }\r\n\r\n public get<ResponseModel>(url: string, requestConfig?: RequestConfigType): Promise<HttpResponseModel<ResponseModel>> {\r\n return this.sendRequest<ResponseModel>(HttpMethods.Get, url, undefined, requestConfig);\r\n }\r\n public post<ResponseModel>(\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n return this.sendRequest<ResponseModel>(HttpMethods.Post, url, data, requestConfig);\r\n }\r\n public put<ResponseModel>(\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n return this.sendRequest<ResponseModel>(HttpMethods.Put, url, data, requestConfig);\r\n }\r\n public patch<ResponseModel>(\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n return this.sendRequest<ResponseModel>(HttpMethods.Patch, url, data, requestConfig);\r\n }\r\n public merge<ResponseModel>(\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n return this.sendRequest<ResponseModel>(\r\n HttpMethods.Post,\r\n url,\r\n data,\r\n this.addHeaderToRequestConfig({ \"X-Http-Method\": \"MERGE\" }, requestConfig)\r\n );\r\n }\r\n public delete(url: string, requestConfig?: RequestConfigType): Promise<HttpResponseModel<void>> {\r\n return this.sendRequest<void>(HttpMethods.Delete, url, undefined, requestConfig);\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function type used to retrieve the error message from the response payload.
|
|
3
|
+
*
|
|
4
|
+
* @return error message or <code>undefined</code> if retrieval failed / had no outcome
|
|
5
|
+
*/
|
|
6
|
+
export type ErrorMessageRetriever = (errorResponse: any) => string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Retrieves the OData error message from the response (V2 and V4 are supported).
|
|
9
|
+
* The structure of the error message is specified by OData.
|
|
10
|
+
*
|
|
11
|
+
* @param errorResponse
|
|
12
|
+
*/
|
|
13
|
+
export declare const retrieveErrorMessage: ErrorMessageRetriever;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.retrieveErrorMessage = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Retrieves the OData error message from the response (V2 and V4 are supported).
|
|
6
|
+
* The structure of the error message is specified by OData.
|
|
7
|
+
*
|
|
8
|
+
* @param errorResponse
|
|
9
|
+
*/
|
|
10
|
+
const retrieveErrorMessage = (errorResponse) => {
|
|
11
|
+
var _a;
|
|
12
|
+
const eMsg = (_a = errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.error) === null || _a === void 0 ? void 0 : _a.message;
|
|
13
|
+
return typeof (eMsg === null || eMsg === void 0 ? void 0 : eMsg.value) === "string" ? eMsg.value : eMsg;
|
|
14
|
+
};
|
|
15
|
+
exports.retrieveErrorMessage = retrieveErrorMessage;
|
|
16
|
+
//# sourceMappingURL=ErrorMessageRetriever.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorMessageRetriever.js","sourceRoot":"","sources":["../src/ErrorMessageRetriever.ts"],"names":[],"mappings":";;;AAOA;;;;;GAKG;AACI,MAAM,oBAAoB,GAA0B,CAAC,aAAkB,EAAsB,EAAE;;IACpG,MAAM,IAAI,GAAG,MAAA,aAAa,aAAb,aAAa,uBAAb,aAAa,CAAE,KAAK,0CAAE,OAAO,CAAC;IAC3C,OAAO,OAAO,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,CAAA,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC,CAAC;AAHW,QAAA,oBAAoB,wBAG/B","sourcesContent":["/**\r\n * Function type used to retrieve the error message from the response payload.\r\n *\r\n * @return error message or <code>undefined</code> if retrieval failed / had no outcome\r\n */\r\nexport type ErrorMessageRetriever = (errorResponse: any) => string | undefined;\r\n\r\n/**\r\n * Retrieves the OData error message from the response (V2 and V4 are supported).\r\n * The structure of the error message is specified by OData.\r\n *\r\n * @param errorResponse\r\n */\r\nexport const retrieveErrorMessage: ErrorMessageRetriever = (errorResponse: any): string | undefined => {\r\n const eMsg = errorResponse?.error?.message;\r\n return typeof eMsg?.value === \"string\" ? eMsg.value : eMsg;\r\n};\r\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpMethods = void 0;
|
|
4
|
+
var HttpMethods;
|
|
5
|
+
(function (HttpMethods) {
|
|
6
|
+
HttpMethods["Get"] = "GET";
|
|
7
|
+
HttpMethods["Post"] = "POST";
|
|
8
|
+
HttpMethods["Put"] = "PUT";
|
|
9
|
+
HttpMethods["Patch"] = "PATCH";
|
|
10
|
+
HttpMethods["Delete"] = "DELETE";
|
|
11
|
+
})(HttpMethods = exports.HttpMethods || (exports.HttpMethods = {}));
|
|
12
|
+
//# sourceMappingURL=HttpMethods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpMethods.js","sourceRoot":"","sources":["../src/HttpMethods.ts"],"names":[],"mappings":";;;AAAA,IAAY,WAMX;AAND,WAAY,WAAW;IACrB,0BAAW,CAAA;IACX,4BAAa,CAAA;IACb,0BAAW,CAAA;IACX,8BAAe,CAAA;IACf,gCAAiB,CAAA;AACnB,CAAC,EANW,WAAW,GAAX,mBAAW,KAAX,mBAAW,QAMtB","sourcesContent":["export enum HttpMethods {\r\n Get = \"GET\",\r\n Post = \"POST\",\r\n Put = \"PUT\",\r\n Patch = \"PATCH\",\r\n Delete = \"DELETE\",\r\n}\r\n"]}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HttpMethods = exports.BaseHttpClient = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
tslib_1.__exportStar(require("./ErrorMessageRetriever"), exports);
|
|
6
|
+
var BaseHttpClient_1 = require("./BaseHttpClient");
|
|
7
|
+
Object.defineProperty(exports, "BaseHttpClient", { enumerable: true, get: function () { return BaseHttpClient_1.BaseHttpClient; } });
|
|
8
|
+
var HttpMethods_1 = require("./HttpMethods");
|
|
9
|
+
Object.defineProperty(exports, "HttpMethods", { enumerable: true, get: function () { return HttpMethods_1.HttpMethods; } });
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;AAAA,kEAAwC;AACxC,mDAAyE;AAAhE,gHAAA,cAAc,OAAA;AACvB,6CAA4C;AAAnC,0GAAA,WAAW,OAAA","sourcesContent":["export * from \"./ErrorMessageRetriever\";\r\nexport { BaseHttpClient, BaseHttpClientOptions } from \"./BaseHttpClient\";\r\nexport { HttpMethods } from \"./HttpMethods\";\r\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@odata2ts/http-client-base",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Core functionality for odata2ts HTTP clients",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": "git@github.com:odata2ts/http-client.git",
|
|
10
|
+
"author": "texttechne",
|
|
11
|
+
"main": "./lib/index.js",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "yarn clean && yarn compile",
|
|
14
|
+
"check-circular-deps": "madge ./src --extensions ts --circular",
|
|
15
|
+
"clean": "rimraf lib coverage",
|
|
16
|
+
"compile": "tsc",
|
|
17
|
+
"prepublish": "yarn build",
|
|
18
|
+
"test": "jest ./test"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"*.md",
|
|
22
|
+
"lib",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"http client",
|
|
27
|
+
"odata",
|
|
28
|
+
"ts",
|
|
29
|
+
"odata2ts"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@odata2ts/http-client-api": "^0.2.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@odata2ts/odata-core": "^0.3.7",
|
|
36
|
+
"@types/jest": "^29.5.2",
|
|
37
|
+
"@types/node": "^20.2.5",
|
|
38
|
+
"jest": "^29.5.0",
|
|
39
|
+
"rimraf": "^5.0.1",
|
|
40
|
+
"ts-jest": "^29.1.0",
|
|
41
|
+
"ts-node": "10.9.1",
|
|
42
|
+
"typescript": "5.0.4"
|
|
43
|
+
},
|
|
44
|
+
"types": "./lib/index.d.ts",
|
|
45
|
+
"gitHead": "485559dce9388b480c7d0b3ded2fe2f3f5f98fad"
|
|
46
|
+
}
|