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.
@@ -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
+