fix(compiler): type-check the emitted ssr binding array and share error handling across call sites

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 16:42:38 +05:30
co-authored by Claude Opus 5
parent 7601477f7d
commit 3252b1b20e
2 changed files with 180 additions and 16 deletions
+36 -15
View File
@@ -964,7 +964,7 @@ function apiBindingMap(ast: PageAst, sharedHelpers: string): Map<string, NamedDa
}
function ssrRuntimeSource(): string {
return `const __wrnexusHtmlEscapes = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\\"": "&quot;", "'": "&#39;" };
return `const __wrnexusHtmlEscapes: Record<string, string> = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\\"": "&quot;", "'": "&#39;" };
function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
}
@@ -1038,20 +1038,38 @@ async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusCont
return type.includes("application/json") ? await res.json() : await res.text();
}
type __WrnexusApiCall = {
path: string;
method: string;
body: string;
helpers: string;
errorBody?: string;
};
type __WrnexusSsrBinding = __WrnexusApiCall & { marker: string };
// Shared by every ssr api-binding consumption site (marker replacement,
// #each loop consts, ...) so the narrow try/catch -- only active when the
// block declared an error section -- cannot drift between call sites.
async function __wrnexusResolveApiBinding(
binding: __WrnexusApiCall,
ctx: __WrnexusContext,
): Promise<unknown> {
if (binding.errorBody) {
try {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
return __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
} catch (err) {
return __wrnexusEvalError(err, binding.errorBody, binding.helpers, ctx);
}
}
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
return __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
}
async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
for (const binding of __wrnexusSsrBindings) {
let value: unknown;
if (binding.errorBody) {
try {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
} catch (err) {
value = __wrnexusEvalError(err, binding.errorBody, binding.helpers, ctx);
}
} else {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
}
const value = await __wrnexusResolveApiBinding(binding, ctx);
html = html.replace(binding.marker, __wrnexusEscapeHtml(value));
}
return html;
@@ -1560,8 +1578,11 @@ function generateInner(ast: PageAst): string {
for (const [name, binding] of apiBindings) {
if (binding.mode !== "ssr") continue;
if (!lists.some((expr) => new RegExp(`\\b${name}\\b`).test(expr))) continue;
const errorBodyProp = binding.errorBody
? `, errorBody: ${JSON.stringify(binding.errorBody)}`
: "";
loopConsts.push(
` const ${name} = __wrnexusEvalData(await __wrnexusCallApi(${JSON.stringify(binding.path)}, ${JSON.stringify(binding.method)}, ctx), ${JSON.stringify(binding.body)}, ${JSON.stringify(binding.helpers)}, ctx);`,
` const ${name} = await __wrnexusResolveApiBinding({ path: ${JSON.stringify(binding.path)}, method: ${JSON.stringify(binding.method)}, body: ${JSON.stringify(binding.body)}, helpers: ${JSON.stringify(binding.helpers)}${errorBodyProp} }, ctx);`,
);
}
}
@@ -1570,7 +1591,7 @@ function generateInner(ast: PageAst): string {
if (needsSsrRuntime) {
out.push(ssrRuntimeSource());
out.push(
`const __wrnexusSsrBindings: Array<{ method: string; path: string; body: string; helpers: string; errorBody?: string }> = ${JSON.stringify(ssrBindings, null, 2)};`,
`const __wrnexusSsrBindings: __WrnexusSsrBinding[] = ${JSON.stringify(ssrBindings, null, 2)};`,
);
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : "";
out.push(