import { type Server as NodeHttpServer } from 'node:http'; import type { EcoPagesAppConfig } from '../../types/internal-types.js'; import type { ApiHandler, ErrorHandler, StaticRoute, EcopagesWebSocketHandler } from '../../types/public-types.js'; import { SharedServerAdapter } from '../shared/runtime/server-adapter.js'; import type { ServerAdapterResult } from '../abstract/server-adapter.js'; import { NodeHttpRequestBridge } from './http-request-bridge.js'; import type { StaticPreviewHost } from '../shared/runtime/static-preview-host.js'; import { type NodeServerDevRuntimeFactory } from './server-adapter-dependencies.js'; export type NodeServerInstance = NodeHttpServer; export type NodeServeAdapterServerOptions = { port?: number; hostname?: string; [key: string]: unknown; }; export interface NodeServerAdapterParams { appConfig: EcoPagesAppConfig; runtimeOrigin: string; serveOptions: NodeServeAdapterServerOptions; apiHandlers?: ApiHandler[]; staticRoutes?: StaticRoute[]; errorHandler?: ErrorHandler; websocketHandlers?: Map>; hostOwnsDevClient?: boolean; options?: { watch?: boolean; }; deferRuntimeAssetSetup?: boolean; allowPortFallback?: boolean; previewHost?: StaticPreviewHost; requestBridge?: NodeHttpRequestBridge; devRuntimeFactory?: NodeServerDevRuntimeFactory; } export interface NodeServerAdapterResult extends ServerAdapterResult { completeInitialization: (server: NodeServerInstance) => Promise; handleRequest: (request: Request) => Promise; attachUserWebSocketUpgrades: (server: NodeServerInstance, options?: { passthroughUnmatched?: boolean; }) => void; dispose: () => Promise; } /** * Node.js HTTP server adapter for the Ecopages runtime. * * `NodeServerAdapter` bridges the Node.js `http` module and the Ecopages * `SharedServerAdapter` abstraction, translating between Node's * `IncomingMessage`/`ServerResponse` API and the platform-agnostic Web * `Request`/`Response` model. * * Lifecycle: * 1. `createAdapter()` — calls `initialize()` and returns the public adapter result. * 2. `completeInitialization(server)` — called once the HTTP server is listening. * Conditionally wires HMR, WebSocket upgrades, and the file watcher when * `options.watch` is `true`. * 3. `handleRequest(request)` — delegates to `handleSharedRequest` for routing; * intercepts `ClientAbortError` to return 499 instead of 500. * 4. `buildStatic()` — spins up an ephemeral runtime server, generates all static * pages against it, then tears it down. * * @see SharedServerAdapter for routing, caching and response handler logic. */ export declare class NodeServerAdapter extends SharedServerAdapter { private serverInstance; private initialized; private apiHandlers; private staticRoutes; private errorHandler?; private bridge; private hmrManager; private projectWatcher; private adapterDisposed; private readonly deferRuntimeAssetSetup; private readonly allowPortFallback; private readonly previewHost; private readonly requestBridge; private readonly devRuntimeFactory; /** * Reference to the application-level WebSocket handlers map. * * @remarks * This is a reference to the map owned by `AbstractApplicationAdapter`, * passed in via the constructor. The Node adapter reads from it to wire * WebSocket upgrades for user-registered patterns. */ protected websocketHandlers: Map>; /** * Wires user WebSocket routes onto a foreign Node HTTP server. * * Host integrations such as the Vite plugin call this so `app.websocket()` * handlers work while HTTP is still served by the host dev server. */ attachUserWebSocketUpgrades(server: NodeServerInstance, options?: { passthroughUnmatched?: boolean; }): void; private wireUserWebSocketUpgrades; /** * @remarks * `previewHost`, `requestBridge`, and `devRuntimeFactory` are optional on the * public {@link NodeServerAdapterParams} so factory callers can omit them, but * they are mandatory by the time the concrete adapter is constructed — * {@link createNodeServerAdapter} fills in Node-specific defaults first. The * constructor signature makes that invariant explicit instead of relying on * non-null assertions. */ constructor(options: NodeServerAdapterParams & { previewHost: StaticPreviewHost; requestBridge: NodeHttpRequestBridge; devRuntimeFactory: NodeServerDevRuntimeFactory; }); /** * Prepares the adapter for use. * * Order is intentional: * 1. **Loaders** are registered first so processors and integrations can * reference loader-provided file types in their own plugins. * 2. **Public dir** is copied before any build so static assets are in `distDir` * before the first request arrives. * 3. **Plugins** (processors, then integrations) are set up after the public dir * is in place so they can safely reference dist-relative paths. * 4. **Router** is initialised last because it may depend on files written by * processors during their `setup()` calls. */ initialize(): Promise; getServerOptions(): NodeServeAdapterServerOptions; buildStatic(options?: { preview?: boolean; force?: boolean; }): Promise; /** * Serves an existing static export without running SSG. Used by e2e preview * launchers after a shared prewarm build. */ servePreviewOnly(): Promise; private startPreviewServer; createAdapter(): Promise; /** * Releases dev-time resources owned by the adapter. * * @remarks * Safe to call multiple times. Does not stop the bound HTTP server — callers * should shut down transport through the runtime host before disposing. */ dispose(): Promise; /** * Handles a single incoming Web `Request` and returns a Web `Response`. * * Delegates to `handleSharedRequest` for all routing, caching, and response * handler logic. The only Node-specific concern here is translating a * `ClientAbortError` — which the body `ReadableStream` raises when the * underlying socket closes early — into a 499 response so it does not * incorrectly surface as a 500 in application logs. */ handleRequest(request: Request): Promise; /** * Called once the HTTP server is bound and listening. * * When `options.watch` is `true` this method wires the full HMR pipeline: * - A `WebSocketServer` is attached to the existing HTTP server via the * `upgrade` event (no separate port needed). * - `NodeClientBridge` tracks active WebSocket connections and handles * broadcast + heartbeat cleanup. * - `NodeHmrManager` watches the filesystem and triggers incremental * rebuilds, notifying connected clients via the bridge. * - Shared watcher bootstrapping listens for route-level file changes and * refreshes the router and response handlers when pages are added or removed. * * WebSocket upgrade requests that do not match a known path are rejected with an * immediate socket destroy to prevent unhandled upgrade leaks. */ completeInitialization(server: NodeServerInstance): Promise; } /** * Factory function that creates and fully initialises a `NodeServerAdapter`. * * `runtimeOrigin` is derived from `serveOptions` when not explicitly provided, * so callers only need to set it when the server is behind a reverse proxy that * changes the effective host or port. */ export declare function createNodeServerAdapter(params: NodeServerAdapterParams): Promise;