@vsaas/error-handler 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +25 -0
- package/README.md +89 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +279 -0
- package/package.json +58 -0
- package/views/default-error.hbs +26 -0
- package/views/style.hbs +7 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Copyright (c) IBM Corp. 2016,2017. All Rights Reserved.
|
|
2
|
+
Node module: strong-error-handler
|
|
3
|
+
This project is licensed under the MIT License, full text below.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
MIT license
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in
|
|
17
|
+
all copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
25
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @vsaas/error-handler
|
|
2
|
+
|
|
3
|
+
HTTP error writer for Express and LoopBack 3 style applications.
|
|
4
|
+
|
|
5
|
+
`@vsaas/error-handler` is a fork of `strong-error-handler`. The goal of this fork is to stay compatible with the original package for the common LoopBack 3 / Strong Remoting use cases, while making the codebase smaller and easier to maintain.
|
|
6
|
+
|
|
7
|
+
Compared with the upstream package, this fork intentionally removes a number of legacy concerns:
|
|
8
|
+
|
|
9
|
+
- TypeScript source with build output in `dist/`
|
|
10
|
+
- English-only messages
|
|
11
|
+
- No i18n catalogs or `strong-globalize`
|
|
12
|
+
- Several legacy files and dependencies removed
|
|
13
|
+
- Modern tooling with `tsdown`, `vitest`, and `oxlint`
|
|
14
|
+
|
|
15
|
+
The public behavior is still meant to be familiar for applications that already depended on `strong-error-handler`, but the internals were simplified on purpose to reduce maintenance overhead.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @vsaas/error-handler
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Express middleware
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
const express = require('express');
|
|
29
|
+
const errorHandler = require('@vsaas/error-handler');
|
|
30
|
+
|
|
31
|
+
const app = express();
|
|
32
|
+
|
|
33
|
+
app.use(
|
|
34
|
+
errorHandler({
|
|
35
|
+
debug: process.env.NODE_ENV !== 'production',
|
|
36
|
+
log: true,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Direct response writer
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const errorHandler = require('@vsaas/error-handler');
|
|
45
|
+
|
|
46
|
+
errorHandler.writeErrorToResponse(new Error('something went wrong'), req, res, {
|
|
47
|
+
debug: false,
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Response formats
|
|
52
|
+
|
|
53
|
+
The handler supports:
|
|
54
|
+
|
|
55
|
+
- `application/json` / `json`
|
|
56
|
+
- `text/html` / `html`
|
|
57
|
+
- `text/xml` / `xml`
|
|
58
|
+
|
|
59
|
+
Content type is negotiated from the request `Accept` header. You can also override it with the `_format` query parameter.
|
|
60
|
+
|
|
61
|
+
## Options
|
|
62
|
+
|
|
63
|
+
| Option | Default | Description |
|
|
64
|
+
| ---------------------- | --------- | --------------------------------------------------------------------------- |
|
|
65
|
+
| `debug` | `false` | Include full error details and stack traces in responses. |
|
|
66
|
+
| `log` | `true` | Log errors to `console.error`. |
|
|
67
|
+
| `safeFields` | `[]` | Extra error properties allowed through for safe responses. |
|
|
68
|
+
| `defaultType` | `"json"` | Fallback response type when negotiation does not resolve one. |
|
|
69
|
+
| `rootProperty` | `"error"` | Root property for JSON/XML responses. Use `false` to omit the JSON wrapper. |
|
|
70
|
+
| `negotiateContentType` | `true` | When `false`, always use `defaultType` unless `_format` overrides it. |
|
|
71
|
+
|
|
72
|
+
## Behavior
|
|
73
|
+
|
|
74
|
+
- `4xx` responses keep safe client-facing fields such as `message`, `details`, and `code`.
|
|
75
|
+
- `5xx` responses are sanitized by default to avoid leaking internals.
|
|
76
|
+
- When `debug: true`, the full error payload is returned.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm run build
|
|
82
|
+
npm run typecheck
|
|
83
|
+
npm run lint
|
|
84
|
+
npm test
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT. See [LICENSE.md](./LICENSE.md).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface RequestLike {
|
|
3
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
4
|
+
method?: string;
|
|
5
|
+
query?: Record<string, unknown>;
|
|
6
|
+
socket?: {
|
|
7
|
+
destroy?: () => void;
|
|
8
|
+
};
|
|
9
|
+
url?: string;
|
|
10
|
+
}
|
|
11
|
+
interface ResponseLike {
|
|
12
|
+
headersSent?: boolean;
|
|
13
|
+
statusCode: number;
|
|
14
|
+
end: (content?: string, encoding?: BufferEncoding) => void;
|
|
15
|
+
setHeader: (name: string, value: string) => void;
|
|
16
|
+
}
|
|
17
|
+
type ErrorHandlerNext = (error?: unknown) => void;
|
|
18
|
+
interface ErrorWriterOptions {
|
|
19
|
+
debug?: boolean;
|
|
20
|
+
defaultType?: string;
|
|
21
|
+
negotiateContentType?: boolean;
|
|
22
|
+
rootProperty?: string | false;
|
|
23
|
+
safeFields?: string | string[];
|
|
24
|
+
}
|
|
25
|
+
interface ErrorHandlerOptions extends ErrorWriterOptions {
|
|
26
|
+
log?: boolean;
|
|
27
|
+
}
|
|
28
|
+
type StrongErrorHandler = (error: unknown, req: RequestLike, res: ResponseLike, next?: ErrorHandlerNext) => void;
|
|
29
|
+
interface StrongErrorHandlerFactory {
|
|
30
|
+
(options?: ErrorHandlerOptions): StrongErrorHandler;
|
|
31
|
+
writeErrorToResponse: (error: unknown, req: RequestLike, res: ResponseLike, options?: ErrorWriterOptions) => void;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/index.d.ts
|
|
35
|
+
declare const strongErrorHandler: StrongErrorHandlerFactory;
|
|
36
|
+
export = strongErrorHandler;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
let debug = require("debug");
|
|
24
|
+
debug = __toESM(debug);
|
|
25
|
+
let node_http = require("node:http");
|
|
26
|
+
let node_util = require("node:util");
|
|
27
|
+
node_util = __toESM(node_util);
|
|
28
|
+
let accepts = require("accepts");
|
|
29
|
+
accepts = __toESM(accepts);
|
|
30
|
+
let node_fs = require("node:fs");
|
|
31
|
+
node_fs = __toESM(node_fs);
|
|
32
|
+
let node_path = require("node:path");
|
|
33
|
+
node_path = __toESM(node_path);
|
|
34
|
+
let handlebars = require("handlebars");
|
|
35
|
+
handlebars = __toESM(handlebars);
|
|
36
|
+
//#region src/lib/clone.ts
|
|
37
|
+
function cloneAllProperties(data, error) {
|
|
38
|
+
data.name = error.name;
|
|
39
|
+
data.message = error.message;
|
|
40
|
+
for (const key in error) {
|
|
41
|
+
if (key in data) continue;
|
|
42
|
+
data[key] = error[key];
|
|
43
|
+
}
|
|
44
|
+
data.stack = error.stack;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/lib/data-builder.ts
|
|
48
|
+
function buildResponseData(error, options = {}) {
|
|
49
|
+
if (Array.isArray(error)) return serializeArrayOfErrors(error, options);
|
|
50
|
+
const normalizedError = normalizeError(error);
|
|
51
|
+
const data = { statusCode: resolveStatusCode(normalizedError) };
|
|
52
|
+
if (options.debug) {
|
|
53
|
+
cloneAllProperties(data, normalizedError);
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
if (data.statusCode >= 400 && data.statusCode <= 499) fillClientError(data, normalizedError);
|
|
57
|
+
else fillServerError(data, normalizedError);
|
|
58
|
+
fillSafeFields(data, normalizedError, options.safeFields);
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
function serializeArrayOfErrors(errors, options) {
|
|
62
|
+
const details = new Array(errors.length);
|
|
63
|
+
for (let index = 0; index < errors.length; index += 1) details[index] = buildResponseData(errors[index], options);
|
|
64
|
+
return {
|
|
65
|
+
statusCode: 500,
|
|
66
|
+
message: "Failed with multiple errors, see `details` for more information.",
|
|
67
|
+
details
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function normalizeError(error) {
|
|
71
|
+
if (typeof error === "object" && error !== null) return error;
|
|
72
|
+
return {
|
|
73
|
+
statusCode: 500,
|
|
74
|
+
message: String(error)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function resolveStatusCode(error) {
|
|
78
|
+
const statusCode = error.statusCode || error.status;
|
|
79
|
+
if (!statusCode || statusCode < 400) return 500;
|
|
80
|
+
return statusCode;
|
|
81
|
+
}
|
|
82
|
+
function fillClientError(data, error) {
|
|
83
|
+
data.name = error.name;
|
|
84
|
+
data.message = error.message;
|
|
85
|
+
data.code = error.code;
|
|
86
|
+
data.details = error.details;
|
|
87
|
+
}
|
|
88
|
+
function fillServerError(data, error) {
|
|
89
|
+
if (error.expose) {
|
|
90
|
+
data.name = node_http.STATUS_CODES[data.statusCode] || "Unknown Error";
|
|
91
|
+
data.message = error.message;
|
|
92
|
+
} else data.message = node_http.STATUS_CODES[data.statusCode] || "Unknown Error";
|
|
93
|
+
}
|
|
94
|
+
function fillSafeFields(data, error, safeFields) {
|
|
95
|
+
if (!safeFields) return;
|
|
96
|
+
const fields = Array.isArray(safeFields) ? safeFields : [safeFields];
|
|
97
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
98
|
+
const field = fields[index];
|
|
99
|
+
if (error[field] !== void 0) data[field] = error[field];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/lib/logger.ts
|
|
104
|
+
function logToConsole(req, error) {
|
|
105
|
+
if (!Array.isArray(error)) {
|
|
106
|
+
console.error("Request %s %s failed: %s", req.method, req.url, formatError(error));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const errors = error.map(formatError).join("\n");
|
|
110
|
+
console.error("Request %s %s failed with multiple errors:\n%s", req.method, req.url, errors);
|
|
111
|
+
}
|
|
112
|
+
function formatError(error) {
|
|
113
|
+
if (typeof error === "object" && error !== null && "stack" in error) return node_util.default.format("%s", error.stack || error);
|
|
114
|
+
return node_util.default.format("%s", error);
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/lib/send-html.ts
|
|
118
|
+
const assetDir = node_path.default.resolve(__dirname, "../views");
|
|
119
|
+
const standardProps = new Set([
|
|
120
|
+
"name",
|
|
121
|
+
"statusCode",
|
|
122
|
+
"message",
|
|
123
|
+
"stack"
|
|
124
|
+
]);
|
|
125
|
+
const stylesPartial = node_fs.default.readFileSync(node_path.default.join(assetDir, "style.hbs"), "utf8");
|
|
126
|
+
const defaultTemplate = handlebars.default.compile(node_fs.default.readFileSync(node_path.default.join(assetDir, "default-error.hbs"), "utf8"));
|
|
127
|
+
handlebars.default.registerHelper("partial", function partial(name) {
|
|
128
|
+
if (name === "style") return stylesPartial;
|
|
129
|
+
return "";
|
|
130
|
+
});
|
|
131
|
+
handlebars.default.registerHelper("standardProps", function standardPropsHelper(property, options) {
|
|
132
|
+
if (!standardProps.has(property)) return options.fn(this);
|
|
133
|
+
return options.inverse(this);
|
|
134
|
+
});
|
|
135
|
+
function sendHtml(res, data, options = {}) {
|
|
136
|
+
const filteredData = {};
|
|
137
|
+
for (const key in data) {
|
|
138
|
+
const value = data[key];
|
|
139
|
+
if (value !== void 0 && value !== null || standardProps.has(key)) filteredData[key] = value;
|
|
140
|
+
}
|
|
141
|
+
const body = defaultTemplate({
|
|
142
|
+
options,
|
|
143
|
+
data: filteredData
|
|
144
|
+
});
|
|
145
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
146
|
+
res.end(body, "utf-8");
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/lib/safe-json-stringify.ts
|
|
150
|
+
const circularMarker = "[Circular]";
|
|
151
|
+
function safeJsonStringify(value) {
|
|
152
|
+
try {
|
|
153
|
+
return JSON.stringify(value);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (!isCircularJsonStringifyError(error)) throw error;
|
|
156
|
+
}
|
|
157
|
+
return JSON.stringify(value, createCircularReplacer());
|
|
158
|
+
}
|
|
159
|
+
function createCircularReplacer() {
|
|
160
|
+
const ancestors = [];
|
|
161
|
+
const activeAncestors = /* @__PURE__ */ new WeakSet();
|
|
162
|
+
return function circularReplacer(_key, value) {
|
|
163
|
+
if (typeof value !== "object" || value === null) return value;
|
|
164
|
+
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
165
|
+
const ancestor = ancestors.pop();
|
|
166
|
+
if (ancestor) activeAncestors.delete(ancestor);
|
|
167
|
+
}
|
|
168
|
+
if (activeAncestors.has(value)) return circularMarker;
|
|
169
|
+
ancestors.push(value);
|
|
170
|
+
activeAncestors.add(value);
|
|
171
|
+
return value;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function isCircularJsonStringifyError(error) {
|
|
175
|
+
return error instanceof TypeError && (error.message.includes("circular") || error.message.includes("Circular"));
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/lib/send-json.ts
|
|
179
|
+
function sendJson(res, data, options = {}) {
|
|
180
|
+
const content = options.rootProperty === false ? safeJsonStringify(data) : safeJsonStringify({ [options.rootProperty || "error"]: data });
|
|
181
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
182
|
+
res.end(content, "utf-8");
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/lib/send-xml.ts
|
|
186
|
+
const js2xmlparser = require("js2xmlparser");
|
|
187
|
+
function sendXml(res, data, options = {}) {
|
|
188
|
+
const root = options.rootProperty || "error";
|
|
189
|
+
const content = js2xmlparser.parse(root, data);
|
|
190
|
+
res.setHeader("Content-Type", "text/xml; charset=utf-8");
|
|
191
|
+
res.end(content, "utf-8");
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/lib/content-negotiation.ts
|
|
195
|
+
const debug$2 = (0, debug.default)("strong-error-handler:http-response");
|
|
196
|
+
const supportedTypes = [
|
|
197
|
+
"application/json",
|
|
198
|
+
"json",
|
|
199
|
+
"text/html",
|
|
200
|
+
"html",
|
|
201
|
+
"text/xml",
|
|
202
|
+
"xml"
|
|
203
|
+
];
|
|
204
|
+
const supportedTypeSet = new Set(supportedTypes);
|
|
205
|
+
function negotiateContentProducer(req, logWarning, options = {}) {
|
|
206
|
+
let defaultType = "json";
|
|
207
|
+
if (typeof options.defaultType === "string" && supportedTypeSet.has(options.defaultType)) {
|
|
208
|
+
debug$2("Accepting options.defaultType `%s`", options.defaultType);
|
|
209
|
+
defaultType = options.defaultType;
|
|
210
|
+
} else if (options.defaultType) debug$2("defaultType: `%s` is not supported, falling back to defaultType: `%s`", options.defaultType, defaultType);
|
|
211
|
+
let contentType = (0, accepts.default)(req).types([...supportedTypes]) || defaultType;
|
|
212
|
+
if (options.negotiateContentType === false) if (typeof options.defaultType === "string" && supportedTypeSet.has(options.defaultType)) {
|
|
213
|
+
contentType = options.defaultType;
|
|
214
|
+
debug$2("Forcing options.defaultType `%s`", options.defaultType);
|
|
215
|
+
} else debug$2("contentType: `%s` is not supported, falling back to contentType: `%s`", options.defaultType, contentType);
|
|
216
|
+
const query = req.query || {};
|
|
217
|
+
if (typeof query._format === "string") if (supportedTypeSet.has(query._format)) contentType = query._format;
|
|
218
|
+
else logWarning(node_util.default.format("Response _format \"%s\" is not supported, used \"%s\" instead", query._format, defaultType));
|
|
219
|
+
debug$2("Content-negotiation: req.headers.accept: `%s` resolved as: `%s`", req.headers?.accept, contentType);
|
|
220
|
+
return resolveOperation(contentType);
|
|
221
|
+
}
|
|
222
|
+
function resolveOperation(contentType) {
|
|
223
|
+
switch (contentType) {
|
|
224
|
+
case "application/json":
|
|
225
|
+
case "json": return sendJson;
|
|
226
|
+
case "text/html":
|
|
227
|
+
case "html": return sendHtml;
|
|
228
|
+
case "text/xml":
|
|
229
|
+
case "xml": return sendXml;
|
|
230
|
+
default: return sendJson;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/lib/handler.ts
|
|
235
|
+
const debug$1 = (0, debug.default)("strong-error-handler");
|
|
236
|
+
function createStrongErrorHandler(options = {}) {
|
|
237
|
+
debug$1("Initializing with options %j", options);
|
|
238
|
+
const logError = options.log !== false ? logToConsole : noop;
|
|
239
|
+
return function strongErrorHandler(error, req, res) {
|
|
240
|
+
logError(req, error);
|
|
241
|
+
writeErrorToResponse(error, req, res, options);
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function writeErrorToResponse(error, req, res, options = {}) {
|
|
245
|
+
debug$1("Handling %s", getDebugValue(error));
|
|
246
|
+
if (res.headersSent) {
|
|
247
|
+
debug$1("Response was already sent, closing the underlying connection");
|
|
248
|
+
req.socket?.destroy?.();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const data = buildResponseData(coerceWritableError(error, res.statusCode), options);
|
|
252
|
+
debug$1("Response status %s data %j", data.statusCode, data);
|
|
253
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
254
|
+
res.statusCode = data.statusCode;
|
|
255
|
+
negotiateContentProducer(req, warn, options)(res, data, options);
|
|
256
|
+
function warn(message) {
|
|
257
|
+
res.setHeader("X-Warning", message);
|
|
258
|
+
debug$1(message);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function noop() {}
|
|
262
|
+
function getDebugValue(error) {
|
|
263
|
+
if (typeof error === "object" && error !== null && "stack" in error) return error.stack || error;
|
|
264
|
+
return error;
|
|
265
|
+
}
|
|
266
|
+
function coerceWritableError(error, responseStatusCode) {
|
|
267
|
+
if (typeof error !== "object" || error === null) return {
|
|
268
|
+
statusCode: responseStatusCode >= 400 ? responseStatusCode : 500,
|
|
269
|
+
message: String(error)
|
|
270
|
+
};
|
|
271
|
+
const writableError = error;
|
|
272
|
+
if (!writableError.status && !writableError.statusCode && responseStatusCode >= 400) writableError.statusCode = responseStatusCode;
|
|
273
|
+
return writableError;
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/index.ts
|
|
277
|
+
const strongErrorHandler = Object.assign(createStrongErrorHandler, { writeErrorToResponse });
|
|
278
|
+
module.exports = strongErrorHandler;
|
|
279
|
+
//#endregion
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vsaas/error-handler",
|
|
3
|
+
"version": "10.0.0",
|
|
4
|
+
"description": "Error handler for use in development and production environments.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "IBM Corp.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/xompass/strong-error-handler"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"views",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE.md"
|
|
16
|
+
],
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"require": "./dist/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"registry": "https://registry.npmjs.org/"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"accepts": "^1.3.8",
|
|
33
|
+
"debug": "^4.4.3",
|
|
34
|
+
"handlebars": "^4.7.9",
|
|
35
|
+
"js2xmlparser": "^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "25.5.0",
|
|
39
|
+
"oxfmt": "0.42.0",
|
|
40
|
+
"oxlint": "1.57.0",
|
|
41
|
+
"supertest": "7.2.2",
|
|
42
|
+
"tsdown": "0.21.5",
|
|
43
|
+
"typescript": "6.0.2",
|
|
44
|
+
"vitest": "4.1.1"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsdown",
|
|
51
|
+
"fmt": "oxfmt -c ../.oxfmtrc.json .",
|
|
52
|
+
"lint": "oxlint -c ../.oxlintrc.json src test",
|
|
53
|
+
"lint:fix": "oxlint -c ../.oxlintrc.json src test --fix",
|
|
54
|
+
"test:run": "vitest run",
|
|
55
|
+
"test": "pnpm run build && vitest run",
|
|
56
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<meta charset='utf-8' />
|
|
4
|
+
<title>{{data.name}}{{#unless data.name}}{{data.message}}{{/unless}}</title>
|
|
5
|
+
<style>
|
|
6
|
+
{{partial 'style'}}
|
|
7
|
+
</style>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id='wrapper'>
|
|
11
|
+
<h1>{{data.name}}</h1>
|
|
12
|
+
<h2>
|
|
13
|
+
<em>{{data.statusCode}}</em>
|
|
14
|
+
{{data.message}}
|
|
15
|
+
</h2>
|
|
16
|
+
{{#each data}}
|
|
17
|
+
{{#standardProps @key}}
|
|
18
|
+
<div><b>{{@key}}</b>: {{this}}</div>
|
|
19
|
+
{{/standardProps}}
|
|
20
|
+
{{/each}}
|
|
21
|
+
{{#if data.stack}}
|
|
22
|
+
<pre id='stacktrace'>{{{data.stack}}}</pre>
|
|
23
|
+
{{/if}}
|
|
24
|
+
</div>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
package/views/style.hbs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
* { margin: 0; padding: 0; outline: 0; } body { padding: 80px 100px; font: 13px
|
|
2
|
+
"Helvetica Neue", "Lucida Grande", "Arial"; background: #ECE9E9
|
|
3
|
+
-webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9)); background:
|
|
4
|
+
#ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9); background-repeat: no-repeat;
|
|
5
|
+
color: #555; -webkit-font-smoothing: antialiased; } h1, h2 { font-size: 22px;
|
|
6
|
+
color: #343434; } h1 em, h2 em { padding: 0 5px; font-weight: normal; } h1 {
|
|
7
|
+
font-size: 60px; } h2 { margin: 10px 0; } ul li { list-style: none; }
|