@dataql/supabase-adapter
Version:
Supabase adapter for DataQL with zero API changes
432 lines • 14.1 kB
JavaScript
"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