740 lines
45 KiB
Plaintext
740 lines
45 KiB
Plaintext
page wrnexusdevserver {
|
|
seo {
|
|
title = "@wrnexus/dev-server"
|
|
description = "Development and production servers, HMR, assets, and gateways."
|
|
}
|
|
|
|
view {
|
|
<div class="docs-shell">
|
|
<a class="skip-link" href="#main">Skip to content</a>
|
|
<header class="topbar">
|
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
|
<nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
|
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.2.19</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
|
</header>
|
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
|
<main class="page package-page">
|
|
<aside class="sidebar"><a href="/packages">← All packages</a><span class="category">Runtime</span><h2>@wrnexus/dev-server</h2><p>Development and production servers, HMR, assets, and gateways.</p><span class="status status-beta">Private preview · 0.2.19</span><nav><a href="#access">Access</a><a href="#guide">Guide</a><a href="#api">Complete API</a></nav></aside>
|
|
<article id="main" class="documentation"><section class="doc-intro"><span class="eyebrow">Runtime · Preview</span><h1>@wrnexus/dev-server</h1><p>Development and production servers, HMR, assets, and gateways.</p><section id="access" class="access-callout"><h2>Private registry access required</h2><p>This package is not available from the public npm registry. After WorkRoot approves access and supplies private registry instructions, install the release-aligned package:</p><pre><code>bun add @wrnexus/dev-server@0.2.19</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide" class="prose"><blockquote>The WRNexusJS HTTP + WebSocket server runtime — request dispatch, SSR document assembly, live-reload (HMR), and the portable production handler.</blockquote>
|
|
<p>Part of the <strong>WRNexusJS</strong> framework — an SSR-first, Bun-native full-stack web framework.</p>
|
|
<h3 id="overview">Overview</h3>
|
|
<p>This package is the server runtime that powers a WRNexusJS app in both development and production. A single <strong>request runtime</strong> (<code>createHandlers</code>) 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 <strong>gateway</strong> (route several apps by <code>Host</code> header behind one port) and a portable <code>node:http</code> adapter for WinterCG hosts. It is entirely server-side and Bun-native (<code>Bun.serve</code>, <code>Bun.file</code>, <code>Bun.gzipSync</code>).</p>
|
|
<pre data-language="bash"><code>bun add @wrnexus/dev-server</code></pre>
|
|
<blockquote>Private package — the machine must be authenticated to the <code>wrnexus</code> npm org</blockquote>
|
|
<blockquote>(a read token in <code>~/.npmrc</code>). Requires <strong>Bun</strong> (Node is not supported for the full server; the <code>node:http</code> adapter is for WinterCG embedding only).</blockquote>
|
|
<h3 id="api">API</h3>
|
|
<h4 id="main-entry-wrnexus-dev-server">Main entry (<code>@wrnexus/dev-server</code>)</h4>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Export</th><th>Kind</th><th>Purpose</th></tr></thead>
|
|
<tbody><tr><td><code>startServer(opts: ServeOptions)</code></td><td><code>Promise<RunningServer></code></td><td>Start the dev server on <code>Bun.serve</code>: builds the router, connects/migrates databases, wires assets + HMR, and starts the file watcher.</td></tr><tr><td><code>createHandlers(deps: RuntimeDeps)</code></td><td><code>Handlers</code></td><td>The shared request runtime (fetch + websocket handlers). Re-exported from <code>runtime.ts</code>.</td></tr><tr><td><code>createProductionServer(manifest, opts)</code></td><td><code>Bun.Server</code></td><td>Start the production server from a precompiled manifest.</td></tr><tr><td><code>createProductionHandlers(manifest, opts)</code></td><td><code>Handlers</code></td><td>Build the portable prod fetch/websocket handlers with no server bound (the deployment-adapter seam).</td></tr><tr><td><code>startGateway(opts: GatewayOptions)</code></td><td><code>Promise<RunningGateway></code></td><td>Boot multiple apps as child processes and route by <code>Host</code>.</td></tr><tr><td><code>toRequest</code>, <code>writeResponse</code>, <code>nodeListener</code>, <code>serveNode</code></td><td>functions</td><td><code>node:http</code> ↔ WinterCG <code>Request</code>/<code>Response</code> adapter.</td></tr><tr><td><code>RESTART_EXIT_CODE</code></td><td><code>number</code> (<code>97</code>)</td><td>Exit code the dev child uses to ask the supervisor for a fresh process.</td></tr><tr><td><code>STYLES_HREF</code>, <code>HMR_CLIENT_JS</code></td><td>constants</td><td>The global stylesheet URL and the inline HMR client script.</td></tr></tbody></table></div>
|
|
<p>Exported types: <code>ServeOptions</code>, <code>RunningServer</code>, <code>RuntimeDeps</code>, <code>AssetServer</code>, <code>WsData</code>, <code>GatewayApp</code>, <code>GatewayOptions</code>, <code>GatewayAuth</code>, <code>GatewaySecurity</code>, <code>RunningGateway</code>, <code>FetchHandler</code>.</p>
|
|
<h4 id="startserver-opts"><code>startServer(opts)</code></h4>
|
|
<pre data-language="ts"><code>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;
|
|
}</code></pre>
|
|
<p>In development, <code>startServer</code> also connects <code>app/db/migrations</code> (and <code>app/db/<name>/migrations</code>) and auto-applies migrations, then starts an in-process file watcher. CSS edits hot-swap live; any other server change triggers <code>process.exit(RESTART_EXIT_CODE)</code> so the dev supervisor (<code>@wrnexus/cli</code>) respawns the process with fresh modules.</p>
|
|
<h4 id="createhandlers-deps"><code>createHandlers(deps)</code></h4>
|
|
<p>The core runtime shared by dev and prod. It handles CORS preflight, <code>/healthz</code> and <code>/__wrnexus/health</code>, request-body size limits (413), HMR socket upgrades (<code>/__wrnexus/hmr</code>), realtime WebSocket upgrades (<code>defineRoom</code> default export or a raw <code>websocket</code> export), the middleware pipeline, API routes (<code>/api/*</code>), framework assets (<code>/__wrnexus/*</code>), public assets, and full SSR page rendering (component mounts, layouts, slots, i18n markers, per-page script selection, ETag/304, gzip).</p>
|
|
<pre data-language="ts"><code>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 };
|
|
}</code></pre>
|
|
<p><code>WsData</code> is the per-connection socket tag — a discriminated union of <code>{ kind: "realtime"; handler }</code>, <code>{ kind: "room"; meta }</code>, or <code>{ kind: "hmr" }</code>.</p>
|
|
<h4 id="createproductionserver-manifest-opts-createproductionhandlers-manifest-opts"><code>createProductionServer(manifest, opts)</code> / <code>createProductionHandlers(manifest, opts)</code></h4>
|
|
<p>Production runs the _same_ request runtime as dev, but with no filesystem scan and no runtime bundling. <code>wrnexus build</code> emits an entry that statically imports every route/component/layout module and passes them as a <code>ProdManifest</code>; the route-matching tables are rebuilt from the raw patterns.</p>
|
|
<pre data-language="ts"><code>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;
|
|
}</code></pre>
|
|
<p><code>createProductionServer</code> also loads the <code>.env</code> cascade for the <code>production</code> profile, installs <code>SIGTERM</code>/<code>SIGINT</code> graceful shutdown, and binds <code>0.0.0.0</code> (port from <code>opts.port</code> or <code>$PORT</code>, default 3000). Migrations are <strong>not</strong> run here — apply them first (<code>wrnexus db migrate</code>). <code>createProductionHandlers</code> returns the bare handlers for edge/serverless/<code>node:http</code> deployment.</p>
|
|
<h4 id="startgateway-opts-multi-app-gateway"><code>startGateway(opts)</code> — multi-app gateway</h4>
|
|
<p>Serves several apps behind one port and routes each request to the right app by its <code>Host</code> header. Each app runs as its own child process (full isolation); the gateway is a thin host-based reverse proxy for HTTP and WebSocket. In development, the gateway supervises every child: editing a page, component, API, or middleware restarts only that app and the HMR client reconnects to display the latest page. Apps communicate at runtime via <code>@wrnexus/pubsub</code> (use the Redis driver so messages cross processes).</p>
|
|
<pre data-language="ts"><code>interface GatewayOptions {
|
|
port?: number; // default 3000
|
|
hostname?: string; // dev: "127.0.0.1"; production: "0.0.0.0"
|
|
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;
|
|
}</code></pre>
|
|
<p>Forward auth is a verification hook, not a login page. Configure <code>forward.url</code> with a dedicated endpoint such as <code>http://sso.localhost:3000/api/verify</code>. The gateway forwards the request's <code>Cookie</code> and <code>Authorization</code> headers plus <code>X-Forwarded-Host</code>, <code>X-Forwarded-Proto</code>, <code>X-Original-Method</code>, and <code>X-Original-Uri</code> (including its query string). The verifier must return 2xx only for an authenticated session and 401/403 otherwise. Pointing forward auth at an SSO home page that always returns 200 allows every request and does not implement SSO.</p>
|
|
<p>For browser SSO, the verifier may return a <code>302</code>/<code>303</code>/<code>307</code>/<code>308</code> with a <code>Location</code> header pointing to its login page. The gateway passes that redirect to the browser. The login flow should validate a signed <code>returnTo</code> value before redirecting back; API clients should receive <code>401</code>/<code>403</code> instead of an HTML login redirect.</p>
|
|
<p>Open the gateway URL (normally <code>http://127.0.0.1:3000</code>), not an app's internal port. The gateway exposes <code>/__gateway/health</code> (JSON list of routed apps) and returns a <code>RunningGateway</code> (<code>{ port, url, stop() }</code>). Use <code>--host=0.0.0.0</code> when other devices need to reach a development gateway.</p>
|
|
<h4 id="node-http-adapter-from-adapters-node-ts"><code>node:http</code> adapter (from <code>./adapters/node.ts</code>)</h4>
|
|
<p>For embedding the WinterCG handler behind an existing Node server or a WinterCG host. Note the full app still needs Bun-compatible globals (<code>Bun.file</code>, <code>bun:sqlite</code>, etc.); only the <code>Request</code>/<code>Response</code> conversion is fully portable.</p>
|
|
<pre data-language="ts"><code>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></code></pre>
|
|
<h4 id="subpath-export-wrnexus-dev-server-serve-entry">Subpath export: <code>@wrnexus/dev-server/serve-entry</code></h4>
|
|
<p>The child process the dev supervisor launches:</p>
|
|
<pre data-language="bash"><code>bun run serve-entry.ts <appDir> <port> <mode></code></pre>
|
|
<p>It loads the optional <code>wrnexus.config.ts</code>, resolves the style entry, calls <code>startServer</code>, 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. <code>startGateway</code> resolves this entry via <code>import.meta.resolve("@wrnexus/dev-server/serve-entry")</code> to spawn each dev app.</p>
|
|
<h3 id="usage">Usage</h3>
|
|
<h4 id="programmatic-dev-server">Programmatic dev server</h4>
|
|
<pre data-language="ts"><code>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();</code></pre>
|
|
<h4 id="production-server-from-a-build-manifest">Production server from a build manifest</h4>
|
|
<pre data-language="ts"><code>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,
|
|
});</code></pre>
|
|
<h4 id="embedding-the-handler-on-node-http">Embedding the handler on <code>node:http</code></h4>
|
|
<pre data-language="ts"><code>import { createProductionHandlers, serveNode } from "@wrnexus/dev-server";
|
|
|
|
const handlers = createProductionHandlers(manifest, opts);
|
|
await serveNode(handlers.fetch, { port: 8080 });</code></pre>
|
|
<h4 id="multi-app-gateway">Multi-app gateway</h4>
|
|
<pre data-language="ts"><code>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 } },
|
|
});</code></pre>
|
|
<h3 id="framework-asset-routes">Framework asset routes</h3>
|
|
<p>The runtime serves these framework-owned paths (dev builds them live; prod serves pre-built/immutable versions):</p>
|
|
<ul>
|
|
<li><code>/__wrnexus/nav.js</code>, <code>/__wrnexus/reactive.js</code>, <code>/__wrnexus/realtime.js</code> — client runtimes</li>
|
|
<li><code>/__wrnexus/validate.js</code>, <code>/__wrnexus/schemas.js</code>, <code>/__wrnexus/i18n.js</code> — validation + i18n runtimes</li>
|
|
<li><code>/__wrnexus/theme.css</code>, <code>/__wrnexus/theme.js</code>, <code>/__wrnexus/ui.css</code>, <code>/__wrnexus/styles.css</code> — styles</li>
|
|
<li><code>/__wrnexus/hmr</code> — dev-only HMR WebSocket</li>
|
|
<li><code>/__wrnexus/csr</code> — server-evaluated CSR bindings for browser-side API fetches</li>
|
|
</ul>
|
|
<p>Pages get only the scripts they use: <code>nav.js</code> always, <code>reactive.js</code> when a page has a <code>data-scope</code>/CSR fetch, plus theme/validation/i18n/realtime runtimes when the relevant markup is present.</p>
|
|
<h3 id="requirements-notes">Requirements / Notes</h3>
|
|
<ul>
|
|
<li><strong>Bun-only.</strong> Uses <code>Bun.serve</code> (HTTP + WebSocket), <code>Bun.file</code>, and <code>Bun.gzipSync</code>. The full app also relies on <code>bun:sqlite</code> / <code>Bun.SQL</code> via <code>@wrnexus/db</code>.</li>
|
|
<li>Orchestrates the whole framework: <code>@wrnexus/core</code> (context, security, realtime registry), <code>@wrnexus/router</code>, <code>@wrnexus/ssr</code> (<code>renderDocument</code>), <code>@wrnexus/csr</code> (client runtimes), <code>@wrnexus/compiler</code> (<code>.wrn</code> → TS), <code>@wrnexus/styles</code>, <code>@wrnexus/ui</code>, <code>@wrnexus/validation</code>, <code>@wrnexus/i18n</code>, <code>@wrnexus/db</code>, and <code>@wrnexus/pubsub</code> (Redis-backed cross-process realtime).</li>
|
|
<li><code>.wrn</code> files are compiled to TypeScript into a hidden sibling <code>.wrnexus/</code> cache dir and dynamically imported; the module cache means each edited server module needs a fresh process (dev) — hence the restart-on-change model.</li>
|
|
<li>Responses are gzipped when the client accepts it and the body is a buffered, compressible payload ≥ 1 KB; streaming/SSE responses opt out via <code>Cache-Control: no-transform</code>.</li>
|
|
<p></content></p>
|
|
</ul>
|
|
<p></invoke></p></section><section id="api" class="prose api"><h2>Complete TypeScript API</h2><p>This declaration comes from the exact installed package and lists its exported functions, classes, interfaces, and types.</p><pre data-language="typescript"><code>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';
|
|
|
|
/** Exit code a dev-server child uses to request a clean supervisor restart. */
|
|
declare const RESTART_EXIT_CODE = 97;
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
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 };
|
|
</code></pre></section><section id="examples" class="prose examples"><h2>Examples</h2><p>Examples are taken from this package's installed documentation and must be evaluated with its requirements and stability notes.</p><div class="example-grid"><article class="example-card"><h3>Example 1</h3><pre data-language="bash"><code>bun add @wrnexus/dev-server</code></pre></article><article class="example-card"><h3>Example 2</h3><pre data-language="ts"><code>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;
|
|
}</code></pre></article><article class="example-card"><h3>Example 3</h3><pre data-language="ts"><code>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 };
|
|
}</code></pre></article><article class="example-card"><h3>Example 4</h3><pre data-language="ts"><code>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;
|
|
}</code></pre></article></div></section></article>
|
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#overview">Overview</a><a class="toc-level-3" href="#api">API</a><a class="toc-level-4" href="#main-entry-wrnexus-dev-server">Main entry (@wrnexus/dev-server)</a><a class="toc-level-4" href="#startserver-opts">startServer(opts)</a><a class="toc-level-4" href="#createhandlers-deps">createHandlers(deps)</a><a class="toc-level-4" href="#createproductionserver-manifest-opts-createproductionhandlers-manifest-opts">createProductionServer(manifest, opts) / createProductionHandlers(manifest, opts)</a><a class="toc-level-4" href="#startgateway-opts-multi-app-gateway">startGateway(opts) — multi-app gateway</a><a class="toc-level-4" href="#node-http-adapter-from-adapters-node-ts">node:http adapter (from ./adapters/node.ts)</a><a class="toc-level-4" href="#subpath-export-wrnexus-dev-server-serve-entry">Subpath export: @wrnexus/dev-server/serve-entry</a><a class="toc-level-3" href="#usage">Usage</a><a class="toc-level-4" href="#programmatic-dev-server">Programmatic dev server</a><a class="toc-level-4" href="#production-server-from-a-build-manifest">Production server from a build manifest</a><a class="toc-level-4" href="#embedding-the-handler-on-node-http">Embedding the handler on node:http</a><a class="toc-level-4" href="#multi-app-gateway">Multi-app gateway</a><a class="toc-level-3" href="#framework-asset-routes">Framework asset routes</a><a class="toc-level-3" href="#requirements-notes">Requirements / Notes</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
|
</main>
|
|
<footer>WRNexusJS 0.2.19 · Private Developer Preview · Bun-native · Documentation generated from installed package APIs.</footer>
|
|
</div>
|
|
}
|
|
}
|