release: WRNexusJS 0.8.3
Quality / quality (ubuntu-latest) (push) Failing after 12m9s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-03 19:47:30 +05:30
parent e8f630f12d
commit 4cebacadfe
156 changed files with 2608 additions and 473 deletions
+150 -12
View File
@@ -363,6 +363,26 @@ export const REACTIVE_RUNTIME = String.raw`
},
};
}
function routeSnapshot() {
var url = new URL(window.location.href);
var params = window.__wrnexusRouteParams || {};
var marker = document.querySelector("[data-wrn-route-params]");
if (marker) {
try { params = JSON.parse(marker.getAttribute("data-wrn-route-params") || "{}"); }
catch (_) { params = {}; }
window.__wrnexusRouteParams = params;
marker.remove();
}
return {
url: url.href,
pathname: url.pathname,
search: url.search,
hash: url.hash,
searchParams: url.searchParams,
params: params,
};
}
function sanitizeReactiveUrl(value) {
var raw = String(value == null ? "" : value);
var compact = raw.trim().replace(/[\u0000-\u0020]+/g, "").toLowerCase();
@@ -755,6 +775,20 @@ export const REACTIVE_RUNTIME = String.raw`
var anyStateListeners = new Set();
var cleanupCallbacks = [];
var disposed = false;
var routeValue = signal(routeSnapshot());
function updateRouteValue() {
routeValue.set(routeSnapshot());
}
window.addEventListener("wrnexus:navigated", updateRouteValue);
window.addEventListener("popstate", updateRouteValue);
window.addEventListener("hashchange", updateRouteValue);
cleanupCallbacks.push(function () {
window.removeEventListener("wrnexus:navigated", updateRouteValue);
window.removeEventListener("popstate", updateRouteValue);
window.removeEventListener("hashchange", updateRouteValue);
});
function readGlobal(name) {
if (declaredEvents.has(name)) {
@@ -785,6 +819,10 @@ export const REACTIVE_RUNTIME = String.raw`
if (name === "location") return window.location;
if (name === "history") return window.history;
if (name === "navigator") return window.navigator;
if (name === "$route" || name === "route") {
if (currentRenderer) routeValue.subscribe(currentRenderer);
return routeValue.get();
}
if (name === "setTimeout") return window.setTimeout.bind(window);
if (name === "clearTimeout") return window.clearTimeout.bind(window);
if (name === "setInterval") return window.setInterval.bind(window);
@@ -965,6 +1003,12 @@ export const REACTIVE_RUNTIME = String.raw`
function installBehaviorFunctions(source) {
extractBehaviorFunctions(source || "").forEach(function (definition) {
// A generated browser module contains the real JavaScript implementation
// of client/shared functions. Keep that implementation when it has already
// been installed instead of replacing it with the small CSP-safe fallback
// interpreter. The fallback intentionally supports only a bounded subset of
// JavaScript and must never shadow the compiled function.
if (typeof behaviorFunctions[definition.name] === "function") return;
behaviorFunctions[
definition.name
] = function () {
@@ -3665,15 +3709,8 @@ export const REACTIVE_RUNTIME = String.raw`
return special.value;
}
// Template literals are evaluated without eval so they remain compatible
// with a strict CSP. Each interpolation uses the same bounded expression
// evaluator as every other reactive binding.
if (
expr.length >= 2 &&
expr[0] === "\`" &&
expr[expr.length - 1] === "\`"
) {
var template = expr.slice(1, -1);
function evaluateTemplateLiteral(source) {
var template = source.slice(1, -1);
var rendered = "";
var cursor = 0;
@@ -3703,7 +3740,7 @@ export const REACTIVE_RUNTIME = String.raw`
else if (character === "}" && --depth === 0) break;
}
if (depth !== 0) throw new Error("Unclosed template interpolation in '" + expr + "'");
if (depth !== 0) throw new Error("Unclosed template interpolation in '" + source + "'");
rendered += String(evaluateExpression(template.slice(expressionStart, expressionEnd), read));
cursor = expressionEnd + 1;
continue;
@@ -3715,6 +3752,18 @@ export const REACTIVE_RUNTIME = String.raw`
return rendered;
}
// Template literals are evaluated without eval so they remain compatible
// with a strict CSP. Each interpolation uses the same bounded expression
// evaluator as every other reactive binding. They are accepted both as a
// complete expression and as a primary inside a larger expression.
if (
expr.length >= 2 &&
expr[0] === "\`" &&
expr[expr.length - 1] === "\`"
) {
return evaluateTemplateLiteral(expr);
}
var tokens =
tokenizeExpression(expr);
@@ -3731,6 +3780,7 @@ export const REACTIVE_RUNTIME = String.raw`
var t = next();
if (!t) throw new Error("Unexpected end of expression in '" + expr + "'");
if (t.type === "number" || t.type === "string") return { value: t.value };
if (t.type === "template") return { value: evaluateTemplateLiteral(t.value) };
if (t.type === "ident") {
if (t.value === "true") return { value: true };
if (t.value === "false") return { value: false };
@@ -3874,6 +3924,37 @@ export const REACTIVE_RUNTIME = String.raw`
tokens.push({ type: "string", value: value });
continue;
}
if (ch === "\`") {
var templateStart = i++;
var templateDepth = 0;
var templateQuote = "";
while (i < input.length) {
ch = input[i++];
if (templateQuote) {
if (ch === "\\") i++;
else if (ch === templateQuote) templateQuote = "";
continue;
}
if (ch === "\\") {
i++;
continue;
}
if (ch === "\`" && templateDepth === 0) break;
if (ch === "$" && input[i] === "{") {
templateDepth++;
i++;
continue;
}
if (templateDepth > 0) {
if (ch === '"' || ch === "'") templateQuote = ch;
else if (ch === "{") templateDepth++;
else if (ch === "}") templateDepth--;
}
}
if (input[i - 1] !== "\`") throw new Error("Unclosed template literal");
tokens.push({ type: "template", value: input.slice(templateStart, i) });
continue;
}
if (/[A-Za-z_$]/.test(ch)) {
var s = i++;
while (i < input.length && /[A-Za-z0-9_$]/.test(input[i])) i++;
@@ -4047,6 +4128,23 @@ export const REACTIVE_RUNTIME = String.raw`
Array.from(node.childNodes || []).forEach(visit);
}
visit(root);
root.querySelectorAll("*").forEach(function (node) {
Array.from(node.attributes || []).forEach(function (attribute) {
if (attribute.name.indexOf("data-wrn-async-") === 0) return;
if (attribute.value.indexOf("{") === -1) return;
var nextValue = String(attribute.value).replace(/\{\s*([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\}/g, function (whole, path) {
var parts = path.split(".");
if (parts[0] === prefix) parts.shift();
if (prefix === "error" && parts[0] !== "message") return whole;
var value = asyncValueAt(model, parts.join("."));
return typeof value === "object" ? JSON.stringify(value) : String(value);
});
if (/^(?:href|src|action|formaction)$/i.test(attribute.name)) {
nextValue = sanitizeReactiveUrl(nextValue);
}
node.setAttribute(attribute.name, nextValue);
});
});
root.querySelectorAll("[data-wrn-async-value]").forEach(function (node) {
var path = node.getAttribute("data-wrn-async-value") || "";
var value = asyncValueAt(model, path);
@@ -4058,7 +4156,9 @@ export const REACTIVE_RUNTIME = String.raw`
var content = boundary.querySelector("[data-wrn-async-content]");
if (!template || !content) return;
var fragment = template.content.cloneNode(true);
interpolateAsync(fragment, model, state === "error" ? "error" : boundary.getAttribute("data-wrn-async"));
var alias = template.getAttribute("data-wrn-async-alias") ||
(state === "error" ? "error" : boundary.getAttribute("data-wrn-async"));
interpolateAsync(fragment, model, alias);
content.replaceChildren(fragment);
boundary.setAttribute("aria-busy", state === "loading" ? "true" : "false");
boundary.setAttribute("data-wrn-async-state", state);
@@ -4073,7 +4173,7 @@ export const REACTIVE_RUNTIME = String.raw`
}
var name = boundary.getAttribute("data-wrn-async") || "";
if (!/^[A-Za-z_$][\w$]{0,63}$/.test(name)) return showAsync(boundary, "error", { message: "Invalid data source" });
var url = "/__wrnexus/client-load?route=" + encodeURIComponent(location.pathname) + "&name=" + encodeURIComponent(name);
var url = "/__wrnexus/client-load?route=" + encodeURIComponent(location.pathname) + "&search=" + encodeURIComponent(location.search) + "&name=" + encodeURIComponent(name);
var attempts = Math.min(5, Math.max(0, Number(boundary.getAttribute("data-wrn-async-retries")) || 0));
var controller = new AbortController();
boundary.__wrnexusAsyncAbort = controller;
@@ -4102,6 +4202,44 @@ export const REACTIVE_RUNTIME = String.raw`
(root || document).querySelectorAll("[data-wrn-async]").forEach(setupAsyncBoundary);
}
var invalidationRefreshPending = false;
function asyncBoundaryTags(boundary) {
var raw = boundary.getAttribute("data-wrn-async-tags") || boundary.getAttribute("data-wrn-async") || "";
return raw.split(",").map(function (tag) { return tag.trim(); }).filter(Boolean);
}
function refreshAsyncBoundary(boundary) {
if (boundary.__wrnexusAsyncAbort) {
try { boundary.__wrnexusAsyncAbort.abort(); } catch (_) {}
}
boundary.__wrnexusAsync = false;
boundary.removeAttribute("data-wrn-async-resolved");
setupAsyncBoundary(boundary);
}
function handleCacheInvalidation(event) {
var detail = event && event.detail && typeof event.detail === "object" ? event.detail : {};
var tags = Array.isArray(detail.tags) ? detail.tags.map(String) : [];
if (!tags.length) return;
var matchedServerBoundary = false;
document.querySelectorAll("[data-wrn-async]").forEach(function (boundary) {
var matches = asyncBoundaryTags(boundary).some(function (tag) { return tags.indexOf(tag) !== -1; });
if (!matches) return;
if (boundary.getAttribute("data-wrn-async-resolved") === "true") {
matchedServerBoundary = true;
return;
}
refreshAsyncBoundary(boundary);
});
if (matchedServerBoundary && !invalidationRefreshPending) {
invalidationRefreshPending = true;
Promise.resolve().then(function () {
invalidationRefreshPending = false;
if (typeof window.__wrnexusRefresh === "function") window.__wrnexusRefresh();
else window.location.reload();
});
}
}
window.addEventListener("wrnexus:cache:invalidate", handleCacheInvalidation);
function mountClientRoots(root) {
(root || document).querySelectorAll("template[data-wrn-client-template]").forEach(function (template) {
var id = template.getAttribute("data-wrn-client-template");