@champpaba/claude-agent-kit 3.0.2 → 3.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 (51) hide show
  1. package/.claude/CHANGELOG.md +707 -0
  2. package/.claude/CLAUDE.md +128 -613
  3. package/.claude/agents/_shared/pre-work-checklist.md +108 -7
  4. package/.claude/commands/cdev.md +36 -0
  5. package/.claude/commands/csetup.md +292 -1791
  6. package/.claude/commands/cview.md +364 -364
  7. package/.claude/contexts/design/accessibility.md +611 -611
  8. package/.claude/contexts/design/layout.md +400 -400
  9. package/.claude/contexts/design/responsive.md +551 -551
  10. package/.claude/contexts/design/shadows.md +522 -522
  11. package/.claude/contexts/design/typography.md +465 -465
  12. package/.claude/contexts/domain/README.md +164 -164
  13. package/.claude/contexts/patterns/agent-coordination.md +388 -388
  14. package/.claude/contexts/patterns/development-principles.md +513 -513
  15. package/.claude/contexts/patterns/error-handling.md +478 -478
  16. package/.claude/contexts/patterns/logging.md +424 -424
  17. package/.claude/contexts/patterns/tdd-classification.md +516 -516
  18. package/.claude/contexts/patterns/testing.md +413 -413
  19. package/.claude/lib/README.md +3 -3
  20. package/.claude/lib/detailed-guides/taskmaster-analysis.md +1 -1
  21. package/.claude/lib/task-analyzer.md +144 -0
  22. package/.claude/lib/tdd-workflow.md +2 -1
  23. package/.claude/lib/validation-gates.md +484 -484
  24. package/.claude/settings.local.json +42 -42
  25. package/.claude/templates/PROJECT_STATUS.template.yml +16 -41
  26. package/.claude/templates/context-template.md +45 -45
  27. package/.claude/templates/flags-template.json +42 -42
  28. package/.claude/templates/phases-sections/accessibility-test.md +17 -17
  29. package/.claude/templates/phases-sections/api-design.md +37 -37
  30. package/.claude/templates/phases-sections/backend-tests.md +16 -16
  31. package/.claude/templates/phases-sections/backend.md +37 -37
  32. package/.claude/templates/phases-sections/business-logic-validation.md +16 -16
  33. package/.claude/templates/phases-sections/component-tests.md +17 -17
  34. package/.claude/templates/phases-sections/contract-backend.md +16 -16
  35. package/.claude/templates/phases-sections/contract-frontend.md +16 -16
  36. package/.claude/templates/phases-sections/database.md +35 -35
  37. package/.claude/templates/phases-sections/e2e-tests.md +16 -16
  38. package/.claude/templates/phases-sections/fix-implementation.md +17 -17
  39. package/.claude/templates/phases-sections/frontend-integration.md +18 -18
  40. package/.claude/templates/phases-sections/manual-flow-test.md +15 -15
  41. package/.claude/templates/phases-sections/manual-ux-test.md +16 -16
  42. package/.claude/templates/phases-sections/refactor-implementation.md +17 -17
  43. package/.claude/templates/phases-sections/refactor.md +16 -16
  44. package/.claude/templates/phases-sections/regression-tests.md +15 -15
  45. package/.claude/templates/phases-sections/responsive-test.md +16 -16
  46. package/.claude/templates/phases-sections/script-implementation.md +43 -43
  47. package/.claude/templates/phases-sections/test-coverage.md +16 -16
  48. package/.claude/templates/phases-sections/user-approval.md +14 -14
  49. package/LICENSE +21 -21
  50. package/package.json +1 -1
  51. package/.claude/lib/tdd-classifier.md +0 -345
