Files
WRNexusJS/packages/dev-server/src/runtime.ts
T
Clintchiz b1086d14e9
Quality / quality (ubuntu-latest) (push) Failing after 6m7s
Quality / quality (windows-latest) (push) Canceled after 0s
fix(auth): secure hydration and align password forms
2026-08-11 17:05:04 +05:30

2511 lines
98 KiB
TypeScript

import { brotliCompressSync, constants as zlibConstants } from "node:zlib";
/**
* 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.
*/
import {
bridgeRealtime,
createContext,
createCorsPreflightResponse,
createRealtimeRegistry,
csrfToken,
verifyCsrf,
escapeHtml,
etag,
isRoomDefinition,
isWebSocketOriginAllowed,
notModified,
isSafeRequestPath,
renderError,
renderNotFound,
withContextHeaders,
withSecurityHeaders,
resolveRequestUrl,
tenantMiddleware,
HealthRegistry,
type Context,
type Middleware,
type Mode,
type PageMeta,
type RealtimeBus,
type RealtimeConnectMeta,
type SecurityConfig,
type SeoConfig,
type TFunction,
} from "@wrnexus/core";
import { requestHardening } from "@wrnexus/security";
import {
createLivenessHandler,
createOtlpTraceExporter,
createReadinessHandler,
createWebVitalsHandler,
defaultMetrics,
metricsMiddleware,
traceMiddleware,
webVitalsClient,
} from "@wrnexus/observability";
import type { Router } from "@wrnexus/router";
import {
partialPrerender,
renderDocument,
streamPartialDocument,
type RenderScript,
type ScriptAsset,
} from "@wrnexus/ssr";
import {
disposeRequestStores,
renderStoreHydration,
requestStoreContainer,
} from "@wrnexus/ssr/store-context";
import type { StoreDefinition } from "@wrnexus/store";
import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
import { CacheCoordinator } from "@wrnexus/cache";
import { generateServiceWorker } from "@wrnexus/pwa";
import { collectScripts, usesMobileRuntime } from "./script-selection.ts";
export { collectScripts, usesMobileRuntime } from "./script-selection.ts";
import {
ACCENT_COOKIE,
THEME_COOKIE,
activeThemeCssHref,
resolveAccentName,
resolveThemeName,
type MobileConfig,
type ObservabilityConfig,
type PwaConfig,
type ResolvedTheme,
type TenancyConfig,
} from "@wrnexus/styles";
import {
renderI18nData,
makeT,
resolveLang,
translateHtml,
type ResolvedI18n,
} from "@wrnexus/i18n";
import { runMiddleware } from "./pipeline.ts";
import { handleRpcRequest, isRpcPath } from "./rpc-dispatch.ts";
import type { ServiceImplementation, StreamImplementation } from "@wrnexus/rpc";
import type { HmrHub } from "./hmr.ts";
import type {
DevToolbarConfig,
DevToolbarPanel,
DevToolbarPlatformSnapshot,
} from "@wrnexus/dev-toolbar/types";
import {
createServerIssue,
handleDevToolbarRoute,
issueFromError,
type DevToolbarCollector,
} from "@wrnexus/dev-toolbar/server";
/** HTTP methods we recognize as API handler exports. */
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] as const;
/** A realtime module's `websocket` export: a bag of optional lifecycle hooks. */
export 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`.
*/
export type WsData =
| { kind: "realtime"; handler: WsHandler } // legacy raw `websocket` export
| { kind: "room"; meta: RealtimeConnectMeta } // `defineRoom` default export
| { kind: "hmr"; baseUrl: string; headers: [string, string][] };
type RouteModule = Record<string, unknown>;
type ApiRegistry = Record<string, unknown>;
interface ActionEntry {
run: (input: unknown, ctx: Context) => unknown | Promise<unknown>;
schema?: {
parse(input: unknown): { ok: boolean; value: unknown; errors: Record<string, string> };
};
}
interface CsrBinding {
id: string;
method?: string;
path: string;
body?: string;
expr?: string;
helpers?: string;
}
/** Serves framework-owned assets under `/__wrnexus/*` (islands, reactive, hmr). */
export interface AssetServer {
serve(pathname: string): Promise<Response | null>;
}
export 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>;
/** 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;
stylesIncludeUi?: 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;
/** Package browser runtimes resolved by the plugin system. */
clientRuntimes?: ClientRuntimeDefinition[];
/** Page navigation strategy. `document` disables same-origin link interception. */
navigation?: { mode?: "auto" | "client" | "document" };
/** 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;
/** Built-in request tracing and Server-Timing policy. */
observability?: ObservabilityConfig;
/** Dependency health checks used by `/readyz` and `/__wrnexus/ready`. */
health?: HealthRegistry;
/** Built-in tenant identity resolution. */
tenancy?: TenancyConfig;
/** 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;
/** Shared first-class data/component/page caches. */
cache?: CacheCoordinator;
/** Final document transform supplied by the plugin render lifecycle. */
renderHtml?: (html: string) => string | Promise<string>;
devToolbar?: {
config: DevToolbarConfig;
collector: DevToolbarCollector;
root: string;
platform?: DevToolbarPlatformSnapshot;
panels?: DevToolbarPanel[] | (() => DevToolbarPanel[] | Promise<DevToolbarPanel[]>);
};
}
const DEV_TOOLBAR_SCRIPT =
'<script type="module" src="/__wrnexus/dev-toolbar.js" data-wrnexus-dev-toolbar></script>';
function shouldEnableDevToolbar(mode: string, deps: RuntimeDeps): boolean {
return (
mode === "development" &&
deps.devToolbar !== undefined &&
deps.devToolbar.config.enabled !== false
);
}
function tenantIdentityFromConfig(
config: TenancyConfig,
): (ctx: Context) => Promise<{ id: string; slug?: string } | null> {
return async (ctx) => {
const host = ctx.url.hostname.toLowerCase();
if (config.mode === "domain") return host ? { id: host, slug: host } : null;
if (config.mode === "path") {
const segments = ctx.url.pathname.split("/").filter(Boolean);
const prefix = config.pathPrefix?.replace(/^\/+|\/+$/g, "");
const slug = prefix ? (segments[0] === prefix ? segments[1] : undefined) : segments[0];
return slug ? { id: slug, slug } : null;
}
if (config.mode === "subdomain" || config.mode === undefined) {
const roots = config.rootDomains?.map((domain) => domain.toLowerCase()) ?? [];
const root = roots.find((domain) => host === domain || host.endsWith(`.${domain}`));
const slug = root ? host.slice(0, -(root.length + 1)) : host.split(".")[0];
if (!slug || slug === host || slug === "www" || slug === "localhost") return null;
return { id: slug, slug };
}
return null;
};
}
function frameworkMiddleware(deps: RuntimeDeps): Middleware[] {
const middleware: Middleware[] = [];
if (deps.security?.requestLimits) {
middleware.push(requestHardening(deps.security.requestLimits));
}
if (deps.observability && deps.observability.enabled !== false) {
middleware.push(metricsMiddleware({ registry: defaultMetrics, includePath: false }));
const traceExporter =
deps.observability.exporter === "otlp" && deps.observability.endpoint
? createOtlpTraceExporter(deps.observability.endpoint, {
serviceName: deps.observability.serviceName,
})
: undefined;
middleware.push(
traceMiddleware({
serviceName: deps.observability.serviceName,
sampleRate: deps.observability.sampleRate,
serverTiming: deps.observability.serverTiming,
exporter: traceExporter,
onSpan:
deps.observability.exporter === "console"
? (span) => {
console.log(
`[wrnexus:trace] ${span.name} ${span.durationMs.toFixed(2)}ms trace=${span.traceId}`,
);
}
: undefined,
onExportError(error) {
console.error("[wrnexus:trace] export failed", error);
},
}),
);
}
if (deps.tenancy && Object.keys(deps.tenancy).length > 0 && deps.tenancy.mode !== "custom") {
middleware.push(
tenantMiddleware(tenantIdentityFromConfig(deps.tenancy), {
required: deps.tenancy.required,
}),
);
}
return middleware;
}
function shouldReportNotFound(pathname: string): boolean {
return !(
pathname.startsWith("/__wrnexus/") ||
pathname.startsWith("/.well-known/") ||
pathname === "/favicon.ico" ||
pathname.endsWith(".map")
);
}
export function renderPwaClient(version = "2") {
return `if ("serviceWorker" in navigator) {
var swVersion = ${JSON.stringify(version)};
var swUrl = "/sw.js?v=" + encodeURIComponent(swVersion);
if (window.trustedTypes) {
try {
swUrl = window.trustedTypes.createPolicy("wrnexus-pwa", { createScriptURL: function(value) { return value; } }).createScriptURL(swUrl);
} catch (_) {}
}
addEventListener("load", function () {
var register = function () {
navigator.serviceWorker.register(swUrl, { updateViaCache: "none" }).catch(function () {});
};
if ("requestIdleCallback" in window) window.requestIdleCallback(register, { timeout: 2000 });
else setTimeout(register, 0);
});
}`;
}
export const PWA_CLIENT = renderPwaClient();
export const PWA_DEV_CLEANUP_CLIENT = `if ("serviceWorker" in navigator && !sessionStorage.getItem("wrnexus-pwa-dev-cleaned")) {
sessionStorage.setItem("wrnexus-pwa-dev-cleaned", "1");
navigator.serviceWorker.getRegistrations().then(function (registrations) {
return Promise.all(registrations.filter(function (registration) {
return new URL(registration.active?.scriptURL || registration.installing?.scriptURL || registration.waiting?.scriptURL || location.origin, location.origin).pathname === "/sw.js";
}).map(function (registration) { return registration.unregister(); }));
}).catch(function () {});
if (window.caches) caches.keys().then(function (keys) {
return Promise.all(keys.filter(function (key) { return key.indexOf("wrnexus-pwa-") === 0; }).map(function (key) { return caches.delete(key); }));
}).catch(function () {});
}`;
function renderPwaServiceWorker(pwa: PwaConfig): string {
return generateServiceWorker(pwa);
}
const DEFAULT_PWA_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" rx="112" fill="#6366f1"/>
<path d="M112 144h64l42 168 38-128h48l38 128 42-168h64l-72 224h-60l-36-117-36 117h-60z" fill="white"/>
</svg>`;
export const MOBILE_CLIENT = `const isNativeApp = window.Capacitor?.isNativePlatform?.() === true ||
["android", "ios"].includes(window.Capacitor?.getPlatform?.());
const nativeTarget = isNativeApp ? "mobile" : "browser";
const nativeCapabilities = new Map();
const registerNative = (name, adapters) => nativeCapabilities.set(name, adapters);
const unregisterNative = name => nativeCapabilities.delete(name);
const nativeAdapter = name => nativeCapabilities.get(name)?.[nativeTarget];
const nativeSupports = name => {
const adapter = nativeAdapter(name);
return !!adapter && (adapter.supported?.() ?? true);
};
const nativeRun = async (name, options) => {
const adapter = nativeAdapter(name);
if (!adapter || !(adapter.supported?.() ?? true)) throw new Error('Native capability "' + name + '" is unavailable on ' + nativeTarget);
return adapter.run(options);
};
const cap = (plugin, method) => ({
supported: () => typeof window.Capacitor?.Plugins?.[plugin]?.[method] === "function",
run: options => window.Capacitor.Plugins[plugin][method](options)
});
const capWith = (plugin, method, transform) => ({
supported: () => typeof window.Capacitor?.Plugins?.[plugin]?.[method] === "function",
run: options => window.Capacitor.Plugins[plugin][method](transform(options))
});
registerNative("clipboard.write", {
browser: { supported: () => !!navigator.clipboard?.writeText, run: o => navigator.clipboard.writeText(String(o?.text ?? "")) },
mobile: capWith("Clipboard", "write", o => ({ string: String(o?.text ?? o?.string ?? "") }))
});
registerNative("share", {
browser: { supported: () => typeof navigator.share === "function", run: o => navigator.share(o) },
mobile: cap("Share", "share")
});
registerNative("geolocation", {
browser: { supported: () => !!navigator.geolocation, run: o => new Promise((resolve, reject) => navigator.geolocation.getCurrentPosition(resolve, reject, o)) },
mobile: cap("Geolocation", "getCurrentPosition")
});
registerNative("network", {
browser: { run: () => ({ connected: navigator.onLine }) }, mobile: cap("Network", "getStatus")
});
registerNative("camera", {
browser: { supported: () => !!navigator.mediaDevices?.getUserMedia, run: o => navigator.mediaDevices.getUserMedia({ video: o ?? true, audio: false }) },
mobile: cap("Camera", "getPhoto")
});
registerNative("haptics", { mobile: cap("Haptics", "impact") });
nativeCapabilities.get("haptics").browser = { supported: () => typeof navigator.vibrate === "function", run: o => navigator.vibrate(o?.duration ?? 20) };
registerNative("storage.get", { browser: { run: o => ({ value: localStorage.getItem(String(o?.key ?? "")) }) }, mobile: cap("Preferences", "get") });
registerNative("storage.set", { browser: { run: o => localStorage.setItem(String(o?.key ?? ""), String(o?.value ?? "")) }, mobile: cap("Preferences", "set") });
registerNative("filesystem.read", {
browser: { supported: () => "showOpenFilePicker" in window, run: async () => { const [handle] = await window.showOpenFilePicker(); return handle?.getFile(); } },
mobile: cap("Filesystem", "readFile")
});
registerNative("filesystem.write", {
browser: { supported: () => "showSaveFilePicker" in window, run: async o => { const handle = await window.showSaveFilePicker(); const writable = await handle.createWritable(); await writable.write(o?.data ?? ""); await writable.close(); } },
mobile: cap("Filesystem", "writeFile")
});
registerNative("notifications.schedule", {
browser: { supported: () => typeof Notification !== "undefined", run: async o => { if (Notification.permission === "default") await Notification.requestPermission(); if (Notification.permission !== "granted") throw new Error("Notification permission was not granted"); return new Notification(o?.title ?? "Notification", { body: o?.body }); } },
mobile: capWith("LocalNotifications", "schedule", o => o?.notifications ? o : ({ notifications: [{ id: o?.id ?? Date.now() % 2147483647, title: o?.title ?? "Notification", body: o?.body ?? "", schedule: o?.schedule }] }))
});
registerNative("device.info", { browser: { run: () => ({ platform: "web", userAgent: navigator.userAgent, language: navigator.language }) }, mobile: cap("Device", "getInfo") });
window.__WrNexusNativePending?.forEach((adapters, name) => registerNative(name, adapters));
delete window.__WrNexusNativePending;
window.WrNexusNative = { target: nativeTarget, isMobile: isNativeApp, register: registerNative, unregister: unregisterNative, supports: nativeSupports, run: nativeRun };
const revealMobileOnly = root => {
const elements = [];
if (root instanceof Element) elements.push(root);
root.querySelectorAll?.("[data-mobile-only],[data-native-only],[data-native-requires],[data-native-unsupported]").forEach(element => elements.push(element));
elements.forEach(element => {
if (element.matches("[data-mobile-only]")) element.hidden = !isNativeApp;
const only = element.getAttribute("data-native-only");
if (only) element.hidden = !((only === "mobile" && isNativeApp) || (["browser", "web"].includes(only) && !isNativeApp) || only === window.Capacitor?.getPlatform?.());
const required = element.getAttribute("data-native-requires");
if (required) element.hidden = !nativeSupports(required);
const unsupported = element.getAttribute("data-native-unsupported");
if (unsupported) element.hidden = nativeSupports(unsupported);
});
};
revealMobileOnly(document);
new MutationObserver(records => {
records.forEach(record => record.addedNodes.forEach(node => {
revealMobileOnly(node);
installNativeEvents(node);
}));
}).observe(document.documentElement, { childList: true, subtree: true });
const handleNativeEvent = async event => {
const eventSelector = "[data-on-wrnexus-browser-" + event.type + "],[data-on-wrnexus-mobile-" + event.type + "]";
const actionSelector = event.type === "click" ? ",[data-native-browser],[data-native-mobile]" : "";
const element = event.target.closest?.(eventSelector + actionSelector);
if (!element) return;
const platformEvent = "wrnexus-" + nativeTarget + "-" + event.type;
if (element.hasAttribute("data-on-" + platformEvent)) element.dispatchEvent(new CustomEvent(platformEvent));
if (event.type !== "click") return;
const capability = element.getAttribute("data-native-" + nativeTarget);
if (!capability) return;
let options = {};
const raw = element.getAttribute("data-native-options");
if (raw) try { options = JSON.parse(raw); } catch (error) {
element.dispatchEvent(new CustomEvent("wrnexus:native-error", { detail: error, bubbles: true }));
return;
}
try {
const result = await nativeRun(capability, options);
element.dispatchEvent(new CustomEvent("wrnexus:native-success", { detail: { capability, result }, bubbles: true }));
} catch (error) {
element.dispatchEvent(new CustomEvent("wrnexus:native-error", { detail: { capability, error }, bubbles: true }));
}
};
const nativeEventTypes = new Set();
const listenNativeEvent = type => {
if (nativeEventTypes.has(type)) return;
nativeEventTypes.add(type);
document.addEventListener(type, handleNativeEvent, true);
};
const installNativeEvents = root => {
const elements = [];
if (root instanceof Element) elements.push(root);
root.querySelectorAll?.("*").forEach(element => elements.push(element));
elements.forEach(element => Array.from(element.attributes).forEach(attribute => {
const match = /^data-on-wrnexus-(?:browser|mobile)-(.+)$/.exec(attribute.name);
if (match) listenNativeEvent(match[1]);
}));
};
listenNativeEvent("click");
installNativeEvents(document);
const openWebCamera = async image => {
if (!navigator.mediaDevices?.getUserMedia) return false;
let stream;
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: "environment" } },
audio: false
});
const overlay = document.createElement("div");
Object.assign(overlay.style, {
position: "fixed", inset: "0", zIndex: "2147483647", display: "grid",
placeItems: "center", padding: "24px", background: "rgba(2,6,23,.92)"
});
const panel = document.createElement("div");
Object.assign(panel.style, { width: "min(720px,100%)", color: "white" });
const video = document.createElement("video");
video.autoplay = true;
video.muted = true;
video.playsInline = true;
video.srcObject = stream;
Object.assign(video.style, {
display: "block", width: "100%", maxHeight: "70vh", objectFit: "contain",
borderRadius: "16px", background: "black"
});
const controls = document.createElement("div");
Object.assign(controls.style, { display: "flex", gap: "12px", marginTop: "16px" });
const capture = document.createElement("button");
capture.type = "button";
capture.textContent = "Capture photo";
const cancel = document.createElement("button");
cancel.type = "button";
cancel.textContent = "Cancel";
[capture, cancel].forEach(control => Object.assign(control.style, {
flex: "1", padding: "14px", border: "0", borderRadius: "12px",
fontWeight: "700", cursor: "pointer"
}));
capture.style.background = "#6366f1";
capture.style.color = "white";
const close = () => {
stream.getTracks().forEach(track => track.stop());
overlay.remove();
};
capture.onclick = () => {
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth || 1280;
canvas.height = video.videoHeight || 720;
canvas.getContext("2d")?.drawImage(video, 0, 0, canvas.width, canvas.height);
if (image) image.src = canvas.toDataURL("image/jpeg", 0.92);
close();
};
cancel.onclick = close;
controls.append(capture, cancel);
panel.append(video, controls);
overlay.append(panel);
document.body.append(overlay);
await video.play();
return true;
} catch (error) {
stream?.getTracks().forEach(track => track.stop());
return false;
}
};
document.addEventListener("click", async event => {
const button = event.target.closest?.("[data-mobile-camera]");
if (!button) return;
const selector = button.getAttribute("data-mobile-camera") || "[data-mobile-photo]";
const image = document.querySelector(selector);
try {
const camera = window.Capacitor?.Plugins?.Camera;
if (isNativeApp && camera) {
const photo = await camera.getPhoto({ quality: 90, resultType: "uri", source: "camera" });
if (image) image.src = photo.webPath || photo.path;
return;
}
if (await openWebCamera(image)) return;
const input = document.createElement("input");
input.type = "file";
input.accept = "image/*";
input.setAttribute("capture", "environment");
input.hidden = true;
input.onchange = () => {
if (image && input.files?.[0]) image.src = URL.createObjectURL(input.files[0]);
input.remove();
};
document.body.append(input);
input.click();
} catch (error) {
window.dispatchEvent(new CustomEvent("wrnexus:camera-error", { detail: error }));
}
});
const nativeApp = window.Capacitor?.Plugins?.App;
nativeApp?.addListener?.("backButton", ({ canGoBack }) => {
if (canGoBack || history.length > 1) history.back();
// At the root screen intentionally do nothing instead of closing the app.
});`;
function revealMobileOnlyHtml(html: string): string {
return html.replace(/<[^>]*\bdata-mobile-only\b[^>]*>/gi, (tag) =>
tag.replace(/\s+hidden(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?/i, ""),
);
}
/** The global stylesheet URL (served by the asset server when styles exist). */
export const STYLES_HREF = "/__wrnexus/styles.css";
/**
* Inline HMR client (WebSocket). Goals:
* - CSS change -> hot-swap the stylesheet, zero reload, zero flash.
* - markup/page -> ask over the existing HMR WebSocket for fresh HTML and
* MORPH the live DOM in place, without restarting the server.
* - island code -> same WebSocket sync path; no location.reload().
*/
export const HMR_CLIENT_JS = `
(function () {
if (!("WebSocket" in window)) return;
var proto = location.protocol === "https:" ? "wss" : "ws";
var url = proto + "://" + location.host + "/__wrnexus/hmr";
var ws, openedBefore = false, timer, pendingSync = false;
function connect() {
ws = new WebSocket(url);
ws.onopen = function () {
if (openedBefore) requestSync(); // reconnect == server restarted
openedBefore = true;
};
ws.onmessage = function (e) {
var msg; try { msg = JSON.parse(e.data); } catch (_) { return; }
if (msg.channel === "toolbar") {
window.dispatchEvent(new CustomEvent("wrnexus:toolbar-message", { detail: msg }));
} else if (msg.type === "css") {
swapCss(msg.version);
window.dispatchEvent(new CustomEvent("wrnexus:hmr", { detail: msg }));
} else if (msg.type === "store-update") {
applyStoreUpdates(msg);
} else if (msg.type === "reload") requestSync();
else if (msg.type === "html") applyHtml(msg.html);
else if (msg.type === "error") console.error("[wrnexus] HMR update failed:", msg.message);
};
ws.onclose = function () { clearTimeout(timer); timer = setTimeout(connect, 400); };
ws.onerror = function () { try { ws.close(); } catch (_) {} };
}
function send(message) {
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
ws.send(JSON.stringify(message));
return true;
}
async function applyStoreUpdates(message) {
var updates = Array.isArray(message.stores) ? message.stores : [];
for (var i = 0; i < updates.length; i++) {
var update = updates[i];
try {
var separator = String(update.url).indexOf("?") >= 0 ? "&" : "?";
var module = await import(String(update.url) + separator + "hmr=" + encodeURIComponent(String(message.version || Date.now())));
var definition = module[String(update.name) + "Definition"];
if (!definition) throw new Error("Generated store module did not export its definition");
if (typeof window.__wrnexusApplyStoreHotUpdate === "function") {
var result = await window.__wrnexusApplyStoreHotUpdate(String(update.name), definition);
window.dispatchEvent(new CustomEvent("wrnexus:hmr-store-updated", { detail: { update: update, result: result } }));
}
} catch (error) {
console.error("[wrnexus] store HMR update failed", update, error);
requestSync();
return;
}
}
}
function requestSync() {
if (pendingSync) return;
pendingSync = true;
if (!send({ type: "sync", path: location.pathname + location.search })) {
pendingSync = false;
}
}
function swapCss(version) {
var links = document.querySelectorAll('link[rel="stylesheet"]');
for (var i = 0; i < links.length; i++) (function (link) {
var href = link.getAttribute("href");
if (!href || href.charAt(0) !== "/") return; // skip CDN links
var next = href.split("?")[0] + "?hmr=" + encodeURIComponent(String(version || "css"));
var copy = link.cloneNode(false);
copy.setAttribute("href", next);
copy.addEventListener("load", function () {
if (link.parentNode) link.parentNode.removeChild(link);
});
link.parentNode.insertBefore(copy, link.nextSibling);
})(links[i]);
}
function applyHtml(html) {
pendingSync = false;
var doc = new DOMParser().parseFromString(html, "text/html");
if (doc.title) {
document.title = doc.title;
}
// HMR morphs only #app. Keep the live browser theme and accent because
// they represent the user's current selection. Language remains SSR-owned.
var nextLang = doc.documentElement.getAttribute("lang");
if (nextLang == null) {
document.documentElement.removeAttribute("lang");
} else {
document.documentElement.setAttribute("lang", nextLang);
}
var from = document.getElementById("app");
var to = doc.getElementById("app");
syncWrnStyles(doc);
if (from && to) {
morph(from, to);
}
swapCss("html");
if (window.__wrnexusHydrateScopes) {
window.__wrnexusHydrateScopes(document);
}
if (window.__wrnexusHydrateCsrFetches) {
window.__wrnexusHydrateCsrFetches(document);
}
if (
window.wireTheme &&
typeof window.wireTheme.bind === "function"
) {
window.wireTheme.bind(document);
}
window.dispatchEvent(
new CustomEvent("wrnexus:hmr-updated", {
detail: {
theme: document.documentElement.getAttribute("data-theme"),
accent: document.documentElement.getAttribute("data-accent"),
},
}),
);
}
function currentDocumentNonce() {
var node = document.querySelector("script[nonce],style[nonce]");
if (!node) return "";
return node.nonce || node.getAttribute("nonce") || "";
}
function syncWrnStyles(nextDocument) {
var current = Array.prototype.slice.call(
document.querySelectorAll("style[data-wrnexus-style-id]"),
);
var next = Array.prototype.slice.call(
nextDocument.querySelectorAll("style[data-wrnexus-style-id]"),
);
var currentById = Object.create(null);
var nextIds = Object.create(null);
var nonce = currentDocumentNonce();
current.forEach(function (style) {
var id = style.getAttribute("data-wrnexus-style-id");
if (id) currentById[id] = style;
});
next.forEach(function (nextStyle) {
var id = nextStyle.getAttribute("data-wrnexus-style-id");
if (!id) return;
nextIds[id] = true;
var currentStyle = currentById[id];
if (!currentStyle) {
currentStyle = document.createElement("style");
currentStyle.setAttribute("data-wrnexus-style-id", id);
}
["data-wrnexus-style-owner", "data-wrnexus-style-kind"].forEach(function (name) {
var value = nextStyle.getAttribute(name);
if (value == null) currentStyle.removeAttribute(name);
else currentStyle.setAttribute(name, value);
});
if (nonce) currentStyle.setAttribute("nonce", nonce);
if (currentStyle.textContent !== nextStyle.textContent) {
currentStyle.textContent = nextStyle.textContent;
}
// Appending an existing node moves it. This keeps every local style after
// global stylesheets and in the exact layout -> page -> component order
// produced by the incoming SSR document.
document.head.appendChild(currentStyle);
});
current.forEach(function (style) {
var id = style.getAttribute("data-wrnexus-style-id");
if (id && !nextIds[id]) style.remove();
});
}
// Minimal index-based DOM morph: preserve matching nodes (keeps state/focus),
// patch text and attributes, clone genuinely new nodes, drop removed ones.
// Preserve a hydrated subtree only while its server hydration signature and
// behavior are unchanged. Component edits must replace and re-hydrate the
// old subtree or HMR will keep stale markup indefinitely.
function morph(from, to) {
if (from.__wrnexusHydrated) {
var sameHydration =
from.getAttribute("data-wrn-hydration") === to.getAttribute("data-wrn-hydration") &&
from.getAttribute("data-wrn-behavior") === to.getAttribute("data-wrn-behavior") &&
from.getAttribute("data-scope") === to.getAttribute("data-scope");
if (sameHydration) return;
if (window.__wrnexusDisposeBehaviors) window.__wrnexusDisposeBehaviors(from);
from.replaceWith(to.cloneNode(true));
return;
}
syncAttrs(from, to);
var fc = from.childNodes, tc = to.childNodes, i;
for (i = 0; i < tc.length; i++) {
var t = tc[i], f = fc[i];
if (!f) { from.appendChild(t.cloneNode(true)); continue; }
if (f.nodeType !== t.nodeType || (f.nodeType === 1 && f.nodeName !== t.nodeName)) {
from.replaceChild(t.cloneNode(true), f); continue;
}
if (f.nodeType === 3 || f.nodeType === 8) { if (f.nodeValue !== t.nodeValue) f.nodeValue = t.nodeValue; continue; }
if (f.nodeType === 1) morph(f, t);
}
while (from.childNodes.length > tc.length) from.removeChild(from.lastChild);
}
function syncAttrs(from, to) {
var ta = to.attributes, fa = from.attributes, i;
for (i = 0; i < ta.length; i++) if (from.getAttribute(ta[i].name) !== ta[i].value) from.setAttribute(ta[i].name, ta[i].value);
for (i = fa.length - 1; i >= 0; i--) if (!to.hasAttribute(fa[i].name)) from.removeAttribute(fa[i].name);
}
// Exposed for tests; harmless (the client is injected only in dev).
window.__wrnexusHmr = { morph: morph, swapCss: swapCss, requestSync: requestSync, applyHtml: applyHtml };
connect();
})();
`;
/** A base64 CSP nonce for this request's inline scripts. */
function randomNonce(): string {
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
let bin = "";
for (const b of bytes) bin += String.fromCharCode(b);
return btoa(bin);
}
/** The dev HMR client as a nonce-tagged inline script (strict-CSP friendly). */
function hmrClientTag(nonce: string): string {
return `<script nonce="${nonce}">${HMR_CLIENT_JS}</script>`;
}
/** 403 for a rejected cross-site WebSocket handshake. */
function forbiddenOrigin(): Response {
return new Response("Forbidden WebSocket origin", { status: 403 });
}
export 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. */
export interface Ws {
data: WsData;
send(data: string | Uint8Array): unknown;
close(code?: number, reason?: string): void;
}
export 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. */
export function createHandlers(deps: RuntimeDeps): Handlers {
const { mode, hmr, router, loadModule, getMiddleware, assets } = deps;
const cache =
deps.cache ??
new CacheCoordinator({
onEvent: (event) => {
if (mode === "development")
console.debug(
`[wrnexus:cache] ${event.layer} ${event.operation}${event.key ? ` ${event.key}` : ""}`,
);
},
});
const initializeRequestCache = (ctx: Context): void => {
ctx.locals.cache = cache;
ctx.locals.requestCache ??= cache.request();
};
const builtInMiddleware = frameworkMiddleware(deps);
const resolveMiddleware = async (): Promise<Middleware[]> => [
...builtInMiddleware,
...(await getMiddleware()),
];
const maxBodyBytes = deps.maxBodyBytes ?? 10 * 1024 * 1024; // 10 MB default
// Not memoized across failures: a single bad file in app/services/ (e.g. a
// co-located helper with no default export) must not permanently break
// every route in the app. Only a SUCCESSFUL load is cached; a failed
// attempt logs loudly and is retried on the next RPC request.
let servicesPromise:
Promise<Map<string, ServiceImplementation | StreamImplementation>> | undefined;
const loadServices = (): Promise<Map<string, ServiceImplementation | StreamImplementation>> => {
if (!servicesPromise) {
servicesPromise = (async () => {
const services = new Map<string, ServiceImplementation | StreamImplementation>();
for (const entry of router.services) {
const imported = await loadModule(entry.file);
const implementation = imported.default as
(ServiceImplementation | StreamImplementation) | undefined;
if (
!implementation ||
(typeof (implementation as ServiceImplementation).invoke !== "function" &&
typeof (implementation as StreamImplementation).stream !== "function")
) {
throw new Error(
`RPC service ${entry.file} must default-export implement(...) or implementStream(...)`,
);
}
if (implementation.contract.name !== entry.name) {
throw new Error(
`RPC service file ${entry.file} is mounted as "${entry.name}" (its filename) ` +
`but its contract is named "${implementation.contract.name}". Rename the file to ` +
`match the contract, or rename the contract to match the file.`,
);
}
services.set(entry.name, implementation);
}
return services;
})().catch((error) => {
servicesPromise = undefined;
const app = process.env.WRNEXUS_APP_NAME ?? "app";
console.error(
`[wrnexus] failed to load RPC services (${app}):`,
error instanceof Error ? (error.stack ?? error.message) : error,
);
throw error;
});
}
return servicesPromise;
};
// Server-side realtime room manager (shared by every `defineRoom` connection).
const realtime = createRealtimeRegistry();
// Bridge across processes/instances when a pub/sub bus is provided, so room
// broadcasts reach clients on every app run sharing the bus (Redis in prod).
if (deps.realtimeBus) bridgeRealtime(realtime, deps.realtimeBus);
// 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
// own CSS (loaded last) can override both the tokens and the UI classes.
const headParts: string[] = [];
if (!deps.theme && deps.hasFrameworkStyles && !deps.stylesIncludeFramework) {
headParts.push(
`<link rel="stylesheet" href="${versionAssetUrl("/__wrnexus/framework.css", deps.assetVersion)}" />`,
);
}
if (deps.hasUi && !deps.stylesIncludeUi && !deps.stylesIncludeFramework) {
headParts.push(
`<link rel="stylesheet" href="${versionAssetUrl("/__wrnexus/ui.css", deps.assetVersion)}" />`,
);
}
if (deps.inlineStyles) {
headParts.push(`<style data-wrnexus-global>${escapeStyleContent(deps.inlineStyles)}</style>`);
} else if (deps.hasStyles) {
const stylesHref = versionAssetUrl(STYLES_HREF, deps.assetVersion);
headParts.push(`<link rel="preload" href="${stylesHref}" as="style" />`);
headParts.push(`<link rel="stylesheet" href="${stylesHref}" />`);
}
if (deps.head) headParts.push(deps.head);
const extraHead = headParts.join("\n ") || undefined;
const pwaEnabled = deps.pwa !== false && deps.pwa?.enabled !== false;
const pwaConfig: PwaConfig = deps.pwa && typeof deps.pwa === "object" ? deps.pwa : {};
const pwaServiceWorkerEnabled =
mode === "production" && pwaEnabled && pwaConfig.serviceWorker !== false;
const pwaDevCleanupEnabled = mode === "development";
const webVitalsEnabled =
deps.observability?.enabled !== false && deps.observability?.webVitals === true;
const webVitalsEndpoint = deps.observability?.webVitalsEndpoint ?? "/__wrnexus/metrics/vitals";
const webVitalsHandler = createWebVitalsHandler({ registry: defaultMetrics });
const livenessHandler = createLivenessHandler();
const readinessHandler = createReadinessHandler(deps.health ?? new HealthRegistry());
const configuredPermissions = deps.security?.permissionsPolicy;
const runtimeSecurity: SecurityConfig | undefined =
deps.mobile?.enabled === false
? deps.security
: {
...deps.security,
permissionsPolicy:
configuredPermissions === false
? false
: { camera: ["self"], ...(configuredPermissions ?? {}) },
};
async function fetchHandler(req: Request, server: UpgradeServer): Promise<Response | undefined> {
// Behind a trusted proxy (nginx / gateway), honor X-Forwarded-Proto/Host so
// ctx.url reflects the external HTTPS scheme — makes CSRF/session cookies Secure.
const url = resolveRequestUrl(req, deps.security?.trustProxy);
const nonce = randomNonce();
const secure = (res: Response): Response =>
withSecurityHeaders(req, res, mode, runtimeSecurity, nonce);
if (isRpcPath(url.pathname)) {
let services: Map<string, ServiceImplementation | StreamImplementation>;
try {
services = await loadServices();
} catch (error) {
const app = process.env.WRNEXUS_APP_NAME ?? "app";
const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
console.error(`[wrnexus] RPC request failed to load services (${app})\n${detail}`);
return secure(
Response.json(
{ ok: false, code: "RPC_UNKNOWN", message: "Service unavailable", retryable: false },
{ headers: { "cache-control": "private, no-store" } },
),
);
}
const rpcResponse = await handleRpcRequest(req, url, services);
if (rpcResponse) return secure(rpcResponse);
}
const preflight = createCorsPreflightResponse(req, deps.security);
if (preflight) return secure(preflight);
// Health check — unauthenticated, skips the middleware pipeline.
if (url.pathname === "/healthz" || url.pathname === "/__wrnexus/health") {
return secure(await livenessHandler(req));
}
if (url.pathname === "/readyz" || url.pathname === "/__wrnexus/ready") {
return secure(await readinessHandler(req));
}
if (webVitalsEnabled && url.pathname === webVitalsEndpoint) {
// Validate same-origin telemetry against the proxy-resolved public URL,
// rather than the internal listener URL used by the hosting platform.
return secure(await webVitalsHandler(new Request(url, req)));
}
if (webVitalsEnabled && url.pathname === "/__wrnexus/vitals.js") {
return secure(
new Response(
webVitalsClient({
endpoint: webVitalsEndpoint,
sampleRate: deps.observability?.sampleRate,
}),
{
headers: {
"content-type": "text/javascript; charset=utf-8",
"cache-control": url.searchParams.has("v")
? "public, max-age=31536000, immutable"
: "no-cache",
},
},
),
);
}
if (url.pathname === "/site.webmanifest" && pwaEnabled) {
const pwa = pwaConfig;
return secure(
Response.json(
{
id: pwa.id ?? pwa.startUrl ?? "/",
name: pwa.name ?? deps.seo?.title ?? "WrNexus App",
short_name: pwa.shortName ?? pwa.name ?? deps.seo?.title ?? "WrNexus",
description: pwa.description ?? deps.seo?.description,
start_url: pwa.startUrl ?? "/",
scope: pwa.scope ?? "/",
lang: pwa.lang ?? "en",
display: pwa.display ?? "standalone",
orientation: pwa.orientation ?? "any",
theme_color: pwa.themeColor ?? deps.seo?.themeColor ?? "#6c8cff",
background_color: pwa.backgroundColor ?? "#0f172a",
icons: pwa.icons ?? [
{
src: "/__wrnexus/pwa-icon.svg",
sizes: "any",
type: "image/svg+xml",
purpose: "any maskable",
},
],
categories: pwa.categories,
screenshots: pwa.screenshots?.map((screenshot) => ({
src: screenshot.src,
sizes: screenshot.sizes,
type: screenshot.type,
form_factor: screenshot.formFactor,
label: screenshot.label,
})),
shortcuts: pwa.shortcuts?.map((shortcut) => ({
name: shortcut.name,
short_name: shortcut.shortName,
description: shortcut.description,
url: shortcut.url,
icons: shortcut.icons,
})),
},
{ headers: { "content-type": "application/manifest+json", "cache-control": "no-cache" } },
),
);
}
if (url.pathname === "/__wrnexus/pwa-icon.svg" && pwaEnabled) {
return secure(
new Response(DEFAULT_PWA_ICON, {
headers: { "content-type": "image/svg+xml", "cache-control": "public, max-age=86400" },
}),
);
}
if (url.pathname === "/sw.js" && pwaServiceWorkerEnabled) {
return secure(
new Response(renderPwaServiceWorker(pwaConfig), {
headers: {
"content-type": "text/javascript; charset=utf-8",
"cache-control": "no-cache",
},
}),
);
}
if (url.pathname === "/__wrnexus/pwa.js" && pwaServiceWorkerEnabled) {
return secure(
new Response(renderPwaClient(deps.assetVersion ?? "2"), {
headers: {
"content-type": "text/javascript; charset=utf-8",
"cache-control": url.searchParams.has("v")
? "public, max-age=31536000, immutable"
: "no-cache",
},
}),
);
}
if (url.pathname === "/__wrnexus/pwa-dev-cleanup.js" && pwaDevCleanupEnabled) {
return secure(
new Response(PWA_DEV_CLEANUP_CLIENT, {
headers: {
"content-type": "text/javascript; charset=utf-8",
"cache-control": "no-store",
},
}),
);
}
if (url.pathname === "/__wrnexus/mobile.js" && deps.mobile?.enabled !== false) {
return secure(
new Response(MOBILE_CLIENT, {
headers: {
"content-type": "text/javascript; charset=utf-8",
"cache-control": url.searchParams.has("v")
? "public, max-age=31536000, immutable"
: "no-cache",
},
}),
);
}
// Reject oversized request bodies early (DoS guard).
const contentLength = Number(req.headers.get("content-length") ?? 0);
if (contentLength > maxBodyBytes) {
return secure(new Response("Payload Too Large", { status: 413 }));
}
if (deps.devToolbar) {
const toolbarResponse = await handleDevToolbarRoute(req, {
mode,
root: deps.devToolbar.root,
collector: deps.devToolbar.collector,
editor: deps.devToolbar.config.editor,
allowOpenEditor: deps.devToolbar.config.openEditor !== false,
platform: deps.devToolbar.platform,
panels: deps.devToolbar.panels,
});
if (toolbarResponse) return secure(toolbarResponse);
}
// --- HMR socket (dev only): upgrade before anything else. ---
if (hmr && url.pathname === "/__wrnexus/hmr") {
if (!isWebSocketOriginAllowed(req, deps.security)) return secure(forbiddenOrigin());
const headers = Array.from(req.headers.entries()).filter(
([name]) => !/^sec-websocket-|^connection$|^upgrade$/i.test(name),
);
if (server.upgrade(req, { data: { kind: "hmr", baseUrl: url.origin, headers } })) {
return undefined;
}
return secure(new Response("Expected a WebSocket upgrade request", { status: 426 }));
}
// --- Realtime: try to upgrade BEFORE the normal HTTP pipeline. ---
const rt = router.matchRealtime(url.pathname);
if (rt) {
// Reject cross-site WebSocket handshakes (CSWSH) before doing any work.
if (!isWebSocketOriginAllowed(req, deps.security)) return secure(forbiddenOrigin());
const mod = await loadModule(rt.route.file);
let data: WsData;
if (isRoomDefinition(mod.default)) {
// Identify the connection: an authenticated session user id, else ?user=.
const wsCtx = createContext(req, url);
const sessionUser = wsCtx.session.get<{ id?: unknown }>("user");
const user =
sessionUser && typeof sessionUser === "object" && sessionUser.id != null
? String(sessionUser.id)
: (url.searchParams.get("user") ?? undefined);
const query = Object.fromEntries(url.searchParams);
// Room-level gate (e.g. require auth) — reject before accepting the socket.
const allowed = await mod.default.handlers.authorize?.({
user,
query,
headers: req.headers,
});
if (allowed === false) {
return secure(new Response("Forbidden", { status: 403 }));
}
data = { kind: "room", meta: { room: url.pathname, def: mod.default, query, user } };
} else if (mod.websocket && typeof mod.websocket === "object") {
data = { kind: "realtime", handler: mod.websocket as WsHandler };
} else {
return secure(
new Response("Realtime route needs a default defineRoom() or a `websocket` export", {
status: 500,
}),
);
}
const upgraded = server.upgrade(req, { data });
if (upgraded) return undefined; // Bun takes over the socket.
return secure(new Response("Expected a WebSocket upgrade request", { status: 426 }));
}
try {
const ctx = createContext(req, url);
initializeRequestCache(ctx);
ctx.ip = server.requestIP?.(req)?.address ?? undefined;
ctx.locals.cspNonce = nonce; // available to pages for their own inline scripts
if (!["GET", "HEAD", "OPTIONS"].includes(req.method.toUpperCase())) {
const contentType = req.headers.get("content-type") ?? "";
if (contentType.includes("form")) {
try {
const form = await req.clone().formData();
const token = form.get("_csrf");
if (typeof token === "string") ctx.locals._csrf = token;
} catch {
// The endpoint will return its normal malformed-input response.
}
}
}
// Resolve the request language so both pages and API can translate.
if (deps.i18n) {
ctx.lang = resolveLang(
deps.i18n,
ctx.cookies.get(deps.i18n.cookie.name),
req.headers.get("accept-language"),
);
ctx.t = makeT(deps.i18n, ctx.lang);
}
const mws = await resolveMiddleware();
const res = secure(
withContextHeaders(ctx, await runMiddleware(mws, ctx, () => dispatch(ctx))),
);
return compressResponse(req, res);
} catch (err) {
const app = process.env.WRNEXUS_APP_NAME ?? "app";
let detail =
err instanceof Error ? (err.stack ?? `${err.name}: ${err.message}`) : String(err);
const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
// AggregateError (e.g. Bun.build() "Bundle failed") hides its real cause in
// `.errors` — the top-level message/stack alone is useless for diagnosing a
// failed bundle. Print every nested error so the actual failure is visible.
const nested = (err as { errors?: unknown[] } | undefined)?.errors;
if (Array.isArray(nested) && nested.length) {
detail +=
"\n caused by:\n" +
nested
.map((e, i) => ` [${i}] ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`)
.join("\n");
}
console.error(
`[wrnexus] unhandled request error (${app}) ${req.method} ${url.pathname}\n${detail}`,
);
deps.devToolbar?.collector.add(
issueFromError(err, {
ruleId: "server/request-error",
category: "server",
title: "Request processing failed",
pathname: url.pathname,
}),
);
const response = secure(renderError(err, mode));
// Gateway-managed production apps bind to loopback. Carry a bounded,
// encoded diagnostic to the parent gateway so centralized log collectors
// can explain child failures; the gateway always strips this header.
response.headers.set("x-wrnexus-internal-error", encodeURIComponent(message.slice(0, 500)));
return compressResponse(req, response);
}
}
async function dispatch(ctx: Context): Promise<Response> {
const { pathname } = ctx.url;
if (pathname === "/__wrnexus/cache") {
if (mode !== "development") return new Response("Not Found", { status: 404 });
return Response.json(cache.inspect(), { headers: { "cache-control": "no-store" } });
}
// Framework-owned assets (island chunks, reactive runtime, HMR stream).
if (pathname === "/__wrnexus/csr") {
return handleCsrBinding(ctx);
}
if (pathname === "/__wrnexus/client-load") {
return handleClientLoad(ctx);
}
if (pathname.startsWith("/__wrnexus/")) {
const res = await assets.serve(pathname);
return res ?? new Response("Not Found", { status: 404 });
}
if (pathname === "/api" || pathname.startsWith("/api/")) {
return handleApi(ctx);
}
const publicAsset = await assets.serve(pathname);
if (publicAsset) return publicAsset;
return handlePage(ctx);
}
async function handleCsrBinding(ctx: Context): Promise<Response> {
const routePath = ctx.url.searchParams.get("route") ?? "";
const id = ctx.url.searchParams.get("id") ?? "";
if (
!routePath.startsWith("/") ||
routePath.startsWith("/__wrnexus/") ||
!isSafeRequestPath(routePath) ||
!/^[A-Za-z0-9_-]+$/.test(id)
) {
return new Response("Not Found", { status: 404 });
}
const page = router.matchPage(routePath);
if (!page) return new Response("Not Found", { status: 404 });
const pageModule = await loadModule(page.route.file);
const bindings = pageModule.__wrnexusCsr;
if (!Array.isArray(bindings)) return new Response("Not Found", { status: 404 });
const binding = bindings.find((item): item is CsrBinding => {
if (!item || typeof item !== "object") return false;
const candidate = item as Partial<CsrBinding>;
return (
candidate.id === id &&
typeof candidate.path === "string" &&
(candidate.method === undefined || typeof candidate.method === "string") &&
(typeof candidate.body === "string" || typeof candidate.expr === "string") &&
(candidate.helpers === undefined || typeof candidate.helpers === "string")
);
});
if (!binding || !isSafeApiPath(binding.path)) return new Response("Not Found", { status: 404 });
const data = await callApiFromContext(ctx, binding.path, binding.method ?? "GET");
const value = evalData(ctx, data, binding.body ?? binding.expr ?? "", binding.helpers ?? "");
return new Response(String(value), {
headers: { "content-type": "text/plain; charset=utf-8" },
});
}
async function handleClientLoad(ctx: Context): Promise<Response> {
const routePath = ctx.url.searchParams.get("route") ?? "";
const routeSearch = ctx.url.searchParams.get("search") ?? "";
const name = ctx.url.searchParams.get("name") ?? "";
if (
!routePath.startsWith("/") ||
routePath.startsWith("/__wrnexus/") ||
!isSafeRequestPath(routePath) ||
(routeSearch !== "" &&
(!routeSearch.startsWith("?") || routeSearch.includes("#") || routeSearch.length > 4096)) ||
!/^[A-Za-z_$][\w$]{0,63}$/.test(name)
) {
return new Response("Not Found", { status: 404 });
}
const page = router.matchPage(routePath);
if (!page) return new Response("Not Found", { status: 404 });
const pageModule = await loadModule(page.route.file);
const load = pageModule.__wrnexusClientLoad;
if (typeof load !== "function") return new Response("Not Found", { status: 404 });
try {
const routeUrl = new URL(routePath + routeSearch, ctx.url.origin);
if (routeUrl.pathname !== routePath) return new Response("Not Found", { status: 404 });
const routeRequest = new Request(routeUrl, { method: "GET", headers: ctx.req.headers });
const loadCtx: Context = { ...ctx, req: routeRequest, url: routeUrl, params: page.params };
const values = await load(loadCtx);
if (!values || typeof values !== "object" || !(name in values)) {
return new Response("Not Found", { status: 404 });
}
return Response.json(
{ data: (values as Record<string, unknown>)[name] },
{ headers: { "cache-control": "private, no-store" } },
);
} catch {
return Response.json(
{ error: { code: "CLIENT_LOAD_FAILED", message: "Client data loading failed." } },
{ status: 500, headers: { "cache-control": "private, no-store" } },
);
}
}
async function callApiFromContext(ctx: Context, path: string, method = "GET"): Promise<unknown> {
if (!isSafeApiPath(path)) {
throw new Error("Unsafe framework API path");
}
const normalizedMethod = method.toUpperCase();
if (!HTTP_METHODS.includes(normalizedMethod as (typeof HTTP_METHODS)[number])) {
throw new Error(`Unsupported framework API method: ${normalizedMethod}`);
}
const apiUrl = new URL(path, ctx.req.url);
const apiReq = new Request(apiUrl, {
method: normalizedMethod,
headers: ctx.req.headers,
});
const apiCtx = createContext(apiReq, apiUrl);
apiCtx.locals = ctx.locals;
apiCtx.cookies = ctx.cookies;
apiCtx.session = ctx.session;
apiCtx.localStorage = ctx.localStorage;
const apiRes = await handleApi(apiCtx);
if (!apiRes.ok) {
throw new Error(`API route ${apiUrl.pathname} returned ${apiRes.status}`);
}
const contentType = apiRes.headers.get("content-type") ?? "";
return contentType.includes("application/json") ? await apiRes.json() : await apiRes.text();
}
function isSafeApiPath(pathname: string): boolean {
return (
(pathname === "/api" || pathname.startsWith("/api/")) &&
isSafeRequestPath(pathname) &&
!pathname.includes("?") &&
!pathname.includes("#")
);
}
function evalData(ctx: Context, data: unknown, body: string, helpers = ""): unknown {
const adapters = {
cookies: ctx.cookies,
session: ctx.session,
localStorage: ctx.localStorage,
};
return new Function(
"$data",
"$adapters",
"const cookies = $adapters.cookies;\nconst session = $adapters.session;\nconst localStorage = $adapters.localStorage;\nwith ($data ?? {}) {\n" +
helpers +
"\n" +
body +
"\n}",
)(data, adapters);
}
async function handleApi(ctx: Context): Promise<Response> {
const matched = router.matchApi(ctx.url.pathname);
if (!matched) return Response.json({ error: "Not Found" }, { status: 404 });
// Expose the canonical matched route to package dispatchers. A package may
// contribute several URL paths from one module, and request URLs can be
// rewritten by gateways or internal framework calls. The router match is
// the authoritative route identity.
ctx.params = matched.params;
ctx.locals.__wrnexusRoute = matched.route.raw;
ctx.locals.__wrnexusRouteKind = "api";
const mod = await loadModule(matched.route.file);
const method = ctx.req.method.toUpperCase();
const embeddedApi = mod.__wrnexusApi as ApiRegistry | undefined;
const handler = embeddedApi?.[`${method} ${matched.route.raw}`] ?? mod[method];
if (typeof handler !== "function") {
const allowed = HTTP_METHODS.filter(
(m) =>
typeof mod[m] === "function" ||
typeof embeddedApi?.[`${m} ${matched.route.raw}`] === "function",
);
return new Response("Method Not Allowed", {
status: 405,
headers: { Allow: allowed.join(", ") },
});
}
return (await handler(ctx)) as Response;
}
/**
* Resolve `<tag data-component="name" ...props></tag>` mounts by rendering the
* matching `.wrn` component on the server and splicing its HTML in place.
* Runs before script collection so the injected `data-scope` is visible.
* Recurses so components can mount other components.
*/
async function renderComponents(
body: string,
translate: TFunction = (key) => key,
language?: string,
depth = 0,
): Promise<string> {
if (depth > 15 || router.components.length === 0 || !body.includes("data-component=")) {
return body;
}
let result = "";
let i = 0;
for (;;) {
const m = MOUNT_OPEN_RE.exec(body.slice(i));
if (!m) {
result += body.slice(i);
break;
}
const tagStart = i + m.index;
result += body.slice(i, tagStart);
const [open, tag, attrStr, name, selfClose] = m;
const openEnd = tagStart + open.length;
const { inner, end } =
selfClose === "/" ? { inner: "", end: openEnd } : readElementBody(body, tag!, openEnd);
i = end;
const normalizedName = normalizeComponentName(name);
const component = router.components.find(
(candidate) => normalizeComponentName(candidate.name) === normalizedName,
);
if (!component) {
console.warn(`[wrnexus] no component registered for '${name}'`);
result += body.slice(tagStart, end);
continue;
}
try {
const mod = await loadModule(component.file);
const render = (mod as { render?: (props: Record<string, string>) => string }).render;
if (typeof render !== "function") {
console.warn(`[wrnexus] component '${name}' has no render() export`);
result += body.slice(tagStart, end);
continue;
}
// Render, drop the mount's children into the component's <slot>, then
// recurse so components-in-the-view and components-in-the-children resolve.
// Props may carry `{t:key}` i18n markers — resolve them for the active
// language before handing them to the component.
const props = resolveTProps(parseComponentProps(attrStr!), translate);
if (normalizedName === "languageswitcher" && deps.i18n) {
props.locales ??= JSON.stringify(
deps.i18n.langs.map((locale) => ({
value: locale,
label: deps.i18n?.labels[locale] ?? locale.toUpperCase(),
shortLabel: locale.split("-")[0]!.toUpperCase(),
})),
);
props.current ??=
language && deps.i18n.langs.includes(language) ? language : deps.i18n.default;
}
const policy = (mod.__wrnexusCache ?? {}) as Record<string, string>;
const strategy = policy.strategy?.toLowerCase();
const renderComponent = () => fillSlots(String(render(props)), inner, true);
const rendered =
strategy && !["none", "no-store", "request"].includes(strategy)
? await cache.getOrLoad(
"component",
`${normalizedName}:${language}:${JSON.stringify(props)}:${inner}`,
renderComponent,
{
ttlMs: cacheDuration(policy.ttl, 60_000),
staleWhileRevalidateMs:
strategy === "stale-while-revalidate"
? cacheDuration(policy.stale ?? policy.ttl, 60_000)
: 0,
tags: cacheList(policy.tags),
},
)
: renderComponent();
result += await renderComponents(rendered, translate, language, depth + 1);
} catch (err) {
console.error(`[wrnexus] component '${name}' failed to render`, err);
deps.devToolbar?.collector.add(
issueFromError(err, {
ruleId: "server/component-render-error",
category: "server",
title: `Component '${name}' failed to render`,
source: { file: component.file, component: name },
}),
);
result += body.slice(tagStart, end);
}
}
return result;
}
async function handlePage(ctx: Context): Promise<Response> {
// Page rendering is also reached by HMR synchronization and internal
// dispatches, so never rely exclusively on the public fetch initializer.
initializeRequestCache(ctx);
const isMobileRequest =
ctx.req.headers.get("x-wrnexus-mobile") === "1" ||
new RegExp(deps.mobile?.userAgent ?? "WrNexusMobile", "i").test(
ctx.req.headers.get("user-agent") ?? "",
);
const matched = router.matchPage(ctx.url.pathname);
if (!matched) {
if (deps.devToolbar && shouldReportNotFound(ctx.url.pathname)) {
deps.devToolbar.collector.add(
createServerIssue({
ruleId: "routing/not-found",
category: "routing",
severity: "warning",
title: "Route not found",
message: `No page route matched ${ctx.url.pathname}.`,
pathname: ctx.url.pathname,
recommendation:
"Verify the URL, page filename, dynamic route parameters, and route casing.",
}),
);
}
const response = renderNotFound();
if (!isMobileRequest) return response;
const headers = new Headers(response.headers);
headers.set("x-wrnexus-original-status", "404");
// Android WebView may replace a main-frame HTTP error with its own error
// surface. Keep the WrNexus 404 document renderable inside Capacitor.
return new Response(response.body, { status: 200, headers });
}
// Issue the CSRF token cookie so forms on this page can echo it back.
const pageCsrf = csrfToken(ctx);
const storeContainer = requestStoreContainer(ctx.req, matched.route.raw);
const mod = await loadModule(matched.route.file);
if (ctx.req.method.toUpperCase() === "POST") {
const actionResponse = await handlePageAction(ctx, mod);
if (actionResponse) return actionResponse;
}
const component = mod.default;
if (typeof component !== "function") {
throw new Error(`Page ${matched.route.file} has no default export`);
}
ctx.params = matched.params;
const meta = (mod.meta ?? {}) as PageMeta;
const pageNavigation = (mod.__wrnexusNavigation ?? {}) as { preserve?: string };
const pageCache = (mod.__wrnexusCache ?? {}) as Record<string, string>;
const fullPageEnabled = ["page", "full-page"].includes(pageCache.scope?.toLowerCase() ?? "");
const fullPageKey = `page:${matched.route.raw}:${ctx.url.search}:${cacheIdentity(ctx, [
"language",
`cookie:${THEME_COOKIE}`,
`cookie:${ACCENT_COOKIE}`,
...cacheList(pageCache.vary),
])}`;
if (fullPageEnabled && ["GET", "HEAD"].includes(ctx.req.method.toUpperCase())) {
const pageHit = cache.page.lookup(fullPageKey);
if (pageHit.state === "fresh") {
const cached = pageHit.entry.value as { html: string; etag: string; nonce: string };
const restoredHtml = cached.nonce
? cached.html.replaceAll(
`nonce="${cached.nonce}"`,
`nonce="${String(ctx.locals.cspNonce ?? "")}"`,
)
: cached.html;
await disposeRequestStores(ctx.req);
return new Response(ctx.req.method.toUpperCase() === "HEAD" ? null : restoredHtml, {
headers: {
"content-type": "text/html; charset=utf-8",
etag: cached.etag,
"cache-control": "public, max-age=0, must-revalidate",
"x-wrnexus-page-cache": "HIT",
},
});
}
}
let dataCacheState: "HIT" | "STALE" | "MISS" | "BYPASS" = "BYPASS";
const preserve = (pageNavigation.preserve ?? "")
.match(/(?:scroll|forms|tabs|expanded|filters|pagination|component|workflow)/g)
?.filter((value, index, values) => values.indexOf(value) === index)
.join(",");
const pageCtx = ctx as Context & {
__wrnexusCallApi?: (path: string, method?: string) => Promise<unknown>;
__wrnexusUseStore?: (definition: StoreDefinition<any, any, any>) => Promise<unknown>;
};
pageCtx.__wrnexusCallApi = (path, method = "GET") => callApiFromContext(ctx, path, method);
pageCtx.__wrnexusUseStore = (definition) => storeContainer.use(definition);
const load = mod.__wrnexusLoad as ((ctx: Context) => Promise<unknown>) | undefined;
if (typeof load === "function") {
const strategy = pageCache.strategy?.toLowerCase();
const cacheEnabled = Boolean(strategy && !["none", "no-store", "request"].includes(strategy));
let data: unknown;
if (cacheEnabled && ["GET", "HEAD"].includes(ctx.req.method.toUpperCase())) {
const key = `route:${matched.route.raw}:${ctx.url.search}:${cacheIdentity(ctx, cacheList(pageCache.vary))}`;
const lookup = cache.data.lookup(key);
dataCacheState =
lookup.state === "fresh" ? "HIT" : lookup.state === "stale" ? "STALE" : "MISS";
data = await cache.getOrLoad("data", key, () => load(pageCtx), {
ttlMs: cacheDuration(pageCache.ttl, 60_000),
staleWhileRevalidateMs:
strategy === "stale-while-revalidate"
? cacheDuration(pageCache.stale ?? pageCache.ttl, 60_000)
: 0,
tags: cacheList(pageCache.tags),
});
} else {
data = await (
ctx.locals.requestCache as {
getOrLoad<V>(key: string, loader: () => Promise<V>): Promise<V>;
}
).getOrLoad(`route:${matched.route.raw}`, () => load(pageCtx));
}
(pageCtx as Context & { data?: unknown }).data = data;
if (data && typeof data === "object") Object.assign(pageCtx, data);
}
let body = await renderComponents(String(await component(pageCtx)), ctx.t, ctx.lang);
const partial = (mod as { __wrnexusRender?: string }).__wrnexusRender === "partial-static";
const precomputedShell = (mod as { __wrnexusStaticShell?: unknown }).__wrnexusStaticShell;
const pagePartial = partial ? partialPrerender(body) : undefined;
if (partial) {
body = typeof precomputedShell === "string" ? precomputedShell : (pagePartial?.shell ?? body);
}
const routeParamsMarker = `<span hidden data-wrn-route-params="${escapeHtml(
JSON.stringify(ctx.params ?? {}),
)}"></span>`;
body = `${routeParamsMarker}${body}`;
if (body.includes("data-wrn-action=")) {
body = body.replace(
/(<form\b[^>]*\bdata-wrn-action=(?:"[^"]+"|'[^']+')[^>]*>)/gi,
`$1<input type="hidden" name="_csrf" value="${pageCsrf}">`,
);
}
const resolvedTheme = deps.theme
? resolveThemeName(ctx.cookies.get(THEME_COOKIE), deps.theme)
: "";
const language = deps.i18n && ctx.lang ? ctx.lang : (meta.lang ?? deps.seo?.lang ?? "en");
let documentTemplate: string | undefined;
// Page layout: a page selects one by exporting `layout = "<name>"`
// (app/layouts/<name>.wrn), else falls back to a `default` layout if one
// exists. `layout = "none"` opts out. The layout wraps the body via <slot>.
const importedLayout =
mod.layout && typeof mod.layout === "object"
? (mod.layout as { name?: string; render?: (props: Record<string, unknown>) => string })
: undefined;
const layoutName =
(isMobileRequest && deps.mobile?.layout
? deps.mobile.layout
: typeof mod.layout === "string"
? mod.layout
: importedLayout?.name) ?? "default";
const layout = importedLayout
? undefined
: layoutName === "none"
? undefined
: router.layouts.find((l) => l.name === layoutName);
if (importedLayout?.render) {
body = await renderComponents(
fillSlots(String(importedLayout.render({})), body),
ctx.t,
ctx.lang,
);
} else if (layout) {
try {
const layoutMod = await loadModule(layout.file);
const layoutRender = (layoutMod as { render?: (p: Record<string, string>) => string })
.render;
if (typeof layoutRender === "function") {
body = await renderComponents(fillSlots(String(layoutRender({})), body), ctx.t, ctx.lang);
}
} catch (err) {
console.error(`[wrnexus] layout '${layoutName}' failed to render`, err);
deps.devToolbar?.collector.add(
issueFromError(err, {
ruleId: "server/layout-render-error",
category: "server",
title: `Layout '${layoutName}' failed to render`,
pathname: ctx.url.pathname,
source: { file: layout.file },
}),
);
}
}
// A conventional app/layouts/document.wrn is a request-aware, top-level
// document shell. It wraps the selected page layout and may own html, head,
// body, and #app. The SSR renderer still merges framework metadata/assets.
const documentLayout =
layoutName === "document"
? undefined
: router.layouts.find((item) => item.name === "document");
if (documentLayout) {
try {
const documentMod = await loadModule(documentLayout.file);
const documentRender = (
documentMod as { render?: (props: Record<string, unknown>) => string }
).render;
if (typeof documentRender === "function") {
const rendered = String(
documentRender({
theme: resolvedTheme,
language,
url: ctx.url.toString(),
pathname: ctx.url.pathname,
}),
);
documentTemplate = await renderComponents(fillSlots(rendered, body), ctx.t, ctx.lang);
body = documentTemplate;
}
} catch (err) {
console.error("[wrnexus] document layout failed to render", err);
deps.devToolbar?.collector.add(
issueFromError(err, {
ruleId: "server/document-layout-render-error",
category: "server",
title: "Document layout failed to render",
pathname: ctx.url.pathname,
source: { file: documentLayout.file },
}),
);
}
}
if (isMobileRequest) body = revealMobileOnlyHtml(body);
// i18n: resolve `{t:key}` / `t:attr` markers against the request language.
if (deps.i18n) {
body = translateHtml(body, ctx.t);
// renderDocument prefers the complete document template when one was
// rendered. Keep it in sync with the translated body; otherwise markers
// owned by document/page layouts are replaced by the stale pre-translation
// template even though page-only responses translate correctly.
if (documentTemplate) documentTemplate = body;
}
// Point 3: only ship the JS this page actually uses.
const scripts = collectScripts(body, deps.clientRuntimes, deps.navigation).map((script) =>
versionRenderScript(script, deps.assetVersion),
);
if (webVitalsEnabled) {
scripts.push(versionAssetUrl("/__wrnexus/vitals.js", deps.assetVersion));
}
if (pwaServiceWorkerEnabled)
scripts.push(versionAssetUrl("/__wrnexus/pwa.js", deps.assetVersion));
if (pwaDevCleanupEnabled) scripts.push("/__wrnexus/pwa-dev-cleanup.js");
if (deps.mobile?.enabled !== false && usesMobileRuntime(body))
scripts.push(versionAssetUrl("/__wrnexus/mobile.js", deps.assetVersion));
// <html> attributes: no-flash theme (from cookie, validated) + active lang.
const attrs: string[] = [];
let activeThemeHead = "";
if (deps.theme) {
const themeName = resolveThemeName(ctx.cookies.get(THEME_COOKIE), deps.theme);
attrs.push(`data-theme="${themeName}"`);
const accentName = resolveAccentName(ctx.cookies.get(ACCENT_COOKIE), deps.theme);
if (accentName) {
attrs.push(`data-accent="${accentName}"`);
}
if (!deps.stylesIncludeFramework) {
const themeHref = versionAssetUrl(
activeThemeCssHref(themeName, accentName),
deps.assetVersion,
);
activeThemeHead = `<link rel="stylesheet" data-wrnexus-theme href="${themeHref}" />`;
}
}
attrs.push(`lang="${safeLanguageTag(language)}"`);
if (deps.i18n) attrs.push(`dir="${deps.i18n.direction[language] ?? "ltr"}"`);
const htmlAttrs = attrs.length ? ` ${attrs.join(" ")}` : undefined;
const outerPartial = partial
? partialPrerender(body, pagePartial?.regions.length ?? 0)
: undefined;
const partialRegions = [...(pagePartial?.regions ?? []), ...(outerPartial?.regions ?? [])];
const renderedBody = outerPartial?.shell ?? body;
if (partial && documentTemplate) documentTemplate = renderedBody;
let html = renderDocument({
meta,
seo: deps.seo,
url: ctx.url,
body: renderedBody,
scripts,
extraHead: [
preserve ? `<meta name="wrnexus-preserve" content="${preserve}" />` : "",
pwaEnabled ? `<link rel="manifest" href="/site.webmanifest" />` : "",
pwaEnabled ? `<meta name="mobile-web-app-capable" content="yes" />` : "",
pwaEnabled ? `<meta name="apple-mobile-web-app-capable" content="yes" />` : "",
activeThemeHead,
extraHead,
]
.filter(Boolean)
.join("\n "),
extraBody:
[
renderStoreHydration(storeContainer, (ctx.locals.cspNonce as string) ?? undefined),
deps.i18n
? `<script${ctx.locals.cspNonce ? ` nonce="${String(ctx.locals.cspNonce)}"` : ""}>${renderI18nData(deps.i18n, language)}</script>`
: "",
hmr ? hmrClientTag((ctx.locals.cspNonce as string) ?? "") : "",
shouldEnableDevToolbar(mode, deps) ? DEV_TOOLBAR_SCRIPT : "",
]
.filter(Boolean)
.join("\n") || undefined,
htmlAttrs,
documentTemplate,
styleNonce: (ctx.locals.cspNonce as string | undefined) ?? undefined,
});
if (deps.renderHtml) html = await deps.renderHtml(html);
// Conditional GET: hash the page CONTENT (`body`), not the assembled shell —
// the shell carries a per-request CSP nonce in dev, which would otherwise make
// the ETag change every request. Same content → same ETag → 304 on revalidate.
const tag = etag(`${htmlAttrs ?? ""}\n${JSON.stringify(scripts)}\n${body}`);
if (
fullPageEnabled &&
["GET", "HEAD"].includes(ctx.req.method.toUpperCase()) &&
!/name=["']_csrf["']/.test(html)
) {
cache.page.set(
fullPageKey,
{ html, etag: tag, nonce: String(ctx.locals.cspNonce ?? "") },
{
ttlMs: cacheDuration(pageCache.ttl, 60_000),
staleWhileRevalidateMs:
pageCache.strategy?.toLowerCase() === "stale-while-revalidate"
? cacheDuration(pageCache.stale ?? pageCache.ttl, 60_000)
: 0,
tags: cacheList(pageCache.tags),
},
);
}
await disposeRequestStores(ctx.req);
const method = ctx.req.method.toUpperCase();
if ((method === "GET" || method === "HEAD") && notModified(ctx.req, tag)) {
return new Response(null, {
status: 304,
headers: { etag: tag, "cache-control": "private, no-cache" },
});
}
return new Response(
partial && method !== "HEAD"
? streamPartialDocument(
{ shell: html, regions: partialRegions },
(ctx.locals.cspNonce as string | undefined) ?? undefined,
)
: html,
{
headers: {
"content-type": "text/html; charset=utf-8",
etag: tag,
"cache-control": "private, no-cache",
"x-wrnexus-data-cache": dataCacheState,
...(partial ? { "x-wrnexus-render": "partial-static" } : {}),
...(partial && typeof precomputedShell === "string"
? { "x-wrnexus-static-shell": "build" }
: {}),
...(fullPageEnabled ? { "x-wrnexus-page-cache": "MISS" } : {}),
...(shouldEnableDevToolbar(mode, deps)
? {
"x-wrnexus-dev-toolbar": "enabled",
"x-wrnexus-route": matched.route.raw,
}
: {}),
},
},
);
}
async function actionInput(request: Request): Promise<{ name?: string; input: unknown }> {
const contentType = request.headers.get("content-type") ?? "";
if (contentType.includes("application/json")) {
return {
name: request.headers.get("x-wrnexus-action") ?? undefined,
input: await request.json(),
};
}
const form = await request.formData();
const input: Record<string, unknown> = {};
for (const [key, value] of form) {
if (key === "_wrnexus_action" || key === "_csrf") continue;
if (!(key in input)) input[key] = value;
else
input[key] = Array.isArray(input[key])
? [...(input[key] as unknown[]), value]
: [input[key], value];
}
const submittedName = form.get("_wrnexus_action");
return {
name:
request.headers.get("x-wrnexus-action") ??
(typeof submittedName === "string" ? submittedName : undefined),
input,
};
}
async function handlePageAction(ctx: Context, mod: RouteModule): Promise<Response | null> {
const actions = mod.__wrnexusActions as Record<string, ActionEntry> | undefined;
if (!actions) return null;
let submitted: Awaited<ReturnType<typeof actionInput>>;
try {
submitted = await actionInput(ctx.req);
} catch {
return Response.json({ error: "Malformed action input" }, { status: 400 });
}
if (!submitted.name) return null;
if (!/^[A-Za-z_$][\w$]*$/.test(submitted.name) || !actions[submitted.name]) {
return Response.json({ error: "Unknown server action" }, { status: 404 });
}
const security = (mod.__wrnexusSecurity ?? {}) as Record<string, string>;
if (/^(?:required|true)$/i.test(security.auth ?? "") && !ctx.user) {
return Response.json({ error: "Authentication required" }, { status: 401 });
}
if (security.permission) {
const permissions = ctx.locals.permissions;
const allowed =
typeof permissions === "function"
? await permissions(security.permission, ctx)
: Array.isArray(permissions) && permissions.includes(security.permission);
if (!allowed) return Response.json({ error: "Permission denied" }, { status: 403 });
}
if (security.csrf !== "false" && !verifyCsrf(ctx)) {
return Response.json({ error: "Invalid CSRF token" }, { status: 403 });
}
const action = actions[submitted.name]!;
let input = submitted.input;
if (action.schema) {
const parsed = action.schema.parse(input);
if (!parsed.ok) {
const acceptsJson = (ctx.req.headers.get("accept") ?? "").includes("application/json");
if (acceptsJson)
return Response.json(
{ error: "Validation failed", errors: parsed.errors },
{ status: 422 },
);
const errors = Object.entries(parsed.errors)
.map(
([field, message]) =>
`<li><strong>${escapeHtml(field)}</strong>: ${escapeHtml(message)}</li>`,
)
.join("");
return new Response(
`<!doctype html><title>Validation failed</title><h1>Validation failed</h1><ul>${errors}</ul><a href="${escapeHtml(ctx.url.pathname)}">Go back</a>`,
{
status: 422,
headers: { "content-type": "text/html; charset=utf-8" },
},
);
}
input = parsed.value;
}
const data = await action.run(input, ctx);
const invalidated = [
...new Set(
Array.isArray(ctx.locals.__wrnexusInvalidatedTags)
? (ctx.locals.__wrnexusInvalidatedTags as string[])
: [],
),
];
if (invalidated.length) cache.invalidateTags(invalidated);
if ((ctx.req.headers.get("accept") ?? "").includes("application/json")) {
return Response.json({ ok: true, data, invalidated });
}
return new Response(null, {
status: 303,
headers: { location: ctx.url.pathname + ctx.url.search },
});
}
const hub = deps.hub;
async function handleHmrMessage(ws: Ws, message: string | Uint8Array): Promise<void> {
let msg: { type?: unknown; path?: unknown };
try {
msg = JSON.parse(typeof message === "string" ? message : new TextDecoder().decode(message));
} catch {
return;
}
if (msg.type !== "sync") return;
const path = typeof msg.path === "string" ? msg.path : "/";
let url: URL;
try {
url = new URL(path, ws.data.kind === "hmr" ? ws.data.baseUrl : "http://localhost");
} catch {
ws.send(JSON.stringify({ type: "error", message: "Invalid HMR sync path" }));
return;
}
if (
url.origin !== (ws.data.kind === "hmr" ? ws.data.baseUrl : url.origin) ||
!url.pathname.startsWith("/") ||
url.pathname.startsWith("/__wrnexus/") ||
!isSafeRequestPath(url.pathname)
) {
ws.send(JSON.stringify({ type: "error", message: "Unsafe HMR sync path" }));
return;
}
try {
const headers = new Headers(ws.data.kind === "hmr" ? ws.data.headers : undefined);
headers.set("accept", "text/html");
headers.set("x-wrnexus-hmr", "1");
const req = new Request(url, { headers });
const ctx = createContext(req, url);
initializeRequestCache(ctx);
ctx.locals.cspNonce = randomNonce();
if (deps.i18n) {
ctx.lang = resolveLang(
deps.i18n,
ctx.cookies.get(deps.i18n.cookie.name),
req.headers.get("accept-language"),
);
ctx.t = makeT(deps.i18n, ctx.lang);
}
const res = withContextHeaders(
ctx,
await runMiddleware(await resolveMiddleware(), ctx, () => dispatch(ctx)),
);
const html = await res.text();
ws.send(JSON.stringify({ type: "html", html }));
} catch (err) {
if (mode === "development") console.error(err);
ws.send(
JSON.stringify({
type: "error",
message: err instanceof Error ? err.message : "Unknown HMR sync error",
}),
);
}
}
return {
fetch: fetchHandler,
websocket: {
open(ws) {
if (ws.data?.kind === "hmr") hub?.add(ws);
else if (ws.data?.kind === "room") void realtime.open(ws, ws.data.meta);
else ws.data?.handler?.open?.(ws);
},
message(ws, message) {
if (ws.data?.kind === "hmr") {
void handleHmrMessage(ws, message);
return;
}
if (ws.data?.kind === "room") void realtime.message(ws, message);
else ws.data?.handler?.message?.(ws, message);
},
close(ws, code, reason) {
if (ws.data?.kind === "hmr") hub?.remove(ws);
else if (ws.data?.kind === "room") void realtime.close(ws);
else ws.data?.handler?.close?.(ws, code, reason);
},
drain(ws) {
if (ws.data?.kind === "hmr" || ws.data?.kind === "room") return;
ws.data?.handler?.drain?.(ws);
},
},
};
}
/**
* Decode the handful of HTML entities the page compiler emits when escaping
* attribute values, so component props arrive as the author wrote them.
*/
function decodeHtmlEntities(value: string): string {
let decoded = value;
// Static page attributes can be escaped by their author/generator and then
// escaped again by the page compiler. Decode both layers so structured
// component props arrive as valid JSON instead of `&quot;` text.
for (let pass = 0; pass < 2; pass += 1) {
const next = decoded
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&amp;/g, "&");
if (next === decoded) break;
decoded = next;
}
return decoded;
}
/** Content types worth gzipping (text + text-like application types). */
const COMPRESSIBLE_TYPE =
/^(?:text\/|application\/(?:json|xml|javascript|manifest\+json)|image\/svg\+xml)/i;
/** Below this size gzip's overhead isn't worth it. */
const COMPRESS_MIN_BYTES = 1024;
const COMPRESSED_RESPONSE_CACHE_MAX = 256;
const compressedResponseCache = new Map<
string,
{ body: Uint8Array; headers: [string, string][]; status: number; statusText: string }
>();
function rememberCompressedResponse(
key: string,
value: { body: Uint8Array; headers: [string, string][]; status: number; statusText: string },
) {
compressedResponseCache.delete(key);
compressedResponseCache.set(key, value);
if (compressedResponseCache.size > COMPRESSED_RESPONSE_CACHE_MAX) {
const oldest = compressedResponseCache.keys().next().value;
if (oldest) compressedResponseCache.delete(oldest);
}
}
/**
* Gzip a response when the client accepts it and the body is a compressible,
* buffered (non-streaming) payload. Streaming/SSE responses opt out via
* `Cache-Control: no-transform`, so they are never buffered here.
*/
async function compressResponse(req: Request, res: Response): Promise<Response> {
if (req.method.toUpperCase() === "HEAD") return res;
const accept = (req.headers.get("accept-encoding") ?? "").toLowerCase();
const acceptsBrotli = /(?:^|,)\s*br(?:\s*;|\s*,|$)/.test(accept);
const acceptsGzip = /(?:^|,)\s*gzip(?:\s*;|\s*,|$)/.test(accept);
if (!acceptsBrotli && !acceptsGzip) return res;
if (res.headers.get("content-encoding")) return res;
if (res.status === 204 || res.status === 304) return res;
if (!COMPRESSIBLE_TYPE.test(res.headers.get("content-type") ?? "")) return res;
if ((res.headers.get("cache-control") ?? "").includes("no-transform")) return res;
const immutable = (res.headers.get("cache-control") ?? "").includes("immutable");
// Brotli is ideal for immutable assets because the result is cached. Prefer
// substantially faster gzip for per-request HTML/JSON when the client allows
// both, avoiding synchronous Brotli work on the request hot path.
const preferredEncoding = acceptsBrotli && (immutable || !acceptsGzip) ? "br" : "gzip";
const cacheKey = immutable ? `${req.url}\n${preferredEncoding}` : "";
if (cacheKey) {
const cached = compressedResponseCache.get(cacheKey);
if (cached) {
compressedResponseCache.delete(cacheKey);
compressedResponseCache.set(cacheKey, cached);
return new Response(cached.body.slice(), {
status: cached.status,
statusText: cached.statusText,
headers: cached.headers,
});
}
}
const body = new Uint8Array(await res.arrayBuffer());
if (body.length < COMPRESS_MIN_BYTES) {
return new Response(body, {
status: res.status,
statusText: res.statusText,
headers: res.headers,
});
}
let encoded: Uint8Array;
let encoding: "br" | "gzip";
if (preferredEncoding === "br") {
encoded = new Uint8Array(
brotliCompressSync(body, {
params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 4 },
}),
);
encoding = "br";
} else {
encoded = Bun.gzipSync(body);
encoding = "gzip";
}
const headers = new Headers(res.headers);
headers.set("content-encoding", encoding);
headers.set("content-length", String(encoded.length));
const vary = headers.get("Vary");
if (!vary) headers.set("Vary", "Accept-Encoding");
else if (!/\baccept-encoding\b/i.test(vary)) headers.set("Vary", `${vary}, Accept-Encoding`);
const responseBody = new ArrayBuffer(encoded.byteLength);
new Uint8Array(responseBody).set(encoded);
if (cacheKey) {
rememberCompressedResponse(cacheKey, {
body: encoded.slice(),
headers: [...headers.entries()],
status: res.status,
statusText: res.statusText,
});
}
return new Response(responseBody, {
status: res.status,
statusText: res.statusText,
headers,
});
}
/** Opening tag of a component mount: captures tag, attrs, name, self-close. */
const MOUNT_OPEN_RE =
/<([A-Za-z][A-Za-z0-9-]*)\b([^>]*?\bdata-component="([A-Za-z0-9_-]+)"[^>]*?)(\/?)>/;
/**
* From `from` (just past a mount's opening `>`), return the element's inner HTML
* and the index just past its matching close tag. Counts same-tag nesting so a
* `<div data-component>` may contain other `<div>`s.
*/
/** Cache of per-tag "open tag" regexes so we don't recompile one per call. */
const OPEN_TAG_RE = new Map<string, RegExp>();
function openTagRe(tag: string): RegExp {
let re = OPEN_TAG_RE.get(tag);
if (!re) {
re = new RegExp(`<${tag}\\b`, "g");
OPEN_TAG_RE.set(tag, re);
}
return re;
}
export function readElementBody(
html: string,
tag: string,
from: number,
): { inner: string; end: number } {
const openRe = openTagRe(tag);
const closeTag = `</${tag}>`;
let depth = 1;
let i = from;
while (i < html.length) {
const nextClose = html.indexOf(closeTag, i);
if (nextClose === -1) return { inner: html.slice(from), end: html.length };
openRe.lastIndex = i;
const openMatch = openRe.exec(html);
if (openMatch && openMatch.index < nextClose) {
const gt = html.indexOf(">", openMatch.index);
if (gt !== -1 && html[gt - 1] === "/") {
i = gt + 1; // self-closing child: no depth change
} else {
depth++;
i = gt === -1 ? html.length : gt + 1;
}
} else {
depth--;
if (depth === 0)
return { inner: html.slice(from, nextClose), end: nextClose + closeTag.length };
i = nextClose + closeTag.length;
}
}
return { inner: html.slice(from), end: html.length };
}
/**
* Split a mount's children into named slots and the default slot. Content inside
* `<tag data-slot="name">…</tag>` goes to that named slot (the wrapper element is
* dropped); everything else is the default slot.
*/
function extractSlots(inner: string): { named: Record<string, string>; def: string } {
const named: Record<string, string> = {};
if (!inner.includes("data-slot=")) return { named, def: inner };
const SLOT_MOUNT_RE =
/<([A-Za-z][A-Za-z0-9-]*)\b([^>]*?\bdata-slot="([A-Za-z0-9_-]+)"[^>]*?)(\/?)>/;
let def = "";
let i = 0;
for (;;) {
const m = SLOT_MOUNT_RE.exec(inner.slice(i));
if (!m) {
def += inner.slice(i);
break;
}
const tagStart = i + m.index;
def += inner.slice(i, tagStart);
const [open, tag, , name, selfClose] = m;
const openEnd = tagStart + open.length;
const { inner: content, end } =
selfClose === "/" ? { inner: "", end: openEnd } : readElementBody(inner, tag!, openEnd);
named[name!] = content;
i = end;
}
return { named, def };
}
/**
* Replace `<slot>` elements in a component's output with the mount's children:
* <slot name="x">…</slot> ← content from `data-slot="x"` on the mount
* <slot>…</slot> ← everything else (the default slot)
* A `<slot>fallback</slot>` keeps its fallback when nothing is provided.
*/
export function fillSlots(html: string, inner: string, markOwner = false): string {
if (!/<slot\b/.test(html)) return html;
const { named, def } = extractSlots(inner);
const defTrimmed = def.trim();
/*
* Slot content is authored by whoever wrote the component tag, but it is
* spliced *inside* the component's own [data-scope] root. Left unmarked the
* client runtime hands it to the component -- so `@input` handlers and
* `{state}` in slot content resolve against the component's scope, where
* the page's functions and state do not exist (and same-named component
* state silently shadows them).
*
* Wrapping it in <wrn-slot data-wrn-slot> gives the runtime an ownership
* boundary to walk back out of, so the markup is hydrated by the scope that
* actually wrote it. The wrapper is display: contents, so it adds an
* ownership marker without adding a box to the layout.
*
* Only component mounts pass markOwner. Layouts also go through fillSlots,
* but a layout wraps the whole page rather than being mounted inside a
* parent scope, so there is no outer scope to hand its content back to.
*/
const wrap = (content: string): string =>
markOwner && content.trim() ? `<wrn-slot data-wrn-slot="">${content}</wrn-slot>` : content;
return html
.replace(/<slot\b[^>]*?\bname="([A-Za-z0-9_-]+)"[^>]*?\/>/g, (_m, name: string) =>
wrap(named[name] ?? ""),
)
.replace(
/<slot\b[^>]*?\bname="([A-Za-z0-9_-]+)"[^>]*?>([\s\S]*?)<\/slot>/g,
(_m, name: string, fallback: string) =>
named[name] != null && named[name]!.trim() ? wrap(named[name]!) : fallback,
)
.replace(/<slot\s*\/>/g, defTrimmed ? wrap(def) : "")
.replace(/<slot\b[^>]*>([\s\S]*?)<\/slot>/g, (_m, fallback: string) =>
defTrimmed ? wrap(def) : fallback,
);
}
/**
* Extract props from a `data-component` mount's attribute string. Every quoted
* attribute other than `data-component` becomes a (string) prop; the component's
* declared defaults coerce them to their final types.
*/
export function parseComponentProps(attrStr: string): Record<string, string> {
const props: Record<string, string> = {};
for (const m of attrStr.matchAll(/([A-Za-z_][\w-]*)(?:="([^"]*)")?/g)) {
const key = m[1]!;
if (key === "data-component") continue;
props[key] = decodeHtmlEntities(m[2] ?? "");
}
return props;
}
/**
* Resolve `{t:key}` i18n markers inside component prop values against the active
* language, so authors can pass localized text into a component:
* `<div data-component="badge" label="{t:status.new}">`.
*/
export function resolveTProps(
props: Record<string, string>,
translate: TFunction,
): Record<string, string> {
for (const key of Object.keys(props)) {
const value = props[key]!;
if (value.includes("{t:")) {
props[key] = value.replace(/\{t:\s*([^}]+?)\s*\}/g, (_m, tkey: string) =>
translate(tkey.trim()),
);
}
}
return props;
}
/**
* Normalize component identifiers for registry lookup.
*
* WRN declarations use PascalCase (`PublicHeader`) while explicit
* `data-component` mounts commonly use kebab-case (`public-header`) or
* snake_case (`public_header`). All three forms identify the same component.
*/
export function normalizeComponentName(name: string): string {
return name.toLowerCase().replace(/[-_]/g, "");
}
function safeLanguageTag(value: string): string {
return /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(value) ? value : "en";
}
function cacheDuration(value: string | undefined, fallback: number): number {
if (!value) return fallback;
const match = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/i.exec(value.trim());
if (!match) return fallback;
const scale = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[
(match[2]?.toLowerCase() ?? "ms") as "ms" | "s" | "m" | "h" | "d"
];
return Math.max(0, Number(match[1]) * scale);
}
function cacheList(value: string | undefined): string[] {
if (!value) return [];
try {
const parsed = JSON.parse(value) as unknown;
if (Array.isArray(parsed))
return parsed.filter((item): item is string => typeof item === "string");
} catch {
// Fall through to a convenient comma-separated form.
}
return value
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}
function cacheIdentity(ctx: Context, vary: string[]): string {
const user = ctx.user as { id?: unknown } | undefined;
const values = new Map<string, string>();
if (ctx.tenant?.id) values.set("tenant", String(ctx.tenant.id));
if (user?.id !== undefined) values.set("user", String(user.id));
for (const item of vary) {
if (item === "tenant") values.set(item, String(ctx.tenant?.id ?? ""));
else if (item === "user") values.set(item, String(user?.id ?? ""));
else if (item === "language") values.set(item, ctx.lang);
else if (item.startsWith("cookie:")) values.set(item, ctx.cookies.get(item.slice(7)) ?? "");
else values.set(`header:${item}`, ctx.req.headers.get(item) ?? "");
}
return [...values.entries()]
.sort()
.map(([key, value]) => `${key}=${value}`)
.join("|");
}
function versionAssetUrl(src: string, version?: string): string {
if (!version) return src;
return `${src}${src.includes("?") ? "&" : "?"}v=${encodeURIComponent(version)}`;
}
function versionRenderScript(script: RenderScript, version?: string): RenderScript {
if (typeof script === "string") return versionAssetUrl(script, version);
return { ...script, src: versionAssetUrl(script.src, version) } satisfies ScriptAsset;
}
function escapeStyleContent(css: string): string {
return css.replace(/<\/style/gi, "<\\/style");
}