W WRNexusJS
Runtime

@wrnexus/dev-server

Development and production servers, HMR, assets, and gateways.

bun add @wrnexus/dev-server@0.2.15
The WRNexusJS HTTP + WebSocket server runtime — request dispatch, SSR document assembly, live-reload (HMR), and the portable production handler.

Part of the WRNexusJS framework — an SSR-first, Bun-native full-stack web framework.

Overview

This package is the server runtime that powers a WRNexusJS app in both development and production. A single request runtime (createHandlers) owns HTTP/WebSocket dispatch and SSR document assembly; it knows nothing about _how_ modules and assets are produced, so the dev and prod entry points wire in different backends: dev uses dynamic module loading plus on-the-fly bundling and injects a live-reload client; prod uses a static, pre-built manifest with cache-immutable assets. The package also ships a multi-app gateway (route several apps by Host header behind one port) and a portable node:http adapter for WinterCG hosts. It is entirely server-side and Bun-native (Bun.serve, Bun.file, Bun.gzipSync).

Installation

bun add @wrnexus/dev-server
Private package — the machine must be authenticated to the wrnexus npm org
(a read token in ~/.npmrc). Requires Bun (Node is not supported for the full server; the node:http adapter is for WinterCG embedding only).

API

Main entry (@wrnexus/dev-server)

ExportKindPurpose
startServer(opts: ServeOptions)Promise<RunningServer>Start the dev server on Bun.serve: builds the router, connects/migrates databases, wires assets + HMR, and starts the file watcher.
createHandlers(deps: RuntimeDeps)HandlersThe shared request runtime (fetch + websocket handlers). Re-exported from runtime.ts.
createProductionServer(manifest, opts)Bun.ServerStart the production server from a precompiled manifest.
createProductionHandlers(manifest, opts)HandlersBuild the portable prod fetch/websocket handlers with no server bound (the deployment-adapter seam).
startGateway(opts: GatewayOptions)Promise<RunningGateway>Boot multiple apps as child processes and route by Host.
toRequest, writeResponse, nodeListener, serveNodefunctionsnode:http ↔ WinterCG Request/Response adapter.
RESTART_EXIT_CODEnumber (97)Exit code the dev child uses to ask the supervisor for a fresh process.
STYLES_HREF, HMR_CLIENT_JSconstantsThe global stylesheet URL and the inline HMR client script.

Exported types: ServeOptions, RunningServer, RuntimeDeps, AssetServer, WsData, GatewayApp, GatewayOptions, GatewayAuth, GatewaySecurity, RunningGateway, FetchHandler.

startServer(opts)

interface ServeOptions {
  appDir: string; // absolute/relative path to the app/ dir
  port?: number; // default 3000
  hostname?: string; // default "localhost"
  mode?: Mode; // "development" | "production"; default "development"
  hmr?: boolean; // inject live-reload client; default (mode === "development")
  styleEntry?: string | null; // resolved absolute path to the global CSS entry
  stylesConfig?: StylesConfig; // custom styles processor (e.g. Tailwind/PostCSS)
  head?: string; // raw HTML appended to every page <head>
  seo?: SeoConfig; // global SEO defaults
  security?: SecurityConfig; // security headers + CORS policy
  theme?: ThemeConfig; // design-token theme (merged over built-in light/dark)
  i18n?: I18nConfig; // default language + supported locales
  db?: { driver: string; url: string }; // default db → getDb(); dev auto-migrates
  databases?: Record<string, { driver: string; url: string }>; // named dbs → getDb("<name>")
  realtime?: { scale?: boolean; redisUrl?: string }; // bridge rooms over Redis across processes
}

interface RunningServer {
  port: number;
  hostname: string;
  url: string;
  router: Router;
  stop(): void;
}

In development, startServer also connects app/db/migrations (and app/db/<name>/migrations) and auto-applies migrations, then starts an in-process file watcher. CSS edits hot-swap live; any other server change triggers process.exit(RESTART_EXIT_CODE) so the dev supervisor (@wrnexus/cli) respawns the process with fresh modules.

createHandlers(deps)

