@geekmidas/errors 0.0.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/README.md ADDED
@@ -0,0 +1,500 @@
1
+ # @geekmidas/errors
2
+
3
+ Type-safe HTTP error classes with full TypeScript support, providing structured error handling for REST APIs and HTTP-based applications.
4
+
5
+ ## Features
6
+
7
+ - **Type-Safe Errors**: Full TypeScript support with proper error hierarchies
8
+ - **HTTP Status Codes**: Pre-built error classes for all common HTTP errors
9
+ - **Structured Error Details**: Include additional context and debugging information
10
+ - **Error Factories**: Convenient factory functions for creating errors
11
+ - **Type Guards**: Runtime type checking for error instances
12
+ - **JSON Serialization**: Built-in support for error serialization
13
+ - **Error Wrapping**: Wrap unknown errors into HTTP errors
14
+ - **Cause Chaining**: ES2022 error cause support for error chains
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pnpm add @geekmidas/errors
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### Basic Usage
25
+
26
+ ```typescript
27
+ import {
28
+ BadRequestError,
29
+ NotFoundError,
30
+ UnauthorizedError,
31
+ InternalServerError
32
+ } from '@geekmidas/errors';
33
+
34
+ // Throw specific error types
35
+ throw new NotFoundError('User not found');
36
+ throw new BadRequestError('Invalid email format');
37
+ throw new UnauthorizedError('Invalid token');
38
+ throw new InternalServerError('Database connection failed');
39
+ ```
40
+
41
+ ### With Error Details
42
+
43
+ ```typescript
44
+ import { BadRequestError, NotFoundError } from '@geekmidas/errors';
45
+
46
+ // Include additional context
47
+ throw new NotFoundError('User not found', { userId: '123' });
48
+
49
+ throw new BadRequestError('Validation failed', {
50
+ field: 'email',
51
+ value: 'invalid-email',
52
+ message: 'Must be a valid email address'
53
+ });
54
+ ```
55
+
56
+ ## Available Error Classes
57
+
58
+ ### Client Errors (4xx)
59
+
60
+ ```typescript
61
+ import {
62
+ BadRequestError, // 400
63
+ UnauthorizedError, // 401
64
+ ForbiddenError, // 403
65
+ NotFoundError, // 404
66
+ MethodNotAllowedError, // 405
67
+ ConflictError, // 409
68
+ UnprocessableEntityError, // 422
69
+ TooManyRequestsError // 429
70
+ } from '@geekmidas/errors';
71
+
72
+ // 400 Bad Request
73
+ throw new BadRequestError('Invalid input');
74
+
75
+ // 401 Unauthorized
76
+ throw new UnauthorizedError('Authentication required');
77
+
78
+ // 403 Forbidden
79
+ throw new ForbiddenError('Insufficient permissions', {
80
+ required: 'admin',
81
+ current: 'user'
82
+ });
83
+
84
+ // 404 Not Found
85
+ throw new NotFoundError('Resource not found');
86
+
87
+ // 405 Method Not Allowed
88
+ throw new MethodNotAllowedError('DELETE not supported', ['GET', 'POST', 'PUT']);
89
+
90
+ // 409 Conflict
91
+ throw new ConflictError('Email already exists', {
92
+ email: 'user@example.com'
93
+ });
94
+
95
+ // 422 Unprocessable Entity
96
+ throw new UnprocessableEntityError('Validation failed', {
97
+ email: 'Invalid email format',
98
+ age: 'Must be 18 or older'
99
+ });
100
+
101
+ // 429 Too Many Requests
102
+ throw new TooManyRequestsError('Rate limit exceeded', 60); // retry after 60 seconds
103
+ ```
104
+
105
+ ### Server Errors (5xx)
106
+
107
+ ```typescript
108
+ import {
109
+ InternalServerError, // 500
110
+ NotImplementedError, // 501
111
+ BadGatewayError, // 502
112
+ ServiceUnavailableError, // 503
113
+ GatewayTimeoutError // 504
114
+ } from '@geekmidas/errors';
115
+
116
+ // 500 Internal Server Error
117
+ throw new InternalServerError('Database connection failed');
118
+
119
+ // 501 Not Implemented
120
+ throw new NotImplementedError('Feature not yet implemented');
121
+
122
+ // 502 Bad Gateway
123
+ throw new BadGatewayError('Upstream server error');
124
+
125
+ // 503 Service Unavailable
126
+ throw new ServiceUnavailableError('Maintenance in progress', 300); // retry after 5 minutes
127
+
128
+ // 504 Gateway Timeout
129
+ throw new GatewayTimeoutError('Upstream server timeout');
130
+ ```
131
+
132
+ ## Error Factories
133
+
134
+ Use convenient factory functions for creating errors:
135
+
136
+ ```typescript
137
+ import { createError } from '@geekmidas/errors';
138
+
139
+ // Short, descriptive method names
140
+ throw createError.badRequest('Invalid input');
141
+ throw createError.notFound('User not found');
142
+ throw createError.unauthorized('Invalid token');
143
+ throw createError.forbidden('Access denied');
144
+ throw createError.conflict('Resource already exists');
145
+ throw createError.internalServerError('Something went wrong');
146
+
147
+ // With details
148
+ throw createError.notFound('User not found', { userId: '123' });
149
+ throw createError.badRequest('Validation failed', {
150
+ errors: ['Invalid email', 'Password too short']
151
+ });
152
+ ```
153
+
154
+ ## Generic Error Factory
155
+
156
+ Create errors dynamically with type-safe options:
157
+
158
+ ```typescript
159
+ import { createHttpError } from '@geekmidas/errors';
160
+
161
+ // TypeScript knows which options are valid for each status code
162
+ throw createHttpError(404, 'Not found');
163
+ throw createHttpError(429, 'Rate limited', { retryAfter: 60 });
164
+ throw createHttpError(422, 'Validation failed', {
165
+ validationErrors: {
166
+ email: 'Invalid format',
167
+ age: 'Must be 18+'
168
+ }
169
+ });
170
+ ```
171
+
172
+ ## Type Guards
173
+
174
+ Check error types at runtime:
175
+
176
+ ```typescript
177
+ import {
178
+ isHttpError,
179
+ isClientError,
180
+ isServerError,
181
+ NotFoundError
182
+ } from '@geekmidas/errors';
183
+
184
+ try {
185
+ // some code
186
+ } catch (error) {
187
+ if (isHttpError(error)) {
188
+ console.log(`HTTP ${error.statusCode}: ${error.message}`);
189
+ console.log('Details:', error.details);
190
+ }
191
+
192
+ if (isClientError(error)) {
193
+ // 4xx errors - client's fault
194
+ console.log('Client error:', error.message);
195
+ }
196
+
197
+ if (isServerError(error)) {
198
+ // 5xx errors - server's fault
199
+ console.error('Server error:', error.message);
200
+ // Alert monitoring system
201
+ }
202
+
203
+ if (error instanceof NotFoundError) {
204
+ // Specific error type
205
+ console.log('Resource not found');
206
+ }
207
+ }
208
+ ```
209
+
210
+ ## Error Wrapping
211
+
212
+ Wrap unknown errors into HTTP errors:
213
+
214
+ ```typescript
215
+ import { wrapError } from '@geekmidas/errors';
216
+
217
+ try {
218
+ await someOperation();
219
+ } catch (error) {
220
+ // Wrap unknown error as 500
221
+ throw wrapError(error);
222
+
223
+ // Or wrap with specific status and message
224
+ throw wrapError(error, 503, 'Service temporarily unavailable');
225
+ }
226
+ ```
227
+
228
+ ## Error Serialization
229
+
230
+ Serialize errors to JSON:
231
+
232
+ ```typescript
233
+ import { NotFoundError } from '@geekmidas/errors';
234
+
235
+ const error = new NotFoundError('User not found', { userId: '123' });
236
+
237
+ // Get JSON representation
238
+ const json = error.toJSON();
239
+ // {
240
+ // name: 'NotFoundError',
241
+ // message: 'User not found',
242
+ // statusCode: 404,
243
+ // statusMessage: 'Not Found',
244
+ // details: { userId: '123' },
245
+ // stack: '...'
246
+ // }
247
+
248
+ // Get error body for HTTP response
249
+ const body = error.body;
250
+ // JSON string: '{"message":"User not found","details":{"userId":"123"}}'
251
+ ```
252
+
253
+ ## Error Cause Chaining
254
+
255
+ Chain errors using ES2022 error cause:
256
+
257
+ ```typescript
258
+ import { InternalServerError } from '@geekmidas/errors';
259
+
260
+ try {
261
+ await database.connect();
262
+ } catch (originalError) {
263
+ throw new InternalServerError('Failed to connect to database', {
264
+ cause: originalError,
265
+ details: { host: 'localhost', port: 5432 }
266
+ });
267
+ }
268
+ ```
269
+
270
+ ## Express Middleware
271
+
272
+ Handle errors in Express applications:
273
+
274
+ ```typescript
275
+ import { isHttpError } from '@geekmidas/errors';
276
+ import type { Request, Response, NextFunction } from 'express';
277
+
278
+ function errorHandler(
279
+ error: unknown,
280
+ req: Request,
281
+ res: Response,
282
+ next: NextFunction
283
+ ) {
284
+ if (isHttpError(error)) {
285
+ res.status(error.statusCode).json({
286
+ error: {
287
+ message: error.message,
288
+ code: error.code,
289
+ details: error.details
290
+ }
291
+ });
292
+ } else {
293
+ // Unknown error
294
+ console.error('Unexpected error:', error);
295
+ res.status(500).json({
296
+ error: {
297
+ message: 'Internal Server Error'
298
+ }
299
+ });
300
+ }
301
+ }
302
+
303
+ // Use in Express app
304
+ app.use(errorHandler);
305
+ ```
306
+
307
+ ## Hono Middleware
308
+
309
+ Handle errors in Hono applications:
310
+
311
+ ```typescript
312
+ import { Hono } from 'hono';
313
+ import { isHttpError } from '@geekmidas/errors';
314
+
315
+ const app = new Hono();
316
+
317
+ app.onError((error, c) => {
318
+ if (isHttpError(error)) {
319
+ return c.json(
320
+ {
321
+ error: {
322
+ message: error.message,
323
+ code: error.code,
324
+ details: error.details
325
+ }
326
+ },
327
+ error.statusCode
328
+ );
329
+ }
330
+
331
+ console.error('Unexpected error:', error);
332
+ return c.json(
333
+ { error: { message: 'Internal Server Error' } },
334
+ 500
335
+ );
336
+ });
337
+ ```
338
+
339
+ ## Custom Error Classes
340
+
341
+ Extend base classes for custom errors:
342
+
343
+ ```typescript
344
+ import { HttpError } from '@geekmidas/errors';
345
+
346
+ export class CustomBusinessError extends HttpError {
347
+ constructor(message?: string, details?: any) {
348
+ super(400, message, { details, code: 'BUSINESS_RULE_VIOLATION' });
349
+ }
350
+ }
351
+
352
+ // Usage
353
+ throw new CustomBusinessError('Cannot delete user with active orders', {
354
+ userId: '123',
355
+ activeOrders: 5
356
+ });
357
+ ```
358
+
359
+ ## Error Context
360
+
361
+ Add context to errors for better debugging:
362
+
363
+ ```typescript
364
+ import { NotFoundError } from '@geekmidas/errors';
365
+
366
+ function getUserById(id: string) {
367
+ const user = database.findUser(id);
368
+
369
+ if (!user) {
370
+ throw new NotFoundError('User not found', {
371
+ userId: id,
372
+ requestId: req.headers['x-request-id'],
373
+ timestamp: new Date().toISOString(),
374
+ source: 'getUserById'
375
+ });
376
+ }
377
+
378
+ return user;
379
+ }
380
+ ```
381
+
382
+ ## HTTP Status Codes
383
+
384
+ Use the HttpStatusCode enum for type-safe status codes:
385
+
386
+ ```typescript
387
+ import { HttpStatusCode } from '@geekmidas/errors';
388
+
389
+ // Success
390
+ HttpStatusCode.OK // 200
391
+ HttpStatusCode.CREATED // 201
392
+ HttpStatusCode.NO_CONTENT // 204
393
+
394
+ // Redirection
395
+ HttpStatusCode.MOVED_PERMANENTLY // 301
396
+ HttpStatusCode.NOT_MODIFIED // 304
397
+
398
+ // Client Errors
399
+ HttpStatusCode.BAD_REQUEST // 400
400
+ HttpStatusCode.UNAUTHORIZED // 401
401
+ HttpStatusCode.FORBIDDEN // 403
402
+ HttpStatusCode.NOT_FOUND // 404
403
+ HttpStatusCode.CONFLICT // 409
404
+ HttpStatusCode.UNPROCESSABLE_ENTITY // 422
405
+ HttpStatusCode.TOO_MANY_REQUESTS // 429
406
+
407
+ // Server Errors
408
+ HttpStatusCode.INTERNAL_SERVER_ERROR // 500
409
+ HttpStatusCode.SERVICE_UNAVAILABLE // 503
410
+ ```
411
+
412
+ ## Best Practices
413
+
414
+ ### 1. Use Specific Error Classes
415
+
416
+ ```typescript
417
+ // ❌ Don't use generic Error
418
+ throw new Error('User not found');
419
+
420
+ // ✅ Use specific HTTP error
421
+ throw new NotFoundError('User not found', { userId: '123' });
422
+ ```
423
+
424
+ ### 2. Include Helpful Details
425
+
426
+ ```typescript
427
+ // ❌ Minimal information
428
+ throw new BadRequestError('Invalid input');
429
+
430
+ // ✅ Include context
431
+ throw new BadRequestError('Invalid email format', {
432
+ field: 'email',
433
+ value: userInput,
434
+ expectedFormat: 'user@example.com'
435
+ });
436
+ ```
437
+
438
+ ### 3. Use Type Guards
439
+
440
+ ```typescript
441
+ // ❌ Assume error type
442
+ catch (error: any) {
443
+ console.log(error.statusCode); // Unsafe
444
+ }
445
+
446
+ // ✅ Check error type
447
+ catch (error) {
448
+ if (isHttpError(error)) {
449
+ console.log(error.statusCode); // Safe
450
+ }
451
+ }
452
+ ```
453
+
454
+ ### 4. Chain Errors
455
+
456
+ ```typescript
457
+ // ✅ Preserve original error context
458
+ try {
459
+ await database.query();
460
+ } catch (originalError) {
461
+ throw new InternalServerError('Database query failed', {
462
+ cause: originalError,
463
+ query: 'SELECT * FROM users'
464
+ });
465
+ }
466
+ ```
467
+
468
+ ## TypeScript Types
469
+
470
+ ```typescript
471
+ import type {
472
+ HttpError,
473
+ HttpErrorOptions,
474
+ HttpErrorConstructor
475
+ } from '@geekmidas/errors';
476
+
477
+ // Options for creating errors
478
+ interface HttpErrorOptions {
479
+ statusMessage?: string;
480
+ details?: any;
481
+ code?: string;
482
+ cause?: Error;
483
+ }
484
+
485
+ // Constructor type for factory patterns
486
+ type HttpErrorConstructor = new (
487
+ message?: string,
488
+ options?: HttpErrorOptions
489
+ ) => HttpError;
490
+ ```
491
+
492
+ ## Related Packages
493
+
494
+ - [@geekmidas/constructs](../constructs) - Uses these error classes in endpoints
495
+ - [@geekmidas/client](../client) - Handles these errors on the client side
496
+ - [@geekmidas/logger](../logger) - Log errors with structured context
497
+
498
+ ## License
499
+
500
+ MIT