UNPKG

express-zod-api

Version:

A Typescript framework to help you get an API server up and running with I/O schema validation and custom middlewares in minutes.

1,270 lines 61.6 kB
import * as _$zod from "zod"; import { z } from "zod"; import { HttpError } from "http-errors"; import * as _$express from "express"; import express, { CookieOptions, IRouter, NextFunction, Request, RequestHandler, Response } from "express"; import http from "node:http"; import https, { ServerOptions } from "node:https"; import { OpenApiBuilder, ReferenceObject, SchemaObject, TagObject } from "openapi3-ts/oas31"; import * as _$node_mocks_http0 from "node-mocks-http"; import { RequestOptions, ResponseOptions } from "node-mocks-http"; import compression from "compression"; import * as _$express_fileupload0 from "express-fileupload"; import fileUpload from "express-fileupload"; import cookieParser from "cookie-parser"; import { ListenOptions } from "node:net"; import * as _$express_serve_static_core0 from "express-serve-static-core"; import * as _$qs from "qs"; import ts from "typescript"; import * as _$zod_v4_core0 from "zod/v4/core"; declare const methods: ("get" | "post" | "put" | "delete" | "patch")[]; declare const clientMethods: ("get" | "post" | "put" | "delete" | "patch" | "head")[]; /** * @desc Methods supported by the framework API to produce Endpoints on EndpointsFactory. * @see BuildProps * @example "get" | "post" | "put" | "delete" | "patch" * */ type Method = (typeof methods)[number]; /** * @desc Methods usable on the client side, available via generated Integration and Documentation * @see withHead * @example Method | "head" * */ type ClientMethod = (typeof clientMethods)[number]; /** * @desc A container for describing an API response: its schema, status code(s) and MIME type(s). * @see ResultHandler * */ interface ApiResponse<S extends z.ZodType> { /** @desc The Zod schema describing the response body. */ schema: S; /** * @desc The status code(s) for this response. * @default 200 for a positive response, 400 for a negative one * */ statusCode?: number | [number, ...number[]]; /** * @desc The MIME type(s) of the response. * @default "application/json" * @example null — no content, typical for 204 and 302 * */ mimeType?: string | [string, ...string[]] | null; } /** @since zod 3.25.61 output type fixed */ declare const emptySchema: z.ZodObject<{}, z.core.$strip>; type EmptySchema = typeof emptySchema; /** @desc this type does not allow props assignment, but it works for reading them when merged with another interface */ type EmptyObject = z.output<EmptySchema>; type FlatObject = Record<string, unknown>; /** @link https://stackoverflow.com/a/65492934 */ type NoNever<T, F> = [T] extends [never] ? F : T; /** * @desc Using module augmentation approach you can specify tags as the keys of this interface * @example declare module "express-zod-api" { interface TagOverrides { users: unknown } } * @link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation * */ interface TagOverrides {} type Tag = NoNever<keyof TagOverrides, string>; declare const getMessageFromError: (error: Error) => string; declare const severity: { debug: number; info: number; warn: number; error: number; }; type Severity = keyof typeof severity; /** @desc You can use any logger compatible with this type. */ type AbstractLogger = Record<Severity, (message: string, meta?: any) => any>; /** * @desc Using module augmentation approach you can set the type of the actual logger used * @example declare module "express-zod-api" { interface LoggerOverrides extends winston.Logger {} } * @link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation * */ interface LoggerOverrides {} type ActualLogger = AbstractLogger & LoggerOverrides; interface Context extends FlatObject { requestId?: string; } interface BuiltinLoggerConfig { /** * @desc The minimal severity to log or "silent" to disable logging * @example "debug" also enables pretty output for inspected entities * */ level: "silent" | "warn" | "info" | "debug"; /** @desc Enables colors on printed severity and inspected entities */ color: boolean; /** * @desc Control how deeply entities should be inspected * @example null * @example Infinity * */ depth: number | null; /** * @desc Context: the metadata applicable for each logged entry, used by .child() method * @see childLoggerProvider * */ ctx: Context; } interface ProfilerOptions { message: string; /** @default "debug" */ severity?: Severity | ((ms: number) => Severity); /** @default formatDuration - adaptive units and limited fraction */ formatter?: (ms: number) => string | number; } /** @desc Built-in console logger with optional colorful inspections */ declare class BuiltinLogger implements AbstractLogger { protected readonly config: BuiltinLoggerConfig; /** @example new BuiltinLogger({ level: "debug", color: true, depth: 4 }) */ constructor({ color, level, depth, ctx }?: Partial<BuiltinLoggerConfig>); protected format(subject: unknown): string; protected print(method: Severity, message: string, meta?: unknown): void; debug(message: string, meta?: unknown): void; info(message: string, meta?: unknown): void; warn(message: string, meta?: unknown): void; error(message: string, meta?: unknown): void; child(ctx: Context): BuiltinLogger; /** * @desc The argument used for instance created by .child() method * @see ChildLoggerProvider * */ get ctx(): Context; /** @desc Measures the duration until you invoke the returned callback */ profile(message: string): () => void; profile(options: ProfilerOptions): () => void; } type Base$1 = object & { [Symbol.iterator]?: never; }; /** @desc The type allowed on the top level of Middlewares and Endpoints */ type IOSchema = z.ZodType<Base$1>; /** EndpointsFactory schema extended type when adding a Middleware */ type Extension<Current extends IOSchema | undefined, Inc extends IOSchema | undefined> = Current extends IOSchema ? Inc extends IOSchema ? z.ZodIntersection<Current, Inc> : Current : Inc; /** The Endpoint input schema type, condition wrapped into schema to make it z.output-compatible */ type FinalInputSchema<FIN extends IOSchema | undefined, BIN extends IOSchema> = z.ZodIntersection< FIN extends IOSchema ? FIN : BIN, BIN >; type LogicalOr<T> = { or: T[]; }; type LogicalAnd<T> = { and: T[]; }; type LogicalContainer<T> = LogicalOr<T | LogicalAnd<T>> | LogicalAnd<T | LogicalOr<T>> | T; interface BasicSecurity { type: "basic"; } interface BearerSecurity { type: "bearer"; format?: "JWT" | string; } interface InputSecurity<K extends string> { type: "input"; name: K; } interface HeaderSecurity { type: "header"; name: string; } interface CookieSecurity { type: "cookie"; name: string; } /** * @see https://swagger.io/docs/specification/authentication/openid-connect-discovery/ * @desc available scopes has to be provided via the specified URL */ interface OpenIdSecurity { type: "openid"; url: string; } interface AuthUrl { /** * @desc The authorization URL to use for this flow. Can be relative to the API server URL. * @see https://swagger.io/docs/specification/api-host-and-base-path/ */ authorizationUrl: string; } interface TokenUrl { /** @desc The token URL to use for this flow. Can be relative to the API server URL. */ tokenUrl: string; } interface RefreshUrl { /** @desc The URL to be used for obtaining refresh tokens. Can be relative to the API server URL. */ refreshUrl?: string; } interface Scopes<K extends string> { /** @desc The available scopes for the OAuth2 security and their short descriptions. Optional. */ scopes?: Record<K, string>; } type AuthCodeFlow<S extends string> = AuthUrl & TokenUrl & RefreshUrl & Scopes<S>; type ImplicitFlow<S extends string> = AuthUrl & RefreshUrl & Scopes<S>; type PasswordFlow<S extends string> = TokenUrl & RefreshUrl & Scopes<S>; type ClientCredFlow<S extends string> = TokenUrl & RefreshUrl & Scopes<S>; /** * @see https://swagger.io/docs/specification/authentication/oauth2/ */ interface OAuth2Security<S extends string> { type: "oauth2"; flows?: { /** @desc Authorization Code flow (previously called accessCode in OpenAPI 2.0) */ authorizationCode?: AuthCodeFlow<S>; /** @desc Implicit flow */ implicit?: ImplicitFlow<S>; /** @desc Resource Owner Password flow */ password?: PasswordFlow<S>; /** @desc Client Credentials flow (previously called application in OpenAPI 2.0) */ clientCredentials?: ClientCredFlow<S>; }; } /** * @desc Middleware security schema descriptor * @param K is an optional input field used by InputSecurity * @param S is an optional union of scopes used by OAuth2Security * */ type Security<K extends string = string, S extends string = string> = | BasicSecurity | BearerSecurity | InputSecurity<K> | HeaderSecurity | CookieSecurity | OpenIdSecurity | OAuth2Security<S>; type Handler$2<IN, CTX, RET> = (params: { /** @desc The inputs from the enabled input sources validated against the input schema of the Middleware. */ input: IN; /** * @desc The returns of the previously executed Middlewares (typed when chaining Middlewares). * @link https://github.com/RobinTail/express-zod-api/discussions/1250 * */ ctx: CTX; /** @link https://expressjs.com/en/5x/api.html#req */ request: Request; /** @link https://expressjs.com/en/5x/api.html#res */ response: Response; /** @desc The instance of the configured logger. */ logger: ActualLogger; }) => Promise<RET>; declare abstract class AbstractMiddleware { abstract execute(params: { input: unknown; ctx: FlatObject; request: Request; response: Response; logger: ActualLogger; }): Promise<FlatObject>; } /** * @desc A Middleware that validates its input schema, executes its handler, and returns context properties available to * next middlewares and the Endpoint handler. Can also declare security schemas for Documentation. * @see EndpointsFactory#addMiddleware * */ declare class Middleware< CTX extends FlatObject, RET extends FlatObject, SCO extends string, IN extends IOSchema | undefined = undefined, > extends AbstractMiddleware { constructor({ input, security, handler, }: { /** * @desc Input schema of the Middleware, combining properties from all the enabled input sources * @default undefined * @see defaultInputSources * */ input?: IN; /** * @desc Declaration of the security schemas implemented within the handler (used by Documentation). * @see Documentation * */ security?: LogicalContainer<Security<Extract<keyof z.input<IN>, string>, SCO>>; /** @desc The handler returning a context available to Endpoints. */ handler: Handler$2<z.output<IN>, CTX, RET>; }); /** @throws InputValidationError */ execute({ input, ...rest }: { input: unknown; ctx: CTX; request: Request; response: Response; logger: ActualLogger; }): Promise<RET>; } /** * @desc A wrapper around native Express middlewares that converts them into the framework's Middleware instances. * Optionally, a `provider` can extend the context, and a `transformer` can convert caught errors. * @see EndpointsFactory#addExpressMiddleware * */ declare class ExpressMiddleware<R extends Request, S extends Response, RET extends FlatObject> extends Middleware< FlatObject, RET, string > { constructor( nativeMw: (request: R, response: S, next: NextFunction) => any, { provider, transformer, }?: { /** @desc Extracts context properties from request and response after the native middleware execution. */ provider?: (request: R, response: S) => RET | Promise<RET>; /** @desc Transforms errors caught from the native middleware before they propagate further. */ transformer?: (err: Error) => Error; }, ); } type ResultSchema<R extends Result> = R extends Result<infer S> ? S : never; type DiscriminatedResult = | { output: FlatObject; error: null; } | { output: null; error: Error; }; /** * @example InputValidationError —> BadRequest(400) * @example Error —> InternalServerError(500) * */ declare const ensureHttpError: (error: Error) => HttpError; type Handler$1<RES = unknown> = ( params: DiscriminatedResult & { /** null in case of failure to parse or to find the matching endpoint (error: not found) */ input: FlatObject | null; /** can be empty: check the presence of the required property using the "in" operator */ ctx: FlatObject; request: Request; response: Response<RES>; logger: ActualLogger; }, ) => void | Promise<void>; /** * @desc Result definition for ResultHandler: a plain schema (for JSON and default status codes) or custom ApiResponse. * @see ApiResponse * */ type Result<S extends z.ZodType = z.ZodType> = S | ApiResponse<S> | ApiResponse<S>[]; /** @desc A function that lazily produces a Result definition. */ type LazyResult<R extends Result, A extends unknown[] = []> = (...args: A) => R; declare abstract class AbstractResultHandler { protected constructor(handler: Handler$1); execute(...params: Parameters<Handler$1>): void | Promise<void>; } /** * @desc The entity responsible to respond consistently. Accepts positive and negative Result definitions. * The positive definition can be a lazy function receiving the output schema of an Endpoint. * @see Result * */ declare class ResultHandler<POS extends Result, NEG extends Result> extends AbstractResultHandler { constructor(params: { /** @desc A description of the API response in case of success (schema, status code, MIME type) */ positive: POS | LazyResult<POS, [IOSchema]>; /** @desc A description of the API response in case of error (schema, status code, MIME type) */ negative: NEG | LazyResult<NEG>; /** @desc The actual implementation to transmit the response in any case */ handler: Handler$1<z.output<ResultSchema<POS> | ResultSchema<NEG>>>; }); } /** * @desc The default ResultHandler wrapping Endpoint output in `{ status: "success", data: output }` * and errors in `{ status: "error", error: { message } }`. Responds with JSON Content-Type. * Respects the status of errors from createHttpError(), others become InternalServerError (500). * @see ensureHttpError * */ declare const defaultResultHandler: ResultHandler< z.ZodObject< { status: z.ZodLiteral<"success">; data: IOSchema; }, z.core.$strip >, z.ZodObject< { status: z.ZodLiteral<"error">; error: z.ZodObject< { message: z.ZodString; }, z.core.$strip >; }, z.core.$strip > >; /** * @deprecated Resist the urge of using it: this handler is designed only to simplify the migration of legacy APIs. * @desc Responding with an array is a bad practice keeping your endpoints from evolving without breaking changes. * @desc This handler expects your endpoint to have the property 'items' in the output object schema * */ declare const arrayResultHandler: ResultHandler< z.ZodArray<z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>> | z.ZodArray<z.ZodAny>, { schema: z.ZodString; mimeType: string; } >; type OriginalStatic = typeof express.static; declare class ServeStatic { constructor(...params: Parameters<OriginalStatic>); } /** @desc Returns child logger for the given request (if configured) or the configured logger otherwise */ type GetLogger = (request?: Request) => ActualLogger; /** * @example { v1: { books: { ":bookId": getBookEndpoint } } } * @example { "v1/books/:bookId": getBookEndpoint } * @example { "get /v1/books/:bookId": getBookEndpoint } * @example { v1: { "patch /books/:bookId": changeBookEndpoint } } * @example { dependsOnMethod: { get: retrieveEndpoint, post: createEndpoint } } * @see CommonConfig.recognizeMethodDependentRoutes * */ interface Routing { [K: string]: Routing | AbstractEndpoint | ServeStatic; } type Handler<IN, OUT, CTX> = (params: { /** @desc The inputs from the enabled input sources validated against the final input schema (incl. Middlewares) */ input: IN; /** @desc The returns of the assigned Middlewares */ ctx: CTX; /** @desc The instance of the configured logger */ logger: ActualLogger; }) => Promise<OUT>; declare abstract class AbstractEndpoint { /** @desc Enables nested routes within the path assigned to the subject */ nest(routing: Routing): Routing; /** @desc Marks the route as deprecated (makes a copy of the endpoint) */ abstract deprecated(): this; abstract execute(params: { request: Request; response: Response; logger: ActualLogger; config: CommonConfig; }): Promise<void>; } declare class Endpoint<IN extends IOSchema, OUT extends IOSchema, CTX extends FlatObject> extends AbstractEndpoint { constructor(def: { deprecated?: boolean; middlewares?: AbstractMiddleware[]; inputSchema: IN; outputSchema: OUT; handler: Handler<z.output<IN>, z.input<OUT>, CTX>; resultHandler: AbstractResultHandler; description?: string; summary?: string; getOperationId?: (method: ClientMethod) => string | undefined; methods?: Method[]; scopes?: string[]; tags?: string[]; }); deprecated(): this; execute({ request, response, logger, config, }: { request: Request; response: Response; logger: ActualLogger; config: CommonConfig; }): Promise<undefined>; } type InputSource = keyof Pick<Request, "query" | "body" | "files" | "params" | "headers" | "cookies" | "signedCookies">; type InputSources = Record<Method, InputSource[]>; type Headers = Record<string, string>; type HeadersProvider = (params: { /** @desc The default headers to be overridden. */ defaultHeaders: Headers; request: Request; endpoint: AbstractEndpoint; logger: ActualLogger; }) => Headers | Promise<Headers>; type ChildLoggerProvider = (params: { request: Request; parent: ActualLogger }) => ActualLogger | Promise<ActualLogger>; type LogAccess = (request: Request, logger: ActualLogger) => void; interface CommonConfig { /** * @desc Enables cross-origin resource sharing. * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS * @desc You can override the default CORS headers by setting up a provider function here. */ cors: boolean | HeadersProvider; /** * @desc Controls how to respond to a request to an existing endpoint with an invalid HTTP method. * @example true — respond with status code 405 and "Allow" header containing a list of valid methods * @example false — respond with status code 404 (Not found) * @default true */ hintAllowedMethods?: boolean; /** * @desc Controls how to treat Routing keys matching HTTP methods ("get", "post") and having Endpoint assigned. * @example true — treat such keys as HTTP methods complementing their parent paths * { users: { get: ... }} becomes GET /users * @example false — treat such keys as nested path segments regardless of the name * { users: { get: ... }} remains /users/get * @default true */ recognizeMethodDependentRoutes?: boolean; /** * @desc The ResultHandler to use for handling routing, parsing and upload errors * @default defaultResultHandler * @see defaultResultHandler */ errorHandler?: AbstractResultHandler; /** * @desc Built-in logger configuration or an instance of any compatible logger. * @example { level: "debug", color: true } * @default { level: NODE_ENV === "production" ? "warn" : "debug", color: isSupported(), depth: 2 } * */ logger?: Partial<BuiltinLoggerConfig> | AbstractLogger; /** * @desc A child logger returned by this function can override the logger in all handlers for each request * @example ({ parent }) => parent.child({ requestId: uuid() }) * */ childLoggerProvider?: ChildLoggerProvider; /** * @desc The function for producing access logs * @default ({ method, path }, logger) => logger.debug(`${method}: ${path}`) * @example null — disables the feature * */ accessLogger?: null | LogAccess; /** * @desc You can disable the startup logo. * @default true */ startupLogo?: boolean; /** * @desc Which properties of request are combined into the input for endpoints and middlewares. * @desc The order matters: priority from lowest to highest * @default defaultInputSources * @see defaultInputSources */ inputSources?: Partial<InputSources>; } type BeforeUpload = (params: { request: Request; logger: ActualLogger }) => void | Promise<void>; type UploadOptions = Pick< fileUpload.Options, | "createParentPath" | "uriDecodeFileNames" | "safeFileNames" | "preserveExtension" | "useTempFiles" | "tempFileDir" | "debug" | "uploadTimeout" | "limits" > & { /** * @desc The error to throw when the file exceeds the configured fileSize limit (handled by errorHandler). * @see limits * @override limitHandler * @example createHttpError(413, "The file is too large") * */ limitError?: Error; /** * @desc A handler to execute before uploading — it can be used for restrictions by throwing an error. * @example ({ request }) => { throw createHttpError(403, "Not authorized"); } * */ beforeUpload?: BeforeUpload; }; interface CookieParserOptions extends cookieParser.CookieParseOptions { /** @desc The secret string or array used by cookie-parser for signed cookies */ secret?: Parameters<typeof cookieParser>[0]; } type CompressionOptions = Pick< compression.CompressionOptions, "threshold" | "level" | "strategy" | "chunkSize" | "memLevel" >; interface GracefulOptions { /** * @desc Time given to drain ongoing requests before closing the server. * @default 1000 * */ timeout?: number; /** * @desc Process event (Signal) that triggers the graceful shutdown. * @see Signals * @default [SIGINT, SIGTERM] * */ events?: string[]; /** @desc The hook to call after the server was closed, but before terminating the process. */ beforeExit?: () => void | Promise<void>; } type ServerHook = (params: { app: IRouter; /** @desc Returns child logger for the given request (if configured) or the configured logger otherwise */ getLogger: GetLogger; }) => void | Promise<void>; interface HttpConfig { /** @desc Port, UNIX socket or custom options. */ listen: number | string | ListenOptions; } interface HttpsConfig extends HttpConfig { /** @desc At least "cert" and "key" options required. */ options: ServerOptions; } interface ServerConfig extends CommonConfig { /** @desc HTTP server configuration. */ http?: HttpConfig; /** @desc HTTPS server configuration. */ https?: HttpsConfig; /** * @desc Custom JSON parser. * @default express.json() * @link https://expressjs.com/en/5x/api.html#express.json * */ jsonParser?: RequestHandler; /** * @desc Enable or configure uploads handling. * @requires express-fileupload * */ upload?: boolean | UploadOptions; /** * @desc Enable or configure response compression. * @requires compression */ compression?: boolean | CompressionOptions; /** * @desc Enable cookie parsing via cookie-parser. * @requires cookie-parser * @example true * @example { secret: "my-secret" } */ cookies?: boolean | CookieParserOptions; /** * @desc Configure or customize the parser for request query string * @example "simple" // for "node:querystring" module, array elements must be repeated: ?a=1&a=2 * @example "extended" // for "qs" module, supports nested objects and arrays with brackets: ?a[]=1&a[]=2 * @example (query) => qs.parse(query, {comma: true}) // for comma-separated arrays: ?a=1,2,3 * @default "simple" * @link https://expressjs.com/en/5x/api.html#req.query */ queryParser?: "simple" | "extended" | ((query: string) => object); /** * @desc Custom raw parser (assigns Buffer to request body) * @default express.raw() * @link https://expressjs.com/en/5x/api.html#express.raw * */ rawParser?: RequestHandler; /** * @desc Custom parser for URL Encoded requests used for submitting HTML forms * @default express.urlencoded() * @link https://expressjs.com/en/5x/api.html#express.urlencoded * */ formParser?: RequestHandler; /** * @desc A code to execute before processing the Routing of your API (and before parsing). * @desc This can be a good place for express middlewares establishing their own routes. * @desc It can help to avoid making a DIY solution based on the attachRouting() approach. * @example ({ app }) => { app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); } * */ beforeRouting?: ServerHook; /** * @desc A code to execute after processing the Routing of your API, but before error handling. * @see beforeRouting * */ afterRouting?: ServerHook; /** * @desc Rejects new connections and attempts to finish ongoing ones in the specified time before exit. * */ gracefulShutdown?: boolean | GracefulOptions; } interface AppConfig extends CommonConfig { /** @desc Your custom express app or express router instead. */ app: IRouter; } declare function createConfig(config: ServerConfig): ServerConfig; declare function createConfig(config: AppConfig): AppConfig; /** * @desc Directives shared by both request and response Cache-Control headers. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#cache_directives */ interface CommonDirectives { /** * @desc Response: the response remains fresh for N seconds after it was generated. * @desc Request: the client will accept a stored response that was generated at most N seconds ago. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#max-age */ maxAge?: number; /** * @desc Forces revalidation with the server before reuse. * @desc In a response this tells caches to revalidate; in a request it asks caches to revalidate. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#no-cache */ noCache?: boolean; /** * @desc Prevents storing the response in any cache. In a response this instructs caches not to store. * @desc In a request it asks caches not to store the request or its response. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#no-store */ noStore?: boolean; /** * @desc Prevents intermediaries from transforming the response body (e.g. converting images). * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#no-transform */ noTransform?: boolean; /** * @desc Allows a stale cached response to be reused for N seconds when the origin server returns an error. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#stale-if-error */ staleIfError?: number; } /** * @desc Directives that clients send in requests to express their caching preferences. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#request_directives */ interface CacheControl extends CommonDirectives { /** * @desc The client will accept a stored response that is stale for up to N seconds beyond its freshness lifetime. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#max-stale */ maxStale?: number; /** * @desc The client requires a stored response that will remain fresh for at least N more seconds. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#min-fresh */ minFresh?: number; /** * @desc The client wants a response only from the cache. Throw createHttpError(504) in this case. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#only-if-cached */ onlyIfCached?: boolean; } /** * @desc Directives that servers send in responses to control how caches store and reuse the response. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#response_directives */ interface CachePolicy extends CommonDirectives { /** * @desc Restricts which caches may store the response. * @example "public" — any cache (browser, proxy, CDN); for static assets, responses without user-specific data. * @example "private" — browser only; for user-specific and personalized content. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#response_directives */ scope?: "public" | "private"; /** * @desc Overrides max-age for shared caches (proxies, CDNs). Ignored by private (browser) caches. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#s-maxage */ sMaxAge?: number; /** * @desc Forces all caches to revalidate stale responses with the origin server before reusing them. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#must-revalidate */ mustRevalidate?: boolean; /** * @desc Forces proxies and CDNs to revalidate stale responses with the origin server before reusing them. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#proxy-revalidate */ proxyRevalidate?: boolean; /** * @desc A cache must understand the caching requirements for the response's status code before storing it. * @desc Pair with no-store as a fallback for caches that don't support it. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#must-understand */ mustUnderstand?: boolean; /** * @desc Indicates that the response body will never change while fresh. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#immutable */ immutable?: boolean; /** * @desc Allows a stale response to be served in the background while the cache revalidates it, for up to N seconds. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#stale-while-revalidate */ staleWhileRevalidate?: number; } /** * @desc Creates a Middleware providing caching helpers. * @param defaultPolicy — Optional default Cache-Control policy applied to all responses. * @example createCacheMiddleware({ noCache: true, scope: "private" }) */ declare const createCacheMiddleware: (defaultPolicy?: CachePolicy) => Middleware< FlatObject, { /** * @desc Provides the parsed If-None-Match request header into an array of ETags. Can also be '*' wildcard. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match */ readonly ifNoneMatch: string[] | "*" | undefined; /** * @desc Provides the parsed If-Modified-Since request header having the timestamp of the client's cached copy. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-Modified-Since */ readonly ifModifiedSince: Date | undefined; /** * @desc Provides the parsed Cache-Control request header to reveal the client's caching intent. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control */ readonly cacheControl: CacheControl | undefined; /** * @desc Augments the Cache-Control response header, merging with the defaultPolicy if provided. * @desc Pass `undefined` for a directive to unset the default value. * @see defaultPolicy * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching */ addCachePolicy: (policy: CachePolicy) => void; /** * @desc Sets the ETag response header with a unique identifier for this version of the resource. * @see ifNoneMatch * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag */ setETag: (value: string) => void; /** * @desc Sets the Last-Modified response header to the timestamp when the resource was last changed. * @see ifModifiedSince * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Last-Modified */ setLastModified: (date: Date) => void; /** * @desc Sets the Vary response header to the list of request headers that influence the response. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Vary */ setVary: (...headers: string[]) => void; /** * @desc Sets the Expires response header with an explicit expiration date. Consider addCachePolicy({ maxAge }). * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Expires */ setExpires: (date: Date) => void; /** * @desc Sets the Clear-Site-Data response header with the "cache" directive to remove all cached responses. * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Clear-Site-Data */ clearSiteData: () => void; /** * @desc Sends an HTTP 304 Not Modified empty response and ends the response stream. * @example return ctx.notModified() as never; // to satisfy the handler's return type * @see ifNoneMatch * @see ifModifiedSince * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/304 */ notModified: () => void; }, string, undefined >; /** * @desc Creates a Middleware providing cookie-setting convenience methods. * @param baseOptions — Default options applied to every setCookie / clearCookie call. * @example createCookieMiddleware({ httpOnly: true, secure: true, path: "/" }) */ declare const createCookieMiddleware: (baseOptions?: CookieOptions) => Middleware< FlatObject, { /** * @desc Reads a cookie value. Checks signedCookies first, then falls back to cookies. * @requires cookie-parser * @see ServerConfig.cookies * */ getCookie: (name: string) => z.core.util.JSONType | undefined; /** @desc Sets a cookie on the response. Express converts non-string values to JSON. */ setCookie: (name: string, value: string | z.core.util.JSONType, overrides?: CookieOptions) => void; /** @desc Clears a cookie on the response. */ clearCookie: (name: string, overrides?: Omit<CookieOptions, "expires" | "maxAge">) => void; }, string, undefined >; interface BuildProps< IN extends IOSchema, OUT extends IOSchema | z.ZodVoid, MIN extends IOSchema | undefined, CTX extends FlatObject, SCO extends string, > { /** * @desc Input schema of the Endpoint, combining properties from all the enabled input sources (path params, headers) * @default z.object({}) * @see defaultInputSources * */ input?: IN; /** @desc The schema by which the returns of the Endpoint handler is validated */ output: OUT; /** @desc The Endpoint handler receiving the validated inputs, returns of added Middlewares (ctx) and a logger */ handler: Handler<z.output<FinalInputSchema<MIN, IN>>, z.input<OUT>, CTX>; /** @desc The operation description for the generated Documentation (may use Markdown) */ description?: string; /** @desc The operation summary for the generated Documentation (short plain string) */ summary?: string; /** @desc The operation ID for the generated Documentation (must be unique) */ operationId?: string | ((method: ClientMethod) => string); /** * @desc HTTP method(s) this endpoint can handle * @default "get" unless method is explicitly defined in Routing keys * */ method?: Method | [Method, ...Method[]]; /** * @desc Scope(s) from the list of the ones defined by the added Middlewares having "oauth2" security type * @see OAuth2Security * */ scope?: SCO | SCO[]; /** * @desc Tag(s) for generating Documentation. For establishing constraints: * @see TagOverrides * */ tag?: Tag | Tag[]; /** @desc Marks the operation deprecated in the generated Documentation */ deprecated?: boolean; } /** * @desc Creates a factory for building Endpoints. It can be extended by adding Middlewares that enrich the context * available to the Endpoint handler. It requires a ResultHandler to respond consistently. * @see Middleware * @see ResultHandler * */ declare class EndpointsFactory< IN extends IOSchema | undefined = undefined, CTX extends FlatObject = EmptyObject, SCO extends string = string, > { protected resultHandler: AbstractResultHandler; protected schema: IN; protected middlewares: AbstractMiddleware[]; /** * @param resultHandler An instance of ResultHandler for handling both Endpoint outputs and all possible errors. * @see ResultHandler * */ constructor(resultHandler: AbstractResultHandler); /** * @desc Attaches a Middleware to the factory, extending the context available to Endpoints built on it. * Accepts either a Middleware instance or a plain object compatible with the Middleware constructor. * @see Middleware * */ addMiddleware<RET extends FlatObject, ASCO extends string, AIN extends IOSchema | undefined = undefined>( subject: Middleware<CTX, RET, ASCO, AIN> | ConstructorParameters<typeof Middleware<CTX, RET, ASCO, AIN>>[0], ): EndpointsFactory<Extension<IN, AIN>, (CTX extends Record<string, never> ? RET : CTX) & RET, SCO & ASCO>; /** @desc Shorthand for .addMiddleware(createCookieMiddleware()) */ useCookies(...args: Parameters<typeof createCookieMiddleware>): EndpointsFactory< Extension<IN, undefined>, (CTX extends Record<string, never> ? { getCookie: (name: string) => z.core.util.JSONType | undefined; setCookie: (name: string, value: string | z.core.util.JSONType, overrides?: _$express.CookieOptions) => void; clearCookie: (name: string, overrides?: Omit<_$express.CookieOptions, "expires" | "maxAge">) => void; } : CTX) & { getCookie: (name: string) => z.core.util.JSONType | undefined; setCookie: (name: string, value: string | z.core.util.JSONType, overrides?: _$express.CookieOptions) => void; clearCookie: (name: string, overrides?: Omit<_$express.CookieOptions, "expires" | "maxAge">) => void; }, SCO >; /** @desc Shorthand for .addMiddleware(createCacheMiddleware()) */ useCache(...args: Parameters<typeof createCacheMiddleware>): EndpointsFactory< Extension<IN, undefined>, (CTX extends Record<string, never> ? { readonly ifNoneMatch: string[] | "*" | undefined; readonly ifModifiedSince: Date | undefined; readonly cacheControl: CacheControl | undefined; addCachePolicy: (policy: CachePolicy) => void; setETag: (value: string) => void; setLastModified: (date: Date) => void; setVary: (...headers: string[]) => void; setExpires: (date: Date) => void; clearSiteData: () => void; notModified: () => void; } : CTX) & { readonly ifNoneMatch: string[] | "*" | undefined; readonly ifModifiedSince: Date | undefined; readonly cacheControl: CacheControl | undefined; addCachePolicy: (policy: CachePolicy) => void; setETag: (value: string) => void; setLastModified: (date: Date) => void; setVary: (...headers: string[]) => void; setExpires: (date: Date) => void; clearSiteData: () => void; notModified: () => void; }, SCO >; /** * @desc Shorthand for addExpressMiddleware(). Use it for wrapping native Express middlewares. * @see addExpressMiddleware * */ use: <R extends Request, S extends Response, AOUT extends FlatObject = Record<string, never>>( nativeMw: (request: R, response: S, next: _$express.NextFunction) => any, params_1?: | { provider?: ((request: R, response: S) => AOUT | Promise<AOUT>) | undefined; transformer?: (err: Error) => Error; } | undefined, ) => EndpointsFactory<Extension<IN, undefined>, (CTX extends Record<string, never> ? AOUT : CTX) & AOUT, SCO>; /** * @desc Wraps a native Express middleware and attaches it to the factory as a Middleware. Optionally, a `provider` * can extract context properties from the request and response, and a `transformer` can convert errors. * @see ExpressMiddleware * */ addExpressMiddleware<R extends Request, S extends Response, AOUT extends FlatObject = EmptyObject>( ...params: ConstructorParameters<typeof ExpressMiddleware<R, S, AOUT>> ): EndpointsFactory<Extension<IN, undefined>, (CTX extends Record<string, never> ? AOUT : CTX) & AOUT, SCO>; /** * @desc Extends the context available to Endpoints built on this factory by resolving additional properties * from an asynchronous callback. The callback receives the current accumulated context, allowing further * context values to depend on previously provided ones. This is a shorthand for addMiddleware() with no schema. * @see addMiddleware * */ addContext<RET extends FlatObject>( provider: (current: CTX) => Promise<RET>, ): EndpointsFactory<Extension<IN, undefined>, (CTX extends Record<string, never> ? RET : CTX) & RET, SCO>; /** * @desc Builds an Endpoint using the accumulated Middlewares, the ResultHandler, and the given configuration. * The output is validated against the output schema; the handler receives the validated input and context. * @see Endpoint * */ build<BOUT extends IOSchema, BIN extends IOSchema = EmptySchema>({ input, output: outputSchema, operationId, scope, tag, method, ...rest }: BuildProps<BIN, BOUT, IN, CTX, SCO>): Endpoint<FinalInputSchema<IN, BIN>, BOUT, CTX>; /** * @desc shorthand for build() having output schema assigned with an empty object * @see build * */ buildVoid<BIN extends IOSchema = EmptySchema>({ handler, ...rest }: Omit<BuildProps<BIN, z.ZodVoid, IN, CTX, SCO>, "output">): Endpoint< FinalInputSchema<IN, BIN>, z.ZodObject<{}, z.core.$strip>, CTX >; } /** * @desc The factory based on the default ResultHandler: suitable for JSON responses. * @see defaultResultHandler * */ declare const defaultEndpointsFactory: EndpointsFactory<undefined, Record<string, never>, string>; /** * @deprecated Resist the urge of using it: this factory is designed only to simplify the migration of legacy APIs. * @desc Responding with an array is a bad practice keeping your endpoints from evolving without breaking changes. * @desc The result handler of this factory expects your endpoint to have the property 'items' in the output schema */ declare const arrayEndpointsFactory: EndpointsFactory<undefined, Record<string, never>, string>; declare const attachRouting: ( config: AppConfig, routing: Routing, ) => { notFoundHandler: express.RequestHandler< _$express_serve_static_core0.ParamsDictionary, any, any, _$qs.ParsedQs, Record<string, any> >; logger: AbstractLogger | BuiltinLogger; }; declare const createServer: ( config: ServerConfig, routing: Routing, ) => Promise<{ app: _$express_serve_static_core0.Express; logger: AbstractLogger | BuiltinLogger; servers: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>[]; }>; interface ReqResCommons { makeRef: (key: object | string, value: SchemaObject | ReferenceObject, proposedName?: string) => ReferenceObject; path: string; method: ClientMethod; } interface OpenAPIContext extends ReqResCommons { isResponse: boolean; } type Depicter = ( zodCtx: { zodSchema: z.core.$ZodType; jsonSchema: z.core.JSONSchema.BaseSchema; }, oasCtx: OpenAPIContext, ) => z.core.JSONSchema.BaseSchema | SchemaObject; /** @desc Using defaultIsHeader when returns null or undefined */ type IsHeader = (name: string, method: ClientMethod, path: string) => boolean | null | undefined; type BrandHandling = Record<string | symbol, Depicter>; declare const depictTags: ( tags: Partial< Record< Tag, | string | { description: string; url?: string; } > >, ) => TagObject[]; /** @desc Ensures the summary string does not exceed the limit */ declare const trimSummary: (summary?: string, limit?: number) => string | undefined; type Component = "positiveResponse" | "negativeResponse" | "requestParameter" | "requestBody"; /** @desc user defined function that creates a component description from its properties */ type Descriptor = ( props: Record<"method" | "path" | "operationId", string> & { statusCode?: number; }, ) => string; type Summarizer = (params: { summary?: string; description?: string; trim: typeof trimSummary }) => string | undefined; interface DocumentationParams { title: string; version: string; serverUrl: string | [string, ...string[]]; routing: Routing; config: CommonConfig; /** * @desc Descriptions of various components based on their properties (method, path, operationId). * @desc When composition set to "components", component name is generated from this description * @default () => `${method} ${path} ${component}` * */ descriptions?: Partial<Record<Component, Descriptor>>; /** * @desc The function that ensures the maximum length for summary fields. Can optionally make them from descriptions. * @see defaultSummarizer * @see trimSummary * */ summarizer?: Summarizer; /** * @desc Depict the HEAD method for each Endpoint supporting the GET method (feature of Express) * @default true * */ hasHeadMethod?: boolean; /** @default inline */ composition?: "inline" | "components"; /** * @desc Handling rules for your own schemas branded with `x-brand` metadata. * @desc Keys: brands (recommended to use unique symbols). * @desc Values: functions having Zod context as first argument, second one is the framework context. * @example { MyBrand: ({ zodSchema, jsonSchema }) => ({ type: "object" }) * @link https://www.npmjs.com/package/@express-zod-api/zod-plugin */ brandHandling?: BrandHandling; /** * @desc Ability to configure recognition of headers among other input data * @desc Only applicable when "headers" is present within inputSources config option * @see defaultIsHeader * @link https://www.iana.org/assignments/http-fields/http-fields.xhtml * */ isHeader?: IsHeader; /** * @desc Extended description of tags used in endpoints. For enforcing constraints: * @see TagOverrides * @example { users: "About users", files: { description: "About files", url: "https://example.com" } } * */ tags?: Parameters<typeof depictTags>[0]; } declare class Documentation extends OpenApiBuilder { constructor({ routing, config, title, version, serverUrl, descriptions, brandHandling, tags, isHeader, hasHeadMethod, summarizer, composition, }: DocumentationParams); } /** @desc An error related to the wrong Routing declaration */ declare class RoutingError extends Error { name: string; readonly cause: { method: Method; path: string; }; constructor(message: string, method: Method, path: string); } /** * @desc An error related to the generating of the documentation * */ declare class DocumentationError extends Error { name: string; readonly cause: string; constructor(message: string, { method, path, isResponse }: Pick<OpenAPIContext, "path" | "method" | "isResponse">); } /** @desc An error related to the input and output schemas declaration */ declare class IOSchemaError extends Error { name: string; } /** @desc An error of validating the Endpoint handler's returns against the Endpoint output schema */ declare class OutputValidationError extends IOSchemaError { readonly cause: z.ZodError; name: string; constructor(cause: z.ZodError); } /** @desc An error of validating the input sources against the Middleware or Endpoint input schema */ declare class InputValidationError extends IOSchemaError { readonly cause: z.ZodError; name: string; constructor(cause: z.ZodError); } declare class MissingPeerError extends Error { name: string; constructor(module: string); } interface TestingProps<REQ, LOG> { /** * @desc Additional properties to set on Request mock * @default { method: "GET", headers: { "content-type": "application/json" } } * */ requestProps?: REQ; /** * @link https://www.npmjs.com/package/node-mocks-http * @default { req: requestMock } * */ responseOptions?: ResponseOptions; /** * @desc Additional properties to set on config mock * @default { cors: false, logger } * */ configProps?: Partial<CommonConfig>; /** * @desc Additional properties to set on logger mock * @default { info, warn, error, debug } * */ loggerProps?: LOG; } declare const testEndpoint: <LOG extends FlatObject, REQ extends RequestOptions>({ endpoint, ...rest }: TestingProps<REQ, LOG> & { /** @desc The endpoint to test */ endpoint: AbstractEndpoint; }) => Promise<{ requestMock: _$node_mocks_http0.MockRequest< Request<_$express_serve_static_core0.ParamsDictionary, any, any, _$qs.ParsedQs, Record<string, any>> & REQ >; responseMock: _$node_mocks_http0.MockResponse<Response<any, Record<string, any>>>; loggerMock: AbstractLogger & LOG & { _getLogs: () => Record<"error" | "debug" | "info" | "warn", unknown[]>; }; }>; interface MiddlewareLike<RET extends FlatObject> { execute(...params: Parameters<AbstractMiddleware["execute"]>): Promise<RET>; } declare const testMiddleware: <LOG extends FlatObject, REQ extends RequestOptions, RET extends FlatObject>({ middleware, ctx, ...rest }: TestingProps<REQ, LOG> & { /** @desc The middleware to test */ middleware: MiddlewareLike<RET>; /** @desc The aggregated returns of previously executed middlewares */ ctx?: FlatObje