http-client-impulse 1.0.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/dist/index.d.ts +8 -0
- package/dist/index.js +165 -0
- package/dist/index.types.d.ts +34 -0
- package/dist/index.types.js +1 -0
- package/dist/tests/index.test.d.ts +1 -0
- package/dist/tests/index.test.js +95 -0
- package/package.json +33 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RequestConfig, HttpResponse, HttpGetOptions, HttpPostOptions, HttpPutOptions, HttpDeleteOptions } from "./index.types.d.ts";
|
|
2
|
+
declare function create(config?: RequestConfig): {
|
|
3
|
+
get<T>(...options: HttpGetOptions): Promise<HttpResponse<T>>;
|
|
4
|
+
post<T>(...options: HttpPostOptions): Promise<HttpResponse<T>>;
|
|
5
|
+
put<T>(...options: HttpPutOptions): Promise<HttpResponse<T>>;
|
|
6
|
+
delete<T>(...options: HttpDeleteOptions): Promise<HttpResponse<T>>;
|
|
7
|
+
};
|
|
8
|
+
export default create;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// проверка на url
|
|
2
|
+
function isUrl(url) {
|
|
3
|
+
if (typeof url === 'string') {
|
|
4
|
+
return true;
|
|
5
|
+
}
|
|
6
|
+
if (typeof url === 'object' && url !== null) {
|
|
7
|
+
return url instanceof URL;
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
// gpt: проверка BodyData , на чистый объект
|
|
12
|
+
function isPlainObject(value) {
|
|
13
|
+
return (typeof value === 'object' &&
|
|
14
|
+
value !== null &&
|
|
15
|
+
!(value instanceof FormData) &&
|
|
16
|
+
!(value instanceof Blob) &&
|
|
17
|
+
!(value instanceof ArrayBuffer) &&
|
|
18
|
+
!(value instanceof URLSearchParams));
|
|
19
|
+
}
|
|
20
|
+
// объединение с baseURL
|
|
21
|
+
function buildUrl(url, baseURL) {
|
|
22
|
+
if (!baseURL)
|
|
23
|
+
return new URL(url);
|
|
24
|
+
const urlString = url.toString();
|
|
25
|
+
const mormalized = urlString.startsWith('/') ? urlString.slice(1) : urlString;
|
|
26
|
+
return new URL(mormalized, baseURL);
|
|
27
|
+
}
|
|
28
|
+
// Обработка search params опции адаптера
|
|
29
|
+
function handleUrlParams(params) {
|
|
30
|
+
return new URLSearchParams(params);
|
|
31
|
+
}
|
|
32
|
+
// обработка(адаптация) кастомных опций для fetch() опций
|
|
33
|
+
function handleRequestOptionsForFetch(options) {
|
|
34
|
+
const { url, params, ...otherParams } = options;
|
|
35
|
+
const newUrl = new URL(url.toString()); // ревью-gpt: лучше сделать новый, дабы было безопаснее, чем мутировать неизвестно-приходящий
|
|
36
|
+
if (params && typeof params === 'object' && params !== null) {
|
|
37
|
+
newUrl.search = handleUrlParams(params).toString();
|
|
38
|
+
}
|
|
39
|
+
return { ...otherParams, url: newUrl };
|
|
40
|
+
}
|
|
41
|
+
// обработка опций при работе с method [get | delete]: [url], [{url}], [url, options]
|
|
42
|
+
function getOptionsWithoutDataForHttp(options, config, method) {
|
|
43
|
+
const argsLength = options.length;
|
|
44
|
+
// [url] | [{url}]
|
|
45
|
+
if (argsLength === 1) {
|
|
46
|
+
const urlOrOptions = options[0];
|
|
47
|
+
// [url]
|
|
48
|
+
if (isUrl(urlOrOptions)) {
|
|
49
|
+
const url = urlOrOptions;
|
|
50
|
+
return {
|
|
51
|
+
method,
|
|
52
|
+
url: buildUrl(url, config.baseURL)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// [{url}]
|
|
56
|
+
const { url, ...optionsWithoutUrl } = urlOrOptions;
|
|
57
|
+
if (!isUrl(url)) {
|
|
58
|
+
throw new Error('Invalid url');
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
...optionsWithoutUrl,
|
|
62
|
+
method,
|
|
63
|
+
url: buildUrl(url, config.baseURL)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// [url, options]
|
|
67
|
+
const [url, _options] = options;
|
|
68
|
+
if (!isUrl(url)) {
|
|
69
|
+
throw new Error('Invalid url');
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
..._options,
|
|
73
|
+
method,
|
|
74
|
+
url: buildUrl(url, config.baseURL)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// обработка опций при работе с method [post | put]: [url, data, options]
|
|
78
|
+
function getOptionsWithDataForHttp(optionsArg, config, method) {
|
|
79
|
+
const [url, data, options] = optionsArg;
|
|
80
|
+
if (!isUrl(url)) {
|
|
81
|
+
throw new Error('Invalid url');
|
|
82
|
+
}
|
|
83
|
+
const urlWithBase = buildUrl(url, config.baseURL);
|
|
84
|
+
let body = data;
|
|
85
|
+
let jsonHeaders;
|
|
86
|
+
if (isPlainObject(data)) {
|
|
87
|
+
body = JSON.stringify(data);
|
|
88
|
+
jsonHeaders = {
|
|
89
|
+
'Content-Type': 'application/json'
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const baseOptions = {
|
|
93
|
+
method,
|
|
94
|
+
url: urlWithBase,
|
|
95
|
+
body,
|
|
96
|
+
};
|
|
97
|
+
if (options) {
|
|
98
|
+
const { headers: headersOption, ...otherOptions } = options;
|
|
99
|
+
const finalHeaders = new Headers(headersOption ?? {}); // gpt: нормализация headers
|
|
100
|
+
if (jsonHeaders) {
|
|
101
|
+
for (const [key, value] of Object.entries(jsonHeaders)) {
|
|
102
|
+
// не перезаписываем, если уже есть
|
|
103
|
+
if (!finalHeaders.has(key)) {
|
|
104
|
+
finalHeaders.set(key, value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...otherOptions,
|
|
110
|
+
...baseOptions,
|
|
111
|
+
headers: finalHeaders
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
...baseOptions,
|
|
116
|
+
...jsonHeaders
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// http адаптер: отправка http запросов
|
|
120
|
+
async function httpRequest(options) {
|
|
121
|
+
const { url, ...optionsWithoutUrl } = handleRequestOptionsForFetch(options);
|
|
122
|
+
const response = await fetch(url, optionsWithoutUrl);
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const errorData = await response.text();
|
|
125
|
+
throw new Error(`HTTP ${response.status}: ${errorData}`);
|
|
126
|
+
}
|
|
127
|
+
const data = await response.json();
|
|
128
|
+
return {
|
|
129
|
+
data,
|
|
130
|
+
status: response.status,
|
|
131
|
+
statusText: response.statusText,
|
|
132
|
+
headers: response.headers,
|
|
133
|
+
url: response.url
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function create(config) {
|
|
137
|
+
const baseURL = config?.baseURL;
|
|
138
|
+
return {
|
|
139
|
+
async get(...options) {
|
|
140
|
+
const optionsForRequest = getOptionsWithoutDataForHttp(options, {
|
|
141
|
+
baseURL
|
|
142
|
+
}, 'GET');
|
|
143
|
+
return await httpRequest(optionsForRequest);
|
|
144
|
+
},
|
|
145
|
+
async post(...options) {
|
|
146
|
+
const optionsForRequest = getOptionsWithDataForHttp(options, {
|
|
147
|
+
baseURL
|
|
148
|
+
}, 'POST');
|
|
149
|
+
return await httpRequest(optionsForRequest);
|
|
150
|
+
},
|
|
151
|
+
async put(...options) {
|
|
152
|
+
const optionsForRequest = getOptionsWithDataForHttp(options, {
|
|
153
|
+
baseURL
|
|
154
|
+
}, 'PUT');
|
|
155
|
+
return await httpRequest(optionsForRequest);
|
|
156
|
+
},
|
|
157
|
+
async delete(...options) {
|
|
158
|
+
const optionsForRequest = getOptionsWithoutDataForHttp(options, {
|
|
159
|
+
baseURL
|
|
160
|
+
}, 'DELETE');
|
|
161
|
+
return await httpRequest(optionsForRequest);
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export default create;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type ImpulseURL = string | URL;
|
|
2
|
+
export interface Params {
|
|
3
|
+
[key: string]: string | number;
|
|
4
|
+
}
|
|
5
|
+
interface DefaultOptions extends RequestInit {
|
|
6
|
+
}
|
|
7
|
+
export interface RequestConfig {
|
|
8
|
+
baseURL?: string;
|
|
9
|
+
defaultOptions?: DefaultOptions;
|
|
10
|
+
}
|
|
11
|
+
interface BodyObject {
|
|
12
|
+
}
|
|
13
|
+
export type RequestDataBody = BodyInit | BodyObject | null;
|
|
14
|
+
export interface HttpResponse<T> {
|
|
15
|
+
data: T;
|
|
16
|
+
status: number;
|
|
17
|
+
statusText: string;
|
|
18
|
+
headers: Headers;
|
|
19
|
+
url: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RequestOptions extends RequestInit {
|
|
22
|
+
url: ImpulseURL;
|
|
23
|
+
params?: Params;
|
|
24
|
+
}
|
|
25
|
+
export type RequestOptionsWithoutUrl = Omit<RequestOptions, "url">;
|
|
26
|
+
type RequestOptionsWithoutUrlAndMethod = Omit<RequestOptionsWithoutUrl, "method">;
|
|
27
|
+
type RequestOptionsWithoutMethod = Omit<RequestOptions, 'method'>;
|
|
28
|
+
type HttpOptions = [ImpulseURL | RequestOptionsWithoutMethod] | [ImpulseURL, RequestOptionsWithoutUrlAndMethod] | [RequestOptionsWithoutMethod];
|
|
29
|
+
type HttpDataOptions = [ImpulseURL] | [ImpulseURL, RequestDataBody] | [ImpulseURL, RequestDataBody, RequestOptionsWithoutUrlAndMethod];
|
|
30
|
+
export type HttpGetOptions = HttpOptions;
|
|
31
|
+
export type HttpDeleteOptions = HttpOptions;
|
|
32
|
+
export type HttpPostOptions = HttpDataOptions;
|
|
33
|
+
export type HttpPutOptions = HttpDataOptions;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { test, describe } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import create from '../index.js';
|
|
4
|
+
import nock from 'nock';
|
|
5
|
+
describe('CONFIG CREATE', () => {
|
|
6
|
+
test('Проверка верной конкатинации baseURL + URL', async (t) => {
|
|
7
|
+
const baseURL = 'http://localhost:3000/api/';
|
|
8
|
+
const url = 'users';
|
|
9
|
+
const expectedUrl = new URL(url, baseURL);
|
|
10
|
+
const mock = nock('http://localhost:3000')
|
|
11
|
+
.get('/api/users')
|
|
12
|
+
.reply(200, {});
|
|
13
|
+
const impulse = create({ baseURL });
|
|
14
|
+
const response = await impulse.get(url);
|
|
15
|
+
//t.diagnostic(response.url)
|
|
16
|
+
assert.strictEqual(response.url, expectedUrl.toString());
|
|
17
|
+
assert.ok(mock.isDone());
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe('HTTP GET', () => {
|
|
21
|
+
test('GET запрос с пустым URL значением должен вызвать ошибку', async () => {
|
|
22
|
+
const impulse = create();
|
|
23
|
+
try {
|
|
24
|
+
await impulse.get('');
|
|
25
|
+
assert.fail('Ожидалась ошибка при пустом URL');
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof Error) {
|
|
29
|
+
assert.ok(err.message.includes('Invalid'));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
assert.fail('Ошибка не является объектом Error');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
test('GET запрос', async () => {
|
|
37
|
+
const impulse = create();
|
|
38
|
+
const response = await impulse.get('https://jsonplaceholder.typicode.com/users');
|
|
39
|
+
assert.strictEqual(response.status, 200);
|
|
40
|
+
});
|
|
41
|
+
test('GET запрос с option params', async () => {
|
|
42
|
+
const impulse = create();
|
|
43
|
+
const response = await impulse.get('https://jsonplaceholder.typicode.com/users', {
|
|
44
|
+
params: {
|
|
45
|
+
id: 10
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
assert.strictEqual(response.status, 200);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('HTTP POST', () => {
|
|
52
|
+
test('POST запрос только с url (data по умолчанию = null), не должен приводить к функциональной ошибке', async () => {
|
|
53
|
+
const impulse = create();
|
|
54
|
+
const response = await impulse.post('https://jsonplaceholder.typicode.com/users');
|
|
55
|
+
assert.strictEqual(response.status, 201);
|
|
56
|
+
});
|
|
57
|
+
test('POST запрос c data', async () => {
|
|
58
|
+
const impulse = create();
|
|
59
|
+
const response = await impulse.post('https://jsonplaceholder.typicode.com/users', {
|
|
60
|
+
name: 'John Smit'
|
|
61
|
+
});
|
|
62
|
+
assert.strictEqual(response.status, 201);
|
|
63
|
+
});
|
|
64
|
+
test('POST запрос c data = null не должен приводить к функциональной ошибке', async () => {
|
|
65
|
+
const impulse = create();
|
|
66
|
+
const response = await impulse.post('https://jsonplaceholder.typicode.com/users', null);
|
|
67
|
+
assert.strictEqual(response.status, 201);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('HTTP PUT', () => {
|
|
71
|
+
test('PUT запрос только с url (data по умолчанию = null), не должен приводить к функциональной ошибке', async () => {
|
|
72
|
+
const impulse = create();
|
|
73
|
+
const response = await impulse.put('https://jsonplaceholder.typicode.com/users/1');
|
|
74
|
+
assert.strictEqual(response.status, 200);
|
|
75
|
+
});
|
|
76
|
+
test('PUT запрос c data', async () => {
|
|
77
|
+
const impulse = create();
|
|
78
|
+
const response = await impulse.put('https://jsonplaceholder.typicode.com/users/1', {
|
|
79
|
+
name: 'John Smit'
|
|
80
|
+
});
|
|
81
|
+
assert.strictEqual(response.status, 200);
|
|
82
|
+
});
|
|
83
|
+
test('PUT запрос c data = null не должен приводить к функциональной ошибке', async () => {
|
|
84
|
+
const impulse = create();
|
|
85
|
+
const response = await impulse.put('https://jsonplaceholder.typicode.com/users/1', null);
|
|
86
|
+
assert.strictEqual(response.status, 200);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('HTTP DELETE', () => {
|
|
90
|
+
test('DELETE запрос', async () => {
|
|
91
|
+
const impulse = create();
|
|
92
|
+
const response = await impulse.delete('https://jsonplaceholder.typicode.com/users/1');
|
|
93
|
+
assert.strictEqual(response.status, 200);
|
|
94
|
+
});
|
|
95
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "http-client-impulse",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A package for implementing HTTP requests in JavaScript",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "PaulBash",
|
|
7
|
+
"keywords": ["http", "get", "post", "put", "delete", "http client", "client"],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Paulbashk?tab=repositories"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist"],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"dev": "tsc & node dist/index.js",
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "tsc & node --test dist/tests/index.test.js",
|
|
24
|
+
"prepublishOnly": "npm run test",
|
|
25
|
+
"prepare": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^25.5.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"nock": "^14.0.11"
|
|
32
|
+
}
|
|
33
|
+
}
|