@fastify-utils/error 0.0.0-stage → 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/.turbo/turbo-build.log +26 -0
- package/.turbo/turbo-test.log +109 -0
- package/LICENSE +22 -0
- package/README.md +439 -2
- package/coverage/base.css +362 -0
- package/coverage/block-navigation.js +82 -0
- package/coverage/coverage-final.json +60 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +281 -0
- package/coverage/prettify.css +101 -0
- package/coverage/prettify.js +937 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +205 -0
- package/out/index.cjs +807 -0
- package/out/index.d.cts +360 -0
- package/out/index.d.mts +360 -0
- package/out/index.mjs +803 -0
- package/package.json +58 -3
- package/tsdown.config.mts +7 -0
- package/vitest.config.mts +17 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[WARN] Failed to replace env in config: ${NODE_AUTH_TOKEN} in .npmrc key "_authToken"
|
|
2
|
+
$ tsdown --config=tsdown.config.mts
|
|
3
|
+
ℹ tsdown v0.23.1 powered by rolldown v1.2.14
|
|
4
|
+
ℹ config file: /home/runner/work/fastify-utils/fastify-utils/packages/error/tsdown.config.mts
|
|
5
|
+
ℹ entry: ./src/index.ts
|
|
6
|
+
ℹ target: node24.0.0
|
|
7
|
+
ℹ tsconfig: tsconfig.json
|
|
8
|
+
ℹ Build start
|
|
9
|
+
|
|
10
|
+
WARN We recommend using the ESM format instead of CommonJS.
|
|
11
|
+
The ESM format is compatible with modern platforms and runtimes, and most new libraries are now distributed only in ESM format.
|
|
12
|
+
Learn more at https://nodejs.org/en/learn/modules/publishing-a-package#how-did-we-get-here
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
WARN TypeScript 7.0 does not yet have a stable API and is experimental. Some options will be unavailable.
|
|
16
|
+
|
|
17
|
+
ℹ Emit types with typescript@7.0.2
|
|
18
|
+
ℹ [CJS] out/index.cjs 24.17 kB │ gzip: 3.73 kB
|
|
19
|
+
ℹ [CJS] 1 files, total: 24.17 kB
|
|
20
|
+
ℹ [CJS] out/index.d.cts 15.01 kB │ gzip: 1.95 kB
|
|
21
|
+
ℹ [CJS] 1 files, total: 15.01 kB
|
|
22
|
+
✔ Build complete in 663ms
|
|
23
|
+
ℹ [ESM] out/index.mjs 24.02 kB │ gzip: 3.67 kB
|
|
24
|
+
ℹ [ESM] out/index.d.mts 15.01 kB │ gzip: 1.95 kB
|
|
25
|
+
ℹ [ESM] 2 files, total: 39.03 kB
|
|
26
|
+
✔ Build complete in 665ms
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
[WARN] Failed to replace env in config: ${NODE_AUTH_TOKEN} in .npmrc key "_authToken"
|
|
2
|
+
$ tsc --noEmit && vitest run
|
|
3
|
+
|
|
4
|
+
[1m[30m[46m RUN [49m[39m[22m [36mv5.0.5 [39m[90m/home/runner/work/fastify-utils/fastify-utils/packages/error[39m
|
|
5
|
+
[2mCoverage enabled with [22m[33mv8[39m
|
|
6
|
+
|
|
7
|
+
[32m✓[39m src/core/http-error.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
8
|
+
[32m✓[39m src/helpers/create-error-by-status/index.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m 15[2mms[22m[39m
|
|
9
|
+
[32m✓[39m src/errors/client/index.test.ts [2m([22m[2m28 tests[22m[2m)[22m[32m 15[2mms[22m[39m
|
|
10
|
+
[32m✓[39m src/errors/server/factories.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
11
|
+
[32m✓[39m src/helpers/is-valid-rfc9457-json/index.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
12
|
+
[32m✓[39m src/errors/client/factories.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
13
|
+
[32m✓[39m src/errors/cause-propagation.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
14
|
+
[32m✓[39m src/errors/server/index.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
15
|
+
[32m✓[39m src/helpers/get-error-type/index.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
16
|
+
[32m✓[39m src/helpers/normalize-to-string/index.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 20[2mms[22m[39m
|
|
17
|
+
[32m✓[39m src/helpers/is-http-error/index.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
18
|
+
[32m✓[39m src/helpers/extract-cause/index.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
19
|
+
[32m✓[39m src/helpers/index.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
20
|
+
[32m✓[39m src/config/configure.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
21
|
+
[32m✓[39m src/index.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
22
|
+
[32m✓[39m src/core/index.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
23
|
+
[32m✓[39m src/config/index.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
24
|
+
|
|
25
|
+
[2m Test Files [22m [1m[32m17 passed[39m[22m[90m (17)[39m
|
|
26
|
+
[2m Tests [22m [1m[32m149 passed[39m[22m[90m (149)[39m
|
|
27
|
+
[2m Start at [22m 06:15:36
|
|
28
|
+
[2m Duration [22m 1.45s[2m (transform 38%, import 31%, worker 21%, tests 9%)[22m
|
|
29
|
+
|
|
30
|
+
[2m Isolate [22m [33m17 workers spawned[39m[2m · ~122ms startup each (spawn + environment, per file)[22m
|
|
31
|
+
[2m [22m [2mat least ~567ms faster with [22m[33misolate: false[39m[2m — reuses workers across files instead of one per file[22m
|
|
32
|
+
|
|
33
|
+
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
34
|
+
-------------------|---------|----------|---------|---------|-------------------
|
|
35
|
+
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
36
|
+
-------------------|---------|----------|---------|---------|-------------------
|
|
37
|
+
All files | 100 | 100 | 100 | 100 |
|
|
38
|
+
src | 100 | 100 | 100 | 100 |
|
|
39
|
+
error.ts | 100 | 100 | 100 | 100 |
|
|
40
|
+
errors.ts | 100 | 100 | 100 | 100 |
|
|
41
|
+
index.ts | 0 | 0 | 0 | 0 |
|
|
42
|
+
src/config | 100 | 100 | 100 | 100 |
|
|
43
|
+
configure.ts | 100 | 100 | 100 | 100 |
|
|
44
|
+
error-config.ts | 100 | 100 | 100 | 100 |
|
|
45
|
+
get-config.ts | 100 | 100 | 100 | 100 |
|
|
46
|
+
index.ts | 0 | 0 | 0 | 0 |
|
|
47
|
+
src/core | 100 | 100 | 100 | 100 |
|
|
48
|
+
http-error.ts | 100 | 100 | 100 | 100 |
|
|
49
|
+
index.ts | 0 | 0 | 0 | 0 |
|
|
50
|
+
src/errors/client | 100 | 100 | 100 | 100 |
|
|
51
|
+
...tion-error.ts | 100 | 100 | 100 | 100 |
|
|
52
|
+
...tion-error.ts | 100 | 100 | 100 | 100 |
|
|
53
|
+
...uest-error.ts | 100 | 100 | 100 | 100 |
|
|
54
|
+
...lict-error.ts | 100 | 100 | 100 | 100 |
|
|
55
|
+
...iled-error.ts | 100 | 100 | 100 | 100 |
|
|
56
|
+
factories.ts | 100 | 100 | 100 | 100 |
|
|
57
|
+
...ency-error.ts | 100 | 100 | 100 | 100 |
|
|
58
|
+
gone-error.ts | 100 | 100 | 100 | 100 |
|
|
59
|
+
index.ts | 0 | 0 | 0 | 0 |
|
|
60
|
+
...ired-error.ts | 100 | 100 | 100 | 100 |
|
|
61
|
+
locked-error.ts | 100 | 100 | 100 | 100 |
|
|
62
|
+
...owed-error.ts | 100 | 100 | 100 | 100 |
|
|
63
|
+
...uest-error.ts | 100 | 100 | 100 | 100 |
|
|
64
|
+
...able-error.ts | 100 | 100 | 100 | 100 |
|
|
65
|
+
...ound-error.ts | 100 | 100 | 100 | 100 |
|
|
66
|
+
...arge-error.ts | 100 | 100 | 100 | 100 |
|
|
67
|
+
...ired-error.ts | 100 | 100 | 100 | 100 |
|
|
68
|
+
...iled-error.ts | 100 | 100 | 100 | 100 |
|
|
69
|
+
...ired-error.ts | 100 | 100 | 100 | 100 |
|
|
70
|
+
...ired-error.ts | 100 | 100 | 100 | 100 |
|
|
71
|
+
...able-error.ts | 100 | 100 | 100 | 100 |
|
|
72
|
+
...imit-error.ts | 100 | 100 | 100 | 100 |
|
|
73
|
+
...arge-error.ts | 100 | 100 | 100 | 100 |
|
|
74
|
+
...eout-error.ts | 100 | 100 | 100 | 100 |
|
|
75
|
+
...arly-error.ts | 100 | 100 | 100 | 100 |
|
|
76
|
+
...sons-error.ts | 100 | 100 | 100 | 100 |
|
|
77
|
+
...type-error.ts | 100 | 100 | 100 | 100 |
|
|
78
|
+
...ired-error.ts | 100 | 100 | 100 | 100 |
|
|
79
|
+
...long-error.ts | 100 | 100 | 100 | 100 |
|
|
80
|
+
...tion-error.ts | 100 | 100 | 100 | 100 |
|
|
81
|
+
src/errors/server | 100 | 100 | 100 | 100 |
|
|
82
|
+
...eway-error.ts | 100 | 100 | 100 | 100 |
|
|
83
|
+
factories.ts | 100 | 100 | 100 | 100 |
|
|
84
|
+
...eout-error.ts | 100 | 100 | 100 | 100 |
|
|
85
|
+
...rted-error.ts | 100 | 100 | 100 | 100 |
|
|
86
|
+
index.ts | 0 | 0 | 0 | 0 |
|
|
87
|
+
...rage-error.ts | 100 | 100 | 100 | 100 |
|
|
88
|
+
...rver-error.ts | 100 | 100 | 100 | 100 |
|
|
89
|
+
...cted-error.ts | 100 | 100 | 100 | 100 |
|
|
90
|
+
...ired-error.ts | 100 | 100 | 100 | 100 |
|
|
91
|
+
...nded-error.ts | 100 | 100 | 100 | 100 |
|
|
92
|
+
...nted-error.ts | 100 | 100 | 100 | 100 |
|
|
93
|
+
...able-error.ts | 100 | 100 | 100 | 100 |
|
|
94
|
+
...ates-error.ts | 100 | 100 | 100 | 100 |
|
|
95
|
+
src/helpers | 0 | 0 | 0 | 0 |
|
|
96
|
+
index.ts | 0 | 0 | 0 | 0 |
|
|
97
|
+
...rror-by-status | 100 | 100 | 100 | 100 |
|
|
98
|
+
index.ts | 100 | 100 | 100 | 100 |
|
|
99
|
+
.../extract-cause | 100 | 100 | 100 | 100 |
|
|
100
|
+
index.ts | 100 | 100 | 100 | 100 |
|
|
101
|
+
...get-error-type | 100 | 100 | 100 | 100 |
|
|
102
|
+
index.ts | 100 | 100 | 100 | 100 |
|
|
103
|
+
.../is-http-error | 100 | 100 | 100 | 100 |
|
|
104
|
+
index.ts | 100 | 100 | 100 | 100 |
|
|
105
|
+
...d-rfc9457-json | 100 | 100 | 100 | 100 |
|
|
106
|
+
index.ts | 100 | 100 | 100 | 100 |
|
|
107
|
+
...lize-to-string | 100 | 100 | 100 | 100 |
|
|
108
|
+
index.ts | 100 | 100 | 100 | 100 |
|
|
109
|
+
-------------------|---------|----------|---------|---------|-------------------
|
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xcfio
|
|
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.
|
|
22
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,440 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @fastify-utils/error
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript-first error handling package implementing [RFC 9457 Problem Details for HTTP APIs](https://www.rfc-editor.org/rfc/rfc9457.html).
|
|
4
|
+
|
|
5
|
+
> RFC 9457 obsoletes [RFC 7807](https://www.rfc-editor.org/rfc/rfc7807.html) - this package implements the latest specification.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **RFC 9457 Compliant** - Strictly follows the Problem Details specification
|
|
10
|
+
- **TypeScript First** - Full type safety with excellent IDE support
|
|
11
|
+
- **Auto-normalization** - Accepts `unknown` errors and normalizes them automatically
|
|
12
|
+
- **Categorized API** - Clean, readable error handling with `errors.client.*` and `errors.server.*`
|
|
13
|
+
- **39 Standard HTTP Errors** - Complete coverage of all standard HTTP error codes
|
|
14
|
+
- **Convenient Aliases** - Common shortcuts like `errors.server.db()` for frequent use cases
|
|
15
|
+
- **Built-in Middleware** - Ready-to-use error handlers for popular frameworks (Hono, and more coming)
|
|
16
|
+
- **Framework Agnostic** - Works with Express, Hono, Fastify, and any Node.js framework
|
|
17
|
+
- **Zero Dependencies** - Lightweight with no external dependencies (middleware have optional peer dependencies)
|
|
18
|
+
- **CommonJS** - Standard CommonJS for Node.js
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @fastify-utils/error
|
|
24
|
+
# or
|
|
25
|
+
pnpm add @fastify-utils/error
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
// CommonJS
|
|
32
|
+
const { errors } = require("@fastify-utils/error")
|
|
33
|
+
|
|
34
|
+
// or TypeScript / ES Modules
|
|
35
|
+
import { errors } from "@fastify-utils/error"
|
|
36
|
+
|
|
37
|
+
// Client errors
|
|
38
|
+
throw errors.client.authentication("Invalid token")
|
|
39
|
+
throw errors.client.notFound("User", "123")
|
|
40
|
+
throw errors.client.validation("Email is required")
|
|
41
|
+
|
|
42
|
+
// Server errors
|
|
43
|
+
throw errors.server.internal("Database connection failed")
|
|
44
|
+
throw errors.server.db("Connection pool exhausted")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Available Errors
|
|
48
|
+
|
|
49
|
+
### Client Errors (4xx)
|
|
50
|
+
|
|
51
|
+
28 client error types covering all standard 4xx HTTP status codes:
|
|
52
|
+
|
|
53
|
+
| Method | Status | Description | Example |
|
|
54
|
+
| ----------------------------- | ------ | ---------------------------- | -------------------------------------------------------------------------- |
|
|
55
|
+
| `badRequest` | 400 | Malformed request | `errors.client.badRequest("Invalid JSON")` |
|
|
56
|
+
| `authentication` | 401 | Missing/invalid credentials | `errors.client.authentication("Token expired")` |
|
|
57
|
+
| `paymentRequired` | 402 | Payment required | `errors.client.paymentRequired("Subscription required")` |
|
|
58
|
+
| `authorization` | 403 | Insufficient permissions | `errors.client.authorization("Admin access required")` |
|
|
59
|
+
| `notFound` | 404 | Resource not found | `errors.client.notFound("User", "123")` |
|
|
60
|
+
| `methodNotAllowed` | 405 | HTTP method not allowed | `errors.client.methodNotAllowed("POST not allowed")` |
|
|
61
|
+
| `notAcceptable` | 406 | Not acceptable | `errors.client.notAcceptable("Only JSON supported")` |
|
|
62
|
+
| `proxyAuthenticationRequired` | 407 | Proxy auth required | `errors.client.proxyAuthenticationRequired("Proxy auth needed")` |
|
|
63
|
+
| `requestTimeout` | 408 | Request timeout | `errors.client.requestTimeout("Request took too long")` |
|
|
64
|
+
| `conflict` | 409 | Resource conflict | `errors.client.conflict("Email already exists")` |
|
|
65
|
+
| `gone` | 410 | Resource permanently deleted | `errors.client.gone("Resource permanently deleted")` |
|
|
66
|
+
| `lengthRequired` | 411 | Length header required | `errors.client.lengthRequired("Content-Length required")` |
|
|
67
|
+
| `preconditionFailed` | 412 | Precondition failed | `errors.client.preconditionFailed("ETag mismatch")` |
|
|
68
|
+
| `payloadTooLarge` | 413 | Payload too large | `errors.client.payloadTooLarge("File too large", 5000000)` |
|
|
69
|
+
| `uriTooLong` | 414 | URI too long | `errors.client.uriTooLong("URL exceeds maximum length")` |
|
|
70
|
+
| `unsupportedMediaType` | 415 | Unsupported media type | `errors.client.unsupportedMediaType("Only image/* allowed")` |
|
|
71
|
+
| `rangeNotSatisfiable` | 416 | Range not satisfiable | `errors.client.rangeNotSatisfiable("Invalid byte range")` |
|
|
72
|
+
| `expectationFailed` | 417 | Expectation failed | `errors.client.expectationFailed("Expect header failed")` |
|
|
73
|
+
| `misdirectedRequest` | 421 | Misdirected request | `errors.client.misdirectedRequest("Wrong server")` |
|
|
74
|
+
| `validation` | 422 | Invalid input data | `errors.client.validation("Invalid email", { email: ["Invalid format"] })` |
|
|
75
|
+
| `locked` | 423 | Resource locked | `errors.client.locked("Resource is locked")` |
|
|
76
|
+
| `failedDependency` | 424 | Failed dependency | `errors.client.failedDependency("Dependency failed")` |
|
|
77
|
+
| `tooEarly` | 425 | Too early | `errors.client.tooEarly("Request too early")` |
|
|
78
|
+
| `upgradeRequired` | 426 | Upgrade required | `errors.client.upgradeRequired("Upgrade to TLS required")` |
|
|
79
|
+
| `preconditionRequired` | 428 | Precondition required | `errors.client.preconditionRequired("If-Match header required")` |
|
|
80
|
+
| `rateLimit` | 429 | Too many requests | `errors.client.rateLimit("Rate limit exceeded", 60)` |
|
|
81
|
+
| `requestHeaderFieldsTooLarge` | 431 | Headers too large | `errors.client.requestHeaderFieldsTooLarge("Request headers too large")` |
|
|
82
|
+
| `unavailableForLegalReasons` | 451 | Legal restriction | `errors.client.unavailableForLegalReasons("Blocked by legal order")` |
|
|
83
|
+
|
|
84
|
+
### Server Errors (5xx)
|
|
85
|
+
|
|
86
|
+
11 server error types plus convenient aliases:
|
|
87
|
+
|
|
88
|
+
| Method | Status | Description | Example |
|
|
89
|
+
| ------------------------------- | ------ | ------------------------------- | -------------------------------------------------------------------- |
|
|
90
|
+
| `internal` | 500 | Internal server error | `errors.server.internal(caughtError)` |
|
|
91
|
+
| `notImplemented` | 501 | Feature not implemented | `errors.server.notImplemented("Feature not available")` |
|
|
92
|
+
| `badGateway` | 502 | External service error | `errors.server.badGateway(stripeError, "Stripe")` |
|
|
93
|
+
| `serviceUnavailable` | 503 | Service temporarily unavailable | `errors.server.serviceUnavailable("Maintenance mode", 60)` |
|
|
94
|
+
| `gatewayTimeout` | 504 | External service timeout | `errors.server.gatewayTimeout("Payment timeout", "Stripe")` |
|
|
95
|
+
| `httpVersionNotSupported` | 505 | HTTP version not supported | `errors.server.httpVersionNotSupported("HTTP/2 required")` |
|
|
96
|
+
| `variantAlsoNegotiates` | 506 | Variant also negotiates | `errors.server.variantAlsoNegotiates("Configuration error")` |
|
|
97
|
+
| `insufficientStorage` | 507 | Insufficient storage | `errors.server.insufficientStorage("Out of storage space")` |
|
|
98
|
+
| `loopDetected` | 508 | Loop detected | `errors.server.loopDetected("Circular dependency detected")` |
|
|
99
|
+
| `notExtended` | 510 | Not extended | `errors.server.notExtended("Extension not supported")` |
|
|
100
|
+
| `networkAuthenticationRequired` | 511 | Network authentication required | `errors.server.networkAuthenticationRequired("Proxy auth required")` |
|
|
101
|
+
|
|
102
|
+
**Convenient Aliases:**
|
|
103
|
+
|
|
104
|
+
Common shortcuts for frequent use cases:
|
|
105
|
+
|
|
106
|
+
| Alias | Maps To | Status | Example |
|
|
107
|
+
| -------------------- | --------------------- | ------ | ----------------------------------------------------------------- |
|
|
108
|
+
| **Client Aliases** | | | |
|
|
109
|
+
| `validate` | `validation` | 422 | `errors.client.validate("Invalid email format")` |
|
|
110
|
+
| `permission` | `authorization` | 403 | `errors.client.permission("Access denied")` |
|
|
111
|
+
| `access` | `authorization` | 403 | `errors.client.access("Insufficient permissions")` |
|
|
112
|
+
| `idNotFound` | `notFound` | 404 | `errors.client.idNotFound("User", "123")` |
|
|
113
|
+
| `duplicate` | `conflict` | 409 | `errors.client.duplicate("Email already exists")` |
|
|
114
|
+
| `thirdParty` | `failedDependency` | 424 | `errors.client.thirdParty("External service failed")` |
|
|
115
|
+
| **Server Aliases** | | | |
|
|
116
|
+
| `db` | `serviceUnavailable` | 503 | `errors.server.db("Connection pool exhausted")` |
|
|
117
|
+
| `fetch` | `badGateway` | 502 | `errors.server.fetch("GitHub API unreachable")` |
|
|
118
|
+
| `envNotSet` | `notImplemented` | 501 | `errors.server.envNotSet("DATABASE_URL not configured")` |
|
|
119
|
+
| `envInvalid` | `notImplemented` | 501 | `errors.server.envInvalid("DATABASE_URL must be a valid URL")` |
|
|
120
|
+
| `maintenance` | `serviceUnavailable` | 503 | `errors.server.maintenance("System under maintenance")` |
|
|
121
|
+
| `migration` | `insufficientStorage` | 507 | `errors.server.migration("Migration storage limit exceeded")` |
|
|
122
|
+
| `unhandledRejection` | `internal` | 500 | `errors.server.unhandledRejection("Unhandled promise rejection")` |
|
|
123
|
+
| `uncaughtException` | `internal` | 500 | `errors.server.uncaughtException("Uncaught exception")` |
|
|
124
|
+
|
|
125
|
+
## Usage Examples
|
|
126
|
+
|
|
127
|
+
### Basic Error Throwing
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { errors } from "@fastify-utils/error"
|
|
131
|
+
|
|
132
|
+
if (!user) {
|
|
133
|
+
throw errors.client.notFound("User", userId)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!hasPermission) {
|
|
137
|
+
throw errors.client.authorization("Admin access required")
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Auto-Normalization
|
|
142
|
+
|
|
143
|
+
The package automatically normalizes any value to a string:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { errors } from "@fastify-utils/error"
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
await externalAPI.call()
|
|
150
|
+
} catch (err) {
|
|
151
|
+
throw errors.server.badGateway(err, "External API")
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Validation Errors
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { errors } from "@fastify-utils/error"
|
|
159
|
+
|
|
160
|
+
const validationErrors = {
|
|
161
|
+
email: ["Invalid email format", "Email already exists"],
|
|
162
|
+
password: ["Password too weak"]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
throw errors.client.validation("Validation failed", validationErrors)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### NotFound with Auto-formatting
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { errors } from "@fastify-utils/error"
|
|
172
|
+
|
|
173
|
+
// Auto-formatted message: "User 123 not found"
|
|
174
|
+
throw errors.client.notFound("User", "123")
|
|
175
|
+
|
|
176
|
+
// Custom message
|
|
177
|
+
throw errors.client.notFound("Custom message: User not found in database")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Using Convenient Aliases
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { errors } from "@fastify-utils/error"
|
|
184
|
+
|
|
185
|
+
// Validation errors
|
|
186
|
+
if (!email.includes("@")) {
|
|
187
|
+
throw errors.client.validate("Invalid email format")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Permission checks
|
|
191
|
+
if (!user.isAdmin) {
|
|
192
|
+
throw errors.client.permission("Admin access required")
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ID lookups
|
|
196
|
+
const user = await db.findUser(userId)
|
|
197
|
+
if (!user) {
|
|
198
|
+
throw errors.client.idNotFound("User", userId)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Duplicate entries
|
|
202
|
+
if (await db.emailExists(email)) {
|
|
203
|
+
throw errors.client.duplicate("User with this email already exists")
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Database errors
|
|
207
|
+
try {
|
|
208
|
+
await db.query("SELECT * FROM users")
|
|
209
|
+
} catch (err) {
|
|
210
|
+
throw errors.server.db(err)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// External API failures
|
|
214
|
+
try {
|
|
215
|
+
await fetch("https://api.github.com/users/octocat")
|
|
216
|
+
} catch (err) {
|
|
217
|
+
throw errors.server.fetch(err)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Third-party integrations
|
|
221
|
+
try {
|
|
222
|
+
await stripe.customers.create({ email })
|
|
223
|
+
} catch (err) {
|
|
224
|
+
throw errors.client.thirdParty(err)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Environment configuration - missing
|
|
228
|
+
if (!process.env.DATABASE_URL) {
|
|
229
|
+
throw errors.server.envNotSet("DATABASE_URL environment variable not set")
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Environment configuration - invalid value
|
|
233
|
+
if (process.env.NODE_ENV && !["development", "production", "test"].includes(process.env.NODE_ENV)) {
|
|
234
|
+
throw errors.server.envInvalid("NODE_ENV must be development, production, or test")
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Maintenance mode
|
|
238
|
+
if (isMaintenanceMode) {
|
|
239
|
+
throw errors.server.maintenance("System is under scheduled maintenance", 3600)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Migration failures
|
|
243
|
+
try {
|
|
244
|
+
await runMigration()
|
|
245
|
+
} catch (err) {
|
|
246
|
+
throw errors.server.migration(err)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Node.js process error handlers
|
|
250
|
+
process.on("unhandledRejection", (reason) => {
|
|
251
|
+
console.error(errors.server.unhandledRejection(reason))
|
|
252
|
+
process.exit(1)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
process.on("uncaughtException", (error) => {
|
|
256
|
+
console.error(errors.server.uncaughtException(error))
|
|
257
|
+
process.exit(1)
|
|
258
|
+
})
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Additional Utilities
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { errors, isValidRFC9457Json } from "@fastify-utils/error"
|
|
265
|
+
|
|
266
|
+
// Create error by status code
|
|
267
|
+
throw errors.byStatus(404, "Not found")
|
|
268
|
+
|
|
269
|
+
// Get JSON without throwing
|
|
270
|
+
const json = errors.client.badRequest("Invalid input").toJSON()
|
|
271
|
+
|
|
272
|
+
// Validate RFC 9457 response
|
|
273
|
+
if (isValidRFC9457Json(data)) {
|
|
274
|
+
// Valid RFC 9457 error
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Framework Integration
|
|
279
|
+
|
|
280
|
+
### Fastify
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import Fastify from "fastify"
|
|
284
|
+
import { errors, isHttpError } from "@fastify-utils/error"
|
|
285
|
+
|
|
286
|
+
const fastify = Fastify()
|
|
287
|
+
|
|
288
|
+
fastify.get("/users/:id", async (request, reply) => {
|
|
289
|
+
const user = await db.users.findById(request.params.id)
|
|
290
|
+
|
|
291
|
+
if (!user) {
|
|
292
|
+
throw errors.client.notFound("User", request.params.id)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return user
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
299
|
+
if (isHttpError(error)) {
|
|
300
|
+
return reply.status(error.status).send(error.toJSON())
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const internalError = errors.server.internal(error)
|
|
304
|
+
reply.status(500).send(internalError.toJSON())
|
|
305
|
+
})
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Express
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import express from "express"
|
|
312
|
+
import { errors, isHttpError } from "@fastify-utils/error"
|
|
313
|
+
|
|
314
|
+
const app = express()
|
|
315
|
+
|
|
316
|
+
app.get("/users/:id", async (req, res) => {
|
|
317
|
+
const user = await db.users.findById(req.params.id)
|
|
318
|
+
|
|
319
|
+
if (!user) {
|
|
320
|
+
throw errors.client.notFound("User", req.params.id)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
res.json(user)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
app.use((err, req, res, next) => {
|
|
327
|
+
if (isHttpError(err)) {
|
|
328
|
+
return res.status(err.status).json(err.toJSON())
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const internalError = errors.server.internal(err)
|
|
332
|
+
res.status(500).json(internalError.toJSON())
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Configuration
|
|
337
|
+
|
|
338
|
+
Set the base URL for error type URIs using the environment variable:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
export RFC9457_BASE_URL=https://api.example.com/errors
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Error Response Format
|
|
345
|
+
|
|
346
|
+
All errors follow RFC 9457 structure:
|
|
347
|
+
|
|
348
|
+
```json
|
|
349
|
+
{
|
|
350
|
+
"type": "about:blank#not-found",
|
|
351
|
+
"title": "Not Found",
|
|
352
|
+
"status": 404,
|
|
353
|
+
"detail": "User 123 not found"
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
With custom base URL:
|
|
358
|
+
|
|
359
|
+
```json
|
|
360
|
+
{
|
|
361
|
+
"type": "https://api.example.com/errors/validation",
|
|
362
|
+
"title": "Validation Error",
|
|
363
|
+
"status": 422,
|
|
364
|
+
"detail": "Validation failed",
|
|
365
|
+
"validationErrors": {
|
|
366
|
+
"email": ["Invalid email format"]
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## TypeScript Support
|
|
372
|
+
|
|
373
|
+
Full type safety and IDE autocomplete:
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
import { errors, isHttpError } from "@fastify-utils/error"
|
|
377
|
+
|
|
378
|
+
const err = errors.client.validation("Invalid data")
|
|
379
|
+
|
|
380
|
+
if (isHttpError(err)) {
|
|
381
|
+
console.log(err.status) // 422
|
|
382
|
+
console.log(err.toJSON()) // { type: "...", title: "...", ... }
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## API Reference
|
|
387
|
+
|
|
388
|
+
### Categorized Errors (Recommended)
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { errors } from "@fastify-utils/error"
|
|
392
|
+
|
|
393
|
+
throw errors.client.badRequest("Invalid input")
|
|
394
|
+
throw errors.server.internal("System error")
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Flat API (Alternative)
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import { error } from "@fastify-utils/error"
|
|
401
|
+
|
|
402
|
+
throw error.badRequest("Invalid input")
|
|
403
|
+
throw error.internal("System error")
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Helpers
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { isHttpError } from "@fastify-utils/error"
|
|
410
|
+
|
|
411
|
+
if (isHttpError(err)) {
|
|
412
|
+
console.log(err.status)
|
|
413
|
+
console.log(err.toJSON())
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Development
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
pnpm install
|
|
421
|
+
node --run build
|
|
422
|
+
node --run lint
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## License
|
|
426
|
+
|
|
427
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
428
|
+
|
|
429
|
+
## Links
|
|
430
|
+
|
|
431
|
+
| Resource | URL |
|
|
432
|
+
| ----------- | --------------------------------------------- |
|
|
433
|
+
| GitHub | https://github.com/xcfio/fastify-utils |
|
|
434
|
+
| npm | https://www.npmjs.com/package/fastify-utils |
|
|
435
|
+
| Bug reports | https://github.com/xcfio/fastify-utils/issues |
|
|
436
|
+
| Help | https://dsc.gg/xcfio |
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
Made with ❤️ by [xcfio](https://github.com/xcfio)
|