better-drizzle

This page is PostgreSQL and MySQL only.

Locking patterns

Claim jobs, lock cursor pages, lock a joined relation, and handle lock timeouts.

These patterns build on row locks. Each one runs inside a transaction, because outside one the lock is released when the statement ends.

Claim a row, then write it

The most common pattern is combining locks with transactions for atomic read-then-write:

await client.transaction(async (tx) => {
	const job = await tx.jobs.findFirst({
		where: { status: 'pending' },
		lock: {
			mode: 'update',
			noWait: true,
		},
	});

	if (!job) return null;

	await tx.jobs.update({
		where: { id: job.id },
		data: { status: 'processing' },
	});

	return job;
});

Lock a cursor page

Locks propagate to cursor pagination, including the internal hasPrevious/hasNext probe queries:

const page = await client.users.cursor({
	where: { active: true },
	lock: {
		mode: 'update',
		skipLocked: true,
	},
	limit: 25,
	after: previousCursor,
});

Lock a row and its to-one relation

Locks work with a single include: { relation: true } on a to-one relation when where also filters that relation with is. The pair compiles to one inner join, so the lock covers both tables:

const posts = await client.posts.findMany({
	where: { published: true, author: { is: { active: true } } },
	include: { author: true },
	lock: 'update',
});

General relation loading (multiple include fields or relation select) is intentionally rejected when lock is present. This avoids silently dropping the lock on the relation query:

// This throws LOCK_NOT_SUPPORTED
const posts = await client.posts.findMany({
	include: { author: true, comments: true },
	lock: 'update',
});

Handle lock timeouts

Lock acquisition failures are normalized to BetterDrizzleError with code LOCK_TIMEOUT:

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

try {
	await client.transaction(async (tx) => {
		return tx.users.findMany({
			where: { id: 1 },
			lock: {
				mode: 'update',
				noWait: true,
			},
		});
	});
} catch (error) {
	if (error instanceof BetterDrizzleError && error.code === BetterDrizzleErrorCode.LockTimeout) {
		console.log('Row is locked by another transaction');
	}
}

On this page