Skip to content
TypeWire

@tahanabavi/typefetch-graphql

documents v0.0.0

GraphQL transport for TypeFetch contracts — one client, one middleware chain, and selection sets generated from your Zod response schema.

Errors

GraphQL error codes are mapped onto typefetch's shared ErrorKind, so one global handler covers every transport:

ts
client.onError((error) => {
if (error.kind === "unauthenticated") redirectToLogin();
});

fires for a GraphQL UNAUTHENTICATED exactly as it does for an HTTP 401.

extensions.codekind
UNAUTHENTICATEDunauthenticated
FORBIDDENpermission_denied
BAD_USER_INPUT, GRAPHQL_VALIDATION_FAILED, GRAPHQL_PARSE_FAILEDinvalid_argument
NOT_FOUNDnot_found
TOO_MANY_REQUESTSresource_exhausted
INTERNAL_SERVER_ERRORinternal
SERVICE_UNAVAILABLEunavailable

Both response media types are handled: the legacy application/json (errors delivered inside a 200) and application/graphql-response+json (errors with a real 4xx/5xx). extensions.http.status is honoured when present.

Typed error bodies

errors is keyed by extensions.code for a GraphQL endpoint, and isContractError narrows off it:

ts
errors: {
UNAUTHENTICATED: z.object({ code: z.literal("UNAUTHENTICATED"), realm: z.string() }),
}

try {
await api.user.get({ id });
} catch (e) {
if (isContractError(contracts.user.get, e, "UNAUTHENTICATED")) {
e.data.realm; // fully typed
}
}

Partial data

A response carrying both data and errors is the case every GraphQL client gets wrong in one direction or the other.

  • errorPolicy: "none" (default) — throw. A partially-failed response is a failed one, and silently returning half a result is how a null reaches a UI three screens from its cause.

  • errorPolicy: "all" — resolve the data and hand the errors to onPartialErrors, so the return type stays Promise<T> and nothing is dropped.

ts
graphqlTransport({
url: "",
errorPolicy: "all",
onPartialErrors: (errors, { endpointId }) => log.warn(endpointId, errors),
});