Add withDb() helper for graceful DB error handling

Shared withDb() wrapper in @trails-cool/db catches database
connection errors and throws a 503 Response instead of crashing
the server. Re-throws React Router responses (redirects, data()).

Applied to Planner session routes — replaces manual try/catch.
Any route can use withDb(() => ...) for automatic 503 on DB failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-03-24 23:39:33 +01:00
parent cadcf753a7
commit 8fb7771223
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
3 changed files with 33 additions and 21 deletions

View file

@ -2,6 +2,7 @@ import { data } from "react-router";
import type { Route } from "./+types/api.sessions";
import { createSession, listSessions } from "~/lib/sessions";
import { parseGpxAsync } from "@trails-cool/gpx";
import { withDb } from "@trails-cool/db";
export async function action({ request }: Route.ActionArgs) {
if (request.method !== "POST") {
@ -15,7 +16,7 @@ export async function action({ request }: Route.ActionArgs) {
gpx?: string;
};
try {
return withDb(async () => {
const session = await createSession({ callbackUrl, callbackToken });
let initialWaypoints: Array<{ lat: number; lon: number; name?: string }> | undefined;
@ -29,26 +30,15 @@ export async function action({ request }: Route.ActionArgs) {
}
return data(
{
sessionId: session.id,
url: `/session/${session.id}`,
initialWaypoints,
},
{ sessionId: session.id, url: `/session/${session.id}`, initialWaypoints },
{ status: 201 },
);
} catch (e) {
return data(
{ error: "Database unavailable", details: (e as Error).message },
{ status: 503 },
);
}
});
}
export async function loader(_args: Route.LoaderArgs) {
try {
return withDb(async () => {
const sessions = await listSessions();
return data({ sessions });
} catch {
return data({ sessions: [], error: "Database unavailable" });
}
});
}

View file

@ -2,6 +2,7 @@ import { useParams, useSearchParams } from "react-router";
import type { Route } from "./+types/session.$id";
import { getSession } from "~/lib/sessions";
import { data } from "react-router";
import { withDb } from "@trails-cool/db";
import { ClientOnly } from "~/components/ClientOnly";
import { lazy, Suspense } from "react";
@ -14,7 +15,7 @@ export function meta(_args: Route.MetaArgs) {
}
export async function loader({ params }: Route.LoaderArgs) {
try {
return withDb(async () => {
const session = await getSession(params.id);
if (!session) {
throw data({ error: "Session not found" }, { status: 404 });
@ -24,10 +25,7 @@ export async function loader({ params }: Route.LoaderArgs) {
callbackUrl: session.callbackUrl ?? null,
callbackToken: session.callbackToken ?? null,
});
} catch (e) {
if (e instanceof Response) throw e; // Re-throw data() responses
throw data({ error: "Database unavailable" }, { status: 503 });
}
});
}
export default function SessionPage({ loaderData }: Route.ComponentProps) {