The core runtime shared by dev and prod. It handles CORS preflight, /healthz and /__wrnexus/health, request-body size limits (413), HMR socket upgrades (/__wrnexus/hmr), realtime WebSocket upgrades (defineRoom default export or a raw websocket export), the middleware pipeline, API routes (/api/*), framework assets (/__wrnexus/*), public assets, and full SSR page rendering (component mounts, layouts, slots, i18n markers, per-page script selection, ETag/304, gzip).

interface RuntimeDeps {
  mode: Mode;
  hmr: boolean; // inject the live-reload client into pages
  router: Router;
  loadModule(file: string): Promise<Record<string, unknown>>;
  getMiddleware(): Promise<Middleware[]>;
  assets: AssetServer; // serves /__wrnexus/* (islands, reactive, hmr)
  hasStyles?: boolean; // inject the global stylesheet link
  hasUi?: boolean; // inject the Wire UI stylesheet (/__wrnexus/ui.css)
  theme?: ResolvedTheme; // enables /__wrnexus/theme.css + <html data-theme>
  i18n?: ResolvedI18n; // enables ctx.t, <html lang>, {t:key} markers
  inlineStyles?: string; // inline small prod stylesheets into <head>
  assetVersion?: string; // cache-busting ?v= on framework asset URLs
  head?: string; // raw HTML appended to every page <head>
  seo?: SeoConfig;
  security?: SecurityConfig;
  maxBodyBytes?: number; // 413 above this; default 10 MB
  hub?: HmrHub; // browser HMR sockets (dev only)
  realtimeBus?: RealtimeBus; // cross-process room bridge (Redis pub/sub)
}

interface Handlers {
  fetch(req: Request, server: UpgradeServer): Promise<Response | undefined>;
  websocket: { open; message; close; drain };
}

WsData is the per-connection socket tag — a discriminated union of { kind: "realtime"; handler }, { kind: "room"; meta }, or { kind: "hmr" }.

createProductionServer(manifest, opts) / createProductionHandlers(manifest, opts)

Production runs the _same_ request runtime as dev, but with no filesystem scan and no runtime bundling. wrnexus build emits an entry that statically imports every route/component/layout module and passes them as a ProdManifest; the route-matching tables are rebuilt from the raw patterns.

interface ProdManifest {
  pages: { raw: string; mod: RouteModule }[];
  api: { raw: string; mod: RouteModule }[];
  realtime: { raw: string; mod: RouteModule }[];
  middleware: Middleware[];
  components: { name: string; mod: RouteModule }[];
  layouts: { name: string; mod: RouteModule }[];
}

interface ProdOptions {
  stylesPath?: string;
  inlineStyles?: string;
  reactivePath?: string;
  themePath?: string;
  themeJsPath?: string;
  theme?: ResolvedTheme;
  uiCssPath?: string;
  schemasJs?: string;
  i18n?: ResolvedI18n;
  db?: { driver: string; url: string };
  databases?: Record<string, { driver: string; url: string }>;
  realtime?: { scale?: boolean; redisUrl?: string };
  assetVersion?: string;
  publicDir?: string;
  head?: string;
  seo?: SeoConfig;
  security?: SecurityConfig;
  port?: number;
  hostname?: string;
  maxBodyBytes?: number;
}

createProductionServer also loads the .env cascade for the production profile, installs SIGTERM/SIGINT graceful shutdown, and binds 0.0.0.0 (port from opts.port or $PORT, default 3000). Migrations are not run here — apply them first (wrnexus db migrate). createProductionHandlers returns the bare handlers for edge/serverless/node:http deployment.

startGateway(opts) — multi-app gateway

Serves several apps behind one port and routes each request to the right app by its Host header. Each app runs as its own child process (full isolation); the gateway is a thin host-based reverse proxy for HTTP and WebSocket. Apps communicate at runtime via @wrnexus/pubsub (use the Redis driver so messages cross processes).

interface GatewayOptions {
  port?: number; // default 3000
  hostname?: string; // default "localhost"
  mode?: "development" | "production";
  apps: GatewayApp[];
  security?: GatewaySecurity;
}

interface GatewayApp {
  name: string; // app id (for logs)
  dir: string; // app root (contains app/ + wrnexus.config.ts)
  domains: string[]; // host names routed here
  port?: number; // fixed internal port; else assigned
  auth?: GatewayAuth; // per-app edge access control
}

interface GatewayAuth {
  basic?: { user: string; pass: string } | Array<{ user: string; pass: string }>;
  allowIps?: string[]; // exact-match IP allowlist
  forward?: { url: string }; // forward-auth (SSO): 2xx allows
}

interface GatewaySecurity {
  trustedHostsOnly?: boolean; // 404 unknown hosts instead of first app
  rateLimit?: { max: number; windowMs?: number }; // global by client IP (429)
  headers?: boolean; // add baseline edge security headers
  forwardedHeaders?: boolean; // set X-Forwarded-* (default true)
  accessLog?: boolean;
}

The gateway exposes /__gateway/health (JSON list of routed apps) and returns a RunningGateway ({ port, url, stop() }).

node:http adapter (from ./adapters/node.ts)

For embedding the WinterCG handler behind an existing Node server or a WinterCG host. Note the full app still needs Bun-compatible globals (Bun.file, bun:sqlite, etc.); only the Request/Response conversion is fully portable.

type FetchHandler = (req: Request) => Response | undefined | Promise<Response | undefined>;

toRequest(req: IncomingMessage, opts?): Promise<Request>
writeResponse(res: ServerResponse, response: Response): Promise<void>  // preserves multiple Set-Cookie
nodeListener(handler: FetchHandler, opts?): (req, res) => Promise<void>
serveNode(handler: FetchHandler, opts?): Promise<Server>

Subpath export: @wrnexus/dev-server/serve-entry

The child process the dev supervisor launches:

bun run serve-entry.ts <appDir> <port> <mode>

It loads the optional wrnexus.config.ts, resolves the style entry, calls startServer, and prints the route table (Pages / API / Realtime / Components). Because it runs in its own process, every restart re-imports all route modules fresh — that is how the supervisor delivers live reload of edited server code. startGateway resolves this entry via import.meta.resolve("@wrnexus/dev-server/serve-entry") to spawn each dev app.

Usage

Programmatic dev server

import { startServer } from "@wrnexus/dev-server";

const server = await startServer({
  appDir: "./app",
  port: 3000,
  mode: "development",
  theme: {/* design tokens */},
  db: { driver: "sqlite", url: "file:./data/app.db" },
});

