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

+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;
}