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)
+ }