core-services-sdk 1.3.2 → 1.3.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-services-sdk",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,12 +21,15 @@
21
21
  "dependencies": {
22
22
  "amqplib": "^0.10.8",
23
23
  "aws-sdk": "^2.1692.0",
24
+ "crypto": "^1.0.1",
24
25
  "dot": "^1.1.3",
26
+ "fastify": "^5.4.0",
25
27
  "http-status": "^2.1.0",
26
28
  "mongodb": "^6.17.0",
27
29
  "node-fetch": "^3.3.2",
28
30
  "nodemailer": "^7.0.5",
29
31
  "nodemailer-sendgrid-transport": "^0.2.0",
32
+ "pino": "^9.7.0",
30
33
  "uuid": "^11.1.0",
31
34
  "xml2js": "^0.6.2"
32
35
  },
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Combines multiple arrays into a single flat array with unique values.
3
+ *
4
+ * Removes duplicate entries and preserves the order of first appearance.
5
+ *
6
+ * @param {...Array<any>} lists - One or more arrays to combine.
7
+ * @returns {Array<any>} A new array containing unique values from all input arrays.
8
+ *
9
+ * @example
10
+ * combineUniqueArrays([1, 2, 3], [3, 4], [5]) // [1, 2, 3, 4, 5]
11
+ * combineUniqueArrays(['a', 'b'], ['b', 'c']) // ['a', 'b', 'c']
12
+ */
13
+ export const combineUniqueArrays = (...lists) => {
14
+ return Array.from(new Set(lists.flat()))
15
+ }
@@ -0,0 +1,5 @@
1
+ export * from './regex-utils.js'
2
+ export * from './otp-generators.js'
3
+ export * from './sanitize-objects.js'
4
+ export * from './normalize-to-array.js'
5
+ export * from './combine-unique-arrays.js'
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Normalizes an input value to an array of trimmed, non-empty strings.
3
+ *
4
+ * - If the input is `undefined` or `null`, returns an empty array.
5
+ * - If the input is an array, it trims each string element and filters out empty values.
6
+ * - If the input is a string (or coercible to string), it splits it by commas,
7
+ * trims each item, and filters out empty values.
8
+ *
9
+ * @param {any} value - The value to normalize. Can be a string, array, or any other type.
10
+ * @returns {string[]} An array of trimmed, non-empty strings.
11
+ *
12
+ * @example
13
+ * normalizeToArray('a,b, c , ,d') // ['a', 'b', 'c', 'd']
14
+ * normalizeToArray([' a ', 'b', '', ' ']) // ['a', 'b']
15
+ * normalizeToArray(null) // []
16
+ * normalizeToArray(123) // ['123']
17
+ */
18
+ export const normalizeToArray = (value) => {
19
+ if (value === undefined || value === null) {
20
+ return []
21
+ }
22
+
23
+ const isArray = Array.isArray(value)
24
+ const rawArray = isArray ? value : String(value).split(',')
25
+
26
+ const arrayFiltered = rawArray
27
+ .map((item) => (typeof item === 'string' ? item.trim() : ''))
28
+ .filter(Boolean)
29
+
30
+ return arrayFiltered
31
+ }
@@ -0,0 +1,113 @@
1
+ const numeric = '0123456789'
2
+ const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'
3
+ const alphaLower = 'abcdefghijklmnopqrstuvwxyz'
4
+ const alphaUpper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
5
+
6
+ const alphanumericSymbols = `${alphaLower}${alphaUpper}${numeric}${symbols}`
7
+
8
+ export const OTP_GENERATOR_TYPES = Object.freeze({
9
+ any: 'any',
10
+ alpha: 'alpha',
11
+ numeric: 'numeric',
12
+ symbols: 'symbols',
13
+ alphaLower: 'alphaLower',
14
+ alphaUpper: 'alphaUpper',
15
+ alphanumeric: 'alphanumeric',
16
+ alphanumericSymbols: 'alphanumericSymbols',
17
+ })
18
+
19
+ const types = Object.freeze({
20
+ [OTP_GENERATOR_TYPES.numeric]: numeric,
21
+ [OTP_GENERATOR_TYPES.symbols]: symbols,
22
+ [OTP_GENERATOR_TYPES.alphaLower]: alphaLower,
23
+ [OTP_GENERATOR_TYPES.alphaUpper]: alphaUpper,
24
+ [OTP_GENERATOR_TYPES.any]: alphanumericSymbols,
25
+ [OTP_GENERATOR_TYPES.alpha]: `${alphaLower}${alphaUpper}`,
26
+ [OTP_GENERATOR_TYPES.alphanumericSymbols]: alphanumericSymbols,
27
+ [OTP_GENERATOR_TYPES.alphanumeric]: `${alphaLower}${alphaUpper}${numeric}`,
28
+ })
29
+
30
+ /**
31
+ * Generates a one-time password (OTP) code based on a specified type or character set.
32
+ *
33
+ * @param {Object} [options={}] - The options object.
34
+ * @param {number} [options.length=4] - The desired length of the generated code (1-30).
35
+ * @param {string} [options.type='numeric'] - The type of characters to use. One of:
36
+ * 'any', 'alpha', 'numeric', 'symbols', 'alphaLower', 'alphaUpper', 'alphanumeric', 'alphanumericSymbols'.
37
+ * @param {string} [options.charset] - A custom string of characters to use instead of the predefined types.
38
+ * @returns {string} The generated code.
39
+ * @throws {Error} If the length is not a number between 1 and 30.
40
+ * @throws {Error} If charset is provided and is not a non-empty string.
41
+ * @throws {Error} If the type is invalid and no valid charset is provided.
42
+ */
43
+ export function generateCode({ length = 4, type = 'numeric', charset } = {}) {
44
+ const max = 30
45
+ const min = 1
46
+
47
+ if (typeof length !== 'number' || length < min || length > max) {
48
+ throw new Error(`length must be a number between ${min} and ${max}`)
49
+ }
50
+
51
+ if (charset !== undefined) {
52
+ if (typeof charset !== 'string') {
53
+ throw new Error('charset must be a string if provided')
54
+ }
55
+ if (charset.length === 0) {
56
+ throw new Error('charset must not be empty')
57
+ }
58
+ }
59
+
60
+ const chars = charset || types[type]
61
+ if (!chars) {
62
+ throw new Error(
63
+ `type must be one of: ${Object.keys(types)
64
+ .map((t) => `'${t}'`)
65
+ .join(', ')}`,
66
+ )
67
+ }
68
+
69
+ return Array.from(
70
+ { length },
71
+ () => chars[Math.floor(Math.random() * chars.length)],
72
+ ).join('')
73
+ }
74
+
75
+ /**
76
+ * Generates an OTP code using alphabetic characters (both lowercase and uppercase).
77
+ *
78
+ * @param {number} [length=4] - The desired length of the code.
79
+ * @returns {string} The generated code.
80
+ */
81
+ export function generateCodeAlpha(length = 4) {
82
+ return generateCode({ length, type: 'alpha' })
83
+ }
84
+
85
+ /**
86
+ * Generates an OTP code using only numeric digits (0-9).
87
+ *
88
+ * @param {number} [length=4] - The desired length of the code.
89
+ * @returns {string} The generated code.
90
+ */
91
+ export function generateCodeDigits(length = 4) {
92
+ return generateCode({ length, type: 'numeric' })
93
+ }
94
+
95
+ /**
96
+ * Generates an OTP code using alphabetic characters and digits.
97
+ *
98
+ * @param {number} [length=4] - The desired length of the code.
99
+ * @returns {string} The generated code.
100
+ */
101
+ export function generateCodeAlphaNumeric(length = 4) {
102
+ return generateCode({ length, type: 'alphanumeric' })
103
+ }
104
+
105
+ /**
106
+ * Generates an OTP code using alphabetic characters, digits, and symbols.
107
+ *
108
+ * @param {number} [length=4] - The desired length of the code.
109
+ * @returns {string} The generated code.
110
+ */
111
+ export function generateCodeAlphaNumericSymbols(length = 4) {
112
+ return generateCode({ length, type: 'any' })
113
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Checks whether the given input is a valid regular expression.
3
+ *
4
+ * - If the input is an instance of `RegExp`, returns `true`.
5
+ * - If the input is a string, attempts to create a `RegExp` object from it and returns `true` if valid.
6
+ * - Returns `false` for any other types or if the string is an invalid regex pattern.
7
+ *
8
+ * @param {string | RegExp} pattern - The regex pattern to validate, either as a string or a RegExp object.
9
+ * @returns {boolean} `true` if the pattern is a valid regular expression, otherwise `false`.
10
+ *
11
+ * @example
12
+ * isValidRegex('^[a-z]+$') // true
13
+ * isValidRegex(/^[a-z]+$/) // true
14
+ * isValidRegex('[') // false
15
+ * isValidRegex(123) // false
16
+ */
17
+
18
+ export const isValidRegex = (pattern) => {
19
+ if (pattern instanceof RegExp) return true
20
+ if (typeof pattern !== 'string') return false
21
+ try {
22
+ new RegExp(pattern)
23
+ return true
24
+ } catch {
25
+ return false
26
+ }
27
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Creates a new object containing only the key-value pairs that pass the provided filter function.
3
+ *
4
+ * @param {Object} obj - The object to sanitize.
5
+ * @param {(entry: [string, any]) => boolean} filter - A function that determines whether to include each `[key, value]` entry.
6
+ * @returns {Object} A new object with only the filtered entries.
7
+ *
8
+ * @example
9
+ * sanitizeObject({ a: 1, b: undefined }, ([, v]) => v !== undefined) // { a: 1 }
10
+ */
11
+ export const sanitizeObject = (obj, filter) =>
12
+ Object.fromEntries(Object.entries(obj).filter(filter))
13
+
14
+ /**
15
+ * Removes all properties from the object whose values are `undefined`.
16
+ *
17
+ * @param {Object} obj - The object to sanitize.
18
+ * @returns {Object} A new object without `undefined` values.
19
+ *
20
+ * @example
21
+ * sanitizeUndefinedFields({ a: 1, b: undefined }) // { a: 1 }
22
+ */
23
+ export const sanitizeUndefinedFields = (obj) =>
24
+ sanitizeObject(obj, ([, value]) => value !== undefined)
25
+
26
+ /**
27
+ * Returns a new object containing only the specified allowed properties.
28
+ *
29
+ * @param {Object} obj - The original object.
30
+ * @param {string[]} [allowedFields=[]] - An array of property names to retain.
31
+ * @returns {Object} A new object containing only the allowed properties.
32
+ *
33
+ * @example
34
+ * sanitizeObjectAllowProps({ a: 1, b: 2 }, ['a']) // { a: 1 }
35
+ */
36
+ export const sanitizeObjectAllowProps = (obj, allowedFields = []) =>
37
+ sanitizeObject(obj, ([key]) => allowedFields.includes(key))
38
+
39
+ /**
40
+ * Returns a new object excluding the specified disallowed properties.
41
+ *
42
+ * @param {Object} obj - The original object.
43
+ * @param {string[]} [disallowedFields=[]] - An array of property names to exclude.
44
+ * @returns {Object} A new object without the disallowed properties.
45
+ *
46
+ * @example
47
+ * sanitizeObjectDisallowProps({ a: 1, b: 2 }, ['b']) // { a: 1 }
48
+ */
49
+ export const sanitizeObjectDisallowProps = (obj, disallowedFields = []) =>
50
+ sanitizeObject(obj, ([key]) => !disallowedFields.includes(key))
@@ -0,0 +1,128 @@
1
+ import { promisify } from 'util'
2
+ import { randomBytes, scrypt } from 'crypto'
3
+
4
+ const scryptPromisify = promisify(scrypt)
5
+
6
+ const keyLength = 64
7
+ const baseFormat = 'hex'
8
+
9
+ /**
10
+ * Generates a cryptographically secure random salt as a Buffer.
11
+ *
12
+ * @param {number} length - The length of the salt in bytes.
13
+ * @returns {Buffer} A Buffer containing random bytes.
14
+ *
15
+ * @example
16
+ * const salt = getSalt(16)
17
+ * console.log(salt.toString('hex')) // 'e4b1c9...'
18
+ */
19
+ export const getSalt = (length) => {
20
+ return randomBytes(length)
21
+ }
22
+
23
+ /**
24
+ * Generates a cryptographically secure random salt and returns it as a hexadecimal string.
25
+ *
26
+ * @param {number} length - The length of the salt in bytes.
27
+ * @returns {string} The salt encoded as a hexadecimal string.
28
+ *
29
+ * @example
30
+ * const saltHex = getSaltHex(16)
31
+ * console.log(saltHex) // 'e4b1c9...'
32
+ */
33
+ export const getSaltHex = (length) => {
34
+ return getSalt(length).toString(baseFormat)
35
+ }
36
+
37
+ /**
38
+ * Encrypts a given expression using scrypt with a provided salt.
39
+ *
40
+ * @param {Object} options - The encryption options.
41
+ * @param {string} options.expression - The expression to encrypt (e.g., a password).
42
+ * @param {string} options.salt - The salt to use (as a string).
43
+ * @param {number} [options.length=64] - The desired key length in bytes for the derived key.
44
+ * @returns {Promise<Buffer>} A Promise that resolves to the derived key as a Buffer.
45
+ *
46
+ * @example
47
+ * const buffer = await getEncryptedBuffer({
48
+ * expression: 'my-password',
49
+ * salt: 'somesalt',
50
+ * length: 32
51
+ * })
52
+ * console.log(buffer.toString('hex'))
53
+ */
54
+ export const getEncryptedBuffer = async ({
55
+ salt,
56
+ expression,
57
+ length = keyLength,
58
+ }) => {
59
+ const encryptedExpression = await scryptPromisify(expression, salt, length)
60
+
61
+ return encryptedExpression
62
+ }
63
+
64
+ /**
65
+ * Encrypts a string expression (e.g., password) using scrypt and returns the result as a hexadecimal string.
66
+ *
67
+ * Optionally appends a `passwordPrivateKey` to the salt for extra security.
68
+ *
69
+ * @param {Object} options
70
+ * @param {string} options.expression - The expression to encrypt.
71
+ * @param {string} options.salt - The salt string.
72
+ * @param {string} [options.passwordPrivateKey] - Optional extra key to enhance the salt.
73
+ * @returns {Promise<string>} A Promise that resolves to the encrypted expression as a hex string.
74
+ *
75
+ * @example
76
+ * const encrypted = await encrypt({
77
+ * expression: 'my-password',
78
+ * salt: 'abc123',
79
+ * passwordPrivateKey: 'my-secret-key'
80
+ * })
81
+ * console.log(encrypted) // '9af0a1b23c...'
82
+ */
83
+ export const encrypt = async ({ salt, expression, passwordPrivateKey }) => {
84
+ const encryptedExpression = await getEncryptedBuffer({
85
+ expression,
86
+ salt: `${salt}${passwordPrivateKey ? passwordPrivateKey : ''}`,
87
+ })
88
+
89
+ return encryptedExpression.toString(baseFormat)
90
+ }
91
+
92
+ /**
93
+ * Compares a plain-text password to an already encrypted one using scrypt.
94
+ *
95
+ * Re-encrypts the input password using the provided salt and optional private key,
96
+ * then compares it to the stored encrypted password.
97
+ *
98
+ * @param {Object} options
99
+ * @param {string} options.salt - The salt used during encryption.
100
+ * @param {string} options.password - The plain-text password to validate.
101
+ * @param {string} options.encryptedPassword - The previously encrypted password (hex string).
102
+ * @param {string} [options.passwordPrivateKey] - Optional private key used in encryption.
103
+ * @returns {Promise<boolean>} A Promise that resolves to `true` if passwords match, otherwise `false`.
104
+ *
105
+ * @example
106
+ * const isValid = await isPasswordMatch({
107
+ * password: 'my-password',
108
+ * salt: 'abc123',
109
+ * encryptedPassword: '9af0a1b23c...',
110
+ * passwordPrivateKey: 'my-secret-key'
111
+ * })
112
+ * console.log(isValid) // true or false
113
+ */
114
+ export const isPasswordMatch = async ({
115
+ salt,
116
+ password,
117
+ encryptedPassword,
118
+ passwordPrivateKey,
119
+ }) => {
120
+ const encryptedCurrentPassword = await getEncryptedBuffer({
121
+ expression: password,
122
+ salt: `${salt}${passwordPrivateKey ? passwordPrivateKey : ''}`,
123
+ })
124
+ const isMatch = encryptedCurrentPassword.equals(
125
+ Buffer.from(encryptedPassword, baseFormat),
126
+ )
127
+ return isMatch
128
+ }
@@ -0,0 +1,48 @@
1
+ import { encrypt, getSaltHex } from './crypto.js'
2
+
3
+ /**
4
+ * Encrypts a plain-text password using scrypt and a salt.
5
+ *
6
+ * This function generates a random salt (in hex) of the specified length,
7
+ * appends an optional passwordPrivateKey (if provided),
8
+ * and uses the Node.js `crypto.scrypt` algorithm to encrypt the password.
9
+ *
10
+ * @async
11
+ * @function encryptPassword
12
+ *
13
+ * @param {Object} input - The input object containing the password.
14
+ * @param {string} input.password - The plain-text password to encrypt.
15
+ *
16
+ * @param {Object} options - Configuration options for encryption.
17
+ * @param {number} options.saltLength - The desired length of the generated salt (in bytes).
18
+ * @param {string} [options.passwordPrivateKey] - An optional private key to strengthen the salt.
19
+ *
20
+ * @returns {Promise<Object>} An object containing:
21
+ * - `salt` {string} The generated salt in hex format.
22
+ * - `password` {string} The encrypted password in hex format.
23
+ *
24
+ * @example
25
+ * const result = await encryptPassword(
26
+ * { password: 'mySecretPassword' },
27
+ * { saltLength: 16, passwordPrivateKey: 'abc123' }
28
+ * );
29
+ * console.log(result); // { salt: 'f3ab...', password: '8e1f...' }
30
+ */
31
+
32
+ export const encryptPassword = async (
33
+ { password },
34
+ { saltLength, passwordPrivateKey },
35
+ ) => {
36
+ const salt = getSaltHex(saltLength)
37
+
38
+ const encrypted = await encrypt({
39
+ salt,
40
+ passwordPrivateKey,
41
+ expression: password,
42
+ })
43
+
44
+ return {
45
+ salt,
46
+ password: encrypted,
47
+ }
48
+ }
@@ -0,0 +1,2 @@
1
+ export * from './crypto.js'
2
+ export * from './encryption.js'
@@ -1,7 +1,11 @@
1
1
  import httpStatus from 'http-status'
2
2
 
3
- import { HttpError } from '../../http/HttpError.js'
4
3
  import { GENERAL_ERROR } from '../error-codes.js'
4
+ import { HttpError } from '../../http/HttpError.js'
5
+
6
+ /** @typedef {import('fastify').FastifyReply} Reply */
7
+ /** @typedef {import('fastify').FastifyRequest} Request */
8
+ /** @typedef {import('pino').Logger} Logger */
5
9
 
6
10
  /**
7
11
  * Generic error-handling wrapper that logs and throws a safe error.
@@ -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,6 +1,8 @@
1
+ export * from './core/index.js'
2
+ export * from './crypto/index.js'
3
+ export * from './logger/index.js'
1
4
  export * from './fastify/index.js'
2
5
  export * from './mongodb/index.js'
3
- export * from './mongodb/connect.js'
4
6
  export * from './rabbit-mq/index.js'
5
7
  export * as http from './http/http.js'
6
8
  export * from './http/responseType.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'
@@ -2,9 +2,33 @@ import aws from 'aws-sdk'
2
2
  import nodemailer from 'nodemailer'
3
3
  import sgTransport from 'nodemailer-sendgrid-transport'
4
4
 
5
+ /**
6
+ * Factory for creating Nodemailer transporters based on configuration.
7
+ */
5
8
  export class TransportFactory {
6
9
  /**
7
- * @param {object} config
10
+ * Create a Nodemailer transporter
11
+ *
12
+ * @param {object} config - Transport configuration object
13
+ * @param {'smtp' | 'gmail' | 'sendgrid' | 'ses'} config.type - Type of email transport
14
+ *
15
+ * For type 'smtp':
16
+ * @param {string} config.host - SMTP server host
17
+ * @param {number} config.port - SMTP server port
18
+ * @param {boolean} config.secure - Use TLS
19
+ * @param {{ user: string, pass: string }} config.auth - SMTP auth credentials
20
+ *
21
+ * For type 'gmail':
22
+ * @param {{ user: string, pass: string }} config.auth - Gmail credentials
23
+ *
24
+ * For type 'sendgrid':
25
+ * @param {string} config.apiKey - SendGrid API key
26
+ *
27
+ * For type 'ses':
28
+ * @param {string} config.accessKeyId - AWS access key
29
+ * @param {string} config.secretAccessKey - AWS secret
30
+ * @param {string} config.region - AWS region
31
+ *
8
32
  * @returns {import('nodemailer').Transporter}
9
33
  */
10
34
  static create(config) {