core-services-sdk 1.3.18 → 1.3.20
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/package.json +1 -2
- package/src/http/http.js +35 -84
- package/tests/http/http.unit.test.js +7 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-services-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.20",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"google-libphonenumber": "^3.2.42",
|
|
29
29
|
"http-status": "^2.1.0",
|
|
30
30
|
"mongodb": "^6.18.0",
|
|
31
|
-
"node-fetch": "^3.3.2",
|
|
32
31
|
"nodemailer": "^7.0.5",
|
|
33
32
|
"pino": "^9.7.0",
|
|
34
33
|
"uuid": "^11.1.0",
|
package/src/http/http.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A lightweight HTTP client wrapper around
|
|
3
|
-
*
|
|
2
|
+
* A lightweight HTTP client wrapper around Node.js native fetch
|
|
3
|
+
* supporting JSON, XML, and plain text responses.
|
|
4
|
+
* Provides simplified helper methods for GET, POST, PUT, PATCH, and DELETE
|
|
5
|
+
* with automatic error handling.
|
|
4
6
|
*
|
|
5
7
|
* @module http
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
|
-
import fetch from 'node-fetch'
|
|
9
10
|
import httpStatus from 'http-status'
|
|
10
11
|
import { parseStringPromise } from 'xml2js'
|
|
11
12
|
|
|
@@ -20,26 +21,27 @@ const JSON_HEADER = {
|
|
|
20
21
|
/**
|
|
21
22
|
* Checks if the HTTP status is considered successful (2xx).
|
|
22
23
|
*
|
|
23
|
-
* @param {
|
|
24
|
+
* @param {Response} response
|
|
24
25
|
* @returns {boolean}
|
|
25
26
|
*/
|
|
26
27
|
const isOkStatus = ({ status }) =>
|
|
27
28
|
status >= httpStatus.OK && status < httpStatus.MULTIPLE_CHOICES
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* Ensures the response has a successful status, otherwise throws HttpError.
|
|
32
|
+
*
|
|
33
|
+
* @param {Response} response
|
|
34
|
+
* @returns {Promise<Response>}
|
|
32
35
|
*/
|
|
33
36
|
const checkStatus = async (response) => {
|
|
34
37
|
if (!isOkStatus(response)) {
|
|
35
38
|
const text = await response.text()
|
|
36
|
-
const info = tryConvertJsonResponse(text)
|
|
37
39
|
const { status, statusText } = response
|
|
38
|
-
|
|
39
40
|
throw new HttpError({
|
|
40
41
|
code: status,
|
|
41
42
|
httpStatusCode: status,
|
|
42
43
|
httpStatusText: statusText,
|
|
44
|
+
extendInfo: { text },
|
|
43
45
|
})
|
|
44
46
|
}
|
|
45
47
|
return response
|
|
@@ -48,19 +50,17 @@ const checkStatus = async (response) => {
|
|
|
48
50
|
/**
|
|
49
51
|
* Reads the raw text from a fetch response.
|
|
50
52
|
*
|
|
51
|
-
* @param {
|
|
52
|
-
* @returns {Promise<string>}
|
|
53
|
+
* @param {Response} response
|
|
54
|
+
* @returns {Promise<string>}
|
|
53
55
|
*/
|
|
54
|
-
const getTextResponse = async (response) =>
|
|
55
|
-
return await response.text()
|
|
56
|
-
}
|
|
56
|
+
const getTextResponse = async (response) => response.text()
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Attempts to parse a JSON string.
|
|
60
60
|
*
|
|
61
|
-
* @param {string} responseText
|
|
62
|
-
* @returns {Object}
|
|
63
|
-
* @throws {SyntaxError} If
|
|
61
|
+
* @param {string} responseText
|
|
62
|
+
* @returns {Object}
|
|
63
|
+
* @throws {SyntaxError} If not valid JSON.
|
|
64
64
|
*/
|
|
65
65
|
const tryConvertJsonResponse = (responseText) => {
|
|
66
66
|
try {
|
|
@@ -72,10 +72,10 @@ const tryConvertJsonResponse = (responseText) => {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Extracts JSON from a response, or returns raw text on failure.
|
|
76
76
|
*
|
|
77
|
-
* @param {
|
|
78
|
-
* @returns {Promise<Object|string>}
|
|
77
|
+
* @param {Response} response
|
|
78
|
+
* @returns {Promise<Object|string>}
|
|
79
79
|
*/
|
|
80
80
|
const tryGetJsonResponse = async (response) => {
|
|
81
81
|
let jsonText
|
|
@@ -89,10 +89,10 @@ const tryGetJsonResponse = async (response) => {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
92
|
+
* Extracts XML from a response, or returns raw text on failure.
|
|
93
93
|
*
|
|
94
|
-
* @param {
|
|
95
|
-
* @returns {Promise<Object|string>}
|
|
94
|
+
* @param {Response} response
|
|
95
|
+
* @returns {Promise<Object|string>}
|
|
96
96
|
*/
|
|
97
97
|
const tryGetXmlResponse = async (response) => {
|
|
98
98
|
let xmlText
|
|
@@ -106,11 +106,11 @@ const tryGetXmlResponse = async (response) => {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
*
|
|
109
|
+
* Parses the fetch response based on expected type.
|
|
110
110
|
*
|
|
111
|
-
* @param {
|
|
112
|
-
* @param {string} responseType
|
|
113
|
-
* @returns {Promise<any>}
|
|
111
|
+
* @param {Response} response
|
|
112
|
+
* @param {string} responseType
|
|
113
|
+
* @returns {Promise<any>}
|
|
114
114
|
*/
|
|
115
115
|
const getResponsePayload = async (response, responseType) => {
|
|
116
116
|
switch (responseType) {
|
|
@@ -119,149 +119,100 @@ const getResponsePayload = async (response, responseType) => {
|
|
|
119
119
|
case ResponseType.xml:
|
|
120
120
|
return tryGetXmlResponse(response)
|
|
121
121
|
default:
|
|
122
|
-
case ResponseType.text:
|
|
123
122
|
return getTextResponse(response)
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
|
|
127
126
|
/**
|
|
128
127
|
* Sends an HTTP GET request.
|
|
129
|
-
*
|
|
130
|
-
* @param {Object} params
|
|
131
|
-
* @param {string} params.url - Target URL.
|
|
132
|
-
* @param {Object} [params.headers] - Optional request headers.
|
|
133
|
-
* @param {string} [params.credentials='include'] - Credential policy.
|
|
134
|
-
* @param {string} [params.expectedType='json'] - Expected response format.
|
|
135
|
-
* @returns {Promise<any>} Parsed response data.
|
|
136
128
|
*/
|
|
137
129
|
export const get = async ({
|
|
138
130
|
url,
|
|
139
131
|
headers = {},
|
|
140
|
-
credentials = 'include',
|
|
141
132
|
expectedType = ResponseType.json,
|
|
142
133
|
}) => {
|
|
143
|
-
/** @type {import('node-fetch').Response} */
|
|
144
134
|
const response = await fetch(url, {
|
|
145
135
|
method: HTTP_METHODS.GET,
|
|
146
136
|
headers: { ...JSON_HEADER, ...headers },
|
|
147
|
-
...(credentials ? { credentials } : {}),
|
|
148
137
|
})
|
|
149
|
-
|
|
150
138
|
await checkStatus(response)
|
|
151
|
-
return
|
|
139
|
+
return getResponsePayload(response, expectedType)
|
|
152
140
|
}
|
|
153
141
|
|
|
154
142
|
/**
|
|
155
143
|
* Sends an HTTP POST request.
|
|
156
|
-
*
|
|
157
|
-
* @param {Object} params
|
|
158
|
-
* @param {string} params.url - Target URL.
|
|
159
|
-
* @param {Object} params.body - Request body (will be JSON.stringify-ed).
|
|
160
|
-
* @param {Object} [params.headers] - Optional request headers.
|
|
161
|
-
* @param {string} [params.credentials='include'] - Credential policy.
|
|
162
|
-
* @param {string} [params.expectedType='json'] - Expected response format.
|
|
163
|
-
* @returns {Promise<any>} Parsed response data.
|
|
164
144
|
*/
|
|
165
145
|
export const post = async ({
|
|
166
146
|
url,
|
|
167
147
|
body,
|
|
168
|
-
headers,
|
|
169
|
-
credentials = 'include',
|
|
148
|
+
headers = {},
|
|
170
149
|
expectedType = ResponseType.json,
|
|
171
150
|
}) => {
|
|
172
151
|
const response = await fetch(url, {
|
|
173
152
|
method: HTTP_METHODS.POST,
|
|
174
153
|
headers: { ...JSON_HEADER, ...headers },
|
|
175
154
|
body: JSON.stringify(body),
|
|
176
|
-
...(credentials ? { credentials } : {}),
|
|
177
155
|
})
|
|
178
156
|
await checkStatus(response)
|
|
179
|
-
return
|
|
157
|
+
return getResponsePayload(response, expectedType)
|
|
180
158
|
}
|
|
181
159
|
|
|
182
160
|
/**
|
|
183
161
|
* Sends an HTTP PUT request.
|
|
184
|
-
*
|
|
185
|
-
* @param {Object} params
|
|
186
|
-
* @param {string} params.url
|
|
187
|
-
* @param {Object} params.body
|
|
188
|
-
* @param {Object} [params.headers]
|
|
189
|
-
* @param {string} [params.credentials='include']
|
|
190
|
-
* @param {string} [params.expectedType='json']
|
|
191
|
-
* @returns {Promise<any>} Parsed response data.
|
|
192
162
|
*/
|
|
193
163
|
export const put = async ({
|
|
194
164
|
url,
|
|
195
165
|
body,
|
|
196
166
|
headers = {},
|
|
197
|
-
credentials = 'include',
|
|
198
167
|
expectedType = ResponseType.json,
|
|
199
168
|
}) => {
|
|
200
169
|
const response = await fetch(url, {
|
|
201
170
|
method: HTTP_METHODS.PUT,
|
|
202
171
|
headers: { ...JSON_HEADER, ...headers },
|
|
203
172
|
body: JSON.stringify(body),
|
|
204
|
-
...(credentials ? { credentials } : {}),
|
|
205
173
|
})
|
|
206
174
|
await checkStatus(response)
|
|
207
|
-
return
|
|
175
|
+
return getResponsePayload(response, expectedType)
|
|
208
176
|
}
|
|
209
177
|
|
|
210
178
|
/**
|
|
211
179
|
* Sends an HTTP PATCH request.
|
|
212
|
-
*
|
|
213
|
-
* @param {Object} params - Same as `post`.
|
|
214
|
-
* @returns {Promise<any>} Parsed response data.
|
|
215
180
|
*/
|
|
216
181
|
export const patch = async ({
|
|
217
182
|
url,
|
|
218
183
|
body,
|
|
219
|
-
headers,
|
|
220
|
-
credentials = 'include',
|
|
184
|
+
headers = {},
|
|
221
185
|
expectedType = ResponseType.json,
|
|
222
186
|
}) => {
|
|
223
187
|
const response = await fetch(url, {
|
|
224
188
|
method: HTTP_METHODS.PATCH,
|
|
225
189
|
headers: { ...JSON_HEADER, ...headers },
|
|
226
190
|
body: JSON.stringify(body),
|
|
227
|
-
...(credentials ? { credentials } : {}),
|
|
228
191
|
})
|
|
229
192
|
await checkStatus(response)
|
|
230
|
-
return
|
|
193
|
+
return getResponsePayload(response, expectedType)
|
|
231
194
|
}
|
|
232
195
|
|
|
233
196
|
/**
|
|
234
197
|
* Sends an HTTP DELETE request.
|
|
235
|
-
*
|
|
236
|
-
* @param {Object} params - Same as `post`, but body is optional.
|
|
237
|
-
* @returns {Promise<any>} Parsed response data.
|
|
238
198
|
*/
|
|
239
199
|
export const deleteApi = async ({
|
|
240
200
|
url,
|
|
241
201
|
body,
|
|
242
|
-
headers,
|
|
243
|
-
credentials = 'include',
|
|
202
|
+
headers = {},
|
|
244
203
|
expectedType = ResponseType.json,
|
|
245
204
|
}) => {
|
|
246
205
|
const response = await fetch(url, {
|
|
247
206
|
method: HTTP_METHODS.DELETE,
|
|
248
207
|
headers: { ...JSON_HEADER, ...headers },
|
|
249
208
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
250
|
-
...(credentials ? { credentials } : {}),
|
|
251
209
|
})
|
|
252
210
|
await checkStatus(response)
|
|
253
|
-
return
|
|
211
|
+
return getResponsePayload(response, expectedType)
|
|
254
212
|
}
|
|
255
213
|
|
|
256
214
|
/**
|
|
257
|
-
* Consolidated HTTP client
|
|
258
|
-
*
|
|
259
|
-
* @namespace http
|
|
260
|
-
* @property {Function} get - HTTP GET method.
|
|
261
|
-
* @property {Function} post - HTTP POST method.
|
|
262
|
-
* @property {Function} put - HTTP PUT method.
|
|
263
|
-
* @property {Function} patch - HTTP PATCH method.
|
|
264
|
-
* @property {Function} deleteApi - HTTP DELETE method.
|
|
215
|
+
* Consolidated HTTP client.
|
|
265
216
|
*/
|
|
266
217
|
export const http = {
|
|
267
218
|
get,
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { vi, it, expect, describe, afterEach } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import fetch from 'node-fetch'
|
|
4
3
|
import { http } from '../../src/http/http.js'
|
|
5
4
|
import { HttpError } from '../../src/http/HttpError.js'
|
|
6
5
|
import { HTTP_METHODS } from '../../src/http/http-method.js'
|
|
7
6
|
import { ResponseType } from '../../src/http/responseType.js'
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
...actual,
|
|
13
|
-
default: vi.fn(),
|
|
14
|
-
}
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const mockFetch = /** @type {typeof fetch} */ (fetch)
|
|
8
|
+
// Mock the global fetch
|
|
9
|
+
const mockFetch = vi.fn()
|
|
10
|
+
global.fetch = mockFetch
|
|
18
11
|
|
|
19
12
|
const createMockResponse = ({
|
|
20
13
|
body = '',
|
|
@@ -23,22 +16,22 @@ const createMockResponse = ({
|
|
|
23
16
|
headers = {},
|
|
24
17
|
} = {}) => {
|
|
25
18
|
return {
|
|
26
|
-
ok: status >= 200 && status < 300,
|
|
27
19
|
status,
|
|
28
20
|
statusText,
|
|
29
21
|
text: vi.fn().mockResolvedValue(body),
|
|
30
|
-
headers: {
|
|
22
|
+
headers: {
|
|
23
|
+
get: vi.fn().mockImplementation((k) => headers[k]),
|
|
24
|
+
},
|
|
31
25
|
}
|
|
32
26
|
}
|
|
33
27
|
|
|
34
|
-
describe('http client', () => {
|
|
28
|
+
describe('http client (native fetch)', () => {
|
|
35
29
|
afterEach(() => {
|
|
36
30
|
vi.clearAllMocks()
|
|
37
31
|
})
|
|
38
32
|
|
|
39
33
|
describe('GET', () => {
|
|
40
34
|
it('should return parsed JSON response', async () => {
|
|
41
|
-
// @ts-ignore
|
|
42
35
|
mockFetch.mockResolvedValueOnce(
|
|
43
36
|
createMockResponse({ body: JSON.stringify({ hello: 'world' }) }),
|
|
44
37
|
)
|
|
@@ -52,7 +45,6 @@ describe('http client', () => {
|
|
|
52
45
|
})
|
|
53
46
|
|
|
54
47
|
it('should throw HttpError on non-2xx status', async () => {
|
|
55
|
-
// @ts-ignore
|
|
56
48
|
mockFetch.mockResolvedValueOnce(
|
|
57
49
|
createMockResponse({
|
|
58
50
|
status: 404,
|
|
@@ -69,7 +61,6 @@ describe('http client', () => {
|
|
|
69
61
|
|
|
70
62
|
describe('POST', () => {
|
|
71
63
|
it('should send a JSON body and return parsed JSON', async () => {
|
|
72
|
-
// @ts-ignore
|
|
73
64
|
mockFetch.mockResolvedValueOnce(
|
|
74
65
|
createMockResponse({ body: JSON.stringify({ ok: true }) }),
|
|
75
66
|
)
|
|
@@ -92,7 +83,6 @@ describe('http client', () => {
|
|
|
92
83
|
|
|
93
84
|
describe('PUT', () => {
|
|
94
85
|
it('should send a PUT request and return JSON', async () => {
|
|
95
|
-
// @ts-ignore
|
|
96
86
|
mockFetch.mockResolvedValueOnce(
|
|
97
87
|
createMockResponse({ body: JSON.stringify({ updated: true }) }),
|
|
98
88
|
)
|
|
@@ -108,7 +98,6 @@ describe('http client', () => {
|
|
|
108
98
|
|
|
109
99
|
describe('PATCH', () => {
|
|
110
100
|
it('should send a PATCH request and return JSON', async () => {
|
|
111
|
-
// @ts-ignore
|
|
112
101
|
mockFetch.mockResolvedValueOnce(
|
|
113
102
|
createMockResponse({ body: JSON.stringify({ patched: true }) }),
|
|
114
103
|
)
|
|
@@ -124,7 +113,6 @@ describe('http client', () => {
|
|
|
124
113
|
|
|
125
114
|
describe('DELETE', () => {
|
|
126
115
|
it('should send a DELETE request with body and return JSON', async () => {
|
|
127
|
-
// @ts-ignore
|
|
128
116
|
mockFetch.mockResolvedValueOnce(
|
|
129
117
|
createMockResponse({ body: JSON.stringify({ deleted: true }) }),
|
|
130
118
|
)
|
|
@@ -140,7 +128,6 @@ describe('http client', () => {
|
|
|
140
128
|
|
|
141
129
|
describe('ResponseType', () => {
|
|
142
130
|
it('should return text if expectedType is text', async () => {
|
|
143
|
-
// @ts-ignore
|
|
144
131
|
mockFetch.mockResolvedValueOnce(createMockResponse({ body: 'hello' }))
|
|
145
132
|
|
|
146
133
|
const result = await http.get({
|
|
@@ -153,7 +140,6 @@ describe('http client', () => {
|
|
|
153
140
|
|
|
154
141
|
it('should return XML as parsed object if expectedType is xml', async () => {
|
|
155
142
|
const xml = `<note><to>User</to><from>ChatGPT</from></note>`
|
|
156
|
-
// @ts-ignore
|
|
157
143
|
mockFetch.mockResolvedValueOnce(createMockResponse({ body: xml }))
|
|
158
144
|
|
|
159
145
|
const result = await http.get({
|