feat: release design system engine, studio and component contracts

This commit is contained in:
Clintchiz committed 2026-10-19 13:09:03 +05:30
1 parent ff1065fb27
commit 3943b946d6
693 files changed
+175546 -3894

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/dev-server",
"version": "0.8.67",
"version": "0.8.83",
"type": "module",
"main": "src/index.ts",
"exports": {
+3 -1
View File
@@ -376,7 +376,9 @@ function createProdAssetServer(opts: ProdOptions): AssetServer {
return new Response(getComponentControllerRuntime(), { headers: JS_HEADERS });
}
if (pathname === "/__wrnexus/nav.js")
return new Response(getNavRuntime(), { headers: JS_HEADERS });
return new Response(getNavRuntime(), {
headers: { ...JS_HEADERS, "cache-control": "no-cache" },
});
if (pathname === "/__wrnexus/realtime.js")
return new Response(getRealtimeRuntime(), { headers: JS_HEADERS });
if (pathname === "/__wrnexus/actions.js")
+13 -1
View File
@@ -15,6 +15,7 @@ import { createContext, runWithRequestContext } from "@wrnexus/core";
* cookie pattern.
*/
export function validateRpcCsrf(request: Request): boolean {
if (request.headers.get("sec-fetch-site") === "cross-site") return false;
const url = new URL(request.url);
const origin = request.headers.get("origin");
if (origin && origin !== url.origin) return false;
@@ -23,7 +24,18 @@ export function validateRpcCsrf(request: Request): boolean {
/(?:^|;\s*)wrn-csrf=([^;]+)/.exec(cookieHeader)?.[1] ??
/(?:^|;\s*)wrnexus_csrf=([^;]+)/.exec(cookieHeader)?.[1];
const headerToken = request.headers.get("x-csrf-token") ?? request.headers.get("x-wrnexus-csrf");
return Boolean(cookieToken && headerToken && decodeURIComponent(cookieToken) === headerToken);
if (!cookieToken || !headerToken) return false;
let decodedToken: string;
try {
decodedToken = decodeURIComponent(cookieToken);
} catch {
return false;
}
let diff = decodedToken.length ^ headerToken.length;
for (let i = 0; i < Math.max(decodedToken.length, headerToken.length); i++) {
diff |= decodedToken.charCodeAt(i) ^ headerToken.charCodeAt(i);
}
return diff === 0;
}
/**
+125 -17
View File
@@ -26,6 +26,8 @@ import {
renderError,
renderNotFound,
runWithRequestContext,
resolveControlTemplate,
encodeControlTemplate,
withContextHeaders,
withSecurityHeaders,
resolveRequestUrl,
@@ -1281,18 +1283,31 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
const query = Object.fromEntries(url.searchParams);
if (mod.default.handlers.security?.requireUser && !user)
return new Response("Forbidden", { status: 403 });
const allowed = await mod.default.handlers.authorize?.({ user, query, headers: req.headers });
const allowed = await mod.default.handlers.authorize?.({
user,
query,
headers: req.headers,
});
if (allowed === false) return 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 new Response("Realtime route needs a default defineRoom() or a `websocket` export", { status: 500 });
return new Response(
"Realtime route needs a default defineRoom() or a `websocket` export",
{ status: 500 },
);
}
// A response lets post-processing middleware complete before upgrading.
return new Response(null, { status: 204, headers: { "x-wrnexus-upgrade-permit": permit } });
return new Response(null, {
status: 204,
headers: { "x-wrnexus-upgrade-permit": permit },
});
});
const allowed = data && response.status === 204 && response.headers.get("x-wrnexus-upgrade-permit") === permit;
const allowed =
data &&
response.status === 204 &&
response.headers.get("x-wrnexus-upgrade-permit") === permit;
response.headers.delete("x-wrnexus-upgrade-permit");
const result = secure(withContextHeaders(wsCtx, response));
if (!allowed) return result;
@@ -1668,33 +1683,75 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
* Runs before script collection so the injected `data-scope` is visible.
* Recurses so components can mount other components.
*/
const preparedControlDefinitions = new WeakSet<object>();
async function renderComponents(
body: string,
translate: TFunction = (key) => key,
language?: string,
depth = 0,
controlDepth = 0,
inertTemplates = new Map<string, string>(),
inlineControls = new Map<string, string>(),
): Promise<string> {
if (depth > 15 || controlDepth > 64) return body;
// Control definitions are inert base64 JSON. Resolve component mounts in
// their branch templates too, otherwise a component first selected by
// client state would materialize only an empty data-component placeholder.
const controlPattern = /data-wrn-(?:if|each)="([A-Za-z0-9+/=]+)"/g;
const controlPattern = /data-wrn-(?:if|each)="(@[A-Za-z0-9-]+:[0-9]+|[A-Za-z0-9+/=]+)"/g;
const replacements: Array<{ from: string; to: string }> = [];
for (const match of body.matchAll(controlPattern)) {
try {
const definition = JSON.parse(Buffer.from(match[1]!, "base64").toString("utf8")) as
Array<{ body?: string }> | { body?: string; empty?: string };
const reference = match[1]!;
const preparedReference = inlineControls.get(reference);
if (preparedReference) {
replacements.push({ from: match[0], to: match[0].replace(reference, preparedReference) });
continue;
}
const definition = (
reference.startsWith("@")
? resolveControlTemplate(reference)
: JSON.parse(Buffer.from(reference, "base64").toString("utf8"))
) as Array<{ body?: string }> | { body?: string; empty?: string };
if (!definition) continue;
// Compiler-authored inline controls recur in cached prototypes too.
// Hoist and memoize them before descending so recursive back-edges
// reuse the same request-owned definition instead of decoding anew.
const encoded = reference.startsWith("@") ? reference : encodeControlTemplate(definition);
if (!reference.startsWith("@")) inlineControls.set(reference, encoded);
if (typeof definition === "object") {
if (preparedControlDefinitions.has(definition)) continue;
preparedControlDefinitions.add(definition);
}
const entries = Array.isArray(definition) ? definition : [definition];
for (const entry of entries) {
if (typeof entry.body === "string" && entry.body.includes("data-component="))
entry.body = await renderComponents(entry.body, translate, language, depth + 1);
if (
typeof entry.body === "string" &&
(entry.body.includes("data-component=") || /data-wrn-(?:if|each)=/.test(entry.body))
)
entry.body = await renderComponents(
entry.body,
translate,
language,
depth,
controlDepth + 1,
inertTemplates,
inlineControls,
);
if (
"empty" in entry &&
typeof entry.empty === "string" &&
entry.empty.includes("data-component=")
(entry.empty.includes("data-component=") || /data-wrn-(?:if|each)=/.test(entry.empty))
)
entry.empty = await renderComponents(entry.empty, translate, language, depth + 1);
entry.empty = await renderComponents(
entry.empty,
translate,
language,
depth,
controlDepth + 1,
inertTemplates,
inlineControls,
);
}
const encoded = Buffer.from(JSON.stringify(definition), "utf8").toString("base64");
replacements.push({ from: match[0], to: match[0].replace(match[1]!, encoded) });
} catch {
// The client reports malformed control metadata with source context.
@@ -1745,6 +1802,25 @@ 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);
// Loop locals are bridged onto the child scope below. They are not
// component props and must not create a distinct recursive prototype
// for every parent row.
delete props["data-wrn-loop-locals"];
if (controlDepth > 0) {
// Inert templates have no live parent scope. Mixed bindings such as
// `{prefix}-{field.id}` must use defaults too, rather than becoming
// ever-growing literal props in recursive component prototypes.
for (const binding of attrStr!.matchAll(
/\sdata-wrn-prop-bind-[A-Za-z0-9_-]+="([^"]*)"/g,
)) {
try {
const pair = JSON.parse(decodeHtmlEntities(binding[1]!));
if (Array.isArray(pair) && typeof pair[0] === "string") delete props[pair[0]];
} catch {
/* malformed bindings are diagnosed by hydration */
}
}
}
// Client control templates retain reactive expressions (for example
// `metrics="{metrics}"`) because their parent state is not available
// while we pre-render the inert branch metadata. Do not feed those
@@ -1752,7 +1828,12 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
// component defaults creates the real child boundary; the bridged prop
// binding synchronizes the live value when the branch is instantiated.
for (const [key, value] of Object.entries(props)) {
if (/^\{[\s\S]*\}$/.test(value) && !/^\{t:/.test(value)) {
if (
/^\{[\s\S]*\}$/.test(value) &&
!/^\{t:/.test(value) &&
!/^\{\{[\s\S]*\}\}$/.test(value)
) {
// Double-brace placeholders are literal user content (for example {{order}}).
// SSR object props also start/end with braces. Preserve serialized
// JSON; only unresolved expressions should fall back to defaults.
try {
@@ -1776,8 +1857,14 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
const policy = (mod.__wrnexusCache ?? {}) as Record<string, string>;
const strategy = policy.strategy?.toLowerCase();
const renderComponent = () => fillSlots(String(render(props)), inner, true);
// Reuse request-local inert component prototypes. Recursive components
// otherwise manufacture a fresh control registry on every preparation
// pass, expanding inactive branches until they exhaust server memory.
const inertKey = controlDepth > 0 ? JSON.stringify([normalizedName, props, inner]) : null;
const existingTemplate = inertKey === null ? undefined : inertTemplates.get(inertKey);
const rendered =
strategy && !["none", "no-store", "request"].includes(strategy)
existingTemplate ??
(strategy && !["none", "no-store", "request"].includes(strategy)
? await cache.getOrLoad(
"component",
`${normalizedName}:${language}:${JSON.stringify(props)}:${inner}`,
@@ -1791,12 +1878,16 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
tags: cacheList(policy.tags),
},
)
: renderComponent();
: renderComponent());
if (inertKey !== null && existingTemplate === undefined)
inertTemplates.set(inertKey, rendered);
// Prop bindings are authored by the parent mount but consumed by the
// rendered child's scope. The mount itself is replaced during SSR, so
// carry those reserved attributes onto the child's scope root.
const propBindings = Array.from(
attrStr!.matchAll(/\s(data-wrn-prop-bind-[A-Za-z0-9_-]+(?:="[^"]*")?)/g),
attrStr!.matchAll(
/\s((?:data-wrn-prop-bind-[A-Za-z0-9_-]+|data-wrn-loop-locals)(?:="[^"]*")?)/g,
),
(match) => ` ${match[1]}`,
).join("");
const bridged = propBindings
@@ -1805,7 +1896,15 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
`$1${propBindings}`,
)
: rendered;
result += await renderComponents(bridged, translate, language, depth + 1);
result += await renderComponents(
bridged,
translate,
language,
depth + 1,
controlDepth,
inertTemplates,
inlineControls,
);
} catch (err) {
console.error(`[wrnexus] component '${name}' failed to render`, err);
deps.devToolbar?.collector.add(
@@ -1819,6 +1918,14 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
result += body.slice(tagStart, end);
}
}
if (depth === 0 && controlDepth === 0 && process.env.WRNEXUS_DEBUG_INERT_TEMPLATES === "1") {
const counts: Record<string, number> = {};
for (const key of inertTemplates.keys()) {
const name = JSON.parse(key)[0];
counts[name] = (counts[name] ?? 0) + 1;
}
console.info("[wrnexus] inert prototype counts", JSON.stringify(counts));
}
return result;
}
@@ -2184,6 +2291,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
htmlAttrs,
documentTemplate,
styleNonce: (ctx.locals.cspNonce as string | undefined) ?? undefined,
componentRecipeCss: deps.theme?.recipeCss,
});
if (deps.renderHtml) html = await deps.renderHtml(html);
// Conditional GET: hash the page CONTENT (`body`), not the assembled shell —
+6 -1
View File
@@ -3,6 +3,11 @@ import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
import { THEME_JS_HREF } from "@wrnexus/styles";
import type { RenderScript } from "@wrnexus/ssr";
import { runtimeScriptsForMarkup } from "./plugin-assets.ts";
import { createHash } from "node:crypto";
import { getNavRuntime } from "@wrnexus/csr";
const navigationScript =
"/__wrnexus/nav.js?v=" + createHash("sha256").update(getNavRuntime()).digest("hex").slice(0, 16);
/** Select browser runtimes from capabilities present in rendered markup. */
export function collectScripts(
@@ -41,7 +46,7 @@ export function collectScripts(
const mode = navigation.mode ?? "auto";
if (mode === "client" || (mode === "auto" && scripts.length > 0)) {
scripts.unshift("/__wrnexus/nav.js");
scripts.unshift(navigationScript);
}
return scripts;
}
@@ -7,6 +7,13 @@ import { resolveI18n } from "@wrnexus/i18n";
import { createHandlers, type RuntimeDeps } from "../src/runtime.ts";
const roots: string[] = [];
function conditionalBranches(html: string) {
const reference = /data-wrn-if="(@[A-Za-z0-9-]+:[0-9]+)"/.exec(html)?.[1];
expect(reference).toBeTruthy();
const registry = /data-wrn-controls[^>]*>(.*?)<\/script>/s.exec(html)?.[1];
expect(registry).toBeTruthy();
return JSON.parse(registry!)[Number(reference!.split(":")[1])];
}
afterEach(() => roots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true })));
test("a component nested in another component's slot keeps its boundary", async () => {
@@ -83,9 +90,7 @@ test("a component in an initially hidden client branch is server-prepared for in
upgrade: () => false,
});
const html = await response!.text();
const encoded = /data-wrn-if="([A-Za-z0-9+/=]+)"/.exec(html)?.[1];
expect(encoded).toBeTruthy();
const branches = JSON.parse(Buffer.from(encoded!, "base64").toString("utf8"));
const branches = conditionalBranches(html);
expect(branches[0].body).toContain('<input value="ready">');
expect(branches[0].body).not.toContain("data-component=");
});
@@ -135,8 +140,7 @@ test("typed components in client branches use defaults until reactive props are
});
const html = await response!.text();
expect(observed).toEqual({});
const encoded = /data-wrn-if="([A-Za-z0-9+/=]+)"/.exec(html)?.[1];
const branches = JSON.parse(Buffer.from(encoded!, "base64").toString("utf8"));
const branches = conditionalBranches(html);
expect(branches[0].body).toContain("<section");
expect(branches[0].body).toContain("data-wrn-prop-bind-0");
expect(branches[0].body).not.toContain("data-component=");
@@ -154,16 +158,21 @@ test("SSR carries a parent prop binding onto the rendered child scope", async ()
"component Child { props { value = 0 } view { <b>{value}</b> } }\n",
);
const marker = "[&quot;value&quot;,&quot;{count}&quot;]";
const locals = Buffer.from(JSON.stringify({ field: { id: "answer" } })).toString("base64");
const childState = Buffer.from(JSON.stringify({ value: 1 })).toString("base64");
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
loadModule: async (file) =>
basename(file) === "Child.wrn"
? { render: () => '<div data-scope="value: 1" data-wrn-scope="e30="><b>1</b></div>' }
? {
render: () =>
`<div data-scope="value: 1" data-wrn-scope="${childState}"><b>1</b></div>`,
}
: {
default: () =>
`<div data-component="Child" value="1" data-wrn-prop-bind-0="${marker}"></div>`,
`<div data-component="Child" value="1" data-wrn-prop-bind-0="${marker}" data-wrn-loop-locals="${locals}"></div>`,
},
getMiddleware: async () => [],
assets: { serve: async () => null },
@@ -173,6 +182,17 @@ test("SSR carries a parent prop binding onto the rendered child scope", async ()
});
const html = await response!.text();
expect(html).toContain(`data-wrn-prop-bind-0="${marker}"`);
const child = /<div data-scope=""[^>]*>/.exec(html)?.[0];
expect(child).toBeTruthy();
expect(child).toContain(`data-wrn-prop-bind-0="${marker}"`);
const registry = JSON.parse(/data-wrn-controls[^>]*>(.*?)<\/script>/s.exec(html)![1]!);
const metadata = (attribute: string) => {
const reference = new RegExp(`${attribute}="@[^"]+:([0-9]+)"`).exec(child!)?.[1];
expect(reference).toBeDefined();
return registry[Number(reference)].value;
};
expect(metadata("data-wrn-loop-locals")).toEqual({ field: { id: "answer" } });
expect(metadata("data-wrn-scope")).toEqual({ value: 1 });
expect(html).not.toContain('data-component="Child"');
});
@@ -0,0 +1,189 @@
import { expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { basename, join } from "node:path";
import { tmpdir } from "node:os";
import { buildRouter } from "@wrnexus/router";
import { createHandlers } from "../src/runtime.ts";
import { encodeControlTemplate } from "@wrnexus/core";
test("nested encoded conditional and empty-list templates prepare shared controls", async () => {
const root = mkdtempSync(join(tmpdir(), "wrn-nested-controls-"));
const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64");
try {
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
mkdirSync(join(app, "components"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default () => '';");
writeFileSync(
join(app, "components/Button.wrn"),
"component Button { view { <button>Review</button> } }",
);
const mount = '<div data-component="Button"></div>';
const inner = encode([{ cond: "review.active", body: mount }]);
const list = encode({ list: "items", item: "item", body: "<p>Item</p>", empty: mount });
const outer = encode([
{
cond: "review",
body: `<template data-wrn-if="${inner}"></template><template data-wrn-control-end></template><template data-wrn-each="${list}"></template><template data-wrn-control-end></template>`,
},
]);
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
loadModule: async (file) =>
basename(file) === "Button.wrn"
? { render: () => "<button>Review</button>" }
: {
default: () =>
`<template data-wrn-if="${encodeControlTemplate(JSON.parse(Buffer.from(outer, "base64").toString()))}"></template><template data-wrn-control-end></template>`,
},
getMiddleware: async () => [],
assets: { serve: async () => null },
});
const html = await (await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
}))!.text();
const registry = /data-wrn-controls[^>]*>(.*?)<\/script>/s.exec(html);
expect(registry).not.toBeNull();
const definitions = JSON.parse(registry![1]!);
const decode = (markup: string, kind: string) => {
const reference = new RegExp(`data-wrn-${kind}="(@[A-Za-z0-9-]+:[0-9]+)"`).exec(markup)![1]!;
return definitions[Number(reference.split(":")[1])];
};
const branch = definitions[0][0].body;
expect(decode(branch, "if")[0].body).toBe("<button>Review</button>");
expect(decode(branch, "each").empty).toBe("<button>Review</button>");
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test("shared branch references are prepared once across a branching control graph", async () => {
const root = mkdtempSync(join(tmpdir(), "wrn-shared-controls-"));
try {
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default () => ''; ");
let reads = 0;
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
loadModule: async () => ({
default: () => {
let body = "<p>Leaf</p>";
for (let depth = 0; depth < 12; depth++) {
let storedBody = body;
const entry = {
cond: "open",
get body() {
reads++;
return storedBody;
},
set body(value: string) {
storedBody = value;
},
};
const reference = encodeControlTemplate([entry]);
body =
`<template data-wrn-if="${reference}"></template><template data-wrn-control-end></template>`.repeat(
2,
);
}
return body;
},
}),
getMiddleware: async () => [],
assets: { serve: async () => null },
});
const html = await (await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
}))!.text();
expect(reads).toBeLessThan(200);
const definitions = JSON.parse(/data-wrn-controls[^>]*>(.*?)<\/script>/s.exec(html)![1]!);
expect(definitions).toHaveLength(12);
expect(definitions[0][0].body).toBe("<p>Leaf</p>");
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test("deep inline loop definitions stay bounded after shared component preparation", async () => {
const root = mkdtempSync(join(tmpdir(), "wrn-loop-size-"));
try {
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
mkdirSync(join(app, "components"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default () => ''; ");
writeFileSync(join(app, "components/Button.wrn"), "component Button { view { <button /> } }");
let body = '<div data-component="Button"></div>';
for (let depth = 0; depth < 12; depth++) {
const encoded = Buffer.from(
JSON.stringify({ list: "items", item: "item", body, empty: "" }),
).toString("base64");
body = `<template data-wrn-each="${encoded}"></template><template data-wrn-control-end></template>`;
}
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
loadModule: async (file) =>
basename(file) === "Button.wrn"
? { render: () => `<button>${"Review".repeat(200)}</button>` }
: { default: () => body },
getMiddleware: async () => [],
assets: { serve: async () => null },
});
const html = await (await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
}))!.text();
const definitions = JSON.parse(/data-wrn-controls[^>]*>(.*?)<\/script>/s.exec(html)![1]!);
expect(definitions).toHaveLength(12);
expect(html.length).toBeLessThan(12000);
expect(
definitions.filter((definition: { body: string }) => definition.body.includes("<button>")),
).toHaveLength(1);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test("inert control nesting does not consume the component composition budget", async () => {
const root = mkdtempSync(join(tmpdir(), "wrn-control-budget-"));
try {
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
mkdirSync(join(app, "components"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default ()=>'';");
writeFileSync(
join(app, "components/Button.wrn"),
"component Button { view { <button>Nested field</button> } }",
);
let body = '<div data-component="Button"></div>';
for (let depth = 0; depth < 20; depth++) {
const encoded = Buffer.from(JSON.stringify([{ cond: "open", body }])).toString("base64");
body = `<template data-wrn-if="${encoded}"></template><template data-wrn-control-end></template>`;
}
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
loadModule: async (file) =>
basename(file) === "Button.wrn"
? { render: () => "<button>Nested field</button>" }
: { default: () => body },
getMiddleware: async () => [],
assets: { serve: async () => null },
});
const html = await (await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
}))!.text();
const definitions = JSON.parse(/data-wrn-controls[^>]*>(.*?)<\/script>/s.exec(html)![1]!);
expect(JSON.stringify(definitions)).toContain("<button>Nested field</button>");
expect(JSON.stringify(definitions)).not.toContain("data-component");
expect(html.length).toBeLessThan(20000);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
@@ -37,19 +37,19 @@ test("serves package assets with a safe content type", async () => {
});
test("published JavaScript runtimes bundle relative chunks by default", async () => {
const entry=fileURLToPath(new URL("./fixtures/plugin-runtime/entry.js",import.meta.url));
const assets=mergePluginAssets([{id:"published",entry}],[]);
const response=await servePluginAsset(assets,"/__wrnexus/assets/published.js","development");
const entry = fileURLToPath(new URL("./fixtures/plugin-runtime/entry.js", import.meta.url));
const assets = mergePluginAssets([{ id: "published", entry }], []);
const response = await servePluginAsset(assets, "/__wrnexus/assets/published.js", "development");
expect(response?.status).toBe(200);
const source=await response!.text();
const source = await response!.text();
expect(source).toContain("published-runtime-chunk-loaded");
expect(source).not.toMatch(/from\s+["']\.\/chunk\.js["']/);
});
test("explicit bundle false preserves prebuilt runtime modules", async () => {
const entry=fileURLToPath(new URL("./fixtures/plugin-runtime/entry.js",import.meta.url));
const assets=mergePluginAssets([{id:"raw",entry,bundle:false}],[]);
const response=await servePluginAsset(assets,"/__wrnexus/assets/raw.js","development");
const entry = fileURLToPath(new URL("./fixtures/plugin-runtime/entry.js", import.meta.url));
const assets = mergePluginAssets([{ id: "raw", entry, bundle: false }], []);
const response = await servePluginAsset(assets, "/__wrnexus/assets/raw.js", "development");
expect(await response!.text()).toContain('from "./chunk.js"');
});
@@ -98,13 +98,13 @@ test("collectScripts ships zero framework JavaScript for static markup by defaul
test("collectScripts adds navigation in explicit client mode", () => {
expect(collectScripts("<main>Server rendered</main>", [], { mode: "client" })).toEqual([
"/__wrnexus/nav.js",
expect.stringMatching(/^\/__wrnexus\/nav\.js\?v=[a-f0-9]{16}$/),
]);
});
test("collectScripts auto-adds navigation when a page already needs a browser runtime", () => {
expect(collectScripts('<button data-scope="{}">Count</button>')).toEqual([
"/__wrnexus/nav.js",
expect.stringMatching(/^\/__wrnexus\/nav\.js\?v=[a-f0-9]{16}$/),
"/__wrnexus/reactive.js",
]);
});
@@ -39,6 +39,24 @@ const manifest: ProdManifest = {
const server = { upgrade: () => false } as never;
test("navigation runtime uses a content version and revalidates its stable URL", async () => {
const { collectScripts } = await import("../src/script-selection.ts");
const href = collectScripts("", [], { mode: "client" })[0] as string;
expect(href).toMatch(/^\/__wrnexus\/nav\.js\?v=[a-f0-9]{16}$/);
const handlers = createProductionHandlers(manifest, {});
const versioned = (await handlers.fetch(
new Request("http://localhost" + href),
server,
)) as Response;
expect(versioned.headers.get("cache-control")).toBe("no-cache");
expect(await versioned.text()).toContain("loadedNavigationScripts");
const stable = (await handlers.fetch(
new Request("http://localhost/__wrnexus/nav.js"),
server,
)) as Response;
expect(stable.headers.get("cache-control")).toBe("no-cache");
});
test("supervised production runtime injects reconnecting DOM-morph support", async () => {
const handlers = createProductionHandlers(manifest, { developmentRuntime: true });
const response = await handlers.fetch(new Request("http://localhost/"), server);
@@ -2,52 +2,122 @@ import { expect, test } from "bun:test";
import { defineRoom, type Middleware } from "@wrnexus/core";
import { createProductionHandlers } from "../src/prod.ts";
const room = defineRoom({ security: { requireUser: true }, authorize: info => info.user === "signed-in-user" });
const room = defineRoom({
security: { requireUser: true },
authorize: (info) => info.user === "signed-in-user",
});
const auth: Middleware = async (ctx, next) => {
if (ctx.req.headers.get("cookie") === "test-session=valid") ctx.user = { id: "signed-in-user" };
return next();
};
function handlers(middleware: Middleware[] = [auth]) {
return createProductionHandlers({ pages: [], api: [], realtime: [{raw:"/realtime/updates",mod:{default:room}}], middleware, components:[], layouts:[] }, {});
return createProductionHandlers(
{
pages: [],
api: [],
realtime: [{ raw: "/realtime/updates", mod: { default: room } }],
middleware,
components: [],
layouts: [],
},
{},
);
}
function request(query = "", cookie = "test-session=valid", origin = "https://account.example.test") {
return new Request("https://account.example.test/realtime/updates"+query, { headers: {host:"account.example.test", origin, cookie, upgrade:"websocket"} });
function request(
query = "",
cookie = "test-session=valid",
origin = "https://account.example.test",
) {
return new Request("https://account.example.test/realtime/updates" + query, {
headers: { host: "account.example.test", origin, cookie, upgrade: "websocket" },
});
}
test("room upgrade receives middleware identity after post-processing completes", async () => {
let finished = false;
const post: Middleware = async (_, next) => { const res = await next(); finished = true; return new Response(res.body, { status:res.status, headers:res.headers }); };
const post: Middleware = async (_, next) => {
const res = await next();
finished = true;
return new Response(res.body, { status: res.status, headers: res.headers });
};
let upgraded = false;
const response = await handlers([auth,post]).fetch(request(), { upgrade: (_, options) => {
expect(finished).toBe(true);
expect((options.data as any).meta.user).toBe("signed-in-user");
expect(options.headers?.has("x-wrnexus-upgrade-permit")).toBe(false);
upgraded = true; return true;
} });
expect(response).toBeUndefined(); expect(upgraded).toBe(true);
const response = await handlers([auth, post]).fetch(request(), {
upgrade: (_, options) => {
expect(finished).toBe(true);
expect((options.data as any).meta.user).toBe("signed-in-user");
expect(options.headers?.has("x-wrnexus-upgrade-permit")).toBe(false);
upgraded = true;
return true;
},
});
expect(response).toBeUndefined();
expect(upgraded).toBe(true);
});
test("query user cannot impersonate an authenticated room member", async () => {
const response = await handlers().fetch(request("?user=signed-in-user", ""), {upgrade:()=>{throw Error("must not upgrade")}});
const response = await handlers().fetch(request("?user=signed-in-user", ""), {
upgrade: () => {
throw Error("must not upgrade");
},
});
expect(response?.status).toBe(403);
});
test("middleware rejection and cross-site origins never upgrade", async () => {
const server = {upgrade:()=>{throw Error("must not upgrade")}};
expect((await handlers([()=>new Response("denied",{status:401})]).fetch(request(),server))?.status).toBe(401);
expect((await handlers().fetch(request("", "test-session=valid", "https://evil.example"),server))?.status).toBe(403);
const server = {
upgrade: () => {
throw Error("must not upgrade");
},
};
expect(
(await handlers([() => new Response("denied", { status: 401 })]).fetch(request(), server))
?.status,
).toBe(401);
expect(
(await handlers().fetch(request("", "test-session=valid", "https://evil.example"), server))
?.status,
).toBe(403);
});
test("post-processing rejection overrides room authorization", async () => {
const deny: Middleware = async (_,next)=>{await next();return new Response("denied",{status:403})};
expect((await handlers([auth,deny]).fetch(request(),{upgrade:()=>{throw Error("must not upgrade")}}))?.status).toBe(403);
const deny: Middleware = async (_, next) => {
await next();
return new Response("denied", { status: 403 });
};
expect(
(
await handlers([auth, deny]).fetch(request(), {
upgrade: () => {
throw Error("must not upgrade");
},
})
)?.status,
).toBe(403);
});
test("Bun accepts a real socket with the middleware-authenticated identity", async () => {
const app = handlers();
const server = Bun.serve({ port:0, fetch:app.fetch, websocket:app.websocket });
const server = Bun.serve({ port: 0, fetch: app.fetch, websocket: app.websocket });
try {
await new Promise<void>((resolve,reject)=>{
const socket = new WebSocket(`ws://localhost:${server.port}/realtime/updates`, {headers:{cookie:"test-session=valid",origin:`http://localhost:${server.port}`}});
const timer=setTimeout(()=>{socket.close();reject(Error("upgrade timed out"))},3000);
socket.onopen=()=>{clearTimeout(timer);socket.close();resolve()};
socket.onerror=()=>{clearTimeout(timer);reject(Error("upgrade failed"))};
await new Promise<void>((resolve, reject) => {
const Socket = WebSocket as unknown as {
new (url: string, options?: import("bun").WebSocketOptions): WebSocket;
};
const socket = new Socket(`ws://localhost:${server.port}/realtime/updates`, {
headers: { cookie: "test-session=valid", origin: `http://localhost:${server.port}` },
});
const timer = setTimeout(() => {
socket.close();
reject(Error("upgrade timed out"));
}, 3000);
socket.onopen = () => {
clearTimeout(timer);
socket.close();
resolve();
};
socket.onerror = () => {
clearTimeout(timer);
reject(Error("upgrade failed"));
};
});
} finally { server.stop(true); await app.shutdown(); }
} finally {
server.stop(true);
await app.shutdown();
}
});
+25
View File
@@ -8,6 +8,31 @@ function request(headers: Record<string, string> = {}): Request {
});
}
test("RPC CSRF rejects cross-site fetch metadata even with matching tokens", () => {
expect(
validateRpcCsrf(
request({
cookie: "wrn-csrf=token",
"x-csrf-token": "token",
"sec-fetch-site": "cross-site",
}),
),
).toBe(false);
});
test("RPC CSRF rejects malformed cookie encoding without throwing", () => {
for (const cookieName of ["wrn-csrf", "wrnexus_csrf"]) {
expect(
validateRpcCsrf(
request({
cookie: `${cookieName}=%E0%A4%A`,
"x-csrf-token": "%E0%A4%A",
}),
),
).toBe(false);
}
});
test("RPC CSRF requires a matching cookie and header from the same origin", () => {
expect(validateRpcCsrf(request())).toBe(false);
expect(validateRpcCsrf(request({ cookie: "wrn-csrf=token" }))).toBe(false);
@@ -10,7 +10,7 @@ test("component resolution preserves JSON user/permission props and drops unreso
raw: "/",
mod: {
default: () =>
'<div data-component="account-shell" identity="{&quot;displayName&quot;:&quot;Alice&quot;}" permissions="{&quot;read&quot;:true,&quot;update&quot;:false}" preferences="{&quot;theme&quot;:&quot;dark&quot;}" pending="{account.pending}"><p>Account content</p></div>',
'<div data-component="account-shell" identity="{&quot;displayName&quot;:&quot;Alice&quot;}" permissions="{&quot;read&quot;:true,&quot;update&quot;:false}" preferences="{&quot;theme&quot;:&quot;dark&quot;}" pending="{account.pending}" value="{{order}}"><p>Account content</p></div>',
},
},
],
@@ -44,4 +44,156 @@ test("component resolution preserves JSON user/permission props and drops unreso
expect(JSON.parse(received.permissions!)).toEqual({ read: true, update: false });
expect(JSON.parse(received.preferences!)).toEqual({ theme: "dark" });
expect(received.pending).toBeUndefined();
expect(received.value).toBe("{{order}}");
});
test("recursive inert components reuse request-local control prototypes", async () => {
const { encodeControlTemplate } = await import("@wrnexus/core");
let renders = 0;
const handlers = createProductionHandlers(
{
pages: [{ raw: "/", mod: { default: () => '<div data-component="Tree"></div>' } }],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "Tree",
mod: {
render: () => {
renders++;
const control = encodeControlTemplate([
{ cond: "open", body: '<div data-component="Tree"></div>' },
]);
return `<div data-scope="open: false"><template data-wrn-if="${control}"></template><template data-wrn-control-end></template></div>`;
},
},
},
],
},
{},
);
for (let index = 0; index < 2; index++) {
const before = renders;
const response = await handlers.fetch(new Request("http://localhost/"), {
upgrade: () => false,
});
expect(response?.status).toBe(200);
const html = await response!.text();
expect(renders - before).toBeLessThanOrEqual(3);
expect(html.length).toBeLessThan(10000);
expect(html).toContain("wrn-controls-");
}
});
test("recursive mixed prop bindings do not grow inert prototypes", async () => {
const { encodeControlTemplate } = await import("@wrnexus/core");
let renders = 0;
const handlers = createProductionHandlers(
{
pages: [
{ raw: "/", mod: { default: () => '<div data-component="Branch" prefix="live"></div>' } },
],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "Branch",
mod: {
render: (props: Record<string, string>) => {
renders++;
const prefix = props.prefix || "";
const binding = "[&quot;prefix&quot;,&quot;{prefix}-child&quot;]";
const body = `<div data-component="Branch" prefix="${prefix}-child" data-wrn-prop-bind-0="${binding}"></div>`;
const control = encodeControlTemplate([{ cond: "open", body }]);
return `<div data-scope="open: false, prefix: '${prefix}'"><p>${prefix}</p><template data-wrn-if="${control}"></template><template data-wrn-control-end></template></div>`;
},
},
},
],
},
{},
);
const response = await handlers.fetch(new Request("http://localhost/"), { upgrade: () => false });
const html = await response!.text();
expect(renders).toBeLessThanOrEqual(3);
expect(html.length).toBeLessThan(10000);
expect(html).toContain("<p>live</p>");
});
test("loop-local bridge metadata does not multiply recursive prototypes", async () => {
const { encodeControlTemplate } = await import("@wrnexus/core");
let renders = 0;
const handlers = createProductionHandlers(
{
pages: [{ raw: "/", mod: { default: () => '<div data-component="LoopTree"></div>' } }],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "LoopTree",
mod: {
render: () => {
renders++;
const locals = Buffer.from(JSON.stringify({ row: renders })).toString("base64");
const control = encodeControlTemplate([
{
cond: "open",
body: `<div data-component="LoopTree" data-wrn-loop-locals="${locals}"></div>`,
},
]);
return `<div data-scope="open: false"><template data-wrn-if="${control}"></template><template data-wrn-control-end></template></div>`;
},
},
},
],
},
{},
);
const response = await handlers.fetch(new Request("http://localhost/"), { upgrade: () => false });
expect(response?.status).toBe(200);
expect(renders).toBeLessThanOrEqual(3);
expect((await response!.text()).length).toBeLessThan(10000);
});
test("inline compiler controls in recursive prototypes are hoisted once", async () => {
let renders = 0;
const encoded = Buffer.from(
JSON.stringify([
{
cond: "open",
body: '<div data-component="InlineTree"></div><div data-component="InlineTree"></div>',
},
]),
).toString("base64");
const handlers = createProductionHandlers(
{
pages: [{ raw: "/", mod: { default: () => '<div data-component="InlineTree"></div>' } }],
api: [],
realtime: [],
middleware: [],
layouts: [],
components: [
{
name: "InlineTree",
mod: {
render: () => {
renders++;
return `<div data-scope="open: false"><template data-wrn-if="${encoded}"></template><template data-wrn-control-end></template></div>`;
},
},
},
],
},
{},
);
const response = await handlers.fetch(new Request("http://localhost/"), { upgrade: () => false });
expect(response?.status).toBe(200);
expect(renders).toBeLessThanOrEqual(3);
expect((await response!.text()).length).toBeLessThan(10000);
});