console.log(`Running at ${server.url}`);
// server.stop();

Production server from a build manifest

import { createProductionServer } from "@wrnexus/dev-server";
import { manifest } from "./dist/manifest.js"; // generated by `wrnexus build`

createProductionServer(manifest, {
  stylesPath: "./dist/styles.css",
  reactivePath: "./dist/reactive.js",
  assetVersion: process.env.BUILD_ID,
  db: { driver: "postgres", url: process.env.DATABASE_URL! },
  port: Number(process.env.PORT) || 3000,
});

Embedding the handler on node:http

import { createProductionHandlers, serveNode } from "@wrnexus/dev-server";

const handlers = createProductionHandlers(manifest, opts);
await serveNode(handlers.fetch, { port: 8080 });

Multi-app gateway

import { startGateway } from "@wrnexus/dev-server";

await startGateway({
  port: 3000,
  apps: [
    { name: "web", dir: "./apps/web", domains: ["localhost", "web.localhost"] },
    {
      name: "admin",
      dir: "./apps/admin",
      domains: ["admin.localhost"],
      auth: { basic: { user: "root", pass: "s3cret" } },
    },
  ],
  security: { trustedHostsOnly: true, rateLimit: { max: 600 } },
});

Framework asset routes

The runtime serves these framework-owned paths (dev builds them live; prod serves pre-built/immutable versions):

  • /__wrnexus/nav.js, /__wrnexus/reactive.js, /__wrnexus/realtime.js — client runtimes
  • /__wrnexus/validate.js, /__wrnexus/schemas.js, /__wrnexus/i18n.js — validation + i18n runtimes
  • /__wrnexus/theme.css, /__wrnexus/theme.js, /__wrnexus/ui.css, /__wrnexus/styles.css — styles
  • /__wrnexus/hmr — dev-only HMR WebSocket
  • /__wrnexus/csr — server-evaluated CSR bindings for browser-side API fetches

Pages get only the scripts they use: nav.js always, reactive.js when a page has a data-scope/CSR fetch, plus theme/validation/i18n/realtime runtimes when the relevant markup is present.

