# Client API reference [#client-api-reference] `@aventara/client` is a development-time generator. It reads your deployment's client contract and writes a standalone typed client into your project; the generated client imports nothing from `@aventara/client` or `@aventara/core` at runtime. This page lists both halves: what you write (`framework.client.ts`, `avclient`) and what is generated. For a guided tour see [Frontend client](/framework/docs/frontend-client). ## What `@aventara/client` exports [#what-aventaraclient-exports] Only the generator's configuration helpers (used in `framework.client.ts`, never at runtime in your app): | Export | Signature | Purpose | | ----------------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------- | | `defineClientConfig` | `(config: AvClientConfigInput) => AvClientConfigInput` | Gives the config file its type. Identity function. | | `env` | `(name: string) => AvEnvReference` | Names an environment variable to be read when the config is evaluated. | | `AVENTARA_CLIENT_GENERATOR_VERSION` | `string` | The generator's version. | | types | `AvClientConfigInput`, `AvClientConfigValue`, `AvEnvReference` | | ```ts // framework.client.ts import { defineClientConfig, env } from "@aventara/client"; export default defineClientConfig({ entrypoint: env("AVENTARA_API_URL"), // or a literal: "https://api.example.com/api" generateAt: "./src/api", }); ``` ### AvClientConfigInput [#avclientconfiginput] | Option | Type | Default (from `avclient init`) | Meaning | | ------------ | -------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `entrypoint` | `string \| AvEnvReference` | `env("AVENTARA_API_URL")`, value `http://localhost:3000/api` | The deployed framework entrypoint: origin plus mount path, one absolute `http(s)` URL. No credentials (the platform's `fetch` refuses them, and the value ships inside the client). No query or fragment; the mount path follows the server's [mount-path rules](/framework/docs/config-reference#api). | | `generateAt` | `string \| AvEnvReference` | `./src/api` | Directory the client is written into, relative to the config file. Shared: the generator owns exactly `AvClient.ts` and `generated/` there and never touches anything else. | The file is `framework.client.ts`. The generator loads it like `prisma.config`: `.ts`, `.mts`, `.cts`, `.js`, `.mjs` or `.cjs`, in ESM or CommonJS syntax (a `framework.client.mts` from `0.1.0-pilot.1` keeps working). With two of them present the generator refuses. The `.env` cascade is read from the current directory before the config is evaluated, highest precedence first: the process environment, `.env..local`, `.env.`, `.env.local`, `.env`. `mode` is `NODE_ENV`, or `development`. `process.env` is never written. Errors, one sentence and exit code 1 (verified): ```text avclient: entrypoint must use http: or https:, not ftp: avclient: entrypoint must not carry credentials: the platform's fetch refuses a URL that includes them, and the entrypoint is embedded in the generated client, which ships — authenticate through a custom fetch instead. avclient: entrypoint reads AVENTARA_API_URL, which is not set in the process environment or in any of .env.development.local, .env.development, .env.local, .env (mode "development"). avclient: Could not fetch the ClientContract from http://localhost:3999/api/_contract: fetch failed (connect ECONNREFUSED 127.0.0.1:3999). Check that the deployment is running and that the entrypoint is its origin plus mount path. ``` ## The avclient command [#the-avclient-command] ```text avclient init [options] Set up this frontend and generate. avclient generate [--yes] Fetch /_contract and write AvClient.ts and generated/. avclient --help Print usage, exit 0. avclient --help Print that command's usage, exit 0. avclient --version, -v Print the generator's version, exit 0. ``` Flags and exit codes are in the [CLI reference](/framework/docs/cli-reference#avclient). `avclient generate` asks the server conditionally (`If-None-Match` with the stored hash): on `304` and identical output it prints `avclient: up to date: ... nothing was written.` and exits 0. ## The generated files [#the-generated-files] Written into `generateAt`: | Path | Contents | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `AvClient.ts` | The entry point you import. | | `generated/client.ts` | The typed `AvClient` class, `AvClientOptions`, `AvCallOptions` and the ready instance. | | `generated/types.d.ts` | The named Resource types. | | `generated/enums.ts` | Each enum as a union type and a same-named `as const` object. | | `generated/contract.ts`, `metadata.ts` | The contract the client was generated against; the hash, protocol version and default entrypoint it is bound to. | | `generated/runtime/*` | Codecs, `AvDecimal`, errors, transport, field-validator checks (when the contract carries any), and (when transactions are interactive) fingerprints and transaction handling. | | `generated/derivation/**` | Type declarations that derive argument and result types from the contract. | Every file is TypeScript source. The files import each other the way the project's `tsconfig.json` says (extensionless, `.js` or `.ts`; see [Client setup](/framework/docs/client-setup#what-is-generated)), and you import `AvClient` the same way ([Importing the client](/framework/docs/client-setup#importing-the-client)). Never edit them: output is deterministic (two runs over one contract write the same bytes) and replaced on every run. Commit the tree; regenerating needs a running server. ## What `AvClient.ts` exports [#what-avclientts-exports] | Export | Kind | Notes | | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `default` | `AvClient` instance | The client of the deployment the tree was generated from: `import avClient from "./api/AvClient"` (spelled per project; see [Importing the client](/framework/docs/client-setup#importing-the-client)). | | `AvClient` | class | `new AvClient(options?)`. | | `avLazy` | function | `avLazy(() => call)`: makes the call on its first `then`, `catch` or `finally`, once. See [Operation Sets](/framework/docs/operation-sets#calls-run-when-you-make-them). | | `AvClientOptions` | type | See below. | | `AvCallOptions` | type | See below. | | `AvFetch` | type | The platform's `fetch` type. | | `AvOperation` | type | A deferred transaction handle. | | named Resource types | types | `User`, `UserWhere`, ... See [Named types](#named-types). | | enums | type and value | `Role` as `"USER" \| "ADMIN"` and `{ USER: "USER", ADMIN: "ADMIN" }`. | | `AvDecimal` | class | The client's decimal value: a constructor, `toString`, `toJSON`. | | `AvError`, `AvAuthError`, `AvConflictError`, `AvContractMismatchError`, `AvInternalError`, `AvNotFoundError`, `AvProtocolError`, `AvValidationError` | classes | See [Error classes](#error-classes). | | `AvTransportError` | class | | | `AvCause`, `AvValidationIssue`, `AvOperationCode`, `AvValidationCode` | types | | ## AvClient [#avclient] ```ts class AvClient { constructor(options?: AvClientOptions); readonly tx: /* deferred operations, only when transactions are interactive */; readonly transaction: /* only when transactions are interactive */; all(calls: { [key: string]: Promise }, options?: { settle?: boolean; signal?: AbortSignal }): Promise; // plus one property per Resource: readonly [Resource]: { [family]: { [variant]: (args?, options?: AvCallOptions) => Promise } }; } ``` Only what the client contract advertises exists, in the type and at runtime: one property per Resource, one per family, one per advertised variant. A call is `avClient...(args, options?)`. `all(calls, options?)` awaits an object of calls (or `avLazy` thenables) together and resolves one typed value per key. Strict by default: the first failure rejects it and aborts its other calls. With `settle: true` each key is `{ status: "fulfilled", value }` or `{ status: "rejected", reason }`. Its `signal` aborts every call in it. See [Several operations in one round trip](/framework/docs/operation-sets). A call runs when it is made, and the calls made in one tick go out as one `POST /_all`, except a call with `headers`, a `requestId` or `grouping: false`, a transaction, and a [cached read](/framework/docs/caching-reads), which goes out as a `GET`. A Resource whose name collides with a client member (`all`, `tx`, `transaction`, `then`, `constructor`, names of `Object.prototype`) is reached as `Model`, with one warning at generation; its wire name is unchanged. A call resolves to the data (record, list, count; `null` for a first-style miss, `A1001`). Every other outcome throws. Values are application values: `bigint`, `Date`, `Uint8Array`, the client's `AvDecimal`, and JSON as is. Arguments are encoded and results revived for you. ### AvClientOptions [#avclientoptions] | Option | Type | Default | Meaning | | ------------ | --------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `entrypoint` | `string` | the entrypoint the client was generated from | Another deployment that serves exactly the same client contract (tests, SSR). Re-binds nothing: the hash still decides whether it accepts you. | | `fetch` | `AvFetch` | the platform's `fetch`, read when a call is made | A custom fetch: authentication, tests, retries. | | `grouping` | `boolean` | `true` | `false` sends every call of this client on its own route, never in a `POST /_all`. | If no `fetch` is available a call throws `No fetch is available here; pass one: new AvClient({ fetch })`. Importing the client never fails. ```ts const client = new AvClient({ fetch: (input, init) => fetch(input, { ...init, headers: { ...init?.headers, Authorization: `Bearer ${token}` } }), }); ``` ### AvCallOptions [#avcalloptions] The last argument of every call. Never sent in the body. | Option | Type | Meaning | | ----------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `signal` | `AbortSignal` | Aborts this call only, with the signal's reason (an `AbortError`). Before it is sent, it is never sent; in a sent `/_all`, the other calls go on. | | `grouping` | `boolean` | `false` sends this call on its own route, never in a `POST /_all`. | | `headers` | `Record` | Extra request headers. The framework's own identity headers are refused: `The "Aventara-Contract-Hash" header is the framework's own; a request cannot set it` (a `TypeError`). | | `requestId` | `string` | Sent as `Aventara-Request-Id`; wins over the same header in `headers`. The empty string means absent. | ```ts await avClient.User.find.count({}, { requestId: "from-client-1", signal }); ``` ### AvFetch [#avfetch] `typeof globalThis.fetch` when your TypeScript `lib` declares one (DOM, `@types/node`); otherwise a structural `(url, init) => Promise` type. The platform `fetch` therefore needs no cast. ## Transactions [#transactions] When the contract advertises interactive transactions the client has `tx` and `transaction`; otherwise neither exists. | Member | Signature | Meaning | | ------------------------------------------ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `avClient.tx...(args)` | returns a handle (`AvOperation`) | Builds a deferred step; sends nothing. A handle's selected fields expose `$ref("field")`. | | `avClient.transaction(steps, options?)` | `Promise` | Sends the plan once; resolves one typed result per handle, in order. A failing step throws its error with `cause.operation` set. | ```ts const user = avClient.tx.User.create.one({ data: { email: "tx@example.com" }, select: ["id"] }); const post = avClient.tx.Post.create.one({ data: { title: "from tx", authorId: user.$ref("id") } }); const [u, p] = await avClient.transaction([user, post]); ``` Refused before anything is sent: an `undefined` in a step's filter, unique selector or cursor (`AvValidationError`, `A2004`/`V1001` at `["operations", i, "args", …]`), the same handle twice (`AvValidationError`, `A2004`/`V1001`) and a `$ref` to a handle not in the list (`A2007`/`V1010`). A `$ref` can sit in a value, a relation identifier, a unique `where`, a `cursor`, a filter at any depth and nested writes ([Transactions with $ref](/framework/docs/transactions-with-ref)); a `$ref` to a step later in the list is `A2007`/`V1016`. More than `maxTransactionOperations` steps throws `AvValidationError` `A2009`/`V1014`. A builder (`tx`) never throws for an unreadable argument: `transaction([...])` rejects with a `TypeError` that names where ([Client errors](/framework/docs/client-errors#a-typeerror-before-anything-is-sent)). See [Transactions](/framework/docs/transactions). ## Error classes [#error-classes] ```ts class AvError extends Error { readonly code: C; // branch on this readonly cause: AvCause; // { message, issues?, operation? } } class AvTransportError extends Error { readonly status: number | null; // null: no response arrived } interface AvCause { readonly message: string; readonly issues?: readonly AvValidationIssue[]; readonly operation?: number } interface AvValidationIssue { readonly path?: readonly (string | number)[]; readonly code: AvValidationCode; readonly message: string; readonly check?: string; readonly params?: Record; // a failed field validator } ``` | Class | `code` values | | ------------------------- | ------------------------------------------------------------------------------------------ | | `AvProtocolError` | `A2000` `A2001` `A2002` `A2006` `A2010` `A2011` `A2012` | | `AvNotFoundError` | `A2003` | | `AvValidationError` | `A2004` `A2007` `A2009` | | `AvContractMismatchError` | `A2005` | | `AvConflictError` | `A2008` `A2013` `A2014` (`A2013`/`A2014` may succeed if sent again; no retry is automatic) | | `AvInternalError` | `A3000` `A3001` `A3002` `A3003` `A3004` | | `AvAuthError` | `A4000` `A4001` `A4002` | Each has `name` equal to its class name. When the contract carries [field validators](/framework/docs/field-validators), every call and every transaction checks its values first and throws the `AvValidationError` the server would answer (`A2004`, the same issues), without sending anything. `cause` is rebuilt member by member from the envelope, so nothing the protocol does not define reaches it. `AvTransportError` carries no cause. Full table with fixes: [Error codes](/framework/docs/error-codes). ## Named types [#named-types] For each Resource `R`, exported only when the operation it reads is advertised: | Type | Is | | -------------- | ----------------------------------------------------------------------------- | | `R` | The default record (`find.unique` with no projection). Fields are `readonly`. | | `RWhere` | `find.many`'s `where`. | | `RUniqueWhere` | `find.unique`'s `where`. | | `ROrderBy` | `find.many`'s `orderBy`. | | `RCreateData` | `create.one`'s `data`. | | `RUpdateData` | `update.unique`'s `data`. | | `RSelect` | `find.many`'s `select`. | | `RInclude` | `find.many`'s `include`. | A name that would clash (a Resource `UserWhere` beside `User`) walks a rename ladder (`User` becomes `UserModel`), one warning each. For the sample schema: `User`, `UserWhere`, `UserUniqueWhere`, `UserOrderBy`, `UserCreateData`, `UserUpdateData`, `UserSelect`, `UserInclude`, and the same for `Post` and `Category`. ```ts import avClient, { type User, type UserWhere } from "./api/AvClient"; const where: UserWhere = { email: { equals: "ada@example.com" } }; const users: readonly User[] = await avClient.User.find.many({ where }); ``` List results are `readonly` arrays, so type them `readonly User[]`. ## Bound to a contract [#bound-to-a-contract] A generated client is bound to the exact contract hash and protocol version it was generated from (both in `generated/metadata.ts`) and sends them on every request. A deployment whose contract differs answers `A2005`, thrown as `AvContractMismatchError`, including when the stale client calls an operation the server no longer advertises. Generate against the deployment the client will call, and regenerate when it changes. See [Contract hash](/framework/docs/contract-hash). ## Verified behavior [#verified-behavior] Run against a live server: `find.unique` miss throws `AvNotFoundError`; `find.first` miss resolves `null`; an `AbortSignal` already aborted rejects with `AbortError`; a client with the wrong hash throws `AvContractMismatchError`; an unreachable server throws `AvTransportError` with `status === null` and the message `The request for "User" find.many failed before any response arrived.`; a 404 without an envelope throws `AvTransportError` with `status === 404`: `The response to "User" find.many (HTTP 404) is not an Aventara response envelope.` > In a CommonJS project, importing the default export (`import avClient from`) can resolve to the module object under some TypeScript runners. Use `new AvClient()` there, or a bundler/ESM setup. ## See also [#see-also] * [Frontend client](/framework/docs/frontend-client), [CLI reference](/framework/docs/cli-reference), [Error codes](/framework/docs/error-codes) * [Protocol API reference](/framework/docs/protocol-api-reference): the wire the client speaks. * Guides: [Authentication and guards](/framework/docs/authentication-and-guards), [Request IDs and diagnostics](/framework/docs/request-ids-and-diagnostics), [Transactions with $ref](/framework/docs/transactions-with-ref)