@cyber-eco/firebase¶
Firebase implementation of the StorageAdapter interface. Provides FirebaseStorageAdapter for Firestore operations, plus auth and initialization helpers.
Installation¶
Runtime Dependency
This package depends on @cyber-eco/types for interface definitions and firebase as a peer dependency. You must install Firebase yourself.
FirebaseStorageAdapter¶
The primary export. Implements the StorageAdapter interface from @cyber-eco/types, backed by Cloud Firestore.
import { FirebaseStorageAdapter } from '@cyber-eco/firebase';
import { getHubFirestore } from '@cyber-eco/firebase';
const adapter = new FirebaseStorageAdapter(() => getHubFirestore());
Lazy Initialization Pattern¶
FirebaseStorageAdapter takes a function () => Firestore rather than a Firestore instance directly. This is a deliberate design choice:
// The constructor signature
class FirebaseStorageAdapter implements StorageAdapter {
constructor(private getFirestore: () => Firestore) {}
private db: Firestore | null = null;
private ensureDb(): Firestore {
if (!this.db) {
this.db = this.getFirestore();
}
return this.db;
}
}
Why Lazy Init?
Module-level Firestore initialization crashes in environments where Firebase has not been configured yet (SSR, tests, edge functions). The getter pattern defers initialization until the first database operation, avoiding FirebaseError: No Firebase App errors at import time.
Supported Operations¶
All StorageAdapter methods are implemented:
| Method | Description |
|---|---|
getDocument<T>(collection, id) |
Fetch a single document by ID |
setDocument<T>(collection, id, data, options?) |
Create or overwrite a document (supports merge: true) |
updateDocument(collection, id, data) |
Partially update an existing document |
deleteDocument(collection, id) |
Delete a document |
query<T>(collection, filters, options?) |
Query with filters, sorting, and cursor-based pagination |
batchWrite(operations) |
Execute multiple writes atomically |
subscribe<T>(collection, id, callback) |
Real-time listener for a single document |
subscribeToQuery<T>(collection, filters, callback) |
Real-time listener for a query |
serverTimestamp() |
Firestore server timestamp sentinel |
generateId(collection) |
Generate a unique document ID |
Query and Pagination¶
Queries support all Firestore operators and return PaginatedResult<T> with cursor-based pagination:
import type { QueryFilter, QueryOptions } from '@cyber-eco/types';
const filters: QueryFilter[] = [
{ field: 'status', operator: '==', value: 'active' },
{ field: 'createdAt', operator: '>=', value: '2024-01-01' },
];
const options: QueryOptions = {
sort: [{ field: 'createdAt', direction: 'desc' }],
limit: 25,
};
const result = await adapter.query('users', filters, options);
// result.data -- array of matching documents
// result.hasMore -- whether more pages exist
// result.cursor -- pass to next query for pagination
Firebase Initialization¶
initializeFirebase()¶
Initializes the Firebase app(s) with optional emulator support.
import { initializeFirebase } from '@cyber-eco/firebase';
const { hubApp, appApp } = initializeFirebase({
hubConfig: {
apiKey: 'your-api-key',
authDomain: 'your-project.firebaseapp.com',
projectId: 'your-project-id',
storageBucket: 'your-project.appspot.com',
messagingSenderId: '123456789',
appId: '1:123456789:web:abcdef',
},
useEmulators: import.meta.env.DEV,
emulatorPorts: {
auth: 9099,
firestore: 8080,
},
});
Idempotent Initialization
initializeFirebase() checks for existing apps by name before creating new ones. It is safe to call multiple times.
Getter Functions¶
import { getHubApp, getHubFirestore, getAppFirestore } from '@cyber-eco/firebase';
// Get the Firebase App instance
const app = getHubApp();
// Get Firestore instances
const hubDb = getHubFirestore(); // Hub's Firestore
const appDb = getAppFirestore(); // App-specific Firestore (if configured)
Auth Operations¶
Convenience wrappers around Firebase Authentication:
import {
getHubAuth,
getCurrentUser,
signIn,
signUp,
signOut,
resetPassword,
onAuthChange,
} from '@cyber-eco/firebase';
// Sign in
const userCredential = await signIn('user@example.com', 'password');
// Sign up
const newUser = await signUp('user@example.com', 'password');
// Listen for auth state changes
const unsubscribe = onAuthChange((user) => {
if (user) {
console.log('Signed in:', user.uid);
} else {
console.log('Signed out');
}
});
// Reset password
await resetPassword('user@example.com');
// Sign out
await signOut();
Firestore Helpers¶
Lower-level Firestore operations for cases where you need direct access outside the StorageAdapter pattern:
import {
getDocumentData,
setDocumentData,
updateDocumentData,
deleteDocumentData,
queryDocuments,
} from '@cyber-eco/firebase';
// Direct document operations
const user = await getDocumentData('users', 'user-123');
await setDocumentData('users', 'user-123', { name: 'Alice' });
await updateDocumentData('users', 'user-123', { name: 'Bob' });
await deleteDocumentData('users', 'user-123');
Real-Time Subscriptions¶
import { subscribeToDocument, subscribeToCollection } from '@cyber-eco/firebase';
// Subscribe to a single document
const unsubscribe = subscribeToDocument('users', 'user-123', (data) => {
console.log('User updated:', data);
});
// Subscribe to a collection query
const unsubscribeQuery = subscribeToCollection('notifications', (docs) => {
console.log('Notifications:', docs);
});
Query Builder and Batch Operations¶
import { buildQueryConstraints } from '@cyber-eco/firebase';
import { executeBatch } from '@cyber-eco/firebase';
// Build Firestore query constraints from QueryFilter[]
const constraints = buildQueryConstraints(filters, options);
// Execute a batch write
const result = await executeBatch(db, [
{ type: 'set', collection: 'users', id: 'u1', data: { name: 'Alice' } },
{ type: 'update', collection: 'users', id: 'u2', data: { name: 'Bob' } },
{ type: 'delete', collection: 'users', id: 'u3' },
]);
Offline Persistence¶
import { enablePersistence, clearPersistence } from '@cyber-eco/firebase';
// Enable offline persistence
await enablePersistence();
// Clear persisted data
await clearPersistence();
Emulator Support¶
Connect to Firebase Local Emulator Suite for development:
import { connectEmulators } from '@cyber-eco/firebase';
import type { EmulatorPorts } from '@cyber-eco/firebase';
const ports: EmulatorPorts = {
auth: 9099,
firestore: 8080,
};
connectEmulators(app, ports);
Full Example: Wiring to DataLayerService¶
The typical integration pattern connects FirebaseStorageAdapter to the createDataLayer() factory from @cyber-eco/services:
import { initializeFirebase, getHubFirestore } from '@cyber-eco/firebase';
import { FirebaseStorageAdapter } from '@cyber-eco/firebase';
import { createDataLayer } from '@cyber-eco/services';
// 1. Initialize Firebase
initializeFirebase({
hubConfig: { /* ... */ },
useEmulators: false,
emulatorPorts: { auth: 9099, firestore: 8080 },
});
// 2. Create the adapter with lazy initialization
const adapter = new FirebaseStorageAdapter(() => getHubFirestore());
// 3. Bootstrap the full data layer
const dataLayer = createDataLayer({
adapter,
cache: { strategy: 'lru', maxSize: 500, defaultTtl: 300 },
permissions: { enabled: true },
sync: { enabled: true },
webhooks: { enabled: false },
});
// 4. Use domain services
const user = await dataLayer.core.get('requester-id', 'users', 'user-123');
const notifications = await dataLayer.notifications.getForUser('user-123');
Exports Summary¶
| Category | Exports |
|---|---|
| Adapter | FirebaseStorageAdapter |
| Config | initializeFirebase, getHubApp, getAppApp |
| Config types | FirebaseConfig, FirebaseEnvironment |
| Auth | getHubAuth, getCurrentUser, signIn, signUp, signOut, resetPassword, onAuthChange |
| Firestore | getHubFirestore, getAppFirestore, getDocumentData, setDocumentData, updateDocumentData, deleteDocumentData, queryDocuments |
| Subscriptions | subscribeToDocument, subscribeToCollection |
| Query | buildQueryConstraints |
| Batch | executeBatch |
| Persistence | enablePersistence, clearPersistence |
| Emulator | connectEmulators, EmulatorPorts |