core-services-sdk 1.3.21 → 1.3.23

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.
Files changed (51) hide show
  1. package/.vscode/settings.json +1 -1
  2. package/package.json +11 -7
  3. package/src/ids/generators.js +13 -11
  4. package/src/ids/prefixes.js +2 -2
  5. package/src/index.js +1 -0
  6. package/src/rabbit-mq/rabbit-mq.js +131 -62
  7. package/src/util/index.js +12 -0
  8. package/src/util/mask-sensitive.js +78 -0
  9. package/tests/ids/generators.unit.test.js +9 -9
  10. package/tests/rabbit-mq/rabbit-mq.test.js +5 -0
  11. package/tests/util/mask-sensitive.unit.test.js +79 -0
  12. package/tsconfig.json +9 -0
  13. package/types/core/combine-unique-arrays.d.ts +1 -0
  14. package/types/core/index.d.ts +7 -0
  15. package/types/core/normalize-min-max.d.ts +10 -0
  16. package/types/core/normalize-phone-number.d.ts +48 -0
  17. package/types/core/normalize-to-array.d.ts +1 -0
  18. package/types/core/otp-generators.d.ts +56 -0
  19. package/types/core/regex-utils.d.ts +1 -0
  20. package/types/core/sanitize-objects.d.ts +4 -0
  21. package/types/crypto/crypto.d.ts +18 -0
  22. package/types/crypto/encryption.d.ts +6 -0
  23. package/types/crypto/index.d.ts +2 -0
  24. package/types/fastify/error-codes.d.ts +29 -0
  25. package/types/fastify/error-handlers/with-error-handling.d.ts +15 -0
  26. package/types/fastify/index.d.ts +2 -0
  27. package/types/http/HttpError.d.ts +82 -0
  28. package/types/http/http-method.d.ts +7 -0
  29. package/types/http/http.d.ts +36 -0
  30. package/types/http/index.d.ts +4 -0
  31. package/types/http/responseType.d.ts +20 -0
  32. package/types/ids/generators.d.ts +10 -0
  33. package/types/ids/index.d.ts +2 -0
  34. package/types/ids/prefixes.d.ts +34 -0
  35. package/types/index.d.ts +11 -0
  36. package/types/logger/get-logger.d.ts +23 -0
  37. package/types/logger/index.d.ts +1 -0
  38. package/types/mailer/index.d.ts +2 -0
  39. package/types/mailer/mailer.service.d.ts +21 -0
  40. package/types/mailer/transport.factory.d.ts +48 -0
  41. package/types/mongodb/connect.d.ts +4 -0
  42. package/types/mongodb/index.d.ts +3 -0
  43. package/types/mongodb/initialize-mongodb.d.ts +13 -0
  44. package/types/mongodb/validate-mongo-uri.d.ts +15 -0
  45. package/types/rabbit-mq/index.d.ts +1 -0
  46. package/types/rabbit-mq/rabbit-mq.d.ts +40 -0
  47. package/types/templates/index.d.ts +1 -0
  48. package/types/templates/template-loader.d.ts +3 -0
  49. package/types/util/index.d.ts +7 -0
  50. package/types/util/mask-sensitive.d.ts +2 -0
  51. package/vitest.config.js +13 -3
@@ -16,6 +16,6 @@
16
16
  "eslint.validate": ["javascript"],
17
17
 
18
18
  "editor.codeActionsOnSave": {
19
- "source.fixAll.eslint": true
19
+ "source.fixAll.eslint": "explicit"
20
20
  }
21
21
  }
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "core-services-sdk",
3
- "version": "1.3.21",
3
+ "version": "1.3.23",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
+ "types": "types/index.d.ts",
6
7
  "scripts": {
7
8
  "lint": "eslint .",
8
9
  "lint:fix": "eslint . --fix",
9
10
  "test": "vitest run --coverage",
10
11
  "format": "prettier --write .",
11
- "bump": "node ./scripts/bump-version.js"
12
+ "bump": "node ./scripts/bump-version.js",
13
+ "build:types": "tsc --declaration --allowJs --emitDeclarationOnly --outDir types ./src/index.js"
12
14
  },
