@dataql/firebase-adapter
Version:
Firebase adapter for DataQL with zero API changes
514 lines (406 loc) • 12.9 kB
Markdown
# @dataql/firebase-adapter
Migrate from Firebase/Firestore to DataQL with zero API changes. This adapter provides a Firebase-compatible API that runs on DataQL with automatic scaling, caching, and offline support.
## Installation
```bash
npm install @dataql/core @dataql/firebase-adapter
```
## Quick Start
```typescript
import { initializeApp, getFirestore } from "@dataql/firebase-adapter";
// Initialize Firebase app with DataQL
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:abcdef123456",
};
const app = initializeApp(firebaseConfig, {
appToken: "your-app-token", // Required for DataQL authentication
env: "prod", // Environment: 'dev' or 'prod'
dbName: "your_app_db", // Database name for data isolation
});
const db = getFirestore(app);
// Use familiar Firestore syntax - all operations powered by DataQL
const usersRef = db.collection("users");
// Add a document
const docRef = await usersRef.add({
name: "John Doe",
email: "john@example.com",
age: 30,
active: true,
});
console.log("Document written with ID: ", docRef.id);
// Query documents
const snapshot = await usersRef
.where("active", "==", true)
.where("age", ">=", 18)
.orderBy("name")
.limit(10)
.get();
snapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
});
// Real-time listener
const unsubscribe = usersRef.onSnapshot((snapshot) => {
snapshot.forEach((doc) => {
console.log("User:", doc.data());
});
});
// Clean up listener
unsubscribe();
```
## Configuration
```typescript
const app = initializeApp(firebaseConfig, {
appToken: "your-app-token", // Required - authentication for DataQL
env: "prod", // Optional - 'dev' or 'prod' (default: 'prod')
devPrefix: "dev_", // Optional - prefix for dev environment collections
dbName: "your_app_db", // Optional - database name for data isolation
customConnection: undefined, // Optional - for custom integrations
});
```
### Configuration Options
- **appToken** (required): Authentication token for DataQL
- **env**: Environment - 'dev' or 'prod' (default: 'prod')
- **devPrefix**: Collection prefix for development environment (default: 'dev\_')
- **dbName**: Database name for data isolation (each client gets dedicated database)
- **customConnection**: Advanced option for custom integrations
## Benefits Over Direct Firebase
While maintaining 100% Firebase API compatibility, you get DataQL's enhanced capabilities:
- **Simplified Setup**: No need to manage Firebase projects, billing, or infrastructure
- **Auto-scaling**: Automatic scaling based on usage
- **Offline-first**: Built-in offline support with automatic sync when online
- **Real-time**: Live data updates across all connected clients
- **Global Performance**: Data served from edge locations worldwide for low latency
- **Data Isolation**: Each client gets their own dedicated database automatically
- **Multi-layer Caching**: Optimized performance with intelligent caching
## Migration Guide
### From Firebase
1. **Replace imports**:
```typescript
// Before
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
// After
import { initializeApp, getFirestore } from "@dataql/firebase-adapter";
```
2. **Update app initialization**:
```typescript
// Before - Direct Firebase connection
const app = initializeApp(firebaseConfig);
// After - DataQL authentication
const app = initializeApp(firebaseConfig, {
appToken: "your-app-token", // Required for DataQL authentication
dbName: "your_app_db", // Your database name
});
```
3. **Your Firestore code works exactly the same**:
```typescript
// This works exactly the same - but now powered by DataQL
const db = getFirestore(app);
const snapshot = await db.collection("users").get();
```
## API Compatibility
### Supported Firebase Features
#### Firestore Database
- ✅ `initializeApp(config, options)` - Initialize Firebase app
- ✅ `getFirestore(app)` - Get Firestore instance
- ✅ `collection(path)` - Get collection reference
- ✅ `doc(path)` - Get document reference
#### Collection Operations
- ✅ `.add(data)` - Add document to collection
- ✅ `.doc(id)` - Get document by ID
- ✅ `.where(field, operator, value)` - Filter documents
- ✅ `.orderBy(field, direction)` - Sort documents
- ✅ `.limit(count)` - Limit number of results
- ✅ `.get()` - Execute query and get snapshot
- ✅ `.onSnapshot(callback)` - Real-time listener
#### Document Operations
- ✅ `.get()` - Get document snapshot
- ✅ `.set(data, options)` - Set document data
- ✅ `.update(data)` - Update document fields
- ✅ `.delete()` - Delete document
- ✅ `.onSnapshot(callback)` - Real-time document listener
#### Query Operators
- ✅ `==` - Equal to
- ✅ `!=` - Not equal to
- ✅ `<` - Less than
- ✅ `<=` - Less than or equal to
- ✅ `>` - Greater than
- ✅ `>=` - Greater than or equal to
- ✅ `array-contains` - Array contains value
- ✅ `in` - Field value is in array
- ✅ `array-contains-any` - Array contains any of the values
#### Response Types
- ✅ `DocumentSnapshot` with `.data()`, `.get()`, `.exists`
- ✅ `QuerySnapshot` with `.docs`, `.size`, `.empty`, `.forEach()`
- ✅ `QueryDocumentSnapshot` with `.data()`, `.get()`
- ✅ `WriteResult` with `.writeTime`
### DataQL Enhancements
While maintaining Firebase compatibility, you also get DataQL's additional features:
- **Offline-first**: Automatic offline support and sync
- **Multi-region**: Global data distribution
- **Schema validation**: Optional schema enforcement
- **WAL support**: Write-ahead logging for reliability
- **Unique document creation**: `createUnique()` method to prevent duplicates
## TypeScript Support
Full TypeScript support with inferred types:
```typescript
import {
initializeApp,
getFirestore,
DocumentData,
} from "@dataql/firebase-adapter";
interface User {
name: string;
email: string;
age?: number;
active: boolean;
createdAt?: string;
}
const app = initializeApp(firebaseConfig, options);
const db = getFirestore(app);
// Type-safe operations
const usersRef = db.collection("users");
const userDoc = await usersRef.doc("user123").get();
if (userDoc.exists) {
const userData = userDoc.data() as User;
console.log("User:", userData.name);
}
// Typed queries
const activeUsers = await usersRef.where("active", "==", true).get();
activeUsers.forEach((doc) => {
const user = doc.data() as User;
console.log("Active user:", user.name);
});
```
## Real-time Subscriptions
Real-time features work just like Firebase:
```typescript
// Collection listener
const unsubscribe = db.collection("messages").onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "added") {
console.log("New message:", change.doc.data());
} else if (change.type === "modified") {
console.log("Modified message:", change.doc.data());
} else if (change.type === "removed") {
console.log("Removed message:", change.doc.data());
}
});
});
// Document listener
const docUnsubscribe = db.doc("users/user123").onSnapshot((doc) => {
if (doc.exists) {
console.log("Current data:", doc.data());
} else {
console.log("Document does not exist");
}
});
// Cleanup
unsubscribe();
docUnsubscribe();
```
## Advanced Queries
Complex queries with multiple filters and ordering:
```typescript
// Complex filtering
const posts = await db
.collection("posts")
.where("published", "==", true)
.where("category", "in", ["tech", "science"])
.where("likes", ">=", 10)
.orderBy("createdAt", "desc")
.limit(20)
.get();
posts.forEach((doc) => {
console.log("Post:", doc.data());
});
// Array queries
const usersByTags = await db
.collection("users")
.where("tags", "array-contains", "developer")
.get();
const usersByMultipleTags = await db
.collection("users")
.where("tags", "array-contains-any", ["developer", "designer"])
.get();
// Compound queries
const recentActiveUsers = await db
.collection("users")
.where("active", "==", true)
.where("lastLoginAt", ">=", "2024-01-01")
.orderBy("lastLoginAt", "desc")
.limit(50)
.get();
```
## Document Operations
Standard Firestore document operations:
```typescript
const userRef = db.collection("users").doc("user123");
// Create/set document
await userRef.set({
name: "John Doe",
email: "john@example.com",
active: true,
});
// Update specific fields
await userRef.update({
name: "John Smith",
lastUpdated: new Date(),
});
// Merge data with existing document
await userRef.set(
{
preferences: {
theme: "dark",
notifications: true,
},
},
{ merge: true }
);
// Get document
const doc = await userRef.get();
if (doc.exists) {
console.log("User data:", doc.data());
} else {
console.log("User not found");
}
// Delete document
await userRef.delete();
```
## Error Handling
Standard Firebase error handling:
```typescript
try {
const doc = await db.collection("users").doc("user123").get();
if (doc.exists) {
console.log("User:", doc.data());
} else {
console.log("User not found");
}
} catch (error) {
console.error("Error getting user:", error);
}
// Handling write operations
try {
await db.collection("users").add({
name: "Jane Doe",
email: "jane@example.com",
});
console.log("User created successfully");
} catch (error) {
console.error("Error creating user:", error);
}
```
## Limitations
Some advanced Firebase features are not yet supported:
- **Authentication**: Firebase Auth methods are not implemented (implement your own authentication layer)
- **Storage**: Firebase Storage methods are not implemented (use DataQL's storage instead)
- **Cloud Functions**: Firebase Functions are not supported (use DataQL's serverless functions instead)
- **Security Rules**: Not implemented (use DataQL's security features instead)
- **Subcollections**: Nested collections are not fully implemented
- **Transactions**: Atomic transactions are not yet supported
- **Batch writes**: Batch operations are not yet supported
- **Pagination cursors**: `startAt`, `endAt` methods are simplified
If you need these features, please [open an issue](https://github.com/dataql/dataql/issues).
## Examples
### Basic CRUD Operations
```typescript
const db = getFirestore(app);
// Create
const newUserRef = await db.collection("users").add({
name: "Alice Johnson",
email: "alice@example.com",
role: "admin",
createdAt: new Date(),
});
// Read
const userSnapshot = await db
.collection("users")
.where("role", "==", "admin")
.get();
userSnapshot.forEach((doc) => {
console.log("Admin user:", doc.data());
});
// Update
await db.collection("users").doc(newUserRef.id).update({
name: "Alice Smith",
updatedAt: new Date(),
});
// Delete
await db.collection("users").doc(newUserRef.id).delete();
```
### Real-time Chat Application
```typescript
// Listen for new messages
const messagesRef = db.collection("messages");
const unsubscribe = messagesRef
.orderBy("timestamp", "desc")
.limit(50)
.onSnapshot((snapshot) => {
snapshot.forEach((doc) => {
const message = doc.data();
addMessageToUI(message);
});
});
// Send a message
async function sendMessage(text: string, userId: string) {
try {
await messagesRef.add({
text,
userId,
timestamp: new Date(),
reactions: [],
});
} catch (error) {
console.error("Failed to send message:", error);
}
}
// Cleanup
function cleanup() {
unsubscribe();
}
```
### E-commerce Product Catalog
```typescript
// Query products by category with pagination
async function getProducts(category: string, limit: number = 20) {
const productsRef = db.collection("products");
const snapshot = await productsRef
.where("category", "==", category)
.where("inStock", "==", true)
.orderBy("price", "asc")
.limit(limit)
.get();
return snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
}
// Search products by tags
async function searchProducts(tags: string[]) {
const productsRef = db.collection("products");
const snapshot = await productsRef
.where("tags", "array-contains-any", tags)
.where("available", "==", true)
.get();
return snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
}
// Update product inventory
async function updateInventory(productId: string, quantity: number) {
const productRef = db.collection("products").doc(productId);
await productRef.update({
inventory: quantity,
inStock: quantity > 0,
lastUpdated: new Date(),
});
}
```
## License
MIT