Handling results
Like fetch, fetchCuple resolves with whatever the server answered, typed as every result the route can send. Check result:
import { fetchCuple } from "@cuple/client";
const res = await fetchCuple(client.getUser.get, { params: { id } });
// ^? success | user-not-found | invalid-params | unexpected-error
if (res.result === "user-not-found") return showNotFound();
if (res.result === "success") showUser(res.user);
It rejects only when there was no answer:
| Outcome | Rejects with |
|---|---|
| No answer: offline, DNS, CORS, a proxy's HTML error page | CupleTransportError |
signal aborted | DOMException (AbortError) |
Just the data
When your code only has something to do with success, keep only that. Anything else rejects as CupleUnexpectedResponseError, with the response, and goes where unhandled errors go:
const { user } = await fetchCuple(client.getUser.get, { params: { id } }).thenKeepSuccess();
Narrowing: keep or reject
List what your code handles, or what it doesn't. A result that isn't kept rejects as CupleUnexpectedResponseError:
const res = await fetchCuple(client.createUser.post, { body }).thenKeep([
"success",
"invalid-body",
]);
// ^? success | invalid-body
if (res.result === "invalid-body") showIssues(res.issues);
const res = await fetchCuple(client.getUser.get, { params: { id } }).thenReject([
"unexpected-error",
]);
// ^? success | user-not-found | invalid-params
| Method | Resolves with |
|---|---|
| (default) | every result the server sent |
.thenKeepSuccess() | success |
.thenKeep([...]) | exactly the listed results; "success" is not implied |
.thenKeepAlso([...]) | what it resolved with so far, plus the listed results |
.thenReject([...]) | every result the server sent, except the listed ones |
.thenRejectAlso([...]) | what it resolved with so far, minus the listed results |
.thenKeepAll() | everything: every result the server sent, plus transport-error and abort. Only a bug in your own code rejects. |
thenKeep and thenReject are complete lists: they start over from the response. The Also methods add to what came before, so they chain: .thenKeepSuccess().thenKeepAlso(["invalid-body"]).
No answer as a value
"transport-error"can be listed like any result, for code that has something to do when the server can't be reached (a form that keeps the user's input). It resolves as{ result: "transport-error", statusCode, message }.statusCodeis set when something answered, but not in Cuple's format (a gateway page)."abort"can be listed too, for a request you cancel on purpose. It resolves as{ result: "abort", statusCode: null, message }.
const res = await fetchCuple(client.search.get, { query, options: { signal } }).thenKeepAlso([
"abort",
]);
if (res.result === "abort") return; // a newer search replaced this one
In React
useGet gives you success, and { resolveAlso: [...] } lists what the component renders instead. useAction gets whatever your function resolves with: narrow fetchCuple with thenKeep, and whatever it doesn't keep goes to the nearest <Boundary>. See Errors.
fetchCuple works like fetch: a response is a value, whatever its status, so nothing is thrown at you by surprise. You narrow it when you know what your code handles.
useGet stays success-only: a component suspends until it has data to render, and dealing with an error is an enhancement.
A result you keep is typed in the return value, with its real shape. A try/catch gives you unknown. "transport-error" and "abort" can be listed like any server result, so handling offline or a cancelled request is the same code as handling any other failure.
The lists are chained methods (.thenKeep([...])), not an option of fetchCuple, because of a typing limit.