UNPKG

@dataql/supabase-adapter

Version:

Supabase adapter for DataQL with zero API changes

432 lines 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SupabaseClient = exports.SupabaseQueryBuilder = void 0; exports.createClient = createClient; const core_1 = require("@dataql/core"); // Query builder class class SupabaseQueryBuilder { constructor(data, tableName, schema) { this._filters = []; this._single = false; this._data = data; this._tableName = tableName; this._collection = data.collection(tableName, schema || this._getDefaultSchema()); } _getDefaultSchema() { // Default schema for tables without explicit schema return { id: { type: "ID", required: true }, created_at: { type: "Date", default: "now" }, updated_at: { type: "Date", default: "now" }, }; } // Select columns select(columns, options) { if (columns) { this._selectFields = columns.split(",").map((col) => col.trim()); } if (options?.count) { this._count = options.count; } return this; } // Filtering methods eq(column, value) { this._filters.push({ column, operator: "eq", value }); return this; } neq(column, value) { this._filters.push({ column, operator: "neq", value }); return this; } gt(column, value) { this._filters.push({ column, operator: "gt", value }); return this; } gte(column, value) { this._filters.push({ column, operator: "gte", value }); return this; } lt(column, value) { this._filters.push({ column, operator: "lt", value }); return this; } lte(column, value) { this._filters.push({ column, operator: "lte", value }); return this; } like(column, pattern) { this._filters.push({ column, operator: "like", value: pattern }); return this; } ilike(column, pattern) { this._filters.push({ column, operator: "ilike", value: pattern }); return this; } in(column, values) { this._filters.push({ column, operator: "in", value: values }); return this; } contains(column, value) { this._filters.push({ column, operator: "cs", value }); return this; } containedBy(column, value) { this._filters.push({ column, operator: "cd", value }); return this; } textSearch(column, query) { this._filters.push({ column, operator: "fts", value: query }); return this; } // Range filtering range(from, to) { this._offset = from; this._limit = to - from + 1; return this; } // Ordering order(column, options) { this._orderBy = { column, ascending: options?.ascending !== false }; return this; } // Limit limit(count) { this._limit = count; return this; } // Single result single() { this._single = true; this._limit = 1; return this; } // Maybe single result maybeSingle() { this._limit = 1; return this; } // Execute query and return response async execute() { try { const filter = this._buildDataQLFilter(); let results = await this._collection.find(filter); // Apply ordering if (this._orderBy) { results = this._applyOrderBy(results); } // Apply pagination if (this._offset) { results = results.slice(this._offset); } if (this._limit) { results = results.slice(0, this._limit); } // Apply field selection if (this._selectFields) { results = results.map((item) => this._applyFieldSelection(item)); } // Return single or array based on query type const data = this._single ? results[0] || null : results; const count = this._count ? results.length : null; return { data: data, error: null, count, status: 200, statusText: "OK", }; } catch (error) { return { data: null, error: { message: error instanceof Error ? error.message : "Unknown error", code: "DATAQL_ERROR", }, status: 500, statusText: "Internal Server Error", }; } } _buildDataQLFilter() { const filter = {}; for (const filterItem of this._filters) { const { column, operator, value } = filterItem; switch (operator) { case "eq": filter[column] = value; break; case "neq": filter[column] = { $ne: value }; break; case "gt": filter[column] = { $gt: value }; break; case "gte": filter[column] = { $gte: value }; break; case "lt": filter[column] = { $lt: value }; break; case "lte": filter[column] = { $lte: value }; break; case "like": case "ilike": filter[column] = { $regex: value, $options: operator === "ilike" ? "i" : "", }; break; case "in": filter[column] = { $in: value }; break; case "cs": filter[column] = { $in: Array.isArray(value) ? value : [value] }; break; case "fts": filter[column] = { $text: { $search: value } }; break; default: filter[column] = value; } } return filter; } _applyOrderBy(results) { if (!this._orderBy) return results; const { column, ascending } = this._orderBy; return results.sort((a, b) => { const aVal = a[column]; const bVal = b[column]; if (aVal < bVal) return ascending ? -1 : 1; if (aVal > bVal) return ascending ? 1 : -1; return 0; }); } _applyFieldSelection(item) { if (!this._selectFields) return item; const result = {}; for (const field of this._selectFields) { if (field.includes(".")) { // Handle nested field selection (simplified) const parts = field.split("."); let value = item; for (const part of parts) { value = value?.[part]; } result[field] = value; } else { result[field] = item[field]; } } return result; } // Insert data async insert(data) { try { const result = await this._collection.create(data); return { data: result, error: null, status: 201, statusText: "Created", }; } catch (error) { return { data: null, error: { message: error instanceof Error ? error.message : "Insert failed", code: "INSERT_ERROR", }, status: 400, statusText: "Bad Request", }; } } // Update data async update(data) { try { const filter = this._buildDataQLFilter(); const result = await this._collection.update(filter, data); return { data: result, error: null, status: 200, statusText: "OK", }; } catch (error) { return { data: null, error: { message: error instanceof Error ? error.message : "Update failed", code: "UPDATE_ERROR", }, status: 400, statusText: "Bad Request", }; } } // Upsert data async upsert(data, options) { try { const result = await this._collection.upsert(data); return { data: result, error: null, status: 200, statusText: "OK", }; } catch (error) { return { data: null, error: { message: error instanceof Error ? error.message : "Upsert failed", code: "UPSERT_ERROR", }, status: 400, statusText: "Bad Request", }; } } // Delete data async delete() { try { const filter = this._buildDataQLFilter(); const result = await this._collection.delete(filter); return { data: result, error: null, status: 200, statusText: "OK", }; } catch (error) { return { data: null, error: { message: error instanceof Error ? error.message : "Delete failed", code: "DELETE_ERROR", }, status: 400, statusText: "Bad Request", }; } } // Real-time subscription on(event, callback) { // Mock real-time subscription for now // In a real implementation, this would use DataQL's real-time capabilities console.log(`Subscribed to ${event} events on table ${this._tableName}`); return { unsubscribe: () => { console.log(`Unsubscribed from ${event} events on table ${this._tableName}`); }, }; } } exports.SupabaseQueryBuilder = SupabaseQueryBuilder; // Supabase client class class SupabaseClient { constructor(supabaseUrl, supabaseKey, options) { this._schemas = new Map(); // Ensure proper DataQL infrastructure routing (Client → Worker → Lambda → Database) const dataqlOptions = { appToken: options?.appToken || "default", // Required for DataQL routing env: options?.env || "prod", // Routes to production infrastructure devPrefix: options?.devPrefix || "dev_", // Optional prefix for dev environments dbName: options?.dbName, // Database isolation per client customConnection: options?.customConnection, // Optional custom connection }; this._data = new core_1.Data(dataqlOptions); } // Register table schema registerTable(tableName, schema) { this._schemas.set(tableName, schema); } // Get table query builder from(table) { const schema = this._schemas.get(table); return new SupabaseQueryBuilder(this._data, table, schema); } // Authentication methods (mocked for now) get auth() { return { signUp: async (credentials) => ({ data: null, error: null }), signIn: async (credentials) => ({ data: null, error: null }), signOut: async () => ({ error: null }), getUser: async () => ({ data: null, error: null }), getSession: async () => ({ data: null, error: null }), onAuthStateChange: (callback) => ({ data: { subscription: { unsubscribe: () => { } } }, }), }; } // Storage methods (mocked for now) get storage() { return { from: (bucket) => ({ upload: async (path, file) => ({ data: null, error: null, }), download: async (path) => ({ data: null, error: null }), remove: async (paths) => ({ data: null, error: null }), list: async (path) => ({ data: [], error: null }), getPublicUrl: (path) => ({ data: { publicUrl: "" } }), }), }; } // RPC (Remote Procedure Call) methods async rpc(fn, args, options) { // Mock RPC calls for now return { data: null, error: { message: "RPC not yet supported in DataQL adapter", code: "RPC_NOT_SUPPORTED", }, status: 501, statusText: "Not Implemented", }; } // Channel for real-time channel(name, options) { return { on: (type, filter, callback) => { // Mock channel subscription console.log(`Subscribed to postgres_changes on ${filter.schema}.${filter.table}`); return { unsubscribe: () => { console.log(`Unsubscribed from postgres_changes on ${filter.schema}.${filter.table}`); }, }; }, subscribe: () => { console.log(`Subscribed to channel ${name}`); return Promise.resolve(); }, unsubscribe: () => { console.log(`Unsubscribed from channel ${name}`); return Promise.resolve(); }, }; } } exports.SupabaseClient = SupabaseClient; // Create Supabase client function createClient(supabaseUrl, supabaseKey, options) { return new SupabaseClient(supabaseUrl, supabaseKey, options); } // Export main client exports.default = createClient; //# sourceMappingURL=index.js.map