frontend-hamroun 1.1.90 → 1.2.0

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 (92) hide show
  1. package/dist/{src/backend → backend}/api-utils.d.ts +2 -2
  2. package/dist/backend/api-utils.js +135 -0
  3. package/dist/backend/auth.js +387 -0
  4. package/dist/{src/backend → backend}/database.d.ts +1 -1
  5. package/dist/backend/database.js +91 -0
  6. package/dist/{src/backend → backend}/model.d.ts +2 -2
  7. package/dist/backend/model.js +176 -0
  8. package/dist/{src/backend → backend}/router.d.ts +1 -1
  9. package/dist/backend/router.js +137 -0
  10. package/dist/backend/server.js +268 -0
  11. package/dist/batch.js +22 -0
  12. package/dist/cli/index.js +1 -0
  13. package/dist/{src/component.d.ts → component.d.ts} +1 -1
  14. package/dist/component.js +84 -0
  15. package/dist/components/Counter.js +2 -0
  16. package/dist/context.js +20 -0
  17. package/dist/frontend-hamroun.es.js +1680 -0
  18. package/dist/frontend-hamroun.es.js.map +1 -0
  19. package/dist/frontend-hamroun.umd.js +2 -0
  20. package/dist/frontend-hamroun.umd.js.map +1 -0
  21. package/dist/hooks.js +164 -0
  22. package/dist/index.d.ts +46 -0
  23. package/dist/index.js +52 -355
  24. package/dist/jsx-runtime/index.d.ts +9 -0
  25. package/dist/jsx-runtime/index.js +16 -0
  26. package/dist/jsx-runtime/jsx-dev-runtime.js +1 -0
  27. package/dist/jsx-runtime/jsx-runtime.js +91 -0
  28. package/dist/{src/jsx-runtime.d.ts → jsx-runtime.d.ts} +1 -1
  29. package/dist/jsx-runtime.js +192 -0
  30. package/dist/renderer.js +51 -0
  31. package/dist/{src/server-renderer.d.ts → server-renderer.d.ts} +3 -0
  32. package/dist/server-renderer.js +102 -0
  33. package/dist/vdom.js +27 -0
  34. package/package.json +38 -52
  35. package/scripts/generate.js +134 -0
  36. package/src/backend/api-utils.ts +178 -0
  37. package/src/backend/auth.ts +543 -0
  38. package/src/backend/database.ts +104 -0
  39. package/src/backend/model.ts +196 -0
  40. package/src/backend/router.ts +176 -0
  41. package/src/backend/server.ts +330 -0
  42. package/src/backend/types.ts +257 -0
  43. package/src/batch.ts +24 -0
  44. package/src/cli/index.js +22 -40
  45. package/src/component.ts +98 -0
  46. package/src/components/Counter.tsx +4 -0
  47. package/src/context.ts +32 -0
  48. package/src/hooks.ts +211 -0
  49. package/src/index.ts +113 -0
  50. package/src/jsx-runtime/index.ts +24 -0
  51. package/src/jsx-runtime/jsx-dev-runtime.ts +0 -0
  52. package/src/jsx-runtime/jsx-runtime.ts +99 -0
  53. package/src/jsx-runtime.ts +226 -0
  54. package/src/renderer.ts +55 -0
  55. package/src/server-renderer.ts +114 -0
  56. package/src/types/bcrypt.d.ts +30 -0
  57. package/src/types/jsonwebtoken.d.ts +55 -0
  58. package/src/types.d.ts +26 -0
  59. package/src/types.ts +21 -0
  60. package/src/vdom.ts +34 -0
  61. package/templates/basic-app/package.json +17 -15
  62. package/templates/basic-app/postcss.config.js +1 -0
  63. package/templates/basic-app/src/App.tsx +65 -0
  64. package/templates/basic-app/src/api.ts +58 -0
  65. package/templates/basic-app/src/components/Counter.tsx +26 -0
  66. package/templates/basic-app/src/components/Header.tsx +9 -0
  67. package/templates/basic-app/src/components/TodoList.tsx +90 -0
  68. package/templates/basic-app/src/main.ts +20 -0
  69. package/templates/basic-app/src/server.ts +99 -0
  70. package/templates/basic-app/tailwind.config.js +23 -2
  71. package/bin/cli.js +0 -371
  72. package/dist/index.js.map +0 -1
  73. package/dist/index.mjs +0 -139269
  74. package/dist/index.mjs.map +0 -1
  75. package/dist/src/index.d.ts +0 -16
  76. package/dist/test/setupTests.d.ts +0 -4
  77. /package/dist/{src/backend → backend}/auth.d.ts +0 -0
  78. /package/dist/{src/backend → backend}/server.d.ts +0 -0
  79. /package/dist/{src/backend → backend}/types.d.ts +0 -0
  80. /package/dist/{test/backend.test.d.ts → backend/types.js} +0 -0
  81. /package/dist/{src/batch.d.ts → batch.d.ts} +0 -0
  82. /package/dist/{src/cli → cli}/index.d.ts +0 -0
  83. /package/dist/{src/components → components}/Counter.d.ts +0 -0
  84. /package/dist/{src/context.d.ts → context.d.ts} +0 -0
  85. /package/dist/{src/hooks.d.ts → hooks.d.ts} +0 -0
  86. /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-dev-runtime.d.ts +0 -0
  87. /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-runtime.d.ts +0 -0
  88. /package/dist/{src/renderer.d.ts → renderer.d.ts} +0 -0
  89. /package/dist/{src/types.d.ts → types.d.ts} +0 -0
  90. /package/dist/{test/mockTest.d.ts → types.js} +0 -0
  91. /package/dist/{src/vdom.d.ts → vdom.d.ts} +0 -0
  92. /package/{dist/test/mongooseSetup.d.ts → src/cli/index.ts} +0 -0
