@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 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"}
@@ -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,2 @@
1
+ export declare const OnPostgresError: () => ClassDecorator;
2
+ //# sourceMappingURL=postgres.error.decorator.d.ts.map
@@ -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
+ }