Skip to content

Commit 95e0bec

Browse files
author
Mohamed Johnson [SNT DSI/DAC/DIM/DS]
committed
feat: 🚀 Initialized Repo
1 parent 2c86ed9 commit 95e0bec

7 files changed

Lines changed: 331 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
yarn.lock

README

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# http-errors-plus
2+
3+
> The next-generation HTTP error system for JavaScript & TypeScript — designed for modern DX, structured observability, and future-proof error handling.
4+
5+
---
6+
7+
## ⚡ Why http-errors-plus?
8+
9+
`http-errors` served us well — but it’s stuck in a CommonJS world with stringly-typed messages and zero structure and inspired me to do this.
10+
11+
**http-errors-plus** gives you:
12+
13+
✅ Fully ESM-ready and works in both JS and TS
14+
✅ Named constants like `NOT_FOUND`, `BAD_REQUEST`, etc.
15+
✅ Overrideable metadata: `detail`, `path`, `traceId`, `requestId`, etc.
16+
✅ Drop-in `generateError()` utility
17+
✅ Strong IntelliSense via JSDoc
18+
✅ `HttpError` class with `.toJSON()` and APM-ready structure
19+
20+
---
21+
22+
## 🚀 Installation
23+
24+
```bash
25+
yarn add http-errors-plus
26+
```
27+
28+
---
29+
30+
## 🧑‍💻 Usage
31+
32+
### Import and Use Named Constants
33+
```js
34+
import { BASE_HTTP_ERRORS, generateError } from 'http-errors-plus';
35+
36+
const { NOT_FOUND } = BASE_HTTP_ERRORS;
37+
38+
throw generateError(NOT_FOUND, {
39+
detail: 'User with ID 42 not found',
40+
path: '/api/users/42',
41+
requestId: 'req-xyz-123',
42+
});
43+
```
44+
45+
### Output
46+
```json
47+
{
48+
"status": 404,
49+
"code": "NOT_FOUND",
50+
"message": "Not Found",
51+
"detail": "User with ID 42 not found",
52+
"path": "/api/users/42",
53+
"timestamp": "2025-05-17T12:00:00Z",
54+
"requestId": "req-xyz-123"
55+
}
56+
```
57+
58+
---
59+
60+
## 📦 Exported API
61+
62+
### ✅ Constants
63+
```js
64+
BASE_HTTP_ERRORS.NOT_FOUND // { status: 404, code: 'NOT_FOUND', message: 'Not Found' }
65+
BASE_HTTP_ERRORS.BAD_REQUEST // { status: 400, code: 'BAD_REQUEST', message: 'Bad Request' }
66+
// ... all standard HTTP codes covered
67+
```
68+
69+
### ✅ generateError(base, overrides?)
70+
Creates a new error object based on a constant.
71+
72+
```js
73+
const error = generateError(BASE_HTTP_ERRORS.CONFLICT, {
74+
detail: 'Email already exists',
75+
requestId: 'abc123',
76+
});
77+
```
78+
79+
### ✅ HttpError Class
80+
```js
81+
new HttpError(403, {
82+
code: 'FORBIDDEN',
83+
message: 'Access denied',
84+
detail: 'Only admins can access this resource.',
85+
})
86+
```
87+
88+
---
89+
90+
## 🔍 Coming Soon
91+
- `isHttpError()` type guard
92+
- RFC 7807 `.toProblemJSON()` support
93+
- Built-in middleware for Express/Fastify
94+
- Auto-i18n key support
95+
96+
---
97+
98+
## 💡 Philosophy
99+
HTTP errors should be:
100+
- Human-readable
101+
- Machine-parseable
102+
- Developer-first
103+
- Easy to maintain and override
104+
105+
---
106+
107+
## 🧠 Inspiration
108+
Built as a modern successor to `http-errors` and `statuses`, with a fresh take on API dev ergonomics.
109+
110+
---
111+
112+
## 🪪 License
113+
MIT — Use it, ship it, improve it.
114+
115+
---
116+
117+
## ✨ Author
118+
Made by Mohamed Johnson. 🧠 Powered by clarity, DX obsession, and no chill mode.

