indusagi-coding-agent 0.1.22 → 0.1.24
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/CHANGELOG.md +72 -11
- package/README.md +2 -36
- package/dist/cli/args.d.ts +117 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +231 -64
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +58 -2
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +130 -12
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/file-processor.d.ts +70 -2
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +240 -15
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/list-models.d.ts +63 -3
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +202 -27
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/login-handler.d.ts +82 -8
- package/dist/cli/login-handler.d.ts.map +1 -1
- package/dist/cli/login-handler.js +410 -77
- package/dist/cli/login-handler.js.map +1 -1
- package/dist/cli/session-picker.d.ts +74 -2
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +236 -12
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/core/agent-session.d.ts +214 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +214 -9
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts +302 -12
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +302 -12
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/diagnostics.d.ts +191 -0
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/diagnostics.js +142 -0
- package/dist/core/diagnostics.js.map +1 -1
- package/dist/core/event-bus.d.ts +146 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +93 -0
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/export-html/ansi-to-html.d.ts +4 -0
- package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
- package/dist/core/export-html/ansi-to-html.js +4 -0
- package/dist/core/export-html/ansi-to-html.js.map +1 -1
- package/dist/core/export-html/index.d.ts +128 -0
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +128 -0
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts +4 -0
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +4 -0
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/keybindings.d.ts +142 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +142 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-registry.d.ts +98 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +98 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +99 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +99 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +0 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +127 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +125 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/subagents.js.map +1 -1
- package/dist/core/tools/bash.d.ts +391 -11
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +269 -2
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +284 -6
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +238 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +169 -5
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +136 -0
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +285 -5
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +247 -0
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +0 -18
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -23
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +6 -0
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +6 -0
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +308 -7
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +231 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/webfetch.d.ts +118 -3
- package/dist/core/tools/webfetch.d.ts.map +1 -1
- package/dist/core/tools/webfetch.js +118 -3
- package/dist/core/tools/webfetch.js.map +1 -1
- package/dist/core/tools/websearch.d.ts +130 -3
- package/dist/core/tools/websearch.d.ts.map +1 -1
- package/dist/core/tools/websearch.js +130 -3
- package/dist/core/tools/websearch.js.map +1 -1
- package/dist/core/tools/write.d.ts +251 -5
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +210 -0
- package/dist/core/tools/write.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +164 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +164 -1
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +297 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +297 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +251 -1
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +186 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +186 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1567 -13
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1567 -13
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +422 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +422 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts +538 -5
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +538 -5
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +921 -8
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +921 -8
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +802 -9
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +802 -9
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +356 -3
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js +356 -3
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/shared.d.ts +386 -0
- package/dist/modes/shared.d.ts.map +1 -0
- package/dist/modes/shared.js +543 -0
- package/dist/modes/shared.js.map +1 -0
- package/dist/utils/array.d.ts +389 -0
- package/dist/utils/array.d.ts.map +1 -0
- package/dist/utils/array.js +585 -0
- package/dist/utils/array.js.map +1 -0
- package/dist/utils/color-formatter.d.ts +318 -0
- package/dist/utils/color-formatter.d.ts.map +1 -0
- package/dist/utils/color-formatter.js +442 -0
- package/dist/utils/color-formatter.js.map +1 -0
- package/dist/utils/data-transformer.d.ts +326 -0
- package/dist/utils/data-transformer.d.ts.map +1 -0
- package/dist/utils/data-transformer.js +512 -0
- package/dist/utils/data-transformer.js.map +1 -0
- package/dist/utils/date-formatter.d.ts +281 -0
- package/dist/utils/date-formatter.d.ts.map +1 -0
- package/dist/utils/date-formatter.js +503 -0
- package/dist/utils/date-formatter.js.map +1 -0
- package/dist/utils/error-handler.d.ts +541 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +726 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/file-operations.d.ts +297 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +505 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +268 -6
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +500 -21
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/json-formatter.d.ts +259 -0
- package/dist/utils/json-formatter.d.ts.map +1 -0
- package/dist/utils/json-formatter.js +517 -0
- package/dist/utils/json-formatter.js.map +1 -0
- package/dist/utils/logger.d.ts +176 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +346 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/markdown-formatter.d.ts +211 -0
- package/dist/utils/markdown-formatter.d.ts.map +1 -0
- package/dist/utils/markdown-formatter.js +482 -0
- package/dist/utils/markdown-formatter.js.map +1 -0
- package/dist/utils/path-validator.d.ts +603 -0
- package/dist/utils/path-validator.d.ts.map +1 -0
- package/dist/utils/path-validator.js +870 -0
- package/dist/utils/path-validator.js.map +1 -0
- package/dist/utils/string-formatter.d.ts +609 -0
- package/dist/utils/string-formatter.d.ts.map +1 -0
- package/dist/utils/string-formatter.js +806 -0
- package/dist/utils/string-formatter.js.map +1 -0
- package/dist/utils/type-guards.d.ts +629 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +662 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/docs/COMPLETE-GUIDE.md +300 -0
- package/docs/MODES-ARCHITECTURE.md +565 -0
- package/docs/PRINT-MODE-GUIDE.md +456 -0
- package/docs/README.md +1 -2
- package/docs/RPC-GUIDE.md +705 -0
- package/docs/UTILS-IMPLEMENTATION-SUMMARY.md +647 -0
- package/docs/UTILS-MODULE-OVERVIEW.md +1480 -0
- package/docs/UTILS-QA-CHECKLIST.md +1061 -0
- package/docs/UTILS-USAGE-GUIDE.md +1419 -0
- package/package.json +3 -3
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Error Handling and Recovery Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides robust error handling utilities for transforming technical
|
|
5
|
+
* errors into user-friendly messages, categorizing errors, and providing recovery
|
|
6
|
+
* suggestions. Error handling is critical for:
|
|
7
|
+
*
|
|
8
|
+
* 1. **User Experience**: Transform cryptic technical errors into actionable messages
|
|
9
|
+
* 2. **Error Categorization**: Classify errors by type (TypeError, FileNotFound, etc.)
|
|
10
|
+
* 3. **Recovery Suggestions**: Provide users with steps to resolve issues
|
|
11
|
+
* 4. **Security**: Avoid leaking sensitive information in error messages
|
|
12
|
+
* 5. **Logging and Debugging**: Format errors for logs and debugging
|
|
13
|
+
* 6. **Error Codes**: Standardize error codes for programmatic error handling
|
|
14
|
+
*
|
|
15
|
+
* ## Error Code Conventions
|
|
16
|
+
*
|
|
17
|
+
* ### System Errors (E-prefix)
|
|
18
|
+
* - E001: Unknown error
|
|
19
|
+
* - E002: Validation error
|
|
20
|
+
* - E003: Configuration error
|
|
21
|
+
* - E004: Permission denied
|
|
22
|
+
* - E005: Resource not found
|
|
23
|
+
* - E006: Timeout error
|
|
24
|
+
* - E007: Network error
|
|
25
|
+
* - E008: Parse error
|
|
26
|
+
*
|
|
27
|
+
* ### Node.js System Errors (POSIX)
|
|
28
|
+
* - EACCES: Permission denied
|
|
29
|
+
* - EEXIST: File already exists
|
|
30
|
+
* - EISDIR: Is a directory
|
|
31
|
+
* - ENOENT: No such file or directory
|
|
32
|
+
* - ENOTDIR: Not a directory
|
|
33
|
+
* - EPERM: Operation not permitted
|
|
34
|
+
* - ETIMEDOUT: Connection timed out
|
|
35
|
+
* - ECONNREFUSED: Connection refused
|
|
36
|
+
*
|
|
37
|
+
* ## Error Type Hierarchy
|
|
38
|
+
*
|
|
39
|
+
* JavaScript error types provide context about what went wrong:
|
|
40
|
+
* - **Error**: Generic error, safe fallback
|
|
41
|
+
* - **TypeError**: Type mismatch or invalid operation
|
|
42
|
+
* - **ReferenceError**: Undefined variable or property
|
|
43
|
+
* - **RangeError**: Value out of valid range
|
|
44
|
+
* - **SyntaxError**: Malformed code or JSON
|
|
45
|
+
* - **EvalError**: eval() function error (rarely thrown)
|
|
46
|
+
* - **URIError**: Invalid URI function argument
|
|
47
|
+
*
|
|
48
|
+
* ## Security Considerations
|
|
49
|
+
*
|
|
50
|
+
* Error messages can leak sensitive information:
|
|
51
|
+
* - File system paths
|
|
52
|
+
* - Internal variable names
|
|
53
|
+
* - API endpoints
|
|
54
|
+
* - Database structure
|
|
55
|
+
* - Configuration secrets
|
|
56
|
+
*
|
|
57
|
+
* Always sanitize error messages before displaying to users.
|
|
58
|
+
*
|
|
59
|
+
* @author Coding Agent
|
|
60
|
+
* @version 1.0.0
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* Standard error code enum for consistent error identification.
|
|
64
|
+
*
|
|
65
|
+
* Error codes allow programmatic error handling and localization.
|
|
66
|
+
* Each code corresponds to an error category and recovery strategy.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* if (error.code === ErrorCode.EACCES) {
|
|
71
|
+
* // Show permission error UI
|
|
72
|
+
* showPermissionError();
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export var ErrorCode;
|
|
77
|
+
(function (ErrorCode) {
|
|
78
|
+
// Generic errors
|
|
79
|
+
ErrorCode["UNKNOWN"] = "E001";
|
|
80
|
+
ErrorCode["VALIDATION"] = "E002";
|
|
81
|
+
ErrorCode["CONFIG"] = "E003";
|
|
82
|
+
ErrorCode["PERMISSION"] = "E004";
|
|
83
|
+
ErrorCode["NOT_FOUND"] = "E005";
|
|
84
|
+
ErrorCode["TIMEOUT"] = "E006";
|
|
85
|
+
ErrorCode["NETWORK"] = "E007";
|
|
86
|
+
ErrorCode["PARSE_ERROR"] = "E008";
|
|
87
|
+
// Node.js system errors
|
|
88
|
+
ErrorCode["EACCES"] = "EACCES";
|
|
89
|
+
ErrorCode["EEXIST"] = "EEXIST";
|
|
90
|
+
ErrorCode["EISDIR"] = "EISDIR";
|
|
91
|
+
ErrorCode["ENOENT"] = "ENOENT";
|
|
92
|
+
ErrorCode["ENOTDIR"] = "ENOTDIR";
|
|
93
|
+
ErrorCode["EPERM"] = "EPERM";
|
|
94
|
+
ErrorCode["ETIMEDOUT"] = "ETIMEDOUT";
|
|
95
|
+
ErrorCode["ECONNREFUSED"] = "ECONNREFUSED";
|
|
96
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
97
|
+
/**
|
|
98
|
+
* Categorizes an error into a standard error type.
|
|
99
|
+
*
|
|
100
|
+
* Error type identification enables targeted error handling and appropriate
|
|
101
|
+
* UI/messaging responses.
|
|
102
|
+
*
|
|
103
|
+
* @param error - The error to categorize
|
|
104
|
+
* @returns Standard error type name
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* try {
|
|
109
|
+
* const value: any = null;
|
|
110
|
+
* value.property.nested = 123; // TypeError: Cannot read property 'property'
|
|
111
|
+
* } catch (error) {
|
|
112
|
+
* const type = categorizeError(error);
|
|
113
|
+
* // type === 'TypeError'
|
|
114
|
+
* if (type === 'TypeError') {
|
|
115
|
+
* showTypeErrorRecovery();
|
|
116
|
+
* }
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @usage
|
|
121
|
+
* ```typescript
|
|
122
|
+
* // Error type routing
|
|
123
|
+
* function handleError(error: unknown) {
|
|
124
|
+
* const errorType = categorizeError(error);
|
|
125
|
+
*
|
|
126
|
+
* switch (errorType) {
|
|
127
|
+
* case 'TypeError':
|
|
128
|
+
* logMetric('type_error');
|
|
129
|
+
* break;
|
|
130
|
+
* case 'ReferenceError':
|
|
131
|
+
* logMetric('reference_error');
|
|
132
|
+
* break;
|
|
133
|
+
* case 'SyntaxError':
|
|
134
|
+
* logMetric('syntax_error');
|
|
135
|
+
* break;
|
|
136
|
+
* default:
|
|
137
|
+
* logMetric('unknown_error');
|
|
138
|
+
* }
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export function categorizeError(error) {
|
|
143
|
+
if (error instanceof TypeError)
|
|
144
|
+
return 'TypeError';
|
|
145
|
+
if (error instanceof ReferenceError)
|
|
146
|
+
return 'ReferenceError';
|
|
147
|
+
if (error instanceof RangeError)
|
|
148
|
+
return 'RangeError';
|
|
149
|
+
if (error instanceof SyntaxError)
|
|
150
|
+
return 'SyntaxError';
|
|
151
|
+
if (error instanceof URIError)
|
|
152
|
+
return 'URIError';
|
|
153
|
+
if (error instanceof EvalError)
|
|
154
|
+
return 'EvalError';
|
|
155
|
+
if (error instanceof Error)
|
|
156
|
+
return error.constructor.name || 'Error';
|
|
157
|
+
return 'UnknownError';
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Extracts and formats stack trace from an error.
|
|
161
|
+
*
|
|
162
|
+
* Stack traces are essential for debugging but should never be shown to
|
|
163
|
+
* end users due to security concerns. Use for logging and diagnostics.
|
|
164
|
+
*
|
|
165
|
+
* @param error - The error to extract stack from
|
|
166
|
+
* @returns Formatted stack trace string or empty string
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* try {
|
|
171
|
+
* riskyOperation();
|
|
172
|
+
* } catch (error) {
|
|
173
|
+
* const stack = extractStackTrace(error);
|
|
174
|
+
* // stack:
|
|
175
|
+
* // "Error: Something went wrong
|
|
176
|
+
* // at riskyOperation (/path/to/file.ts:42:10)
|
|
177
|
+
* // at main (/path/to/file.ts:1:5)"
|
|
178
|
+
* logger.error(stack);
|
|
179
|
+
* }
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @usage
|
|
183
|
+
* ```typescript
|
|
184
|
+
* // Log error details without exposing to users
|
|
185
|
+
* function logErrorForSupport(error: unknown): void {
|
|
186
|
+
* const stack = extractStackTrace(error);
|
|
187
|
+
* const timestamp = new Date().toISOString();
|
|
188
|
+
* const entry = `[${timestamp}] ${String(error)}\n${stack}`;
|
|
189
|
+
*
|
|
190
|
+
* fs.appendFileSync('error.log', entry + '\n');
|
|
191
|
+
* }
|
|
192
|
+
*
|
|
193
|
+
* // Send to error tracking service
|
|
194
|
+
* function reportToSentry(error: unknown): void {
|
|
195
|
+
* const stack = extractStackTrace(error);
|
|
196
|
+
* Sentry.captureException(error, {
|
|
197
|
+
* extra: { stack }
|
|
198
|
+
* });
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export function extractStackTrace(error) {
|
|
203
|
+
if (error instanceof Error && error.stack) {
|
|
204
|
+
return error.stack;
|
|
205
|
+
}
|
|
206
|
+
if (typeof error === 'object' && error !== null && 'stack' in error) {
|
|
207
|
+
const stack = error.stack;
|
|
208
|
+
return typeof stack === 'string' ? stack : '';
|
|
209
|
+
}
|
|
210
|
+
return '';
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Sanitizes error messages to prevent information leakage.
|
|
214
|
+
*
|
|
215
|
+
* Removes sensitive information like:
|
|
216
|
+
* - File system paths
|
|
217
|
+
* - Environment variables
|
|
218
|
+
* - Internal file names
|
|
219
|
+
* - Memory addresses
|
|
220
|
+
* - API endpoints
|
|
221
|
+
*
|
|
222
|
+
* @param message - Raw error message
|
|
223
|
+
* @returns Sanitized error message safe for user display
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```typescript
|
|
227
|
+
* // Input (dangerous):
|
|
228
|
+
* const raw = 'Error reading /home/user/secret.key: EACCES';
|
|
229
|
+
* const safe = sanitizeErrorMessage(raw);
|
|
230
|
+
* // Output: 'Error reading file: Permission denied'
|
|
231
|
+
*
|
|
232
|
+
* // Input (dangerous):
|
|
233
|
+
* const raw2 = 'Failed to connect to DATABASE_URL=postgres://user:pass@host';
|
|
234
|
+
* const safe2 = sanitizeErrorMessage(raw2);
|
|
235
|
+
* // Output: 'Failed to connect to database'
|
|
236
|
+
* ```
|
|
237
|
+
*
|
|
238
|
+
* @usage
|
|
239
|
+
* ```typescript
|
|
240
|
+
* // Safe error display in UI
|
|
241
|
+
* function showUserError(error: unknown): void {
|
|
242
|
+
* const message = extractErrorMessage(error);
|
|
243
|
+
* const safe = sanitizeErrorMessage(message);
|
|
244
|
+
*
|
|
245
|
+
* showNotification({
|
|
246
|
+
* type: 'error',
|
|
247
|
+
* message: safe,
|
|
248
|
+
* duration: 5000
|
|
249
|
+
* });
|
|
250
|
+
* }
|
|
251
|
+
*
|
|
252
|
+
* // Log technical details separately
|
|
253
|
+
* function logErrorSecurely(error: unknown): void {
|
|
254
|
+
* const technical = extractErrorMessage(error);
|
|
255
|
+
* const safe = sanitizeErrorMessage(technical);
|
|
256
|
+
*
|
|
257
|
+
* // Log full details to secure server log
|
|
258
|
+
* internalLogger.error(technical);
|
|
259
|
+
*
|
|
260
|
+
* // Show safe version to user
|
|
261
|
+
* userNotification.error(safe);
|
|
262
|
+
* }
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
export function sanitizeErrorMessage(message) {
|
|
266
|
+
let sanitized = message;
|
|
267
|
+
// Remove file paths (Unix/Windows)
|
|
268
|
+
sanitized = sanitized.replace(/\/[^\s]*\//g, '/.../')
|
|
269
|
+
.replace(/C:\\[^\s]*/g, 'path\\...');
|
|
270
|
+
// Remove URLs and credentials
|
|
271
|
+
sanitized = sanitized.replace(/https?:\/\/[^\s]*/g, 'URL')
|
|
272
|
+
.replace(/([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)@/g, 'credentials@');
|
|
273
|
+
// Remove environment variable patterns
|
|
274
|
+
sanitized = sanitized.replace(/\w+=.*?(?=\s|$)/g, (match) => {
|
|
275
|
+
if (match.includes('://') || match.includes('token') || match.includes('key')) {
|
|
276
|
+
return '[REDACTED]';
|
|
277
|
+
}
|
|
278
|
+
return match;
|
|
279
|
+
});
|
|
280
|
+
// Remove process/memory information
|
|
281
|
+
sanitized = sanitized.replace(/0x[0-9a-f]+/gi, 'address');
|
|
282
|
+
sanitized = sanitized.replace(/pid\s*\d+/gi, 'pid');
|
|
283
|
+
return sanitized;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Extracts the error message from various error types.
|
|
287
|
+
*
|
|
288
|
+
* Handles Error objects, plain objects with message property, and stringified errors.
|
|
289
|
+
*
|
|
290
|
+
* @param error - The error to extract message from
|
|
291
|
+
* @returns Error message string
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```typescript
|
|
295
|
+
* extractErrorMessage(new Error('File not found'))
|
|
296
|
+
* // 'File not found'
|
|
297
|
+
*
|
|
298
|
+
* extractErrorMessage({ message: 'Custom error' })
|
|
299
|
+
* // 'Custom error'
|
|
300
|
+
*
|
|
301
|
+
* extractErrorMessage('Simple string error')
|
|
302
|
+
* // 'Simple string error'
|
|
303
|
+
*
|
|
304
|
+
* extractErrorMessage(null)
|
|
305
|
+
* // 'An unknown error occurred'
|
|
306
|
+
* ```
|
|
307
|
+
*
|
|
308
|
+
* @usage
|
|
309
|
+
* ```typescript
|
|
310
|
+
* // Unified error message extraction
|
|
311
|
+
* async function executeWithErrorHandling<T>(
|
|
312
|
+
* fn: () => Promise<T>
|
|
313
|
+
* ): Promise<T> {
|
|
314
|
+
* try {
|
|
315
|
+
* return await fn();
|
|
316
|
+
* } catch (error) {
|
|
317
|
+
* const message = extractErrorMessage(error);
|
|
318
|
+
* throw new Error(`Operation failed: ${message}`);
|
|
319
|
+
* }
|
|
320
|
+
* }
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
export function extractErrorMessage(error) {
|
|
324
|
+
if (error instanceof Error) {
|
|
325
|
+
return error.message;
|
|
326
|
+
}
|
|
327
|
+
if (typeof error === 'object' && error !== null && 'message' in error) {
|
|
328
|
+
const msg = error.message;
|
|
329
|
+
if (typeof msg === 'string')
|
|
330
|
+
return msg;
|
|
331
|
+
}
|
|
332
|
+
if (typeof error === 'string') {
|
|
333
|
+
return error;
|
|
334
|
+
}
|
|
335
|
+
return 'An unknown error occurred';
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Gets recovery suggestions based on error type.
|
|
339
|
+
*
|
|
340
|
+
* Provides users with actionable steps to resolve common errors.
|
|
341
|
+
*
|
|
342
|
+
* @param error - The error to get suggestions for
|
|
343
|
+
* @param errorCode - Optional error code for more specific suggestions
|
|
344
|
+
* @returns Array of suggestion strings
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```typescript
|
|
348
|
+
* const error = new Error('ENOENT: no such file or directory');
|
|
349
|
+
* const suggestions = getRecoverySuggestions(error, ErrorCode.ENOENT);
|
|
350
|
+
* // [
|
|
351
|
+
* // 'Check that the file path is correct',
|
|
352
|
+
* // 'Verify the file exists in the specified location',
|
|
353
|
+
* // 'Try using an absolute file path instead of relative'
|
|
354
|
+
* // ]
|
|
355
|
+
* ```
|
|
356
|
+
*
|
|
357
|
+
* @usage
|
|
358
|
+
* ```typescript
|
|
359
|
+
* // Show recovery steps in UI
|
|
360
|
+
* function showDetailedError(error: unknown) {
|
|
361
|
+
* const message = extractErrorMessage(error);
|
|
362
|
+
* const suggestions = getRecoverySuggestions(error);
|
|
363
|
+
*
|
|
364
|
+
* showErrorDialog({
|
|
365
|
+
* title: 'Operation Failed',
|
|
366
|
+
* message: sanitizeErrorMessage(message),
|
|
367
|
+
* suggestions: suggestions,
|
|
368
|
+
* actions: [
|
|
369
|
+
* { label: 'Try Again', handler: retry },
|
|
370
|
+
* { label: 'Cancel', handler: cancel }
|
|
371
|
+
* ]
|
|
372
|
+
* });
|
|
373
|
+
* }
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
export function getRecoverySuggestions(error, errorCode) {
|
|
377
|
+
const suggestions = [];
|
|
378
|
+
const message = extractErrorMessage(error);
|
|
379
|
+
const errorType = categorizeError(error);
|
|
380
|
+
// Code-based suggestions
|
|
381
|
+
if (errorCode === ErrorCode.ENOENT) {
|
|
382
|
+
suggestions.push('Check that the file path is correct');
|
|
383
|
+
suggestions.push('Verify the file exists in the specified location');
|
|
384
|
+
suggestions.push('Try using an absolute file path instead of relative');
|
|
385
|
+
}
|
|
386
|
+
else if (errorCode === ErrorCode.EACCES || errorCode === ErrorCode.PERMISSION) {
|
|
387
|
+
suggestions.push('Check file/directory permissions');
|
|
388
|
+
suggestions.push('Try running with elevated privileges if necessary');
|
|
389
|
+
suggestions.push('Ensure the file owner allows read/write access');
|
|
390
|
+
}
|
|
391
|
+
else if (errorCode === ErrorCode.ETIMEDOUT || errorCode === ErrorCode.TIMEOUT) {
|
|
392
|
+
suggestions.push('Check your internet connection');
|
|
393
|
+
suggestions.push('Try again in a few moments');
|
|
394
|
+
suggestions.push('Verify the server is online');
|
|
395
|
+
}
|
|
396
|
+
else if (errorCode === ErrorCode.ECONNREFUSED || errorCode === ErrorCode.NETWORK) {
|
|
397
|
+
suggestions.push('Verify the server address and port');
|
|
398
|
+
suggestions.push('Check your network connection');
|
|
399
|
+
suggestions.push('Ensure firewall is not blocking the connection');
|
|
400
|
+
}
|
|
401
|
+
else if (errorCode === ErrorCode.VALIDATION) {
|
|
402
|
+
suggestions.push('Check that all required fields are filled');
|
|
403
|
+
suggestions.push('Verify data formats match requirements');
|
|
404
|
+
suggestions.push('Review any validation error details above');
|
|
405
|
+
}
|
|
406
|
+
// Type-based suggestions
|
|
407
|
+
if (errorType === 'TypeError') {
|
|
408
|
+
suggestions.push('Verify that variables are initialized before use');
|
|
409
|
+
suggestions.push('Check that function arguments are of correct type');
|
|
410
|
+
suggestions.push('Ensure objects have expected properties');
|
|
411
|
+
}
|
|
412
|
+
else if (errorType === 'ReferenceError') {
|
|
413
|
+
suggestions.push('Check variable name spelling');
|
|
414
|
+
suggestions.push('Ensure variable is declared before use');
|
|
415
|
+
suggestions.push('Verify variable scope and imports');
|
|
416
|
+
}
|
|
417
|
+
else if (errorType === 'RangeError') {
|
|
418
|
+
suggestions.push('Check that values are within acceptable range');
|
|
419
|
+
suggestions.push('Verify numeric limits are not exceeded');
|
|
420
|
+
}
|
|
421
|
+
else if (errorType === 'SyntaxError') {
|
|
422
|
+
suggestions.push('Check file syntax for errors');
|
|
423
|
+
suggestions.push('Verify JSON structure if parsing JSON');
|
|
424
|
+
suggestions.push('Review recent changes to the file');
|
|
425
|
+
}
|
|
426
|
+
// Message-based suggestions
|
|
427
|
+
if (message.includes('memory')) {
|
|
428
|
+
suggestions.push('Close other applications to free memory');
|
|
429
|
+
suggestions.push('Increase available system memory');
|
|
430
|
+
suggestions.push('Break operation into smaller chunks');
|
|
431
|
+
}
|
|
432
|
+
else if (message.includes('timeout')) {
|
|
433
|
+
suggestions.push('Increase timeout duration');
|
|
434
|
+
suggestions.push('Check network connectivity');
|
|
435
|
+
suggestions.push('Optimize operation performance');
|
|
436
|
+
}
|
|
437
|
+
// Default suggestion if none matched
|
|
438
|
+
if (suggestions.length === 0) {
|
|
439
|
+
suggestions.push('Check the error message for more details');
|
|
440
|
+
suggestions.push('Try the operation again');
|
|
441
|
+
suggestions.push('Contact support if the problem persists');
|
|
442
|
+
}
|
|
443
|
+
return suggestions;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Main error formatting function for user-friendly error display.
|
|
447
|
+
*
|
|
448
|
+
* Transforms a raw error into a structured, sanitized error object suitable
|
|
449
|
+
* for display to end users while preserving technical details for logging.
|
|
450
|
+
*
|
|
451
|
+
* @param error - The error to format
|
|
452
|
+
* @param options - Optional formatting options
|
|
453
|
+
* @param options.code - Override error code
|
|
454
|
+
* @param options.includeStack - Include stack trace in output (default: false for users)
|
|
455
|
+
* @param options.sanitize - Sanitize message for user display (default: true)
|
|
456
|
+
* @returns Formatted error object with user-friendly message
|
|
457
|
+
*
|
|
458
|
+
* @throws Never throws, always returns a valid FormattedError
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```typescript
|
|
462
|
+
* // File system error
|
|
463
|
+
* try {
|
|
464
|
+
* fs.readFileSync('/etc/shadow'); // EACCES
|
|
465
|
+
* } catch (error) {
|
|
466
|
+
* const formatted = formatErrorForUser(error, { code: ErrorCode.EACCES });
|
|
467
|
+
* // {
|
|
468
|
+
* // code: 'EACCES',
|
|
469
|
+
* // message: 'Permission denied. Check file permissions.',
|
|
470
|
+
* // technicalDetails: 'Error: EACCES: permission denied, open ...',
|
|
471
|
+
* // suggestions: [
|
|
472
|
+
* // 'Check file/directory permissions',
|
|
473
|
+
* // 'Try running with elevated privileges if necessary',
|
|
474
|
+
* // 'Ensure the file owner allows read/write access'
|
|
475
|
+
* // ],
|
|
476
|
+
* // timestamp: 2024-01-15T10:30:45.123Z
|
|
477
|
+
* // }
|
|
478
|
+
* }
|
|
479
|
+
* ```
|
|
480
|
+
*
|
|
481
|
+
* ```typescript
|
|
482
|
+
* // API validation error
|
|
483
|
+
* const error = new TypeError('Cannot read property "name" of undefined');
|
|
484
|
+
* const formatted = formatErrorForUser(error);
|
|
485
|
+
* // {
|
|
486
|
+
* // code: 'E002',
|
|
487
|
+
* // message: 'Invalid input provided',
|
|
488
|
+
* // technicalDetails: 'TypeError: Cannot read property "name" of undefined',
|
|
489
|
+
* // suggestions: [
|
|
490
|
+
* // 'Verify that variables are initialized before use',
|
|
491
|
+
* // 'Check that function arguments are of correct type',
|
|
492
|
+
* // 'Ensure objects have expected properties'
|
|
493
|
+
* // ],
|
|
494
|
+
* // timestamp: 2024-01-15T10:30:45.123Z
|
|
495
|
+
* // }
|
|
496
|
+
* ```
|
|
497
|
+
*
|
|
498
|
+
* ```typescript
|
|
499
|
+
* // Network timeout
|
|
500
|
+
* const error = new Error('ETIMEDOUT: connection timed out');
|
|
501
|
+
* const formatted = formatErrorForUser(error, { code: ErrorCode.ETIMEDOUT });
|
|
502
|
+
* // {
|
|
503
|
+
* // code: 'ETIMEDOUT',
|
|
504
|
+
* // message: 'Request timed out. Please try again.',
|
|
505
|
+
* // suggestions: [
|
|
506
|
+
* // 'Check your internet connection',
|
|
507
|
+
* // 'Try again in a few moments',
|
|
508
|
+
* // 'Verify the server is online'
|
|
509
|
+
* // ],
|
|
510
|
+
* // timestamp: 2024-01-15T10:30:45.123Z
|
|
511
|
+
* // }
|
|
512
|
+
* ```
|
|
513
|
+
*
|
|
514
|
+
* @usage
|
|
515
|
+
* ```typescript
|
|
516
|
+
* // In Express middleware
|
|
517
|
+
* app.use((err, req, res, next) => {
|
|
518
|
+
* const formatted = formatErrorForUser(err);
|
|
519
|
+
*
|
|
520
|
+
* // Log full technical details
|
|
521
|
+
* internalLogger.error(formatted.technicalDetails);
|
|
522
|
+
*
|
|
523
|
+
* // Send user-friendly response
|
|
524
|
+
* res.status(500).json({
|
|
525
|
+
* error: formatted.message,
|
|
526
|
+
* code: formatted.code,
|
|
527
|
+
* suggestions: formatted.suggestions
|
|
528
|
+
* });
|
|
529
|
+
* });
|
|
530
|
+
*
|
|
531
|
+
* // In CLI applications
|
|
532
|
+
* async function main() {
|
|
533
|
+
* try {
|
|
534
|
+
* await performAction();
|
|
535
|
+
* } catch (error) {
|
|
536
|
+
* const formatted = formatErrorForUser(error);
|
|
537
|
+
* console.error(`Error: ${formatted.message}`);
|
|
538
|
+
* console.error('Recovery steps:');
|
|
539
|
+
* formatted.suggestions.forEach((s, i) => {
|
|
540
|
+
* console.error(` ${i + 1}. ${s}`);
|
|
541
|
+
* });
|
|
542
|
+
* process.exit(1);
|
|
543
|
+
* }
|
|
544
|
+
* }
|
|
545
|
+
*
|
|
546
|
+
* // In React components
|
|
547
|
+
* function ErrorBoundary(props: any) {
|
|
548
|
+
* const [error, setError] = React.useState<FormattedError | null>(null);
|
|
549
|
+
*
|
|
550
|
+
* if (error) {
|
|
551
|
+
* const formatted = formatErrorForUser(error.originalError);
|
|
552
|
+
* return (
|
|
553
|
+
* <ErrorDisplay
|
|
554
|
+
* message={formatted.message}
|
|
555
|
+
* suggestions={formatted.suggestions}
|
|
556
|
+
* onRetry={retry}
|
|
557
|
+
* />
|
|
558
|
+
* );
|
|
559
|
+
* }
|
|
560
|
+
* }
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
563
|
+
export function formatErrorForUser(error, options) {
|
|
564
|
+
const sanitize = options?.sanitize !== false;
|
|
565
|
+
const includeStack = options?.includeStack ?? false;
|
|
566
|
+
const rawMessage = extractErrorMessage(error);
|
|
567
|
+
const errorType = categorizeError(error);
|
|
568
|
+
const stack = extractStackTrace(error);
|
|
569
|
+
const suggestions = getRecoverySuggestions(error, options?.code);
|
|
570
|
+
// Determine error code
|
|
571
|
+
let code = options?.code ?? ErrorCode.UNKNOWN;
|
|
572
|
+
if (!options?.code) {
|
|
573
|
+
// Try to infer code from error
|
|
574
|
+
if (error instanceof Error) {
|
|
575
|
+
if (rawMessage.includes('ENOENT'))
|
|
576
|
+
code = ErrorCode.ENOENT;
|
|
577
|
+
else if (rawMessage.includes('EACCES'))
|
|
578
|
+
code = ErrorCode.EACCES;
|
|
579
|
+
else if (rawMessage.includes('EISDIR'))
|
|
580
|
+
code = ErrorCode.EISDIR;
|
|
581
|
+
else if (rawMessage.includes('EEXIST'))
|
|
582
|
+
code = ErrorCode.EEXIST;
|
|
583
|
+
else if (rawMessage.includes('ETIMEDOUT'))
|
|
584
|
+
code = ErrorCode.ETIMEDOUT;
|
|
585
|
+
else if (rawMessage.includes('ECONNREFUSED'))
|
|
586
|
+
code = ErrorCode.ECONNREFUSED;
|
|
587
|
+
else if (errorType === 'TypeError')
|
|
588
|
+
code = ErrorCode.VALIDATION;
|
|
589
|
+
else if (errorType === 'SyntaxError')
|
|
590
|
+
code = ErrorCode.PARSE_ERROR;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// Create user-friendly message
|
|
594
|
+
let userMessage = sanitize ? sanitizeErrorMessage(rawMessage) : rawMessage;
|
|
595
|
+
// Enhance message with context if it's too generic
|
|
596
|
+
if (userMessage === 'An unknown error occurred' || userMessage.length < 5) {
|
|
597
|
+
if (code === ErrorCode.EACCES)
|
|
598
|
+
userMessage = 'Permission denied. Check your access rights.';
|
|
599
|
+
else if (code === ErrorCode.ENOENT)
|
|
600
|
+
userMessage = 'The requested file or resource was not found.';
|
|
601
|
+
else if (code === ErrorCode.TIMEOUT)
|
|
602
|
+
userMessage = 'The operation timed out. Please try again.';
|
|
603
|
+
else if (code === ErrorCode.NETWORK)
|
|
604
|
+
userMessage = 'A network error occurred. Check your connection.';
|
|
605
|
+
else if (code === ErrorCode.VALIDATION)
|
|
606
|
+
userMessage = 'The provided input is invalid.';
|
|
607
|
+
else if (code === ErrorCode.PARSE_ERROR)
|
|
608
|
+
userMessage = 'Failed to parse data. Check the format.';
|
|
609
|
+
else
|
|
610
|
+
userMessage = `An error occurred (${errorType}). Please try again.`;
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
code,
|
|
614
|
+
message: userMessage,
|
|
615
|
+
technicalDetails: `${errorType}: ${rawMessage}${stack ? '\n' + stack : ''}`,
|
|
616
|
+
stack: includeStack ? stack : undefined,
|
|
617
|
+
suggestions,
|
|
618
|
+
originalError: error,
|
|
619
|
+
timestamp: new Date(),
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Wraps a promise to handle errors gracefully.
|
|
624
|
+
*
|
|
625
|
+
* Returns [data, error] tuple similar to Go's error handling pattern.
|
|
626
|
+
* Makes error handling explicit and type-safe.
|
|
627
|
+
*
|
|
628
|
+
* @param promise - Promise to wrap
|
|
629
|
+
* @returns Tuple of [data, error]
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* ```typescript
|
|
633
|
+
* const [data, error] = await handlePromise(fetch('/api/users'));
|
|
634
|
+
*
|
|
635
|
+
* if (error) {
|
|
636
|
+
* const formatted = formatErrorForUser(error);
|
|
637
|
+
* showErrorNotification(formatted.message);
|
|
638
|
+
* return;
|
|
639
|
+
* }
|
|
640
|
+
*
|
|
641
|
+
* // data is guaranteed to exist here
|
|
642
|
+
* processUsers(data);
|
|
643
|
+
* ```
|
|
644
|
+
*
|
|
645
|
+
* @usage
|
|
646
|
+
* ```typescript
|
|
647
|
+
* // Cleaner async/await error handling
|
|
648
|
+
* async function loadUserData(userId: string) {
|
|
649
|
+
* const [user, error] = await handlePromise(
|
|
650
|
+
* fetch(`/api/users/${userId}`).then(r => r.json())
|
|
651
|
+
* );
|
|
652
|
+
*
|
|
653
|
+
* if (error) {
|
|
654
|
+
* return { success: false, error: formatErrorForUser(error) };
|
|
655
|
+
* }
|
|
656
|
+
*
|
|
657
|
+
* return { success: true, data: user };
|
|
658
|
+
* }
|
|
659
|
+
* ```
|
|
660
|
+
*/
|
|
661
|
+
export async function handlePromise(promise) {
|
|
662
|
+
try {
|
|
663
|
+
const data = await promise;
|
|
664
|
+
return [data, null];
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
668
|
+
return [null, err];
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Retries an operation with exponential backoff.
|
|
673
|
+
*
|
|
674
|
+
* Useful for transient failures (network issues, temporary unavailability).
|
|
675
|
+
*
|
|
676
|
+
* @param fn - Async function to retry
|
|
677
|
+
* @param maxAttempts - Maximum number of attempts (default: 3)
|
|
678
|
+
* @param baseDelay - Base delay in milliseconds (default: 1000)
|
|
679
|
+
* @returns Promise that resolves with function result or rejects with last error
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* ```typescript
|
|
683
|
+
* const data = await retryWithBackoff(
|
|
684
|
+
* () => fetch('/api/data').then(r => r.json()),
|
|
685
|
+
* 3,
|
|
686
|
+
* 1000
|
|
687
|
+
* );
|
|
688
|
+
* // Attempts:
|
|
689
|
+
* // 1. Immediately
|
|
690
|
+
* // 2. After ~1000ms
|
|
691
|
+
* // 3. After ~2000ms
|
|
692
|
+
* ```
|
|
693
|
+
*
|
|
694
|
+
* @usage
|
|
695
|
+
* ```typescript
|
|
696
|
+
* // Resilient API calls
|
|
697
|
+
* async function fetchUserWithRetry(userId: string) {
|
|
698
|
+
* return retryWithBackoff(
|
|
699
|
+
* async () => {
|
|
700
|
+
* const response = await fetch(`/api/users/${userId}`);
|
|
701
|
+
* if (!response.ok) throw new Error('API error');
|
|
702
|
+
* return response.json();
|
|
703
|
+
* },
|
|
704
|
+
* 3,
|
|
705
|
+
* 1000
|
|
706
|
+
* );
|
|
707
|
+
* }
|
|
708
|
+
* ```
|
|
709
|
+
*/
|
|
710
|
+
export async function retryWithBackoff(fn, maxAttempts = 3, baseDelay = 1000) {
|
|
711
|
+
let lastError = null;
|
|
712
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
713
|
+
try {
|
|
714
|
+
return await fn();
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
718
|
+
if (attempt < maxAttempts - 1) {
|
|
719
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
720
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
throw lastError || new Error('Operation failed after retries');
|
|
725
|
+
}
|
|
726
|
+
//# sourceMappingURL=error-handler.js.map
|