import { type ConfigSource } from './config'; import { Container, type PostProcessor, type ProviderDef } from './di'; import { type Context, type ControllerMeta, type Deriver, type ErrorHandler, type Guard, type Interceptor } from './http'; import { type Ctor } from './metadata'; import { type OpenApiDocument, type OpenApiOptions } from './openapi'; import { Scheduler } from './scheduling'; import type { WebSocketRoute } from './websocket'; /** The `Bun.serve` server returned by `App.listen`. */ type BunServer = ReturnType; /** Runs before routing on every request; return a `Response` to short-circuit. */ export type RequestHook = (req: Request) => void | Response | Promise; /** * Runs after a response is produced (including 404s and errors). Return a * `Response` to replace it, or nothing to keep it. */ export type ResponseHook = (res: Response, req: Request) => void | Response | Promise; /** Runs (fire-and-forget) after a response is produced — for metrics/telemetry. */ export type AfterResponseHook = (res: Response, req: Request) => void | Promise; /** A per-request timing event passed to `onTrace` hooks. */ export interface TraceEvent { /** The request that was handled. */ req: Request; /** The response that was produced (including 404s and errors). */ response: Response; /** Total time to handle the request, in milliseconds. */ durationMs: number; } /** Runs (fire-and-forget) after each request with its timing. */ export type TraceHook = (event: TraceEvent) => void; /** Runs once after the server starts listening. */ export type StartHook = (server: BunServer) => void | Promise; /** Runs once when the app is stopping (before the server closes). */ export type StopHook = () => void | Promise; /** * A WinterTC / WHATWG fetch handler: `(Request) => Response`. This is what * `app.fetch` is, and what `app.delegate()` composes — any WinterTC-compliant * app or handler (another Turnover app, or a raw function). */ export type FetchHandler = (request: Request) => Response | Promise; /** Parses a request body for one or more content types. */ export interface BodyParser { /** Media types this parser handles — exact, a subtype wildcard, or catch-all. */ contentTypes: string[]; /** * Parse the request body into the value `ctx.body()` returns. Invoked only for * a request whose content-type matched this parser's {@link BodyParser.contentTypes}; * the first registered parser to match wins, ahead of the built-in JSON/text default. * * @param req - The incoming request whose body to parse. * @returns The parsed body value (sync or async) that `ctx.body()` resolves to. */ parse(req: Request): unknown | Promise; } /** * Serializes a non-`Response` handler return value into a `Response`, or returns * `undefined` to defer to the next serializer (and finally the JSON default). */ export interface ResponseSerializer { /** * Serialize `value` into a `Response`, or return `undefined` to defer. * * @param value - The handler's (non-`Response`) return value to serialize. * @param ctx - The request context, e.g. to read `ctx.set` or negotiate content. * @returns A `Response`, or `undefined` to defer to the next serializer. */ serialize(value: unknown, ctx: Context): Response | undefined | Promise; } /** A bundle of hooks registered together (e.g. what `cors()` returns). */ export interface Plugin { /** * Runs once when the plugin is registered, with the app's container — after * `providers` are bound, before requests. The place to resolve DI-registered * collaborators (e.g. `container.resolveAll(TOKEN)`). */ onInit?: (container: Container) => void; /** Pre-routing request hook(s). See {@link App.onRequest}. */ onRequest?: RequestHook | RequestHook[]; /** Post-response hook(s), which may replace the response. See {@link App.onResponse}. */ onResponse?: ResponseHook | ResponseHook[]; /** Fire-and-forget post-response hook(s). See {@link App.onAfterResponse}. */ onAfterResponse?: AfterResponseHook | AfterResponseHook[]; /** Per-request timing hook(s). See {@link App.onTrace}. */ onTrace?: TraceHook | TraceHook[]; /** Hook(s) run once after the server starts. See {@link App.onStart}. */ onStart?: StartHook | StartHook[]; /** Hook(s) run once when the app stops. See {@link App.onStop}. */ onStop?: StopHook | StopHook[]; /** Error handler(s) added to the global chain. See {@link App.onError}. */ onError?: ErrorHandler | ErrorHandler[]; /** Body parsers to register. See {@link App.addParser}. */ parsers?: BodyParser[]; /** Response serializers to register. See {@link App.addSerializer}. */ serializers?: ResponseSerializer[]; /** * Wrap every request — outermost, around guards, the handler, and error * handling (unlike `@intercept`, which wraps only the handler, after guards). * The place to establish a per-request ambient context (e.g. an * OpenTelemetry server span). See {@link App.wrap}. */ wrap?: Interceptor | Interceptor[]; } /** Options for {@link createApp} — what to mount, how to wire DI, and lifecycle hooks. */ export interface CreateAppOptions { /** Directory to scan for `@controller` files. Defaults to the entry's dir. */ dir?: string; /** * Provide controller classes explicitly instead of scanning. Only these are * mounted (handy for tests and bundling); importing them runs their decorators. */ controllers?: Ctor[]; /** Mount `@module`-decorated classes (prefix + shared cross-cutting). */ modules?: Ctor[]; /** Bind tokens to providers (`useValue`/`useClass`/`useFactory`/`useExisting`). */ providers?: ProviderDef[]; /** Config source for `Config`/`value()` — a `ConfigSource` or a plain object. */ config?: ConfigSource | Record; /** Active profiles for `@profile` gating (defaults from env). */ profiles?: string[]; /** Hooks that wrap/replace each constructed instance (the AOP seam). */ postProcessors?: PostProcessor[]; /** Classes to construct eagerly at boot (e.g. `@onEvent` listener services). */ listeners?: Ctor[]; /** Reuse an existing container. */ container?: Container; /** * Global error handler(s), tried after any route/controller `@catchError` * handlers when a handler or guard throws. See {@link App.onError}. */ onError?: ErrorHandler | ErrorHandler[]; /** Hook(s) run before routing on every request. See {@link App.onRequest}. */ onRequest?: RequestHook | RequestHook[]; /** Hook(s) run after every response. See {@link App.onResponse}. */ onResponse?: ResponseHook | ResponseHook[]; /** Fire-and-forget hook(s) after each response. See {@link App.onAfterResponse}. */ onAfterResponse?: AfterResponseHook | AfterResponseHook[]; /** Per-request timing hook(s). See {@link App.onTrace}. */ onTrace?: TraceHook | TraceHook[]; /** Hook(s) run once after `listen()`. See {@link App.onStart}. */ onStart?: StartHook | StartHook[]; /** Hook(s) run once on `stop()`. See {@link App.onStop}. */ onStop?: StopHook | StopHook[]; /** Plugins (hook bundles) to register, e.g. `cors(...)`. */ plugins?: Plugin[]; /** Body parsers, tried by content type before the JSON/text default. */ parsers?: BodyParser[]; /** Response serializers, tried before the JSON default. */ serializers?: ResponseSerializer[]; /** Wrapper(s) around every request (outermost). See {@link App.wrap}. */ wrap?: Interceptor | Interceptor[]; /** * Compose other WinterTC handlers at path prefixes, e.g. * `{ "/legacy": legacy.fetch }`. See {@link App.delegate}. */ delegate?: Record; /** A WebSocket endpoint served alongside the HTTP routes. See {@link App.websocket}. */ websocket?: WebSocketRoute; } /** Options for {@link App.listen}. */ export interface ListenOptions { /** Bind address; defaults to the `HOST` env var, else all interfaces. */ hostname?: string; /** * Install SIGTERM/SIGINT handlers that gracefully {@link App.stop} then exit * (code 0, or 1 if shutdown fails). Default `true`. Set `false` to manage * signals yourself. */ signals?: boolean; } /** Options for {@link App.docs}. */ export interface DocsOptions { /** Path serving the OpenAPI JSON. Default `/openapi.json`. */ jsonPath?: string; /** Path serving the docs UI, or `false` to disable it. Default `/docs`. */ uiPath?: string | false; /** OpenAPI options passed to {@link App.openapi}. */ openapi?: OpenApiOptions; } /** Cross-cutting context a module (or nesting of modules) passes to a mount. */ interface InheritedContext { prefix: string; guards: Guard[]; derivers: Deriver[]; interceptors: Interceptor[]; errorHandlers: ErrorHandler[]; } /** * The mounted application built by {@link createApp} — a router, DI container, * and lifecycle host in one. Exposes a WinterTC fetch handler * ({@link App.fetch}/{@link App.handle}) plus Bun server bootstrap * ({@link App.listen}). * * @remarks * Don't construct it directly; `await createApp(options)` wires the container, * post-processors, plugins, and controllers, then returns the ready `App`. Start * it with `app.listen()` (Bun), drive it in-memory with `app.handle(req)`, or * deploy on any compliant runtime via `export default app` (its `fetch`). */ export declare class App { /** The DI container backing this app; controllers and services resolve through it. */ readonly container: Container; private readonly byPattern; private readonly staticRoutes; private readonly dynamicRoutes; private readonly operations; private readonly errorHandlers; private readonly requestHooks; private readonly responseHooks; private readonly afterResponseHooks; private readonly traceHooks; private readonly startHooks; private readonly stopHooks; private readonly parsers; private readonly serializers; private readonly requestWrappers; private readonly delegates; private server?; private wsRoute?; private readonly scheduler; /** * The WinterTC / WHATWG fetch handler for this app — `(Request) => * Promise`, bound to the app. Equivalent to {@link App.handle}; use * it to deploy on any compliant runtime (Cloudflare Workers, Deno Deploy, * Vercel, …) as `export default app` or `export default { fetch: app.fetch }`. */ readonly fetch: FetchHandler; /** * Register body parser(s), tried by content type before the default. * * @param parsers - Body parsers to add, each matched by its content types. */ addParser(...parsers: BodyParser[]): this; /** * Register response serializer(s), tried before the JSON default. * * @param serializers - Response serializers to add, tried before the JSON default. */ addSerializer(...serializers: ResponseSerializer[]): this; /** Parse a request body via the registered parsers, else the built-in default. */ private parseBody; /** * Construct an app around a DI `container` and optional `scheduler`. Prefer * {@link createApp}, which builds and wires everything for you. */ constructor(container: Container, scheduler?: Scheduler); /** * Register global error handler(s). They run (in registration order) after a * route's/controller's own `@catchError` handlers when a handler or guard * throws, until one returns a `Response`. Returns `this` for chaining. * * @param handlers - Global error handlers, run in registration order. */ onError(...handlers: ErrorHandler[]): this; /** * Register hook(s) run before routing on every request (e.g. CORS). * * @param hooks - Pre-routing hooks; a returned `Response` short-circuits. */ onRequest(...hooks: RequestHook[]): this; /** * Register hook(s) run after every response (including 404s and errors), * in registration order. Each may return a `Response` to replace the current * one, or nothing to keep it. * * @param hooks - Post-response hooks; each may return a `Response` to replace it. */ onResponse(...hooks: ResponseHook[]): this; /** * Register fire-and-forget hook(s) run after each response (metrics, logging). * * @param hooks - Hooks run after the response is settled; a thrown error or * rejected promise is caught and logged, and never delays the response. */ onAfterResponse(...hooks: AfterResponseHook[]): this; /** * Register hook(s) that receive each request's total timing. * * @param hooks - Hooks called with each request's `TraceEvent` timing. */ onTrace(...hooks: TraceHook[]): this; /** * Wrap every request with `(ctx, next) => Response`. Wrappers are outermost — * they run around guards, the handler, and error handling, and see the final * `Response` (including error-converted 5xx). The first registered is * outermost. Use it to establish a per-request ambient context, e.g. an * OpenTelemetry server span whose `context` the handler's spans nest under. * * @param wrappers - Outermost request wrappers; the first registered is outermost. */ wrap(...wrappers: Interceptor[]): this; /** * Compose another WinterTC / WHATWG fetch handler at a path prefix. Requests * under `path` are handed to `handler` with the prefix **stripped**, so a * sub-app sees paths relative to its mount point (`delegate("/legacy", sub)` * routes `/legacy/users` to `sub` as `/users`). `handler` is any * `(Request) => Response` — another Turnover app's `app.fetch`, or a raw * handler. The delegate owns its whole prefix (including * its own 404s); the app's own response hooks still apply to the result. * * @param path - Path prefix to mount under; stripped before handing off. * @param handler - The WinterTC handler that owns requests under `path`. */ delegate(path: string, handler: FetchHandler): this; /** * Serve a WebSocket endpoint alongside the HTTP routes. `listen()` upgrades a * matching request (see {@link WebSocketRoute.path}/{@link WebSocketRoute.upgrade}) * and dispatches its lifecycle callbacks; everything else routes through * `handle()` as usual. Only meaningful under `listen()` — `handle()` has no * socket to upgrade. One route per app (register the newest). * * @param route - The WebSocket route: its path, upgrade, and lifecycle callbacks. */ websocket(route: WebSocketRoute): this; /** Find the delegate that owns `path` (most specific first), and the subpath. */ private matchDelegate; /** * Register a plugin — a bundle of hooks (e.g. `cors(...)`). * * @param plugin - The plugin whose hooks, parsers, and wrappers to register. */ register(plugin: Plugin): this; /** * Register hook(s) run once after the server starts listening. * * @param hooks - Hooks run once after `listen()`, each given the server. */ onStart(...hooks: StartHook[]): this; /** * Register hook(s) run once when the app is stopping. * * @param hooks - Hooks run once while stopping, before the server closes. */ onStop(...hooks: StopHook[]): this; /** * Stop scheduled tasks, run `onStop` hooks, stop the server, run `@preDestroy`. * * @param closeActiveConnections - When `true`, close in-flight connections instead of draining them. */ stop(closeActiveConnections?: boolean): Promise; /** * Run the error-handler chain for a thrown value: scoped handlers first * (route → controller), then the global handlers, then the framework default. * Never throws, so `handle()` always resolves to a `Response`. */ private handleError; /** Register one handler under a normalized pattern + HTTP method. */ private addRoute; /** Match a path against the dynamic routes, capturing params. */ private matchDynamic; /** * Instantiate a controller (with DI) and wire its routes + guards. * * @param meta - The controller to mount: its class and base path. * @param inherited - Cross-cutting context (prefix, guards, …) from an enclosing module. */ mount(meta: ControllerMeta, inherited?: InheritedContext): void; /** * Handle a Web `Request` and return a `Response`, without opening a socket. * This is the single request path — `listen()` serves through it — so an * in-memory `app.handle(new Request(...))` behaves exactly like a live server. * Ideal for tests and offline tooling (e.g. OpenAPI extraction). * * @param req - The incoming Web `Request` to route. * @returns The `Response`, after response and trace hooks have run. */ handle(req: Request): Promise; /** Route a request to its handler (before response hooks are applied). */ private dispatch; /** * A `{ pattern: [methods] }` view of what's mounted — handy for logging. * * @returns A map from each mounted route pattern to its HTTP methods. */ routeTable(): Record; /** * Build an OpenAPI 3.1 document from the mounted routes. Provide * `options.toJsonSchema` to include body/query/params/response schemas * (Standard Schema doesn't mandate a JSON-Schema export). Serve it however you * like — e.g. `app.onRequest((req) => url==="/openapi.json" ? Response.json(app.openapi()) : undefined)`. * * @param options - OpenAPI build options (info, servers, `toJsonSchema`, …). * @returns The generated OpenAPI 3.1 document. */ openapi(options?: OpenApiOptions): OpenApiDocument; /** * Serve the OpenAPI document and an interactive docs page. Mounts * `GET /openapi.json` (the spec from {@link openapi}) and, unless disabled, * `GET /docs` (an API reference UI). Chain it after `createApp`: * * ```ts * const app = (await createApp()).docs() * app.listen() // GET /openapi.json and /docs are live * ``` * * @param options - Paths for the JSON spec and UI, and OpenAPI build options. */ docs(options?: DocsOptions): this; private sigHandlers?; /** * Start a `Bun.serve` server. Routing goes through `handle()`, so the served * behavior matches in-memory `handle()` exactly. Returns Bun's `Server` * (`.stop()`, `.port`, `.url`, `.reload()`); pass `0` for an OS-assigned port. * * @param port - Port to bind; defaults to the `PORT` env var, else `3000`. * @param options - Listen options (`hostname`, `signals`). * @returns The Bun `Server` (`.stop()`, `.port`, `.url`, `.reload()`). */ listen(port?: number, options?: ListenOptions): Bun.Server; /** Install SIGTERM/SIGINT handlers that gracefully stop, then exit. */ private installSignalHandlers; /** Remove the signal handlers installed by {@link installSignalHandlers}. */ private removeSignalHandlers; private gracefulExit; } /** * Create an app. Provide `modules` and/or `controllers` explicitly, or neither * to scan the entry directory for `@controller` files. Each controller is * instantiated through the DI container and its routes are built. Call * `.listen()` to start a `Bun.serve` server, or `.handle(req)` to drive it * in-memory. * * @param options - What to mount, DI wiring, plugins, and lifecycle hooks. * @returns A promise of the ready `App`, after async `@postConstruct` hooks run. */ export declare function createApp(options?: CreateAppOptions): Promise; export {}; //# sourceMappingURL=app.d.ts.map