UNPKG

solobase-js

Version:

A 100% drop-in replacement for the Supabase JavaScript client. Self-hosted Supabase alternative with complete API compatibility.

548 lines (454 loc) 12.2 kB
# Solobase JS SDK A 100% drop-in replacement for the Supabase JavaScript client. Replace `@supabase/supabase-js` with `solobase-js` and everything works identically. ## Features - 🔄 **Drop-in Replacement**: Change one import line and you're done - 🔐 **Complete Auth System**: Sign up, sign in, OAuth, password reset, user management - 🗄️ **PostgreSQL Database**: Full query builder with filters, joins, and transactions - 📁 **File Storage**: Upload, download, and manage files with signed URLs - ⚡ **Real-time Subscriptions**: WebSocket-based live data updates - 📝 **TypeScript Support**: Full type safety and IntelliSense - 🛡️ **Security**: Built-in RLS, JWT tokens, and secure file access ## Installation ```bash npm install solobase-js ``` ## Quick Start Replace your Supabase client with Solobase: ```javascript // Before (Supabase) import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://project.supabase.co', 'anon-key') // After (Solobase) - Just change this line! import { createClient } from 'solobase-js' const solobase = createClient('https://your-solobase.com', 'your-api-key') // Everything else works exactly the same const { data, error } = await solobase.from('users').select('*') ``` ## Complete API Reference ### Authentication ```javascript // Sign up new user const { data, error } = await solobase.auth.signUp({ email: 'user@example.com', password: 'password', options: { data: { first_name: 'John', last_name: 'Doe' } } }) // Sign in with email/password const { data, error } = await solobase.auth.signInWithPassword({ email: 'user@example.com', password: 'password' }) // Sign in with OAuth const { data, error } = await solobase.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: 'https://yourapp.com/callback' } }) // Get current user const { data: { user } } = await solobase.auth.getUser() // Get current session const { data: { session } } = await solobase.auth.getSession() // Update user const { data, error } = await solobase.auth.updateUser({ email: 'newemail@example.com', password: 'newpassword', data: { username: 'newusername' } }) // Sign out const { error } = await solobase.auth.signOut() // Listen to auth changes solobase.auth.onAuthStateChange((event, session) => { console.log(event, session) }) // Reset password const { data, error } = await solobase.auth.resetPasswordForEmail({ email: 'user@example.com', options: { redirectTo: 'https://yourapp.com/reset' } }) ``` ### Database Operations ```javascript // Select data const { data, error } = await solobase .from('users') .select('*') // Select specific columns const { data, error } = await solobase .from('users') .select('id, name, email') // Filter data const { data, error } = await solobase .from('users') .select('*') .eq('status', 'active') .gt('age', 18) .order('created_at', { ascending: false }) .limit(10) // Insert data const { data, error } = await solobase .from('users') .insert([ { name: 'John Doe', email: 'john@example.com' }, { name: 'Jane Doe', email: 'jane@example.com' } ]) .select() // Update data const { data, error } = await solobase .from('users') .update({ status: 'inactive' }) .eq('id', userId) .select() // Upsert data const { data, error } = await solobase .from('users') .upsert({ id: 1, name: 'Updated Name' }) .select() // Delete data const { data, error } = await solobase .from('users') .delete() .eq('id', userId) // Single row const { data, error } = await solobase .from('users') .select('*') .eq('id', userId) .single() // Maybe single (won't error if no rows) const { data, error } = await solobase .from('users') .select('*') .eq('email', email) .maybeSingle() // Count rows const { data, error, count } = await solobase .from('users') .select('*', { count: 'exact' }) .eq('status', 'active') // Call stored procedures/functions const { data, error } = await solobase.rpc('get_user_profile', { user_id: 123 }) ``` ### Advanced Filtering ```javascript // All filter operators const { data } = await solobase .from('users') .select('*') .eq('status', 'active') // equals .neq('role', 'admin') // not equals .gt('age', 18) // greater than .gte('age', 18) // greater than or equal .lt('age', 65) // less than .lte('age', 65) // less than or equal .like('name', '%john%') // LIKE .ilike('name', '%JOHN%') // case-insensitive LIKE .is('deleted_at', null) // IS NULL .in('role', ['user', 'admin']) // IN array .contains('tags', ['react']) // contains .textSearch('title', 'javascript', { type: 'websearch' }) // Complex OR queries const { data } = await solobase .from('users') .select('*') .or('status.eq.active,role.eq.admin') // Ordering and pagination const { data } = await solobase .from('users') .select('*') .order('created_at', { ascending: false }) .order('name', { ascending: true }) .range(0, 9) // First 10 rows .limit(10) ``` ### File Storage ```javascript // List buckets const { data, error } = await solobase.storage.listBuckets() // Create bucket const { data, error } = await solobase.storage.createBucket('avatars', { public: true, fileSizeLimit: 1024 * 1024 * 10, // 10MB allowedMimeTypes: ['image/png', 'image/jpeg'] }) // Upload file const { data, error } = await solobase.storage .from('avatars') .upload('user-123.png', file, { contentType: 'image/png', cacheControl: '3600', upsert: false }) // Download file const { data, error } = await solobase.storage .from('avatars') .download('user-123.png') // List files const { data, error } = await solobase.storage .from('avatars') .list('folder', { limit: 100, offset: 0, sortBy: { column: 'name', order: 'asc' } }) // Get public URL const { data } = solobase.storage .from('avatars') .getPublicUrl('user-123.png') // Create signed URL (for private files) const { data, error } = await solobase.storage .from('private-files') .createSignedUrl('document.pdf', 3600) // 1 hour expiry // Update file const { data, error } = await solobase.storage .from('avatars') .update('user-123.png', newFile) // Move file const { data, error } = await solobase.storage .from('avatars') .move('old-path.png', 'new-path.png') // Copy file const { data, error } = await solobase.storage .from('avatars') .copy('original.png', 'copy.png') // Delete files const { data, error } = await solobase.storage .from('avatars') .remove(['file1.png', 'file2.png']) ``` ### Real-time Subscriptions ```javascript // Listen to database changes const subscription = solobase .channel('db-changes') .on('postgres_changes', { event: '*', schema: 'public', table: 'users' }, (payload) => { console.log('Change received!', payload) }) .subscribe() // Listen to specific events solobase .channel('users') .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'users' }, (payload) => { console.log('New user:', payload.new) }) .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'users' }, (payload) => { console.log('Updated user:', payload.new) }) .subscribe() // Unsubscribe subscription.unsubscribe() // Remove all subscriptions solobase.removeAllChannels() ``` ### Custom Channels (Broadcast/Presence) ```javascript // Join a custom channel const channel = solobase.channel('room-1') // Send broadcast messages channel.send('custom-event', { message: 'Hello everyone!' }) // Listen to broadcast messages channel.on('custom-event', (payload) => { console.log('Received:', payload) }) // Subscribe to the channel channel.subscribe((status) => { if (status === 'SUBSCRIBED') { console.log('Connected to channel') } }) ``` ## TypeScript Support Full TypeScript support with type inference: ```typescript // Define your database types interface Database { public: { Tables: { users: { Row: { id: number name: string email: string created_at: string } Insert: { id?: number name: string email: string created_at?: string } Update: { id?: number name?: string email?: string created_at?: string } } } } } // Create typed client const solobase = createClient<Database>(url, key) // Get full type safety const { data, error } = await solobase .from('users') // ✅ Typed table name .select('name, email') // ✅ Typed column names .eq('id', 1) // ✅ Typed column and value ``` ## Error Handling ```javascript const { data, error } = await solobase .from('users') .select('*') if (error) { console.error('Error:', error.message) console.error('Details:', error.details) console.error('Code:', error.code) } else { console.log('Data:', data) } ``` ## Environment Configuration ```javascript // Development const solobase = createClient( 'http://localhost:8000', 'your-local-key', { auth: { persistSession: true, autoRefreshToken: true, }, realtime: { logger: (level, message, data) => { console.log(`[${level}] ${message}`, data) } } } ) // Production const solobase = createClient( process.env.SOLOBASE_URL, process.env.SOLOBASE_ANON_KEY, { auth: { persistSession: true, autoRefreshToken: true, detectSessionInUrl: true } } ) ``` ## Migration from Supabase 1. **Install Solobase SDK**: ```bash npm uninstall @supabase/supabase-js npm install solobase-js ``` 2. **Update your import**: ```javascript // Change this: import { createClient } from '@supabase/supabase-js' // To this: import { createClient } from 'solobase-js' ``` 3. **Update your URL and key**: ```javascript const solobase = createClient( 'https://your-solobase-instance.com', 'your-solobase-api-key' ) ``` 4. **That's it!** All your existing code will work without changes. ## React Integration Example ```jsx import { createClient } from 'solobase-js' import { useEffect, useState } from 'react' const solobase = createClient( process.env.REACT_APP_SOLOBASE_URL, process.env.REACT_APP_SOLOBASE_ANON_KEY ) function App() { const [user, setUser] = useState(null) const [posts, setPosts] = useState([]) useEffect(() => { // Check active session solobase.auth.getSession().then(({ data: { session } }) => { setUser(session?.user ?? null) }) // Listen for auth changes const { data: { subscription } } = solobase.auth.onAuthStateChange( (_event, session) => { setUser(session?.user ?? null) } ) return subscription.unsubscribe }, []) useEffect(() => { // Fetch posts fetchPosts() // Subscribe to real-time changes const subscription = solobase .channel('posts') .on('postgres_changes', { event: '*', schema: 'public', table: 'posts' }, () => { fetchPosts() // Refetch when posts change }) .subscribe() return () => subscription.unsubscribe() }, []) const fetchPosts = async () => { const { data } = await solobase .from('posts') .select('*') .order('created_at', { ascending: false }) setPosts(data || []) } const signIn = async (email, password) => { const { error } = await solobase.auth.signInWithPassword({ email, password }) if (error) alert(error.message) } const signOut = async () => { const { error } = await solobase.auth.signOut() if (error) alert(error.message) } if (!user) { return <LoginForm onSignIn={signIn} /> } return ( <div> <h1>Welcome {user.email}</h1> <button onClick={signOut}>Sign Out</button> <PostList posts={posts} /> </div> ) } ``` ## License MIT License - Feel free to use in your projects!