@uber-clone/common 1.0.5 → 1.0.7

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/build/index.d.ts CHANGED
@@ -11,3 +11,6 @@ export * from "./middlewares/validate-request";
11
11
  export * from "./events/kafka-client";
12
12
  export * from "./events/types";
13
13
  export * from "./events/subjects";
14
+ export * from "./metrics/app.metrics";
15
+ export * from "./utils/logger";
16
+ export * from "./redis/redis-client";
package/build/index.js CHANGED
@@ -24,8 +24,9 @@ __exportStar(require("./middlewares/current-user"), exports);
24
24
  __exportStar(require("./middlewares/error-handler"), exports);
25
25
  __exportStar(require("./middlewares/require-auth"), exports);
26
26
  __exportStar(require("./middlewares/validate-request"), exports);
27
- // export * from "./events/kafka-listener";
28
- // export * from "./events/kafka-publisher";
29
27
  __exportStar(require("./events/kafka-client"), exports);
30
28
  __exportStar(require("./events/types"), exports);
31
29
  __exportStar(require("./events/subjects"), exports);
30
+ __exportStar(require("./metrics/app.metrics"), exports);
31
+ __exportStar(require("./utils/logger"), exports);
32
+ __exportStar(require("./redis/redis-client"), exports);
@@ -0,0 +1,5 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ export declare const metricsMiddleware: (req: Request, res: Response, next: NextFunction) => void;
3
+ export declare const metricsEndpoint: (req: Request, res: Response) => Promise<void>;
4
+ export declare const trackDatabaseQuery: <T>(operation: string, collection: string, query: () => Promise<T>) => Promise<T>;
5
+ export declare const trackRedisOperation: <T>(operation: string, redisOp: () => Promise<T>) => Promise<T>;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.trackRedisOperation = exports.trackDatabaseQuery = exports.metricsEndpoint = exports.metricsMiddleware = void 0;
16
+ const prom_client_1 = __importDefault(require("prom-client"));
17
+ // Create a Registry
18
+ const register = new prom_client_1.default.Registry();
19
+ // Add default metrics (CPU, memory, etc.)
20
+ prom_client_1.default.collectDefaultMetrics({ register });
21
+ // Custom Metrics
22
+ const httpRequestDuration = new prom_client_1.default.Histogram({
23
+ name: "http_request_duration_seconds",
24
+ help: "Duration of HTTP requests in seconds",
25
+ labelNames: ["method", "route", "status_code"],
26
+ buckets: [0.1, 0.5, 1, 2, 5],
27
+ registers: [register],
28
+ });
29
+ const httpRequestTotal = new prom_client_1.default.Counter({
30
+ name: "http_requests_total",
31
+ help: "Total number of HTTP requests",
32
+ labelNames: ["method", "route", "status_code"],
33
+ registers: [register],
34
+ });
35
+ const activeConnections = new prom_client_1.default.Gauge({
36
+ name: "active_connections",
37
+ help: "Number of active connections",
38
+ registers: [register],
39
+ });
40
+ const databaseQueryDuration = new prom_client_1.default.Histogram({
41
+ name: "database_query_duration_seconds",
42
+ help: "Duration of database queries in seconds",
43
+ labelNames: ["operation", "collection"],
44
+ buckets: [0.01, 0.05, 0.1, 0.5, 1],
45
+ registers: [register],
46
+ });
47
+ const redisOperationDuration = new prom_client_1.default.Histogram({
48
+ name: "redis_operation_duration_seconds",
49
+ help: "Duration of Redis operations in seconds",
50
+ labelNames: ["operation", "status"],
51
+ buckets: [0.001, 0.005, 0.01, 0.05, 0.1],
52
+ registers: [register],
53
+ });
54
+ // Middleware to track HTTP metrics
55
+ const metricsMiddleware = (req, res, next) => {
56
+ const start = Date.now();
57
+ activeConnections.inc();
58
+ res.on("finish", () => {
59
+ var _a;
60
+ const duration = (Date.now() - start) / 1000;
61
+ const route = ((_a = req.route) === null || _a === void 0 ? void 0 : _a.path) || req.path;
62
+ httpRequestDuration.observe({
63
+ method: req.method,
64
+ route: route,
65
+ status_code: res.statusCode,
66
+ }, duration);
67
+ httpRequestTotal.inc({
68
+ method: req.method,
69
+ route: route,
70
+ status_code: res.statusCode,
71
+ });
72
+ activeConnections.dec();
73
+ });
74
+ next();
75
+ };
76
+ exports.metricsMiddleware = metricsMiddleware;
77
+ // Metrics endpoint
78
+ const metricsEndpoint = (req, res) => __awaiter(void 0, void 0, void 0, function* () {
79
+ res.set("Content-Type", register.contentType);
80
+ const metrics = yield register.metrics();
81
+ res.end(metrics);
82
+ });
83
+ exports.metricsEndpoint = metricsEndpoint;
84
+ // Helper functions to track operations
85
+ const trackDatabaseQuery = (operation, collection, query) => __awaiter(void 0, void 0, void 0, function* () {
86
+ const end = databaseQueryDuration.startTimer({ operation, collection });
87
+ try {
88
+ const result = yield query();
89
+ end();
90
+ return result;
91
+ }
92
+ catch (error) {
93
+ end();
94
+ throw error;
95
+ }
96
+ });
97
+ exports.trackDatabaseQuery = trackDatabaseQuery;
98
+ const trackRedisOperation = (operation, redisOp) => __awaiter(void 0, void 0, void 0, function* () {
99
+ const end = redisOperationDuration.startTimer({
100
+ operation,
101
+ status: "success",
102
+ });
103
+ try {
104
+ const result = yield redisOp();
105
+ end();
106
+ return result;
107
+ }
108
+ catch (error) {
109
+ redisOperationDuration.labels(operation, "error").observe(0);
110
+ throw error;
111
+ }
112
+ });
113
+ exports.trackRedisOperation = trackRedisOperation;
@@ -0,0 +1,46 @@
1
+ export interface GeoLocation {
2
+ type?: "Point";
3
+ coordinates: [number, number];
4
+ address?: string;
5
+ city?: string;
6
+ state?: string;
7
+ country?: string;
8
+ postalCode?: string;
9
+ }
10
+ export interface RedisConfig {
11
+ url?: string;
12
+ host?: string;
13
+ port?: number;
14
+ password?: string;
15
+ db?: number;
16
+ cluster?: boolean;
17
+ connectTimeout?: number;
18
+ lazyConnect?: boolean;
19
+ retryDelay?: number;
20
+ maxRetries?: number;
21
+ }
22
+ export declare class RedisClient {
23
+ private config;
24
+ private redis;
25
+ private redlock;
26
+ private logger;
27
+ private isConnected;
28
+ constructor(config?: RedisConfig);
29
+ private initializeRedlock;
30
+ private setupEventListeners;
31
+ connect(): Promise<void>;
32
+ disconnect(): Promise<void>;
33
+ private ensureConnected;
34
+ healthCheck(): Promise<boolean>;
35
+ incrementCounter(key: string, incrementBy?: number): Promise<number>;
36
+ getCounterValue(key: string): Promise<number>;
37
+ acquireLock(lockKey: string, ttl?: number): Promise<any | string | null>;
38
+ releaseLock(lockOrKey: any | string | null): Promise<boolean>;
39
+ setCache(key: string, value: any, ttl?: number): Promise<void>;
40
+ getCache<T>(key: string): Promise<T | null>;
41
+ deleteCache(key: string): Promise<void>;
42
+ publish(channel: string, message: any): Promise<void>;
43
+ subscribe(channel: string, handler: (message: any) => void): Promise<void>;
44
+ flushAll(): Promise<void>;
45
+ getRedisInfo(): Promise<Record<string, string>>;
46
+ }
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.RedisClient = void 0;
16
+ // libs/common/src/redis/redis-client.ts
17
+ const redis_1 = require("redis");
18
+ const redlock_1 = __importDefault(require("redlock"));
19
+ const logger_1 = require("../utils/logger");
20
+ class RedisClient {
21
+ constructor(config = {}) {
22
+ this.config = config;
23
+ this.redlock = null;
24
+ this.isConnected = false;
25
+ this.logger = new logger_1.Logger("RedisClient");
26
+ this.redis = (0, redis_1.createClient)({
27
+ url: process.env.REDIS_URL || "redis://redis-cluster:6379",
28
+ socket: {
29
+ reconnectStrategy: (retries) => {
30
+ this.logger.debug(`Redis reconnection attempt ${retries}`);
31
+ return Math.min(retries * 1000, 10000);
32
+ },
33
+ connectTimeout: 10000,
34
+ },
35
+ // password: config.password,
36
+ // database: config.db || 0,
37
+ });
38
+ this.initializeRedlock();
39
+ this.setupEventListeners();
40
+ this.connect().catch((err) => this.logger.error("Auto-connect failed:", err));
41
+ }
42
+ initializeRedlock() {
43
+ try {
44
+ this.redlock = new redlock_1.default([this.redis], {
45
+ driftFactor: 0.01,
46
+ retryCount: 3,
47
+ retryDelay: 200,
48
+ retryJitter: 100,
49
+ });
50
+ this.redlock.on("error", (error) => {
51
+ this.logger.error("Redlock error:", error);
52
+ });
53
+ }
54
+ catch (err) {
55
+ this.logger.warn("Redlock initialization failed:", err);
56
+ this.redlock = null;
57
+ }
58
+ }
59
+ setupEventListeners() {
60
+ this.redis.on("connect", () => {
61
+ this.isConnected = true;
62
+ this.logger.info("✅ Connected to Redis");
63
+ });
64
+ this.redis.on("error", (error) => {
65
+ this.logger.error("❌ Redis error:", error);
66
+ this.isConnected = false;
67
+ });
68
+ this.redis.on("end", () => {
69
+ this.logger.warn("⚠️ Redis connection closed");
70
+ this.isConnected = false;
71
+ });
72
+ this.redis.on("ready", () => {
73
+ this.logger.info("Redis client ready");
74
+ this.isConnected = true;
75
+ });
76
+ }
77
+ // Core Connection Methods
78
+ connect() {
79
+ return __awaiter(this, void 0, void 0, function* () {
80
+ if (this.isConnected)
81
+ return;
82
+ try {
83
+ yield this.redis.connect();
84
+ this.isConnected = true;
85
+ }
86
+ catch (error) {
87
+ this.logger.error("Failed to connect to Redis:", error);
88
+ throw error;
89
+ }
90
+ });
91
+ }
92
+ disconnect() {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ try {
95
+ yield this.redis.quit();
96
+ this.isConnected = false;
97
+ this.logger.info("Redis connection closed");
98
+ }
99
+ catch (error) {
100
+ this.logger.error("Error disconnecting from Redis:", error);
101
+ throw error;
102
+ }
103
+ });
104
+ }
105
+ ensureConnected() {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ if (!this.isConnected) {
108
+ yield this.connect();
109
+ }
110
+ });
111
+ }
112
+ // Health Check
113
+ healthCheck() {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ try {
116
+ yield this.redis.ping();
117
+ return true;
118
+ }
119
+ catch (error) {
120
+ this.logger.error("Redis health check failed:", error);
121
+ return false;
122
+ }
123
+ });
124
+ }
125
+ incrementCounter(key_1) {
126
+ return __awaiter(this, arguments, void 0, function* (key, incrementBy = 1) {
127
+ return yield this.redis.incrBy(key, incrementBy);
128
+ });
129
+ }
130
+ getCounterValue(key) {
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ const val = yield this.redis.get(key);
133
+ return val ? parseInt(val) : 0;
134
+ });
135
+ }
136
+ // ========== LOCKING METHODS ==========
137
+ acquireLock(lockKey_1) {
138
+ return __awaiter(this, arguments, void 0, function* (lockKey, ttl = 5000) {
139
+ yield this.ensureConnected();
140
+ if (this.redlock) {
141
+ try {
142
+ const lock = yield this.redlock.acquire([lockKey], ttl);
143
+ return lock;
144
+ }
145
+ catch (error) {
146
+ return null;
147
+ }
148
+ }
149
+ // Fallback: SET NX
150
+ try {
151
+ const result = yield this.redis.set(lockKey, "locked", {
152
+ NX: true,
153
+ PX: ttl,
154
+ });
155
+ return result === "OK" ? lockKey : null;
156
+ }
157
+ catch (error) {
158
+ this.logger.error(`Error acquiring lock ${lockKey}:`, error);
159
+ return null;
160
+ }
161
+ });
162
+ }
163
+ releaseLock(lockOrKey) {
164
+ return __awaiter(this, void 0, void 0, function* () {
165
+ var _a;
166
+ if (!lockOrKey)
167
+ return true;
168
+ // Redlock instance
169
+ if (typeof lockOrKey === "object" &&
170
+ typeof lockOrKey.release === "function") {
171
+ try {
172
+ (_a = this.redlock) === null || _a === void 0 ? void 0 : _a.release(lockOrKey.resources[0]);
173
+ return true;
174
+ }
175
+ catch (error) {
176
+ this.logger.warn("Error releasing Redlock:", error);
177
+ return false;
178
+ }
179
+ }
180
+ // Fallback: key string
181
+ if (typeof lockOrKey === "string") {
182
+ try {
183
+ yield this.redis.del(lockOrKey);
184
+ return true;
185
+ }
186
+ catch (error) {
187
+ this.logger.warn(`Error releasing lock ${lockOrKey}:`, error);
188
+ return false;
189
+ }
190
+ }
191
+ return false;
192
+ });
193
+ }
194
+ // ========== CACHING METHODS ==========
195
+ setCache(key, value, ttl) {
196
+ return __awaiter(this, void 0, void 0, function* () {
197
+ try {
198
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
199
+ if (ttl) {
200
+ yield this.redis.setEx(key, ttl, serialized);
201
+ }
202
+ else {
203
+ yield this.redis.set(key, serialized);
204
+ }
205
+ }
206
+ catch (error) {
207
+ this.logger.error(`Error setting cache key ${key}:`, error);
208
+ throw error;
209
+ }
210
+ });
211
+ }
212
+ getCache(key) {
213
+ return __awaiter(this, void 0, void 0, function* () {
214
+ try {
215
+ const value = yield this.redis.get(key);
216
+ if (!value)
217
+ return null;
218
+ try {
219
+ return JSON.parse(value);
220
+ }
221
+ catch (_a) {
222
+ return value;
223
+ }
224
+ }
225
+ catch (error) {
226
+ this.logger.error(`Error getting cache key ${key}:`, error);
227
+ return null;
228
+ }
229
+ });
230
+ }
231
+ deleteCache(key) {
232
+ return __awaiter(this, void 0, void 0, function* () {
233
+ try {
234
+ yield this.redis.del(key);
235
+ }
236
+ catch (error) {
237
+ this.logger.error(`Error deleting cache key ${key}:`, error);
238
+ throw error;
239
+ }
240
+ });
241
+ }
242
+ // ========== PUB/SUB METHODS ==========
243
+ publish(channel, message) {
244
+ return __awaiter(this, void 0, void 0, function* () {
245
+ try {
246
+ yield this.redis.publish(channel, JSON.stringify(message));
247
+ }
248
+ catch (error) {
249
+ this.logger.error(`Error publishing to channel ${channel}:`, error);
250
+ throw error;
251
+ }
252
+ });
253
+ }
254
+ subscribe(channel, handler) {
255
+ return __awaiter(this, void 0, void 0, function* () {
256
+ const subscriber = this.redis.duplicate();
257
+ yield subscriber.connect();
258
+ yield subscriber.subscribe(channel, (message) => {
259
+ try {
260
+ handler(JSON.parse(message));
261
+ }
262
+ catch (error) {
263
+ this.logger.error(`Error handling message on ${channel}:`, error);
264
+ }
265
+ });
266
+ subscriber.on("error", (error) => {
267
+ this.logger.error(`Subscriber error for ${channel}:`, error);
268
+ });
269
+ });
270
+ }
271
+ // ========== UTILITY METHODS ==========
272
+ flushAll() {
273
+ return __awaiter(this, void 0, void 0, function* () {
274
+ try {
275
+ yield this.redis.flushAll();
276
+ this.logger.warn("All Redis data cleared!");
277
+ }
278
+ catch (error) {
279
+ this.logger.error("Error flushing Redis:", error);
280
+ throw error;
281
+ }
282
+ });
283
+ }
284
+ getRedisInfo() {
285
+ return __awaiter(this, void 0, void 0, function* () {
286
+ try {
287
+ const info = yield this.redis.info();
288
+ const lines = info.split("\n");
289
+ const result = {};
290
+ for (const line of lines) {
291
+ if (line.includes(":")) {
292
+ const [key, value] = line.split(":");
293
+ result[key.trim()] = value.trim();
294
+ }
295
+ }
296
+ return result;
297
+ }
298
+ catch (error) {
299
+ this.logger.error("Error getting Redis info:", error);
300
+ throw error;
301
+ }
302
+ });
303
+ }
304
+ }
305
+ exports.RedisClient = RedisClient;
@@ -0,0 +1,8 @@
1
+ export declare class Logger {
2
+ private logger;
3
+ constructor(service: string);
4
+ info(message: string, meta?: any): void;
5
+ error(message: string, error?: any): void;
6
+ warn(message: string, meta?: any): void;
7
+ debug(message: string, meta?: any): void;
8
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Logger = void 0;
7
+ // libs/common/src/utils/logger.ts
8
+ const winston_1 = __importDefault(require("winston"));
9
+ class Logger {
10
+ constructor(service) {
11
+ this.logger = winston_1.default.createLogger({
12
+ level: process.env.LOG_LEVEL || "info",
13
+ format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.errors({ stack: true }), winston_1.default.format.json()),
14
+ defaultMeta: { service },
15
+ transports: [
16
+ new winston_1.default.transports.Console({
17
+ format: winston_1.default.format.combine(winston_1.default.format.colorize(), winston_1.default.format.simple()),
18
+ }),
19
+ new winston_1.default.transports.File({
20
+ filename: "logs/error.log",
21
+ level: "error",
22
+ }),
23
+ new winston_1.default.transports.File({
24
+ filename: "logs/combined.log",
25
+ }),
26
+ ],
27
+ });
28
+ }
29
+ info(message, meta) {
30
+ this.logger.info(message, meta);
31
+ }
32
+ error(message, error) {
33
+ this.logger.error(message, { error });
34
+ }
35
+ warn(message, meta) {
36
+ this.logger.warn(message, meta);
37
+ }
38
+ debug(message, meta) {
39
+ this.logger.debug(message, meta);
40
+ }
41
+ }
42
+ exports.Logger = Logger;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uber-clone/common",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "clean": "del-cli ./build/*",
11
11
  "build": "npm run clean && tsc",
12
- "pub": "git add . && git commit -m \"New Updated\" && npm version patch && npm run build && npm publish"
12
+ "pub": "cd common && git add . && git commit -m \"New Updated\" && npm version patch && npm run build && npm publish"
13
13
  },
14
14
  "keywords": [],
15
15
  "author": "",
@@ -28,6 +28,9 @@
28
28
  "express-validator": "^7.2.1",
29
29
  "jsonwebtoken": "^9.0.2",
30
30
  "kafkajs": "^2.2.4",
31
- "node-nats-streaming": "^0.3.2"
31
+ "prom-client": "^15.1.3",
32
+ "redis": "^5.9.0",
33
+ "redlock": "^5.0.0-beta.2",
34
+ "winston": "^3.18.3"
32
35
  }
33
36
  }