/** * This file defines the abstract class for application adapters in EcoPages. * It provides a common interface for different runtimes (e.g., Node.js, Deno) to implement. * The class includes methods for handling HTTP requests and managing application state. * It also includes a method for parsing command-line arguments. * * @module ApplicationAdapter */ import type { SourceModuleLoader } from '../../services/module-loading/module-loading-types.js'; import type { EcoPagesAppConfig } from '../../types/internal-types.js'; import type { ApiHandler, ApiHandlerContext, ErrorHandler, Middleware, RouteOptions, StaticRoute, ViewLoader, EcopagesRouteInfo } from '../../types/public-types.js'; import type { EcopagesWebSocketHandler } from '../../types/public-types.js'; import { type EcopagesRuntimeLabel } from '../../dev/runtime-server-started-message.js'; import { type ReturnParseCliArgs } from '../../utils/parse-cli-args.js'; /** * Runtime bootstrap options layered on top of the app config. * * These options let a host runtime embed Ecopages without mutating process * globals or app runtime state before calling `createApp()`. */ export interface ApplicationRuntimeOptions { /** * Forces the app into the embedded-runtime CLI mode used by host * environments. * * When enabled and no explicit `hostModuleLoader` is provided, the * adapter auto-detects a host module loader from the global scope. */ embedded?: boolean; /** * Selects which layer injects browser dev-client bootstrap (HMR runtime, reload). * * `host` disables core injection and reload signaling so embedded hosts like * Vite own the full dev-client surface. */ devClientOwner?: 'core' | 'host'; /** * Explicit source module loader for request-time imports. * * When omitted in embedded mode, the adapter attempts automatic * detection from globals set by the host environment. */ hostModuleLoader?: SourceModuleLoader; } export interface AppStartInfo { origin: string; /** * Static-generation routes available when the runtime becomes ready. * * @remarks * Empty when the server adapter has no route registry yet, or when route * resolution fails (failures never block startup). Route resolution completes * before the `onAppStart` callback runs. */ routes: EcopagesRouteInfo[]; } export type OnAppStartCallback = (info: AppStartInfo) => void; /** @deprecated Use {@link AppStartInfo}. */ export type ApplicationListeningInfo = AppStartInfo; /** @deprecated Use {@link OnAppStartCallback}. */ export type StartCallback = OnAppStartCallback; /** @deprecated Use {@link OnAppStartCallback}. */ export type ListenCallback = OnAppStartCallback; /** @deprecated Use {@link OnAppStartCallback}. */ export type ApplicationListeningCallback = OnAppStartCallback; /** * Configuration options for application adapters */ export interface ApplicationAdapterOptions { appConfig: EcoPagesAppConfig; serverOptions?: Record; runtime?: ApplicationRuntimeOptions; /** * Options for clearing the output directory before starting the server * @default false */ clearOutput?: boolean; } /** * Common interface for application adapters */ export interface ApplicationAdapter extends AsyncDisposable { /** Boot the server. Pass a callback to run when the runtime is ready (optional). */ start(onAppStart?: OnAppStartCallback): Promise; /** Invoked by embedded hosts once the app can take traffic. */ handleListening(origin: string): void | Promise; stop(force?: boolean): Promise; } /** * Handler function type for route handlers */ export type RouteHandler = ApiHandlerContext> = (context: TContext) => Promise | Response; export type RouteGroupDefinition = { prefix: string; middleware?: readonly Middleware[]; routes: readonly ApiHandler[]; }; /** * Abstract base class for application adapters across different runtimes */ export declare abstract class AbstractApplicationAdapter implements ApplicationAdapter { protected appConfig: EcoPagesAppConfig; protected serverOptions: Record; protected cliArgs: ReturnParseCliArgs; protected runtimeOptions: ApplicationRuntimeOptions; protected apiHandlers: ApiHandler[]; protected staticRoutes: StaticRoute[]; protected errorHandler?: ErrorHandler; /** * App-level WebSocket handlers keyed by URL path pattern (e.g. '/ws/chat/:id'). * Both Bun and Node adapters read this map to register upgrade routes. */ protected websocketHandlers: Map>; private onAppStartCallback?; protected readonly runtimeLabel: EcopagesRuntimeLabel; constructor(options: TOptions, runtimeLabel: EcopagesRuntimeLabel); private clearDistFolder; /** * Register a GET route handler. * * Use verb methods for inline route definitions. * For dynamic HTTP method registration, use `route(...)`. */ abstract get

= ApiHandlerContext>(path: P, handler: RouteHandler, options?: RouteOptions): this; /** * Register a POST route handler */ abstract post

= ApiHandlerContext>(path: P, handler: RouteHandler, options?: RouteOptions): this; /** * Register a PUT route handler */ abstract put

= ApiHandlerContext>(path: P, handler: RouteHandler, options?: RouteOptions): this; /** * Register a DELETE route handler */ abstract delete

= ApiHandlerContext>(path: P, handler: RouteHandler, options?: RouteOptions): this; /** * Register a PATCH route handler */ abstract patch

= ApiHandlerContext>(path: P, handler: RouteHandler, options?: RouteOptions): this; /** * Register an OPTIONS route handler */ abstract options

= ApiHandlerContext>(path: P, handler: RouteHandler, options?: RouteOptions): this; /** * Register a HEAD route handler */ abstract head

= ApiHandlerContext>(path: P, handler: RouteHandler, options?: RouteOptions): this; /** * Register a route with an explicit HTTP method. * * This is useful when the method is determined programmatically, or when * registering a pre-built route declaration object by forwarding its * `path`, `method`, and `handler` fields. */ abstract route

(path: P, method: ApiHandler['method'], handler: RouteHandler, options?: RouteOptions): this; /** * Register a pre-built API handler declaration. */ abstract add(handler: ApiHandler): this; /** * Internal method to add route handlers to the API handlers array */ protected addRouteHandler

= ApiHandlerContext>(path: P, method: ApiHandler['method'], handler: RouteHandler, middleware?: Middleware[], schema?: ApiHandler['schema']): this; /** * Create a route group with shared prefix and middleware. * Routes defined within the group inherit the prefix and middleware. * * Each adapter implements this with its own builder type to support * runtime-specific features (e.g., Bun's path parameter inference). * Implementations may also support passing a pre-built group object. * * @param prefix - URL prefix for all routes in the group (e.g., '/api/v1') * @param callback - Function that receives a builder to define routes * @param options - Optional group-level middleware */ abstract group(prefix: string, callback: (builder: unknown) => void, options?: { middleware?: readonly Middleware[]; }): this; abstract group(group: RouteGroupDefinition): this; /** * Get all registered API handlers */ getApiHandlers(): ApiHandler[]; /** * Register a view for static generation at build time. * The view must have staticPaths defined for dynamic routes. * * Uses a loader function to enable HMR in development. * * @param path - URL path pattern (e.g., '/posts/:slug') * @param loader - A function that dynamically imports the eco.page view module * @example * ```typescript * app.static('/login', () => import('./src/views/login.kita')) * app.static('/posts/:slug', () => import('./src/views/post-view.kita')) * ``` */ static

(path: string, loader: ViewLoader

): this; /** * Get all registered static routes */ getStaticRoutes(): StaticRoute[]; /** * Register a WebSocket handler for the given path pattern. * * The runtime adapter handles the HTTP→WebSocket upgrade for this path * and routes lifecycle events to `handler`. * * Supports dynamic segments via `:param` syntax. The handler receives * typed `params` and `search` fields, and a typed `context` produced by * the optional `context()` factory. * * One pattern registration matches infinite path variations. For example, * `app.websocket('/ws/chat/:roomId', handler)` matches `/ws/chat/abc`, * `/ws/chat/xyz`, etc. Each connection receives its own `params.roomId`. * * Works across both Bun and Node runtimes — no runtime-specific imports needed. * * @example * ```typescript * app.websocket('/ws/chat/:roomId', { * async context({ params, search }) { * return { username: search.username ?? 'anonymous', roomId: params.roomId }; * }, * onConnect(socket) { * socket.send(`Welcome to room ${socket.context.roomId}`); * }, * onMessage(socket, message) { * if (message.kind === 'text') { * socket.send(message.text); * } * }, * }); * ``` */ websocket = Record>(path: string, handler: EcopagesWebSocketHandler): this; /** * Get the registered WebSocket handlers map. * * @returns The map of WebSocket route patterns to handlers */ getWebsocketHandlers(): Map>; /** * Register a global error handler for all routes. * Useful for logging, monitoring integration, and custom error formatting. * * @example * ```typescript * app.onError(async (error, ctx) => { * logger.error(error); * return ctx.response.status(500).json({ error: 'Something went wrong' }); * }); * ``` */ onError(handler: ErrorHandler): this; /** * Get the registered error handler */ getErrorHandler(): ErrorHandler | undefined; /** * Initialize the server adapter based on the runtime */ protected abstract initializeServerAdapter(): Promise; /** * Boot the server. When `onAppStart` is passed, it runs once the runtime can take traffic. * Embedded apps only register the callback — the host (for example Vite) boots the port. */ start(onAppStart?: OnAppStartCallback): Promise; /** Runtime-specific server boot (dev, preview, build). */ protected abstract bootServer(): Promise; protected logServerStarted(origin: string): void; /** * Invoked by embedded hosts (for example Vite) once the app can take traffic. */ handleListening(origin: string): Promise; protected notifyListening(origin: string): Promise; /** * Resolves routes exposed on {@link AppStartInfo}. * * @remarks * Default is empty. Runtime adapters override this to read from the * initialized server route registry. */ protected resolveAppRoutes(): Promise; private invokeAppStartCallback; /** * Stops the application server and releases runtime resources. * * @remarks * Subclasses override this to shut down bound servers, watchers, and other * dev-time resources. The default implementation is a no-op so embedded * adapters that never call `start()` can still be used with `await using`. */ stop(_force?: boolean): Promise; [Symbol.asyncDispose](): Promise; /** * Handles a standard Web request without requiring a bound network server. * This is the primary interoperability surface for embedding Ecopages inside * other runtimes and frameworks. */ abstract fetch(request: TRequest): Promise; }