@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 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.1",
4
- "description": "HTTP error classes for web applications",
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
- "web",
9
- "status-codes"
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.10",
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
- return value instanceof HTTPError || value instanceof Error && typeof value.status === "number" && typeof value.statusCode === "number" && value.status === value.statusCode;
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);