better-drizzle
Reference

Error helpers

BetterDrizzleError, its error codes, and cross-dialect constraint detection helpers.

better-drizzle exports helpers that normalize database errors across PostgreSQL, SQLite, and MySQL, so you can classify failures without string-matching driver messages. See error handling for end-to-end usage.

import {
	isDatabaseError,
	getDatabaseErrorInfo,
	isUniqueViolation,
	isForeignKeyViolation,
	isNotNullViolation,
	isCheckViolation,
} from 'better-drizzle';

Predicates

Each returns a boolean and accepts an optional constraint name (or column name, for isNotNullViolation) to match a specific one.

FunctionSignature
isDatabaseError(error: unknown) => boolean
isUniqueViolation(error: unknown, constraint?: string) => boolean
isForeignKeyViolation(error: unknown, constraint?: string) => boolean
isNotNullViolation(error: unknown, column?: string) => boolean
isCheckViolation(error: unknown, constraint?: string) => boolean
try {
	await client.users.create({ data });
} catch (error) {
	if (isUniqueViolation(error, 'users_email_unique')) {
		// handle duplicate email specifically
	}
	throw error;
}

getDatabaseErrorInfo

Parses any caught error into a normalized object, regardless of driver:

const info = getDatabaseErrorInfo(error);

Prop

Type

BetterDrizzleError

Every error thrown by better-drizzle itself is a BetterDrizzleError (it extends Error). Database failures that surface through an operation are normalized into the same shape.

import { BetterDrizzleError, BetterDrizzleErrorCode } from 'better-drizzle';

try {
	await client.users.findUnique({ where: { id: 1 } }).throw();
} catch (error) {
	if (
		BetterDrizzleError.is(error) &&
		error.code === BetterDrizzleErrorCode.ResultNotFound
	) {
		// error.status === 404
	}
	throw error;
}

Fields

Prop

Type

Static helpers

HelperDescription
BetterDrizzleError.is(error)Type guard, same as error instanceof BetterDrizzleError.
BetterDrizzleError.from(error, overrides?)Normalizes any value. An existing BetterDrizzleError is cloned with the overrides applied. A value that looks like a database error (an object with a string message, code, or sqlState, or a numeric errno, which includes plain Error instances) becomes DATABASE_ERROR with the parsed driver, constraint, table, and column. Anything else becomes UNKNOWN. The original value is kept as cause.
BetterDrizzleError.fromDatabaseError(error, overrides?)Always parses the value with getDatabaseErrorInfo and returns DATABASE_ERROR (unless overrides.code is set), with the parsed info under details.database.

Instances also have withCause(cause) and withDetails(details), which return a new error (details are shallow-merged), and toJSON(), which returns a plain object with every field except cause.

try {
	await db.insert(users).values(data);
} catch (error) {
	throw BetterDrizzleError.fromDatabaseError(error, {
		operation: 'create',
		table: 'users',
	});
}

Error codes

BetterDrizzleErrorCode is an exported string enum. The status column is the default status for each code; any code not given a specific status defaults to 500.

AreaCodeStatusMeaning
ResultsRESULT_NOT_FOUND404.throw() found no matching row.
ResultsREPOSITORY_NOT_FOUND500repository(name) matched no schema key or table name.
DatabaseDATABASE_ERROR500The database rejected the statement (unique, foreign key, not null, and so on).
DatabaseDIALECT_INFERENCE_FAILED500The SQL dialect could not be inferred from the Drizzle client.
DatabaseJSONB_QUERY_UNSUPPORTED400A JSONB path filter was used on a dialect other than PostgreSQL.
OperationsOPERATION_ERROR500An operation received invalid input or failed, for example an invalid batchSize or a bad cursor.
OperationsUNKNOWN500An uncategorized error.
HooksHOOK_ERROR500A lifecycle hook threw.
PluginsPLUGIN_DIALECT_UNSUPPORTED500A plugin does not support the current dialect.
PluginsPLUGIN_DUPLICATE_ID500Two plugins share the same id.
PluginsPLUGIN_EXTENSION_CONFLICT500Two plugins or extensions add the same client property.
PluginsPLUGIN_OPERATION_ARG_CONFLICT500Two plugins add the same operation arg field.
PluginsPLUGIN_REQUIRED_COLUMN_MISSING500A model lacks a column a plugin requires.
PluginsPLUGIN_REQUIRED_COLUMN_TYPE500A required column exists with a different Drizzle type.
Raw SQLRAW_DISABLED500Raw SQL is disabled with raw.enabled: false.
Raw SQLRAW_UNSAFE_DISABLED500$rawUnsafe() was called without raw.allowUnsafe: true.
Raw SQLRAW_COMMENT_REQUIRED400raw.requireComment is on and the call has no options.comment.
Raw SQLRAW_INVALID_QUERY400$raw / $executeRaw got something other than a tagged template or Drizzle SQL object.
Raw SQLRAW_UNSAFE_PLACEHOLDER_MISMATCH400The ? placeholder count does not match the params count.
Raw SQLRAW_UNSUPPORTED_OPTION400A raw option is not supported by the dialect.
Raw SQLRAW_TIMEOUT408A raw query (or .explain() with timeoutMs) timed out.
Raw SQLRAW_ABORTED408A raw query was aborted through its AbortSignal.
TransactionsTRANSACTION_ROLLBACK409The transaction was rolled back with tx.rollback().
TransactionsTRANSACTION_TIMEOUT408The transaction exceeded timeoutMs.
TransactionsTRANSACTION_ABORTED408The transaction was aborted through its AbortSignal.
TransactionsTRANSACTION_UNSUPPORTED_OPTION400A transaction option is not supported by the dialect.
TransactionsTRANSACTIONS_UNSUPPORTED500The Drizzle client does not support transactions.
TransactionsAFTER_COMMIT_OUTSIDE_TRANSACTION500afterCommit() was called outside a transaction.
TransactionsAFTER_ROLLBACK_OUTSIDE_TRANSACTION500afterRollback() was called outside a transaction.
LocksLOCK_NOT_SUPPORTED400The dialect or query shape does not support the requested row lock.
LocksLOCK_REQUIRES_TRANSACTION400locks.transactionsOnly: true is set and the locked read ran outside a transaction.
LocksLOCK_TIMEOUT409The lock could not be acquired in time, including NOWAIT failures.
InternalTABLE_RUNTIME_NOT_FOUND500Internal: table metadata is missing.
InternalTRANSACTION_LIFECYCLE_STATE_MISSING500Internal: transaction lifecycle state is missing.
InternalTRANSACTION_RUNTIME_NOT_INITIALIZED500Internal: the transaction runtime was not initialized.

BetterDrizzleTransactionRollbackError

Thrown when a transaction is explicitly rolled back via tx.rollback(reason). It extends BetterDrizzleError with code TRANSACTION_ROLLBACK (status 409). The reason you pass is available as error.reason (also as cause and details.reason), and a non-empty string reason becomes the message:

import { BetterDrizzleTransactionRollbackError } from 'better-drizzle';

try {
	await client.transaction(async (tx) => {
		tx.rollback('duplicate email');
	});
} catch (error) {
	if (error instanceof BetterDrizzleTransactionRollbackError) {
		console.log(error.reason); // 'duplicate email'
	}
}

On this page