chanfana
Version:
OpenAPI 3 and 3.1 schema generator and validator for Hono, itty-router and more!
1,186 lines (1,169 loc) • 61.8 kB
TypeScript
import * as _asteasolutions_zod_to_openapi from '@asteasolutions/zod-to-openapi';
import { RouteConfig, ZodMediaTypeObject, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
export { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
import { Hono, Input } from 'hono';
import { Env, Schema, MergeSchemaPath, MergePath, BlankInput, HandlerResponse, H, ToSchema, TypedResponse } from 'hono/types';
import * as openapi3_ts_oas31 from 'openapi3-ts/oas31';
import { HeadersObject as HeadersObject$2, LinksObject as LinksObject$2 } from 'openapi3-ts/oas31';
import * as openapi3_ts_oas30 from 'openapi3-ts/oas30';
import { OpenAPIObject, HeadersObject as HeadersObject$1, LinksObject as LinksObject$1 } from 'openapi3-ts/oas30';
import { ZodObject, ZodType, z, ZodPipe } from 'zod';
type AnyZodObject = ZodObject<any, any>;
type OrderByDirection = "asc" | "desc";
type Simplify<T> = {
[KeyType in keyof T]: T[KeyType];
} & {};
type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends <G>() => G extends B ? 1 : 2 ? true : false;
type Filter<KeyType, ExcludeType> = IsEqual<KeyType, ExcludeType> extends true ? never : KeyType extends ExcludeType ? never : KeyType;
type ExceptOptions = {
requireExactProps?: boolean;
};
type Except<ObjectType, KeysType extends keyof ObjectType, Options extends ExceptOptions = {
requireExactProps: false;
}> = {
[KeyType in keyof ObjectType as Filter<KeyType, KeysType>]: ObjectType[KeyType];
} & (Options["requireExactProps"] extends true ? Partial<Record<KeysType, never>> : {});
type SetOptional<BaseType, Keys extends keyof BaseType> = Simplify<Except<BaseType, Keys> & Partial<Pick<BaseType, Keys>>>;
type SetRequired<BaseType, Keys extends keyof BaseType> = BaseType extends unknown ? Simplify<Except<BaseType, Keys> & Required<Pick<BaseType, Keys>>> : never;
type OpenAPIObjectConfig = Omit<OpenAPIObject, "paths" | "components" | "webhooks">;
type OpenAPIObjectConfigV31 = Omit<OpenAPIObject, "paths" | "components" | "webhooks">;
type HeadersObject = HeadersObject$1 | HeadersObject$2;
type LinksObject = LinksObject$1 | LinksObject$2;
type ZodMediaType = "application/json" | "text/html" | "text/plain" | "application/xml" | (string & {});
type ZodContentObject = Partial<Record<ZodMediaType, ZodMediaTypeObject>>;
interface ZodRequestBody {
description?: string;
content: ZodContentObject;
required?: boolean;
}
interface ResponseConfig {
description: string;
headers?: AnyZodObject | HeadersObject;
links?: LinksObject;
content?: ZodContentObject;
}
type RouteParameter = AnyZodObject | ZodPipe<AnyZodObject, any> | undefined;
interface RouterOptions {
base?: string;
schema?: Partial<OpenAPIObjectConfigV31 | OpenAPIObjectConfig>;
docs_url?: string | null;
redoc_url?: string | null;
openapi_url?: string | null;
raiseUnknownParameters?: boolean;
generateOperationIds?: boolean;
openapiVersion?: "3" | "3.1";
raiseOnError?: boolean;
passthroughErrors?: boolean;
/**
* When enabled, response bodies are parsed through their Zod schema,
* stripping unknown fields and validating required fields/types.
* Validation failures result in a 500 error response and a console.error log.
* Responses without a Zod schema are passed through unchanged.
*/
validateResponse?: boolean;
}
interface RouteOptions {
router: any;
raiseUnknownParameters: boolean;
raiseOnError?: boolean;
passthroughErrors?: boolean;
validateResponse?: boolean;
route: string;
urlParams: Array<string>;
}
type RequestTypes = {
body?: ZodRequestBody;
params?: AnyZodObject;
query?: AnyZodObject;
cookies?: AnyZodObject;
headers?: AnyZodObject | ZodType<unknown>[];
};
type OpenAPIRouteSchema = Simplify<Omit<RouteConfig, "responses" | "method" | "path" | "request"> & {
request?: RequestTypes;
responses?: {
[statusCode: string]: ResponseConfig;
};
"x-ignore"?: boolean;
}>;
type ValidatedData<S> = S extends OpenAPIRouteSchema ? {
query: GetRequest<S> extends NonNullable<GetRequest<S>> ? GetOutput<GetRequest<S>, "query"> : undefined;
params: GetRequest<S> extends NonNullable<GetRequest<S>> ? GetOutput<GetRequest<S>, "params"> : undefined;
headers: GetRequest<S> extends NonNullable<GetRequest<S>> ? GetOutput<GetRequest<S>, "headers"> : undefined;
body: GetRequest<S> extends NonNullable<GetRequest<S>> ? GetBody<GetPartBody<GetRequest<S>, "body">> : undefined;
} : {
query: undefined;
params: undefined;
headers: undefined;
body: undefined;
};
type GetRequest<T extends OpenAPIRouteSchema> = T["request"];
type GetOutput<T extends object | undefined, P extends keyof T> = T extends NonNullable<T> ? (T[P] extends AnyZodObject ? z.output<T[P]> : undefined) : undefined;
type GetPartBody<T extends RequestTypes, P extends keyof T> = T[P] extends ZodRequestBody ? T[P] : undefined;
type GetBody<T extends ZodRequestBody | undefined> = T extends NonNullable<T> ? T["content"]["application/json"] extends NonNullable<T["content"]["application/json"]> ? T["content"]["application/json"]["schema"] extends z.ZodType ? z.output<T["content"]["application/json"]["schema"]> : undefined : undefined : undefined;
declare class OpenAPIRegistryMerger extends OpenAPIRegistry {
_definitions: {
route: {
path: string;
};
}[];
merge(registry: OpenAPIRegistryMerger, basePath?: string): void;
}
type OpenAPIRouterType<M> = {
original: M;
options: RouterOptions;
registry: OpenAPIRegistryMerger;
schema: any;
};
/**
* Valid HTTP methods for OpenAPI routes.
* These are the standard methods supported by the OpenAPI specification.
*/
type HttpMethod = "get" | "head" | "post" | "put" | "delete" | "patch";
/**
* Handles the generation of OpenAPI schema and serves the documentation UI.
*
* Paths defined with `x-ignore: true` in their `OpenAPIRouteSchema`
* will be excluded from the generated OpenAPI specification by the CLI tool.
*/
declare class OpenAPIHandler {
router: any;
options: RouterOptions;
registry: OpenAPIRegistryMerger;
allowedMethods: string[];
/**
* When true, the underlying router handles base path prefixing for route
* registration (e.g. Hono's basePath()). Doc route paths will be registered
* without the base prefix since the router adds it automatically.
* The base is still used for schema generation and HTML references.
*
* This is a getter (not a field) so that subclass overrides take effect
* even when accessed during the base class constructor (createDocsRoutes).
*/
protected get routerHandlesBasePrefix(): boolean;
/**
* Hook for adapters to wrap route handler functions.
* Called for each OpenAPIRoute handler during route registration.
* The base implementation returns the handler as-is.
* Subclasses (e.g. HonoOpenAPIHandler) can override this to add
* error conversion or other adapter-specific behavior.
*/
protected wrapHandler(handler: (...args: any[]) => Promise<Response>): (...args: any[]) => Promise<Response>;
constructor(router: any, options?: RouterOptions);
/**
* Creates the documentation routes for Swagger UI, ReDoc, and OpenAPI JSON/YAML.
* Respects the base path configuration for consistent URL generation.
*/
createDocsRoutes(): void;
/**
* Generates the OpenAPI schema document from registered routes.
* @returns The complete OpenAPI specification object
*/
getGeneratedSchema(): openapi3_ts_oas30.OpenAPIObject | openapi3_ts_oas31.OpenAPIObject;
/**
* Registers a nested router and merges its OpenAPI registry.
* @param params - Nested router parameters
* @returns Array containing the nested router's fetch handler
*/
registerNestedRouter(params: {
method: string;
nestedRouter: any;
path?: string;
}): any[];
/**
* Parses a route path, applying base path and converting to OpenAPI format.
* @param path - The route path to parse
* @returns The parsed and formatted path
*/
parseRoute(path: string): string;
/**
* Sanitizes an operationId to ensure it's valid for OpenAPI.
* @param operationId - The raw operationId
* @returns A sanitized operationId
*/
private sanitizeOperationId;
/**
* Registers a route with the OpenAPI registry.
* @param params - Route registration parameters
* @returns Array of wrapped handlers
*/
registerRoute(params: {
method: string;
path: string;
handlers: any[];
doRegister?: boolean;
}): any[];
/**
* Handles common proxy properties for the wrapped router.
* Provides access to isChanfana flag, original router, schema, and registry.
*/
handleCommonProxy(_target: any, prop: string, ..._args: any[]): any;
/**
* Gets the Request object from handler arguments.
* Must be implemented by subclasses.
* @param _args - Handler arguments
*/
getRequest(_args: any[]): Request;
/**
* Gets URL parameters from handler arguments.
* Must be implemented by subclasses.
* @param _args - Handler arguments
*/
getUrlParams(_args: any[]): Record<string, any>;
/**
* Gets environment bindings from handler arguments.
* Must be implemented by subclasses.
* @param _args - Handler arguments
*/
getBindings(_args: any[]): Record<string, any>;
}
/**
* Base class for all OpenAPI route handlers.
* Provides request validation, error handling, and response formatting.
*
* @template HandleArgs - Router handler arguments type
*/
declare class OpenAPIRoute<HandleArgs extends Array<object> = any> {
/**
* The main handler method to be implemented by subclasses.
* @param _args - Handler arguments (context, request, etc. depending on router)
* @returns Response object or plain object (will be auto-converted to JSON)
*/
handle(..._args: any[]): Response | Promise<Response> | object | Promise<object>;
static isRoute: boolean;
/** Args the execute() was called with */
args: HandleArgs;
/** Cache for validated data - prevents re-validation on multiple calls */
validatedData: any;
/** Cache for raw request data before Zod applies defaults/transformations */
unvalidatedData: any;
/** Route configuration options */
params: RouteOptions;
/** OpenAPI schema definition for this route */
schema: OpenAPIRouteSchema;
constructor(params: RouteOptions);
/**
* Gets validated request data, validating the request if not already done.
* Results are cached for subsequent calls.
*
* @returns Validated data including params, query, headers, and body
*/
getValidatedData<S = any>(): Promise<ValidatedData<S>>;
/**
* Gets raw request data before Zod validation/transformation.
* Useful for checking which fields were actually sent in the request,
* especially when using Zod 4 with optional fields that have defaults.
*
* @returns Raw request data object
*/
getUnvalidatedData(): Promise<any>;
/**
* Returns the OpenAPI schema for this route.
* Override this method to customize schema properties.
*/
getSchema(): OpenAPIRouteSchema;
/**
* Returns the schema with Zod types, adding default response if not provided.
* Note: This creates a shallow copy - nested objects are still references.
*/
getSchemaZod(): OpenAPIRouteSchema;
/**
* Hook to transform errors thrown during handle().
* Override this method to wrap, replace, or re-classify errors before
* chanfana's default error formatting runs.
*
* The returned value is used for all subsequent error handling:
* - If `raiseOnError` is true, the returned error is re-thrown (e.g. to Hono's onError).
* - Otherwise, chanfana's `formatChanfanaError` is called on the returned error.
*
* @example
* ```typescript
* class MyRoute extends OpenAPIRoute {
* protected handleError(error: unknown): unknown {
* // Wrap ApiExceptions so they bypass chanfana's formatter
* // and reach Hono's onError handler directly
* if (error instanceof ApiException) {
* return new MyCustomError(error);
* }
* return error;
* }
* }
* ```
*
* @param error - The caught error
* @returns The error (possibly transformed) to be handled by chanfana.
* Should be an Error instance. Returning non-Error values (null, strings, etc.)
* may produce confusing stack traces if the error is ultimately re-thrown.
*/
protected handleError(error: unknown): unknown;
/**
* Main execution method called by the router.
* Handles validation, error catching, and response formatting.
*
* Caches are reset on each execution to ensure request isolation.
*
* @param args - Handler arguments from the router
* @returns Response object
*/
execute(...args: HandleArgs): Promise<any>;
/**
* Finds the Zod schema for a response with the given status code.
* Falls back to the "default" response if no exact match is found.
* @param statusCode - HTTP status code to look up
* @returns Zod schema for the response body, or undefined if not found
*/
getResponseSchema(statusCode: number): z.ZodType | undefined;
/**
* Validates a response body against the response schema.
* For plain objects, parses through Zod to strip unknown fields and validate types.
* For Response objects with JSON content, clones the body, parses, and reconstructs
* with corrected headers (Content-Length/Transfer-Encoding are removed).
* Responses without a matching Zod schema (including non-JSON responses) are passed through unchanged.
*
* Note: Body-dependent headers such as ETag or Content-MD5 are preserved from the
* original response and may become stale after fields are stripped or defaults applied.
*
* @param resp - The response from handle()
* @returns The validated/stripped response
* @throws ZodError if the response body fails schema validation
* @throws SyntaxError if a Response claims application/json but the body is not valid JSON
*/
validateResponse(resp: any): Promise<any>;
/**
* Validates the incoming request against the schema.
* @param request - The incoming Request object
* @returns Validated and typed request data
* @throws ZodError if validation fails
*/
validateRequest(request: Request): Promise<any>;
}
type MergeTypedResponse<T> = T extends Promise<infer T2> ? T2 extends TypedResponse ? T2 : TypedResponse : T extends TypedResponse ? T : TypedResponse;
type HonoOpenAPIRouterType<E extends Env = Env, S extends Schema = {}, BasePath extends string = "/"> = OpenAPIRouterType<Hono<E, S, BasePath>> & {
on(method: string, path: string, endpoint: typeof OpenAPIRoute<any>): Hono<E, S, BasePath>["on"];
on(method: string, path: string, router: Hono<E, S, BasePath>): Hono<E, S, BasePath>["on"];
route<SubPath extends string, SubEnv extends Env, SubSchema extends Schema, SubBasePath extends string>(path: SubPath, app: HonoOpenAPIRouterType<SubEnv, SubSchema, SubBasePath>): HonoOpenAPIRouterType<E, MergeSchemaPath<SubSchema, MergePath<BasePath, SubPath>> | S, BasePath>;
all<P extends string, I extends Input = BlankInput, R extends HandlerResponse<any> = any>(path: P, endpoint: typeof OpenAPIRoute<any> | H): HonoOpenAPIRouterType<E, S & ToSchema<"all", MergePath<BasePath, P>, I, MergeTypedResponse<R>>, BasePath>;
delete<P extends string, I extends Input = BlankInput, R extends HandlerResponse<any> = any>(path: P, endpoint: typeof OpenAPIRoute<any> | H): HonoOpenAPIRouterType<E, S & ToSchema<"delete", MergePath<BasePath, P>, I, MergeTypedResponse<R>>, BasePath>;
delete(path: string, router: Hono<E, S, BasePath>): Hono<E, S, BasePath>["delete"];
get<P extends string, I extends Input = BlankInput, R extends HandlerResponse<any> = any>(path: P, endpoint: typeof OpenAPIRoute<any> | H): HonoOpenAPIRouterType<E, S & ToSchema<"get", MergePath<BasePath, P>, I, MergeTypedResponse<R>>, BasePath>;
get(path: string, router: Hono<E, S, BasePath>): Hono<E, S, BasePath>["get"];
patch<P extends string, I extends Input = BlankInput, R extends HandlerResponse<any> = any>(path: P, endpoint: typeof OpenAPIRoute<any> | H): HonoOpenAPIRouterType<E, S & ToSchema<"patch", MergePath<BasePath, P>, I, MergeTypedResponse<R>>, BasePath>;
patch(path: string, router: Hono<E, S, BasePath>): Hono<E, S, BasePath>["patch"];
post<P extends string, I extends Input = BlankInput, R extends HandlerResponse<any> = any>(path: P, endpoint: typeof OpenAPIRoute<any> | H): HonoOpenAPIRouterType<E, S & ToSchema<"post", MergePath<BasePath, P>, I, MergeTypedResponse<R>>, BasePath>;
post(path: string, router: Hono<E, S, BasePath>): Hono<E, S, BasePath>["post"];
put<P extends string, I extends Input = BlankInput, R extends HandlerResponse<any> = any>(path: P, endpoint: typeof OpenAPIRoute<any> | H): HonoOpenAPIRouterType<E, S & ToSchema<"put", MergePath<BasePath, P>, I, MergeTypedResponse<R>>, BasePath>;
put(path: string, router: Hono<E, S, BasePath>): Hono<E, S, BasePath>["put"];
} & Hono<E, S, BasePath>;
declare class HonoOpenAPIHandler extends OpenAPIHandler {
protected get routerHandlesBasePrefix(): boolean;
/**
* Wraps route handlers to catch chanfana errors (ZodError, ApiException)
* and convert them to Hono HTTPException instances. This allows errors to
* flow through Hono's onError handler while preserving chanfana's default
* error response format via HTTPException.getResponse().
*/
protected wrapHandler(handler: (...args: any[]) => Promise<Response>): (...args: any[]) => Promise<Response>;
getRequest(args: any[]): any;
getUrlParams(args: any[]): Record<string, any>;
getBindings(args: any[]): Record<string, any>;
}
declare function fromHono<M extends Hono<E, S, BasePath>, E extends Env = M extends Hono<infer E, any, any> ? E : never, S extends Schema = M extends Hono<any, infer S, any> ? S : never, BasePath extends string = M extends Hono<any, any, infer BP> ? BP : never>(router: M, options?: RouterOptions): HonoOpenAPIRouterType<E, S, BasePath>;
type IttyRouterOpenAPIRouterType<M> = OpenAPIRouterType<M> & {
all(path: string, endpoint: typeof OpenAPIRoute<any>): (M & any)["all"];
all(path: string, router: M): (M & any)["all"];
delete(path: string, endpoint: typeof OpenAPIRoute<any>): (M & any)["delete"];
delete(path: string, router: M): (M & any)["delete"];
get(path: string, endpoint: typeof OpenAPIRoute<any>): (M & any)["get"];
get(path: string, router: M): (M & any)["get"];
head(path: string, endpoint: typeof OpenAPIRoute<any>): (M & any)["head"];
head(path: string, router: M): (M & any)["head"];
patch(path: string, endpoint: typeof OpenAPIRoute<any>): (M & any)["patch"];
patch(path: string, router: M): (M & any)["patch"];
post(path: string, endpoint: typeof OpenAPIRoute<any>): (M & any)["post"];
post(path: string, router: M): (M & any)["post"];
put(path: string, endpoint: typeof OpenAPIRoute<any>): (M & any)["put"];
put(path: string, router: M): (M & any)["put"];
};
declare class IttyRouterOpenAPIHandler extends OpenAPIHandler {
getRequest(args: any[]): any;
getUrlParams(args: any[]): Record<string, any>;
getBindings(args: any[]): Record<string, any>;
}
declare function fromIttyRouter<M>(router: M, options?: RouterOptions): M & IttyRouterOpenAPIRouterType<M>;
type JsonContent<T> = {
content: {
"application/json": {
schema: z.ZodType<T>;
};
};
};
declare const contentJson: <T>(schema: z.ZodType<T>) => JsonContent<T>;
type FilterCondition = {
field: string;
operator: string;
value: string | number | boolean | null;
};
type ListFilters = {
filters: Array<FilterCondition>;
options: {
page?: number;
per_page?: number;
order_by?: string;
order_by_direction?: OrderByDirection;
[key: string]: unknown;
};
};
type Filters = {
filters: Array<FilterCondition>;
};
type UpdateFilters = {
filters: Array<FilterCondition>;
updatedData: Record<string, any>;
};
type UpdatedData = {
updatedData: Record<string, any>;
};
type SerializerContext = {
filters?: Array<FilterCondition>;
options?: {
page?: number;
per_page?: number;
order_by?: string;
order_by_direction?: OrderByDirection;
[key: string]: unknown;
};
};
type Model = {
tableName: string;
schema: AnyZodObject;
primaryKeys: Array<string>;
serializer?: (obj: object, context?: SerializerContext) => object;
serializerSchema?: AnyZodObject;
};
type ModelComplete = SetRequired<Model, "serializer" | "serializerSchema">;
type MetaInput = {
model: Model;
fields?: AnyZodObject;
pathParameters?: Array<string>;
tags?: Array<string>;
};
type Meta = {
model: ModelComplete;
fields: AnyZodObject;
tags?: Array<string>;
};
type O<M extends MetaInput> = z.infer<M["model"]["schema"]>;
type ListResult<O> = {
result: Array<O>;
};
declare function MetaGenerator(meta: MetaInput): {
fields: AnyZodObject;
model: {
tableName: string;
schema: AnyZodObject;
primaryKeys: Array<string>;
serializer: ((obj: object, context?: SerializerContext) => object) | ((obj: any, _context?: SerializerContext) => any);
serializerSchema: AnyZodObject;
};
pathParameters: string[] | null;
tags: string[] | undefined;
};
declare function metaSchemaProps(meta: MetaInput): Record<string, unknown>;
type Logger = {
log: (...args: any[]) => void;
info: (...args: any[]) => void;
warn: (...args: any[]) => void;
error: (...args: any[]) => void;
debug: (...args: any[]) => void;
trace: (...args: any[]) => void;
};
declare class CreateEndpoint<HandleArgs extends Array<object> = Array<object>> extends OpenAPIRoute<HandleArgs> {
_meta: MetaInput;
get meta(): {
fields: AnyZodObject;
model: {
tableName: string;
schema: AnyZodObject;
primaryKeys: Array<string>;
serializer: ((obj: object, context?: SerializerContext) => object) | ((obj: any, _context?: SerializerContext) => any);
serializerSchema: AnyZodObject;
};
pathParameters: string[] | null;
tags: string[] | undefined;
};
getSchema(): {
servers?: openapi3_ts_oas30.ServerObject[] | undefined;
security?: openapi3_ts_oas30.SecurityRequirementObject[] | openapi3_ts_oas31.SecurityRequirementObject[] | undefined;
tags?: string[] | undefined;
externalDocs?: openapi3_ts_oas30.ExternalDocumentationObject | openapi3_ts_oas31.ExternalDocumentationObject | undefined;
summary?: string | undefined;
description?: string | undefined;
operationId?: string | undefined;
parameters?: (openapi3_ts_oas30.ParameterObject | openapi3_ts_oas30.ReferenceObject)[] | (openapi3_ts_oas31.ParameterObject | openapi3_ts_oas31.ReferenceObject)[] | undefined;
requestBody?: openapi3_ts_oas30.ReferenceObject | openapi3_ts_oas31.ReferenceObject | openapi3_ts_oas30.RequestBodyObject | openapi3_ts_oas31.RequestBodyObject | undefined;
callbacks?: openapi3_ts_oas30.CallbacksObject | openapi3_ts_oas31.CallbacksObject | undefined;
deprecated?: boolean | undefined;
request: RequestTypes | {
body: ZodRequestBody | {
content: {
"application/json": {
schema: z.ZodType<Record<string, unknown>, unknown, z.core.$ZodTypeInternals<Record<string, unknown>, unknown>>;
};
};
};
params: AnyZodObject;
query?: AnyZodObject;
cookies?: AnyZodObject;
headers?: AnyZodObject | z.ZodType<unknown>[];
};
responses: {
[statusCode: string]: ResponseConfig;
} | {
"201": {
description: string;
headers?: AnyZodObject | (openapi3_ts_oas30.HeadersObject | openapi3_ts_oas31.HeadersObject);
links?: openapi3_ts_oas30.LinksObject | openapi3_ts_oas31.LinksObject;
content: Partial<Record<ZodMediaType, _asteasolutions_zod_to_openapi.ZodMediaTypeObject>> | {
"application/json": {
schema: z.ZodType<{
success: boolean;
result: Record<string, unknown>;
}, unknown, z.core.$ZodTypeInternals<{
success: boolean;
result: Record<string, unknown>;
}, unknown>>;
};
};
};
};
"x-ignore"?: boolean | undefined;
};
getObject(): Promise<O<typeof this._meta>>;
before(data: O<typeof this._meta>): Promise<O<typeof this._meta>>;
after(data: O<typeof this._meta>): Promise<O<typeof this._meta>>;
create(data: O<typeof this._meta>): Promise<O<typeof this._meta>>;
handle(..._args: HandleArgs): Promise<Response>;
}
/**
* Base exception class for API errors.
* Extend this class to create custom API exceptions with specific status codes and error codes.
*
* @example
* ```typescript
* throw new ApiException("Something went wrong");
* ```
*/
declare class ApiException extends Error {
isVisible: boolean;
message: string;
default_message: string;
status: number;
code: number;
includesPath: boolean;
constructor(message?: string);
buildResponse(): {
code: number;
message: string;
}[];
static schema(): {
[x: number]: {
content: {
"application/json": {
schema: z.ZodType<{
success: false;
errors: {
code: number;
message: string;
}[];
}, unknown, z.core.$ZodTypeInternals<{
success: false;
errors: {
code: number;
message: string;
}[];
}, unknown>>;
};
};
description: string;
};
};
}
/**
* Exception for input validation errors (400).
* Used when request data fails Zod schema validation.
*
* @example
* ```typescript
* throw new InputValidationException("Invalid email format", ["body", "email"]);
* ```
*/
declare class InputValidationException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
path: null;
includesPath: boolean;
constructor(message?: string, path?: any);
buildResponse(): {
code: number;
message: string;
path: null;
}[];
}
/**
* Exception that aggregates multiple API exceptions.
* The highest status code among all errors will be used as the response status.
*
* @example
* ```typescript
* throw new MultiException([
* new InputValidationException("Invalid email", ["body", "email"]),
* new InputValidationException("Invalid name", ["body", "name"]),
* ]);
* ```
*/
declare class MultiException extends ApiException {
isVisible: boolean;
errors: Array<ApiException>;
status: number;
constructor(errors: Array<ApiException>);
buildResponse(): {
code: number;
message: string;
}[];
}
/**
* Exception for resource not found (404).
* Used when the requested resource doesn't exist.
*
* @example
* ```typescript
* throw new NotFoundException("User not found");
* ```
*/
declare class NotFoundException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
}
/**
* Exception for unauthorized access (401).
* Used when authentication is required but not provided or invalid.
*/
declare class UnauthorizedException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
}
/**
* Exception for forbidden access (403).
* Used when the user is authenticated but doesn't have permission.
*/
declare class ForbiddenException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
}
/**
* Exception for method not allowed (405).
* Used when the HTTP method is not supported for the requested resource.
*/
declare class MethodNotAllowedException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
}
/**
* Exception for conflict errors (409).
* Used when the request conflicts with the current state (e.g., duplicate resource).
*/
declare class ConflictException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
}
/**
* Exception for unprocessable entity (422).
* Used when the request is well-formed but semantically incorrect.
*/
declare class UnprocessableEntityException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
includesPath: boolean;
path: any;
constructor(message?: string, path?: any);
buildResponse(): {
code: number;
message: string;
path: any;
}[];
}
/**
* Exception for rate limiting (429).
* Used when the user has sent too many requests in a given time period.
*/
declare class TooManyRequestsException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
retryAfter?: number;
constructor(message?: string, retryAfter?: number);
}
/**
* Exception for unexpected server errors (500).
* Unlike the base ApiException (also 500, code 7000), this class defaults to isVisible=false,
* meaning the error message is hidden from clients and replaced with "Internal Error".
* Use this for errors that should be logged but not exposed to end users.
* Use the base ApiException when the error message is safe to expose.
*/
declare class InternalServerErrorException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
}
/**
* Exception for bad gateway errors (502).
* Used when an upstream server returns an invalid response.
*/
declare class BadGatewayException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
}
/**
* Exception for service unavailable (503).
* Used when the server is temporarily unavailable (maintenance, overload).
*/
declare class ServiceUnavailableException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
retryAfter?: number;
constructor(message?: string, retryAfter?: number);
}
/**
* Exception for gateway timeout (504).
* Used when an upstream server doesn't respond in time.
*/
declare class GatewayTimeoutException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
}
/**
* Exception for response validation errors (500).
* Used when a handler's response doesn't match its declared Zod response schema.
* This is a server-side bug (the handler produced invalid data), not a client error.
* Error details are hidden from clients (isVisible=false) and logged via console.error.
*/
declare class ResponseValidationException extends ApiException {
isVisible: boolean;
default_message: string;
status: number;
code: number;
constructor(message?: string, options?: ErrorOptions);
}
/**
* Result from getSafeFilters - contains validated SQL conditions and parameters
*/
interface SafeFilters {
/** Array of SQL condition strings (e.g., ["id = ?1", "name = ?2"]) */
conditions: string[];
/** Array of parameter values to bind to the prepared statement */
conditionsParams: (string | number | boolean | null)[];
}
/**
* Validates that a string is a safe SQL identifier (table name, column name).
* Prevents SQL injection by only allowing alphanumeric characters and underscores.
*
* @param identifier - The identifier to validate
* @param type - Type of identifier for error message (e.g., "table", "column")
* @returns The validated identifier
* @throws ApiException if the identifier is invalid
*/
declare function validateSqlIdentifier(identifier: string, type: string): string;
/**
* Validates a table name for safe use in SQL queries.
*
* @param tableName - The table name to validate
* @returns The validated table name
* @throws ApiException if the table name is invalid
*/
declare function validateTableName(tableName: string): string;
/**
* Validates a column name for safe use in SQL queries.
*
* @param columnName - The column name to validate
* @param validColumns - Optional array of valid column names to check against
* @returns The validated column name
* @throws ApiException if the column name is invalid or not in the valid list
*/
declare function validateColumnName(columnName: string, validColumns?: string[]): string;
/**
* Validates and normalizes an ORDER BY direction.
*
* @param direction - The direction string to validate
* @returns "asc" or "desc"
*/
declare function validateOrderDirection(direction: string | undefined): OrderByDirection;
/**
* Validates an ORDER BY column against a whitelist of allowed columns.
*
* @param column - The column name to order by
* @param allowedColumns - Array of columns that are allowed for ordering
* @param fallbackColumn - Column to use if the provided column is not allowed
* @returns The validated column name or fallback
*/
declare function validateOrderByColumn(column: string | undefined, allowedColumns: string[], fallbackColumn: string): string;
/**
* Builds safe SQL filter conditions from an array of filter conditions.
* Validates all column names against the provided schema columns.
*
* @param filters - Array of filter conditions
* @param validColumns - Array of valid column names from the schema
* @param startParamIndex - Starting index for parameter placeholders (default: 1)
* @returns SafeFilters object with conditions and parameters
* @throws ApiException if any column name is invalid or operator is not supported
*/
declare function buildSafeFilters(filters: FilterCondition[], validColumns: string[], startParamIndex?: number): SafeFilters;
/**
* Builds safe SQL filter conditions for primary key lookups only.
* Filters out any conditions that are not on primary key columns.
*
* @param filters - Filters object containing filter conditions
* @param primaryKeys - Array of primary key column names
* @param validColumns - Array of valid column names from the schema
* @param startParamIndex - Starting index for parameter placeholders (default: 1)
* @returns SafeFilters object with conditions and parameters
*/
declare function buildPrimaryKeyFilters(filters: Filters, primaryKeys: string[], validColumns: string[], startParamIndex?: number): SafeFilters;
/**
* Gets a D1 database binding from the worker environment.
*
* @param getBindings - Function to get bindings from router args
* @param args - Handler arguments
* @param dbName - Name of the D1 binding
* @returns D1Database instance
* @throws ApiException if binding is not defined or is not a D1 binding
*/
declare function getD1Binding(getBindings: (args: any[]) => Record<string, any>, args: any[], dbName: string): D1Database;
/**
* Handles database errors and maps UNIQUE constraint violations to custom exceptions.
*
* @param error - The caught error
* @param constraintsMessages - Map of constraint names to custom exceptions
* @param logger - Optional logger for error tracking
* @param operation - Description of the operation for logging
* @throws The mapped InputValidationException or a sanitized ApiException
*/
declare function handleDbError(error: Error, constraintsMessages: Record<string, InputValidationException>, logger?: Logger, operation?: string): never;
/**
* Builds a safe WHERE clause from conditions array.
*
* @param conditions - Array of condition strings
* @returns WHERE clause string or empty string if no conditions
*/
declare function buildWhereClause(conditions: string[]): string;
/**
* Builds a safe ORDER BY clause.
*
* @param column - Validated column name
* @param direction - Validated direction ("asc" or "desc")
* @returns ORDER BY clause string
*/
declare function buildOrderByClause(column: string, direction: OrderByDirection): string;
/**
* D1-specific CreateEndpoint implementation.
* Provides automatic INSERT operations with SQL injection prevention.
*/
declare class D1CreateEndpoint<HandleArgs extends Array<object> = Array<object>> extends CreateEndpoint<HandleArgs> {
/** Name of the D1 database binding in the worker environment. Defaults to "DB" */
dbName: string;
/** Optional logger for debugging and error tracking */
logger?: Logger;
/** Custom error messages for UNIQUE constraint violations. Keys are constraint names (e.g., "users.email") */
constraintsMessages: Record<string, InputValidationException>;
/**
* Gets the D1 database binding from the worker environment.
* @returns D1Database instance
* @throws ApiException if binding is not defined or is not a D1 binding
*/
getDBBinding(): D1Database;
/**
* Gets the list of valid column names from the model schema.
* @returns Array of valid column names
*/
protected getValidColumns(): string[];
/**
* Creates a new record in the database.
* @param data - The validated data to insert
* @returns The created record
* @throws ApiException on database errors
*/
create(data: O<typeof this._meta>): Promise<O<typeof this._meta>>;
}
declare class DeleteEndpoint<HandleArgs extends Array<object> = Array<object>> extends OpenAPIRoute<HandleArgs> {
_meta: MetaInput;
get meta(): {
fields: AnyZodObject;
model: {
tableName: string;
schema: AnyZodObject;
primaryKeys: Array<string>;
serializer: ((obj: object, context?: SerializerContext) => object) | ((obj: any, _context?: SerializerContext) => any);
serializerSchema: AnyZodObject;
};
pathParameters: string[] | null;
tags: string[] | undefined;
};
getSchema(): {
servers?: openapi3_ts_oas30.ServerObject[] | undefined;
security?: openapi3_ts_oas30.SecurityRequirementObject[] | openapi3_ts_oas31.SecurityRequirementObject[] | undefined;
tags?: string[] | undefined;
externalDocs?: openapi3_ts_oas30.ExternalDocumentationObject | openapi3_ts_oas31.ExternalDocumentationObject | undefined;
summary?: string | undefined;
description?: string | undefined;
operationId?: string | undefined;
parameters?: (openapi3_ts_oas30.ParameterObject | openapi3_ts_oas30.ReferenceObject)[] | (openapi3_ts_oas31.ParameterObject | openapi3_ts_oas31.ReferenceObject)[] | undefined;
requestBody?: openapi3_ts_oas30.ReferenceObject | openapi3_ts_oas31.ReferenceObject | openapi3_ts_oas30.RequestBodyObject | openapi3_ts_oas31.RequestBodyObject | undefined;
callbacks?: openapi3_ts_oas30.CallbacksObject | openapi3_ts_oas31.CallbacksObject | undefined;
deprecated?: boolean | undefined;
request: RequestTypes | {
body: ZodRequestBody | {
content: {
"application/json": {
schema: z.ZodType<{
[x: string]: any;
}, unknown, z.core.$ZodTypeInternals<{
[x: string]: any;
}, unknown>>;
};
};
} | undefined;
params: AnyZodObject;
query?: AnyZodObject;
cookies?: AnyZodObject;
headers?: AnyZodObject | z.ZodType<unknown>[];
};
responses: {
[statusCode: string]: ResponseConfig;
} | {
"200": {
description: string;
headers?: AnyZodObject | (openapi3_ts_oas30.HeadersObject | openapi3_ts_oas31.HeadersObject);
links?: openapi3_ts_oas30.LinksObject | openapi3_ts_oas31.LinksObject;
content: Partial<Record<ZodMediaType, _asteasolutions_zod_to_openapi.ZodMediaTypeObject>> | {
"application/json": {
schema: z.ZodType<{
success: boolean;
result: Record<string, unknown>;
}, unknown, z.core.$ZodTypeInternals<{
success: boolean;
result: Record<string, unknown>;
}, unknown>>;
};
};
};
};
"x-ignore"?: boolean | undefined;
};
getFilters(): Promise<Filters>;
before(_oldObj: O<typeof this._meta>, filters: Filters): Promise<Filters>;
after(data: O<typeof this._meta>): Promise<O<typeof this._meta>>;
delete(_oldObj: O<typeof this._meta>, _filters: Filters): Promise<O<typeof this._meta> | null>;
getObject(_filters: Filters): Promise<O<typeof this._meta> | null>;
handle(..._args: HandleArgs): Promise<{
success: boolean;
result: any;
}>;
}
/**
* D1-specific DeleteEndpoint implementation.
* Provides automatic DELETE operations with SQL injection prevention.
* Only allows deletion by primary key fields for safety.
*/
declare class D1DeleteEndpoint<HandleArgs extends Array<object> = Array<object>> extends DeleteEndpoint<HandleArgs> {
/** Name of the D1 database binding in the worker environment. Defaults to "DB" */
dbName: string;
/** Optional logger for debugging and error tracking */
logger?: Logger;
/** Custom error messages for UNIQUE constraint violations. Keys are constraint names (e.g., "users.email") */
constraintsMessages: Record<string, InputValidationException>;
/**
* Gets the D1 database binding from the worker environment.
* @returns D1Database instance
* @throws ApiException if binding is not defined or is not a D1 binding
*/
getDBBinding(): D1Database;
/**
* Gets the list of valid column names from the model schema.
* @returns Array of valid column names
*/
protected getValidColumns(): string[];
/**
* Builds safe filters that only apply to primary keys.
* This ensures that deletes can only target specific records by primary key.
* @param filters - Filters object containing all filter conditions
* @returns SafeFilters with validated conditions and parameters
*/
protected getSafeFilters(filters: Filters): SafeFilters;
/**
* Fetches the existing object before deletion.
* @param filters - Filter conditions for finding the object
* @returns The existing record or null if not found
*/
getObject(filters: Filters): Promise<O<typeof this._meta> | null>;
/**
* Deletes a record from the database.
* @param oldObj - The existing record to delete
* @param filters - Filter conditions for the deletion
* @returns The deleted record or null if deletion failed
* @throws ApiException on database errors
*/
delete(oldObj: O<typeof this._meta>, filters: Filters): Promise<O<typeof this._meta> | null>;
}
declare class ListEndpoint<HandleArgs extends Array<object> = Array<object>> extends OpenAPIRoute<HandleArgs> {
_meta: MetaInput;
get meta(): {
fields: AnyZodObject;
model: {
tableName: string;
schema: AnyZodObject;
primaryKeys: Array<string>;
serializer: ((obj: object, context?: SerializerContext) => object) | ((obj: any, _context?: SerializerContext) => any);
serializerSchema: AnyZodObject;
};
pathParameters: string[] | null;
tags: string[] | undefined;
};
filterFields?: Array<string>;
searchFields?: Array<string>;
searchFieldName: string;
pageFieldName: string;
perPageFieldName: string;
orderByFieldName: string;
orderByDirectionFieldName: string;
orderByFields: string[];
defaultOrderBy?: string;
/** Default sort direction when order_by is used. Defaults to "asc". */
defaultOrderByDirection: OrderByDirection;
get optionFields(): string[];
getSchema(): {
servers?: openapi3_ts_oas30.ServerObject[] | undefined;
security?: openapi3_ts_oas30.SecurityRequirementObject[] | openapi3_ts_oas31.SecurityRequirementObject[] | undefined;
tags?: string[] | undefined;
externalDocs?: openapi3_ts_oas30.ExternalDocumentationObject | openapi3_ts_oas31.ExternalDocumentationObject | undefined;
summary?: string | undefined;
description?: string | undefined;
operationId?: string | undefined;
parameters?: (openapi3_ts_oas30.ParameterObject | openapi3_ts_oas30.ReferenceObject)[] | (openapi3_ts_oas31.ParameterObject | openapi3_ts_oas31.ReferenceObject)[] | undefined;
requestBody?: openapi3_ts_oas30.ReferenceObject | openapi3_ts_oas31.ReferenceObject | openapi3_ts_oas30.RequestBodyObject | openapi3_ts_oas31.RequestBodyObject | undefined;
callbacks?: openapi3_ts_oas30.CallbacksObject | openapi3_ts_oas31.CallbacksObject | undefined;
deprecated?: boolean | undefined;
request: RequestTypes;
responses: {
[statusCode: string]: ResponseConfig;
} | {
"200": {
description: string;
headers?: AnyZodObject | (openapi3_ts_oas30.HeadersObject | openapi3_ts_oas31.HeadersObject);
links?: openapi3_ts_oas30.LinksObject | openapi3_ts_oas31.LinksObject;
content: Partial<Record<ZodMediaType, _asteasolutions_zod_to_openapi.ZodMediaTypeObject>> | {
"application/json": {
schema: z.ZodType<{
success: boolean;
result: Record<string, unknown>[];
}, unknown, z.core.$ZodTypeInternals<{
success: boolean;
result: Record<string, unknown>[];
}, unknown>>;
};
};
};
};
"x-ignore"?: boolean | undefined;
};
getFilters(): Promise<ListFilters>;
before(filters: ListFilters): Promise<ListFilters>;
after(data: ListResult<O<typeof this._meta>>): Promise<ListResult<O<typeof this._meta>>>;
list(_filters: ListFilters): Promise<ListResult<O<typeof this._meta>>>;
handle(..._args: HandleArgs): Promise<{
result: Record<string, unknown>[];
success: boolean;
}>;
}
/**
* D1-specific ListEndpoint implementation.
* Provides automatic SELECT with pagination, filtering, and ordering.
* Includes SQL injection prevention for all query components.
*/
declare class D1ListEndpoint<HandleArgs extends Array<object> = Array<object>> extends ListEndpoint<HandleArgs> {
/** Name of the D1 database binding in the worker environment. Defaults to "DB" */
dbName: string;
/** Optional logger for debugging and error tracking */
logger?: Logger;
/** Maximum number of results per page. Override to change the limit. */
maxPerPage: number;
/**
* Gets the D1 database binding from the worker environment.
* @returns D1Database instance
* @throws ApiException if binding is not defined or is not a D1 binding
*/
getDBBinding(): D1Database;
/**
* Gets the list of valid column names from the model schema.
* @returns Array of valid column names
*/
protected getValidColumns(): string[];
/**
* Lists records with pagination, filtering, and ordering.
* @param filters - Filter conditions and pagination options
* @returns Object containing results and pagination info
*/
list(filters: {
filters?: Array<{
field: string;
operator: string;
value: unknown;
}>;
options?: ListFilters["options"];
}): Promise<{
result: Record<string, unknown>[];
result_info: {
count: number;
page: number;
per_page: number;
total_count: number;
};
}>;
}
declare class ReadEndpoint<HandleArgs extends Array<object> = Array<object>> extends OpenAPIRoute<HandleArgs> {
_meta: MetaInput;
get meta(): {
fields: AnyZodObject;
model: {
tableName: string;
schema: AnyZodObject;
primaryKeys: Array<string>;
serializer: ((obj: object, context?: SerializerContext) => object) | ((obj: any, _context?: SerializerContext) => any);
serializerSchema: AnyZodObject;
};
pathParameters: string[] | null;
tags: string[] | undefined;
};
getSchema(): {
servers?: openapi3_ts_oas30.ServerObject[] | undefined;
security?: openapi3_ts_oas30.SecurityRequirementObject[] | openapi3_ts_oas31.SecurityRequirementObject[] | undefined;
tag