index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// import { BASE_HTTP_ERRORS } from './constants.js';
2+
// import { HttpError } from './http-error.js';
3+
// import { generateError } from './generateError.js';
4+
5+
import { BASE_HTTP_ERRORS } from "./src/constants/index.js";
6+
import { generateError } from "./utils/generateError.js";
7+
import { HttpError } from "./utils/http-error.js";
8+
9+
export { HttpError, generateError, BASE_HTTP_ERRORS };

package.json

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"name": "http-errors-plus",
3+
"version": "1.0.0",
4+
"description": "Next-gen HTTP error handling for modern JavaScript and TypeScript apps — structured, overrideable, and developer-focused.",
5+
"main": "./src/index.js",
6+
"exports": {
7+
".": {
8+
"require": "./dist/index.cjs",
9+
"import": "./dist/index.esm.js",
10+
"types": "./dist/index.d.ts"
11+
}
12+
},
13+
"types": "./types/index.d.ts",
14+
"files": [
15+
"dist",
16+
"src",
17+
"types",
18+
"README.md",
19+
"LICENSE"
20+
],
21+
"scripts": {
22+
"test": "node test/basic.test.js",
23+
"lint": "echo 'linting coming soon'",
24+
"build": "echo 'no build needed — pure JS'"
25+
},
26+
"keywords": [
27+
"http",
28+
"error",
29+
"errors",
30+
"http-errors",
31+
"structured-errors",
32+
"http status",
33+
"api",
34+
"developer-experience",
35+
"observable",
36+
"typescript",
37+
"javascript",
38+
"ESM",
39+
"fastify",
40+
"express"
41+
],
42+
"repository": {
43+
"type": "git",
44+
"url": "https://github.com/your-username/http-errors-plus.git"
45+
},
46+
"bugs": {
47+
"url": "https://github.com/your-username/http-errors-plus/issues"
48+
},
49+
"homepage": "https://github.com/your-username/http-errors-plus#readme",
50+
"author": {
51+
"name": "Mohamed Johnson",
52+
"email": "you@example.com",
53+
"url": "https://github.com/LPIX-11"
54+
},
55+
"license": "MIT",
56+
"type": "module",
57+
"engines": {
58+
"node": ">=18.0.0"
59+
},
60+
"devDependencies": {
61+
"@jsdoc/salty": "^0.2.8",
62+
"@types/events": "^3.0.3",
63+
"@types/linkify-it": "^5.0.0",
64+
"@types/node": "^22.5.4",
65+
"better-docs": "^2.7.3",
66+
"prettier": "^3.2.5",
67+
"typescript": "^5.5.4",
68+
"vite": "^5.4.3"
69+
},
70+
"dependencies": {
71+
"statuses": "^2.0.1"
72+
}
73+
}

