@b9g/http-errors 0.1.1 → 0.1.4
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 +353 -0
- package/package.json +9 -5
- package/src/index.d.ts +0 -4
- package/src/index.js +7 -12
package/README.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# @b9g/http-errors
|
|
2
|
+
|
|
3
|
+
**Standard HTTP error responses for ServiceWorker applications. Returns proper Response objects with status codes, not thrown exceptions.**
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
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
|
+
- **Universal**: Works in browsers, Node.js, Bun, and edge platforms
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @b9g/http-errors
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
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
|
+
```javascript
|
|
45
|
+
import {
|
|
46
|
+
BadRequest, // 400
|
|
47
|
+
Unauthorized, // 401
|
|
48
|
+
PaymentRequired, // 402
|
|
49
|
+
Forbidden, // 403
|
|
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
|
|
66
|
+
} 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
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
import { Router } from '@b9g/router';
|
|
103
|
+
import { NotFound, BadRequest, Unauthorized } from '@b9g/http-errors';
|
|
104
|
+
|
|
105
|
+
const router = new Router();
|
|
106
|
+
|
|
107
|
+
router.get('/users/:id', async (request, context) => {
|
|
108
|
+
const { id } = context.params;
|
|
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
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Middleware Error Handling
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
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
|
+
```
|
|
142
|
+
|
|
143
|
+
### Custom Error Details
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
import { BadRequest, Conflict } from '@b9g/http-errors';
|
|
147
|
+
|
|
148
|
+
// With error code
|
|
149
|
+
const validationError = BadRequest('Validation failed', {
|
|
150
|
+
code: 'VALIDATION_ERROR',
|
|
151
|
+
fields: ['email', 'password']
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// With retry information
|
|
155
|
+
const rateLimitError = TooManyRequests('Rate limit exceeded', {
|
|
156
|
+
retryAfter: 60,
|
|
157
|
+
limit: 100,
|
|
158
|
+
window: 3600
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// With conflict details
|
|
162
|
+
const duplicateError = Conflict('Email already exists', {
|
|
163
|
+
field: 'email',
|
|
164
|
+
value: 'user@example.com'
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Exports
|
|
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
|
|
202
|
+
|
|
203
|
+
### Default Export
|
|
204
|
+
|
|
205
|
+
- `createHTTPError` - Factory function for creating HTTP errors
|
|
206
|
+
|
|
207
|
+
## API Reference
|
|
208
|
+
|
|
209
|
+
### Error Classes
|
|
210
|
+
|
|
211
|
+
All error functions follow the same signature:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
function ErrorName(message?: string, details?: any): Response
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Parameters
|
|
218
|
+
|
|
219
|
+
- `message` (optional): Human-readable error message
|
|
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
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
{
|
|
233
|
+
"error": {
|
|
234
|
+
"type": "NotFound",
|
|
235
|
+
"message": "User not found",
|
|
236
|
+
"status": 404,
|
|
237
|
+
"details": {
|
|
238
|
+
// Any additional details provided
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## TypeScript Support
|
|
245
|
+
|
|
246
|
+
Full TypeScript definitions included:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import type { ErrorResponse } from '@b9g/http-errors';
|
|
250
|
+
|
|
251
|
+
function handleError(): ErrorResponse {
|
|
252
|
+
return NotFound('Resource not found');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ErrorResponse extends Response
|
|
256
|
+
const response: Response = handleError();
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Integration Examples
|
|
260
|
+
|
|
261
|
+
### API Error Handling
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
import {
|
|
265
|
+
BadRequest,
|
|
266
|
+
NotFound,
|
|
267
|
+
Conflict,
|
|
268
|
+
InternalServerError
|
|
269
|
+
} from '@b9g/http-errors';
|
|
270
|
+
|
|
271
|
+
router.post('/api/users', async (request) => {
|
|
272
|
+
try {
|
|
273
|
+
const data = await request.json();
|
|
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
|
+
}
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Auth Middleware
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
import { Unauthorized, Forbidden } from '@b9g/http-errors';
|
|
300
|
+
|
|
301
|
+
const authMiddleware = async function* (request, context) {
|
|
302
|
+
const token = request.headers.get('authorization');
|
|
303
|
+
|
|
304
|
+
if (!token) {
|
|
305
|
+
return Unauthorized('Authentication required');
|
|
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');
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
router.use('/api/admin/*', authMiddleware);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Input Validation
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
import { BadRequest } from '@b9g/http-errors';
|
|
324
|
+
|
|
325
|
+
function validateUser(data) {
|
|
326
|
+
const errors = [];
|
|
327
|
+
|
|
328
|
+
if (!data.email) errors.push('email is required');
|
|
329
|
+
if (!data.password) errors.push('password is required');
|
|
330
|
+
if (data.password && data.password.length < 8) {
|
|
331
|
+
errors.push('password must be at least 8 characters');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (errors.length > 0) {
|
|
335
|
+
return BadRequest('Validation failed', { errors });
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return null; // Valid
|
|
339
|
+
}
|
|
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
|
+
```
|
|
350
|
+
|
|
351
|
+
## License
|
|
352
|
+
|
|
353
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/http-errors",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "HTTP error
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Standard HTTP error responses for ServiceWorker applications. Returns proper Response objects with status codes, not thrown exceptions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"http",
|
|
7
7
|
"errors",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
8
|
+
"response",
|
|
9
|
+
"serviceworker",
|
|
10
|
+
"status-codes",
|
|
11
|
+
"web-standards",
|
|
12
|
+
"functional",
|
|
13
|
+
"shovel"
|
|
10
14
|
],
|
|
11
15
|
"dependencies": {},
|
|
12
16
|
"devDependencies": {
|
|
13
|
-
"@b9g/libuild": "^0.1.
|
|
17
|
+
"@b9g/libuild": "^0.1.11",
|
|
14
18
|
"bun-types": "latest"
|
|
15
19
|
},
|
|
16
20
|
"type": "module",
|
package/src/index.d.ts
CHANGED
|
@@ -35,10 +35,6 @@ export declare class HTTPError extends Error {
|
|
|
35
35
|
expose: boolean;
|
|
36
36
|
headers: Record<string, string>;
|
|
37
37
|
};
|
|
38
|
-
/**
|
|
39
|
-
* Create a Response object from this error
|
|
40
|
-
*/
|
|
41
|
-
toResponse(): Response;
|
|
42
38
|
}
|
|
43
39
|
/**
|
|
44
40
|
* Special error for middleware fallthrough (not an HTTP error)
|
package/src/index.js
CHANGED
|
@@ -71,17 +71,6 @@ var HTTPError = class extends Error {
|
|
|
71
71
|
headers: this.headers
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Create a Response object from this error
|
|
76
|
-
*/
|
|
77
|
-
toResponse() {
|
|
78
|
-
const body = this.expose ? this.message : STATUS_CODES[this.status];
|
|
79
|
-
return new Response(body, {
|
|
80
|
-
status: this.status,
|
|
81
|
-
statusText: STATUS_CODES[this.status],
|
|
82
|
-
headers: this.headers
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
74
|
};
|
|
86
75
|
var NotHandled = class extends Error {
|
|
87
76
|
constructor(message = "Request not handled by middleware") {
|
|
@@ -90,7 +79,13 @@ var NotHandled = class extends Error {
|
|
|
90
79
|
}
|
|
91
80
|
};
|
|
92
81
|
function isHTTPError(value) {
|
|
93
|
-
|
|
82
|
+
if (value instanceof HTTPError)
|
|
83
|
+
return true;
|
|
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;
|
|
94
89
|
}
|
|
95
90
|
function createHTTPError(status, message, options) {
|
|
96
91
|
return new HTTPError(status, message, options);
|