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
Markdown
# 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!