< Summary

Information
Class: action-helpers.server.ts
Assembly: app.lib
File(s): /home/runner/work/ClutterStock/ClutterStock/frontend/app/lib/action-helpers.server.ts
Tag: 58_25416222083
Line coverage
93%
Covered lines: 14
Uncovered lines: 1
Coverable lines: 15
Total lines: 48
Line coverage: 93.3%
Branch coverage
100%
Covered branches: 10
Total branches: 10
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/ClutterStock/ClutterStock/frontend/app/lib/action-helpers.server.ts

#LineLine coverage
 1import { ApiProblemError, isApiProblem, type ProblemBody } from "~/api/problem";
 2
 3export type FieldErrors = Record<string, string[]>;
 4
 5export type ApiActionResult<T> =
 6  | { ok: true; data: T }
 7  | { ok: false; error: string; fieldErrors?: FieldErrors };
 8
 9/**
 10 * Run a backend call from a route action and translate validation/auth
 11 * problems into a structured result so the form can re-render in place.
 12 *
 13 * Returns `{ ok: false }` for 4xx (except 404, which bubbles to the nearest
 14 * ErrorBoundary because the resource itself is missing). 5xx and network
 15 * failures rethrow so the ErrorBoundary catches them with full ProblemDetails.
 16 */
 817export async function tryApi<T>(fn: () => Promise<T>): Promise<ApiActionResult<T>> {
 818  try {
 819    return { ok: true, data: await fn() };
 20  } catch (e) {
 721    const problem = await coerceProblem(e);
 722    if (problem && problem.status >= 400 && problem.status < 500 && problem.status !== 404) {
 323      return {
 24        ok: false,
 25        error: problem.detail ?? problem.title,
 26        fieldErrors: problem.errors,
 27      };
 28    }
 429    throw e;
 30  }
 31}
 32
 733async function coerceProblem(e: unknown): Promise<ApiProblemError | null> {
 734  if (isApiProblem(e)) return e;
 35  // The typed wrapper throws a Response with the ProblemDetails body so the
 36  // payload survives RR7's SSR serialization on the loader path. Actions need
 37  // to read it back here.
 338  if (e instanceof Response) {
 39    let body: ProblemBody;
 240    try {
 241      body = (await e.clone().json()) as ProblemBody;
 42    } catch {
 043      body = { title: e.statusText, status: e.status };
 44    }
 245    return new ApiProblemError(e.status, body);
 46  }
 147  return null;
 48}

Methods/Properties

tryApi()V
coerceProblem()V