navis.js 5.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
+
package/src/index.js CHANGED
@@ -57,6 +57,17 @@ const compress = require('./middleware/compression');
57
57
  const { HealthChecker, createHealthChecker } = require('./health/health-checker');
58
58
  const gracefulShutdown = require('./core/graceful-shutdown');
59
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
+
60
71
  module.exports = {
61
72
  // Core
62
73
  NavisApp,
@@ -121,6 +132,25 @@ module.exports = {
121
132
  createHealthChecker,
122
133
  gracefulShutdown,
123
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
+
124
154
  // Utilities
125
155
  response: {
126
156
  success,
@@ -0,0 +1,151 @@
1
+ /**
2
+ * File Upload Middleware
3
+ * v5.1: Multipart form data and file upload handling
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const crypto = require('crypto');
9
+
10
+ /**
11
+ * File upload middleware
12
+ * @param {Object} options - Upload options
13
+ * @returns {Function} - Middleware function
14
+ */
15
+ function upload(options = {}) {
16
+ const {
17
+ dest = '/tmp/uploads',
18
+ limits = {
19
+ fileSize: 5 * 1024 * 1024, // 5MB default
20
+ files: 10, // Max 10 files
21
+ },
22
+ fileFilter = null, // Function to filter files
23
+ preserveExtension = true,
24
+ generateFilename = (file) => {
25
+ // Generate unique filename
26
+ const ext = preserveExtension ? path.extname(file.originalName || '') : '';
27
+ return `${crypto.randomBytes(16).toString('hex')}${ext}`;
28
+ },
29
+ } = options;
30
+
31
+ // Ensure destination directory exists
32
+ if (!fs.existsSync(dest)) {
33
+ fs.mkdirSync(dest, { recursive: true });
34
+ }
35
+
36
+ return async (req, res, next) => {
37
+ // Check content type
38
+ const contentType = req.headers['content-type'] || req.headers['Content-Type'] || '';
39
+
40
+ if (!contentType.includes('multipart/form-data')) {
41
+ return next();
42
+ }
43
+
44
+ // Parse multipart form data (simplified implementation)
45
+ // In production, use a library like busboy or multer
46
+ try {
47
+ req.files = [];
48
+ req.body = req.body || {};
49
+
50
+ // For Lambda, body is already parsed
51
+ if (req.event && req.event.isBase64Encoded) {
52
+ // Handle base64 encoded body
53
+ const body = Buffer.from(req.event.body, 'base64').toString();
54
+ // Parse multipart data (simplified)
55
+ // In production, use proper multipart parser
56
+ }
57
+
58
+ // For Node.js HTTP, parse from request stream
59
+ if (req.on && typeof req.on === 'function') {
60
+ await parseMultipart(req, dest, limits, fileFilter, generateFilename, (files, fields) => {
61
+ req.files = files;
62
+ req.body = { ...req.body, ...fields };
63
+ });
64
+ }
65
+
66
+ next();
67
+ } catch (error) {
68
+ res.statusCode = 400;
69
+ res.body = { error: error.message || 'File upload failed' };
70
+ }
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Parse multipart form data (simplified)
76
+ * @private
77
+ */
78
+ function parseMultipart(req, dest, limits, fileFilter, generateFilename, callback) {
79
+ return new Promise((resolve, reject) => {
80
+ const files = [];
81
+ const fields = {};
82
+ let totalSize = 0;
83
+ let fileCount = 0;
84
+
85
+ // Simplified multipart parser
86
+ // In production, use busboy or multer
87
+ let buffer = '';
88
+
89
+ req.on('data', (chunk) => {
90
+ buffer += chunk.toString();
91
+ totalSize += chunk.length;
92
+
93
+ if (totalSize > limits.fileSize) {
94
+ reject(new Error('File size exceeds limit'));
95
+ return;
96
+ }
97
+ });
98
+
99
+ req.on('end', () => {
100
+ // Basic multipart parsing (simplified)
101
+ // This is a placeholder - in production use proper parser
102
+ try {
103
+ callback(files, fields);
104
+ resolve();
105
+ } catch (error) {
106
+ reject(error);
107
+ }
108
+ });
109
+
110
+ req.on('error', reject);
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Save uploaded file
116
+ * @param {Object} file - File object
117
+ * @param {string} dest - Destination directory
118
+ * @param {Function} generateFilename - Filename generator
119
+ * @returns {Promise<string>} - File path
120
+ */
121
+ async function saveFile(file, dest, generateFilename) {
122
+ const filename = generateFilename(file);
123
+ const filepath = path.join(dest, filename);
124
+
125
+ // Ensure directory exists
126
+ const dir = path.dirname(filepath);
127
+ if (!fs.existsSync(dir)) {
128
+ fs.mkdirSync(dir, { recursive: true });
129
+ }
130
+
131
+ // Write file
132
+ if (file.buffer) {
133
+ fs.writeFileSync(filepath, file.buffer);
134
+ } else if (file.stream) {
135
+ // Handle stream
136
+ const writeStream = fs.createWriteStream(filepath);
137
+ file.stream.pipe(writeStream);
138
+ await new Promise((resolve, reject) => {
139
+ writeStream.on('finish', resolve);
140
+ writeStream.on('error', reject);
141
+ });
142
+ }
143
+
144
+ return filepath;
145
+ }
146
+
147
+ module.exports = {
148
+ upload,
149
+ saveFile,
150
+ };
151
+