@@ -0,0 +1,543 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import jwt from 'jsonwebtoken';
3
+ import bcrypt from 'bcrypt';
4
+ import crypto from 'crypto';
5
+
6
+ /**
7
+ * Authentication configuration options
8
+ */
9
+ export interface AuthOptions {
10
+ /**
11
+ * Secret key for JWT signing
12
+ */
13
+ jwtSecret: string;
14
+
15
+ /**
16
+ * Secret key for refresh token signing (defaults to jwtSecret if not provided)
17
+ */
18
+ refreshSecret?: string;
19
+
20
+ /**
21
+ * JWT token expiration time (default: '15m')
22
+ */
23
+ tokenExpiration?: string;
24
+
25
+ /**
26
+ * Refresh token expiration time (default: '7d')
27
+ */
28
+ refreshExpiration?: string;
29
+
30
+ /**
31
+ * Number of bcrypt salt rounds (default: 10)
32
+ */
33
+ saltRounds?: number;
34
+
35
+ /**
36
+ * Custom user finder function
37
+ */
38
+ findUser?: (username: string) => Promise<any>;
39
+
40
+ /**
41
+ * Custom password verification (defaults to bcrypt compare)
42
+ */
43
+ verifyPassword?: (password: string, hashedPassword: string) => Promise<boolean>;
44
+
45
+ /**
46
+ * Custom function to save refresh token
47
+ */
48
+ saveRefreshToken?: (userId: string, token: string, expires: Date) => Promise<void>;
49
+
50
+ /**
51
+ * Custom function to verify refresh token
52
+ */
53
+ verifyRefreshToken?: (userId: string, token: string) => Promise<boolean>;
54
+
55
+ /**
56
+ * Use secure cookies for tokens (default: process.env.NODE_ENV === 'production')
57
+ */
58
+ secureCookies?: boolean;
59
+
60
+ /**
61
+ * Cookie domain
62
+ */
63
+ cookieDomain?: string;
64
+
65
+ /**
66
+ * Use HTTP-only cookies (default: true)
67
+ */
68
+ httpOnlyCookies?: boolean;
69
+
70
+ /**
71
+ * Implement rate limiting for login attempts (default: true)
72
+ */
73
+ rateLimit?: boolean;
74
+ }
75
+
76
+ /**
77
+ * Token pair containing access and refresh tokens
78
+ */
79
+ export interface TokenPair {
80
+ accessToken: string;
81
+ refreshToken: string;
82
+ expiresIn: number;
83
+ }
84
+
85
+ /**
86
+ * Authentication service
87
+ */
88
+ export class Auth {
89
+ private options: AuthOptions;
90
+ private loginAttempts: Map<string, { count: number, resetTime: number }> = new Map();
91
+
92
+ constructor(options: AuthOptions) {
93
+ this.options = {
94
+ tokenExpiration: '15m',
95
+ refreshExpiration: '7d',
96
+ saltRounds: 10,
97
+ secureCookies: process.env.NODE_ENV === 'production',
98
+ httpOnlyCookies: true,
99
+ rateLimit: true,
100
+ ...options,
101
+ refreshSecret: options.refreshSecret || options.jwtSecret
102
+ };
103
+
104
+ if (!options.jwtSecret) {
105
+ throw new Error('JWT secret is required for authentication');
106
+ }
107
+
108
+ // Set default password verification if not provided
109
+ if (!this.options.verifyPassword) {
110
+ this.options.verifyPassword = this.verifyPasswordWithBcrypt;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Hash a password using bcrypt
116
+ */
117
+ async hashPassword(password: string): Promise<string> {
118
+ return await bcrypt.hash(password, this.options.saltRounds || 10);
119
+ }
120
+
121
+ /**
122
+ * Verify a password against a hash using bcrypt
123
+ */
124
+ private async verifyPasswordWithBcrypt(password: string, hashedPassword: string): Promise<boolean> {
125
+ return await bcrypt.compare(password, hashedPassword);
126
+ }
127
+
128
+ /**
129
+ * Generate a cryptographically secure random token
130
+ */
131
+ generateSecureToken(length = 32): string {
132
+ return crypto.randomBytes(length).toString('hex');
133
+ }
134
+
135
+ /**
136
+ * Generate token pair (access token + refresh token)
137
+ */
138
+ generateTokenPair(payload: any): TokenPair {
139
+ // Calculate expiration times
140
+ const expiresIn = this.getExpirationSeconds(this.options.tokenExpiration || '15m');
141
+
142
+ // Generate the access token
143
+ const accessToken = jwt.sign(
144
+ { ...payload, type: 'access' },
145
+ this.options.jwtSecret,
146
+ { expiresIn: this.options.tokenExpiration }
147
+ );
148
+
149
+ // Generate the refresh token
150
+ const refreshToken = jwt.sign(
151
+ { id: payload.id, type: 'refresh' },
152
+ this.options.refreshSecret!,
153
+ { expiresIn: this.options.refreshExpiration }
154
+ );
155
+
156
+ return {
157
+ accessToken,
158
+ refreshToken,
159
+ expiresIn
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Convert JWT expiration time to seconds
165
+ */
166
+ private getExpirationSeconds(expiration: string): number {
167
+ const unit = expiration.charAt(expiration.length - 1);
168
+ const value = parseInt(expiration.slice(0, -1));
169
+
170
+ switch (unit) {
171
+ case 's': return value;
172
+ case 'm': return value * 60;
173
+ case 'h': return value * 60 * 60;
174
+ case 'd': return value * 60 * 60 * 24;
175
+ default: return 3600; // Default 1 hour
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Verify a JWT token
181
+ */
182
+ verifyToken(token: string, type: 'access' | 'refresh' = 'access'): any {
183
+ try {
184
+ const secret = type === 'access' ? this.options.jwtSecret : this.options.refreshSecret;
185
+ const decoded = jwt.verify(token, secret!);
186
+
187
+ // Verify token type matches expected type
188
+ if (typeof decoded === 'object' && decoded.type !== type) {
189
+ throw new Error('Invalid token type');
190
+ }
191
+
192
+ return decoded;
193
+ } catch (error) {
194
+ throw new Error('Invalid or expired token');
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Set authentication cookies
200
+ */
201
+ setAuthCookies(res: Response, tokens: TokenPair): void {
202
+ // Set access token cookie
203
+ res.cookie('accessToken', tokens.accessToken, {
204
+ httpOnly: this.options.httpOnlyCookies,
205
+ secure: this.options.secureCookies,
206
+ domain: this.options.cookieDomain,
207
+ sameSite: 'strict',
208
+ maxAge: tokens.expiresIn * 1000
209
+ });
210
+
211
+ // Set refresh token cookie with longer expiration
212
+ const refreshExpiresIn = this.getExpirationSeconds(this.options.refreshExpiration || '7d');
213
+ res.cookie('refreshToken', tokens.refreshToken, {
214
+ httpOnly: true, // Always HTTP only for refresh tokens
215
+ secure: this.options.secureCookies,
216
+ domain: this.options.cookieDomain,
217
+ sameSite: 'strict',
218
+ maxAge: refreshExpiresIn * 1000,
219
+ path: '/api/auth/refresh' // Restrict to refresh endpoint
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Clear authentication cookies
225
+ */
226
+ clearAuthCookies(res: Response): void {
227
+ res.clearCookie('accessToken');
228
+ res.clearCookie('refreshToken', { path: '/api/auth/refresh' });
229
+ }
230
+
231
+ /**
232
+ * Check and handle rate limiting
233
+ */
234
+ private checkRateLimit(ip: string): boolean {
235
+ if (!this.options.rateLimit) {
236
+ return true;
237
+ }
238
+
239
+ const now = Date.now();
240
+ const attempt = this.loginAttempts.get(ip);
241
+
242
+ if (!attempt) {
243
+ this.loginAttempts.set(ip, { count: 1, resetTime: now + 3600000 });
244
+ return true;
245
+ }
246
+
247
+ // Reset if time expired
248
+ if (now > attempt.resetTime) {
249
+ this.loginAttempts.set(ip, { count: 1, resetTime: now + 3600000 });
250
+ return true;
251
+ }
252
+
253
+ // Check attempts
254
+ if (attempt.count >= 5) {
255
+ return false;
256
+ }
257
+
258
+ // Increment counter
259
+ attempt.count++;
260
+ this.loginAttempts.set(ip, attempt);
261
+ return true;
262
+ }
263
+
264
+ /**
265
+ * Login middleware to authenticate users
266
+ */
267
+ login = async (req: Request, res: Response): Promise<void> => {
268
+ try {
269
+ const { username, password } = req.body;
270
+ const ip = req.ip || req.connection.remoteAddress || '';
271
+
272
+ // Check rate limiting
273
+ if (!this.checkRateLimit(ip)) {
274
+ res.status(429).json({
275
+ success: false,
276
+ message: 'Too many login attempts. Please try again later.'
277
+ });
278
+ return;
279
+ }
280
+
281
+ if (!username || !password) {
282
+ res.status(400).json({
283
+ success: false,
284
+ message: 'Username and password are required'
285
+ });
286
+ return;
287
+ }
288
+
289
+ // Use custom find user function if provided
290
+ if (!this.options.findUser) {
291
+ res.status(500).json({
292
+ success: false,
293
+ message: 'User finder function not configured'
294
+ });
295
+ return;
296
+ }
297
+
298
+ const user = await this.options.findUser(username);
299
+
300
+ if (!user) {
301
+ res.status(401).json({
302
+ success: false,
303
+ message: 'Invalid credentials'
304
+ });
305
+ return;
306
+ }
307
+
308
+ // Verify password
309
+ const isPasswordValid = await this.options.verifyPassword!(
310
+ password,
311
+ user.password
312
+ );
313
+
314
+ if (!isPasswordValid) {
315
+ res.status(401).json({
316
+ success: false,
317
+ message: 'Invalid credentials'
318
+ });
319
+ return;
320
+ }
321
+
322
+ // Create a sanitized user object (without password)
323
+ const userWithoutPassword = { ...user };
324
+ delete userWithoutPassword.password;
325
+
326
+ // Generate tokens
327
+ const tokenPair = this.generateTokenPair({
328
+ id: user.id || user._id,
329
+ username: user.username,
330
+ role: user.role || 'user'
331
+ });
332
+
333
+ // Save refresh token if the function is provided
334
+ if (this.options.saveRefreshToken) {
335
+ const refreshExpiry = new Date();
336
+ refreshExpiry.setSeconds(refreshExpiry.getSeconds() +
337
+ this.getExpirationSeconds(this.options.refreshExpiration || '7d'));
338
+
339
+ await this.options.saveRefreshToken(
340
+ user.id || user._id,
341
+ tokenPair.refreshToken,
342
+ refreshExpiry
343
+ );
344
+ }
345
+
346
+ // Set authentication cookies if using cookie-based auth
347
+ if (req.body.useCookies) {
348
+ this.setAuthCookies(res, tokenPair);
349
+ }
350
+
351
+ res.json({
352
+ success: true,
353
+ message: 'Authentication successful',
354
+ tokens: tokenPair,
355
+ user: userWithoutPassword
356
+ });
357
+ } catch (error) {
358
+ console.error('Authentication error:', error);
359
+ res.status(500).json({
360
+ success: false,
361
+ message: 'Authentication failed',
362
+ error: process.env.NODE_ENV !== 'production' ? (error as Error).message : undefined
363
+ });
364
+ }
365
+ };
366
+
367
+ /**
368
+ * Refresh token handler
369
+ */
370
+ refreshToken = async (req: Request, res: Response): Promise<void> => {
371
+ try {
372
+ // Get refresh token from cookies or request body
373
+ const refreshToken = req.cookies?.refreshToken || req.body.refreshToken;
374
+
375
+ if (!refreshToken) {
376
+ res.status(401).json({
377
+ success: false,
378
+ message: 'Refresh token required'
379
+ });
380
+ return;
381
+ }
382
+
383
+ // Verify the refresh token
384
+ const decoded = this.verifyToken(refreshToken, 'refresh');
385
+
386
+ // Check if token is still valid in database if verification function provided
387
+ if (this.options.verifyRefreshToken) {
388
+ const isValid = await this.options.verifyRefreshToken(decoded.id, refreshToken);
389
+ if (!isValid) {
390
+ this.clearAuthCookies(res);
391
+ res.status(401).json({
392
+ success: false,
393
+ message: 'Invalid refresh token'
394
+ });
395
+ return;
396
+ }
397
+ }
398
+
399
+ // Generate new token pair
400
+ const tokenPair = this.generateTokenPair({
401
+ id: decoded.id,
402
+ // We need to fetch the user data again for complete payload
403
+ ...(this.options.findUser ? await this.options.findUser(decoded.id) : {})
404
+ });
405
+
406
+ // Update refresh token in database if save function provided
407
+ if (this.options.saveRefreshToken) {
408
+ const refreshExpiry = new Date();
409
+ refreshExpiry.setSeconds(refreshExpiry.getSeconds() +
410
+ this.getExpirationSeconds(this.options.refreshExpiration || '7d'));
411
+
412
+ await this.options.saveRefreshToken(
413
+ decoded.id,
414
+ tokenPair.refreshToken,
415
+ refreshExpiry
416
+ );
417
+ }
418
+
419
+ // Set cookies if cookie-based auth is used
420
+ const useCookies = req.cookies?.accessToken || req.body.useCookies;
421
+ if (useCookies) {
422
+ this.setAuthCookies(res, tokenPair);
423
+ }
424
+
425
+ res.json({
426
+ success: true,
427
+ message: 'Token refreshed successfully',
428
+ tokens: tokenPair
429
+ });
430
+ } catch (error) {
431
+ this.clearAuthCookies(res);
432
+ res.status(401).json({
433
+ success: false,
434
+ message: 'Invalid or expired refresh token',
435
+ error: process.env.NODE_ENV !== 'production' ? (error as Error).message : undefined
436
+ });
437
+ }
438
+ };
439
+
440
+ /**
441
+ * Logout handler
442
+ */
443
+ logout = async (req: Request, res: Response): Promise<void> => {
444
+ try {
445
+ // Clear cookies
446
+ this.clearAuthCookies(res);
447
+
448
+ // Invalidate refresh token if verification function provided
449
+ const refreshToken = req.cookies?.refreshToken || req.body.refreshToken;
450
+ if (refreshToken && this.options.saveRefreshToken) {
451
+ try {
452
+ const decoded = this.verifyToken(refreshToken, 'refresh');
453
+
454
+ // Check if invalidateToken function exists, otherwise we just remove it from storage
455
+ if (typeof this.options.saveRefreshToken === 'function') {
456
+ // We pass null as the token to indicate deletion/invalidation
457
+ await this.options.saveRefreshToken(decoded.id, '', new Date());
458
+ }
459
+ } catch (e) {
460
+ // Token already invalid, nothing to do
461
+ }
462
+ }
463
+
464
+ res.json({ success: true, message: 'Logged out successfully' });
465
+ } catch (error) {
466
+ console.error('Logout error:', error);
467
+ res.status(500).json({ success: false, message: 'Logout failed', error: (error as Error).message });
468
+ }
469
+ };
470
+
471
+ /**
472
+ * Middleware to verify user is authenticated
473
+ */
474
+ authenticate = (req: Request, res: Response, next: NextFunction): void => {
475
+ try {
476
+ // Get token from Authorization header or cookies
477
+ let token = req.cookies?.accessToken;
478
+
479
+ if (!token) {
480
+ const authHeader = req.headers.authorization;
481
+ if (authHeader && authHeader.startsWith('Bearer ')) {
482
+ token = authHeader.split(' ')[1];
483
+ }
484
+ }
485
+
486
+ if (!token) {
487
+ res.status(401).json({
488
+ success: false,
489
+ message: 'Authentication required'
490
+ });
491
+ return;
492
+ }
493
+
494
+ const decoded = this.verifyToken(token, 'access');
495
+
496
+ // Add user info to request
497
+ (req as any).user = decoded;
498
+
499
+ next();
500
+ } catch (error) {
501
+ res.status(401).json({
502
+ success: false,
503
+ message: 'Invalid or expired token',
504
+ error: process.env.NODE_ENV !== 'production' ? (error as Error).message : undefined
505
+ });
506
+ }
507
+ };
508
+
509
+ /**
510
+ * Middleware to check if user has required role
511
+ */
512
+ hasRole = (role: string | string[]) => {
513
+ return (req: Request, res: Response, next: NextFunction): void => {
514
+ const user = (req as any).user;
515
+
516
+ if (!user) {
517
+ res.status(401).json({
518
+ success: false,
519
+ message: 'Authentication required'
520
+ });
521
+ return;
522
+ }
523
+
524
+ const roles = Array.isArray(role) ? role : [role];
525
+
526
+ if (roles.includes(user.role)) {
527
+ next();
528
+ } else {
529
+ res.status(403).json({
530
+ success: false,
531
+ message: 'Insufficient permissions'
532
+ });
533
+ }
534
+ };
535
+ };
536
+ }
537
+
538
+ /**
539
+ * Create an authentication service
540
+ */
541
+ export function createAuth(options: AuthOptions): Auth {
542
+ return new Auth(options);
543
+ }
@@ -0,0 +1,104 @@
1
+ import mongoose from 'mongoose';
2
+ import { DatabaseOptions } from './types';
3
+
4
+ /**
5
+ * Database connector for MongoDB using Mongoose
6
+ */
7
+ export class DatabaseConnector {
8
+ private options: DatabaseOptions;
9
+ private connection: mongoose.Connection | null = null;
10
+ private _connected = false; // Renamed from isConnected to _connected
11
+
12
+ constructor(options: DatabaseOptions) {
13
+ this.options = {
14
+ retryAttempts: 3,
15
+ retryDelay: 1000,
16
+ connectionTimeout: 10000,
17
+ autoIndex: true,
18
+ ...options
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Connect to the database
24
+ */
25
+ async connect(): Promise<mongoose.Connection> {
26
+ try {
27
+ if (this._connected && this.connection) {
28
+ return this.connection;
29
+ }
30
+
31
+ // Set Mongoose options
32
+ mongoose.set('strictQuery', true);
33
+
34
+ // Connect with retry logic
35
+ let attempts = 0;
36
+ while (attempts < (this.options.retryAttempts || 3)) {
37
+ try {
38
+ await mongoose.connect(this.options.uri, {
39
+ dbName: this.options.name,
40
+ connectTimeoutMS: this.options.connectionTimeout,
41
+ autoIndex: this.options.autoIndex,
42
+ ...this.options.options
43
+ });
44
+ break;
45
+ } catch (error) {
46
+ attempts++;
47
+ if (attempts >= (this.options.retryAttempts || 3)) {
48
+ throw error;
49
+ }
50
+ console.log(`Connection attempt ${attempts} failed. Retrying in ${this.options.retryDelay}ms...`);
51
+ await new Promise(resolve => setTimeout(resolve, this.options.retryDelay));
52
+ }
53
+ }
54
+
55
+ this.connection = mongoose.connection;
56
+ this._connected = true;
57
+
58
+ // Log successful connection
59
+ console.log(`Connected to MongoDB at ${this.options.uri}/${this.options.name}`);
60
+
61
+ // Handle connection events
62
+ this.connection.on('error', (err) => {
63
+ console.error('MongoDB connection error:', err);
64
+ this._connected = false;
65
+ });
66
+
67
+ this.connection.on('disconnected', () => {
68
+ console.log('MongoDB disconnected');
69
+ this._connected = false;
70
+ });
71
+
72
+ return this.connection;
73
+ } catch (error) {
74
+ console.error('Failed to connect to MongoDB:', error);
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Disconnect from the database
81
+ */
82
+ async disconnect(): Promise<void> {
83
+ if (this.connection) {
84
+ await mongoose.disconnect();
85
+ this._connected = false;
86
+ this.connection = null;
87
+ console.log('Disconnected from MongoDB');
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Check if connected to the database
93
+ */
94
+ isConnected(): boolean {
95
+ return this._connected;
96
+ }
97
+
98
+ /**
99
+ * Get the mongoose connection
100
+ */
101
+ getConnection(): mongoose.Connection | null {
102
+ return this.connection;
103
+ }
104
+ }