@bombillazo/error-x 0.1.1
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/LICENSE +21 -0
- package/README.md +352 -0
- package/dist/index.cjs +534 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +537 -0
- package/dist/index.d.ts +537 -0
- package/dist/index.js +527 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hector Ayala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# error-x
|
|
2
|
+
|
|
3
|
+
A smart, isomorphic, and satisfying error library for TypeScript applications. Provides type-safe error handling with great DX, solving common pain points like unknown error types, lost stack traces, async error handling, and error serialization.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Type-safe error handling** for great DX
|
|
8
|
+
- 🔄 **Smart error conversion** from various formats (API responses, strings, Error objects)
|
|
9
|
+
- 📝 **Auto-formatted messages and error codes** with proper capitalization and punctuation
|
|
10
|
+
- 👤 **User-friendly messages** separate from technical messages
|
|
11
|
+
- 🕒 **Automatic timestamps** for error tracking
|
|
12
|
+
- 🔗 **Error chaining** with cause preservation and stack trace preservation
|
|
13
|
+
- 📊 **Flexible metadata** for additional context
|
|
14
|
+
- 🎛️ **Error handling options** for UI behavior and application actions
|
|
15
|
+
- 📦 **Serialization/deserialization** support for network transfer and storage
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add @bombillazo/error-x
|
|
21
|
+
# or
|
|
22
|
+
npm install @bombillazo/error-x
|
|
23
|
+
# or
|
|
24
|
+
yarn add @bombillazo/error-x
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> [!WARNING]
|
|
28
|
+
>
|
|
29
|
+
> This library is currently in pre-v1.0 development. While we strive to minimize breaking changes, the API may evolve based on feedback and real-world usage. We recommend pinning to specific versions and reviewing release notes when updating.
|
|
30
|
+
>
|
|
31
|
+
> Once we reach version 1.0, we plan to minimize API changes and follow semantic versioning.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { ErrorX, HandlingTargets, type HandlingTarget, type ErrorAction } from 'error-x'
|
|
37
|
+
|
|
38
|
+
// Minimal usage (all parameters optional)
|
|
39
|
+
const error = new ErrorX()
|
|
40
|
+
|
|
41
|
+
// Simple usage
|
|
42
|
+
const error = new ErrorX({ message: 'Database connection failed' })
|
|
43
|
+
|
|
44
|
+
// With full options
|
|
45
|
+
const error = new ErrorX({
|
|
46
|
+
message: 'User authentication failed',
|
|
47
|
+
name: 'AuthError',
|
|
48
|
+
code: 'AUTH_FAILED',
|
|
49
|
+
uiMessage: 'Please check your credentials and try again',
|
|
50
|
+
cause: originalError, // Chain errors while preserving stack traces
|
|
51
|
+
metadata: {
|
|
52
|
+
userId: 123,
|
|
53
|
+
loginAttempt: 3,
|
|
54
|
+
},
|
|
55
|
+
actions: [
|
|
56
|
+
{ action: 'notify', payload: { targets: [HandlingTargets.TOAST, HandlingTargets.BANNER] } },
|
|
57
|
+
{ action: 'redirect', payload: { redirectURL: '/login', delay: 1000 } },
|
|
58
|
+
{ action: 'custom', payload: { type: 'analytics', event: 'auth_failed', userId: 123, category: 'errors', severity: 'high' } }
|
|
59
|
+
]
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Documentation
|
|
64
|
+
|
|
65
|
+
### API Reference
|
|
66
|
+
|
|
67
|
+
For complete API documentation with detailed descriptions, examples, and type information, see:
|
|
68
|
+
|
|
69
|
+
- **[📖 Complete API Documentation](docs/api/error-x.md)** - Full API reference with examples
|
|
70
|
+
- **[🏗️ ErrorX Class](docs/api/error-x.errorx.md)** - Main ErrorX class documentation
|
|
71
|
+
- **[🔧 Types](docs/api/error-x.md#type-aliases)** - All available types and interfaces
|
|
72
|
+
|
|
73
|
+
### Constructor
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
new ErrorX(options?: {
|
|
77
|
+
name?: string // Optional: Error type
|
|
78
|
+
message?: string // Optional: Technical error message (default: 'An error occurred')
|
|
79
|
+
code?: string | number // Optional: Error code (auto-generated from name if not provided)
|
|
80
|
+
uiMessage?: string // Optional: User-friendly message
|
|
81
|
+
cause?: Error | unknown // Optional: Original error that caused this (preserves stack traces)
|
|
82
|
+
metadata?: Record<string, any> // Optional: Additional context data
|
|
83
|
+
actions?: ErrorAction[] // Optional: Configuration for application actions to perform when error occurs
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**All parameters are optional** - ErrorX uses sensible defaults and auto-generates missing values.
|
|
88
|
+
|
|
89
|
+
### Properties
|
|
90
|
+
|
|
91
|
+
| Property | Type | Default Value | Description |
|
|
92
|
+
| --------- | ---------------------------- | ------------------------------------- | ----------------------------------------------------------------- |
|
|
93
|
+
| name | `string` | `'Error'` | Error type/title |
|
|
94
|
+
| code | `string` | Auto-generated from name or `'ERROR'` | Error identifier (auto-generated from name in UPPER_SNAKE_CASE) |
|
|
95
|
+
| message | `string` | `'An error occurred'` | Auto-formatted technical error message |
|
|
96
|
+
| uiMessage | `string \| undefined` | `undefined` | User-friendly message for display |
|
|
97
|
+
| stack | `string` | Auto-generated | Stack trace with preservation and cleaning (inherited from Error) |
|
|
98
|
+
| cause | `unknown` | `undefined` | Original error that caused this (preserves full error chain) |
|
|
99
|
+
| timestamp | `Date` | `new Date()` | When the error was created (readonly) |
|
|
100
|
+
| metadata | `Record<string, any> \| undefined` | `undefined` | Additional context and data |
|
|
101
|
+
| actions | `ErrorAction[] \| undefined` | `undefined` | Array of actions to perform when error occurs (readonly) |
|
|
102
|
+
|
|
103
|
+
### Actions System
|
|
104
|
+
|
|
105
|
+
The `actions` property allows errors to trigger application logic, passing along the necessary data. Your application error handler can route or execute these actions to achieve the desired behavior.
|
|
106
|
+
|
|
107
|
+
`actions` accepts an array of `ErrorAction` objects. The library provides predefined action types with type-safe payloads, and a `CustomAction` type for application-specific actions.
|
|
108
|
+
|
|
109
|
+
#### Action Types
|
|
110
|
+
|
|
111
|
+
| Action Type | Action Value | Required Payload | Description |
|
|
112
|
+
| ----------- | ------------ | ---------------- | ----------- |
|
|
113
|
+
| NotifyAction | `'notify'` | `{ targets: HandlingTarget[], ...any }` | Display notification in specified UI targets |
|
|
114
|
+
| LogoutAction | `'logout'` | `{ ...any }` (optional) | Log out the current user |
|
|
115
|
+
| RedirectAction | `'redirect'` | `{ redirectURL: string, ...any }` | Redirect to a specific URL |
|
|
116
|
+
| CustomAction | `'custom'` | `{ ...any }` (optional) | Application-specific actions with flexible payload structure |
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { HandlingTargets, type ErrorAction, type CustomAction } from 'error-x'
|
|
120
|
+
|
|
121
|
+
// Predefined actions with typed payloads
|
|
122
|
+
const error1 = new ErrorX({
|
|
123
|
+
message: 'Payment failed',
|
|
124
|
+
actions: [
|
|
125
|
+
{ action: 'notify', payload: { targets: [HandlingTargets.MODAL] } },
|
|
126
|
+
{ action: 'redirect', payload: { redirectURL: '/payment', delay: 2000 } }
|
|
127
|
+
]
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Logout action
|
|
131
|
+
const error2 = new ErrorX({
|
|
132
|
+
message: 'Session expired',
|
|
133
|
+
actions: [
|
|
134
|
+
{ action: 'logout', payload: { clearStorage: true } },
|
|
135
|
+
{ action: 'notify', payload: { targets: [HandlingTargets.TOAST] } }
|
|
136
|
+
]
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// Custom actions for application-specific logic
|
|
140
|
+
const error3 = new ErrorX({
|
|
141
|
+
message: 'API rate limit exceeded',
|
|
142
|
+
actions: [
|
|
143
|
+
{
|
|
144
|
+
action: 'custom',
|
|
145
|
+
payload: {
|
|
146
|
+
type: 'show-rate-limit-modal',
|
|
147
|
+
resetTime: Date.now() + 60000,
|
|
148
|
+
message: 'Too many requests. Please wait.'
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
action: 'custom',
|
|
153
|
+
payload: {
|
|
154
|
+
type: 'analytics-track',
|
|
155
|
+
event: 'rate_limit_hit',
|
|
156
|
+
severity: 'warning',
|
|
157
|
+
category: 'api'
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
action: 'custom',
|
|
162
|
+
payload: {
|
|
163
|
+
type: 'cache-request',
|
|
164
|
+
retryAfter: 60,
|
|
165
|
+
endpoint: '/api/users'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Notify Targets
|
|
173
|
+
|
|
174
|
+
For the `NotifyAction`, notify targets can be predefined enum values or custom strings for flexibility:
|
|
175
|
+
|
|
176
|
+
#### Predefined Display Targets
|
|
177
|
+
|
|
178
|
+
| Target | Enum Value | Description |
|
|
179
|
+
| ------ | ---------- | ----------- |
|
|
180
|
+
| MODAL | `'modal'` | Display in a modal dialog |
|
|
181
|
+
| TOAST | `'toast'` | Display as a toast notification |
|
|
182
|
+
| INLINE | `'inline'` | Display inline with content |
|
|
183
|
+
| BANNER | `'banner'` | Display as a banner/alert bar |
|
|
184
|
+
| CONSOLE | `'console'` | Log to browser/server console |
|
|
185
|
+
| LOGGER | `'logger'` | Send to logging service |
|
|
186
|
+
| NOTIFICATION | `'notification'` | System notification |
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { HandlingTargets, type HandlingTarget } from 'error-x'
|
|
190
|
+
|
|
191
|
+
const error = new ErrorX({
|
|
192
|
+
message: 'Mixed error',
|
|
193
|
+
actions: [
|
|
194
|
+
{
|
|
195
|
+
action: 'notify',
|
|
196
|
+
payload: {
|
|
197
|
+
targets: [
|
|
198
|
+
HandlingTargets.CONSOLE, // Predefined
|
|
199
|
+
'my-custom-logger', // Custom
|
|
200
|
+
HandlingTargets.BANNER, // Predefined
|
|
201
|
+
'analytics-tracker' // Custom
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Smart Features
|
|
210
|
+
|
|
211
|
+
### Auto Code Generation
|
|
212
|
+
|
|
213
|
+
Error codes are automatically generated from the error name:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
new ErrorX({ message: 'Failed', name: 'DatabaseError' })
|
|
217
|
+
// code: 'DATABASE_ERROR'
|
|
218
|
+
|
|
219
|
+
new ErrorX({ message: 'Failed', name: 'userAuthError' })
|
|
220
|
+
// code: 'USER_AUTH_ERROR'
|
|
221
|
+
|
|
222
|
+
new ErrorX({ message: 'Failed', name: 'API Timeout' })
|
|
223
|
+
// code: 'API_TIMEOUT'
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Message Formatting
|
|
227
|
+
|
|
228
|
+
Messages are automatically formatted with proper capitalization and punctuation:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
new ErrorX({ message: 'database connection failed' })
|
|
232
|
+
// message: 'Database connection failed.'
|
|
233
|
+
|
|
234
|
+
new ErrorX({ message: 'user not found. please check credentials' })
|
|
235
|
+
// message: 'User not found. Please check credentials.'
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Usage Examples
|
|
239
|
+
|
|
240
|
+
### Basic Error Handling
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import { ErrorX } from 'error-x'
|
|
244
|
+
|
|
245
|
+
function validateUser(user: unknown) {
|
|
246
|
+
if (!user) {
|
|
247
|
+
throw new ErrorX({
|
|
248
|
+
message: 'User validation failed: user is required',
|
|
249
|
+
name: 'ValidationError',
|
|
250
|
+
code: 'USER_REQUIRED',
|
|
251
|
+
uiMessage: 'Please provide user information',
|
|
252
|
+
metadata: { field: 'user', received: user }
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### API Error Handling
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
async function fetchUser(id: string) {
|
|
262
|
+
try {
|
|
263
|
+
const response = await fetch(`/api/users/${id}`)
|
|
264
|
+
if (!response.ok) {
|
|
265
|
+
throw new ErrorX({
|
|
266
|
+
message: `Failed to fetch user: ${response.statusText}`,
|
|
267
|
+
code: `HTTP_${response.status}`,
|
|
268
|
+
uiMessage: 'Unable to load user data',
|
|
269
|
+
metadata: { status: response.status, statusText: response.statusText }
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
return response.json()
|
|
273
|
+
} catch (error) {
|
|
274
|
+
// Convert any error to ErrorX and add context
|
|
275
|
+
const errorX = ErrorX.toErrorX(error)
|
|
276
|
+
throw errorX.withMetadata({
|
|
277
|
+
userId: id,
|
|
278
|
+
operation: 'fetchUser',
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Error Chaining and Stack Trace Preservation
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
try {
|
|
288
|
+
await database.transaction(async (tx) => {
|
|
289
|
+
await tx.users.create(userData)
|
|
290
|
+
})
|
|
291
|
+
} catch (dbError) {
|
|
292
|
+
// Create new ErrorX while preserving the original error in the cause chain
|
|
293
|
+
const error = new ErrorX({
|
|
294
|
+
message: 'User creation failed',
|
|
295
|
+
name: 'UserCreationError',
|
|
296
|
+
code: 'USER_CREATE_FAILED',
|
|
297
|
+
uiMessage: 'Unable to create user account',
|
|
298
|
+
cause: dbError, // Preserves original stack trace and error details
|
|
299
|
+
metadata: {
|
|
300
|
+
operation: 'userRegistration',
|
|
301
|
+
userData: { email: userData.email } // Don't log sensitive data
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// Add more context while preserving the error chain
|
|
306
|
+
throw error.withMetadata({
|
|
307
|
+
requestId: generateRequestId(),
|
|
308
|
+
userAgent: request.headers['user-agent']
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## FAQ
|
|
314
|
+
|
|
315
|
+
### Why use action type "custom" instead of an open string type for CustomAction?
|
|
316
|
+
|
|
317
|
+
The `ErrorAction` type uses a discriminated union based on the `action` property. When you use arbitrary values instead of the predefined action types (`'notify'`, `'logout'`, `'redirect'`, `'custom'`), it breaks TypeScript's ability to properly narrow the payload types.
|
|
318
|
+
|
|
319
|
+
**The Problem:** If `ErrorAction` allowed any string as the action type, TypeScript would default to the most permissive payload type (`{ ...any }`) for all actions, causing type definition to leak between different action types.
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// ❌ Cannot be done - breaks discriminated union
|
|
323
|
+
const error = new ErrorX({
|
|
324
|
+
actions: [
|
|
325
|
+
{ action: 'analytics', payload: { event: 'error' } }, // Loses type safety
|
|
326
|
+
{ action: 'notify', payload: { targets: ['toast'] } }, // Payload type becomes too permissive
|
|
327
|
+
{ action: 'redirect', payload: { redirectURL: '/home' } } // Required properties not enforced
|
|
328
|
+
]
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// ✅ Do this - maintains proper type discrimination
|
|
332
|
+
const error = new ErrorX({
|
|
333
|
+
actions: [
|
|
334
|
+
{ action: 'custom', payload: { type: 'analytics', event: 'error' } },
|
|
335
|
+
{ action: 'notify', payload: { targets: ['toast'] } }, // Properly typed with required 'targets'
|
|
336
|
+
{ action: 'redirect', payload: { redirectURL: '/home' } } // Properly typed with required 'redirectURL'
|
|
337
|
+
]
|
|
338
|
+
})
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**The Solution:** Using `action: 'custom'` with a discriminating `type` property in the payload preserves the discriminated union while allowing unlimited flexibility for custom actions. This approach:
|
|
342
|
+
|
|
343
|
+
- Maintains type safety for predefined actions (`notify`, `logout`, `redirect`)
|
|
344
|
+
- Provides a structured way to handle custom application logic
|
|
345
|
+
- Allows your error handlers to properly switch on action types
|
|
346
|
+
- Enables you to create your own discriminated unions within custom payloads
|
|
347
|
+
|
|
348
|
+
Ideally, we would support custom action types directly in the action. If there is a solution to this problem, we are more than happy to review it. Please open an issue or PR!.
|
|
349
|
+
|
|
350
|
+
## License
|
|
351
|
+
|
|
352
|
+
MIT
|