@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.
Files changed (55) hide show
  1. package/dist/apiUtils/api.d.ts +5 -0
  2. package/dist/apiUtils/api.js +113 -0
  3. package/dist/apiUtils/httpUtils.d.ts +12 -0
  4. package/dist/apiUtils/httpUtils.js +106 -0
  5. package/dist/index.d.ts +12 -0
  6. package/dist/index.js +12 -0
  7. package/dist/logger/LoggerService.d.ts +20 -0
  8. package/dist/logger/LoggerService.js +121 -0
  9. package/dist/mq/client.d.ts +57 -0
  10. package/dist/mq/client.js +143 -0
  11. package/dist/schema/files/index.d.ts +1 -0
  12. package/dist/schema/files/index.js +1 -0
  13. package/dist/schema/files/person/address.d.ts +41 -0
  14. package/dist/schema/files/person/address.js +15 -0
  15. package/dist/schema/files/person/emergencyContacts.d.ts +23 -0
  16. package/dist/schema/files/person/emergencyContacts.js +9 -0
  17. package/dist/schema/files/person/index.d.ts +7 -0
  18. package/dist/schema/files/person/index.js +7 -0
  19. package/dist/schema/files/person/pastExperiences.d.ts +32 -0
  20. package/dist/schema/files/person/pastExperiences.js +12 -0
  21. package/dist/schema/files/person/personSchema.d.ts +59 -0
  22. package/dist/schema/files/person/personSchema.js +21 -0
  23. package/dist/schema/files/person/primaryContacts.d.ts +23 -0
  24. package/dist/schema/files/person/primaryContacts.js +9 -0
  25. package/dist/schema/files/person/qualifications.d.ts +35 -0
  26. package/dist/schema/files/person/qualifications.js +13 -0
  27. package/dist/schema/files/person/workDetails.d.ts +53 -0
  28. package/dist/schema/files/person/workDetails.js +19 -0
  29. package/dist/schema/index.d.ts +1 -0
  30. package/dist/schema/index.js +1 -0
  31. package/dist/schema/schemaMappings.d.ts +2 -0
  32. package/dist/schema/schemaMappings.js +25 -0
  33. package/dist/schema/validation.d.ts +2 -0
  34. package/dist/schema/validation.js +41 -0
  35. package/dist/secrets/CognitoUserService.d.ts +28 -0
  36. package/dist/secrets/CognitoUserService.js +121 -0
  37. package/dist/secrets/aws.d.ts +18 -0
  38. package/dist/secrets/aws.js +193 -0
  39. package/dist/shared/context/index.d.ts +6 -0
  40. package/dist/shared/context/index.js +15 -0
  41. package/dist/shared/enums/Environments.d.ts +6 -0
  42. package/dist/shared/enums/Environments.js +7 -0
  43. package/dist/shared/enums/HttpStatusCodes.d.ts +23 -0
  44. package/dist/shared/enums/HttpStatusCodes.js +29 -0
  45. package/dist/shared/exceptions/index.d.ts +13 -0
  46. package/dist/shared/exceptions/index.js +27 -0
  47. package/dist/shared/helper/ImageCompressor.d.ts +3 -0
  48. package/dist/shared/helper/ImageCompressor.js +16 -0
  49. package/dist/shared/services.d.ts +1 -0
  50. package/dist/shared/services.js +31 -0
  51. package/dist/types/GHRContext.d.ts +10 -0
  52. package/dist/types/GHRContext.js +1 -0
  53. package/dist/types/logTypes.d.ts +23 -0
  54. package/dist/types/logTypes.js +1 -0
  55. package/package.json +55 -0
