@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.
- package/README.md +147 -7
- package/dist/{index.d.ts → cjs/index.d.ts} +2 -3
- package/dist/{index.js → cjs/index.js} +1 -3
- package/dist/{middleware.js → cjs/middleware.js} +37 -10
- package/dist/{periodic-health.d.ts → cjs/periodic-health.d.ts} +0 -1
- package/dist/{periodic-health.js → cjs/periodic-health.js} +0 -4
- package/dist/{server.js → cjs/server.js} +1 -1
- package/dist/{utils.js → cjs/utils.js} +14 -8
- package/dist/esm/health.d.ts +5 -0
- package/dist/esm/health.js +40 -0
- package/dist/esm/index.d.ts +46 -0
- package/dist/esm/index.js +58 -0
- package/dist/esm/middleware.d.ts +37 -0
- package/dist/esm/middleware.js +229 -0
- package/dist/esm/periodic-health.d.ts +11 -0
- package/dist/esm/periodic-health.js +64 -0
- package/dist/esm/server.d.ts +69 -0
- package/dist/esm/server.js +271 -0
- package/dist/esm/shutdown.d.ts +5 -0
- package/dist/esm/shutdown.js +52 -0
- package/dist/esm/types.d.ts +70 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +38 -0
- package/dist/types/health.d.ts +5 -0
- package/dist/types/index.d.ts +46 -0
- package/dist/types/middleware.d.ts +37 -0
- package/dist/types/periodic-health.d.ts +11 -0
- package/dist/types/server.d.ts +69 -0
- package/dist/types/shutdown.d.ts +5 -0
- package/dist/types/types.d.ts +70 -0
- package/dist/types/utils.d.ts +3 -0
- package/package.json +22 -7
- package/src/health.ts +0 -47
- package/src/index.ts +0 -127
- package/src/middleware.ts +0 -275
- package/src/periodic-health.ts +0 -87
- package/src/server.ts +0 -412
- package/src/shutdown.ts +0 -69
- package/src/types.ts +0 -80
- package/src/utils.ts +0 -34
- package/tsconfig.json +0 -21
- /package/dist/{health.d.ts → cjs/health.d.ts} +0 -0
- /package/dist/{health.js → cjs/health.js} +0 -0
- /package/dist/{middleware.d.ts → cjs/middleware.d.ts} +0 -0
- /package/dist/{server.d.ts → cjs/server.d.ts} +0 -0
- /package/dist/{shutdown.d.ts → cjs/shutdown.d.ts} +0 -0
- /package/dist/{shutdown.js → cjs/shutdown.js} +0 -0
- /package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
- /package/dist/{types.js → cjs/types.js} +0 -0
- /package/dist/{utils.d.ts → cjs/utils.d.ts} +0 -0
package/src/server.ts
DELETED
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { Server } from 'http';
|
|
3
|
-
import { ServerConfig, SocketIOConfig, SocketInstance } from './types';
|
|
4
|
-
import { createGracefulShutdown } from './shutdown';
|
|
5
|
-
import { PeriodicHealthMonitor, createPeriodicHealthMonitor } from './periodic-health';
|
|
6
|
-
import crypto from 'crypto';
|
|
7
|
-
|
|
8
|
-
export interface GrpcService {
|
|
9
|
-
service: Record<string, unknown>;
|
|
10
|
-
implementation: Record<string, (...args: unknown[]) => unknown>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface RpcMethod {
|
|
14
|
-
[key: string]: (params: unknown[], callback: (error: Error | null, result?: unknown) => void) => void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface WebhookConfig {
|
|
18
|
-
path: string;
|
|
19
|
-
secret?: string;
|
|
20
|
-
handler: (payload: Record<string, unknown>, headers: Record<string, string | string[]>) => void | Promise<void>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface GrpcServerInstance {
|
|
24
|
-
start(): void;
|
|
25
|
-
forceShutdown(): void;
|
|
26
|
-
addService(service: unknown, implementation: unknown): void;
|
|
27
|
-
bindAsync(address: string, credentials: unknown, callback: () => void): void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export interface ServerInstanceConfig extends Required<Omit<ServerConfig, 'socketIO' | 'name' | 'version'>> {
|
|
33
|
-
name: string;
|
|
34
|
-
version: string;
|
|
35
|
-
startTime: Date;
|
|
36
|
-
socketIO?: SocketIOConfig;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface ServerInstance {
|
|
40
|
-
app: express.Application;
|
|
41
|
-
server?: Server;
|
|
42
|
-
config: ServerInstanceConfig;
|
|
43
|
-
start(): Promise<ServerInstance>;
|
|
44
|
-
stop(): Promise<void>;
|
|
45
|
-
getInfo(): ServerInfo;
|
|
46
|
-
|
|
47
|
-
// Multi-protocol support
|
|
48
|
-
addGrpcService(service: Record<string, unknown>, implementation: Record<string, (...args: unknown[]) => unknown>, port?: number): void;
|
|
49
|
-
addRpcMethods(methods: RpcMethod, path?: string): void;
|
|
50
|
-
addWebhook(config: WebhookConfig): void;
|
|
51
|
-
addSocketIO(config?: SocketIOConfig): unknown;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface ServerInfo {
|
|
55
|
-
name: string;
|
|
56
|
-
version: string;
|
|
57
|
-
port: number;
|
|
58
|
-
uptime: number;
|
|
59
|
-
status: 'starting' | 'running' | 'stopping' | 'stopped';
|
|
60
|
-
startTime: Date;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export class ExpressServer implements ServerInstance {
|
|
64
|
-
public app: express.Application;
|
|
65
|
-
public server?: Server;
|
|
66
|
-
public config: ServerInstanceConfig;
|
|
67
|
-
private status: 'starting' | 'running' | 'stopping' | 'stopped' = 'stopped';
|
|
68
|
-
private grpcServices: GrpcService[] = [];
|
|
69
|
-
private grpcServer?: GrpcServerInstance;
|
|
70
|
-
private rpcMethods: RpcMethod = {};
|
|
71
|
-
private socketIO?: { close(): void };
|
|
72
|
-
private healthMonitor?: PeriodicHealthMonitor;
|
|
73
|
-
|
|
74
|
-
constructor(
|
|
75
|
-
name: string = 'Express Server',
|
|
76
|
-
version: string = '1.0.0',
|
|
77
|
-
config: ServerConfig = {}
|
|
78
|
-
) {
|
|
79
|
-
this.app = express();
|
|
80
|
-
this.config = {
|
|
81
|
-
name,
|
|
82
|
-
version,
|
|
83
|
-
startTime: new Date(),
|
|
84
|
-
port: config.port || 3000,
|
|
85
|
-
cors: config.cors ?? true,
|
|
86
|
-
helmet: config.helmet ?? true,
|
|
87
|
-
json: config.json ?? true,
|
|
88
|
-
cookieParser: config.cookieParser ?? false,
|
|
89
|
-
customMiddleware: config.customMiddleware || [],
|
|
90
|
-
healthCheck: config.healthCheck ?? true,
|
|
91
|
-
gracefulShutdown: config.gracefulShutdown ?? true,
|
|
92
|
-
socketIO: config.socketIO,
|
|
93
|
-
periodicHealthCheck: config.periodicHealthCheck || { enabled: false }
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Apply middleware based on configuration
|
|
97
|
-
this.setupMiddleware();
|
|
98
|
-
|
|
99
|
-
// Setup periodic health monitoring
|
|
100
|
-
this.setupPeriodicHealthMonitoring();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private setupMiddleware(): void {
|
|
104
|
-
// Apply CORS if enabled
|
|
105
|
-
if (this.config.cors) {
|
|
106
|
-
try {
|
|
107
|
-
const cors = require('cors');
|
|
108
|
-
const corsOptions = typeof this.config.cors === 'object' ? this.config.cors : undefined;
|
|
109
|
-
this.app.use(cors(corsOptions));
|
|
110
|
-
} catch (error) {
|
|
111
|
-
console.warn(`${this.config.name}: CORS middleware not available. Install cors package.`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Apply Helmet if enabled
|
|
116
|
-
if (this.config.helmet) {
|
|
117
|
-
try {
|
|
118
|
-
const helmet = require('helmet');
|
|
119
|
-
this.app.use(helmet());
|
|
120
|
-
} catch (error) {
|
|
121
|
-
console.warn(`${this.config.name}: Helmet middleware not available. Install helmet package.`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Apply JSON parser if enabled
|
|
126
|
-
if (this.config.json) {
|
|
127
|
-
this.app.use(express.json());
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Apply cookie parser if enabled
|
|
131
|
-
if (this.config.cookieParser) {
|
|
132
|
-
try {
|
|
133
|
-
const cookieParser = require('cookie-parser');
|
|
134
|
-
this.app.use(cookieParser());
|
|
135
|
-
} catch (error) {
|
|
136
|
-
console.warn(`${this.config.name}: Cookie parser middleware not available. Install cookie-parser package.`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Apply custom middleware
|
|
141
|
-
if (this.config.customMiddleware && this.config.customMiddleware.length > 0) {
|
|
142
|
-
this.config.customMiddleware.forEach(middleware => {
|
|
143
|
-
this.app.use(middleware);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Add health check if enabled
|
|
148
|
-
if (this.config.healthCheck) {
|
|
149
|
-
const healthPath = typeof this.config.healthCheck === 'string' ? this.config.healthCheck : '/health';
|
|
150
|
-
this.app.get(healthPath, (req, res) => {
|
|
151
|
-
res.status(200).json({
|
|
152
|
-
status: 'healthy',
|
|
153
|
-
service: this.config.name,
|
|
154
|
-
version: this.config.version,
|
|
155
|
-
uptime: Date.now() - this.config.startTime.getTime(),
|
|
156
|
-
timestamp: new Date().toISOString()
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private setupPeriodicHealthMonitoring(): void {
|
|
163
|
-
if (this.config.periodicHealthCheck?.enabled) {
|
|
164
|
-
this.healthMonitor = createPeriodicHealthMonitor(
|
|
165
|
-
this.config.periodicHealthCheck,
|
|
166
|
-
this.config.name
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async start(): Promise<ServerInstance> {
|
|
172
|
-
this.status = 'starting';
|
|
173
|
-
|
|
174
|
-
return new Promise((resolve, reject) => {
|
|
175
|
-
try {
|
|
176
|
-
this.server = this.app.listen(this.config.port, () => {
|
|
177
|
-
this.status = 'running';
|
|
178
|
-
console.log(`🚀 ${this.config.name} v${this.config.version} running on http://localhost:${this.config.port}`);
|
|
179
|
-
|
|
180
|
-
if (this.config.gracefulShutdown) {
|
|
181
|
-
createGracefulShutdown(this.server!, {
|
|
182
|
-
onShutdown: async () => {
|
|
183
|
-
this.status = 'stopping';
|
|
184
|
-
// Stop health monitoring during shutdown
|
|
185
|
-
if (this.healthMonitor) {
|
|
186
|
-
this.healthMonitor.stop();
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Start periodic health monitoring after server is running
|
|
193
|
-
if (this.healthMonitor) {
|
|
194
|
-
this.healthMonitor.start();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
resolve(this);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
this.server.on('error', reject);
|
|
201
|
-
} catch (error: unknown) {
|
|
202
|
-
this.status = 'stopped';
|
|
203
|
-
reject(error);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async stop(): Promise<void> {
|
|
209
|
-
this.status = 'stopping';
|
|
210
|
-
|
|
211
|
-
// Stop gRPC server if running
|
|
212
|
-
if (this.grpcServer) {
|
|
213
|
-
this.grpcServer.forceShutdown();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Stop periodic health monitoring
|
|
217
|
-
if (this.healthMonitor) {
|
|
218
|
-
this.healthMonitor.stop();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Stop Socket.IO server if running
|
|
222
|
-
if (this.socketIO) {
|
|
223
|
-
this.socketIO.close();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (!this.server) {
|
|
227
|
-
this.status = 'stopped';
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return new Promise((resolve) => {
|
|
232
|
-
this.server!.close(() => {
|
|
233
|
-
this.status = 'stopped';
|
|
234
|
-
console.log(`👋 ${this.config.name} stopped`);
|
|
235
|
-
resolve();
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
getInfo(): ServerInfo {
|
|
241
|
-
return {
|
|
242
|
-
name: this.config.name,
|
|
243
|
-
version: this.config.version,
|
|
244
|
-
port: this.config.port,
|
|
245
|
-
uptime: Date.now() - this.config.startTime.getTime(),
|
|
246
|
-
status: this.status,
|
|
247
|
-
startTime: this.config.startTime
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
addGrpcService(service: Record<string, unknown>, implementation: Record<string, (...args: unknown[]) => unknown>, port: number = 50051): void {
|
|
252
|
-
this.grpcServices.push({ service, implementation });
|
|
253
|
-
|
|
254
|
-
// Lazy load gRPC to avoid dependency issues
|
|
255
|
-
if (!this.grpcServer) {
|
|
256
|
-
try {
|
|
257
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
258
|
-
const grpc = require('@grpc/grpc-js') as {
|
|
259
|
-
Server: new () => {
|
|
260
|
-
start(): void;
|
|
261
|
-
forceShutdown(): void;
|
|
262
|
-
addService(service: unknown, implementation: unknown): void;
|
|
263
|
-
bindAsync(address: string, credentials: unknown, callback: () => void): void;
|
|
264
|
-
};
|
|
265
|
-
ServerCredentials: { createInsecure(): unknown };
|
|
266
|
-
};
|
|
267
|
-
this.grpcServer = new grpc.Server();
|
|
268
|
-
|
|
269
|
-
// Add all services
|
|
270
|
-
this.grpcServices.forEach(({ service, implementation }) => {
|
|
271
|
-
this.grpcServer!.addService(service, implementation);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
this.grpcServer.bindAsync(
|
|
275
|
-
`0.0.0.0:${port}`,
|
|
276
|
-
grpc.ServerCredentials.createInsecure(),
|
|
277
|
-
() => {
|
|
278
|
-
this.grpcServer!.start();
|
|
279
|
-
console.log(`🔗 ${this.config.name} gRPC server running on port ${port}`);
|
|
280
|
-
}
|
|
281
|
-
);
|
|
282
|
-
} catch (error: unknown) {
|
|
283
|
-
console.warn(`${this.config.name}: gRPC not available. Install @grpc/grpc-js to use gRPC features.`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
addRpcMethods(methods: RpcMethod, path: string = '/rpc'): void {
|
|
289
|
-
Object.assign(this.rpcMethods, methods);
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
293
|
-
const jayson = require('jayson') as {
|
|
294
|
-
server: (methods: RpcMethod) => {
|
|
295
|
-
middleware(): express.RequestHandler;
|
|
296
|
-
};
|
|
297
|
-
};
|
|
298
|
-
const rpcServer = jayson.server(this.rpcMethods);
|
|
299
|
-
this.app.use(path, rpcServer.middleware());
|
|
300
|
-
console.log(`📡 ${this.config.name} JSON-RPC server mounted on ${path}`);
|
|
301
|
-
} catch (error: unknown) {
|
|
302
|
-
console.warn(`${this.config.name}: JSON-RPC not available. Install jayson to use RPC features.`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
addWebhook(config: WebhookConfig): void {
|
|
307
|
-
this.app.post(config.path, express.raw({ type: 'application/json' }), async (req, res) => {
|
|
308
|
-
try {
|
|
309
|
-
// Verify signature if secret provided
|
|
310
|
-
if (config.secret) {
|
|
311
|
-
const signature = req.headers['x-hub-signature-256'] || req.headers['x-signature-256'];
|
|
312
|
-
if (signature) {
|
|
313
|
-
const expectedSignature = crypto
|
|
314
|
-
.createHmac('sha256', config.secret)
|
|
315
|
-
.update(req.body)
|
|
316
|
-
.digest('hex');
|
|
317
|
-
|
|
318
|
-
const providedSignature = Array.isArray(signature) ? signature[0] : signature;
|
|
319
|
-
if (!providedSignature.includes(expectedSignature)) {
|
|
320
|
-
return res.status(401).json({ error: 'Invalid signature' });
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Parse JSON payload
|
|
326
|
-
const payload = JSON.parse(req.body.toString());
|
|
327
|
-
|
|
328
|
-
// Call handler
|
|
329
|
-
await config.handler(payload, req.headers as Record<string, string | string[]>);
|
|
330
|
-
|
|
331
|
-
res.status(200).json({ success: true });
|
|
332
|
-
} catch (error: unknown) {
|
|
333
|
-
console.error('Webhook error:', error);
|
|
334
|
-
res.status(500).json({ error: 'Webhook processing failed' });
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
console.log(`🪝 ${this.config.name} webhook registered at ${config.path}${config.secret ? ' (with signature verification)' : ''}`);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
addSocketIO(config: SocketIOConfig = {}): unknown {
|
|
342
|
-
if (!this.server) {
|
|
343
|
-
throw new Error(`${this.config.name}: Server must be started before adding Socket.IO`);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
try {
|
|
347
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
348
|
-
const { Server } = require('socket.io') as {
|
|
349
|
-
Server: new (server: Server, options?: {
|
|
350
|
-
cors?: {
|
|
351
|
-
origin?: string | string[] | boolean;
|
|
352
|
-
methods?: string[];
|
|
353
|
-
credentials?: boolean;
|
|
354
|
-
};
|
|
355
|
-
path?: string;
|
|
356
|
-
}) => {
|
|
357
|
-
on: (event: string, handler: (socket: unknown) => void) => void;
|
|
358
|
-
close: () => void;
|
|
359
|
-
};
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
// Configure CORS
|
|
363
|
-
const corsConfig = config.cors === true
|
|
364
|
-
? { origin: '*', methods: ['GET', 'POST'] }
|
|
365
|
-
: config.cors || undefined;
|
|
366
|
-
|
|
367
|
-
// Create Socket.IO server
|
|
368
|
-
const io = new Server(this.server, {
|
|
369
|
-
cors: config.cors ? corsConfig : undefined,
|
|
370
|
-
path: config.path || '/socket.io'
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
// Store reference for cleanup
|
|
374
|
-
this.socketIO = io;
|
|
375
|
-
|
|
376
|
-
// Handle connections
|
|
377
|
-
io.on('connection', (socket: unknown) => {
|
|
378
|
-
const typedSocket = socket as SocketInstance;
|
|
379
|
-
console.log(`🔌 ${this.config.name}: Socket connected [${typedSocket.id}]`);
|
|
380
|
-
|
|
381
|
-
// Call user-defined connection handler
|
|
382
|
-
if (config.onConnection) {
|
|
383
|
-
config.onConnection(socket);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Handle disconnection
|
|
387
|
-
typedSocket.on('disconnect', (reason) => {
|
|
388
|
-
console.log(`🔌 ${this.config.name}: Socket disconnected [${typedSocket.id}] - ${reason}`);
|
|
389
|
-
|
|
390
|
-
// Call user-defined disconnection handler
|
|
391
|
-
if (config.onDisconnection) {
|
|
392
|
-
config.onDisconnection(socket, reason as string);
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
console.log(`🔌 ${this.config.name} Socket.IO server attached${config.path ? ` at ${config.path}` : ''}${config.cors ? ' (CORS enabled)' : ''}`);
|
|
398
|
-
return io;
|
|
399
|
-
} catch (error: unknown) {
|
|
400
|
-
console.warn(`${this.config.name}: Socket.IO not available. Install socket.io to use WebSocket features.`);
|
|
401
|
-
return null;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
export function createServer(
|
|
407
|
-
name?: string,
|
|
408
|
-
version?: string,
|
|
409
|
-
config?: ServerConfig
|
|
410
|
-
): ServerInstance {
|
|
411
|
-
return new ExpressServer(name, version, config);
|
|
412
|
-
}
|
package/src/shutdown.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Server } from 'http';
|
|
2
|
-
import { GracefulShutdownConfig, ServerPlugin } from './types';
|
|
3
|
-
|
|
4
|
-
export function createGracefulShutdown(server: Server, config: GracefulShutdownConfig = {}): void {
|
|
5
|
-
const { timeout = 10000, onShutdown, serverName, serverVersion } = config;
|
|
6
|
-
const nameVersion = serverName && serverVersion ? `${serverName} v${serverVersion}` : 'Server';
|
|
7
|
-
|
|
8
|
-
const shutdown = async (signal: string) => {
|
|
9
|
-
console.log(`🛑 ${nameVersion} received ${signal}, shutting down gracefully...`);
|
|
10
|
-
|
|
11
|
-
const shutdownTimer = setTimeout(() => {
|
|
12
|
-
console.log(`⏰ ${nameVersion} shutdown timeout reached, forcing exit`);
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}, timeout);
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
// Run custom shutdown logic
|
|
18
|
-
if (onShutdown) {
|
|
19
|
-
await onShutdown();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Close server
|
|
23
|
-
server.close(() => {
|
|
24
|
-
clearTimeout(shutdownTimer);
|
|
25
|
-
console.log(`👋 ${nameVersion} closed. Exiting now.`);
|
|
26
|
-
process.exit(0);
|
|
27
|
-
});
|
|
28
|
-
} catch (error) {
|
|
29
|
-
clearTimeout(shutdownTimer);
|
|
30
|
-
console.error(`❌ ${nameVersion} error during shutdown:`, error);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
36
|
-
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function withGracefulShutdown(config: GracefulShutdownConfig = {}): ServerPlugin {
|
|
40
|
-
return (app, serverConfig) => {
|
|
41
|
-
// This plugin needs to be applied after server.listen()
|
|
42
|
-
// Store config for later use
|
|
43
|
-
(app as any).__gracefulShutdownConfig = config;
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function startServerWithShutdown(
|
|
48
|
-
app: any,
|
|
49
|
-
port: number,
|
|
50
|
-
shutdownConfig: GracefulShutdownConfig = {},
|
|
51
|
-
serverName?: string,
|
|
52
|
-
serverVersion?: string
|
|
53
|
-
): Server {
|
|
54
|
-
const server = app.listen(port, () => {
|
|
55
|
-
const nameVersion = serverName && serverVersion ? `${serverName} v${serverVersion}` : 'Server';
|
|
56
|
-
console.log(`🚀 ${nameVersion} running on http://localhost:${port}`);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Apply graceful shutdown from stored config or provided config
|
|
60
|
-
const config = app.__gracefulShutdownConfig || shutdownConfig;
|
|
61
|
-
const enhancedConfig = {
|
|
62
|
-
...config,
|
|
63
|
-
serverName,
|
|
64
|
-
serverVersion
|
|
65
|
-
};
|
|
66
|
-
createGracefulShutdown(server, enhancedConfig);
|
|
67
|
-
|
|
68
|
-
return server;
|
|
69
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
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
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ServerConfig {
|
|
15
|
-
port?: number;
|
|
16
|
-
cors?: boolean | CorsOptions;
|
|
17
|
-
helmet?: boolean;
|
|
18
|
-
json?: boolean;
|
|
19
|
-
cookieParser?: boolean;
|
|
20
|
-
customMiddleware?: express.RequestHandler[];
|
|
21
|
-
healthCheck?: boolean | string;
|
|
22
|
-
gracefulShutdown?: boolean;
|
|
23
|
-
socketIO?: SocketIOConfig;
|
|
24
|
-
periodicHealthCheck?: PeriodicHealthCheckConfig;
|
|
25
|
-
name?: string;
|
|
26
|
-
version?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface PeriodicHealthCheckConfig {
|
|
30
|
-
enabled?: boolean;
|
|
31
|
-
interval?: number;
|
|
32
|
-
services?: HealthCheckService[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface HealthCheckService {
|
|
36
|
-
name: string;
|
|
37
|
-
url: string;
|
|
38
|
-
timeout?: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface SocketIOConfig {
|
|
42
|
-
enabled?: boolean;
|
|
43
|
-
cors?: boolean | {
|
|
44
|
-
origin?: string | string[] | boolean;
|
|
45
|
-
methods?: string[];
|
|
46
|
-
credentials?: boolean;
|
|
47
|
-
};
|
|
48
|
-
onConnection?: (socket: unknown) => void;
|
|
49
|
-
onDisconnection?: (socket: unknown, reason: string) => void;
|
|
50
|
-
path?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface HealthCheckConfig {
|
|
54
|
-
path?: string;
|
|
55
|
-
customChecks?: HealthCheck[];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export interface HealthCheck {
|
|
59
|
-
name: string;
|
|
60
|
-
check: () => Promise<boolean>;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface GracefulShutdownConfig {
|
|
64
|
-
timeout?: number;
|
|
65
|
-
onShutdown?: () => Promise<void>;
|
|
66
|
-
serverName?: string;
|
|
67
|
-
serverVersion?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface SocketInstance {
|
|
71
|
-
id: string;
|
|
72
|
-
emit: (event: string, data?: unknown) => void;
|
|
73
|
-
on: (event: string, handler: (...args: unknown[]) => void) => void;
|
|
74
|
-
broadcast: {
|
|
75
|
-
emit: (event: string, data?: unknown) => void;
|
|
76
|
-
};
|
|
77
|
-
disconnect: () => void;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export type ServerPlugin = (app: express.Application, config: ServerConfig) => void;
|
package/src/utils.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// Environment utilities
|
|
2
|
-
export function getEnv(key: string, defaultValue?: string): string {
|
|
3
|
-
const value = process.env[key];
|
|
4
|
-
if (value === undefined && defaultValue === undefined) {
|
|
5
|
-
throw new Error(`Environment variable ${key} is required`);
|
|
6
|
-
}
|
|
7
|
-
return value || defaultValue!;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function getEnvNumber(key: string, defaultValue?: number): number {
|
|
11
|
-
const value = process.env[key];
|
|
12
|
-
if (value === undefined) {
|
|
13
|
-
if (defaultValue === undefined) {
|
|
14
|
-
throw new Error(`Environment variable ${key} is required`);
|
|
15
|
-
}
|
|
16
|
-
return defaultValue;
|
|
17
|
-
}
|
|
18
|
-
const parsed = parseInt(value, 10);
|
|
19
|
-
if (isNaN(parsed)) {
|
|
20
|
-
throw new Error(`Environment variable ${key} must be a number`);
|
|
21
|
-
}
|
|
22
|
-
return parsed;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getEnvBoolean(key: string, defaultValue?: boolean): boolean {
|
|
26
|
-
const value = process.env[key];
|
|
27
|
-
if (value === undefined) {
|
|
28
|
-
if (defaultValue === undefined) {
|
|
29
|
-
throw new Error(`Environment variable ${key} is required`);
|
|
30
|
-
}
|
|
31
|
-
return defaultValue;
|
|
32
|
-
}
|
|
33
|
-
return value.toLowerCase() === 'true';
|
|
34
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "CommonJS",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"rootDir": "./src",
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"allowSyntheticDefaultImports": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"baseUrl": ".",
|
|
15
|
-
"paths": {
|
|
16
|
-
"*": ["*", "*.ts", "*.js"]
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/**/*"],
|
|
20
|
-
"exclude": ["node_modules", "dist"]
|
|
21
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|