refactor: migrate legacy wire namespace to wrn
Quality / quality (ubuntu-latest) (push) Failing after 9m49s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-12 18:51:15 +05:30
parent ec23dd4c93
commit 2c960fc1dc
488 changed files with 27306 additions and 19063 deletions
+12 -12
View File
@@ -6,7 +6,7 @@ Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web fr
## Overview
This package is the server runtime that powers a WrNexus 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`).
This package is the server runtime that powers a WrNexus 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 wrn 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
@@ -21,16 +21,16 @@ bun add @wrnexus/dev-server
### 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. |
| Export | Kind | Purpose |
| --------------------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `startServer(opts: ServeOptions)` | `Promise<RunningServer>` | Start the dev server on `Bun.serve`: builds the router, connects/migrates databases, connects 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`.
@@ -85,7 +85,7 @@ interface RuntimeDeps {
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)
hasUi?: boolean; // inject the WrNexus 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>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/dev-server",
"version": "0.8.15",
"version": "0.8.16",
"type": "module",
"main": "src/index.ts",
"exports": {
+1 -1
View File
@@ -80,7 +80,7 @@ export function createDevAssetServer(
pluginAssets: readonly ServedPluginAsset[] = [],
): DevAssetServer {
let cssCache: string | null = null;
let schemasCode = schemasJs ?? "window.__wireSchemas={};";
let schemasCode = schemasJs ?? "window.__wrnSchemas={};";
return {
invalidateCss() {
+1 -1
View File
@@ -567,7 +567,7 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
if (origin && origin !== url.origin) return false;
const cookieHeader = request.headers.get("cookie") ?? "";
const cookieToken =
/(?:^|;\s*)wire-csrf=([^;]+)/.exec(cookieHeader)?.[1] ??
/(?:^|;\s*)wrn-csrf=([^;]+)/.exec(cookieHeader)?.[1] ??
/(?:^|;\s*)wrnexus_csrf=([^;]+)/.exec(cookieHeader)?.[1];
const headerToken =
request.headers.get("x-csrf-token") ?? request.headers.get("x-wrnexus-csrf") ?? "";
+10 -10
View File
@@ -160,7 +160,7 @@ function rewriteArtifactImports(
if (entry.resolved.endsWith(".wrn")) {
const dependencySource = readFileSync(entry.resolved, "utf8");
const isStore = /\b(?:global|page)\s+store\s+[A-Za-z_$][\w$]*\s*\{/.test(dependencySource);
const dependency = compileWireArtifacts(
const dependency = compileWrnArtifacts(
entry.resolved,
moduleVersions.get(entry.resolved) ?? 0,
);
@@ -230,7 +230,7 @@ async function rewriteArtifactImportsAsync(
if (replacement.endsWith(".wrn")) {
const dependencySource = readFileSync(replacement, "utf8");
const isStore = /\b(?:global|page)\s+store\s+[A-Za-z_$][\w$]*\s*\{/.test(dependencySource);
const dependency = await compileWireArtifactsAsync(
const dependency = await compileWrnArtifactsAsync(
replacement,
moduleVersions.get(replacement) ?? 0,
);
@@ -266,7 +266,7 @@ export async function loadModule(file: string): Promise<Record<string, unknown>>
const version = moduleVersions.get(file) ?? 0;
// `.wrn` files are compiled to TypeScript first, then imported.
let target = file.endsWith(".wrn")
? (await compileWireArtifactsAsync(file, version)).main
? (await compileWrnArtifactsAsync(file, version)).main
: file;
let temporary = false;
// Bun intentionally caches local TS/JS modules by filesystem path and ignores
@@ -441,13 +441,13 @@ async function bundleBrowserArtifact(
}
}
export function compileWireArtifactsAsync(file: string, version = 0): Promise<WrnCompileArtifacts> {
export function compileWrnArtifactsAsync(file: string, version = 0): Promise<WrnCompileArtifacts> {
const key = `${file}:${version}`;
const active = asyncCompileInProgress.get(key);
if (active) return active;
const task = (async () => {
if (!devCompilerPipeline) {
const artifacts = compileWireArtifacts(file, version);
const artifacts = compileWrnArtifacts(file, version);
const code = readFileSync(artifacts.browser, "utf8");
const bundled = await bundleBrowserArtifact(
code,
@@ -525,7 +525,7 @@ export function resetWrnCompileMetrics(): void {
});
}
export function compileWireArtifacts(file: string, version = 0): WrnCompileArtifacts {
export function compileWrnArtifacts(file: string, version = 0): WrnCompileArtifacts {
const active = compileInProgress.get(file);
if (active) return active;
const cacheDir = compileCacheDir ?? join(dirname(file), ".wrnexus");
@@ -598,22 +598,22 @@ export function compileWireArtifacts(file: string, version = 0): WrnCompileArtif
export async function loadWrnServerModule(file: string): Promise<Record<string, unknown>> {
const version = moduleVersions.get(file) ?? 0;
const artifact = compileWireArtifacts(file, version).server;
const artifact = compileWrnArtifacts(file, version).server;
return import(pathToFileURL(artifact).href) as Promise<Record<string, unknown>>;
}
export function wrnBrowserArtifact(file: string): string {
return compileWireArtifacts(file, moduleVersions.get(file) ?? 0).browser;
return compileWrnArtifacts(file, moduleVersions.get(file) ?? 0).browser;
}
export function wrnBrowserArtifactUrl(file: string): string {
const artifact = compileWireArtifacts(file, moduleVersions.get(file) ?? 0).browser;
const artifact = compileWrnArtifacts(file, moduleVersions.get(file) ?? 0).browser;
return `/__wrnexus/client/${basename(artifact).replace(/\.client\.mjs$/, ".mjs")}`;
}
/** Async browser artifact URL used by HMR so imported client modules are bundled before delivery. */
export async function wrnBrowserArtifactUrlAsync(file: string): Promise<string> {
const artifact = (await compileWireArtifactsAsync(file, moduleVersions.get(file) ?? 0)).browser;
const artifact = (await compileWrnArtifactsAsync(file, moduleVersions.get(file) ?? 0)).browser;
return `/__wrnexus/client/${basename(artifact).replace(/\.client\.mjs$/, ".mjs")}`;
}
+6 -6
View File
@@ -91,7 +91,7 @@ export interface ProdOptions {
inlineStyles?: string;
/** `stylesPath` contains theme + UI + app CSS in cascade order. */
stylesIncludeFramework?: boolean;
/** `stylesPath` already contains the shared Wire UI stylesheet. */
/** `stylesPath` already contains the shared WrNexus UI stylesheet. */
stylesIncludeUi?: boolean;
/** Absolute path to the pre-built reactive runtime. */
reactivePath?: string;
@@ -107,11 +107,11 @@ export interface ProdOptions {
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`). */
/** Absolute path to the pre-built WrNexus UI stylesheet (`ui.css`). */
uiCssPath?: string;
/** Combined production theme + Wire UI stylesheet. */
/** Combined production theme + WrNexus UI stylesheet. */
frameworkCssPath?: string;
/** Pre-built `window.__wireSchemas = {...}` script for client validation. */
/** Pre-built `window.__wrnSchemas = {...}` script for client validation. */
schemasJs?: string;
/**
* Authorization declarations discovered by `wrnexus build` from
@@ -367,7 +367,7 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
return (await serveStoredFile(pathname)) ?? new Response("Not Found", { status: 404 });
}
if (pathname === "/__wrnexus/schemas.js") {
return new Response(opts.schemasJs ?? "window.__wireSchemas={};", { headers: JS_HEADERS });
return new Response(opts.schemasJs ?? "window.__wrnSchemas={};", { headers: JS_HEADERS });
}
if (pathname === "/__wrnexus/theme.css") return serveFile(opts.themePath, CSS_HEADERS);
const activeThemeMatch = /^\/__wrnexus\/theme\/([^/]+)\/([^/]+)\.css$/.exec(pathname);
@@ -524,7 +524,7 @@ export function createProductionHandlers(
/**
* Apply migrations bundled into the build before the server accepts traffic, so
* a fresh deploy always runs on the latest schema — exactly like the dev server
* auto-migrates on startup. Applied migrations are tracked in `_wire_migrations`,
* auto-migrates on startup. Applied migrations are tracked in `_wrn_migrations`,
* so this is idempotent and safe to run on every boot. Opt out with
* `autoMigrate: false` (e.g. multi-instance deploys that migrate in a release
* step). A failed migration is logged but does not crash the server: each
+7 -7
View File
@@ -4,8 +4,8 @@ import { brotliCompressSync, constants as zlibConstants } from "node:zlib";
*
* 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.
* `RuntimeDeps`. Dev connects in dynamic module loading + on-the-fly bundling;
* prod connects in a static manifest + pre-built chunks on disk.
*/
import {
@@ -154,7 +154,7 @@ export interface RuntimeDeps {
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`). */
/** When true, inject the WrNexus UI stylesheet link (`/__wrnexus/ui.css`). */
hasUi?: boolean;
/** Production build combined theme + UI stylesheet. */
hasFrameworkStyles?: boolean;
@@ -705,10 +705,10 @@ export const HMR_CLIENT_JS = `
}
if (
window.wireTheme &&
typeof window.wireTheme.bind === "function"
window.wrnTheme &&
typeof window.wrnTheme.bind === "function"
) {
window.wireTheme.bind(document);
window.wrnTheme.bind(document);
}
window.dispatchEvent(
@@ -938,7 +938,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
// Global <head> additions, identical on every page (styles are global).
// Theme tokens load first so global.css and component styles can override them.
// Order: theme tokens, then Wire UI, then the app stylesheet — so the app's
// Order: theme tokens, then WrNexus UI, then the app stylesheet — so the app's
// own CSS (loaded last) can override both the tokens and the UI classes.
const headParts: string[] = [];
if (!deps.theme && deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
+3 -3
View File
@@ -21,15 +21,15 @@ export function collectScripts(
}
if (/\bdata-wrn-action=/.test(body)) scripts.push("/__wrnexus/actions.js");
if (
/\bdata-wire-theme-(toggle|set)\b/.test(body) ||
/\bdata-wire-accent-(set|clear)\b/.test(body)
/\bdata-wrn-theme-(toggle|set)\b/.test(body) ||
/\bdata-wrn-accent-(set|clear)\b/.test(body)
) {
scripts.push(THEME_JS_HREF);
}
if (/\bdata-schema="[A-Za-z0-9_-]+"/.test(body)) {
scripts.push("/__wrnexus/schemas.js", "/__wrnexus/validate.js");
}
if (/\bdata-wire-lang-set\b/.test(body) || /\bselect[^>]*\bdata-wire-lang\b/.test(body)) {
if (/\bdata-wrn-lang-set\b/.test(body) || /\bselect[^>]*\bdata-wrn-lang\b/.test(body)) {
scripts.push(I18N_JS_HREF);
}
if (/\bdata-room=/.test(body)) scripts.push("/__wrnexus/realtime.js");
@@ -54,7 +54,7 @@ test("server actions validate, enforce CSRF, invalidate, and progressively enhan
expect(html).toContain('name="wrnexus-csrf"');
expect(html).toContain("/__wrnexus/actions.js");
const cookie = page!.headers.get("set-cookie")!;
const token = /wire-csrf=([^;]+)/.exec(cookie)?.[1];
const token = /wrn-csrf=([^;]+)/.exec(cookie)?.[1];
if (!token) throw new Error("expected CSRF cookie");
const invalid = await handlers.fetch(
@@ -3,7 +3,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "nod
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
compileWireArtifactsAsync,
compileWrnArtifactsAsync,
setCompileCacheDir,
setCompileImportOptions,
} from "../src/pipeline.ts";
@@ -27,7 +27,7 @@ page Settings {
try {
setCompileCacheDir(cache);
const artifacts = await compileWireArtifactsAsync(page);
const artifacts = await compileWrnArtifactsAsync(page);
const browser = readFileSync(artifacts.browser, "utf8");
expect(browser).toContain("wrnexus-client-bundled");
expect(browser).not.toContain("file://");
@@ -65,7 +65,7 @@ page Settings {
aliases: { "~": "./app" },
autoImport: false,
});
const artifacts = await compileWireArtifactsAsync(page);
const artifacts = await compileWrnArtifactsAsync(page);
const browser = readFileSync(artifacts.browser, "utf8");
expect(browser).toContain("wrnexus-client-bundled");
expect(browser).not.toContain("~/client/storage.ts");
@@ -81,7 +81,7 @@ page Settings {
aliases: { "~": "./alternate" },
autoImport: false,
});
const changed = await compileWireArtifactsAsync(page);
const changed = await compileWrnArtifactsAsync(page);
expect(changed.browser).not.toBe(artifacts.browser);
} finally {
setCompileCacheDir(null);
@@ -3,7 +3,7 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import {
compileWireArtifacts,
compileWrnArtifacts,
setCompileCacheDir,
setCompileImportOptions,
} from "../src/pipeline.ts";
@@ -31,24 +31,24 @@ function fixture(): { root: string; page: string } {
test("legacy, compatible, and explicit import modes are enforced from app config", () => {
const { root, page } = fixture();
setCompileImportOptions(root, { mode: "legacy" });
expect(() => compileWireArtifacts(page, 1)).not.toThrow();
expect(() => compileWrnArtifacts(page, 1)).not.toThrow();
const warnings: string[] = [];
const originalWarn = console.warn;
console.warn = (...args) => warnings.push(args.join(" "));
try {
setCompileImportOptions(root, { mode: "compatible" });
expect(() => compileWireArtifacts(page, 2)).not.toThrow();
expect(() => compileWrnArtifacts(page, 2)).not.toThrow();
expect(warnings.some((message) => message.includes("WRN-IMPORT-IMPLICIT"))).toBe(true);
setCompileImportOptions(root, { mode: "explicit" });
expect(() => compileWireArtifacts(page, 3)).toThrow("WRN-IMPORT-IMPLICIT");
expect(() => compileWrnArtifacts(page, 3)).toThrow("WRN-IMPORT-IMPLICIT");
writeFileSync(
page,
'import Card from "@/components/Card.wrn"\npage Home { view { <Card /> } }',
);
expect(() => compileWireArtifacts(page, 4)).not.toThrow();
expect(() => compileWrnArtifacts(page, 4)).not.toThrow();
} finally {
console.warn = originalWarn;
}
@@ -68,5 +68,5 @@ test("compiler-native reactive elements do not require application imports", ()
}`,
);
setCompileImportOptions(root, { mode: "explicit" });
expect(() => compileWireArtifacts(page, 5)).not.toThrow();
expect(() => compileWrnArtifacts(page, 5)).not.toThrow();
});
+6 -6
View File
@@ -3,8 +3,8 @@ import { mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
compileWireArtifacts,
compileWireArtifactsAsync,
compileWrnArtifacts,
compileWrnArtifactsAsync,
getWrnCompileMetrics,
invalidateModule,
loadModule,
@@ -32,7 +32,7 @@ test("development compilation awaits plugin AST and code transforms", async () =
});
try {
writeFileSync(file, "page Home { view { <h1>Plugin</h1> } }\n");
const artifact = await compileWireArtifactsAsync(file);
const artifact = await compileWrnArtifactsAsync(file);
expect(astTransformed).toBeTrue();
expect((await import(artifact.main)).pluginTransformed).toBeTrue();
} finally {
@@ -48,10 +48,10 @@ test("WRN compilation exposes cache hit, miss, timing, and error metrics", () =>
resetWrnCompileMetrics();
try {
writeFileSync(file, "page Home { view { <h1>Metrics</h1> } }\n");
compileWireArtifacts(file);
compileWireArtifacts(file);
compileWrnArtifacts(file);
compileWrnArtifacts(file);
writeFileSync(file, "page Broken { view { <h1> } }\n");
expect(() => compileWireArtifacts(file)).toThrow();
expect(() => compileWrnArtifacts(file)).toThrow();
expect(getWrnCompileMetrics()).toMatchObject({
hits: 1,
misses: 2,
@@ -2,13 +2,13 @@ import { expect, test } from "bun:test";
import { HMR_CLIENT_JS, collectScripts } from "../src/runtime.ts";
test("accent controls load the shared theme runtime", () => {
expect(collectScripts('<button data-wire-accent-set="rose">Rose</button>')).toContain(
expect(collectScripts('<button data-wrn-accent-set="rose">Rose</button>')).toContain(
"/__wrnexus/theme.js",
);
});
test("accent clear controls load the shared theme runtime", () => {
expect(collectScripts("<button data-wire-accent-clear>Reset</button>")).toContain(
expect(collectScripts("<button data-wrn-accent-clear>Reset</button>")).toContain(
"/__wrnexus/theme.js",
);
});
@@ -20,9 +20,9 @@ test("HMR preserves the active browser theme and accent", () => {
expect(HMR_CLIENT_JS).toMatch(/var nextLang\s*=\s*doc\.documentElement\.getAttribute\("lang"\)/);
expect(HMR_CLIENT_JS).toMatch(/typeof\s+window\.wireTheme\.bind\s*===\s*"function"/);
expect(HMR_CLIENT_JS).toMatch(/typeof\s+window\.wrnTheme\.bind\s*===\s*"function"/);
expect(HMR_CLIENT_JS).toMatch(/window\.wireTheme\.bind\(document\)/);
expect(HMR_CLIENT_JS).toMatch(/window\.wrnTheme\.bind\(document\)/);
expect(HMR_CLIENT_JS).toContain('new CustomEvent("wrnexus:hmr-updated"');
});
+2 -2
View File
@@ -2,7 +2,7 @@ import { expect, test } from "bun:test";
import { createDevAssetServer } from "../src/assets.ts";
test("UI CSS is read from its live source after an HMR change", async () => {
let css = ".wire-card { color: red; }";
let css = ".wrn-card { color: red; }";
const assets = createDevAssetServer(
process.cwd(),
"development",
@@ -14,7 +14,7 @@ test("UI CSS is read from its live source after an HMR change", async () => {
const first = await assets.serve("/__wrnexus/ui.css");
expect(await first?.text()).toContain("color: red");
css = ".wire-card { color: blue; }";
css = ".wrn-card { color: blue; }";
assets.invalidateCss();
const second = await assets.serve("/__wrnexus/ui.css");