express-genix 4.3.0 → 4.4.1

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/index.js CHANGED
@@ -54,7 +54,7 @@ async function promptCodaExtension() {
54
54
  } catch {
55
55
  console.log('\n⚠️ Could not install automatically. Install it manually:');
56
56
  console.log(' 1. Download from: https://github.com/LambdaAI001/coda/releases');
57
- console.log(' 2. Then run: code --install-extension coda-ai-0.1.0.vsix');
57
+ console.log(' 2. Then run: code --install-extension coda-ai-0.2.0.vsix');
58
58
  }
59
59
  }
60
60
 
package/lib/features.js CHANGED
@@ -242,6 +242,11 @@ const inferConfig = (projectDir, pkg) => {
242
242
  hasGraphQL: !!(pkg.dependencies && pkg.dependencies['@apollo/server']),
243
243
  hasMCP: !!(pkg.dependencies && pkg.dependencies['@modelcontextprotocol/sdk']),
244
244
  hasAI: !!(pkg.dependencies && pkg.dependencies['@langchain/core']),
245
+ aiProvider: pkg.dependencies && pkg.dependencies['@langchain/openai'] ? 'openai'
246
+ : pkg.dependencies && pkg.dependencies['@langchain/anthropic'] ? 'anthropic'
247
+ : pkg.dependencies && pkg.dependencies['@langchain/google-genai'] ? 'gemini'
248
+ : pkg.dependencies && pkg.dependencies['@langchain/ollama'] ? 'ollama'
249
+ : 'openai',
245
250
  logger: pkg.dependencies && pkg.dependencies.pino ? 'pino' : 'winston',
246
251
  };
247
252
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-genix",
3
- "version": "4.3.0",
3
+ "version": "4.4.1",
4
4
  "description": "Production-grade CLI to generate Express apps with JWT, RBAC, GraphQL, TypeScript, Prisma, MongoDB, PostgreSQL, file uploads, email, background jobs, and more",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  const { Queue } = require('bullmq');
2
- const logger = require('../utils/logger');
2
+ const { logger } = require('../utils/logger');
3
3
 