@@ -0,0 +1,193 @@
1
+ import { SSMClient, GetParameterCommand, GetParametersByPathCommand, } from '@aws-sdk/client-ssm';
2
+ import { fromIni } from '@aws-sdk/credential-providers';
3
+ import { AppException } from '../shared/exceptions/index.js';
4
+ import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
5
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
6
+ import { CognitoIdentityProviderClient, AdminUpdateUserAttributesCommand, AdminGetUserCommand } from '@aws-sdk/client-cognito-identity-provider';
7
+ let ssm;
8
+ let rand = Math.random().toString();
9
+ const env = process.env.NODE_ENV || rand;
10
+ if (env == rand)
11
+ throw new AppException('Invalid Environment', 'E-Invalid-Environment');
12
+ import { RekognitionClient, IndexFacesCommand, SearchFacesByImageCommand } from '@aws-sdk/client-rekognition';
13
+ const rekognition = new RekognitionClient({ region: 'ap-south-1', credentials: env === 'local' ? fromIni({ profile: 'ghr-aws-dev-profile' }) : undefined });
14
+ const s3 = new S3Client({
15
+ region: 'ap-south-1',
16
+ credentials: env === 'local' ? fromIni({ profile: 'ghr-aws-dev-profile' }) : undefined
17
+ });
18
+ const client = new CognitoIdentityProviderClient({
19
+ region: 'ap-south-1',
20
+ credentials: env === 'local' ? fromIni({ profile: 'ghr-aws-dev-profile' }) : undefined
21
+ });
22
+ /**
23
+ * Fetch a single parameter or all parameters under a prefix.
24
+ * @param nameOrPrefix Full name (e.g. /dev/mysql/person) or prefix (e.g. /dev/mysql/)
25
+ */
26
+ export async function getAWSParameters(nameOrPrefix) {
27
+ ssm = new SSMClient({
28
+ region: 'ap-south-1',
29
+ credentials: process.env.NODE_ENV == 'local' ? fromIni({ profile: 'ghr-aws-dev-profile' }) : undefined,
30
+ });
31
+ const result = {};
32
+ const isPrefix = nameOrPrefix.endsWith('/');
33
+ if (!isPrefix) {
34
+ const command = new GetParameterCommand({
35
+ Name: nameOrPrefix,
36
+ WithDecryption: true,
37
+ });
38
+ const res = await ssm.send(command);
39
+ if (res.Parameter?.Name && res.Parameter?.Value !== undefined) {
40
+ const name = res.Parameter.Name.split('/').pop();
41
+ result[name] = res.Parameter.Value;
42
+ }
43
+ return result;
44
+ }
45
+ // handle prefix mode
46
+ let nextToken = undefined;
47
+ do {
48
+ const input = {
49
+ Path: nameOrPrefix,
50
+ Recursive: true,
51
+ WithDecryption: true,
52
+ NextToken: nextToken,
53
+ };
54
+ const command = new GetParametersByPathCommand(input);
55
+ const response = await ssm.send(command);
56
+ for (const param of response.Parameters || []) {
57
+ if (param.Name && param.Value !== undefined) {
58
+ const key = param.Name.replace(nameOrPrefix, '').replace(/^\//, '');
59
+ result[key] = param.Value;
60
+ }
61
+ }
62
+ nextToken = response.NextToken;
63
+ } while (nextToken);
64
+ return result;
65
+ }
66
+ export async function matchFace(imageInBase64) {
67
+ const command = new SearchFacesByImageCommand({
68
+ CollectionId: 'employee_faces',
69
+ Image: { Bytes: Buffer.from(imageInBase64, 'base64') },
70
+ MaxFaces: 1,
71
+ FaceMatchThreshold: 95,
72
+ });
73
+ const result = await rekognition.send(command);
74
+ return result;
75
+ }
76
+ export async function registerFace(employeeId, imageInBase64) {
77
+ const command = new IndexFacesCommand({
78
+ CollectionId: 'employee_faces',
79
+ ExternalImageId: employeeId,
80
+ Image: { Bytes: Buffer.from(imageInBase64, 'base64') },
81
+ DetectionAttributes: ['DEFAULT'],
82
+ });
83
+ const result = await rekognition.send(command);
84
+ return result;
85
+ }
86
+ export async function generatePresignedUrl(entity, fileName, bucketName, idToken, action, expiresIn = 3600) {
87
+ let token = idToken?.startsWith("Bearer ") ? idToken.split(" ")[1] : idToken;
88
+ const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
89
+ const userId = Number(payload["custom:id"]);
90
+ if (!userId)
91
+ throw new AppException("User ID not found in token", "E-User-ID-Not-Found");
92
+ const REGION = "ap-south-1";
93
+ const s3 = new S3Client({ region: REGION });
94
+ const key = `${userId}/${entity}/${fileName}`;
95
+ let command;
96
+ switch (action) {
97
+ case 'put':
98
+ command = new PutObjectCommand({
99
+ Bucket: bucketName,
100
+ Key: key,
101
+ });
102
+ break;
103
+ case 'get': command = new GetObjectCommand({
104
+ Bucket: bucketName,
105
+ Key: key,
106
+ });
107
+ }
108
+ try {
109
+ return await getSignedUrl(s3, command, { expiresIn });
110
+ }
111
+ catch (error) {
112
+ throw new AppException(`Failed to generate presigned URL: ${error instanceof Error ? error.message : "Unknown error"}`, "E-Presigned-URL-Generation-Failed");
113
+ }
114
+ }
115
+ export async function executeS3Action(action, bucketName, key, body, contentType) {
116
+ try {
117
+ let command;
118
+ switch (action) {
119
+ case "put":
120
+ if (!body)
121
+ throw new AppException("Missing file body for PUT operation", "E-Missing-Body");
122
+ const putParams = {
123
+ Bucket: bucketName,
124
+ Key: key,
125
+ Body: body,
126
+ ContentType: contentType || "application/octet-stream",
127
+ };
128
+ command = new PutObjectCommand(putParams);
129
+ break;
130
+ case "get":
131
+ const getParams = {
132
+ Bucket: bucketName,
133
+ Key: key,
134
+ };
135
+ command = new GetObjectCommand(getParams);
136
+ break;
137
+ default:
138
+ throw new AppException("Invalid S3 action type", "E-Invalid-Action");
139
+ }
140
+ const result = await s3.send(command);
141
+ return result;
142
+ }
143
+ catch (error) {
144
+ throw new AppException(`S3 operation failed: ${error instanceof Error ? error.message : "Unknown error"}`, "E-S3-Operation-Failed");
145
+ }
146
+ }
147
+ export async function updateCustomAttribute(username, userPoolId, attributeName, attributeValue) {
148
+ try {
149
+ const command = new AdminUpdateUserAttributesCommand({
150
+ UserPoolId: userPoolId,
151
+ Username: username,
152
+ UserAttributes: [
153
+ {
154
+ Name: attributeName,
155
+ Value: attributeValue
156
+ }
157
+ ]
158
+ });
159
+ await client.send(command);
160
+ return {
161
+ success: true,
162
+ message: `Custom attribute '${attributeName}' updated successfully for user '${username}'.`
163
+ };
164
+ }
165
+ catch (error) {
166
+ return {
167
+ success: false,
168
+ message: `Error updating custom attribute: ${error.message}`
169
+ };
170
+ }
171
+ }
172
+ export async function getCustomAttribute(username, userPoolId, attributeName) {
173
+ try {
174
+ const command = new AdminGetUserCommand({
175
+ UserPoolId: userPoolId,
176
+ Username: username
177
+ });
178
+ const response = await client.send(command);
179
+ const customAttribute = response.UserAttributes?.find((attr) => attr.Name === attributeName);
180
+ const value = customAttribute?.Value;
181
+ return {
182
+ success: true,
183
+ value,
184
+ message: value ? `Value for custom attribute '${attributeName}' is '${value}'.` : `Custom attribute '${attributeName}' not found.`
185
+ };
186
+ }
187
+ catch (error) {
188
+ return {
189
+ success: false,
190
+ message: `Error getting custom attribute: ${error.message}`
191
+ };
192
+ }
193
+ }
@@ -0,0 +1,6 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ import { GHRContext } from '../../types/GHRContext.js';
3
+ export declare const context: AsyncLocalStorage<GHRContext>;
4
+ export declare function getContext(): GHRContext;
5
+ export declare function setExtras(key: string, value: any): void;
6
+ export declare function setContext(initial: GHRContext, fn: () => Promise<void> | void): void | Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ export const context = new AsyncLocalStorage();
3
+ export function getContext() {
4
+ return context.getStore() ?? {};
5
+ }
6
+ export function setExtras(key, value) {
7
+ const ctx = context.getStore();
8
+ if (ctx) {
9
+ ctx.extras = ctx.extras || {};
10
+ ctx.extras[key] = value;
11
+ }
12
+ }
13
+ export function setContext(initial, fn) {
14
+ return context.run(initial, fn);
15
+ }
@@ -0,0 +1,6 @@
1
+ export declare enum Environment {
2
+ LOCAL = 0,
3
+ DEV = 1,
4
+ UAT = 2,
5
+ PROD = 3
6
+ }
@@ -0,0 +1,7 @@
1
+ export var Environment;
2
+ (function (Environment) {
3
+ Environment[Environment["LOCAL"] = 0] = "LOCAL";
4
+ Environment[Environment["DEV"] = 1] = "DEV";
5
+ Environment[Environment["UAT"] = 2] = "UAT";
6
+ Environment[Environment["PROD"] = 3] = "PROD";
7
+ })(Environment || (Environment = {}));
@@ -0,0 +1,23 @@
1
+ export declare enum HttpStatusCode {
2
+ CONTINUE = 100,
3
+ SWITCHING_PROTOCOLS = 101,
4
+ PROCESSING = 102,
5
+ OK = 200,
6
+ CREATED = 201,
7
+ ACCEPTED = 202,
8
+ NO_CONTENT = 204,
9
+ MOVED_PERMANENTLY = 301,
10
+ FOUND = 302,
11
+ NOT_MODIFIED = 304,
12
+ BAD_REQUEST = 400,
13
+ UNAUTHORIZED = 401,
14
+ FORBIDDEN = 403,
15
+ NOT_FOUND = 404,
16
+ CONFLICT = 409,
17
+ UNPROCESSABLE_ENTITY = 422,
18
+ INTERNAL_SERVER_ERROR = 500,
19
+ NOT_IMPLEMENTED = 501,
20
+ BAD_GATEWAY = 502,
21
+ SERVICE_UNAVAILABLE = 503,
22
+ GATEWAY_TIMEOUT = 504
23
+ }
@@ -0,0 +1,29 @@
1
+ export var HttpStatusCode;
2
+ (function (HttpStatusCode) {
3
+ // 1xx Informational
4
+ HttpStatusCode[HttpStatusCode["CONTINUE"] = 100] = "CONTINUE";
5
+ HttpStatusCode[HttpStatusCode["SWITCHING_PROTOCOLS"] = 101] = "SWITCHING_PROTOCOLS";
6
+ HttpStatusCode[HttpStatusCode["PROCESSING"] = 102] = "PROCESSING";
7
+ // 2xx Success
8
+ HttpStatusCode[HttpStatusCode["OK"] = 200] = "OK";
9
+ HttpStatusCode[HttpStatusCode["CREATED"] = 201] = "CREATED";
10
+ HttpStatusCode[HttpStatusCode["ACCEPTED"] = 202] = "ACCEPTED";
11
+ HttpStatusCode[HttpStatusCode["NO_CONTENT"] = 204] = "NO_CONTENT";
12
+ // 3xx Redirection
13
+ HttpStatusCode[HttpStatusCode["MOVED_PERMANENTLY"] = 301] = "MOVED_PERMANENTLY";
14
+ HttpStatusCode[HttpStatusCode["FOUND"] = 302] = "FOUND";
15
+ HttpStatusCode[HttpStatusCode["NOT_MODIFIED"] = 304] = "NOT_MODIFIED";
16
+ // 4xx Client Errors
17
+ HttpStatusCode[HttpStatusCode["BAD_REQUEST"] = 400] = "BAD_REQUEST";
18
+ HttpStatusCode[HttpStatusCode["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
19
+ HttpStatusCode[HttpStatusCode["FORBIDDEN"] = 403] = "FORBIDDEN";
20
+ HttpStatusCode[HttpStatusCode["NOT_FOUND"] = 404] = "NOT_FOUND";
21
+ HttpStatusCode[HttpStatusCode["CONFLICT"] = 409] = "CONFLICT";
22
+ HttpStatusCode[HttpStatusCode["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
23
+ // 5xx Server Errors
24
+ HttpStatusCode[HttpStatusCode["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
25
+ HttpStatusCode[HttpStatusCode["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
26
+ HttpStatusCode[HttpStatusCode["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
27
+ HttpStatusCode[HttpStatusCode["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
28
+ HttpStatusCode[HttpStatusCode["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
29
+ })(HttpStatusCode || (HttpStatusCode = {}));
@@ -0,0 +1,13 @@
1
+ export declare class AppException extends Error {
2
+ errorCode?: string | undefined;
3
+ details?: any;
4
+ constructor(message: string, errorCode?: string | undefined, details?: any);
5
+ }
6
+ export declare class InvalidEnvironmentException extends Error {
7
+ constructor();
8
+ }
9
+ export declare class InvalidRequestException extends Error {
10
+ errorCode?: string | undefined;
11
+ details?: any;
12
+ constructor(message: string[] | string, errorCode?: string | undefined, details?: any);
13
+ }
@@ -0,0 +1,27 @@
1
+ export class AppException extends Error {
2
+ errorCode;
3
+ details;
4
+ constructor(message, errorCode, details) {
5
+ super(message);
6
+ this.errorCode = errorCode;
7
+ this.details = details;
8
+ this.name = 'RequestInvalid';
9
+ }
10
+ }
11
+ // test
12
+ export class InvalidEnvironmentException extends Error {
13
+ constructor() {
14
+ super();
15
+ this.name = `Invalid Environment ${process.env.NODE_ENV}`;
16
+ }
17
+ }
18
+ export class InvalidRequestException extends Error {
19
+ errorCode;
20
+ details;
21
+ constructor(message, errorCode, details) {
22
+ super(Array.isArray(message) ? message.join(", ") : message);
23
+ this.errorCode = errorCode;
24
+ this.details = details;
25
+ this.name = 'Bad Request';
26
+ }
27
+ }
@@ -0,0 +1,3 @@
1
+ export declare class ImageCompressor {
2
+ static compressImage(buffer: Buffer, width?: number, height?: number, quality?: number): Promise<Buffer>;
3
+ }
@@ -0,0 +1,16 @@
1
+ import sharp from "sharp";
2
+ import { AppException } from '../exceptions/index.js';
3
+ export class ImageCompressor {
4
+ static async compressImage(buffer, width = 64, height = 96, quality = 70) {
5
+ try {
6
+ return await sharp(buffer)
7
+ .resize(width, height)
8
+ .jpeg({ quality })
9
+ .toBuffer();
10
+ }
11
+ catch (error) {
12
+ console.error("Sharp compression failed:", error);
13
+ throw new AppException("Image compression failed", "E-COMPRESSION-FAILED");
14
+ }
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ export declare const services: Record<string, Record<string, string>>;
@@ -0,0 +1,31 @@
1
+ export const services = {
2
+ 'local': {
3
+ 'bff-ms': 'http://localhost:3000',
4
+ 'people-ms': 'http://localhost:3001',
5
+ 'iam-ms': 'http://localhost:3002',
6
+ 'mdm-ms': 'http://localhost:3003',
7
+ 'orgs-ms': 'http://localhost:3004',
8
+ 'shifts-ms': 'http://localhost:3005',
9
+ 'document-ms': 'http://localhost:3006',
10
+ 'claim-ms': 'http://localhost:3007',
11
+ 'external-ms': 'http://localhost:3008',
12
+ 'cache-ms': 'http://localhost:3009',
13
+ 'migration-ms': 'http://localhost:3010',
14
+ 'search-ms': 'http://localhost:3011'
15
+ },
16
+ 'dev': {
17
+ 'bff-ms': 'http://bff-ms-dev:3000',
18
+ 'people-ms': 'http://people-ms-dev:3001',
19
+ 'iam-ms': 'http://iam-ms-dev:3002',
20
+ 'mdm-ms': 'http://mdm-ms-dev:3003',
21
+ 'orgs-ms': 'http://orgs-ms-dev:3004',
22
+ 'shifts-ms': 'http://shifts-ms-dev:3005',
23
+ 'document-ms': 'http://document-ms-dev:3006',
24
+ 'claim-ms': 'http://claim-ms-dev:3007',
25
+ 'external-ms': 'http://external-ms-dev:3008',
26
+ 'cache-ms': 'http://cache-ms-dev:3009',
27
+ 'migration-ms': 'http://migration-ms-dev:3010',
28
+ 'search-ms': 'http://search-ms-dev:3011'
29
+ },
30
+ 'uat': {}
31
+ };
@@ -0,0 +1,10 @@
1
+ import type { MQClient } from '../mq/client.js';
2
+ import Redis from 'ioredis';
3
+ export type GHRContext<TPrismaClient = any> = {
4
+ service?: string;
5
+ correlationId?: string;
6
+ mq?: MQClient;
7
+ prisma?: TPrismaClient;
8
+ redis?: Redis;
9
+ extras?: Record<string, any>;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
2
+ export interface BaseLogPayload {
3
+ message: string | Record<string, any>;
4
+ correlationId: string;
5
+ userId?: string;
6
+ [key: string]: any;
7
+ }
8
+ export interface ErrorLogPayload extends BaseLogPayload {
9
+ errorCode: string;
10
+ errorStack?: string;
11
+ }
12
+ export type LogPayloadByLevel = {
13
+ debug?: BaseLogPayload;
14
+ info?: BaseLogPayload;
15
+ warn?: BaseLogPayload;
16
+ error?: ErrorLogPayload;
17
+ };
18
+ export type ErrorInput = {
19
+ ex: unknown;
20
+ correlationId: string;
21
+ service?: string;
22
+ [key: string]: any;
23
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@geniehr/utilities",
3
+ "version": "1.0.5",
4
+ "description": "",
5
+ "homepage": "https://github.com/Genie-HR/ghr-utilities#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/Genie-HR/ghr-utilities/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Genie-HR/ghr-utilities.git"
12
+ },
13
+ "license": "ISC",
14
+ "author": "",
15
+ "type": "module",
16
+ "exports": {
17
+ ".": "./dist/index.js"
18
+ },
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "watch": "tsc -p tsconfig.json --watch",
27
+ "prepare": "test $HUSKY_SKIP != 'true' && husky install || echo 'Skipping husky install'"
28
+ },
29
+ "dependencies": {
30
+ "@aws-sdk/client-cognito-identity-provider": "^3.859.0",
31
+ "@aws-sdk/client-rekognition": "^3.846.0",
32
+ "@aws-sdk/client-s3": "^3.848.0",
33
+ "@aws-sdk/client-ssm": "^3.839.0",
34
+ "@aws-sdk/credential-providers": "^3.839.0",
35
+ "@aws-sdk/s3-request-presigner": "^3.848.0",
36
+ "@prisma/client": "^6.11.1",
37
+ "amqplib": "^0.10.8",
38
+ "axios": "^1.10.0",
39
+ "express": "^5.1.0",
40
+ "ioredis": "^5.8.2",
41
+ "sharp": "^0.34.4",
42
+ "winston": "^3.17.0",
43
+ "winston-cloudwatch": "^6.3.0",
44
+ "zod": "^3.25.74",
45
+ "zod-error": "^1.5.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/amqplib": "^0.10.7",
49
+ "@types/express": "^5.0.3",
50
+ "@types/node": "^24.0.12",
51
+ "husky": "^9.1.7",
52
+ "tsx": "^4.20.3",
53
+ "typescript": "^5.4.0"
54
+ }
55
+ }