import "server-only"; import { and, asc, desc, eq, gt, gte, inArray, isNotNull, isNull, like, lt, lte, ne, not, notInArray, or, SQL, sql, } from "drizzle-orm"; import { createTableRelationsHelpers, One } from "drizzle-orm/relations"; import * as relationsDefs from "@/db/relations"; import * as schema from "@/db/schema"; import { db } from "@/lib/db"; type Client = typeof db; const FULL = { ...schema, ...relationsDefs } as Record; const COLUMNS_SYM = Symbol.for("drizzle:Columns"); function capitalize(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1); } function lowerFirst(s: string): string { return s.charAt(0).toLowerCase() + s.slice(1); } function getTable(model: string): any { const table = FULL[capitalize(model)]; if (!table) { throw new Error(`[prisma] unknown model "${model}"`); } return table; } function tableName(table: any): string { return table[Symbol.for("drizzle:Name")] ?? "?"; } function tableColumns(table: any): Record { return table[COLUMNS_SYM] ?? table; } function getRelations(table: any): Record { const relDef = FULL[`${lowerFirst(tableName(table))}Relations`]; if (!relDef) return {}; return relDef.config(createTableRelationsHelpers(table)); } function coerceValue(col: any, value: any): any { if (value === null || value === undefined) return value; if (typeof value === "object" && typeof value.toNumber === "function") { return Number(value.toNumber()); } if (col?.dataType === "bigint") { if (typeof value === "number") return BigInt(Math.trunc(value)); if (typeof value === "string" && /^-?\d+$/.test(value)) { return BigInt(value); } } return value; } function coerceArray(col: any, values: unknown[]): unknown[] { return values.map((v) => coerceValue(col, v)); } function buildFieldCondition(col: any, value: any): SQL { if (value === null) return isNull(col); if (typeof value !== "object") return eq(col, coerceValue(col, value)); const parts: SQL[] = []; for (const [op, v] of Object.entries(value)) { switch (op) { case "equals": parts.push(eq(col, coerceValue(col, v))); break; case "not": if (v === null) parts.push(isNotNull(col)); else if (typeof v === "object") parts.push(not(buildFieldCondition(col, v))); else parts.push(ne(col, coerceValue(col, v))); break; case "in": parts.push(inArray(col, coerceArray(col, v as unknown[]))); break; case "notIn": parts.push(notInArray(col, coerceArray(col, v as unknown[]))); break; case "lt": parts.push(lt(col, coerceValue(col, v))); break; case "lte": parts.push(lte(col, coerceValue(col, v))); break; case "gt": parts.push(gt(col, coerceValue(col, v))); break; case "gte": parts.push(gte(col, coerceValue(col, v))); break; case "contains": parts.push(like(col, `%${v}%`)); break; case "startsWith": parts.push(like(col, `${v}%`)); break; case "endsWith": parts.push(like(col, `%${v}`)); break; case "mode": case "search": // MySQL collations are case-insensitive by default; full-text not used. break; default: break; } } return parts.length === 1 ? parts[0] : (and(...(parts as [SQL, ...SQL[]])) as SQL); } function buildCondition(table: any, where: any): SQL | undefined { if (where === undefined || where === null) return undefined; const cols = tableColumns(table); const conds: SQL[] = []; for (const [key, value] of Object.entries(where)) { if (key === "AND") { const list = Array.isArray(value) ? value : [value]; const inner = list .map((w: any) => buildCondition(table, w)) .filter((w): w is SQL => Boolean(w)); if (inner.length) conds.push(and(...(inner as [SQL, ...SQL[]])) as SQL); } else if (key === "OR") { const list = Array.isArray(value) ? value : [value]; const inner = list .map((w: any) => buildCondition(table, w)) .filter((w): w is SQL => Boolean(w)); if (inner.length) conds.push(or(...(inner as [SQL, ...SQL[]])) as SQL); } else if (key === "NOT") { const inner = Array.isArray(value) ? and( ...value .map((w: any) => buildCondition(table, w)) .filter((w): w is SQL => Boolean(w)), ) : buildCondition(table, value); if (inner) conds.push(not(inner)); } else { const col = cols[key]; if (!col) { // Compound unique key, e.g. `eventId_userId`. const parts = key.split("_"); if (parts.length > 1 && parts.every((p) => cols[p])) { conds.push( and( ...(parts.map((p) => eq( (cols as Record)[p], coerceValue( (cols as Record)[p], (value as any)?.[p], ), ), ) as [SQL, ...SQL[]]), ) as SQL, ); } else { throw new Error( `[prisma] unknown field "${key}" on model "${tableName(table)}"`, ); } continue; } conds.push(buildFieldCondition(col, value)); } } if (conds.length === 0) return undefined; return conds.length === 1 ? conds[0] : and(...conds); } function buildOrderBy(table: any, orderBy: any): SQL[] { if (!orderBy) return []; const entries = Array.isArray(orderBy) ? orderBy : [orderBy]; const cols = tableColumns(table); return entries.flatMap((entry: any) => { if (typeof entry === "string") { const col = cols[entry]; return col ? [asc(col)] : []; } return Object.entries(entry).flatMap(([key, dir]) => { if (key === "_count") return []; const col = cols[key]; if (!col) return []; return dir === "desc" ? [desc(col)] : [asc(col)]; }); }); } function projection(table: any, select: any): Record | undefined { if (!select || typeof select !== "object") return undefined; const cols = tableColumns(table); const out: Record = {}; for (const [key, enabled] of Object.entries(select)) { if (key === "_count") continue; if (enabled && cols[key]) out[key] = cols[key]; } return Object.keys(out).length ? out : undefined; } function pickRow(row: any, select: any): any { if (!select || typeof select !== "object") return row; const out: any = {}; for (const [key, enabled] of Object.entries(select)) { if (enabled && key in row) out[key] = row[key]; } return out; } interface Join { fkCols: any[]; // columns on the *target* used to filter/group refCols: any[]; // columns on the *source* used to match } function resolveJoin(table: any, rel: any): Join { if (rel instanceof One) { const cfg = rel.config ?? { fields: [], references: [] }; return { fkCols: cfg.references, refCols: cfg.fields }; } if (rel.config?.fields && rel.config.references) { return { fkCols: rel.config.fields, refCols: rel.config.references }; } // Implicit many: find the reverse one-relation on the target. const targetRels = getRelations(rel.referencedTable); for (const r of Object.values(targetRels)) { if (r instanceof One && r.referencedTable === table) { const cfg = r.config ?? { fields: [], references: [] }; return { fkCols: cfg.fields, refCols: cfg.references }; } } throw new Error( `[prisma] cannot resolve join for relation "${rel.fieldName}" on "${tableName(table)}"`, ); } function colKey(col: any): string { return col.key ?? col.name; } async function queryRelationRows( client: Client, table: any, rel: any, opts: any, keyValues: unknown[], mode: "one" | "many", ): Promise> { const join = resolveJoin(table, rel); const byCol = join.fkCols[0]; const parts = [ inArray(byCol, coerceArray(byCol, keyValues)), buildCondition(rel.referencedTable, opts.where), ].filter(Boolean); const condition = parts.length === 1 ? parts[0] : and(...parts); const sel = projection(rel.referencedTable, opts.select); const orderBy = buildOrderBy(rel.referencedTable, opts.orderBy); let stmt: any = sel ? client.select({ ...sel, __fk: byCol }).from(rel.referencedTable) : client.select().from(rel.referencedTable); if (condition) stmt = stmt.where(condition); if (orderBy.length) stmt = stmt.orderBy(...orderBy); const rows = (await stmt) as any[]; const map = new Map(); for (const row of rows) { const key = String(row.__fk ?? row[colKey(byCol)]); const cleaned = sel ? pickRow(row, opts.select) : row; if (mode === "many") { const list = map.get(key); if (Array.isArray(list)) list.push(cleaned); else map.set(key, [cleaned]); } else if (!map.has(key)) { map.set(key, cleaned); } } return map; } function attachCounts( rows: any[], table: any, rel: any, counts: Map, ): void { const join = resolveJoin(table, rel); const refCol = join.refCols[0]; for (const row of rows) { if (!row._count) row._count = {}; const refValue = row[colKey(refCol)]; row._count[rel.fieldName] = refValue == null ? 0 : (counts.get(String(refValue)) ?? 0); } } async function runCounts( client: Client, table: any, rows: any[], countSpec: Record, ): Promise { const rels = getRelations(table); for (const [relName, enabled] of Object.entries(countSpec)) { if (!enabled) continue; const rel = rels[relName]; if (!rel) continue; const join = resolveJoin(table, rel); const refCol = join.refCols[0]; const values = rows .map((r) => r[colKey(refCol)]) .filter((v) => v !== null && v !== undefined); const counts = new Map(); if (values.length > 0) { const byCol = join.fkCols[0]; const grouped = (await client .select({ __fk: byCol, __c: sql`COUNT(*)`, }) .from(rel.referencedTable) .where(inArray(byCol, coerceArray(byCol, values))) .groupBy(byCol)) as any[]; for (const g of grouped) counts.set(String(g.__fk), Number(g.__c)); } attachCounts(rows, table, rel, counts); } } async function attachIncludes( client: Client, table: any, rows: any[], includes: any, ): Promise { if (!includes || typeof includes !== "object" || rows.length === 0) return; const rels = getRelations(table); const oneJobs: Array<{ rel: any; opts: any; values: unknown[] }> = []; const manyJobs: Array<{ rel: any; opts: any; values: unknown[] }> = []; const countSpec: Record = {}; for (const [name, optsRaw] of Object.entries(includes)) { if (name === "_count") { if ( optsRaw !== null && typeof optsRaw === "object" && (optsRaw as any).select ) { Object.assign(countSpec, (optsRaw as any).select); } continue; } const rel = rels[name]; if (!rel) continue; const opts = optsRaw === true ? {} : (optsRaw ?? {}); const join = resolveJoin(table, rel); const refCol = join.refCols[0]; const values = rows .map((r) => r[colKey(refCol)]) .filter((v) => v !== null && v !== undefined); if (rel instanceof One) { oneJobs.push({ rel, opts, values }); } else { manyJobs.push({ rel, opts, values }); } } for (const job of oneJobs) { const { rel, opts, values } = job; if (values.length === 0) { for (const row of rows) row[rel.fieldName] = null; continue; } const map = await queryRelationRows( client, table, rel, opts, values, "one", ); const join = resolveJoin(table, rel); const refCol = join.refCols[0]; const related: any[] = []; for (const row of rows) { const refValue = row[colKey(refCol)]; const hit = refValue == null ? null : (map.get(String(refValue)) ?? null); row[rel.fieldName] = hit; if (hit) related.push(hit); } if (opts.include && related.length > 0) { await attachIncludes(client, rel.referencedTable, related, opts.include); } } for (const job of manyJobs) { const { rel, opts, values } = job; if (values.length === 0) { for (const row of rows) row[rel.fieldName] = []; continue; } const map = await queryRelationRows( client, table, rel, opts, values, "many", ); const join = resolveJoin(table, rel); const refCol = join.refCols[0]; const related: any[] = []; for (const row of rows) { const refValue = row[colKey(refCol)]; const list = refValue == null ? [] : (map.get(String(refValue)) ?? []); row[rel.fieldName] = list; for (const item of list) related.push(item); } if (opts.include && related.length > 0) { await attachIncludes(client, rel.referencedTable, related, opts.include); } } if (Object.keys(countSpec).length) { await runCounts(client, table, rows, countSpec); } } // ---------- Query execution ---------- interface ReadArgs { where?: any; select?: any; include?: any; orderBy?: any; take?: number; skip?: number; } async function runFindMany( client: Client, model: string, args: ReadArgs, ): Promise { const table = getTable(model); const condition = buildCondition(table, args.where); const orderBy = buildOrderBy(table, args.orderBy); const sel = projection(table, args.select); let stmt: any = sel ? client.select({ ...sel }).from(table) : client.select().from(table); if (condition) stmt = stmt.where(condition); if (orderBy.length) stmt = stmt.orderBy(...orderBy); if (args.take !== undefined) stmt = stmt.limit(args.take); else if (args.skip) // biome-ignore lint/correctness/noPrecisionLoss: SQL literal for BIGINT UNSIGNED max value (2^64-1 — exceeds JS number precision) stmt = stmt.limit(18446744073709551615); if (args.skip) stmt = stmt.offset(args.skip); const rows = (await stmt) as any[]; await attachIncludes(client, table, rows, args.include); if (args.select && typeof args.select === "object" && args.select._count) { const countSpec: Record = {}; for (const [relName, enabled] of Object.entries( args.select._count.select ?? {}, )) { if (enabled) countSpec[relName] = true; } if (Object.keys(countSpec).length) { await runCounts(client, table, rows, countSpec); } const projected = rows.map((row) => pickRow(row, args.select)); return projected; } return rows; } async function runFindOne( client: Client, model: string, args: ReadArgs, orThrow: boolean, ): Promise { const rows = await runFindMany(client, model, { ...args, take: 1 }); if (rows.length === 0) { if (orThrow) throw new Error(`[prisma] ${model} not found`); return null; } return rows[0]; } // ---------- Mutations ---------- function pkColumns(table: any): any[] { const cols = tableColumns(table); return Object.values(cols).filter((c) => c.primary); } function coerceData(table: any, data: any): Record { const cols = tableColumns(table); const coerced: Record = {}; for (const [key, value] of Object.entries(data ?? {})) { coerced[key] = coerceValue(cols[key], value); } return coerced; } async function runCreate( client: Client, model: string, args: any, ): Promise { const table = getTable(model); const [result] = (await client .insert(table) .values(coerceData(table, args.data))) as any; const pks = pkColumns(table); const where: any = {}; if (pks.length === 1 && pks[0].autoIncrement) { where[colKey(pks[0])] = result.insertId; } else { for (const pk of pks) { where[colKey(pk)] = args.data?.[colKey(pk)]; } } return runFindOne( client, model, { where, select: args.select, include: args.include }, false, ); } async function runUpdate( client: Client, model: string, args: any, ): Promise { const table = getTable(model); const condition = buildCondition(table, args.where); if (condition) { await client .update(table) .set(coerceData(table, args.data)) .where(condition); } return runFindOne( client, model, { where: args.where, select: args.select, include: args.include }, false, ); } async function runDelete( client: Client, model: string, args: any, ): Promise { const table = getTable(model); const row = await runFindOne(client, model, { where: args.where }, false); if (!row) return null; const condition = buildCondition(table, args.where); if (condition) { await client.delete(table).where(condition); } return row; } async function runDeleteMany( client: Client, model: string, args: any, ): Promise<{ count: number }> { const table = getTable(model); const condition = buildCondition(table, args.where); if (!condition) { const [result] = (await client.delete(table)) as any; return { count: Number(result.affectedRows) }; } const [result] = (await client.delete(table).where(condition)) as any; return { count: Number(result.affectedRows) }; } async function runUpdateMany( client: Client, model: string, args: any, ): Promise<{ count: number }> { const table = getTable(model); const condition = buildCondition(table, args.where); const [result] = condition ? ((await client .update(table) .set(coerceData(table, args.data)) .where(condition)) as any) : ((await client.update(table).set(coerceData(table, args.data))) as any); return { count: Number(result.affectedRows) }; } async function runCreateMany( client: Client, model: string, args: any, ): Promise<{ count: number }> { const table = getTable(model); const rowsData = Array.isArray(args.data) ? args.data : [args.data]; const values = rowsData.map((data: any) => coerceData(table, data)); const [result] = (await client.insert(table).values(values)) as any; return { count: Number(result.affectedRows) }; } async function runUpsert( client: Client, model: string, args: any, ): Promise { const existing = await runFindOne( client, model, { where: args.where }, false, ); if (existing) { return runUpdate(client, model, { where: args.where, data: args.update, select: args.select, include: args.include, }); } return runCreate(client, model, { data: args.create, select: args.select, include: args.include, }); } async function runCount( client: Client, model: string, args: any, ): Promise { const table = getTable(model); const condition = buildCondition(table, args.where); let stmt: any = client.select({ __c: sql`COUNT(*)` }).from(table); if (condition) stmt = stmt.where(condition); const [row] = (await stmt) as any; return Number(row?.__c ?? 0); } const AGG_FNS = { _sum: "SUM", _min: "MIN", _max: "MAX", _avg: "AVG", } as const; async function runAggregate( client: Client, model: string, args: any, ): Promise { const table = getTable(model); const cols = tableColumns(table); const condition = buildCondition(table, args.where); const sel: Record = {}; const meta: Record< string, { agg: keyof typeof AGG_FNS; field: string; col: any } > = {}; for (const agg of ["_sum", "_min", "_max", "_avg"] as const) { if (!args[agg]) continue; for (const [field, enabled] of Object.entries(args[agg])) { if (!enabled) continue; const col = cols[field]; if (!col) continue; const alias = `__${agg}${field}`; sel[alias] = sql`${sql.raw(AGG_FNS[agg])}(${col})`; meta[alias] = { agg, field, col }; } } const out: Record = {}; if (Object.keys(sel).length) { let stmt: any = client.select(sel).from(table); if (condition) stmt = stmt.where(condition); if (args.take) stmt = stmt.limit(args.take); if (args.skip) stmt = stmt.limit(Number.MAX_SAFE_INTEGER).offset(args.skip); const [row] = (await stmt) as any; for (const [alias, { agg, field, col }] of Object.entries(meta)) { if (!out[agg]) out[agg] = {}; const raw = row?.[alias]; if (raw === null || raw === undefined) { out[agg][field] = null; } else if (col.dataType === "bigint") { out[agg][field] = BigInt(raw); } else { out[agg][field] = Number(raw); } } } if (args._count) { let stmt: any = client.select({ __c: sql`COUNT(*)` }).from(table); if (condition) stmt = stmt.where(condition); const [row] = (await stmt) as any; out._count = Number(row?.__c ?? 0); } return out; } async function runGroupBy( client: Client, model: string, args: any, ): Promise { const table = getTable(model); const cols = tableColumns(table); const condition = buildCondition(table, args.where); const byCols: any[] = (args.by ?? []).map((f: string) => { const col = cols[f]; if (!col) throw new Error(`[prisma] groupBy unknown field "${f}" on "${model}"`); return col; }); const sel: Record = {}; for (const col of byCols) sel[colKey(col)] = col; const aggMeta: Record< string, Record > = {}; for (const agg of ["_count", "_sum", "_min", "_max", "_avg"] as const) { const spec = args[agg]; if (!spec) continue; aggMeta[agg] = {}; for (const [field, enabled] of Object.entries(spec)) { if (!enabled) continue; if (field === "_all") { const alias = `__${agg}_all`; sel[alias] = sql`COUNT(*)`; aggMeta[agg]._all = { alias, all: true }; continue; } const col = cols[field]; if (!col) continue; const fn = agg === "_count" ? "COUNT" : agg === "_sum" ? "SUM" : agg === "_min" ? "MIN" : agg === "_max" ? "MAX" : "AVG"; const alias = `__${agg}${field}`; sel[alias] = sql`${sql.raw(fn)}(${col})`; aggMeta[agg][field] = { alias, col, all: false }; } } let stmt: any = client.select(sel).from(table); if (condition) stmt = stmt.where(condition); if (byCols.length) stmt = stmt.groupBy(...byCols); if (args.having) { const havings: SQL[] = []; for (const [field, ops] of Object.entries(args.having as any)) { for (const [agg, op] of Object.entries(ops as any)) { if (agg !== "_count") continue; const col = cols[field]; const countExpr = col ? sql`COUNT(${col})` : sql`COUNT(*)`; for (const [cmp, val] of Object.entries(op as any)) { if (cmp === "gte") havings.push(sql`${countExpr} >= ${val}`); else if (cmp === "gt") havings.push(sql`${countExpr} > ${val}`); else if (cmp === "lte") havings.push(sql`${countExpr} <= ${val}`); else if (cmp === "lt") havings.push(sql`${countExpr} < ${val}`); else if (cmp === "equals") havings.push(sql`${countExpr} = ${val}`); } } } if (havings.length) stmt = stmt.having(...(havings as [SQL])); } if (args.orderBy) { const entries = Array.isArray(args.orderBy) ? args.orderBy : [args.orderBy]; const orderSql: SQL[] = []; for (const entry of entries) { for (const [field, dir] of Object.entries(entry as any)) { if (field === "_count") { for (const [f, d] of Object.entries(dir as any)) { const col = f === "_all" ? undefined : cols[f]; const target = col ? sql`COUNT(${col})` : sql`COUNT(*)`; orderSql.push( d === "desc" ? desc(target as any) : asc(target as any), ); } } else { const col = cols[field]; if (col) orderSql.push(dir === "desc" ? desc(col) : asc(col)); } } } if (orderSql.length) stmt = stmt.orderBy(...orderSql); } if (args.take !== undefined) stmt = stmt.limit(args.take); else if (args.skip) // biome-ignore lint/correctness/noPrecisionLoss: SQL literal for BIGINT UNSIGNED max value (2^64-1 — exceeds JS number precision) stmt = stmt.limit(18446744073709551615); if (args.skip) stmt = stmt.offset(args.skip); const rows = (await stmt) as any[]; return rows.map((row) => { const out: any = {}; for (const col of byCols) out[colKey(col)] = row[colKey(col)]; for (const [agg, fields] of Object.entries(aggMeta)) { out[agg] = {}; for (const [field, { alias, col, all }] of Object.entries(fields)) { const raw = row[alias]; if (all) { out[agg]._all = Number(raw); } else if (agg === "_count") { out[agg][field] = Number(raw); } else if (col?.dataType === "bigint" && raw != null) { out[agg][field] = BigInt(raw); } else { out[agg][field] = raw == null ? null : Number(raw); } } } return out; }); } // ---------- Raw queries ---------- async function runQuery(client: Client, chunk: SQL): Promise { const [rows] = (await client.execute(chunk)) as any; return rows; } async function runExecute(client: Client, chunk: SQL): Promise { const [result] = (await client.execute(chunk)) as any; return Number(result?.affectedRows ?? 0); } function toSqlChunk(strings: TemplateStringsArray, values: unknown[]): SQL { return sql(strings, ...values); } function unsafeSql(query: string, values: unknown[]): SQL { if (values.length === 0) return sql.raw(query); const parts = query.split("?"); if (parts.length !== values.length + 1) { throw new Error( `[prisma] $queryRawUnsafe: expected ${parts.length - 1} params, got ${values.length}`, ); } const chunks: SQL[] = [sql.raw(parts[0])]; for (let i = 0; i < values.length; i++) { const value = values[i]; chunks.push(value instanceof SQL ? value : sql`${value}`); chunks.push(sql.raw(parts[i + 1])); } return sql.join(chunks); } // ---------- PrismaPromise (lazy, batchable) ---------- class PrismaPromise implements PromiseLike { constructor( private readonly runner: (client: Client) => Promise, private readonly client: Client, ) {} // biome-ignore lint/suspicious/noThenProperty: Custom thenable for $transaction chaining — routes through Drizzle at runtime then( onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, onRejected?: ((reason: any) => TResult2 | PromiseLike) | null, ): PromiseLike { return this.runner(this.client).then(onFulfilled, onRejected); } catch( onRejected?: ((reason: any) => TResult | PromiseLike) | null, ): PromiseLike { return this.runner(this.client).catch(onRejected); } finally(onFinally?: (() => void) | null): PromiseLike { return this.runner(this.client).finally(onFinally); } run(client: Client): Promise { return this.runner(client); } } function prismaPromise( runner: (client: Client) => Promise, client: Client, ): PrismaPromise { return new PrismaPromise(runner, client); } // ---------- Model delegate ---------- function makeDelegate(model: string, client: Client) { return { findMany: (args: ReadArgs = {}) => prismaPromise((c) => runFindMany(c, model, args), client), findUnique: (args: ReadArgs) => prismaPromise((c) => runFindOne(c, model, args, false), client), findUniqueOrThrow: (args: ReadArgs) => prismaPromise((c) => runFindOne(c, model, args, true), client), findFirst: (args: ReadArgs = {}) => prismaPromise((c) => runFindOne(c, model, args, false), client), findFirstOrThrow: (args: ReadArgs = {}) => prismaPromise((c) => runFindOne(c, model, args, true), client), create: (args: any) => prismaPromise((c) => runCreate(c, model, args), client), createMany: (args: any) => prismaPromise((c) => runCreateMany(c, model, args), client), update: (args: any) => prismaPromise((c) => runUpdate(c, model, args), client), updateMany: (args: any) => prismaPromise((c) => runUpdateMany(c, model, args), client), delete: (args: any) => prismaPromise((c) => runDelete(c, model, args), client), deleteMany: (args: any) => prismaPromise((c) => runDeleteMany(c, model, args), client), upsert: (args: any) => prismaPromise((c) => runUpsert(c, model, args), client), count: (args: any = {}) => prismaPromise((c) => runCount(c, model, args), client), aggregate: (args: any = {}) => prismaPromise((c) => runAggregate(c, model, args), client), groupBy: (args: any) => prismaPromise((c) => runGroupBy(c, model, args), client), }; } // ---------- Facade ---------- interface ModelDelegate { findMany(args?: any): PrismaPromise; findUnique(args: any): PrismaPromise; findUniqueOrThrow(args: any): PrismaPromise; findFirst(args?: any): PrismaPromise; findFirstOrThrow(args?: any): PrismaPromise; create(args: any): PrismaPromise; createMany(args: any): PrismaPromise<{ count: number }>; update(args: any): PrismaPromise; updateMany(args: any): PrismaPromise<{ count: number }>; delete(args: any): PrismaPromise; deleteMany(args: any): PrismaPromise<{ count: number }>; upsert(args: any): PrismaPromise; count(args?: any): PrismaPromise; aggregate(args?: any): PrismaPromise; groupBy(args: any): PrismaPromise; } interface PrismaFacadeSpecific { $queryRaw: (...args: any[]) => PrismaPromise; $executeRaw: (...args: any[]) => PrismaPromise; $queryRawUnsafe: (...args: any[]) => PrismaPromise; $executeRawUnsafe: (...args: any[]) => PrismaPromise; $transaction: (arg: any, options?: any) => Promise; $connect: () => Promise; $disconnect: () => Promise; $on: (...args: any[]) => void; $use: (...args: any[]) => void; } type PrismaFacade = PrismaFacadeSpecific & Record; function isSqlChunk(value: any): value is SQL { return Boolean(value && typeof value === "object" && "queryChunks" in value); } export function makePrisma(client: Client): PrismaFacade { const methods: Record = { $queryRaw: (strings: TemplateStringsArray | SQL, ...values: unknown[]) => { const chunk = isSqlChunk(strings) ? strings : toSqlChunk(strings as TemplateStringsArray, values); return prismaPromise((c) => runQuery(c, chunk), client); }, $executeRaw: ( strings: TemplateStringsArray | SQL, ...values: unknown[] ) => { const chunk = isSqlChunk(strings) ? strings : toSqlChunk(strings as TemplateStringsArray, values); return prismaPromise((c) => runExecute(c, chunk), client); }, $queryRawUnsafe: (query: string, ...values: unknown[]) => prismaPromise((c) => runQuery(c, unsafeSql(query, values)), client), $executeRawUnsafe: (query: string, ...values: unknown[]) => prismaPromise((c) => runExecute(c, unsafeSql(query, values)), client), $transaction: (arg: any, options?: any) => { if (typeof arg === "function") { return client.transaction(async (tx) => { const txClient = tx as unknown as Client; return arg(makePrisma(txClient)); }, options); } if (Array.isArray(arg)) { return client.transaction(async (tx) => { const txClient = tx as unknown as Client; const results: any[] = []; for (const op of arg) { results.push( op instanceof PrismaPromise ? await op.run(txClient) : await (op as any), ); } return results; }, options); } return Promise.resolve(arg); }, $connect: async () => {}, $disconnect: async () => {}, $on: () => {}, $use: () => {}, }; return new Proxy(methods, { get(target, prop) { if (typeof prop !== "string") return undefined; if (prop in target) return (target as any)[prop]; return makeDelegate(prop, client); }, }) as PrismaFacade; } export const prisma = makePrisma(db); export const sqlHelper = sql; export const rawHelper = (query: string): SQL => sql.raw(query); export const joinHelper = (values: unknown[]): SQL => sql.join( values.map((v) => (v instanceof SQL ? v : sql`${v}`)), sql`, `, ); export const emptyHelper = sql.raw(""); export const Prisma: Record = { sql: sqlHelper, raw: rawHelper, join: joinHelper, empty: emptyHelper, };