Client
better() - options, client-level methods, and the values exported from better-drizzle.
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 }) },
}));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
Prop
Type
Many-to-many relations are declared in defineRelations with .through(); see relations.
raw options
Prop
Type
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.
Client methods
Beyond the per-table delegates, the client exposes:
| Method | Description |
|---|---|
client.<table> | the model delegate 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 |
$raw(...) | run a safe raw 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 merged into every operation |
repository(name) accepts either the TypeScript schema key or the underlying database table name:
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:
const extended = client.extends((client) => ({
findById(id: number) {
return client.users.findFirst({ where: { id } });
},
}));See the full guide at 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.
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. The root client also exposes afterCommit / afterRollback, but outside an active transaction they throw AFTER_COMMIT_OUTSIDE_TRANSACTION / AFTER_ROLLBACK_OUTSIDE_TRANSACTION.
Exports
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:
import { version } from 'better-drizzle';
import { version as softDeleteVersion } from 'better-drizzle/soft-delete';
version === softDeleteVersion; // true