@dataql/firebase-adapter
v1.4.0
Published
Firebase adapter for DataQL with zero API changes
Downloads
20
Maintainers
Readme
@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
npm install @dataql/core @dataql/firebase-adapterQuick Start
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: "[email protected]",
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
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
Replace imports:
// Before import { initializeApp } from "firebase/app"; import { getFirestore } from "firebase/firestore"; // After import { initializeApp, getFirestore } from "@dataql/firebase-adapter";Update app initialization:
// 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 });Your Firestore code works exactly the same:
// 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
- ✅
DocumentSnapshotwith.data(),.get(),.exists - ✅
QuerySnapshotwith.docs,.size,.empty,.forEach() - ✅
QueryDocumentSnapshotwith.data(),.get() - ✅
WriteResultwith.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:
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:
// 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:
// 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:
const userRef = db.collection("users").doc("user123");
// Create/set document
await userRef.set({
name: "John Doe",
email: "[email protected]",
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:
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: "[email protected]",
});
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,endAtmethods are simplified
If you need these features, please open an issue.
Examples
Basic CRUD Operations
const db = getFirestore(app);
// Create
const newUserRef = await db.collection("users").add({
name: "Alice Johnson",
email: "[email protected]",
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
// 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
// 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