Requirements / Notes

  • Bun-only. Uses Bun.serve (HTTP + WebSocket), Bun.file, and Bun.gzipSync. The full app also relies on bun:sqlite / Bun.SQL via @wrnexus/db.
  • Orchestrates the whole framework: @wrnexus/core (context, security, realtime registry), @wrnexus/router, @wrnexus/ssr (renderDocument), @wrnexus/csr (client runtimes), @wrnexus/compiler (.wrn → TS), @wrnexus/styles, @wrnexus/ui, @wrnexus/validation, @wrnexus/i18n, @wrnexus/db, and @wrnexus/pubsub (Redis-backed cross-process realtime).
  • .wrn files are compiled to TypeScript into a hidden sibling .wrnexus/ cache dir and dynamically imported; the module cache means each edited server module needs a fresh process (dev) — hence the restart-on-change model.
  • Responses are gzipped when the client accepts it and the body is a buffered, compressible payload ≥ 1 KB; streaming/SSE responses opt out via Cache-Control: no-transform.
  • </content>

</invoke>

Complete TypeScript API

This declaration is generated from the exact published package and lists its exported functions, classes, interfaces, and types.

import { Mode, Middleware, SeoConfig, SecurityConfig, RealtimeBus, RealtimeConnectMeta } from '@wrnexus/core';
import { Router } from '@wrnexus/router';
import { ResolvedTheme, MobileConfig, PwaConfig, StylesConfig, ThemeConfig } from '@wrnexus/styles';
import { ResolvedI18n, I18nConfig } from '@wrnexus/i18n';
import { StorageConfig } from '@wrnexus/uploader';
import { IncomingMessage, ServerResponse, Server } from 'node:http';

/**
 * HMR hub — tracks connected browser HMR sockets and broadcasts update events.
 *
 * Each open page holds one WebSocket to `/__wrnexus/hmr`. The in-process file
 * watcher (see index.ts) classifies a change and broadcasts a typed message:
 *
 *   { type: "css" }     -> the browser hot-swaps the stylesheet (no reload)
 *   { type: "reload" }  -> the browser asks for fresh HTML over the HMR socket
 *
 * Server-logic changes (pages/api/middleware/realtime) are NOT broadcast here:
 * they require a fresh process, so the child exits and the supervisor respawns
 * it. The browser then reconnects and performs a soft DOM morph automatically.
 */
type HmrMessage = {
    type: "css";
    version: number;
} | {
    type: "reload";
    version: number;
};
/** Minimal shape of a Bun ServerWebSocket we rely on. */
interface Socket {
    send(data: string): unknown;
}
declare class HmrHub {
    private sockets;
    private version;
    add(ws: Socket): void;
    remove(ws: Socket): void;
    broadcast(message: HmrMessage): void;
    get size(): number;
    css(): void;
    reload(): void;
}

/**
 * Shared request runtime used by BOTH the dev server and the production server.
 *
 * It owns the HTTP/WebSocket dispatch and the SSR document assembly, but knows
 * nothing about *how* modules or assets are produced — those come in via
 * `RuntimeDeps`. Dev wires in dynamic module loading + on-the-fly bundling;
 * prod wires in a static manifest + pre-built chunks on disk.
 */

/** A realtime module's `websocket` export: a bag of optional lifecycle hooks. */
type WsHandler = Record<string, (...args: any[]) => unknown>;
/**
 * Per-connection socket data. A socket is either an app realtime connection or
 * an internal HMR connection — discriminated by `kind`.
 */
