# Client

> better() - options, client-level methods, and the values exported from better-drizzle.

URL: https://better-drizzle.com/docs/reference/client

```ts title="relations.ts"
import { defineRelations } from 'drizzle-orm';
import * as schema from './schema';

export const relations = defineRelations(schema, (r) => ({
	users: { posts: r.many.posts() },
	posts: { author: r.one.users({ from: r.posts.authorId, to: r.users.id }) },
}));
```

```ts title="db.ts"
import { better } from 'better-drizzle';
import { drizzle } from 'drizzle-orm/node-postgres';
import { relations } from './relations';

const db = drizzle({ connection: process.env.DATABASE_URL!, relations });

export const client = better(db, options); // options are optional: better(db)
```

`better(db, options?)` wraps a Drizzle database instance and returns a fully typed client. Tables and relations are read from the Drizzle instance (`db._.relations`), so it must be created with `relations`; use `defineRelations(schema)` without a callback when there are no relations. A Drizzle instance without tables makes `better()` throw `No tables found on the Drizzle instance. Pass your relations to drizzle(), e.g. drizzle({ client, relations: defineRelations(schema) }).`

The client's type parameter is the relations config: `BetterDrizzleClient<typeof relations>`.

## Options

```js
{
  	plugins: {
  		description: 'Plugins to extend the client. Run in array order.',
  		type: 'Plugin[]',
  	},
  	hooks: {
  		description: 'Lifecycle hooks for cross-cutting concerns.',
  		type: 'BetterClientHooks',
  	},
  	raw: {
  		description: 'Raw SQL configuration (see below).',
  		type: 'RawClientOptions',
  	},
  	transaction: {
  		description: 'Transaction configuration, e.g. how to handle dialect-unsupported options.',
  		type: '{ unsupportedOptions?: "warn" | "throw" | "ignore" }',
  	},
  	transactions: {
  		description: 'Whether the driver runs transactions. "none" runs transactions and relation writes as separate statements with no rollback on any SQLite driver; Cloudflare D1 is detected automatically (see Cloudflare D1).',
  		type: '"auto" | "none"',
  		default: '"auto"',
  	},
  	locks: {
  		description: 'Row lock configuration, e.g. requiring locked reads to run inside transactions.',
  		type: '{ transactionsOnly?: boolean }',
  	},
  }
```

Many-to-many relations are declared in `defineRelations` with `.through()`; see [relations](/docs/querying/relations#many-to-many-relations).

### `raw` options

```js
{
  	enabled: {
  		description: 'Enable the raw SQL methods on the client.',
  		type: 'boolean',
  		default: 'true',
  	},
  	allowUnsafe: {
  		description: 'Allow calling $rawUnsafe().',
  		type: 'boolean',
  		default: 'false',
  	},
  	requireComment: {
  		description: 'Require every raw call to provide options.comment.',
  		type: 'boolean',
  		default: 'false',
  	},
  	timeoutMs: {
  		description: 'Default timeout applied when a raw call omits timeoutMs.',
  		type: 'number',
  	},
  	unsupportedOptions: {
  		description: 'How unsupported raw options behave.',
  		type: '"warn" | "throw" | "ignore"',
  		default: '"warn"',
  	},
  }
```

When `raw.enabled` is `false`, every raw method throws a `BetterDrizzleError` with code `RAW_DISABLED`. Calling `$rawUnsafe()` without `raw.allowUnsafe: true` throws `RAW_UNSAFE_DISABLED`. With `raw.requireComment`, a call without `options.comment` throws `RAW_COMMENT_REQUIRED`. See [error codes](/docs/reference/errors#error-codes).

## Client methods

Beyond the per-table delegates, the client exposes:

| Method                                 | Description                                                                                      |
| -------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `client.<table>`                       | the [model delegate](/docs/reference/model-api) for each table                                   |
| `repository(name)`                     | resolve a delegate by schema key or DB table name                                                |
| `extends(objectOrFactory)`             | attach app-specific helpers or values to the client                                              |
| `transaction(fn, options?)`            | run `fn` in a [transaction](/docs/advanced/transactions)                                         |
| `$raw(...)`                            | run a [safe raw](/docs/advanced/raw-sql) read, returns rows                                      |
| `$executeRaw(...)`                     | run a safe raw write, returns `{ rowsAffected }`                                                 |
| `$rawUnsafe(query, params?, options?)` | run a raw string query (requires `raw.allowUnsafe`)                                              |
| `$withContext(meta)`                   | clone the client with default [metadata](/docs/guides/multi-tenancy) merged into every operation |

`repository(name)` accepts either the TypeScript schema key or the underlying database table name:

```ts
const usersByKey = client.repository('users');
const usersByDbName = client.repository('app_users');
```

`extends(...)` accepts either a plain object or a callback that receives the bound client:

```ts
const extended = client.extends((client) => ({
	findById(id: number) {
		return client.users.findFirst({ where: { id } });
	},
}));
```

See the full guide at [Client extensions](/docs/guides/client-extensions).

### `$rawUnsafe`

`$rawUnsafe(query, params?, options?)` takes a plain SQL string. Use `?` placeholders for values: each one is replaced by the matching entry of `params` as a bound parameter, in order. It returns rows like `$raw`.

```ts
const client = better(db, { raw: { allowUnsafe: true } });

const rows = await client.$rawUnsafe(
	'select id, name from users where id = ? and active = ?',
	[1, true],
	{ name: 'admin-user-lookup' },
);
```

When `params` is non-empty and the number of `?` placeholders differs from `params.length`, the call throws `RAW_UNSAFE_PLACEHOLDER_MISMATCH` (status `400`) with `details: { params, placeholders }`. Without `params`, the string runs as-is.

### Transaction clients

The client passed to a `transaction(...)` callback also has `rollback(reason?)`, `afterCommit(cb)`, and `afterRollback(cb)`. See [transactions](/docs/advanced/transactions). The root client also exposes `afterCommit` / `afterRollback`, but outside an active transaction they throw `AFTER_COMMIT_OUTSIDE_TRANSACTION` / `AFTER_ROLLBACK_OUTSIDE_TRANSACTION`.

## Exports

```ts
import {
	better,
	definePlugin,
	version,
	// error helpers
	isDatabaseError,
	getDatabaseErrorInfo,
	isUniqueViolation,
	isForeignKeyViolation,
	isNotNullViolation,
	isCheckViolation,
	// errors
	BetterDrizzleError,
	BetterDrizzleErrorCode,
	BetterDrizzleTransactionRollbackError,
} from 'better-drizzle';
```

The library also exports its full type surface (`BetterDrizzleClient`, `BetterDrizzleModelDelegate`, `QueryArgs`, `WhereInput`, `Plugin`, hook context types, and more) for typing your own helpers.

The root and individual plugin entrypoints export the package `version`. Since
they ship in one package, these are always the same value. The
`better-drizzle/plugins` authoring entrypoint exports `definePlugin` only:

```ts
import { version } from 'better-drizzle';
import { version as softDeleteVersion } from 'better-drizzle/soft-delete';

version === softDeleteVersion; // true
```
