core-services-sdk 1.3.7 → 1.3.9
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 +6 -5
- package/src/fastify/error-codes.js +11 -0
- package/src/http/HttpError.js +84 -10
- package/src/http/http.js +41 -31
- package/src/http/index.js +4 -0
- package/src/http/responseType.js +10 -0
- package/src/ids/index.js +2 -0
- package/src/index.js +7 -21
- package/src/mailer/transport.factory.js +28 -14
- package/src/mongodb/initialize-mongodb.js +9 -7
- package/src/rabbit-mq/index.js +1 -186
- package/src/rabbit-mq/rabbit-mq.js +189 -0
- package/src/templates/index.js +1 -0
- package/tests/fastify/error-handler.unit.test.js +39 -0
- package/tests/{with-error-handling.test.js → fastify/error-handlers/with-error-handling.test.js} +4 -3
- package/tests/http/HttpError.unit.test.js +112 -0
- package/tests/http/http-method.unit.test.js +29 -0
- package/tests/http/http.unit.test.js +167 -0
- package/tests/http/responseType.unit.test.js +45 -0
- package/tests/ids/prefixes.unit.test.js +1 -0
- package/tests/mailer/mailer.integration.test.js +95 -0
- package/tests/{mailer.unit.test.js → mailer/mailer.unit.test.js} +7 -11
- package/tests/mailer/transport.factory.unit.test.js +204 -0
- package/tests/mongodb/connect.unit.test.js +60 -0
- package/tests/mongodb/initialize-mongodb.unit.test.js +98 -0
- package/tests/mongodb/validate-mongo-uri.unit.test.js +52 -0
- package/tests/{rabbit-mq.test.js → rabbit-mq/rabbit-mq.test.js} +3 -2
- package/tests/{template-loader.integration.test.js → templates/template-loader.integration.test.js} +1 -1
- package/tests/{template-loader.unit.test.js → templates/template-loader.unit.test.js} +1 -1
- package/vitest.config.js +3 -0
- package/index.js +0 -3
- package/tests/HttpError.test.js +0 -80
- package/tests/core-util.js +0 -24
- package/tests/mailer.integration.test.js +0 -46
- package/tests/mongodb.test.js +0 -70
- package/tests/transport.factory.unit.test.js +0 -128
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-services-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -19,16 +19,17 @@
|
|
|
19
19
|
"homepage": "https://github.com/haim-rubin/core-services-sdk#readme",
|
|
20
20
|
"description": "",
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@aws-sdk/client-ses": "^3.862.0",
|
|
23
|
+
"@aws-sdk/credential-provider-node": "^3.862.0",
|
|
24
|
+
"@sendgrid/mail": "^8.1.5",
|
|
22
25
|
"amqplib": "^0.10.8",
|
|
23
|
-
"aws-sdk": "^2.1692.0",
|
|
24
26
|
"crypto": "^1.0.1",
|
|
25
27
|
"dot": "^1.1.3",
|
|
26
28
|
"fastify": "^5.4.0",
|
|
27
29
|
"http-status": "^2.1.0",
|
|
28
|
-
"mongodb": "^6.
|
|
30
|
+
"mongodb": "^6.18.0",
|
|
29
31
|
"node-fetch": "^3.3.2",
|
|
30
32
|
"nodemailer": "^7.0.5",
|
|
31
|
-
"nodemailer-sendgrid-transport": "^0.2.0",
|
|
32
33
|
"pino": "^9.7.0",
|
|
33
34
|
"uuid": "^11.1.0",
|
|
34
35
|
"xml2js": "^0.6.2"
|
|
@@ -39,4 +40,4 @@
|
|
|
39
40
|
"url": "^0.11.4",
|
|
40
41
|
"vitest": "^3.2.4"
|
|
41
42
|
}
|
|
42
|
-
}
|
|
43
|
+
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import httpStatus from 'http-status'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A reusable generic error object representing a server-side failure (HTTP 500).
|
|
5
|
+
* Useful as a fallback error descriptor for unhandled or unexpected failures.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {Object} GeneralError
|
|
8
|
+
* @property {number} httpStatusCode - The HTTP status code (500).
|
|
9
|
+
* @property {string} httpStatusText - The human-readable status text ("Internal Server Error").
|
|
10
|
+
* @property {string} code - An application-specific error code in the format "GENERAL.<StatusText>".
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @type {GeneralError} */
|
|
3
14
|
export const GENERAL_ERROR = {
|
|
4
15
|
httpStatusCode: httpStatus.INTERNAL_SERVER_ERROR,
|
|
5
16
|
httpStatusText: httpStatus[httpStatus.INTERNAL_SERVER_ERROR],
|
package/src/http/HttpError.js
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
1
|
import httpStatus from 'http-status'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Represents a custom HTTP error with optional status code, status text, error code, and extra metadata.
|
|
5
|
+
* Useful for consistent error handling across services.
|
|
6
|
+
*/
|
|
3
7
|
export class HttpError extends Error {
|
|
4
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* @type {string | number | undefined}
|
|
10
|
+
* A short application-specific error code (e.g., "INVALID_INPUT" or a numeric code).
|
|
11
|
+
*/
|
|
5
12
|
code
|
|
6
13
|
|
|
7
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* @type {number | undefined}
|
|
16
|
+
* HTTP status code associated with the error (e.g., 400, 500).
|
|
17
|
+
*/
|
|
8
18
|
httpStatusCode
|
|
9
19
|
|
|
10
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* @type {string | undefined}
|
|
22
|
+
* Human-readable HTTP status text (e.g., "Bad Request").
|
|
23
|
+
*/
|
|
11
24
|
httpStatusText
|
|
12
25
|
|
|
13
26
|
/**
|
|
14
|
-
* @
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
* @type {object | undefined}
|
|
28
|
+
* Optional metadata for debugging/logging (e.g., request ID, user ID, retryAfter).
|
|
29
|
+
*/
|
|
30
|
+
extendInfo
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates an instance of HttpError.
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} [error] - Optional error object.
|
|
36
|
+
* @param {string | number} [error.code] - Application-specific error code.
|
|
37
|
+
* @param {string} [error.message] - Custom error message.
|
|
38
|
+
* @param {number} [error.httpStatusCode] - HTTP status code (e.g., 404, 500).
|
|
39
|
+
* @param {string} [error.httpStatusText] - Optional human-readable HTTP status text.
|
|
40
|
+
* @param {object} [error.extendInfo] - Optional extended metadata for diagnostics.
|
|
19
41
|
*/
|
|
20
42
|
constructor(error = {}) {
|
|
21
|
-
const { code, message, httpStatusCode, httpStatusText } = error
|
|
43
|
+
const { code, message, httpStatusCode, httpStatusText, extendInfo } = error
|
|
22
44
|
|
|
23
45
|
super(
|
|
24
46
|
message ||
|
|
@@ -31,16 +53,29 @@ export class HttpError extends Error {
|
|
|
31
53
|
this.httpStatusCode = httpStatusCode
|
|
32
54
|
this.httpStatusText =
|
|
33
55
|
httpStatusText || (httpStatusCode && httpStatus[httpStatusCode])
|
|
56
|
+
this.extendInfo = extendInfo
|
|
34
57
|
|
|
35
58
|
if (typeof Error.captureStackTrace === 'function') {
|
|
36
59
|
Error.captureStackTrace(this, this.constructor)
|
|
37
60
|
}
|
|
38
61
|
}
|
|
39
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Checks if a given object is an instance of `HttpError`.
|
|
65
|
+
*
|
|
66
|
+
* @param {*} instance - The object to check.
|
|
67
|
+
* @returns {boolean} True if `instance` is an instance of `HttpError`.
|
|
68
|
+
*/
|
|
40
69
|
static isInstanceOf(instance) {
|
|
41
70
|
return instance instanceof HttpError
|
|
42
71
|
}
|
|
43
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Custom implementation for `instanceof` checks.
|
|
75
|
+
*
|
|
76
|
+
* @param {*} instance - The object to check.
|
|
77
|
+
* @returns {boolean} True if it has HttpError-like structure.
|
|
78
|
+
*/
|
|
44
79
|
static [Symbol.hasInstance](instance) {
|
|
45
80
|
return (
|
|
46
81
|
instance &&
|
|
@@ -51,18 +86,57 @@ export class HttpError extends Error {
|
|
|
51
86
|
)
|
|
52
87
|
}
|
|
53
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Converts the error to a plain object (useful for logging or sending as JSON).
|
|
91
|
+
*
|
|
92
|
+
* @returns {{
|
|
93
|
+
* code: string | number | undefined,
|
|
94
|
+
* message: string,
|
|
95
|
+
* httpStatusCode: number | undefined,
|
|
96
|
+
* httpStatusText: string | undefined,
|
|
97
|
+
* extendInfo?: object
|
|
98
|
+
* }}
|
|
99
|
+
*/
|
|
54
100
|
toJSON() {
|
|
55
101
|
return {
|
|
56
102
|
code: this.code,
|
|
57
103
|
message: this.message,
|
|
58
104
|
httpStatusCode: this.httpStatusCode,
|
|
59
105
|
httpStatusText: this.httpStatusText,
|
|
106
|
+
...(this.extendInfo ? { extendInfo: this.extendInfo } : {}),
|
|
60
107
|
}
|
|
61
108
|
}
|
|
62
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Checks if the error is an instance of `HttpError` or has similar shape.
|
|
112
|
+
*
|
|
113
|
+
* @param {object} error
|
|
114
|
+
* @returns {error is HttpError}
|
|
115
|
+
*/
|
|
116
|
+
static isHttpError(error) {
|
|
117
|
+
return (
|
|
118
|
+
error instanceof HttpError ||
|
|
119
|
+
(error &&
|
|
120
|
+
typeof error === 'object' &&
|
|
121
|
+
'httpStatusCode' in error &&
|
|
122
|
+
'httpStatusText' in error &&
|
|
123
|
+
'toJSON' in error)
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates an HttpError from a generic Error instance or returns it if already an HttpError.
|
|
129
|
+
*
|
|
130
|
+
* @param {Error | HttpError} error
|
|
131
|
+
* @returns {HttpError}
|
|
132
|
+
*/
|
|
63
133
|
static FromError(error) {
|
|
134
|
+
if (HttpError.isHttpError(error)) {
|
|
135
|
+
return error
|
|
136
|
+
}
|
|
137
|
+
|
|
64
138
|
const httpError = new HttpError({
|
|
65
|
-
code:
|
|
139
|
+
code: 'UNHANDLED_ERROR',
|
|
66
140
|
message: error.message || 'An unexpected error occurred',
|
|
67
141
|
httpStatusCode: httpStatus.INTERNAL_SERVER_ERROR,
|
|
68
142
|
httpStatusText: httpStatus[httpStatus.INTERNAL_SERVER_ERROR],
|
package/src/http/http.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A lightweight HTTP client wrapper around `node-fetch` supporting JSON, XML, and plain text responses.
|
|
3
|
+
* Provides simplified helper methods for GET, POST, PUT, PATCH, and DELETE with automatic error handling.
|
|
4
|
+
*
|
|
5
|
+
* @module http
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import fetch from 'node-fetch'
|
|
2
9
|
import httpStatus from 'http-status'
|
|
3
10
|
import { parseStringPromise } from 'xml2js'
|
|
@@ -13,40 +20,36 @@ const JSON_HEADER = {
|
|
|
13
20
|
/**
|
|
14
21
|
* Checks if the HTTP status is considered successful (2xx).
|
|
15
22
|
*
|
|
16
|
-
* @param {Response}
|
|
17
|
-
* @returns {boolean}
|
|
23
|
+
* @param {import('node-fetch').Response} response
|
|
24
|
+
* @returns {boolean}
|
|
18
25
|
*/
|
|
19
26
|
const isOkStatus = ({ status }) =>
|
|
20
27
|
status >= httpStatus.OK && status < httpStatus.MULTIPLE_CHOICES
|
|
21
28
|
|
|
22
29
|
/**
|
|
23
|
-
*
|
|
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.
|
|
30
|
+
* @param {import('node-fetch').Response} response
|
|
31
|
+
* @returns {Promise<import('node-fetch').Response>}
|
|
28
32
|
*/
|
|
29
|
-
const checkStatus = async (
|
|
30
|
-
if (!isOkStatus(
|
|
31
|
-
const text = await
|
|
33
|
+
const checkStatus = async (response) => {
|
|
34
|
+
if (!isOkStatus(response)) {
|
|
35
|
+
const text = await response.text()
|
|
32
36
|
const info = tryConvertJsonResponse(text)
|
|
33
|
-
const { status, statusText } =
|
|
37
|
+
const { status, statusText } = response
|
|
34
38
|
|
|
35
39
|
throw new HttpError({
|
|
36
40
|
code: status,
|
|
37
41
|
httpStatusCode: status,
|
|
38
42
|
httpStatusText: statusText,
|
|
39
|
-
details: info,
|
|
40
43
|
})
|
|
41
44
|
}
|
|
42
|
-
return
|
|
45
|
+
return response
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Reads the raw text from a fetch response.
|
|
47
50
|
*
|
|
48
|
-
* @param {Response} response
|
|
49
|
-
* @returns {Promise<string>} The text body.
|
|
51
|
+
* @param {import('node-fetch').Response} response
|
|
52
|
+
* @returns {Promise<string>} The plain text body.
|
|
50
53
|
*/
|
|
51
54
|
const getTextResponse = async (response) => {
|
|
52
55
|
return await response.text()
|
|
@@ -71,8 +74,8 @@ const tryConvertJsonResponse = (responseText) => {
|
|
|
71
74
|
/**
|
|
72
75
|
* Attempts to extract a JSON object from a fetch response.
|
|
73
76
|
*
|
|
74
|
-
* @param {Response} response
|
|
75
|
-
* @returns {Promise<Object|string>} Parsed
|
|
77
|
+
* @param {import('node-fetch').Response} response
|
|
78
|
+
* @returns {Promise<Object|string>} Parsed JSON or raw string if parsing fails.
|
|
76
79
|
*/
|
|
77
80
|
const tryGetJsonResponse = async (response) => {
|
|
78
81
|
let jsonText
|
|
@@ -88,8 +91,8 @@ const tryGetJsonResponse = async (response) => {
|
|
|
88
91
|
/**
|
|
89
92
|
* Attempts to extract an XML object from a fetch response.
|
|
90
93
|
*
|
|
91
|
-
* @param {Response} response
|
|
92
|
-
* @returns {Promise<Object|string>} Parsed XML object or raw string.
|
|
94
|
+
* @param {import('node-fetch').Response} response
|
|
95
|
+
* @returns {Promise<Object|string>} Parsed XML object or raw string if parsing fails.
|
|
93
96
|
*/
|
|
94
97
|
const tryGetXmlResponse = async (response) => {
|
|
95
98
|
let xmlText
|
|
@@ -105,8 +108,8 @@ const tryGetXmlResponse = async (response) => {
|
|
|
105
108
|
/**
|
|
106
109
|
* Extracts and parses the fetch response body based on expected type.
|
|
107
110
|
*
|
|
108
|
-
* @param {Response} response
|
|
109
|
-
* @param {string} responseType -
|
|
111
|
+
* @param {import('node-fetch').Response} response
|
|
112
|
+
* @param {string} responseType - Expected type: 'json', 'xml', or 'text'.
|
|
110
113
|
* @returns {Promise<any>} The parsed response payload.
|
|
111
114
|
*/
|
|
112
115
|
const getResponsePayload = async (response, responseType) => {
|
|
@@ -125,10 +128,10 @@ const getResponsePayload = async (response, responseType) => {
|
|
|
125
128
|
* Sends an HTTP GET request.
|
|
126
129
|
*
|
|
127
130
|
* @param {Object} params
|
|
128
|
-
* @param {string} params.url -
|
|
129
|
-
* @param {Object} [params.headers] - Optional
|
|
130
|
-
* @param {string} [params.credentials='include'] - Credential
|
|
131
|
-
* @param {string} [params.expectedType='json'] -
|
|
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.
|
|
132
135
|
* @returns {Promise<any>} Parsed response data.
|
|
133
136
|
*/
|
|
134
137
|
export const get = async ({
|
|
@@ -137,11 +140,13 @@ export const get = async ({
|
|
|
137
140
|
credentials = 'include',
|
|
138
141
|
expectedType = ResponseType.json,
|
|
139
142
|
}) => {
|
|
143
|
+
/** @type {import('node-fetch').Response} */
|
|
140
144
|
const response = await fetch(url, {
|
|
141
145
|
method: HTTP_METHODS.GET,
|
|
142
146
|
headers: { ...JSON_HEADER, ...headers },
|
|
143
147
|
...(credentials ? { credentials } : {}),
|
|
144
148
|
})
|
|
149
|
+
|
|
145
150
|
await checkStatus(response)
|
|
146
151
|
return await getResponsePayload(response, expectedType)
|
|
147
152
|
}
|
|
@@ -150,11 +155,11 @@ export const get = async ({
|
|
|
150
155
|
* Sends an HTTP POST request.
|
|
151
156
|
*
|
|
152
157
|
* @param {Object} params
|
|
153
|
-
* @param {string} params.url -
|
|
154
|
-
* @param {Object} params.body -
|
|
155
|
-
* @param {Object} [params.headers] - Optional
|
|
156
|
-
* @param {string} [params.credentials='include'] - Credential
|
|
157
|
-
* @param {string} [params.expectedType='json'] -
|
|
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.
|
|
158
163
|
* @returns {Promise<any>} Parsed response data.
|
|
159
164
|
*/
|
|
160
165
|
export const post = async ({
|
|
@@ -177,7 +182,12 @@ export const post = async ({
|
|
|
177
182
|
/**
|
|
178
183
|
* Sends an HTTP PUT request.
|
|
179
184
|
*
|
|
180
|
-
* @param {Object} params
|
|
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']
|
|
181
191
|
* @returns {Promise<any>} Parsed response data.
|
|
182
192
|
*/
|
|
183
193
|
export const put = async ({
|
package/src/http/responseType.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enum representing supported response types for HTTP client parsing.
|
|
3
|
+
*
|
|
4
|
+
* @readonly
|
|
5
|
+
* @enum {string}
|
|
6
|
+
* @property {string} xml - XML response (parsed using xml2js).
|
|
7
|
+
* @property {string} json - JSON response (parsed via JSON.parse).
|
|
8
|
+
* @property {string} text - Plain text response.
|
|
9
|
+
* @property {string} file - Binary file or blob (not automatically parsed).
|
|
10
|
+
*/
|
|
1
11
|
export const ResponseType = Object.freeze({
|
|
2
12
|
xml: 'xml',
|
|
3
13
|
json: 'json',
|
package/src/ids/index.js
ADDED
package/src/index.js
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './core/index.js'
|
|
2
|
+
export * from './crypto/index.js'
|
|
2
3
|
export * from './fastify/index.js'
|
|
4
|
+
export * from './http/index.js'
|
|
5
|
+
export * from './ids/index.js'
|
|
3
6
|
export * from './mongodb/index.js'
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
7
|
+
export * from './logger/index.js'
|
|
8
|
+
export * from './mailer/index.js'
|
|
6
9
|
export * from './rabbit-mq/index.js'
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './logger/get-logger.js'
|
|
9
|
-
export * as http from './http/http.js'
|
|
10
|
-
export * from './http/responseType.js'
|
|
11
|
-
export * from './crypto/encryption.js'
|
|
12
|
-
export * from './core/otp-generators.js'
|
|
13
|
-
export * from './core/sanitize-objects.js'
|
|
14
|
-
export * from './core/normalize-to-array.js'
|
|
15
|
-
export { initMailer } from './mailer/index.js'
|
|
16
|
-
export * from './core/combine-unique-arrays.js'
|
|
17
|
-
export { HttpError } from './http/HttpError.js'
|
|
18
|
-
export { Mailer } from './mailer/mailer.service.js'
|
|
19
|
-
export { TransportFactory } from './mailer/transport.factory.js'
|
|
20
|
-
export {
|
|
21
|
-
isItFile,
|
|
22
|
-
loadTemplates,
|
|
23
|
-
getTemplateContent,
|
|
24
|
-
} from './templates/template-loader.js'
|
|
10
|
+
export * from './templates/index.js'
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import aws from 'aws-sdk'
|
|
2
1
|
import nodemailer from 'nodemailer'
|
|
3
|
-
import
|
|
2
|
+
import sgMail from '@sendgrid/mail'
|
|
3
|
+
import { defaultProvider } from '@aws-sdk/credential-provider-node'
|
|
4
|
+
import { SESClient, SendRawEmailCommand } from '@aws-sdk/client-ses'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Factory for creating
|
|
7
|
+
* Factory for creating email transporters based on configuration.
|
|
7
8
|
*/
|
|
8
9
|
export class TransportFactory {
|
|
9
10
|
/**
|
|
@@ -29,8 +30,9 @@ export class TransportFactory {
|
|
|
29
30
|
* @param {string} config.secretAccessKey - AWS secret
|
|
30
31
|
* @param {string} config.region - AWS region
|
|
31
32
|
*
|
|
32
|
-
* @returns {import('nodemailer').Transporter}
|
|
33
|
+
* @returns {import('nodemailer').Transporter | typeof import('@sendgrid/mail')}
|
|
33
34
|
*/
|
|
35
|
+
|
|
34
36
|
static create(config) {
|
|
35
37
|
switch (config.type) {
|
|
36
38
|
case 'smtp':
|
|
@@ -48,19 +50,31 @@ export class TransportFactory {
|
|
|
48
50
|
})
|
|
49
51
|
|
|
50
52
|
case 'sendgrid':
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if (!config.apiKey) {
|
|
54
|
+
throw new Error('Missing SendGrid API key')
|
|
55
|
+
}
|
|
56
|
+
sgMail.setApiKey(config.apiKey)
|
|
57
|
+
return sgMail // Not a Nodemailer transport, but SendGrid's mail API
|
|
56
58
|
|
|
57
|
-
case 'ses':
|
|
58
|
-
const
|
|
59
|
-
accessKeyId: config.accessKeyId,
|
|
60
|
-
secretAccessKey: config.secretAccessKey,
|
|
59
|
+
case 'ses': {
|
|
60
|
+
const sesClient = new SESClient({
|
|
61
61
|
region: config.region,
|
|
62
|
+
credentials:
|
|
63
|
+
config.accessKeyId && config.secretAccessKey
|
|
64
|
+
? {
|
|
65
|
+
accessKeyId: config.accessKeyId,
|
|
66
|
+
secretAccessKey: config.secretAccessKey,
|
|
67
|
+
}
|
|
68
|
+
: defaultProvider(),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return nodemailer.createTransport({
|
|
72
|
+
SES: {
|
|
73
|
+
ses: sesClient,
|
|
74
|
+
aws: { SendRawEmailCommand },
|
|
75
|
+
},
|
|
62
76
|
})
|
|
63
|
-
|
|
77
|
+
}
|
|
64
78
|
|
|
65
79
|
default:
|
|
66
80
|
throw new Error(`Unsupported transport type: ${config.type}`)
|
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
import { mongoConnect } from './connect.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Initializes MongoDB collections and provides
|
|
5
|
+
* Initializes MongoDB collections and provides:
|
|
6
|
+
* - Named collection references
|
|
7
|
+
* - A transaction wrapper (`withTransaction`)
|
|
8
|
+
* - A read-only MongoDB client accessor
|
|
6
9
|
*
|
|
7
10
|
* @param {Object} options
|
|
8
11
|
* @param {{ uri: string, options: { dbName: string } }} options.config - MongoDB connection config
|
|
9
|
-
* @param {Record<string, string>} options.collectionNames - Map of
|
|
12
|
+
* @param {Record<string, string>} options.collectionNames - Map of keys to actual MongoDB collection names
|
|
10
13
|
*
|
|
11
14
|
* @returns {Promise<
|
|
12
15
|
* Record<string, import('mongodb').Collection> & {
|
|
13
|
-
* withTransaction: (action: ({ session: import('mongodb').ClientSession }) => Promise<
|
|
16
|
+
* withTransaction: <T>(action: ({ session: import('mongodb').ClientSession }) => Promise<T>) => Promise<T>,
|
|
14
17
|
* readonly client: import('mongodb').MongoClient
|
|
15
18
|
* }
|
|
16
19
|
* >}
|
|
@@ -32,7 +35,7 @@ import { mongoConnect } from './connect.js'
|
|
|
32
35
|
* await logs.insertOne({ event: 'UserCreated', user: 'Alice' }, { session });
|
|
33
36
|
* });
|
|
34
37
|
*
|
|
35
|
-
* await client.close(); // Close connection manually
|
|
38
|
+
* await client.close(); // Close connection manually when done
|
|
36
39
|
*/
|
|
37
40
|
export const initializeMongoDb = async ({ config, collectionNames = {} }) => {
|
|
38
41
|
const client = await mongoConnect(config)
|
|
@@ -48,17 +51,16 @@ export const initializeMongoDb = async ({ config, collectionNames = {} }) => {
|
|
|
48
51
|
|
|
49
52
|
const withTransaction = async (action) => {
|
|
50
53
|
const session = client.startSession()
|
|
51
|
-
let actionResponse
|
|
52
54
|
try {
|
|
53
55
|
session.startTransaction()
|
|
54
|
-
actionResponse = await action({ session })
|
|
56
|
+
const actionResponse = await action({ session })
|
|
55
57
|
await session.commitTransaction()
|
|
58
|
+
return actionResponse
|
|
56
59
|
} catch (error) {
|
|
57
60
|
await session.abortTransaction()
|
|
58
61
|
throw error
|
|
59
62
|
} finally {
|
|
60
63
|
await session.endSession()
|
|
61
|
-
return actionResponse
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|