create-wabck 1.0.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,179 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Route Templates
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate src/routes/index.js — Master router */
6
+ export function routesIndex(options) {
7
+ const { auth, language } = options;
8
+ const isTS = language === 'typescript';
9
+ const hasAuth = auth !== 'none';
10
+
11
+ return `import { Router } from 'express';
12
+ import healthRoutes from './health.route.js';
13
+ import userRoutes from './user.route.js';
14
+ ${hasAuth && auth === 'jwt' ? "import authRoutes from './auth.route.js';\n" : ''}
15
+ const router = Router();
16
+
17
+ // ─────────────────────────────────────────────────────────────────────────────
18
+ // Route Registration
19
+ // All routes here are mounted at /api/v1 (configured in app.js)
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ router.use('/health', healthRoutes);
22
+ router.use('/users', userRoutes);
23
+ ${hasAuth && auth === 'jwt' ? "router.use('/auth', authRoutes);\n" : ''}
24
+ export default router;
25
+ `;
26
+ }
27
+
28
+ /** Generate src/routes/health.route.js */
29
+ export function healthRoute(options) {
30
+ const { language, database, projectName } = options;
31
+ const isTS = language === 'typescript';
32
+
33
+ const typeImports = isTS ? `import { Request, Response } from 'express';\n` : '';
34
+
35
+ return `${typeImports}import { Router } from 'express';
36
+ import { readFileSync } from 'fs';
37
+ import { fileURLToPath } from 'url';
38
+ import path from 'path';
39
+
40
+ const __filename = fileURLToPath(import.meta.url);
41
+ const __dirname = path.dirname(__filename);
42
+
43
+ let _version = '1.0.0';
44
+ try {
45
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8'));
46
+ _version = pkg.version;
47
+ } catch { /* ignore */ }
48
+
49
+ const router = Router();
50
+
51
+ /**
52
+ * GET /api/v1/health
53
+ *
54
+ * Returns the current health status of the API including uptime,
55
+ * version, environment, and timestamp.
56
+ */
57
+ router.get('/', (_req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
58
+ res.status(200).json({
59
+ status: 'ok',
60
+ uptime: Math.floor(process.uptime()),
61
+ version: _version,
62
+ env: process.env.NODE_ENV || 'development',
63
+ timestamp: new Date().toISOString(),
64
+ memory: {
65
+ used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + ' MB',
66
+ total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024) + ' MB',
67
+ },
68
+ });
69
+ });
70
+
71
+ export default router;
72
+ `;
73
+ }
74
+
75
+ /** Generate src/routes/user.route.js */
76
+ export function userRoute(options) {
77
+ const { auth, language, validation } = options;
78
+ const isTS = language === 'typescript';
79
+ const hasJwt = auth === 'jwt';
80
+ const hasValidation = validation && validation !== 'none';
81
+
82
+ const authImport = hasJwt
83
+ ? `import { authenticate, authorize } from '../middlewares/auth.middleware.js';\n`
84
+ : auth === 'clerk'
85
+ ? `import { requireAuth } from '../middlewares/auth.middleware.js';\n`
86
+ : auth === 'firebase'
87
+ ? `import { authenticate } from '../middlewares/auth.middleware.js';\n`
88
+ : '';
89
+
90
+ const guardStart = hasJwt ? 'authenticate, ' : auth !== 'none' ? 'requireAuth, ' : '';
91
+ const adminGuard = hasJwt ? 'authenticate, authorize("admin"), ' : guardStart;
92
+
93
+ let validationImport = '';
94
+ let validateCreate = '';
95
+ let validateUpdate = '';
96
+
97
+ if (hasValidation) {
98
+ if (validation === 'zod' || validation === 'joi') {
99
+ validationImport = `import { validate, createUserSchema, updateUserSchema } from '../validators/user.validator.js';\n`;
100
+ validateCreate = `validate(createUserSchema), `;
101
+ validateUpdate = `validate(updateUserSchema), `;
102
+ } else if (validation === 'express-validator') {
103
+ validationImport = `import { createUserRules, updateUserRules, handleValidationErrors } from '../validators/user.validator.js';\n`;
104
+ validateCreate = `createUserRules, handleValidationErrors, `;
105
+ validateUpdate = `updateUserRules, handleValidationErrors, `;
106
+ }
107
+ }
108
+
109
+ return `import { Router } from 'express';
110
+ import {
111
+ getAllUsers,
112
+ getUserById,
113
+ createUser,
114
+ updateUser,
115
+ deleteUser,
116
+ } from '../controllers/user.controller.js';
117
+ ${authImport}${validationImport}
118
+ const router = Router();
119
+
120
+ // ─────────────────────────────────────────────────────────────────────────────
121
+ // User Routes — /api/v1/users
122
+ // ─────────────────────────────────────────────────────────────────────────────
123
+
124
+ router.get('/', ${guardStart}getAllUsers);
125
+ router.post('/', ${guardStart}${validateCreate}createUser);
126
+ router.get('/:id', ${guardStart}getUserById);
127
+ router.put('/:id', ${guardStart}${validateUpdate}updateUser);
128
+ router.delete('/:id', ${adminGuard}deleteUser);
129
+
130
+ export default router;
131
+ `;
132
+ }
133
+
134
+ /** Generate src/routes/auth.route.js (JWT only) */
135
+ export function authRoute(options) {
136
+ const { language, validation } = options;
137
+ const isTS = language === 'typescript';
138
+ const hasValidation = validation && validation !== 'none';
139
+
140
+ let validationImport = '';
141
+ let validateRegister = '';
142
+ let validateLogin = '';
143
+
144
+ if (hasValidation) {
145
+ if (validation === 'zod' || validation === 'joi') {
146
+ validationImport = `import { validate, createUserSchema, loginSchema } from '../validators/user.validator.js';\n`;
147
+ validateRegister = `validate(createUserSchema), `;
148
+ validateLogin = `validate(loginSchema), `;
149
+ } else if (validation === 'express-validator') {
150
+ validationImport = `import { createUserRules, loginRules, handleValidationErrors } from '../validators/user.validator.js';\n`;
151
+ validateRegister = `createUserRules, handleValidationErrors, `;
152
+ validateLogin = `loginRules, handleValidationErrors, `;
153
+ }
154
+ }
155
+
156
+ return `import { Router } from 'express';
157
+ import {
158
+ register,
159
+ login,
160
+ logout,
161
+ getMe,
162
+ } from '../controllers/user.controller.js';
163
+ import { authenticate } from '../middlewares/auth.middleware.js';
164
+ import { authRateLimiter } from '../middlewares/rateLimiter.middleware.js';
165
+ ${validationImport}
166
+ const router = Router();
167
+
168
+ // ─────────────────────────────────────────────────────────────────────────────
169
+ // Auth Routes — /api/v1/auth
170
+ // ─────────────────────────────────────────────────────────────────────────────
171
+
172
+ router.post('/register', authRateLimiter, ${validateRegister}register);
173
+ router.post('/login', authRateLimiter, ${validateLogin}login);
174
+ router.post('/logout', authenticate, logout);
175
+ router.get('/me', authenticate, getMe);
176
+
177
+ export default router;
178
+ `;
179
+ }
@@ -0,0 +1,252 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Service Templates — user.service.js
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate src/services/user.service.js */
6
+ export function userService(options) {
7
+ const { database, auth, language } = options;
8
+ const isTS = language === 'typescript';
9
+ const hasDB = database !== 'none';
10
+
11
+ // ── MongoDB ────────────────────────────────────────────────────────────────
12
+ if (database === 'mongodb') {
13
+ return `import { User } from '../models/user.model.js';
14
+ import { ApiError } from '../utils/apiError.js';
15
+ ${auth === 'jwt' ? "import { generateTokens } from '../helpers/token.helper.js';\n" : ''}
16
+ /**
17
+ * User Service (MongoDB)
18
+ *
19
+ * Encapsulates all business logic related to users.
20
+ * Controllers should call service methods and never directly touch models.
21
+ */
22
+ export const UserService = {
23
+ /** Retrieve all active users (passwords excluded) */
24
+ async getAll()${isTS ? ': Promise<any[]>' : ''} {
25
+ return User.find({ isActive: true }).select('-password').lean();
26
+ },
27
+
28
+ /** Retrieve a single user by ID */
29
+ async getById(id${isTS ? ': string' : ''}) {
30
+ const user = await User.findById(id).select('-password');
31
+ if (!user) throw new ApiError(404, 'User not found');
32
+ return user;
33
+ },
34
+
35
+ /** Create a new user */
36
+ async create(data${isTS ? ': { name: string; email: string; password: string }' : ''}) {
37
+ const exists = await User.findOne({ email: data.email });
38
+ if (exists) throw new ApiError(409, 'A user with this email already exists');
39
+ return User.create(data);
40
+ },
41
+
42
+ /** Update an existing user */
43
+ async update(id${isTS ? ': string' : ''}, data${isTS ? ': Partial<{ name: string; email: string }>' : ''}) {
44
+ const user = await User.findByIdAndUpdate(id, { $set: data }, { new: true, runValidators: true }).select('-password');
45
+ if (!user) throw new ApiError(404, 'User not found');
46
+ return user;
47
+ },
48
+
49
+ /** Soft-delete a user (sets isActive = false) */
50
+ async remove(id${isTS ? ': string' : ''}) {
51
+ const user = await User.findByIdAndUpdate(id, { isActive: false }, { new: true });
52
+ if (!user) throw new ApiError(404, 'User not found');
53
+ return { message: 'User deleted successfully' };
54
+ },
55
+ ${auth === 'jwt' ? `
56
+ /** Register + issue tokens */
57
+ async register(data${isTS ? ': { name: string; email: string; password: string }' : ''}) {
58
+ const exists = await User.findOne({ email: data.email });
59
+ if (exists) throw new ApiError(409, 'Email already in use');
60
+ const user = await User.create(data);
61
+ const tokens = generateTokens({ id: user._id, email: user.email, role: user.role });
62
+ return { user: { id: user._id, name: user.name, email: user.email, role: user.role }, ...tokens };
63
+ },
64
+
65
+ /** Login + issue tokens */
66
+ async login(email${isTS ? ': string' : ''}, password${isTS ? ': string' : ''}) {
67
+ const user = await User.findOne({ email }).select('+password');
68
+ if (!user) throw new ApiError(401, 'Invalid credentials');
69
+ const isMatch = await user.comparePassword(password);
70
+ if (!isMatch) throw new ApiError(401, 'Invalid credentials');
71
+ const tokens = generateTokens({ id: user._id, email: user.email, role: user.role });
72
+ const { password: _pw, ...safeUser } = user.toObject();
73
+ return { user: safeUser, ...tokens };
74
+ },
75
+ ` : ''}};
76
+ `;
77
+ }
78
+
79
+ // ── PostgreSQL / Prisma ────────────────────────────────────────────────────
80
+ if (database === 'postgresql') {
81
+ return `import { prisma } from '../config/db.js';
82
+ import { ApiError } from '../utils/apiError.js';
83
+ import bcrypt from 'bcryptjs';
84
+ ${auth === 'jwt' ? "import { generateTokens } from '../helpers/token.helper.js';\n" : ''}
85
+ export const UserService = {
86
+ async getAll() {
87
+ return prisma.user.findMany({
88
+ where: { isActive: true },
89
+ select: { id: true, name: true, email: true, role: true, createdAt: true, updatedAt: true },
90
+ });
91
+ },
92
+
93
+ async getById(id${isTS ? ': string' : ''}) {
94
+ const user = await prisma.user.findUnique({
95
+ where: { id },
96
+ select: { id: true, name: true, email: true, role: true, createdAt: true, updatedAt: true },
97
+ });
98
+ if (!user) throw new ApiError(404, 'User not found');
99
+ return user;
100
+ },
101
+
102
+ async create(data${isTS ? ': { name: string; email: string; password: string }' : ''}) {
103
+ const exists = await prisma.user.findUnique({ where: { email: data.email } });
104
+ if (exists) throw new ApiError(409, 'A user with this email already exists');
105
+ const password = await bcrypt.hash(data.password, 12);
106
+ const user = await prisma.user.create({ data: { ...data, password } });
107
+ const { password: _pw, ...safe } = user;
108
+ return safe;
109
+ },
110
+
111
+ async update(id${isTS ? ': string' : ''}, data${isTS ? ': any' : ''}) {
112
+ try {
113
+ const user = await prisma.user.update({ where: { id }, data });
114
+ const { password: _pw, ...safe } = user;
115
+ return safe;
116
+ } catch {
117
+ throw new ApiError(404, 'User not found');
118
+ }
119
+ },
120
+
121
+ async remove(id${isTS ? ': string' : ''}) {
122
+ try {
123
+ await prisma.user.update({ where: { id }, data: { isActive: false } });
124
+ return { message: 'User deleted successfully' };
125
+ } catch {
126
+ throw new ApiError(404, 'User not found');
127
+ }
128
+ },
129
+ ${auth === 'jwt' ? `
130
+ async register(data${isTS ? ': { name: string; email: string; password: string }' : ''}) {
131
+ const exists = await prisma.user.findUnique({ where: { email: data.email } });
132
+ if (exists) throw new ApiError(409, 'Email already in use');
133
+ const password = await bcrypt.hash(data.password, 12);
134
+ const user = await prisma.user.create({ data: { ...data, password } });
135
+ const tokens = generateTokens({ id: user.id, email: user.email, role: user.role });
136
+ const { password: _pw, ...safeUser } = user;
137
+ return { user: safeUser, ...tokens };
138
+ },
139
+
140
+ async login(email${isTS ? ': string' : ''}, password${isTS ? ': string' : ''}) {
141
+ const user = await prisma.user.findUnique({ where: { email } });
142
+ if (!user) throw new ApiError(401, 'Invalid credentials');
143
+ const isMatch = await bcrypt.compare(password, user.password);
144
+ if (!isMatch) throw new ApiError(401, 'Invalid credentials');
145
+ const tokens = generateTokens({ id: user.id, email: user.email, role: user.role });
146
+ const { password: _pw, ...safeUser } = user;
147
+ return { user: safeUser, ...tokens };
148
+ },
149
+ ` : ''}};
150
+ `;
151
+ }
152
+
153
+ // ── MySQL ──────────────────────────────────────────────────────────────────
154
+ if (database === 'mysql' || database === 'sqlite') {
155
+ const Model = 'UserModel';
156
+ return `import { ${Model} } from '../models/user.model.js';
157
+ import { ApiError } from '../utils/apiError.js';
158
+ ${auth === 'jwt' ? "import { generateTokens } from '../helpers/token.helper.js';\n" : ''}
159
+ export const UserService = {
160
+ async getAll() {
161
+ return ${Model}.findAll();
162
+ },
163
+
164
+ async getById(id${isTS ? ': number' : ''}) {
165
+ const user = await ${Model}.findById(id);
166
+ if (!user) throw new ApiError(404, 'User not found');
167
+ return user;
168
+ },
169
+
170
+ async create(data${isTS ? ': { name: string; email: string; password: string }' : ''}) {
171
+ const exists = await ${Model}.findByEmail(data.email);
172
+ if (exists) throw new ApiError(409, 'A user with this email already exists');
173
+ return ${Model}.create(data);
174
+ },
175
+
176
+ async update(id${isTS ? ': number' : ''}, data${isTS ? ': Record<string, any>' : ''}) {
177
+ const user = await ${Model}.findById(id);
178
+ if (!user) throw new ApiError(404, 'User not found');
179
+ return ${Model}.update(id, data);
180
+ },
181
+
182
+ async remove(id${isTS ? ': number' : ''}) {
183
+ const user = await ${Model}.findById(id);
184
+ if (!user) throw new ApiError(404, 'User not found');
185
+ await ${Model}.delete(id);
186
+ return { message: 'User deleted successfully' };
187
+ },
188
+ ${auth === 'jwt' ? `
189
+ async register(data${isTS ? ': { name: string; email: string; password: string }' : ''}) {
190
+ const exists = await ${Model}.findByEmail(data.email);
191
+ if (exists) throw new ApiError(409, 'Email already in use');
192
+ const user = await ${Model}.create(data);
193
+ const tokens = generateTokens({ id: String(user.id), email: user.email, role: user.role });
194
+ const { password: _pw, ...safeUser } = user;
195
+ return { user: safeUser, ...tokens };
196
+ },
197
+
198
+ async login(email${isTS ? ': string' : ''}, password${isTS ? ': string' : ''}) {
199
+ const user = await ${Model}.findByEmail(email);
200
+ if (!user) throw new ApiError(401, 'Invalid credentials');
201
+ const isMatch = await ${Model}.comparePassword(password, user.password || '');
202
+ if (!isMatch) throw new ApiError(401, 'Invalid credentials');
203
+ const tokens = generateTokens({ id: String(user.id), email: user.email, role: user.role });
204
+ const { password: _pw, ...safeUser } = user;
205
+ return { user: safeUser, ...tokens };
206
+ },
207
+ ` : ''}};
208
+ `;
209
+ }
210
+
211
+ // ── No database ────────────────────────────────────────────────────────────
212
+ return `import { ApiError } from '../utils/apiError.js';
213
+
214
+ /**
215
+ * User Service — In-Memory Store
216
+ *
217
+ * No database is configured. Uses an in-memory array as a placeholder.
218
+ * Replace with a real database integration before going to production.
219
+ */
220
+
221
+ ${isTS ? 'interface User { id: string; name: string; email: string; createdAt: string; }' : ''}
222
+
223
+ const users${isTS ? ': User[]' : ''} = [];
224
+ let nextId = 1;
225
+
226
+ export const UserService = {
227
+ async getAll() { return users; },
228
+ async getById(id${isTS ? ': string' : ''}) {
229
+ const user = users.find((u) => u.id === id);
230
+ if (!user) throw new ApiError(404, 'User not found');
231
+ return user;
232
+ },
233
+ async create(data${isTS ? ': { name: string; email: string }' : ''}) {
234
+ const user = { id: String(nextId++), ...data, createdAt: new Date().toISOString() };
235
+ users.push(user);
236
+ return user;
237
+ },
238
+ async update(id${isTS ? ': string' : ''}, data${isTS ? ': Partial<User>' : ''}) {
239
+ const idx = users.findIndex((u) => u.id === id);
240
+ if (idx === -1) throw new ApiError(404, 'User not found');
241
+ users[idx] = { ...users[idx], ...data };
242
+ return users[idx];
243
+ },
244
+ async remove(id${isTS ? ': string' : ''}) {
245
+ const idx = users.findIndex((u) => u.id === id);
246
+ if (idx === -1) throw new ApiError(404, 'User not found');
247
+ users.splice(idx, 1);
248
+ return { message: 'User deleted' };
249
+ },
250
+ };
251
+ `;
252
+ }
@@ -0,0 +1,96 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // TypeScript-Specific Templates — tsconfig.json, src/types/index.ts
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate tsconfig.json */
6
+ export function tsconfig() {
7
+ return JSON.stringify(
8
+ {
9
+ compilerOptions: {
10
+ target: 'ES2022',
11
+ module: 'NodeNext',
12
+ moduleResolution: 'NodeNext',
13
+ outDir: './dist',
14
+ rootDir: './src',
15
+ strict: true,
16
+ esModuleInterop: true,
17
+ skipLibCheck: true,
18
+ forceConsistentCasingInFileNames: true,
19
+ resolveJsonModule: true,
20
+ declaration: true,
21
+ declarationMap: true,
22
+ sourceMap: true,
23
+ removeComments: false,
24
+ noUnusedLocals: true,
25
+ noUnusedParameters: false,
26
+ noImplicitReturns: true,
27
+ noFallthroughCasesInSwitch: true,
28
+ useUnknownInCatchVariables: true,
29
+ exactOptionalPropertyTypes: false,
30
+ lib: ['ES2022'],
31
+ types: ['node'],
32
+ },
33
+ include: ['src/**/*.ts'],
34
+ exclude: ['node_modules', 'dist', 'coverage'],
35
+ },
36
+ null,
37
+ 2
38
+ );
39
+ }
40
+
41
+ /** Generate src/types/index.ts */
42
+ export function tsTypes(options) {
43
+ const { auth } = options;
44
+
45
+ return `import type { Request } from 'express';
46
+
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+ // Global Type Extensions
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+
51
+ /**
52
+ * Extend Express Request to carry the authenticated user.
53
+ * Set by the auth middleware after token verification.
54
+ */
55
+ declare global {
56
+ namespace Express {
57
+ interface Request {
58
+ user?: AuthUser;
59
+ }
60
+ }
61
+ }
62
+
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+ // Shared Application Types
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+
67
+ export interface AuthUser {
68
+ id: string;
69
+ email: string;
70
+ role: string;
71
+ }
72
+
73
+ export interface PaginationQuery {
74
+ page?: string;
75
+ limit?: string;
76
+ sort?: string;
77
+ order?: 'asc' | 'desc';
78
+ }
79
+
80
+ export interface PaginatedResult<T> {
81
+ data: T[];
82
+ total: number;
83
+ page: number;
84
+ limit: number;
85
+ totalPages: number;
86
+ }
87
+
88
+ export type AsyncHandler = (
89
+ req: Request,
90
+ res: import('express').Response,
91
+ next: import('express').NextFunction
92
+ ) => Promise<void>;
93
+
94
+ export {};
95
+ `;
96
+ }
@@ -0,0 +1,109 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Utility Templates — asyncHandler, ApiError, ApiResponse
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate src/utils/asyncHandler.js */
6
+ export function asyncHandler(options) {
7
+ const { language } = options;
8
+ const isTS = language === 'typescript';
9
+
10
+ return `${isTS ? `import { Request, Response, NextFunction } from 'express';\n\ntype AsyncRouteHandler = (req: Request, res: Response, next: NextFunction) => Promise<any>;\n` : ''}
11
+ /**
12
+ * asyncHandler
13
+ *
14
+ * Wraps an async Express route handler to catch unhandled promise rejections
15
+ * and pass them to the next error-handling middleware (errorMiddleware).
16
+ *
17
+ * Usage:
18
+ * router.get('/resource', asyncHandler(async (req, res) => { ... }));
19
+ *
20
+ * @param {Function} fn - Async route handler
21
+ * @returns {Function} - Standard Express (req, res, next) handler
22
+ */
23
+ export const asyncHandler = (fn${isTS ? ': AsyncRouteHandler' : ''}) =>
24
+ (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}, next${isTS ? ': NextFunction' : ''}) => {
25
+ Promise.resolve(fn(req, res, next)).catch(next);
26
+ };
27
+ `;
28
+ }
29
+
30
+ /** Generate src/utils/apiError.js */
31
+ export function apiError(options) {
32
+ const { language } = options;
33
+ const isTS = language === 'typescript';
34
+
35
+ return `/**
36
+ * ApiError — Custom Application Error Class
37
+ *
38
+ * Extends the native Error with HTTP status code and structured error list.
39
+ * Throw this in controllers/services and it will be caught by errorMiddleware.
40
+ *
41
+ * @example
42
+ * throw new ApiError(404, 'User not found');
43
+ * throw new ApiError(422, 'Validation failed', ['email is required']);
44
+ */
45
+ export class ApiError extends Error {
46
+ statusCode${isTS ? ': number' : ''};
47
+ errors${isTS ? ': string[]' : ''};
48
+ success${isTS ? ': boolean' : ''};
49
+ data${isTS ? ': null' : ''};
50
+
51
+ constructor(
52
+ statusCode${isTS ? ': number' : ''},
53
+ message = 'An unexpected error occurred',
54
+ errors${isTS ? ': string[]' : ''} = [],
55
+ stack = ''
56
+ ) {
57
+ super(message);
58
+ this.name = 'ApiError';
59
+ this.statusCode = statusCode;
60
+ this.errors = errors;
61
+ this.success = false;
62
+ this.data = null;
63
+
64
+ if (stack) {
65
+ this.stack = stack;
66
+ } else {
67
+ Error.captureStackTrace(this, this.constructor);
68
+ }
69
+ }
70
+ }
71
+ `;
72
+ }
73
+
74
+ /** Generate src/utils/apiResponse.js */
75
+ export function apiResponse(options) {
76
+ const { language } = options;
77
+ const isTS = language === 'typescript';
78
+
79
+ return `/**
80
+ * ApiResponse — Standardized HTTP Response Wrapper
81
+ *
82
+ * Creates a consistent JSON response shape across the entire API.
83
+ *
84
+ * Shape:
85
+ * { success: boolean, statusCode: number, message: string, data: T }
86
+ *
87
+ * @example
88
+ * res.status(200).json(new ApiResponse(200, users, 'Users fetched'));
89
+ * res.status(201).json(new ApiResponse(201, newUser, 'User created'));
90
+ */
91
+ export class ApiResponse${isTS ? '<T = unknown>' : ''} {
92
+ statusCode${isTS ? ': number' : ''};
93
+ data${isTS ? ': T' : ''};
94
+ message${isTS ? ': string' : ''};
95
+ success${isTS ? ': boolean' : ''};
96
+
97
+ constructor(
98
+ statusCode${isTS ? ': number' : ''},
99
+ data${isTS ? ': T' : ''},
100
+ message = 'Success'
101
+ ) {
102
+ this.statusCode = statusCode;
103
+ this.data = data;
104
+ this.message = message;
105
+ this.success = statusCode < 400;
106
+ }
107
+ }
108
+ `;
109
+ }