import { and, eq, gt, gte, lt, lte, ne } from 'drizzle-orm' import { type NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres' import { PgTimestampString } from 'drizzle-orm/pg-core' import type { AnyRelations, EmptyRelations, InferInsertModel, SQL } from 'drizzle-orm' import type { PgTable } from 'drizzle-orm/pg-core' import type { Pool } from 'pg' let patched = false function patchTimestampForZero() { if (patched) return patched = true const orig = PgTimestampString.prototype.mapToDriverValue PgTimestampString.prototype.mapToDriverValue = function (value: any) { if (typeof value === 'number') return new Date(value).toISOString() return orig.call(this, value) } } const operators = { eq, ne, lt, lte, gt, gte } as const type Op = keyof typeof operators type WhereOp = Partial> type WhereValue = string | number | boolean | null | WhereOp type WithSQL = { [K in keyof T]: T[K] | SQL } type TableName = { [K in keyof TSchema]: TSchema[K] extends PgTable ? K : never }[keyof TSchema] type WhereClause> = Partial< Record< keyof InferInsertModel & string, WhereValue > > type InsertMap = { [K in TableName]: Partial< WithSQL> > } function resolveWhere(table: any, where: Record) { const conditions = Object.entries(where).map(([key, value]) => { if (value !== null && typeof value === 'object') { const entries = Object.entries(value) as [Op, any][] if (entries.length === 1) { return operators[entries[0]![0]](table[key], entries[0]![1]) } return and(...entries.map(([op, v]) => operators[op](table[key], v)))! } return eq(table[key], value) }) return conditions.length === 1 ? conditions[0]! : and(...conditions)! } export type CreateDrizzleOptions< TSchema extends Record, TRelations extends AnyRelations = EmptyRelations, > = { pool: Pool schema: TSchema relations?: TRelations } type ExtractTable> = TSchema[T] extends PgTable ? TSchema[T] : never export type DrizzleHelpers< TSchema extends Record = Record, TDb extends NodePgDatabase = NodePgDatabase, > = { insertRow>( tableName: T, values: InsertMap[T] ): ReturnType['values']> updateRow>( tableName: T, where: WhereClause, set: InsertMap[T] ): ReturnType['set']>['where']> deleteRow>( tableName: T, where: WhereClause ): ReturnType['where']> } export function createDrizzle< TSchema extends Record, TRelations extends AnyRelations = EmptyRelations, >( options: CreateDrizzleOptions ): NodePgDatabase & DrizzleHelpers> { patchTimestampForZero() // drizzle v1 overloads don't support generic schema+relations together at the call site const instance = drizzle({ client: options.pool, schema: options.schema as Record, relations: options.relations, logger: process.env.DEBUG_SQL === '1', } as any) as unknown as NodePgDatabase const helpers: DrizzleHelpers> = { insertRow(tableName, values) { const table = options.schema[tableName] as any return instance.insert(table).values(values as Record) }, updateRow(tableName, where, set) { const table = options.schema[tableName] as any return instance .update(table) .set(set as Record) .where(resolveWhere(table, where as Record)) }, deleteRow(tableName, where) { const table = options.schema[tableName] as any return instance .delete(table) .where(resolveWhere(table, where as Record)) }, } return Object.assign(instance, helpers) }