Skip to content

@cyber-eco/firebase

Firebase implementation of the StorageAdapter interface. Provides FirebaseStorageAdapter for Firestore operations, plus auth and initialization helpers.

Installation

npm install @cyber-eco/firebase firebase
Peer Version Required
firebase >=11.0.0 Yes

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