type WsData = {
    kind: "realtime";
    handler: WsHandler;
} | {
    kind: "room";
    meta: RealtimeConnectMeta;
} | {
    kind: "hmr";
    baseUrl: string;
    headers: [string, string][];
};
type RouteModule$1 = Record<string, unknown>;
/** Serves framework-owned assets under `/__wrnexus/*` (islands, reactive, hmr). */
interface AssetServer {
    serve(pathname: string): Promise<Response | null>;
}
interface RuntimeDeps {
    mode: Mode;
    /** When true, inject the live-reload client into rendered pages. */
    hmr: boolean;
    router: Router;
    /** Load a route module by absolute path (dev: dynamic import; prod: manifest). */
    loadModule(file: string): Promise<RouteModule$1>;
    /** Resolve the ordered middleware chain. */
    getMiddleware(): Promise<Middleware[]>;
    /** Serve `/__wrnexus/*` assets. */
    assets: AssetServer;
    /** When true, inject the global stylesheet link into every page head. */
    hasStyles?: boolean;
    /** When true, inject the Wire UI stylesheet link (`/__wrnexus/ui.css`). */
    hasUi?: boolean;
    /** Production build combined theme + UI stylesheet. */
    hasFrameworkStyles?: boolean;
    /** App stylesheet already contains theme + UI CSS and is the only CSS request needed. */
    stylesIncludeFramework?: boolean;
    /** Resolved theme config: enables `/__wrnexus/theme.css` + `<html data-theme>`. */
    theme?: ResolvedTheme;
    /** Resolved i18n bundle: enables `ctx.t`, `<html lang>`, and `{t:key}` markers. */
    i18n?: ResolvedI18n;
    /** Small production stylesheets can be inlined to avoid a render-blocking request. */
    inlineStyles?: string;
    /** Production cache-busting version appended to framework asset URLs. */
    assetVersion?: string;
    /** Raw HTML appended to every page head (e.g. CDN framework links). */
    head?: string;
    /** Global SEO defaults. */
    seo?: SeoConfig;
    mobile?: MobileConfig;
    pwa?: PwaConfig | false;
    /** Framework security headers and CORS policy. */
    security?: SecurityConfig;
    /** Max request body size in bytes (413 above this). Default 10 MB. */
    maxBodyBytes?: number;
    /** HMR hub for browser live-update sockets (dev only). */
    hub?: HmrHub;
    /**
     * Cross-process realtime bus. When provided, room broadcasts/`toUser` sends are
     * bridged to it so they reach clients on every app process/instance sharing the
     * bus (use the Redis pub/sub driver). Enables realtime across multiple apps.
     */
    realtimeBus?: RealtimeBus;
}
interface UpgradeServer {
    upgrade(req: Request, opts: {
        data: WsData;
    }): boolean;
    /** Bun's per-request socket peer address (used for the non-spoofable client IP). */
    requestIP?(req: Request): {
        address: string;
    } | null;
}
/** The subset of Bun's ServerWebSocket the runtime touches. */
interface Ws {
    data: WsData;
    send(data: string | Uint8Array): unknown;
    close(code?: number, reason?: string): void;
}
interface Handlers {
    fetch(req: Request, server: UpgradeServer): Promise<Response | undefined>;
    websocket: {
        open(ws: Ws): void;
        message(ws: Ws, message: string | Uint8Array): void;
        close(ws: Ws, code?: number, reason?: string): void;
        drain(ws: Ws): void;
    };
}
/** Build the fetch + websocket handlers from a set of dependencies. */
declare function createHandlers(deps: RuntimeDeps): Handlers;

/**
 * The multi-app **gateway** — serves several WRNexusJS apps behind one port and
 * routes each request to the right app by its `Host` header (domain). This is how
 * a monorepo becomes a multi-domain SaaS: `app-a.com` → apps/a, `app-b.com` → apps/b.
 *
 * Each app runs as its own **process** (full isolation — its own database
 * registry, pubsub, in-memory state), and the gateway is a thin host-based
 * reverse proxy for both HTTP and WebSocket. Apps talk to each other at runtime
 * via @wrnexus/pubsub (use the Redis driver so messages cross processes).
 */
/** Per-app access control, enforced at the gateway before proxying. */
interface GatewayAuth {
    /** HTTP Basic auth — one or more allowed user/password pairs. */
    basic?: {
        user: string;
        pass: string;
    } | Array<{
        user: string;
        pass: string;
    }>;
    /** Allow only these client IPs (exact match; others get 403). */
    allowIps?: string[];
    /**
     * Forward-auth (SSO): the gateway GETs `url` forwarding the request's cookies +
     * Authorization; a 2xx allows the request, anything else blocks it (its status
     * is returned). Point it at your own verify endpoint.
     */
    forward?: {
        url: string;
    };
}
interface GatewayApp {
    /** App id (for logs). */
    name: string;
    /** Path to the app root (the dir containing `app/` and wrnexus.config.ts). */
    dir: string;
    /** Host names routed to this app (e.g. ["localhost", "web.localhost"]). */
    domains: string[];
    /** Optional fixed internal port; otherwise assigned from the gateway port. */
    port?: number;
    /** Access control enforced at the edge for this app. */
    auth?: GatewayAuth;
}
/** Gateway-wide security controls, enforced for every app. */
interface GatewaySecurity {
    /** Reject requests whose Host matches no app (404) instead of routing to the first. */
    trustedHostsOnly?: boolean;
    /** Global rate limit by client IP (429 over the limit). */
    rateLimit?: {
        max: number;
        windowMs?: number;
    };
    /** Add baseline security headers to responses (only where the app didn't set them). */
    headers?: boolean;
    /** Set X-Forwarded-For/Host/Proto so apps see the real client. Default true. */
    forwardedHeaders?: boolean;
    /** Log each request (host → app, method, path, status). */
    accessLog?: boolean;
}
interface GatewayOptions {
    port?: number;
    hostname?: string;
    mode?: "development" | "production";
    apps: GatewayApp[];
    security?: GatewaySecurity;
}
interface RunningGateway {
    port: number;
    url: string;
    stop(): void;
}
/** Boot every app as a child process, then route by Host on one gateway port. */
declare function startGateway(opts: GatewayOptions): Promise<RunningGateway>;

