From 2810d37f031c2f040e0f9f2957cb1e8d06bb8921 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Wed, 19 Nov 2025 14:23:14 +0200 Subject: [PATCH] Enhance error handling in all package version routes --- .../cyberstorm-remix/app/p/packageVersion.tsx | 211 +++++++++---- .../app/p/packageVersionWithoutCommunity.tsx | 286 ++++++++++-------- 2 files changed, 307 insertions(+), 190 deletions(-) diff --git a/apps/cyberstorm-remix/app/p/packageVersion.tsx b/apps/cyberstorm-remix/app/p/packageVersion.tsx index ec91ac413..a5f71c941 100644 --- a/apps/cyberstorm-remix/app/p/packageVersion.tsx +++ b/apps/cyberstorm-remix/app/p/packageVersion.tsx @@ -6,10 +6,15 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { faArrowUpRight } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { getPublicEnvVariables } from "cyberstorm/security/publicEnvVariables"; import { - getPublicEnvVariables, - getSessionTools, -} from "cyberstorm/security/publicEnvVariables"; + NimbusAwaitErrorElement, + NimbusDefaultRouteErrorBoundary, +} from "cyberstorm/utils/errors/NimbusErrorBoundary"; +import { handleLoaderError } from "cyberstorm/utils/errors/handleLoaderError"; +import { createNotFoundMapping } from "cyberstorm/utils/errors/loaderMappings"; +import { throwUserFacingPayloadResponse } from "cyberstorm/utils/errors/userFacingErrorResponse"; +import { getLoaderTools } from "cyberstorm/utils/getLoaderTools"; import { isPromise } from "cyberstorm/utils/typeChecks"; import { type ReactElement, @@ -51,13 +56,15 @@ import { formatInteger, formatToDisplayName, } from "@thunderstore/cyberstorm"; -import { - DapperTs, - getPackageVersionDetails, - getTeamDetails, -} from "@thunderstore/dapper-ts"; +import { getPackageVersionDetails } from "@thunderstore/dapper-ts/src/methods/packageVersion"; +import { getTeamDetails } from "@thunderstore/dapper-ts/src/methods/team"; -import "./packageListing.css"; +const packageVersionNotFoundMappings = [ + createNotFoundMapping( + "Package version not found.", + "We could not find the requested package version." + ), +]; export async function loader({ params }: LoaderFunctionArgs) { if ( @@ -66,55 +73,81 @@ export async function loader({ params }: LoaderFunctionArgs) { params.packageId && params.packageVersion ) { - const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); - const dapper = new DapperTs(() => { + const { dapper } = getLoaderTools(); + try { + const [community, version, team] = await Promise.all([ + dapper.getCommunity(params.communityId), + dapper.getPackageVersionDetails( + params.namespaceId, + params.packageId, + params.packageVersion + ), + dapper.getTeamDetails(params.namespaceId), + ]); + return { - apiHost: publicEnvVariables.VITE_API_URL, - sessionId: undefined, + communityId: params.communityId, + community, + version, + team, }; - }); - - return { - communityId: params.communityId, - community: await dapper.getCommunity(params.communityId), - version: await dapper.getPackageVersionDetails( - params.namespaceId, - params.packageId, - params.packageVersion - ), - team: await dapper.getTeamDetails(params.namespaceId), - }; + } catch (error) { + handleLoaderError(error, { mappings: packageVersionNotFoundMappings }); + } } - throw new Response("Package not found", { status: 404 }); + throwUserFacingPayloadResponse({ + headline: "Package not found.", + description: "We could not find the requested package.", + category: "not_found", + status: 404, + }); } -export async function clientLoader({ params }: LoaderFunctionArgs) { +export function clientLoader({ params }: LoaderFunctionArgs) { if ( params.communityId && params.namespaceId && params.packageId && params.packageVersion ) { - const tools = getSessionTools(); - const dapper = new DapperTs(() => { - return { - apiHost: tools?.getConfig().apiHost, - sessionId: tools?.getConfig().sessionId, - }; - }); - - return { - communityId: params.communityId, - community: dapper.getCommunity(params.communityId), - version: dapper.getPackageVersionDetails( + const { dapper } = getLoaderTools(); + const community = dapper + .getCommunity(params.communityId) + .catch((error) => + handleLoaderError(error, { mappings: packageVersionNotFoundMappings }) + ); + const version = dapper + .getPackageVersionDetails( params.namespaceId, params.packageId, params.packageVersion - ), - team: dapper.getTeamDetails(params.namespaceId), + ) + .catch((error) => + handleLoaderError(error, { mappings: packageVersionNotFoundMappings }) + ); + const team = dapper + .getTeamDetails(params.namespaceId) + .catch((error) => + handleLoaderError(error, { mappings: packageVersionNotFoundMappings }) + ); + + return { + communityId: params.communityId, + community, + version, + team, }; } - throw new Response("Package not found", { status: 404 }); + throwUserFacingPayloadResponse({ + headline: "Package not found.", + description: "We could not find the requested package.", + category: "not_found", + status: 404, + }); +} + +export function ErrorBoundary() { + return ; } export function shouldRevalidate(arg: ShouldRevalidateFunctionArgs) { @@ -154,39 +187,65 @@ export default function PackageVersion() { // If strict mode is removed from the entry.client.tsx, this should only run once useEffect(() => { if (!startsHydrated.current && isHydrated) return; - if (isPromise(version)) { - version.then((versionData) => { - setFirstUploaded( - - ); - }); - } else { + if (!isPromise(version)) { setFirstUploaded( ); + return; } - }, []); + + let isCancelled = false; + + const resolveVersionTimes = async () => { + try { + const versionData = await version; + if (isCancelled) { + return; + } + + setFirstUploaded( + + ); + } catch (error) { + if (!isCancelled) { + console.error("Failed to resolve version metadata", error); + } + } + }; + + resolveVersionTimes(); + + return () => { + isCancelled = true; + }; + }, [isHydrated, version]); // END: For sidebar meta dates const currentTab = location.pathname.split("/")[8] || "details"; const versionAndCommunityPromise = useMemo( () => Promise.all([version, community]), - [] + [version, community] ); - const versionAndTeamPromise = useMemo(() => Promise.all([version, team]), []); + const versionAndTeamPromise = useMemo( + () => Promise.all([version, team]), + [version, team] + ); return ( <> - + } + > {(resolvedValue) => ( <> } > - + } + > {(resolvedValue) => ( You are viewing a potentially older version of this @@ -259,7 +321,10 @@ export default function PackageVersion() { } > - + } + > {(resolvedValue) => ( Loading...

}> - + } + > {(resolvedValue) => ( <>{packageMeta(firstUploaded, resolvedValue)} )} @@ -333,7 +401,10 @@ export default function PackageVersion() {
Loading...

}> - + } + > {(resolvedValue) => ( } > - + } + > {(resolvedValue) => ( <> @@ -418,7 +492,10 @@ export default function PackageVersion() { } > - + } + > {(resolvedValue) => ( } > - + } + > {(resolvedValue) => ( } > - + } + > {(resolvedValue) => ( <>{packageMeta(firstUploaded, resolvedValue)} )} diff --git a/apps/cyberstorm-remix/app/p/packageVersionWithoutCommunity.tsx b/apps/cyberstorm-remix/app/p/packageVersionWithoutCommunity.tsx index b14755702..668ebfe41 100644 --- a/apps/cyberstorm-remix/app/p/packageVersionWithoutCommunity.tsx +++ b/apps/cyberstorm-remix/app/p/packageVersionWithoutCommunity.tsx @@ -6,17 +6,21 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { faArrowUpRight } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { getPublicEnvVariables } from "cyberstorm/security/publicEnvVariables"; import { - getPublicEnvVariables, - getSessionTools, -} from "cyberstorm/security/publicEnvVariables"; + NimbusAwaitErrorElement, + NimbusDefaultRouteErrorBoundary, +} from "cyberstorm/utils/errors/NimbusErrorBoundary"; +import { handleLoaderError } from "cyberstorm/utils/errors/handleLoaderError"; +import { createNotFoundMapping } from "cyberstorm/utils/errors/loaderMappings"; +import { throwUserFacingPayloadResponse } from "cyberstorm/utils/errors/userFacingErrorResponse"; +import { getLoaderTools } from "cyberstorm/utils/getLoaderTools"; import { isPromise } from "cyberstorm/utils/typeChecks"; import { type ReactElement, Suspense, memo, useEffect, - useMemo, useRef, useState, } from "react"; @@ -61,36 +65,41 @@ import "./packageListing.css"; export async function loader({ params }: LoaderFunctionArgs) { if (params.namespaceId && params.packageId && params.packageVersion) { - const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); - const dapper = new DapperTs(() => { - return { - apiHost: publicEnvVariables.VITE_API_URL, - sessionId: undefined, - }; - }); - - return { - version: await dapper.getPackageVersionDetails( + const { dapper } = getLoaderTools(); + try { + const version = await dapper.getPackageVersionDetails( params.namespaceId, params.packageId, params.packageVersion - ), - team: await dapper.getTeamDetails(params.namespaceId), - }; - } - throw new Response("Package not found", { status: 404 }); -} + ); + const team = await dapper.getTeamDetails(params.namespaceId); -export async function clientLoader({ params }: LoaderFunctionArgs) { - if (params.namespaceId && params.packageId && params.packageVersion) { - const tools = getSessionTools(); - const dapper = new DapperTs(() => { return { - apiHost: tools?.getConfig().apiHost, - sessionId: tools?.getConfig().sessionId, + version, + team, }; - }); + } catch (error) { + handleLoaderError(error, { + mappings: [ + createNotFoundMapping( + "Package version not found.", + "We could not find the requested package version." + ), + ], + }); + } + } + throwUserFacingPayloadResponse({ + headline: "Package not found.", + description: "We could not find the requested package.", + category: "not_found", + status: 404, + }); +} +export function clientLoader({ params }: LoaderFunctionArgs) { + if (params.namespaceId && params.packageId && params.packageVersion) { + const { dapper } = getLoaderTools(); return { version: dapper.getPackageVersionDetails( params.namespaceId, @@ -100,7 +109,16 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { team: dapper.getTeamDetails(params.namespaceId), }; } - throw new Response("Package not found", { status: 404 }); + throwUserFacingPayloadResponse({ + headline: "Package not found.", + description: "We could not find the requested package.", + category: "not_found", + status: 404, + }); +} + +export function ErrorBoundary() { + return ; } export function shouldRevalidate(arg: ShouldRevalidateFunctionArgs) { @@ -139,69 +157,70 @@ export default function PackageVersion() { // If strict mode is removed from the entry.client.tsx, this should only run once useEffect(() => { if (!startsHydrated.current && isHydrated) return; - if (isPromise(version)) { - version.then((versionData) => { - setFirstUploaded( - - ); - }); - } else { + + const applyRelativeTime = ( + result: Awaited> + ) => { setFirstUploaded( - + ); + }; + + if (isPromise(version)) { + void version.then(applyRelativeTime); + } else { + applyRelativeTime(version); } - }, []); + }, [isHydrated, version]); // END: For sidebar meta dates const currentTab = location.pathname.split("/")[6] || "details"; - const versionAndTeamPromise = useMemo(() => Promise.all([version, team]), []); - return ( <> - - {(resolvedValue) => ( - <> - - - - - - - - - - - - )} + }> + {(results) => { + const [versionResult, teamResult] = results; + const metaVersion = versionResult; + const metaTeam = teamResult; + + return ( + <> + + + + + + + + + + + + ); + }}
@@ -216,13 +235,16 @@ export default function PackageVersion() { } > - - {(resolvedValue) => ( + } + > + {(versionResult) => ( @@ -230,16 +252,16 @@ export default function PackageVersion() { - {resolvedValue.namespace} + {versionResult.namespace} - {resolvedValue.website_url ? ( + {versionResult.website_url ? ( - {resolvedValue.website_url} + {versionResult.website_url} @@ -248,7 +270,7 @@ export default function PackageVersion() { } > - {formatToDisplayName(resolvedValue.name)} + {formatToDisplayName(versionResult.name)} )} @@ -275,20 +297,23 @@ export default function PackageVersion() { rootClasses="package-listing__drawer" > Loading...

}> - - {(resolvedValue) => ( - <>{packageMeta(firstUploaded, resolvedValue)} - )} + } + > + {(versionResult) => { + return packageMeta(firstUploaded, versionResult); + }}
Loading...

}> - - {(resolvedValue) => ( - + } + > + {(results) => ( + )}
@@ -298,17 +323,20 @@ export default function PackageVersion() { } > - - {(resolvedValue) => ( + } + > + {(versionResult) => ( <> - Required ({resolvedValue.dependency_count}) + Required ({versionResult.dependency_count}) } > - - {(resolvedValue) => ( + } + > + {(versionResult) => ( @@ -389,12 +420,12 @@ export default function PackageVersion() { } > - - {(resolvedValue) => ( - + } + > + {(result) => ( + )} @@ -403,10 +434,13 @@ export default function PackageVersion() { } > - - {(resolvedValue) => ( - <>{packageMeta(firstUploaded, resolvedValue)} - )} + } + > + {(versionResult) => + packageMeta(firstUploaded, versionResult) + }