/** * Supabase-like wrapper for direct PostgreSQL * Provides similar API to @supabase/supabase-js but uses pg pool directly */ const { pool } = require('./database'); class QueryBuilder { constructor(tableName) { this.tableName = tableName; this._select = '*'; this._where = []; this._whereParams = []; this._orderBy = null; this._limit = null; this._offset = null; this._single = false; this._returning = true; this._count = false; } select(columns, options = {}) { if (typeof columns === 'string') { this._select = columns || '*'; } else { this._select = '*'; } if (options && options.count === 'exact') { this._count = true; } return this; } eq(column, value) { this._where.push(`"${column}" = $${this._whereParams.length + 1}`); this._whereParams.push(value); return this; } neq(column, value) { this._where.push(`"${column}" != $${this._whereParams.length + 1}`); this._whereParams.push(value); return this; } gt(column, value) { this._where.push(`"${column}" > $${this._whereParams.length + 1}`); this._whereParams.push(value); return this; } gte(column, value) { this._where.push(`"${column}" >= $${this._whereParams.length + 1}`); this._whereParams.push(value); return this; } lt(column, value) { this._where.push(`"${column}" < $${this._whereParams.length + 1}`); this._whereParams.push(value); return this; } lte(column, value) { this._where.push(`"${column}" <= $${this._whereParams.length + 1}`); this._whereParams.push(value); return this; } is(column, value) { if (value === null) { this._where.push(`"${column}" IS NULL`); } else { this._where.push(`"${column}" IS $${this._whereParams.length + 1}`); this._whereParams.push(value); } return this; } in(column, values) { const placeholders = values.map((_, i) => `$${this._whereParams.length + i + 1}`).join(', '); this._where.push(`"${column}" IN (${placeholders})`); this._whereParams.push(...values); return this; } order(column, options = {}) { const direction = options.ascending === false ? 'DESC' : 'ASC'; this._orderBy = `"${column}" ${direction}`; return this; } limit(count) { this._limit = count; return this; } range(from, to) { this._offset = from; this._limit = to - from + 1; return this; } single() { this._single = true; this._limit = 1; return this; } async _buildSelectQuery() { let query = `SELECT ${this._select} FROM "${this.tableName}"`; if (this._where.length > 0) { query += ` WHERE ${this._where.join(' AND ')}`; } if (this._orderBy) { query += ` ORDER BY ${this._orderBy}`; } if (this._limit) { query += ` LIMIT ${this._limit}`; } if (this._offset) { query += ` OFFSET ${this._offset}`; } return query; } async _buildCountQuery() { let query = `SELECT COUNT(*) as count FROM "${this.tableName}"`; if (this._where.length > 0) { query += ` WHERE ${this._where.join(' AND ')}`; } return query; } async then(resolve, reject) { try { const query = await this._buildSelectQuery(); const result = await pool.query(query, this._whereParams); let count = null; if (this._count) { const countQuery = await this._buildCountQuery(); const countResult = await pool.query(countQuery, this._whereParams); count = parseInt(countResult.rows[0]?.count || 0); } if (this._single) { if (result.rows.length === 0) { resolve({ data: null, error: { code: 'PGRST116', message: 'No rows returned' }, count }); } else { resolve({ data: result.rows[0], error: null, count }); } } else { resolve({ data: result.rows, error: null, count }); } } catch (error) { resolve({ data: null, error: { code: error.code, message: error.message, details: error.detail, hint: error.hint }, count: null }); } } } class InsertBuilder { constructor(tableName, data) { this.tableName = tableName; this.data = Array.isArray(data) ? data : [data]; this._select = null; this._single = false; } select(columns) { this._select = columns || '*'; return this; } single() { this._single = true; return this; } async then(resolve, reject) { try { const record = this.data[0]; const columns = Object.keys(record); const values = Object.values(record); const placeholders = columns.map((_, i) => `$${i + 1}`).join(', '); let query = `INSERT INTO "${this.tableName}" ("${columns.join('", "')}") VALUES (${placeholders})`; if (this._select) { query += ` RETURNING ${this._select}`; } else { query += ' RETURNING *'; } const result = await pool.query(query, values); if (this._single) { resolve({ data: result.rows[0] || null, error: null }); } else { resolve({ data: result.rows, error: null }); } } catch (error) { resolve({ data: null, error: { code: error.code, message: error.message, details: error.detail, hint: error.hint } }); } } } class UpdateBuilder { constructor(tableName, data) { this.tableName = tableName; this.data = data; this._where = []; this._whereParams = []; this._select = null; this._single = false; } eq(column, value) { this._where.push({ column, value }); return this; } select(columns) { this._select = columns || '*'; return this; } single() { this._single = true; return this; } async then(resolve, reject) { try { const columns = Object.keys(this.data); const values = Object.values(this.data); const setClause = columns.map((col, i) => `"${col}" = $${i + 1}`).join(', '); let paramIndex = columns.length + 1; const whereClause = this._where.map(w => { values.push(w.value); return `"${w.column}" = $${paramIndex++}`; }).join(' AND '); let query = `UPDATE "${this.tableName}" SET ${setClause}`; if (whereClause) { query += ` WHERE ${whereClause}`; } if (this._select) { query += ` RETURNING ${this._select}`; } else { query += ' RETURNING *'; } const result = await pool.query(query, values); if (this._single) { resolve({ data: result.rows[0] || null, error: null }); } else { resolve({ data: result.rows, error: null }); } } catch (error) { resolve({ data: null, error: { code: error.code, message: error.message, details: error.detail, hint: error.hint } }); } } } class UpsertBuilder { constructor(tableName, data, options = {}) { this.tableName = tableName; this.data = Array.isArray(data) ? data : [data]; this.onConflict = options.onConflict; this._select = null; this._single = false; } select(columns) { this._select = columns || '*'; return this; } single() { this._single = true; return this; } async then(resolve, reject) { try { const record = this.data[0]; const columns = Object.keys(record); const values = Object.values(record); const placeholders = columns.map((_, i) => `$${i + 1}`).join(', '); const updateClause = columns .filter(col => col !== this.onConflict) .map(col => `"${col}" = EXCLUDED."${col}"`) .join(', '); let query = `INSERT INTO "${this.tableName}" ("${columns.join('", "')}") VALUES (${placeholders})`; query += ` ON CONFLICT ("${this.onConflict}") DO UPDATE SET ${updateClause}`; query += ' RETURNING *'; const result = await pool.query(query, values); if (this._single) { resolve({ data: result.rows[0] || null, error: null }); } else { resolve({ data: result.rows, error: null }); } } catch (error) { resolve({ data: null, error: { code: error.code, message: error.message, details: error.detail, hint: error.hint } }); } } } class DeleteBuilder { constructor(tableName) { this.tableName = tableName; this._where = []; this._whereParams = []; } eq(column, value) { this._where.push(`"${column}" = $${this._whereParams.length + 1}`); this._whereParams.push(value); return this; } is(column, value) { if (value === null) { this._where.push(`"${column}" IS NULL`); } return this; } async then(resolve, reject) { try { let query = `DELETE FROM "${this.tableName}"`; if (this._where.length > 0) { query += ` WHERE ${this._where.join(' AND ')}`; } const result = await pool.query(query, this._whereParams); resolve({ data: null, error: null, count: result.rowCount }); } catch (error) { resolve({ data: null, error: { code: error.code, message: error.message, details: error.detail, hint: error.hint } }); } } } class TableClient { constructor(tableName) { this.tableName = tableName; } select(columns) { const builder = new QueryBuilder(this.tableName); return builder.select(columns); } insert(data) { return new InsertBuilder(this.tableName, data); } update(data) { return new UpdateBuilder(this.tableName, data); } upsert(data, options) { return new UpsertBuilder(this.tableName, data, options); } delete() { return new DeleteBuilder(this.tableName); } } // Supabase-compatible client const supabase = { from: (tableName) => new TableClient(tableName) }; module.exports = { supabase };