/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/ban-ts-comment */ import { and, asc, desc, eq } from "drizzle-orm"; import type { PgTableWithColumns } from "drizzle-orm/pg-core"; import type { RelationalQueryBuilder } from "drizzle-orm/pg-core/query-builders/query"; import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"; import type { KnownKeysOnly } from "drizzle-orm/utils"; type CriteriaType = keyof T; const buildWhereClause = ( // biome-ignore lint/suspicious/noExplicitAny: TBD table: PgTableWithColumns, criteria?: Partial, ) => { const conditions = criteria ? Object.keys(criteria) .filter((key) => criteria[key as CriteriaType] !== undefined) .map((key) => { const criteriaType = key as CriteriaType; // biome-ignore lint/style/noNonNullAssertion: TBD return eq(table[criteriaType], criteria[criteriaType]!); }) : []; if (conditions.length === 1) { return conditions[0]; } return and(...conditions); }; export interface OrderBy { sortBy: string; sortOrder: "asc" | "desc"; } // eslint-disable-next-line @typescript-eslint/no-unused-vars const buildOrderByClause = ( // biome-ignore lint/suspicious/noExplicitAny: TBD table: PgTableWithColumns, order?: OrderBy, ) => { if (!order) return; if (order.sortOrder === "asc") { // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return [asc(table[order.sortBy])]; } // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return [desc(table[order.sortBy])]; }; export interface Paging { page: number; pageSize: number; } // biome-ignore lint/suspicious/noExplicitAny: TBD export interface Config extends KnownKeysOnly { criteria?: Partial; order?: OrderBy; paging?: Paging; limit?: number; offset?: number; // biome-ignore lint/suspicious/noExplicitAny: TBD with?: Record; } export interface Page { result: T[]; hasMore: boolean; } const DEFAULT_PAGE_SIZE = 20; export interface IRepository { findFirst(criteria: Partial, config?: Config): Promise; findMany(config?: Config): Promise; findPage(config?: Config): Promise>; create(createWith: TI): Promise; update(id: string, updateWith: TI): Promise; delete(id: string): Promise; } export const createRepo = < TSchema extends Record, // biome-ignore lint/suspicious/noExplicitAny: TBD T extends { [key: string]: any }, // biome-ignore lint/suspicious/noExplicitAny: TBD TI extends { [key: string]: any }, >( db: PostgresJsDatabase, // replace with the appropriate type for db // biome-ignore lint/suspicious/noExplicitAny: TBD table: PgTableWithColumns, // biome-ignore lint/suspicious/noExplicitAny: TBD queryBuilder: RelationalQueryBuilder, ): IRepository => { const queryMany = async ( // biome-ignore lint/suspicious/noExplicitAny: TBD table: PgTableWithColumns, // biome-ignore lint/suspicious/noExplicitAny: TBD queryBuilder: RelationalQueryBuilder, config?: Config, ): Promise => { const where = buildWhereClause(table, config?.criteria); const orderBy = buildOrderByClause(table, config?.order); return queryBuilder.findMany({ where, orderBy, limit: config?.limit, offset: config?.offset, with: config?.with, }) as unknown as Promise; }; return { findFirst: async (criteria: Partial, config?: Config): Promise => { const where = buildWhereClause(table, criteria); return queryBuilder.findFirst({ where, with: config?.with, }) as unknown as Promise; }, findMany: async (config?: Config): Promise => { return queryMany(table, queryBuilder, config); }, findPage: async (config?: Config): Promise> => { const pageSize = config?.paging?.pageSize || DEFAULT_PAGE_SIZE; // Convert to 0-based index for internal calculations. const currentPage = (config?.paging?.page || 1) - 1; // Calculate limit and offset based on page and pageSize. const limit = pageSize + 1; // Add 1 to check for additional items const offset = currentPage * pageSize; const rawResult = await queryMany(table, queryBuilder, { ...config, limit, offset, }); // Check if there are more items and slice the result array if needed. const hasMore = rawResult.length > pageSize; const result: T[] = hasMore ? rawResult.slice(0, -1) : rawResult; return { result, hasMore, }; }, create: async (createWith: TI): Promise => { const inserted = await db.insert(table).values(createWith).returning(); // biome-ignore lint/style/noNonNullAssertion: TBD return inserted.shift()! as T; }, update: async (id: string, updateWith: TI): Promise => { const updated = await db .update(table) .set(updateWith) .where(eq(table.id, id)) .returning(); // biome-ignore lint/style/noNonNullAssertion: TBD return updated.shift()! as T; }, delete: async (id: string): Promise => { const deleteResult = await db .delete(table) .where(eq(table.id, id)) .returning(); // biome-ignore lint/style/noNonNullAssertion: TBD return deleteResult.shift()! as T; }, }; };