@batkit/express-middleware 0.1.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 +21 -0
- package/README.md +331 -0
- package/dist/error-handler.d.ts +23 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +138 -0
- package/dist/error-handler.js.map +1 -0
- package/dist/index.cjs +169 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/log-context-middleware.d.ts +8 -0
- package/dist/log-context-middleware.d.ts.map +1 -0
- package/dist/log-context-middleware.js +8 -0
- package/dist/log-context-middleware.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ken Courville
|
|
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,331 @@
|
|
|
1
|
+
# @batkit/express-middleware
|
|
2
|
+
|
|
3
|
+
Express middleware for error handling with RFC 9457 Problem Details format.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @batkit/express-middleware express
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Production-ready Express middleware for error handling with automatic RFC 9457 Problem Details formatting.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- ✅ RFC 9457 Problem Details error responses
|
|
18
|
+
- ✅ Handles common error types (AppError, Axios, Zod)
|
|
19
|
+
- ✅ Customizable error formatters
|
|
20
|
+
- ✅ Support for async route handlers
|
|
21
|
+
- ✅ TypeScript-first
|
|
22
|
+
- ✅ Production and development modes
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Basic Setup
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { errorHandler } from '@batkit/express-middleware';
|
|
30
|
+
import express from 'express';
|
|
31
|
+
|
|
32
|
+
const app = express();
|
|
33
|
+
|
|
34
|
+
// Your routes
|
|
35
|
+
app.get('/users/:id', (req, res) => {
|
|
36
|
+
// ... your logic
|
|
37
|
+
res.json({ user: {} });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Add error handler (must be LAST)
|
|
41
|
+
app.use(errorHandler());
|
|
42
|
+
|
|
43
|
+
app.listen(3000);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Error Handling
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { asyncHandler, errorHandler } from "@batkit/express-middleware";
|
|
50
|
+
import { NotFoundError, ValidationError } from "@batkit/errors";
|
|
51
|
+
import express from "express";
|
|
52
|
+
|
|
53
|
+
const app = express();
|
|
54
|
+
|
|
55
|
+
// Async route handler with error handling
|
|
56
|
+
app.get(
|
|
57
|
+
"/users/:id",
|
|
58
|
+
asyncHandler(async (req, res) => {
|
|
59
|
+
const user = await db.users.findById(req.params.id);
|
|
60
|
+
|
|
61
|
+
if (!user) {
|
|
62
|
+
// Automatically converted to RFC 9457 format
|
|
63
|
+
throw new NotFoundError("User", req.params.id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
res.json(user);
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Synchronous route
|
|
71
|
+
app.post("/users", (req, res) => {
|
|
72
|
+
if (!req.body.email) {
|
|
73
|
+
throw new ValidationError("Invalid user data", [
|
|
74
|
+
{ field: "email", message: "Email is required" },
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
res.json({ created: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Error handler converts all errors to RFC 9457
|
|
82
|
+
app.use(errorHandler());
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Async-local log context
|
|
86
|
+
|
|
87
|
+
`logContextMiddleware` wraps each request in [`runWithContext`](https://nodejs.org/api/async_context.html#asynchronous-context-tracking) from `@batkit/logger/async-local` so your code can call `mergeLogContext`, use `getLogContext`, and attach a [`ContextualLoggerProvider`](../logger/README.md)—without `req.logger` or Express-specific types inside the logger package.
|
|
88
|
+
|
|
89
|
+
**Background:** see the toolkit guide [Understanding AsyncLocalStorage](../../docs/async-local-storage.md).
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { logContextMiddleware } from "@batkit/express-middleware";
|
|
93
|
+
import { LoggerFacade } from "@batkit/logger";
|
|
94
|
+
import { ContextualLoggerProvider, mergeLogContext } from "@batkit/logger/async-local";
|
|
95
|
+
import { PinoLoggerProvider } from "@batkit/logger-pino";
|
|
96
|
+
import express from "express";
|
|
97
|
+
import { randomUUID } from "node:crypto";
|
|
98
|
+
|
|
99
|
+
LoggerFacade.setProvider(new ContextualLoggerProvider(new PinoLoggerProvider({ level: "info" })));
|
|
100
|
+
|
|
101
|
+
const app = express();
|
|
102
|
+
app.use(express.json());
|
|
103
|
+
|
|
104
|
+
app.use(
|
|
105
|
+
logContextMiddleware({
|
|
106
|
+
initialContext: (req) => ({
|
|
107
|
+
requestId: req.get("x-request-id") ?? randomUUID(),
|
|
108
|
+
}),
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
app.post("/orders/:id/submit", (req, res) => {
|
|
113
|
+
mergeLogContext({ transactionId: req.get("x-transaction-id") ?? randomUUID() });
|
|
114
|
+
LoggerFacade.getLogger("orders").info("Submitting"); // includes requestId + transactionId in structured output
|
|
115
|
+
res.status(204).end();
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Custom Error Formatter
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { errorHandler, type ErrorFormatter } from "@batkit/express-middleware";
|
|
123
|
+
import type { ExtendedProblemDetails } from "@batkit/rfc9457";
|
|
124
|
+
|
|
125
|
+
class CustomErrorFormatter implements ErrorFormatter {
|
|
126
|
+
canFormat(error: unknown): boolean {
|
|
127
|
+
return error instanceof MyCustomError;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
format(error: MyCustomError): ExtendedProblemDetails {
|
|
131
|
+
return {
|
|
132
|
+
type: "error:custom",
|
|
133
|
+
title: "Custom Error",
|
|
134
|
+
status: 400,
|
|
135
|
+
detail: error.message,
|
|
136
|
+
customField: error.customData,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
app.use(
|
|
142
|
+
errorHandler({
|
|
143
|
+
formatters: [new CustomErrorFormatter()],
|
|
144
|
+
includeStack: true, // Include stack traces
|
|
145
|
+
logErrors: true,
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## API Reference
|
|
151
|
+
|
|
152
|
+
### Log context middleware
|
|
153
|
+
|
|
154
|
+
#### `logContextMiddleware(options?): Middleware`
|
|
155
|
+
|
|
156
|
+
Runs `next()` inside `runWithContext(initialContext(req), …)` so nested async work can use `getLogContext` / `mergeLogContext` from `@batkit/logger/async-local`.
|
|
157
|
+
|
|
158
|
+
**Options:**
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
interface LogContextMiddlewareOptions {
|
|
162
|
+
/** Default: `() => ({})` */
|
|
163
|
+
initialContext?: (req: Request) => Record<string, import("@batkit/logger").LogValue>;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Returns:** Express middleware function. Mount it **early** (after body parsers if you need `req` fields).
|
|
168
|
+
|
|
169
|
+
**Note:** Prefer a synchronous call to `next()` inside the ALS scope. Avoid `async` middleware that `await`s before calling `next()` unless the entire downstream pipeline stays in the same async context.
|
|
170
|
+
|
|
171
|
+
### Error Handler
|
|
172
|
+
|
|
173
|
+
#### `errorHandler(options?): ErrorMiddleware`
|
|
174
|
+
|
|
175
|
+
Creates error handling middleware that converts errors to RFC 9457 format.
|
|
176
|
+
|
|
177
|
+
**Options:**
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
interface ErrorHandlerOptions {
|
|
181
|
+
formatters?: ErrorFormatter[]; // Custom error formatters
|
|
182
|
+
includeStack?: boolean; // Include stack traces (default: only in dev)
|
|
183
|
+
logErrors?: boolean; // Whether to log errors (default: true)
|
|
184
|
+
onError?: (error: unknown, req: Request) => void; // Custom error logger
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Returns:** Express error middleware function (must have 4 parameters)
|
|
189
|
+
|
|
190
|
+
#### `asyncHandler(fn): Middleware`
|
|
191
|
+
|
|
192
|
+
Wraps async route handlers to catch errors.
|
|
193
|
+
|
|
194
|
+
**Parameters:**
|
|
195
|
+
|
|
196
|
+
- `fn`: Async route handler function
|
|
197
|
+
|
|
198
|
+
**Returns:** Express middleware function
|
|
199
|
+
|
|
200
|
+
### Types
|
|
201
|
+
|
|
202
|
+
#### `RequestContext`
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
interface RequestContext {
|
|
206
|
+
requestId: string;
|
|
207
|
+
logger: Logger;
|
|
208
|
+
userId?: string;
|
|
209
|
+
metadata: Record<string, unknown>;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### `ErrorFormatter`
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
interface ErrorFormatter {
|
|
217
|
+
canFormat(error: unknown): boolean;
|
|
218
|
+
format(error: unknown): ExtendedProblemDetails;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Error Response Format
|
|
223
|
+
|
|
224
|
+
All errors are returned in RFC 9457 format:
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"type": "error:not-found",
|
|
229
|
+
"title": "Resource Not Found",
|
|
230
|
+
"status": 404,
|
|
231
|
+
"detail": "User with id '123' was not found",
|
|
232
|
+
"instance": "/users/123",
|
|
233
|
+
"entityName": "User",
|
|
234
|
+
"entityId": "123"
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Built-in Error Support
|
|
239
|
+
|
|
240
|
+
The default error formatter handles:
|
|
241
|
+
|
|
242
|
+
- **@batkit/errors** - All AppError subclasses
|
|
243
|
+
- **Axios errors** - Formatted as upstream service errors
|
|
244
|
+
- **Zod errors** - Formatted as validation errors
|
|
245
|
+
- **Standard Error** - Formatted as internal server errors
|
|
246
|
+
|
|
247
|
+
## Best Practices
|
|
248
|
+
|
|
249
|
+
1. **Add `logContextMiddleware` early** in the middleware chain when using async-local logging
|
|
250
|
+
2. **Add `errorHandler` last** after all routes
|
|
251
|
+
3. **Use `asyncHandler`** for async routes to catch errors
|
|
252
|
+
4. **Use `ContextualLoggerProvider`** with `LoggerFacade` (or your DI) so structured logs pick up ALS fields automatically
|
|
253
|
+
5. **Throw `@batkit/errors`** for consistent error handling
|
|
254
|
+
6. **Don't expose stack traces** in production (default behavior)
|
|
255
|
+
|
|
256
|
+
## Example: Complete Setup
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { errorHandler, asyncHandler, logContextMiddleware } from "@batkit/express-middleware";
|
|
260
|
+
import { NotFoundError, ValidationError } from "@batkit/errors";
|
|
261
|
+
import { LoggerFacade } from "@batkit/logger";
|
|
262
|
+
import { ContextualLoggerProvider } from "@batkit/logger/async-local";
|
|
263
|
+
import { PinoLoggerProvider } from "@batkit/logger-pino";
|
|
264
|
+
import express from "express";
|
|
265
|
+
import { randomUUID } from "node:crypto";
|
|
266
|
+
|
|
267
|
+
LoggerFacade.setProvider(new ContextualLoggerProvider(new PinoLoggerProvider({ level: "info" })));
|
|
268
|
+
|
|
269
|
+
const app = express();
|
|
270
|
+
const logger = LoggerFacade.getLogger("server");
|
|
271
|
+
|
|
272
|
+
// Middleware
|
|
273
|
+
app.use(express.json());
|
|
274
|
+
app.use(
|
|
275
|
+
logContextMiddleware({
|
|
276
|
+
initialContext: (req) => ({ requestId: req.get("x-request-id") ?? randomUUID() }),
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// Routes
|
|
281
|
+
app.get("/health", (req, res) => {
|
|
282
|
+
res.json({ status: "ok" });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
app.get(
|
|
286
|
+
"/users/:id",
|
|
287
|
+
asyncHandler(async (req, res) => {
|
|
288
|
+
LoggerFacade.getLogger("users").info("Fetching user", { userId: req.params.id });
|
|
289
|
+
|
|
290
|
+
const user = await db.users.findById(req.params.id);
|
|
291
|
+
if (!user) {
|
|
292
|
+
throw new NotFoundError("User", req.params.id);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
res.json(user);
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
app.post(
|
|
300
|
+
"/users",
|
|
301
|
+
asyncHandler(async (req, res) => {
|
|
302
|
+
const validation = validateUser(req.body);
|
|
303
|
+
if (!validation.success) {
|
|
304
|
+
throw new ValidationError("Invalid user data", validation.errors);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const user = await db.users.create(req.body);
|
|
308
|
+
res.status(201).json(user);
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// Error handler (MUST be last)
|
|
313
|
+
app.use(
|
|
314
|
+
errorHandler({
|
|
315
|
+
includeStack: true,
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const PORT = process.env.PORT || 3000;
|
|
320
|
+
app.listen(PORT, () => {
|
|
321
|
+
logger.info(`Server listening on port ${PORT}`);
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## TypeScript
|
|
326
|
+
|
|
327
|
+
`logContextMiddleware` uses standard Express typings. Use `getLogContext()` from `@batkit/logger/async-local` when you need the current bag of log fields in a handler or service.
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
MIT © Ken Courville
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ExtendedProblemDetails } from "@batkit/rfc9457";
|
|
2
|
+
import type { NextFunction, Request, Response } from "express";
|
|
3
|
+
export interface ErrorFormatter {
|
|
4
|
+
canFormat(error: unknown): boolean;
|
|
5
|
+
format(error: unknown): ExtendedProblemDetails;
|
|
6
|
+
}
|
|
7
|
+
export declare class DefaultErrorFormatter implements ErrorFormatter {
|
|
8
|
+
canFormat(error: unknown): boolean;
|
|
9
|
+
format(error: unknown): ExtendedProblemDetails;
|
|
10
|
+
private isAxiosError;
|
|
11
|
+
private isZodError;
|
|
12
|
+
private formatAxiosError;
|
|
13
|
+
private formatZodError;
|
|
14
|
+
}
|
|
15
|
+
export interface ErrorHandlerOptions {
|
|
16
|
+
formatters?: ErrorFormatter[];
|
|
17
|
+
includeStack?: boolean;
|
|
18
|
+
logErrors?: boolean;
|
|
19
|
+
onError?: (error: unknown, req: Request) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function errorHandler(options?: ErrorHandlerOptions): (error: Error, req: Request, res: Response, next: NextFunction) => void;
|
|
22
|
+
export declare function asyncHandler(fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>): (req: Request, res: Response, next: NextFunction) => void;
|
|
23
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,sBAAsB,EAG5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAM/D,MAAM,WAAW,cAAc;IAI7B,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAKnC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB,CAAC;CAChD;AAKD,qBAAa,qBAAsB,YAAW,cAAc;IAC1D,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IASlC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB;IAmC9C,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,cAAc;CAevB;AAKD,MAAM,WAAW,mBAAmB;IAIlC,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAK9B,YAAY,CAAC,EAAE,OAAO,CAAC;IAKvB,SAAS,CAAC,EAAE,OAAO,CAAC;IAKpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAClD;AAwBD,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAwEzE;AAsBD,wBAAgB,YAAY,CAC1B,EAAE,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,GACxE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAI3D"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { isAppError } from "@batkit/errors";
|
|
2
|
+
import { LoggerFacade } from "@batkit/logger";
|
|
3
|
+
import { PROBLEM_DETAILS_CONTENT_TYPE, createExtendedProblemDetails, } from "@batkit/rfc9457";
|
|
4
|
+
const logger = LoggerFacade.getLogger("error-handler");
|
|
5
|
+
export class DefaultErrorFormatter {
|
|
6
|
+
canFormat(error) {
|
|
7
|
+
return (isAppError(error) ||
|
|
8
|
+
error instanceof Error ||
|
|
9
|
+
this.isAxiosError(error) ||
|
|
10
|
+
this.isZodError(error));
|
|
11
|
+
}
|
|
12
|
+
format(error) {
|
|
13
|
+
if (isAppError(error)) {
|
|
14
|
+
return error.toRFC9457();
|
|
15
|
+
}
|
|
16
|
+
if (this.isAxiosError(error)) {
|
|
17
|
+
return this.formatAxiosError(error);
|
|
18
|
+
}
|
|
19
|
+
if (this.isZodError(error)) {
|
|
20
|
+
return this.formatZodError(error);
|
|
21
|
+
}
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
return createExtendedProblemDetails({
|
|
24
|
+
type: "error:internal",
|
|
25
|
+
title: "Internal Server Error",
|
|
26
|
+
status: 500,
|
|
27
|
+
detail: error.message,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return createExtendedProblemDetails({
|
|
31
|
+
type: "error:unknown",
|
|
32
|
+
title: "Unknown Error",
|
|
33
|
+
status: 500,
|
|
34
|
+
detail: "An unknown error occurred",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
isAxiosError(error) {
|
|
38
|
+
return (typeof error === "object" &&
|
|
39
|
+
error !== null &&
|
|
40
|
+
"isAxiosError" in error &&
|
|
41
|
+
error.isAxiosError === true);
|
|
42
|
+
}
|
|
43
|
+
isZodError(error) {
|
|
44
|
+
return (typeof error === "object" &&
|
|
45
|
+
error !== null &&
|
|
46
|
+
"issues" in error &&
|
|
47
|
+
Array.isArray(error.issues));
|
|
48
|
+
}
|
|
49
|
+
formatAxiosError(error) {
|
|
50
|
+
const status = error.response?.status || 502;
|
|
51
|
+
return createExtendedProblemDetails({
|
|
52
|
+
type: "error:upstream",
|
|
53
|
+
title: "Upstream Service Error",
|
|
54
|
+
status,
|
|
55
|
+
detail: error.message,
|
|
56
|
+
upstreamUrl: error.config?.url,
|
|
57
|
+
upstreamMethod: error.config?.method?.toUpperCase(),
|
|
58
|
+
upstreamStatus: error.response?.status,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
formatZodError(error) {
|
|
62
|
+
const validationErrors = error.issues.map((issue) => ({
|
|
63
|
+
field: issue.path.join("."),
|
|
64
|
+
message: issue.message,
|
|
65
|
+
code: issue.code,
|
|
66
|
+
}));
|
|
67
|
+
return createExtendedProblemDetails({
|
|
68
|
+
type: "error:validation",
|
|
69
|
+
title: "Validation Error",
|
|
70
|
+
status: 400,
|
|
71
|
+
detail: "Request validation failed",
|
|
72
|
+
validationErrors,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function errorHandler(options = {}) {
|
|
77
|
+
const { formatters = [new DefaultErrorFormatter()], includeStack = true, logErrors = true, onError, } = options;
|
|
78
|
+
return (error, req, res, next) => {
|
|
79
|
+
if (res.headersSent) {
|
|
80
|
+
next(error);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (logErrors) {
|
|
84
|
+
if (onError) {
|
|
85
|
+
onError(error, req);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
if (isAppError(error) && error.isOperational) {
|
|
89
|
+
logger.warn("Operational error occurred", {
|
|
90
|
+
error: error.message,
|
|
91
|
+
code: error.code,
|
|
92
|
+
statusCode: error.statusCode,
|
|
93
|
+
path: req.path,
|
|
94
|
+
method: req.method,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
logger.error(error, {
|
|
99
|
+
path: req.path,
|
|
100
|
+
method: req.method,
|
|
101
|
+
querd: req.method,
|
|
102
|
+
consoley: req.query,
|
|
103
|
+
body: req.body,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
let problemDetails;
|
|
109
|
+
const formatter = formatters.find((f) => f.canFormat(error));
|
|
110
|
+
if (formatter) {
|
|
111
|
+
problemDetails = formatter.format(error);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
problemDetails = createExtendedProblemDetails({
|
|
115
|
+
type: "error:internal",
|
|
116
|
+
title: "Internal Server Error",
|
|
117
|
+
status: 500,
|
|
118
|
+
detail: "An unexpected error occurred",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (includeStack && error.stack) {
|
|
122
|
+
problemDetails.stack = error.stack.split("\n").map((line) => line.trim());
|
|
123
|
+
}
|
|
124
|
+
if (!problemDetails.instance) {
|
|
125
|
+
problemDetails.instance = req.path;
|
|
126
|
+
}
|
|
127
|
+
res
|
|
128
|
+
.status(problemDetails.status || 500)
|
|
129
|
+
.set("Content-Type", PROBLEM_DETAILS_CONTENT_TYPE)
|
|
130
|
+
.json(problemDetails);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function asyncHandler(fn) {
|
|
134
|
+
return (req, res, next) => {
|
|
135
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=error-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _batkit_errors = require("@batkit/errors");
|
|
3
|
+
let _batkit_logger = require("@batkit/logger");
|
|
4
|
+
let _batkit_rfc9457 = require("@batkit/rfc9457");
|
|
5
|
+
let _batkit_logger_async_local = require("@batkit/logger/async-local");
|
|
6
|
+
//#region src/error-handler.ts
|
|
7
|
+
const logger = _batkit_logger.LoggerFacade.getLogger("error-handler");
|
|
8
|
+
/**
|
|
9
|
+
* Default error formatter that handles common error types
|
|
10
|
+
*/
|
|
11
|
+
var DefaultErrorFormatter = class {
|
|
12
|
+
canFormat(error) {
|
|
13
|
+
return (0, _batkit_errors.isAppError)(error) || error instanceof Error || this.isAxiosError(error) || this.isZodError(error);
|
|
14
|
+
}
|
|
15
|
+
format(error) {
|
|
16
|
+
if ((0, _batkit_errors.isAppError)(error)) return error.toRFC9457();
|
|
17
|
+
if (this.isAxiosError(error)) return this.formatAxiosError(error);
|
|
18
|
+
if (this.isZodError(error)) return this.formatZodError(error);
|
|
19
|
+
if (error instanceof Error) return (0, _batkit_rfc9457.createExtendedProblemDetails)({
|
|
20
|
+
type: "error:internal",
|
|
21
|
+
title: "Internal Server Error",
|
|
22
|
+
status: 500,
|
|
23
|
+
detail: error.message
|
|
24
|
+
});
|
|
25
|
+
return (0, _batkit_rfc9457.createExtendedProblemDetails)({
|
|
26
|
+
type: "error:unknown",
|
|
27
|
+
title: "Unknown Error",
|
|
28
|
+
status: 500,
|
|
29
|
+
detail: "An unknown error occurred"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
isAxiosError(error) {
|
|
33
|
+
return typeof error === "object" && error !== null && "isAxiosError" in error && error.isAxiosError === true;
|
|
34
|
+
}
|
|
35
|
+
isZodError(error) {
|
|
36
|
+
return typeof error === "object" && error !== null && "issues" in error && Array.isArray(error.issues);
|
|
37
|
+
}
|
|
38
|
+
formatAxiosError(error) {
|
|
39
|
+
return (0, _batkit_rfc9457.createExtendedProblemDetails)({
|
|
40
|
+
type: "error:upstream",
|
|
41
|
+
title: "Upstream Service Error",
|
|
42
|
+
status: error.response?.status || 502,
|
|
43
|
+
detail: error.message,
|
|
44
|
+
upstreamUrl: error.config?.url,
|
|
45
|
+
upstreamMethod: (error.config?.method)?.toUpperCase(),
|
|
46
|
+
upstreamStatus: error.response?.status
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
formatZodError(error) {
|
|
50
|
+
return (0, _batkit_rfc9457.createExtendedProblemDetails)({
|
|
51
|
+
type: "error:validation",
|
|
52
|
+
title: "Validation Error",
|
|
53
|
+
status: 400,
|
|
54
|
+
detail: "Request validation failed",
|
|
55
|
+
validationErrors: error.issues.map((issue) => ({
|
|
56
|
+
field: issue.path.join("."),
|
|
57
|
+
message: issue.message,
|
|
58
|
+
code: issue.code
|
|
59
|
+
}))
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Express error handling middleware that converts errors to RFC 9457 format
|
|
65
|
+
*
|
|
66
|
+
* @param options - Configuration options
|
|
67
|
+
* @returns Express error middleware function
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { errorHandler } from '@batkit/express-middleware';
|
|
72
|
+
* import { NotFoundError } from '@batkit/errors';
|
|
73
|
+
* import express from 'express';
|
|
74
|
+
*
|
|
75
|
+
* const app = express();
|
|
76
|
+
*
|
|
77
|
+
* app.get('/users/:id', (req, res) => {
|
|
78
|
+
* throw new NotFoundError('User', req.params.id);
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* // Must be added AFTER all routes
|
|
82
|
+
* app.use(errorHandler());
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
function errorHandler(options = {}) {
|
|
86
|
+
const { formatters = [new DefaultErrorFormatter()], includeStack = true, logErrors = true, onError } = options;
|
|
87
|
+
return (error, req, res, next) => {
|
|
88
|
+
if (res.headersSent) {
|
|
89
|
+
next(error);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (logErrors) if (onError) onError(error, req);
|
|
93
|
+
else if ((0, _batkit_errors.isAppError)(error) && error.isOperational) logger.warn("Operational error occurred", {
|
|
94
|
+
error: error.message,
|
|
95
|
+
code: error.code,
|
|
96
|
+
statusCode: error.statusCode,
|
|
97
|
+
path: req.path,
|
|
98
|
+
method: req.method
|
|
99
|
+
});
|
|
100
|
+
else logger.error(error, {
|
|
101
|
+
path: req.path,
|
|
102
|
+
method: req.method,
|
|
103
|
+
querd: req.method,
|
|
104
|
+
consoley: req.query,
|
|
105
|
+
body: req.body
|
|
106
|
+
});
|
|
107
|
+
let problemDetails;
|
|
108
|
+
const formatter = formatters.find((f) => f.canFormat(error));
|
|
109
|
+
if (formatter) problemDetails = formatter.format(error);
|
|
110
|
+
else problemDetails = (0, _batkit_rfc9457.createExtendedProblemDetails)({
|
|
111
|
+
type: "error:internal",
|
|
112
|
+
title: "Internal Server Error",
|
|
113
|
+
status: 500,
|
|
114
|
+
detail: "An unexpected error occurred"
|
|
115
|
+
});
|
|
116
|
+
if (includeStack && error.stack) problemDetails.stack = error.stack.split("\n").map((line) => line.trim());
|
|
117
|
+
if (!problemDetails.instance) problemDetails.instance = req.path;
|
|
118
|
+
res.status(problemDetails.status || 500).set("Content-Type", _batkit_rfc9457.PROBLEM_DETAILS_CONTENT_TYPE).json(problemDetails);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Express middleware to catch async errors
|
|
123
|
+
* Wraps async route handlers to ensure errors are passed to error middleware
|
|
124
|
+
*
|
|
125
|
+
* @param fn - Async route handler
|
|
126
|
+
* @returns Wrapped route handler
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* import { asyncHandler } from '@batkit/express-middleware';
|
|
131
|
+
*
|
|
132
|
+
* app.get('/users/:id', asyncHandler(async (req, res) => {
|
|
133
|
+
* const user = await db.users.findById(req.params.id);
|
|
134
|
+
* if (!user) {
|
|
135
|
+
* throw new NotFoundError('User', req.params.id);
|
|
136
|
+
* }
|
|
137
|
+
* res.json(user);
|
|
138
|
+
* }));
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
function asyncHandler(fn) {
|
|
142
|
+
return (req, res, next) => {
|
|
143
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/log-context-middleware.ts
|
|
148
|
+
/**
|
|
149
|
+
* Runs each request inside {@link runWithContext} so later code can use
|
|
150
|
+
* {@link mergeLogContext} and a {@link ContextualLoggerProvider} without
|
|
151
|
+
* depending on Express (`req`) deep in the stack.
|
|
152
|
+
*
|
|
153
|
+
* Mount this **early** in the middleware chain. Use synchronous `next()` inside
|
|
154
|
+
* the callback; avoid `async` middleware that calls `next` after an `await`
|
|
155
|
+
* without keeping the rest of the work in the same async context.
|
|
156
|
+
*/
|
|
157
|
+
function logContextMiddleware(options = {}) {
|
|
158
|
+
const initialContext = options.initialContext ?? (() => ({}));
|
|
159
|
+
return (req, _res, next) => {
|
|
160
|
+
(0, _batkit_logger_async_local.runWithContext)(initialContext(req), () => next());
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
exports.DefaultErrorFormatter = DefaultErrorFormatter;
|
|
165
|
+
exports.asyncHandler = asyncHandler;
|
|
166
|
+
exports.errorHandler = errorHandler;
|
|
167
|
+
exports.logContextMiddleware = logContextMiddleware;
|
|
168
|
+
|
|
169
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["LoggerFacade","PROBLEM_DETAILS_CONTENT_TYPE"],"sources":["../src/error-handler.ts","../src/log-context-middleware.ts"],"sourcesContent":["import { isAppError } from \"@batkit/errors\";\nimport { LoggerFacade } from \"@batkit/logger\";\nimport {\n type ExtendedProblemDetails,\n PROBLEM_DETAILS_CONTENT_TYPE,\n createExtendedProblemDetails,\n} from \"@batkit/rfc9457\";\nimport type { NextFunction, Request, Response } from \"express\";\n\nconst logger = LoggerFacade.getLogger(\"error-handler\");\n/**\n * Interface for custom error formatters\n */\nexport interface ErrorFormatter {\n /**\n * Check if this formatter can handle the error\n */\n canFormat(error: unknown): boolean;\n\n /**\n * Format the error as RFC 9457 Problem Details\n */\n format(error: unknown): ExtendedProblemDetails;\n}\n\n/**\n * Default error formatter that handles common error types\n */\nexport class DefaultErrorFormatter implements ErrorFormatter {\n canFormat(error: unknown): boolean {\n return (\n isAppError(error) ||\n error instanceof Error ||\n this.isAxiosError(error) ||\n this.isZodError(error)\n );\n }\n\n format(error: unknown): ExtendedProblemDetails {\n // Handle AppError (from @batkit/errors)\n if (isAppError(error)) {\n return error.toRFC9457();\n }\n\n // Handle Axios errors\n if (this.isAxiosError(error)) {\n return this.formatAxiosError(error as Record<string, unknown>);\n }\n\n // Handle Zod validation errors\n if (this.isZodError(error)) {\n return this.formatZodError(error as Record<string, unknown>);\n }\n\n // Handle generic Error\n if (error instanceof Error) {\n return createExtendedProblemDetails({\n type: \"error:internal\",\n title: \"Internal Server Error\",\n status: 500,\n detail: error.message,\n });\n }\n\n // Unknown error type\n return createExtendedProblemDetails({\n type: \"error:unknown\",\n title: \"Unknown Error\",\n status: 500,\n detail: \"An unknown error occurred\",\n });\n }\n\n private isAxiosError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"isAxiosError\" in error &&\n error.isAxiosError === true\n );\n }\n\n private isZodError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"issues\" in error &&\n Array.isArray((error as Record<string, unknown>).issues)\n );\n }\n\n private formatAxiosError(error: Record<string, unknown>): ExtendedProblemDetails {\n const status = ((error.response as Record<string, unknown>)?.status as number) || 502;\n return createExtendedProblemDetails({\n type: \"error:upstream\",\n title: \"Upstream Service Error\",\n status,\n detail: error.message as string,\n upstreamUrl: (error.config as Record<string, unknown>)?.url as string | undefined,\n upstreamMethod: ((error.config as Record<string, unknown>)?.method as string)?.toUpperCase(),\n upstreamStatus: (error.response as Record<string, unknown>)?.status as number | undefined,\n });\n }\n\n private formatZodError(error: Record<string, unknown>): ExtendedProblemDetails {\n const validationErrors = (error.issues as Array<Record<string, unknown>>).map((issue) => ({\n field: (issue.path as unknown[]).join(\".\"),\n message: issue.message as string,\n code: issue.code as string,\n }));\n\n return createExtendedProblemDetails({\n type: \"error:validation\",\n title: \"Validation Error\",\n status: 400,\n detail: \"Request validation failed\",\n validationErrors,\n });\n }\n}\n\n/**\n * Options for error handler middleware\n */\nexport interface ErrorHandlerOptions {\n /**\n * Custom error formatters (in order of precedence)\n */\n formatters?: ErrorFormatter[];\n\n /**\n * Whether to include stack traces in responses (default: only in non-production)\n */\n includeStack?: boolean;\n\n /**\n * Whether to log errors (default: true)\n */\n logErrors?: boolean;\n\n /**\n * Custom error logger function\n */\n onError?: (error: unknown, req: Request) => void;\n}\n\n/**\n * Express error handling middleware that converts errors to RFC 9457 format\n *\n * @param options - Configuration options\n * @returns Express error middleware function\n *\n * @example\n * ```ts\n * import { errorHandler } from '@batkit/express-middleware';\n * import { NotFoundError } from '@batkit/errors';\n * import express from 'express';\n *\n * const app = express();\n *\n * app.get('/users/:id', (req, res) => {\n * throw new NotFoundError('User', req.params.id);\n * });\n *\n * // Must be added AFTER all routes\n * app.use(errorHandler());\n * ```\n */\nexport function errorHandler(\n options: ErrorHandlerOptions = {},\n): (error: Error, req: Request, res: Response, next: NextFunction) => void {\n const {\n formatters = [new DefaultErrorFormatter()],\n includeStack = true,\n logErrors = true,\n onError,\n } = options;\n\n return (error: Error, req: Request, res: Response, next: NextFunction): void => {\n // If headers already sent, delegate to default Express error handler\n if (res.headersSent) {\n next(error);\n return;\n }\n\n // Log the error\n if (logErrors) {\n if (onError) {\n onError(error, req);\n } else {\n if (isAppError(error) && error.isOperational) {\n logger.warn(\"Operational error occurred\", {\n error: error.message,\n code: error.code,\n statusCode: error.statusCode,\n path: req.path,\n method: req.method,\n });\n } else {\n logger.error(error, {\n path: req.path,\n method: req.method,\n querd: req.method,\n consoley: req.query,\n body: req.body,\n });\n }\n }\n }\n\n // Format the error\n let problemDetails: ExtendedProblemDetails;\n\n const formatter = formatters.find((f) => f.canFormat(error));\n if (formatter) {\n problemDetails = formatter.format(error);\n } else {\n // Fallback to generic error\n problemDetails = createExtendedProblemDetails({\n type: \"error:internal\",\n title: \"Internal Server Error\",\n status: 500,\n detail: \"An unexpected error occurred\",\n });\n }\n\n // Add stack trace if enabled\n if (includeStack && error.stack) {\n problemDetails.stack = error.stack.split(\"\\n\").map((line) => line.trim());\n }\n\n // Add instance (request path)\n if (!problemDetails.instance) {\n problemDetails.instance = req.path;\n }\n\n // Send RFC 9457 response\n res\n .status(problemDetails.status || 500)\n .set(\"Content-Type\", PROBLEM_DETAILS_CONTENT_TYPE)\n .json(problemDetails);\n };\n}\n\n/**\n * Express middleware to catch async errors\n * Wraps async route handlers to ensure errors are passed to error middleware\n *\n * @param fn - Async route handler\n * @returns Wrapped route handler\n *\n * @example\n * ```ts\n * import { asyncHandler } from '@batkit/express-middleware';\n *\n * app.get('/users/:id', asyncHandler(async (req, res) => {\n * const user = await db.users.findById(req.params.id);\n * if (!user) {\n * throw new NotFoundError('User', req.params.id);\n * }\n * res.json(user);\n * }));\n * ```\n */\nexport function asyncHandler(\n fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>,\n): (req: Request, res: Response, next: NextFunction) => void {\n return (req: Request, res: Response, next: NextFunction): void => {\n Promise.resolve(fn(req, res, next)).catch(next);\n };\n}\n","import type { LogValue } from \"@batkit/logger\";\nimport { runWithContext } from \"@batkit/logger/async-local\";\nimport type { NextFunction, Request, Response } from \"express\";\n\nexport type LogContextInitializer = (req: Request) => Record<string, LogValue>;\n\nexport interface LogContextMiddlewareOptions {\n /**\n * Build initial async-local log fields for this HTTP request.\n * @default () => ({})\n */\n initialContext?: LogContextInitializer;\n}\n\n/**\n * Runs each request inside {@link runWithContext} so later code can use\n * {@link mergeLogContext} and a {@link ContextualLoggerProvider} without\n * depending on Express (`req`) deep in the stack.\n *\n * Mount this **early** in the middleware chain. Use synchronous `next()` inside\n * the callback; avoid `async` middleware that calls `next` after an `await`\n * without keeping the rest of the work in the same async context.\n */\nexport function logContextMiddleware(\n options: LogContextMiddlewareOptions = {},\n): (req: Request, res: Response, next: NextFunction) => void {\n const initialContext = options.initialContext ?? (() => ({}));\n\n return (req: Request, _res: Response, next: NextFunction) => {\n runWithContext(initialContext(req), () => next());\n };\n}\n"],"mappings":";;;;;;AASA,MAAM,SAASA,eAAAA,aAAa,UAAU,eAAe;;;;AAmBrD,IAAa,wBAAb,MAA6D;CAC3D,UAAU,OAAyB;EACjC,QAAA,GAAA,eAAA,YACa,KAAK,KAChB,iBAAiB,SACjB,KAAK,aAAa,KAAK,KACvB,KAAK,WAAW,KAAK;CAEzB;CAEA,OAAO,OAAwC;EAE7C,KAAA,GAAA,eAAA,YAAe,KAAK,GAClB,OAAO,MAAM,UAAU;EAIzB,IAAI,KAAK,aAAa,KAAK,GACzB,OAAO,KAAK,iBAAiB,KAAgC;EAI/D,IAAI,KAAK,WAAW,KAAK,GACvB,OAAO,KAAK,eAAe,KAAgC;EAI7D,IAAI,iBAAiB,OACnB,QAAA,GAAA,gBAAA,8BAAoC;GAClC,MAAM;GACN,OAAO;GACP,QAAQ;GACR,QAAQ,MAAM;EAChB,CAAC;EAIH,QAAA,GAAA,gBAAA,8BAAoC;GAClC,MAAM;GACN,OAAO;GACP,QAAQ;GACR,QAAQ;EACV,CAAC;CACH;CAEA,aAAqB,OAAyB;EAC5C,OACE,OAAO,UAAU,YACjB,UAAU,QACV,kBAAkB,SAClB,MAAM,iBAAiB;CAE3B;CAEA,WAAmB,OAAyB;EAC1C,OACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,MAAM,QAAS,MAAkC,MAAM;CAE3D;CAEA,iBAAyB,OAAwD;EAE/E,QAAA,GAAA,gBAAA,8BAAoC;GAClC,MAAM;GACN,OAAO;GACP,QAJe,MAAM,UAAsC,UAAqB;GAKhF,QAAQ,MAAM;GACd,aAAc,MAAM,QAAoC;GACxD,iBAAkB,MAAM,QAAoC,SAAmB,YAAY;GAC3F,gBAAiB,MAAM,UAAsC;EAC/D,CAAC;CACH;CAEA,eAAuB,OAAwD;EAO7E,QAAA,GAAA,gBAAA,8BAAoC;GAClC,MAAM;GACN,OAAO;GACP,QAAQ;GACR,QAAQ;GACR,kBAXwB,MAAM,OAA0C,KAAK,WAAW;IACxF,OAAQ,MAAM,KAAmB,KAAK,GAAG;IACzC,SAAS,MAAM;IACf,MAAM,MAAM;GACd,EAOiB;EACjB,CAAC;CACH;AACF;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,aACd,UAA+B,CAAC,GACyC;CACzE,MAAM,EACJ,aAAa,CAAC,IAAI,sBAAsB,CAAC,GACzC,eAAe,MACf,YAAY,MACZ,YACE;CAEJ,QAAQ,OAAc,KAAc,KAAe,SAA6B;EAE9E,IAAI,IAAI,aAAa;GACnB,KAAK,KAAK;GACV;EACF;EAGA,IAAI,WACF,IAAI,SACF,QAAQ,OAAO,GAAG;OAElB,KAAA,GAAA,eAAA,YAAe,KAAK,KAAK,MAAM,eAC7B,OAAO,KAAK,8BAA8B;GACxC,OAAO,MAAM;GACb,MAAM,MAAM;GACZ,YAAY,MAAM;GAClB,MAAM,IAAI;GACV,QAAQ,IAAI;EACd,CAAC;OAED,OAAO,MAAM,OAAO;GAClB,MAAM,IAAI;GACV,QAAQ,IAAI;GACZ,OAAO,IAAI;GACX,UAAU,IAAI;GACd,MAAM,IAAI;EACZ,CAAC;EAMP,IAAI;EAEJ,MAAM,YAAY,WAAW,MAAM,MAAM,EAAE,UAAU,KAAK,CAAC;EAC3D,IAAI,WACF,iBAAiB,UAAU,OAAO,KAAK;OAGvC,kBAAA,GAAA,gBAAA,8BAA8C;GAC5C,MAAM;GACN,OAAO;GACP,QAAQ;GACR,QAAQ;EACV,CAAC;EAIH,IAAI,gBAAgB,MAAM,OACxB,eAAe,QAAQ,MAAM,MAAM,MAAM,IAAI,EAAE,KAAK,SAAS,KAAK,KAAK,CAAC;EAI1E,IAAI,CAAC,eAAe,UAClB,eAAe,WAAW,IAAI;EAIhC,IACG,OAAO,eAAe,UAAU,GAAG,EACnC,IAAI,gBAAgBC,gBAAAA,4BAA4B,EAChD,KAAK,cAAc;CACxB;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,aACd,IAC2D;CAC3D,QAAQ,KAAc,KAAe,SAA6B;EAChE,QAAQ,QAAQ,GAAG,KAAK,KAAK,IAAI,CAAC,EAAE,MAAM,IAAI;CAChD;AACF;;;;;;;;;;;;ACvPA,SAAgB,qBACd,UAAuC,CAAC,GACmB;CAC3D,MAAM,iBAAiB,QAAQ,0BAA0B,CAAC;CAE1D,QAAQ,KAAc,MAAgB,SAAuB;EAC3D,CAAA,GAAA,2BAAA,gBAAe,eAAe,GAAG,SAAS,KAAK,CAAC;CAClD;AACF"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ExtendedProblemDetails } from "@batkit/rfc9457";
|
|
2
|
+
import { NextFunction, Request, Response } from "express";
|
|
3
|
+
import { LogValue } from "@batkit/logger";
|
|
4
|
+
|
|
5
|
+
//#region src/error-handler.d.ts
|
|
6
|
+
interface ErrorFormatter {
|
|
7
|
+
canFormat(error: unknown): boolean;
|
|
8
|
+
format(error: unknown): ExtendedProblemDetails;
|
|
9
|
+
}
|
|
10
|
+
declare class DefaultErrorFormatter implements ErrorFormatter {
|
|
11
|
+
canFormat(error: unknown): boolean;
|
|
12
|
+
format(error: unknown): ExtendedProblemDetails;
|
|
13
|
+
private isAxiosError;
|
|
14
|
+
private isZodError;
|
|
15
|
+
private formatAxiosError;
|
|
16
|
+
private formatZodError;
|
|
17
|
+
}
|
|
18
|
+
interface ErrorHandlerOptions {
|
|
19
|
+
formatters?: ErrorFormatter[];
|
|
20
|
+
includeStack?: boolean;
|
|
21
|
+
logErrors?: boolean;
|
|
22
|
+
onError?: (error: unknown, req: Request) => void;
|
|
23
|
+
}
|
|
24
|
+
declare function errorHandler(options?: ErrorHandlerOptions): (error: Error, req: Request, res: Response, next: NextFunction) => void;
|
|
25
|
+
declare function asyncHandler(fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>): (req: Request, res: Response, next: NextFunction) => void;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/log-context-middleware.d.ts
|
|
28
|
+
type LogContextInitializer = (req: Request) => Record<string, LogValue>;
|
|
29
|
+
interface LogContextMiddlewareOptions {
|
|
30
|
+
initialContext?: LogContextInitializer;
|
|
31
|
+
}
|
|
32
|
+
declare function logContextMiddleware(options?: LogContextMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { DefaultErrorFormatter, type ErrorFormatter, type ErrorHandlerOptions, type LogContextInitializer, type LogContextMiddlewareOptions, asyncHandler, errorHandler, logContextMiddleware };
|
|
35
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/error-handler.ts","../src/log-context-middleware.ts"],"mappings":";;;;;UAaiB,cAAA;EAIf,SAAA,CAAU,KAAA;EAKV,MAAA,CAAO,KAAA,YAAiB,sBAAsB;AAAA;AAAA,cAMnC,qBAAA,YAAiC,cAAc;EAC1D,SAAA,CAAU,KAAA;EASV,MAAA,CAAO,KAAA,YAAiB,sBAAA;EAAA,QAmChB,YAAA;EAAA,QASA,UAAA;EAAA,QASA,gBAAA;EAAA,QAaA,cAAA;AAAA;AAAA,UAoBO,mBAAA;EAIf,UAAA,GAAa,cAAA;EAKb,YAAA;EAKA,SAAA;EAKA,OAAA,IAAW,KAAA,WAAgB,GAAA,EAAK,OAAO;AAAA;AAAA,iBAyBzB,YAAA,CACd,OAAA,GAAS,mBAAA,IACP,KAAA,EAAO,KAAA,EAAO,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,YAAA;AAAA,iBA8FrC,YAAA,CACd,EAAA,GAAK,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,YAAA,KAAiB,OAAA,aACvD,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,YAAA;;;KCtQ3B,qBAAA,IAAyB,GAAA,EAAK,OAAA,KAAY,MAAA,SAAe,QAAA;AAAA,UAEpD,2BAAA;EAKf,cAAA,GAAiB,qBAAqB;AAAA;AAAA,iBAYxB,oBAAA,CACd,OAAA,GAAS,2BAAA,IACP,GAAA,EAAK,OAAA,EAAS,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,YAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { DefaultErrorFormatter, asyncHandler, errorHandler, type ErrorFormatter, type ErrorHandlerOptions, } from "./error-handler.js";
|
|
2
|
+
export { logContextMiddleware, type LogContextInitializer, type LogContextMiddlewareOptions, } from "./log-context-middleware.js";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,YAAY,EACZ,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,GACjC,MAAM,6BAA6B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,YAAY,EACZ,YAAY,GAGb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,GAGrB,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LogValue } from "@batkit/logger";
|
|
2
|
+
import type { NextFunction, Request, Response } from "express";
|
|
3
|
+
export type LogContextInitializer = (req: Request) => Record<string, LogValue>;
|
|
4
|
+
export interface LogContextMiddlewareOptions {
|
|
5
|
+
initialContext?: LogContextInitializer;
|
|
6
|
+
}
|
|
7
|
+
export declare function logContextMiddleware(options?: LogContextMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
8
|
+
//# sourceMappingURL=log-context-middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-context-middleware.d.ts","sourceRoot":"","sources":["../src/log-context-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/D,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAE/E,MAAM,WAAW,2BAA2B;IAK1C,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACxC;AAWD,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAM3D"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { runWithContext } from "@batkit/logger/async-local";
|
|
2
|
+
export function logContextMiddleware(options = {}) {
|
|
3
|
+
const initialContext = options.initialContext ?? (() => ({}));
|
|
4
|
+
return (req, _res, next) => {
|
|
5
|
+
runWithContext(initialContext(req), () => next());
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=log-context-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-context-middleware.js","sourceRoot":"","sources":["../src/log-context-middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAsB5D,MAAM,UAAU,oBAAoB,CAClC,UAAuC,EAAE;IAEzC,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE9D,OAAO,CAAC,GAAY,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE;QAC1D,cAAc,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@batkit/express-middleware",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Express middleware for error handling and request context (Node.js only)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"error-handling",
|
|
7
|
+
"express",
|
|
8
|
+
"middleware",
|
|
9
|
+
"rfc9457",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/krcourville/better-application-toolkit/tree/main/packages/express-middleware#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/krcourville/better-application-toolkit/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Ken Courville",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/krcourville/better-application-toolkit.git",
|
|
21
|
+
"directory": "packages/express-middleware"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./dist/index.cjs",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./dist/index.d.cts",
|
|
40
|
+
"default": "./dist/index.cjs"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "pnpm clean && vp pack",
|
|
49
|
+
"dev": "vp pack -w",
|
|
50
|
+
"test": "vp test",
|
|
51
|
+
"test:watch": "vp test -w",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"lint": "vp check .",
|
|
54
|
+
"clean": "rm -rf dist"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@batkit/errors": "workspace:*",
|
|
58
|
+
"@batkit/logger": "workspace:*",
|
|
59
|
+
"@batkit/rfc9457": "workspace:*"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/express": "^5.0.0",
|
|
63
|
+
"express": "^4.21.2",
|
|
64
|
+
"typescript": "^5.7.2",
|
|
65
|
+
"vite-plus": "^0.1.14",
|
|
66
|
+
"vitest": "^4.1.2"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"express": "^4.18.0 || ^5.0.0"
|
|
70
|
+
},
|
|
71
|
+
"engines": {
|
|
72
|
+
"node": ">=22.0.0"
|
|
73
|
+
}
|
|
74
|
+
}
|