nestcraftx 0.4.0 → 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.
Files changed (54) hide show
  1. package/.github/workflows/ci.yml +35 -0
  2. package/CHANGELOG.fr.md +74 -0
  3. package/CHANGELOG.md +74 -0
  4. package/PROGRESS.md +59 -57
  5. package/README.fr.md +60 -69
  6. package/bin/nestcraft.js +8 -0
  7. package/commands/demo.js +10 -0
  8. package/commands/generate.js +551 -2
  9. package/commands/generateConf.js +4 -4
  10. package/commands/help.js +11 -0
  11. package/commands/info.js +2 -3
  12. package/commands/list.js +93 -0
  13. package/commands/new.js +26 -9
  14. package/jest.config.js +9 -0
  15. package/package.json +7 -2
  16. package/readme.md +59 -68
  17. package/tests/e2e/generator.spec.js +71 -0
  18. package/tests/unit/appModuleUpdater.spec.js +111 -0
  19. package/tests/unit/cliParser.spec.js +74 -0
  20. package/tests/unit/controllerGenerator.spec.js +145 -0
  21. package/tests/unit/dtoGenerator.spec.js +87 -0
  22. package/tests/unit/entityGenerator.spec.js +65 -0
  23. package/tests/unit/generateCommand.spec.js +100 -0
  24. package/tests/unit/health.spec.js +69 -0
  25. package/tests/unit/listCommand.spec.js +91 -0
  26. package/tests/unit/middlewareGenerator.spec.js +64 -0
  27. package/tests/unit/newCommand.spec.js +88 -0
  28. package/tests/unit/relationCommand.spec.js +202 -0
  29. package/tests/unit/throttler.spec.js +117 -0
  30. package/utils/configs/setupCleanArchitecture.js +1 -1
  31. package/utils/configs/setupLightArchitecture.js +98 -26
  32. package/utils/envGenerator.js +3 -1
  33. package/utils/file-utils/saveProjectConfig.js +3 -0
  34. package/utils/fullModeInput.js +4 -0
  35. package/utils/generators/application/dtoGenerator.js +20 -3
  36. package/utils/generators/cleanModuleGenerator.js +40 -11
  37. package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
  38. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +78 -1
  39. package/utils/generators/infrastructure/repositoryGenerator.js +114 -14
  40. package/utils/generators/lightModuleGenerator.js +14 -0
  41. package/utils/generators/presentation/controllerGenerator.js +70 -19
  42. package/utils/interactive/askRelationCommand.js +98 -0
  43. package/utils/interactive/entityBuilder.js +11 -0
  44. package/utils/lightModeInput.js +14 -0
  45. package/utils/setups/orms/typeOrmSetup.js +11 -4
  46. package/utils/setups/projectSetup.js +10 -2
  47. package/utils/setups/setupAuth.js +334 -18
  48. package/utils/setups/setupDatabase.js +4 -1
  49. package/utils/setups/setupHealth.js +74 -0
  50. package/utils/setups/setupMongoose.js +4 -1
  51. package/utils/setups/setupPrisma.js +110 -1
  52. package/utils/setups/setupSwagger.js +4 -1
  53. package/utils/setups/setupThrottler.js +92 -0
  54. package/utils/shell.js +21 -4
@@ -211,6 +211,8 @@ async function getFullModeInputs(projectName, flags) {
211
211
  { name: "auth", default: true, prompt: "Add JWT authentication?" },
212
212
 
213
213
  { name: "swagger", default: true, prompt: "Install Swagger?" },
214
+
215
+ { name: "throttler", default: true, prompt: "Enable Rate Limiting (Throttler)?" },
214
216
  ];
215
217
 
216
218
  const booleanResults = {};
