import { isModuleDatabaseError, type ModuleDatabaseError, type ModuleDatabaseParameter, type ModuleDatabaseQuery, } from "@ekklesia/contracts"; /** SQL text and values ready to pass directly to a Module Database ABI v1 executor. */ export type ModuleSqlQuery = Readonly>>; /** * Builds one PostgreSQL statement without ever interpolating a value into its text. * * Every interpolation becomes the next positional parameter. Dynamic identifiers and trusted SQL * fragments are deliberately unsupported in v1: keep identifiers in the static template text. */ export function sql( strings: TemplateStringsArray, ...values: readonly ModuleDatabaseParameter[] ): ModuleSqlQuery { let text = strings[0] ?? ""; for (let index = 0; index < values.length; index += 1) { text += `$${index + 1}${strings[index + 1] ?? ""}`; } return Object.freeze({ text, values: Object.freeze([...values]) }); } export type EkklesiaDatabaseError = Pick< ModuleDatabaseError, "code" | "message" | "name" | "sqlState" >; /** * Finds a stable Ekklesia database error through wrappers such as DrizzleQueryError. * * Callers inspect the returned `code` and `sqlState`; they never need a PostgreSQL driver error, * `.cause.code`, or message parsing. */ export function findEkklesiaDatabaseError(value: unknown): EkklesiaDatabaseError | undefined { const visited = new Set(); let current = value; while (current instanceof Error && !visited.has(current)) { if (isModuleDatabaseError(current)) { return current; } visited.add(current); current = current.cause; } return undefined; }