@wrnexus/dev-server
Development and production servers, HMR, assets, and gateways.
bun add @wrnexus/dev-server@0.2.15The 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; thenode:httpadapter is for WinterCG embedding only).
API
Main entry (@wrnexus/dev-server)
| Export | Kind | Purpose |
|---|---|---|
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) | Handlers | The shared request runtime (fetch + websocket handlers). Re-exported from runtime.ts. |
createProductionServer(manifest, opts) | Bun.Server | Start the production server from a precompiled manifest. |
createProductionHandlers(manifest, opts) | Handlers | Build 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, serveNode | functions | node:http ↔ WinterCG Request/Response adapter. |
RESTART_EXIT_CODE | number (97) | Exit code the dev child uses to ask the supervisor for a fresh process. |
STYLES_HREF, HMR_CLIENT_JS | constants | The 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, andBun.gzipSync. The full app also relies onbun:sqlite/Bun.SQLvia@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). .wrnfiles 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-serverExample 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;
}