src/constants/index.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import statuses from 'statuses';
2+
3+
/**
4+
* @typedef {Object} StatusEntry
5+
* @property {number} code
6+
* @property {string} name
7+
* @property {string} message
8+
*/
9+
10+
/**
11+
* Maps HTTP status code to name/message
12+
* @type {Record<number, StatusEntry>}
13+
*/
14+
export const STATUS_CODE_MAP = statuses.codes.reduce((acc, code) => {
15+
const name = statuses.message[code].toUpperCase().replace(/[^A-Z0-9]+/g, '_');
16+
acc[code] = {
17+
code,
18+
name,
19+
message: statuses.message[code],
20+
};
21+
return acc;
22+
}, {});
23+
24+
/**
25+
* Maps HTTP status name to code
26+
* @type {Record<string, StatusEntry>}
27+
*/
28+
export const STATUS_NAME_MAP = Object.values(STATUS_CODE_MAP).reduce((acc, { code, name, message }) => {
29+
acc[name] = { code, name, message };
30+
return acc;
31+
}, {});
32+
33+
/**
34+
* @typedef {Object} HttpErrorConstant
35+
* @property {number} status - HTTP status code
36+
* @property {string} code - Uppercase constant name (e.g., NOT_FOUND)
37+
* @property {string} message - Human-readable HTTP message
38+
*/
39+
40+
/**
41+
* @typedef {{
42+
* CONTINUE: HttpErrorConstant,
43+
* SWITCHING_PROTOCOLS: HttpErrorConstant,
44+
* PROCESSING: HttpErrorConstant,
45+
* OK: HttpErrorConstant,
46+
* CREATED: HttpErrorConstant,
47+
* ACCEPTED: HttpErrorConstant,
48+
* NO_CONTENT: HttpErrorConstant,
49+
* MOVED_PERMANENTLY: HttpErrorConstant,
50+
* FOUND: HttpErrorConstant,
51+
* NOT_MODIFIED: HttpErrorConstant,
52+
* BAD_REQUEST: HttpErrorConstant,
53+
* UNAUTHORIZED: HttpErrorConstant,
54+
* FORBIDDEN: HttpErrorConstant,
55+
* NOT_FOUND: HttpErrorConstant,
56+
* METHOD_NOT_ALLOWED: HttpErrorConstant,
57+
* CONFLICT: HttpErrorConstant,
58+
* UNPROCESSABLE_ENTITY: HttpErrorConstant,
59+
* TOO_MANY_REQUESTS: HttpErrorConstant,
60+
* INTERNAL_SERVER_ERROR: HttpErrorConstant,
61+
* BAD_GATEWAY: HttpErrorConstant,
62+
* SERVICE_UNAVAILABLE: HttpErrorConstant,
63+
* GATEWAY_TIMEOUT: HttpErrorConstant
64+
* }} BaseHttpErrors
65+
*/
66+
67+
/**
68+
* @type {BaseHttpErrors}
69+
*/
70+
export const BASE_HTTP_ERRORS = Object.fromEntries(
71+
Object.values(STATUS_CODE_MAP).map(({ code, name, message }) => {
72+
return [name, Object.freeze({ status: code, code: name, message })];
73+
})
74+
);
75+
// /**
76+
// * Export default constants (e.g. NOT_FOUND, BAD_REQUEST)
77+
// * @type {Record<string, { status: number, code: string, message: string }>}
78+
// */

utils/generateError.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { HttpError } from './http-error.js';
2+
3+
/**
4+
* @param {{ status: number, code: string, message: string }} base
5+
* @param {Partial<import('./http-error.js').SerializedHttpError>} [override]
6+
* @returns {HttpError}
7+
*/
8+
export function generateError(base, override = {}) {
9+
return new HttpError(base.status, {
10+
code: base.code,
11+
message: override.message || base.message,
12+
...override,
13+
});
14+
}

utils/http-error.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @typedef {Object} SerializedHttpError
3+
* @property {number} status
4+
* @property {string} code
5+
* @property {string} message
6+
* @property {string} [detail]
7+
* @property {string} [path]
8+
* @property {string} [method]
9+
* @property {string} [timestamp]
10+
* @property {string} [requestId]
11+
* @property {string} [traceId]
12+
*/
13+
14+
export class HttpError extends Error {
15+
/**
16+
* @param {number} status
17+
* @param {Omit<SerializedHttpError, 'status'>} options
18+
*/
19+
constructor(status, options) {
20+
super(options.message);
21+
this.name = 'HttpError';
22+
23+
/** @type {SerializedHttpError} */
24+
this.meta = {
25+
status,
26+
timestamp: new Date().toISOString(),
27+
...options,
28+
};
29+
}
30+
31+
/**
32+
* @returns {SerializedHttpError}
33+
*/
34+
toJSON() {
35+
return this.meta;
36+
}
37+
}

0 commit comments

Comments
 (0)