@stacksjs/error-handling
Version:
Type safe error handling.
241 lines (240 loc) • 9.4 kB
TypeScript
import { ERROR_PAGE_CSS } from './error-page-styles';
export declare function isFrameworkFrame(file: string): boolean;
/**
* Render a simple production error page.
*
* Checks for a userland override at `resources/views/errors/<status>.html`
* (or `error.html` as a generic fallback) first; renders the built-in
* template only when no custom page is provided. stacksjs/stacks#863.
*/
export declare function renderProductionErrorPage(status: number): string;
/**
* Render the contextual hint block (common causes, suggestion, doc link)
* for a given HTTP status. Returns an empty string for statuses without
* enriched data so the dev page degrades gracefully.
*/
export declare function renderHttpErrorHints(status: number): string;
/**
* Create an error handler instance
*/
export declare function createErrorHandler(config?: ErrorPageConfig): ErrorPageHandler;
/**
* Render an error page (alias)
*/
export declare function renderErrorPage(error: Error, status?: number, config?: ErrorPageConfig): Promise<string>;
/**
* Render error (alias)
*/
export declare function renderError(error: Error, status?: number): Promise<string>;
/**
* Create an error response
*/
export declare function errorResponse(error: Error, status?: number, config?: ErrorPageConfig): Promise<Response>;
/**
* HTTP error definitions. Each entry includes a doc link, likely causes,
// and a concrete suggestion so the dev-mode error page reads like a hint
// instead of just "something went wrong".
* @defaultValue
* ```ts
* {
* 400: {
* status: 400,
* title: 'Bad Request',
* message: 'The request was malformed or invalid.',
* 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', ],
* suggestion: 'Inspect the request body and Content-Type — most 400s come from malformed JSON or a missing required field.'
* },
* 401: {
* status: 401,
* title: 'Unauthorized',
* message: 'Authentication is required to access this resource.',
* commonCauses: [ 'No Authorization header was sent', 'The bearer token expired', 'The session cookie was cleared', ],
* suggestion: 'Confirm a valid `Authorization: Bearer <token>` header is sent and the token has not expired.'
* },
* 403: {
* status: 403,
* title: 'Forbidden',
* message: 'You do not have permission to access this resource.',
* 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', ],
* suggestion: 'Check Gates / policies and the abilities encoded in the access token.'
* },
* 404: {
* status: 404,
* title: 'Not Found',
* message: 'The requested resource could not be found.',
* commonCauses: [ 'The route is not registered in app/Routes.ts', 'A typo in the URL path', 'A model lookup returned no row (ModelNotFoundError)', ],
* suggestion: 'Run `buddy route:list` to see registered routes, or verify the model exists with the given id.'
* },
* 405: {
* status: 405,
* title: 'Method Not Allowed',
* message: 'The request method is not supported for this resource.',
* commonCauses: [ 'The route is registered for a different HTTP method', 'A form posted GET when the route expects POST', ],
* suggestion: 'Confirm the HTTP method in `app/Routes.ts` matches what the client sent.'
* },
* 408: {
* status: 408,
* title: 'Request Timeout',
* message: 'The request took too long to complete.',
* commonCauses: [ 'A long-running query or external API call exceeded the timeout', 'The client uploaded a slow body that stalled', ],
* suggestion: 'Move slow work into a queued job, or raise the route timeout if the work is genuinely long.'
* },
* 409: {
* status: 409,
* title: 'Conflict',
* message: 'The request conflicts with the current state of the resource.',
* commonCauses: [ 'A unique-constraint violation (duplicate email, slug, etc.)', 'Optimistic locking detected a stale write', ],
* suggestion: 'Re-fetch the resource and retry, or surface the conflict to the user.'
* },
* 410: {
* status: 410,
* title: 'Gone',
* message: 'The requested resource is no longer available.',
* commonCauses: ['The resource was permanently deleted', 'A signed URL expired'],
* suggestion: 'Issue a fresh signed URL or fall back to the canonical resource.'
* },
* 422: {
* status: 422,
* title: 'Unprocessable Entity',
* message: 'The request was well-formed but could not be processed.',
* commonCauses: [ 'Validation rules from the action / model rejected the payload', 'A field value is outside the allowed range or shape', ],
* suggestion: 'Inspect `errors` in the response body — each key maps to a failing field.'
* },
* 429: {
* status: 429,
* title: 'Too Many Requests',
* message: 'You have exceeded the rate limit.',
* commonCauses: ['Rate-limit middleware tripped on this IP / token', 'A retry loop is hammering the endpoint'],
* suggestion: 'Honor the `Retry-After` response header and back off before retrying.'
* },
* 500: {
* status: 500,
* title: 'Internal Server Error',
* message: 'An unexpected error occurred on the server.',
* commonCauses: [ 'An unhandled exception in an action or middleware', 'A failing database connection or migration', 'A misconfigured environment variable', ],
* suggestion: 'Check server logs for the original stack trace — the error page above shows the throw site in dev.'
* },
* 502: {
* status: 502,
* title: 'Bad Gateway',
* message: 'The server received an invalid response from an upstream server.',
* commonCauses: ['An upstream HTTP API returned a malformed response', 'A reverse proxy could not reach the origin'],
* suggestion: 'Verify the upstream service is healthy and returning the expected content type.'
* },
* 503: {
* status: 503,
* title: 'Service Unavailable',
* message: 'The service is temporarily unavailable.',
* commonCauses: ['Maintenance mode is enabled', 'A health check is failing', 'A dependency (db, redis, queue) is down'],
* suggestion: 'Run `buddy doctor` and check dependent services.'
* },
* 504: {
* status: 504,
* title: 'Gateway Timeout',
* message: 'The upstream server did not respond in time.',
* commonCauses: ['An upstream HTTP call exceeded its deadline', 'A long-running database query timed out'],
* suggestion: 'Move the work to a queued job or raise the upstream timeout if the latency is expected.'
* }
* }
* ```
*/
export declare const HTTP_ERRORS: Record<HttpStatusCode, HttpError>;
/**
* Error Page Rendering - Ignition-style error pages
*
* Provides beautiful development error pages with full stack traces,
* database queries, and request context.
*/
// Types
export declare interface ErrorPageConfig {
appName?: string
theme?: 'light' | 'dark' | 'auto'
showEnvironment?: boolean
showQueries?: boolean
showRequest?: boolean
enableCopyMarkdown?: boolean
snippetLines?: number
basePaths?: string[]
showFrameworkFrames?: boolean
}
export declare interface RequestContext {
method: string
url: string
headers: Record<string, string>
queryParams?: Record<string, string>
body?: unknown
}
export declare interface RoutingContext {
controller?: string
routeName?: string
middleware?: string[]
}
export declare interface UserContext {
id?: string | number
email?: string
name?: string
}
export declare interface QueryInfo {
query: string
time?: number
connection?: string
}
export declare interface StackFrame {
file: string
line: number
column?: number
function?: string
code?: string
}
export declare interface CodeSnippet {
file: string
line: number
code: string[]
highlight: number
}
export declare interface EnvironmentContext {
nodeVersion?: string
platform?: string
arch?: string
env?: Record<string, string>
}
export declare interface JobContext {
name?: string
queue?: string
attempts?: number
}
export declare interface ErrorPageData {
error: Error
status: number
stack: StackFrame[]
request?: RequestContext
routing?: RoutingContext
user?: UserContext
queries?: QueryInfo[]
environment?: EnvironmentContext
job?: JobContext
framework?: { name: string, version?: string }
}
export declare interface HttpError {
status: HttpStatusCode
title: string
message: string
docLink?: string
commonCauses?: string[]
suggestion?: string
}
export type HttpStatusCode = 400 | 401 | 403 | 404 | 405 | 408 | 409 | 410 | 422 | 429 | 500 | 502 | 503 | 504;
/**
* Error Page Handler class
*/
export declare class ErrorPageHandler {
constructor(config?: ErrorPageConfig);
setFramework(name: string, version?: string): this;
setRequest(request: Request | RequestContext): this;
setRouting(routing: RoutingContext): this;
setUser(user: UserContext): this;
addQuery(query: string, time?: number, connection?: string): this;
render(error: Error, status?: number): Promise<string>;
handleError(error: Error, status?: number): Promise<Response>;
}
export { ERROR_PAGE_CSS };