4
4
  const redisConnection = {
5
5
  host: process.env.REDIS_HOST || 'localhost',
@@ -1,5 +1,5 @@
1
1
  const Redis = require('ioredis');
2
- const logger = require('../utils/logger');
2
+ const { logger } = require('../utils/logger');
3
3
 
4
4
  const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', {
5
5
  maxRetriesPerRequest: 3,
@@ -10,7 +10,7 @@ const chatHandler = async (req, res, next) => {
10
10
  const { message, systemPrompt, history, model, temperature, maxTokens } = req.body;
11
11
 
12
12
  if (!message) {
13
- return res.status(400).json(error('Message is required'));
13
+ return error(res, 'Message is required', 400);
14
14
  }
15
15
 
16
16
  const result = await aiService.chat(message, {
@@ -21,7 +21,7 @@ const chatHandler = async (req, res, next) => {
21
21
  maxTokens,
22
22
  });
23
23
 
24
- res.json(success(result));
24
+ return success(res, result);
25
25
  } catch (err) {
26
26
  next(err);
27
27
  }
@@ -35,7 +35,7 @@ const streamHandler = async (req, res, next) => {
35
35
  const { message, systemPrompt, history, model, temperature, maxTokens } = req.body;
36
36
 
37
37
  if (!message) {
38
- return res.status(400).json(error('Message is required'));
38
+ return error(res, 'Message is required', 400);
39
39
  }
40
40
 
41
41
  res.setHeader('Content-Type', 'text/event-stream');
@@ -69,11 +69,11 @@ const chainHandler = async (req, res, next) => {
69
69
  const { template, variables, model } = req.body;
70
70
 
71
71
  if (!template) {
72
- return res.status(400).json(error('Template is required'));
72
+ return error(res, 'Template is required', 400);
73
73
  }
74
74
 
75
75
  const result = await aiService.chain(template, variables || {}, { model });
76
- res.json(success({ content: result }));
76
+ return success(res, { content: result });
77
77
  } catch (err) {
78
78
  next(err);
79
79
  }
@@ -87,11 +87,11 @@ const agentHandler = async (req, res, next) => {
87
87
  const { message, systemPrompt, model } = req.body;
88
88
 
89
89
  if (!message) {
90
- return res.status(400).json(error('Message is required'));
90
+ return error(res, 'Message is required', 400);
91
91
  }
92
92
 
93
93
  const result = await runAgent(message, { systemPrompt, model });
94
- res.json(success(result));
94
+ return success(res, result);
95
95
  } catch (err) {
96
96
  next(err);
97
97
  }
@@ -65,7 +65,7 @@ const refreshToken = async (req, res, next) => {
65
65
  throw new AppError('Refresh token is required', 400);
66
66
  }
67
67
 
68
- if (authService.isTokenBlacklisted(token)) {
68
+ if (await authService.isTokenBlacklisted(token)) {
69
69
  throw new AppError('Token has been revoked', 401);
70
70
  }
71
71
 
@@ -77,7 +77,7 @@ const refreshToken = async (req, res, next) => {
77
77
  }
78
78
 
79
79
  // Blacklist old refresh token (rotation)
80
- authService.blacklistToken(token);
80
+ await authService.blacklistToken(token);
81
81
 
82
82
  const tokens = authService.generateTokens(user);
83
83
  return success(res, tokens);
@@ -92,12 +92,12 @@ const logout = async (req, res, next) => {
92
92
  const accessToken = authHeader && authHeader.split(' ')[1];
93
93
 
94
94
  if (accessToken) {
95
- authService.blacklistToken(accessToken);
95
+ await authService.blacklistToken(accessToken);
96
96
  }
97
97
 
98
98
  const { refreshToken: token } = req.body;
99
99
  if (token) {
100
- authService.blacklistToken(token);
100
+ await authService.blacklistToken(token);
101
101
  }
102
102
 
103
103
  return success(res, { message: 'Logout successful' });
@@ -1,4 +1,16 @@
1
- FROM node:20-alpine
1
+ FROM node:20-alpine<% if (isTypescript) { %> AS builder
2
+
3
+ # Build stage for TypeScript
4
+ WORKDIR /app
5
+ COPY package*.json ./
6
+ <% if (isPrisma) { %>COPY prisma ./prisma/<% } %>
7
+ RUN npm ci
8
+ <% if (isPrisma) { %>RUN npx prisma generate<% } %>
9
+ COPY tsconfig.json ./
10
+ COPY src ./src/
11
+ RUN npx tsc
12
+
13
+ FROM node:20-alpine<% } %>
2
14
 
3
15
  # Set working directory
4
16
  WORKDIR /app
@@ -9,16 +21,14 @@ COPY package*.json ./
9
21
  # Copy Prisma schema for generation
10
22
  COPY prisma ./prisma/
11
23
  <% } %>
12
- # Install dependencies
24
+ # Install production dependencies
13
25
  RUN npm ci --only=production && npm cache clean --force
14
26
  <% if (isPrisma) { %>
15
27
  # Generate Prisma client
16
28
  RUN npx prisma generate
17
29
  <% } %><% if (isTypescript) { %>
18
- # Copy source and build
19
- COPY tsconfig.json ./
20
- COPY src ./src/
21
- RUN npx tsc
30
+ # Copy compiled output from builder
31
+ COPY --from=builder /app/dist ./dist/
22
32
  <% } else { %>
23
33
  # Copy application code
24
34
  COPY . .
@@ -126,27 +126,27 @@ Interactive docs available at [http://localhost:3000/api-docs](http://localhost:
126
126
 
127
127
  | Method | Endpoint | Description |
128
128
  |--------|----------|-------------|
129
- | POST | `/api/auth/register` | Register a new user |
130
- | POST | `/api/auth/login` | Login and receive tokens |
131
- | POST | `/api/auth/refresh` | Refresh access token |
132
- | POST | `/api/auth/logout` | Logout (blacklist token) |
129
+ | POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/register` | Register a new user |
130
+ | POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/login` | Login and receive tokens |
131
+ | POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/refresh` | Refresh access token |
132
+ | POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/auth/logout` | Logout (blacklist token) |
133
133
 
134
134
  ### Users (protected)
135
135
 
136
136
  | Method | Endpoint | Description |
137
137
  |--------|----------|-------------|
138
- | GET | `/api/users/profile` | Get current user profile |
139
- | PUT | `/api/users/profile` | Update profile |
140
- | DELETE | `/api/users/profile` | Delete account |
138
+ | GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Get current user profile |
139
+ | PUT | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Update profile |
140
+ | DELETE | `/api<% if (hasApiVersioning) { %>/v1<% } %>/users/profile` | Delete account |
141
141
  <% } else { %>### Examples
142
142
 
143
143
  | Method | Endpoint | Description |
144
144
  |--------|----------|-------------|
145
- | GET | `/api/examples` | List all (paginated) |
146
- | GET | `/api/examples/:id` | Get by ID |
147
- | POST | `/api/examples` | Create |
148
- | PUT | `/api/examples/:id` | Update |
149
- | DELETE | `/api/examples/:id` | Delete |
145
+ | GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples` | List all (paginated) |
146
+ | GET | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Get by ID |
147
+ | POST | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples` | Create |
148
+ | PUT | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Update |
149
+ | DELETE | `/api<% if (hasApiVersioning) { %>/v1<% } %>/examples/:id` | Delete |
150
150
  <% } %>
151
151
  ### Health
152
152
 
@@ -11,7 +11,7 @@ services:
11
11
  env_file:
12
12
  - .env<% if (hasDatabase) { %>
13
13
  depends_on:
14
- - <%= db === 'mongodb' ? 'mongodb' : 'postgres' %><% } %><% if (hasRedis) { %>
14
+ - <%= db === 'mongodb' ? 'mongodb' : 'postgres' %><% } %><% if (hasRedis || hasBackgroundJobs) { %>
15
15
  - redis<% } %>
16
16
  restart: unless-stopped
17
17
  networks:
@@ -27,7 +27,7 @@ services:
27
27
  - mongodb_data:/data/db
28
28
  restart: unless-stopped
29
29
  networks:
30
- - app-network<% } else if (db === 'postgresql') { %> postgres:
30
+ - app-network<% } else if (db === 'postgresql' || db === 'prisma') { %> postgres:
31
31
  image: postgres:15-alpine
32
32
  ports:
33
33
  - "5432:5432"
@@ -41,7 +41,7 @@ services:
41
41
  networks:
42
42
  - app-network<% } %>
43
43
 
44
- <% if (hasRedis) { %> redis:
44
+ <% if (hasRedis || hasBackgroundJobs) { %> redis:
45
45
  image: redis:7-alpine
46
46
  ports:
47
47
  - "6379:6379"
@@ -51,9 +51,9 @@ services:
51
51
  networks:
52
52
  - app-network
53
53
  <% } %>
54
- <% if (hasDatabase || hasRedis) { %>volumes:<% } %>
54
+ <% if (hasDatabase || hasRedis || hasBackgroundJobs) { %>volumes:<% } %>
55
55
  <% if (hasDatabase) { %> <%= db === 'mongodb' ? 'mongodb_data:' : 'postgres_data:' %><% } %>
56
- <% if (hasRedis) { %> redis_data:<% } %>
56
+ <% if (hasRedis || hasBackgroundJobs) { %> redis_data:<% } %>
57
57
 
58
58
  networks:
59
59
  app-network:
@@ -19,7 +19,7 @@
19
19
  "db:migrate:undo": "npx sequelize-cli db:migrate:undo",
20
20
  "db:seed": "npx sequelize-cli db:seed:all",
21
21
  "db:create": "npx sequelize-cli db:create"<% } %><% if (hasMCP) { %>,
22
- "mcp:start": "node src/mcp/server.js"<% } %>
22
+ "mcp:start": "<% if (isTypescript) { %>tsx src/mcp/server.ts<% } else { %>node src/mcp/server.js<% } %>"<% } %>
23
23
  },
24
24
  "dependencies": {
25
25
  "express": "^4.21.0",
@@ -73,7 +73,10 @@
73
73
  "@types/swagger-jsdoc": "^6.0.4",
74
74
  "@types/swagger-ui-express": "^4.1.6"<% } %><% if (hasRequestId) { %>,
75
75
  "@types/uuid": "^10.0.0"<% } %>,<% } %>
76
- "jest": "^29.7.0",
76
+ "jest": "^29.7.0",<% if (isTypescript) { %>
77
+ "ts-jest": "^29.2.0",
78
+ "@typescript-eslint/parser": "^7.0.0",
79
+ "@typescript-eslint/eslint-plugin": "^7.0.0",<% } %>
77
80
  "supertest": "^7.0.0",
78
81
  "eslint": "^8.57.0",
79
82
  "eslint-config-airbnb-base": "^15.0.0",
@@ -1,6 +1,6 @@
1
1
  const { Worker } = require('bullmq');
2
2
  const { redisConnection } = require('../config/queue');
3
- const logger = require('../utils/logger');
3
+ const { logger } = require('../utils/logger');
4
4
 
5
5
  // Email job processor
6
6
  const emailProcessor = async (job) => {
@@ -5,7 +5,7 @@ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio
5
5
  const { z } = require('zod');
6
6
  <% if (hasDatabase) { %>const db = require('../config/database');
7
7
  <% } %><% if (hasAuth) { %>const userService = require('../services/userService');
8
- <% } %><% if (!hasAuth && !isNoDatabase) { %>const exampleService = require('../services/exampleService');
8
+ <% } %><% if (!hasAuth) { %>const exampleService = require('../services/exampleService');
9
9
  <% } %><% if (hasEmail) { %>const emailService = require('../services/emailService');
10
10
  <% } %>
11
11
 
@@ -82,7 +82,7 @@ server.tool(
82
82
  limit: z.number().optional().describe('Items per page (default: 20)'),
83
83
  },
84
84
  async ({ page = 1, limit = 20 }) => {
85
- const result = await exampleService.getAll({ page, limit });
85
+ const result = await exampleService.getAllExamples(page, limit);
86
86
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
87
87
  }
88
88
  );
@@ -95,7 +95,7 @@ server.tool(
95
95
  description: z.string().optional().describe('Item description'),
96
96
  },
97
97
  async ({ title, description }) => {
98
- const item = await exampleService.create({ title, description });
98
+ const item = await exampleService.createExample({ title, description });
99
99
  return { content: [{ type: 'text', text: JSON.stringify(item, null, 2) }] };
100
100
  }
101
101
  );
