@malamute/ai-rules 1.0.0 → 1.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.
Files changed (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,300 @@
1
+ ---
2
+ paths:
3
+ - "src/common/**/*.ts"
4
+ - "src/**/*.decorator.ts"
5
+ - "src/**/*.filter.ts"
6
+ - "src/**/*.interceptor.ts"
7
+ - "src/**/*.pipe.ts"
8
+ - "src/main.ts"
9
+ ---
10
+
11
+ # NestJS Common Patterns
12
+
13
+ ## Global Setup (main.ts)
14
+
15
+ ```typescript
16
+ import { NestFactory } from '@nestjs/core';
17
+ import { ValidationPipe } from '@nestjs/common';
18
+ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
19
+ import { AppModule } from './app.module';
20
+
21
+ async function bootstrap() {
22
+ const app = await NestFactory.create(AppModule);
23
+
24
+ // Global validation pipe
25
+ app.useGlobalPipes(
26
+ new ValidationPipe({
27
+ whitelist: true,
28
+ forbidNonWhitelisted: true,
29
+ transform: true,
30
+ transformOptions: {
31
+ enableImplicitConversion: true,
32
+ },
33
+ }),
34
+ );
35
+
36
+ // Global prefix
37
+ app.setGlobalPrefix('api/v1');
38
+
39
+ // CORS
40
+ app.enableCors({
41
+ origin: process.env.CORS_ORIGIN?.split(',') || '*',
42
+ credentials: true,
43
+ });
44
+
45
+ // Swagger (dev only)
46
+ if (process.env.NODE_ENV !== 'production') {
47
+ const config = new DocumentBuilder()
48
+ .setTitle('API')
49
+ .setVersion('1.0')
50
+ .addBearerAuth()
51
+ .build();
52
+ const document = SwaggerModule.createDocument(app, config);
53
+ SwaggerModule.setup('docs', app, document);
54
+ }
55
+
56
+ await app.listen(process.env.PORT ?? 3000);
57
+ }
58
+ bootstrap();
59
+ ```
60
+
61
+ ## Custom Decorators
62
+
63
+ ### @CurrentUser Decorator
64
+
65
+ ```typescript
66
+ // common/decorators/current-user.decorator.ts
67
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
68
+
69
+ export const CurrentUser = createParamDecorator(
70
+ (data: string | undefined, ctx: ExecutionContext) => {
71
+ const request = ctx.switchToHttp().getRequest();
72
+ const user = request.user;
73
+ return data ? user?.[data] : user;
74
+ },
75
+ );
76
+
77
+ // Usage
78
+ @Get('profile')
79
+ getProfile(@CurrentUser() user: User) {
80
+ return user;
81
+ }
82
+
83
+ @Get('email')
84
+ getEmail(@CurrentUser('email') email: string) {
85
+ return { email };
86
+ }
87
+ ```
88
+
89
+ ### @Public Decorator
90
+
91
+ ```typescript
92
+ // common/decorators/public.decorator.ts
93
+ import { SetMetadata } from '@nestjs/common';
94
+
95
+ export const IS_PUBLIC_KEY = 'isPublic';
96
+ export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
97
+
98
+ // Usage
99
+ @Public()
100
+ @Get('health')
101
+ healthCheck() {
102
+ return { status: 'ok' };
103
+ }
104
+ ```
105
+
106
+ ### @Roles Decorator
107
+
108
+ ```typescript
109
+ // common/decorators/roles.decorator.ts
110
+ import { SetMetadata } from '@nestjs/common';
111
+
112
+ export const ROLES_KEY = 'roles';
113
+ export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
114
+
115
+ // Usage
116
+ @Roles('admin')
117
+ @Get('admin')
118
+ adminOnly() { ... }
119
+ ```
120
+
121
+ ## Exception Filters
122
+
123
+ ### Global Exception Filter
124
+
125
+ ```typescript
126
+ // common/filters/all-exceptions.filter.ts
127
+ import {
128
+ ExceptionFilter,
129
+ Catch,
130
+ ArgumentsHost,
131
+ HttpException,
132
+ HttpStatus,
133
+ Logger,
134
+ } from '@nestjs/common';
135
+ import { Request, Response } from 'express';
136
+
137
+ @Catch()
138
+ export class AllExceptionsFilter implements ExceptionFilter {
139
+ private readonly logger = new Logger(AllExceptionsFilter.name);
140
+
141
+ catch(exception: unknown, host: ArgumentsHost) {
142
+ const ctx = host.switchToHttp();
143
+ const response = ctx.getResponse<Response>();
144
+ const request = ctx.getRequest<Request>();
145
+
146
+ const status =
147
+ exception instanceof HttpException
148
+ ? exception.getStatus()
149
+ : HttpStatus.INTERNAL_SERVER_ERROR;
150
+
151
+ const message =
152
+ exception instanceof HttpException
153
+ ? exception.message
154
+ : 'Internal server error';
155
+
156
+ // Log error
157
+ this.logger.error(
158
+ `${request.method} ${request.url} - ${status} - ${message}`,
159
+ exception instanceof Error ? exception.stack : undefined,
160
+ );
161
+
162
+ response.status(status).json({
163
+ statusCode: status,
164
+ message,
165
+ timestamp: new Date().toISOString(),
166
+ path: request.url,
167
+ });
168
+ }
169
+ }
170
+
171
+ // Register globally in main.ts
172
+ app.useGlobalFilters(new AllExceptionsFilter());
173
+ ```
174
+
175
+ ## Interceptors
176
+
177
+ ### Transform Response Interceptor
178
+
179
+ ```typescript
180
+ // common/interceptors/transform.interceptor.ts
181
+ import {
182
+ Injectable,
183
+ NestInterceptor,
184
+ ExecutionContext,
185
+ CallHandler,
186
+ } from '@nestjs/common';
187
+ import { Observable } from 'rxjs';
188
+ import { map } from 'rxjs/operators';
189
+
190
+ export interface Response<T> {
191
+ success: boolean;
192
+ data: T;
193
+ }
194
+
195
+ @Injectable()
196
+ export class TransformInterceptor<T>
197
+ implements NestInterceptor<T, Response<T>>
198
+ {
199
+ intercept(
200
+ context: ExecutionContext,
201
+ next: CallHandler,
202
+ ): Observable<Response<T>> {
203
+ return next.handle().pipe(
204
+ map((data) => ({
205
+ success: true,
206
+ data,
207
+ })),
208
+ );
209
+ }
210
+ }
211
+ ```
212
+
213
+ ### Logging Interceptor
214
+
215
+ ```typescript
216
+ // common/interceptors/logging.interceptor.ts
217
+ import {
218
+ Injectable,
219
+ NestInterceptor,
220
+ ExecutionContext,
221
+ CallHandler,
222
+ Logger,
223
+ } from '@nestjs/common';
224
+ import { Observable } from 'rxjs';
225
+ import { tap } from 'rxjs/operators';
226
+
227
+ @Injectable()
228
+ export class LoggingInterceptor implements NestInterceptor {
229
+ private readonly logger = new Logger(LoggingInterceptor.name);
230
+
231
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
232
+ const request = context.switchToHttp().getRequest();
233
+ const { method, url } = request;
234
+ const now = Date.now();
235
+
236
+ return next.handle().pipe(
237
+ tap(() => {
238
+ this.logger.log(`${method} ${url} - ${Date.now() - now}ms`);
239
+ }),
240
+ );
241
+ }
242
+ }
243
+ ```
244
+
245
+ ## Guards
246
+
247
+ ### Roles Guard
248
+
249
+ ```typescript
250
+ // common/guards/roles.guard.ts
251
+ import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
252
+ import { Reflector } from '@nestjs/core';
253
+ import { ROLES_KEY } from '../decorators/roles.decorator';
254
+
255
+ @Injectable()
256
+ export class RolesGuard implements CanActivate {
257
+ constructor(private reflector: Reflector) {}
258
+
259
+ canActivate(context: ExecutionContext): boolean {
260
+ const requiredRoles = this.reflector.getAllAndOverride<string[]>(
261
+ ROLES_KEY,
262
+ [context.getHandler(), context.getClass()],
263
+ );
264
+
265
+ if (!requiredRoles) {
266
+ return true;
267
+ }
268
+
269
+ const { user } = context.switchToHttp().getRequest();
270
+ return requiredRoles.some((role) => user.roles?.includes(role));
271
+ }
272
+ }
273
+ ```
274
+
275
+ ## Pipes
276
+
277
+ ### Parse Optional Int Pipe
278
+
279
+ ```typescript
280
+ // common/pipes/parse-optional-int.pipe.ts
281
+ import { PipeTransform, Injectable } from '@nestjs/common';
282
+
283
+ @Injectable()
284
+ export class ParseOptionalIntPipe implements PipeTransform {
285
+ transform(value: string | undefined): number | undefined {
286
+ if (value === undefined || value === '') {
287
+ return undefined;
288
+ }
289
+ const val = parseInt(value, 10);
290
+ return isNaN(val) ? undefined : val;
291
+ }
292
+ }
293
+
294
+ // Usage
295
+ @Get()
296
+ findAll(
297
+ @Query('page', ParseOptionalIntPipe) page?: number,
298
+ @Query('limit', ParseOptionalIntPipe) limit?: number,
299
+ ) { ... }
300
+ ```
@@ -0,0 +1,376 @@
1
+ ---
2
+ paths:
3
+ - "**/*.filter.ts"
4
+ - "**/filters/**/*.ts"
5
+ ---
6
+
7
+ # NestJS Exception Filters
8
+
9
+ ## Built-in Exceptions
10
+
11
+ ```typescript
12
+ import {
13
+ BadRequestException,
14
+ UnauthorizedException,
15
+ ForbiddenException,
16
+ NotFoundException,
17
+ ConflictException,
18
+ GoneException,
19
+ PayloadTooLargeException,
20
+ UnsupportedMediaTypeException,
21
+ UnprocessableEntityException,
22
+ InternalServerErrorException,
23
+ NotImplementedException,
24
+ BadGatewayException,
25
+ ServiceUnavailableException,
26
+ GatewayTimeoutException,
27
+ } from '@nestjs/common';
28
+
29
+ // Usage with message
30
+ throw new NotFoundException('User not found');
31
+
32
+ // Usage with object
33
+ throw new BadRequestException({
34
+ message: 'Validation failed',
35
+ errors: [{ field: 'email', message: 'Invalid email format' }],
36
+ });
37
+ ```
38
+
39
+ ## Global Exception Filter
40
+
41
+ ```typescript
42
+ // filters/all-exceptions.filter.ts
43
+ import {
44
+ ExceptionFilter,
45
+ Catch,
46
+ ArgumentsHost,
47
+ HttpException,
48
+ HttpStatus,
49
+ Logger,
50
+ } from '@nestjs/common';
51
+ import { Request, Response } from 'express';
52
+
53
+ @Catch()
54
+ export class AllExceptionsFilter implements ExceptionFilter {
55
+ private readonly logger = new Logger(AllExceptionsFilter.name);
56
+
57
+ catch(exception: unknown, host: ArgumentsHost) {
58
+ const ctx = host.switchToHttp();
59
+ const response = ctx.getResponse<Response>();
60
+ const request = ctx.getRequest<Request>();
61
+
62
+ const status =
63
+ exception instanceof HttpException
64
+ ? exception.getStatus()
65
+ : HttpStatus.INTERNAL_SERVER_ERROR;
66
+
67
+ const message =
68
+ exception instanceof HttpException
69
+ ? exception.getResponse()
70
+ : 'Internal server error';
71
+
72
+ const errorResponse = {
73
+ statusCode: status,
74
+ timestamp: new Date().toISOString(),
75
+ path: request.url,
76
+ method: request.method,
77
+ message: typeof message === 'string' ? message : (message as Record<string, unknown>).message,
78
+ ...(typeof message === 'object' && message !== null
79
+ ? { details: message }
80
+ : {}),
81
+ };
82
+
83
+ // Log error
84
+ this.logger.error(
85
+ `${request.method} ${request.url} ${status}`,
86
+ exception instanceof Error ? exception.stack : undefined,
87
+ );
88
+
89
+ response.status(status).json(errorResponse);
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## HTTP Exception Filter
95
+
96
+ ```typescript
97
+ // filters/http-exception.filter.ts
98
+ import {
99
+ ExceptionFilter,
100
+ Catch,
101
+ ArgumentsHost,
102
+ HttpException,
103
+ } from '@nestjs/common';
104
+ import { Request, Response } from 'express';
105
+
106
+ @Catch(HttpException)
107
+ export class HttpExceptionFilter implements ExceptionFilter {
108
+ catch(exception: HttpException, host: ArgumentsHost) {
109
+ const ctx = host.switchToHttp();
110
+ const response = ctx.getResponse<Response>();
111
+ const request = ctx.getRequest<Request>();
112
+ const status = exception.getStatus();
113
+ const exceptionResponse = exception.getResponse();
114
+
115
+ response.status(status).json({
116
+ statusCode: status,
117
+ timestamp: new Date().toISOString(),
118
+ path: request.url,
119
+ ...(typeof exceptionResponse === 'object'
120
+ ? exceptionResponse
121
+ : { message: exceptionResponse }),
122
+ });
123
+ }
124
+ }
125
+ ```
126
+
127
+ ## Custom Exception Classes
128
+
129
+ ```typescript
130
+ // exceptions/business.exception.ts
131
+ import { HttpException, HttpStatus } from '@nestjs/common';
132
+
133
+ export class BusinessException extends HttpException {
134
+ constructor(
135
+ public readonly code: string,
136
+ message: string,
137
+ status: HttpStatus = HttpStatus.BAD_REQUEST,
138
+ ) {
139
+ super({ code, message }, status);
140
+ }
141
+ }
142
+
143
+ // exceptions/domain-exceptions.ts
144
+ export class UserNotFoundException extends BusinessException {
145
+ constructor(userId: string) {
146
+ super('USER_NOT_FOUND', `User with ID ${userId} not found`, HttpStatus.NOT_FOUND);
147
+ }
148
+ }
149
+
150
+ export class InsufficientCreditsException extends BusinessException {
151
+ constructor(required: number, available: number) {
152
+ super(
153
+ 'INSUFFICIENT_CREDITS',
154
+ `Required ${required} credits but only ${available} available`,
155
+ HttpStatus.PAYMENT_REQUIRED,
156
+ );
157
+ }
158
+ }
159
+
160
+ export class DuplicateEmailException extends BusinessException {
161
+ constructor(email: string) {
162
+ super('DUPLICATE_EMAIL', `Email ${email} is already registered`, HttpStatus.CONFLICT);
163
+ }
164
+ }
165
+ ```
166
+
167
+ ## Domain Exception Filter
168
+
169
+ ```typescript
170
+ // filters/business-exception.filter.ts
171
+ import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
172
+ import { Response } from 'express';
173
+ import { BusinessException } from '../exceptions/business.exception';
174
+
175
+ @Catch(BusinessException)
176
+ export class BusinessExceptionFilter implements ExceptionFilter {
177
+ catch(exception: BusinessException, host: ArgumentsHost) {
178
+ const ctx = host.switchToHttp();
179
+ const response = ctx.getResponse<Response>();
180
+ const status = exception.getStatus();
181
+
182
+ response.status(status).json({
183
+ type: `https://api.example.com/errors/${exception.code}`,
184
+ title: exception.code.replace(/_/g, ' ').toLowerCase(),
185
+ status,
186
+ detail: exception.message,
187
+ });
188
+ }
189
+ }
190
+ ```
191
+
192
+ ## Database Exception Filter
193
+
194
+ ```typescript
195
+ // filters/prisma-exception.filter.ts
196
+ import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common';
197
+ import { Prisma } from '@prisma/client';
198
+ import { Response } from 'express';
199
+
200
+ @Catch(Prisma.PrismaClientKnownRequestError)
201
+ export class PrismaExceptionFilter implements ExceptionFilter {
202
+ catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) {
203
+ const ctx = host.switchToHttp();
204
+ const response = ctx.getResponse<Response>();
205
+
206
+ switch (exception.code) {
207
+ case 'P2002': // Unique constraint violation
208
+ const field = (exception.meta?.target as string[])?.[0] || 'field';
209
+ response.status(HttpStatus.CONFLICT).json({
210
+ statusCode: HttpStatus.CONFLICT,
211
+ message: `Duplicate value for ${field}`,
212
+ error: 'Conflict',
213
+ });
214
+ break;
215
+
216
+ case 'P2025': // Record not found
217
+ response.status(HttpStatus.NOT_FOUND).json({
218
+ statusCode: HttpStatus.NOT_FOUND,
219
+ message: 'Record not found',
220
+ error: 'Not Found',
221
+ });
222
+ break;
223
+
224
+ case 'P2003': // Foreign key constraint
225
+ response.status(HttpStatus.BAD_REQUEST).json({
226
+ statusCode: HttpStatus.BAD_REQUEST,
227
+ message: 'Related record not found',
228
+ error: 'Bad Request',
229
+ });
230
+ break;
231
+
232
+ default:
233
+ response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
234
+ statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
235
+ message: 'Database error',
236
+ error: 'Internal Server Error',
237
+ });
238
+ }
239
+ }
240
+ }
241
+ ```
242
+
243
+ ## Validation Exception Filter
244
+
245
+ ```typescript
246
+ // filters/validation-exception.filter.ts
247
+ import { ExceptionFilter, Catch, ArgumentsHost, BadRequestException } from '@nestjs/common';
248
+ import { Response } from 'express';
249
+
250
+ @Catch(BadRequestException)
251
+ export class ValidationExceptionFilter implements ExceptionFilter {
252
+ catch(exception: BadRequestException, host: ArgumentsHost) {
253
+ const ctx = host.switchToHttp();
254
+ const response = ctx.getResponse<Response>();
255
+ const exceptionResponse = exception.getResponse() as Record<string, unknown>;
256
+
257
+ // Handle class-validator errors
258
+ if (exceptionResponse.message && Array.isArray(exceptionResponse.message)) {
259
+ response.status(400).json({
260
+ statusCode: 400,
261
+ error: 'Validation Error',
262
+ message: 'Request validation failed',
263
+ details: exceptionResponse.message.map((msg: string) => {
264
+ const [field, ...rest] = msg.split(' ');
265
+ return { field, message: rest.join(' ') };
266
+ }),
267
+ });
268
+ return;
269
+ }
270
+
271
+ response.status(400).json(exceptionResponse);
272
+ }
273
+ }
274
+ ```
275
+
276
+ ## Filter Binding
277
+
278
+ ```typescript
279
+ // Method level
280
+ @Post()
281
+ @UseFilters(HttpExceptionFilter)
282
+ create(@Body() dto: CreateDto) {}
283
+
284
+ // Controller level
285
+ @Controller('users')
286
+ @UseFilters(AllExceptionsFilter)
287
+ export class UsersController {}
288
+
289
+ // Global level (main.ts)
290
+ app.useGlobalFilters(new AllExceptionsFilter());
291
+
292
+ // Global with DI
293
+ @Module({
294
+ providers: [
295
+ {
296
+ provide: APP_FILTER,
297
+ useClass: AllExceptionsFilter,
298
+ },
299
+ ],
300
+ })
301
+ export class AppModule {}
302
+ ```
303
+
304
+ ## Filter Order
305
+
306
+ ```typescript
307
+ // Filters are applied in reverse order (last registered = first executed)
308
+ @UseFilters(
309
+ AllExceptionsFilter, // Fallback (executed last)
310
+ HttpExceptionFilter, // HTTP exceptions
311
+ BusinessExceptionFilter, // Business exceptions (executed first)
312
+ )
313
+ export class AppController {}
314
+ ```
315
+
316
+ ## WebSocket Exception Filter
317
+
318
+ ```typescript
319
+ // filters/ws-exception.filter.ts
320
+ import { Catch, ArgumentsHost } from '@nestjs/common';
321
+ import { BaseWsExceptionFilter, WsException } from '@nestjs/websockets';
322
+
323
+ @Catch()
324
+ export class WsExceptionsFilter extends BaseWsExceptionFilter {
325
+ catch(exception: unknown, host: ArgumentsHost) {
326
+ const client = host.switchToWs().getClient();
327
+
328
+ const error =
329
+ exception instanceof WsException
330
+ ? exception.getError()
331
+ : { message: 'Internal error' };
332
+
333
+ client.emit('error', {
334
+ event: 'error',
335
+ data: error,
336
+ });
337
+ }
338
+ }
339
+ ```
340
+
341
+ ## Anti-patterns
342
+
343
+ ```typescript
344
+ // BAD: Swallowing errors without logging
345
+ @Catch()
346
+ export class SilentFilter implements ExceptionFilter {
347
+ catch(exception: unknown, host: ArgumentsHost) {
348
+ const response = host.switchToHttp().getResponse();
349
+ response.status(500).json({ error: 'Error' }); // No logging!
350
+ }
351
+ }
352
+
353
+ // GOOD: Always log errors
354
+ this.logger.error(exception);
355
+
356
+ // BAD: Exposing internal details
357
+ catch(exception: Error, host: ArgumentsHost) {
358
+ response.json({
359
+ stack: exception.stack, // Security risk!
360
+ query: request.query, // Leaking data!
361
+ });
362
+ }
363
+
364
+ // GOOD: Sanitize response
365
+ response.json({
366
+ statusCode: 500,
367
+ message: 'Internal server error',
368
+ });
369
+
370
+ // BAD: Not handling specific exceptions
371
+ @Catch()
372
+ export class GenericFilter {} // Catches everything the same way
373
+
374
+ // GOOD: Layer filters by specificity
375
+ @UseFilters(AllExceptionsFilter, HttpExceptionFilter, BusinessExceptionFilter)
376
+ ```