13
15
  "repository": {
14
16
  "type": "git",
@@ -34,17 +36,19 @@
34
36
  "mongodb": "^6.18.0",
35
37
  "nodemailer": "^7.0.5",
36
38
  "pino": "^9.7.0",
39
+ "ulid": "^3.0.1",
37
40
  "uuid": "^11.1.0",
38
41
  "xml2js": "^0.6.2"
39
42
  },
40
43
  "devDependencies": {
41
44
  "@vitest/coverage-v8": "^3.2.4",
42
- "path": "^0.12.7",
43
- "url": "^0.11.4",
44
- "vitest": "^3.2.4",
45
45
  "eslint": "^9.30.0",
46
46
  "eslint-config-prettier": "^10.1.5",
47
47
  "eslint-plugin-prettier": "^5.5.1",
48
- "prettier": "^3.6.2"
48
+ "path": "^0.12.7",
49
+ "prettier": "^3.6.2",
50
+ "typescript": "^5.9.2",
51
+ "url": "^0.11.4",
52
+ "vitest": "^3.2.4"
49
53
  }
50
- }
54
+ }
@@ -1,26 +1,28 @@
1
- import { v4 as uuidv4 } from 'uuid'
1
+ import { ulid } from 'ulid'
2
2
  import { ID_PREFIXES } from './prefixes.js'
3
3
 
4
4
  /**
5
- * Generates a new UUID v4 string.
5
+ * Generates a new ULID string.
6
6
  *
7
- * @returns {string} A new UUID (version 4).
7
+ * ULIDs are 26-character, lexicographically sortable identifiers.
8
+ *
9
+ * @returns {string} A new ULID.
8
10
  *
9
11
  * @example
10
- * generateId() // '550e8400-e29b-41d4-a716-446655440000'
12
+ * generateId() // '01HZY3M7K4FJ9A8Q4Y1ZB5NX3T'
11
13
  */
12
14
  export const generateId = () => {
13
- return uuidv4()
15
+ return ulid()
14
16
  }
15
17
 
16
18
  /**
17
19
  * Generates a unique ID with the given prefix.
18
20
  *
19
- * @param {string} prefix - A prefix string to prepend to the UUID.
20
- * @returns {string} A unique ID in the format `${prefix}_${uuid}`.
21
+ * @param {string} prefix - A prefix string to prepend to the ULID.
22
+ * @returns {string} A unique ID in the format `${prefix}_${ulid}`.
21
23
  *
22
24
  * @example
23
- * generatePrefixedId('usr') // 'usr_550e8400-e29b-41d4-a716-446655440000'
25
+ * generatePrefixedId('usr') // 'usr_01HZY3M7K4FJ9A8Q4Y1ZB5NX3T'
24
26
  */
