Centralize API calls: mandatory error handling, TanStack Query, and more (all TBD)
Nothing here is decided. This is a brainstorm surfaced while working the "API error codes: envelope, status fallback and guardrails" ticket — every idea below needs discussion and a real decision before any of it gets built. Do not treat any option as chosen.
Where this came from
That ticket's §3.4 mechanically routed ~50 Svelte components through resolveApiError(err), each wrapped in its own try { await someRoute(...) } catch (err) { error = resolveApiError(err); }. It works, but it's still up to every call site to remember the try/catch. Nothing stops a future component from skipping it and silently regressing to a raw/blank error, the exact problem the other ticket exists to close.
Today apps/web/src/lib/api/*.ts (catalog.ts, library.ts, etc.) are plain async () => request(...) functions with no shared calling convention beyond request() itself.
The core question
Should API calls go through a mandatory, centralized wrapper instead of bare route functions each component catches by hand?
Option A — a useApiCall-style factory per call
Instead of exporting getStatsOverview(domain) that can throw, export something that returns { data, error, loading } directly, replacing the $state ×3 + $effect + try/catch boilerplate seen in most components. apps/web/src/lib/components/stats/stats-resource.svelte.ts's statsResource() is close to this shape already, but scoped to /stats — this would generalize it app-wide.
Option B — keep bare route functions, but make the catch structural
Keep request()/route functions as-is, but provide a mandatory-feeling helper (createAsyncAction() or similar) every component calls explicitly, so nothing can bypass error resolution without visibly opting out.
TanStack Query — separate question, more mixed
@tanstack/svelte-query is already a dependency (CommentThread.svelte is the reference implementation — createInfiniteQuery, createMutation, queryClient.invalidateQueries, polling via refetchInterval). CLAUDE.md already says: "Reach for it, not another local-$state pattern, when a mutation needs to invalidate data shown in more than one component."
But adopting it isn't free even though it's zero new dependency — it's a paradigm shift (server state owned by the query, queryKey design, invalidation planning) for every screen migrated. Migrating ~50 call sites to it in one pass would be a second large mechanical migration, not an extension of the error-handling wrapper. If pursued, this should stay a separate, incremental migration (screen by screen, same pace as CommentThread.svelte set), not bundled into whatever the error-handling wrapper ends up being.
Refetch-on-window-focus (mentioned as a candidate use) is one of TanStack Query's built-ins, currently barely used in this codebase — worth deciding whether it's actually wanted broadly or just for specific screens.
Other ideas raised, also undecided
- Automatic request cancellation (
AbortController) when the initiating component unmounts, or when reactive params change before the previous response lands — would close a class of Svelte race-condition bugs ($effectre-firing before the prior fetch resolves) that likely already exist somewhere unnoticed. - A home for
requestId. The API error envelope (from the other ticket) already carriesrequestIdonApiError, but nothing on the web side surfaces or uses it yet. A centralized wrapper would be the natural place to, e.g., tag it onto whatever error-reporting call the web app makes (Sentry/GlitchTip), so a user-visible failure can be traced back to a server log line.
Explicitly not decided
- Whether to centralize at all vs. accept the current per-component discipline now that
resolveApiErrorexists. - Option A vs. Option B vs. something else entirely.
- Whether/how far to take TanStack Query adoption, and on what timeline relative to this.
- Whether request cancellation and
requestIdsurfacing belong in this effort or their own tickets. - Scope: touching all ~50 call sites again, or only new code going forward.
Depends on
"API error codes: envelope, status fallback and guardrails" — ApiError/resolveApiError/requestId need to exist first (they do now, pending review).
0 Comments
Sign in to comment
No comments yet. Be the first to share your thoughts!
