# Client errors [#client-errors] A client call resolves to the data, or throws. There is no `{ data, code, cause }` envelope to inspect: the generated client unwraps successes and turns every failure into a typed error. Branch on the class (or on `code`), never on the message text. ```ts import avClient, { AvNotFoundError } from "./api/AvClient"; try { await avClient.User.find.unique({ where: { id: 9999 } }); } catch (e) { if (e instanceof AvNotFoundError) { console.log(e.code); // "A2003" console.log(e.cause.message); // "No record matched the unique selector." } else { throw e; } } ``` ## The classes [#the-classes] All are exported from `AvClient.ts`. Every class except `AvTransportError` extends `AvError` and carries `code` and `cause`. | Class | Codes | Thrown when | | ------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `AvValidationError` | `A2004`, `A2007`, `A2009` | The request breaks a contract rule: unknown field, wrong type, field not selectable, bad `$ref`, limit exceeded, or a [field validator](/framework/docs/field-validators), which the client checks before it sends. `cause.issues` lists the problems. | | `AvNotFoundError` | `A2003` | A `unique` operation matched nothing, or a `$connect` names a record that does not exist. | | `AvConflictError` | `A2008`, `A2013`, `A2014` | A uniqueness or reference constraint (`A2008`), a concurrent modification (`A2013`) or a stale selection (`A2014`). The last two may succeed if retried. | | `AvAuthError` | `A4000`, `A4001`, `A4002` | A pipeline guard or the configuration's [policy](/framework/docs/policies) refused: unauthenticated, forbidden, policy denied. | | `AvContractMismatchError` | `A2005` | The generated client is stale. Regenerate. | | `AvProtocolError` | `A2000`, `A2001`, `A2002`, `A2006`, `A2010`, `A2011`, `A2012` | The request could not be understood or routed. Rare with a generated client. | | `AvInternalError` | `A3000` to `A3004` | An unexpected server or adapter failure. | | `AvTransportError` | none | No framework answer arrived. | The full code table, with HTTP statuses, is in [HTTP protocol](/framework/docs/http-protocol#codes). ### Refused before anything is sent [#refused-before-anything-is-sent] The client checks a few things locally and throws before any request is made: validators listed in the contract (`V1004` and the other validator codes), and an `undefined` in a filter, a unique selector or a cursor (`A2004` / `V1001` at that path, [Filtering](/framework/docs/filtering-sorting-paging#undefined-in-a-filter)). These are `AvValidationError`s with the same code and path the server would answer. ### A `TypeError` before anything is sent [#a-typeerror-before-anything-is-sent] One refusal is not an `AvError` at all. The client reads the arguments you pass it through a guard, and a value that cannot be read (a revoked Proxy, a getter that throws) is refused with a `TypeError` that names where, before a request is made: ```text TypeError: An operation's arguments must be readable plain data, and the value at /where/id could not be read. ``` A `tx` builder never throws for this either: `avClient.transaction([...])` rejects with that `TypeError`. Server-side calls answer the same mistake as `A2004` / `V1001` instead ([Server-side usage](/framework/docs/server-side-usage#what-a-server-side-call-accepts)). ## cause and issues [#cause-and-issues] `AvError.cause` has this shape: ```ts interface AvCause { readonly message: string; readonly issues?: readonly AvValidationIssue[]; // validation failures readonly operation?: number; // transaction step that failed } interface AvValidationIssue { readonly path?: readonly (string | number)[]; readonly code: AvValidationCode; // "V1001", ... readonly message: string; readonly check?: string; // a failed field validator: zod's check name readonly params?: Record; // and its parameters } ``` A validation failure, as thrown for `where: { email: { contains: 5 } }`: ```ts // AvValidationError, e.code === "A2004" // e.cause === { // message: "Operation arguments failed framework validation.", // issues: [{ // path: ["arguments", "where", "email", "contains"], // code: "V1001", // message: "Value must be a string." // }] // } ``` A failed [field validator](/framework/docs/field-validators) also says which rule broke, so a form can show its own text: ```ts // thrown before any request, by the client's own check // e.cause.issues[0] === { // code: "V1004", path: ["arguments", "data", "name"], // message: "Value must be at least 3 characters long.", // check: "min_length", params: { minimum: 3 } // } ``` The server would answer the same issue, so the client and server checks read alike. When the issue comes from your own pipeline code, `message` is the text you wrote, if it was non-blank and at most 1,000 characters; otherwise it is the standard text for the code. Use `path` to attach the message to a form field: ```ts import { AvValidationError } from "./api/AvClient"; function fieldErrors(e: unknown): Record { if (!(e instanceof AvValidationError)) return {}; const out: Record = {}; for (const issue of e.cause.issues ?? []) { // paths look like ["arguments", "data", "email"] const field = issue.path?.[issue.path.length - 1]; if (typeof field === "string") out[field] = issue.message; } return out; } ``` A transaction error carries `cause.operation`, the zero-based index of the failing step ([Transactions](/framework/docs/transactions#rollback)). ## Handling the common cases [#handling-the-common-cases] ```ts import avClient, { AvAuthError, AvConflictError, AvContractMismatchError, AvNotFoundError, AvTransportError, AvValidationError, } from "./api/AvClient"; try { await signedInClient.User.create.one({ data: { email: "ada@example.com" } }); } catch (e) { if (e instanceof AvConflictError) { // A2008: "The operation conflicts with the current state of the resource." (duplicate email) } else if (e instanceof AvAuthError) { // A4000: "Sign in first." -> send the user to the login screen } else if (e instanceof AvValidationError) { // show e.cause.issues } else if (e instanceof AvContractMismatchError) { // the app is older than the API: ask for a reload / redeploy } else if (e instanceof AvTransportError) { // offline, DNS, CORS, or a non-Aventara answer } else { throw e; } } ``` `AvAuthError` comes from your own guards ([Configuration](/framework/docs/configuration#pipelines)); the message is whatever your guard threw. The same holds for the `message` of each issue a pipeline throws (`AvError("A2004", { message, issues })`), when its path names a member of the request's arguments. Without a guard nothing throws it. ## AvTransportError versus AvError [#avtransporterror-versus-averror] | | `AvError` family | `AvTransportError` | | ------- | ----------------------------------- | --------------------------------- | | Meaning | The framework answered, and said no | No framework answer arrived | | Has | `code`, `cause` | `status` (HTTP status or `null`) | | Remedy | By code | Check the network, URL and server | `AvTransportError` covers: * A failed request (server down, refused connection, CORS failure in a browser): `status` is `null`, message `The request for "User" find.many failed before any response arrived.` * A response that is not an Aventara envelope, such as a proxy's or a host's own 404 for a path it does not mount: `status` is the HTTP status, message `The response to "User" find.many (HTTP 404) is not an Aventara response envelope.` * A value in a response that cannot be read. An `AvTransportError` names no remedy: nothing in it says the client is stale or the server is wrong. An aborted call is neither: it rejects with exactly what `fetch` rejected with, an `AbortError` `DOMException` ([Client options](/framework/docs/client-options#aborting)). ## AvContractMismatchError [#avcontractmismatcherror] A generated client is bound to the contract hash and protocol version it was generated from, and sends both on every request. When the server's contract differs, it refuses with `A2005`: ```text AvContractMismatchError: Generated client contract does not match the server. Regenerate the client. ``` This also happens when the client calls an operation the server no longer advertises, because the server checks the client's identity before it routes. The remedy is always the same: regenerate against the deployment the client calls ([Keeping in sync](/framework/docs/keeping-in-sync)). ## No automatic retries [#no-automatic-retries] The client never retries. `AvConflictError` with `A2013` or `A2014` is the one case where retrying the same call can succeed; do it yourself, with a limit. ## See also [#see-also] * [Client options](/framework/docs/client-options) * [Keeping in sync](/framework/docs/keeping-in-sync) * [HTTP protocol](/framework/docs/http-protocol#codes) * [Troubleshooting](/framework/docs/troubleshooting) * Guides: [Authentication and guards](/framework/docs/authentication-and-guards), [Request IDs and diagnostics](/framework/docs/request-ids-and-diagnostics), [Limits and safety](/framework/docs/limits-and-safety)