@erangamadhushan/express-error-handle-middleware 1.6.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) 2026 Eranga Madhushan (EM956)
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,345 @@
1
+ # @erangamadhushan/express-error-handle-middleware
2
+
3
+ Advanced TypeScript-based error handling middleware for Express.js.
4
+
5
+ ![npm version](https://img.shields.io/npm/v/@erangamadhushan/express-error-handle-middleware)
6
+ ![npm downloads](https://img.shields.io/npm/dm/@erangamadhushan/express-error-handle-middleware)
7
+ ![CI](https://img.shields.io/npm/ci/@erangamadhushan/express-error-handle-middleware)
8
+ ![License](https://img.shields.io/npm/l/@erangamadhushan/express-error-handle-middleware)
9
+
10
+ ---
11
+
12
+ ## ✨ Features
13
+
14
+ - Async handler wrapper
15
+ - Custom `ApiError` class
16
+ - Predefined error classes (NotFound, BadRequest, etc.)
17
+ - `createError` helper for clean DX
18
+ - Global error middleware
19
+ - 404 Not Found middleware
20
+ - Logger integration (Pino, Winston, custom)
21
+ - Request ID and correlation metadata
22
+ - Structured logger context
23
+ - Custom error adapters
24
+ - Reusable adapter registry
25
+ - MongoDB duplicate key smart parsing
26
+ - Zod validation error formatting
27
+ - Standardized error response structure
28
+ - RFC 9457-style problem details
29
+ - Custom response serializers
30
+ - Configurable message exposure
31
+ - Production-safe stack handling
32
+ - ESM + CommonJS support
33
+ - Full TypeScript support
34
+
35
+ ---
36
+
37
+ ## 📦 Installation
38
+
39
+ ```bash
40
+ npm install @erangamadhushan/express-error-handle-middleware
41
+ ```
42
+
43
+ ## Compatibility
44
+
45
+ - Node.js 18.18 or newer
46
+ - Express 4.18 or newer, including Express 5
47
+ - Zod 4 is optional and only required when using Zod validation errors
48
+
49
+ Express is a peer dependency because the middleware uses the host application's Express runtime. The package does not bundle Express, Zod, or other runtime dependencies.
50
+
51
+ ## 🚀 Quick Start
52
+
53
+ ```ts
54
+ import express from "express";
55
+ import {
56
+ asyncHandler,
57
+ createError,
58
+ notFoundMiddleware,
59
+ errorMiddleware,
60
+ requestIdMiddleware,
61
+ } from "@erangamadhushan/express-error-handle-middleware";
62
+
63
+ const app = express();
64
+ app.use(express.json());
65
+ app.use(requestIdMiddleware());
66
+
67
+ app.get(
68
+ "/users/:id",
69
+ asyncHandler(async (req, res) => {
70
+ if (req.params.id !== "1") {
71
+ throw createError.notFound("User not found");
72
+ }
73
+
74
+ res.json({ id: 1, name: "John" });
75
+ }),
76
+ );
77
+
78
+ app.use(notFoundMiddleware);
79
+ app.use(errorMiddleware());
80
+
81
+ app.listen(5000);
82
+ ```
83
+
84
+ ## 🧠 Recommended Workflow
85
+
86
+ - Wrap all controllers using asyncHandler.
87
+ - Throw errors using createError.* (recommended) or ApiError.
88
+ - Add requestIdMiddleware near the start of the application.
89
+ - Use global errorMiddleware.
90
+ - Integrate a logger in production.
91
+ - Use problem details or a custom serializer when an API-specific response contract is required.
92
+
93
+ ## 🧩 Error Creation Options
94
+
95
+ ### Using createError (Recommended)
96
+
97
+ ```ts
98
+ throw createError.badRequest("Invalid input");
99
+ throw createError.notFound("User not found");
100
+ throw createError.unauthorized();
101
+ ```
102
+
103
+ ### Using ApiError
104
+
105
+ ```ts
106
+ throw new ApiError("User not found", 404, "USER_NOT_FOUND");
107
+ ```
108
+
109
+ ### Using Predefined Classes
110
+
111
+ ```ts
112
+ import { NotFoundError } from "...";
113
+
114
+ throw new NotFoundError("User not found");
115
+ ```
116
+
117
+ ## 📤 Response Format
118
+
119
+ All errors follow a consistent structure:
120
+
121
+ ```json
122
+ {
123
+ "success": false,
124
+ "statusCode": 404,
125
+ "message": "User not found",
126
+ "error": "NotFoundError",
127
+ "code": "NOT_FOUND"
128
+ }
129
+ ```
130
+
131
+ The legacy response format is the default for compatibility. Enable RFC 9457-style responses with `responseFormat: "problem"`:
132
+
133
+ ```json
134
+ {
135
+ "type": "urn:express-error-kit:NOT_FOUND",
136
+ "title": "NotFoundError",
137
+ "status": 404,
138
+ "detail": "User not found",
139
+ "instance": "/users/42",
140
+ "code": "NOT_FOUND",
141
+ "requestId": "request-123"
142
+ }
143
+ ```
144
+
145
+ ## 🧠 Smart MongoDB Error Handling
146
+
147
+ ### Duplicate key errors are automatically formatted
148
+
149
+ ```ts
150
+ // Mongo duplicate key error
151
+ {
152
+ code: 11000,
153
+ keyValue: { email: "test@example.com" }
154
+ }
155
+ ```
156
+
157
+ Response:
158
+
159
+ ```json
160
+ {
161
+ "success": false,
162
+ "message": "email already exists",
163
+ "code": "DUPLICATE_FIELD"
164
+ }
165
+ ```
166
+
167
+ ## 🧾 Zod Validation Formatting
168
+
169
+ If using Zod:
170
+
171
+ ```ts
172
+ throw new ZodError([...]);
173
+ ```
174
+
175
+ Response:
176
+
177
+ ```json
178
+ {
179
+ "success": false,
180
+ "message": "email: Expected string",
181
+ "code": "VALIDATION_ERROR"
182
+ }
183
+ ```
184
+
185
+ ## 🪵 Logger Integration
186
+
187
+ Use any logger:
188
+
189
+ ```ts
190
+ import pino from "pino";
191
+
192
+ const logger = pino();
193
+
194
+ app.use(
195
+ errorMiddleware({
196
+ logger: (error, context) => logger.error({ error, ...context }),
197
+ showStack: false,
198
+ }),
199
+ );
200
+ ```
201
+
202
+ The logger receives the normalized error and structured request context containing `requestId`, `method`, `url`, `path`, `statusCode`, `code`, and `errorName`.
203
+
204
+ ## 📚 Middleware Order (Important)
205
+
206
+ ```js
207
+ app.use(routes);
208
+
209
+ app.use(notFoundMiddleware);
210
+ app.use(errorMiddleware());
211
+ ```
212
+
213
+ ## 🧩 Creating Custom Errors
214
+
215
+ ### Using createError (Recommended)
216
+
217
+ ```ts
218
+ import { createError } from "@erangamadhushan/express-error-handle-middleware";
219
+
220
+ throw createError.notFound("User not found");
221
+ throw createError.badRequest("Invalid input");
222
+ throw createError.unauthorized();
223
+ ```
224
+
225
+ ### Using ApiError
226
+
227
+ ```ts
228
+ import { ApiError } from "@erangamadhushan/express-error-handle-middleware";
229
+
230
+ throw new ApiError("User not found", 404, "USER_NOT_FOUND");
231
+ ```
232
+
233
+ You can extend it like this:
234
+
235
+ ```ts
236
+ import { ApiError } from "@erangamadhushan/express-error-handle-middleware";
237
+
238
+ class ValidationError extends ApiError {
239
+ constructor(message: string) {
240
+ super(message, 400, "VALIDATION_ERROR");
241
+ }
242
+ }
243
+ ```
244
+
245
+ ## ⚙️ Configuration Options
246
+
247
+ ```ts
248
+ errorMiddleware(options?: {
249
+ logger?: (error: unknown, context: ErrorLogContext) => void;
250
+ showStack?: boolean;
251
+ expose?: boolean | ((error: ApiError, context: ErrorRequestContext) => boolean);
252
+ responseFormat?: "legacy" | "problem";
253
+ serializer?: (error: ApiError, context: ErrorSerializationContext) => unknown;
254
+ requestId?: {
255
+ headerName?: string;
256
+ generator?: () => string;
257
+ };
258
+ adapters?: readonly ErrorAdapter[] | ErrorAdapterRegistry;
259
+ });
260
+ ```
261
+
262
+ Request IDs are read from `x-request-id` by default, generated when absent, returned in the response header, and exposed through the logger and serializer context. Use `requestIdMiddleware()` near the start of the application to correlate successful requests as well as failures. Set `responseFormat: "problem"` for an RFC 9457-style response with `application/problem+json` content type.
263
+
264
+ Use `expose` to control whether an error message is returned. In production, 500-level messages are hidden by default.
265
+
266
+ Custom adapters can map application or library errors to `ApiError` instances. They run before the built-in MongoDB and Zod adapters.
267
+
268
+ ```ts
269
+ import {
270
+ ApiError,
271
+ errorMiddleware,
272
+ } from "@erangamadhushan/express-error-handle-middleware";
273
+
274
+ const domainErrorAdapter = (error: unknown) => {
275
+ if (
276
+ typeof error === "object" &&
277
+ error !== null &&
278
+ "type" in error &&
279
+ error.type === "domain_error"
280
+ ) {
281
+ return new ApiError("The resource is in an invalid state", 409, "INVALID_STATE");
282
+ }
283
+
284
+ return undefined;
285
+ };
286
+
287
+ app.use(errorMiddleware({ adapters: [domainErrorAdapter] }));
288
+ ```
289
+
290
+ For shared application configuration, register adapters once and reuse the registry:
291
+
292
+ ```ts
293
+ const adapters = new ErrorAdapterRegistry()
294
+ .register(domainErrorAdapter);
295
+
296
+ app.use(errorMiddleware({ adapters }));
297
+ ```
298
+
299
+ ## 🛡 Production Behavior
300
+
301
+ - Stack traces hidden automatically in production
302
+ - 500-level messages hidden automatically in production
303
+ - Request IDs returned through the response header
304
+ - Structured context available to loggers and serializers
305
+ - Clean legacy JSON or problem-details response formats
306
+ - Centralized error normalization and exposure control
307
+
308
+ ## 🧪 Testing
309
+
310
+ ```bash
311
+ npm ci
312
+ npm test -- --runInBand
313
+ npx tsc --noEmit
314
+ npm run build
315
+ npm run smoke:package
316
+ ```
317
+
318
+ ## Docker Validation
319
+
320
+ The repository Dockerfile is a reproducible validation image for contributors and CI. It runs the typecheck, test suite, package build, and packed-package smoke test; it is not a production runtime image because this project publishes an npm library rather than an Express application.
321
+
322
+ ```bash
323
+ docker build --tag express-error-handle-kit-validation .
324
+ docker run --rm express-error-handle-kit-validation
325
+ ```
326
+
327
+ ## 🔄 Automated Releases
328
+
329
+ - Conventional commits
330
+ - semantic-release
331
+ - GitHub Actions CI
332
+ - Automatic versioning and changelog generation
333
+
334
+ ## Contributing
335
+
336
+ Contributions are welcome!
337
+
338
+ Please read CONTRIBUTING.md before opening a pull request.
339
+
340
+ ## Engineering Documents
341
+
342
+ - [Architecture](ARCHITECTURE.md)
343
+ - [Error contract ADR](ADRs/001-error-contract.md)
344
+ - [Adapter system ADR](ADRs/002-adapter-system.md)
345
+ - [Compatibility matrix](COMPATIBILITY.md)
package/dist/index.cjs ADDED
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ApiError: () => ApiError,
24
+ BadRequestError: () => BadRequestError,
25
+ ConflictError: () => ConflictError,
26
+ ErrorAdapterRegistry: () => ErrorAdapterRegistry,
27
+ ForbiddenError: () => ForbiddenError,
28
+ InternalServerError: () => InternalServerError,
29
+ NotFoundError: () => NotFoundError,
30
+ UnauthorizedError: () => UnauthorizedError,
31
+ ValidationError: () => ValidationError,
32
+ asyncHandler: () => asyncHandler,
33
+ createError: () => createError,
34
+ createRequestContext: () => createRequestContext,
35
+ errorMiddleware: () => errorMiddleware,
36
+ getOrCreateRequestContext: () => getOrCreateRequestContext,
37
+ mongoDuplicateKeyAdapter: () => mongoDuplicateKeyAdapter,
38
+ normalizeError: () => normalizeError,
39
+ notFoundMiddleware: () => notFoundMiddleware,
40
+ requestIdMiddleware: () => requestIdMiddleware,
41
+ serializeError: () => serializeError,
42
+ zodErrorAdapter: () => zodErrorAdapter
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/asyncHandler.ts
47
+ var asyncHandler = (fn) => {
48
+ return (req, res, next) => {
49
+ Promise.resolve().then(() => fn(req, res, next)).catch(next);
50
+ };
51
+ };
52
+
53
+ // src/ApiError.ts
54
+ var ApiError = class extends Error {
55
+ constructor(message, statusCode, code) {
56
+ super(message);
57
+ this.statusCode = statusCode;
58
+ this.code = code;
59
+ this.isOperational = true;
60
+ Object.setPrototypeOf(this, new.target.prototype);
61
+ Error.captureStackTrace(this);
62
+ }
63
+ };
64
+
65
+ // src/adapters/mongoDuplicateKey.ts
66
+ var isRecord = (value) => typeof value === "object" && value !== null;
67
+ var mongoDuplicateKeyAdapter = (error) => {
68
+ if (!isRecord(error) || error.code !== 11e3) {
69
+ return void 0;
70
+ }
71
+ const keyValue = isRecord(error.keyValue) ? error.keyValue : void 0;
72
+ const field = keyValue ? Object.keys(keyValue)[0] : void 0;
73
+ const message = field ? `${field} already exists` : "Duplicate value";
74
+ return new ApiError(message, 400, "DUPLICATE_FIELD");
75
+ };
76
+
77
+ // src/adapters/zod.ts
78
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
79
+ var zodErrorAdapter = (error) => {
80
+ if (!isRecord2(error) || error.name !== "ZodError" || !Array.isArray(error.issues)) {
81
+ return void 0;
82
+ }
83
+ const message = error.issues.map((issue) => {
84
+ if (!isRecord2(issue)) {
85
+ return String(issue);
86
+ }
87
+ const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
88
+ return path ? `${path}: ${String(issue.message)}` : String(issue.message);
89
+ }).join(", ");
90
+ return new ApiError(message || "Validation error", 400, "VALIDATION_ERROR");
91
+ };
92
+
93
+ // src/types.ts
94
+ var ErrorAdapterRegistry = class {
95
+ constructor(adapters = []) {
96
+ this.registeredAdapters = [...adapters];
97
+ }
98
+ register(adapter) {
99
+ this.registeredAdapters.push(adapter);
100
+ return this;
101
+ }
102
+ registerMany(adapters) {
103
+ this.registeredAdapters.push(...adapters);
104
+ return this;
105
+ }
106
+ getAdapters() {
107
+ return this.registeredAdapters;
108
+ }
109
+ };
110
+
111
+ // src/normalizeError.ts
112
+ var defaultAdapters = [
113
+ mongoDuplicateKeyAdapter,
114
+ zodErrorAdapter
115
+ ];
116
+ var normalizeError = (error, adapters = []) => {
117
+ if (error instanceof ApiError) {
118
+ return error;
119
+ }
120
+ const registeredAdapters = adapters instanceof ErrorAdapterRegistry ? adapters.getAdapters() : adapters;
121
+ for (const adapter of [...registeredAdapters, ...defaultAdapters]) {
122
+ const normalizedError = adapter(error);
123
+ if (normalizedError) {
124
+ return normalizedError;
125
+ }
126
+ }
127
+ return new ApiError("Internal Server Error", 500, "INTERNAL_ERROR");
128
+ };
129
+
130
+ // src/requestContext.ts
131
+ var import_node_crypto = require("crypto");
132
+ var defaultHeaderName = "x-request-id";
133
+ var createRequestContext = (req, res, options = {}) => {
134
+ var _a, _b, _c;
135
+ const headerName = (_a = options.headerName) != null ? _a : defaultHeaderName;
136
+ const incomingRequestId = (_b = req.get(headerName)) == null ? void 0 : _b.trim();
137
+ const requestId = incomingRequestId || ((_c = options.generator) != null ? _c : import_node_crypto.randomUUID)();
138
+ res.setHeader(headerName, requestId);
139
+ res.locals.requestId = requestId;
140
+ return {
141
+ requestId,
142
+ method: req.method,
143
+ url: req.originalUrl || req.url,
144
+ path: req.path
145
+ };
146
+ };
147
+ var requestIdMiddleware = (options = {}) => (req, res, next) => {
148
+ createRequestContext(req, res, options);
149
+ next();
150
+ };
151
+ var getOrCreateRequestContext = (req, res, options = {}) => {
152
+ const existingRequestId = res.locals.requestId;
153
+ if (typeof existingRequestId === "string" && existingRequestId.length > 0) {
154
+ return {
155
+ requestId: existingRequestId,
156
+ method: req.method,
157
+ url: req.originalUrl || req.url,
158
+ path: req.path
159
+ };
160
+ }
161
+ return createRequestContext(req, res, options);
162
+ };
163
+
164
+ // src/serializeError.ts
165
+ var serializeError = (error, options = {}) => {
166
+ var _a, _b, _c, _d;
167
+ const isProduction = (_a = options.isProduction) != null ? _a : process.env.NODE_ENV === "production";
168
+ const exposeMessage = (_b = options.exposeMessage) != null ? _b : !(isProduction && error.statusCode >= 500);
169
+ const message = exposeMessage ? error.message : "Internal Server Error";
170
+ const includeStack = options.showStack === true && !isProduction;
171
+ if (options.format === "problem") {
172
+ const problem = {
173
+ type: error.code ? `urn:express-error-kit:${error.code}` : "about:blank",
174
+ title: error.constructor.name,
175
+ status: error.statusCode,
176
+ ...message && { detail: message },
177
+ ...((_c = options.context) == null ? void 0 : _c.url) && { instance: options.context.url },
178
+ ...error.code && { code: error.code },
179
+ ...((_d = options.context) == null ? void 0 : _d.requestId) && { requestId: options.context.requestId }
180
+ };
181
+ return problem;
182
+ }
183
+ return {
184
+ success: false,
185
+ statusCode: error.statusCode,
186
+ message,
187
+ error: error.constructor.name,
188
+ ...error.code && { code: error.code },
189
+ ...includeStack && error.stack ? { stack: error.stack } : {}
190
+ };
191
+ };
192
+
193
+ // src/errorMiddleware.ts
194
+ var errorMiddleware = (options = {}) => (err, req, res, next) => {
195
+ var _a, _b;
196
+ if (res.headersSent) {
197
+ next(err);
198
+ return;
199
+ }
200
+ const requestContext = getOrCreateRequestContext(req, res, options.requestId);
201
+ const processedError = normalizeError(err, (_a = options.adapters) != null ? _a : []);
202
+ const isProduction = process.env.NODE_ENV === "production";
203
+ const exposeMessage = typeof options.expose === "function" ? options.expose(processedError, requestContext) : (_b = options.expose) != null ? _b : !(isProduction && processedError.statusCode >= 500);
204
+ const serializationContext = {
205
+ ...requestContext,
206
+ isProduction,
207
+ exposeMessage,
208
+ includeStack: options.showStack === true && !isProduction
209
+ };
210
+ const logContext = {
211
+ ...requestContext,
212
+ statusCode: processedError.statusCode,
213
+ code: processedError.code,
214
+ errorName: processedError.constructor.name
215
+ };
216
+ if (options.logger) {
217
+ options.logger(processedError, logContext);
218
+ } else {
219
+ console.error({ error: processedError, context: logContext });
220
+ }
221
+ const response = options.serializer ? options.serializer(processedError, serializationContext) : serializeError(processedError, {
222
+ exposeMessage,
223
+ format: options.responseFormat,
224
+ showStack: options.showStack,
225
+ context: serializationContext
226
+ });
227
+ if (options.responseFormat === "problem") {
228
+ res.type("application/problem+json");
229
+ }
230
+ res.status(processedError.statusCode).json(response);
231
+ };
232
+
233
+ // src/notFoundMiddleware.ts
234
+ var notFoundMiddleware = (req, res, next) => {
235
+ next(new ApiError("Route not found", 404, "NOT_FOUND"));
236
+ };
237
+
238
+ // src/errors.ts
239
+ var NotFoundError = class extends ApiError {
240
+ constructor(message = "Resource not found") {
241
+ super(message, 404, "NOT_FOUND");
242
+ }
243
+ };
244
+ var BadRequestError = class extends ApiError {
245
+ constructor(message = "Bad request") {
246
+ super(message, 400, "BAD_REQUEST");
247
+ }
248
+ };
249
+ var UnauthorizedError = class extends ApiError {
250
+ constructor(message = "Unauthorized") {
251
+ super(message, 401, "UNAUTHORIZED");
252
+ }
253
+ };
254
+ var ForbiddenError = class extends ApiError {
255
+ constructor(message = "Forbidden") {
256
+ super(message, 403, "FORBIDDEN");
257
+ }
258
+ };
259
+ var ConflictError = class extends ApiError {
260
+ constructor(message = "Conflict") {
261
+ super(message, 409, "CONFLICT");
262
+ }
263
+ };
264
+ var ValidationError = class extends ApiError {
265
+ constructor(message = "Validation error") {
266
+ super(message, 400, "VALIDATION_ERROR");
267
+ }
268
+ };
269
+ var InternalServerError = class extends ApiError {
270
+ constructor(message = "Internal Server Error") {
271
+ super(message, 500, "INTERNAL_ERROR");
272
+ }
273
+ };
274
+
275
+ // src/createError.ts
276
+ var createError = {
277
+ badRequest: (message = "Bad request") => new BadRequestError(message),
278
+ unauthorized: (message = "Unauthorized") => new UnauthorizedError(message),
279
+ forbidden: (message = "Forbidden") => new ForbiddenError(message),
280
+ notFound: (message = "Resource not found") => new NotFoundError(message),
281
+ conflict: (message = "Conflict") => new ConflictError(message),
282
+ validation: (message = "Validation error") => new ValidationError(message),
283
+ internal: (message = "Internal Server Error") => new InternalServerError(message),
284
+ // Custom flexible error
285
+ custom: (message, statusCode, code) => new ApiError(message, statusCode, code)
286
+ };
287
+ // Annotate the CommonJS export names for ESM import in node:
288
+ 0 && (module.exports = {
289
+ ApiError,
290
+ BadRequestError,
291
+ ConflictError,
292
+ ErrorAdapterRegistry,
293
+ ForbiddenError,
294
+ InternalServerError,
295
+ NotFoundError,
296
+ UnauthorizedError,
297
+ ValidationError,
298
+ asyncHandler,
299
+ createError,
300
+ createRequestContext,
301
+ errorMiddleware,
302
+ getOrCreateRequestContext,
303
+ mongoDuplicateKeyAdapter,
304
+ normalizeError,
305
+ notFoundMiddleware,
306
+ requestIdMiddleware,
307
+ serializeError,
308
+ zodErrorAdapter
309
+ });
@@ -0,0 +1,126 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+
3
+ declare const asyncHandler: (fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown> | unknown) => (req: Request, res: Response, next: NextFunction) => void;
4
+
5
+ declare class ApiError extends Error {
6
+ statusCode: number;
7
+ code?: string;
8
+ isOperational: boolean;
9
+ constructor(message: string, statusCode: number, code?: string);
10
+ }
11
+
12
+ type ErrorAdapter = (error: unknown) => ApiError | undefined;
13
+ interface ErrorRequestContext {
14
+ requestId: string;
15
+ method: string;
16
+ url: string;
17
+ path: string;
18
+ }
19
+ interface ErrorLogContext extends ErrorRequestContext {
20
+ statusCode: number;
21
+ code?: string;
22
+ errorName: string;
23
+ }
24
+ interface ErrorSerializationContext extends ErrorRequestContext {
25
+ isProduction: boolean;
26
+ exposeMessage: boolean;
27
+ includeStack: boolean;
28
+ }
29
+ type ErrorExposurePolicy = boolean | ((error: ApiError, context: ErrorRequestContext) => boolean);
30
+ type ErrorResponseSerializer = (error: ApiError, context: ErrorSerializationContext) => unknown;
31
+ type ErrorResponseFormat = "legacy" | "problem";
32
+ interface RequestIdOptions {
33
+ headerName?: string;
34
+ generator?: () => string;
35
+ }
36
+ interface ErrorMiddlewareOptions {
37
+ logger?: (error: unknown, context: ErrorLogContext) => void;
38
+ showStack?: boolean;
39
+ expose?: ErrorExposurePolicy;
40
+ responseFormat?: ErrorResponseFormat;
41
+ serializer?: ErrorResponseSerializer;
42
+ requestId?: RequestIdOptions;
43
+ adapters?: readonly ErrorAdapter[] | ErrorAdapterRegistry;
44
+ }
45
+ declare class ErrorAdapterRegistry {
46
+ private readonly registeredAdapters;
47
+ constructor(adapters?: readonly ErrorAdapter[]);
48
+ register(adapter: ErrorAdapter): this;
49
+ registerMany(adapters: readonly ErrorAdapter[]): this;
50
+ getAdapters(): readonly ErrorAdapter[];
51
+ }
52
+
53
+ declare const errorMiddleware: (options?: ErrorMiddlewareOptions) => (err: unknown, req: Request, res: Response, next: NextFunction) => void;
54
+
55
+ declare const notFoundMiddleware: (req: Request, res: Response, next: NextFunction) => void;
56
+
57
+ declare class NotFoundError extends ApiError {
58
+ constructor(message?: string);
59
+ }
60
+ declare class BadRequestError extends ApiError {
61
+ constructor(message?: string);
62
+ }
63
+ declare class UnauthorizedError extends ApiError {
64
+ constructor(message?: string);
65
+ }
66
+ declare class ForbiddenError extends ApiError {
67
+ constructor(message?: string);
68
+ }
69
+ declare class ConflictError extends ApiError {
70
+ constructor(message?: string);
71
+ }
72
+ declare class ValidationError extends ApiError {
73
+ constructor(message?: string);
74
+ }
75
+ declare class InternalServerError extends ApiError {
76
+ constructor(message?: string);
77
+ }
78
+
79
+ declare const createError: {
80
+ badRequest: (message?: string) => BadRequestError;
81
+ unauthorized: (message?: string) => UnauthorizedError;
82
+ forbidden: (message?: string) => ForbiddenError;
83
+ notFound: (message?: string) => NotFoundError;
84
+ conflict: (message?: string) => ConflictError;
85
+ validation: (message?: string) => ValidationError;
86
+ internal: (message?: string) => InternalServerError;
87
+ custom: (message: string, statusCode: number, code?: string) => ApiError;
88
+ };
89
+
90
+ declare const normalizeError: (error: unknown, adapters?: readonly ErrorAdapter[] | ErrorAdapterRegistry) => ApiError;
91
+
92
+ interface SerializedError {
93
+ success: false;
94
+ statusCode: number;
95
+ message: string;
96
+ error: string;
97
+ code?: string;
98
+ stack?: string;
99
+ }
100
+ interface SerializeErrorOptions {
101
+ isProduction?: boolean;
102
+ showStack?: boolean;
103
+ exposeMessage?: boolean;
104
+ format?: ErrorResponseFormat;
105
+ context?: ErrorSerializationContext;
106
+ }
107
+ interface ProblemDetails {
108
+ type: string;
109
+ title: string;
110
+ status: number;
111
+ detail?: string;
112
+ instance?: string;
113
+ code?: string;
114
+ requestId?: string;
115
+ }
116
+ declare const serializeError: (error: ApiError, options?: SerializeErrorOptions) => SerializedError | ProblemDetails;
117
+
118
+ declare const mongoDuplicateKeyAdapter: ErrorAdapter;
119
+
120
+ declare const zodErrorAdapter: ErrorAdapter;
121
+
122
+ declare const createRequestContext: (req: Request, res: Response, options?: RequestIdOptions) => ErrorRequestContext;
123
+ declare const requestIdMiddleware: (options?: RequestIdOptions) => (req: Request, res: Response, next: NextFunction) => void;
124
+ declare const getOrCreateRequestContext: (req: Request, res: Response, options?: RequestIdOptions) => ErrorRequestContext;
125
+
126
+ export { ApiError, BadRequestError, ConflictError, type ErrorAdapter, ErrorAdapterRegistry, type ErrorExposurePolicy, type ErrorLogContext, type ErrorMiddlewareOptions, type ErrorRequestContext, type ErrorResponseFormat, type ErrorResponseSerializer, type ErrorSerializationContext, ForbiddenError, InternalServerError, NotFoundError, type ProblemDetails, type RequestIdOptions, type SerializeErrorOptions, type SerializedError, UnauthorizedError, ValidationError, asyncHandler, createError, createRequestContext, errorMiddleware, getOrCreateRequestContext, mongoDuplicateKeyAdapter, normalizeError, notFoundMiddleware, requestIdMiddleware, serializeError, zodErrorAdapter };
@@ -0,0 +1,126 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+
3
+ declare const asyncHandler: (fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown> | unknown) => (req: Request, res: Response, next: NextFunction) => void;
4
+
5
+ declare class ApiError extends Error {
6
+ statusCode: number;
7
+ code?: string;
8
+ isOperational: boolean;
9
+ constructor(message: string, statusCode: number, code?: string);
10
+ }
11
+
12
+ type ErrorAdapter = (error: unknown) => ApiError | undefined;
13
+ interface ErrorRequestContext {
14
+ requestId: string;
15
+ method: string;
16
+ url: string;
17
+ path: string;
18
+ }
19
+ interface ErrorLogContext extends ErrorRequestContext {
20
+ statusCode: number;
21
+ code?: string;
22
+ errorName: string;
23
+ }
24
+ interface ErrorSerializationContext extends ErrorRequestContext {
25
+ isProduction: boolean;
26
+ exposeMessage: boolean;
27
+ includeStack: boolean;
28
+ }
29
+ type ErrorExposurePolicy = boolean | ((error: ApiError, context: ErrorRequestContext) => boolean);
30
+ type ErrorResponseSerializer = (error: ApiError, context: ErrorSerializationContext) => unknown;
31
+ type ErrorResponseFormat = "legacy" | "problem";
32
+ interface RequestIdOptions {
33
+ headerName?: string;
34
+ generator?: () => string;
35
+ }
36
+ interface ErrorMiddlewareOptions {
37
+ logger?: (error: unknown, context: ErrorLogContext) => void;
38
+ showStack?: boolean;
39
+ expose?: ErrorExposurePolicy;
40
+ responseFormat?: ErrorResponseFormat;
41
+ serializer?: ErrorResponseSerializer;
42
+ requestId?: RequestIdOptions;
43
+ adapters?: readonly ErrorAdapter[] | ErrorAdapterRegistry;
44
+ }
45
+ declare class ErrorAdapterRegistry {
46
+ private readonly registeredAdapters;
47
+ constructor(adapters?: readonly ErrorAdapter[]);
48
+ register(adapter: ErrorAdapter): this;
49
+ registerMany(adapters: readonly ErrorAdapter[]): this;
50
+ getAdapters(): readonly ErrorAdapter[];
51
+ }
52
+
53
+ declare const errorMiddleware: (options?: ErrorMiddlewareOptions) => (err: unknown, req: Request, res: Response, next: NextFunction) => void;
54
+
55
+ declare const notFoundMiddleware: (req: Request, res: Response, next: NextFunction) => void;
56
+
57
+ declare class NotFoundError extends ApiError {
58
+ constructor(message?: string);
59
+ }
60
+ declare class BadRequestError extends ApiError {
61
+ constructor(message?: string);
62
+ }
63
+ declare class UnauthorizedError extends ApiError {
64
+ constructor(message?: string);
65
+ }
66
+ declare class ForbiddenError extends ApiError {
67
+ constructor(message?: string);
68
+ }
69
+ declare class ConflictError extends ApiError {
70
+ constructor(message?: string);
71
+ }
72
+ declare class ValidationError extends ApiError {
73
+ constructor(message?: string);
74
+ }
75
+ declare class InternalServerError extends ApiError {
76
+ constructor(message?: string);
77
+ }
78
+
79
+ declare const createError: {
80
+ badRequest: (message?: string) => BadRequestError;
81
+ unauthorized: (message?: string) => UnauthorizedError;
82
+ forbidden: (message?: string) => ForbiddenError;
83
+ notFound: (message?: string) => NotFoundError;
84
+ conflict: (message?: string) => ConflictError;
85
+ validation: (message?: string) => ValidationError;
86
+ internal: (message?: string) => InternalServerError;
87
+ custom: (message: string, statusCode: number, code?: string) => ApiError;
88
+ };
89
+
90
+ declare const normalizeError: (error: unknown, adapters?: readonly ErrorAdapter[] | ErrorAdapterRegistry) => ApiError;
91
+
92
+ interface SerializedError {
93
+ success: false;
94
+ statusCode: number;
95
+ message: string;
96
+ error: string;
97
+ code?: string;
98
+ stack?: string;
99
+ }
100
+ interface SerializeErrorOptions {
101
+ isProduction?: boolean;
102
+ showStack?: boolean;
103
+ exposeMessage?: boolean;
104
+ format?: ErrorResponseFormat;
105
+ context?: ErrorSerializationContext;
106
+ }
107
+ interface ProblemDetails {
108
+ type: string;
109
+ title: string;
110
+ status: number;
111
+ detail?: string;
112
+ instance?: string;
113
+ code?: string;
114
+ requestId?: string;
115
+ }
116
+ declare const serializeError: (error: ApiError, options?: SerializeErrorOptions) => SerializedError | ProblemDetails;
117
+
118
+ declare const mongoDuplicateKeyAdapter: ErrorAdapter;
119
+
120
+ declare const zodErrorAdapter: ErrorAdapter;
121
+
122
+ declare const createRequestContext: (req: Request, res: Response, options?: RequestIdOptions) => ErrorRequestContext;
123
+ declare const requestIdMiddleware: (options?: RequestIdOptions) => (req: Request, res: Response, next: NextFunction) => void;
124
+ declare const getOrCreateRequestContext: (req: Request, res: Response, options?: RequestIdOptions) => ErrorRequestContext;
125
+
126
+ export { ApiError, BadRequestError, ConflictError, type ErrorAdapter, ErrorAdapterRegistry, type ErrorExposurePolicy, type ErrorLogContext, type ErrorMiddlewareOptions, type ErrorRequestContext, type ErrorResponseFormat, type ErrorResponseSerializer, type ErrorSerializationContext, ForbiddenError, InternalServerError, NotFoundError, type ProblemDetails, type RequestIdOptions, type SerializeErrorOptions, type SerializedError, UnauthorizedError, ValidationError, asyncHandler, createError, createRequestContext, errorMiddleware, getOrCreateRequestContext, mongoDuplicateKeyAdapter, normalizeError, notFoundMiddleware, requestIdMiddleware, serializeError, zodErrorAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,263 @@
1
+ // src/asyncHandler.ts
2
+ var asyncHandler = (fn) => {
3
+ return (req, res, next) => {
4
+ Promise.resolve().then(() => fn(req, res, next)).catch(next);
5
+ };
6
+ };
7
+
8
+ // src/ApiError.ts
9
+ var ApiError = class extends Error {
10
+ constructor(message, statusCode, code) {
11
+ super(message);
12
+ this.statusCode = statusCode;
13
+ this.code = code;
14
+ this.isOperational = true;
15
+ Object.setPrototypeOf(this, new.target.prototype);
16
+ Error.captureStackTrace(this);
17
+ }
18
+ };
19
+
20
+ // src/adapters/mongoDuplicateKey.ts
21
+ var isRecord = (value) => typeof value === "object" && value !== null;
22
+ var mongoDuplicateKeyAdapter = (error) => {
23
+ if (!isRecord(error) || error.code !== 11e3) {
24
+ return void 0;
25
+ }
26
+ const keyValue = isRecord(error.keyValue) ? error.keyValue : void 0;
27
+ const field = keyValue ? Object.keys(keyValue)[0] : void 0;
28
+ const message = field ? `${field} already exists` : "Duplicate value";
29
+ return new ApiError(message, 400, "DUPLICATE_FIELD");
30
+ };
31
+
32
+ // src/adapters/zod.ts
33
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
34
+ var zodErrorAdapter = (error) => {
35
+ if (!isRecord2(error) || error.name !== "ZodError" || !Array.isArray(error.issues)) {
36
+ return void 0;
37
+ }
38
+ const message = error.issues.map((issue) => {
39
+ if (!isRecord2(issue)) {
40
+ return String(issue);
41
+ }
42
+ const path = Array.isArray(issue.path) ? issue.path.join(".") : "";
43
+ return path ? `${path}: ${String(issue.message)}` : String(issue.message);
44
+ }).join(", ");
45
+ return new ApiError(message || "Validation error", 400, "VALIDATION_ERROR");
46
+ };
47
+
48
+ // src/types.ts
49
+ var ErrorAdapterRegistry = class {
50
+ constructor(adapters = []) {
51
+ this.registeredAdapters = [...adapters];
52
+ }
53
+ register(adapter) {
54
+ this.registeredAdapters.push(adapter);
55
+ return this;
56
+ }
57
+ registerMany(adapters) {
58
+ this.registeredAdapters.push(...adapters);
59
+ return this;
60
+ }
61
+ getAdapters() {
62
+ return this.registeredAdapters;
63
+ }
64
+ };
65
+
66
+ // src/normalizeError.ts
67
+ var defaultAdapters = [
68
+ mongoDuplicateKeyAdapter,
69
+ zodErrorAdapter
70
+ ];
71
+ var normalizeError = (error, adapters = []) => {
72
+ if (error instanceof ApiError) {
73
+ return error;
74
+ }
75
+ const registeredAdapters = adapters instanceof ErrorAdapterRegistry ? adapters.getAdapters() : adapters;
76
+ for (const adapter of [...registeredAdapters, ...defaultAdapters]) {
77
+ const normalizedError = adapter(error);
78
+ if (normalizedError) {
79
+ return normalizedError;
80
+ }
81
+ }
82
+ return new ApiError("Internal Server Error", 500, "INTERNAL_ERROR");
83
+ };
84
+
85
+ // src/requestContext.ts
86
+ import { randomUUID } from "crypto";
87
+ var defaultHeaderName = "x-request-id";
88
+ var createRequestContext = (req, res, options = {}) => {
89
+ var _a, _b, _c;
90
+ const headerName = (_a = options.headerName) != null ? _a : defaultHeaderName;
91
+ const incomingRequestId = (_b = req.get(headerName)) == null ? void 0 : _b.trim();
92
+ const requestId = incomingRequestId || ((_c = options.generator) != null ? _c : randomUUID)();
93
+ res.setHeader(headerName, requestId);
94
+ res.locals.requestId = requestId;
95
+ return {
96
+ requestId,
97
+ method: req.method,
98
+ url: req.originalUrl || req.url,
99
+ path: req.path
100
+ };
101
+ };
102
+ var requestIdMiddleware = (options = {}) => (req, res, next) => {
103
+ createRequestContext(req, res, options);
104
+ next();
105
+ };
106
+ var getOrCreateRequestContext = (req, res, options = {}) => {
107
+ const existingRequestId = res.locals.requestId;
108
+ if (typeof existingRequestId === "string" && existingRequestId.length > 0) {
109
+ return {
110
+ requestId: existingRequestId,
111
+ method: req.method,
112
+ url: req.originalUrl || req.url,
113
+ path: req.path
114
+ };
115
+ }
116
+ return createRequestContext(req, res, options);
117
+ };
118
+
119
+ // src/serializeError.ts
120
+ var serializeError = (error, options = {}) => {
121
+ var _a, _b, _c, _d;
122
+ const isProduction = (_a = options.isProduction) != null ? _a : process.env.NODE_ENV === "production";
123
+ const exposeMessage = (_b = options.exposeMessage) != null ? _b : !(isProduction && error.statusCode >= 500);
124
+ const message = exposeMessage ? error.message : "Internal Server Error";
125
+ const includeStack = options.showStack === true && !isProduction;
126
+ if (options.format === "problem") {
127
+ const problem = {
128
+ type: error.code ? `urn:express-error-kit:${error.code}` : "about:blank",
129
+ title: error.constructor.name,
130
+ status: error.statusCode,
131
+ ...message && { detail: message },
132
+ ...((_c = options.context) == null ? void 0 : _c.url) && { instance: options.context.url },
133
+ ...error.code && { code: error.code },
134
+ ...((_d = options.context) == null ? void 0 : _d.requestId) && { requestId: options.context.requestId }
135
+ };
136
+ return problem;
137
+ }
138
+ return {
139
+ success: false,
140
+ statusCode: error.statusCode,
141
+ message,
142
+ error: error.constructor.name,
143
+ ...error.code && { code: error.code },
144
+ ...includeStack && error.stack ? { stack: error.stack } : {}
145
+ };
146
+ };
147
+
148
+ // src/errorMiddleware.ts
149
+ var errorMiddleware = (options = {}) => (err, req, res, next) => {
150
+ var _a, _b;
151
+ if (res.headersSent) {
152
+ next(err);
153
+ return;
154
+ }
155
+ const requestContext = getOrCreateRequestContext(req, res, options.requestId);
156
+ const processedError = normalizeError(err, (_a = options.adapters) != null ? _a : []);
157
+ const isProduction = process.env.NODE_ENV === "production";
158
+ const exposeMessage = typeof options.expose === "function" ? options.expose(processedError, requestContext) : (_b = options.expose) != null ? _b : !(isProduction && processedError.statusCode >= 500);
159
+ const serializationContext = {
160
+ ...requestContext,
161
+ isProduction,
162
+ exposeMessage,
163
+ includeStack: options.showStack === true && !isProduction
164
+ };
165
+ const logContext = {
166
+ ...requestContext,
167
+ statusCode: processedError.statusCode,
168
+ code: processedError.code,
169
+ errorName: processedError.constructor.name
170
+ };
171
+ if (options.logger) {
172
+ options.logger(processedError, logContext);
173
+ } else {
174
+ console.error({ error: processedError, context: logContext });
175
+ }
176
+ const response = options.serializer ? options.serializer(processedError, serializationContext) : serializeError(processedError, {
177
+ exposeMessage,
178
+ format: options.responseFormat,
179
+ showStack: options.showStack,
180
+ context: serializationContext
181
+ });
182
+ if (options.responseFormat === "problem") {
183
+ res.type("application/problem+json");
184
+ }
185
+ res.status(processedError.statusCode).json(response);
186
+ };
187
+
188
+ // src/notFoundMiddleware.ts
189
+ var notFoundMiddleware = (req, res, next) => {
190
+ next(new ApiError("Route not found", 404, "NOT_FOUND"));
191
+ };
192
+
193
+ // src/errors.ts
194
+ var NotFoundError = class extends ApiError {
195
+ constructor(message = "Resource not found") {
196
+ super(message, 404, "NOT_FOUND");
197
+ }
198
+ };
199
+ var BadRequestError = class extends ApiError {
200
+ constructor(message = "Bad request") {
201
+ super(message, 400, "BAD_REQUEST");
202
+ }
203
+ };
204
+ var UnauthorizedError = class extends ApiError {
205
+ constructor(message = "Unauthorized") {
206
+ super(message, 401, "UNAUTHORIZED");
207
+ }
208
+ };
209
+ var ForbiddenError = class extends ApiError {
210
+ constructor(message = "Forbidden") {
211
+ super(message, 403, "FORBIDDEN");
212
+ }
213
+ };
214
+ var ConflictError = class extends ApiError {
215
+ constructor(message = "Conflict") {
216
+ super(message, 409, "CONFLICT");
217
+ }
218
+ };
219
+ var ValidationError = class extends ApiError {
220
+ constructor(message = "Validation error") {
221
+ super(message, 400, "VALIDATION_ERROR");
222
+ }
223
+ };
224
+ var InternalServerError = class extends ApiError {
225
+ constructor(message = "Internal Server Error") {
226
+ super(message, 500, "INTERNAL_ERROR");
227
+ }
228
+ };
229
+
230
+ // src/createError.ts
231
+ var createError = {
232
+ badRequest: (message = "Bad request") => new BadRequestError(message),
233
+ unauthorized: (message = "Unauthorized") => new UnauthorizedError(message),
234
+ forbidden: (message = "Forbidden") => new ForbiddenError(message),
235
+ notFound: (message = "Resource not found") => new NotFoundError(message),
236
+ conflict: (message = "Conflict") => new ConflictError(message),
237
+ validation: (message = "Validation error") => new ValidationError(message),
238
+ internal: (message = "Internal Server Error") => new InternalServerError(message),
239
+ // Custom flexible error
240
+ custom: (message, statusCode, code) => new ApiError(message, statusCode, code)
241
+ };
242
+ export {
243
+ ApiError,
244
+ BadRequestError,
245
+ ConflictError,
246
+ ErrorAdapterRegistry,
247
+ ForbiddenError,
248
+ InternalServerError,
249
+ NotFoundError,
250
+ UnauthorizedError,
251
+ ValidationError,
252
+ asyncHandler,
253
+ createError,
254
+ createRequestContext,
255
+ errorMiddleware,
256
+ getOrCreateRequestContext,
257
+ mongoDuplicateKeyAdapter,
258
+ normalizeError,
259
+ notFoundMiddleware,
260
+ requestIdMiddleware,
261
+ serializeError,
262
+ zodErrorAdapter
263
+ };
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@erangamadhushan/express-error-handle-middleware",
3
+ "version": "1.6.0",
4
+ "description": "Advanced TypeScript error handling middleware for Express applications.",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format cjs,esm --dts",
20
+ "smoke:package": "npm run build && node scripts/verify-package.mjs",
21
+ "prepublishOnly": "npm run build",
22
+ "test": "jest --coverage"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/Erangamadhushan/express-error-handle-middleware.git"
27
+ },
28
+ "keywords": [
29
+ "express",
30
+ "middleware",
31
+ "error-handler",
32
+ "typescript"
33
+ ],
34
+ "author": "Eranga Madhushan",
35
+ "license": "MIT",
36
+ "type": "module",
37
+ "bugs": {
38
+ "url": "https://github.com/Erangamadhushan/express-error-handle-middleware/issues"
39
+ },
40
+ "homepage": "https://github.com/Erangamadhushan/express-error-handle-middleware#readme",
41
+ "engines": {
42
+ "node": ">=18.18"
43
+ },
44
+ "peerDependencies": {
45
+ "express": "^4.18.0 || ^5.0.0",
46
+ "zod": "^4.3.6"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "zod": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "devDependencies": {
54
+ "@semantic-release/changelog": "^6.0.3",
55
+ "@semantic-release/git": "^10.0.1",
56
+ "@semantic-release/github": "^12.0.6",
57
+ "@semantic-release/npm": "^13.1.4",
58
+ "@types/express": "^5.0.6",
59
+ "@types/jest": "^30.0.0",
60
+ "@types/supertest": "^6.0.3",
61
+ "conventional-changelog-conventionalcommits": "^9.1.0",
62
+ "express": "^5.2.1",
63
+ "jest": "^30.2.0",
64
+ "semantic-release": "^25.0.3",
65
+ "supertest": "^7.2.2",
66
+ "ts-jest": "^29.4.6",
67
+ "tsup": "^8.5.1",
68
+ "typescript": "^5.9.3",
69
+ "zod": "^4.3.6"
70
+ }
71
+ }