@geniehr/utilities 1.0.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/dist/apiUtils/api.d.ts +5 -0
- package/dist/apiUtils/api.js +113 -0
- package/dist/apiUtils/httpUtils.d.ts +12 -0
- package/dist/apiUtils/httpUtils.js +106 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/logger/LoggerService.d.ts +20 -0
- package/dist/logger/LoggerService.js +121 -0
- package/dist/mq/client.d.ts +57 -0
- package/dist/mq/client.js +143 -0
- package/dist/schema/files/index.d.ts +1 -0
- package/dist/schema/files/index.js +1 -0
- package/dist/schema/files/person/address.d.ts +41 -0
- package/dist/schema/files/person/address.js +15 -0
- package/dist/schema/files/person/emergencyContacts.d.ts +23 -0
- package/dist/schema/files/person/emergencyContacts.js +9 -0
- package/dist/schema/files/person/index.d.ts +7 -0
- package/dist/schema/files/person/index.js +7 -0
- package/dist/schema/files/person/pastExperiences.d.ts +32 -0
- package/dist/schema/files/person/pastExperiences.js +12 -0
- package/dist/schema/files/person/personSchema.d.ts +59 -0
- package/dist/schema/files/person/personSchema.js +21 -0
- package/dist/schema/files/person/primaryContacts.d.ts +23 -0
- package/dist/schema/files/person/primaryContacts.js +9 -0
- package/dist/schema/files/person/qualifications.d.ts +35 -0
- package/dist/schema/files/person/qualifications.js +13 -0
- package/dist/schema/files/person/workDetails.d.ts +53 -0
- package/dist/schema/files/person/workDetails.js +19 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/schemaMappings.d.ts +2 -0
- package/dist/schema/schemaMappings.js +25 -0
- package/dist/schema/validation.d.ts +2 -0
- package/dist/schema/validation.js +41 -0
- package/dist/secrets/CognitoUserService.d.ts +28 -0
- package/dist/secrets/CognitoUserService.js +121 -0
- package/dist/secrets/aws.d.ts +18 -0
- package/dist/secrets/aws.js +193 -0
- package/dist/shared/context/index.d.ts +6 -0
- package/dist/shared/context/index.js +15 -0
- package/dist/shared/enums/Environments.d.ts +6 -0
- package/dist/shared/enums/Environments.js +7 -0
- package/dist/shared/enums/HttpStatusCodes.d.ts +23 -0
- package/dist/shared/enums/HttpStatusCodes.js +29 -0
- package/dist/shared/exceptions/index.d.ts +13 -0
- package/dist/shared/exceptions/index.js +27 -0
- package/dist/shared/helper/ImageCompressor.d.ts +3 -0
- package/dist/shared/helper/ImageCompressor.js +16 -0
- package/dist/shared/services.d.ts +1 -0
- package/dist/shared/services.js +31 -0
- package/dist/types/GHRContext.d.ts +10 -0
- package/dist/types/GHRContext.js +1 -0
- package/dist/types/logTypes.d.ts +23 -0
- package/dist/types/logTypes.js +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import { AppException } from '../shared/exceptions/index.js';
|
|
3
|
+
import { HttpStatusCode } from '../shared/enums/HttpStatusCodes.js';
|
|
4
|
+
import { InvalidRequestException } from '../shared/exceptions/index';
|
|
5
|
+
export declare function sendResponse(req: Request, res: Response, code: HttpStatusCode, response: any | null, ex?: AppException | InvalidRequestException | undefined): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { LoggerService } from '../logger/LoggerService.js';
|
|
2
|
+
import { getContext } from '../shared/context/index.js';
|
|
3
|
+
const context = getContext();
|
|
4
|
+
const logger = new LoggerService();
|
|
5
|
+
export function sendResponse(req, res, code, response, ex) {
|
|
6
|
+
const correlationId = getContext().correlationId;
|
|
7
|
+
if (code >= 200 && code < 300) {
|
|
8
|
+
res.status(code).send({ data: response });
|
|
9
|
+
}
|
|
10
|
+
else if (code == 400) {
|
|
11
|
+
logger.error({ correlationId: correlationId || '', ex });
|
|
12
|
+
res.status(code).send({ data: null, error: { message: ex?.message, code: ex?.errorCode } });
|
|
13
|
+
}
|
|
14
|
+
// else if (code == 401) {
|
|
15
|
+
// }
|
|
16
|
+
else if (code == 500) {
|
|
17
|
+
logger.error({ correlationId: correlationId, ex });
|
|
18
|
+
res.status(code).send({
|
|
19
|
+
data: null,
|
|
20
|
+
error: {
|
|
21
|
+
code: ex?.errorCode,
|
|
22
|
+
message: ex?.message,
|
|
23
|
+
details: ex?.details
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// else if (code > 500) {
|
|
28
|
+
// }
|
|
29
|
+
else {
|
|
30
|
+
logger.error({ correlationId: correlationId, ex });
|
|
31
|
+
res.status(code).send({
|
|
32
|
+
data: null,
|
|
33
|
+
error: {
|
|
34
|
+
code: ex?.errorCode,
|
|
35
|
+
message: ex?.message,
|
|
36
|
+
details: ex?.details
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function extractErrorMessage(error) {
|
|
42
|
+
if (!error)
|
|
43
|
+
return 'Unknown error';
|
|
44
|
+
// If it's a standard Error object, use its message
|
|
45
|
+
if (typeof error.message === 'string') {
|
|
46
|
+
// Clean up multiline Prisma-style messages by picking the last meaningful line
|
|
47
|
+
const lines = error.message.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
48
|
+
if (lines.length > 1) {
|
|
49
|
+
// Try to return the last non-empty line if it looks meaningful
|
|
50
|
+
const lastLine = lines[lines.length - 1];
|
|
51
|
+
if (lastLine.length > 10)
|
|
52
|
+
return lastLine;
|
|
53
|
+
}
|
|
54
|
+
return lines[0]; // fallback to first line
|
|
55
|
+
}
|
|
56
|
+
// Handle string errors
|
|
57
|
+
if (typeof error === 'string') {
|
|
58
|
+
return error;
|
|
59
|
+
}
|
|
60
|
+
// Fallback for non-standard error shapes
|
|
61
|
+
return JSON.stringify(error);
|
|
62
|
+
}
|
|
63
|
+
/*
|
|
64
|
+
{
|
|
65
|
+
"error": "Validation failed",
|
|
66
|
+
"details": [
|
|
67
|
+
{
|
|
68
|
+
"code": "invalid_type",
|
|
69
|
+
"expected": "string",
|
|
70
|
+
"received": "undefined",
|
|
71
|
+
"path": [
|
|
72
|
+
"lastName"
|
|
73
|
+
],
|
|
74
|
+
"message": "Required"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"code": "invalid_type",
|
|
78
|
+
"expected": "string",
|
|
79
|
+
"received": "undefined",
|
|
80
|
+
"path": [
|
|
81
|
+
"emailAddress"
|
|
82
|
+
],
|
|
83
|
+
"message": "Required"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"code": "invalid_date",
|
|
87
|
+
"path": [
|
|
88
|
+
"dateOfBirth"
|
|
89
|
+
],
|
|
90
|
+
"message": "Invalid date"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"code": "invalid_type",
|
|
94
|
+
"expected": "string",
|
|
95
|
+
"received": "undefined",
|
|
96
|
+
"path": [
|
|
97
|
+
"genderName"
|
|
98
|
+
],
|
|
99
|
+
"message": "Required"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"code": "invalid_type",
|
|
103
|
+
"expected": "string",
|
|
104
|
+
"received": "undefined",
|
|
105
|
+
"path": [
|
|
106
|
+
"mobileNo"
|
|
107
|
+
],
|
|
108
|
+
"message": "Required"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AxiosRequestConfig } from 'axios';
|
|
2
|
+
export declare class HttpClient {
|
|
3
|
+
private client;
|
|
4
|
+
constructor(baseURL?: string);
|
|
5
|
+
get<T = any>(url: string, correlationId: string, config?: AxiosRequestConfig): Promise<T>;
|
|
6
|
+
post<T = any>(url: string, data: any, correlationId: string, config?: AxiosRequestConfig): Promise<T>;
|
|
7
|
+
put<T = any>(url: string, data: any, correlationId: string, config?: AxiosRequestConfig): Promise<T>;
|
|
8
|
+
delete<T = any>(url: string, data: any, correlationId: string, config?: AxiosRequestConfig): Promise<T>;
|
|
9
|
+
patch<T = any>(url: string, data: any, correlationId: string, config?: AxiosRequestConfig): Promise<T>;
|
|
10
|
+
private formatError;
|
|
11
|
+
}
|
|
12
|
+
export default HttpClient;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
export class HttpClient {
|
|
3
|
+
client;
|
|
4
|
+
constructor(baseURL) {
|
|
5
|
+
this.client = axios.create({
|
|
6
|
+
baseURL,
|
|
7
|
+
timeout: 30000,
|
|
8
|
+
headers: {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async get(url, correlationId, config) {
|
|
14
|
+
try {
|
|
15
|
+
const mergedConfig = {
|
|
16
|
+
...config,
|
|
17
|
+
headers: {
|
|
18
|
+
...(config?.headers || {}),
|
|
19
|
+
...{ 'X-Correlation-Id': correlationId },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const response = await this.client.get(url, mergedConfig);
|
|
23
|
+
return response.data.data;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
throw this.formatError(error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async post(url, data, correlationId, config) {
|
|
30
|
+
try {
|
|
31
|
+
const mergedConfig = {
|
|
32
|
+
...config,
|
|
33
|
+
headers: {
|
|
34
|
+
...(config?.headers || {}),
|
|
35
|
+
'X-Correlation-Id': correlationId,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const response = await this.client.post(url, data, mergedConfig);
|
|
39
|
+
return response.data.data;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw this.formatError(error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async put(url, data, correlationId, config) {
|
|
46
|
+
try {
|
|
47
|
+
const mergedConfig = {
|
|
48
|
+
...config,
|
|
49
|
+
headers: {
|
|
50
|
+
...(config?.headers || {}),
|
|
51
|
+
'X-Correlation-Id': correlationId,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const response = await this.client.put(url, data, mergedConfig);
|
|
55
|
+
return response.data.data;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw this.formatError(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async delete(url, data, correlationId, config) {
|
|
62
|
+
try {
|
|
63
|
+
const mergedConfig = {
|
|
64
|
+
...config,
|
|
65
|
+
headers: {
|
|
66
|
+
...(config?.headers || {}),
|
|
67
|
+
'X-Correlation-Id': correlationId,
|
|
68
|
+
},
|
|
69
|
+
data, // Attach data to config for DELETE
|
|
70
|
+
};
|
|
71
|
+
const response = await this.client.delete(url, mergedConfig);
|
|
72
|
+
return response.data.data;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw this.formatError(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async patch(url, data, correlationId, config) {
|
|
79
|
+
try {
|
|
80
|
+
const mergedConfig = {
|
|
81
|
+
...config,
|
|
82
|
+
headers: {
|
|
83
|
+
...(config?.headers || {}),
|
|
84
|
+
'X-Correlation-Id': correlationId,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
const response = await this.client.patch(url, data, mergedConfig);
|
|
88
|
+
return response.data.data;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw this.formatError(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
formatError(error) {
|
|
95
|
+
if (error.response) {
|
|
96
|
+
return new Error(`Request failed with status ${error.response.status}: ${JSON.stringify(error.response.data)}`);
|
|
97
|
+
}
|
|
98
|
+
else if (error.request) {
|
|
99
|
+
return new Error('No response received from server');
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
return new Error(`HTTP client error: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export default HttpClient;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { validateRequest } from './schema/validation.js';
|
|
2
|
+
export { MQClient } from './mq/client.js';
|
|
3
|
+
export { getContext, setExtras, setContext } from './shared/context/index.js';
|
|
4
|
+
export { LoggerService as Logger } from './logger/LoggerService.js';
|
|
5
|
+
export { HttpStatusCode } from './shared/enums/HttpStatusCodes.js';
|
|
6
|
+
export { InvalidRequestException, InvalidEnvironmentException, AppException } from './shared/exceptions/index.js';
|
|
7
|
+
export { getAWSParameters, matchFace, registerFace, generatePresignedUrl, getCustomAttribute, updateCustomAttribute, executeS3Action } from './secrets/aws.js';
|
|
8
|
+
export { CognitoUserService } from './secrets/CognitoUserService.js';
|
|
9
|
+
export { HttpClient } from './apiUtils/httpUtils.js';
|
|
10
|
+
export { sendResponse } from './apiUtils/api.js';
|
|
11
|
+
export { services } from './shared/services.js';
|
|
12
|
+
export { ImageCompressor } from './shared/helper/ImageCompressor.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { validateRequest } from './schema/validation.js';
|
|
2
|
+
export { MQClient } from './mq/client.js';
|
|
3
|
+
export { getContext, setExtras, setContext } from './shared/context/index.js';
|
|
4
|
+
export { LoggerService as Logger } from './logger/LoggerService.js';
|
|
5
|
+
export { HttpStatusCode } from './shared/enums/HttpStatusCodes.js';
|
|
6
|
+
export { InvalidRequestException, InvalidEnvironmentException, AppException } from './shared/exceptions/index.js';
|
|
7
|
+
export { getAWSParameters, matchFace, registerFace, generatePresignedUrl, getCustomAttribute, updateCustomAttribute, executeS3Action } from './secrets/aws.js';
|
|
8
|
+
export { CognitoUserService } from './secrets/CognitoUserService.js';
|
|
9
|
+
export { HttpClient } from './apiUtils/httpUtils.js';
|
|
10
|
+
export { sendResponse } from './apiUtils/api.js';
|
|
11
|
+
export { services } from './shared/services.js';
|
|
12
|
+
export { ImageCompressor } from './shared/helper/ImageCompressor.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BaseLogPayload, ErrorLogPayload } from '../types/logTypes';
|
|
2
|
+
export declare class LoggerService {
|
|
3
|
+
private logger;
|
|
4
|
+
private cloudWatch;
|
|
5
|
+
private service;
|
|
6
|
+
constructor(service?: string);
|
|
7
|
+
private isAppException;
|
|
8
|
+
private extractErrorPayload;
|
|
9
|
+
private formatMessage;
|
|
10
|
+
debug(payload: BaseLogPayload): void;
|
|
11
|
+
info(payload: BaseLogPayload): void;
|
|
12
|
+
warn(payload: BaseLogPayload): void;
|
|
13
|
+
error(args: {
|
|
14
|
+
correlationId: string;
|
|
15
|
+
ex: unknown;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}): void;
|
|
18
|
+
error(payload: ErrorLogPayload): void;
|
|
19
|
+
flush(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
import WinstonCloudWatch from 'winston-cloudwatch';
|
|
3
|
+
process.env.AWS_SDK_LOAD_CONFIG = '1';
|
|
4
|
+
process.env.AWS_PROFILE = 'ghr-aws-dev-profile';
|
|
5
|
+
const { NODE_ENV, AWS_REGION = 'ap-south-1' } = process.env;
|
|
6
|
+
export class LoggerService {
|
|
7
|
+
logger;
|
|
8
|
+
cloudWatch;
|
|
9
|
+
service = '';
|
|
10
|
+
constructor(service) {
|
|
11
|
+
if (service)
|
|
12
|
+
this.service = service;
|
|
13
|
+
const logGroupName = `ghr-${NODE_ENV}-logs`;
|
|
14
|
+
this.cloudWatch = new WinstonCloudWatch({
|
|
15
|
+
logGroupName,
|
|
16
|
+
logStreamName: () => {
|
|
17
|
+
const date = new Date().toISOString().split('T')[0];
|
|
18
|
+
return `ghr-${NODE_ENV}-${date}`;
|
|
19
|
+
},
|
|
20
|
+
awsRegion: AWS_REGION,
|
|
21
|
+
jsonMessage: true,
|
|
22
|
+
level: 'debug',
|
|
23
|
+
messageFormatter: (item) => item.message,
|
|
24
|
+
});
|
|
25
|
+
const transports = [
|
|
26
|
+
new winston.transports.Console({
|
|
27
|
+
format: winston.format.combine(winston.format.timestamp(), winston.format.printf(({ timestamp, level, message }) => {
|
|
28
|
+
return `[${timestamp}] [${level.toUpperCase()}] ${message} `;
|
|
29
|
+
})),
|
|
30
|
+
})
|
|
31
|
+
];
|
|
32
|
+
if (NODE_ENV !== 'Dev') {
|
|
33
|
+
process.env.AWS_SDK_LOAD_CONFIG = '1';
|
|
34
|
+
process.env.AWS_PROFILE = 'ghr-aws-dev-profile';
|
|
35
|
+
transports.push(this.cloudWatch);
|
|
36
|
+
}
|
|
37
|
+
this.logger = winston.createLogger({
|
|
38
|
+
level: 'debug',
|
|
39
|
+
transports,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
isAppException(obj) {
|
|
43
|
+
// Duck-typing: If it has an errorCode, it's one of our exceptions.
|
|
44
|
+
return obj && typeof obj.errorCode === 'string';
|
|
45
|
+
}
|
|
46
|
+
extractErrorPayload(input) {
|
|
47
|
+
const { ex, correlationId, service, ...rest } = input;
|
|
48
|
+
if (!correlationId)
|
|
49
|
+
throw new Error('correlationId is mandatory');
|
|
50
|
+
let message = 'Unknown error';
|
|
51
|
+
let stack = '';
|
|
52
|
+
let errorCode = 'UNKNOWN';
|
|
53
|
+
if (ex instanceof Error) {
|
|
54
|
+
message = ex.message;
|
|
55
|
+
stack = ex.stack || '';
|
|
56
|
+
}
|
|
57
|
+
// This check will now work reliably
|
|
58
|
+
if (this.isAppException(ex)) {
|
|
59
|
+
message = ex.message; // Ensure message is from AppException
|
|
60
|
+
errorCode = ex.errorCode; // Correctly assign the error code
|
|
61
|
+
if (ex.details)
|
|
62
|
+
rest.details = ex.details;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
message,
|
|
66
|
+
correlationId,
|
|
67
|
+
errorCode,
|
|
68
|
+
errorStack: stack,
|
|
69
|
+
service: service || this.service, // Use provided service or fallback to the class's service
|
|
70
|
+
...rest,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
formatMessage(level, payload) {
|
|
74
|
+
const base = {
|
|
75
|
+
...payload,
|
|
76
|
+
service: this.service,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
level,
|
|
79
|
+
};
|
|
80
|
+
return base;
|
|
81
|
+
}
|
|
82
|
+
debug(payload) {
|
|
83
|
+
this.logger.debug(this.formatMessage('debug', payload));
|
|
84
|
+
}
|
|
85
|
+
info(payload) {
|
|
86
|
+
this.logger.info(this.formatMessage('info', payload));
|
|
87
|
+
}
|
|
88
|
+
warn(payload) {
|
|
89
|
+
this.logger.warn(this.formatMessage('warn', payload));
|
|
90
|
+
}
|
|
91
|
+
error(input) {
|
|
92
|
+
let finalPayload;
|
|
93
|
+
// This check correctly narrows down the type of 'input'.
|
|
94
|
+
if ('ex' in input) {
|
|
95
|
+
// If 'ex' exists, 'input' matches the 'ErrorInput' type.
|
|
96
|
+
// It's now safe to call the extractor.
|
|
97
|
+
finalPayload = this.extractErrorPayload(input);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// If 'ex' does not exist, 'input' is an 'ErrorLogPayload'.
|
|
101
|
+
// We can use it directly after ensuring mandatory fields are present.
|
|
102
|
+
if (!input.message || !input.errorCode || !input.correlationId) {
|
|
103
|
+
throw new Error('message, errorCode, and correlationId are mandatory in ErrorLogPayload');
|
|
104
|
+
}
|
|
105
|
+
finalPayload = {
|
|
106
|
+
service: this.service,
|
|
107
|
+
...input,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
this.logger.error(this.formatMessage('error', finalPayload));
|
|
111
|
+
}
|
|
112
|
+
flush() {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
this.cloudWatch.kthxbye((err) => {
|
|
115
|
+
if (err)
|
|
116
|
+
return reject(err);
|
|
117
|
+
resolve();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ConsumeMessage } from 'amqplib';
|
|
2
|
+
interface RabbitMQCredentials {
|
|
3
|
+
host: string;
|
|
4
|
+
port: string | number;
|
|
5
|
+
user: string;
|
|
6
|
+
password: string;
|
|
7
|
+
}
|
|
8
|
+
interface DLQOptions {
|
|
9
|
+
dlqName: string;
|
|
10
|
+
messageTTL: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class MQClient {
|
|
13
|
+
private connection;
|
|
14
|
+
private channel;
|
|
15
|
+
private readonly exchangeName;
|
|
16
|
+
private readonly exchangeType;
|
|
17
|
+
constructor();
|
|
18
|
+
/**
|
|
19
|
+
* Initializes the RabbitMQ connection and channel.
|
|
20
|
+
* Establishes a connection, creates a channel, asserts the exchange,
|
|
21
|
+
* and sets up a listener for connection closure.
|
|
22
|
+
* @param creds - RabbitMQ connection credentials.
|
|
23
|
+
*/
|
|
24
|
+
init(creds: RabbitMQCredentials | Record<string, string>): Promise<Record<string, any>>;
|
|
25
|
+
/**
|
|
26
|
+
* Retrieves the current channel instance.
|
|
27
|
+
* Throws an error if the channel has not been initialized.
|
|
28
|
+
* @returns The AMQP channel.
|
|
29
|
+
*/
|
|
30
|
+
private getChannel;
|
|
31
|
+
/**
|
|
32
|
+
* Publishes a message to the defined exchange with a specified routing key.
|
|
33
|
+
* The message is persistent, meaning it will survive server restarts if queues are durable.
|
|
34
|
+
* @param routingKey - The routing key for the message.
|
|
35
|
+
* @param message - The message payload (object or string).
|
|
36
|
+
*/
|
|
37
|
+
publish(routingKey: string, message: object | string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Binds a queue to the exchange with specified routing keys.
|
|
40
|
+
* Optionally sets up a Dead Letter Queue (DLQ) for the main queue.
|
|
41
|
+
* @param queue - The name of the queue to bind.
|
|
42
|
+
* @param routingKeys - An array of routing keys to bind the queue with.
|
|
43
|
+
* @param dlq - Optional DLQ options, including DLQ name and message TTL.
|
|
44
|
+
*/
|
|
45
|
+
bindQueue(queue: string, routingKeys: string[], dlq?: DLQOptions): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Starts consuming messages from a specified queue.
|
|
48
|
+
* Messages are acknowledged (acked) on successful processing,
|
|
49
|
+
* or negatively acknowledged (nacked) on failure, causing them to be
|
|
50
|
+
* requeued or sent to DLQ if configured.
|
|
51
|
+
* @param queue - The name of the queue to consume from.
|
|
52
|
+
* @param onMessage - An async callback function to process each message.
|
|
53
|
+
* Receives the parsed message data and the raw ConsumeMessage object.
|
|
54
|
+
*/
|
|
55
|
+
consume(queue: string, onMessage: (msg: any, raw: ConsumeMessage) => Promise<void>): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as amqp from 'amqplib';
|
|
2
|
+
export class MQClient {
|
|
3
|
+
connection = null;
|
|
4
|
+
channel = null;
|
|
5
|
+
exchangeName = 'ghr.events';
|
|
6
|
+
exchangeType = 'topic';
|
|
7
|
+
constructor() { }
|
|
8
|
+
/**
|
|
9
|
+
* Initializes the RabbitMQ connection and channel.
|
|
10
|
+
* Establishes a connection, creates a channel, asserts the exchange,
|
|
11
|
+
* and sets up a listener for connection closure.
|
|
12
|
+
* @param creds - RabbitMQ connection credentials.
|
|
13
|
+
*/
|
|
14
|
+
async init(creds) {
|
|
15
|
+
const { host, port, user, password } = creds;
|
|
16
|
+
// Construct the connection string for AMQPS (secure AMQP).
|
|
17
|
+
const connStr = `amqps://${user}:${password}@${host}:${port}`;
|
|
18
|
+
try {
|
|
19
|
+
// Connect to RabbitMQ.
|
|
20
|
+
const rawConn = await amqp.connect(connStr);
|
|
21
|
+
// @ts-expect-error Fixing broken types
|
|
22
|
+
this.connection = rawConn;
|
|
23
|
+
// Create a channel from the connection.
|
|
24
|
+
// @ts-expect-error Fixing broken types
|
|
25
|
+
this.channel = await this.connection.createChannel();
|
|
26
|
+
// Assert the existence of the exchange.
|
|
27
|
+
// @ts-expect-error Fixing broken types
|
|
28
|
+
await this.channel.assertExchange(this.exchangeName, this.exchangeType, {
|
|
29
|
+
durable: true, // Exchange will survive server restarts.
|
|
30
|
+
});
|
|
31
|
+
// Set up an event listener for connection closure.
|
|
32
|
+
// @ts-expect-error Fixing broken types
|
|
33
|
+
this.connection.on('close', () => {
|
|
34
|
+
this.connection = null;
|
|
35
|
+
this.channel = null;
|
|
36
|
+
console.warn('RabbitMQ connection closed'); // allow-console
|
|
37
|
+
});
|
|
38
|
+
return { connected: true, message: 'MQ client is initialized successfully.' };
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return { connected: false, message: `'Failed to initialize RabbitMQ client:', ${error}` };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Retrieves the current channel instance.
|
|
46
|
+
* Throws an error if the channel has not been initialized.
|
|
47
|
+
* @returns The AMQP channel.
|
|
48
|
+
*/
|
|
49
|
+
getChannel() {
|
|
50
|
+
if (!this.channel) {
|
|
51
|
+
throw new Error('RabbitMQ not initialized. Call init() first.');
|
|
52
|
+
}
|
|
53
|
+
return this.channel;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Publishes a message to the defined exchange with a specified routing key.
|
|
57
|
+
* The message is persistent, meaning it will survive server restarts if queues are durable.
|
|
58
|
+
* @param routingKey - The routing key for the message.
|
|
59
|
+
* @param message - The message payload (object or string).
|
|
60
|
+
*/
|
|
61
|
+
async publish(routingKey, message) {
|
|
62
|
+
const channel = this.getChannel();
|
|
63
|
+
// Convert the message to a string if it's an object, then to a Buffer.
|
|
64
|
+
const payload = typeof message === 'string' ? message : JSON.stringify(message);
|
|
65
|
+
// Publish the message.
|
|
66
|
+
const sent = channel.publish(this.exchangeName, routingKey, Buffer.from(payload), {
|
|
67
|
+
persistent: true, // Make the message persistent.
|
|
68
|
+
});
|
|
69
|
+
if (!sent) {
|
|
70
|
+
// If the message could not be sent (e.g., channel buffer full), throw an error.
|
|
71
|
+
throw new Error(`Failed to publish to exchange ${this.exchangeName} with key ${routingKey}. Channel buffer likely full.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Binds a queue to the exchange with specified routing keys.
|
|
76
|
+
* Optionally sets up a Dead Letter Queue (DLQ) for the main queue.
|
|
77
|
+
* @param queue - The name of the queue to bind.
|
|
78
|
+
* @param routingKeys - An array of routing keys to bind the queue with.
|
|
79
|
+
* @param dlq - Optional DLQ options, including DLQ name and message TTL.
|
|
80
|
+
*/
|
|
81
|
+
async bindQueue(queue, routingKeys, dlq) {
|
|
82
|
+
const channel = this.getChannel();
|
|
83
|
+
if (dlq) {
|
|
84
|
+
// Assert the DLQ first.
|
|
85
|
+
await channel.assertQueue(dlq.dlqName, { durable: true });
|
|
86
|
+
// Assert the main queue with DLQ arguments.
|
|
87
|
+
await channel.assertQueue(queue, {
|
|
88
|
+
durable: true, // Queue will survive server restarts.
|
|
89
|
+
arguments: {
|
|
90
|
+
'x-dead-letter-exchange': '', // Route to the default exchange.
|
|
91
|
+
'x-dead-letter-routing-key': dlq.dlqName, // Route to the DLQ by its name.
|
|
92
|
+
'x-message-ttl': dlq.messageTTL, // Messages in this queue expire after this time.
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Assert the main queue without DLQ arguments.
|
|
98
|
+
await channel.assertQueue(queue, { durable: true });
|
|
99
|
+
}
|
|
100
|
+
// Bind the queue to the exchange for each routing key.
|
|
101
|
+
for (const key of routingKeys) {
|
|
102
|
+
await channel.bindQueue(queue, this.exchangeName, key);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Starts consuming messages from a specified queue.
|
|
107
|
+
* Messages are acknowledged (acked) on successful processing,
|
|
108
|
+
* or negatively acknowledged (nacked) on failure, causing them to be
|
|
109
|
+
* requeued or sent to DLQ if configured.
|
|
110
|
+
* @param queue - The name of the queue to consume from.
|
|
111
|
+
* @param onMessage - An async callback function to process each message.
|
|
112
|
+
* Receives the parsed message data and the raw ConsumeMessage object.
|
|
113
|
+
*/
|
|
114
|
+
async consume(queue, onMessage) {
|
|
115
|
+
const channel = this.getChannel();
|
|
116
|
+
// Ensure the queue exists before consuming.
|
|
117
|
+
await channel.assertQueue(queue, { durable: true });
|
|
118
|
+
// Start consuming messages.
|
|
119
|
+
await channel.consume(queue, async (msg) => {
|
|
120
|
+
if (!msg) {
|
|
121
|
+
// This can happen if the consumer is cancelled by RabbitMQ.
|
|
122
|
+
console.warn(`Consumer received null message.`); // allow-console
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
// Parse the message content.
|
|
127
|
+
const data = JSON.parse(msg.content.toString());
|
|
128
|
+
// Process the message using the provided callback.
|
|
129
|
+
await onMessage(data, msg);
|
|
130
|
+
// Acknowledge the message if processed successfully.
|
|
131
|
+
channel.ack(msg);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.error(`Error processing message from queue "${queue}":`, err); // allow-console
|
|
135
|
+
// Negative acknowledge the message.
|
|
136
|
+
// 'false' for multiple (don't nack multiple messages), 'false' for requeue (send to DLQ or drop).
|
|
137
|
+
channel.nack(msg, false, false);
|
|
138
|
+
}
|
|
139
|
+
}, { noAck: false } // We are manually acknowledging messages.
|
|
140
|
+
);
|
|
141
|
+
// log.debug(`Started consuming from queue: ${queue}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './person/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './person/index.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const addressSchema: z.ZodObject<{
|
|
3
|
+
employeeId: z.ZodNumber;
|
|
4
|
+
addressName: z.ZodString;
|
|
5
|
+
addressLine1: z.ZodString;
|
|
6
|
+
addressLine2: z.ZodOptional<z.ZodString>;
|
|
7
|
+
addressLine3: z.ZodOptional<z.ZodString>;
|
|
8
|
+
countryId: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
countryName: z.ZodOptional<z.ZodString>;
|
|
10
|
+
stateId: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
stateName: z.ZodOptional<z.ZodString>;
|
|
12
|
+
cityId: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
cityName: z.ZodOptional<z.ZodString>;
|
|
14
|
+
pinCode: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
employeeId: number;
|
|
17
|
+
addressName: string;
|
|
18
|
+
addressLine1: string;
|
|
19
|
+
addressLine2?: string | undefined;
|
|
20
|
+
addressLine3?: string | undefined;
|
|
21
|
+
countryId?: number | undefined;
|
|
22
|
+
countryName?: string | undefined;
|
|
23
|
+
stateId?: number | undefined;
|
|
24
|
+
stateName?: string | undefined;
|
|
25
|
+
cityId?: number | undefined;
|
|
26
|
+
cityName?: string | undefined;
|
|
27
|
+
pinCode?: string | undefined;
|
|
28
|
+
}, {
|
|
29
|
+
employeeId: number;
|
|
30
|
+
addressName: string;
|
|
31
|
+
addressLine1: string;
|
|
32
|
+
addressLine2?: string | undefined;
|
|
33
|
+
addressLine3?: string | undefined;
|
|
34
|
+
countryId?: number | undefined;
|
|
35
|
+
countryName?: string | undefined;
|
|
36
|
+
stateId?: number | undefined;
|
|
37
|
+
stateName?: string | undefined;
|
|
38
|
+
cityId?: number | undefined;
|
|
39
|
+
cityName?: string | undefined;
|
|
40
|
+
pinCode?: string | undefined;
|
|
41
|
+
}>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const addressSchema = z.object({
|
|
3
|
+
employeeId: z.number().int(),
|
|
4
|
+
addressName: z.string().trim().min(1).max(100),
|
|
5
|
+
addressLine1: z.string().trim().min(1).max(255),
|
|
6
|
+
addressLine2: z.string().trim().max(255).optional(),
|
|
7
|
+
addressLine3: z.string().trim().max(255).optional(),
|
|
8
|
+
countryId: z.number().int().optional(),
|
|
9
|
+
countryName: z.string().trim().max(100).optional(),
|
|
10
|
+
stateId: z.number().int().optional(),
|
|
11
|
+
stateName: z.string().trim().max(100).optional(),
|
|
12
|
+
cityId: z.number().int().optional(),
|
|
13
|
+
cityName: z.string().trim().max(100).optional(),
|
|
14
|
+
pinCode: z.string().trim().max(20).optional()
|
|
15
|
+
});
|