@naman_deep_singh/server-utils 1.0.0 → 1.0.2

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/server.js CHANGED
@@ -3,41 +3,245 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ExpressServer = void 0;
6
7
  exports.createServer = createServer;
7
- exports.withPlugin = withPlugin;
8
- exports.createServerWithPlugins = createServerWithPlugins;
9
8
  const express_1 = __importDefault(require("express"));
10
- const cors_1 = __importDefault(require("cors"));
11
- const helmet_1 = __importDefault(require("helmet"));
12
- function createServer(config = {}) {
13
- const app = (0, express_1.default)();
14
- // Apply default middleware
15
- if (config.helmet !== false) {
16
- app.use((0, helmet_1.default)());
17
- }
18
- if (config.cors !== false) {
19
- const corsOptions = typeof config.cors === 'object' ? config.cors : undefined;
20
- app.use((0, cors_1.default)(corsOptions));
21
- }
22
- if (config.json !== false) {
23
- app.use(express_1.default.json());
24
- }
25
- // Apply custom middleware
26
- if (config.customMiddleware) {
27
- config.customMiddleware.forEach(middleware => {
28
- app.use(middleware);
9
+ const shutdown_1 = require("./shutdown");
10
+ const crypto_1 = __importDefault(require("crypto"));
11
+ class ExpressServer {
12
+ constructor(name = 'Express Server', version = '1.0.0', config = {}) {
13
+ this.status = 'stopped';
14
+ this.grpcServices = [];
15
+ this.rpcMethods = {};
16
+ this.app = (0, express_1.default)();
17
+ this.config = {
18
+ name,
19
+ version,
20
+ startTime: new Date(),
21
+ port: config.port || 3000,
22
+ cors: config.cors ?? true,
23
+ helmet: config.helmet ?? true,
24
+ json: config.json ?? true,
25
+ customMiddleware: config.customMiddleware || [],
26
+ healthCheck: config.healthCheck ?? true,
27
+ gracefulShutdown: config.gracefulShutdown ?? true,
28
+ socketIO: config.socketIO
29
+ };
30
+ // Apply middleware based on configuration
31
+ this.setupMiddleware();
32
+ }
33
+ setupMiddleware() {
34
+ // Apply CORS if enabled
35
+ if (this.config.cors) {
36
+ try {
37
+ const cors = require('cors');
38
+ const corsOptions = typeof this.config.cors === 'object' ? this.config.cors : undefined;
39
+ this.app.use(cors(corsOptions));
40
+ }
41
+ catch (error) {
42
+ console.warn('CORS middleware not available. Install cors package.');
43
+ }
44
+ }
45
+ // Apply Helmet if enabled
46
+ if (this.config.helmet) {
47
+ try {
48
+ const helmet = require('helmet');
49
+ this.app.use(helmet());
50
+ }
51
+ catch (error) {
52
+ console.warn('Helmet middleware not available. Install helmet package.');
53
+ }
54
+ }
55
+ // Apply JSON parser if enabled
56
+ if (this.config.json) {
57
+ this.app.use(express_1.default.json());
58
+ }
59
+ // Apply custom middleware
60
+ if (this.config.customMiddleware && this.config.customMiddleware.length > 0) {
61
+ this.config.customMiddleware.forEach(middleware => {
62
+ this.app.use(middleware);
63
+ });
64
+ }
65
+ // Add health check if enabled
66
+ if (this.config.healthCheck) {
67
+ const healthPath = typeof this.config.healthCheck === 'string' ? this.config.healthCheck : '/health';
68
+ this.app.get(healthPath, (req, res) => {
69
+ res.status(200).json({
70
+ status: 'healthy',
71
+ service: this.config.name,
72
+ version: this.config.version,
73
+ uptime: Date.now() - this.config.startTime.getTime(),
74
+ timestamp: new Date().toISOString()
75
+ });
76
+ });
77
+ }
78
+ }
79
+ async start() {
80
+ this.status = 'starting';
81
+ return new Promise((resolve, reject) => {
82
+ try {
83
+ this.server = this.app.listen(this.config.port, () => {
84
+ this.status = 'running';
85
+ console.log(`🚀 ${this.config.name} v${this.config.version} running on http://localhost:${this.config.port}`);
86
+ if (this.config.gracefulShutdown) {
87
+ (0, shutdown_1.createGracefulShutdown)(this.server, {
88
+ onShutdown: async () => {
89
+ this.status = 'stopping';
90
+ }
91
+ });
92
+ }
93
+ resolve(this);
94
+ });
95
+ this.server.on('error', reject);
96
+ }
97
+ catch (error) {
98
+ this.status = 'stopped';
99
+ reject(error);
100
+ }
29
101
  });
30
102
  }
31
- return app;
32
- }
33
- function withPlugin(app, plugin, config = {}) {
34
- plugin(app, config);
35
- return app;
103
+ async stop() {
104
+ this.status = 'stopping';
105
+ // Stop gRPC server if running
106
+ if (this.grpcServer) {
107
+ this.grpcServer.forceShutdown();
108
+ }
109
+ // Stop Socket.IO server if running
110
+ if (this.socketIO) {
111
+ this.socketIO.close();
112
+ }
113
+ if (!this.server) {
114
+ this.status = 'stopped';
115
+ return;
116
+ }
117
+ return new Promise((resolve) => {
118
+ this.server.close(() => {
119
+ this.status = 'stopped';
120
+ console.log(`👋 ${this.config.name} stopped`);
121
+ resolve();
122
+ });
123
+ });
124
+ }
125
+ getInfo() {
126
+ return {
127
+ name: this.config.name,
128
+ version: this.config.version,
129
+ port: this.config.port,
130
+ uptime: Date.now() - this.config.startTime.getTime(),
131
+ status: this.status,
132
+ startTime: this.config.startTime
133
+ };
134
+ }
135
+ addGrpcService(service, implementation, port = 50051) {
136
+ this.grpcServices.push({ service, implementation });
137
+ // Lazy load gRPC to avoid dependency issues
138
+ if (!this.grpcServer) {
139
+ try {
140
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
141
+ const grpc = require('@grpc/grpc-js');
142
+ this.grpcServer = new grpc.Server();
143
+ // Add all services
144
+ this.grpcServices.forEach(({ service, implementation }) => {
145
+ this.grpcServer.addService(service, implementation);
146
+ });
147
+ this.grpcServer.bindAsync(`0.0.0.0:${port}`, grpc.ServerCredentials.createInsecure(), () => {
148
+ this.grpcServer.start();
149
+ console.log(`🔗 gRPC server running on port ${port}`);
150
+ });
151
+ }
152
+ catch (error) {
153
+ console.warn('gRPC not available. Install @grpc/grpc-js to use gRPC features.');
154
+ }
155
+ }
156
+ }
157
+ addRpcMethods(methods, path = '/rpc') {
158
+ Object.assign(this.rpcMethods, methods);
159
+ try {
160
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
161
+ const jayson = require('jayson');
162
+ const rpcServer = jayson.server(this.rpcMethods);
163
+ this.app.use(path, rpcServer.middleware());
164
+ console.log(`📡 JSON-RPC server mounted on ${path}`);
165
+ }
166
+ catch (error) {
167
+ console.warn('JSON-RPC not available. Install jayson to use RPC features.');
168
+ }
169
+ }
170
+ addWebhook(config) {
171
+ this.app.post(config.path, express_1.default.raw({ type: 'application/json' }), async (req, res) => {
172
+ try {
173
+ // Verify signature if secret provided
174
+ if (config.secret) {
175
+ const signature = req.headers['x-hub-signature-256'] || req.headers['x-signature-256'];
176
+ if (signature) {
177
+ const expectedSignature = crypto_1.default
178
+ .createHmac('sha256', config.secret)
179
+ .update(req.body)
180
+ .digest('hex');
181
+ const providedSignature = Array.isArray(signature) ? signature[0] : signature;
182
+ if (!providedSignature.includes(expectedSignature)) {
183
+ return res.status(401).json({ error: 'Invalid signature' });
184
+ }
185
+ }
186
+ }
187
+ // Parse JSON payload
188
+ const payload = JSON.parse(req.body.toString());
189
+ // Call handler
190
+ await config.handler(payload, req.headers);
191
+ res.status(200).json({ success: true });
192
+ }
193
+ catch (error) {
194
+ console.error('Webhook error:', error);
195
+ res.status(500).json({ error: 'Webhook processing failed' });
196
+ }
197
+ });
198
+ console.log(`🪝 Webhook registered at ${config.path}`);
199
+ }
200
+ addSocketIO(config = {}) {
201
+ if (!this.server) {
202
+ throw new Error('Server must be started before adding Socket.IO');
203
+ }
204
+ try {
205
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
206
+ const { Server } = require('socket.io');
207
+ // Configure CORS
208
+ const corsConfig = config.cors === true
209
+ ? { origin: '*', methods: ['GET', 'POST'] }
210
+ : config.cors || undefined;
211
+ // Create Socket.IO server
212
+ const io = new Server(this.server, {
213
+ cors: config.cors ? corsConfig : undefined,
214
+ path: config.path || '/socket.io'
215
+ });
216
+ // Store reference for cleanup
217
+ this.socketIO = io;
218
+ // Handle connections
219
+ io.on('connection', (socket) => {
220
+ const typedSocket = socket;
221
+ console.log(`🔌 Socket connected: ${typedSocket.id}`);
222
+ // Call user-defined connection handler
223
+ if (config.onConnection) {
224
+ config.onConnection(socket);
225
+ }
226
+ // Handle disconnection
227
+ typedSocket.on('disconnect', (reason) => {
228
+ console.log(`🔌 Socket disconnected: ${typedSocket.id} - ${reason}`);
229
+ // Call user-defined disconnection handler
230
+ if (config.onDisconnection) {
231
+ config.onDisconnection(socket, reason);
232
+ }
233
+ });
234
+ });
235
+ console.log(`🔌 Socket.IO server attached${config.path ? ` at ${config.path}` : ''}`);
236
+ return io;
237
+ }
238
+ catch (error) {
239
+ console.warn('Socket.IO not available. Install socket.io to use WebSocket features.');
240
+ return null;
241
+ }
242
+ }
36
243
  }
37
- function createServerWithPlugins(config = {}, ...plugins) {
38
- const app = createServer(config);
39
- plugins.forEach(plugin => {
40
- plugin(app, config);
41
- });
42
- return app;
244
+ exports.ExpressServer = ExpressServer;
245
+ function createServer(name, version, config) {
246
+ return new ExpressServer(name, version, config);
43
247
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  import express from 'express';
2
- import { CorsOptions } from 'cors';
2
+ export interface CorsOptions {
3
+ origin?: string | string[] | boolean | RegExp | ((origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => void);
4
+ methods?: string | string[];
5
+ allowedHeaders?: string | string[];
6
+ exposedHeaders?: string | string[];
7
+ credentials?: boolean;
8
+ maxAge?: number;
9
+ preflightContinue?: boolean;
10
+ optionsSuccessStatus?: number;
11
+ }
3
12
  export interface ServerConfig {
4
13
  port?: number;
5
14
  cors?: boolean | CorsOptions;
@@ -8,6 +17,20 @@ export interface ServerConfig {
8
17
  customMiddleware?: express.RequestHandler[];
9
18
  healthCheck?: boolean | string;
10
19
  gracefulShutdown?: boolean;
20
+ socketIO?: SocketIOConfig;
21
+ name?: string;
22
+ version?: string;
23
+ }
24
+ export interface SocketIOConfig {
25
+ enabled?: boolean;
26
+ cors?: boolean | {
27
+ origin?: string | string[] | boolean;
28
+ methods?: string[];
29
+ credentials?: boolean;
30
+ };
31
+ onConnection?: (socket: unknown) => void;
32
+ onDisconnection?: (socket: unknown, reason: string) => void;
33
+ path?: string;
11
34
  }
12
35
  export interface HealthCheckConfig {
13
36
  path?: string;
@@ -21,4 +44,13 @@ export interface GracefulShutdownConfig {
21
44
  timeout?: number;
22
45
  onShutdown?: () => Promise<void>;
23
46
  }
47
+ export interface SocketInstance {
48
+ id: string;
49
+ emit: (event: string, data?: unknown) => void;
50
+ on: (event: string, handler: (...args: unknown[]) => void) => void;
51
+ broadcast: {
52
+ emit: (event: string, data?: unknown) => void;
53
+ };
54
+ disconnect: () => void;
55
+ }
24
56
  export type ServerPlugin = (app: express.Application, config: ServerConfig) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naman_deep_singh/server-utils",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Extensible server utilities for Express.js microservices with TypeScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,15 +18,10 @@
18
18
  "license": "ISC",
19
19
  "packageManager": "pnpm@10.20.0",
20
20
  "dependencies": {
21
- "express": "^5.1.0",
22
- "cors": "^2.8.5",
23
- "helmet": "^8.1.0",
24
- "cookie-parser": "^1.4.6"
21
+ "express": "^4.21.1"
25
22
  },
26
23
  "devDependencies": {
27
- "@types/express": "^5.0.5",
28
- "@types/cors": "^2.8.19",
29
- "@types/cookie-parser": "^1.4.7",
24
+ "@types/express": "^4.17.21",
30
25
  "typescript": "^5.9.3"
31
26
  }
32
27
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Core server utilities
2
- export { createServer, withPlugin, createServerWithPlugins } from './server';
2
+ export { ExpressServer, createServer } from './server';
3
+ export type { ServerInstance, ServerInfo, GrpcService, RpcMethod, WebhookConfig } from './server';
3
4
 
4
5
  // Health check utilities
5
6
  export { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
@@ -42,5 +43,71 @@ export type {
42
43
  HealthCheckConfig,
43
44
  HealthCheck,
44
45
  GracefulShutdownConfig,
45
- ServerPlugin
46
- } from './types';
46
+ ServerPlugin,
47
+ SocketIOConfig,
48
+ SocketInstance
49
+ } from './types';
50
+
51
+ // Import all exports for default export
52
+ import { ExpressServer, createServer } from './server';
53
+ import { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
54
+ import { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
55
+ import {
56
+ createLoggingMiddleware,
57
+ createErrorHandler,
58
+ createRequestIdMiddleware,
59
+ createValidationMiddleware,
60
+ createRateLimitMiddleware,
61
+ createAuthMiddleware,
62
+ withLogging,
63
+ withErrorHandler,
64
+ withRequestId,
65
+ withValidation,
66
+ withRateLimit,
67
+ withAuth,
68
+ validateFields,
69
+ rateLimit,
70
+ requireAuth
71
+ } from './middleware';
72
+ import { getEnv, getEnvNumber, getEnvBoolean } from './utils';
73
+
74
+ // Default export for namespace usage
75
+ const ServerUtils = {
76
+ // Server creation
77
+ createServer,
78
+ ExpressServer,
79
+
80
+ // Health checks
81
+ createHealthCheck,
82
+ withHealthCheck,
83
+ addHealthCheck,
84
+
85
+ // Graceful shutdown
86
+ createGracefulShutdown,
87
+ withGracefulShutdown,
88
+ startServerWithShutdown,
89
+
90
+ // Middleware
91
+ createLoggingMiddleware,
92
+ createErrorHandler,
93
+ createRequestIdMiddleware,
94
+ createValidationMiddleware,
95
+ createRateLimitMiddleware,
96
+ createAuthMiddleware,
97
+ withLogging,
98
+ withErrorHandler,
99
+ withRequestId,
100
+ withValidation,
101
+ withRateLimit,
102
+ withAuth,
103
+ validateFields,
104
+ rateLimit,
105
+ requireAuth,
106
+
107
+ // Utils
108
+ getEnv,
109
+ getEnvNumber,
110
+ getEnvBoolean
111
+ };
112
+
113
+ export default ServerUtils;
package/src/middleware.ts CHANGED
@@ -22,22 +22,25 @@ export function createLoggingMiddleware(format: 'simple' | 'detailed' = 'simple'
22
22
 
23
23
  // Error handling middleware
24
24
  export function createErrorHandler(): express.ErrorRequestHandler {
25
- return (err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
25
+ return (err: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
26
26
  console.error('Error:', err);
27
27
 
28
28
  if (res.headersSent) {
29
29
  return next(err);
30
30
  }
31
31
 
32
- const status = err.status || err.statusCode || 500;
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;
33
36
  const message = process.env.NODE_ENV === 'production'
34
37
  ? 'Internal Server Error'
35
- : err.message;
38
+ : errorObj.message || 'Unknown error';
36
39
 
37
40
  res.status(status).json({
38
41
  status: false,
39
42
  message,
40
- ...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
43
+ ...(process.env.NODE_ENV !== 'production' && { stack: errorObj.stack })
41
44
  });
42
45
  };
43
46
  }
@@ -46,7 +49,7 @@ export function createErrorHandler(): express.ErrorRequestHandler {
46
49
  export function createRequestIdMiddleware(): express.RequestHandler {
47
50
  return (req: express.Request, res: express.Response, next: express.NextFunction) => {
48
51
  const requestId = Math.random().toString(36).substring(2, 15);
49
- (req as any).requestId = requestId;
52
+ (req as express.Request & { requestId: string }).requestId = requestId;
50
53
  res.setHeader('X-Request-ID', requestId);
51
54
  next();
52
55
  };
@@ -60,7 +63,7 @@ export interface ValidationRule {
60
63
  minLength?: number;
61
64
  maxLength?: number;
62
65
  pattern?: RegExp;
63
- custom?: (value: any) => boolean | string;
66
+ custom?: (value: unknown) => boolean | string;
64
67
  }
65
68
 
66
69
  export function createValidationMiddleware(rules: ValidationRule[]): express.RequestHandler {
@@ -181,7 +184,7 @@ export function createRateLimitMiddleware(config: RateLimitConfig = {}): express
181
184
  // Authentication middleware helper
182
185
  export interface AuthConfig {
183
186
  tokenExtractor?: (req: express.Request) => string | null;
184
- tokenValidator?: (token: string) => Promise<any> | any;
187
+ tokenValidator?: (token: string) => Promise<unknown> | unknown;
185
188
  unauthorizedMessage?: string;
186
189
  }
187
190
 
@@ -210,7 +213,7 @@ export function createAuthMiddleware(config: AuthConfig): express.RequestHandler
210
213
  }
211
214
 
212
215
  const user = await tokenValidator(token);
213
- (req as any).user = user;
216
+ (req as express.Request & { user: unknown }).user = user;
214
217
  next();
215
218
  } catch (error) {
216
219
  return res.status(401).json({