@naman_deep_singh/server-utils 1.0.7 → 1.1.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 (51) hide show
  1. package/README.md +147 -7
  2. package/dist/{index.d.ts → cjs/index.d.ts} +2 -3
  3. package/dist/{index.js → cjs/index.js} +1 -3
  4. package/dist/{middleware.js → cjs/middleware.js} +37 -10
  5. package/dist/{periodic-health.d.ts → cjs/periodic-health.d.ts} +0 -1
  6. package/dist/{periodic-health.js → cjs/periodic-health.js} +0 -4
  7. package/dist/{server.js → cjs/server.js} +1 -1
  8. package/dist/{utils.js → cjs/utils.js} +14 -8
  9. package/dist/esm/health.d.ts +5 -0
  10. package/dist/esm/health.js +40 -0
  11. package/dist/esm/index.d.ts +46 -0
  12. package/dist/esm/index.js +58 -0
  13. package/dist/esm/middleware.d.ts +37 -0
  14. package/dist/esm/middleware.js +229 -0
  15. package/dist/esm/periodic-health.d.ts +11 -0
  16. package/dist/esm/periodic-health.js +64 -0
  17. package/dist/esm/server.d.ts +69 -0
  18. package/dist/esm/server.js +271 -0
  19. package/dist/esm/shutdown.d.ts +5 -0
  20. package/dist/esm/shutdown.js +52 -0
  21. package/dist/esm/types.d.ts +70 -0
  22. package/dist/esm/types.js +1 -0
  23. package/dist/esm/utils.d.ts +3 -0
  24. package/dist/esm/utils.js +38 -0
  25. package/dist/types/health.d.ts +5 -0
  26. package/dist/types/index.d.ts +46 -0
  27. package/dist/types/middleware.d.ts +37 -0
  28. package/dist/types/periodic-health.d.ts +11 -0
  29. package/dist/types/server.d.ts +69 -0
  30. package/dist/types/shutdown.d.ts +5 -0
  31. package/dist/types/types.d.ts +70 -0
  32. package/dist/types/utils.d.ts +3 -0
  33. package/package.json +22 -7
  34. package/src/health.ts +0 -47
  35. package/src/index.ts +0 -127
  36. package/src/middleware.ts +0 -275
  37. package/src/periodic-health.ts +0 -87
  38. package/src/server.ts +0 -412
  39. package/src/shutdown.ts +0 -69
  40. package/src/types.ts +0 -80
  41. package/src/utils.ts +0 -34
  42. package/tsconfig.json +0 -21
  43. /package/dist/{health.d.ts → cjs/health.d.ts} +0 -0
  44. /package/dist/{health.js → cjs/health.js} +0 -0
  45. /package/dist/{middleware.d.ts → cjs/middleware.d.ts} +0 -0
  46. /package/dist/{server.d.ts → cjs/server.d.ts} +0 -0
  47. /package/dist/{shutdown.d.ts → cjs/shutdown.d.ts} +0 -0
  48. /package/dist/{shutdown.js → cjs/shutdown.js} +0 -0
  49. /package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
  50. /package/dist/{types.js → cjs/types.js} +0 -0
  51. /package/dist/{utils.d.ts → cjs/utils.d.ts} +0 -0
