core-services-sdk 1.3.3 → 1.3.5
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 -1
- package/src/core/combine-unique-arrays.js +15 -0
- package/src/core/index.js +5 -0
- package/src/core/normalize-to-array.js +31 -0
- package/src/core/otp-generators.js +113 -0
- package/src/core/regex-utils.js +27 -0
- package/src/core/sanitize-objects.js +50 -0
- package/src/crypto/crypto.js +128 -0
- package/src/crypto/encryption.js +48 -0
- package/src/crypto/index.js +2 -0
- package/src/fastify/error-handlers/with-error-handling.js +5 -1
- package/src/http/http.js +111 -52
- package/src/ids/generators.js +73 -0
- package/src/ids/prefixes.js +28 -0
- package/src/index.js +8 -1
- package/src/logger/get-logger.js +64 -0
- package/src/logger/index.js +1 -0
- package/src/mongodb/index.js +3 -73
- package/src/mongodb/initialize-mongodb.js +73 -0
- package/src/mongodb/validate-mongo-uri.js +32 -0
- package/tests/core/combine-unique-arrays.unit.test.js +35 -0
- package/tests/core/normalize-to-array.unit.test.js +43 -0
- package/tests/core/otp-generators.unit.test.js +93 -0
- package/tests/core/regex-utils.unit.test.js +41 -0
- package/tests/core/sanitize-objects.unit.test.js +78 -0
- package/tests/crypto/crypto.unit.test.js +130 -0
- package/tests/crypto/encryption.unit.test.js +73 -0
- package/tests/ids/generators.unit.test.js +72 -0
- package/tests/ids/prefixes.unit.test.js +42 -0
- package/tests/logger/get-logger.unit.test.js +64 -0
- package/src/fastify/error-handlers/with-error-handling.types.js +0 -10
package/src/http/http.js
CHANGED
|
@@ -10,10 +10,22 @@ const JSON_HEADER = {
|
|
|
10
10
|
'Content-Type': 'application/json',
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the HTTP status is considered successful (2xx).
|
|
15
|
+
*
|
|
16
|
+
* @param {Response} res - The fetch response object.
|
|
17
|
+
* @returns {boolean} `true` if status code is between 200-299.
|
|
18
|
+
*/
|
|
13
19
|
const isOkStatus = ({ status }) =>
|
|
14
|
-
// Range of response OK
|
|
15
20
|
status >= httpStatus.OK && status < httpStatus.MULTIPLE_CHOICES
|
|
16
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Verifies response status and throws a structured HttpError if not OK.
|
|
24
|
+
*
|
|
25
|
+
* @param {Response} res - The fetch response object.
|
|
26
|
+
* @throws {HttpError} When the response status is not OK.
|
|
27
|
+
* @returns {Response} The response if status is OK.
|
|
28
|
+
*/
|
|
17
29
|
const checkStatus = async (res) => {
|
|
18
30
|
if (!isOkStatus(res)) {
|
|
19
31
|
const text = await res.text()
|
|
@@ -30,63 +42,95 @@ const checkStatus = async (res) => {
|
|
|
30
42
|
return res
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Reads the raw text from a fetch response.
|
|
47
|
+
*
|
|
48
|
+
* @param {Response} response - The fetch response.
|
|
49
|
+
* @returns {Promise<string>} The text body.
|
|
50
|
+
*/
|
|
33
51
|
const getTextResponse = async (response) => {
|
|
34
|
-
|
|
35
|
-
return text
|
|
52
|
+
return await response.text()
|
|
36
53
|
}
|
|
37
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Attempts to parse a JSON string.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} responseText - A raw JSON string.
|
|
59
|
+
* @returns {Object} Parsed JSON object.
|
|
60
|
+
* @throws {SyntaxError} If the string is not valid JSON.
|
|
61
|
+
*/
|
|
38
62
|
const tryConvertJsonResponse = (responseText) => {
|
|
39
63
|
try {
|
|
40
|
-
|
|
41
|
-
return obj
|
|
64
|
+
return JSON.parse(responseText)
|
|
42
65
|
} catch (error) {
|
|
43
66
|
error.responseText = responseText
|
|
44
67
|
throw error
|
|
45
68
|
}
|
|
46
69
|
}
|
|
47
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Attempts to extract a JSON object from a fetch response.
|
|
73
|
+
*
|
|
74
|
+
* @param {Response} response - The fetch response.
|
|
75
|
+
* @returns {Promise<Object|string>} Parsed object or raw string on failure.
|
|
76
|
+
*/
|
|
48
77
|
const tryGetJsonResponse = async (response) => {
|
|
49
78
|
let jsonText
|
|
50
79
|
try {
|
|
51
|
-
jsonText = getTextResponse(response)
|
|
52
|
-
|
|
53
|
-
return obj
|
|
80
|
+
jsonText = await getTextResponse(response)
|
|
81
|
+
return tryConvertJsonResponse(jsonText)
|
|
54
82
|
} catch (error) {
|
|
55
|
-
if (!jsonText)
|
|
56
|
-
throw error
|
|
57
|
-
}
|
|
83
|
+
if (!jsonText) throw error
|
|
58
84
|
return jsonText
|
|
59
85
|
}
|
|
60
86
|
}
|
|
61
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Attempts to extract an XML object from a fetch response.
|
|
90
|
+
*
|
|
91
|
+
* @param {Response} response - The fetch response.
|
|
92
|
+
* @returns {Promise<Object|string>} Parsed XML object or raw string.
|
|
93
|
+
*/
|
|
62
94
|
const tryGetXmlResponse = async (response) => {
|
|
63
95
|
let xmlText
|
|
64
96
|
try {
|
|
65
97
|
xmlText = await getTextResponse(response)
|
|
66
|
-
|
|
67
|
-
return xml
|
|
98
|
+
return await parseStringPromise(xmlText)
|
|
68
99
|
} catch (error) {
|
|
69
|
-
if (!xmlText)
|
|
70
|
-
throw error
|
|
71
|
-
}
|
|
100
|
+
if (!xmlText) throw error
|
|
72
101
|
return xmlText
|
|
73
102
|
}
|
|
74
103
|
}
|
|
75
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Extracts and parses the fetch response body based on expected type.
|
|
107
|
+
*
|
|
108
|
+
* @param {Response} response - The fetch response.
|
|
109
|
+
* @param {string} responseType - The expected response type ('json', 'xml', 'text').
|
|
110
|
+
* @returns {Promise<any>} The parsed response payload.
|
|
111
|
+
*/
|
|
76
112
|
const getResponsePayload = async (response, responseType) => {
|
|
77
113
|
switch (responseType) {
|
|
78
114
|
case ResponseType.json:
|
|
79
115
|
return tryGetJsonResponse(response)
|
|
80
|
-
|
|
81
116
|
case ResponseType.xml:
|
|
82
117
|
return tryGetXmlResponse(response)
|
|
83
|
-
|
|
84
118
|
default:
|
|
85
119
|
case ResponseType.text:
|
|
86
120
|
return getTextResponse(response)
|
|
87
121
|
}
|
|
88
122
|
}
|
|
89
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Sends an HTTP GET request.
|
|
126
|
+
*
|
|
127
|
+
* @param {Object} params
|
|
128
|
+
* @param {string} params.url - The target URL.
|
|
129
|
+
* @param {Object} [params.headers] - Optional custom headers.
|
|
130
|
+
* @param {string} [params.credentials='include'] - Credential mode.
|
|
131
|
+
* @param {string} [params.expectedType='json'] - Response type.
|
|
132
|
+
* @returns {Promise<any>} Parsed response data.
|
|
133
|
+
*/
|
|
90
134
|
export const get = async ({
|
|
91
135
|
url,
|
|
92
136
|
headers = {},
|
|
@@ -95,18 +139,24 @@ export const get = async ({
|
|
|
95
139
|
}) => {
|
|
96
140
|
const response = await fetch(url, {
|
|
97
141
|
method: HTTP_METHODS.GET,
|
|
98
|
-
headers: {
|
|
99
|
-
...JSON_HEADER,
|
|
100
|
-
...headers,
|
|
101
|
-
},
|
|
142
|
+
headers: { ...JSON_HEADER, ...headers },
|
|
102
143
|
...(credentials ? { credentials } : {}),
|
|
103
144
|
})
|
|
104
|
-
|
|
105
145
|
await checkStatus(response)
|
|
106
|
-
|
|
107
|
-
return data
|
|
146
|
+
return await getResponsePayload(response, expectedType)
|
|
108
147
|
}
|
|
109
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Sends an HTTP POST request.
|
|
151
|
+
*
|
|
152
|
+
* @param {Object} params
|
|
153
|
+
* @param {string} params.url - The target URL.
|
|
154
|
+
* @param {Object} params.body - Body data to send.
|
|
155
|
+
* @param {Object} [params.headers] - Optional custom headers.
|
|
156
|
+
* @param {string} [params.credentials='include'] - Credential mode.
|
|
157
|
+
* @param {string} [params.expectedType='json'] - Response type.
|
|
158
|
+
* @returns {Promise<any>} Parsed response data.
|
|
159
|
+
*/
|
|
110
160
|
export const post = async ({
|
|
111
161
|
url,
|
|
112
162
|
body,
|
|
@@ -116,18 +166,20 @@ export const post = async ({
|
|
|
116
166
|
}) => {
|
|
117
167
|
const response = await fetch(url, {
|
|
118
168
|
method: HTTP_METHODS.POST,
|
|
119
|
-
headers: {
|
|
120
|
-
...JSON_HEADER,
|
|
121
|
-
...headers,
|
|
122
|
-
},
|
|
169
|
+
headers: { ...JSON_HEADER, ...headers },
|
|
123
170
|
body: JSON.stringify(body),
|
|
124
171
|
...(credentials ? { credentials } : {}),
|
|
125
172
|
})
|
|
126
173
|
await checkStatus(response)
|
|
127
|
-
|
|
128
|
-
return data
|
|
174
|
+
return await getResponsePayload(response, expectedType)
|
|
129
175
|
}
|
|
130
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Sends an HTTP PUT request.
|
|
179
|
+
*
|
|
180
|
+
* @param {Object} params - Same as `post`.
|
|
181
|
+
* @returns {Promise<any>} Parsed response data.
|
|
182
|
+
*/
|
|
131
183
|
export const put = async ({
|
|
132
184
|
url,
|
|
133
185
|
body,
|
|
@@ -137,19 +189,20 @@ export const put = async ({
|
|
|
137
189
|
}) => {
|
|
138
190
|
const response = await fetch(url, {
|
|
139
191
|
method: HTTP_METHODS.PUT,
|
|
140
|
-
headers: {
|
|
141
|
-
...JSON_HEADER,
|
|
142
|
-
...headers,
|
|
143
|
-
},
|
|
192
|
+
headers: { ...JSON_HEADER, ...headers },
|
|
144
193
|
body: JSON.stringify(body),
|
|
145
194
|
...(credentials ? { credentials } : {}),
|
|
146
195
|
})
|
|
147
|
-
|
|
148
196
|
await checkStatus(response)
|
|
149
|
-
|
|
150
|
-
return data
|
|
197
|
+
return await getResponsePayload(response, expectedType)
|
|
151
198
|
}
|
|
152
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Sends an HTTP PATCH request.
|
|
202
|
+
*
|
|
203
|
+
* @param {Object} params - Same as `post`.
|
|
204
|
+
* @returns {Promise<any>} Parsed response data.
|
|
205
|
+
*/
|
|
153
206
|
export const patch = async ({
|
|
154
207
|
url,
|
|
155
208
|
body,
|
|
@@ -159,19 +212,20 @@ export const patch = async ({
|
|
|
159
212
|
}) => {
|
|
160
213
|
const response = await fetch(url, {
|
|
161
214
|
method: HTTP_METHODS.PATCH,
|
|
162
|
-
headers: {
|
|
163
|
-
...JSON_HEADER,
|
|
164
|
-
...headers,
|
|
165
|
-
},
|
|
215
|
+
headers: { ...JSON_HEADER, ...headers },
|
|
166
216
|
body: JSON.stringify(body),
|
|
167
217
|
...(credentials ? { credentials } : {}),
|
|
168
218
|
})
|
|
169
|
-
|
|
170
219
|
await checkStatus(response)
|
|
171
|
-
|
|
172
|
-
return data
|
|
220
|
+
return await getResponsePayload(response, expectedType)
|
|
173
221
|
}
|
|
174
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Sends an HTTP DELETE request.
|
|
225
|
+
*
|
|
226
|
+
* @param {Object} params - Same as `post`, but body is optional.
|
|
227
|
+
* @returns {Promise<any>} Parsed response data.
|
|
228
|
+
*/
|
|
175
229
|
export const deleteApi = async ({
|
|
176
230
|
url,
|
|
177
231
|
body,
|
|
@@ -181,19 +235,24 @@ export const deleteApi = async ({
|
|
|
181
235
|
}) => {
|
|
182
236
|
const response = await fetch(url, {
|
|
183
237
|
method: HTTP_METHODS.DELETE,
|
|
184
|
-
headers: {
|
|
185
|
-
...JSON_HEADER,
|
|
186
|
-
...headers,
|
|
187
|
-
},
|
|
238
|
+
headers: { ...JSON_HEADER, ...headers },
|
|
188
239
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
189
240
|
...(credentials ? { credentials } : {}),
|
|
190
241
|
})
|
|
191
|
-
|
|
192
242
|
await checkStatus(response)
|
|
193
|
-
|
|
194
|
-
return data
|
|
243
|
+
return await getResponsePayload(response, expectedType)
|
|
195
244
|
}
|
|
196
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Consolidated HTTP client with method shortcuts.
|
|
248
|
+
*
|
|
249
|
+
* @namespace http
|
|
250
|
+
* @property {Function} get - HTTP GET method.
|
|
251
|
+
* @property {Function} post - HTTP POST method.
|
|
252
|
+
* @property {Function} put - HTTP PUT method.
|
|
253
|
+
* @property {Function} patch - HTTP PATCH method.
|
|
254
|
+
* @property {Function} deleteApi - HTTP DELETE method.
|
|
255
|
+
*/
|
|
197
256
|
export const http = {
|
|
198
257
|
get,
|
|
199
258
|
put,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
2
|
+
import { ID_PREFIXES } from './prefixes.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates a new UUID v4 string.
|
|
6
|
+
*
|
|
7
|
+
* @returns {string} A new UUID (version 4).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* generateId() // '550e8400-e29b-41d4-a716-446655440000'
|
|
11
|
+
*/
|
|
12
|
+
export const generateId = () => {
|
|
13
|
+
return uuidv4()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generates a unique ID with the given prefix.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} prefix - A prefix string to prepend to the UUID.
|
|
20
|
+
* @returns {string} A unique ID in the format `${prefix}_${uuid}`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* generatePrefixedId('usr') // 'usr_550e8400-e29b-41d4-a716-446655440000'
|
|
24
|
+
*/
|
|
25
|
+
export const generatePrefixedId = (prefix) => {
|
|
26
|
+
return `${prefix}_${generateId()}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generates a user ID with a `usr_` prefix.
|
|
31
|
+
*
|
|
32
|
+
* @returns {string} A user ID.
|
|
33
|
+
*/
|
|
34
|
+
export const generateUserId = () => generatePrefixedId(ID_PREFIXES.USER)
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generates a tenant ID with a `tnt_` prefix.
|
|
38
|
+
*
|
|
39
|
+
* @returns {string} A tenant ID.
|
|
40
|
+
*/
|
|
41
|
+
export const generateTenantId = () => generatePrefixedId(ID_PREFIXES.TENANT)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generates a permission ID with a `prm_` prefix.
|
|
45
|
+
*
|
|
46
|
+
* @returns {string} A permission ID.
|
|
47
|
+
*/
|
|
48
|
+
export const generatePermissionId = () =>
|
|
49
|
+
generatePrefixedId(ID_PREFIXES.PERMISSION)
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generates a correlation ID with a `crln_` prefix.
|
|
53
|
+
*
|
|
54
|
+
* @returns {string} A correlation ID.
|
|
55
|
+
*/
|
|
56
|
+
export const generateCorrelationId = () =>
|
|
57
|
+
generatePrefixedId(ID_PREFIXES.CORRELATION)
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generates a verification ID with a `vrf_` prefix.
|
|
61
|
+
*
|
|
62
|
+
* @returns {string} A verification ID.
|
|
63
|
+
*/
|
|
64
|
+
export const generateVerificationId = () =>
|
|
65
|
+
generatePrefixedId(ID_PREFIXES.VERIFICATION)
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generates a role permissions ID with a `role_` prefix.
|
|
69
|
+
*
|
|
70
|
+
* @returns {string} A role permissions ID.
|
|
71
|
+
*/
|
|
72
|
+
export const generateRolePermissionsId = () =>
|
|
73
|
+
generatePrefixedId(ID_PREFIXES.ROLE_PERMISSIONS)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping of entity types to their unique ID prefixes.
|
|
3
|
+
*
|
|
4
|
+
* These prefixes are prepended to UUIDs to create consistent and identifiable IDs across the system.
|
|
5
|
+
* For example: 'usr_550e8400-e29b-41d4-a716-446655440000'
|
|
6
|
+
*
|
|
7
|
+
* @readonly
|
|
8
|
+
* @enum {string}
|
|
9
|
+
*/
|
|
10
|
+
export const ID_PREFIXES = Object.freeze({
|
|
11
|
+
/** User entity ID prefix */
|
|
12
|
+
USER: 'usr',
|
|
13
|
+
|
|
14
|
+
/** Tenant entity ID prefix */
|
|
15
|
+
TENANT: 'tnt',
|
|
16
|
+
|
|
17
|
+
/** Permission entity ID prefix */
|
|
18
|
+
PERMISSION: 'prm',
|
|
19
|
+
|
|
20
|
+
/** Correlation ID prefix (e.g., for tracing requests) */
|
|
21
|
+
CORRELATION: 'crln',
|
|
22
|
+
|
|
23
|
+
/** Verification entity ID prefix (e.g., email/phone code) */
|
|
24
|
+
VERIFICATION: 'vrf',
|
|
25
|
+
|
|
26
|
+
/** Role-permissions mapping ID prefix */
|
|
27
|
+
ROLE_PERMISSIONS: 'role',
|
|
28
|
+
})
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
export * from './fastify/index.js'
|
|
2
2
|
export * from './mongodb/index.js'
|
|
3
|
-
export * from './
|
|
3
|
+
export * from './crypto/crypto.js'
|
|
4
4
|
export * from './rabbit-mq/index.js'
|
|
5
|
+
export * from './logger/get-logger.js'
|
|
6
|
+
export * from './core/regex-utils.js'
|
|
5
7
|
export * as http from './http/http.js'
|
|
6
8
|
export * from './http/responseType.js'
|
|
9
|
+
export * from './crypto/encryption.js'
|
|
10
|
+
export * from './core/otp-generators.js'
|
|
11
|
+
export * from './core/sanitize-objects.js'
|
|
12
|
+
export * from './core/normalize-to-array.js'
|
|
7
13
|
export { initMailer } from './mailer/index.js'
|
|
14
|
+
export * from './core/combine-unique-arrays.js'
|
|
8
15
|
export { HttpError } from './http/HttpError.js'
|
|
9
16
|
export { Mailer } from './mailer/mailer.service.js'
|
|
10
17
|
export { TransportFactory } from './mailer/transport.factory.js'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import pino from 'pino'
|
|
2
|
+
|
|
3
|
+
const requiredMethods = ['info', 'warn', 'error', 'debug']
|
|
4
|
+
|
|
5
|
+
const dummyLogger = {
|
|
6
|
+
info: () => {},
|
|
7
|
+
warn: () => {},
|
|
8
|
+
error: () => {},
|
|
9
|
+
debug: () => {},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {any} logger
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
const isValidLogger = (logger) =>
|
|
17
|
+
typeof logger === 'object' &&
|
|
18
|
+
requiredMethods.every((method) => typeof logger[method] === 'function')
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates or returns a logger instance based on user-provided options.
|
|
22
|
+
*
|
|
23
|
+
* Supported usages:
|
|
24
|
+
* - `undefined` → pino with level 'info'
|
|
25
|
+
* - `true` → pino with level 'info'
|
|
26
|
+
* - `{ logger: true, level?: string }` → pino with given level
|
|
27
|
+
* - `{ logger: LoggerObject }` → use provided logger
|
|
28
|
+
* - fallback → dummy logger
|
|
29
|
+
*
|
|
30
|
+
* @param {true | Object | undefined} option
|
|
31
|
+
* @returns {import('pino').Logger | typeof dummyLogger}
|
|
32
|
+
*/
|
|
33
|
+
export const getLogger = (option) => {
|
|
34
|
+
// No option provided or just true: use pino default
|
|
35
|
+
if (option === undefined || option === true) {
|
|
36
|
+
const logger = pino({ level: 'info' })
|
|
37
|
+
logger.info(
|
|
38
|
+
'[getLogger] No logger config provided. Using default Pino logger (level: info).',
|
|
39
|
+
)
|
|
40
|
+
return logger
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Option object provided
|
|
44
|
+
if (typeof option === 'object') {
|
|
45
|
+
const { logger, level } = option
|
|
46
|
+
|
|
47
|
+
// User asked for internal pino with level
|
|
48
|
+
if (logger === true) {
|
|
49
|
+
const pinoLogger = pino({ level: level || 'info' })
|
|
50
|
+
pinoLogger.info(
|
|
51
|
+
`[getLogger] Using internal Pino logger (level: ${level || 'info'}).`,
|
|
52
|
+
)
|
|
53
|
+
return pinoLogger
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// User supplied a custom logger
|
|
57
|
+
if (isValidLogger(logger)) {
|
|
58
|
+
return logger
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Invalid or no-op fallback
|
|
63
|
+
return dummyLogger
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './get-logger.js'
|
package/src/mongodb/index.js
CHANGED
|
@@ -1,73 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Initializes MongoDB collections and provides a transaction wrapper and read-only client accessor.
|
|
6
|
-
*
|
|
7
|
-
* @param {Object} options
|
|
8
|
-
* @param {{ uri: string, options: { dbName: string } }} options.config - MongoDB connection config
|
|
9
|
-
* @param {Record<string, string>} options.collectionNames - Map of collection keys to MongoDB collection names
|
|
10
|
-
*
|
|
11
|
-
* @returns {Promise<
|
|
12
|
-
* Record<string, import('mongodb').Collection> & {
|
|
13
|
-
* withTransaction: (action: ({ session: import('mongodb').ClientSession }) => Promise<void>) => Promise<void>,
|
|
14
|
-
* readonly client: import('mongodb').MongoClient
|
|
15
|
-
* }
|
|
16
|
-
* >}
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* const { users, logs, withTransaction, client } = await initializeMongoDb({
|
|
20
|
-
* config: {
|
|
21
|
-
* uri: 'mongodb://localhost:27017',
|
|
22
|
-
* options: { dbName: 'mydb' },
|
|
23
|
-
* },
|
|
24
|
-
* collectionNames: {
|
|
25
|
-
* users: 'users',
|
|
26
|
-
* logs: 'system_logs',
|
|
27
|
-
* },
|
|
28
|
-
* });
|
|
29
|
-
*
|
|
30
|
-
* await withTransaction(async ({ session }) => {
|
|
31
|
-
* await users.insertOne({ name: 'Alice' }, { session });
|
|
32
|
-
* await logs.insertOne({ event: 'UserCreated', user: 'Alice' }, { session });
|
|
33
|
-
* });
|
|
34
|
-
*
|
|
35
|
-
* await client.close(); // Close connection manually
|
|
36
|
-
*/
|
|
37
|
-
export const initializeMongoDb = async ({ config, collectionNames = {} }) => {
|
|
38
|
-
const client = await mongoConnect(config)
|
|
39
|
-
const db = client.db(config.options.dbName)
|
|
40
|
-
|
|
41
|
-
const collectionRefs = Object.entries(collectionNames).reduce(
|
|
42
|
-
(collections, [key, collectionName]) => ({
|
|
43
|
-
...collections,
|
|
44
|
-
[key]: db.collection(collectionName),
|
|
45
|
-
}),
|
|
46
|
-
{},
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
const withTransaction = async (action) => {
|
|
50
|
-
const session = client.startSession()
|
|
51
|
-
let actionResponse
|
|
52
|
-
try {
|
|
53
|
-
session.startTransaction()
|
|
54
|
-
actionResponse = await action({ session })
|
|
55
|
-
await session.commitTransaction()
|
|
56
|
-
} catch (error) {
|
|
57
|
-
await session.abortTransaction()
|
|
58
|
-
throw error
|
|
59
|
-
} finally {
|
|
60
|
-
await session.endSession()
|
|
61
|
-
return actionResponse
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
...collectionRefs,
|
|
67
|
-
withTransaction,
|
|
68
|
-
/** @type {import('mongodb').MongoClient} */
|
|
69
|
-
get client() {
|
|
70
|
-
return client
|
|
71
|
-
},
|
|
72
|
-
}
|
|
73
|
-
}
|
|
1
|
+
export * from './connect.js'
|
|
2
|
+
export * from './initialize-mongodb.js'
|
|
3
|
+
export * from './validate-mongo-uri.js'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { mongoConnect } from './connect.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Initializes MongoDB collections and provides a transaction wrapper and read-only client accessor.
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} options
|
|
8
|
+
* @param {{ uri: string, options: { dbName: string } }} options.config - MongoDB connection config
|
|
9
|
+
* @param {Record<string, string>} options.collectionNames - Map of collection keys to MongoDB collection names
|
|
10
|
+
*
|
|
11
|
+
* @returns {Promise<
|
|
12
|
+
* Record<string, import('mongodb').Collection> & {
|
|
13
|
+
* withTransaction: (action: ({ session: import('mongodb').ClientSession }) => Promise<void>) => Promise<void>,
|
|
14
|
+
* readonly client: import('mongodb').MongoClient
|
|
15
|
+
* }
|
|
16
|
+
* >}
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const { users, logs, withTransaction, client } = await initializeMongoDb({
|
|
20
|
+
* config: {
|
|
21
|
+
* uri: 'mongodb://localhost:27017',
|
|
22
|
+
* options: { dbName: 'mydb' },
|
|
23
|
+
* },
|
|
24
|
+
* collectionNames: {
|
|
25
|
+
* users: 'users',
|
|
26
|
+
* logs: 'system_logs',
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* await withTransaction(async ({ session }) => {
|
|
31
|
+
* await users.insertOne({ name: 'Alice' }, { session });
|
|
32
|
+
* await logs.insertOne({ event: 'UserCreated', user: 'Alice' }, { session });
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* await client.close(); // Close connection manually
|
|
36
|
+
*/
|
|
37
|
+
export const initializeMongoDb = async ({ config, collectionNames = {} }) => {
|
|
38
|
+
const client = await mongoConnect(config)
|
|
39
|
+
const db = client.db(config.options.dbName)
|
|
40
|
+
|
|
41
|
+
const collectionRefs = Object.entries(collectionNames).reduce(
|
|
42
|
+
(collections, [key, collectionName]) => ({
|
|
43
|
+
...collections,
|
|
44
|
+
[key]: db.collection(collectionName),
|
|
45
|
+
}),
|
|
46
|
+
{},
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const withTransaction = async (action) => {
|
|
50
|
+
const session = client.startSession()
|
|
51
|
+
let actionResponse
|
|
52
|
+
try {
|
|
53
|
+
session.startTransaction()
|
|
54
|
+
actionResponse = await action({ session })
|
|
55
|
+
await session.commitTransaction()
|
|
56
|
+
} catch (error) {
|
|
57
|
+
await session.abortTransaction()
|
|
58
|
+
throw error
|
|
59
|
+
} finally {
|
|
60
|
+
await session.endSession()
|
|
61
|
+
return actionResponse
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
...collectionRefs,
|
|
67
|
+
withTransaction,
|
|
68
|
+
/** @type {import('mongodb').MongoClient} */
|
|
69
|
+
get client() {
|
|
70
|
+
return client
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates whether a given string is a properly formatted MongoDB URI.
|
|
3
|
+
*
|
|
4
|
+
* Supports both standard (`mongodb://`) and SRV (`mongodb+srv://`) protocols.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} uri - The URI string to validate.
|
|
7
|
+
* @returns {boolean} `true` if the URI is a valid MongoDB connection string, otherwise `false`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* isValidMongoUri('mongodb://localhost:27017/mydb') // true
|
|
11
|
+
* isValidMongoUri('mongodb+srv://cluster.example.com/test') // true
|
|
12
|
+
* isValidMongoUri('http://localhost') // false
|
|
13
|
+
* isValidMongoUri('') // false
|
|
14
|
+
*/
|
|
15
|
+
export function isValidMongoUri(uri) {
|
|
16
|
+
if (typeof uri !== 'string' || !uri.trim()) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(uri)
|
|
22
|
+
|
|
23
|
+
const isValidProtocol =
|
|
24
|
+
parsed.protocol === 'mongodb:' || parsed.protocol === 'mongodb+srv:'
|
|
25
|
+
|
|
26
|
+
const hasHostname = Boolean(parsed.hostname)
|
|
27
|
+
|
|
28
|
+
return isValidProtocol && hasHostname
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { combineUniqueArrays } from '../../src/core/combine-unique-arrays.js'
|
|
4
|
+
|
|
5
|
+
describe('combineUniqueArrays', () => {
|
|
6
|
+
it('should combine arrays and remove duplicates (numbers)', () => {
|
|
7
|
+
const result = combineUniqueArrays([1, 2], [2, 3], [3, 4])
|
|
8
|
+
expect(result).toEqual([1, 2, 3, 4])
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('should combine arrays and remove duplicates (strings)', () => {
|
|
12
|
+
const result = combineUniqueArrays(['a', 'b'], ['b', 'c'])
|
|
13
|
+
expect(result).toEqual(['a', 'b', 'c'])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should return empty array when no input is provided', () => {
|
|
17
|
+
const result = combineUniqueArrays()
|
|
18
|
+
expect(result).toEqual([])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should handle single array', () => {
|
|
22
|
+
const result = combineUniqueArrays([1, 2, 2, 3])
|
|
23
|
+
expect(result).toEqual([1, 2, 3])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should preserve order of first appearance', () => {
|
|
27
|
+
const result = combineUniqueArrays(['b', 'a'], ['a', 'c'])
|
|
28
|
+
expect(result).toEqual(['b', 'a', 'c'])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should handle mixed types', () => {
|
|
32
|
+
const result = combineUniqueArrays(['1', 1, true, 'true'], [true, '1'])
|
|
33
|
+
expect(result).toEqual(['1', 1, true, 'true'])
|
|
34
|
+
})
|
|
35
|
+
})
|