@maroonedsoftware/errors 1.0.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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/http/http.error.d.ts +154 -0
- package/dist/http/http.error.d.ts.map +1 -0
- package/dist/http/http.status.map.d.ts +55 -0
- package/dist/http/http.status.map.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +269 -0
- package/dist/index.js.map +1 -0
- package/dist/on.error.decorator.d.ts +46 -0
- package/dist/on.error.decorator.d.ts.map +1 -0
- package/dist/postgres/postgres.error.decorator.d.ts +2 -0
- package/dist/postgres/postgres.error.decorator.d.ts.map +1 -0
- package/dist/postgres/postgres.error.handler.d.ts +51 -0
- package/dist/postgres/postgres.error.handler.d.ts.map +1 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Marooned Software
|
|
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,176 @@
|
|
|
1
|
+
# @maroonedsoftware/errors
|
|
2
|
+
|
|
3
|
+
A comprehensive error handling library for HTTP APIs with built-in support for PostgreSQL error mapping and class-level error decorators.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @maroonedsoftware/errors
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **HttpError** — Fluent HTTP error class with support for status codes, headers, details, and error chaining
|
|
14
|
+
- **OnError** — Class decorator for automatic error handling on all methods
|
|
15
|
+
- **PostgresErrorHandler** — Maps PostgreSQL error codes to appropriate HTTP errors
|
|
16
|
+
- **Type-safe** — Full TypeScript support with inferred status messages
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### HttpError
|
|
21
|
+
|
|
22
|
+
Create HTTP errors with fluent method chaining:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { HttpError, httpError, unauthorizedError } from '@maroonedsoftware/errors';
|
|
26
|
+
|
|
27
|
+
// Using the factory function
|
|
28
|
+
throw httpError(404);
|
|
29
|
+
|
|
30
|
+
// With custom message
|
|
31
|
+
throw httpError(400, 'Validation failed');
|
|
32
|
+
|
|
33
|
+
// With error details (great for form validation)
|
|
34
|
+
throw httpError(400).withErrors({
|
|
35
|
+
email: 'Invalid email format',
|
|
36
|
+
password: 'Must be at least 8 characters',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// With response headers
|
|
40
|
+
throw httpError(401).withHeaders({
|
|
41
|
+
'WWW-Authenticate': 'Bearer realm="api"',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Shorthand for unauthorized with WWW-Authenticate header
|
|
45
|
+
throw unauthorizedError('Bearer realm="api"');
|
|
46
|
+
|
|
47
|
+
// With error chaining
|
|
48
|
+
throw httpError(500).withCause(originalError);
|
|
49
|
+
|
|
50
|
+
// With internal details (for logging, not exposed to clients)
|
|
51
|
+
throw httpError(500).withInternalDetails({
|
|
52
|
+
userId: 123,
|
|
53
|
+
requestId: 'abc-123',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Combine multiple options
|
|
57
|
+
throw httpError(409).withErrors({ username: 'Already taken' }).withCause(dbError).withInternalDetails({ attemptedUsername: 'john_doe' });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Type Guard
|
|
61
|
+
|
|
62
|
+
Check if an error is an HttpError:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { IsHttpError } from '@maroonedsoftware/errors';
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await someOperation();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (IsHttpError(error)) {
|
|
71
|
+
console.log(error.statusCode); // Typed access
|
|
72
|
+
console.log(error.details);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### OnError Decorator
|
|
78
|
+
|
|
79
|
+
Automatically wrap all class methods with error handling:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { OnError, httpError } from '@maroonedsoftware/errors';
|
|
83
|
+
|
|
84
|
+
@OnError(error => {
|
|
85
|
+
console.error('Error caught:', error);
|
|
86
|
+
throw httpError(500).withCause(error);
|
|
87
|
+
})
|
|
88
|
+
class MyService {
|
|
89
|
+
async doSomething() {
|
|
90
|
+
// If this throws, it will be caught and handled
|
|
91
|
+
throw new Error('Something went wrong');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get computedValue() {
|
|
95
|
+
// Getters are also wrapped
|
|
96
|
+
throw new Error('Getter failed');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### PostgreSQL Error Handling
|
|
102
|
+
|
|
103
|
+
Convert PostgreSQL errors to appropriate HTTP errors:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { PostgresErrorHandler, OnPostgresError } from '@maroonedsoftware/errors';
|
|
107
|
+
|
|
108
|
+
// Manual usage
|
|
109
|
+
try {
|
|
110
|
+
await db.insert(users).values({ email: 'duplicate@example.com' });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
PostgresErrorHandler(error);
|
|
113
|
+
// 23505 (unique violation) → 409 Conflict
|
|
114
|
+
// 23503 (foreign key violation) → 404 Not Found
|
|
115
|
+
// 23502, 22P02, 22003, 23514 (validation) → 400 Bad Request
|
|
116
|
+
// 40000, 40001, 40002 (transaction rollback) → 500 Internal Server Error
|
|
117
|
+
// 40P01 (deadlock) → 500 Internal Server Error
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Using the decorator (recommended)
|
|
121
|
+
@OnPostgresError()
|
|
122
|
+
class UserRepository {
|
|
123
|
+
async create(data: UserData) {
|
|
124
|
+
return await db.insert(users).values(data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async findById(id: number) {
|
|
128
|
+
return await db.select().from(users).where(eq(users.id, id));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Supported HTTP Status Codes
|
|
134
|
+
|
|
135
|
+
All standard 4xx and 5xx status codes are supported with their default messages:
|
|
136
|
+
|
|
137
|
+
| Code | Message |
|
|
138
|
+
| ---- | ----------------------------------------- |
|
|
139
|
+
| 400 | Bad Request |
|
|
140
|
+
| 401 | Unauthorized |
|
|
141
|
+
| 403 | Forbidden |
|
|
142
|
+
| 404 | Not Found |
|
|
143
|
+
| 409 | Conflict |
|
|
144
|
+
| 422 | Unprocessable Entity |
|
|
145
|
+
| 429 | Too Many Requests |
|
|
146
|
+
| 500 | Internal Server Error |
|
|
147
|
+
| 502 | Bad Gateway |
|
|
148
|
+
| 503 | Service Unavailable |
|
|
149
|
+
| ... | [and more](./src/http/http.status.map.ts) |
|
|
150
|
+
|
|
151
|
+
## API Reference
|
|
152
|
+
|
|
153
|
+
### HttpError
|
|
154
|
+
|
|
155
|
+
| Property | Type | Description |
|
|
156
|
+
| ----------------- | ------------------------- | ----------------------------------------- |
|
|
157
|
+
| `statusCode` | `HttpStatusCodes` | The HTTP status code |
|
|
158
|
+
| `message` | `string` | Error message |
|
|
159
|
+
| `details` | `Record<string, unknown>` | Validation/error details for response |
|
|
160
|
+
| `headers` | `Record<string, string>` | HTTP headers to include in response |
|
|
161
|
+
| `cause` | `Error` | Underlying error (for error chaining) |
|
|
162
|
+
| `internalDetails` | `Record<string, unknown>` | Internal debugging info (not for clients) |
|
|
163
|
+
|
|
164
|
+
### Methods
|
|
165
|
+
|
|
166
|
+
| Method | Returns | Description |
|
|
167
|
+
| ------------------------------ | ----------- | --------------------------- |
|
|
168
|
+
| `withErrors(errors)` | `HttpError` | Add error details |
|
|
169
|
+
| `withHeaders(headers)` | `HttpError` | Add response headers |
|
|
170
|
+
| `addHeader(key, value)` | `HttpError` | Add a single header |
|
|
171
|
+
| `withCause(error)` | `HttpError` | Set the underlying cause |
|
|
172
|
+
| `withInternalDetails(details)` | `HttpError` | Add internal debugging info |
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { HttpStatusMap } from './http.status.map.js';
|
|
2
|
+
/**
|
|
3
|
+
* Type representing valid HTTP status codes from the HttpStatusMap.
|
|
4
|
+
*/
|
|
5
|
+
export type HttpStatusCodes = keyof typeof HttpStatusMap;
|
|
6
|
+
/**
|
|
7
|
+
* Type representing the status message for a given HTTP status code.
|
|
8
|
+
* @template T - The HTTP status code type.
|
|
9
|
+
*/
|
|
10
|
+
export type HttpStatusMessage<T extends HttpStatusCodes> = (typeof HttpStatusMap)[T];
|
|
11
|
+
/**
|
|
12
|
+
* Custom error class for HTTP errors with support for status codes, details, headers, and error chaining.
|
|
13
|
+
* Extends the native Error class and provides a fluent API for building error responses.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* throw new HttpError(404).withErrors({ field: 'not found' });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* throw new HttpError(401)
|
|
23
|
+
* .withHeaders({ 'WWW-Authenticate': 'Bearer' })
|
|
24
|
+
* .withCause(originalError);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class HttpError extends Error {
|
|
28
|
+
readonly statusCode: HttpStatusCodes;
|
|
29
|
+
/**
|
|
30
|
+
* Optional validation or error details to include in the response.
|
|
31
|
+
* Typically used for 400-level errors to provide field-specific error information.
|
|
32
|
+
*/
|
|
33
|
+
details?: Record<string, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* Optional HTTP headers to include in the error response.
|
|
36
|
+
* Useful for authentication errors (e.g., WWW-Authenticate header).
|
|
37
|
+
*/
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
/**
|
|
40
|
+
* Optional underlying error that caused this HTTP error.
|
|
41
|
+
* Follows the Error.cause pattern for error chaining.
|
|
42
|
+
*/
|
|
43
|
+
cause?: Error;
|
|
44
|
+
/**
|
|
45
|
+
* Optional internal details that should not be exposed to clients.
|
|
46
|
+
* Useful for debugging and logging purposes.
|
|
47
|
+
*/
|
|
48
|
+
internalDetails?: Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* Creates a new HttpError instance.
|
|
51
|
+
*
|
|
52
|
+
* @param statusCode - The HTTP status code (must be a key from HttpStatusMap).
|
|
53
|
+
* @param message - Optional custom error message. If not provided, uses the default message from HttpStatusMap.
|
|
54
|
+
*/
|
|
55
|
+
constructor(statusCode: HttpStatusCodes, message?: HttpStatusMessage<HttpStatusCodes>);
|
|
56
|
+
/**
|
|
57
|
+
* Adds error details to the HTTP error and returns the instance for method chaining.
|
|
58
|
+
*
|
|
59
|
+
* @param errors - Object containing field-specific error information.
|
|
60
|
+
* @returns The HttpError instance for method chaining.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* error.withErrors({ email: 'Invalid email format', password: 'Too short' });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
withErrors(errors: Record<string, unknown>): HttpError;
|
|
68
|
+
/**
|
|
69
|
+
* Adds HTTP headers to the error response and returns the instance for method chaining.
|
|
70
|
+
*
|
|
71
|
+
* @param headers - Object containing HTTP header key-value pairs.
|
|
72
|
+
* @returns The HttpError instance for method chaining.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* error.withHeaders({ 'WWW-Authenticate': 'Bearer realm="api"' });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
withHeaders(headers: Record<string, string>): HttpError;
|
|
80
|
+
/**
|
|
81
|
+
* Sets the underlying cause of this error and returns the instance for method chaining.
|
|
82
|
+
*
|
|
83
|
+
* @param cause - The original error that caused this HTTP error.
|
|
84
|
+
* @returns The HttpError instance for method chaining.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* error.withCause(new Error('Database connection failed'));
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
withCause(cause: Error): HttpError;
|
|
92
|
+
/**
|
|
93
|
+
* Adds internal details that should not be exposed to clients and returns the instance for method chaining.
|
|
94
|
+
*
|
|
95
|
+
* @param internalDetails - Object containing internal debugging information.
|
|
96
|
+
* @returns The HttpError instance for method chaining.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* error.withInternalDetails({ userId: 123, requestId: 'abc-123' });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
withInternalDetails(internalDetails: Record<string, unknown>): HttpError;
|
|
104
|
+
/**
|
|
105
|
+
* Adds a HTTP header to the error response and returns the instance for method chaining.
|
|
106
|
+
*
|
|
107
|
+
* @param key - The HTTP header key.
|
|
108
|
+
* @param value - The HTTP header value.
|
|
109
|
+
* @returns The HttpError instance for method chaining.
|
|
110
|
+
*/
|
|
111
|
+
addHeader(key: string, value: string): HttpError;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Type guard to check if an unknown value is an HttpError instance.
|
|
115
|
+
*
|
|
116
|
+
* @param error - The value to check.
|
|
117
|
+
* @returns True if the value is an HttpError instance, false otherwise.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* if (IsHttpError(error)) {
|
|
122
|
+
* console.log(error.statusCode);
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export declare const IsHttpError: (error: unknown) => error is HttpError;
|
|
127
|
+
/**
|
|
128
|
+
* Factory function to create an HttpError instance with a specific status code.
|
|
129
|
+
*
|
|
130
|
+
* @template StatusCode - The HTTP status code type.
|
|
131
|
+
* @template StatusMessage - The status message type for the given status code.
|
|
132
|
+
* @param statusCode - The HTTP status code.
|
|
133
|
+
* @param message - Optional custom error message.
|
|
134
|
+
* @returns A new HttpError instance.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* throw httpError(404);
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare const httpError: <StatusCode extends HttpStatusCodes, StatusMessage extends HttpStatusMessage<StatusCode>>(statusCode: StatusCode, message?: StatusMessage) => HttpError;
|
|
142
|
+
/**
|
|
143
|
+
* Factory function to create an unauthorized HttpError instance.
|
|
144
|
+
*
|
|
145
|
+
* @param error - The error message of the WWW-Authenticate header.
|
|
146
|
+
* @returns A new HttpError instance with the WWW-Authenticate header set to the given error message.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* throw unauthorizedError('Bearer realm="api"');
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export declare const unauthorizedError: (error: string) => HttpError;
|
|
154
|
+
//# sourceMappingURL=http.error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.error.d.ts","sourceRoot":"","sources":["../../src/http/http.error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,aAAa,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,eAAe,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAErF;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAgChC,QAAQ,CAAC,UAAU,EAAE,eAAe;IA/BtC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE1C;;;;;OAKG;gBAEQ,UAAU,EAAE,eAAe,EACpC,OAAO,CAAC,EAAE,iBAAiB,CAAC,eAAe,CAAC;IAW9C;;;;;;;;;;OAUG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAKtD;;;;;;;;;;OAUG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;IAKvD;;;;;;;;;;OAUG;IACH,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS;IAKlC;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS;IAKxE;;;;;;OAMG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS;CAKjD;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,SAErD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,GAAI,UAAU,SAAS,eAAe,EAAE,aAAa,SAAS,iBAAiB,CAAC,UAAU,CAAC,EAC/G,YAAY,UAAU,EACtB,UAAU,aAAa,cACc,CAAC;AAExC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,cAAwD,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map of HTTP status codes to their default status messages.
|
|
3
|
+
* Includes both client error (4xx) and server error (5xx) status codes.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* HttpStatusMap[404] // 'Not Found'
|
|
8
|
+
* HttpStatusMap[500] // 'Internal Server Error'
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare const HttpStatusMap: {
|
|
12
|
+
readonly 400: "Bad Request";
|
|
13
|
+
readonly 401: "Unauthorized";
|
|
14
|
+
readonly 402: "Payment Required";
|
|
15
|
+
readonly 403: "Forbidden";
|
|
16
|
+
readonly 404: "Not Found";
|
|
17
|
+
readonly 405: "Method Not Allowed";
|
|
18
|
+
readonly 406: "Not Acceptable";
|
|
19
|
+
readonly 407: "Proxy Authentication Required";
|
|
20
|
+
readonly 408: "Request Timeout";
|
|
21
|
+
readonly 409: "Conflict";
|
|
22
|
+
readonly 410: "Gone";
|
|
23
|
+
readonly 411: "Length Required";
|
|
24
|
+
readonly 412: "Precondition Failed";
|
|
25
|
+
readonly 413: "Payload Too Large";
|
|
26
|
+
readonly 414: "Request-URI Too Long";
|
|
27
|
+
readonly 415: "Unsupported Media Type";
|
|
28
|
+
readonly 416: "Requested Range Not Satisfiable";
|
|
29
|
+
readonly 417: "Expectation Failed";
|
|
30
|
+
readonly 418: "I'm a teapot";
|
|
31
|
+
readonly 421: "Misdirected Request";
|
|
32
|
+
readonly 422: "Unprocessable Entity";
|
|
33
|
+
readonly 423: "Locked";
|
|
34
|
+
readonly 424: "Failed Dependency";
|
|
35
|
+
readonly 426: "Upgrade Required";
|
|
36
|
+
readonly 428: "Precondition Required";
|
|
37
|
+
readonly 429: "Too Many Requests";
|
|
38
|
+
readonly 431: "Request Header Fields Too Large";
|
|
39
|
+
readonly 444: "Connection Closed Without Response";
|
|
40
|
+
readonly 451: "Unavailable For Legal Reasons";
|
|
41
|
+
readonly 499: "Client Closed Request";
|
|
42
|
+
readonly 500: "Internal Server Error";
|
|
43
|
+
readonly 501: "Not Implemented";
|
|
44
|
+
readonly 502: "Bad Gateway";
|
|
45
|
+
readonly 503: "Service Unavailable";
|
|
46
|
+
readonly 504: "Gateway Timeout";
|
|
47
|
+
readonly 505: "HTTP Version Not Supported";
|
|
48
|
+
readonly 506: "Variant Also Negotiates";
|
|
49
|
+
readonly 507: "Insufficient Storage";
|
|
50
|
+
readonly 508: "Loop Detected";
|
|
51
|
+
readonly 510: "Not Extended";
|
|
52
|
+
readonly 511: "Network Authentication Required";
|
|
53
|
+
readonly 599: "Network Connect Timeout Error";
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=http.status.map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.status.map.d.ts","sourceRoot":"","sources":["../../src/http/http.status.map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2ChB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from './http/http.error.js';
|
|
2
|
+
export { OnError } from './on.error.decorator.js';
|
|
3
|
+
export { PostgresErrorHandler } from './postgres/postgres.error.handler.js';
|
|
4
|
+
export { OnPostgresError } from './postgres/postgres.error.decorator.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/http/http.status.map.ts
|
|
5
|
+
var HttpStatusMap = {
|
|
6
|
+
400: "Bad Request",
|
|
7
|
+
401: "Unauthorized",
|
|
8
|
+
402: "Payment Required",
|
|
9
|
+
403: "Forbidden",
|
|
10
|
+
404: "Not Found",
|
|
11
|
+
405: "Method Not Allowed",
|
|
12
|
+
406: "Not Acceptable",
|
|
13
|
+
407: "Proxy Authentication Required",
|
|
14
|
+
408: "Request Timeout",
|
|
15
|
+
409: "Conflict",
|
|
16
|
+
410: "Gone",
|
|
17
|
+
411: "Length Required",
|
|
18
|
+
412: "Precondition Failed",
|
|
19
|
+
413: "Payload Too Large",
|
|
20
|
+
414: "Request-URI Too Long",
|
|
21
|
+
415: "Unsupported Media Type",
|
|
22
|
+
416: "Requested Range Not Satisfiable",
|
|
23
|
+
417: "Expectation Failed",
|
|
24
|
+
418: "I'm a teapot",
|
|
25
|
+
421: "Misdirected Request",
|
|
26
|
+
422: "Unprocessable Entity",
|
|
27
|
+
423: "Locked",
|
|
28
|
+
424: "Failed Dependency",
|
|
29
|
+
426: "Upgrade Required",
|
|
30
|
+
428: "Precondition Required",
|
|
31
|
+
429: "Too Many Requests",
|
|
32
|
+
431: "Request Header Fields Too Large",
|
|
33
|
+
444: "Connection Closed Without Response",
|
|
34
|
+
451: "Unavailable For Legal Reasons",
|
|
35
|
+
499: "Client Closed Request",
|
|
36
|
+
500: "Internal Server Error",
|
|
37
|
+
501: "Not Implemented",
|
|
38
|
+
502: "Bad Gateway",
|
|
39
|
+
503: "Service Unavailable",
|
|
40
|
+
504: "Gateway Timeout",
|
|
41
|
+
505: "HTTP Version Not Supported",
|
|
42
|
+
506: "Variant Also Negotiates",
|
|
43
|
+
507: "Insufficient Storage",
|
|
44
|
+
508: "Loop Detected",
|
|
45
|
+
510: "Not Extended",
|
|
46
|
+
511: "Network Authentication Required",
|
|
47
|
+
599: "Network Connect Timeout Error"
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/http/http.error.ts
|
|
51
|
+
var HttpError = class _HttpError extends Error {
|
|
52
|
+
static {
|
|
53
|
+
__name(this, "HttpError");
|
|
54
|
+
}
|
|
55
|
+
statusCode;
|
|
56
|
+
/**
|
|
57
|
+
* Optional validation or error details to include in the response.
|
|
58
|
+
* Typically used for 400-level errors to provide field-specific error information.
|
|
59
|
+
*/
|
|
60
|
+
details;
|
|
61
|
+
/**
|
|
62
|
+
* Optional HTTP headers to include in the error response.
|
|
63
|
+
* Useful for authentication errors (e.g., WWW-Authenticate header).
|
|
64
|
+
*/
|
|
65
|
+
headers;
|
|
66
|
+
/**
|
|
67
|
+
* Optional underlying error that caused this HTTP error.
|
|
68
|
+
* Follows the Error.cause pattern for error chaining.
|
|
69
|
+
*/
|
|
70
|
+
cause;
|
|
71
|
+
/**
|
|
72
|
+
* Optional internal details that should not be exposed to clients.
|
|
73
|
+
* Useful for debugging and logging purposes.
|
|
74
|
+
*/
|
|
75
|
+
internalDetails;
|
|
76
|
+
/**
|
|
77
|
+
* Creates a new HttpError instance.
|
|
78
|
+
*
|
|
79
|
+
* @param statusCode - The HTTP status code (must be a key from HttpStatusMap).
|
|
80
|
+
* @param message - Optional custom error message. If not provided, uses the default message from HttpStatusMap.
|
|
81
|
+
*/
|
|
82
|
+
constructor(statusCode, message) {
|
|
83
|
+
super(message ?? HttpStatusMap[statusCode]), this.statusCode = statusCode;
|
|
84
|
+
this[Symbol.toStringTag] = "Object";
|
|
85
|
+
Object.setPrototypeOf(this, _HttpError.prototype);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Adds error details to the HTTP error and returns the instance for method chaining.
|
|
89
|
+
*
|
|
90
|
+
* @param errors - Object containing field-specific error information.
|
|
91
|
+
* @returns The HttpError instance for method chaining.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* error.withErrors({ email: 'Invalid email format', password: 'Too short' });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
withErrors(errors) {
|
|
99
|
+
this.details = errors;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Adds HTTP headers to the error response and returns the instance for method chaining.
|
|
104
|
+
*
|
|
105
|
+
* @param headers - Object containing HTTP header key-value pairs.
|
|
106
|
+
* @returns The HttpError instance for method chaining.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* error.withHeaders({ 'WWW-Authenticate': 'Bearer realm="api"' });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
withHeaders(headers) {
|
|
114
|
+
this.headers = headers;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Sets the underlying cause of this error and returns the instance for method chaining.
|
|
119
|
+
*
|
|
120
|
+
* @param cause - The original error that caused this HTTP error.
|
|
121
|
+
* @returns The HttpError instance for method chaining.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* error.withCause(new Error('Database connection failed'));
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
withCause(cause) {
|
|
129
|
+
this.cause = cause;
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Adds internal details that should not be exposed to clients and returns the instance for method chaining.
|
|
134
|
+
*
|
|
135
|
+
* @param internalDetails - Object containing internal debugging information.
|
|
136
|
+
* @returns The HttpError instance for method chaining.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* error.withInternalDetails({ userId: 123, requestId: 'abc-123' });
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
withInternalDetails(internalDetails) {
|
|
144
|
+
this.internalDetails = internalDetails;
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Adds a HTTP header to the error response and returns the instance for method chaining.
|
|
149
|
+
*
|
|
150
|
+
* @param key - The HTTP header key.
|
|
151
|
+
* @param value - The HTTP header value.
|
|
152
|
+
* @returns The HttpError instance for method chaining.
|
|
153
|
+
*/
|
|
154
|
+
addHeader(key, value) {
|
|
155
|
+
this.headers ??= {};
|
|
156
|
+
this.headers[key] = value;
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var IsHttpError = /* @__PURE__ */ __name((error) => {
|
|
161
|
+
return error instanceof HttpError;
|
|
162
|
+
}, "IsHttpError");
|
|
163
|
+
var httpError = /* @__PURE__ */ __name((statusCode, message) => new HttpError(statusCode, message), "httpError");
|
|
164
|
+
var unauthorizedError = /* @__PURE__ */ __name((error) => httpError(401).addHeader("WWW-Authenticate", error), "unauthorizedError");
|
|
165
|
+
|
|
166
|
+
// src/on.error.decorator.ts
|
|
167
|
+
var generateDescriptor = /* @__PURE__ */ __name((descriptor, handler) => {
|
|
168
|
+
if (!descriptor.value) {
|
|
169
|
+
const getter = descriptor.get;
|
|
170
|
+
const setter = descriptor.set;
|
|
171
|
+
if (getter) {
|
|
172
|
+
descriptor.get = () => {
|
|
173
|
+
try {
|
|
174
|
+
return getter.apply(void 0);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
handler(error);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (setter) {
|
|
181
|
+
descriptor.set = (v) => {
|
|
182
|
+
try {
|
|
183
|
+
return setter.apply(void 0, [
|
|
184
|
+
v
|
|
185
|
+
]);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
handler(error);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return descriptor;
|
|
192
|
+
}
|
|
193
|
+
const method = descriptor.value;
|
|
194
|
+
descriptor.value = function(...args) {
|
|
195
|
+
try {
|
|
196
|
+
const result = method.apply(this, args);
|
|
197
|
+
if (result && result instanceof Promise) {
|
|
198
|
+
return result.catch((error) => {
|
|
199
|
+
handler(error);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
handler(error);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
return descriptor;
|
|
208
|
+
}, "generateDescriptor");
|
|
209
|
+
var OnError = /* @__PURE__ */ __name((handler) => {
|
|
210
|
+
return (target) => {
|
|
211
|
+
for (const propertyName of Reflect.ownKeys(target.prototype).filter((prop) => prop !== "constructor")) {
|
|
212
|
+
const desc = Object.getOwnPropertyDescriptor(target.prototype, propertyName);
|
|
213
|
+
if (desc) {
|
|
214
|
+
const isMethod = desc.value instanceof Function || desc.get instanceof Function || desc.set instanceof Function;
|
|
215
|
+
if (isMethod) {
|
|
216
|
+
Object.defineProperty(target.prototype, propertyName, generateDescriptor(desc, handler));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return target;
|
|
221
|
+
};
|
|
222
|
+
}, "OnError");
|
|
223
|
+
|
|
224
|
+
// src/postgres/postgres.error.handler.ts
|
|
225
|
+
var isPostgresError = /* @__PURE__ */ __name((error) => {
|
|
226
|
+
return "code" in error;
|
|
227
|
+
}, "isPostgresError");
|
|
228
|
+
var PostgresErrorHandler = /* @__PURE__ */ __name((error) => {
|
|
229
|
+
if (isPostgresError(error)) {
|
|
230
|
+
switch (error.code) {
|
|
231
|
+
case "23505":
|
|
232
|
+
throw httpError(409).withCause(error);
|
|
233
|
+
case "23503":
|
|
234
|
+
throw httpError(404).withCause(error);
|
|
235
|
+
case "23502":
|
|
236
|
+
case "22P02":
|
|
237
|
+
case "22003":
|
|
238
|
+
case "23514":
|
|
239
|
+
throw httpError(400).withCause(error);
|
|
240
|
+
case "40000":
|
|
241
|
+
case "40001":
|
|
242
|
+
case "40002":
|
|
243
|
+
throw httpError(500).withCause(error).withInternalDetails({
|
|
244
|
+
msg: "Transaction rollback"
|
|
245
|
+
});
|
|
246
|
+
case "40P01":
|
|
247
|
+
throw httpError(500).withCause(error).withInternalDetails({
|
|
248
|
+
msg: "Deadlock"
|
|
249
|
+
});
|
|
250
|
+
default:
|
|
251
|
+
throw httpError(500).withCause(error);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}, "PostgresErrorHandler");
|
|
257
|
+
|
|
258
|
+
// src/postgres/postgres.error.decorator.ts
|
|
259
|
+
var OnPostgresError = /* @__PURE__ */ __name(() => OnError(PostgresErrorHandler), "OnPostgresError");
|
|
260
|
+
export {
|
|
261
|
+
HttpError,
|
|
262
|
+
IsHttpError,
|
|
263
|
+
OnError,
|
|
264
|
+
OnPostgresError,
|
|
265
|
+
PostgresErrorHandler,
|
|
266
|
+
httpError,
|
|
267
|
+
unauthorizedError
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http/http.status.map.ts","../src/http/http.error.ts","../src/on.error.decorator.ts","../src/postgres/postgres.error.handler.ts","../src/postgres/postgres.error.decorator.ts"],"sourcesContent":["/**\n * Map of HTTP status codes to their default status messages.\n * Includes both client error (4xx) and server error (5xx) status codes.\n *\n * @example\n * ```ts\n * HttpStatusMap[404] // 'Not Found'\n * HttpStatusMap[500] // 'Internal Server Error'\n * ```\n */\nexport const HttpStatusMap = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 402: 'Payment Required',\n 403: 'Forbidden',\n 404: 'Not Found',\n 405: 'Method Not Allowed',\n 406: 'Not Acceptable',\n 407: 'Proxy Authentication Required',\n 408: 'Request Timeout',\n 409: 'Conflict',\n 410: 'Gone',\n 411: 'Length Required',\n 412: 'Precondition Failed',\n 413: 'Payload Too Large',\n 414: 'Request-URI Too Long',\n 415: 'Unsupported Media Type',\n 416: 'Requested Range Not Satisfiable',\n 417: 'Expectation Failed',\n 418: \"I'm a teapot\",\n 421: 'Misdirected Request',\n 422: 'Unprocessable Entity',\n 423: 'Locked',\n 424: 'Failed Dependency',\n 426: 'Upgrade Required',\n 428: 'Precondition Required',\n 429: 'Too Many Requests',\n 431: 'Request Header Fields Too Large',\n 444: 'Connection Closed Without Response',\n 451: 'Unavailable For Legal Reasons',\n 499: 'Client Closed Request',\n 500: 'Internal Server Error',\n 501: 'Not Implemented',\n 502: 'Bad Gateway',\n 503: 'Service Unavailable',\n 504: 'Gateway Timeout',\n 505: 'HTTP Version Not Supported',\n 506: 'Variant Also Negotiates',\n 507: 'Insufficient Storage',\n 508: 'Loop Detected',\n 510: 'Not Extended',\n 511: 'Network Authentication Required',\n 599: 'Network Connect Timeout Error',\n} as const;\n","import { HttpStatusMap } from './http.status.map.js';\n\n/**\n * Type representing valid HTTP status codes from the HttpStatusMap.\n */\nexport type HttpStatusCodes = keyof typeof HttpStatusMap;\n\n/**\n * Type representing the status message for a given HTTP status code.\n * @template T - The HTTP status code type.\n */\nexport type HttpStatusMessage<T extends HttpStatusCodes> = (typeof HttpStatusMap)[T];\n\n/**\n * Custom error class for HTTP errors with support for status codes, details, headers, and error chaining.\n * Extends the native Error class and provides a fluent API for building error responses.\n *\n * @example\n * ```ts\n * throw new HttpError(404).withErrors({ field: 'not found' });\n * ```\n *\n * @example\n * ```ts\n * throw new HttpError(401)\n * .withHeaders({ 'WWW-Authenticate': 'Bearer' })\n * .withCause(originalError);\n * ```\n */\nexport class HttpError extends Error {\n /**\n * Optional validation or error details to include in the response.\n * Typically used for 400-level errors to provide field-specific error information.\n */\n details?: Record<string, unknown>;\n\n /**\n * Optional HTTP headers to include in the error response.\n * Useful for authentication errors (e.g., WWW-Authenticate header).\n */\n headers?: Record<string, string>;\n\n /**\n * Optional underlying error that caused this HTTP error.\n * Follows the Error.cause pattern for error chaining.\n */\n cause?: Error;\n\n /**\n * Optional internal details that should not be exposed to clients.\n * Useful for debugging and logging purposes.\n */\n internalDetails?: Record<string, unknown>;\n\n /**\n * Creates a new HttpError instance.\n *\n * @param statusCode - The HTTP status code (must be a key from HttpStatusMap).\n * @param message - Optional custom error message. If not provided, uses the default message from HttpStatusMap.\n */\n constructor(\n readonly statusCode: HttpStatusCodes,\n message?: HttpStatusMessage<HttpStatusCodes>,\n ) {\n super(message ?? HttpStatusMap[statusCode]);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any)[Symbol.toStringTag] = 'Object';\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, HttpError.prototype);\n }\n\n /**\n * Adds error details to the HTTP error and returns the instance for method chaining.\n *\n * @param errors - Object containing field-specific error information.\n * @returns The HttpError instance for method chaining.\n *\n * @example\n * ```ts\n * error.withErrors({ email: 'Invalid email format', password: 'Too short' });\n * ```\n */\n withErrors(errors: Record<string, unknown>): HttpError {\n this.details = errors;\n return this;\n }\n\n /**\n * Adds HTTP headers to the error response and returns the instance for method chaining.\n *\n * @param headers - Object containing HTTP header key-value pairs.\n * @returns The HttpError instance for method chaining.\n *\n * @example\n * ```ts\n * error.withHeaders({ 'WWW-Authenticate': 'Bearer realm=\"api\"' });\n * ```\n */\n withHeaders(headers: Record<string, string>): HttpError {\n this.headers = headers;\n return this;\n }\n\n /**\n * Sets the underlying cause of this error and returns the instance for method chaining.\n *\n * @param cause - The original error that caused this HTTP error.\n * @returns The HttpError instance for method chaining.\n *\n * @example\n * ```ts\n * error.withCause(new Error('Database connection failed'));\n * ```\n */\n withCause(cause: Error): HttpError {\n this.cause = cause;\n return this;\n }\n\n /**\n * Adds internal details that should not be exposed to clients and returns the instance for method chaining.\n *\n * @param internalDetails - Object containing internal debugging information.\n * @returns The HttpError instance for method chaining.\n *\n * @example\n * ```ts\n * error.withInternalDetails({ userId: 123, requestId: 'abc-123' });\n * ```\n */\n withInternalDetails(internalDetails: Record<string, unknown>): HttpError {\n this.internalDetails = internalDetails;\n return this;\n }\n\n /**\n * Adds a HTTP header to the error response and returns the instance for method chaining.\n *\n * @param key - The HTTP header key.\n * @param value - The HTTP header value.\n * @returns The HttpError instance for method chaining.\n */\n addHeader(key: string, value: string): HttpError {\n this.headers ??= {};\n this.headers[key] = value;\n return this;\n }\n}\n\n/**\n * Type guard to check if an unknown value is an HttpError instance.\n *\n * @param error - The value to check.\n * @returns True if the value is an HttpError instance, false otherwise.\n *\n * @example\n * ```ts\n * if (IsHttpError(error)) {\n * console.log(error.statusCode);\n * }\n * ```\n */\nexport const IsHttpError = (error: unknown): error is HttpError => {\n return error instanceof HttpError;\n};\n\n/**\n * Factory function to create an HttpError instance with a specific status code.\n *\n * @template StatusCode - The HTTP status code type.\n * @template StatusMessage - The status message type for the given status code.\n * @param statusCode - The HTTP status code.\n * @param message - Optional custom error message.\n * @returns A new HttpError instance.\n *\n * @example\n * ```ts\n * throw httpError(404);\n * ```\n */\nexport const httpError = <StatusCode extends HttpStatusCodes, StatusMessage extends HttpStatusMessage<StatusCode>>(\n statusCode: StatusCode,\n message?: StatusMessage,\n) => new HttpError(statusCode, message);\n\n/**\n * Factory function to create an unauthorized HttpError instance.\n *\n * @param error - The error message of the WWW-Authenticate header.\n * @returns A new HttpError instance with the WWW-Authenticate header set to the given error message.\n *\n * @example\n * ```ts\n * throw unauthorizedError('Bearer realm=\"api\"');\n * ```\n */\nexport const unauthorizedError = (error: string) => httpError(401).addHeader('WWW-Authenticate', error);\n","/* eslint-disable @typescript-eslint/no-unsafe-function-type */\n\n/**\n * Type definition for an error handler function.\n * Called when an error is caught in a decorated method, getter, or setter.\n */\ntype ErrorHandler = (error: Error) => void;\n\n/**\n * Generates a property descriptor that wraps the original method/getter/setter with error handling.\n * For methods, handles both synchronous and asynchronous errors.\n * For getters/setters, wraps the accessor with try-catch.\n *\n * @param descriptor - The original property descriptor to wrap.\n * @param handler - The error handler function to call when an error occurs.\n * @returns A new property descriptor with error handling.\n */\nconst generateDescriptor = (descriptor: PropertyDescriptor, handler: ErrorHandler): PropertyDescriptor => {\n if (!descriptor.value) {\n const getter = descriptor.get;\n const setter = descriptor.set;\n\n if (getter) {\n descriptor.get = () => {\n try {\n return getter.apply(this);\n } catch (error) {\n handler(error as Error);\n }\n };\n }\n\n if (setter) {\n descriptor.set = (v: unknown) => {\n try {\n return setter.apply(this, [v]);\n } catch (error) {\n handler(error as Error);\n }\n };\n }\n\n return descriptor;\n }\n\n const method = descriptor.value;\n\n descriptor.value = function (...args: unknown[]) {\n try {\n const result = method.apply(this, args);\n\n if (result && result instanceof Promise) {\n return result.catch((error: unknown) => {\n handler(error as Error);\n });\n }\n\n return result;\n } catch (error) {\n handler(error as Error);\n }\n };\n\n return descriptor;\n};\n\n/**\n * Class decorator that automatically wraps all methods, getters, and setters in a class\n * with error handling. When any decorated method throws an error (synchronously or asynchronously),\n * the provided error handler is called.\n *\n * The decorator:\n * - Wraps all methods (including async methods) with try-catch\n * - Wraps getters and setters with try-catch\n * - Does not wrap the constructor\n * - Does not wrap non-method properties\n *\n * @param handler - The error handler function to call when an error is caught.\n * @returns A class decorator function.\n *\n * @example\n * ```ts\n * @OnError((error) => {\n * console.error('Error caught:', error);\n * throw new HttpError(500).withCause(error);\n * })\n * class MyService {\n * async doSomething() {\n * throw new Error('Something went wrong');\n * }\n * }\n * ```\n *\n * @example\n * ```ts\n * @OnError(PostgresErrorHandler)\n * class UserRepository {\n * async findById(id: number) {\n * // If this throws a PostgresError, it will be handled\n * return await db.select().from('users').where('id', id);\n * }\n * }\n * ```\n */\nexport const OnError = (handler: ErrorHandler): ClassDecorator => {\n return <TFunction extends Function>(target: TFunction): TFunction => {\n for (const propertyName of Reflect.ownKeys(target.prototype).filter(prop => prop !== 'constructor')) {\n const desc = Object.getOwnPropertyDescriptor(target.prototype, propertyName);\n if (desc) {\n const isMethod = desc.value instanceof Function || desc.get instanceof Function || desc.set instanceof Function;\n if (isMethod) {\n Object.defineProperty(target.prototype, propertyName, generateDescriptor(desc, handler));\n }\n }\n }\n return target;\n };\n};\n","import { httpError } from '../http/http.error.js';\n\n/**\n * Interface representing a PostgreSQL error with a specific error code.\n * PostgreSQL errors include a `code` property that identifies the type of error.\n */\nexport interface PostgresError extends Error {\n /**\n * PostgreSQL error code (e.g., '23505' for unique constraint violation).\n */\n code: string;\n}\n\n/**\n * Type guard to check if an error is a PostgreSQL error with a code property.\n *\n * @param error - The error to check.\n * @returns True if the error has a `code` property, false otherwise.\n *\n * @example\n * ```ts\n * if (isPostgresError(error)) {\n * console.log(error.code); // '23505'\n * }\n * ```\n */\nexport const isPostgresError = (error: Error): error is PostgresError => {\n return 'code' in error;\n};\n\n/**\n * Handles PostgreSQL errors by converting them to appropriate HTTP errors.\n * Maps PostgreSQL error codes to HTTP status codes:\n * - 23505 (unique constraint violation) → 409 Conflict\n * - 23503 (foreign key violation) → 404 Not Found\n * - 23502, 22P02, 22003, 23514 (validation errors) → 400 Bad Request\n * - 40000, 40001, 40002 (transaction rollback) → 500 Internal Server Error\n * - 40P01 (deadlock) → 500 Internal Server Error\n * - Unknown codes → 500 Internal Server Error\n *\n * If the error is not a PostgreSQL error, it is re-thrown as-is.\n *\n * @param error - The error to handle.\n * @throws {HttpError} An HttpError with the appropriate status code and cause.\n * @throws {Error} The original error if it's not a PostgreSQL error.\n *\n * @example\n * ```ts\n * try {\n * await db.insert(...);\n * } catch (error) {\n * PostgresErrorHandler(error); // Converts to HttpError\n * }\n * ```\n */\nexport const PostgresErrorHandler = (error: Error) => {\n if (isPostgresError(error)) {\n switch (error.code) {\n case '23505':\n throw httpError(409).withCause(error);\n case '23503':\n throw httpError(404).withCause(error);\n case '23502':\n case '22P02':\n case '22003':\n case '23514':\n throw httpError(400).withCause(error);\n case '40000':\n case '40001':\n case '40002':\n throw httpError(500).withCause(error).withInternalDetails({ msg: 'Transaction rollback' });\n case '40P01':\n throw httpError(500).withCause(error).withInternalDetails({ msg: 'Deadlock' });\n default:\n throw httpError(500).withCause(error);\n }\n } else {\n throw error;\n }\n};\n","import { PostgresErrorHandler } from './postgres.error.handler.js';\nimport { OnError } from '../on.error.decorator.js';\n\nexport const OnPostgresError = () => OnError(PostgresErrorHandler);\n"],"mappings":"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","names":["HttpStatusMap","HttpError","Error","details","headers","cause","internalDetails","statusCode","message","HttpStatusMap","Symbol","toStringTag","Object","setPrototypeOf","prototype","withErrors","errors","withHeaders","withCause","withInternalDetails","addHeader","key","value","IsHttpError","error","httpError","unauthorizedError","generateDescriptor","descriptor","handler","value","getter","get","setter","set","apply","error","v","method","args","result","Promise","catch","OnError","target","propertyName","Reflect","ownKeys","prototype","filter","prop","desc","Object","getOwnPropertyDescriptor","isMethod","Function","defineProperty","isPostgresError","error","PostgresErrorHandler","code","httpError","withCause","withInternalDetails","msg","OnPostgresError","OnError","PostgresErrorHandler"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definition for an error handler function.
|
|
3
|
+
* Called when an error is caught in a decorated method, getter, or setter.
|
|
4
|
+
*/
|
|
5
|
+
type ErrorHandler = (error: Error) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Class decorator that automatically wraps all methods, getters, and setters in a class
|
|
8
|
+
* with error handling. When any decorated method throws an error (synchronously or asynchronously),
|
|
9
|
+
* the provided error handler is called.
|
|
10
|
+
*
|
|
11
|
+
* The decorator:
|
|
12
|
+
* - Wraps all methods (including async methods) with try-catch
|
|
13
|
+
* - Wraps getters and setters with try-catch
|
|
14
|
+
* - Does not wrap the constructor
|
|
15
|
+
* - Does not wrap non-method properties
|
|
16
|
+
*
|
|
17
|
+
* @param handler - The error handler function to call when an error is caught.
|
|
18
|
+
* @returns A class decorator function.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* @OnError((error) => {
|
|
23
|
+
* console.error('Error caught:', error);
|
|
24
|
+
* throw new HttpError(500).withCause(error);
|
|
25
|
+
* })
|
|
26
|
+
* class MyService {
|
|
27
|
+
* async doSomething() {
|
|
28
|
+
* throw new Error('Something went wrong');
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* @OnError(PostgresErrorHandler)
|
|
36
|
+
* class UserRepository {
|
|
37
|
+
* async findById(id: number) {
|
|
38
|
+
* // If this throws a PostgresError, it will be handled
|
|
39
|
+
* return await db.select().from('users').where('id', id);
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare const OnError: (handler: ErrorHandler) => ClassDecorator;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=on.error.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on.error.decorator.d.ts","sourceRoot":"","sources":["../src/on.error.decorator.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AA4D3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,OAAO,GAAI,SAAS,YAAY,KAAG,cAa/C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.error.decorator.d.ts","sourceRoot":"","sources":["../../src/postgres/postgres.error.decorator.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,sBAAsC,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface representing a PostgreSQL error with a specific error code.
|
|
3
|
+
* PostgreSQL errors include a `code` property that identifies the type of error.
|
|
4
|
+
*/
|
|
5
|
+
export interface PostgresError extends Error {
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL error code (e.g., '23505' for unique constraint violation).
|
|
8
|
+
*/
|
|
9
|
+
code: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Type guard to check if an error is a PostgreSQL error with a code property.
|
|
13
|
+
*
|
|
14
|
+
* @param error - The error to check.
|
|
15
|
+
* @returns True if the error has a `code` property, false otherwise.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* if (isPostgresError(error)) {
|
|
20
|
+
* console.log(error.code); // '23505'
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const isPostgresError: (error: Error) => error is PostgresError;
|
|
25
|
+
/**
|
|
26
|
+
* Handles PostgreSQL errors by converting them to appropriate HTTP errors.
|
|
27
|
+
* Maps PostgreSQL error codes to HTTP status codes:
|
|
28
|
+
* - 23505 (unique constraint violation) → 409 Conflict
|
|
29
|
+
* - 23503 (foreign key violation) → 404 Not Found
|
|
30
|
+
* - 23502, 22P02, 22003, 23514 (validation errors) → 400 Bad Request
|
|
31
|
+
* - 40000, 40001, 40002 (transaction rollback) → 500 Internal Server Error
|
|
32
|
+
* - 40P01 (deadlock) → 500 Internal Server Error
|
|
33
|
+
* - Unknown codes → 500 Internal Server Error
|
|
34
|
+
*
|
|
35
|
+
* If the error is not a PostgreSQL error, it is re-thrown as-is.
|
|
36
|
+
*
|
|
37
|
+
* @param error - The error to handle.
|
|
38
|
+
* @throws {HttpError} An HttpError with the appropriate status code and cause.
|
|
39
|
+
* @throws {Error} The original error if it's not a PostgreSQL error.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* try {
|
|
44
|
+
* await db.insert(...);
|
|
45
|
+
* } catch (error) {
|
|
46
|
+
* PostgresErrorHandler(error); // Converts to HttpError
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare const PostgresErrorHandler: (error: Error) => never;
|
|
51
|
+
//# sourceMappingURL=postgres.error.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.error.handler.d.ts","sourceRoot":"","sources":["../../src/postgres/postgres.error.handler.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,aAAc,SAAQ,KAAK;IAC1C;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,KAAK,KAAG,KAAK,IAAI,aAEvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,KAAK,UAwBhD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maroonedsoftware/errors",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A comprehensive error handling library for HTTP APIs with built-in support for PostgreSQL error mapping and class-level error decorators.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Marooned Software",
|
|
7
|
+
"url": "https://github.com/MaroonedSoftware/serverkit"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/MaroonedSoftware/serverkit/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/MaroonedSoftware/serverkit/packages/errors#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"backend",
|
|
15
|
+
"errors",
|
|
16
|
+
"http",
|
|
17
|
+
"postgresql",
|
|
18
|
+
"serverkit",
|
|
19
|
+
"typescript"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/MaroonedSoftware/serverkit.git"
|
|
24
|
+
},
|
|
25
|
+
"private": false,
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist/**"
|
|
33
|
+
],
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@repo/config-eslint": "0.0.0",
|
|
36
|
+
"@repo/config-typescript": "0.0.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly --declaration",
|
|
40
|
+
"build:ci": "eslint --max-warnings=0 && pnpm run build",
|
|
41
|
+
"lint": "eslint --fix",
|
|
42
|
+
"format": "prettier --write .",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:ci": "vitest run --coverage"
|
|
45
|
+
}
|
|
46
|
+
}
|