@stacksjs/error-handling 0.70.88 → 0.70.91
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/dist/error-page-highlighter.d.ts +4 -0
- package/dist/error-page-highlighter.js +39 -0
- package/dist/error-page-renderer.d.ts +11 -0
- package/dist/error-page-renderer.js +21 -0
- package/dist/error-page-styles.d.ts +5 -0
- package/dist/error-page-styles.js +620 -0
- package/dist/error-page-template.d.ts +66 -0
- package/dist/error-page-template.js +224 -0
- package/dist/error-page-view-model.d.ts +70 -0
- package/dist/error-page-view-model.js +166 -0
- package/dist/error-page.d.ts +240 -0
- package/dist/error-page.js +373 -0
- package/dist/handler.d.ts +30 -0
- package/dist/handler.js +108 -0
- package/dist/http.d.ts +49 -0
- package/dist/http.js +165 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +20 -0
- package/dist/model.d.ts +4 -0
- package/dist/model.js +8 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +17 -0
- package/package.json +6 -6
package/dist/handler.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import * as process from "node:process";
|
|
4
|
+
function italic(str) {
|
|
5
|
+
return `\x1B[3m${str}\x1B[23m`;
|
|
6
|
+
}
|
|
7
|
+
function stripAnsi(str) {
|
|
8
|
+
return str.replace(/\x1B\[[0-9;]*m/g, "");
|
|
9
|
+
}
|
|
10
|
+
import * as path from "@stacksjs/path";
|
|
11
|
+
import { ExitCode } from "@stacksjs/types";
|
|
12
|
+
|
|
13
|
+
export class ErrorHandler {
|
|
14
|
+
static isTestEnvironment = !1;
|
|
15
|
+
static shouldExitProcess = !1;
|
|
16
|
+
static handle(err, options) {
|
|
17
|
+
if (!this.isTestEnvironment)
|
|
18
|
+
this.shouldExitProcess = options?.shouldExit === !0;
|
|
19
|
+
if (options?.silent !== !0)
|
|
20
|
+
this.writeErrorToConsole(err);
|
|
21
|
+
let errorMessage;
|
|
22
|
+
if (options?.message)
|
|
23
|
+
errorMessage = options.message;
|
|
24
|
+
else if (err instanceof Error)
|
|
25
|
+
errorMessage = err.message;
|
|
26
|
+
else if (typeof err === "string")
|
|
27
|
+
errorMessage = err;
|
|
28
|
+
else
|
|
29
|
+
errorMessage = JSON.stringify(err);
|
|
30
|
+
const error = Error(errorMessage);
|
|
31
|
+
if (err instanceof Error)
|
|
32
|
+
Object.assign(error, err);
|
|
33
|
+
this.writeErrorToFile(error).catch((e) => console.error(e));
|
|
34
|
+
return error;
|
|
35
|
+
}
|
|
36
|
+
static handleError(err, options) {
|
|
37
|
+
this.handle(err, options);
|
|
38
|
+
return err;
|
|
39
|
+
}
|
|
40
|
+
static async writeErrorToFile(err, context) {
|
|
41
|
+
if (!(err instanceof Error)) {
|
|
42
|
+
console.error("Error is not an instance of Error:", err);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const contextStr = context ? ` | url=${context.url || "N/A"} method=${context.method || "N/A"} user=${context.userId || "anonymous"}` : "", stackLine = err.stack ? `
|
|
46
|
+
${err.stack.split(`
|
|
47
|
+
`).slice(1, 4).join(`
|
|
48
|
+
`)}` : "", formattedError = `[${new Date().toISOString()}] ${err.name}: ${err.message}${contextStr}${stackLine}
|
|
49
|
+
`, logFilePath = path.logsPath("stacks.log") ?? path.logsPath("errors.log");
|
|
50
|
+
try {
|
|
51
|
+
await fs.promises.mkdir(path.dirname(logFilePath), { recursive: !0 });
|
|
52
|
+
await fs.promises.appendFile(logFilePath, formattedError);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Failed to write to error file:", error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
static writeErrorToConsole(err) {
|
|
58
|
+
let errorString;
|
|
59
|
+
if (err instanceof Error)
|
|
60
|
+
errorString = err.message;
|
|
61
|
+
else if (typeof err === "string")
|
|
62
|
+
errorString = err;
|
|
63
|
+
else
|
|
64
|
+
errorString = JSON.stringify(err);
|
|
65
|
+
console.error(errorString);
|
|
66
|
+
if (errorString.includes("bunx --bun cdk destroy") || errorString === `Failed to execute command: ${italic("bunx --bun eslint . --fix")}` || errorString === `Failed to execute command: ${italic("bun storage/framework/core/actions/src/lint/fix.ts")}`) {
|
|
67
|
+
if (!this.isTestEnvironment) {
|
|
68
|
+
console.log("No need to worry. The edge function is currently being destroyed. Please run `buddy undeploy` shortly again, and continue doing so until it succeeds running.");
|
|
69
|
+
console.log("Hoping to see you back soon!");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (this.shouldExitProcess)
|
|
73
|
+
process.exit(ExitCode.FatalError);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
let defaultLogPath = "storage/logs/stacks.log";
|
|
77
|
+
export function setLogPath(path) {
|
|
78
|
+
defaultLogPath = path;
|
|
79
|
+
}
|
|
80
|
+
export async function writeToLogFile(message, options) {
|
|
81
|
+
const formattedMessage = `[${new Date().toISOString()}] ${message}
|
|
82
|
+
`, logFile = options?.logFile ?? defaultLogPath, dirPath = dirname(logFile);
|
|
83
|
+
await fs.promises.mkdir(dirPath, { recursive: !0 });
|
|
84
|
+
await fs.promises.appendFile(logFile, formattedMessage);
|
|
85
|
+
}
|
|
86
|
+
export function handleError(err, options) {
|
|
87
|
+
let errorMessage, contextData, errorOptions;
|
|
88
|
+
if (options && typeof options === "object" && (("shouldExit" in options) || ("silent" in options)))
|
|
89
|
+
errorOptions = options;
|
|
90
|
+
else if (options !== void 0)
|
|
91
|
+
contextData = options && typeof options === "object" ? options : { error: options };
|
|
92
|
+
const errMsg = err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err);
|
|
93
|
+
if (errorOptions?.message)
|
|
94
|
+
errorMessage = `${errMsg}: ${errorOptions.message}`;
|
|
95
|
+
else
|
|
96
|
+
errorMessage = errMsg;
|
|
97
|
+
let logMessage = `ERROR: ${stripAnsi(errorMessage)}`;
|
|
98
|
+
if (contextData)
|
|
99
|
+
logMessage += `
|
|
100
|
+
Context: ${JSON.stringify(contextData, null, 2)}`;
|
|
101
|
+
writeToLogFile(logMessage).catch((err) => {
|
|
102
|
+
console.error("Failed to write error log:", err);
|
|
103
|
+
});
|
|
104
|
+
const error = Error(errorMessage);
|
|
105
|
+
if (err instanceof Error)
|
|
106
|
+
Object.assign(error, err);
|
|
107
|
+
return ErrorHandler.handle(error, errorOptions);
|
|
108
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ErrorPageConfig, RequestContext, RoutingContext } from './error-page';
|
|
2
|
+
/**
|
|
3
|
+
* Create a new HTTP error handler
|
|
4
|
+
*/
|
|
5
|
+
export declare function createHttpErrorHandler(options?: {
|
|
6
|
+
isDevelopment?: boolean
|
|
7
|
+
config?: ErrorPageConfig
|
|
8
|
+
}): HttpErrorHandler;
|
|
9
|
+
/**
|
|
10
|
+
* Quick helper to render an error page for HTTP errors
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderHttpError(error: Error, request?: Request, options?: {
|
|
13
|
+
status?: number
|
|
14
|
+
isDevelopment?: boolean
|
|
15
|
+
config?: ErrorPageConfig
|
|
16
|
+
}): Promise<Response>;
|
|
17
|
+
/**
|
|
18
|
+
* Express/Hono style error middleware
|
|
19
|
+
*/
|
|
20
|
+
export declare function errorMiddleware(options?: {
|
|
21
|
+
isDevelopment?: boolean
|
|
22
|
+
config?: ErrorPageConfig
|
|
23
|
+
}): void;
|
|
24
|
+
export declare class HttpError extends Error {
|
|
25
|
+
details?: unknown;
|
|
26
|
+
public status: number;
|
|
27
|
+
constructor(status: number, message: string, details?: unknown);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* HTTP error handler with Ignition-style error pages
|
|
31
|
+
*/
|
|
32
|
+
export declare class HttpErrorHandler {
|
|
33
|
+
constructor(options?: {
|
|
34
|
+
isDevelopment?: boolean
|
|
35
|
+
config?: ErrorPageConfig
|
|
36
|
+
});
|
|
37
|
+
setRequest(request: Request | RequestContext): this;
|
|
38
|
+
setRouting(routing: RoutingContext): this;
|
|
39
|
+
addQuery(query: string, time?: number, connection?: string): this;
|
|
40
|
+
handle(error: Error, status?: number): Promise<Response>;
|
|
41
|
+
notFound(message?: string): Promise<Response>;
|
|
42
|
+
serverError(error: Error): Promise<Response>;
|
|
43
|
+
forbidden(message?: string): Promise<Response>;
|
|
44
|
+
unauthorized(message?: string): Promise<Response>;
|
|
45
|
+
badRequest(message?: string): Promise<Response>;
|
|
46
|
+
validationError(message?: string): Promise<Response>;
|
|
47
|
+
tooManyRequests(message?: string): Promise<Response>;
|
|
48
|
+
serviceUnavailable(message?: string): Promise<Response>;
|
|
49
|
+
}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createErrorHandler,
|
|
3
|
+
HTTP_ERRORS,
|
|
4
|
+
renderProductionErrorPage
|
|
5
|
+
} from "./error-page";
|
|
6
|
+
|
|
7
|
+
export class HttpError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
details;
|
|
10
|
+
constructor(status, message, details) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.name = httpStatusName(status);
|
|
14
|
+
if (details !== void 0)
|
|
15
|
+
this.details = details;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function httpStatusName(status) {
|
|
19
|
+
switch (status) {
|
|
20
|
+
case 400:
|
|
21
|
+
return "Bad Request";
|
|
22
|
+
case 401:
|
|
23
|
+
return "Unauthorized";
|
|
24
|
+
case 402:
|
|
25
|
+
return "Payment Required";
|
|
26
|
+
case 403:
|
|
27
|
+
return "Forbidden";
|
|
28
|
+
case 404:
|
|
29
|
+
return "Not Found";
|
|
30
|
+
case 405:
|
|
31
|
+
return "Method Not Allowed";
|
|
32
|
+
case 408:
|
|
33
|
+
return "Request Timeout";
|
|
34
|
+
case 409:
|
|
35
|
+
return "Conflict";
|
|
36
|
+
case 410:
|
|
37
|
+
return "Gone";
|
|
38
|
+
case 413:
|
|
39
|
+
return "Payload Too Large";
|
|
40
|
+
case 415:
|
|
41
|
+
return "Unsupported Media Type";
|
|
42
|
+
case 422:
|
|
43
|
+
return "Unprocessable Entity";
|
|
44
|
+
case 423:
|
|
45
|
+
return "Locked";
|
|
46
|
+
case 425:
|
|
47
|
+
return "Too Early";
|
|
48
|
+
case 426:
|
|
49
|
+
return "Upgrade Required";
|
|
50
|
+
case 428:
|
|
51
|
+
return "Precondition Required";
|
|
52
|
+
case 429:
|
|
53
|
+
return "Too Many Requests";
|
|
54
|
+
case 431:
|
|
55
|
+
return "Request Header Fields Too Large";
|
|
56
|
+
case 451:
|
|
57
|
+
return "Unavailable For Legal Reasons";
|
|
58
|
+
case 500:
|
|
59
|
+
return "Internal Server Error";
|
|
60
|
+
case 501:
|
|
61
|
+
return "Not Implemented";
|
|
62
|
+
case 502:
|
|
63
|
+
return "Bad Gateway";
|
|
64
|
+
case 503:
|
|
65
|
+
return "Service Unavailable";
|
|
66
|
+
case 504:
|
|
67
|
+
return "Gateway Timeout";
|
|
68
|
+
case 507:
|
|
69
|
+
return "Insufficient Storage";
|
|
70
|
+
case 508:
|
|
71
|
+
return "Loop Detected";
|
|
72
|
+
case 511:
|
|
73
|
+
return "Network Authentication Required";
|
|
74
|
+
default:
|
|
75
|
+
return status >= 400 && status < 500 ? "Client Error" : "Server Error";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class HttpErrorHandler {
|
|
80
|
+
handler = createErrorHandler();
|
|
81
|
+
isDevelopment;
|
|
82
|
+
constructor(options) {
|
|
83
|
+
this.isDevelopment = options?.isDevelopment ?? !0;
|
|
84
|
+
if (options?.config)
|
|
85
|
+
this.handler = createErrorHandler(options.config);
|
|
86
|
+
this.handler.setFramework("Stacks");
|
|
87
|
+
}
|
|
88
|
+
setRequest(request) {
|
|
89
|
+
this.handler.setRequest(request);
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
setRouting(routing) {
|
|
93
|
+
this.handler.setRouting(routing);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
addQuery(query, time, connection) {
|
|
97
|
+
this.handler.addQuery(query, time, connection);
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
async handle(error, status = 500) {
|
|
101
|
+
if (this.isDevelopment)
|
|
102
|
+
return this.handler.handleError(error, status);
|
|
103
|
+
return new Response(renderProductionErrorPage(status), {
|
|
104
|
+
status,
|
|
105
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
notFound(message) {
|
|
109
|
+
const error = new HttpError(404, message || "The requested resource could not be found.");
|
|
110
|
+
return this.handle(error, 404);
|
|
111
|
+
}
|
|
112
|
+
serverError(error) {
|
|
113
|
+
return this.handle(error, 500);
|
|
114
|
+
}
|
|
115
|
+
forbidden(message) {
|
|
116
|
+
const error = new HttpError(403, message || "You do not have permission to access this resource.");
|
|
117
|
+
return this.handle(error, 403);
|
|
118
|
+
}
|
|
119
|
+
unauthorized(message) {
|
|
120
|
+
const error = new HttpError(401, message || "Authentication is required to access this resource.");
|
|
121
|
+
return this.handle(error, 401);
|
|
122
|
+
}
|
|
123
|
+
badRequest(message) {
|
|
124
|
+
const error = new HttpError(400, message || "The request was malformed or invalid.");
|
|
125
|
+
return this.handle(error, 400);
|
|
126
|
+
}
|
|
127
|
+
validationError(message) {
|
|
128
|
+
const error = new HttpError(422, message || "The request was well-formed but could not be processed.");
|
|
129
|
+
return this.handle(error, 422);
|
|
130
|
+
}
|
|
131
|
+
tooManyRequests(message) {
|
|
132
|
+
const error = new HttpError(429, message || "You have exceeded the rate limit.");
|
|
133
|
+
return this.handle(error, 429);
|
|
134
|
+
}
|
|
135
|
+
serviceUnavailable(message) {
|
|
136
|
+
const error = new HttpError(503, message || "The service is temporarily unavailable.");
|
|
137
|
+
return this.handle(error, 503);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function createHttpErrorHandler(options) {
|
|
141
|
+
return new HttpErrorHandler(options);
|
|
142
|
+
}
|
|
143
|
+
export async function renderHttpError(error, request, options) {
|
|
144
|
+
const handler = createHttpErrorHandler({
|
|
145
|
+
isDevelopment: options?.isDevelopment,
|
|
146
|
+
config: options?.config
|
|
147
|
+
});
|
|
148
|
+
if (request)
|
|
149
|
+
handler.setRequest(request);
|
|
150
|
+
return handler.handle(error, options?.status);
|
|
151
|
+
}
|
|
152
|
+
export function errorMiddleware(options) {
|
|
153
|
+
const handler = createHttpErrorHandler(options);
|
|
154
|
+
return async (error, request) => {
|
|
155
|
+
handler.setRequest(request);
|
|
156
|
+
let status = 500;
|
|
157
|
+
if (error instanceof HttpError)
|
|
158
|
+
status = error.status;
|
|
159
|
+
else if ("status" in error && typeof error.status === "number")
|
|
160
|
+
status = error.status;
|
|
161
|
+
else if ("statusCode" in error && typeof error.statusCode === "number")
|
|
162
|
+
status = error.statusCode;
|
|
163
|
+
return handler.handle(error, status);
|
|
164
|
+
};
|
|
165
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Result type exports
|
|
2
|
+
export type {
|
|
3
|
+
Err,
|
|
4
|
+
Ok,
|
|
5
|
+
Result,
|
|
6
|
+
ResultAsync,
|
|
7
|
+
} from 'ts-error-handling';
|
|
8
|
+
// Error page exports (Ignition-style) - local implementation
|
|
9
|
+
export type {
|
|
10
|
+
CodeSnippet,
|
|
11
|
+
EnvironmentContext,
|
|
12
|
+
ErrorPageConfig,
|
|
13
|
+
ErrorPageData,
|
|
14
|
+
HttpError as HttpErrorInfo,
|
|
15
|
+
HttpStatusCode,
|
|
16
|
+
JobContext,
|
|
17
|
+
QueryInfo,
|
|
18
|
+
RequestContext,
|
|
19
|
+
RoutingContext,
|
|
20
|
+
StackFrame,
|
|
21
|
+
UserContext,
|
|
22
|
+
} from './error-page';
|
|
23
|
+
export * from './handler';
|
|
24
|
+
export * from './http';
|
|
25
|
+
export * from './model';
|
|
26
|
+
export * from './utils';
|
|
27
|
+
export {
|
|
28
|
+
err,
|
|
29
|
+
fromPromise,
|
|
30
|
+
ok,
|
|
31
|
+
} from 'ts-error-handling';
|
|
32
|
+
export {
|
|
33
|
+
createErrorHandler,
|
|
34
|
+
ERROR_PAGE_CSS,
|
|
35
|
+
ErrorPageHandler,
|
|
36
|
+
errorResponse,
|
|
37
|
+
HTTP_ERRORS,
|
|
38
|
+
renderError,
|
|
39
|
+
renderErrorPage,
|
|
40
|
+
renderHttpErrorHints,
|
|
41
|
+
renderProductionErrorPage,
|
|
42
|
+
} from './error-page';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export * from "./handler";
|
|
2
|
+
export * from "./http";
|
|
3
|
+
export * from "./model";
|
|
4
|
+
export * from "./utils";
|
|
5
|
+
export {
|
|
6
|
+
err,
|
|
7
|
+
fromPromise,
|
|
8
|
+
ok
|
|
9
|
+
} from "ts-error-handling";
|
|
10
|
+
export {
|
|
11
|
+
createErrorHandler,
|
|
12
|
+
ERROR_PAGE_CSS,
|
|
13
|
+
ErrorPageHandler,
|
|
14
|
+
errorResponse,
|
|
15
|
+
HTTP_ERRORS,
|
|
16
|
+
renderError,
|
|
17
|
+
renderErrorPage,
|
|
18
|
+
renderHttpErrorHints,
|
|
19
|
+
renderProductionErrorPage
|
|
20
|
+
} from "./error-page";
|
package/dist/model.d.ts
ADDED
package/dist/model.js
ADDED
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function rescue<T, F>(fn: () => T | Promise<T>, fallback: F, onError?: (error: Error) => void): T | F | Promise<T | F>;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ErrorHandler } from "./handler";
|
|
2
|
+
export function rescue(fn, fallback, onError) {
|
|
3
|
+
try {
|
|
4
|
+
const result = fn();
|
|
5
|
+
if (result instanceof Promise)
|
|
6
|
+
return result.catch((error) => {
|
|
7
|
+
if (onError)
|
|
8
|
+
onError(ErrorHandler.handle(error));
|
|
9
|
+
return fallback;
|
|
10
|
+
});
|
|
11
|
+
return result;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (onError)
|
|
14
|
+
onError(ErrorHandler.handle(error));
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/error-handling",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.91",
|
|
5
5
|
"description": "Type safe error handling.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"ts-syntax-highlighter": "^0.2.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@stacksjs/cli": "0.70.
|
|
61
|
-
"@stacksjs/config": "0.70.
|
|
60
|
+
"@stacksjs/cli": "0.70.91",
|
|
61
|
+
"@stacksjs/config": "0.70.91",
|
|
62
62
|
"better-dx": "^0.2.16",
|
|
63
|
-
"@stacksjs/path": "0.70.
|
|
64
|
-
"@stacksjs/types": "0.70.
|
|
65
|
-
"@stacksjs/validation": "0.70.
|
|
63
|
+
"@stacksjs/path": "0.70.91",
|
|
64
|
+
"@stacksjs/types": "0.70.91",
|
|
65
|
+
"@stacksjs/validation": "0.70.91"
|
|
66
66
|
},
|
|
67
67
|
"sideEffects": false
|
|
68
68
|
}
|