@@ -27,7 +27,7 @@ const authenticateToken = <% if (hasRedis) { %>async <% } %>(req, res, next) =>
27
27
 
28
28
  jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
29
29
  if (err) {
30
- return next(new AppError('Invalid or expired token', 403));
30
+ return next(new AppError('Invalid or expired token', 401));
31
31
  }
32
32
  req.user = decoded;
33
33
  next();
@@ -65,16 +65,4 @@ const optionalAuth = <% if (hasRedis) { %>async <% } %>(req, res, next) => {
65
65
  next();
66
66
  };
67
67
 
68
- const requireRole = (role) => {
69
- return (req, res, next) => {
70
- if (!req.user) {
71
- return next(new AppError('Authentication required', 401));
72
- }
73
- if (req.user.role !== role) {
74
- return next(new AppError('Insufficient permissions', 403));
75
- }
76
- next();
77
- };
78
- };
79
-
80
- module.exports = { authenticateToken, optionalAuth, requireRole };
68
+ module.exports = { authenticateToken, optionalAuth };
@@ -5,7 +5,7 @@ const router = express.Router();
5
5
 
6
6
  /**
7
7
  * @swagger
8
- * /api<% if (hasApiVersioning) { %>/v1<% } %>/ai/chat:
8
+ * <% if (hasApiVersioning) { %>/v1<% } %>/ai/chat:
9
9
  * post:
10
10
  * summary: Send a chat message to the AI
11
11
  * tags: [AI]
@@ -33,9 +33,6 @@ const router = express.Router();
33
33
  * enum: [user, assistant]
34
34
  * content:
35
35
  * type: string
36
- * provider:
37
- * type: string
38
- * enum: [openai, anthropic, gemini, ollama]
39
36
  * model:
40
37
  * type: string
41
38
  * responses:
@@ -46,7 +43,7 @@ router.post('/chat', chatHandler);
46
43
 
47
44
  /**
48
45
  * @swagger
49
- * /api<% if (hasApiVersioning) { %>/v1<% } %>/ai/stream:
46
+ * <% if (hasApiVersioning) { %>/v1<% } %>/ai/stream:
50
47
  * post:
51
48
  * summary: Stream an AI response via Server-Sent Events
52
49
  * tags: [AI]
@@ -68,7 +65,7 @@ router.post('/stream', streamHandler);
68
65
 
69
66
  /**
70
67
  * @swagger
71
- * /api<% if (hasApiVersioning) { %>/v1<% } %>/ai/chain:
68
+ * <% if (hasApiVersioning) { %>/v1<% } %>/ai/chain:
72
69
  * post:
73
70
  * summary: Run a prompt template chain with variable substitution
74
71
  * tags: [AI]
@@ -86,9 +83,6 @@ router.post('/stream', streamHandler);
86
83
  * variables:
87
84
  * type: object
88
85
  * example: { "text": "Node.js is a JavaScript runtime..." }
89
- * provider:
90
- * type: string
91
- * enum: [openai, anthropic, gemini, ollama]
92
86
  * responses:
93
87
  * 200:
94
88
  * description: Chain result
@@ -97,7 +91,7 @@ router.post('/chain', chainHandler);
97
91
 
98
92
  /**
99
93
  * @swagger
100
- * /api<% if (hasApiVersioning) { %>/v1<% } %>/ai/agent:
94
+ * <% if (hasApiVersioning) { %>/v1<% } %>/ai/agent:
101
95
  * post:
102
96
  * summary: Run the LangGraph ReAct agent with tool calling
103
97
  * tags: [AI]
@@ -114,9 +108,6 @@ router.post('/chain', chainHandler);
114
108
  * example: "What time is it right now?"
115
109
  * systemPrompt:
116
110
  * type: string
117
- * provider:
118
- * type: string
119
- * enum: [openai, anthropic, gemini, ollama]
120
111
  * responses:
121
112
  * 200:
122
113
  * description: Agent response with tool-use steps
@@ -21,7 +21,7 @@ router.use('/admin', adminRoutes);
21
21
 
22
22
  /**
23
23
  * @swagger
24
- * /api:
24
+ * /:
25
25
  * get:
26
26
  * summary: API root
27
27
  * responses:
@@ -2,7 +2,7 @@ const { getModel } = require('../config/ai');
2
2
  const { HumanMessage, SystemMessage, AIMessage } = require('@langchain/core/messages');
3
3
  const { StringOutputParser } = require('@langchain/core/output_parsers');
4
4
  const { ChatPromptTemplate } = require('@langchain/core/prompts');
5
- const logger = require('../utils/logger');
5
+ const { logger } = require('../utils/logger');
6
6
 
7
7
  const DEFAULT_SYSTEM_PROMPT = 'You are a helpful AI assistant.';
8
8
 
@@ -1,5 +1,5 @@
1
1
  const nodemailer = require('nodemailer');
2
- const logger = require('../utils/logger');
2
+ const { logger } = require('../utils/logger');
3
3
 
4
4
  let transporter;
5
5
 
@@ -17,9 +17,9 @@ describe('Example Endpoints', () => {
17
17
  .get('/api/examples?page=1&limit=1');
18
18
 
19
19
  expect(res.statusCode).toBe(200);
20
- expect(res.body.meta).toHaveProperty('page', 1);
21
- expect(res.body.meta).toHaveProperty('limit', 1);
22
- expect(res.body.meta).toHaveProperty('total');
20
+ expect(res.body.meta.pagination).toHaveProperty('page', 1);
21
+ expect(res.body.meta.pagination).toHaveProperty('limit', 1);
22
+ expect(res.body.meta.pagination).toHaveProperty('total');
23
23
  });
24
24
  });
25
25
 
@@ -1,7 +1,7 @@
1
1
  // templates/tests/setup.js.ejs
2
2
  // Global test setup
3
3
  process.env.NODE_ENV = 'test';
4
- <% if (hasDatabase) { %>process.env.JWT_SECRET = 'test-secret';
4
+ <% if (hasAuth) { %>process.env.JWT_SECRET = 'test-secret';
5
5
  process.env.JWT_REFRESH_SECRET = 'test-refresh-secret';<% } %>
6
6
 
7
7
  // Suppress console logs during tests