@stacksjs/error-handling 0.70.87 → 0.70.88
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/package.json +6 -6
- package/dist/error-page-highlighter.d.ts +0 -4
- package/dist/error-page-highlighter.js +0 -39
- package/dist/error-page-renderer.d.ts +0 -11
- package/dist/error-page-renderer.js +0 -21
- package/dist/error-page-styles.d.ts +0 -5
- package/dist/error-page-styles.js +0 -620
- package/dist/error-page-template.d.ts +0 -66
- package/dist/error-page-template.js +0 -224
- package/dist/error-page-view-model.d.ts +0 -70
- package/dist/error-page-view-model.js +0 -166
- package/dist/error-page.d.ts +0 -240
- package/dist/error-page.js +0 -373
- package/dist/handler.d.ts +0 -30
- package/dist/handler.js +0 -108
- package/dist/http.d.ts +0 -49
- package/dist/http.js +0 -165
- package/dist/index.d.ts +0 -42
- package/dist/index.js +0 -20
- package/dist/model.d.ts +0 -4
- package/dist/model.js +0 -8
- package/dist/utils.d.ts +0 -1
- package/dist/utils.js +0 -17
package/dist/error-page.d.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { ERROR_PAGE_CSS } from './error-page-styles';
|
|
2
|
-
export declare function isFrameworkFrame(file: string): boolean;
|
|
3
|
-
/**
|
|
4
|
-
* Render a simple production error page.
|
|
5
|
-
*
|
|
6
|
-
* Checks for a userland override at `resources/views/errors/<status>.html`
|
|
7
|
-
* (or `error.html` as a generic fallback) first; renders the built-in
|
|
8
|
-
* template only when no custom page is provided. stacksjs/stacks#863.
|
|
9
|
-
*/
|
|
10
|
-
export declare function renderProductionErrorPage(status: number): string;
|
|
11
|
-
/**
|
|
12
|
-
* Render the contextual hint block (common causes, suggestion, doc link)
|
|
13
|
-
* for a given HTTP status. Returns an empty string for statuses without
|
|
14
|
-
* enriched data so the dev page degrades gracefully.
|
|
15
|
-
*/
|
|
16
|
-
export declare function renderHttpErrorHints(status: number): string;
|
|
17
|
-
/**
|
|
18
|
-
* Create an error handler instance
|
|
19
|
-
*/
|
|
20
|
-
export declare function createErrorHandler(config?: ErrorPageConfig): ErrorPageHandler;
|
|
21
|
-
/**
|
|
22
|
-
* Render an error page (alias)
|
|
23
|
-
*/
|
|
24
|
-
export declare function renderErrorPage(error: Error, status?: number, config?: ErrorPageConfig): Promise<string>;
|
|
25
|
-
/**
|
|
26
|
-
* Render error (alias)
|
|
27
|
-
*/
|
|
28
|
-
export declare function renderError(error: Error, status?: number): Promise<string>;
|
|
29
|
-
/**
|
|
30
|
-
* Create an error response
|
|
31
|
-
*/
|
|
32
|
-
export declare function errorResponse(error: Error, status?: number, config?: ErrorPageConfig): Promise<Response>;
|
|
33
|
-
/**
|
|
34
|
-
* HTTP error definitions. Each entry includes a doc link, likely causes,
|
|
35
|
-
// and a concrete suggestion so the dev-mode error page reads like a hint
|
|
36
|
-
// instead of just "something went wrong".
|
|
37
|
-
* @defaultValue
|
|
38
|
-
* ```ts
|
|
39
|
-
* {
|
|
40
|
-
* 400: {
|
|
41
|
-
* status: 400,
|
|
42
|
-
* title: 'Bad Request',
|
|
43
|
-
* message: 'The request was malformed or invalid.',
|
|
44
|
-
* commonCauses: [ 'JSON body is missing or has a syntax error', 'A required field is absent from the payload', 'Content-Type header does not match the body format', ],
|
|
45
|
-
* suggestion: 'Inspect the request body and Content-Type — most 400s come from malformed JSON or a missing required field.'
|
|
46
|
-
* },
|
|
47
|
-
* 401: {
|
|
48
|
-
* status: 401,
|
|
49
|
-
* title: 'Unauthorized',
|
|
50
|
-
* message: 'Authentication is required to access this resource.',
|
|
51
|
-
* commonCauses: [ 'No Authorization header was sent', 'The bearer token expired', 'The session cookie was cleared', ],
|
|
52
|
-
* suggestion: 'Confirm a valid `Authorization: Bearer <token>` header is sent and the token has not expired.'
|
|
53
|
-
* },
|
|
54
|
-
* 403: {
|
|
55
|
-
* status: 403,
|
|
56
|
-
* title: 'Forbidden',
|
|
57
|
-
* message: 'You do not have permission to access this resource.',
|
|
58
|
-
* commonCauses: [ 'The authenticated user lacks the required ability or role', 'A Gate or policy denied access (see app/Gates.ts)', 'The token was issued without the needed ability', ],
|
|
59
|
-
* suggestion: 'Check Gates / policies and the abilities encoded in the access token.'
|
|
60
|
-
* },
|
|
61
|
-
* 404: {
|
|
62
|
-
* status: 404,
|
|
63
|
-
* title: 'Not Found',
|
|
64
|
-
* message: 'The requested resource could not be found.',
|
|
65
|
-
* commonCauses: [ 'The route is not registered in app/Routes.ts', 'A typo in the URL path', 'A model lookup returned no row (ModelNotFoundError)', ],
|
|
66
|
-
* suggestion: 'Run `buddy route:list` to see registered routes, or verify the model exists with the given id.'
|
|
67
|
-
* },
|
|
68
|
-
* 405: {
|
|
69
|
-
* status: 405,
|
|
70
|
-
* title: 'Method Not Allowed',
|
|
71
|
-
* message: 'The request method is not supported for this resource.',
|
|
72
|
-
* commonCauses: [ 'The route is registered for a different HTTP method', 'A form posted GET when the route expects POST', ],
|
|
73
|
-
* suggestion: 'Confirm the HTTP method in `app/Routes.ts` matches what the client sent.'
|
|
74
|
-
* },
|
|
75
|
-
* 408: {
|
|
76
|
-
* status: 408,
|
|
77
|
-
* title: 'Request Timeout',
|
|
78
|
-
* message: 'The request took too long to complete.',
|
|
79
|
-
* commonCauses: [ 'A long-running query or external API call exceeded the timeout', 'The client uploaded a slow body that stalled', ],
|
|
80
|
-
* suggestion: 'Move slow work into a queued job, or raise the route timeout if the work is genuinely long.'
|
|
81
|
-
* },
|
|
82
|
-
* 409: {
|
|
83
|
-
* status: 409,
|
|
84
|
-
* title: 'Conflict',
|
|
85
|
-
* message: 'The request conflicts with the current state of the resource.',
|
|
86
|
-
* commonCauses: [ 'A unique-constraint violation (duplicate email, slug, etc.)', 'Optimistic locking detected a stale write', ],
|
|
87
|
-
* suggestion: 'Re-fetch the resource and retry, or surface the conflict to the user.'
|
|
88
|
-
* },
|
|
89
|
-
* 410: {
|
|
90
|
-
* status: 410,
|
|
91
|
-
* title: 'Gone',
|
|
92
|
-
* message: 'The requested resource is no longer available.',
|
|
93
|
-
* commonCauses: ['The resource was permanently deleted', 'A signed URL expired'],
|
|
94
|
-
* suggestion: 'Issue a fresh signed URL or fall back to the canonical resource.'
|
|
95
|
-
* },
|
|
96
|
-
* 422: {
|
|
97
|
-
* status: 422,
|
|
98
|
-
* title: 'Unprocessable Entity',
|
|
99
|
-
* message: 'The request was well-formed but could not be processed.',
|
|
100
|
-
* commonCauses: [ 'Validation rules from the action / model rejected the payload', 'A field value is outside the allowed range or shape', ],
|
|
101
|
-
* suggestion: 'Inspect `errors` in the response body — each key maps to a failing field.'
|
|
102
|
-
* },
|
|
103
|
-
* 429: {
|
|
104
|
-
* status: 429,
|
|
105
|
-
* title: 'Too Many Requests',
|
|
106
|
-
* message: 'You have exceeded the rate limit.',
|
|
107
|
-
* commonCauses: ['Rate-limit middleware tripped on this IP / token', 'A retry loop is hammering the endpoint'],
|
|
108
|
-
* suggestion: 'Honor the `Retry-After` response header and back off before retrying.'
|
|
109
|
-
* },
|
|
110
|
-
* 500: {
|
|
111
|
-
* status: 500,
|
|
112
|
-
* title: 'Internal Server Error',
|
|
113
|
-
* message: 'An unexpected error occurred on the server.',
|
|
114
|
-
* commonCauses: [ 'An unhandled exception in an action or middleware', 'A failing database connection or migration', 'A misconfigured environment variable', ],
|
|
115
|
-
* suggestion: 'Check server logs for the original stack trace — the error page above shows the throw site in dev.'
|
|
116
|
-
* },
|
|
117
|
-
* 502: {
|
|
118
|
-
* status: 502,
|
|
119
|
-
* title: 'Bad Gateway',
|
|
120
|
-
* message: 'The server received an invalid response from an upstream server.',
|
|
121
|
-
* commonCauses: ['An upstream HTTP API returned a malformed response', 'A reverse proxy could not reach the origin'],
|
|
122
|
-
* suggestion: 'Verify the upstream service is healthy and returning the expected content type.'
|
|
123
|
-
* },
|
|
124
|
-
* 503: {
|
|
125
|
-
* status: 503,
|
|
126
|
-
* title: 'Service Unavailable',
|
|
127
|
-
* message: 'The service is temporarily unavailable.',
|
|
128
|
-
* commonCauses: ['Maintenance mode is enabled', 'A health check is failing', 'A dependency (db, redis, queue) is down'],
|
|
129
|
-
* suggestion: 'Run `buddy doctor` and check dependent services.'
|
|
130
|
-
* },
|
|
131
|
-
* 504: {
|
|
132
|
-
* status: 504,
|
|
133
|
-
* title: 'Gateway Timeout',
|
|
134
|
-
* message: 'The upstream server did not respond in time.',
|
|
135
|
-
* commonCauses: ['An upstream HTTP call exceeded its deadline', 'A long-running database query timed out'],
|
|
136
|
-
* suggestion: 'Move the work to a queued job or raise the upstream timeout if the latency is expected.'
|
|
137
|
-
* }
|
|
138
|
-
* }
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
export declare const HTTP_ERRORS: Record<HttpStatusCode, HttpError>;
|
|
142
|
-
/**
|
|
143
|
-
* Error Page Rendering - Ignition-style error pages
|
|
144
|
-
*
|
|
145
|
-
* Provides beautiful development error pages with full stack traces,
|
|
146
|
-
* database queries, and request context.
|
|
147
|
-
*/
|
|
148
|
-
// Types
|
|
149
|
-
export declare interface ErrorPageConfig {
|
|
150
|
-
appName?: string
|
|
151
|
-
theme?: 'light' | 'dark' | 'auto'
|
|
152
|
-
showEnvironment?: boolean
|
|
153
|
-
showQueries?: boolean
|
|
154
|
-
showRequest?: boolean
|
|
155
|
-
enableCopyMarkdown?: boolean
|
|
156
|
-
snippetLines?: number
|
|
157
|
-
basePaths?: string[]
|
|
158
|
-
showFrameworkFrames?: boolean
|
|
159
|
-
}
|
|
160
|
-
export declare interface RequestContext {
|
|
161
|
-
method: string
|
|
162
|
-
url: string
|
|
163
|
-
headers: Record<string, string>
|
|
164
|
-
queryParams?: Record<string, string>
|
|
165
|
-
body?: unknown
|
|
166
|
-
}
|
|
167
|
-
export declare interface RoutingContext {
|
|
168
|
-
controller?: string
|
|
169
|
-
routeName?: string
|
|
170
|
-
middleware?: string[]
|
|
171
|
-
}
|
|
172
|
-
export declare interface UserContext {
|
|
173
|
-
id?: string | number
|
|
174
|
-
email?: string
|
|
175
|
-
name?: string
|
|
176
|
-
}
|
|
177
|
-
export declare interface QueryInfo {
|
|
178
|
-
query: string
|
|
179
|
-
time?: number
|
|
180
|
-
connection?: string
|
|
181
|
-
}
|
|
182
|
-
export declare interface StackFrame {
|
|
183
|
-
file: string
|
|
184
|
-
line: number
|
|
185
|
-
column?: number
|
|
186
|
-
function?: string
|
|
187
|
-
code?: string
|
|
188
|
-
}
|
|
189
|
-
export declare interface CodeSnippet {
|
|
190
|
-
file: string
|
|
191
|
-
line: number
|
|
192
|
-
code: string[]
|
|
193
|
-
highlight: number
|
|
194
|
-
}
|
|
195
|
-
export declare interface EnvironmentContext {
|
|
196
|
-
nodeVersion?: string
|
|
197
|
-
platform?: string
|
|
198
|
-
arch?: string
|
|
199
|
-
env?: Record<string, string>
|
|
200
|
-
}
|
|
201
|
-
export declare interface JobContext {
|
|
202
|
-
name?: string
|
|
203
|
-
queue?: string
|
|
204
|
-
attempts?: number
|
|
205
|
-
}
|
|
206
|
-
export declare interface ErrorPageData {
|
|
207
|
-
error: Error
|
|
208
|
-
status: number
|
|
209
|
-
stack: StackFrame[]
|
|
210
|
-
request?: RequestContext
|
|
211
|
-
routing?: RoutingContext
|
|
212
|
-
user?: UserContext
|
|
213
|
-
queries?: QueryInfo[]
|
|
214
|
-
environment?: EnvironmentContext
|
|
215
|
-
job?: JobContext
|
|
216
|
-
framework?: { name: string, version?: string }
|
|
217
|
-
}
|
|
218
|
-
export declare interface HttpError {
|
|
219
|
-
status: HttpStatusCode
|
|
220
|
-
title: string
|
|
221
|
-
message: string
|
|
222
|
-
docLink?: string
|
|
223
|
-
commonCauses?: string[]
|
|
224
|
-
suggestion?: string
|
|
225
|
-
}
|
|
226
|
-
export type HttpStatusCode = 400 | 401 | 403 | 404 | 405 | 408 | 409 | 410 | 422 | 429 | 500 | 502 | 503 | 504;
|
|
227
|
-
/**
|
|
228
|
-
* Error Page Handler class
|
|
229
|
-
*/
|
|
230
|
-
export declare class ErrorPageHandler {
|
|
231
|
-
constructor(config?: ErrorPageConfig);
|
|
232
|
-
setFramework(name: string, version?: string): this;
|
|
233
|
-
setRequest(request: Request | RequestContext): this;
|
|
234
|
-
setRouting(routing: RoutingContext): this;
|
|
235
|
-
setUser(user: UserContext): this;
|
|
236
|
-
addQuery(query: string, time?: number, connection?: string): this;
|
|
237
|
-
render(error: Error, status?: number): Promise<string>;
|
|
238
|
-
handleError(error: Error, status?: number): Promise<Response>;
|
|
239
|
-
}
|
|
240
|
-
export { ERROR_PAGE_CSS };
|
package/dist/error-page.js
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
var {require}=import.meta;const DOCS_BASE = "https://stacksjs.org/docs/errors";
|
|
2
|
-
export const HTTP_ERRORS = {
|
|
3
|
-
400: {
|
|
4
|
-
status: 400,
|
|
5
|
-
title: "Bad Request",
|
|
6
|
-
message: "The request was malformed or invalid.",
|
|
7
|
-
docLink: `${DOCS_BASE}/400`,
|
|
8
|
-
commonCauses: [
|
|
9
|
-
"JSON body is missing or has a syntax error",
|
|
10
|
-
"A required field is absent from the payload",
|
|
11
|
-
"Content-Type header does not match the body format"
|
|
12
|
-
],
|
|
13
|
-
suggestion: "Inspect the request body and Content-Type \u2014 most 400s come from malformed JSON or a missing required field."
|
|
14
|
-
},
|
|
15
|
-
401: {
|
|
16
|
-
status: 401,
|
|
17
|
-
title: "Unauthorized",
|
|
18
|
-
message: "Authentication is required to access this resource.",
|
|
19
|
-
docLink: `${DOCS_BASE}/401`,
|
|
20
|
-
commonCauses: [
|
|
21
|
-
"No Authorization header was sent",
|
|
22
|
-
"The bearer token expired",
|
|
23
|
-
"The session cookie was cleared"
|
|
24
|
-
],
|
|
25
|
-
suggestion: "Confirm a valid `Authorization: Bearer <token>` header is sent and the token has not expired."
|
|
26
|
-
},
|
|
27
|
-
403: {
|
|
28
|
-
status: 403,
|
|
29
|
-
title: "Forbidden",
|
|
30
|
-
message: "You do not have permission to access this resource.",
|
|
31
|
-
docLink: `${DOCS_BASE}/403`,
|
|
32
|
-
commonCauses: [
|
|
33
|
-
"The authenticated user lacks the required ability or role",
|
|
34
|
-
"A Gate or policy denied access (see app/Gates.ts)",
|
|
35
|
-
"The token was issued without the needed ability"
|
|
36
|
-
],
|
|
37
|
-
suggestion: "Check Gates / policies and the abilities encoded in the access token."
|
|
38
|
-
},
|
|
39
|
-
404: {
|
|
40
|
-
status: 404,
|
|
41
|
-
title: "Not Found",
|
|
42
|
-
message: "The requested resource could not be found.",
|
|
43
|
-
docLink: `${DOCS_BASE}/404`,
|
|
44
|
-
commonCauses: [
|
|
45
|
-
"The route is not registered in app/Routes.ts",
|
|
46
|
-
"A typo in the URL path",
|
|
47
|
-
"A model lookup returned no row (ModelNotFoundError)"
|
|
48
|
-
],
|
|
49
|
-
suggestion: "Run `buddy route:list` to see registered routes, or verify the model exists with the given id."
|
|
50
|
-
},
|
|
51
|
-
405: {
|
|
52
|
-
status: 405,
|
|
53
|
-
title: "Method Not Allowed",
|
|
54
|
-
message: "The request method is not supported for this resource.",
|
|
55
|
-
docLink: `${DOCS_BASE}/405`,
|
|
56
|
-
commonCauses: [
|
|
57
|
-
"The route is registered for a different HTTP method",
|
|
58
|
-
"A form posted GET when the route expects POST"
|
|
59
|
-
],
|
|
60
|
-
suggestion: "Confirm the HTTP method in `app/Routes.ts` matches what the client sent."
|
|
61
|
-
},
|
|
62
|
-
408: {
|
|
63
|
-
status: 408,
|
|
64
|
-
title: "Request Timeout",
|
|
65
|
-
message: "The request took too long to complete.",
|
|
66
|
-
docLink: `${DOCS_BASE}/408`,
|
|
67
|
-
commonCauses: [
|
|
68
|
-
"A long-running query or external API call exceeded the timeout",
|
|
69
|
-
"The client uploaded a slow body that stalled"
|
|
70
|
-
],
|
|
71
|
-
suggestion: "Move slow work into a queued job, or raise the route timeout if the work is genuinely long."
|
|
72
|
-
},
|
|
73
|
-
409: {
|
|
74
|
-
status: 409,
|
|
75
|
-
title: "Conflict",
|
|
76
|
-
message: "The request conflicts with the current state of the resource.",
|
|
77
|
-
docLink: `${DOCS_BASE}/409`,
|
|
78
|
-
commonCauses: [
|
|
79
|
-
"A unique-constraint violation (duplicate email, slug, etc.)",
|
|
80
|
-
"Optimistic locking detected a stale write"
|
|
81
|
-
],
|
|
82
|
-
suggestion: "Re-fetch the resource and retry, or surface the conflict to the user."
|
|
83
|
-
},
|
|
84
|
-
410: {
|
|
85
|
-
status: 410,
|
|
86
|
-
title: "Gone",
|
|
87
|
-
message: "The requested resource is no longer available.",
|
|
88
|
-
docLink: `${DOCS_BASE}/410`,
|
|
89
|
-
commonCauses: ["The resource was permanently deleted", "A signed URL expired"],
|
|
90
|
-
suggestion: "Issue a fresh signed URL or fall back to the canonical resource."
|
|
91
|
-
},
|
|
92
|
-
422: {
|
|
93
|
-
status: 422,
|
|
94
|
-
title: "Unprocessable Entity",
|
|
95
|
-
message: "The request was well-formed but could not be processed.",
|
|
96
|
-
docLink: `${DOCS_BASE}/422`,
|
|
97
|
-
commonCauses: [
|
|
98
|
-
"Validation rules from the action / model rejected the payload",
|
|
99
|
-
"A field value is outside the allowed range or shape"
|
|
100
|
-
],
|
|
101
|
-
suggestion: "Inspect `errors` in the response body \u2014 each key maps to a failing field."
|
|
102
|
-
},
|
|
103
|
-
429: {
|
|
104
|
-
status: 429,
|
|
105
|
-
title: "Too Many Requests",
|
|
106
|
-
message: "You have exceeded the rate limit.",
|
|
107
|
-
docLink: `${DOCS_BASE}/429`,
|
|
108
|
-
commonCauses: ["Rate-limit middleware tripped on this IP / token", "A retry loop is hammering the endpoint"],
|
|
109
|
-
suggestion: "Honor the `Retry-After` response header and back off before retrying."
|
|
110
|
-
},
|
|
111
|
-
500: {
|
|
112
|
-
status: 500,
|
|
113
|
-
title: "Internal Server Error",
|
|
114
|
-
message: "An unexpected error occurred on the server.",
|
|
115
|
-
docLink: `${DOCS_BASE}/500`,
|
|
116
|
-
commonCauses: [
|
|
117
|
-
"An unhandled exception in an action or middleware",
|
|
118
|
-
"A failing database connection or migration",
|
|
119
|
-
"A misconfigured environment variable"
|
|
120
|
-
],
|
|
121
|
-
suggestion: "Check server logs for the original stack trace \u2014 the error page above shows the throw site in dev."
|
|
122
|
-
},
|
|
123
|
-
502: {
|
|
124
|
-
status: 502,
|
|
125
|
-
title: "Bad Gateway",
|
|
126
|
-
message: "The server received an invalid response from an upstream server.",
|
|
127
|
-
docLink: `${DOCS_BASE}/502`,
|
|
128
|
-
commonCauses: ["An upstream HTTP API returned a malformed response", "A reverse proxy could not reach the origin"],
|
|
129
|
-
suggestion: "Verify the upstream service is healthy and returning the expected content type."
|
|
130
|
-
},
|
|
131
|
-
503: {
|
|
132
|
-
status: 503,
|
|
133
|
-
title: "Service Unavailable",
|
|
134
|
-
message: "The service is temporarily unavailable.",
|
|
135
|
-
docLink: `${DOCS_BASE}/503`,
|
|
136
|
-
commonCauses: ["Maintenance mode is enabled", "A health check is failing", "A dependency (db, redis, queue) is down"],
|
|
137
|
-
suggestion: "Run `buddy doctor` and check dependent services."
|
|
138
|
-
},
|
|
139
|
-
504: {
|
|
140
|
-
status: 504,
|
|
141
|
-
title: "Gateway Timeout",
|
|
142
|
-
message: "The upstream server did not respond in time.",
|
|
143
|
-
docLink: `${DOCS_BASE}/504`,
|
|
144
|
-
commonCauses: ["An upstream HTTP call exceeded its deadline", "A long-running database query timed out"],
|
|
145
|
-
suggestion: "Move the work to a queued job or raise the upstream timeout if the latency is expected."
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
import {
|
|
149
|
-
buildErrorMarkdown,
|
|
150
|
-
escapeHtml,
|
|
151
|
-
renderExceptionTrace,
|
|
152
|
-
wrapErrorPage
|
|
153
|
-
} from "./error-page-template";
|
|
154
|
-
import { ERROR_PAGE_CSS } from "./error-page-styles";
|
|
155
|
-
|
|
156
|
-
export { ERROR_PAGE_CSS };
|
|
157
|
-
const FRAMEWORK_FRAME_FRAGMENTS = [
|
|
158
|
-
"/storage/framework/core/",
|
|
159
|
-
"/node_modules/@stacksjs/",
|
|
160
|
-
"node:internal/",
|
|
161
|
-
"bun:wrap"
|
|
162
|
-
];
|
|
163
|
-
export function isFrameworkFrame(file) {
|
|
164
|
-
if (!file)
|
|
165
|
-
return !1;
|
|
166
|
-
return FRAMEWORK_FRAME_FRAGMENTS.some((f) => file.includes(f));
|
|
167
|
-
}
|
|
168
|
-
function parseStackTrace(stack, basePaths, options = {}) {
|
|
169
|
-
if (!stack)
|
|
170
|
-
return [];
|
|
171
|
-
const lines = stack.split(`
|
|
172
|
-
`).slice(1), frames = [], includeAll = options.includeFrameworkFrames === !0;
|
|
173
|
-
for (const line of lines) {
|
|
174
|
-
const match = line.match(/^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
|
|
175
|
-
if (match) {
|
|
176
|
-
let file = match[2];
|
|
177
|
-
if (file === void 0)
|
|
178
|
-
continue;
|
|
179
|
-
const original = file, isFramework = isFrameworkFrame(original);
|
|
180
|
-
if (basePaths) {
|
|
181
|
-
for (const basePath of basePaths)
|
|
182
|
-
if (file.startsWith(basePath)) {
|
|
183
|
-
file = file.slice(basePath.length + 1);
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (!includeAll && isFramework)
|
|
188
|
-
continue;
|
|
189
|
-
frames.push({
|
|
190
|
-
function: match[1] || "<anonymous>",
|
|
191
|
-
file,
|
|
192
|
-
absoluteFile: original,
|
|
193
|
-
isFramework,
|
|
194
|
-
line: parseInt(match[3] ?? "0", 10),
|
|
195
|
-
column: parseInt(match[4] ?? "0", 10)
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (!includeAll && frames.length === 0)
|
|
200
|
-
return parseStackTrace(stack, basePaths, { includeFrameworkFrames: !0 });
|
|
201
|
-
return frames;
|
|
202
|
-
}
|
|
203
|
-
function loadCustomErrorPage(status, title, message) {
|
|
204
|
-
let resourcesPath;
|
|
205
|
-
try {
|
|
206
|
-
const mod = require("@stacksjs/path");
|
|
207
|
-
resourcesPath = mod.resourcesPath ?? mod.path?.resourcesPath;
|
|
208
|
-
} catch {}
|
|
209
|
-
if (!resourcesPath)
|
|
210
|
-
return null;
|
|
211
|
-
const fs = require("node:fs"), candidates = [
|
|
212
|
-
resourcesPath(`views/errors/${status}.html`),
|
|
213
|
-
resourcesPath("views/errors/error.html")
|
|
214
|
-
];
|
|
215
|
-
for (const filePath of candidates)
|
|
216
|
-
try {
|
|
217
|
-
if (!fs.existsSync(filePath))
|
|
218
|
-
continue;
|
|
219
|
-
return fs.readFileSync(filePath, "utf-8").replace(/\{\{\s*status\s*\}\}/g, escapeHtml(String(status))).replace(/\{\{\s*title\s*\}\}/g, escapeHtml(title)).replace(/\{\{\s*message\s*\}\}/g, escapeHtml(message));
|
|
220
|
-
} catch {}
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
export function renderProductionErrorPage(status) {
|
|
224
|
-
const httpError = HTTP_ERRORS[status] || {
|
|
225
|
-
status,
|
|
226
|
-
title: "Error",
|
|
227
|
-
message: "An unexpected error occurred."
|
|
228
|
-
}, custom = loadCustomErrorPage(httpError.status, httpError.title, httpError.message);
|
|
229
|
-
if (custom !== null)
|
|
230
|
-
return custom;
|
|
231
|
-
return `<!DOCTYPE html>
|
|
232
|
-
<html lang="en">
|
|
233
|
-
<head>
|
|
234
|
-
<meta charset="UTF-8">
|
|
235
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
236
|
-
<title>${httpError.status} - ${httpError.title}</title>
|
|
237
|
-
<style>${ERROR_PAGE_CSS}</style>
|
|
238
|
-
</head>
|
|
239
|
-
<body>
|
|
240
|
-
<div class="production-page">
|
|
241
|
-
<div class="production-status">${httpError.status}</div>
|
|
242
|
-
<h1 class="production-title">${escapeHtml(httpError.title)}</h1>
|
|
243
|
-
<p class="production-message">${escapeHtml(httpError.message)}</p>
|
|
244
|
-
<a href="/" class="production-link">\u2190 Back to Home</a>
|
|
245
|
-
</div>
|
|
246
|
-
</body>
|
|
247
|
-
</html>`;
|
|
248
|
-
}
|
|
249
|
-
export function renderHttpErrorHints(status) {
|
|
250
|
-
const info = HTTP_ERRORS[status];
|
|
251
|
-
if (!info)
|
|
252
|
-
return "";
|
|
253
|
-
const hasCauses = Array.isArray(info.commonCauses) && info.commonCauses.length > 0, hasSuggestion = typeof info.suggestion === "string" && info.suggestion.length > 0, hasDoc = typeof info.docLink === "string" && info.docLink.length > 0;
|
|
254
|
-
if (!hasCauses && !hasSuggestion && !hasDoc)
|
|
255
|
-
return "";
|
|
256
|
-
const causes = hasCauses ? `<ul class="error-hint-causes">${info.commonCauses.map((c) => `<li>${escapeHtml(c)}</li>`).join("")}</ul>` : "", suggestion = hasSuggestion ? `<p class="error-hint-suggestion">${escapeHtml(info.suggestion)}</p>` : "", doc = hasDoc ? `<a class="text-blue-600 text-sm dark:text-blue-500 hover:underline" href="${escapeHtml(info.docLink)}" target="_blank" rel="noreferrer noopener">Read the docs \u2192</a>` : "";
|
|
257
|
-
return `<section class="p-4 bg-amber-200/30 dark:bg-amber-950/40 border border-amber-200 rounded-xl dark:border-amber-800 shadow-xs">
|
|
258
|
-
<div class="mb-2 font-semibold text-amber-900 text-sm dark:text-amber-300">Likely causes & next steps</div>
|
|
259
|
-
<div class="text-neutral-700 text-sm dark:text-neutral-300">${causes}${suggestion}${doc}</div>
|
|
260
|
-
</section>`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export class ErrorPageHandler {
|
|
264
|
-
config;
|
|
265
|
-
framework;
|
|
266
|
-
request;
|
|
267
|
-
routing;
|
|
268
|
-
user;
|
|
269
|
-
queries = [];
|
|
270
|
-
constructor(config) {
|
|
271
|
-
this.config = {
|
|
272
|
-
appName: "App",
|
|
273
|
-
theme: "auto",
|
|
274
|
-
showEnvironment: !0,
|
|
275
|
-
showQueries: !0,
|
|
276
|
-
showRequest: !0,
|
|
277
|
-
enableCopyMarkdown: !0,
|
|
278
|
-
snippetLines: 8,
|
|
279
|
-
...config
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
setFramework(name, version) {
|
|
283
|
-
this.framework = { name, version };
|
|
284
|
-
return this;
|
|
285
|
-
}
|
|
286
|
-
setRequest(request) {
|
|
287
|
-
if (request instanceof Request) {
|
|
288
|
-
const url = new URL(request.url);
|
|
289
|
-
this.request = {
|
|
290
|
-
method: request.method,
|
|
291
|
-
url: request.url,
|
|
292
|
-
headers: Object.fromEntries(request.headers.entries()),
|
|
293
|
-
queryParams: Object.fromEntries(url.searchParams.entries())
|
|
294
|
-
};
|
|
295
|
-
} else
|
|
296
|
-
this.request = request;
|
|
297
|
-
return this;
|
|
298
|
-
}
|
|
299
|
-
setRouting(routing) {
|
|
300
|
-
this.routing = routing;
|
|
301
|
-
return this;
|
|
302
|
-
}
|
|
303
|
-
setUser(user) {
|
|
304
|
-
this.user = user;
|
|
305
|
-
return this;
|
|
306
|
-
}
|
|
307
|
-
addQuery(query, time, connection) {
|
|
308
|
-
this.queries.push({ query, time, connection });
|
|
309
|
-
return this;
|
|
310
|
-
}
|
|
311
|
-
async render(error, status = 500) {
|
|
312
|
-
try {
|
|
313
|
-
const { renderDevErrorPage } = await import("./error-page-renderer");
|
|
314
|
-
return await renderDevErrorPage({
|
|
315
|
-
error,
|
|
316
|
-
status,
|
|
317
|
-
config: this.config,
|
|
318
|
-
framework: this.framework,
|
|
319
|
-
request: this.request,
|
|
320
|
-
routing: this.routing,
|
|
321
|
-
user: this.user,
|
|
322
|
-
queries: this.queries
|
|
323
|
-
});
|
|
324
|
-
} catch (renderError) {
|
|
325
|
-
console.error("[ErrorPageHandler] STX render failed, using fallback:", renderError);
|
|
326
|
-
return this.renderFallback(error, status);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
renderFallback(error, status) {
|
|
330
|
-
const frames = parseStackTrace(error.stack, this.config.basePaths, {
|
|
331
|
-
includeFrameworkFrames: this.config.showFrameworkFrames === !0
|
|
332
|
-
}), statusTitle = HTTP_ERRORS[status]?.title ?? "Error", topFrame = frames[0], body = `
|
|
333
|
-
<header class="header"><div class="header-title"><span class="header-dot"></span><span>${escapeHtml(statusTitle)}</span></div></header>
|
|
334
|
-
<section class="summary">
|
|
335
|
-
<h1>${escapeHtml(error.name || "Error")}</h1>
|
|
336
|
-
${topFrame ? `<div class="summary-file">${escapeHtml(topFrame.file)}:${topFrame.line}</div>` : ""}
|
|
337
|
-
<p class="summary-message">${escapeHtml(error.message)}</p>
|
|
338
|
-
</section>
|
|
339
|
-
${renderHttpErrorHints(status)}
|
|
340
|
-
${renderExceptionTrace(frames, this.config.snippetLines ?? 8)}
|
|
341
|
-
`, markdown = buildErrorMarkdown({
|
|
342
|
-
statusTitle,
|
|
343
|
-
errorName: error.name || "Error",
|
|
344
|
-
errorMessage: error.message,
|
|
345
|
-
status,
|
|
346
|
-
file: topFrame?.file,
|
|
347
|
-
line: topFrame?.line,
|
|
348
|
-
request: this.request,
|
|
349
|
-
framework: this.framework,
|
|
350
|
-
frames
|
|
351
|
-
});
|
|
352
|
-
return wrapErrorPage(body, markdown);
|
|
353
|
-
}
|
|
354
|
-
async handleError(error, status = 500) {
|
|
355
|
-
const html = await this.render(error, status);
|
|
356
|
-
return new Response(html, {
|
|
357
|
-
status,
|
|
358
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
export function createErrorHandler(config) {
|
|
363
|
-
return new ErrorPageHandler(config);
|
|
364
|
-
}
|
|
365
|
-
export async function renderErrorPage(error, status = 500, config) {
|
|
366
|
-
return createErrorHandler(config).render(error, status);
|
|
367
|
-
}
|
|
368
|
-
export async function renderError(error, status = 500) {
|
|
369
|
-
return renderErrorPage(error, status);
|
|
370
|
-
}
|
|
371
|
-
export async function errorResponse(error, status = 500, config) {
|
|
372
|
-
return createErrorHandler(config).handleError(error, status);
|
|
373
|
-
}
|
package/dist/handler.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as path from '@stacksjs/path';
|
|
2
|
-
import type { LogErrorOptions } from '@stacksjs/logging';
|
|
3
|
-
// Function to update the default log path when config is available
|
|
4
|
-
export declare function setLogPath(path: string): void;
|
|
5
|
-
export declare function writeToLogFile(message: string, options?: WriteOptions): Promise<void>;
|
|
6
|
-
export declare function handleError(err: string | Error | object | unknown, options?: unknown): Error;
|
|
7
|
-
/**
|
|
8
|
-
* Context information attached to errors for better debugging.
|
|
9
|
-
*/
|
|
10
|
-
export declare interface ErrorContext {
|
|
11
|
-
requestId?: string
|
|
12
|
-
url?: string
|
|
13
|
-
method?: string
|
|
14
|
-
userId?: string | number
|
|
15
|
-
ip?: string
|
|
16
|
-
userAgent?: string
|
|
17
|
-
[key: string]: unknown
|
|
18
|
-
}
|
|
19
|
-
declare interface WriteOptions {
|
|
20
|
-
logFile?: string
|
|
21
|
-
}
|
|
22
|
-
declare type ErrorMessage = string;
|
|
23
|
-
export declare class ErrorHandler {
|
|
24
|
-
static isTestEnvironment: boolean;
|
|
25
|
-
static shouldExitProcess: boolean;
|
|
26
|
-
static handle(err: Error | ErrorMessage | unknown, options?: LogErrorOptions): Error;
|
|
27
|
-
static handleError(err: Error, options?: LogErrorOptions): Error;
|
|
28
|
-
static writeErrorToFile(err: Error | unknown, context?: ErrorContext): Promise<void>;
|
|
29
|
-
static writeErrorToConsole(err: string | Error | unknown): void;
|
|
30
|
-
}
|