package/src/middleware.ts DELETED
@@ -1,275 +0,0 @@
1
- import express from 'express';
2
- import { ServerPlugin } from './types';
3
-
4
- // Logging middleware
5
- export function createLoggingMiddleware(format: 'simple' | 'detailed' = 'simple'): express.RequestHandler {
6
- return (req: express.Request, res: express.Response, next: express.NextFunction) => {
7
- const start = Date.now();
8
-
9
- res.on('finish', () => {
10
- const duration = Date.now() - start;
11
-
12
- if (format === 'detailed') {
13
- console.log(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms - ${req.ip}`);
14
- } else {
15
- console.log(`${req.method} ${req.url} - ${res.statusCode}`);
16
- }
17
- });
18
-
19
- next();
20
- };
21
- }
22
-
23
- // Error handling middleware
24
- export function createErrorHandler(): express.ErrorRequestHandler {
25
- return (err: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
26
- console.error('Error:', err);
27
-
28
- if (res.headersSent) {
29
- return next(err);
30
- }
31
-
32
- // Type guard for error objects
33
- const errorObj = err as { status?: number; statusCode?: number; message?: string; stack?: string };
34
-
35
- const status = errorObj.status || errorObj.statusCode || 500;
36
- const message = process.env.NODE_ENV === 'production'
37
- ? 'Internal Server Error'
38
- : errorObj.message || 'Unknown error';
39
-
40
- res.status(status).json({
41
- status: false,
42
- message,
43
- ...(process.env.NODE_ENV !== 'production' && { stack: errorObj.stack })
44
- });
45
- };
46
- }
47
-
48
- // Request ID middleware
49
- export function createRequestIdMiddleware(): express.RequestHandler {
50
- return (req: express.Request, res: express.Response, next: express.NextFunction) => {
51
- const requestId = Math.random().toString(36).substring(2, 15);
52
- (req as express.Request & { requestId: string }).requestId = requestId;
53
- res.setHeader('X-Request-ID', requestId);
54
- next();
55
- };
56
- }
57
-
58
- // Validation middleware
59
- export interface ValidationRule {
60
- field: string;
61
- required?: boolean;
62
- type?: 'string' | 'number' | 'email' | 'boolean';
63
- minLength?: number;
64
- maxLength?: number;
65
- pattern?: RegExp;
66
- custom?: (value: unknown) => boolean | string;
67
- }
68
-
69
- export function createValidationMiddleware(rules: ValidationRule[]): express.RequestHandler {
70
- return (req: express.Request, res: express.Response, next: express.NextFunction) => {
71
- const errors: string[] = [];
72
-
73
- for (const rule of rules) {
74
- const value = req.body[rule.field];
75
-
76
- if (rule.required && (value === undefined || value === null || value === '')) {
77
- errors.push(`${rule.field} is required`);
78
- continue;
79
- }
80
-
81
- if (value === undefined || value === null) continue;
82
-
83
- if (rule.type) {
84
- switch (rule.type) {
85
- case 'email':
86
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
87
- if (!emailRegex.test(value)) {
88
- errors.push(`${rule.field} must be a valid email`);
89
- }
90
- break;
91
- case 'string':
92
- if (typeof value !== 'string') {
93
- errors.push(`${rule.field} must be a string`);
94
- }
95
- break;
96
- case 'number':
97
- if (typeof value !== 'number' && isNaN(Number(value))) {
98
- errors.push(`${rule.field} must be a number`);
99
- }
100
- break;
101
- case 'boolean':
102
- if (typeof value !== 'boolean') {
103
- errors.push(`${rule.field} must be a boolean`);
104
- }
105
- break;
106
- }
107
- }
108
-
109
- if (rule.minLength && value.length < rule.minLength) {
110
- errors.push(`${rule.field} must be at least ${rule.minLength} characters`);
111
- }
112
- if (rule.maxLength && value.length > rule.maxLength) {
113
- errors.push(`${rule.field} must be no more than ${rule.maxLength} characters`);
114
- }
115
-
116
- if (rule.pattern && !rule.pattern.test(value)) {
117
- errors.push(`${rule.field} format is invalid`);
118
- }
119
-
120
- if (rule.custom) {
121
- const result = rule.custom(value);
122
- if (result !== true) {
123
- errors.push(typeof result === 'string' ? result : `${rule.field} is invalid`);
124
- }
125
- }
126
- }
127
-
128
- if (errors.length > 0) {
129
- return res.status(400).json({
130
- status: false,
131
- message: 'Validation failed',
132
- errors
133
- });
134
- }
135
-
136
- next();
137
- };
138
- }
139
-
140
- // Rate limiting middleware
141
- export interface RateLimitConfig {
142
- windowMs?: number;
143
- maxRequests?: number;
144
- message?: string;
145
- keyGenerator?: (req: express.Request) => string;
146
- }
147
-
148
- const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
149
-
150
- export function createRateLimitMiddleware(config: RateLimitConfig = {}): express.RequestHandler {
151
- const {
152
- windowMs = 15 * 60 * 1000,
153
- maxRequests = 100,
154
- message = 'Too many requests, please try again later',
155
- keyGenerator = (req) => req.ip || 'unknown'
156
- } = config;
157
-
158
- return (req: express.Request, res: express.Response, next: express.NextFunction) => {
159
- const key = keyGenerator(req);
160
- const now = Date.now();
161
- const record = rateLimitStore.get(key);
162
-
163
- if (!record || now > record.resetTime) {
164
- rateLimitStore.set(key, {
165
- count: 1,
166
- resetTime: now + windowMs
167
- });
168
- return next();
169
- }
170
-
171
- if (record.count >= maxRequests) {
172
- return res.status(429).json({
173
- status: false,
174
- message,
175
- retryAfter: Math.ceil((record.resetTime - now) / 1000)
176
- });
177
- }
178
-
179
- record.count++;
180
- next();
181
- };
182
- }
183
-
184
- // Authentication middleware helper
185
- export interface AuthConfig {
186
- tokenExtractor?: (req: express.Request) => string | null;
187
- tokenValidator?: (token: string) => Promise<unknown> | unknown;
188
- unauthorizedMessage?: string;
189
- }
190
-
191
- export function createAuthMiddleware(config: AuthConfig): express.RequestHandler {
192
- const {
193
- tokenExtractor = (req) => {
194
- const authHeader = req.headers.authorization;
195
- if (authHeader && authHeader.startsWith('Bearer ')) {
196
- return authHeader.substring(7);
197
- }
198
- return req.cookies?.token || null;
199
- },
200
- tokenValidator = () => { throw new Error('Token validator not implemented'); },
201
- unauthorizedMessage = 'Unauthorized access'
202
- } = config;
203
-
204
- return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
205
- try {
206
- const token = tokenExtractor(req);
207
-
208
- if (!token) {
209
- return res.status(401).json({
210
- status: false,
211
- message: unauthorizedMessage
212
- });
213
- }
214
-
215
- const user = await tokenValidator(token);
216
- (req as express.Request & { user: unknown }).user = user;
217
- next();
218
- } catch (error) {
219
- return res.status(401).json({
220
- status: false,
221
- message: unauthorizedMessage
222
- });
223
- }
224
- };
225
- }
226
-
227
- // Plugin versions
228
- export function withLogging(format: 'simple' | 'detailed' = 'simple'): ServerPlugin {
229
- return (app: express.Application) => {
230
- app.use(createLoggingMiddleware(format));
231
- };
232
- }
233
-
234
- export function withErrorHandler(): ServerPlugin {
235
- return (app: express.Application) => {
236
- app.use(createErrorHandler());
237
- };
238
- }
239
-
240
- export function withRequestId(): ServerPlugin {
241
- return (app: express.Application) => {
242
- app.use(createRequestIdMiddleware());
243
- };
244
- }
245
-
246
- export function withValidation(rules: ValidationRule[]): ServerPlugin {
247
- return (app: express.Application) => {
248
- app.use(createValidationMiddleware(rules));
249
- };
250
- }
251
-
252
- export function withRateLimit(config: RateLimitConfig = {}): ServerPlugin {
253
- return (app: express.Application) => {
254
- app.use(createRateLimitMiddleware(config));
255
- };
256
- }
257
-
258
- export function withAuth(config: AuthConfig): ServerPlugin {
259
- return (app: express.Application) => {
260
- app.use(createAuthMiddleware(config));
261
- };
262
- }
263
-
264
- // Convenience functions for route-specific middleware
265
- export function validateFields(rules: ValidationRule[]): express.RequestHandler {
266
- return createValidationMiddleware(rules);
267
- }
268
-
269
- export function rateLimit(config: RateLimitConfig = {}): express.RequestHandler {
270
- return createRateLimitMiddleware(config);
271
- }
272
-
273
- export function requireAuth(config: AuthConfig): express.RequestHandler {
274
- return createAuthMiddleware(config);
275
- }
@@ -1,87 +0,0 @@
1
- import { PeriodicHealthCheckConfig, HealthCheckService } from './types';
2
-
3
- export class PeriodicHealthMonitor {
4
- private intervals: NodeJS.Timeout[] = [];
5
- private config: PeriodicHealthCheckConfig;
6
- private serviceName: string;
7
-
8
- constructor(config: PeriodicHealthCheckConfig, serviceName: string) {
9
- this.config = config;
10
- this.serviceName = serviceName;
11
- }
12
-
13
- start(): void {
14
- if (!this.config.enabled || !this.config.services?.length) {
15
- return;
16
- }
17
-
18
- const interval = this.config.interval || 30000;
19
-
20
- this.config.services.forEach(service => {
21
- const intervalId = setInterval(async () => {
22
- await this.checkServiceHealth(service);
23
- }, interval);
24
-
25
- this.intervals.push(intervalId);
26
- });
27
-
28
- console.log(`📊 ${this.serviceName}: Periodic health monitoring enabled (${interval}ms interval) for ${this.config.services.length} service(s)`);
29
- }
30
-
31
- stop(): void {
32
- this.intervals.forEach(interval => clearInterval(interval));
33
- this.intervals = [];
34
- console.log(`🛑 ${this.serviceName}: Periodic health monitoring stopped`);
35
- }
36
-
37
- private async checkServiceHealth(service: HealthCheckService): Promise<boolean> {
38
- try {
39
- const controller = new AbortController();
40
- const timeout = service.timeout || 5000;
41
- const timeoutId = setTimeout(() => controller.abort(), timeout);
42
-
43
- const response = await fetch(service.url, {
44
- method: 'GET',
45
- signal: controller.signal,
46
- headers: {
47
- 'User-Agent': `${this.serviceName}-health-monitor`
48
- }
49
- });
50
-
51
- clearTimeout(timeoutId);
52
-
53
- if (response.ok) {
54
- console.log(`🟢 ${service.name} is healthy`);
55
- return true;
56
- } else {
57
- console.log(`🔴 ${service.name} returned ${response.status}`);
58
- return false;
59
- }
60
- } catch (error) {
61
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
62
- console.log(`🔴 ${service.name} health check failed: ${errorMessage}`);
63
- return false;
64
- }
65
- }
66
-
67
- // Get current health status of all services
68
- async getHealthStatus(): Promise<Record<string, boolean>> {
69
- if (!this.config.services?.length) {
70
- return {};
71
- }
72
-
73
- const results: Record<string, boolean> = {};
74
-
75
- await Promise.all(
76
- this.config.services.map(async service => {
77
- results[service.name] = await this.checkServiceHealth(service);
78
- })
79
- );
80
-
81
- return results;
82
- }
83
- }
84
-
85
- export function createPeriodicHealthMonitor(config: PeriodicHealthCheckConfig, serviceName: string): PeriodicHealthMonitor {
86
- return new PeriodicHealthMonitor(config, serviceName);
87
- }