@nextrush/errors 3.0.7 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,84 +1,107 @@
1
1
  # @nextrush/errors
2
2
 
3
- > Standardized HTTP error handling that eliminates response inconsistency and builds API client trust.
3
+ > A typed `HttpError` hierarchy and error-handling middleware that turn thrown errors into consistent, client-safe JSON responses — for NextRush application and middleware authors.
4
4
 
5
- ## The Problem
5
+ [![npm version](https://img.shields.io/npm/v/@nextrush/errors.svg)](https://www.npmjs.com/package/@nextrush/errors)
6
+ [![downloads](https://img.shields.io/npm/dm/@nextrush/errors.svg)](https://www.npmjs.com/package/@nextrush/errors)
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@nextrush/errors.svg)](https://bundlephobia.com/package/@nextrush/errors)
8
+ [![types](https://img.shields.io/npm/types/@nextrush/errors.svg)](https://www.npmjs.com/package/@nextrush/errors)
9
+ [![ESM only](https://img.shields.io/badge/module-ESM--only-blue.svg)](https://nodejs.org/api/esm.html)
10
+ [![license](https://img.shields.io/npm/l/@nextrush/errors.svg)](https://github.com/0xTanzim/nextRush/blob/main/LICENSE)
6
11
 
7
- Every API returns errors differently. This creates chaos for both developers and API consumers:
12
+ | | |
13
+ | --- | --- |
14
+ | **Purpose** | Typed HTTP errors + error-handling middleware for NextRush — throw an error, get a consistent JSON response |
15
+ | **Package type** | Core |
16
+ | **Status** | Stable ✅ |
17
+ | **Included in `nextrush`?** | ✅ Yes — the common error classes, `errorHandler`, `ValidationError`, `createError`, `ERROR_CODES` are re-exported. Install directly for the full catalog (every 4xx/5xx class, every factory helper, every validation subclass). |
18
+ | **Support tier** | Public — core (stable, semver-guarded) — see [ADR-0005](https://github.com/0xTanzim/nextRush/blob/main/docs/adr/ADR-0005-package-tiers-sealed-surface-deprecation.md) |
19
+ | **Maintenance** | Active |
20
+ | **Runtime** | Universal — Node · Bun · Deno · Edge |
21
+ | **Requires** | Node `>=22` · ESM-only · TypeScript `>=5.x` |
22
+ | **Introduced** | `v3.0.0` |
8
23
 
9
- **Inconsistent error responses** plague every backend project. One endpoint returns `{error: "..."}`, another returns `{message: "..."}`, and a third leaks stack traces in production. API clients can't reliably handle errors because there's no standard format.
24
+ ## Highlights
10
25
 
11
- **Internal errors leak to users.** A database connection timeout becomes `PostgreSQL connection failed on pool 'primary'` in the API response. Security researchers see your infrastructure. Users see confusing technical jargon instead of actionable messages.
26
+ - **No third-party dependencies** depends only on `@nextrush/types` (types, erased at build)
27
+ - ✅ **ESM-only**, tree-shakable, side-effect-free — a thrown error pulls in only its own class
28
+ - ✅ **Fully typed** — strict TypeScript, zero `any`; every error carries `status`, `code`, and `expose`
29
+ - 🛡️ **Client-safe by default** — 5xx messages are hidden unless you opt in; internal detail never leaks
12
30
 
13
- **Manual error formatting is tedious.** Every route handler manually sets status codes, constructs JSON responses, and decides what to expose. Copy-paste error handling leads to bugs. Forgetting `try-catch` crashes the server.
31
+ <details>
32
+ <summary><strong>Table of contents</strong></summary>
14
33
 
15
- **No programmatic error handling.** API clients resort to parsing error messages with regex because there are no stable error codes. A typo in an error message breaks production integrations.
34
+ [The problem](#the-problem) · [When to use](#when-to-use) · [Installation](#installation) · [Quick start](#quick-start) · [Capabilities](#capabilities) · [Mental model](#mental-model) · [Common tasks](#common-tasks) · [API overview](#api-overview) · [Options](#options) · [Compatibility](#compatibility) · [Troubleshooting](#troubleshooting) · [FAQ](#faq) · [Package relationships](#package-relationships) · [Architecture](#architecture) · [Resources](#resources)
16
35
 
17
- ## How NextRush Approaches This
36
+ </details>
18
37
 
19
- NextRush treats **errors as API contracts**, not exceptions.
38
+ ---
20
39
 
21
- Every error has three responsibilities:
40
+ ## The problem
22
41
 
23
- 1. **HTTP status code** - Semantic meaning for browsers and clients
24
- 2. **Human message** - Clear explanation for developers/users
25
- 3. **Machine code** - Stable identifier for programmatic handling
42
+ Error handling is where consistency quietly erodes. One handler returns `{ error: "..." }`, the next returns `{ message: "..." }`, and a forgotten `try/catch` leaks a database stack trace straight to the client. Each handler re-decides the status code, the response shape, and what is safe to expose — and each one gets it slightly differently.
26
43
 
27
- The framework distinguishes between **client-safe errors** (4xx) and **server-internal errors** (5xx) with an `expose` flag. Client errors show detailed messages. Server errors hide implementation details by default.
28
-
29
- All errors serialize to a consistent JSON format automatically. No manual response formatting. No leaked stack traces. No security risks.
30
-
31
- ## Mental Model
32
-
33
- Think of errors as **structured API responses**, not crashes.
34
-
35
- ### Errors Are Contracts
36
-
37
- ```
38
- User Request → Handler Logic → Error Thrown → Middleware Catches → JSON Response
44
+ ```ts
45
+ // TODAY, without a typed error layer — the shape drifts per handler,
46
+ // and the internal message goes straight to the client:
47
+ app.get('/users/:id', async (ctx) => {
48
+ const user = await db.findUser(ctx.params.id);
49
+ if (!user) {
50
+ ctx.status = 404;
51
+ return ctx.json({ error: 'not found' }); // one shape here…
52
+ }
53
+ // if db.findUser throws, the raw message ("ECONNREFUSED 10.0.0.5:5432")
54
+ // reaches the client unless every handler remembers to catch it.
55
+ });
39
56
  ```
40
57
 
41
- When you throw `NotFoundError`, you're declaring an API contract:
58
+ As the app grows, API consumers can't handle errors programmatically (no stable codes), and each new endpoint is another chance to leak infrastructure detail. `@nextrush/errors` makes the error *itself* carry its status, machine code, and exposure rule — so the response shape is decided once, not per handler.
42
59
 
43
- - **Status:** 404 Not Found
44
- - **Code:** `NOT_FOUND`
45
- - **Message:** Custom message you provide
46
- - **Format:** Consistent JSON structure
60
+ ## When to use
47
61
 
48
- ### The Expose Flag
62
+ `@nextrush/errors` is the error layer built into NextRush. `throw new NotFoundError(...)` from any handler, add `errorHandler()` to the middleware chain, and every error serializes the same way.
49
63
 
50
- Every error has an `expose` flag that acts as a **privacy boundary**:
64
+ **Use `@nextrush/errors` if:**
51
65
 
52
- ```typescript
53
- // Client errors (4xx): expose = true by default
54
- throw new NotFoundError('User #123 not found');
55
- // Client sees: {"message": "User #123 not found", "code": "NOT_FOUND"}
66
+ - ✓ You're building a NextRush app or middleware and want thrown errors to become consistent JSON automatically
67
+ - You need stable machine-readable `code`s (`NOT_FOUND`, `RATE_LIMIT`) that API clients can branch on
68
+ - You want 5xx internals hidden from clients by default, while still logging the full error server-side
69
+ - You need structured field-level validation errors (`ValidationError`) or to rebuild a typed error across a service boundary (`fromJSON`)
56
70
 
57
- // Server errors (5xx): expose = false by default
58
- throw new InternalServerError('Redis connection timeout');
59
- // Client sees: {"message": "Internal Server Error", "code": "INTERNAL_ERROR"}
60
- // Server logs: Full error with stack trace
61
- ```
71
+ **Reach for something else if:**
72
+
73
+ - You're writing a reusable, transport-agnostic library return a result value instead of throwing HTTP errors; throw at the HTTP boundary only
74
+ - You want request-body size limits or content negotiation — those live in [`@nextrush/body-parser`](../middleware/body-parser) and the handler, not here
62
75
 
63
- This prevents security leaks while maintaining debuggability.
76
+ ---
64
77
 
65
78
  ## Installation
66
79
 
67
80
  ```bash
68
81
  pnpm add @nextrush/errors
82
+ # npm i @nextrush/errors · yarn add @nextrush/errors · bun add @nextrush/errors
69
83
  ```
70
84
 
71
- ## Quick Start
85
+ > [!NOTE]
86
+ > Already using `nextrush`? The common error classes plus `errorHandler`, `notFoundHandler`,
87
+ > `createError`, `ValidationError`, `ERROR_CODES`, and `codeForStatus` are re-exported from the
88
+ > meta package — `import { NotFoundError, errorHandler } from 'nextrush'` works without installing
89
+ > this directly. Install `@nextrush/errors` when you need the full catalog or want to depend on it
90
+ > explicitly.
72
91
 
73
- ```typescript
74
- import { createApp } from '@nextrush/core';
92
+ ## Quick start
93
+
94
+ ```ts
95
+ import { createApp, listen } from 'nextrush';
75
96
  import { errorHandler, NotFoundError, BadRequestError } from '@nextrush/errors';
76
97
 
77
98
  const app = createApp();
78
99
 
79
- // Add error handling middleware FIRST
100
+ // Register the handler BEFORE your routes — it wraps the chain in a try/catch.
80
101
  app.use(errorHandler());
81
102
 
103
+ const users = new Map([['1', { id: '1', name: 'Ada' }]]);
104
+
82
105
  app.get('/users/:id', (ctx) => {
83
106
  const user = users.get(ctx.params.id);
84
107
  if (!user) {
@@ -88,594 +111,370 @@ app.get('/users/:id', (ctx) => {
88
111
  });
89
112
 
90
113
  app.post('/users', (ctx) => {
91
- if (!ctx.body.email) {
92
- throw new BadRequestError('Email is required');
114
+ const { email } = (ctx.body ?? {}) as { email?: string };
115
+ if (!email) {
116
+ throw new BadRequestError('Email is required', { code: 'EMAIL_REQUIRED' });
93
117
  }
94
- // Create user...
95
- ctx.json({ success: true });
118
+ ctx.status = 201;
119
+ ctx.json({ ok: true });
96
120
  });
97
121
 
98
- // Request: GET /users/999
99
- // Response: 404 Not Found
100
- // {
101
- // "error": "NotFoundError",
102
- // "message": "User not found",
103
- // "code": "NOT_FOUND",
104
- // "status": 404
105
- // }
106
-
107
- // Request: POST /users (no email)
108
- // Response: 400 Bad Request
109
- // {
110
- // "error": "BadRequestError",
111
- // "message": "Email is required",
112
- // "code": "BAD_REQUEST",
113
- // "status": 400
114
- // }
122
+ listen(app, 8080);
123
+
124
+ // GET /users/999 → 404
125
+ // { "error": "NotFoundError", "message": "User not found", "code": "NOT_FOUND", "status": 404 }
126
+ //
127
+ // POST /users (no email) → 400
128
+ // { "error": "BadRequestError", "message": "Email is required", "code": "EMAIL_REQUIRED", "status": 400 }
115
129
  ```
116
130
 
117
- ## What NextRush Does Automatically
131
+ You never write the response shape. You declare an error state by throwing a typed error, and `errorHandler()` catches it, sets the status, and serializes it through the error's own `toJSON()`.
118
132
 
119
- When you throw an `HttpError` with error middleware enabled:
133
+ ## Capabilities
120
134
 
121
- 1. **Catches the error** - No uncaught exceptions crash your server
122
- 2. **Sets HTTP status** - Correct status code from error class
123
- 3. **Formats JSON response** - Consistent `{error, message, code, status}` structure
124
- 4. **Applies expose flag** - Hides sensitive 5xx details, shows 4xx details
125
- 5. **Logs appropriately** - 5xx logged as errors, 4xx as warnings
126
- 6. **Preserves stack traces** - Full debugging in development, hidden in production
135
+ **Error types**
136
+ - **Full `HttpError` catalog** 28 client (4xx) and 11 server (5xx) classes, each with the correct status and canonical `code`
137
+ - **`ValidationError` family** structured, multi-issue validation errors with field-level helpers
138
+ - **Custom errors** extend `HttpError` or `NextRushError` to add your own typed errors
127
139
 
128
- You don't write error handling code. You **declare error states** and NextRush handles the rest.
140
+ **Safety**
141
+ - **`expose` privacy boundary** — 4xx messages are shown, 5xx messages hidden by default; internal detail and stack traces stay server-side
142
+ - **Bounded `cause` serialization** — nested `cause` chains are walked to a fixed depth with a cycle guard, and only on exposed errors
143
+ - **Immutable details** — `details` and validation `issues` are frozen at construction, so an error can't be mutated after it's thrown
129
144
 
130
- ## Features
145
+ **Integration**
146
+ - **`errorHandler()` middleware** — one Koa-style middleware catches, logs, and serializes every thrown error
147
+ - **Stable machine codes** — a central `ERROR_CODES` registry maps every status to one canonical code
148
+ - **Cross-service transport** — `toJSON()` / `fromJSON()` round-trips a typed error across an HTTP boundary
131
149
 
132
- - **Type-Safe Errors**: Full TypeScript support with proper error inheritance
133
- - **HTTP Status Codes**: All standard 4xx and 5xx errors included
134
- - **Validation Errors**: Built-in validation error types
135
- - **Factory Functions**: Quick error creation with `badRequest()`, `notFound()`, etc.
136
- - **Error Middleware**: Automatic error response formatting
137
- - **Error Codes**: Custom error codes for API consumers
150
+ **Developer experience**
151
+ - **Factory helpers** `notFound()`, `badRequest()`, `createError(status)` for terse construction
152
+ - **Fully typed** strict TypeScript, zero `any`; contracts shared via `@nextrush/types`
138
153
 
139
- ## HTTP Errors
154
+ ## Mental model
140
155
 
141
- ### 4xx Client Errors
156
+ An error is an **API response object**, not a crash. Throwing it declares a status, a machine code, and whether its message is safe to show — the framework does the rest.
142
157
 
143
- ```typescript
144
- import {
145
- BadRequestError, // 400
146
- UnauthorizedError, // 401
147
- PaymentRequiredError, // 402
148
- ForbiddenError, // 403
149
- NotFoundError, // 404
150
- MethodNotAllowedError, // 405
151
- NotAcceptableError, // 406
152
- RequestTimeoutError, // 408
153
- ConflictError, // 409
154
- GoneError, // 410
155
- LengthRequiredError, // 411
156
- PreconditionFailedError, // 412
157
- PayloadTooLargeError, // 413
158
- UriTooLongError, // 414
159
- UnsupportedMediaTypeError, // 415
160
- RangeNotSatisfiableError, // 416
161
- ExpectationFailedError, // 417
162
- ImATeapotError, // 418
163
- UnprocessableEntityError, // 422
164
- LockedError, // 423
165
- FailedDependencyError, // 424
166
- TooEarlyError, // 425
167
- UpgradeRequiredError, // 426
168
- PreconditionRequiredError, // 428
169
- TooManyRequestsError, // 429
170
- RequestHeaderFieldsTooLargeError, // 431
171
- UnavailableForLegalReasonsError, // 451
172
- } from '@nextrush/errors';
158
+ ```text
159
+ throw new XError(msg)
160
+ status · code · expose · details
161
+
162
+ errorHandler() catch ──▶ log (5xx→error, 4xx→warn) ──▶ err.toJSON() ──▶ ctx.json(body)
163
+
164
+ expose === false ───────┴──▶ message becomes "Internal Server Error"
173
165
  ```
174
166
 
175
- ### 5xx Server Errors
167
+ **Rule:** the `expose` flag is the privacy boundary — `true` for 4xx (client's fault, safe to explain), `false` for 5xx (your fault, don't leak). Override it deliberately, never by accident.
176
168
 
177
- ```typescript
178
- import {
179
- InternalServerError, // 500
180
- NotImplementedError, // 501
181
- BadGatewayError, // 502
182
- ServiceUnavailableError, // 503
183
- GatewayTimeoutError, // 504
184
- HttpVersionNotSupportedError, // 505
185
- VariantAlsoNegotiatesError, // 506
186
- InsufficientStorageError, // 507
187
- LoopDetectedError, // 508
188
- NotExtendedError, // 510
189
- NetworkAuthRequiredError, // 511
190
- } from '@nextrush/errors';
191
- ```
169
+ > [!TIP]
170
+ > The full error hierarchy, the throw→response sequence, and the state lifecycle (with Mermaid
171
+ > diagrams) are in [`ARCHITECTURE.md`](./ARCHITECTURE.md).
192
172
 
193
- ## Usage Examples
173
+ ---
194
174
 
195
- ### Basic Usage
175
+ ## Common tasks
196
176
 
197
- ```typescript
198
- // Simple message
199
- throw new NotFoundError('User not found');
177
+ ### Throw a typed HTTP error
200
178
 
201
- // With error code
202
- throw new BadRequestError('Invalid email format', {
203
- code: 'INVALID_EMAIL',
204
- });
179
+ ```ts
180
+ import { NotFoundError, ForbiddenError } from '@nextrush/errors';
205
181
 
206
- // With additional details
207
- throw new UnprocessableEntityError('Validation failed', {
208
- code: 'VALIDATION_ERROR',
209
- details: {
210
- email: 'Invalid format',
211
- age: 'Must be positive',
212
- },
213
- });
182
+ throw new NotFoundError('User not found'); // 404, code NOT_FOUND, expose true
183
+ throw new ForbiddenError(); // 403, default message "Forbidden"
214
184
  ```
215
185
 
216
- ### With Error Codes
217
-
218
- ```typescript
219
- throw new UnauthorizedError('Token expired', {
220
- code: 'TOKEN_EXPIRED',
221
- expose: true,
222
- });
223
-
224
- // API response:
225
- // {
226
- // "error": "Token expired",
227
- // "code": "TOKEN_EXPIRED",
228
- // "status": 401
229
- // }
230
- ```
186
+ Every class defaults its message, status, and `code` — pass a message to override the default, and options to add more.
231
187
 
232
- ## Factory Functions
188
+ ### Attach a machine code and details for API clients
233
189
 
234
- Create errors with less boilerplate:
190
+ ```ts
191
+ import { UnprocessableEntityError } from '@nextrush/errors';
235
192
 
236
- ```typescript
237
- import {
238
- badRequest,
239
- unauthorized,
240
- forbidden,
241
- notFound,
242
- methodNotAllowed,
243
- conflict,
244
- unprocessableEntity,
245
- tooManyRequests,
246
- internalError,
247
- serviceUnavailable,
248
- badGateway,
249
- gatewayTimeout,
250
- createError,
251
- } from '@nextrush/errors';
252
-
253
- // Quick creation
254
- throw badRequest('Invalid input');
255
- throw notFound('Resource not found');
256
- throw unauthorized('Please login');
257
-
258
- // With options
259
- throw tooManyRequests('Rate limit exceeded', {
260
- code: 'RATE_LIMIT',
261
- details: { retryAfter: 60 },
193
+ throw new UnprocessableEntityError('Validation failed', {
194
+ code: 'VALIDATION_ERROR',
195
+ details: { email: 'Invalid format', age: 'Must be positive' },
262
196
  });
263
-
264
- // Custom status code
265
- throw createError(418, "I'm a teapot");
197
+ // → 422 { error, message, code: 'VALIDATION_ERROR', status: 422, details: { … } }
266
198
  ```
267
199
 
268
- ## Validation Errors
200
+ `details` is surfaced in the JSON only when the error is exposed (all 4xx are, by default), and it's frozen so the error owns an immutable snapshot.
269
201
 
270
- Specialized errors for input validation:
202
+ ### Report field-level validation failures
271
203
 
272
- ```typescript
273
- import {
274
- ValidationError,
275
- RequiredFieldError,
276
- TypeMismatchError,
277
- RangeValidationError,
278
- LengthError,
279
- PatternError,
280
- InvalidEmailError,
281
- InvalidUrlError,
282
- } from '@nextrush/errors';
204
+ ```ts
205
+ import { ValidationError, RequiredFieldError } from '@nextrush/errors';
283
206
 
284
- // Generic validation error with issues
207
+ // Multiple issues at once
285
208
  throw new ValidationError([
286
209
  { path: 'email', message: 'Required', rule: 'required' },
287
210
  { path: 'age', message: 'Must be positive', rule: 'range' },
288
211
  ]);
289
212
 
290
- // Static factory methods
213
+ // Or the terse factory forms
291
214
  throw ValidationError.fromField('email', 'Invalid format', 'email');
292
- throw ValidationError.fromFields({ email: 'Required', age: 'Must be number' });
293
-
294
- // Specific field errors
215
+ throw ValidationError.fromFields({ email: 'Required', age: 'Must be a number' });
295
216
  throw new RequiredFieldError('email');
296
- throw new TypeMismatchError('age', 'number', 'string');
297
- throw new RangeValidationError('age', 18, 100);
298
- throw new LengthError('password', 8, 128);
299
- throw new PatternError('username', '^[a-z0-9]+$');
300
- throw new InvalidEmailError('email');
301
- throw new InvalidUrlError('website');
302
- ```
303
-
304
- ## Error Middleware
305
-
306
- ### errorHandler(options?)
307
-
308
- Format errors as JSON responses:
309
-
310
- ```typescript
311
- import { errorHandler } from '@nextrush/errors';
312
-
313
- app.use(
314
- errorHandler({
315
- // Include stack trace (default: false)
316
- includeStack: process.env.NODE_ENV !== 'production',
317
-
318
- // Custom error logger (default: logs 5xx as errors, 4xx as warnings)
319
- logger: (err, ctx) => {
320
- myLogger.error(err, { path: ctx.path });
321
- },
322
-
323
- // Custom error response transformer
324
- transform: (err, ctx) => ({
325
- error: err.message,
326
- status: ctx.status,
327
- }),
328
- })
329
- );
330
- ```
331
-
332
- ### notFoundHandler()
333
-
334
- Catch-all 404 handler:
335
-
336
- ```typescript
337
- import { notFoundHandler } from '@nextrush/errors';
338
-
339
- // Add after all routes
340
- app.use(notFoundHandler());
341
-
342
- // Custom message
343
- app.use(notFoundHandler('Route not found'));
344
217
  ```
345
218
 
346
- ### catchAsync(fn)
219
+ `ValidationError` serializes an `issues` array and strips each issue's `received` value, so a rejected password or token is never echoed back.
347
220
 
348
- Wrap async handlers to catch errors:
221
+ ### Install the error handler and a 404 fallback
349
222
 
350
- ```typescript
351
- import { catchAsync } from '@nextrush/errors';
223
+ ```ts
224
+ import { errorHandler, notFoundHandler } from '@nextrush/errors';
352
225
 
353
- app.get(
354
- '/users/:id',
355
- catchAsync(async (ctx) => {
356
- const user = await db.findUser(ctx.params.id);
357
- if (!user) throw new NotFoundError('User not found');
358
- ctx.json(user);
359
- })
360
- );
226
+ app.use(errorHandler({ includeStack: process.env.NODE_ENV !== 'production' })); // first
227
+ // … your routes …
228
+ app.use(notFoundHandler('Route not found')); // last — turns an unhandled 404 into JSON
361
229
  ```
362
230
 
363
- ## Error Utilities
231
+ `errorHandler()` wraps the rest of the chain in a `try/catch`; `notFoundHandler()` responds only when nothing else did and the status is `404`.
364
232
 
365
- ```typescript
366
- import { isHttpError, getErrorStatus, getSafeErrorMessage } from '@nextrush/errors';
233
+ ### Create an error by status, or rebuild one across a service boundary
367
234
 
368
- // Check if error is HTTP error
369
- if (isHttpError(err)) {
370
- console.log(err.status, err.code);
371
- }
235
+ ```ts
236
+ import { createError, codeForStatus, HttpError } from '@nextrush/errors';
372
237
 
373
- // Get status from any error
374
- const status = getErrorStatus(err); // Returns 500 for unknown errors
238
+ throw createError(413, 'Upload too large'); // PayloadTooLargeError, code PAYLOAD_TOO_LARGE
239
+ codeForStatus(429); // 'TOO_MANY_REQUESTS'
375
240
 
376
- // Get safe message (hides internal errors)
377
- const message = getSafeErrorMessage(err);
378
- // Returns generic message for 500 errors in production
241
+ // Downstream service: rebuild a typed error from the JSON it received
242
+ const restored = HttpError.fromJSON(payload); // instanceof HttpError works again
379
243
  ```
380
244
 
381
- ## Base Classes
382
-
383
- ### HttpError
245
+ `createError(status)` returns the correctly-typed class for any status that has one (falling back to a bare `HttpError` otherwise); `fromJSON()` reverses `toJSON()`.
384
246
 
385
- Base class for all HTTP errors:
247
+ ## API overview
386
248
 
387
- ```typescript
388
- import { HttpError } from '@nextrush/errors';
389
-
390
- class CustomError extends HttpError {
391
- constructor(message: string) {
392
- super(422, message, { code: 'CUSTOM_ERROR' });
393
- }
394
- }
395
- ```
249
+ The sealed public surface (ADR-0005), grouped by role.
396
250
 
397
- ### NextRushError
251
+ | Export | Signature | Since | Stability | Description |
252
+ | ------ | --------- | ----- | --------- | ----------- |
253
+ | `NextRushError` | `class (message, options?)` | `3.0.0` | Stable ✅ | Base error — owns `status`, `code`, `expose`, `details`, `cause`, `toJSON()`, `toResponse()`, `fromJSON()`. |
254
+ | `HttpError` | `class (status, message?, options?)` | `3.0.0` | Stable ✅ | Base for all HTTP status errors; resolves `code` via the registry and `expose` from the status. |
255
+ | `ValidationError` | `class (issues, message?)` | `3.0.0` | Stable ✅ | Multi-issue validation error (extends `NextRushError`, **not** `HttpError`); status `400`. |
256
+ | `createError` | `(status, message?, options?) => HttpError` | `3.0.0` | Stable ✅ | Build the correctly-typed error for a status code. |
257
+ | `errorHandler` | `(options?: ErrorHandlerOptions) => Middleware` | `3.0.0` | Stable ✅ | Catch, log, and serialize thrown errors as JSON. |
258
+ | `notFoundHandler` | `(message?: string) => Middleware` | `3.0.0` | Stable ✅ | JSON 404 fallback for unhandled requests. |
259
+ | `isHttpError` | `(error) => error is HttpError` | `3.0.0` | Stable ✅ | Type guard for `HttpError` (note: `false` for `ValidationError`). |
260
+ | `getErrorStatus` | `(error) => number` | `3.0.0` | Stable ✅ | Status from any error (any `NextRushError`, duck-typed `status`, else `500`). |
261
+ | `getSafeErrorMessage` | `(error) => string` | `3.0.0` | Stable ✅ | Message if the error is exposed, else `'Internal Server Error'`. |
262
+ | `getHttpStatusMessage` | `(status) => string` | `3.0.0` | Stable ✅ | Canonical reason phrase for a status. |
263
+ | `ERROR_CODES` | `Readonly<Record<number, string>>` | `3.1.0` | Stable ✅ | Frozen status→canonical-code registry. |
264
+ | `codeForStatus` | `(status) => string` | `3.1.0` | Stable ✅ | Canonical code for a status (`HTTP_<status>` if none). |
265
+ | `GENERIC_ERROR_CODE` · `VALIDATION_ERROR_CODE` | `string` | `3.1.0` | Stable ✅ | `'INTERNAL_ERROR'` · `'VALIDATION_ERROR'`. |
266
+ | `type HttpErrorOptions` · `ValidationIssue` · `ErrorHandlerOptions` | — | `3.0.0` | Stable ✅ | Public option/data contracts. |
398
267
 
399
- Base class for framework errors:
268
+ ### HTTP error classes
400
269
 
401
- ```typescript
402
- import { NextRushError } from '@nextrush/errors';
270
+ Each class sets its status and canonical `code`; the message defaults to the status reason phrase.
403
271
 
404
- class ConfigError extends NextRushError {
405
- constructor(message: string) {
406
- super(message, { code: 'CONFIG_ERROR' });
407
- }
408
- }
272
+ ```ts
273
+ import {
274
+ // 4xx client errors — expose: true by default
275
+ BadRequestError, // 400 BAD_REQUEST
276
+ UnauthorizedError, // 401 UNAUTHORIZED
277
+ PaymentRequiredError, // 402 PAYMENT_REQUIRED
278
+ ForbiddenError, // 403 FORBIDDEN
279
+ NotFoundError, // 404 NOT_FOUND
280
+ MethodNotAllowedError, // 405 METHOD_NOT_ALLOWED (constructor: allowedMethods first)
281
+ NotAcceptableError, // 406 NOT_ACCEPTABLE
282
+ ProxyAuthRequiredError, // 407 PROXY_AUTH_REQUIRED
283
+ RequestTimeoutError, // 408 REQUEST_TIMEOUT
284
+ ConflictError, // 409 CONFLICT
285
+ GoneError, // 410 GONE
286
+ LengthRequiredError, // 411 LENGTH_REQUIRED
287
+ PreconditionFailedError, // 412 PRECONDITION_FAILED
288
+ PayloadTooLargeError, // 413 PAYLOAD_TOO_LARGE
289
+ UriTooLongError, // 414 URI_TOO_LONG
290
+ UnsupportedMediaTypeError, // 415 UNSUPPORTED_MEDIA_TYPE
291
+ RangeNotSatisfiableError, // 416 RANGE_NOT_SATISFIABLE
292
+ ExpectationFailedError, // 417 EXPECTATION_FAILED
293
+ ImATeapotError, // 418 IM_A_TEAPOT
294
+ UnprocessableEntityError, // 422 UNPROCESSABLE_ENTITY
295
+ LockedError, // 423 LOCKED
296
+ FailedDependencyError, // 424 FAILED_DEPENDENCY
297
+ TooEarlyError, // 425 TOO_EARLY
298
+ UpgradeRequiredError, // 426 UPGRADE_REQUIRED
299
+ PreconditionRequiredError, // 428 PRECONDITION_REQUIRED
300
+ TooManyRequestsError, // 429 TOO_MANY_REQUESTS (options.retryAfter)
301
+ RequestHeaderFieldsTooLargeError, // 431 REQUEST_HEADER_FIELDS_TOO_LARGE
302
+ UnavailableForLegalReasonsError, // 451 UNAVAILABLE_FOR_LEGAL_REASONS
303
+
304
+ // 5xx server errors — expose: false by default
305
+ InternalServerError, // 500 INTERNAL_SERVER_ERROR
306
+ NotImplementedError, // 501 NOT_IMPLEMENTED
307
+ BadGatewayError, // 502 BAD_GATEWAY
308
+ ServiceUnavailableError, // 503 SERVICE_UNAVAILABLE (options.retryAfter)
309
+ GatewayTimeoutError, // 504 GATEWAY_TIMEOUT
310
+ HttpVersionNotSupportedError, // 505 HTTP_VERSION_NOT_SUPPORTED
311
+ VariantAlsoNegotiatesError, // 506 VARIANT_ALSO_NEGOTIATES
312
+ InsufficientStorageError, // 507 INSUFFICIENT_STORAGE
313
+ LoopDetectedError, // 508 LOOP_DETECTED
314
+ NotExtendedError, // 510 NOT_EXTENDED
315
+ NetworkAuthRequiredError, // 511 NETWORK_AUTH_REQUIRED
316
+ } from '@nextrush/errors';
409
317
  ```
410
318
 
411
- ## API Reference
319
+ ### Validation error classes
412
320
 
413
- ### Exports
414
-
415
- ```typescript
321
+ ```ts
416
322
  import {
417
- // Base classes
418
- HttpError,
419
- NextRushError,
420
-
421
- // 4xx errors
422
- BadRequestError,
423
- UnauthorizedError,
424
- ForbiddenError,
425
- NotFoundError,
426
- // ... all 4xx errors
427
-
428
- // 5xx errors
429
- InternalServerError,
430
- ServiceUnavailableError,
431
- // ... all 5xx errors
432
-
433
- // Validation errors
434
- ValidationError,
435
- RequiredFieldError,
436
- TypeMismatchError,
437
- // ... all validation errors
438
-
439
- // Factory functions
440
- badRequest,
441
- unauthorized,
442
- notFound,
443
- createError,
444
- // ... all factory functions
445
-
446
- // Middleware
447
- errorHandler,
448
- notFoundHandler,
449
- catchAsync,
450
-
451
- // Utilities
452
- isHttpError,
453
- getErrorStatus,
454
- getSafeErrorMessage,
323
+ ValidationError, // base — issues[], fromField(), fromFields(), hasErrorFor(), toFlatObject()
324
+ RequiredFieldError, // "<field> is required" rule: required
325
+ TypeMismatchError, // expected vs received type rule: type
326
+ RangeValidationError, // numeric min/max rule: range
327
+ LengthError, // string length min/max rule: length
328
+ PatternError, // regex mismatch rule: pattern
329
+ InvalidEmailError, // email format rule: email
330
+ InvalidUrlError, // URL format rule: url
455
331
  } from '@nextrush/errors';
456
332
  ```
457
333
 
458
- ### Types
334
+ ### Factory functions
459
335
 
460
- ```typescript
461
- import type {
462
- HttpErrorOptions,
463
- ValidationIssue,
464
- ErrorContext,
465
- ErrorHandlerOptions,
466
- ErrorMiddleware,
336
+ ```ts
337
+ import {
338
+ badRequest, unauthorized, forbidden, notFound, methodNotAllowed,
339
+ conflict, unprocessableEntity, tooManyRequests,
340
+ internalError, serviceUnavailable, badGateway, gatewayTimeout,
341
+ createError,
467
342
  } from '@nextrush/errors';
468
343
 
469
- interface HttpErrorOptions {
470
- code?: string;
471
- expose?: boolean;
472
- details?: Record<string, unknown>;
473
- cause?: unknown;
474
- }
475
-
476
- interface ValidationIssue {
477
- path: string;
478
- message: string;
479
- rule?: string;
480
- expected?: unknown;
481
- received?: unknown;
482
- }
344
+ throw notFound('User not found');
345
+ throw tooManyRequests('Rate limit exceeded', { retryAfter: 60 });
483
346
  ```
484
347
 
485
- ````
348
+ ## Options
486
349
 
487
- ## Common Mistakes
350
+ `errorHandler(options?)` is the only configurable surface. Error classes take a plain `HttpErrorOptions` object (`code`, `expose`, `details`, `cause`, `requestId`, `traceId`, `timestamp`).
488
351
 
489
- ### Mistake 1: Using Generic Errors for Specific Cases
352
+ | Option | Type | Required | Default | Security-sensitive | Description |
353
+ | ------ | ---- | -------- | ------- | ------------------ | ----------- |
354
+ | `includeStack` | `boolean` | No | `false` | ⚠️ | Append the stack trace to the JSON body. Keep `false` in production — a stack trace leaks internal paths. |
355
+ | `logger` | `(error, ctx) => void` | No | logs 5xx as `error`, 4xx as `warn` | — | Custom sink for caught errors (route to your structured logger). |
356
+ | `transform` | `(error, ctx) => Record<string, unknown>` | No | the error's own `toJSON()` | — | Replace the serialized response body shape. |
357
+ | `handlers` | `Map<ErrorClass, (error, ctx) => void>` | No | `undefined` | — | Per-error-type handlers; the first `instanceof` match runs and short-circuits serialization. |
490
358
 
491
- ```typescript
492
- // ❌ Don't do this
493
- throw new Error('User not found');
494
- // → Returns 500 Internal Server Error, no error code
359
+ ## Compatibility
495
360
 
496
- // ✅ Do this instead
497
- throw new NotFoundError('User not found');
498
- // → Returns 404 Not Found with NOT_FOUND code
499
- ````
361
+ **Requirements**
500
362
 
501
- **Why it's wrong:** Generic JavaScript `Error` becomes 500 Internal Server Error. The client can't distinguish between "not found" and "server crash".
363
+ | Requirement | Version |
364
+ | ----------- | ------- |
365
+ | NextRush | `3.x` |
366
+ | Node.js | `>=22` |
367
+ | TypeScript | `>=5.x` |
502
368
 
503
- ### Mistake 2: Exposing Internal Implementation Details
369
+ **Runtimes**
504
370
 
505
- ```typescript
506
- // Don't do this
507
- throw new InternalServerError('PostgreSQL connection timeout on pool "primary"', {
508
- expose: true, // Leaks infrastructure details!
509
- });
371
+ | Runtime | Supported | Notes |
372
+ | ------- | --------- | ----- |
373
+ | Node.js `>=22` | | ESM-only |
374
+ | Bun / Deno / Edge | ✅ / ✅ / ✅ | Uses only the native `Error` and Web-standard JavaScript; `Error.captureStackTrace` is feature-detected and skipped where absent |
510
375
 
511
- // ✅ Do this instead
512
- throw new InternalServerError('Database temporarily unavailable');
513
- // expose defaults to false for 5xx errors
514
- // Full error logged server-side for debugging
515
- ```
376
+ **Integration**
377
+ - **Peer dependencies:** none — depends only on `@nextrush/types` (types, erased at build).
378
+ - **Works with:** `@nextrush/core` (the middleware chain that runs `errorHandler()`), any `@nextrush/*` middleware that throws (`body-parser` → `413`, `rate-limit` → `429`).
379
+ - **Incompatible with:** none.
516
380
 
517
- **Why it's wrong:** Exposing database names, connection pools, or internal service names helps attackers understand your infrastructure.
381
+ > [!IMPORTANT]
382
+ > NextRush is **ESM-only, permanently** — no CommonJS build. On Node `>=22`, CommonJS consumers
383
+ > can `require()` this ESM package natively. See the
384
+ > [Module Format Policy](https://github.com/0xTanzim/nextRush#module-format-policy).
518
385
 
519
- ### Mistake 3: Forgetting Error Middleware
386
+ ---
520
387
 
521
- ```typescript
522
- // ❌ Errors won't be formatted
523
- const app = createApp();
388
+ ## Troubleshooting
524
389
 
525
- app.get('/users', (ctx) => {
526
- throw new NotFoundError('User not found'); // Crashes or returns HTML error page!
527
- });
390
+ <details>
391
+ <summary><strong>A thrown error returns <code>500</code> instead of my status code</strong></summary>
528
392
 
529
- // Add errorHandler BEFORE routes
530
- const app = createApp();
531
- app.use(errorHandler()); // This catches and formats errors
393
+ **Cause:** you threw a generic `Error` (or `errorHandler()` isn't registered), so there's no `status` to read — the handler falls back to `500`. **Fix:** throw a typed error and register `errorHandler()` before your routes.
532
394
 
533
- app.get('/users', (ctx) => {
534
- throw new NotFoundError('User not found'); // Returns proper JSON
535
- });
395
+ ```ts
396
+ app.use(errorHandler()); // register first
397
+ throw new NotFoundError('User not found'); // ← typed, not `new Error(...)`
536
398
  ```
537
399
 
538
- ### Mistake 4: Using Errors for Control Flow
400
+ </details>
539
401
 
540
- ```typescript
541
- // Don't use errors for expected business logic
542
- async function getUser(id: string): Promise<User> {
543
- const user = await db.findUser(id);
544
- if (!user) throw new NotFoundError(); // Too expensive for expected case
545
- return user;
546
- }
402
+ <details>
403
+ <summary><strong>My 5xx response shows "Internal Server Error", not my message</strong></summary>
547
404
 
548
- // Use nullable returns for expected cases
549
- async function getUser(id: string): Promise<User | null> {
550
- return await db.findUser(id);
551
- }
405
+ **Cause:** this is intended 5xx errors default to `expose: false`, so `toJSON()` replaces the message to avoid leaking internals. The full error is still passed to the logger. **Fix:** only override `expose` for a message you've confirmed is client-safe.
552
406
 
553
- // ✅ Throw errors at the HTTP boundary
554
- app.get('/users/:id', async (ctx) => {
555
- const user = await getUser(ctx.params.id);
556
- if (!user) throw new NotFoundError('User not found');
557
- ctx.json(user);
558
- });
407
+ ```ts
408
+ throw new ServiceUnavailableError('Down for maintenance until 14:00 UTC', { expose: true });
559
409
  ```
560
410
 
561
- **Why it's wrong:** Throwing errors for control flow is expensive (stack trace construction) and makes code harder to reason about.
562
-
563
- ### Mistake 5: Missing Error Codes for API Clients
411
+ </details>
564
412
 
565
- ```typescript
566
- // Clients can't handle errors programmatically
567
- throw new BadRequestError('Invalid email format');
568
- // Response: {"message": "Invalid email format"} // No code!
413
+ <details>
414
+ <summary><strong><code>isHttpError(validationError)</code> returns <code>false</code></strong></summary>
569
415
 
570
- // Include error codes
571
- throw new BadRequestError('Invalid email format', {
572
- code: 'INVALID_EMAIL',
573
- });
574
- // Response: {"message": "Invalid email format", "code": "INVALID_EMAIL"}
416
+ **Cause:** `ValidationError` extends `NextRushError` directly, not `HttpError`, so the `HttpError` type guard rejects it. **Fix:** use `getErrorStatus()` / `getSafeErrorMessage()` (which handle any `NextRushError`), or check `instanceof ValidationError`. `errorHandler()` already serializes it correctly.
575
417
 
576
- // Client can now:
577
- if (error.code === 'INVALID_EMAIL') {
578
- // Show email field error
579
- }
418
+ ```ts
419
+ import { getErrorStatus, ValidationError } from '@nextrush/errors';
420
+ if (err instanceof ValidationError) { /* read err.issues */ }
421
+ const status = getErrorStatus(err); // 400 for a ValidationError
580
422
  ```
581
423
 
582
- ## When NOT to Use
424
+ </details>
583
425
 
584
- ### Don't Use for Validation in Reusable Libraries
426
+ <details>
427
+ <summary><strong><code>details</code> or <code>cause</code> is missing from the JSON response</strong></summary>
585
428
 
586
- If you're building a reusable library (not an HTTP handler), return validation results instead of throwing:
429
+ **Cause:** both are serialized only when the error is exposed (`expose === true`) — a non-exposed 5xx hides them to prevent leaking internal detail. **Fix:** read `error.cause` server-side (never gated), or set a client-safe error with `expose: true` if the detail is meant for the client.
587
430
 
588
- ```typescript
589
- // ❌ Don't throw in library code
590
- export function validateEmail(email: string): string {
591
- if (!isValid(email)) throw new InvalidEmailError(); // Caller can't control behavior
592
- return email;
593
- }
431
+ </details>
594
432
 
595
- // ✅ Return validation result
596
- export function validateEmail(email: string): { valid: boolean; error?: string } {
597
- return isValid(email) ? { valid: true } : { valid: false, error: 'Invalid email format' };
598
- }
433
+ ## FAQ
599
434
 
600
- // Throw at the HTTP boundary
601
- app.post('/users', (ctx) => {
602
- const result = validateEmail(ctx.body.email);
603
- if (!result.valid) {
604
- throw new BadRequestError(result.error!, { code: 'INVALID_EMAIL' });
605
- }
606
- });
607
- ```
435
+ **Can I use `@nextrush/errors` without the rest of NextRush?**
436
+ Yes. The error classes depend on nothing but the native `Error`. `errorHandler()` needs the `Context` / `Middleware` type contracts from `@nextrush/types`, which are erased at build — there's no runtime dependency to install.
608
437
 
609
- **Why:** Libraries should be composable. Throwing errors forces a specific error handling strategy on consumers.
438
+ **Why ESM-only?**
439
+ See the [Module Format Policy](https://github.com/0xTanzim/nextRush#module-format-policy).
610
440
 
611
- ### Don't Use for Expected Empty Results
441
+ **Does it work on Bun, Deno, and Edge?**
442
+ Yes. The classes use only the native `Error` and standard JavaScript; the V8-specific `Error.captureStackTrace` is feature-detected and skipped where it isn't available, so behavior is identical across runtimes.
612
443
 
613
- ```typescript
614
- // Don't throw for queries that might return nothing
615
- async function searchUsers(query: string): Promise<User[]> {
616
- const users = await db.search(query);
617
- if (users.length === 0) throw new NotFoundError(); // Expected case!
618
- return users;
619
- }
444
+ **Why doesn't `ValidationError` extend `HttpError`?**
445
+ By design a validation failure is a `NextRushError` that carries structured `issues`, not a status alone. It still resolves to `400`, is serialized by `errorHandler()`, and is recognized by `getErrorStatus()`; only the `isHttpError()` guard (which is `HttpError`-specific) returns `false` for it.
620
446
 
621
- // ✅ Return empty arrays for "no results"
622
- async function searchUsers(query: string): Promise<User[]> {
623
- return await db.search(query); // Empty array is valid
624
- }
447
+ ---
625
448
 
626
- // Only throw when the resource *should* exist
627
- app.get('/users/:id', async (ctx) => {
628
- const user = await db.getById(ctx.params.id);
629
- if (!user) throw new NotFoundError(); // Specific ID should exist
630
- ctx.json(user);
631
- });
632
- ```
449
+ ## Package relationships
633
450
 
634
- ### Don't Use for Non-HTTP Contexts
635
-
636
- ```typescript
637
- // Don't use HTTP errors in background jobs
638
- async function processQueue() {
639
- const job = await queue.pop();
640
- if (!job) throw new NotFoundError(); // Wrong abstraction!
641
- }
642
-
643
- // ✅ Use domain-specific errors or return values
644
- async function processQueue() {
645
- const job = await queue.pop();
646
- if (!job) return { processed: false, reason: 'queue_empty' };
647
- // Process job...
648
- return { processed: true };
649
- }
451
+ ```text
452
+ depends on @nextrush/types (Context / Middleware / Next contracts, types only)
453
+ @nextrush/errors ─────────────▶
454
+ often used with @nextrush/core (runs errorHandler() in the middleware chain)
455
+ usually used next @nextrush/validation · @nextrush/body-parser (throw these errors)
650
456
  ```
651
457
 
652
- ## Runtime Compatibility
458
+ - **Depends on:** [`@nextrush/types`](../types) — the shared `Context` / `Middleware` / `Next` contracts, used only by the middleware (types, erased at build).
459
+ - **Often used with:** [`@nextrush/core`](../core) — the `Application` whose middleware chain runs `errorHandler()`; every handler throws these classes.
460
+ - **Usually used next:** [`@nextrush/validation`](../middleware/validation) (surfaces `ValidationError`-shaped failures) · [`@nextrush/body-parser`](../middleware/body-parser) (throws `413`) · [`@nextrush/rate-limit`](../middleware/rate-limit) (throws `429`).
461
+ - **Alternative:** none — the `HttpError` hierarchy is the framework's error contract.
653
462
 
654
- Works on all NextRush-supported runtimes:
463
+ ## Architecture
655
464
 
656
- | Runtime | Supported | Notes |
657
- | ------------------- | --------- | ------------ |
658
- | Node.js 20+ | ✅ | Full support |
659
- | Bun 1.0+ | ✅ | Full support |
660
- | Deno 2.0+ | ✅ | Full support |
661
- | Cloudflare Workers | ✅ | Full support |
662
- | Vercel Edge Runtime | ✅ | Full support |
465
+ Maintaining or contributing to this package? The internal design — the `NextRushError` → `HttpError` /
466
+ `ValidationError` hierarchy, the `expose` privacy boundary, `cause`-chain serialization, the
467
+ throw→response lifecycle, the architectural invariants, and the decisions and trade-offs behind them
468
+ (with diagrams) is in **[`ARCHITECTURE.md`](./ARCHITECTURE.md)**. Design history:
469
+ [ADR-0005 (package tiers & sealed surface)](https://github.com/0xTanzim/nextRush/blob/main/docs/adr/ADR-0005-package-tiers-sealed-surface-deprecation.md).
663
470
 
664
- **Zero external dependencies.** Uses only standard JavaScript `Error` APIs and NextRush types.
471
+ ## Resources
665
472
 
666
- ## Best Practices
473
+ - 📖 **Learn** — [Documentation](https://0xtanzim.github.io/nextRush/docs) · [Error handling guide](https://0xtanzim.github.io/nextRush/docs/guides/error-handling) · [Architecture](./ARCHITECTURE.md) · [RFCs](https://github.com/0xTanzim/nextRush/tree/main/docs/RFC)
474
+ - 📝 **Changelog** — [CHANGELOG.md](./CHANGELOG.md)
475
+ - 🐛 **Report an issue** — [GitHub Issues](https://github.com/0xTanzim/nextRush/issues)
476
+ - 🤝 **Contribute** — [CONTRIBUTING.md](https://github.com/0xTanzim/nextRush/blob/main/CONTRIBUTING.md)
667
477
 
668
- 1. **Use specific errors**: `NotFoundError` over generic `HttpError`
669
- 2. **Include error codes**: Help API consumers handle errors programmatically
670
- 3. **Don't expose internals**: Keep `expose: false` for 5xx errors (default)
671
- 4. **Add error middleware first**: Before all routes
672
- 5. **Validate early**: Throw validation errors at the start of handlers
673
- 6. **Return nulls for expected "not found"**: Throw errors only at HTTP boundaries
478
+ ---
674
479
 
675
- ## License
676
-
677
- MIT
678
-
679
- ```
680
-
681
- ```
480
+ MIT © [Tanzim Hossain](https://github.com/0xTanzim)