25
27
  export const generatePrefixedId = (prefix) => {
26
28
  return `${prefix}_${generateId()}`
@@ -73,9 +75,9 @@ export const generateRolePermissionsId = () =>
73
75
  generatePrefixedId(ID_PREFIXES.ROLE_PERMISSIONS)
74
76
 
75
77
  /**
76
- * Generates a onboarding ID with a `onb_` prefix.
78
+ * Generates an onboarding ID with a `onb_` prefix.
77
79
  *
78
- * @returns {string} A onboarding ID.
80
+ * @returns {string} An onboarding ID.
79
81
  */
80
82
  export const generateOnboardingId = () =>
81
83
  generatePrefixedId(ID_PREFIXES.ONBOARDING)
@@ -83,6 +85,6 @@ export const generateOnboardingId = () =>
83
85
  /**
84
86
  * Generates a session ID with a `sess_` prefix.
85
87
  *
86
- * @returns {string} A onboarding ID.
88
+ * @returns {string} A session ID.
87
89
  */
88
90
  export const generateSessionId = () => generatePrefixedId(ID_PREFIXES.SESSION)
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Mapping of entity types to their unique ID prefixes.
3
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'
4
+ * These prefixes are prepended to ULIDs to create consistent and identifiable IDs across the system.
5
+ * For example: 'usr_01HZY3M7K4FJ9A8Q4Y1ZB5NX3T'
6
6
  *
7
7
  * @readonly
8
8
  * @enum {string}
package/src/index.js CHANGED
@@ -8,3 +8,4 @@ export * from './logger/index.js'
8
8
  export * from './mailer/index.js'
9
9
  export * from './rabbit-mq/index.js'
10
10
  export * from './templates/index.js'
11
+ export * from './util/index.js'
@@ -1,48 +1,83 @@
1
1
  // @ts-nocheck
2
2
  import * as amqp from 'amqplib'
3
- import { v4 as uuidv4 } from 'uuid'
3
+ import { ulid } from 'ulid'
4
4
 
5
5
  /**
6
6
  * @typedef {Object} Log
7
- * @property {(msg: string) => void} info
8
- * @property {(msg: string, ...args: any[]) => void} error
7
+ * @property {(obj: any, msg?: string) => void} info
8
+ * @property {(obj: any, msg?: string) => void} error
9
+ * @property {(obj: any, msg?: string) => void} debug
9
10
  */
10
11
 
12
+ const generateMsgId = () => `rbt_${ulid()}`
13
+
11
14
  /**
12
- * Connects to RabbitMQ server.
13
- * @param {{ host: string }} options
15
+ * Connects to a RabbitMQ server.
16
+ *
17
+ * @param {{ host: string, log: import('pino').Logger }} options
14
18
  * @returns {Promise<amqp.Connection>}
15
19
  */
16
- export const connectQueueService = async ({ host }) => {
20
+ export const connectQueueService = async ({ host, log }) => {
21
+ const t0 = Date.now()
22
+ const logger = log.child({ op: 'connectQueueService', host })
23
+
17
24
  try {
18
- return await amqp.connect(host)
19
- } catch (error) {
20
- console.error('Failed to connect to RabbitMQ:', error)
21
- throw error
25
+ logger.debug('start')
26
+ const connection = await amqp.connect(host)
27
+
28
+ logger.info({
29
+ event: 'ok',
30
+ ms: Date.now() - t0,
31
+ })
32
+
33
+ return connection
34
+ } catch (err) {
35
+ logger.error(err, {
36
+ event: 'error',
37
+ ms: Date.now() - t0,
38
+ })
39
+ throw err
22
40
  }
23
41
  }
24
42
 
25
43
  /**
26
- * Creates a channel from RabbitMQ connection.
27
- * @param {{ host: string }} options
44
+ * Creates a channel from a RabbitMQ connection.
45
+ *
46
+ * @param {{ host: string, log: import('pino').Logger }} options
28
47
  * @returns {Promise<amqp.Channel>}
29
48
  */
30
- export const createChannel = async ({ host }) => {
49
+ export const createChannel = async ({ host, log }) => {
50
+ const t0 = Date.now()
51
+ const logger = log.child({ op: 'createChannel', host })
52
+
31
53
  try {
54
+ logger.debug('start')
32
55
  const connection = /** @type {amqp.Connection} */ (
33
- await connectQueueService({ host })
56
+ await connectQueueService({ host, log })
34
57
  )
35
- return await connection.createChannel()
36
- } catch (error) {
37
- console.error('Failed to create channel:', error)
38
- throw error
58
+ const channel = await connection.createChannel()
59
+
60
+ logger.debug('channel-created')
61
+ logger.info({
62
+ event: 'ok',
63
+ ms: Date.now() - t0,
64
+ })
65
+
66
+ return channel
67
+ } catch (err) {
68
+ logger.error(err, {
69
+ event: 'error',
70
+ ms: Date.now() - t0,
71
+ })
72
+ throw err
39
73
  }
40
74
  }
41
75
 
42
76
  /**
43
- * Parses a RabbitMQ message.
77
+ * Parses a RabbitMQ message into a structured object.
78
+ *
44
79
  * @param {amqp.ConsumeMessage} msgInfo
45
- * @returns {{ msgId: string, data: any }}
80
+ * @returns {{ msgId: string, data: any, correlationId?: string }}
46
81
  */
47
82
  const parseMessage = (msgInfo) => {
48
83
  return JSON.parse(msgInfo.content.toString())
@@ -54,8 +89,8 @@ const parseMessage = (msgInfo) => {
54
89
  * @param {Object} options
55
90
  * @param {import('amqplib').Channel} options.channel - RabbitMQ channel
56
91
  * @param {string} options.queue - Queue name to subscribe to
57
- * @param {(data: any) => Promise<void>} options.onReceive - Async handler for incoming message
58
- * @param {Log} options.log - Logging utility
92
+ * @param {(data: any, correlationId?: string) => Promise<void>} options.onReceive - Async handler
93
+ * @param {import('pino').Logger} options.log - Base logger
59
94
  * @param {boolean} [options.nackOnError=false] - Whether to nack the message on error (default: false)
60
95
  * @param {number} [options.prefetch=1] - Max unacked messages per consumer (default: 1)
61
96
  *
@@ -69,31 +104,48 @@ export const subscribeToQueue = async ({
69
104
  onReceive,
70
105
  nackOnError = false,
71
106
  }) => {
107
+ const logger = log.child({ op: 'subscribeToQueue', queue })
108
+
72
109
  try {
73
110
  await channel.assertQueue(queue, { durable: true })
74
-
75
111
  !!prefetch && (await channel.prefetch(prefetch))
76
112
 
77
113
  channel.consume(queue, async (msgInfo) => {
78
114
  if (!msgInfo) {
79
115
  return
80
116
  }
117
+ const t0 = Date.now()
118
+
119
+ const { msgId, data, correlationId } = parseMessage(msgInfo)
120
+ const child = logger.child({ msgId, correlationId })
81
121
 
82
122
  try {
83
- const { msgId, data } = parseMessage(msgInfo)
84
- log.info(`Handling message from '${queue}' msgId: ${msgId}`)
85
- await onReceive(data)
123
+ child.debug('start')
124
+ child.info('message-received')
125
+
126
+ await onReceive(data, correlationId)
86
127
  channel.ack(msgInfo)
87
- } catch (error) {
88
- const { msgId } = parseMessage(msgInfo)
89
- log.error(`Error handling message: ${msgId} on queue '${queue}'`)
90
- log.error(error)
128
+
129
+ child.info({
130
+ event: 'ok',
131
+ ms: Date.now() - t0,
132
+ })
133
+ return
134
+ } catch (err) {
91
135
  nackOnError ? channel.nack(msgInfo) : channel.ack(msgInfo)
136
+
137
+ child.error(err, {
138
+ event: 'error',
139
+ ms: Date.now() - t0,
140
+ })
141
+ return
92
142
  }
93
143
  })
94
- } catch (error) {
95
- console.error('Failed to subscribe to queue:', error)
96
- throw error
144
+ } catch (err) {
145
+ logger.error(err, {
146
+ event: 'error',
147
+ })
148
+ throw err
97
149
  }
98
150
  }
99
151
 
@@ -101,57 +153,73 @@ export const subscribeToQueue = async ({
101
153
  * Initializes RabbitMQ integration with publish and subscribe support.
102
154
  *
103
155
  * @param {Object} options
104
- * @param {string} options.host - RabbitMQ connection URI (e.g., 'amqp://user:pass@localhost:5672')
105
- * @param {Log} options.log - Logging utility with `info()` and `error()` methods
156
+ * @param {string} options.host - RabbitMQ connection URI
157
+ * @param {import('pino').Logger} options.log - Logger
106
158
  *
107
159
  * @returns {Promise<{
108
- * publish: (queue: string, data: any) => Promise<boolean>,
160
+ * publish: (queue: string, data: any, correlationId?: string) => Promise<boolean>,
109
161
  * subscribe: (options: {
110
162
  * queue: string,
111
- * onReceive: (data: any) => Promise<void>,
163
+ * onReceive: (data: any, correlationId?: string) => Promise<void>,
112
164
  * nackOnError?: boolean
113
165
  * }) => Promise<void>,
114
166
  * channel: amqp.Channel
115
167
  * }>}
116
- *
117
- * @example
118
- * const rabbit = await initializeQueue({ host, log });
119
- * await rabbit.publish('jobs', { task: 'sendEmail' });
120
- * await rabbit.subscribe({
121
- * queue: 'jobs',
122
- * onReceive: async (data) => { console.log(data); },
123
- * });
124
168
  */
125
169
  export const initializeQueue = async ({ host, log }) => {
126
- const channel = await createChannel({ host })
170
+ const channel = await createChannel({ host, log })
171
+ const logger = log.child({ op: 'initializeQueue' })
127
172
 
128
173
  /**
129
- * Publishes a message to a queue.
130
- * @param {string} queue
131
- * @param {any} data
132
- * @returns {Promise<boolean>}
174
+ * Publishes a message to a queue with a generated `rbt_<ulid>` ID.
175
+ *
176
+ * @param {string} queue - Queue name
177
+ * @param {any} data - Payload to send
178
+ * @param {string} [correlationId] - Correlation ID for tracing
179
+ * @returns {Promise<boolean>} True if the message was sent successfully
133
180
  */
134
- const publish = async (queue, data) => {
135
- const msgId = uuidv4()
181
+ const publish = async (queue, data, correlationId) => {
182
+ const msgId = generateMsgId()
183
+ const t0 = Date.now()
184
+ const logChild = logger.child({
185
+ op: 'publish',
186
+ queue,
187
+ msgId,
188
+ correlationId,
189
+ })
190
+
136
191
  try {
192
+ logChild.debug('start')
193
+
137
194
  await channel.assertQueue(queue, { durable: true })
138
- log.info(`Publishing to '${queue}' msgId: ${msgId}`)
139
- return channel.sendToQueue(
195
+ const payload = { msgId, data, correlationId }
196
+ const sent = channel.sendToQueue(
140
197
  queue,
141
- Buffer.from(JSON.stringify({ msgId, data })),
198
+ Buffer.from(JSON.stringify(payload)),
142
199
  )
143
- } catch (error) {
144
- log.error(`Error publishing to '${queue}' msgId: ${msgId}`)
145
- log.error(error)
146
- throw error
200
+
201
+ logChild.debug('message-sent')
202
+ logChild.info({
203
+ event: 'ok',
204
+ ms: Date.now() - t0,
205
+ })
206
+
207
+ return sent
208
+ } catch (err) {
209
+ logChild.error(err, {
210
+ event: 'error',
211
+ ms: Date.now() - t0,
212
+ })
213
+ throw err
147
214
  }
148
215
  }
149
216
 
150
217
  /**
151
- * Subscribes to a queue.
218
+ * Subscribes to a queue for incoming messages.
219
+ *
152
220
  * @param {{
153
221
  * queue: string,
154
- * onReceive: (data: any) => Promise<void>,
222
+ * onReceive: (data: any, correlationId?: string) => Promise<void>,
155
223
  * nackOnError?: boolean
156
224
  * }} options
157
225
  * @returns {Promise<void>}
@@ -168,7 +236,8 @@ export const initializeQueue = async ({ host, log }) => {
168
236
  }
169
237
 
170
238
  /**
171
- * Builds RabbitMQ URI from environment variables.
239
+ * Builds a RabbitMQ URI string from environment variables.
240
+ *
172
241
  * @param {{
173
242
  * RABBIT_HOST: string,
174
243
  * RABBIT_PORT: string | number,
@@ -0,0 +1,12 @@
1
+ import { mask, maskSingle } from './mask-sensitive.js'
2
+
3
+ /**
4
+ * @namespace util
5
+ * Common utility functions.
6
+ */
7
+ export const util = {
8
+ mask,
9
+ maskSingle,
10
+ }
11
+
12
+ export { mask, maskSingle }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Mask middle of a primitive value while keeping left/right edges.
3
+ * @param {string|number|boolean|null|undefined} value
4
+ * @param {string} [fill='•']
5
+ * @param {number} [maskLen=3]
6
+ * @param {number} [left=4]
7
+ * @param {number} [right=4]
8
+ * @returns {string}
9
+ */
10
+ export const maskSingle = (
11
+ value,
12
+ fill = '•',
13
+ maskLen = 3,
14
+ left = 4,
15
+ right = 4,
16
+ ) => {
17
+ if (value == null) {
18
+ return ''
19
+ }
20
+ const str = String(value)
21
+ if (str.length === 0) {
22
+ return ''
23
+ }
24
+ const m = Math.max(1, maskLen)
25
+
26
+ if (str.length <= left + right) {
27
+ if (str.length === 1) {
28
+ return fill
29
+ }
30
+ if (str.length === 2) {
31
+ return str[0] + fill.repeat(2) // "ab" -> "a••"
32
+ }
33
+ return str.slice(0, 1) + fill.repeat(m) + str.slice(-1)
34
+ }
35
+
36
+ return str.slice(0, left) + fill.repeat(m) + str.slice(-right)
37
+ }
38
+
39
+ /**
40
+ * Recursively mask values in strings, numbers, booleans, arrays, and objects.
41
+ * @param {string|number|boolean|Array|Object|null|undefined} value
42
+ * @param {string} [fill='•']
43
+ * @param {number} [maskLen=3]
44
+ * @param {number} [left=4]
45
+ * @param {number} [right=4]
46
+ * @returns {string|Array|Object}
47
+ */
48
+ export const mask = (value, fill = '•', maskLen = 3, left = 4, right = 4) => {
49
+ const type = typeof value
50
+
51
+ if (value instanceof Date) {
52
+ const isoDate = value.toISOString()
53
+ return maskSingle(isoDate, '', isoDate.length, 0, 0)
54
+ }
55
+
56
+ if (value == null || (value && ['string', 'number'].includes(type))) {
57
+ return maskSingle(value, fill, maskLen, left, right)
58
+ }
59
+
60
+ if (type === 'boolean') {
61
+ return maskSingle(value, '', 0, 0, 0)
62
+ }
63
+
64
+ if (Array.isArray(value)) {
65
+ return value.map((aValue) => mask(aValue, fill, maskLen, left, right))
66
+ }
67
+
68
+ if (typeof value === 'object') {
69
+ return Object.fromEntries(
70
+ Object.entries(value).map(([prop, propValue]) => [
71
+ prop,
72
+ mask(propValue, fill, maskLen, left, right),
73
+ ]),
74
+ )
75
+ }
76
+
77
+ return value
78
+ }
@@ -14,25 +14,25 @@ import {
14
14
  } from '../../src/ids/generators.js'
15
15
  import { ID_PREFIXES } from '../../src/ids/prefixes.js'
16
16
 
17
- const UUID_V4_REGEX =
18
- /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
17
+ // ULID is a 26-character Base32 string (no I, L, O, U).
18
+ const ULID_REGEX = /^[0-9A-HJKMNP-TV-Z]{26}$/
19
19
 
20
20
  const testPrefixFunction = (fn, expectedPrefix) => {
21
21
  const id = fn()
22
22
  expect(typeof id).toBe('string')
23
- const [prefix, uuid] = id.split('_')
23
+ const [prefix, ulid] = id.split('_')
24
24
  expect(prefix).toBe(expectedPrefix)
25
- expect(uuid).toMatch(UUID_V4_REGEX)
25
+ expect(ulid).toMatch(ULID_REGEX)
26
26
  }
27
27
 
28
28
  describe('generateId', () => {
29
- it('generates a valid UUID v4', () => {
29
+ it('generates a valid ULID', () => {
30
30
  const id = generateId()
31
31
  expect(typeof id).toBe('string')
32
- expect(id).toMatch(UUID_V4_REGEX)
32
+ expect(id).toMatch(ULID_REGEX)
33
33
  })
34
34
 
35
- it('generates unique UUIDs', () => {
35
+ it('generates unique ULIDs', () => {
36
36
  const ids = new Set(Array.from({ length: 10 }, () => generateId()))
37
37
  expect(ids.size).toBe(10)
38
38
  })
@@ -41,9 +41,9 @@ describe('generateId', () => {
41
41
  describe('generatePrefixedId', () => {
42
42
  it('generates an ID with the correct prefix', () => {
43
43
  const prefixed = generatePrefixedId('test')
44
- const [prefix, uuid] = prefixed.split('_')
44
+ const [prefix, ulid] = prefixed.split('_')
45
45
  expect(prefix).toBe('test')
46
- expect(uuid).toMatch(UUID_V4_REGEX)
46
+ expect(ulid).toMatch(ULID_REGEX)
47
47
  })
48
48
  })
49
49
 
@@ -7,6 +7,10 @@ const sleep = (ms) => new Promise((res) => setTimeout(res, ms))
7
7
  const testLog = {
8
8
  info: () => {},
9
9
  error: console.error,
10
+ debug: console.debug,
11
+ child() {
12
+ return testLog
13
+ },
10
14
  }
11
15
 
12
16
  describe('RabbitMQ SDK', () => {
@@ -18,6 +22,7 @@ describe('RabbitMQ SDK', () => {
18
22
  let received = null
19
23
 
20
24
  beforeAll(async () => {
25
+ // @ts-ignore
21
26
  sdk = await initializeQueue({ host, log: testLog })
22
27
 
23
28
  await sdk.subscribe({
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import { mask, maskSingle } from '../../src/util/index.js'
4
+
5
+ describe('maskSingle', () => {
6
+ it('masks middle of a regular string (length == left+right)', () => {
7
+ expect(maskSingle('abcdefgh')).toBe('a•••h')
8
+ })
9
+
10
+ it('masks numbers with default settings', () => {
11
+ expect(maskSingle(12345678)).toBe('1•••8')
12
+ })
13
+
14
+ it('masks booleans', () => {
15
+ expect(maskSingle(true)).toBe('t•••e')
16
+ expect(maskSingle(false)).toBe('f•••e')
17
+ })
18
+
19
+ it('masks very short strings correctly', () => {
20
+ expect(maskSingle('ab')).toBe('a••b'.slice(0, 3)) // will produce 'a••'
21
+ expect(maskSingle('a')).toBe('•')
22
+ expect(maskSingle('')).toBe('')
23
+ })
24
+
25
+ it('respects custom fill and mask length', () => {
26
+ expect(maskSingle('abcdefgh', '*', 5)).toBe('a*****h')
27
+ })
28
+
29
+ it('ensures maskLen is at least 1', () => {
30
+ expect(maskSingle('abcdefgh', '*', 0)).toBe('a*h')
31
+ })
32
+
33
+ it('returns empty string for null/undefined', () => {
34
+ expect(maskSingle(null)).toBe('')
35
+ expect(maskSingle(undefined)).toBe('')
36
+ })
37
+ })
38
+
39
+ describe('mask', () => {
40
+ it('masks primitives (string, number, boolean)', () => {
41
+ expect(mask('abcdefgh')).toBe('a•••h')
42
+ expect(mask(12345678)).toBe('1•••8')
43
+ expect(mask(true)).toBe('true')
44
+ expect(mask(false)).toBe('false')
45
+ })
46
+
47
+ it('returns empty string for null/undefined', () => {
48
+ expect(mask(null)).toBe('')
49
+ expect(mask(undefined)).toBe('')
50
+ })
51
+
52
+ it('masks arrays recursively', () => {
53
+ expect(mask(['abcdefgh', 12345678])).toEqual(['a•••h', '1•••8'])
54
+ })
55
+
56
+ it('masks objects recursively', () => {
57
+ expect(mask({ a: 'abcdefgh', b: 12345678 })).toEqual({
58
+ a: 'a•••h',
59
+ b: '1•••8',
60
+ })
61
+ })
62
+
63
+ it('masks nested objects/arrays recursively', () => {
64
+ const input = { arr: ['abcdefgh', { num: 12345678 }] }
65
+ const expected = { arr: ['a•••h', { num: '1•••8' }] }
66
+ expect(mask(input)).toEqual(expected)
67
+ })
68
+
69
+ it('handles Date instances by returning full ISO string', () => {
70
+ const d = new Date('2025-08-15T12:34:56.789Z')
71
+ expect(mask(d)).toBe(d.toISOString())
72
+ })
73
+
74
+ it('respects custom fill and mask length in recursive calls', () => {
75
+ const input = { val: 'abcdefgh' }
76
+ const expected = { val: 'a*****h' }
77
+ expect(mask(input, '*', 5)).toEqual(expected)
78
+ })
79
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "declaration": true,
4
+ "allowJs": true,
5
+ "emitDeclarationOnly": true,
6
+ "outDir": "types"
7
+ },
8
+ "include": ["src"]
9
+ }
@@ -0,0 +1 @@
1
+ export function combineUniqueArrays(...lists: Array<any>[]): Array<any>;
@@ -0,0 +1,7 @@
1
+ export * from "./regex-utils.js";
2
+ export * from "./otp-generators.js";
3
+ export * from "./sanitize-objects.js";
4
+ export * from "./normalize-min-max.js";
5
+ export * from "./normalize-to-array.js";
6
+ export * from "./combine-unique-arrays.js";
7
+ export * from "./normalize-phone-number.js";