opencode-metis 0.1.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.
- package/README.md +140 -0
- package/dist/cli.cjs +63 -0
- package/dist/mcp-server.cjs +51 -0
- package/dist/plugin.cjs +4 -0
- package/dist/worker.cjs +224 -0
- package/opencode/agent/the-analyst/feature-prioritization.md +66 -0
- package/opencode/agent/the-analyst/market-research.md +77 -0
- package/opencode/agent/the-analyst/project-coordination.md +81 -0
- package/opencode/agent/the-analyst/requirements-analysis.md +77 -0
- package/opencode/agent/the-architect/compatibility-review.md +138 -0
- package/opencode/agent/the-architect/complexity-review.md +137 -0
- package/opencode/agent/the-architect/quality-review.md +67 -0
- package/opencode/agent/the-architect/security-review.md +127 -0
- package/opencode/agent/the-architect/system-architecture.md +119 -0
- package/opencode/agent/the-architect/system-documentation.md +83 -0
- package/opencode/agent/the-architect/technology-research.md +85 -0
- package/opencode/agent/the-chief.md +79 -0
- package/opencode/agent/the-designer/accessibility-implementation.md +101 -0
- package/opencode/agent/the-designer/design-foundation.md +74 -0
- package/opencode/agent/the-designer/interaction-architecture.md +75 -0
- package/opencode/agent/the-designer/user-research.md +70 -0
- package/opencode/agent/the-meta-agent.md +155 -0
- package/opencode/agent/the-platform-engineer/ci-cd-pipelines.md +109 -0
- package/opencode/agent/the-platform-engineer/containerization.md +106 -0
- package/opencode/agent/the-platform-engineer/data-architecture.md +81 -0
- package/opencode/agent/the-platform-engineer/dependency-review.md +144 -0
- package/opencode/agent/the-platform-engineer/deployment-automation.md +81 -0
- package/opencode/agent/the-platform-engineer/infrastructure-as-code.md +107 -0
- package/opencode/agent/the-platform-engineer/performance-tuning.md +82 -0
- package/opencode/agent/the-platform-engineer/pipeline-engineering.md +81 -0
- package/opencode/agent/the-platform-engineer/production-monitoring.md +105 -0
- package/opencode/agent/the-qa-engineer/exploratory-testing.md +66 -0
- package/opencode/agent/the-qa-engineer/performance-testing.md +81 -0
- package/opencode/agent/the-qa-engineer/quality-assurance.md +77 -0
- package/opencode/agent/the-qa-engineer/test-execution.md +66 -0
- package/opencode/agent/the-software-engineer/api-development.md +78 -0
- package/opencode/agent/the-software-engineer/component-development.md +79 -0
- package/opencode/agent/the-software-engineer/concurrency-review.md +141 -0
- package/opencode/agent/the-software-engineer/domain-modeling.md +66 -0
- package/opencode/agent/the-software-engineer/performance-optimization.md +113 -0
- package/opencode/command/analyze.md +149 -0
- package/opencode/command/constitution.md +178 -0
- package/opencode/command/debug.md +194 -0
- package/opencode/command/document.md +178 -0
- package/opencode/command/implement.md +225 -0
- package/opencode/command/refactor.md +207 -0
- package/opencode/command/review.md +229 -0
- package/opencode/command/simplify.md +267 -0
- package/opencode/command/specify.md +191 -0
- package/opencode/command/validate.md +224 -0
- package/opencode/skill/accessibility-design/SKILL.md +566 -0
- package/opencode/skill/accessibility-design/checklists/wcag-checklist.md +435 -0
- package/opencode/skill/agent-coordination/SKILL.md +224 -0
- package/opencode/skill/api-contract-design/SKILL.md +550 -0
- package/opencode/skill/api-contract-design/templates/graphql-schema-template.md +818 -0
- package/opencode/skill/api-contract-design/templates/rest-api-template.md +417 -0
- package/opencode/skill/architecture-design/SKILL.md +160 -0
- package/opencode/skill/architecture-design/examples/architecture-examples.md +170 -0
- package/opencode/skill/architecture-design/template.md +749 -0
- package/opencode/skill/architecture-design/validation.md +99 -0
- package/opencode/skill/architecture-selection/SKILL.md +522 -0
- package/opencode/skill/architecture-selection/examples/adrs/001-example-adr.md +71 -0
- package/opencode/skill/architecture-selection/examples/architecture-patterns.md +239 -0
- package/opencode/skill/bug-diagnosis/SKILL.md +235 -0
- package/opencode/skill/code-quality-review/SKILL.md +337 -0
- package/opencode/skill/code-quality-review/examples/anti-patterns.md +629 -0
- package/opencode/skill/code-quality-review/reference.md +322 -0
- package/opencode/skill/code-review/SKILL.md +363 -0
- package/opencode/skill/code-review/reference.md +450 -0
- package/opencode/skill/codebase-analysis/SKILL.md +139 -0
- package/opencode/skill/codebase-navigation/SKILL.md +227 -0
- package/opencode/skill/codebase-navigation/examples/exploration-patterns.md +263 -0
- package/opencode/skill/coding-conventions/SKILL.md +178 -0
- package/opencode/skill/coding-conventions/checklists/accessibility-checklist.md +176 -0
- package/opencode/skill/coding-conventions/checklists/performance-checklist.md +154 -0
- package/opencode/skill/coding-conventions/checklists/security-checklist.md +127 -0
- package/opencode/skill/constitution-validation/SKILL.md +315 -0
- package/opencode/skill/constitution-validation/examples/CONSTITUTION.md +202 -0
- package/opencode/skill/constitution-validation/reference/rule-patterns.md +328 -0
- package/opencode/skill/constitution-validation/template.md +115 -0
- package/opencode/skill/context-preservation/SKILL.md +445 -0
- package/opencode/skill/data-modeling/SKILL.md +385 -0
- package/opencode/skill/data-modeling/templates/schema-design-template.md +268 -0
- package/opencode/skill/deployment-pipeline-design/SKILL.md +579 -0
- package/opencode/skill/deployment-pipeline-design/templates/pipeline-template.md +633 -0
- package/opencode/skill/documentation-extraction/SKILL.md +259 -0
- package/opencode/skill/documentation-sync/SKILL.md +431 -0
- package/opencode/skill/domain-driven-design/SKILL.md +509 -0
- package/opencode/skill/domain-driven-design/examples/ddd-patterns.md +688 -0
- package/opencode/skill/domain-driven-design/reference.md +465 -0
- package/opencode/skill/drift-detection/SKILL.md +383 -0
- package/opencode/skill/drift-detection/reference.md +340 -0
- package/opencode/skill/error-recovery/SKILL.md +162 -0
- package/opencode/skill/error-recovery/examples/error-patterns.md +484 -0
- package/opencode/skill/feature-prioritization/SKILL.md +419 -0
- package/opencode/skill/feature-prioritization/examples/rice-template.md +139 -0
- package/opencode/skill/feature-prioritization/reference.md +256 -0
- package/opencode/skill/git-workflow/SKILL.md +453 -0
- package/opencode/skill/implementation-planning/SKILL.md +215 -0
- package/opencode/skill/implementation-planning/examples/phase-examples.md +217 -0
- package/opencode/skill/implementation-planning/template.md +220 -0
- package/opencode/skill/implementation-planning/validation.md +88 -0
- package/opencode/skill/implementation-verification/SKILL.md +272 -0
- package/opencode/skill/knowledge-capture/SKILL.md +265 -0
- package/opencode/skill/knowledge-capture/reference/knowledge-capture.md +402 -0
- package/opencode/skill/knowledge-capture/reference.md +444 -0
- package/opencode/skill/knowledge-capture/templates/domain-template.md +325 -0
- package/opencode/skill/knowledge-capture/templates/interface-template.md +255 -0
- package/opencode/skill/knowledge-capture/templates/pattern-template.md +144 -0
- package/opencode/skill/observability-design/SKILL.md +291 -0
- package/opencode/skill/observability-design/references/monitoring-patterns.md +461 -0
- package/opencode/skill/pattern-detection/SKILL.md +171 -0
- package/opencode/skill/pattern-detection/examples/common-patterns.md +359 -0
- package/opencode/skill/performance-analysis/SKILL.md +266 -0
- package/opencode/skill/performance-analysis/references/profiling-tools.md +499 -0
- package/opencode/skill/requirements-analysis/SKILL.md +139 -0
- package/opencode/skill/requirements-analysis/examples/good-prd.md +66 -0
- package/opencode/skill/requirements-analysis/template.md +177 -0
- package/opencode/skill/requirements-analysis/validation.md +69 -0
- package/opencode/skill/requirements-elicitation/SKILL.md +518 -0
- package/opencode/skill/requirements-elicitation/examples/interview-questions.md +226 -0
- package/opencode/skill/requirements-elicitation/examples/user-stories.md +414 -0
- package/opencode/skill/safe-refactoring/SKILL.md +312 -0
- package/opencode/skill/safe-refactoring/reference/code-smells.md +347 -0
- package/opencode/skill/security-assessment/SKILL.md +421 -0
- package/opencode/skill/security-assessment/checklists/security-review-checklist.md +285 -0
- package/opencode/skill/specification-management/SKILL.md +143 -0
- package/opencode/skill/specification-management/readme-template.md +32 -0
- package/opencode/skill/specification-management/reference.md +115 -0
- package/opencode/skill/specification-management/spec.py +229 -0
- package/opencode/skill/specification-validation/SKILL.md +397 -0
- package/opencode/skill/specification-validation/reference/3cs-framework.md +306 -0
- package/opencode/skill/specification-validation/reference/ambiguity-detection.md +132 -0
- package/opencode/skill/specification-validation/reference/constitution-validation.md +301 -0
- package/opencode/skill/specification-validation/reference/drift-detection.md +383 -0
- package/opencode/skill/task-delegation/SKILL.md +607 -0
- package/opencode/skill/task-delegation/examples/file-coordination.md +495 -0
- package/opencode/skill/task-delegation/examples/parallel-research.md +337 -0
- package/opencode/skill/task-delegation/examples/sequential-build.md +504 -0
- package/opencode/skill/task-delegation/reference.md +825 -0
- package/opencode/skill/tech-stack-detection/SKILL.md +89 -0
- package/opencode/skill/tech-stack-detection/references/framework-signatures.md +598 -0
- package/opencode/skill/technical-writing/SKILL.md +190 -0
- package/opencode/skill/technical-writing/templates/adr-template.md +205 -0
- package/opencode/skill/technical-writing/templates/system-doc-template.md +380 -0
- package/opencode/skill/test-design/SKILL.md +464 -0
- package/opencode/skill/test-design/examples/test-pyramid.md +724 -0
- package/opencode/skill/testing/SKILL.md +213 -0
- package/opencode/skill/testing/examples/test-pyramid.md +724 -0
- package/opencode/skill/user-insight-synthesis/SKILL.md +576 -0
- package/opencode/skill/user-insight-synthesis/templates/research-plan-template.md +217 -0
- package/opencode/skill/user-research/SKILL.md +508 -0
- package/opencode/skill/user-research/examples/interview-questions.md +265 -0
- package/opencode/skill/user-research/examples/personas.md +267 -0
- package/opencode/skill/vibe-security/SKILL.md +654 -0
- package/package.json +45 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: error-recovery
|
|
3
|
+
description: Consistent error patterns, validation approaches, and recovery strategies. Use when implementing input validation, designing error responses, handling failures gracefully, or establishing logging practices. Covers operational vs programmer errors, user-facing vs internal errors, and recovery mechanisms.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: opencode
|
|
6
|
+
metadata:
|
|
7
|
+
category: development
|
|
8
|
+
version: "1.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Error Handling
|
|
12
|
+
|
|
13
|
+
Roleplay as an error handling specialist that designs consistent error patterns, validation approaches, and recovery strategies for robust systems.
|
|
14
|
+
|
|
15
|
+
ErrorRecovery {
|
|
16
|
+
Activation {
|
|
17
|
+
When implementing input validation at system boundaries
|
|
18
|
+
When designing error responses for APIs or user interfaces
|
|
19
|
+
When building recovery mechanisms for transient failures
|
|
20
|
+
When establishing logging and monitoring patterns
|
|
21
|
+
When distinguishing between errors that need user action vs system intervention
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Constraints {
|
|
25
|
+
Errors are not exceptional - they are expected
|
|
26
|
+
Good error handling treats errors as first-class citizens of system design
|
|
27
|
+
Fail safely, provide actionable feedback, and enable recovery
|
|
28
|
+
Fail fast on programmer errors - do not mask bugs
|
|
29
|
+
Handle operational errors gracefully with recovery options
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ErrorClassification {
|
|
33
|
+
OperationalErrors {
|
|
34
|
+
Runtime problems that occur during normal operation
|
|
35
|
+
These are expected and must be handled
|
|
36
|
+
|
|
37
|
+
Characteristics:
|
|
38
|
+
- External system failures (network, database, filesystem)
|
|
39
|
+
- Invalid user input
|
|
40
|
+
- Resource exhaustion (memory, disk, connections)
|
|
41
|
+
- Timeout conditions
|
|
42
|
+
- Rate limiting
|
|
43
|
+
|
|
44
|
+
Response: Handle gracefully, log appropriately, provide user feedback, implement recovery
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ProgrammerErrors {
|
|
48
|
+
Bugs in the code that should not happen if the code is correct
|
|
49
|
+
|
|
50
|
+
Characteristics:
|
|
51
|
+
- Type errors caught at runtime
|
|
52
|
+
- Null/undefined access on required values
|
|
53
|
+
- Failed assertions on invariants
|
|
54
|
+
- Invalid internal state
|
|
55
|
+
|
|
56
|
+
Response: Fail fast, log full context, alert developers. Do not attempt recovery - fix the bug
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
CorePatterns {
|
|
61
|
+
InputValidation {
|
|
62
|
+
Validate early, validate completely, provide specific feedback
|
|
63
|
+
|
|
64
|
+
FailFastValidation {
|
|
65
|
+
1. Validate at system boundaries (API entry, user input, file reads)
|
|
66
|
+
2. Check all constraints before processing
|
|
67
|
+
3. Return ALL validation errors, not just the first one
|
|
68
|
+
4. Include field name, actual value (if safe), and expected format
|
|
69
|
+
5. Never trust data from external sources
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ValidationChecklist:
|
|
73
|
+
- Required fields present
|
|
74
|
+
- Types correct
|
|
75
|
+
- Values within allowed ranges
|
|
76
|
+
- Formats match expectations (email, URL, date)
|
|
77
|
+
- Business rules satisfied
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ErrorMessages {
|
|
81
|
+
Different audiences need different information
|
|
82
|
+
|
|
83
|
+
UserFacingErrors:
|
|
84
|
+
- Clear action the user can take
|
|
85
|
+
- No technical jargon or stack traces
|
|
86
|
+
- Consistent tone and format
|
|
87
|
+
- Localization-ready
|
|
88
|
+
|
|
89
|
+
InternalLoggedErrors:
|
|
90
|
+
- Full technical context
|
|
91
|
+
- Request/correlation IDs
|
|
92
|
+
- Timestamp and service identifier
|
|
93
|
+
- Stack trace for programmer errors
|
|
94
|
+
- Sanitized sensitive data
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
RecoveryStrategies {
|
|
98
|
+
RetryWithBackoff {
|
|
99
|
+
For transient failures (network timeouts, rate limits)
|
|
100
|
+
- Exponential backoff with jitter
|
|
101
|
+
- Maximum retry count with circuit breaker
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Fallback {
|
|
105
|
+
- Degraded functionality over complete failure
|
|
106
|
+
- Cached data when live data unavailable
|
|
107
|
+
- Default values when configuration missing
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Compensation {
|
|
111
|
+
- Undo partial operations on failure
|
|
112
|
+
- Maintain consistency in distributed operations
|
|
113
|
+
- Saga pattern for multi-step processes
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
LoggingLevels {
|
|
118
|
+
| Level | Use For |
|
|
119
|
+
|-------|---------|
|
|
120
|
+
| ERROR | Operational errors requiring attention |
|
|
121
|
+
| WARN | Recoverable issues, degraded performance |
|
|
122
|
+
| INFO | Significant state changes, request lifecycle |
|
|
123
|
+
| DEBUG | Detailed flow for troubleshooting |
|
|
124
|
+
|
|
125
|
+
WhatToLog:
|
|
126
|
+
- Correlation/request ID
|
|
127
|
+
- User context (sanitized)
|
|
128
|
+
- Operation being attempted
|
|
129
|
+
- Error type and message
|
|
130
|
+
- Duration and timing
|
|
131
|
+
|
|
132
|
+
WhatNOTToLog:
|
|
133
|
+
- Passwords, tokens, secrets
|
|
134
|
+
- Full credit card numbers
|
|
135
|
+
- Personal identifiable information (PII)
|
|
136
|
+
- Raw request/response bodies containing sensitive data
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
BestPractices {
|
|
141
|
+
- Fail fast on programmer errors - do not mask bugs
|
|
142
|
+
- Handle operational errors gracefully with recovery options
|
|
143
|
+
- Provide correlation IDs for tracing requests across services
|
|
144
|
+
- Use structured logging (JSON) for machine parseability
|
|
145
|
+
- Centralize error handling logic - avoid scattered try/catch blocks
|
|
146
|
+
- Test error paths as rigorously as success paths
|
|
147
|
+
- Monitor error rates and set alerts for anomalies
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
AntiPatterns {
|
|
151
|
+
- Catching all exceptions silently (`catch {}`)
|
|
152
|
+
- Logging sensitive data in error messages
|
|
153
|
+
- Returning generic "Something went wrong" without context
|
|
154
|
+
- Retrying non-idempotent operations without safeguards
|
|
155
|
+
- Mixing validation errors with system errors in responses
|
|
156
|
+
- Treating all errors the same regardless of recoverability
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
## References
|
|
161
|
+
|
|
162
|
+
- [examples/error-patterns.md](examples/error-patterns.md) - Concrete examples across languages
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
# Error Patterns Examples
|
|
2
|
+
|
|
3
|
+
Concrete examples demonstrating error handling patterns. Language-agnostic principles with syntax examples.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Input Validation
|
|
8
|
+
|
|
9
|
+
### Context
|
|
10
|
+
|
|
11
|
+
Validate at system boundaries before processing. Return all errors, not just the first one.
|
|
12
|
+
|
|
13
|
+
### Pattern: Collect All Validation Errors
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// TypeScript example
|
|
17
|
+
interface ValidationError {
|
|
18
|
+
field: string;
|
|
19
|
+
message: string;
|
|
20
|
+
code: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function validateUserInput(input: unknown): ValidationError[] {
|
|
24
|
+
const errors: ValidationError[] = [];
|
|
25
|
+
|
|
26
|
+
if (!input || typeof input !== 'object') {
|
|
27
|
+
return [{ field: 'root', message: 'Input must be an object', code: 'INVALID_TYPE' }];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const data = input as Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
if (!data.email) {
|
|
33
|
+
errors.push({ field: 'email', message: 'Email is required', code: 'REQUIRED' });
|
|
34
|
+
} else if (!isValidEmail(data.email)) {
|
|
35
|
+
errors.push({ field: 'email', message: 'Email format is invalid', code: 'INVALID_FORMAT' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!data.age) {
|
|
39
|
+
errors.push({ field: 'age', message: 'Age is required', code: 'REQUIRED' });
|
|
40
|
+
} else if (typeof data.age !== 'number' || data.age < 0 || data.age > 150) {
|
|
41
|
+
errors.push({ field: 'age', message: 'Age must be between 0 and 150', code: 'OUT_OF_RANGE' });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return errors;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# Python example
|
|
50
|
+
from dataclasses import dataclass
|
|
51
|
+
from typing import List, Any, Dict
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ValidationError:
|
|
55
|
+
field: str
|
|
56
|
+
message: str
|
|
57
|
+
code: str
|
|
58
|
+
|
|
59
|
+
def validate_user_input(data: Any) -> List[ValidationError]:
|
|
60
|
+
errors = []
|
|
61
|
+
|
|
62
|
+
if not isinstance(data, dict):
|
|
63
|
+
return [ValidationError('root', 'Input must be a dictionary', 'INVALID_TYPE')]
|
|
64
|
+
|
|
65
|
+
if not data.get('email'):
|
|
66
|
+
errors.append(ValidationError('email', 'Email is required', 'REQUIRED'))
|
|
67
|
+
elif not is_valid_email(data['email']):
|
|
68
|
+
errors.append(ValidationError('email', 'Email format is invalid', 'INVALID_FORMAT'))
|
|
69
|
+
|
|
70
|
+
if 'age' not in data:
|
|
71
|
+
errors.append(ValidationError('age', 'Age is required', 'REQUIRED'))
|
|
72
|
+
elif not isinstance(data['age'], int) or not 0 <= data['age'] <= 150:
|
|
73
|
+
errors.append(ValidationError('age', 'Age must be between 0 and 150', 'OUT_OF_RANGE'))
|
|
74
|
+
|
|
75
|
+
return errors
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Explanation
|
|
79
|
+
|
|
80
|
+
1. Check the most fundamental constraint first (is it an object/dict?)
|
|
81
|
+
2. For each field, check required before format/range
|
|
82
|
+
3. Collect all errors into a list
|
|
83
|
+
4. Return the complete list so users can fix all issues at once
|
|
84
|
+
|
|
85
|
+
### Anti-Pattern
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// BAD: Throws on first error, user must fix one at a time
|
|
89
|
+
function validateBad(data: any): void {
|
|
90
|
+
if (!data.email) throw new Error('Email required');
|
|
91
|
+
if (!data.age) throw new Error('Age required');
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Error Types and Custom Errors
|
|
98
|
+
|
|
99
|
+
### Context
|
|
100
|
+
|
|
101
|
+
Distinguish error types to enable appropriate handling at different layers.
|
|
102
|
+
|
|
103
|
+
### Pattern: Error Hierarchy
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Base error with shared properties
|
|
107
|
+
class AppError extends Error {
|
|
108
|
+
constructor(
|
|
109
|
+
message: string,
|
|
110
|
+
public readonly code: string,
|
|
111
|
+
public readonly isOperational: boolean,
|
|
112
|
+
public readonly context?: Record<string, unknown>
|
|
113
|
+
) {
|
|
114
|
+
super(message);
|
|
115
|
+
this.name = this.constructor.name;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Operational errors - expected, handle gracefully
|
|
120
|
+
class ValidationError extends AppError {
|
|
121
|
+
constructor(message: string, context?: Record<string, unknown>) {
|
|
122
|
+
super(message, 'VALIDATION_ERROR', true, context);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class NotFoundError extends AppError {
|
|
127
|
+
constructor(resource: string, id: string) {
|
|
128
|
+
super(`${resource} not found: ${id}`, 'NOT_FOUND', true, { resource, id });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class ExternalServiceError extends AppError {
|
|
133
|
+
constructor(service: string, cause?: Error) {
|
|
134
|
+
super(`External service failure: ${service}`, 'EXTERNAL_SERVICE_ERROR', true, {
|
|
135
|
+
service,
|
|
136
|
+
cause: cause?.message
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Programmer errors - unexpected, fail fast
|
|
142
|
+
class InvariantViolation extends AppError {
|
|
143
|
+
constructor(invariant: string) {
|
|
144
|
+
super(`Invariant violated: ${invariant}`, 'INVARIANT_VIOLATION', false);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
# Python example
|
|
151
|
+
class AppError(Exception):
|
|
152
|
+
def __init__(self, message: str, code: str, is_operational: bool, context: dict = None):
|
|
153
|
+
super().__init__(message)
|
|
154
|
+
self.code = code
|
|
155
|
+
self.is_operational = is_operational
|
|
156
|
+
self.context = context or {}
|
|
157
|
+
|
|
158
|
+
class ValidationError(AppError):
|
|
159
|
+
def __init__(self, message: str, context: dict = None):
|
|
160
|
+
super().__init__(message, 'VALIDATION_ERROR', True, context)
|
|
161
|
+
|
|
162
|
+
class NotFoundError(AppError):
|
|
163
|
+
def __init__(self, resource: str, resource_id: str):
|
|
164
|
+
super().__init__(
|
|
165
|
+
f'{resource} not found: {resource_id}',
|
|
166
|
+
'NOT_FOUND',
|
|
167
|
+
True,
|
|
168
|
+
{'resource': resource, 'id': resource_id}
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Explanation
|
|
173
|
+
|
|
174
|
+
1. Base error carries common properties: code, operational flag, context
|
|
175
|
+
2. `isOperational` distinguishes expected errors from bugs
|
|
176
|
+
3. Context provides structured data for logging without string parsing
|
|
177
|
+
4. Specific error types enable pattern matching in handlers
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Recovery Strategies
|
|
182
|
+
|
|
183
|
+
### Context
|
|
184
|
+
|
|
185
|
+
Implement retry with exponential backoff for transient failures.
|
|
186
|
+
|
|
187
|
+
### Pattern: Retry with Backoff
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
interface RetryConfig {
|
|
191
|
+
maxAttempts: number;
|
|
192
|
+
baseDelayMs: number;
|
|
193
|
+
maxDelayMs: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function withRetry<T>(
|
|
197
|
+
operation: () => Promise<T>,
|
|
198
|
+
config: RetryConfig,
|
|
199
|
+
isRetryable: (error: unknown) => boolean
|
|
200
|
+
): Promise<T> {
|
|
201
|
+
let lastError: unknown;
|
|
202
|
+
|
|
203
|
+
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
|
|
204
|
+
try {
|
|
205
|
+
return await operation();
|
|
206
|
+
} catch (error) {
|
|
207
|
+
lastError = error;
|
|
208
|
+
|
|
209
|
+
if (!isRetryable(error) || attempt === config.maxAttempts) {
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Exponential backoff with jitter
|
|
214
|
+
const delay = Math.min(
|
|
215
|
+
config.baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 100,
|
|
216
|
+
config.maxDelayMs
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
await sleep(delay);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw lastError;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Usage
|
|
227
|
+
const result = await withRetry(
|
|
228
|
+
() => fetchFromExternalAPI(url),
|
|
229
|
+
{ maxAttempts: 3, baseDelayMs: 100, maxDelayMs: 5000 },
|
|
230
|
+
(error) => error instanceof ExternalServiceError
|
|
231
|
+
);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Explanation
|
|
235
|
+
|
|
236
|
+
1. Accept retry configuration as parameter for flexibility
|
|
237
|
+
2. Predicate function determines which errors are retryable
|
|
238
|
+
3. Exponential backoff: 100ms, 200ms, 400ms...
|
|
239
|
+
4. Jitter prevents thundering herd when many clients retry
|
|
240
|
+
5. Max delay caps the wait time
|
|
241
|
+
6. Re-throw on final attempt or non-retryable errors
|
|
242
|
+
|
|
243
|
+
### Variations
|
|
244
|
+
|
|
245
|
+
- **Circuit breaker:** Track failure rate, stop retrying when threshold exceeded
|
|
246
|
+
- **Fallback:** Return cached/default value instead of retrying
|
|
247
|
+
- **Hedged requests:** Start second request before first times out
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## User-Facing vs Internal Errors
|
|
252
|
+
|
|
253
|
+
### Context
|
|
254
|
+
|
|
255
|
+
Users need actionable guidance. Logs need technical detail.
|
|
256
|
+
|
|
257
|
+
### Pattern: Error Response Transformation
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
interface UserErrorResponse {
|
|
261
|
+
message: string;
|
|
262
|
+
code: string;
|
|
263
|
+
requestId: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
interface LogEntry {
|
|
267
|
+
timestamp: string;
|
|
268
|
+
requestId: string;
|
|
269
|
+
userId?: string;
|
|
270
|
+
error: {
|
|
271
|
+
name: string;
|
|
272
|
+
message: string;
|
|
273
|
+
code: string;
|
|
274
|
+
stack?: string;
|
|
275
|
+
};
|
|
276
|
+
context: Record<string, unknown>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function handleError(
|
|
280
|
+
error: unknown,
|
|
281
|
+
requestId: string,
|
|
282
|
+
userId?: string
|
|
283
|
+
): { response: UserErrorResponse; logEntry: LogEntry } {
|
|
284
|
+
const timestamp = new Date().toISOString();
|
|
285
|
+
|
|
286
|
+
// Determine if error is safe to expose
|
|
287
|
+
const isOperational = error instanceof AppError && error.isOperational;
|
|
288
|
+
|
|
289
|
+
// User gets sanitized message
|
|
290
|
+
const response: UserErrorResponse = {
|
|
291
|
+
message: isOperational
|
|
292
|
+
? (error as AppError).message
|
|
293
|
+
: 'An unexpected error occurred. Please try again.',
|
|
294
|
+
code: isOperational
|
|
295
|
+
? (error as AppError).code
|
|
296
|
+
: 'INTERNAL_ERROR',
|
|
297
|
+
requestId
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// Logs get full context
|
|
301
|
+
const logEntry: LogEntry = {
|
|
302
|
+
timestamp,
|
|
303
|
+
requestId,
|
|
304
|
+
userId,
|
|
305
|
+
error: {
|
|
306
|
+
name: error instanceof Error ? error.name : 'UnknownError',
|
|
307
|
+
message: error instanceof Error ? error.message : String(error),
|
|
308
|
+
code: error instanceof AppError ? error.code : 'UNKNOWN',
|
|
309
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
310
|
+
},
|
|
311
|
+
context: error instanceof AppError ? error.context ?? {} : {}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
return { response, logEntry };
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Explanation
|
|
319
|
+
|
|
320
|
+
1. Request ID links user error to log entry for support
|
|
321
|
+
2. Operational errors: safe to show message to user
|
|
322
|
+
3. Programmer errors: generic message, full details in logs
|
|
323
|
+
4. Never expose stack traces to users
|
|
324
|
+
5. Structured log entry enables querying and alerting
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Centralized Error Handling
|
|
329
|
+
|
|
330
|
+
### Context
|
|
331
|
+
|
|
332
|
+
Avoid scattered try/catch blocks. Handle errors at defined boundaries.
|
|
333
|
+
|
|
334
|
+
### Pattern: Error Boundary Middleware
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
// Express.js example
|
|
338
|
+
function errorHandler(
|
|
339
|
+
error: unknown,
|
|
340
|
+
req: Request,
|
|
341
|
+
res: Response,
|
|
342
|
+
next: NextFunction
|
|
343
|
+
): void {
|
|
344
|
+
const requestId = req.headers['x-request-id'] as string;
|
|
345
|
+
const { response, logEntry } = handleError(error, requestId, req.user?.id);
|
|
346
|
+
|
|
347
|
+
// Log with appropriate level
|
|
348
|
+
if (error instanceof AppError && error.isOperational) {
|
|
349
|
+
logger.warn(logEntry);
|
|
350
|
+
} else {
|
|
351
|
+
logger.error(logEntry);
|
|
352
|
+
// Alert on programmer errors
|
|
353
|
+
alerting.notify('Unexpected error', logEntry);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Send appropriate status code
|
|
357
|
+
const statusCode = getStatusCode(error);
|
|
358
|
+
res.status(statusCode).json(response);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function getStatusCode(error: unknown): number {
|
|
362
|
+
if (error instanceof ValidationError) return 400;
|
|
363
|
+
if (error instanceof NotFoundError) return 404;
|
|
364
|
+
if (error instanceof AuthenticationError) return 401;
|
|
365
|
+
if (error instanceof AuthorizationError) return 403;
|
|
366
|
+
if (error instanceof ExternalServiceError) return 502;
|
|
367
|
+
return 500;
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Explanation
|
|
372
|
+
|
|
373
|
+
1. Single error handler for the entire application
|
|
374
|
+
2. Maps error types to HTTP status codes
|
|
375
|
+
3. Different log levels for operational vs programmer errors
|
|
376
|
+
4. Alerts only on unexpected errors to avoid noise
|
|
377
|
+
5. Controllers throw errors, middleware handles them
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Structured Logging
|
|
382
|
+
|
|
383
|
+
### Context
|
|
384
|
+
|
|
385
|
+
Machine-parseable logs enable querying, aggregation, and alerting.
|
|
386
|
+
|
|
387
|
+
### Pattern: Structured Log Format
|
|
388
|
+
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"timestamp": "2024-01-15T10:23:45.123Z",
|
|
392
|
+
"level": "error",
|
|
393
|
+
"service": "user-service",
|
|
394
|
+
"requestId": "req-abc-123",
|
|
395
|
+
"traceId": "trace-xyz-789",
|
|
396
|
+
"userId": "user-456",
|
|
397
|
+
"operation": "createUser",
|
|
398
|
+
"duration_ms": 234,
|
|
399
|
+
"error": {
|
|
400
|
+
"name": "ValidationError",
|
|
401
|
+
"code": "VALIDATION_ERROR",
|
|
402
|
+
"message": "Email format is invalid",
|
|
403
|
+
"fields": ["email"]
|
|
404
|
+
},
|
|
405
|
+
"metadata": {
|
|
406
|
+
"endpoint": "/api/users",
|
|
407
|
+
"method": "POST"
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Key Fields
|
|
413
|
+
|
|
414
|
+
| Field | Purpose |
|
|
415
|
+
|-------|---------|
|
|
416
|
+
| `timestamp` | ISO 8601 format for sorting and filtering |
|
|
417
|
+
| `level` | Severity for filtering (error, warn, info, debug) |
|
|
418
|
+
| `service` | Origin service in distributed systems |
|
|
419
|
+
| `requestId` | Links logs for single request |
|
|
420
|
+
| `traceId` | Links logs across services |
|
|
421
|
+
| `operation` | Business operation being performed |
|
|
422
|
+
| `duration_ms` | Performance monitoring |
|
|
423
|
+
| `error` | Structured error details |
|
|
424
|
+
|
|
425
|
+
### Anti-Pattern
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
// BAD: Unstructured, hard to parse
|
|
429
|
+
console.log("Error: " + error.message + " for user " + userId);
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Testing Error Paths
|
|
435
|
+
|
|
436
|
+
### Context
|
|
437
|
+
|
|
438
|
+
Error handling code must be tested as rigorously as success paths.
|
|
439
|
+
|
|
440
|
+
### Pattern: Test Error Scenarios
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
describe('UserService', () => {
|
|
444
|
+
describe('createUser', () => {
|
|
445
|
+
it('returns validation errors for invalid email', async () => {
|
|
446
|
+
const result = await userService.createUser({ email: 'invalid', age: 25 });
|
|
447
|
+
|
|
448
|
+
expect(result.isErr()).toBe(true);
|
|
449
|
+
expect(result.error).toBeInstanceOf(ValidationError);
|
|
450
|
+
expect(result.error.context.fields).toContain('email');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('retries on transient database errors', async () => {
|
|
454
|
+
database.save
|
|
455
|
+
.mockRejectedValueOnce(new ConnectionError())
|
|
456
|
+
.mockRejectedValueOnce(new ConnectionError())
|
|
457
|
+
.mockResolvedValueOnce({ id: '123' });
|
|
458
|
+
|
|
459
|
+
const result = await userService.createUser(validInput);
|
|
460
|
+
|
|
461
|
+
expect(result.isOk()).toBe(true);
|
|
462
|
+
expect(database.save).toHaveBeenCalledTimes(3);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('fails fast on constraint violations', async () => {
|
|
466
|
+
database.save.mockRejectedValue(new UniqueConstraintError('email'));
|
|
467
|
+
|
|
468
|
+
const result = await userService.createUser(validInput);
|
|
469
|
+
|
|
470
|
+
expect(result.isErr()).toBe(true);
|
|
471
|
+
expect(database.save).toHaveBeenCalledTimes(1); // No retry
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### What to Test
|
|
478
|
+
|
|
479
|
+
- Validation errors return correct field information
|
|
480
|
+
- Retryable errors trigger retry logic
|
|
481
|
+
- Non-retryable errors fail immediately
|
|
482
|
+
- Error messages are appropriate for audience
|
|
483
|
+
- Sensitive data is not logged
|
|
484
|
+
- Recovery mechanisms work correctly
|