@naman_deep_singh/server-utils 1.0.0 → 1.0.1

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/src/server.ts CHANGED
@@ -1,46 +1,320 @@
1
1
  import express from 'express';
2
- import cors from 'cors';
3
- import helmet from 'helmet';
4
- import { ServerConfig, ServerPlugin } from './types';
2
+ import { Server } from 'http';
3
+ import { ServerConfig, SocketIOConfig, SocketInstance } from './types';
4
+ import { createGracefulShutdown } from './shutdown';
5
+ import crypto from 'crypto';
5
6
 
6
- export function createServer(config: ServerConfig = {}): express.Application {
7
- const app = express();
7
+ export interface GrpcService {
8
+ service: Record<string, unknown>;
9
+ implementation: Record<string, (...args: unknown[]) => unknown>;
10
+ }
11
+
12
+ export interface RpcMethod {
13
+ [key: string]: (params: unknown[], callback: (error: Error | null, result?: unknown) => void) => void;
14
+ }
15
+
16
+ export interface WebhookConfig {
17
+ path: string;
18
+ secret?: string;
19
+ handler: (payload: Record<string, unknown>, headers: Record<string, string | string[]>) => void | Promise<void>;
20
+ }
21
+
22
+ export interface GrpcServerInstance {
23
+ start(): void;
24
+ forceShutdown(): void;
25
+ addService(service: unknown, implementation: unknown): void;
26
+ bindAsync(address: string, credentials: unknown, callback: () => void): void;
27
+ }
28
+
29
+
30
+
31
+ export interface ServerInstanceConfig extends Required<Omit<ServerConfig, 'socketIO' | 'name' | 'version'>> {
32
+ name: string;
33
+ version: string;
34
+ startTime: Date;
35
+ socketIO?: SocketIOConfig;
36
+ }
37
+
38
+ export interface ServerInstance {
39
+ app: express.Application;
40
+ server?: Server;
41
+ config: ServerInstanceConfig;
42
+ start(): Promise<ServerInstance>;
43
+ stop(): Promise<void>;
44
+ getInfo(): ServerInfo;
45
+
46
+ // Multi-protocol support
47
+ addGrpcService(service: Record<string, unknown>, implementation: Record<string, (...args: unknown[]) => unknown>, port?: number): void;
48
+ addRpcMethods(methods: RpcMethod, path?: string): void;
49
+ addWebhook(config: WebhookConfig): void;
50
+ addSocketIO(config?: SocketIOConfig): unknown;
51
+ }
8
52
 
9
- // Apply default middleware
10
- if (config.helmet !== false) {
11
- app.use(helmet());
53
+ export interface ServerInfo {
54
+ name: string;
55
+ version: string;
56
+ port: number;
57
+ uptime: number;
58
+ status: 'starting' | 'running' | 'stopping' | 'stopped';
59
+ startTime: Date;
60
+ }
61
+
62
+ export class ExpressServer implements ServerInstance {
63
+ public app: express.Application;
64
+ public server?: Server;
65
+ public config: ServerInstanceConfig;
66
+ private status: 'starting' | 'running' | 'stopping' | 'stopped' = 'stopped';
67
+ private grpcServices: GrpcService[] = [];
68
+ private grpcServer?: GrpcServerInstance;
69
+ private rpcMethods: RpcMethod = {};
70
+ private socketIO?: { close(): void };
71
+
72
+ constructor(
73
+ name: string = 'Express Server',
74
+ version: string = '1.0.0',
75
+ config: ServerConfig = {}
76
+ ) {
77
+ this.app = express();
78
+ this.config = {
79
+ name,
80
+ version,
81
+ startTime: new Date(),
82
+ port: config.port || 3000,
83
+ cors: config.cors ?? true,
84
+ helmet: config.helmet ?? true,
85
+ json: config.json ?? true,
86
+ customMiddleware: config.customMiddleware || [],
87
+ healthCheck: config.healthCheck ?? true,
88
+ gracefulShutdown: config.gracefulShutdown ?? true,
89
+ socketIO: config.socketIO
90
+ };
12
91
  }
13
92
 
14
- if (config.cors !== false) {
15
- const corsOptions = typeof config.cors === 'object' ? config.cors : undefined;
16
- app.use(cors(corsOptions));
93
+ async start(): Promise<ServerInstance> {
94
+ this.status = 'starting';
95
+
96
+ return new Promise((resolve, reject) => {
97
+ try {
98
+ this.server = this.app.listen(this.config.port, () => {
99
+ this.status = 'running';
100
+ console.log(`🚀 ${this.config.name} v${this.config.version} running on http://localhost:${this.config.port}`);
101
+
102
+ if (this.config.gracefulShutdown) {
103
+ createGracefulShutdown(this.server!, {
104
+ onShutdown: async () => {
105
+ this.status = 'stopping';
106
+ }
107
+ });
108
+ }
109
+
110
+ resolve(this);
111
+ });
112
+
113
+ this.server.on('error', reject);
114
+ } catch (error: unknown) {
115
+ this.status = 'stopped';
116
+ reject(error);
117
+ }
118
+ });
17
119
  }
18
120
 
19
- if (config.json !== false) {
20
- app.use(express.json());
121
+ async stop(): Promise<void> {
122
+ this.status = 'stopping';
123
+
124
+ // Stop gRPC server if running
125
+ if (this.grpcServer) {
126
+ this.grpcServer.forceShutdown();
127
+ }
128
+
129
+ // Stop Socket.IO server if running
130
+ if (this.socketIO) {
131
+ this.socketIO.close();
132
+ }
133
+
134
+ if (!this.server) {
135
+ this.status = 'stopped';
136
+ return;
137
+ }
138
+
139
+ return new Promise((resolve) => {
140
+ this.server!.close(() => {
141
+ this.status = 'stopped';
142
+ console.log(`👋 ${this.config.name} stopped`);
143
+ resolve();
144
+ });
145
+ });
21
146
  }
22
147
 
23
- // Apply custom middleware
24
- if (config.customMiddleware) {
25
- config.customMiddleware.forEach(middleware => {
26
- app.use(middleware);
148
+ getInfo(): ServerInfo {
149
+ return {
150
+ name: this.config.name,
151
+ version: this.config.version,
152
+ port: this.config.port,
153
+ uptime: Date.now() - this.config.startTime.getTime(),
154
+ status: this.status,
155
+ startTime: this.config.startTime
156
+ };
157
+ }
158
+
159
+ addGrpcService(service: Record<string, unknown>, implementation: Record<string, (...args: unknown[]) => unknown>, port: number = 50051): void {
160
+ this.grpcServices.push({ service, implementation });
161
+
162
+ // Lazy load gRPC to avoid dependency issues
163
+ if (!this.grpcServer) {
164
+ try {
165
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
166
+ const grpc = require('@grpc/grpc-js') as {
167
+ Server: new () => {
168
+ start(): void;
169
+ forceShutdown(): void;
170
+ addService(service: unknown, implementation: unknown): void;
171
+ bindAsync(address: string, credentials: unknown, callback: () => void): void;
172
+ };
173
+ ServerCredentials: { createInsecure(): unknown };
174
+ };
175
+ this.grpcServer = new grpc.Server();
176
+
177
+ // Add all services
178
+ this.grpcServices.forEach(({ service, implementation }) => {
179
+ this.grpcServer!.addService(service, implementation);
180
+ });
181
+
182
+ this.grpcServer.bindAsync(
183
+ `0.0.0.0:${port}`,
184
+ grpc.ServerCredentials.createInsecure(),
185
+ () => {
186
+ this.grpcServer!.start();
187
+ console.log(`🔗 gRPC server running on port ${port}`);
188
+ }
189
+ );
190
+ } catch (error: unknown) {
191
+ console.warn('gRPC not available. Install @grpc/grpc-js to use gRPC features.');
192
+ }
193
+ }
194
+ }
195
+
196
+ addRpcMethods(methods: RpcMethod, path: string = '/rpc'): void {
197
+ Object.assign(this.rpcMethods, methods);
198
+
199
+ try {
200
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
201
+ const jayson = require('jayson') as {
202
+ server: (methods: RpcMethod) => {
203
+ middleware(): express.RequestHandler;
204
+ };
205
+ };
206
+ const rpcServer = jayson.server(this.rpcMethods);
207
+ this.app.use(path, rpcServer.middleware());
208
+ console.log(`📡 JSON-RPC server mounted on ${path}`);
209
+ } catch (error: unknown) {
210
+ console.warn('JSON-RPC not available. Install jayson to use RPC features.');
211
+ }
212
+ }
213
+
214
+ addWebhook(config: WebhookConfig): void {
215
+ this.app.post(config.path, express.raw({ type: 'application/json' }), async (req, res) => {
216
+ try {
217
+ // Verify signature if secret provided
218
+ if (config.secret) {
219
+ const signature = req.headers['x-hub-signature-256'] || req.headers['x-signature-256'];
220
+ if (signature) {
221
+ const expectedSignature = crypto
222
+ .createHmac('sha256', config.secret)
223
+ .update(req.body)
224
+ .digest('hex');
225
+
226
+ const providedSignature = Array.isArray(signature) ? signature[0] : signature;
227
+ if (!providedSignature.includes(expectedSignature)) {
228
+ return res.status(401).json({ error: 'Invalid signature' });
229
+ }
230
+ }
231
+ }
232
+
233
+ // Parse JSON payload
234
+ const payload = JSON.parse(req.body.toString());
235
+
236
+ // Call handler
237
+ await config.handler(payload, req.headers as Record<string, string | string[]>);
238
+
239
+ res.status(200).json({ success: true });
240
+ } catch (error: unknown) {
241
+ console.error('Webhook error:', error);
242
+ res.status(500).json({ error: 'Webhook processing failed' });
243
+ }
27
244
  });
245
+
246
+ console.log(`🪝 Webhook registered at ${config.path}`);
28
247
  }
29
248
 
30
- return app;
31
- }
249
+ addSocketIO(config: SocketIOConfig = {}): unknown {
250
+ if (!this.server) {
251
+ throw new Error('Server must be started before adding Socket.IO');
252
+ }
32
253
 
33
- export function withPlugin(app: express.Application, plugin: ServerPlugin, config: ServerConfig = {}): express.Application {
34
- plugin(app, config);
35
- return app;
36
- }
254
+ try {
255
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
256
+ const { Server } = require('socket.io') as {
257
+ Server: new (server: Server, options?: {
258
+ cors?: {
259
+ origin?: string | string[] | boolean;
260
+ methods?: string[];
261
+ credentials?: boolean;
262
+ };
263
+ path?: string;
264
+ }) => {
265
+ on: (event: string, handler: (socket: unknown) => void) => void;
266
+ close: () => void;
267
+ };
268
+ };
269
+
270
+ // Configure CORS
271
+ const corsConfig = config.cors === true
272
+ ? { origin: '*', methods: ['GET', 'POST'] }
273
+ : config.cors || undefined;
274
+
275
+ // Create Socket.IO server
276
+ const io = new Server(this.server, {
277
+ cors: config.cors ? corsConfig : undefined,
278
+ path: config.path || '/socket.io'
279
+ });
280
+
281
+ // Store reference for cleanup
282
+ this.socketIO = io;
37
283
 
38
- export function createServerWithPlugins(config: ServerConfig = {}, ...plugins: ServerPlugin[]): express.Application {
39
- const app = createServer(config);
40
-
41
- plugins.forEach(plugin => {
42
- plugin(app, config);
43
- });
284
+ // Handle connections
285
+ io.on('connection', (socket: unknown) => {
286
+ const typedSocket = socket as SocketInstance;
287
+ console.log(`🔌 Socket connected: ${typedSocket.id}`);
288
+
289
+ // Call user-defined connection handler
290
+ if (config.onConnection) {
291
+ config.onConnection(socket);
292
+ }
293
+
294
+ // Handle disconnection
295
+ typedSocket.on('disconnect', (reason) => {
296
+ console.log(`🔌 Socket disconnected: ${typedSocket.id} - ${reason}`);
297
+
298
+ // Call user-defined disconnection handler
299
+ if (config.onDisconnection) {
300
+ config.onDisconnection(socket, reason as string);
301
+ }
302
+ });
303
+ });
304
+
305
+ console.log(`🔌 Socket.IO server attached${config.path ? ` at ${config.path}` : ''}`);
306
+ return io;
307
+ } catch (error: unknown) {
308
+ console.warn('Socket.IO not available. Install socket.io to use WebSocket features.');
309
+ return null;
310
+ }
311
+ }
312
+ }
44
313
 
45
- return app;
314
+ export function createServer(
315
+ name?: string,
316
+ version?: string,
317
+ config?: ServerConfig
318
+ ): ServerInstance {
319
+ return new ExpressServer(name, version, config);
46
320
  }
package/src/types.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  import express from 'express';
2
- import { CorsOptions } from 'cors';
2
+
3
+ export interface CorsOptions {
4
+ origin?: string | string[] | boolean | RegExp | ((origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => void);
5
+ methods?: string | string[];
6
+ allowedHeaders?: string | string[];
7
+ exposedHeaders?: string | string[];
8
+ credentials?: boolean;
9
+ maxAge?: number;
10
+ preflightContinue?: boolean;
11
+ optionsSuccessStatus?: number;
12
+ }
3
13
 
4
14
  export interface ServerConfig {
5
15
  port?: number;
@@ -9,6 +19,21 @@ export interface ServerConfig {
9
19
  customMiddleware?: express.RequestHandler[];
10
20
  healthCheck?: boolean | string;
11
21
  gracefulShutdown?: boolean;
22
+ socketIO?: SocketIOConfig;
23
+ name?: string;
24
+ version?: string;
25
+ }
26
+
27
+ export interface SocketIOConfig {
28
+ enabled?: boolean;
29
+ cors?: boolean | {
30
+ origin?: string | string[] | boolean;
31
+ methods?: string[];
32
+ credentials?: boolean;
33
+ };
34
+ onConnection?: (socket: unknown) => void;
35
+ onDisconnection?: (socket: unknown, reason: string) => void;
36
+ path?: string;
12
37
  }
13
38
 
14
39
  export interface HealthCheckConfig {
@@ -26,4 +51,14 @@ export interface GracefulShutdownConfig {
26
51
  onShutdown?: () => Promise<void>;
27
52
  }
28
53
 
54
+ export interface SocketInstance {
55
+ id: string;
56
+ emit: (event: string, data?: unknown) => void;
57
+ on: (event: string, handler: (...args: unknown[]) => void) => void;
58
+ broadcast: {
59
+ emit: (event: string, data?: unknown) => void;
60
+ };
61
+ disconnect: () => void;
62
+ }
63
+
29
64
  export type ServerPlugin = (app: express.Application, config: ServerConfig) => void;
package/tsconfig.json CHANGED
@@ -3,14 +3,18 @@
3
3
  "target": "ES2020",
4
4
  "module": "CommonJS",
5
5
  "moduleResolution": "node",
6
- "outDir": "./dist",
7
6
  "rootDir": "./src",
7
+ "outDir": "./dist",
8
8
  "strict": true,
9
9
  "esModuleInterop": true,
10
10
  "allowSyntheticDefaultImports": true,
11
11
  "skipLibCheck": true,
12
12
  "forceConsistentCasingInFileNames": true,
13
- "declaration": true
13
+ "declaration": true,
14
+ "baseUrl": ".",
15
+ "paths": {
16
+ "*": ["*", "*.ts", "*.js"]
17
+ }
14
18
  },
15
19
  "include": ["src/**/*"],
16
20
  "exclude": ["node_modules", "dist"]