1import 'server-only';2import { sql, type SQL } from 'drizzle-orm';3import { getDb } from '@rareindex/database';45/** Run a raw SQL statement and return typed rows (drizzle `execute` returns row arrays for postgres-js). */6export async function rows<T extends Record<string, unknown>>(query: SQL): Promise<T[]> {7 const res = await getDb().execute(query);8 return res as unknown as T[];9}1011export async function one<T extends Record<string, unknown>>(query: SQL): Promise<T | null> {12 const r = await rows<T>(query);13 return r[0] ?? null;14}1516export function num(v: unknown): number | null {17 if (v === null || v === undefined || v === '') return null;18 const n = Number(v);19 return Number.isFinite(n) ? n : null;20}2122export function int(v: unknown): number {23 const n = Number(v);24 return Number.isFinite(n) ? Math.trunc(n) : 0;25}2627export function str(v: unknown): string | null {28 return v === null || v === undefined ? null : String(v);29}3031export function date(v: unknown): Date | null {32 if (!v) return null;33 const d = v instanceof Date ? v : new Date(String(v));34 return Number.isNaN(d.getTime()) ? null : d;35}3637export function joinAnd(parts: SQL[]): SQL {38 return parts.length ? sql.join(parts, sql` AND `) : sql`true`;39}4041/** Postgres text[] literal from a JS string array (drizzle expands bare arrays into tuples, not arrays). */42export function textArray(values: string[]): SQL {43 if (!values.length) return sql`ARRAY[]::text[]`;44 return sql`ARRAY[${sql.join(values.map((v) => sql`${v}`), sql`, `)}]::text[]`;45}4647/** ISO string for timestamp params (postgres-js `execute` does not serialise Date instances). */48export function iso(d: Date): string {49 return d.toISOString();50}5152export function clampPage(page: unknown, size = 50): { limit: number; offset: number; page: number } {53 const p = Math.max(1, Math.trunc(Number(page) || 1));54 return { limit: size, offset: (p - 1) * size, page: p };55}5657export { sql };58