@naman_deep_singh/server-utils 1.1.0 → 1.3.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 +271 -62
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/middleware.d.ts +2 -0
- package/dist/cjs/middleware.js +102 -0
- package/dist/cjs/server.d.ts +3 -0
- package/dist/cjs/server.js +117 -4
- package/dist/cjs/shutdown.d.ts +1 -1
- package/dist/cjs/types.d.ts +17 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/middleware.d.ts +2 -0
- package/dist/esm/middleware.js +100 -0
- package/dist/esm/server.d.ts +3 -0
- package/dist/esm/server.js +117 -4
- package/dist/esm/shutdown.d.ts +1 -1
- package/dist/esm/types.d.ts +17 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/middleware.d.ts +2 -0
- package/dist/types/server.d.ts +3 -0
- package/dist/types/shutdown.d.ts +1 -1
- package/dist/types/types.d.ts +17 -0
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @naman_deep_singh/server-utils
|
|
2
2
|
|
|
3
|
-
**Version:** 1.
|
|
3
|
+
**Version:** 1.3.0 (with integrated cache & session support)
|
|
4
4
|
|
|
5
|
-
Extensible server utilities for Express.js microservices with multi-protocol support and TypeScript.
|
|
5
|
+
Extensible server utilities for Express.js microservices with multi-protocol support, integrated caching, session management, and TypeScript.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -13,17 +13,19 @@ npm install @naman_deep_singh/server-utils
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
15
|
- ✅ **Multi-protocol support** - HTTP, gRPC, JSON-RPC, WebSockets, Webhooks
|
|
16
|
+
- ✅ **Integrated caching** - Redis, Memcache, in-memory with automatic fallback
|
|
17
|
+
- ✅ **Session management** - Distributed session store with configurable TTL
|
|
16
18
|
- ✅ **Express.js integration** with middleware collection
|
|
17
|
-
- ✅ **Graceful shutdown** handling
|
|
18
|
-
- ✅ **Health checks** with custom checks
|
|
19
|
+
- ✅ **Graceful shutdown** handling with cache/session cleanup
|
|
20
|
+
- ✅ **Health checks** with custom checks and cache health integration
|
|
19
21
|
- ✅ **TypeScript support** with full type safety
|
|
20
|
-
|
|
21
|
-
- ✅ **
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
- ✅ **Built-in middleware** - logging, validation, rate limiting, auth, caching, sessions
|
|
24
|
+
|
|
23
25
|
|
|
24
26
|
## Quick Start
|
|
25
27
|
|
|
26
|
-
###
|
|
28
|
+
### Basic Usage
|
|
27
29
|
```typescript
|
|
28
30
|
import { createServer } from '@naman_deep_singh/server-utils';
|
|
29
31
|
|
|
@@ -42,13 +44,6 @@ server.app.get('/users', (req, res) => {
|
|
|
42
44
|
await server.start();
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
### Namespace Import
|
|
46
|
-
```typescript
|
|
47
|
-
import ServerUtils from '@naman_deep_singh/server-utils';
|
|
48
|
-
|
|
49
|
-
const server = ServerUtils.createServer('My API', '1.0.0');
|
|
50
|
-
```
|
|
51
|
-
|
|
52
47
|
## Multi-Protocol Support
|
|
53
48
|
|
|
54
49
|
### HTTP + Express Routes
|
|
@@ -129,17 +124,13 @@ server.addWebhook({
|
|
|
129
124
|
|
|
130
125
|
## Built-in Middleware
|
|
131
126
|
|
|
127
|
+
|
|
132
128
|
### Logging Middleware
|
|
133
129
|
```typescript
|
|
134
|
-
import { createLoggingMiddleware
|
|
130
|
+
import { createLoggingMiddleware } from '@naman_deep_singh/server-utils';
|
|
135
131
|
|
|
136
132
|
// Direct usage
|
|
137
133
|
server.app.use(createLoggingMiddleware('detailed'));
|
|
138
|
-
|
|
139
|
-
// Plugin usage
|
|
140
|
-
const server = createServerWithPlugins('My API', '1.0.0', [
|
|
141
|
-
withLogging('detailed')
|
|
142
|
-
]);
|
|
143
134
|
```
|
|
144
135
|
|
|
145
136
|
### Validation Middleware
|
|
@@ -182,52 +173,16 @@ server.app.use('/protected', requireAuth({
|
|
|
182
173
|
|
|
183
174
|
## Health Checks
|
|
184
175
|
|
|
176
|
+
|
|
185
177
|
### Basic Health Check
|
|
186
178
|
```typescript
|
|
187
|
-
import {
|
|
179
|
+
import { createHealthCheck } from '@naman_deep_singh/server-utils';
|
|
188
180
|
|
|
189
|
-
//
|
|
190
|
-
|
|
181
|
+
// The health check is automatically enabled when healthCheck is not false
|
|
182
|
+
// Health endpoint is available at /health by default
|
|
191
183
|
|
|
192
|
-
//
|
|
184
|
+
// If you need to customize or disable it:
|
|
193
185
|
server.app.get('/health', createHealthCheck());
|
|
194
|
-
|
|
195
|
-
// Method 3: As plugin
|
|
196
|
-
const server = createServerWithPlugins('My API', '1.0.0', [
|
|
197
|
-
withHealthCheck('/health')
|
|
198
|
-
]);
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Advanced Health Checks
|
|
202
|
-
```typescript
|
|
203
|
-
addHealthCheck(server.app, '/health', {
|
|
204
|
-
customChecks: [
|
|
205
|
-
{
|
|
206
|
-
name: 'database',
|
|
207
|
-
check: async () => {
|
|
208
|
-
// Check database connection
|
|
209
|
-
return await db.ping();
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
name: 'redis',
|
|
214
|
-
check: async () => {
|
|
215
|
-
return await redis.ping() === 'PONG';
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
]
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// Response format:
|
|
222
|
-
// {
|
|
223
|
-
// "status": "healthy",
|
|
224
|
-
// "checks": {
|
|
225
|
-
// "server": true,
|
|
226
|
-
// "timestamp": 1640995200000,
|
|
227
|
-
// "database": true,
|
|
228
|
-
// "redis": false
|
|
229
|
-
// }
|
|
230
|
-
// }
|
|
231
186
|
```
|
|
232
187
|
|
|
233
188
|
## Server Management
|
|
@@ -330,6 +285,12 @@ import { Request, Response, NextFunction, Router, Application } from '@naman_dee
|
|
|
330
285
|
import type { RequestHandler, ErrorRequestHandler } from '@naman_deep_singh/server-utils';
|
|
331
286
|
|
|
332
287
|
// No need to install Express separately in your services
|
|
288
|
+
|
|
289
|
+
## TypeScript Notes
|
|
290
|
+
|
|
291
|
+
- This package includes TypeScript augmentations for Express `Request` and `Application` that expose runtime helpers used by the middleware (for example `req.cache`, `req.sessionStore`, `req.getSession`, and `req.createSession`). Installing and importing `@naman_deep_singh/server-utils` in your project will surface these types automatically.
|
|
292
|
+
- Middleware that attaches runtime props uses `unknown` internally and runtime guards — prefer the provided helpers rather than casting `req` to `any`.
|
|
293
|
+
|
|
333
294
|
```
|
|
334
295
|
|
|
335
296
|
## API Reference
|
|
@@ -418,4 +379,252 @@ server.app.get('/users', (req, res) => {
|
|
|
418
379
|
const responder = (res as any).responder();
|
|
419
380
|
return responder.okAndSend({ users: [] });
|
|
420
381
|
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Cache & Session Integration
|
|
385
|
+
|
|
386
|
+
Built-in support for distributed caching and session management (disabled by default).
|
|
387
|
+
|
|
388
|
+
### Enable Redis Cache
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const server = createServer('My API', '1.0.0', {
|
|
392
|
+
port: 3000,
|
|
393
|
+
cache: {
|
|
394
|
+
enabled: true,
|
|
395
|
+
adapter: 'redis',
|
|
396
|
+
options: {
|
|
397
|
+
host: 'localhost',
|
|
398
|
+
port: 6379,
|
|
399
|
+
password: 'your_password'
|
|
400
|
+
},
|
|
401
|
+
defaultTTL: 3600 // seconds
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Access cache in routes
|
|
406
|
+
server.app.get('/user/:id', async (req, res) => {
|
|
407
|
+
const key = `user:${req.params.id}`;
|
|
408
|
+
const cached = await (req as any).cache.get(key);
|
|
409
|
+
if (cached) return res.json(cached);
|
|
410
|
+
|
|
411
|
+
// Fetch from DB, then cache
|
|
412
|
+
const user = { id: req.params.id, name: 'John' };
|
|
413
|
+
await (req as any).cache.set(key, user, 3600);
|
|
414
|
+
return res.json(user);
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Enable Redis Cluster Cache
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
const server = createServer('My API', '1.0.0', {
|
|
422
|
+
port: 3000,
|
|
423
|
+
cache: {
|
|
424
|
+
enabled: true,
|
|
425
|
+
adapter: 'redis',
|
|
426
|
+
options: {
|
|
427
|
+
cluster: [
|
|
428
|
+
{ host: 'redis-node-1', port: 6379 },
|
|
429
|
+
{ host: 'redis-node-2', port: 6379 },
|
|
430
|
+
{ host: 'redis-node-3', port: 6379 }
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Enable Memcache
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
const server = createServer('My API', '1.0.0', {
|
|
441
|
+
port: 3000,
|
|
442
|
+
cache: {
|
|
443
|
+
enabled: true,
|
|
444
|
+
adapter: 'memcache',
|
|
445
|
+
options: {
|
|
446
|
+
servers: ['localhost:11211', 'localhost:11212']
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Enable Sessions
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
const server = createServer('My API', '1.0.0', {
|
|
456
|
+
port: 3000,
|
|
457
|
+
cache: {
|
|
458
|
+
enabled: true,
|
|
459
|
+
adapter: 'redis',
|
|
460
|
+
options: { host: 'localhost', port: 6379 }
|
|
461
|
+
},
|
|
462
|
+
session: {
|
|
463
|
+
enabled: true,
|
|
464
|
+
cookieName: 'my_app.sid', // Defaults to {servername}.sid
|
|
465
|
+
ttl: 3600, // 1 hour
|
|
466
|
+
cookieOptions: {
|
|
467
|
+
httpOnly: true,
|
|
468
|
+
secure: true, // HTTPS only
|
|
469
|
+
sameSite: 'strict'
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Use sessions in routes
|
|
475
|
+
server.app.post('/login', async (req, res) => {
|
|
476
|
+
const sessionStore = (req.app as any).locals.sessionStore;
|
|
477
|
+
const sessionId = Math.random().toString(36).substring(7);
|
|
478
|
+
|
|
479
|
+
await sessionStore.create(sessionId, {
|
|
480
|
+
userId: 123,
|
|
481
|
+
username: 'john_doe',
|
|
482
|
+
loginTime: new Date()
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
res.cookie((req.app as any).locals.sessionCookieName, sessionId, {
|
|
486
|
+
httpOnly: true,
|
|
487
|
+
secure: true
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
return res.json({ message: 'Logged in' });
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
server.app.get('/profile', async (req, res) => {
|
|
494
|
+
const sessionId = (req as any).sessionId;
|
|
495
|
+
if (!sessionId) return res.status(401).json({ error: 'No session' });
|
|
496
|
+
|
|
497
|
+
const session = await (req as any).getSession();
|
|
498
|
+
if (!session) return res.status(401).json({ error: 'Session expired' });
|
|
499
|
+
|
|
500
|
+
return res.json({ user: session.username, loginTime: session.loginTime });
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Per-Route Response Caching
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import { cacheResponse } from '@naman_deep_singh/server-utils';
|
|
508
|
+
|
|
509
|
+
// Cache GET response for 1 hour (3600 seconds)
|
|
510
|
+
server.app.get('/api/posts', cacheResponse(3600), (req, res) => {
|
|
511
|
+
// This endpoint's response is cached
|
|
512
|
+
res.json({ posts: [...] });
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Cache with default TTL from server config
|
|
516
|
+
server.app.get('/api/trending', cacheResponse(), (req, res) => {
|
|
517
|
+
res.json({ trending: [...] });
|
|
518
|
+
});
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Health Check with Cache Status
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
const server = createServer('My API', '1.0.0', {
|
|
525
|
+
healthCheck: '/health', // Automatic health endpoint
|
|
526
|
+
cache: { enabled: true, adapter: 'redis', ... }
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Health endpoint now includes cache status
|
|
530
|
+
// GET /health returns:
|
|
531
|
+
// {
|
|
532
|
+
// "status": "healthy",
|
|
533
|
+
// "service": "My API",
|
|
534
|
+
// "version": "1.0.0",
|
|
535
|
+
// "uptime": 12345,
|
|
536
|
+
// "timestamp": "2025-12-12T...",
|
|
537
|
+
// "cache": {
|
|
538
|
+
// "isAlive": true,
|
|
539
|
+
// "adapter": "redis",
|
|
540
|
+
// "timestamp": "2025-12-12T..."
|
|
541
|
+
// }
|
|
542
|
+
// }
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Cache Configuration Options
|
|
546
|
+
|
|
547
|
+
All configuration is optional — cache and session are disabled by default:
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
interface CacheConfig {
|
|
551
|
+
enabled?: boolean; // Default: false
|
|
552
|
+
adapter?: 'redis' | 'memcache' | 'memory'; // Default: 'memory'
|
|
553
|
+
options?: {
|
|
554
|
+
// Redis single instance
|
|
555
|
+
host?: string; // Default: 'localhost'
|
|
556
|
+
port?: number; // Default: 6379
|
|
557
|
+
username?: string;
|
|
558
|
+
password?: string;
|
|
559
|
+
db?: number; // 0-15
|
|
560
|
+
tls?: boolean;
|
|
561
|
+
|
|
562
|
+
// Redis cluster
|
|
563
|
+
cluster?: Array<{ host: string; port: number }> | {
|
|
564
|
+
nodes: Array<{ host: string; port: number }>;
|
|
565
|
+
options?: { maxRedirections?: number; ... };
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// Memcache
|
|
569
|
+
servers?: string | string[]; // e.g., 'localhost:11211'
|
|
570
|
+
|
|
571
|
+
namespace?: string; // Key prefix
|
|
572
|
+
ttl?: number; // Default TTL in seconds
|
|
573
|
+
};
|
|
574
|
+
defaultTTL?: number; // Fallback TTL for routes
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
interface SessionConfig {
|
|
578
|
+
enabled?: boolean; // Default: false
|
|
579
|
+
cookieName?: string; // Default: {servername}.sid
|
|
580
|
+
ttl?: number; // Default: 3600 (1 hour)
|
|
581
|
+
cookieOptions?: {
|
|
582
|
+
path?: string;
|
|
583
|
+
httpOnly?: boolean; // Default: true
|
|
584
|
+
secure?: boolean; // HTTPS only
|
|
585
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### Graceful Shutdown
|
|
591
|
+
|
|
592
|
+
Cache and session stores are automatically closed on graceful shutdown:
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
const server = createServer('My API', '1.0.0', {
|
|
596
|
+
gracefulShutdown: true, // Enabled by default
|
|
597
|
+
cache: { enabled: true, adapter: 'redis', ... },
|
|
598
|
+
session: { enabled: true, ... }
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// On SIGTERM/SIGINT, server will:
|
|
602
|
+
// 1. Stop accepting requests
|
|
603
|
+
// 2. Close cache connection (Redis/Memcache)
|
|
604
|
+
// 3. Close session store
|
|
605
|
+
// 4. Exit gracefully
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Feature Flags
|
|
609
|
+
|
|
610
|
+
All major features can be toggled independently:
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
const server = createServer('My API', '1.0.0', {
|
|
614
|
+
port: 3000,
|
|
615
|
+
cors: true, // Default: true
|
|
616
|
+
helmet: true, // Default: true
|
|
617
|
+
json: true, // Default: true
|
|
618
|
+
cookieParser: false, // Default: false
|
|
619
|
+
healthCheck: '/health', // Default: true
|
|
620
|
+
gracefulShutdown: true, // Default: true
|
|
621
|
+
cache: { enabled: false }, // Default: disabled
|
|
622
|
+
session: { enabled: false }, // Default: disabled
|
|
623
|
+
socketIO: { enabled: false }, // Default: disabled
|
|
624
|
+
periodicHealthCheck: { // Default: disabled
|
|
625
|
+
enabled: false,
|
|
626
|
+
interval: 30000,
|
|
627
|
+
services: [...]
|
|
628
|
+
}
|
|
629
|
+
});
|
|
421
630
|
```
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { Request, Response, NextFunction, Router, Application } from 'express';
|
|
|
4
4
|
export type { RequestHandler, ErrorRequestHandler } from 'express';
|
|
5
5
|
export { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
|
|
6
6
|
export { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
|
|
7
|
-
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, type ValidationRule, type RateLimitConfig, type AuthConfig } from './middleware';
|
|
7
|
+
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, cacheResponse, useSession, type ValidationRule, type RateLimitConfig, type AuthConfig } from './middleware';
|
|
8
8
|
export { getEnv, getEnvNumber, getEnvBoolean } from './utils';
|
|
9
9
|
export { PeriodicHealthMonitor } from './periodic-health';
|
|
10
10
|
export type { ServerConfig, HealthCheckConfig, HealthCheck, GracefulShutdownConfig, ServerPlugin, SocketIOConfig, SocketInstance, PeriodicHealthCheckConfig, HealthCheckService } from './types';
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PeriodicHealthMonitor = exports.getEnvBoolean = exports.getEnvNumber = exports.getEnv = exports.requireAuth = exports.rateLimit = exports.validateFields = exports.withAuth = exports.withRateLimit = exports.withValidation = exports.withRequestId = exports.withErrorHandler = exports.withLogging = exports.createAuthMiddleware = exports.createRateLimitMiddleware = exports.createValidationMiddleware = exports.createRequestIdMiddleware = exports.createErrorHandler = exports.createLoggingMiddleware = exports.startServerWithShutdown = exports.withGracefulShutdown = exports.createGracefulShutdown = exports.addHealthCheck = exports.withHealthCheck = exports.createHealthCheck = exports.Router = exports.createServer = exports.ExpressServer = void 0;
|
|
3
|
+
exports.PeriodicHealthMonitor = exports.getEnvBoolean = exports.getEnvNumber = exports.getEnv = exports.useSession = exports.cacheResponse = exports.requireAuth = exports.rateLimit = exports.validateFields = exports.withAuth = exports.withRateLimit = exports.withValidation = exports.withRequestId = exports.withErrorHandler = exports.withLogging = exports.createAuthMiddleware = exports.createRateLimitMiddleware = exports.createValidationMiddleware = exports.createRequestIdMiddleware = exports.createErrorHandler = exports.createLoggingMiddleware = exports.startServerWithShutdown = exports.withGracefulShutdown = exports.createGracefulShutdown = exports.addHealthCheck = exports.withHealthCheck = exports.createHealthCheck = exports.Router = exports.createServer = exports.ExpressServer = void 0;
|
|
4
4
|
// Core server utilities
|
|
5
5
|
var server_1 = require("./server");
|
|
6
6
|
Object.defineProperty(exports, "ExpressServer", { enumerable: true, get: function () { return server_1.ExpressServer; } });
|
|
@@ -35,6 +35,8 @@ Object.defineProperty(exports, "withAuth", { enumerable: true, get: function ()
|
|
|
35
35
|
Object.defineProperty(exports, "validateFields", { enumerable: true, get: function () { return middleware_1.validateFields; } });
|
|
36
36
|
Object.defineProperty(exports, "rateLimit", { enumerable: true, get: function () { return middleware_1.rateLimit; } });
|
|
37
37
|
Object.defineProperty(exports, "requireAuth", { enumerable: true, get: function () { return middleware_1.requireAuth; } });
|
|
38
|
+
Object.defineProperty(exports, "cacheResponse", { enumerable: true, get: function () { return middleware_1.cacheResponse; } });
|
|
39
|
+
Object.defineProperty(exports, "useSession", { enumerable: true, get: function () { return middleware_1.useSession; } });
|
|
38
40
|
// Utility functions
|
|
39
41
|
var utils_1 = require("./utils");
|
|
40
42
|
Object.defineProperty(exports, "getEnv", { enumerable: true, get: function () { return utils_1.getEnv; } });
|
package/dist/cjs/middleware.d.ts
CHANGED
|
@@ -35,3 +35,5 @@ export declare function withAuth(config: AuthConfig): ServerPlugin;
|
|
|
35
35
|
export declare function validateFields(rules: ValidationRule[]): express.RequestHandler;
|
|
36
36
|
export declare function rateLimit(config?: RateLimitConfig): express.RequestHandler;
|
|
37
37
|
export declare function requireAuth(config: AuthConfig): express.RequestHandler;
|
|
38
|
+
export declare function cacheResponse(ttl?: number): express.RequestHandler;
|
|
39
|
+
export declare function useSession(cookieName?: string): express.RequestHandler;
|
package/dist/cjs/middleware.js
CHANGED
|
@@ -15,6 +15,8 @@ exports.withAuth = withAuth;
|
|
|
15
15
|
exports.validateFields = validateFields;
|
|
16
16
|
exports.rateLimit = rateLimit;
|
|
17
17
|
exports.requireAuth = requireAuth;
|
|
18
|
+
exports.cacheResponse = cacheResponse;
|
|
19
|
+
exports.useSession = useSession;
|
|
18
20
|
// Logging middleware
|
|
19
21
|
function createLoggingMiddleware(format = 'simple') {
|
|
20
22
|
return (req, res, next) => {
|
|
@@ -244,3 +246,103 @@ function rateLimit(config = {}) {
|
|
|
244
246
|
function requireAuth(config) {
|
|
245
247
|
return createAuthMiddleware(config);
|
|
246
248
|
}
|
|
249
|
+
// Cache response middleware (per-route opt-in)
|
|
250
|
+
function cacheResponse(ttl) {
|
|
251
|
+
return async (req, res, next) => {
|
|
252
|
+
try {
|
|
253
|
+
if (req.method !== 'GET')
|
|
254
|
+
return next();
|
|
255
|
+
const cache = (req.cache ?? req.app.locals.cache);
|
|
256
|
+
const defaultTTL = req.app.locals.cacheDefaultTTL;
|
|
257
|
+
if (!cache || typeof cache.get !== 'function')
|
|
258
|
+
return next();
|
|
259
|
+
const key = `${req.originalUrl}`;
|
|
260
|
+
try {
|
|
261
|
+
const cached = await cache.get(key);
|
|
262
|
+
if (cached !== null && cached !== undefined) {
|
|
263
|
+
res.setHeader('X-Cache', 'HIT');
|
|
264
|
+
return res.json(cached);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (cacheErr) {
|
|
268
|
+
console.error(`[Cache] Failed to retrieve key "${key}":`, cacheErr);
|
|
269
|
+
// Continue without cache hit
|
|
270
|
+
}
|
|
271
|
+
const originalJson = res.json.bind(res);
|
|
272
|
+
res.json = (body) => {
|
|
273
|
+
try {
|
|
274
|
+
const expiry = ttl ?? defaultTTL;
|
|
275
|
+
if (expiry && cache && typeof cache.set === 'function') {
|
|
276
|
+
cache.set(key, body, expiry).catch((err) => {
|
|
277
|
+
console.error(`[Cache] Failed to set key "${key}" with TTL ${expiry}:`, err);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
else if (cache) {
|
|
281
|
+
if (typeof cache.set === 'function') {
|
|
282
|
+
cache.set(key, body).catch((err) => {
|
|
283
|
+
console.error(`[Cache] Failed to set key "${key}":`, err);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
console.error(`[Cache] Error during cache.set operation:`, e);
|
|
290
|
+
}
|
|
291
|
+
res.setHeader('X-Cache', 'MISS');
|
|
292
|
+
return originalJson(body);
|
|
293
|
+
};
|
|
294
|
+
next();
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
console.error('[Cache] Unexpected error in cacheResponse middleware:', err);
|
|
298
|
+
next();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// Session middleware helper (attaches sessionStore and helpers to req)
|
|
303
|
+
function useSession(cookieName) {
|
|
304
|
+
return async (req, res, next) => {
|
|
305
|
+
try {
|
|
306
|
+
const store = req.app.locals.sessionStore;
|
|
307
|
+
if (!store)
|
|
308
|
+
return next();
|
|
309
|
+
const name = cookieName || req.app.locals.sessionCookieName || 'sid';
|
|
310
|
+
let sid = req.cookies?.[name];
|
|
311
|
+
if (!sid) {
|
|
312
|
+
const cookieHeader = req.headers.cookie;
|
|
313
|
+
if (cookieHeader) {
|
|
314
|
+
const match = cookieHeader.split(';').map(s => s.trim()).find(s => s.startsWith(name + '='));
|
|
315
|
+
if (match)
|
|
316
|
+
sid = match.split('=')[1];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
req.sessionId = sid;
|
|
320
|
+
req.sessionStore = store;
|
|
321
|
+
req.getSession = async () => {
|
|
322
|
+
if (!sid)
|
|
323
|
+
return null;
|
|
324
|
+
try {
|
|
325
|
+
return await store.get(sid);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
console.error(`[Session] Failed to get session "${sid}":`, err);
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
req.createSession = async (id, data, ttl) => {
|
|
333
|
+
try {
|
|
334
|
+
return await store.create(id, data, ttl);
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
console.error(`[Session] Failed to create session "${id}":`, err);
|
|
338
|
+
throw err;
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
next();
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
console.error('[Session] Unexpected error in useSession middleware:', err);
|
|
345
|
+
next();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
package/dist/cjs/server.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export declare class ExpressServer implements ServerInstance {
|
|
|
49
49
|
app: express.Application;
|
|
50
50
|
server?: Server;
|
|
51
51
|
config: ServerInstanceConfig;
|
|
52
|
+
cache?: import('@naman_deep_singh/cache').ICache<unknown>;
|
|
53
|
+
sessionStore?: import('@naman_deep_singh/cache').SessionStore | undefined;
|
|
52
54
|
private status;
|
|
53
55
|
private grpcServices;
|
|
54
56
|
private grpcServer?;
|
|
@@ -57,6 +59,7 @@ export declare class ExpressServer implements ServerInstance {
|
|
|
57
59
|
private healthMonitor?;
|
|
58
60
|
constructor(name?: string, version?: string, config?: ServerConfig);
|
|
59
61
|
private setupMiddleware;
|
|
62
|
+
private setupCacheAndSession;
|
|
60
63
|
private setupPeriodicHealthMonitoring;
|
|
61
64
|
start(): Promise<ServerInstance>;
|
|
62
65
|
stop(): Promise<void>;
|
package/dist/cjs/server.js
CHANGED
|
@@ -9,6 +9,7 @@ const express_1 = __importDefault(require("express"));
|
|
|
9
9
|
const shutdown_1 = require("./shutdown");
|
|
10
10
|
const periodic_health_1 = require("./periodic-health");
|
|
11
11
|
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const cache_1 = require("@naman_deep_singh/cache");
|
|
12
13
|
class ExpressServer {
|
|
13
14
|
constructor(name = 'Express Server', version = '1.0.0', config = {}) {
|
|
14
15
|
this.status = 'stopped';
|
|
@@ -28,8 +29,14 @@ class ExpressServer {
|
|
|
28
29
|
healthCheck: config.healthCheck ?? true,
|
|
29
30
|
gracefulShutdown: config.gracefulShutdown ?? true,
|
|
30
31
|
socketIO: config.socketIO,
|
|
31
|
-
periodicHealthCheck: config.periodicHealthCheck || { enabled: false }
|
|
32
|
+
periodicHealthCheck: config.periodicHealthCheck || { enabled: false },
|
|
33
|
+
cache: config.cache || { enabled: false },
|
|
34
|
+
session: config.session || { enabled: false }
|
|
32
35
|
};
|
|
36
|
+
// Initialize locals for cache/session
|
|
37
|
+
this.app.locals.cache = undefined;
|
|
38
|
+
this.app.locals.sessionStore = undefined;
|
|
39
|
+
this.app.locals.cacheDefaultTTL = config.cache?.defaultTTL;
|
|
33
40
|
// Apply middleware based on configuration
|
|
34
41
|
this.setupMiddleware();
|
|
35
42
|
// Setup periodic health monitoring
|
|
@@ -80,17 +87,102 @@ class ExpressServer {
|
|
|
80
87
|
// Add health check if enabled
|
|
81
88
|
if (this.config.healthCheck) {
|
|
82
89
|
const healthPath = typeof this.config.healthCheck === 'string' ? this.config.healthCheck : '/health';
|
|
83
|
-
this.app.get(healthPath, (req, res) => {
|
|
84
|
-
|
|
90
|
+
this.app.get(healthPath, async (req, res) => {
|
|
91
|
+
const base = {
|
|
85
92
|
status: 'healthy',
|
|
86
93
|
service: this.config.name,
|
|
87
94
|
version: this.config.version,
|
|
88
95
|
uptime: Date.now() - this.config.startTime.getTime(),
|
|
89
96
|
timestamp: new Date().toISOString()
|
|
90
|
-
}
|
|
97
|
+
};
|
|
98
|
+
// If cache is enabled, include its health
|
|
99
|
+
const cache = req.app.locals.cache;
|
|
100
|
+
if (cache && typeof cache.isAlive === 'function') {
|
|
101
|
+
try {
|
|
102
|
+
base.cache = await cache.isAlive();
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
base.cache = { isAlive: false, adapter: 'unknown', timestamp: new Date(), error: e.message };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
res.status(200).json(base);
|
|
91
109
|
});
|
|
92
110
|
}
|
|
93
111
|
}
|
|
112
|
+
async setupCacheAndSession(config, serverName) {
|
|
113
|
+
try {
|
|
114
|
+
// Initialize cache if enabled
|
|
115
|
+
if (config.cache && config.cache.enabled) {
|
|
116
|
+
try {
|
|
117
|
+
const provided = config.cache?.options;
|
|
118
|
+
let cacheConfig = provided && typeof provided === 'object' ? provided : undefined;
|
|
119
|
+
if (!cacheConfig) {
|
|
120
|
+
cacheConfig = { adapter: config.cache.adapter || 'memory' };
|
|
121
|
+
}
|
|
122
|
+
console.log(`🔄 [${serverName}] Initializing cache adapter: ${config.cache.adapter || 'memory'}...`);
|
|
123
|
+
// Use createWithFallback to prefer primary and fall back to memory when configured
|
|
124
|
+
const cfg = { ...(cacheConfig || {}), ttl: cacheConfig?.ttl ?? config.cache?.defaultTTL };
|
|
125
|
+
const cache = await cache_1.CacheFactory.createWithFallback(cfg);
|
|
126
|
+
this.app.locals.cache = cache;
|
|
127
|
+
this.cache = cache;
|
|
128
|
+
this.app.locals.cacheDefaultTTL = config.cache?.defaultTTL;
|
|
129
|
+
// attach per-request helper middleware
|
|
130
|
+
this.app.use((req, _res, next) => {
|
|
131
|
+
req.cache = cache;
|
|
132
|
+
next();
|
|
133
|
+
});
|
|
134
|
+
console.log(`✅ [${serverName}] Cache initialized successfully (adapter: ${(cacheConfig.adapter || 'memory')})`);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
console.error(`❌ [${serverName}] Failed to initialize cache (fallback to memory if enabled):`, err instanceof Error ? err.message : err);
|
|
138
|
+
// Cache initialization error is critical but we continue to allow graceful fallback
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Initialize session if enabled
|
|
142
|
+
if (config.session && config.session.enabled) {
|
|
143
|
+
const cookieName = config.session.cookieName || `${serverName.replace(/\s+/g, '_').toLowerCase()}.sid`;
|
|
144
|
+
const ttl = config.session.ttl ?? 3600;
|
|
145
|
+
let cache = this.app.locals.cache;
|
|
146
|
+
if (!cache) {
|
|
147
|
+
// fallback to in-memory cache for session store
|
|
148
|
+
try {
|
|
149
|
+
cache = cache_1.CacheFactory.create({ adapter: 'memory' });
|
|
150
|
+
this.app.locals.cache = cache;
|
|
151
|
+
this.cache = cache;
|
|
152
|
+
console.log(`📝 [${serverName}] Session store using in-memory cache`);
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
console.error(`❌ [${serverName}] Failed to create in-memory cache for sessions:`, e instanceof Error ? e.message : e);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.log(`📝 [${serverName}] Session store initialized with configured cache adapter`);
|
|
160
|
+
}
|
|
161
|
+
if (!cache) {
|
|
162
|
+
console.error(`❌ [${serverName}] CRITICAL: Session enabled but no cache available to store sessions. Session functionality will be unavailable.`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const store = new cache_1.SessionStore(cache, { ttl });
|
|
166
|
+
this.app.locals.sessionStore = store;
|
|
167
|
+
this.app.locals.sessionCookieName = cookieName;
|
|
168
|
+
this.sessionStore = store;
|
|
169
|
+
// attach session middleware globally so req.sessionStore is available
|
|
170
|
+
try {
|
|
171
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
172
|
+
const { useSession } = require('./middleware');
|
|
173
|
+
this.app.use(useSession(cookieName));
|
|
174
|
+
console.log(`✅ [${serverName}] Session middleware enabled (cookie: ${cookieName}, TTL: ${ttl}s)`);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
console.error(`❌ [${serverName}] Session middleware not available:`, err instanceof Error ? err.message : err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
console.error(`❌ [${serverName}] Error during cache/session setup:`, err instanceof Error ? err.message : err);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
94
186
|
setupPeriodicHealthMonitoring() {
|
|
95
187
|
if (this.config.periodicHealthCheck?.enabled) {
|
|
96
188
|
this.healthMonitor = new periodic_health_1.PeriodicHealthMonitor(this.config.periodicHealthCheck, this.config.name);
|
|
@@ -98,6 +190,8 @@ class ExpressServer {
|
|
|
98
190
|
}
|
|
99
191
|
async start() {
|
|
100
192
|
this.status = 'starting';
|
|
193
|
+
// Initialize cache and session before starting the server
|
|
194
|
+
await this.setupCacheAndSession(this.config, this.config.name);
|
|
101
195
|
return new Promise((resolve, reject) => {
|
|
102
196
|
try {
|
|
103
197
|
this.server = this.app.listen(this.config.port, () => {
|
|
@@ -111,6 +205,25 @@ class ExpressServer {
|
|
|
111
205
|
if (this.healthMonitor) {
|
|
112
206
|
this.healthMonitor.stop();
|
|
113
207
|
}
|
|
208
|
+
// Close cache and session store if present
|
|
209
|
+
try {
|
|
210
|
+
const cache = this.app.locals.cache;
|
|
211
|
+
if (cache && typeof cache.close === 'function') {
|
|
212
|
+
await cache.close();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
console.warn(`${this.config.name}: Error closing cache`, e);
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
const store = this.app.locals.sessionStore;
|
|
220
|
+
if (store && typeof store.close === 'function') {
|
|
221
|
+
await store.close();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
// SessionStore may not have close; ignore
|
|
226
|
+
}
|
|
114
227
|
}
|
|
115
228
|
});
|
|
116
229
|
}
|
package/dist/cjs/shutdown.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import { Server } from 'http';
|
|
|
2
2
|
import { GracefulShutdownConfig, ServerPlugin } from './types';
|
|
3
3
|
export declare function createGracefulShutdown(server: Server, config?: GracefulShutdownConfig): void;
|
|
4
4
|
export declare function withGracefulShutdown(config?: GracefulShutdownConfig): ServerPlugin;
|
|
5
|
-
export declare function startServerWithShutdown(app:
|
|
5
|
+
export declare function startServerWithShutdown(app: import('express').Application, port: number, shutdownConfig?: GracefulShutdownConfig, serverName?: string, serverVersion?: string): Server;
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -22,6 +22,23 @@ export interface ServerConfig {
|
|
|
22
22
|
periodicHealthCheck?: PeriodicHealthCheckConfig;
|
|
23
23
|
name?: string;
|
|
24
24
|
version?: string;
|
|
25
|
+
cache?: {
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
adapter?: 'redis' | 'memcache' | 'memory';
|
|
28
|
+
options?: unknown;
|
|
29
|
+
defaultTTL?: number;
|
|
30
|
+
};
|
|
31
|
+
session?: {
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
cookieName?: string;
|
|
34
|
+
ttl?: number;
|
|
35
|
+
cookieOptions?: {
|
|
36
|
+
path?: string;
|
|
37
|
+
httpOnly?: boolean;
|
|
38
|
+
secure?: boolean;
|
|
39
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
40
|
+
};
|
|
41
|
+
};
|
|
25
42
|
}
|
|
26
43
|
export interface PeriodicHealthCheckConfig {
|
|
27
44
|
enabled?: boolean;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { Request, Response, NextFunction, Router, Application } from 'express';
|
|
|
4
4
|
export type { RequestHandler, ErrorRequestHandler } from 'express';
|
|
5
5
|
export { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
|
|
6
6
|
export { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
|
|
7
|
-
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, type ValidationRule, type RateLimitConfig, type AuthConfig } from './middleware';
|
|
7
|
+
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, cacheResponse, useSession, type ValidationRule, type RateLimitConfig, type AuthConfig } from './middleware';
|
|
8
8
|
export { getEnv, getEnvNumber, getEnvBoolean } from './utils';
|
|
9
9
|
export { PeriodicHealthMonitor } from './periodic-health';
|
|
10
10
|
export type { ServerConfig, HealthCheckConfig, HealthCheck, GracefulShutdownConfig, ServerPlugin, SocketIOConfig, SocketInstance, PeriodicHealthCheckConfig, HealthCheckService } from './types';
|
package/dist/esm/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
|
|
|
7
7
|
// Graceful shutdown utilities
|
|
8
8
|
export { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
|
|
9
9
|
// Middleware utilities
|
|
10
|
-
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth } from './middleware';
|
|
10
|
+
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, cacheResponse, useSession } from './middleware';
|
|
11
11
|
// Utility functions
|
|
12
12
|
export { getEnv, getEnvNumber, getEnvBoolean } from './utils';
|
|
13
13
|
// Periodic health monitoring
|
package/dist/esm/middleware.d.ts
CHANGED
|
@@ -35,3 +35,5 @@ export declare function withAuth(config: AuthConfig): ServerPlugin;
|
|
|
35
35
|
export declare function validateFields(rules: ValidationRule[]): express.RequestHandler;
|
|
36
36
|
export declare function rateLimit(config?: RateLimitConfig): express.RequestHandler;
|
|
37
37
|
export declare function requireAuth(config: AuthConfig): express.RequestHandler;
|
|
38
|
+
export declare function cacheResponse(ttl?: number): express.RequestHandler;
|
|
39
|
+
export declare function useSession(cookieName?: string): express.RequestHandler;
|
package/dist/esm/middleware.js
CHANGED
|
@@ -227,3 +227,103 @@ export function rateLimit(config = {}) {
|
|
|
227
227
|
export function requireAuth(config) {
|
|
228
228
|
return createAuthMiddleware(config);
|
|
229
229
|
}
|
|
230
|
+
// Cache response middleware (per-route opt-in)
|
|
231
|
+
export function cacheResponse(ttl) {
|
|
232
|
+
return async (req, res, next) => {
|
|
233
|
+
try {
|
|
234
|
+
if (req.method !== 'GET')
|
|
235
|
+
return next();
|
|
236
|
+
const cache = (req.cache ?? req.app.locals.cache);
|
|
237
|
+
const defaultTTL = req.app.locals.cacheDefaultTTL;
|
|
238
|
+
if (!cache || typeof cache.get !== 'function')
|
|
239
|
+
return next();
|
|
240
|
+
const key = `${req.originalUrl}`;
|
|
241
|
+
try {
|
|
242
|
+
const cached = await cache.get(key);
|
|
243
|
+
if (cached !== null && cached !== undefined) {
|
|
244
|
+
res.setHeader('X-Cache', 'HIT');
|
|
245
|
+
return res.json(cached);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (cacheErr) {
|
|
249
|
+
console.error(`[Cache] Failed to retrieve key "${key}":`, cacheErr);
|
|
250
|
+
// Continue without cache hit
|
|
251
|
+
}
|
|
252
|
+
const originalJson = res.json.bind(res);
|
|
253
|
+
res.json = (body) => {
|
|
254
|
+
try {
|
|
255
|
+
const expiry = ttl ?? defaultTTL;
|
|
256
|
+
if (expiry && cache && typeof cache.set === 'function') {
|
|
257
|
+
cache.set(key, body, expiry).catch((err) => {
|
|
258
|
+
console.error(`[Cache] Failed to set key "${key}" with TTL ${expiry}:`, err);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
else if (cache) {
|
|
262
|
+
if (typeof cache.set === 'function') {
|
|
263
|
+
cache.set(key, body).catch((err) => {
|
|
264
|
+
console.error(`[Cache] Failed to set key "${key}":`, err);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
console.error(`[Cache] Error during cache.set operation:`, e);
|
|
271
|
+
}
|
|
272
|
+
res.setHeader('X-Cache', 'MISS');
|
|
273
|
+
return originalJson(body);
|
|
274
|
+
};
|
|
275
|
+
next();
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
console.error('[Cache] Unexpected error in cacheResponse middleware:', err);
|
|
279
|
+
next();
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
// Session middleware helper (attaches sessionStore and helpers to req)
|
|
284
|
+
export function useSession(cookieName) {
|
|
285
|
+
return async (req, res, next) => {
|
|
286
|
+
try {
|
|
287
|
+
const store = req.app.locals.sessionStore;
|
|
288
|
+
if (!store)
|
|
289
|
+
return next();
|
|
290
|
+
const name = cookieName || req.app.locals.sessionCookieName || 'sid';
|
|
291
|
+
let sid = req.cookies?.[name];
|
|
292
|
+
if (!sid) {
|
|
293
|
+
const cookieHeader = req.headers.cookie;
|
|
294
|
+
if (cookieHeader) {
|
|
295
|
+
const match = cookieHeader.split(';').map(s => s.trim()).find(s => s.startsWith(name + '='));
|
|
296
|
+
if (match)
|
|
297
|
+
sid = match.split('=')[1];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
req.sessionId = sid;
|
|
301
|
+
req.sessionStore = store;
|
|
302
|
+
req.getSession = async () => {
|
|
303
|
+
if (!sid)
|
|
304
|
+
return null;
|
|
305
|
+
try {
|
|
306
|
+
return await store.get(sid);
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
console.error(`[Session] Failed to get session "${sid}":`, err);
|
|
310
|
+
throw err;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
req.createSession = async (id, data, ttl) => {
|
|
314
|
+
try {
|
|
315
|
+
return await store.create(id, data, ttl);
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
console.error(`[Session] Failed to create session "${id}":`, err);
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
next();
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
console.error('[Session] Unexpected error in useSession middleware:', err);
|
|
326
|
+
next();
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
package/dist/esm/server.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export declare class ExpressServer implements ServerInstance {
|
|
|
49
49
|
app: express.Application;
|
|
50
50
|
server?: Server;
|
|
51
51
|
config: ServerInstanceConfig;
|
|
52
|
+
cache?: import('@naman_deep_singh/cache').ICache<unknown>;
|
|
53
|
+
sessionStore?: import('@naman_deep_singh/cache').SessionStore | undefined;
|
|
52
54
|
private status;
|
|
53
55
|
private grpcServices;
|
|
54
56
|
private grpcServer?;
|
|
@@ -57,6 +59,7 @@ export declare class ExpressServer implements ServerInstance {
|
|
|
57
59
|
private healthMonitor?;
|
|
58
60
|
constructor(name?: string, version?: string, config?: ServerConfig);
|
|
59
61
|
private setupMiddleware;
|
|
62
|
+
private setupCacheAndSession;
|
|
60
63
|
private setupPeriodicHealthMonitoring;
|
|
61
64
|
start(): Promise<ServerInstance>;
|
|
62
65
|
stop(): Promise<void>;
|
package/dist/esm/server.js
CHANGED
|
@@ -2,6 +2,7 @@ import express from 'express';
|
|
|
2
2
|
import { createGracefulShutdown } from './shutdown';
|
|
3
3
|
import { PeriodicHealthMonitor } from './periodic-health';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
|
+
import { CacheFactory, SessionStore } from '@naman_deep_singh/cache';
|
|
5
6
|
export class ExpressServer {
|
|
6
7
|
constructor(name = 'Express Server', version = '1.0.0', config = {}) {
|
|
7
8
|
this.status = 'stopped';
|
|
@@ -21,8 +22,14 @@ export class ExpressServer {
|
|
|
21
22
|
healthCheck: config.healthCheck ?? true,
|
|
22
23
|
gracefulShutdown: config.gracefulShutdown ?? true,
|
|
23
24
|
socketIO: config.socketIO,
|
|
24
|
-
periodicHealthCheck: config.periodicHealthCheck || { enabled: false }
|
|
25
|
+
periodicHealthCheck: config.periodicHealthCheck || { enabled: false },
|
|
26
|
+
cache: config.cache || { enabled: false },
|
|
27
|
+
session: config.session || { enabled: false }
|
|
25
28
|
};
|
|
29
|
+
// Initialize locals for cache/session
|
|
30
|
+
this.app.locals.cache = undefined;
|
|
31
|
+
this.app.locals.sessionStore = undefined;
|
|
32
|
+
this.app.locals.cacheDefaultTTL = config.cache?.defaultTTL;
|
|
26
33
|
// Apply middleware based on configuration
|
|
27
34
|
this.setupMiddleware();
|
|
28
35
|
// Setup periodic health monitoring
|
|
@@ -73,17 +80,102 @@ export class ExpressServer {
|
|
|
73
80
|
// Add health check if enabled
|
|
74
81
|
if (this.config.healthCheck) {
|
|
75
82
|
const healthPath = typeof this.config.healthCheck === 'string' ? this.config.healthCheck : '/health';
|
|
76
|
-
this.app.get(healthPath, (req, res) => {
|
|
77
|
-
|
|
83
|
+
this.app.get(healthPath, async (req, res) => {
|
|
84
|
+
const base = {
|
|
78
85
|
status: 'healthy',
|
|
79
86
|
service: this.config.name,
|
|
80
87
|
version: this.config.version,
|
|
81
88
|
uptime: Date.now() - this.config.startTime.getTime(),
|
|
82
89
|
timestamp: new Date().toISOString()
|
|
83
|
-
}
|
|
90
|
+
};
|
|
91
|
+
// If cache is enabled, include its health
|
|
92
|
+
const cache = req.app.locals.cache;
|
|
93
|
+
if (cache && typeof cache.isAlive === 'function') {
|
|
94
|
+
try {
|
|
95
|
+
base.cache = await cache.isAlive();
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
base.cache = { isAlive: false, adapter: 'unknown', timestamp: new Date(), error: e.message };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
res.status(200).json(base);
|
|
84
102
|
});
|
|
85
103
|
}
|
|
86
104
|
}
|
|
105
|
+
async setupCacheAndSession(config, serverName) {
|
|
106
|
+
try {
|
|
107
|
+
// Initialize cache if enabled
|
|
108
|
+
if (config.cache && config.cache.enabled) {
|
|
109
|
+
try {
|
|
110
|
+
const provided = config.cache?.options;
|
|
111
|
+
let cacheConfig = provided && typeof provided === 'object' ? provided : undefined;
|
|
112
|
+
if (!cacheConfig) {
|
|
113
|
+
cacheConfig = { adapter: config.cache.adapter || 'memory' };
|
|
114
|
+
}
|
|
115
|
+
console.log(`🔄 [${serverName}] Initializing cache adapter: ${config.cache.adapter || 'memory'}...`);
|
|
116
|
+
// Use createWithFallback to prefer primary and fall back to memory when configured
|
|
117
|
+
const cfg = { ...(cacheConfig || {}), ttl: cacheConfig?.ttl ?? config.cache?.defaultTTL };
|
|
118
|
+
const cache = await CacheFactory.createWithFallback(cfg);
|
|
119
|
+
this.app.locals.cache = cache;
|
|
120
|
+
this.cache = cache;
|
|
121
|
+
this.app.locals.cacheDefaultTTL = config.cache?.defaultTTL;
|
|
122
|
+
// attach per-request helper middleware
|
|
123
|
+
this.app.use((req, _res, next) => {
|
|
124
|
+
req.cache = cache;
|
|
125
|
+
next();
|
|
126
|
+
});
|
|
127
|
+
console.log(`✅ [${serverName}] Cache initialized successfully (adapter: ${(cacheConfig.adapter || 'memory')})`);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
console.error(`❌ [${serverName}] Failed to initialize cache (fallback to memory if enabled):`, err instanceof Error ? err.message : err);
|
|
131
|
+
// Cache initialization error is critical but we continue to allow graceful fallback
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Initialize session if enabled
|
|
135
|
+
if (config.session && config.session.enabled) {
|
|
136
|
+
const cookieName = config.session.cookieName || `${serverName.replace(/\s+/g, '_').toLowerCase()}.sid`;
|
|
137
|
+
const ttl = config.session.ttl ?? 3600;
|
|
138
|
+
let cache = this.app.locals.cache;
|
|
139
|
+
if (!cache) {
|
|
140
|
+
// fallback to in-memory cache for session store
|
|
141
|
+
try {
|
|
142
|
+
cache = CacheFactory.create({ adapter: 'memory' });
|
|
143
|
+
this.app.locals.cache = cache;
|
|
144
|
+
this.cache = cache;
|
|
145
|
+
console.log(`📝 [${serverName}] Session store using in-memory cache`);
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
console.error(`❌ [${serverName}] Failed to create in-memory cache for sessions:`, e instanceof Error ? e.message : e);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log(`📝 [${serverName}] Session store initialized with configured cache adapter`);
|
|
153
|
+
}
|
|
154
|
+
if (!cache) {
|
|
155
|
+
console.error(`❌ [${serverName}] CRITICAL: Session enabled but no cache available to store sessions. Session functionality will be unavailable.`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const store = new SessionStore(cache, { ttl });
|
|
159
|
+
this.app.locals.sessionStore = store;
|
|
160
|
+
this.app.locals.sessionCookieName = cookieName;
|
|
161
|
+
this.sessionStore = store;
|
|
162
|
+
// attach session middleware globally so req.sessionStore is available
|
|
163
|
+
try {
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
165
|
+
const { useSession } = require('./middleware');
|
|
166
|
+
this.app.use(useSession(cookieName));
|
|
167
|
+
console.log(`✅ [${serverName}] Session middleware enabled (cookie: ${cookieName}, TTL: ${ttl}s)`);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
console.error(`❌ [${serverName}] Session middleware not available:`, err instanceof Error ? err.message : err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
console.error(`❌ [${serverName}] Error during cache/session setup:`, err instanceof Error ? err.message : err);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
87
179
|
setupPeriodicHealthMonitoring() {
|
|
88
180
|
if (this.config.periodicHealthCheck?.enabled) {
|
|
89
181
|
this.healthMonitor = new PeriodicHealthMonitor(this.config.periodicHealthCheck, this.config.name);
|
|
@@ -91,6 +183,8 @@ export class ExpressServer {
|
|
|
91
183
|
}
|
|
92
184
|
async start() {
|
|
93
185
|
this.status = 'starting';
|
|
186
|
+
// Initialize cache and session before starting the server
|
|
187
|
+
await this.setupCacheAndSession(this.config, this.config.name);
|
|
94
188
|
return new Promise((resolve, reject) => {
|
|
95
189
|
try {
|
|
96
190
|
this.server = this.app.listen(this.config.port, () => {
|
|
@@ -104,6 +198,25 @@ export class ExpressServer {
|
|
|
104
198
|
if (this.healthMonitor) {
|
|
105
199
|
this.healthMonitor.stop();
|
|
106
200
|
}
|
|
201
|
+
// Close cache and session store if present
|
|
202
|
+
try {
|
|
203
|
+
const cache = this.app.locals.cache;
|
|
204
|
+
if (cache && typeof cache.close === 'function') {
|
|
205
|
+
await cache.close();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
console.warn(`${this.config.name}: Error closing cache`, e);
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const store = this.app.locals.sessionStore;
|
|
213
|
+
if (store && typeof store.close === 'function') {
|
|
214
|
+
await store.close();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
// SessionStore may not have close; ignore
|
|
219
|
+
}
|
|
107
220
|
}
|
|
108
221
|
});
|
|
109
222
|
}
|
package/dist/esm/shutdown.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import { Server } from 'http';
|
|
|
2
2
|
import { GracefulShutdownConfig, ServerPlugin } from './types';
|
|
3
3
|
export declare function createGracefulShutdown(server: Server, config?: GracefulShutdownConfig): void;
|
|
4
4
|
export declare function withGracefulShutdown(config?: GracefulShutdownConfig): ServerPlugin;
|
|
5
|
-
export declare function startServerWithShutdown(app:
|
|
5
|
+
export declare function startServerWithShutdown(app: import('express').Application, port: number, shutdownConfig?: GracefulShutdownConfig, serverName?: string, serverVersion?: string): Server;
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -22,6 +22,23 @@ export interface ServerConfig {
|
|
|
22
22
|
periodicHealthCheck?: PeriodicHealthCheckConfig;
|
|
23
23
|
name?: string;
|
|
24
24
|
version?: string;
|
|
25
|
+
cache?: {
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
adapter?: 'redis' | 'memcache' | 'memory';
|
|
28
|
+
options?: unknown;
|
|
29
|
+
defaultTTL?: number;
|
|
30
|
+
};
|
|
31
|
+
session?: {
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
cookieName?: string;
|
|
34
|
+
ttl?: number;
|
|
35
|
+
cookieOptions?: {
|
|
36
|
+
path?: string;
|
|
37
|
+
httpOnly?: boolean;
|
|
38
|
+
secure?: boolean;
|
|
39
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
40
|
+
};
|
|
41
|
+
};
|
|
25
42
|
}
|
|
26
43
|
export interface PeriodicHealthCheckConfig {
|
|
27
44
|
enabled?: boolean;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { Request, Response, NextFunction, Router, Application } from 'express';
|
|
|
4
4
|
export type { RequestHandler, ErrorRequestHandler } from 'express';
|
|
5
5
|
export { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
|
|
6
6
|
export { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
|
|
7
|
-
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, type ValidationRule, type RateLimitConfig, type AuthConfig } from './middleware';
|
|
7
|
+
export { createLoggingMiddleware, createErrorHandler, createRequestIdMiddleware, createValidationMiddleware, createRateLimitMiddleware, createAuthMiddleware, withLogging, withErrorHandler, withRequestId, withValidation, withRateLimit, withAuth, validateFields, rateLimit, requireAuth, cacheResponse, useSession, type ValidationRule, type RateLimitConfig, type AuthConfig } from './middleware';
|
|
8
8
|
export { getEnv, getEnvNumber, getEnvBoolean } from './utils';
|
|
9
9
|
export { PeriodicHealthMonitor } from './periodic-health';
|
|
10
10
|
export type { ServerConfig, HealthCheckConfig, HealthCheck, GracefulShutdownConfig, ServerPlugin, SocketIOConfig, SocketInstance, PeriodicHealthCheckConfig, HealthCheckService } from './types';
|
|
@@ -35,3 +35,5 @@ export declare function withAuth(config: AuthConfig): ServerPlugin;
|
|
|
35
35
|
export declare function validateFields(rules: ValidationRule[]): express.RequestHandler;
|
|
36
36
|
export declare function rateLimit(config?: RateLimitConfig): express.RequestHandler;
|
|
37
37
|
export declare function requireAuth(config: AuthConfig): express.RequestHandler;
|
|
38
|
+
export declare function cacheResponse(ttl?: number): express.RequestHandler;
|
|
39
|
+
export declare function useSession(cookieName?: string): express.RequestHandler;
|
package/dist/types/server.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export declare class ExpressServer implements ServerInstance {
|
|
|
49
49
|
app: express.Application;
|
|
50
50
|
server?: Server;
|
|
51
51
|
config: ServerInstanceConfig;
|
|
52
|
+
cache?: import('@naman_deep_singh/cache').ICache<unknown>;
|
|
53
|
+
sessionStore?: import('@naman_deep_singh/cache').SessionStore | undefined;
|
|
52
54
|
private status;
|
|
53
55
|
private grpcServices;
|
|
54
56
|
private grpcServer?;
|
|
@@ -57,6 +59,7 @@ export declare class ExpressServer implements ServerInstance {
|
|
|
57
59
|
private healthMonitor?;
|
|
58
60
|
constructor(name?: string, version?: string, config?: ServerConfig);
|
|
59
61
|
private setupMiddleware;
|
|
62
|
+
private setupCacheAndSession;
|
|
60
63
|
private setupPeriodicHealthMonitoring;
|
|
61
64
|
start(): Promise<ServerInstance>;
|
|
62
65
|
stop(): Promise<void>;
|
package/dist/types/shutdown.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import { Server } from 'http';
|
|
|
2
2
|
import { GracefulShutdownConfig, ServerPlugin } from './types';
|
|
3
3
|
export declare function createGracefulShutdown(server: Server, config?: GracefulShutdownConfig): void;
|
|
4
4
|
export declare function withGracefulShutdown(config?: GracefulShutdownConfig): ServerPlugin;
|
|
5
|
-
export declare function startServerWithShutdown(app:
|
|
5
|
+
export declare function startServerWithShutdown(app: import('express').Application, port: number, shutdownConfig?: GracefulShutdownConfig, serverName?: string, serverVersion?: string): Server;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -22,6 +22,23 @@ export interface ServerConfig {
|
|
|
22
22
|
periodicHealthCheck?: PeriodicHealthCheckConfig;
|
|
23
23
|
name?: string;
|
|
24
24
|
version?: string;
|
|
25
|
+
cache?: {
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
adapter?: 'redis' | 'memcache' | 'memory';
|
|
28
|
+
options?: unknown;
|
|
29
|
+
defaultTTL?: number;
|
|
30
|
+
};
|
|
31
|
+
session?: {
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
cookieName?: string;
|
|
34
|
+
ttl?: number;
|
|
35
|
+
cookieOptions?: {
|
|
36
|
+
path?: string;
|
|
37
|
+
httpOnly?: boolean;
|
|
38
|
+
secure?: boolean;
|
|
39
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
40
|
+
};
|
|
41
|
+
};
|
|
25
42
|
}
|
|
26
43
|
export interface PeriodicHealthCheckConfig {
|
|
27
44
|
enabled?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naman_deep_singh/server-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Extensible server utilities for Express.js microservices with TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -27,16 +27,17 @@
|
|
|
27
27
|
"author": "Naman Deep Singh",
|
|
28
28
|
"license": "ISC",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"helmet": "^8.1.0",
|
|
30
|
+
"@naman_deep_singh/cache": "^1.2.0",
|
|
31
|
+
"@types/express": "^5.0.5",
|
|
33
32
|
"cookie-parser": "^1.4.6",
|
|
34
|
-
"
|
|
33
|
+
"cors": "^2.8.5",
|
|
34
|
+
"express": "^5.1.0",
|
|
35
|
+
"helmet": "^8.1.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/cors": "^2.8.19",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
39
|
+
"rimraf": "^5.0.5",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|
|
42
43
|
"build": "pnpm run build:types && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json",
|