core-services-sdk 1.3.80 → 1.3.81
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 +4 -4
- package/src/http/http.js +79 -3
- package/src/http/responseType.js +1 -0
- package/tests/env/env-validation.demo.js +0 -1
- package/tests/http/http.unit.test.js +34 -0
- package/tests/http/responseType.unit.test.js +3 -1
- package/tests/mailer/mailer.unit.test.js +3 -3
- package/tests/mailer/transport.factory.unit.test.js +14 -14
- package/types/http/http.d.ts +2 -0
- package/types/http/responseType.d.ts +1 -0
- package/.claude/settings.local.json +0 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-services-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.81",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -49,14 +49,14 @@
|
|
|
49
49
|
"zod": "^4.3.6"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@vitest/coverage-v8": "^
|
|
53
|
-
"eslint": "^
|
|
52
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
53
|
+
"eslint": "^10.0.1",
|
|
54
54
|
"eslint-config-prettier": "^10.1.5",
|
|
55
55
|
"eslint-plugin-prettier": "^5.5.1",
|
|
56
56
|
"path": "^0.12.7",
|
|
57
57
|
"prettier": "^3.6.2",
|
|
58
58
|
"typescript": "^5.9.2",
|
|
59
59
|
"url": "^0.11.4",
|
|
60
|
-
"vitest": "^
|
|
60
|
+
"vitest": "^4.0.18"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/src/http/http.js
CHANGED
|
@@ -7,6 +7,50 @@
|
|
|
7
7
|
* @module http
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {'json' | 'xml' | 'text' | 'raw' | 'file'} ResponseTypeValue
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} HttpGetOptions
|
|
16
|
+
* @property {string} url - The URL to send the request to.
|
|
17
|
+
* @property {Record<string, string>} [headers] - Optional HTTP headers.
|
|
18
|
+
* @property {RequestInit} [extraParams] - Additional fetch options.
|
|
19
|
+
* @property {ResponseTypeValue} [expectedType] - Expected response type.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} HttpPostOptions
|
|
24
|
+
* @property {string} url - The URL to send the request to.
|
|
25
|
+
* @property {any} body - The request body to send.
|
|
26
|
+
* @property {Record<string, string>} [headers] - Optional HTTP headers.
|
|
27
|
+
* @property {ResponseTypeValue} [expectedType] - Expected response type.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} HttpPutOptions
|
|
32
|
+
* @property {string} url - The URL to send the request to.
|
|
33
|
+
* @property {any} body - The request body to send.
|
|
34
|
+
* @property {Record<string, string>} [headers] - Optional HTTP headers.
|
|
35
|
+
* @property {ResponseTypeValue} [expectedType] - Expected response type.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} HttpPatchOptions
|
|
40
|
+
* @property {string} url - The URL to send the request to.
|
|
41
|
+
* @property {any} body - The request body to send.
|
|
42
|
+
* @property {Record<string, string>} [headers] - Optional HTTP headers.
|
|
43
|
+
* @property {ResponseTypeValue} [expectedType] - Expected response type.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {Object} HttpDeleteOptions
|
|
48
|
+
* @property {string} url - The URL to send the request to.
|
|
49
|
+
* @property {any} [body] - Optional request body to send.
|
|
50
|
+
* @property {Record<string, string>} [headers] - Optional HTTP headers.
|
|
51
|
+
* @property {ResponseTypeValue} [expectedType] - Expected response type.
|
|
52
|
+
*/
|
|
53
|
+
|
|
10
54
|
import httpStatus from 'http-status'
|
|
11
55
|
import { parseStringPromise } from 'xml2js'
|
|
12
56
|
|
|
@@ -25,7 +69,7 @@ const JSON_HEADER = {
|
|
|
25
69
|
* @returns {boolean}
|
|
26
70
|
*/
|
|
27
71
|
const isOkStatus = ({ status }) =>
|
|
28
|
-
status >= httpStatus.OK && status < httpStatus.
|
|
72
|
+
status >= httpStatus.OK && status < httpStatus.BAD_REQUEST
|
|
29
73
|
|
|
30
74
|
/**
|
|
31
75
|
* Ensures the response has a successful status, otherwise throws HttpError.
|
|
@@ -121,6 +165,8 @@ const getResponsePayload = async (response, responseType) => {
|
|
|
121
165
|
return tryGetJsonResponse(response)
|
|
122
166
|
case ResponseType.xml:
|
|
123
167
|
return tryGetXmlResponse(response)
|
|
168
|
+
case ResponseType.raw:
|
|
169
|
+
return response
|
|
124
170
|
default:
|
|
125
171
|
return getTextResponse(response)
|
|
126
172
|
}
|
|
@@ -128,13 +174,19 @@ const getResponsePayload = async (response, responseType) => {
|
|
|
128
174
|
|
|
129
175
|
/**
|
|
130
176
|
* Sends an HTTP GET request.
|
|
177
|
+
*
|
|
178
|
+
* @param {HttpGetOptions} options - The request options.
|
|
179
|
+
* @returns {Promise<any>} The parsed response based on expectedType.
|
|
180
|
+
* @throws {HttpError} If the response status is not successful.
|
|
131
181
|
*/
|
|
132
182
|
export const get = async ({
|
|
133
183
|
url,
|
|
134
184
|
headers = {},
|
|
185
|
+
extraParams = {},
|
|
135
186
|
expectedType = ResponseType.json,
|
|
136
187
|
}) => {
|
|
137
188
|
const response = await fetch(url, {
|
|
189
|
+
...extraParams,
|
|
138
190
|
method: HTTP_METHODS.GET,
|
|
139
191
|
headers: { ...JSON_HEADER, ...headers },
|
|
140
192
|
})
|
|
@@ -144,6 +196,10 @@ export const get = async ({
|
|
|
144
196
|
|
|
145
197
|
/**
|
|
146
198
|
* Sends an HTTP POST request.
|
|
199
|
+
*
|
|
200
|
+
* @param {HttpPostOptions} options - The request options.
|
|
201
|
+
* @returns {Promise<any>} The parsed response based on expectedType.
|
|
202
|
+
* @throws {HttpError} If the response status is not successful.
|
|
147
203
|
*/
|
|
148
204
|
export const post = async ({
|
|
149
205
|
url,
|
|
@@ -162,6 +218,10 @@ export const post = async ({
|
|
|
162
218
|
|
|
163
219
|
/**
|
|
164
220
|
* Sends an HTTP PUT request.
|
|
221
|
+
*
|
|
222
|
+
* @param {HttpPutOptions} options - The request options.
|
|
223
|
+
* @returns {Promise<any>} The parsed response based on expectedType.
|
|
224
|
+
* @throws {HttpError} If the response status is not successful.
|
|
165
225
|
*/
|
|
166
226
|
export const put = async ({
|
|
167
227
|
url,
|
|
@@ -172,7 +232,7 @@ export const put = async ({
|
|
|
172
232
|
const response = await fetch(url, {
|
|
173
233
|
method: HTTP_METHODS.PUT,
|
|
174
234
|
headers: { ...JSON_HEADER, ...headers },
|
|
175
|
-
body: JSON.stringify(body),
|
|
235
|
+
body: expectedType === ResponseType.json ? JSON.stringify(body) : body,
|
|
176
236
|
})
|
|
177
237
|
await checkStatus(response)
|
|
178
238
|
return getResponsePayload(response, expectedType)
|
|
@@ -180,6 +240,10 @@ export const put = async ({
|
|
|
180
240
|
|
|
181
241
|
/**
|
|
182
242
|
* Sends an HTTP PATCH request.
|
|
243
|
+
*
|
|
244
|
+
* @param {HttpPatchOptions} options - The request options.
|
|
245
|
+
* @returns {Promise<any>} The parsed response based on expectedType.
|
|
246
|
+
* @throws {HttpError} If the response status is not successful.
|
|
183
247
|
*/
|
|
184
248
|
export const patch = async ({
|
|
185
249
|
url,
|
|
@@ -198,6 +262,10 @@ export const patch = async ({
|
|
|
198
262
|
|
|
199
263
|
/**
|
|
200
264
|
* Sends an HTTP DELETE request.
|
|
265
|
+
*
|
|
266
|
+
* @param {HttpDeleteOptions} options - The request options.
|
|
267
|
+
* @returns {Promise<any>} The parsed response based on expectedType.
|
|
268
|
+
* @throws {HttpError} If the response status is not successful.
|
|
201
269
|
*/
|
|
202
270
|
export const deleteApi = async ({
|
|
203
271
|
url,
|
|
@@ -215,7 +283,15 @@ export const deleteApi = async ({
|
|
|
215
283
|
}
|
|
216
284
|
|
|
217
285
|
/**
|
|
218
|
-
* Consolidated HTTP client.
|
|
286
|
+
* Consolidated HTTP client with methods for common HTTP operations.
|
|
287
|
+
*
|
|
288
|
+
* @type {{
|
|
289
|
+
* get: (options: HttpGetOptions) => Promise<any>,
|
|
290
|
+
* put: (options: HttpPutOptions) => Promise<any>,
|
|
291
|
+
* post: (options: HttpPostOptions) => Promise<any>,
|
|
292
|
+
* patch: (options: HttpPatchOptions) => Promise<any>,
|
|
293
|
+
* deleteApi: (options: HttpDeleteOptions) => Promise<any>
|
|
294
|
+
* }}
|
|
219
295
|
*/
|
|
220
296
|
export const http = {
|
|
221
297
|
get,
|
package/src/http/responseType.js
CHANGED
|
@@ -149,5 +149,39 @@ describe('http client (native fetch)', () => {
|
|
|
149
149
|
|
|
150
150
|
expect(result).toHaveProperty('note')
|
|
151
151
|
})
|
|
152
|
+
|
|
153
|
+
it('should return raw response object if expectedType is raw', async () => {
|
|
154
|
+
const mockResponse = createMockResponse({ body: 'raw data', status: 200 })
|
|
155
|
+
mockFetch.mockResolvedValueOnce(mockResponse)
|
|
156
|
+
|
|
157
|
+
const result = await http.get({
|
|
158
|
+
url: 'http://test.com',
|
|
159
|
+
expectedType: ResponseType.raw,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
expect(result).toBe(mockResponse)
|
|
163
|
+
expect(result.status).toBe(200)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('PUT with non-JSON body', () => {
|
|
168
|
+
it('should not stringify body when expectedType is not json', async () => {
|
|
169
|
+
const rawBody = 'plain text body'
|
|
170
|
+
mockFetch.mockResolvedValueOnce(createMockResponse({ body: 'OK' }))
|
|
171
|
+
|
|
172
|
+
await http.put({
|
|
173
|
+
url: 'http://test.com',
|
|
174
|
+
body: rawBody,
|
|
175
|
+
expectedType: ResponseType.text,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
179
|
+
'http://test.com',
|
|
180
|
+
expect.objectContaining({
|
|
181
|
+
method: 'PUT',
|
|
182
|
+
body: rawBody,
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
})
|
|
152
186
|
})
|
|
153
187
|
})
|
|
@@ -7,6 +7,7 @@ describe('ResponseType', () => {
|
|
|
7
7
|
it('should contain correct response type mappings', () => {
|
|
8
8
|
expect(ResponseType).toEqual({
|
|
9
9
|
xml: 'xml',
|
|
10
|
+
raw: 'raw',
|
|
10
11
|
json: 'json',
|
|
11
12
|
text: 'text',
|
|
12
13
|
file: 'file',
|
|
@@ -32,7 +33,7 @@ describe('ResponseType', () => {
|
|
|
32
33
|
|
|
33
34
|
it('should include expected keys', () => {
|
|
34
35
|
const keys = Object.keys(ResponseType)
|
|
35
|
-
expect(keys).toEqual(['xml', 'json', 'text', 'file'])
|
|
36
|
+
expect(keys).toEqual(['xml', 'raw', 'json', 'text', 'file'])
|
|
36
37
|
})
|
|
37
38
|
|
|
38
39
|
it('should include expected values', () => {
|
|
@@ -41,5 +42,6 @@ describe('ResponseType', () => {
|
|
|
41
42
|
expect(values).toContain('xml')
|
|
42
43
|
expect(values).toContain('text')
|
|
43
44
|
expect(values).toContain('file')
|
|
45
|
+
expect(values).toContain('raw')
|
|
44
46
|
})
|
|
45
47
|
})
|
|
@@ -17,9 +17,9 @@ vi.mock('../../src/mailer/transport.factory.js', async () => {
|
|
|
17
17
|
|
|
18
18
|
// Mock Mailer class
|
|
19
19
|
vi.mock('../../src/mailer/mailer.service.js', async () => {
|
|
20
|
-
const Mailer = vi.fn(
|
|
21
|
-
send
|
|
22
|
-
})
|
|
20
|
+
const Mailer = vi.fn(function (transport) {
|
|
21
|
+
this.send = vi.fn((opts) => transport.sendMail(opts))
|
|
22
|
+
})
|
|
23
23
|
return { Mailer }
|
|
24
24
|
})
|
|
25
25
|
|
|
@@ -30,8 +30,12 @@ vi.mock('@sendgrid/mail', () => {
|
|
|
30
30
|
// ---------------- Mock AWS SESv2 ----------------
|
|
31
31
|
vi.mock('@aws-sdk/client-sesv2', () => {
|
|
32
32
|
const mockSesInstance = { mocked: true }
|
|
33
|
-
const SESv2Client = vi.fn(()
|
|
34
|
-
|
|
33
|
+
const SESv2Client = vi.fn(function () {
|
|
34
|
+
Object.assign(this, mockSesInstance)
|
|
35
|
+
})
|
|
36
|
+
const SendEmailCommand = vi.fn(function () {
|
|
37
|
+
return 'mocked-command'
|
|
38
|
+
})
|
|
35
39
|
|
|
36
40
|
globalThis.__mockedSesv2Client__ = SESv2Client
|
|
37
41
|
globalThis.__mockedSesv2Instance__ = mockSesInstance
|
|
@@ -137,12 +141,10 @@ describe('TransportFactory', () => {
|
|
|
137
141
|
})
|
|
138
142
|
|
|
139
143
|
const args = nodemailer.createTransport.mock.calls[0][0]
|
|
140
|
-
expect(args).
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
},
|
|
145
|
-
})
|
|
144
|
+
expect(args.SES.sesClient).toBeInstanceOf(globalThis.__mockedSesv2Client__)
|
|
145
|
+
expect(args.SES.SendEmailCommand).toBe(
|
|
146
|
+
globalThis.__mockedSendEmailCommand__,
|
|
147
|
+
)
|
|
146
148
|
|
|
147
149
|
expect(transport.type).toBe('mock-transport')
|
|
148
150
|
})
|
|
@@ -162,12 +164,10 @@ describe('TransportFactory', () => {
|
|
|
162
164
|
})
|
|
163
165
|
|
|
164
166
|
const args = nodemailer.createTransport.mock.calls[0][0]
|
|
165
|
-
expect(args).
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
})
|
|
167
|
+
expect(args.SES.sesClient).toBeInstanceOf(globalThis.__mockedSesv2Client__)
|
|
168
|
+
expect(args.SES.SendEmailCommand).toBe(
|
|
169
|
+
globalThis.__mockedSendEmailCommand__,
|
|
170
|
+
)
|
|
171
171
|
|
|
172
172
|
expect(transport.type).toBe('mock-transport')
|
|
173
173
|
})
|
package/types/http/http.d.ts
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(npm test:*)",
|
|
5
|
-
"Bash(docker rm:*)",
|
|
6
|
-
"Bash(docker run:*)",
|
|
7
|
-
"Bash(mongosh:*)",
|
|
8
|
-
"Bash(for:*)",
|
|
9
|
-
"Bash(do if mongosh --port 27099 --eval \"db.runCommand({ ping: 1 })\")",
|
|
10
|
-
"Bash(then echo \"Connected after $i attempts\")",
|
|
11
|
-
"Bash(break)",
|
|
12
|
-
"Bash(fi)",
|
|
13
|
-
"Bash(echo:*)",
|
|
14
|
-
"Bash(done)",
|
|
15
|
-
"Bash(docker logs:*)",
|
|
16
|
-
"Bash(docker system:*)",
|
|
17
|
-
"Bash(docker volume prune:*)",
|
|
18
|
-
"Bash(docker image prune:*)",
|
|
19
|
-
"Bash(docker builder prune:*)"
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
}
|