/**
 * @wrnexus/dev-server/prod — the production server (Point 4).
 *
 * Unlike dev, there is NO filesystem scan and NO on-the-fly bundling at runtime.
 * `wrnexus build` generates an entry that statically imports every route and
 * component module and hands them here as a manifest. We rebuild the (cheap)
 * route-matching tables from the raw patterns and run the exact same request
 * runtime as dev — just with production error pages and no live-reload client.
 */

type RouteModule = Record<string, unknown>;
interface ManifestRoute {
    /** URL pattern, e.g. `/users/[id]`. */
    raw: string;
    /** The statically-imported route module. */
    mod: RouteModule;
}
interface ProdManifest {
    pages: ManifestRoute[];
    api: ManifestRoute[];
    realtime: ManifestRoute[];
    middleware: Middleware[];
    /** Server-rendered components, statically imported and keyed by name. */
    components: {
        name: string;
        mod: RouteModule;
    }[];
    /** Named page layouts (from app/layouts/*.wrn). */
    layouts: {
        name: string;
        mod: RouteModule;
    }[];
}
interface ProdOptions {
    /** Absolute path to the pre-built global stylesheet, if any. */
    stylesPath?: string;
    /** Small production stylesheet inlined into the document head. */
    inlineStyles?: string;
    /** `stylesPath` contains theme + UI + app CSS in cascade order. */
    stylesIncludeFramework?: boolean;
    /** Absolute path to the pre-built reactive runtime. */
    reactivePath?: string;
    /** Absolute path to the pre-built theme stylesheet (`theme.css`). */
    themePath?: string;
    /** Absolute path to the pre-built theme runtime (`theme.js`). */
    themeJsPath?: string;
    /** Resolved theme config: enables `<html data-theme>` + `theme.css` link. */
    theme?: ResolvedTheme;
    /** Absolute path to the pre-built Wire UI stylesheet (`ui.css`). */
    uiCssPath?: string;
    /** Combined production theme + Wire UI stylesheet. */
    frameworkCssPath?: string;
    /** Pre-built `window.__wireSchemas = {...}` script for client validation. */
    schemasJs?: string;
    /** Resolved i18n bundle (default lang + locale messages). */
    i18n?: ResolvedI18n;
    /** Default database connection (driver + url); enables `getDb()`. */
    db?: {
        driver: string;
        url: string;
    };
    /** Named databases, reached with `getDb("<name>")`. */
    databases?: Record<string, {
        driver: string;
        url: string;
    }>;
    /**
     * Absolute path to the default db's migrations bundled into the build
     * (`dist/migrations`). When set, they are applied on startup — like dev.
     */
    migrationsDir?: string;
    /** Bundled migrations dirs for named dbs (name → `dist/db/<name>/migrations`). */
    databaseMigrationDirs?: Record<string, string>;
    /**
     * Auto-apply bundled migrations on server startup (default: true). Set false
     * for deploys that migrate in a separate release step (e.g. multiple instances
     * behind a load balancer, where you migrate once before rolling out).
     */
    autoMigrate?: boolean;
    /** Realtime scaling: bridge room broadcasts over Redis across app processes. */
    realtime?: {
        scale?: boolean;
        redisUrl?: string;
    };
    /** File-upload storage: named stores (local dir / S3). Local dirs resolve against cwd. */
    storage?: StorageConfig;
    /** Cache-busting version appended to framework asset URLs. */
    assetVersion?: string;
    /** Absolute path to copied public assets, if any. */
    publicDir?: string;
    /** Raw HTML appended to every page head. */
    head?: string;
    /** Global SEO defaults. */
    seo?: SeoConfig;
    mobile?: MobileConfig;
    pwa?: PwaConfig | false;
    /** Framework security headers and CORS policy. */
    security?: SecurityConfig;
    port?: number;
    hostname?: string;
    maxBodyBytes?: number;
}
/**
 * Build the portable request handler from a precompiled manifest — a
 * WinterCG-style `fetch(request) => Response` plus the websocket handlers, with
 * NO server bound. This is the deployment-adapter seam: `createProductionServer`
 * wraps it in `Bun.serve`, `serveNode` bridges it onto `node:http`, and edge or
 * serverless targets can call `fetch` directly.
 */
