@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.
What @aventara/client 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 |
// 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
| 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. |
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.<mode>.local, .env.<mode>, .env.local, .env. mode is NODE_ENV, or development. process.env is never written.
Errors, one sentence and exit code 1 (verified):
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
avclient init [options] Set up this frontend and generate.
avclient generate [--yes] Fetch <entrypoint>/_contract and write AvClient.ts and generated/.
avclient --help Print usage, exit 0.
avclient <command> --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. 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
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), and you import AvClient the same way (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
| 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). |
AvClient | class | new AvClient(options?). |
avLazy | function | avLazy(() => call): makes the call on its first then, catch or finally, once. See Operation Sets. |
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. |
| 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. |
AvTransportError | class | |
AvCause, AvValidationIssue, AvOperationCode, AvValidationCode | types |
AvClient
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<unknown> }, options?: { settle?: boolean; signal?: AbortSignal }): Promise</* one value per key */>;
// plus one property per Resource:
readonly [Resource]: { [family]: { [variant]: (args?, options?: AvCallOptions) => Promise<Result> } };
}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.<Resource>.<family>.<variant>(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.
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, 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 <name>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
| 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.
const client = new AvClient({
fetch: (input, init) =>
fetch(input, { ...init, headers: { ...init?.headers, Authorization: `Bearer ${token}` } }),
});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<string, string> | 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. |
await avClient.User.find.count({}, { requestId: "from-client-1", signal });AvFetch
typeof globalThis.fetch when your TypeScript lib declares one (DOM, @types/node); otherwise a structural (url, init) => Promise<response> type. The platform fetch therefore needs no cast.
Transactions
When the contract advertises interactive transactions the client has tx and transaction; otherwise neither exists.
| Member | Signature | Meaning |
|---|---|---|
avClient.tx.<R>.<family>.<variant>(args) | returns a handle (AvOperation) | Builds a deferred step; sends nothing. A handle's selected fields expose $ref("field"). |
avClient.transaction(steps, options?) | Promise<tuple of results> | Sends the plan once; resolves one typed result per handle, in order. A failing step throws its error with cause.operation set. |
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); 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). See Transactions.
Error classes
class AvError<C extends AvOperationErrorCode = AvOperationErrorCode> 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<string, unknown>; // 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, 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.
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.
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
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.
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. Usenew AvClient()there, or a bundler/ESM setup.
See also
- Frontend client, CLI reference, Error codes
- Protocol API reference: the wire the client speaks.
- Guides: Authentication and guards, Request IDs and diagnostics, Transactions with $ref