katax-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ import { MongoClient, Db } from 'mongodb';
2
+
3
+ let client: MongoClient;
4
+ let db: Db;
5
+
6
+ export async function connect(): Promise<Db> {
7
+ if (db) {
8
+ return db;
9
+ }
10
+
11
+ const uri = process.env.DATABASE_URL;
12
+ if (!uri) {
13
+ throw new Error('DATABASE_URL is not defined in environment variables');
14
+ }
15
+
16
+ client = new MongoClient(uri);
17
+
18
+ try {
19
+ await client.connect();
20
+ console.log('✅ Connected to MongoDB database');
21
+
22
+ const dbName = uri.split('/').pop()?.split('?')[0];
23
+ db = client.db(dbName);
24
+
25
+ return db;
26
+ } catch (error) {
27
+ console.error('❌ MongoDB connection error:', error);
28
+ throw error;
29
+ }
30
+ }
31
+
32
+ export async function disconnect(): Promise<void> {
33
+ if (client) {
34
+ await client.close();
35
+ console.log('Disconnected from MongoDB');
36
+ }
37
+ }
38
+
39
+ export function getDb(): Db {
40
+ if (!db) {
41
+ throw new Error('Database not initialized. Call connect() first.');
42
+ }
43
+ return db;
44
+ }
45
+
46
+ export default { connect, disconnect, getDb };
@@ -0,0 +1,26 @@
1
+ import mysql from 'mysql2/promise';
2
+
3
+ const pool = mysql.createPool({
4
+ uri: process.env.DATABASE_URL,
5
+ waitForConnections: true,
6
+ connectionLimit: 10,
7
+ queueLimit: 0
8
+ });
9
+
10
+ pool.on('connection', () => {
11
+ console.log('✅ Connected to MySQL database');
12
+ });
13
+
14
+ export default pool;
15
+
16
+ export async function query(sql: string, params?: any[]) {
17
+ const start = Date.now();
18
+ const [rows] = await pool.execute(sql, params);
19
+ const duration = Date.now() - start;
20
+ console.log('Executed query', { sql, duration, rows: Array.isArray(rows) ? rows.length : 0 });
21
+ return rows;
22
+ }
23
+
24
+ export async function getConnection() {
25
+ return await pool.getConnection();
26
+ }
@@ -0,0 +1,52 @@
1
+ import { Pool } from 'pg';
2
+
3
+ const pool = new Pool({
4
+ connectionString: process.env.DATABASE_URL,
5
+ ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
6
+ });
7
+
8
+ pool.on('connect', () => {
9
+ console.log('✅ Connected to PostgreSQL database');
10
+ });
11
+
12
+ pool.on('error', (err) => {
13
+ console.error('❌ PostgreSQL connection error:', err);
14
+ process.exit(-1);
15
+ });
16
+
17
+ export default pool;
18
+
19
+ export async function query(text: string, params?: any[]) {
20
+ const start = Date.now();
21
+ const res = await pool.query(text, params);
22
+ const duration = Date.now() - start;
23
+ console.log('Executed query', { text, duration, rows: res.rowCount });
24
+ return res;
25
+ }
26
+
27
+ export async function getClient() {
28
+ const client = await pool.connect();
29
+ const query = client.query;
30
+ const release = client.release;
31
+
32
+ // Set a timeout of 5 seconds, after which we will log this client's last query
33
+ const timeout = setTimeout(() => {
34
+ console.error('A client has been checked out for more than 5 seconds!');
35
+ console.error(`The last executed query on this client was: ${(client as any).lastQuery}`);
36
+ }, 5000);
37
+
38
+ // Monkey patch the query method to keep track of the last query executed
39
+ client.query = (...args: any[]) => {
40
+ (client as any).lastQuery = args;
41
+ return query.apply(client, args);
42
+ };
43
+
44
+ client.release = () => {
45
+ clearTimeout(timeout);
46
+ client.query = query;
47
+ client.release = release;
48
+ return release.apply(client);
49
+ };
50
+
51
+ return client;
52
+ }
@@ -0,0 +1,2 @@
1
+ import { EndpointConfig } from '../types/index.js';
2
+ export declare function generateController(config: EndpointConfig): string;
@@ -0,0 +1,223 @@
1
+ import { toPascalCase, toCamelCase } from '../utils/file-utils.js';
2
+ export function generateController(config) {
3
+ const { name, method, fields = [] } = config;
4
+ const pascalName = toPascalCase(name);
5
+ const camelName = toCamelCase(name);
6
+ let content = '';
7
+ // Imports
8
+ if (config.addValidation) {
9
+ content += `import { ${pascalName}Data } from './${name.toLowerCase()}.validator.js';\n`;
10
+ }
11
+ content += `import {\n`;
12
+ content += ` ControllerResult,\n`;
13
+ content += ` createSuccessResult,\n`;
14
+ content += ` createErrorResult\n`;
15
+ content += `} from '../../shared/api.utils.js';\n`;
16
+ content += `// import pool from '../../database/db.config.js'; // Uncomment if using database\n\n`;
17
+ // Generate controller function based on method
18
+ switch (method) {
19
+ case 'POST':
20
+ content += generateCreateController(pascalName, camelName, fields);
21
+ break;
22
+ case 'GET':
23
+ content += generateGetController(pascalName, camelName);
24
+ break;
25
+ case 'PUT':
26
+ case 'PATCH':
27
+ content += generateUpdateController(pascalName, camelName, fields);
28
+ break;
29
+ case 'DELETE':
30
+ content += generateDeleteController(pascalName, camelName);
31
+ break;
32
+ default:
33
+ content += generateGenericController(pascalName, camelName, method);
34
+ }
35
+ return content;
36
+ }
37
+ function generateCreateController(pascalName, camelName, fields) {
38
+ return `/**
39
+ * Create a new ${camelName}
40
+ */
41
+ export async function create${pascalName}(
42
+ data: ${pascalName}Data
43
+ ): Promise<ControllerResult<any>> {
44
+ try {
45
+ console.log(\`[${pascalName.toUpperCase()}] Creating ${camelName}:\`, data);
46
+
47
+ // TODO: Implement database insertion
48
+ // const result = await pool.query(
49
+ // 'INSERT INTO ${camelName}s (${fields.map(f => f.name).join(', ')}) VALUES (${fields.map((_, i) => `$${i + 1}`).join(', ')}) RETURNING *',
50
+ // [${fields.map(f => `data.${f.name}`).join(', ')}]
51
+ // );
52
+ // const new${pascalName} = result.rows[0];
53
+
54
+ // Mock response for now
55
+ const new${pascalName} = {
56
+ id: Math.floor(Math.random() * 1000),
57
+ ...data,
58
+ createdAt: new Date().toISOString()
59
+ };
60
+
61
+ return createSuccessResult(
62
+ '${pascalName} created successfully',
63
+ new${pascalName},
64
+ undefined,
65
+ 201
66
+ );
67
+ } catch (error) {
68
+ console.error(\`[${pascalName.toUpperCase()}] Error creating ${camelName}:\`, error);
69
+ return createErrorResult(
70
+ 'Failed to create ${camelName}',
71
+ error instanceof Error ? error.message : 'Unknown error',
72
+ 500
73
+ );
74
+ }
75
+ }
76
+ `;
77
+ }
78
+ function generateGetController(pascalName, camelName) {
79
+ return `/**
80
+ * Get ${camelName}(s)
81
+ */
82
+ export async function get${pascalName}(
83
+ id?: string
84
+ ): Promise<ControllerResult<any>> {
85
+ try {
86
+ console.log(\`[${pascalName.toUpperCase()}] Getting ${camelName}\`, id ? \`with id: \${id}\` : '(all)');
87
+
88
+ // TODO: Implement database query
89
+ // if (id) {
90
+ // const result = await pool.query('SELECT * FROM ${camelName}s WHERE id = $1', [id]);
91
+ // if (result.rows.length === 0) {
92
+ // return createErrorResult('${pascalName} not found', undefined, 404);
93
+ // }
94
+ // return createSuccessResult('${pascalName} retrieved', result.rows[0]);
95
+ // } else {
96
+ // const result = await pool.query('SELECT * FROM ${camelName}s');
97
+ // return createSuccessResult('${pascalName}s retrieved', result.rows);
98
+ // }
99
+
100
+ // Mock response for now
101
+ const mock${pascalName} = {
102
+ id: id || 1,
103
+ name: 'Sample ${pascalName}',
104
+ createdAt: new Date().toISOString()
105
+ };
106
+
107
+ return createSuccessResult(
108
+ '${pascalName} retrieved successfully',
109
+ id ? mock${pascalName} : [mock${pascalName}]
110
+ );
111
+ } catch (error) {
112
+ console.error(\`[${pascalName.toUpperCase()}] Error getting ${camelName}:\`, error);
113
+ return createErrorResult(
114
+ 'Failed to get ${camelName}',
115
+ error instanceof Error ? error.message : 'Unknown error',
116
+ 500
117
+ );
118
+ }
119
+ }
120
+ `;
121
+ }
122
+ function generateUpdateController(pascalName, camelName, fields) {
123
+ return `/**
124
+ * Update ${camelName}
125
+ */
126
+ export async function update${pascalName}(
127
+ id: string,
128
+ data: Partial<${pascalName}Data>
129
+ ): Promise<ControllerResult<any>> {
130
+ try {
131
+ console.log(\`[${pascalName.toUpperCase()}] Updating ${camelName} \${id}:\`, data);
132
+
133
+ // TODO: Implement database update
134
+ // const result = await pool.query(
135
+ // 'UPDATE ${camelName}s SET ... WHERE id = $1 RETURNING *',
136
+ // [id, ...]
137
+ // );
138
+ // if (result.rows.length === 0) {
139
+ // return createErrorResult('${pascalName} not found', undefined, 404);
140
+ // }
141
+
142
+ // Mock response for now
143
+ const updated${pascalName} = {
144
+ id: parseInt(id),
145
+ ...data,
146
+ updatedAt: new Date().toISOString()
147
+ };
148
+
149
+ return createSuccessResult(
150
+ '${pascalName} updated successfully',
151
+ updated${pascalName}
152
+ );
153
+ } catch (error) {
154
+ console.error(\`[${pascalName.toUpperCase()}] Error updating ${camelName}:\`, error);
155
+ return createErrorResult(
156
+ 'Failed to update ${camelName}',
157
+ error instanceof Error ? error.message : 'Unknown error',
158
+ 500
159
+ );
160
+ }
161
+ }
162
+ `;
163
+ }
164
+ function generateDeleteController(pascalName, camelName) {
165
+ return `/**
166
+ * Delete ${camelName}
167
+ */
168
+ export async function delete${pascalName}(
169
+ id: string
170
+ ): Promise<ControllerResult<void>> {
171
+ try {
172
+ console.log(\`[${pascalName.toUpperCase()}] Deleting ${camelName} \${id}\`);
173
+
174
+ // TODO: Implement database deletion
175
+ // const result = await pool.query('DELETE FROM ${camelName}s WHERE id = $1 RETURNING id', [id]);
176
+ // if (result.rows.length === 0) {
177
+ // return createErrorResult('${pascalName} not found', undefined, 404);
178
+ // }
179
+
180
+ return createSuccessResult(
181
+ '${pascalName} deleted successfully',
182
+ undefined,
183
+ undefined,
184
+ 200
185
+ );
186
+ } catch (error) {
187
+ console.error(\`[${pascalName.toUpperCase()}] Error deleting ${camelName}:\`, error);
188
+ return createErrorResult(
189
+ 'Failed to delete ${camelName}',
190
+ error instanceof Error ? error.message : 'Unknown error',
191
+ 500
192
+ );
193
+ }
194
+ }
195
+ `;
196
+ }
197
+ function generateGenericController(pascalName, camelName, method) {
198
+ return `/**
199
+ * Handle ${method} request for ${camelName}
200
+ */
201
+ export async function handle${pascalName}${method}(
202
+ data?: any
203
+ ): Promise<ControllerResult<any>> {
204
+ try {
205
+ console.log(\`[${pascalName.toUpperCase()}] Handling ${method} request\`, data);
206
+
207
+ // TODO: Implement your logic here
208
+
209
+ return createSuccessResult(
210
+ 'Request processed successfully',
211
+ { message: '${method} ${camelName} endpoint' }
212
+ );
213
+ } catch (error) {
214
+ console.error(\`[${pascalName.toUpperCase()}] Error:\`, error);
215
+ return createErrorResult(
216
+ 'Failed to process request',
217
+ error instanceof Error ? error.message : 'Unknown error',
218
+ 500
219
+ );
220
+ }
221
+ }
222
+ `;
223
+ }
@@ -0,0 +1,2 @@
1
+ import { EndpointConfig } from '../types/index.js';
2
+ export declare function generateHandler(config: EndpointConfig): string;
@@ -0,0 +1,84 @@
1
+ import { toPascalCase, toCamelCase } from '../utils/file-utils.js';
2
+ export function generateHandler(config) {
3
+ const { name, method } = config;
4
+ const pascalName = toPascalCase(name);
5
+ const camelName = toCamelCase(name);
6
+ let content = "import { Request, Response } from 'express';\n";
7
+ if (config.addValidation) {
8
+ content += `import { validate${pascalName} } from './${name.toLowerCase()}.validator.js';\n`;
9
+ }
10
+ // Import controller functions
11
+ switch (method) {
12
+ case 'POST':
13
+ content += `import { create${pascalName} } from './${name.toLowerCase()}.controller.js';\n`;
14
+ break;
15
+ case 'GET':
16
+ content += `import { get${pascalName} } from './${name.toLowerCase()}.controller.js';\n`;
17
+ break;
18
+ case 'PUT':
19
+ case 'PATCH':
20
+ content += `import { update${pascalName} } from './${name.toLowerCase()}.controller.js';\n`;
21
+ break;
22
+ case 'DELETE':
23
+ content += `import { delete${pascalName} } from './${name.toLowerCase()}.controller.js';\n`;
24
+ break;
25
+ }
26
+ content += "import { sendResponse } from '../../shared/api.utils.js';\n\n";
27
+ content += "// ==================== HANDLERS ====================\n\n";
28
+ // Generate handler based on method
29
+ content += generateHandlerFunction(method, pascalName, camelName, config.addValidation);
30
+ return content;
31
+ }
32
+ function generateHandlerFunction(method, pascalName, camelName, hasValidation) {
33
+ const handlerName = getHandlerName(method, pascalName);
34
+ let content = `/**\n * Handler for ${method} ${camelName}\n`;
35
+ content += ` * Uses sendResponse utility for automatic validation and response handling\n */\n`;
36
+ content += `export async function ${handlerName}(req: Request, res: Response): Promise<void> {\n`;
37
+ content += ` await sendResponse(\n`;
38
+ content += ` req,\n`;
39
+ content += ` res,\n`;
40
+ // Validator based on method
41
+ if (hasValidation && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
42
+ content += ` () => validate${pascalName}(req.body),\n`;
43
+ }
44
+ else if (method === 'GET' || method === 'DELETE') {
45
+ content += ` () => validate${pascalName}Id(req.params.id),\n`;
46
+ }
47
+ else {
48
+ content += ` () => validate${pascalName}(req.body),\n`;
49
+ }
50
+ // Controller call based on method
51
+ switch (method) {
52
+ case 'POST':
53
+ content += ` (validData) => create${pascalName}(validData)\n`;
54
+ break;
55
+ case 'GET':
56
+ content += ` (validData) => get${pascalName}(validData)\n`;
57
+ break;
58
+ case 'PUT':
59
+ case 'PATCH':
60
+ content += ` (validData) => update${pascalName}(req.params.id, validData)\n`;
61
+ break;
62
+ case 'DELETE':
63
+ content += ` (validData) => delete${pascalName}(validData)\n`;
64
+ break;
65
+ }
66
+ content += ` );\n`;
67
+ content += `}\n`;
68
+ return content;
69
+ }
70
+ function getHandlerName(method, pascalName) {
71
+ switch (method) {
72
+ case 'POST':
73
+ return `create${pascalName}Handler`;
74
+ case 'GET':
75
+ return `get${pascalName}Handler`;
76
+ case 'PUT':
77
+ case 'PATCH':
78
+ return `update${pascalName}Handler`;
79
+ case 'DELETE':
80
+ return `delete${pascalName}Handler`;
81
+ default:
82
+ return `handle${pascalName}${method}`;
83
+ }
84
+ }
@@ -0,0 +1,2 @@
1
+ import { EndpointConfig } from '../types/index.js';
2
+ export declare function generateRoute(config: EndpointConfig): string;
@@ -0,0 +1,50 @@
1
+ import { toPascalCase } from '../utils/file-utils.js';
2
+ export function generateRoute(config) {
3
+ const { name, method, path } = config;
4
+ const pascalName = toPascalCase(name);
5
+ let content = "import { Router } from 'express';\n";
6
+ // Import handler function
7
+ const handlerName = getHandlerName(method, pascalName);
8
+ content += `import { ${handlerName} } from './${name.toLowerCase()}.handler.js';\n\n`;
9
+ content += `const router = Router();\n\n`;
10
+ content += `// ==================== ROUTES ====================\n\n`;
11
+ // Generate route
12
+ const routePath = path.replace(/^\/api/, '');
13
+ content += `/**\n`;
14
+ content += ` * @route ${method} ${path}\n`;
15
+ content += ` * @desc ${getRouteDescription(method, pascalName)}\n`;
16
+ content += ` */\n`;
17
+ content += `router.${method.toLowerCase()}('${routePath}', ${handlerName});\n\n`;
18
+ content += `export default router;\n`;
19
+ return content;
20
+ }
21
+ function getHandlerName(method, pascalName) {
22
+ switch (method) {
23
+ case 'POST':
24
+ return `create${pascalName}Handler`;
25
+ case 'GET':
26
+ return `get${pascalName}Handler`;
27
+ case 'PUT':
28
+ case 'PATCH':
29
+ return `update${pascalName}Handler`;
30
+ case 'DELETE':
31
+ return `delete${pascalName}Handler`;
32
+ default:
33
+ return `handle${pascalName}${method}`;
34
+ }
35
+ }
36
+ function getRouteDescription(method, name) {
37
+ switch (method) {
38
+ case 'POST':
39
+ return `Create a new ${name}`;
40
+ case 'GET':
41
+ return `Get ${name}(s)`;
42
+ case 'PUT':
43
+ case 'PATCH':
44
+ return `Update ${name}`;
45
+ case 'DELETE':
46
+ return `Delete ${name}`;
47
+ default:
48
+ return `Handle ${method} request for ${name}`;
49
+ }
50
+ }
@@ -0,0 +1,2 @@
1
+ import { EndpointConfig } from '../types/index.js';
2
+ export declare function updateMainRouter(name: string, config: EndpointConfig): Promise<void>;
@@ -0,0 +1,48 @@
1
+ import path from 'path';
2
+ import { fileExists, writeFile } from '../utils/file-utils.js';
3
+ export async function updateMainRouter(name, config) {
4
+ const routesPath = path.join(process.cwd(), 'src', 'api', 'routes.ts');
5
+ let content;
6
+ if (fileExists(routesPath)) {
7
+ // Read existing routes.ts
8
+ const fs = await import('fs');
9
+ content = await fs.promises.readFile(routesPath, 'utf-8');
10
+ // Check if import already exists
11
+ const importStatement = `import ${name.toLowerCase()}Router from './${name.toLowerCase()}/${name.toLowerCase()}.routes.js';`;
12
+ if (!content.includes(importStatement)) {
13
+ // Add import after last import
14
+ const lastImportIndex = content.lastIndexOf('import ');
15
+ const endOfLineIndex = content.indexOf('\n', lastImportIndex);
16
+ content = content.slice(0, endOfLineIndex + 1) + importStatement + '\n' + content.slice(endOfLineIndex + 1);
17
+ }
18
+ // Add route registration
19
+ const routePath = config.path.replace(/^\/api/, '');
20
+ const basePath = routePath.split('/')[1] || name.toLowerCase();
21
+ const routeStatement = `router.use('/${basePath}', ${name.toLowerCase()}Router);`;
22
+ if (!content.includes(routeStatement)) {
23
+ // Add before export default
24
+ const exportIndex = content.lastIndexOf('export default router;');
25
+ if (exportIndex !== -1) {
26
+ content = content.slice(0, exportIndex) + routeStatement + '\n\n' + content.slice(exportIndex);
27
+ }
28
+ else {
29
+ content += `\n${routeStatement}\n`;
30
+ }
31
+ }
32
+ }
33
+ else {
34
+ // Create new routes.ts
35
+ content = `import { Router } from 'express';\n`;
36
+ content += `import ${name.toLowerCase()}Router from './${name.toLowerCase()}/${name.toLowerCase()}.routes.js';\n\n`;
37
+ content += `const router = Router();\n\n`;
38
+ content += `// Health check\n`;
39
+ content += `router.get('/health', (req, res) => {\n`;
40
+ content += ` res.json({ status: 'ok', timestamp: new Date().toISOString() });\n`;
41
+ content += `});\n\n`;
42
+ const routePath = config.path.replace(/^\/api/, '');
43
+ const basePath = routePath.split('/')[1] || name.toLowerCase();
44
+ content += `router.use('/${basePath}', ${name.toLowerCase()}Router);\n\n`;
45
+ content += `export default router;\n`;
46
+ }
47
+ await writeFile(routesPath, content);
48
+ }
@@ -0,0 +1,2 @@
1
+ import { EndpointConfig } from '../types/index.js';
2
+ export declare function generateValidator(config: EndpointConfig): string;