@@ -1,478 +1,478 @@
1
- # Error Handling & Resilience Patterns
2
-
3
- **Core Principles:**
4
- 1. **Fail Fast** - Detect errors early and raise exceptions immediately
5
- 2. **Log Everything** - Every error must be logged with context
6
- 3. **User-Friendly Messages** - Never expose technical details to users
7
- 4. **Graceful Degradation** - System should degrade gracefully, not crash
8
- 5. **Retry with Backoff** - Transient errors should be retried intelligently
9
-
10
- ---
11
-
12
- ## API Error Boundaries
13
-
14
- ### Next.js API Route Pattern
15
-
16
- ```typescript
17
- // app/api/items/route.ts
18
- import { NextRequest, NextResponse } from 'next/server'
19
- import { z } from 'zod'
20
- import { Prisma } from '@prisma/client'
21
- import { logger } from '@/lib/logger'
22
-
23
- export async function POST(request: NextRequest) {
24
- const requestId = crypto.randomUUID()
25
-
26
- try {
27
- // 1. Parse and validate input
28
- const body = await request.json()
29
- const validated = schema.parse(body)
30
-
31
- // 2. Business logic
32
- const result = await createItem(validated)
33
-
34
- // 3. Log success
35
- logger.info('api_success', { requestId, route: '/api/items' })
36
-
37
- return NextResponse.json(result)
38
-
39
- } catch (error) {
40
- logger.error('api_error', {
41
- requestId,
42
- route: '/api/items',
43
- error: error instanceof Error ? error.message : 'Unknown error',
44
- stack: error instanceof Error ? error.stack : undefined
45
- })
46
-
47
- return handleError(error, requestId)
48
- }
49
- }
50
-
51
- function handleError(error: unknown, requestId: string): NextResponse {
52
- // 1. Zod validation errors
53
- if (error instanceof z.ZodError) {
54
- return NextResponse.json(
55
- {
56
- error: 'Validation failed',
57
- details: error.flatten().fieldErrors,
58
- requestId
59
- },
60
- { status: 400 }
61
- )
62
- }
63
-
64
- // 2. Prisma database errors
65
- if (error instanceof Prisma.PrismaClientKnownRequestError) {
66
- // P2002: Unique constraint violation
67
- if (error.code === 'P2002') {
68
- return NextResponse.json(
69
- {
70
- error: 'Resource already exists',
71
- field: error.meta?.target,
72
- requestId
73
- },
74
- { status: 409 }
75
- )
76
- }
77
-
78
- // P2025: Record not found
79
- if (error.code === 'P2025') {
80
- return NextResponse.json(
81
- { error: 'Resource not found', requestId },
82
- { status: 404 }
83
- )
84
- }
85
-
86
- return NextResponse.json(
87
- { error: 'Database error', requestId },
88
- { status: 500 }
89
- )
90
- }
91
-
92
- // 3. Custom application errors
93
- if (error instanceof AppError) {
94
- return NextResponse.json(
95
- { error: error.message, code: error.code, requestId },
96
- { status: error.statusCode }
97
- )
98
- }
99
-
100
- // 4. Unknown errors
101
- return NextResponse.json(
102
- {
103
- error: 'Internal server error',
104
- message: process.env.NODE_ENV === 'development'
105
- ? (error as Error).message
106
- : undefined,
107
- requestId
108
- },
109
- { status: 500 }
110
- )
111
- }
112
- ```
113
-
114
- ---
115
-
116
- ### FastAPI Error Handler
117
-
118
- ```python
119
- # app/api/items.py
120
- from fastapi import APIRouter, HTTPException, Request
121
- from pydantic import BaseModel, ValidationError
122
- from sqlalchemy.exc import IntegrityError
123
- import logging
124
-
125
- router = APIRouter()
126
- logger = logging.getLogger(__name__)
127
-
128
- class CreateItemRequest(BaseModel):
129
- name: str
130
- price: float
131
-
132
- @router.post("/items")
133
- async def create_item(request: CreateItemRequest):
134
- request_id = str(uuid.uuid4())
135
-
136
- try:
137
- # Business logic
138
- item = await db.create_item(request.name, request.price)
139
-
140
- logger.info({"event": "api_success", "request_id": request_id})
141
-
142
- return {"id": item.id, "name": item.name, "price": item.price}
143
-
144
- except ValidationError as e:
145
- logger.warning({"event": "validation_error", "request_id": request_id, "errors": e.errors()})
146
- raise HTTPException(status_code=400, detail={"error": "Validation failed", "details": e.errors()})
147
-
148
- except IntegrityError as e:
149
- logger.error({"event": "db_integrity_error", "request_id": request_id})
150
- raise HTTPException(status_code=409, detail={"error": "Resource already exists"})
151
-
152
- except Exception as e:
153
- logger.error({"event": "api_error", "request_id": request_id, "error": str(e)})
154
- raise HTTPException(status_code=500, detail={"error": "Internal server error", "request_id": request_id})
155
- ```
156
-
157
- ---
158
-
159
- ## Custom Error Classes
160
-
161
- ### TypeScript
162
-
163
- ```typescript
164
- // lib/errors.ts
165
-
166
- export class AppError extends Error {
167
- constructor(
168
- public message: string,
169
- public statusCode: number,
170
- public code?: string
171
- ) {
172
- super(message)
173
- this.name = 'AppError'
174
- }
175
- }
176
-
177
- export class ValidationError extends AppError {
178
- constructor(message: string, public field?: string) {
179
- super(message, 400, 'VALIDATION_ERROR')
180
- this.name = 'ValidationError'
181
- }
182
- }
183
-
184
- export class NotFoundError extends AppError {
185
- constructor(resource: string) {
186
- super(`${resource} not found`, 404, 'NOT_FOUND')
187
- this.name = 'NotFoundError'
188
- }
189
- }
190
-
191
- export class UnauthorizedError extends AppError {
192
- constructor(message = 'Unauthorized') {
193
- super(message, 401, 'UNAUTHORIZED')
194
- this.name = 'UnauthorizedError'
195
- }
196
- }
197
-
198
- export class ExternalServiceError extends AppError {
199
- constructor(
200
- service: string,
201
- public originalError?: Error
202
- ) {
203
- super(`${service} service error`, 503, 'EXTERNAL_SERVICE_ERROR')
204
- this.name = 'ExternalServiceError'
205
- }
206
- }
207
-
208
- // Usage
209
- throw new NotFoundError('User')
210
- throw new ValidationError('Invalid email format', 'email')
211
- throw new ExternalServiceError('PaymentGateway', originalError)
212
- ```
213
-
214
- ### Python
215
-
216
- ```python
217
- # lib/errors.py
218
-
219
- class AppError(Exception):
220
- def __init__(self, message: str, status_code: int, code: str = None):
221
- self.message = message
222
- self.status_code = status_code
223
- self.code = code
224
- super().__init__(self.message)
225
-
226
- class ValidationError(AppError):
227
- def __init__(self, message: str, field: str = None):
228
- super().__init__(message, 400, "VALIDATION_ERROR")
229
- self.field = field
230
-
231
- class NotFoundError(AppError):
232
- def __init__(self, resource: str):
233
- super().__init__(f"{resource} not found", 404, "NOT_FOUND")
234
-
235
- class UnauthorizedError(AppError):
236
- def __init__(self, message: str = "Unauthorized"):
237
- super().__init__(message, 401, "UNAUTHORIZED")
238
-
239
- class ExternalServiceError(AppError):
240
- def __init__(self, service: str, original_error: Exception = None):
241
- super().__init__(f"{service} service error", 503, "EXTERNAL_SERVICE_ERROR")
242
- self.original_error = original_error
243
-
244
- # Usage
245
- raise NotFoundError("User")
246
- raise ValidationError("Invalid email format", field="email")
247
- raise ExternalServiceError("PaymentGateway", original_error)
248
- ```
249
-
250
- ---
251
-
252
- ## Retry Logic with Exponential Backoff
253
-
254
- ```typescript
255
- // lib/retry.ts
256
- import { logger } from '@/lib/logger'
257
-
258
- interface RetryOptions {
259
- maxRetries?: number
260
- initialDelayMs?: number
261
- maxDelayMs?: number
262
- exponentialBase?: number
263
- shouldRetry?: (error: Error) => boolean
264
- }
265
-
266
- export async function withRetry<T>(
267
- fn: () => Promise<T>,
268
- options: RetryOptions = {}
269
- ): Promise<T> {
270
- const {
271
- maxRetries = 3,
272
- initialDelayMs = 1000,
273
- maxDelayMs = 10000,
274
- exponentialBase = 2,
275
- shouldRetry = () => true
276
- } = options
277
-
278
- let lastError: Error
279
-
280
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
281
- try {
282
- logger.info('retry_attempt', { attempt, maxRetries })
283
- return await fn()
284
-
285
- } catch (error) {
286
- lastError = error as Error
287
-
288
- if (!shouldRetry(lastError)) {
289
- logger.error('retry_non_retryable', { attempt, error: lastError.message })
290
- throw lastError
291
- }
292
-
293
- if (attempt === maxRetries) {
294
- logger.error('retry_exhausted', { attempt, maxRetries, error: lastError.message })
295
- throw lastError
296
- }
297
-
298
- const delay = Math.min(
299
- initialDelayMs * Math.pow(exponentialBase, attempt - 1),
300
- maxDelayMs
301
- )
302
-
303
- logger.warn('retry_waiting', {
304
- attempt,
305
- nextAttempt: attempt + 1,
306
- delayMs: delay,
307
- error: lastError.message
308
- })
309
-
310
- await new Promise(resolve => setTimeout(resolve, delay))
311
- }
312
- }
313
-
314
- throw lastError!
315
- }
316
-
317
- // Usage - External API with retry
318
- export async function fetchExternalDataWithRetry(userId: string) {
319
- return withRetry(
320
- () => fetchExternalData(userId),
321
- {
322
- maxRetries: 3,
323
- initialDelayMs: 1000,
324
- shouldRetry: (error) => {
325
- // Retry on network errors or 5xx status codes
326
- return (
327
- error.message.includes('network') ||
328
- error.message.includes('timeout') ||
329
- error.message.includes('503')
330
- )
331
- }
332
- }
333
- )
334
- }
335
- ```
336
-
337
- ---
338
-
339
- ## Circuit Breaker Pattern
340
-
341
- For external services that may go down.
342
-
343
- ```typescript
344
- // lib/circuit-breaker.ts
345
- import { logger } from '@/lib/logger'
346
-
347
- export class CircuitBreaker {
348
- private failures = 0
349
- private lastFailureTime: number | null = null
350
- private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'
351
-
352
- constructor(
353
- private threshold: number = 5, // Open after 5 failures
354
- private timeout: number = 60000, // Reset after 1 minute
355
- private halfOpenRequests: number = 1 // Test with 1 request
356
- ) {}
357
-
358
- async execute<T>(fn: () => Promise<T>): Promise<T> {
359
- if (this.state === 'OPEN') {
360
- // Check if we should try again (timeout elapsed)
361
- if (Date.now() - this.lastFailureTime! >= this.timeout) {
362
- logger.info('circuit_breaker_half_open')
363
- this.state = 'HALF_OPEN'
364
- } else {
365
- logger.warn('circuit_breaker_rejected')
366
- throw new Error('Circuit breaker is OPEN')
367
- }
368
- }
369
-
370
- try {
371
- const result = await fn()
372
-
373
- // Success - reset failures
374
- if (this.state === 'HALF_OPEN') {
375
- logger.info('circuit_breaker_closed')
376
- this.state = 'CLOSED'
377
- this.failures = 0
378
- this.lastFailureTime = null
379
- }
380
-
381
- return result
382
-
383
- } catch (error) {
384
- this.failures++
385
- this.lastFailureTime = Date.now()
386
-
387
- if (this.failures >= this.threshold) {
388
- logger.error('circuit_breaker_opened', {
389
- failures: this.failures,
390
- threshold: this.threshold
391
- })
392
- this.state = 'OPEN'
393
- }
394
-
395
- throw error
396
- }
397
- }
398
-
399
- getState() {
400
- return this.state
401
- }
402
- }
403
-
404
- // Usage
405
- const externalApiCircuit = new CircuitBreaker(5, 60000)
406
-
407
- export async function fetchDataSafe(userId: string) {
408
- return externalApiCircuit.execute(() =>
409
- fetchExternalData(userId)
410
- )
411
- }
412
- ```
413
-
414
- ---
415
-
416
- ## Input Validation (Zod)
417
-
418
- ```typescript
419
- // Always validate with Zod
420
- import { z } from 'zod'
421
-
422
- const userSchema = z.object({
423
- email: z.string().email(),
424
- age: z.number().min(18).max(120),
425
- country: z.string().length(2), // ISO country code
426
- newsletter: z.boolean().optional()
427
- })
428
-
429
- export async function POST(request: NextRequest) {
430
- const body = await request.json()
431
-
432
- // Safe parse (returns result object)
433
- const result = userSchema.safeParse(body)
434
-
435
- if (!result.success) {
436
- logger.warn('validation_error', {
437
- errors: result.error.flatten()
438
- })
439
-
440
- return NextResponse.json(
441
- {
442
- error: 'Invalid input',
443
- details: result.error.flatten().fieldErrors
444
- },
445
- { status: 400 }
446
- )
447
- }
448
-
449
- // Use validated data
450
- const data = result.data // Type-safe!
451
- // ...
452
- }
453
- ```
454
-
455
- ---
456
-
457
- ## Best Practices
458
-
459
- ### DO:
460
- - ✅ Validate all inputs (never trust user data)
461
- - ✅ Log every error with context (requestId, userId, etc.)
462
- - ✅ Return user-friendly error messages
463
- - ✅ Use custom error classes for domain errors
464
- - ✅ Implement retry logic for transient failures
465
- - ✅ Use circuit breakers for external services
466
- - ✅ Fail fast (don't continue with invalid data)
467
-
468
- ### DON'T:
469
- - ❌ Expose stack traces to users
470
- - ❌ Return raw database errors
471
- - ❌ Catch errors and do nothing
472
- - ❌ Use generic error messages ("Something went wrong")
473
- - ❌ Retry non-retryable errors (validation, 404, etc.)
474
- - ❌ Hardcode API keys in error logs
475
-
476
- ---
477
-
478
- **💡 Remember:** Good error handling makes debugging 10x easier!
1
+ # Error Handling & Resilience Patterns
2
+
3
+ **Core Principles:**
4
+ 1. **Fail Fast** - Detect errors early and raise exceptions immediately
5
+ 2. **Log Everything** - Every error must be logged with context
6
+ 3. **User-Friendly Messages** - Never expose technical details to users
7
+ 4. **Graceful Degradation** - System should degrade gracefully, not crash
8
+ 5. **Retry with Backoff** - Transient errors should be retried intelligently
9
+
10
+ ---
11
+
12
+ ## API Error Boundaries
13
+
14
+ ### Next.js API Route Pattern
15
+
16
+ ```typescript
17
+ // app/api/items/route.ts
18
+ import { NextRequest, NextResponse } from 'next/server'
19
+ import { z } from 'zod'
20
+ import { Prisma } from '@prisma/client'
21
+ import { logger } from '@/lib/logger'
22
+
23
+ export async function POST(request: NextRequest) {
24
+ const requestId = crypto.randomUUID()
25
+
26
+ try {
27
+ // 1. Parse and validate input
28
+ const body = await request.json()
29
+ const validated = schema.parse(body)
30
+
31
+ // 2. Business logic
32
+ const result = await createItem(validated)
33
+
34
+ // 3. Log success
35
+ logger.info('api_success', { requestId, route: '/api/items' })
36
+
37
+ return NextResponse.json(result)
38
+
39
+ } catch (error) {
40
+ logger.error('api_error', {
41
+ requestId,
42
+ route: '/api/items',
43
+ error: error instanceof Error ? error.message : 'Unknown error',
44
+ stack: error instanceof Error ? error.stack : undefined
45
+ })
46
+
47
+ return handleError(error, requestId)
48
+ }
49
+ }
50
+
51
+ function handleError(error: unknown, requestId: string): NextResponse {
52
+ // 1. Zod validation errors
53
+ if (error instanceof z.ZodError) {
54
+ return NextResponse.json(
55
+ {
56
+ error: 'Validation failed',
57
+ details: error.flatten().fieldErrors,
58
+ requestId
59
+ },
60
+ { status: 400 }
61
+ )
62
+ }
63
+
64
+ // 2. Prisma database errors
65
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
66
+ // P2002: Unique constraint violation
67
+ if (error.code === 'P2002') {
68
+ return NextResponse.json(
69
+ {
70
+ error: 'Resource already exists',
71
+ field: error.meta?.target,
72
+ requestId
73
+ },
74
+ { status: 409 }
75
+ )
76
+ }
77
+
78
+ // P2025: Record not found
79
+ if (error.code === 'P2025') {
80
+ return NextResponse.json(
81
+ { error: 'Resource not found', requestId },
82
+ { status: 404 }
83
+ )
84
+ }
85
+
86
+ return NextResponse.json(
87
+ { error: 'Database error', requestId },
88
+ { status: 500 }
89
+ )
90
+ }
91
+
92
+ // 3. Custom application errors
93
+ if (error instanceof AppError) {
94
+ return NextResponse.json(
95
+ { error: error.message, code: error.code, requestId },
96
+ { status: error.statusCode }
97
+ )
98
+ }
99
+
100
+ // 4. Unknown errors
101
+ return NextResponse.json(
102
+ {
103
+ error: 'Internal server error',
104
+ message: process.env.NODE_ENV === 'development'
105
+ ? (error as Error).message
106
+ : undefined,
107
+ requestId
108
+ },
109
+ { status: 500 }
110
+ )
111
+ }
112
+ ```
113
+
114
+ ---
115
+
116
+ ### FastAPI Error Handler
117
+
118
+ ```python
119
+ # app/api/items.py
120
+ from fastapi import APIRouter, HTTPException, Request
121
+ from pydantic import BaseModel, ValidationError
122
+ from sqlalchemy.exc import IntegrityError
123
+ import logging
124
+
125
+ router = APIRouter()
126
+ logger = logging.getLogger(__name__)
127
+
128
+ class CreateItemRequest(BaseModel):
129
+ name: str
130
+ price: float
131
+
132
+ @router.post("/items")
133
+ async def create_item(request: CreateItemRequest):
134
+ request_id = str(uuid.uuid4())
135
+
136
+ try:
137
+ # Business logic
138
+ item = await db.create_item(request.name, request.price)
139
+
140
+ logger.info({"event": "api_success", "request_id": request_id})
141
+
142
+ return {"id": item.id, "name": item.name, "price": item.price}
143
+
144
+ except ValidationError as e:
145
+ logger.warning({"event": "validation_error", "request_id": request_id, "errors": e.errors()})
146
+ raise HTTPException(status_code=400, detail={"error": "Validation failed", "details": e.errors()})
147
+
148
+ except IntegrityError as e:
149
+ logger.error({"event": "db_integrity_error", "request_id": request_id})
150
+ raise HTTPException(status_code=409, detail={"error": "Resource already exists"})
151
+
152
+ except Exception as e:
153
+ logger.error({"event": "api_error", "request_id": request_id, "error": str(e)})
154
+ raise HTTPException(status_code=500, detail={"error": "Internal server error", "request_id": request_id})
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Custom Error Classes
160
+
161
+ ### TypeScript
162
+
163
+ ```typescript
164
+ // lib/errors.ts
165
+
166
+ export class AppError extends Error {
167
+ constructor(
168
+ public message: string,
169
+ public statusCode: number,
170
+ public code?: string
171
+ ) {
172
+ super(message)
173
+ this.name = 'AppError'
174
+ }
175
+ }
176
+
177
+ export class ValidationError extends AppError {
178
+ constructor(message: string, public field?: string) {
179
+ super(message, 400, 'VALIDATION_ERROR')
180
+ this.name = 'ValidationError'
181
+ }
182
+ }
183
+
184
+ export class NotFoundError extends AppError {
185
+ constructor(resource: string) {
186
+ super(`${resource} not found`, 404, 'NOT_FOUND')
187
+ this.name = 'NotFoundError'
188
+ }
189
+ }
190
+
191
+ export class UnauthorizedError extends AppError {
192
+ constructor(message = 'Unauthorized') {
193
+ super(message, 401, 'UNAUTHORIZED')
194
+ this.name = 'UnauthorizedError'
195
+ }
196
+ }
197
+
198
+ export class ExternalServiceError extends AppError {
199
+ constructor(
200
+ service: string,
201
+ public originalError?: Error
202
+ ) {
203
+ super(`${service} service error`, 503, 'EXTERNAL_SERVICE_ERROR')
204
+ this.name = 'ExternalServiceError'
205
+ }
206
+ }
207
+
208
+ // Usage
209
+ throw new NotFoundError('User')
210
+ throw new ValidationError('Invalid email format', 'email')
211
+ throw new ExternalServiceError('PaymentGateway', originalError)
212
+ ```
213
+
214
+ ### Python
215
+
216
+ ```python
217
+ # lib/errors.py
218
+
219
+ class AppError(Exception):
220
+ def __init__(self, message: str, status_code: int, code: str = None):
221
+ self.message = message
222
+ self.status_code = status_code
223
+ self.code = code
224
+ super().__init__(self.message)
225
+
226
+ class ValidationError(AppError):
227
+ def __init__(self, message: str, field: str = None):
228
+ super().__init__(message, 400, "VALIDATION_ERROR")
229
+ self.field = field
230
+
231
+ class NotFoundError(AppError):
232
+ def __init__(self, resource: str):
233
+ super().__init__(f"{resource} not found", 404, "NOT_FOUND")
234
+
235
+ class UnauthorizedError(AppError):
236
+ def __init__(self, message: str = "Unauthorized"):
237
+ super().__init__(message, 401, "UNAUTHORIZED")
238
+
239
+ class ExternalServiceError(AppError):
240
+ def __init__(self, service: str, original_error: Exception = None):
241
+ super().__init__(f"{service} service error", 503, "EXTERNAL_SERVICE_ERROR")
242
+ self.original_error = original_error
243
+
244
+ # Usage
245
+ raise NotFoundError("User")
246
+ raise ValidationError("Invalid email format", field="email")
247
+ raise ExternalServiceError("PaymentGateway", original_error)
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Retry Logic with Exponential Backoff
253
+
254
+ ```typescript
255
+ // lib/retry.ts
256
+ import { logger } from '@/lib/logger'
257
+
258
+ interface RetryOptions {
259
+ maxRetries?: number
260
+ initialDelayMs?: number
261
+ maxDelayMs?: number
262
+ exponentialBase?: number
263
+ shouldRetry?: (error: Error) => boolean
264
+ }
265
+
266
+ export async function withRetry<T>(
267
+ fn: () => Promise<T>,
268
+ options: RetryOptions = {}
269
+ ): Promise<T> {
270
+ const {
271
+ maxRetries = 3,
272
+ initialDelayMs = 1000,
273
+ maxDelayMs = 10000,
274
+ exponentialBase = 2,
275
+ shouldRetry = () => true
276
+ } = options
277
+
278
+ let lastError: Error
279
+
280
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
281
+ try {
282
+ logger.info('retry_attempt', { attempt, maxRetries })
283
+ return await fn()
284
+
285
+ } catch (error) {
286
+ lastError = error as Error
287
+
288
+ if (!shouldRetry(lastError)) {
289
+ logger.error('retry_non_retryable', { attempt, error: lastError.message })
290
+ throw lastError
291
+ }
292
+
293
+ if (attempt === maxRetries) {
294
+ logger.error('retry_exhausted', { attempt, maxRetries, error: lastError.message })
295
+ throw lastError
296
+ }
297
+
298
+ const delay = Math.min(
299
+ initialDelayMs * Math.pow(exponentialBase, attempt - 1),
300
+ maxDelayMs
301
+ )
302
+
303
+ logger.warn('retry_waiting', {
304
+ attempt,
305
+ nextAttempt: attempt + 1,
306
+ delayMs: delay,
307
+ error: lastError.message
308
+ })
309
+
310
+ await new Promise(resolve => setTimeout(resolve, delay))
311
+ }
312
+ }
313
+
314
+ throw lastError!
315
+ }
316
+
317
+ // Usage - External API with retry
318
+ export async function fetchExternalDataWithRetry(userId: string) {
319
+ return withRetry(
320
+ () => fetchExternalData(userId),
321
+ {
322
+ maxRetries: 3,
323
+ initialDelayMs: 1000,
324
+ shouldRetry: (error) => {
325
+ // Retry on network errors or 5xx status codes
326
+ return (
327
+ error.message.includes('network') ||
328
+ error.message.includes('timeout') ||
329
+ error.message.includes('503')
330
+ )
331
+ }
332
+ }
333
+ )
334
+ }
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Circuit Breaker Pattern
340
+
341
+ For external services that may go down.
342
+
343
+ ```typescript
344
+ // lib/circuit-breaker.ts
345
+ import { logger } from '@/lib/logger'
346
+
347
+ export class CircuitBreaker {
348
+ private failures = 0
349
+ private lastFailureTime: number | null = null
350
+ private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'
351
+
352
+ constructor(
353
+ private threshold: number = 5, // Open after 5 failures
354
+ private timeout: number = 60000, // Reset after 1 minute
355
+ private halfOpenRequests: number = 1 // Test with 1 request
356
+ ) {}
357
+
358
+ async execute<T>(fn: () => Promise<T>): Promise<T> {
359
+ if (this.state === 'OPEN') {
360
+ // Check if we should try again (timeout elapsed)
361
+ if (Date.now() - this.lastFailureTime! >= this.timeout) {
362
+ logger.info('circuit_breaker_half_open')
363
+ this.state = 'HALF_OPEN'
364
+ } else {
365
+ logger.warn('circuit_breaker_rejected')
366
+ throw new Error('Circuit breaker is OPEN')
367
+ }
368
+ }
369
+
370
+ try {
371
+ const result = await fn()
372
+
373
+ // Success - reset failures
374
+ if (this.state === 'HALF_OPEN') {
375
+ logger.info('circuit_breaker_closed')
376
+ this.state = 'CLOSED'
377
+ this.failures = 0
378
+ this.lastFailureTime = null
379
+ }
380
+
381
+ return result
382
+
383
+ } catch (error) {
384
+ this.failures++
385
+ this.lastFailureTime = Date.now()
386
+
387
+ if (this.failures >= this.threshold) {
388
+ logger.error('circuit_breaker_opened', {
389
+ failures: this.failures,
390
+ threshold: this.threshold
391
+ })
392
+ this.state = 'OPEN'
393
+ }
394
+
395
+ throw error
396
+ }
397
+ }
398
+
399
+ getState() {
400
+ return this.state
401
+ }
402
+ }
403
+
404
+ // Usage
405
+ const externalApiCircuit = new CircuitBreaker(5, 60000)
406
+
407
+ export async function fetchDataSafe(userId: string) {
408
+ return externalApiCircuit.execute(() =>
409
+ fetchExternalData(userId)
410
+ )
411
+ }
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Input Validation (Zod)
417
+
418
+ ```typescript
419
+ // Always validate with Zod
420
+ import { z } from 'zod'
421
+
422
+ const userSchema = z.object({
423
+ email: z.string().email(),
424
+ age: z.number().min(18).max(120),
425
+ country: z.string().length(2), // ISO country code
426
+ newsletter: z.boolean().optional()
427
+ })
428
+
429
+ export async function POST(request: NextRequest) {
430
+ const body = await request.json()
431
+
432
+ // Safe parse (returns result object)
433
+ const result = userSchema.safeParse(body)
434
+
435
+ if (!result.success) {
436
+ logger.warn('validation_error', {
437
+ errors: result.error.flatten()
438
+ })
439
+
440
+ return NextResponse.json(
441
+ {
442
+ error: 'Invalid input',
443
+ details: result.error.flatten().fieldErrors
444
+ },
445
+ { status: 400 }
446
+ )
447
+ }
448
+
449
+ // Use validated data
450
+ const data = result.data // Type-safe!
451
+ // ...
452
+ }
453
+ ```
454
+
455
+ ---
456
+
457
+ ## Best Practices
458
+
459
+ ### DO:
460
+ - ✅ Validate all inputs (never trust user data)
461
+ - ✅ Log every error with context (requestId, userId, etc.)
462
+ - ✅ Return user-friendly error messages
463
+ - ✅ Use custom error classes for domain errors
464
+ - ✅ Implement retry logic for transient failures
465
+ - ✅ Use circuit breakers for external services
466
+ - ✅ Fail fast (don't continue with invalid data)
467
+
468
+ ### DON'T:
469
+ - ❌ Expose stack traces to users
470
+ - ❌ Return raw database errors
471
+ - ❌ Catch errors and do nothing
472
+ - ❌ Use generic error messages ("Something went wrong")
473
+ - ❌ Retry non-retryable errors (validation, 404, etc.)
474
+ - ❌ Hardcode API keys in error logs
475
+
476
+ ---
477
+
478
+ **💡 Remember:** Good error handling makes debugging 10x easier!