navis.js 4.0.0 → 5.2.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 +37 -2
- package/examples/v5-features-demo.js +167 -0
- package/examples/v5.1-features-demo.js +192 -0
- package/examples/v5.2-features-demo.js +153 -0
- package/package.json +1 -1
- package/src/cache/cache.js +157 -0
- package/src/cache/redis-cache.js +174 -0
- package/src/core/app.js +9 -0
- package/src/core/graceful-shutdown.js +77 -0
- package/src/core/versioning.js +124 -0
- package/src/db/db-pool.js +195 -0
- package/src/docs/swagger.js +188 -0
- package/src/health/health-checker.js +120 -0
- package/src/index.js +51 -0
- package/src/middleware/cache-middleware.js +105 -0
- package/src/middleware/compression.js +97 -0
- package/src/middleware/cors.js +86 -0
- package/src/middleware/security.js +107 -0
- package/src/middleware/upload.js +151 -0
- package/src/sse/server-sent-events.js +171 -0
- package/src/testing/test-helper.js +167 -0
- package/src/websocket/websocket-server.js +301 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Connection Pool
|
|
3
|
+
* v5.2: Database integration helpers with connection pooling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class DatabasePool {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.type = options.type || 'postgres';
|
|
9
|
+
this.connectionString = options.connectionString || process.env.DATABASE_URL;
|
|
10
|
+
this.pool = null;
|
|
11
|
+
this.maxConnections = options.maxConnections || 10;
|
|
12
|
+
this.minConnections = options.minConnections || 2;
|
|
13
|
+
this.idleTimeout = options.idleTimeout || 30000;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initialize connection pool
|
|
18
|
+
*/
|
|
19
|
+
async connect() {
|
|
20
|
+
if (this.pool) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
switch (this.type.toLowerCase()) {
|
|
25
|
+
case 'postgres':
|
|
26
|
+
case 'postgresql':
|
|
27
|
+
await this._connectPostgres();
|
|
28
|
+
break;
|
|
29
|
+
case 'mysql':
|
|
30
|
+
case 'mariadb':
|
|
31
|
+
await this._connectMySQL();
|
|
32
|
+
break;
|
|
33
|
+
case 'mongodb':
|
|
34
|
+
await this._connectMongoDB();
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`Unsupported database type: ${this.type}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Connect to PostgreSQL
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
async _connectPostgres() {
|
|
46
|
+
try {
|
|
47
|
+
const { Pool } = require('pg');
|
|
48
|
+
this.pool = new Pool({
|
|
49
|
+
connectionString: this.connectionString,
|
|
50
|
+
max: this.maxConnections,
|
|
51
|
+
min: this.minConnections,
|
|
52
|
+
idleTimeoutMillis: this.idleTimeout,
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new Error('pg package not installed. Install with: npm install pg');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Connect to MySQL
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
async _connectMySQL() {
|
|
64
|
+
try {
|
|
65
|
+
const mysql = require('mysql2/promise');
|
|
66
|
+
this.pool = mysql.createPool({
|
|
67
|
+
uri: this.connectionString,
|
|
68
|
+
connectionLimit: this.maxConnections,
|
|
69
|
+
waitForConnections: true,
|
|
70
|
+
idleTimeout: this.idleTimeout,
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new Error('mysql2 package not installed. Install with: npm install mysql2');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Connect to MongoDB
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
async _connectMongoDB() {
|
|
82
|
+
try {
|
|
83
|
+
const { MongoClient } = require('mongodb');
|
|
84
|
+
const client = new MongoClient(this.connectionString);
|
|
85
|
+
await client.connect();
|
|
86
|
+
this.pool = client;
|
|
87
|
+
this.db = client.db();
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw new Error('mongodb package not installed. Install with: npm install mongodb');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Execute a query
|
|
95
|
+
* @param {string} query - SQL query or MongoDB operation
|
|
96
|
+
* @param {Array} params - Query parameters
|
|
97
|
+
* @returns {Promise<*>} - Query result
|
|
98
|
+
*/
|
|
99
|
+
async query(query, params = []) {
|
|
100
|
+
if (!this.pool) {
|
|
101
|
+
await this.connect();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
switch (this.type.toLowerCase()) {
|
|
105
|
+
case 'postgres':
|
|
106
|
+
case 'postgresql':
|
|
107
|
+
return await this.pool.query(query, params);
|
|
108
|
+
case 'mysql':
|
|
109
|
+
case 'mariadb':
|
|
110
|
+
const [rows] = await this.pool.execute(query, params);
|
|
111
|
+
return rows;
|
|
112
|
+
case 'mongodb':
|
|
113
|
+
// MongoDB uses different query syntax
|
|
114
|
+
// This is a placeholder - implement based on your needs
|
|
115
|
+
return await this.db.collection(query).find(params[0] || {}).toArray();
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`Unsupported database type: ${this.type}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get a connection from pool
|
|
123
|
+
* @returns {Promise<Object>} - Database connection
|
|
124
|
+
*/
|
|
125
|
+
async getConnection() {
|
|
126
|
+
if (!this.pool) {
|
|
127
|
+
await this.connect();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (this.type === 'mongodb') {
|
|
131
|
+
return this.pool;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return await this.pool.getConnection();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Close connection pool
|
|
139
|
+
*/
|
|
140
|
+
async close() {
|
|
141
|
+
if (this.pool) {
|
|
142
|
+
if (this.pool.end) {
|
|
143
|
+
await this.pool.end();
|
|
144
|
+
} else if (this.pool.close) {
|
|
145
|
+
await this.pool.close();
|
|
146
|
+
}
|
|
147
|
+
this.pool = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Ping database connection
|
|
153
|
+
* @returns {Promise<boolean>} - True if connection is alive
|
|
154
|
+
*/
|
|
155
|
+
async ping() {
|
|
156
|
+
try {
|
|
157
|
+
if (!this.pool) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
switch (this.type.toLowerCase()) {
|
|
162
|
+
case 'postgres':
|
|
163
|
+
case 'postgresql':
|
|
164
|
+
await this.pool.query('SELECT 1');
|
|
165
|
+
return true;
|
|
166
|
+
case 'mysql':
|
|
167
|
+
case 'mariadb':
|
|
168
|
+
await this.pool.execute('SELECT 1');
|
|
169
|
+
return true;
|
|
170
|
+
case 'mongodb':
|
|
171
|
+
await this.db.admin().ping();
|
|
172
|
+
return true;
|
|
173
|
+
default:
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create database pool
|
|
184
|
+
* @param {Object} options - Database options
|
|
185
|
+
* @returns {DatabasePool} - Database pool instance
|
|
186
|
+
*/
|
|
187
|
+
function createPool(options = {}) {
|
|
188
|
+
return new DatabasePool(options);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
DatabasePool,
|
|
193
|
+
createPool,
|
|
194
|
+
};
|
|
195
|
+
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI/Swagger Documentation Generator
|
|
3
|
+
* v5.1: Auto-generate OpenAPI 3.0 specification
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class SwaggerGenerator {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.info = {
|
|
9
|
+
title: options.title || 'Navis.js API',
|
|
10
|
+
version: options.version || '1.0.0',
|
|
11
|
+
description: options.description || '',
|
|
12
|
+
...options.info,
|
|
13
|
+
};
|
|
14
|
+
this.servers = options.servers || [{ url: '/', description: 'Default server' }];
|
|
15
|
+
this.paths = {};
|
|
16
|
+
this.components = {
|
|
17
|
+
schemas: {},
|
|
18
|
+
securitySchemes: {},
|
|
19
|
+
};
|
|
20
|
+
this.tags = options.tags || [];
|
|
21
|
+
this.basePath = options.basePath || '/';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Add a route to the specification
|
|
26
|
+
* @param {string} method - HTTP method
|
|
27
|
+
* @param {string} path - Route path
|
|
28
|
+
* @param {Object} spec - OpenAPI operation spec
|
|
29
|
+
*/
|
|
30
|
+
addRoute(method, path, spec = {}) {
|
|
31
|
+
const normalizedPath = this._normalizePath(path);
|
|
32
|
+
|
|
33
|
+
if (!this.paths[normalizedPath]) {
|
|
34
|
+
this.paths[normalizedPath] = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.paths[normalizedPath][method.toLowerCase()] = {
|
|
38
|
+
summary: spec.summary || '',
|
|
39
|
+
description: spec.description || '',
|
|
40
|
+
tags: spec.tags || [],
|
|
41
|
+
parameters: spec.parameters || [],
|
|
42
|
+
requestBody: spec.requestBody || null,
|
|
43
|
+
responses: spec.responses || {
|
|
44
|
+
'200': { description: 'Success' },
|
|
45
|
+
},
|
|
46
|
+
security: spec.security || [],
|
|
47
|
+
...spec,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Add a schema component
|
|
53
|
+
* @param {string} name - Schema name
|
|
54
|
+
* @param {Object} schema - JSON Schema
|
|
55
|
+
*/
|
|
56
|
+
addSchema(name, schema) {
|
|
57
|
+
this.components.schemas[name] = schema;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Add a security scheme
|
|
62
|
+
* @param {string} name - Security scheme name
|
|
63
|
+
* @param {Object} scheme - Security scheme
|
|
64
|
+
*/
|
|
65
|
+
addSecurityScheme(name, scheme) {
|
|
66
|
+
this.components.securitySchemes[name] = scheme;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate OpenAPI specification
|
|
71
|
+
* @returns {Object} - OpenAPI 3.0 specification
|
|
72
|
+
*/
|
|
73
|
+
generate() {
|
|
74
|
+
return {
|
|
75
|
+
openapi: '3.0.0',
|
|
76
|
+
info: this.info,
|
|
77
|
+
servers: this.servers,
|
|
78
|
+
paths: this.paths,
|
|
79
|
+
components: this.components,
|
|
80
|
+
tags: this.tags,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generate JSON string
|
|
86
|
+
* @returns {string} - JSON string
|
|
87
|
+
*/
|
|
88
|
+
toJSON() {
|
|
89
|
+
return JSON.stringify(this.generate(), null, 2);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalize path for OpenAPI (convert :param to {param})
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
_normalizePath(path) {
|
|
97
|
+
return path.replace(/:([^/]+)/g, '{$1}');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Swagger middleware
|
|
103
|
+
* @param {Object} options - Swagger options
|
|
104
|
+
* @returns {Function} - Middleware function
|
|
105
|
+
*/
|
|
106
|
+
function swagger(options = {}) {
|
|
107
|
+
const {
|
|
108
|
+
title = 'Navis.js API',
|
|
109
|
+
version = '1.0.0',
|
|
110
|
+
description = '',
|
|
111
|
+
path = '/swagger.json',
|
|
112
|
+
uiPath = '/docs',
|
|
113
|
+
servers = [],
|
|
114
|
+
tags = [],
|
|
115
|
+
} = options;
|
|
116
|
+
|
|
117
|
+
const generator = new SwaggerGenerator({
|
|
118
|
+
title,
|
|
119
|
+
version,
|
|
120
|
+
description,
|
|
121
|
+
servers,
|
|
122
|
+
tags,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Add default security schemes
|
|
126
|
+
generator.addSecurityScheme('bearerAuth', {
|
|
127
|
+
type: 'http',
|
|
128
|
+
scheme: 'bearer',
|
|
129
|
+
bearerFormat: 'JWT',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
generator,
|
|
134
|
+
middleware: async (req, res, next) => {
|
|
135
|
+
const requestPath = req.path || req.url;
|
|
136
|
+
|
|
137
|
+
// Serve OpenAPI JSON
|
|
138
|
+
if (requestPath === path) {
|
|
139
|
+
res.statusCode = 200;
|
|
140
|
+
res.headers = res.headers || {};
|
|
141
|
+
res.headers['Content-Type'] = 'application/json';
|
|
142
|
+
res.body = generator.generate();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Serve Swagger UI (basic HTML)
|
|
147
|
+
if (requestPath === uiPath) {
|
|
148
|
+
const spec = generator.toJSON();
|
|
149
|
+
const html = `
|
|
150
|
+
<!DOCTYPE html>
|
|
151
|
+
<html>
|
|
152
|
+
<head>
|
|
153
|
+
<title>${title} - API Documentation</title>
|
|
154
|
+
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui.css" />
|
|
155
|
+
</head>
|
|
156
|
+
<body>
|
|
157
|
+
<div id="swagger-ui"></div>
|
|
158
|
+
<script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-bundle.js"></script>
|
|
159
|
+
<script>
|
|
160
|
+
SwaggerUIBundle({
|
|
161
|
+
url: '${path}',
|
|
162
|
+
dom_id: '#swagger-ui',
|
|
163
|
+
presets: [
|
|
164
|
+
SwaggerUIBundle.presets.apis,
|
|
165
|
+
SwaggerUIBundle.presets.standalone
|
|
166
|
+
]
|
|
167
|
+
});
|
|
168
|
+
</script>
|
|
169
|
+
</body>
|
|
170
|
+
</html>
|
|
171
|
+
`;
|
|
172
|
+
res.statusCode = 200;
|
|
173
|
+
res.headers = res.headers || {};
|
|
174
|
+
res.headers['Content-Type'] = 'text/html';
|
|
175
|
+
res.body = html;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
next();
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
SwaggerGenerator,
|
|
186
|
+
swagger,
|
|
187
|
+
};
|
|
188
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Middleware
|
|
3
|
+
* v5: Liveness and readiness probes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class HealthChecker {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.checks = options.checks || {};
|
|
9
|
+
this.livenessPath = options.livenessPath || '/health/live';
|
|
10
|
+
this.readinessPath = options.readinessPath || '/health/ready';
|
|
11
|
+
this.enabled = options.enabled !== false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Add a health check
|
|
16
|
+
* @param {string} name - Check name
|
|
17
|
+
* @param {Function} checkFn - Async function that returns true/false or throws
|
|
18
|
+
*/
|
|
19
|
+
addCheck(name, checkFn) {
|
|
20
|
+
this.checks[name] = checkFn;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Remove a health check
|
|
25
|
+
* @param {string} name - Check name
|
|
26
|
+
*/
|
|
27
|
+
removeCheck(name) {
|
|
28
|
+
delete this.checks[name];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Run all checks
|
|
33
|
+
* @param {boolean} includeReadiness - Include readiness checks
|
|
34
|
+
* @returns {Promise<Object>} - Health status
|
|
35
|
+
*/
|
|
36
|
+
async runChecks(includeReadiness = true) {
|
|
37
|
+
const results = {};
|
|
38
|
+
let allHealthy = true;
|
|
39
|
+
|
|
40
|
+
for (const [name, checkFn] of Object.entries(this.checks)) {
|
|
41
|
+
try {
|
|
42
|
+
const result = await checkFn();
|
|
43
|
+
results[name] = {
|
|
44
|
+
status: result === false ? 'unhealthy' : 'healthy',
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (result === false) {
|
|
49
|
+
allHealthy = false;
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
results[name] = {
|
|
53
|
+
status: 'unhealthy',
|
|
54
|
+
error: error.message,
|
|
55
|
+
timestamp: new Date().toISOString(),
|
|
56
|
+
};
|
|
57
|
+
allHealthy = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
status: allHealthy ? 'healthy' : 'unhealthy',
|
|
63
|
+
checks: results,
|
|
64
|
+
timestamp: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create health check middleware
|
|
70
|
+
* @returns {Function} - Middleware function
|
|
71
|
+
*/
|
|
72
|
+
middleware() {
|
|
73
|
+
return async (req, res, next) => {
|
|
74
|
+
if (!this.enabled) {
|
|
75
|
+
return next();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const path = req.path || req.url;
|
|
79
|
+
|
|
80
|
+
// Liveness probe (always returns 200 if service is running)
|
|
81
|
+
if (path === this.livenessPath) {
|
|
82
|
+
res.statusCode = 200;
|
|
83
|
+
res.headers = res.headers || {};
|
|
84
|
+
res.headers['Content-Type'] = 'application/json';
|
|
85
|
+
res.body = {
|
|
86
|
+
status: 'alive',
|
|
87
|
+
timestamp: new Date().toISOString(),
|
|
88
|
+
};
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Readiness probe (checks all health checks)
|
|
93
|
+
if (path === this.readinessPath) {
|
|
94
|
+
const healthStatus = await this.runChecks(true);
|
|
95
|
+
res.statusCode = healthStatus.status === 'healthy' ? 200 : 503;
|
|
96
|
+
res.headers = res.headers || {};
|
|
97
|
+
res.headers['Content-Type'] = 'application/json';
|
|
98
|
+
res.body = healthStatus;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
next();
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create health checker
|
|
109
|
+
* @param {Object} options - Health checker options
|
|
110
|
+
* @returns {HealthChecker} - Health checker instance
|
|
111
|
+
*/
|
|
112
|
+
function createHealthChecker(options = {}) {
|
|
113
|
+
return new HealthChecker(options);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
HealthChecker,
|
|
118
|
+
createHealthChecker,
|
|
119
|
+
};
|
|
120
|
+
|
package/src/index.js
CHANGED
|
@@ -47,6 +47,27 @@ const {
|
|
|
47
47
|
notFoundHandler,
|
|
48
48
|
} = require('./errors/error-handler');
|
|
49
49
|
|
|
50
|
+
// v5: Enterprise Features
|
|
51
|
+
const Cache = require('./cache/cache');
|
|
52
|
+
const RedisCache = require('./cache/redis-cache');
|
|
53
|
+
const cache = require('./middleware/cache-middleware');
|
|
54
|
+
const cors = require('./middleware/cors');
|
|
55
|
+
const security = require('./middleware/security');
|
|
56
|
+
const compress = require('./middleware/compression');
|
|
57
|
+
const { HealthChecker, createHealthChecker } = require('./health/health-checker');
|
|
58
|
+
const gracefulShutdown = require('./core/graceful-shutdown');
|
|
59
|
+
|
|
60
|
+
// v5.1: Developer Experience
|
|
61
|
+
const { SwaggerGenerator, swagger } = require('./docs/swagger');
|
|
62
|
+
const { VersionManager, createVersionManager, headerVersioning } = require('./core/versioning');
|
|
63
|
+
const { upload, saveFile } = require('./middleware/upload');
|
|
64
|
+
const { TestApp, testApp } = require('./testing/test-helper');
|
|
65
|
+
|
|
66
|
+
// v5.2: Real-time Features
|
|
67
|
+
const WebSocketServer = require('./websocket/websocket-server');
|
|
68
|
+
const { SSEServer, createSSEServer, sse } = require('./sse/server-sent-events');
|
|
69
|
+
const { DatabasePool, createPool } = require('./db/db-pool');
|
|
70
|
+
|
|
50
71
|
module.exports = {
|
|
51
72
|
// Core
|
|
52
73
|
NavisApp,
|
|
@@ -100,6 +121,36 @@ module.exports = {
|
|
|
100
121
|
asyncHandler,
|
|
101
122
|
notFoundHandler,
|
|
102
123
|
|
|
124
|
+
// v5: Enterprise Features
|
|
125
|
+
Cache,
|
|
126
|
+
RedisCache,
|
|
127
|
+
cache,
|
|
128
|
+
cors,
|
|
129
|
+
security,
|
|
130
|
+
compress,
|
|
131
|
+
HealthChecker,
|
|
132
|
+
createHealthChecker,
|
|
133
|
+
gracefulShutdown,
|
|
134
|
+
|
|
135
|
+
// v5.1: Developer Experience
|
|
136
|
+
SwaggerGenerator,
|
|
137
|
+
swagger,
|
|
138
|
+
VersionManager,
|
|
139
|
+
createVersionManager,
|
|
140
|
+
headerVersioning,
|
|
141
|
+
upload,
|
|
142
|
+
saveFile,
|
|
143
|
+
TestApp,
|
|
144
|
+
testApp,
|
|
145
|
+
|
|
146
|
+
// v5.2: Real-time Features
|
|
147
|
+
WebSocketServer,
|
|
148
|
+
SSEServer,
|
|
149
|
+
createSSEServer,
|
|
150
|
+
sse,
|
|
151
|
+
DatabasePool,
|
|
152
|
+
createPool,
|
|
153
|
+
|
|
103
154
|
// Utilities
|
|
104
155
|
response: {
|
|
105
156
|
success,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Middleware
|
|
3
|
+
* v5: Response caching middleware
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create cache middleware
|
|
10
|
+
* @param {Object} options - Cache options
|
|
11
|
+
* @returns {Function} - Middleware function
|
|
12
|
+
*/
|
|
13
|
+
function cache(options = {}) {
|
|
14
|
+
const {
|
|
15
|
+
ttl = 3600, // 1 hour in seconds
|
|
16
|
+
keyGenerator = (req) => {
|
|
17
|
+
// Default: method + path + query string
|
|
18
|
+
const queryStr = JSON.stringify(req.query || {});
|
|
19
|
+
return `${req.method}:${req.path}:${queryStr}`;
|
|
20
|
+
},
|
|
21
|
+
cacheStore = null, // Must be provided
|
|
22
|
+
skipCache = (req, res) => {
|
|
23
|
+
// Skip cache for non-GET requests or if status >= 400
|
|
24
|
+
return req.method !== 'GET' || (res.statusCode && res.statusCode >= 400);
|
|
25
|
+
},
|
|
26
|
+
vary = [], // Vary headers
|
|
27
|
+
} = options;
|
|
28
|
+
|
|
29
|
+
if (!cacheStore) {
|
|
30
|
+
throw new Error('cacheStore is required');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return async (req, res, next) => {
|
|
34
|
+
// Generate cache key
|
|
35
|
+
const cacheKey = keyGenerator(req);
|
|
36
|
+
|
|
37
|
+
// Check if should skip cache
|
|
38
|
+
if (skipCache(req, res)) {
|
|
39
|
+
return next();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Try to get from cache
|
|
43
|
+
try {
|
|
44
|
+
const cached = await (cacheStore.get ? cacheStore.get(cacheKey) : cacheStore.get(cacheKey));
|
|
45
|
+
|
|
46
|
+
if (cached) {
|
|
47
|
+
// Set cache headers
|
|
48
|
+
res.headers = res.headers || {};
|
|
49
|
+
res.headers['X-Cache'] = 'HIT';
|
|
50
|
+
res.headers['Cache-Control'] = `public, max-age=${ttl}`;
|
|
51
|
+
|
|
52
|
+
// Set Vary headers
|
|
53
|
+
if (vary.length > 0) {
|
|
54
|
+
res.headers['Vary'] = vary.join(', ');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Return cached response
|
|
58
|
+
res.statusCode = cached.statusCode || 200;
|
|
59
|
+
res.body = cached.body;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
// Cache error - continue without cache
|
|
64
|
+
console.error('Cache get error:', error);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Cache miss - continue to handler
|
|
68
|
+
res.headers = res.headers || {};
|
|
69
|
+
res.headers['X-Cache'] = 'MISS';
|
|
70
|
+
|
|
71
|
+
// Store original end/finish to capture response
|
|
72
|
+
const originalBody = res.body;
|
|
73
|
+
const originalStatusCode = res.statusCode;
|
|
74
|
+
|
|
75
|
+
// Wrap response to cache it
|
|
76
|
+
const originalFinish = res.finish || (() => {});
|
|
77
|
+
res.finish = async function(...args) {
|
|
78
|
+
// Only cache successful GET requests
|
|
79
|
+
if (req.method === 'GET' && res.statusCode < 400) {
|
|
80
|
+
try {
|
|
81
|
+
const cacheValue = {
|
|
82
|
+
statusCode: res.statusCode,
|
|
83
|
+
body: res.body,
|
|
84
|
+
headers: res.headers,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (cacheStore.set) {
|
|
88
|
+
await cacheStore.set(cacheKey, cacheValue, ttl * 1000);
|
|
89
|
+
} else {
|
|
90
|
+
cacheStore.set(cacheKey, cacheValue, ttl * 1000);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Cache set error:', error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return originalFinish.apply(this, args);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
next();
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = cache;
|
|
105
|
+
|