declare function createProductionHandlers(manifest: ProdManifest, opts: ProdOptions): ReturnType<typeof createHandlers>;
/** Start the production server on Bun from a precompiled manifest. */
declare function createProductionServer(manifest: ProdManifest, opts: ProdOptions): Promise<Bun.Server<WsData>>;

/**
 * node:http adapter — bridge a WinterCG `fetch(request) => Response` handler
 * onto a Node HTTP server, with no external dependencies. Converts a Node
 * `IncomingMessage` into a web `Request` and writes a web `Response` back into a
 * `ServerResponse` (preserving multiple `Set-Cookie` headers).
 *
 * Caveat: the production handler uses Bun-native APIs (Bun.file for assets,
 * Bun.serve for websockets, Bun.SQL / bun:sqlite for the database), so running
 * the FULL app under plain Node needs Bun-compatible globals. This adapter is
 * for WinterCG hosts and for embedding the handler behind an existing
 * `node:http` server; the Request/Response conversion itself is fully portable.
 */

type FetchHandler = (req: Request) => Response | undefined | Promise<Response | undefined>;
/** Convert a Node IncomingMessage into a web Request (buffers the body). */
declare function toRequest(req: IncomingMessage, opts?: {
    origin?: string;
}): Promise<Request>;
/** Write a web Response into a Node ServerResponse. */
declare function writeResponse(res: ServerResponse, response: Response): Promise<void>;
/** A `node:http` request listener that dispatches to a fetch handler. */
declare function nodeListener(handler: FetchHandler, opts?: {
    origin?: string;
}): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
/** Create and start a `node:http` server for a fetch handler. */
declare function serveNode(handler: FetchHandler, opts?: {
    port?: number;
    hostname?: string;
}): Promise<Server>;

/**
 * @wrnexus/dev-server — the development HTTP + WebSocket server.
 *
 * Thin Bun.serve wrapper around the shared runtime (runtime.ts). Dynamic module
 * loading makes it fast to iterate; the dev supervisor (see @wrnexus/cli)
 * restarts this process on file changes.
 */

/** Exit code the child uses to ask the dev supervisor for a fresh process. */
declare const RESTART_EXIT_CODE = 97;
interface ServeOptions {
    appDir: string;
    port?: number;
    hostname?: string;
    mode?: Mode;
    /** Inject the live-reload client (defaults to true in development). */
    hmr?: boolean;
    /** Resolved absolute path to the global CSS entry, or null. */
    styleEntry?: string | null;
    /** Custom styles config (e.g. a Tailwind/PostCSS processor). */
    stylesConfig?: StylesConfig;
    /** Raw HTML appended to every page head (from wrnexus.config.ts). */
    head?: string;
    /** Global SEO defaults. */
    seo?: SeoConfig;
    /** Framework security headers and CORS policy. */
    security?: SecurityConfig;
    /** Design-token theme config (merged over the built-in light/dark). */
    theme?: ThemeConfig;
    /** i18n config (default language + supported locales). */
    i18n?: I18nConfig;
    /** Default database connection (driver + url). Enables `getDb()` and dev auto-migrate. */
    db?: {
        driver: string;
        url: string;
    };
    /** Named databases, reached with `getDb("<name>")`; migrations under app/db/<name>/. */
    databases?: Record<string, {
        driver: string;
        url: string;
    }>;
    /** Realtime scaling: bridge room broadcasts over Redis across app processes. */
    realtime?: {
        scale?: boolean;
        redisUrl?: string;
    };
    /** File-upload storage: named stores (local dir / S3), reached with `getStore()`. */
    storage?: StorageConfig;
    mobile?: MobileConfig;
    pwa?: PwaConfig | false;
}
interface RunningServer {
    port: number;
    hostname: string;
    url: string;
    router: Router;
    stop(): void;
}
declare function startServer(opts: ServeOptions): Promise<RunningServer>;

