release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+482 -58
View File
@@ -14,6 +14,8 @@ import {
createCorsPreflightResponse,
createRealtimeRegistry,
csrfToken,
verifyCsrf,
escapeHtml,
etag,
isRoomDefinition,
isWebSocketOriginAllowed,
@@ -25,7 +27,7 @@ import {
withSecurityHeaders,
resolveRequestUrl,
tenantMiddleware,
tracingMiddleware,
HealthRegistry,
type Context,
type Middleware,
type Mode,
@@ -38,13 +40,23 @@ import {
} 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 { renderDocument, type RenderScript, type ScriptAsset } from "@wrnexus/ssr";
import {
partialPrerender,
renderDocument,
streamPartialDocument,
type RenderScript,
type ScriptAsset,
} from "@wrnexus/ssr";
import {
disposeRequestStores,
renderStoreHydration,
@@ -52,6 +64,8 @@ import {
} 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 { runtimeScriptsForMarkup } from "./plugin-assets.ts";
import {
ACCENT_COOKIE,
@@ -67,8 +81,8 @@ import {
type TenancyConfig,
} from "@wrnexus/styles";
import {
LANG_COOKIE,
I18N_JS_HREF,
renderI18nData,
makeT,
resolveLang,
translateHtml,
@@ -106,6 +120,12 @@ export type WsData =
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;
@@ -161,6 +181,8 @@ export interface RuntimeDeps {
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. */
@@ -173,13 +195,17 @@ export interface RuntimeDeps {
* 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[];
panels?: DevToolbarPanel[] | (() => DevToolbarPanel[] | Promise<DevToolbarPanel[]>);
};
}
@@ -230,24 +256,34 @@ function frameworkMiddleware(deps: RuntimeDeps): Middleware[] {
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(
tracingMiddleware(undefined, {
traceMiddleware({
serviceName: deps.observability.serviceName,
sampleRate: deps.observability.sampleRate,
serverTiming: deps.observability.serverTiming,
onComplete:
exporter: traceExporter,
onSpan:
deps.observability.exporter === "console"
? (ctx, records) => {
const total = records.find((record) => record.name === "http.request")?.durationMs;
? (span) => {
console.log(
`[wrnexus:trace] ${ctx.req.method} ${ctx.url.pathname} ${total?.toFixed(2) ?? "0.00"}ms`,
`[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 && deps.tenancy.mode !== "custom") {
if (deps.tenancy && Object.keys(deps.tenancy).length > 0 && deps.tenancy.mode !== "custom") {
middleware.push(
tenantMiddleware(tenantIdentityFromConfig(deps.tenancy), {
required: deps.tenancy.required,
@@ -277,30 +313,20 @@ export const PWA_CLIENT = `if ("serviceWorker" in navigator) {
addEventListener("load", () => navigator.serviceWorker.register(swUrl).catch(() => {}));
}`;
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 {
const offlineUrl = pwa.offlineUrl ?? pwa.startUrl ?? "/";
const cacheUrls = [...new Set([offlineUrl, ...(pwa.cacheUrls ?? [])])];
return `const CACHE = ${JSON.stringify(pwa.cacheName ?? "wrnexus-pwa-v1")};
const OFFLINE_URL = ${JSON.stringify(offlineUrl)};
const PRECACHE_URLS = ${JSON.stringify(cacheUrls)};
self.addEventListener("install", event => {
event.waitUntil(caches.open(CACHE).then(cache => cache.addAll(PRECACHE_URLS)).catch(() => {}));
self.skipWaiting();
});
self.addEventListener("activate", event => event.waitUntil(
caches.keys().then(keys => Promise.all(keys.filter(key => key.startsWith("wrnexus-pwa-") && key !== CACHE).map(key => caches.delete(key))))
.then(() => self.clients.claim())
));
self.addEventListener("fetch", event => {
if (event.request.method !== "GET" || event.request.mode !== "navigate") return;
event.respondWith(fetch(event.request).then(response => {
if (response.ok) {
const copy = response.clone();
caches.open(CACHE).then(cache => cache.put(event.request, copy));
}
return response;
}).catch(() => caches.match(event.request).then(hit => hit || caches.match(OFFLINE_URL))));
});`;
return generateServiceWorker(pwa);
}
const DEFAULT_PWA_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
@@ -825,6 +851,20 @@ export interface Handlers {
/** 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,
@@ -867,11 +907,15 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
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 = pwaEnabled && pwaConfig.serviceWorker !== false;
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 =
@@ -898,7 +942,10 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
// Health check — unauthenticated, skips the middleware pipeline.
if (url.pathname === "/healthz" || url.pathname === "/__wrnexus/health") {
return secure(Response.json({ status: "ok" }));
return secure(await livenessHandler(req));
}
if (url.pathname === "/readyz" || url.pathname === "/__wrnexus/ready") {
return secure(await readinessHandler(req));
}
if (webVitalsEnabled && url.pathname === webVitalsEndpoint) {
return secure(await webVitalsHandler(req));
@@ -995,6 +1042,16 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
}),
);
}
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, {
@@ -1081,13 +1138,26 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
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(LANG_COOKIE),
ctx.cookies.get(deps.i18n.cookie.name),
req.headers.get("accept-language"),
);
ctx.t = makeT(deps.i18n, ctx.lang);
@@ -1125,10 +1195,18 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
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);
@@ -1186,6 +1264,40 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
});
}
async function handleClientLoad(ctx: Context): Promise<Response> {
const routePath = ctx.url.searchParams.get("route") ?? "";
const name = ctx.url.searchParams.get("name") ?? "";
if (
!routePath.startsWith("/") ||
routePath.startsWith("/__wrnexus/") ||
!isSafeRequestPath(routePath) ||
!/^[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 {
ctx.params = page.params;
const values = await load(ctx);
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");
@@ -1283,6 +1395,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
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=")) {
@@ -1328,8 +1441,37 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
// 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);
const rendered = fillSlots(String(render(props)), inner);
result += await renderComponents(rendered, translate, depth + 1);
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);
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(
@@ -1347,6 +1489,9 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
}
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(
@@ -1378,10 +1523,14 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
}
// Issue the CSRF token cookie so forms on this page can echo it back.
csrfToken(ctx);
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`);
@@ -1389,13 +1538,88 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
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);
let body = await renderComponents(String(await component(pageCtx)), ctx.t);
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);
}
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)
: "";
@@ -1421,14 +1645,18 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
? undefined
: router.layouts.find((l) => l.name === layoutName);
if (importedLayout?.render) {
body = await renderComponents(fillSlots(String(importedLayout.render({})), body), ctx.t);
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);
body = await renderComponents(fillSlots(String(layoutRender({})), body), ctx.t, ctx.lang);
}
} catch (err) {
console.error(`[wrnexus] layout '${layoutName}' failed to render`, err);
@@ -1467,7 +1695,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
pathname: ctx.url.pathname,
}),
);
documentTemplate = await renderComponents(fillSlots(rendered, body), ctx.t);
documentTemplate = await renderComponents(fillSlots(rendered, body), ctx.t, ctx.lang);
body = documentTemplate;
}
} catch (err) {
@@ -1498,6 +1726,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
}
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));
@@ -1516,15 +1745,24 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
}
}
attrs.push(`lang="${safeLanguageTag(language)}"`);
if (deps.i18n) attrs.push(`dir="${deps.i18n.direction[language] ?? "ltr"}"`);
const htmlAttrs = attrs.length ? ` ${attrs.join(" ")}` : undefined;
const html = renderDocument({
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,
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" />` : "",
@@ -1535,6 +1773,9 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
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 : "",
]
@@ -1544,10 +1785,29 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
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)) {
@@ -1556,18 +1816,132 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
headers: { etag: tag, "cache-control": "private, no-cache" },
});
}
return new Response(html, {
headers: {
"content-type": "text/html; charset=utf-8",
etag: tag,
"cache-control": "private, no-cache",
...(shouldEnableDevToolbar(mode, deps)
? {
"x-wrnexus-dev-toolbar": "enabled",
"x-wrnexus-route": matched.route.raw,
}
: {}),
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 },
});
}
@@ -1605,11 +1979,12 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
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(LANG_COOKIE),
ctx.cookies.get(deps.i18n.cookie.name),
req.headers.get("accept-language"),
);
ctx.t = makeT(deps.i18n, ctx.lang);
@@ -1908,9 +2283,15 @@ export function collectScripts(
navigation: { mode?: "auto" | "client" | "document" } = {},
): RenderScript[] {
const scripts: RenderScript[] = [];
if (/\bdata-scope=/.test(body) || /\bdata-wrnexus-csr=/.test(body)) {
if (
/\bdata-scope=/.test(body) ||
/\bdata-wrnexus-csr=/.test(body) ||
/\bdata-wrn-client-template=/.test(body) ||
/\bdata-wrn-async=/.test(body)
) {
scripts.push("/__wrnexus/reactive.js");
}
if (/\bdata-wrn-action=/.test(body)) scripts.push("/__wrnexus/actions.js");
// The theme runtime is only needed when the page can switch themes.
if (
/\bdata-wire-theme-(toggle|set)\b/.test(body) ||
@@ -1961,6 +2342,49 @@ 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)}`;