@@ -239,6 +241,7 @@ async function getFullModeInputs(projectName, flags) {
239
241
  const useDocker = booleanResults.docker;
240
242
  const useAuth = booleanResults.auth;
241
243
  const useSwagger = booleanResults.swagger;
244
+ const useThrottler = booleanResults.throttler;
242
245
 
243
246
  // --- 5. Swagger Configuration (Prioritize flags) ---
244
247
  let swaggerInputs;
@@ -357,6 +360,7 @@ async function getFullModeInputs(projectName, flags) {
357
360
  useDocker,
358
361
  useAuth,
359
362
  useSwagger,
363
+ useThrottler,
360
364
  swaggerInputs,
361
365
  packageManager,
362
366
  entitiesData,
@@ -47,13 +47,30 @@ async function generateDto(
47
47
  SWAGGER HELPERS
48
48
  =============================== */
49
49
  const getFieldDescription = (f) => {
50
+ if (f.description) return f.description;
51
+
50
52
  const name = f.name.toLowerCase();
51
53
  if (name.includes("email")) return "The official email address of the user";
52
54
  if (name.includes("password"))
53
55
  return "Must contain at least 8 characters, one letter and one number";
54
56
  if (name.includes("token")) return "Authentication token";
55
- if (name.includes("id") && name !== "id")
56
- return `Unique identifier of the related ${name.replace("id", "")}`;
57
+ if (name.endsWith("id") && name !== "id")
58
+ return `UUID of the referenced ${capitalize(f.name.slice(0, -2))}`;
59
+ if (name.endsWith("ids"))
60
+ return `Array of UUIDs of the referenced ${capitalize(f.name.slice(0, -3))}`;
61
+
62
+ const cleanType = f.type.toLowerCase().replace("[]", "");
63
+ if (cleanType === "date" || name.endsWith("date") || name.endsWith("at")) {
64
+ return "ISO 8601 date string (e.g. 2026-06-16T15:00:00Z)";
65
+ }
66
+ if (name.includes("price") || name.includes("amount")) {
67
+ return "Decimal value in USD (e.g. 99.99)";
68
+ }
69
+ if (cleanType === "boolean") {
70
+ const defSuffix = f.default !== undefined && f.default !== null ? ` — default: ${f.default}` : "";
71
+ return `true/false flag${defSuffix}`;
72
+ }
73
+
57
74
  return `The ${f.name} of the ${entityNameLower}`;
58
75
  };
59
76
 
@@ -229,7 +246,7 @@ export class Create${entityName}Dto {
229
246
  }
230
247
 
231
248
  /**
232
- * DTO for updating a ${entityName}
249
+ * DTO for updating a ${entityName} (All fields are optional)
233
250
  */
234
251
  ${
235
252
  useSwagger
@@ -64,7 +64,7 @@ async function generateCleanModule(name, config, entityData) {
64
64
  });
65
65
 
66
66
  // 4. Génération du Repository (Implementation)
67
- await generateRepository(name, config.orm);
67
+ await generateRepository(name, config.orm, entityData);
68
68
 
69
69
  // 5. Génération des Use Cases (Create, Get, Update, Delete)
70
70
  await generateUseCases(entityPath, entityNameCapitalized, entityNameLower);
@@ -83,10 +83,25 @@ async function generateCleanModule(name, config, entityData) {
83
83
  contente: mapperContent,
84
84
  });
85
85
 
86
+ if (entityNameLower === "user") {
87
+ await createDirectory(`${entityPath}/domain/enums`);
88
+ await createFile({
89
+ path: `${entityPath}/domain/enums/role.enum.ts`,
90
+ contente: `// Enumération des rôles utilisateurs
91
+ export enum Role {
92
+ USER = 'USER',
93
+ ADMIN = 'ADMIN',
94
+ SUPER_ADMIN = 'SUPER_ADMIN',
95
+ }
96
+ `,
97
+ });
98
+ }
99
+
86
100
  // ÉTAPE RELATIONS : Patching des fichiers si une relation existe
87
101
  if (
88
102
  entityData.relation &&
89
- (entityData.relation.type != "1-n" || entityData.relation.type != "n-n")
103
+ entityData.relation.type !== "1-n" &&
104
+ entityData.relation.type !== "n-n"
90
105
  ) {
91
106
  const { target, type } = entityData.relation;
92
107
  logInfo(
@@ -114,6 +129,7 @@ async function generateCleanModule(name, config, entityData) {
114
129
  name,
115
130
  entityPath,
116
131
  config.swagger,
132
+ config.auth || false,
117
133
  );
118
134
  await createFile({
119
135
  path: `${entityPath}/presentation/controllers/${entityNameLower}.controller.ts`,
@@ -146,6 +162,10 @@ async function generateCleanModule(name, config, entityData) {
146
162
  // --- HELPERS DE TEMPLATES (Extraits de ton setup original) ---
147
163
 
148
164
  function getRepositoryInterfaceTemplate(cap, low) {
165
+ let findByEmailMethod = "";
166
+ if (low === "user") {
167
+ findByEmailMethod = `\n findByEmail(email: string): Promise<${cap}Entity | null>;`;
168
+ }
149
169
  return `import { Create${cap}Dto, Update${cap}Dto } from 'src/${low}/application/dtos/${low}.dto';
150
170
  import { ${cap}Entity } from 'src/${low}/domain/entities/${low}.entity';
151
171
 
@@ -154,9 +174,9 @@ export const I${cap}RepositoryName = 'I${cap}Repository';
154
174
  export interface I${cap}Repository {
155
175
  create(data: Create${cap}Dto): Promise<${cap}Entity>;
156
176
  findById(id: string): Promise<${cap}Entity | null>;
157
- findAll(): Promise<${cap}Entity[]>;
177
+ findAll(page?: number, limit?: number, search?: string, sortBy?: string, sortOrder?: string): Promise<{ data: ${cap}Entity[], total: number }>;
158
178
  update(id: string, data: Update${cap}Dto): Promise<${cap}Entity | null>;
159
- delete(id: string): Promise<void>;
179
+ delete(id: string): Promise<void>;${findByEmailMethod}
160
180
  }`;
161
181
  }
162
182
 
@@ -237,11 +257,20 @@ export class GetAll${cap}UseCase {
237
257
  private readonly repository: I${cap}Repository,
238
258
  ) {}
239
259
 
240
- async execute(): Promise<${cap}Entity[]> {
241
- this.logger.log('Requesting all ${cap} records');
242
- const results = await this.repository.findAll();
243
- this.logger.log(\`Retrieved \${results.length} ${cap}(s)\`);
244
- return results;
260
+ async execute(page: number = 1, limit: number = 10, search?: string, sortBy?: string, sortOrder?: string): Promise<{ data: ${cap}Entity[], meta: { total: number, page: number, limit: number, totalPages: number } }> {
261
+ this.logger.log(\`Requesting page \${page} of ${cap} records (limit: \${limit}, search: \${search}, sortBy: \${sortBy}, sortOrder: \${sortOrder})\`);
262
+ const { data, total } = await this.repository.findAll(page, limit, search, sortBy, sortOrder);
263
+ const totalPages = Math.ceil(total / limit);
264
+ this.logger.log(\`Retrieved \${data.length} of \${total} ${cap}(s)\`);
265
+ return {
266
+ data,
267
+ meta: {
268
+ total,
269
+ page,
270
+ limit,
271
+ totalPages,
272
+ },
273
+ };
245
274
  }
246
275
  }`;
247
276
  break;
@@ -430,8 +459,8 @@ export class ${entityNameCapitalized}Service {
430
459
  async getById(id: string): Promise<${entityNameCapitalized}Entity | null> {
431
460
  return await this.getByIdUseCase.execute(id);
432
461
  }
433
- async getAll(): Promise<${entityNameCapitalized}Entity[]> {
434
- return await this.getAllUseCase.execute();
462
+ async getAll(page: number = 1, limit: number = 10, search?: string, sortBy?: string, sortOrder?: string): Promise<{ data: ${entityNameCapitalized}Entity[], meta: { total: number, page: number, limit: number, totalPages: number } }> {
463
+ return await this.getAllUseCase.execute(page, limit, search, sortBy, sortOrder);
435
464
  }
436
465
  async delete(id: string): Promise<void> {
437
466
  return await this.deleteUseCase.execute(id);
@@ -33,42 +33,10 @@ const { createDirectory, createFile, updateFile } = require("../../file-system")
33
33
  * @returns {string}
34
34
  */
35
35
  function getExceptionFilterContent(orm) {
36
+ let dbSpecificChecks = "";
36
37
  if (orm === "prisma") {
37
- return `
38
- import {
39
- ExceptionFilter,
40
- Catch,
41
- ArgumentsHost,
42
- HttpException,
43
- HttpStatus,
44
- Logger,
45
- } from '@nestjs/common';
46
- import { Request, Response } from 'express';
47
-
48
- @Catch()
49
- export class AllExceptionsFilter implements ExceptionFilter {
50
- private readonly logger = new Logger(AllExceptionsFilter.name);
51
-
52
- catch(exception: unknown, host: ArgumentsHost) {
53
- const ctx = host.switchToHttp();
54
- const response = ctx.getResponse<Response>();
55
- const request = ctx.getRequest<Request>();
56
-
57
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
58
- let message: string | string[] = 'Internal server error';
59
- let errorDetails: any = null;
60
-
61
- if (exception instanceof HttpException) {
62
- status = exception.getStatus();
63
- const res = exception.getResponse();
64
- if (typeof res === 'string') {
65
- message = res;
66
- } else if (typeof res === 'object' && res !== null) {
67
- const resObj = res as any;
68
- message = resObj.message || resObj.error || 'HttpException';
69
- errorDetails = resObj;
70
- }
71
- } else if (
38
+ dbSpecificChecks = `
39
+ else if (
72
40
  typeof exception === 'object' &&
73
41
  exception &&
74
42
  exception.constructor &&
@@ -78,75 +46,13 @@ export class AllExceptionsFilter implements ExceptionFilter {
78
46
  )
79
47
  ) {
80
48
  status = HttpStatus.BAD_REQUEST;
81
- message = (exception as any).message || 'Prisma error';
49
+ message = (exception as any).message || 'Prisma database error';
50
+ errorResponseName = exception.constructor.name;
82
51
  errorDetails = exception;
83
- } else if (exception instanceof Error) {
84
- message = exception.message;
85
- errorDetails = {
86
- name: exception.name,
87
- stack: exception.stack,
88
- };
89
- } else {
90
- message = 'Une erreur inattendue est survenue';
91
- errorDetails = exception;
92
- }
93
-
94
- if (process.env.NODE_ENV !== 'production') {
95
- this.logger.error(
96
- \`Exception on \${request.method} \${request.url}\`,
97
- JSON.stringify({ message, status, errorDetails, exception }),
98
- );
99
- }
100
-
101
- response.status(status).json({
102
- statusCode: status,
103
- timestamp: new Date().toISOString(),
104
- path: request.url,
105
- method: request.method,
106
- message,
107
- error: errorDetails,
108
- });
109
- }
110
- }
111
- `.trim();
112
- }
113
-
114
- if (orm === "mongoose") {
115
- return `
116
- import {
117
- ExceptionFilter,
118
- Catch,
119
- ArgumentsHost,
120
- HttpException,
121
- HttpStatus,
122
- Logger,
123
- } from '@nestjs/common';
124
- import { Request, Response } from 'express';
125
-
126
- @Catch()
127
- export class AllExceptionsFilter implements ExceptionFilter {
128
- private readonly logger = new Logger(AllExceptionsFilter.name);
129
-
130
- catch(exception: unknown, host: ArgumentsHost) {
131
- const ctx = host.switchToHttp();
132
- const response = ctx.getResponse<Response>();
133
- const request = ctx.getRequest<Request>();
134
-
135
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
136
- let message: string | string[] = 'Internal server error';
137
- let errorDetails: any = null;
138
-
139
- if (exception instanceof HttpException) {
140
- status = exception.getStatus();
141
- const res = exception.getResponse();
142
- if (typeof res === 'string') {
143
- message = res;
144
- } else if (typeof res === 'object' && res !== null) {
145
- const resObj = res as any;
146
- message = resObj.message || resObj.error || 'HttpException';
147
- errorDetails = resObj;
148
- }
149
- } else if (
52
+ }`;
53
+ } else if (orm === "mongoose") {
54
+ dbSpecificChecks = `
55
+ else if (
150
56
  typeof exception === 'object' &&
151
57
  exception &&
152
58
  'name' in exception &&
@@ -156,75 +62,78 @@ export class AllExceptionsFilter implements ExceptionFilter {
156
62
  )
157
63
  ) {
158
64
  status = HttpStatus.BAD_REQUEST;
159
- message = (exception as any).message || 'MongoDB error';
65
+ message = (exception as any).message || 'Mongoose database error';
66
+ errorResponseName = (exception as any).name;
160
67
  errorDetails = exception;
161
- } else if (exception instanceof Error) {
162
- message = exception.message;
163
- errorDetails = {
164
- name: exception.name,
165
- stack: exception.stack,
166
- };
167
- } else {
168
- message = 'Une erreur inattendue est survenue';
68
+ }`;
69
+ } else if (orm === "typeorm") {
70
+ dbSpecificChecks = `
71
+ else if (
72
+ typeof exception === 'object' &&
73
+ exception &&
74
+ 'name' in exception &&
75
+ (
76
+ (exception as any).name === 'QueryFailedError' ||
77
+ (exception as any).name === 'EntityNotFoundError' ||
78
+ (exception as any).name === 'CannotCreateEntityIdMapError'
79
+ )
80
+ ) {
81
+ status = HttpStatus.BAD_REQUEST;
82
+ message = (exception as any).message || 'TypeORM database error';
83
+ errorResponseName = (exception as any).name;
84
+ errorDetails = exception;
85
+ }`;
86
+ } else if (orm === "sequelize") {
87
+ dbSpecificChecks = `
88
+ else if (
89
+ typeof exception === 'object' &&
90
+ exception &&
91
+ exception.constructor &&
92
+ (
93
+ exception.constructor.name === 'SequelizeDatabaseError' ||
94
+ exception.constructor.name === 'SequelizeValidationError'
95
+ )
96
+ ) {
97
+ status = HttpStatus.BAD_REQUEST;
98
+ message = (exception as any).message || 'Sequelize database error';
99
+ errorResponseName = exception.constructor.name;
100
+ errorDetails = exception;
101
+ }`;
102
+ } else {
103
+ // fallback global / universal
104
+ dbSpecificChecks = `
105
+ // Prisma
106
+ else if (
107
+ typeof exception === 'object' &&
108
+ exception &&
109
+ exception.constructor &&
110
+ (
111
+ exception.constructor.name === 'PrismaClientKnownRequestError' ||
112
+ exception.constructor.name === 'PrismaClientValidationError'
113
+ )
114
+ ) {
115
+ status = HttpStatus.BAD_REQUEST;
116
+ message = (exception as any).message || 'Prisma database error';
117
+ errorResponseName = exception.constructor.name;
169
118
  errorDetails = exception;
170
119
  }
171
-
172
- if (process.env.NODE_ENV !== 'production') {
173
- this.logger.error(
174
- \`Exception on \${request.method} \${request.url}\`,
175
- JSON.stringify({ message, status, errorDetails, exception }),
176
- );
120
+ // Mongoose/Mongo
121
+ else if (
122
+ typeof exception === 'object' &&
123
+ exception &&
124
+ 'name' in exception &&
125
+ (
126
+ (exception as any).name === 'MongoError' ||
127
+ (exception as any).name === 'MongooseError'
128
+ )
129
+ ) {
130
+ status = HttpStatus.BAD_REQUEST;
131
+ message = (exception as any).message || 'Mongoose database error';
132
+ errorResponseName = (exception as any).name;
133
+ errorDetails = exception;
177
134
  }
178
-
179
- response.status(status).json({
180
- statusCode: status,
181
- timestamp: new Date().toISOString(),
182
- path: request.url,
183
- method: request.method,
184
- message,
185
- error: errorDetails,
186
- });
187
- }
188
- }
189
- `.trim();
190
- }
191
-
192
- if (orm === "typeorm") {
193
- return `
194
- import {
195
- ExceptionFilter,
196
- Catch,
197
- ArgumentsHost,
198
- HttpException,
199
- HttpStatus,
200
- Logger,
201
- } from '@nestjs/common';
202
- import { Request, Response } from 'express';
203
-
204
- @Catch()
205
- export class AllExceptionsFilter implements ExceptionFilter {
206
- private readonly logger = new Logger(AllExceptionsFilter.name);
207
-
208
- catch(exception: unknown, host: ArgumentsHost) {
209
- const ctx = host.switchToHttp();
210
- const response = ctx.getResponse<Response>();
211
- const request = ctx.getRequest<Request>();
212
-
213
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
214
- let message: string | string[] = 'Internal server error';
215
- let errorDetails: any = null;
216
-
217
- if (exception instanceof HttpException) {
218
- status = exception.getStatus();
219
- const res = exception.getResponse();
220
- if (typeof res === 'string') {
221
- message = res;
222
- } else if (typeof res === 'object' && res !== null) {
223
- const resObj = res as any;
224
- message = resObj.message || resObj.error || 'HttpException';
225
- errorDetails = resObj;
226
- }
227
- } else if (
135
+ // TypeORM
136
+ else if (
228
137
  typeof exception === 'object' &&
229
138
  exception &&
230
139
  'name' in exception &&
@@ -235,75 +144,12 @@ export class AllExceptionsFilter implements ExceptionFilter {
235
144
  )
236
145
  ) {
237
146
  status = HttpStatus.BAD_REQUEST;
238
- message = (exception as any).message || 'TypeORM error';
239
- errorDetails = exception;
240
- } else if (exception instanceof Error) {
241
- message = exception.message;
242
- errorDetails = {
243
- name: exception.name,
244
- stack: exception.stack,
245
- };
246
- } else {
247
- message = 'Une erreur inattendue est survenue';
147
+ message = (exception as any).message || 'TypeORM database error';
148
+ errorResponseName = (exception as any).name;
248
149
  errorDetails = exception;
249
150
  }
250
-
251
- if (process.env.NODE_ENV !== 'production') {
252
- this.logger.error(
253
- \`Exception on \${request.method} \${request.url}\`,
254
- JSON.stringify({ message, status, errorDetails, exception }),
255
- );
256
- }
257
-
258
- response.status(status).json({
259
- statusCode: status,
260
- timestamp: new Date().toISOString(),
261
- path: request.url,
262
- method: request.method,
263
- message,
264
- error: errorDetails,
265
- });
266
- }
267
- }
268
- `.trim();
269
- }
270
-
271
- if (orm === "sequelize") {
272
- return `
273
- import {
274
- ExceptionFilter,
275
- Catch,
276
- ArgumentsHost,
277
- HttpException,
278
- HttpStatus,
279
- Logger,
280
- } from '@nestjs/common';
281
- import { Request, Response } from 'express';
282
-
283
- @Catch()
284
- export class AllExceptionsFilter implements ExceptionFilter {
285
- private readonly logger = new Logger(AllExceptionsFilter.name);
286
-
287
- catch(exception: unknown, host: ArgumentsHost) {
288
- const ctx = host.switchToHttp();
289
- const response = ctx.getResponse<Response>();
290
- const request = ctx.getRequest<Request>();
291
-
292
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
293
- let message: string | string[] = 'Internal server error';
294
- let errorDetails: any = null;
295
-
296
- if (exception instanceof HttpException) {
297
- status = exception.getStatus();
298
- const res = exception.getResponse();
299
- if (typeof res === 'string') {
300
- message = res;
301
- } else if (typeof res === 'object' && res !== null) {
302
- const resObj = res as any;
303
- message = resObj.message || resObj.error || 'HttpException';
304
- errorDetails = resObj;
305
- }
306
- } else if (
151
+ // Sequelize
152
+ else if (
307
153
  typeof exception === 'object' &&
308
154
  exception &&
309
155
  exception.constructor &&
@@ -313,40 +159,12 @@ export class AllExceptionsFilter implements ExceptionFilter {
313
159
  )
314
160
  ) {
315
161
  status = HttpStatus.BAD_REQUEST;
316
- message = (exception as any).message || 'Sequelize error';
317
- errorDetails = exception;
318
- } else if (exception instanceof Error) {
319
- message = exception.message;
320
- errorDetails = {
321
- name: exception.name,
322
- stack: exception.stack,
323
- };
324
- } else {
325
- message = 'Une erreur inattendue est survenue';
162
+ message = (exception as any).message || 'Sequelize database error';
163
+ errorResponseName = exception.constructor.name;
326
164
  errorDetails = exception;
327
- }
328
-
329
- if (process.env.NODE_ENV !== 'production') {
330
- this.logger.error(
331
- \`Exception on \${request.method} \${request.url}\`,
332
- JSON.stringify({ message, status, errorDetails, exception }),
333
- );
334
- }
335
-
336
- response.status(status).json({
337
- statusCode: status,
338
- timestamp: new Date().toISOString(),
339
- path: request.url,
340
- method: request.method,
341
- message,
342
- error: errorDetails,
343
- });
344
- }
345
- }
346
- `.trim();
165
+ }`;
347
166
  }
348
167
 
349
- // Version universelle (multi-ORM / fallback)
350
168
  return `
351
169
  import {
352
170
  ExceptionFilter,
@@ -358,6 +176,15 @@ import {
358
176
  } from '@nestjs/common';
359
177
  import { Request, Response } from 'express';
360
178
 
179
+ export interface ErrorResponse {
180
+ statusCode: number;
181
+ timestamp: string;
182
+ path: string;
183
+ method: string;
184
+ message: string | string[];
185
+ error: string;
186
+ }
187
+
361
188
  @Catch()
362
189
  export class AllExceptionsFilter implements ExceptionFilter {
363
190
  private readonly logger = new Logger(AllExceptionsFilter.name);
@@ -369,11 +196,13 @@ export class AllExceptionsFilter implements ExceptionFilter {
369
196
 
370
197
  let status = HttpStatus.INTERNAL_SERVER_ERROR;
371
198
  let message: string | string[] = 'Internal server error';
199
+ let errorResponseName = 'InternalServerError';
372
200
  let errorDetails: any = null;
373
201
 
374
202
  if (exception instanceof HttpException) {
375
203
  status = exception.getStatus();
376
204
  const res = exception.getResponse();
205
+ errorResponseName = exception.name || 'HttpException';
377
206
  if (typeof res === 'string') {
378
207
  message = res;
379
208
  } else if (typeof res === 'object' && res !== null) {
@@ -381,51 +210,10 @@ export class AllExceptionsFilter implements ExceptionFilter {
381
210
  message = resObj.message || resObj.error || 'HttpException';
382
211
  errorDetails = resObj;
383
212
  }
384
- }
385
- // Prisma
386
- else if (
387
- typeof exception === 'object' &&
388
- exception &&
389
- exception.constructor &&
390
- (
391
- exception.constructor.name === 'PrismaClientKnownRequestError' ||
392
- exception.constructor.name === 'PrismaClientValidationError'
393
- )
394
- ) {
395
- status = HttpStatus.BAD_REQUEST;
396
- message = (exception as any).message || 'Prisma error';
397
- errorDetails = exception;
398
- }
399
- // Mongoose/Mongo
400
- else if (
401
- typeof exception === 'object' &&
402
- exception &&
403
- 'name' in exception &&
404
- (
405
- (exception as any).name === 'MongoError' ||
406
- (exception as any).name === 'MongooseError'
407
- )
408
- ) {
409
- status = HttpStatus.BAD_REQUEST;
410
- message = (exception as any).message || 'MongoDB error';
411
- errorDetails = exception;
412
- }
413
- // Sequelize
414
- else if (
415
- typeof exception === 'object' &&
416
- exception &&
417
- exception.constructor &&
418
- (
419
- exception.constructor.name === 'SequelizeDatabaseError' ||
420
- exception.constructor.name === 'SequelizeValidationError'
421
- )
422
- ) {
423
- status = HttpStatus.BAD_REQUEST;
424
- message = (exception as any).message || 'Sequelize error';
425
- errorDetails = exception;
426
- }
213
+ }${dbSpecificChecks}
427
214
  else if (exception instanceof Error) {
428
215
  message = exception.message;
216
+ errorResponseName = exception.name;
429
217
  errorDetails = {
430
218
  name: exception.name,
431
219
  stack: exception.stack,
@@ -436,18 +224,27 @@ export class AllExceptionsFilter implements ExceptionFilter {
436
224
  }
437
225
 
438
226
  this.logger.error(
439
- \`Exception on \${request.method} \${request.url}\`,
440
- JSON.stringify({ message, status, errorDetails, exception }),
227
+ \`[\${request.method}] \${request.url} - Error status: \${status}\`,
228
+ exception instanceof Error ? exception.stack : JSON.stringify(exception),
441
229
  );
442
230
 
443
- response.status(status).json({
231
+ const isProduction = process.env.NODE_ENV === 'production';
232
+ const payload: ErrorResponse = {
444
233
  statusCode: status,
445
234
  timestamp: new Date().toISOString(),
446
235
  path: request.url,
447
236
  method: request.method,
448
- message,
449
- error: errorDetails,
450
- });
237
+ message: (status === HttpStatus.INTERNAL_SERVER_ERROR && isProduction)
238
+ ? 'Internal server error'
239
+ : message,
240
+ error: isProduction ? errorResponseName : (errorDetails?.error || errorResponseName),
241
+ };
242
+
243
+ if (!isProduction && errorDetails) {
244
+ (payload as any).details = errorDetails;
245
+ }
246
+
247
+ response.status(status).json(payload);
451
248
  }
452
249
  }
453
250
  `.trim();