export { type AssetServer, type FetchHandler, type GatewayApp, type GatewayAuth, type GatewayOptions, type GatewaySecurity, RESTART_EXIT_CODE, type RunningGateway, type RunningServer, type RuntimeDeps, type ServeOptions, type WsData, createHandlers, createProductionHandlers, createProductionServer, nodeListener, serveNode, startGateway, startServer, toRequest, writeResponse };

Examples

Copy-ready examples taken from this package's published documentation.

Example 1

bun add @wrnexus/dev-server

Example 2

interface ServeOptions {
  appDir: string; // absolute/relative path to the app/ dir
  port?: number; // default 3000
  hostname?: string; // default "localhost"
  mode?: Mode; // "development" | "production"; default "development"
  hmr?: boolean; // inject live-reload client; default (mode === "development")
  styleEntry?: string | null; // resolved absolute path to the global CSS entry
  stylesConfig?: StylesConfig; // custom styles processor (e.g. Tailwind/PostCSS)
  head?: string; // raw HTML appended to every page <head>
  seo?: SeoConfig; // global SEO defaults
  security?: SecurityConfig; // security headers + CORS policy
  theme?: ThemeConfig; // design-token theme (merged over built-in light/dark)
  i18n?: I18nConfig; // default language + supported locales
  db?: { driver: string; url: string }; // default db → getDb(); dev auto-migrates
  databases?: Record<string, { driver: string; url: string }>; // named dbs → getDb("<name>")
  realtime?: { scale?: boolean; redisUrl?: string }; // bridge rooms over Redis across processes
}

interface RunningServer {
  port: number;
  hostname: string;
  url: string;
  router: Router;
  stop(): void;
}

Example 3

interface RuntimeDeps {
  mode: Mode;
  hmr: boolean; // inject the live-reload client into pages
  router: Router;
  loadModule(file: string): Promise<Record<string, unknown>>;
  getMiddleware(): Promise<Middleware[]>;
  assets: AssetServer; // serves /__wrnexus/* (islands, reactive, hmr)
  hasStyles?: boolean; // inject the global stylesheet link
  hasUi?: boolean; // inject the Wire UI stylesheet (/__wrnexus/ui.css)
  theme?: ResolvedTheme; // enables /__wrnexus/theme.css + <html data-theme>
  i18n?: ResolvedI18n; // enables ctx.t, <html lang>, {t:key} markers
  inlineStyles?: string; // inline small prod stylesheets into <head>
  assetVersion?: string; // cache-busting ?v= on framework asset URLs
  head?: string; // raw HTML appended to every page <head>
  seo?: SeoConfig;
  security?: SecurityConfig;
  maxBodyBytes?: number; // 413 above this; default 10 MB
  hub?: HmrHub; // browser HMR sockets (dev only)
  realtimeBus?: RealtimeBus; // cross-process room bridge (Redis pub/sub)
}

interface Handlers {
  fetch(req: Request, server: UpgradeServer): Promise<Response | undefined>;
  websocket: { open; message; close; drain };
}

Example 4

interface ProdManifest {
  pages: { raw: string; mod: RouteModule }[];
  api: { raw: string; mod: RouteModule }[];
  realtime: { raw: string; mod: RouteModule }[];
  middleware: Middleware[];
  components: { name: string; mod: RouteModule }[];
  layouts: { name: string; mod: RouteModule }[];
}

interface ProdOptions {
  stylesPath?: string;
  inlineStyles?: string;
  reactivePath?: string;
  themePath?: string;
  themeJsPath?: string;
  theme?: ResolvedTheme;
  uiCssPath?: string;
  schemasJs?: string;
  i18n?: ResolvedI18n;
  db?: { driver: string; url: string };
  databases?: Record<string, { driver: string; url: string }>;
  realtime?: { scale?: boolean; redisUrl?: string };
  assetVersion?: string;
  publicDir?: string;
  head?: string;
  seo?: SeoConfig;
  security?: SecurityConfig;
  port?: number;
  hostname?: string;
  maxBodyBytes?: number;
}