@b9g/http-errors 0.1.4 → 0.2.0-beta.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 +112 -286
- package/package.json +3 -4
- package/src/index.d.ts +10 -22
- package/src/index.js +52 -23
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# @b9g/http-errors
|
|
2
|
-
|
|
3
|
-
**Standard HTTP error responses for ServiceWorker applications. Returns proper Response objects with status codes, not thrown exceptions.**
|
|
2
|
+
**Standard HTTP error classes with native cause support and automatic serialization**
|
|
4
3
|
|
|
5
4
|
## Features
|
|
6
5
|
|
|
7
|
-
- **ServiceWorker Compatible**: Returns Response objects perfect for `event.respondWith()`
|
|
8
|
-
- **Standard HTTP Status Codes**: Pre-defined functions for all common HTTP errors
|
|
9
|
-
- **No Exceptions**: Functional approach - return errors, don't throw them
|
|
10
|
-
- **TypeScript Support**: Full type definitions for all error classes
|
|
11
6
|
- **Universal**: Works in browsers, Node.js, Bun, and edge platforms
|
|
7
|
+
- **Response Protocol**: Implements `toResponse()` for automatic HTTP response conversion
|
|
8
|
+
- **Structured Logging**: Built-in `toJSON()` for clean error serialization
|
|
9
|
+
- **Standard HTTP Status Codes**: Pre-defined classes for all common HTTP errors
|
|
10
|
+
- **TypeScript Support**: Full type definitions for all error classes
|
|
11
|
+
- **Error Chaining**: Native `cause` support for error context
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
@@ -18,336 +18,162 @@ npm install @b9g/http-errors
|
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
21
|
-
```javascript
|
|
22
|
-
import {
|
|
23
|
-
NotFound,
|
|
24
|
-
BadRequest,
|
|
25
|
-
Unauthorized,
|
|
26
|
-
InternalServerError
|
|
27
|
-
} from '@b9g/http-errors';
|
|
28
|
-
|
|
29
|
-
// Create error responses
|
|
30
|
-
const notFound = NotFound('Page not found');
|
|
31
|
-
const badRequest = BadRequest('Invalid input data');
|
|
32
|
-
const unauthorized = Unauthorized('Authentication required');
|
|
33
|
-
const serverError = InternalServerError('Database connection failed');
|
|
34
|
-
|
|
35
|
-
// All return Response objects
|
|
36
|
-
console.log(notFound instanceof Response); // true
|
|
37
|
-
console.log(notFound.status); // 404
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Available Error Classes
|
|
41
|
-
|
|
42
|
-
### Client Errors (4xx)
|
|
43
|
-
|
|
44
21
|
```javascript
|
|
45
22
|
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
NotFound, // 404
|
|
51
|
-
MethodNotAllowed, // 405
|
|
52
|
-
NotAcceptable, // 406
|
|
53
|
-
RequestTimeout, // 408
|
|
54
|
-
Conflict, // 409
|
|
55
|
-
Gone, // 410
|
|
56
|
-
LengthRequired, // 411
|
|
57
|
-
PreconditionFailed, // 412
|
|
58
|
-
PayloadTooLarge, // 413
|
|
59
|
-
URITooLong, // 414
|
|
60
|
-
UnsupportedMediaType, // 415
|
|
61
|
-
RangeNotSatisfiable, // 416
|
|
62
|
-
ExpectationFailed, // 417
|
|
63
|
-
ImATeapot, // 418
|
|
64
|
-
UnprocessableEntity, // 422
|
|
65
|
-
TooManyRequests, // 429
|
|
23
|
+
NotFound,
|
|
24
|
+
BadRequest,
|
|
25
|
+
Unauthorized,
|
|
26
|
+
InternalServerError
|
|
66
27
|
} from '@b9g/http-errors';
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Server Errors (5xx)
|
|
70
|
-
|
|
71
|
-
```javascript
|
|
72
|
-
import {
|
|
73
|
-
InternalServerError, // 500
|
|
74
|
-
NotImplemented, // 501
|
|
75
|
-
BadGateway, // 502
|
|
76
|
-
ServiceUnavailable, // 503
|
|
77
|
-
GatewayTimeout, // 504
|
|
78
|
-
HTTPVersionNotSupported, // 505
|
|
79
|
-
} from '@b9g/http-errors';
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Usage Examples
|
|
83
|
-
|
|
84
|
-
### Basic Error Responses
|
|
85
|
-
|
|
86
|
-
```javascript
|
|
87
|
-
import { NotFound, BadRequest } from '@b9g/http-errors';
|
|
88
|
-
|
|
89
|
-
// Simple message
|
|
90
|
-
const error1 = NotFound('User not found');
|
|
91
|
-
|
|
92
|
-
// With additional details
|
|
93
|
-
const error2 = BadRequest('Invalid email format', {
|
|
94
|
-
field: 'email',
|
|
95
|
-
code: 'INVALID_FORMAT'
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Router Integration
|
|
100
28
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
29
|
+
// Throw as exceptions
|
|
30
|
+
throw new NotFound('Page not found');
|
|
31
|
+
throw new BadRequest('Invalid input data');
|
|
104
32
|
|
|
105
|
-
|
|
33
|
+
// Or convert to Response objects
|
|
34
|
+
const error = new NotFound('Page not found');
|
|
35
|
+
return error.toResponse(); // Response with status 404
|
|
106
36
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Validate input
|
|
111
|
-
if (!id || isNaN(Number(id))) {
|
|
112
|
-
return BadRequest('Invalid user ID');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Check authentication
|
|
116
|
-
if (!request.headers.get('authorization')) {
|
|
117
|
-
return Unauthorized('Authentication required');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Find user
|
|
121
|
-
const user = await db.users.find(id);
|
|
122
|
-
if (!user) {
|
|
123
|
-
return NotFound('User not found');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return Response.json(user);
|
|
127
|
-
});
|
|
37
|
+
// In development, get detailed error pages
|
|
38
|
+
return error.toResponse(true); // HTML page with stack trace
|
|
128
39
|
```
|
|
129
40
|
|
|
130
|
-
|
|
41
|
+
## API
|
|
131
42
|
|
|
132
|
-
|
|
133
|
-
router.use(async function* (request, context) {
|
|
134
|
-
try {
|
|
135
|
-
return yield request;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
console.error('Request failed:', error);
|
|
138
|
-
return InternalServerError('Something went wrong');
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
```
|
|
43
|
+
### HTTPError Class
|
|
142
44
|
|
|
143
|
-
|
|
45
|
+
Base class for all HTTP errors. Extends `Error`.
|
|
144
46
|
|
|
145
47
|
```javascript
|
|
146
|
-
import {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// With retry information
|
|
155
|
-
const rateLimitError = TooManyRequests('Rate limit exceeded', {
|
|
156
|
-
retryAfter: 60,
|
|
157
|
-
limit: 100,
|
|
158
|
-
window: 3600
|
|
48
|
+
import { HTTPError } from '@b9g/http-errors';
|
|
49
|
+
|
|
50
|
+
const error = new HTTPError(404, 'Resource not found', {
|
|
51
|
+
cause: originalError, // Error that caused this
|
|
52
|
+
headers: { // Custom headers for response
|
|
53
|
+
'Cache-Control': 'no-store'
|
|
54
|
+
},
|
|
55
|
+
expose: true // Whether to expose message to client
|
|
159
56
|
});
|
|
160
57
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
58
|
+
error.status; // 404
|
|
59
|
+
error.message; // 'Resource not found'
|
|
60
|
+
error.expose; // true (client errors default to true, server errors to false)
|
|
61
|
+
error.headers; // { 'Cache-Control': 'no-store' }
|
|
166
62
|
```
|
|
167
63
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
### Classes
|
|
171
|
-
|
|
172
|
-
- `HTTPError` - Base HTTP error class (extends Error)
|
|
173
|
-
- `NotHandled` - Special error for unhandled requests
|
|
174
|
-
|
|
175
|
-
### Client Error Classes (4xx)
|
|
176
|
-
|
|
177
|
-
- `BadRequest` (400)
|
|
178
|
-
- `Unauthorized` (401)
|
|
179
|
-
- `Forbidden` (403)
|
|
180
|
-
- `NotFound` (404)
|
|
181
|
-
- `MethodNotAllowed` (405)
|
|
182
|
-
- `Conflict` (409)
|
|
183
|
-
- `UnprocessableEntity` (422)
|
|
184
|
-
- `TooManyRequests` (429)
|
|
185
|
-
|
|
186
|
-
### Server Error Classes (5xx)
|
|
187
|
-
|
|
188
|
-
- `InternalServerError` (500)
|
|
189
|
-
- `NotImplemented` (501)
|
|
190
|
-
- `BadGateway` (502)
|
|
191
|
-
- `ServiceUnavailable` (503)
|
|
192
|
-
- `GatewayTimeout` (504)
|
|
193
|
-
|
|
194
|
-
### Functions
|
|
195
|
-
|
|
196
|
-
- `createHTTPError(status, message?, options?)` - Create an HTTPError with a specific status code
|
|
197
|
-
- `isHTTPError(value)` - Type guard to check if a value is an HTTPError
|
|
198
|
-
|
|
199
|
-
### Types
|
|
200
|
-
|
|
201
|
-
- `HTTPErrorOptions` - Options for HTTPError constructor
|
|
64
|
+
### Methods
|
|
202
65
|
|
|
203
|
-
|
|
66
|
+
#### `toResponse(isDev?: boolean): Response`
|
|
204
67
|
|
|
205
|
-
|
|
68
|
+
Converts the error to an HTTP Response object.
|
|
206
69
|
|
|
207
|
-
|
|
70
|
+
- In development mode (`isDev = true`): Returns HTML page with stack trace
|
|
71
|
+
- In production mode: Returns plain text with minimal information
|
|
208
72
|
|
|
209
|
-
|
|
73
|
+
```javascript
|
|
74
|
+
const error = new NotFound('Page not found');
|
|
210
75
|
|
|
211
|
-
|
|
76
|
+
// Production response
|
|
77
|
+
error.toResponse(); // Response { status: 404, body: 'Page not found' }
|
|
212
78
|
|
|
213
|
-
|
|
214
|
-
|
|
79
|
+
// Development response with stack trace
|
|
80
|
+
error.toResponse(true); // Response { status: 404, body: '<html>...</html>' }
|
|
215
81
|
```
|
|
216
82
|
|
|
217
|
-
####
|
|
83
|
+
#### `toJSON(): object`
|
|
218
84
|
|
|
219
|
-
|
|
220
|
-
- `details` (optional): Additional error details (serialized as JSON)
|
|
221
|
-
|
|
222
|
-
#### Returns
|
|
223
|
-
|
|
224
|
-
Returns a `Response` object with:
|
|
225
|
-
- Appropriate HTTP status code
|
|
226
|
-
- `Content-Type: application/json`
|
|
227
|
-
- JSON body containing error information
|
|
228
|
-
|
|
229
|
-
### Response Format
|
|
85
|
+
Converts the error to a plain object for logging and serialization.
|
|
230
86
|
|
|
231
87
|
```javascript
|
|
232
|
-
{
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"message": "User not found",
|
|
236
|
-
"status": 404,
|
|
237
|
-
"details": {
|
|
238
|
-
// Any additional details provided
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
88
|
+
const error = new BadRequest('Invalid email', {
|
|
89
|
+
headers: { 'X-Custom': 'value' }
|
|
90
|
+
});
|
|
243
91
|
|
|
244
|
-
|
|
92
|
+
JSON.stringify(error);
|
|
93
|
+
// {
|
|
94
|
+
// "name": "BadRequest",
|
|
95
|
+
// "message": "Invalid email",
|
|
96
|
+
// "status": 400,
|
|
97
|
+
// "statusCode": 400,
|
|
98
|
+
// "expose": true,
|
|
99
|
+
// "headers": { "X-Custom": "value" }
|
|
100
|
+
// }
|
|
245
101
|
|
|
246
|
-
|
|
102
|
+
### Client Error Classes (4xx)
|
|
247
103
|
|
|
248
|
-
```
|
|
249
|
-
import
|
|
104
|
+
```javascript
|
|
105
|
+
import {
|
|
106
|
+
BadRequest, // 400
|
|
107
|
+
Unauthorized, // 401
|
|
108
|
+
Forbidden, // 403
|
|
109
|
+
NotFound, // 404
|
|
110
|
+
MethodNotAllowed, // 405
|
|
111
|
+
Conflict, // 409
|
|
112
|
+
UnprocessableEntity, // 422
|
|
113
|
+
TooManyRequests // 429
|
|
114
|
+
} from '@b9g/http-errors';
|
|
250
115
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
116
|
+
// All accept message and options
|
|
117
|
+
throw new Unauthorized('Invalid credentials', {
|
|
118
|
+
headers: { 'WWW-Authenticate': 'Bearer realm="api"' }
|
|
119
|
+
});
|
|
254
120
|
|
|
255
|
-
|
|
256
|
-
|
|
121
|
+
throw new TooManyRequests('Rate limit exceeded', {
|
|
122
|
+
headers: { 'Retry-After': '60' }
|
|
123
|
+
});
|
|
257
124
|
```
|
|
258
125
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
### API Error Handling
|
|
126
|
+
### Server Error Classes (5xx)
|
|
262
127
|
|
|
263
128
|
```javascript
|
|
264
|
-
import {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
129
|
+
import {
|
|
130
|
+
InternalServerError, // 500
|
|
131
|
+
NotImplemented, // 501
|
|
132
|
+
BadGateway, // 502
|
|
133
|
+
ServiceUnavailable, // 503
|
|
134
|
+
GatewayTimeout // 504
|
|
269
135
|
} from '@b9g/http-errors';
|
|
270
136
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// Validation
|
|
276
|
-
if (!data.email) {
|
|
277
|
-
return BadRequest('Email is required');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Check for existing user
|
|
281
|
-
const existing = await db.users.findByEmail(data.email);
|
|
282
|
-
if (existing) {
|
|
283
|
-
return Conflict('Email already registered');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Create user
|
|
287
|
-
const user = await db.users.create(data);
|
|
288
|
-
return Response.json(user, { status: 201 });
|
|
289
|
-
|
|
290
|
-
} catch (error) {
|
|
291
|
-
return InternalServerError('Failed to create user');
|
|
292
|
-
}
|
|
137
|
+
// Server errors default to expose: false
|
|
138
|
+
throw new InternalServerError('Database connection failed', {
|
|
139
|
+
cause: dbError // Chain the original error
|
|
293
140
|
});
|
|
294
141
|
```
|
|
295
142
|
|
|
296
|
-
###
|
|
143
|
+
### Functions
|
|
144
|
+
|
|
145
|
+
#### `isHTTPError(value): value is HTTPError`
|
|
146
|
+
|
|
147
|
+
Type guard to check if a value is an HTTPError.
|
|
297
148
|
|
|
298
149
|
```javascript
|
|
299
|
-
import {
|
|
150
|
+
import {isHTTPError} from '@b9g/http-errors';
|
|
300
151
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
return
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
const user = await verifyToken(token);
|
|
310
|
-
context.user = user;
|
|
311
|
-
return yield request;
|
|
312
|
-
} catch (error) {
|
|
313
|
-
return Forbidden('Invalid or expired token');
|
|
152
|
+
try {
|
|
153
|
+
// ...
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (isHTTPError(err)) {
|
|
156
|
+
return err.toResponse();
|
|
314
157
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
router.use('/api/admin/*', authMiddleware);
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
318
160
|
```
|
|
319
161
|
|
|
320
|
-
###
|
|
162
|
+
### Types
|
|
321
163
|
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (errors.length > 0) {
|
|
335
|
-
return BadRequest('Validation failed', { errors });
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return null; // Valid
|
|
164
|
+
```typescript
|
|
165
|
+
interface HTTPErrorOptions {
|
|
166
|
+
/** Original error that caused this HTTP error */
|
|
167
|
+
cause?: Error;
|
|
168
|
+
/** Custom headers to include in the error response */
|
|
169
|
+
headers?: Record<string, string>;
|
|
170
|
+
/** Whether error details should be exposed to clients (defaults based on status) */
|
|
171
|
+
expose?: boolean;
|
|
172
|
+
/** Additional properties to attach to the error */
|
|
173
|
+
[key: string]: any;
|
|
339
174
|
}
|
|
340
|
-
|
|
341
|
-
router.post('/register', async (request) => {
|
|
342
|
-
const data = await request.json();
|
|
343
|
-
|
|
344
|
-
const validationError = validateUser(data);
|
|
345
|
-
if (validationError) return validationError;
|
|
346
|
-
|
|
347
|
-
// Process valid data...
|
|
348
|
-
});
|
|
349
175
|
```
|
|
350
176
|
|
|
351
177
|
## License
|
|
352
178
|
|
|
353
|
-
MIT
|
|
179
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/http-errors",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Standard HTTP error
|
|
3
|
+
"version": "0.2.0-beta.0",
|
|
4
|
+
"description": "Standard HTTP error classes with native cause support and automatic serialization",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"http",
|
|
7
7
|
"errors",
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@b9g/libuild": "^0.1.
|
|
18
|
-
"bun-types": "latest"
|
|
17
|
+
"@b9g/libuild": "^0.1.18"
|
|
19
18
|
},
|
|
20
19
|
"type": "module",
|
|
21
20
|
"types": "src/index.d.ts",
|
package/src/index.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Lightweight alternative to http-errors with native Error cause support
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Options for creating HTTP errors
|
|
2
|
+
* Standard HTTP error classes with native cause support and automatic serialization
|
|
7
3
|
*/
|
|
4
|
+
/** Options for creating HTTP errors */
|
|
8
5
|
export interface HTTPErrorOptions {
|
|
9
6
|
/** Original error that caused this HTTP error */
|
|
10
7
|
cause?: Error;
|
|
@@ -12,18 +9,14 @@ export interface HTTPErrorOptions {
|
|
|
12
9
|
headers?: Record<string, string>;
|
|
13
10
|
/** Whether the error details should be exposed to clients (defaults based on status) */
|
|
14
11
|
expose?: boolean;
|
|
15
|
-
/** Additional properties to attach to the error */
|
|
16
|
-
[key: string]: any;
|
|
17
12
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Base HTTP error class
|
|
20
|
-
*/
|
|
13
|
+
/** Base HTTP error class */
|
|
21
14
|
export declare class HTTPError extends Error {
|
|
22
15
|
readonly status: number;
|
|
23
|
-
readonly statusCode: number;
|
|
24
16
|
readonly expose: boolean;
|
|
25
17
|
readonly headers?: Record<string, string>;
|
|
26
18
|
constructor(status: number, message?: string, options?: HTTPErrorOptions);
|
|
19
|
+
get statusCode(): number;
|
|
27
20
|
/**
|
|
28
21
|
* Convert error to a plain object for serialization
|
|
29
22
|
*/
|
|
@@ -35,21 +28,17 @@ export declare class HTTPError extends Error {
|
|
|
35
28
|
expose: boolean;
|
|
36
29
|
headers: Record<string, string>;
|
|
37
30
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Convert error to an HTTP Response
|
|
33
|
+
* In development mode, shows detailed error page with stack trace
|
|
34
|
+
* In production mode, shows minimal error message
|
|
35
|
+
*/
|
|
36
|
+
toResponse(isDev?: boolean): Response;
|
|
44
37
|
}
|
|
45
38
|
/**
|
|
46
39
|
* Check if a value is an HTTP error
|
|
47
40
|
*/
|
|
48
41
|
export declare function isHTTPError(value: any): value is HTTPError;
|
|
49
|
-
/**
|
|
50
|
-
* Create an HTTP error with the given status code
|
|
51
|
-
*/
|
|
52
|
-
export declare function createHTTPError(status: number, message?: string, options?: HTTPErrorOptions): HTTPError;
|
|
53
42
|
export declare class BadRequest extends HTTPError {
|
|
54
43
|
constructor(message?: string, options?: HTTPErrorOptions);
|
|
55
44
|
}
|
|
@@ -89,4 +78,3 @@ export declare class ServiceUnavailable extends HTTPError {
|
|
|
89
78
|
export declare class GatewayTimeout extends HTTPError {
|
|
90
79
|
constructor(message?: string, options?: HTTPErrorOptions);
|
|
91
80
|
}
|
|
92
|
-
export default createHTTPError;
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="./index.d.ts" />
|
|
2
2
|
// src/index.ts
|
|
3
|
-
var
|
|
3
|
+
var STATUS_CODE_DEFAULTS = {
|
|
4
4
|
// 4xx Client Errors
|
|
5
5
|
400: "Bad Request",
|
|
6
6
|
401: "Unauthorized",
|
|
@@ -44,20 +44,23 @@ var STATUS_CODES = {
|
|
|
44
44
|
510: "Not Extended",
|
|
45
45
|
511: "Network Authentication Required"
|
|
46
46
|
};
|
|
47
|
+
var HTTP_ERROR = /* @__PURE__ */ Symbol.for("shovel.http-error");
|
|
47
48
|
var HTTPError = class extends Error {
|
|
48
49
|
status;
|
|
49
|
-
statusCode;
|
|
50
50
|
expose;
|
|
51
51
|
headers;
|
|
52
52
|
constructor(status, message, options = {}) {
|
|
53
|
-
const defaultMessage =
|
|
53
|
+
const defaultMessage = STATUS_CODE_DEFAULTS[status] || "Unknown Error";
|
|
54
54
|
super(message || defaultMessage, { cause: options.cause });
|
|
55
55
|
this.name = this.constructor.name;
|
|
56
|
-
this.status =
|
|
56
|
+
this.status = status;
|
|
57
57
|
this.expose = options.expose ?? status < 500;
|
|
58
58
|
this.headers = options.headers;
|
|
59
59
|
Object.assign(this, options);
|
|
60
60
|
}
|
|
61
|
+
get statusCode() {
|
|
62
|
+
return this.status;
|
|
63
|
+
}
|
|
61
64
|
/**
|
|
62
65
|
* Convert error to a plain object for serialization
|
|
63
66
|
*/
|
|
@@ -71,24 +74,54 @@ var HTTPError = class extends Error {
|
|
|
71
74
|
headers: this.headers
|
|
72
75
|
};
|
|
73
76
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Convert error to an HTTP Response
|
|
79
|
+
* In development mode, shows detailed error page with stack trace
|
|
80
|
+
* In production mode, shows minimal error message
|
|
81
|
+
*/
|
|
82
|
+
toResponse(isDev) {
|
|
83
|
+
const headers = new Headers(this.headers);
|
|
84
|
+
if (isDev && this.expose) {
|
|
85
|
+
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
86
|
+
const statusText = STATUS_CODE_DEFAULTS[this.status] || "Unknown Error";
|
|
87
|
+
const html = `<!DOCTYPE html>
|
|
88
|
+
<html>
|
|
89
|
+
<head>
|
|
90
|
+
<title>${this.status} ${escapeHTML(statusText)}</title>
|
|
91
|
+
<style>
|
|
92
|
+
body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
|
93
|
+
h1 { color: ${this.status >= 500 ? "#c00" : "#e67700"}; }
|
|
94
|
+
.message { font-size: 1.2em; color: #333; }
|
|
95
|
+
pre { background: #f5f5f5; padding: 1rem; overflow-x: auto; border-radius: 4px; }
|
|
96
|
+
</style>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<h1>${this.status} ${escapeHTML(statusText)}</h1>
|
|
100
|
+
<p class="message">${escapeHTML(this.message)}</p>
|
|
101
|
+
<pre>${escapeHTML(this.stack || "No stack trace available")}</pre>
|
|
102
|
+
</body>
|
|
103
|
+
</html>`;
|
|
104
|
+
return new Response(html, {
|
|
105
|
+
status: this.status,
|
|
106
|
+
statusText,
|
|
107
|
+
headers
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
headers.set("Content-Type", "text/plain; charset=utf-8");
|
|
111
|
+
const body = this.expose ? this.message : STATUS_CODE_DEFAULTS[this.status] || "Unknown Error";
|
|
112
|
+
return new Response(body, {
|
|
113
|
+
status: this.status,
|
|
114
|
+
statusText: STATUS_CODE_DEFAULTS[this.status],
|
|
115
|
+
headers
|
|
116
|
+
});
|
|
79
117
|
}
|
|
80
118
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (!(value instanceof Error))
|
|
85
|
-
return false;
|
|
86
|
-
const hasStatus = "status" in value && typeof value.status === "number";
|
|
87
|
-
const hasStatusCode = "statusCode" in value && typeof value.statusCode === "number";
|
|
88
|
-
return hasStatus && hasStatusCode && value.status === value.statusCode;
|
|
119
|
+
HTTPError.prototype[HTTP_ERROR] = true;
|
|
120
|
+
function escapeHTML(str) {
|
|
121
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
89
122
|
}
|
|
90
|
-
function
|
|
91
|
-
return
|
|
123
|
+
function isHTTPError(value) {
|
|
124
|
+
return !!(value && value[HTTP_ERROR]);
|
|
92
125
|
}
|
|
93
126
|
var BadRequest = class extends HTTPError {
|
|
94
127
|
constructor(message, options) {
|
|
@@ -155,7 +188,6 @@ var GatewayTimeout = class extends HTTPError {
|
|
|
155
188
|
super(504, message, options);
|
|
156
189
|
}
|
|
157
190
|
};
|
|
158
|
-
var src_default = createHTTPError;
|
|
159
191
|
export {
|
|
160
192
|
BadGateway,
|
|
161
193
|
BadRequest,
|
|
@@ -166,13 +198,10 @@ export {
|
|
|
166
198
|
InternalServerError,
|
|
167
199
|
MethodNotAllowed,
|
|
168
200
|
NotFound,
|
|
169
|
-
NotHandled,
|
|
170
201
|
NotImplemented,
|
|
171
202
|
ServiceUnavailable,
|
|
172
203
|
TooManyRequests,
|
|
173
204
|
Unauthorized,
|
|
174
205
|
UnprocessableEntity,
|
|
175
|
-
createHTTPError,
|
|
176
|
-
src_default as default,
|
|
177
206
|
isHTTPError
|
|
178
207
|
};
|