diff --git a/packages/compiler/src/codegen.ts b/packages/compiler/src/codegen.ts index 507c3199..d88f3604 100644 --- a/packages/compiler/src/codegen.ts +++ b/packages/compiler/src/codegen.ts @@ -35,6 +35,10 @@ interface RenderBinding { path: string; body: string; helpers: string; + // Present only for a sectioned ssr block with a non-empty `error {}` section. + // When set, a failed API call runs this body (with `status`/`message`/`data` + // bound) instead of propagating. Absent -> failures propagate, unchanged. + errorBody?: string; } interface SsrBinding extends RenderBinding { @@ -873,6 +877,7 @@ function renderBinding(binding: NamedDataBinding): RenderBinding { path: binding.path, body: binding.body, helpers: binding.helpers, + ...(binding.errorBody ? { errorBody: binding.errorBody } : {}), }; } @@ -937,6 +942,7 @@ function apiBindingMap(ast: PageAst, sharedHelpers: string): Map undefined) + : await res.text().catch(() => undefined); + throw Object.assign(new Error(".wrn data API request failed with status " + res.status), { + status: res.status, + data, + }); } - const type = res.headers.get("content-type") || ""; return type.includes("application/json") ? await res.json() : await res.text(); } async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise { for (const binding of __wrnexusSsrBindings) { - const data = await __wrnexusCallApi(binding.path, binding.method, ctx); - const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx); + 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); + } html = html.replace(binding.marker, __wrnexusEscapeHtml(value)); } return html; @@ -1531,7 +1569,9 @@ function generateInner(ast: PageAst): string { const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || runtimeStateNames.size > 0; if (needsSsrRuntime) { out.push(ssrRuntimeSource()); - out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`); + out.push( + `const __wrnexusSsrBindings: Array<{ method: string; path: string; body: string; helpers: string; errorBody?: string }> = ${JSON.stringify(ssrBindings, null, 2)};`, + ); const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : ""; out.push( `export default async function ${ast.name}(ctx: __WrnexusContext) { diff --git a/packages/compiler/test/api-block-ssr.test.ts b/packages/compiler/test/api-block-ssr.test.ts index f4e52f02..5d51cc3f 100644 --- a/packages/compiler/test/api-block-ssr.test.ts +++ b/packages/compiler/test/api-block-ssr.test.ts @@ -32,3 +32,31 @@ test("a legacy ssr block is unchanged", () => { expect(generated).toContain("users.length"); }); + +test("an ssr block with an error section emits the error body and binds status/message/data", () => { + const generated = serverModule(` api ssrUsers GET /api/users { + response { + return data.users.length + } + error { + return message + status + data + } + }`); + + expect(generated).toContain('"errorBody"'); + expect(generated).toContain("return message + status + data"); + expect(generated).toContain("const status = $status"); + expect(generated).toContain("const message = $message"); + expect(generated).toContain("const data = $data"); + expect(generated).toContain("__wrnexusEvalError"); +}); + +test("an ssr block without an error section emits no catch entry for that binding", () => { + const generated = serverModule(` api ssrUsers GET /api/users { + response { + return data.users.length + } + }`); + + expect(generated).not.toContain('"errorBody"'); +});