UNPKG

@dataql/supabase-adapter

Version:

Supabase adapter for DataQL with zero API changes

421 lines (338 loc) 9.88 kB
# @dataql/supabase-adapter Migrate from Supabase to DataQL with zero API changes. This adapter provides a Supabase Client-compatible API that uses DataQL under the hood. ## Installation ```bash npm install @dataql/core @dataql/supabase-adapter ``` ## Quick Start ```typescript import { createClient } from "@dataql/supabase-adapter"; // Create Supabase client with DataQL backend const supabase = createClient( "https://your-project.supabase.co", "your-anon-key", { appToken: "your-app-token", } ); // Register table schemas (optional but recommended) supabase.registerTable("users", { id: { type: "ID", required: true }, email: { type: "String", required: true, unique: true }, name: { type: "String" }, avatar_url: { type: "String" }, created_at: { type: "Date", default: "now" }, updated_at: { type: "Date", default: "now" }, }); // Use familiar Supabase syntax const { data, error } = await supabase .from("users") .select("id, name, email") .eq("email", "john@example.com") .single(); if (error) { console.error("Error:", error.message); } else { console.log("User:", data); } // Insert data const { data: newUser, error: insertError } = await supabase .from("users") .insert({ name: "John Doe", email: "john@example.com", avatar_url: "https://example.com/avatar.jpg", }); // Query with filters and pagination const { data: users, error: queryError } = await supabase .from("users") .select("*") .gte("created_at", "2024-01-01") .order("created_at", { ascending: false }) .range(0, 9); // Real-time subscriptions const subscription = supabase.from("users").on("INSERT", (payload) => { console.log("New user:", payload.new); }); // Unsubscribe when done subscription.unsubscribe(); ``` ## API Compatibility ### Supported Supabase Features #### Query Builder - ✅ `.from(table)` - Select table - ✅ `.select(columns)` - Column selection with count options - ✅ `.insert(data)` - Insert records - ✅ `.update(data)` - Update records - ✅ `.upsert(data)` - Insert or update records - ✅ `.delete()` - Delete records #### Filtering - ✅ `.eq(column, value)` - Equals - ✅ `.neq(column, value)` - Not equals - ✅ `.gt(column, value)` - Greater than - ✅ `.gte(column, value)` - Greater than or equal - ✅ `.lt(column, value)` - Less than - ✅ `.lte(column, value)` - Less than or equal - ✅ `.like(column, pattern)` - Pattern matching - ✅ `.ilike(column, pattern)` - Case-insensitive pattern matching - ✅ `.in(column, values)` - In array - ✅ `.contains(column, value)` - Array/JSON contains - ✅ `.textSearch(column, query)` - Full-text search #### Modifiers - ✅ `.order(column, options)` - Ordering - ✅ `.range(from, to)` - Pagination - ✅ `.limit(count)` - Limit results - ✅ `.single()` - Return single record - ✅ `.maybeSingle()` - Return single record or null #### Real-time - ✅ `.on(event, callback)` - Real-time subscriptions - ✅ Event types: `INSERT`, `UPDATE`, `DELETE`, `*` - ✅ Subscription management with `unsubscribe()` #### Response Format - ✅ Supabase response format with `data`, `error`, `status` - ✅ Error handling with detailed error messages - ✅ Count support for pagination ### DataQL Enhancements While maintaining Supabase compatibility, you also get DataQL's additional features: - **Offline-first**: Automatic offline support and sync - **Multi-region**: Global data distribution - **Schema evolution**: Dynamic schema updates - **WAL support**: Write-ahead logging for reliability - **Unique document creation**: `createUnique()` method to prevent duplicates ## Migration Guide ### From Supabase 1. **Replace imports**: ```typescript // Before import { createClient } from "@supabase/supabase-js"; // After import { createClient } from "@dataql/supabase-adapter"; ``` 2. **Update client configuration**: ```typescript // Before const supabase = createClient( "https://your-project.supabase.co", "your-anon-key" ); // After const supabase = createClient( "https://your-project.supabase.co", "your-anon-key", { appToken: "your-app-token", } ); ``` 3. **Register table schemas** (recommended): ```typescript // Define your table schemas for better type safety and validation supabase.registerTable("users", { id: { type: "ID", required: true }, email: { type: "String", required: true, unique: true }, name: { type: "String" }, created_at: { type: "Date", default: "now" }, }); ``` 4. **Your queries work the same**: ```typescript // This works exactly the same const { data, error } = await supabase .from("users") .select("*") .eq("active", true); ``` ## Configuration ```typescript const supabase = createClient(supabaseUrl, supabaseKey, { appToken: "your-app-token", // Authentication token env: "prod", // 'dev' or 'prod' devPrefix: "dev_", // Prefix for dev tables // Supabase-specific options (mocked for now) auth: { autoRefreshToken: true, persistSession: true, detectSessionInUrl: true, }, realtime: { params: { eventsPerSecond: 10, }, }, global: { headers: { "X-Custom-Header": "value", }, }, }); ``` ## TypeScript Support Full TypeScript support with inferred types: ```typescript import { createClient, SupabaseResponse } from "@dataql/supabase-adapter"; interface User { id: string; email: string; name?: string; avatar_url?: string; created_at?: string; updated_at?: string; } const supabase = createClient(supabaseUrl, supabaseKey, options); // Type-safe operations const response: SupabaseResponse<User[]> = await supabase .from("users") .select("*"); if (response.error) { console.error("Error:", response.error.message); } else { const users: User[] = response.data || []; console.log("Users:", users); } ``` ## Real-time Subscriptions Real-time features work just like Supabase: ```typescript // Table-level subscription const subscription = supabase.from("messages").on("INSERT", (payload) => { console.log("New message:", payload.new); }); // Channel-based subscription const channel = supabase.channel("room-1"); channel .on( "postgres_changes", { event: "INSERT", schema: "public", table: "messages", }, (payload) => { console.log("Database change:", payload); } ) .subscribe(); // Cleanup subscription.unsubscribe(); channel.unsubscribe(); ``` ## Advanced Queries Complex queries with multiple filters and joins: ```typescript // Complex filtering const { data: posts } = await supabase .from("posts") .select( ` id, title, content, author:users!author_id ( id, name, email ), tags:post_tags ( tag:tags ( name ) ) ` ) .eq("published", true) .gte("created_at", "2024-01-01") .textSearch("title", "javascript OR typescript") .order("created_at", { ascending: false }) .range(0, 19); // Aggregation queries const { data: userCount } = await supabase .from("users") .select("*", { count: "exact" }) .eq("active", true); console.log("Active users:", userCount?.count); ``` ## Error Handling Standard Supabase error handling: ```typescript const { data, error } = await supabase .from("users") .insert({ email: "invalid-email" }); if (error) { console.error("Error code:", error.code); console.error("Error message:", error.message); console.error("Error details:", error.details); console.error("Error hint:", error.hint); } else { console.log("Success:", data); } ``` ## Limitations Some advanced Supabase features are not yet supported: - **Auth**: Authentication methods are not implemented (implement your own authentication layer) - **Storage**: File storage methods are mocked (use DataQL's storage instead) - **Edge Functions**: Not supported (use DataQL's serverless functions instead) - **RPC**: Remote procedure calls are not yet supported - **Advanced joins**: Complex table joins need DataQL enhancement - **Row Level Security**: Not implemented (use DataQL's security features instead) - **Triggers**: Database triggers are not supported If you need these features, please [open an issue](https://github.com/dataql/dataql/issues). ## Examples ### Basic CRUD Operations ```typescript // Create const { data: newPost, error } = await supabase.from("posts").insert({ title: "Getting Started with DataQL", content: "DataQL provides powerful offline-first capabilities...", author_id: "user-123", published: true, }); // Read const { data: posts } = await supabase .from("posts") .select("id, title, content, created_at") .eq("published", true) .order("created_at", { ascending: false }); // Update const { data: updatedPost } = await supabase .from("posts") .update({ title: "Updated Title" }) .eq("id", "post-123"); // Delete const { data: deletedPost } = await supabase .from("posts") .delete() .eq("id", "post-123"); ``` ### Real-time Chat Application ```typescript // Listen for new messages const messagesSubscription = supabase .from("messages") .on("INSERT", (payload) => { addMessageToUI(payload.new); }); // Listen for message updates const updatesSubscription = supabase .from("messages") .on("UPDATE", (payload) => { updateMessageInUI(payload.new); }); // Send a message async function sendMessage(text: string, userId: string, roomId: string) { const { data, error } = await supabase.from("messages").insert({ text, user_id: userId, room_id: roomId, }); if (error) { console.error("Failed to send message:", error.message); } } // Cleanup subscriptions function cleanup() { messagesSubscription.unsubscribe(); updatesSubscription.unsubscribe(); } ``` ## License MIT