Generic base repository abstract class

So, I’m trying to create a base repository class to extend based on my schema definitions, thing is I’m having trouble getting the types to work, here is my example
import type { InferSelectModel, SQL } from 'drizzle-orm';
import type { AnyPgTable } from 'drizzle-orm/pg-core';

import type { DB, DBTransaction } from './client';

export interface RepositoryOptions {
db?: DB;
tx?: DBTransaction;
}

/**
* A generic base repository class for Drizzle ORM
*
* Note: This implementation uses type assertions in some places to work
* around limitations in TypeScript's ability to fully type the Drizzle ORM.
* The public API is fully typed, but internal implementations may use 'any'.
*/
export class BaseRepository<
TTable extends AnyPgTable,
TSelect = InferSelectModel<TTable>,
> {
protected table: TTable;
protected db: DB | DBTransaction;
constructor(table: TTable, db: DB | DBTransaction) {
this.table = table;
this.db = db;
}

/**
* Find all records matching the given criteria
*/
async findAll(
options: {
limit?: number;
offset?: number;
orderBy?: SQL<unknown>;
where?: SQL<unknown>;
} = {},
): Promise<TSelect[]> {
const { limit, offset, orderBy, where } = options;

// Using any to avoid complex type issues with the query builder
let query = this.db.select().from(this.table).$dynamic();

if (where) {
query = query.where(where);
}

if (orderBy) {
query = query.orderBy(orderBy);
}

if (limit) {
query = query.limit(limit);
}

if (offset) {
query = query.offset(offset);
}

return query;
}
}
import type { InferSelectModel, SQL } from 'drizzle-orm';
import type { AnyPgTable } from 'drizzle-orm/pg-core';

import type { DB, DBTransaction } from './client';

export interface RepositoryOptions {
db?: DB;
tx?: DBTransaction;
}

/**
* A generic base repository class for Drizzle ORM
*
* Note: This implementation uses type assertions in some places to work
* around limitations in TypeScript's ability to fully type the Drizzle ORM.
* The public API is fully typed, but internal implementations may use 'any'.
*/
export class BaseRepository<
TTable extends AnyPgTable,
TSelect = InferSelectModel<TTable>,
> {
protected table: TTable;
protected db: DB | DBTransaction;
constructor(table: TTable, db: DB | DBTransaction) {
this.table = table;
this.db = db;
}

/**
* Find all records matching the given criteria
*/
async findAll(
options: {
limit?: number;
offset?: number;
orderBy?: SQL<unknown>;
where?: SQL<unknown>;
} = {},
): Promise<TSelect[]> {
const { limit, offset, orderBy, where } = options;

// Using any to avoid complex type issues with the query builder
let query = this.db.select().from(this.table).$dynamic();

if (where) {
query = query.where(where);
}

if (orderBy) {
query = query.orderBy(orderBy);
}

if (limit) {
query = query.limit(limit);
}

if (offset) {
query = query.offset(offset);
}

return query;
}
}
8 Replies
Monopolo11
Monopolo11OP6mo ago
It looks like it should work but I get the following error in the this.table when creating the select
Argument of type 'TTable' is not assignable to parameter of type 'TableLikeHasEmptySelection<TTable> extends true ? DrizzleTypeError<"Cannot reference a data-modifying statement subquery if it doesn't contain a `returning` clause"> : TTable'.
Type 'PgTable<Required<{ schema: string | undefined; name: string; dialect: string; columns: Record<string, PgColumn<ColumnBaseConfig<ColumnDataType, string>, {}, {}>>; }>>' is not assignable to type 'TableLikeHasEmptySelection<TTable> extends true ? DrizzleTypeError<"Cannot reference a data-modifying statement subquery if it doesn't contain a `returning` clause"> : TTable'.
Argument of type 'TTable' is not assignable to parameter of type 'TableLikeHasEmptySelection<TTable> extends true ? DrizzleTypeError<"Cannot reference a data-modifying statement subquery if it doesn't contain a `returning` clause"> : TTable'.
Type 'PgTable<Required<{ schema: string | undefined; name: string; dialect: string; columns: Record<string, PgColumn<ColumnBaseConfig<ColumnDataType, string>, {}, {}>>; }>>' is not assignable to type 'TableLikeHasEmptySelection<TTable> extends true ? DrizzleTypeError<"Cannot reference a data-modifying statement subquery if it doesn't contain a `returning` clause"> : TTable'.
Also for reference, this is how I’m getting the DB and DBTransaction types
export const db = drizzle({
client: psClient,
schema,
casing: "snake_case",
});

export type DBTransaction = Parameters<Parameters<typeof db.transaction>[0]>[0];
export type DB = typeof db;
export const db = drizzle({
client: psClient,
schema,
casing: "snake_case",
});

export type DBTransaction = Parameters<Parameters<typeof db.transaction>[0]>[0];
export type DB = typeof db;
Any clues or examples on how to achieve this?
teo.villanueva
teo.villanueva3mo ago
Hey! I'm experiencing the same type issue, did you manage to solve this?
dfrn
dfrn3mo ago
I generally do this:
No description
JustWayne
JustWayne3mo ago
@dfrn That's good for the base client type, but I think the problem is more surrounding the type to use for TTable.
JustWayne
JustWayne3mo ago
I still have to use my own Tbl* types shown here if I switch to that DrizzleClientType definition which is shown here as the DbClient type and modified for Neon postgres (see attached).
JustWayne
JustWayne3mo ago
The point being, I still have to do 2 casts here, one for the table and one for the result.
const result = (await db
.select(...selecting)
.from(table as any)
.where(and(...conditions))
.limit(1)) as SelectResult<R, "single", {}>[];
const result = (await db
.select(...selecting)
.from(table as any)
.where(and(...conditions))
.limit(1)) as SelectResult<R, "single", {}>[];
Monopolo11
Monopolo11OP2mo ago
Not sure if this is what you are looking for. https://discord.com/channels/1043890932593987624/1372028149121945641/1372376611651059803 but that is what I needed in this thread

Did you find this page helpful?