@dataql/supabase-adapter
Version:
Supabase adapter for DataQL with zero API changes
421 lines (338 loc) • 9.88 kB
Markdown
# @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