CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-firebase--firestore-types

TypeScript type definitions for Firebase Firestore providing comprehensive type safety for database operations

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

transactions-batches.mddocs/

Transactions and Batches

Types for atomic operations, transactions, and batch writes that ensure data consistency across multiple documents.

Capabilities

Transaction Class

Provides atomic read and write operations that execute as a single unit.

class Transaction {
  private constructor();
  
  /** Read a document within the transaction */
  get<T>(documentRef: DocumentReference<T>): Promise<DocumentSnapshot<T>>;
  
  /** Set document data within the transaction */
  set<T>(
    documentRef: DocumentReference<T>,
    data: Partial<T>,
    options: SetOptions
  ): Transaction;
  set<T>(documentRef: DocumentReference<T>, data: T): Transaction;
  
  /** Update document fields within the transaction */
  update(documentRef: DocumentReference<any>, data: UpdateData): Transaction;
  update(
    documentRef: DocumentReference<any>,
    field: string | FieldPath,
    value: any,
    ...moreFieldsAndValues: any[]
  ): Transaction;
  
  /** Delete a document within the transaction */
  delete(documentRef: DocumentReference<any>): Transaction;
}

WriteBatch Class

Provides batched write operations that execute atomically.

class WriteBatch {
  private constructor();
  
  /** Add a set operation to the batch */
  set<T>(
    documentRef: DocumentReference<T>,
    data: Partial<T>,
    options: SetOptions
  ): WriteBatch;
  set<T>(documentRef: DocumentReference<T>, data: T): WriteBatch;
  
  /** Add an update operation to the batch */
  update(documentRef: DocumentReference<any>, data: UpdateData): WriteBatch;
  update(
    documentRef: DocumentReference<any>,
    field: string | FieldPath,
    value: any,
    ...moreFieldsAndValues: any[]
  ): WriteBatch;
  
  /** Add a delete operation to the batch */
  delete(documentRef: DocumentReference<any>): WriteBatch;
  
  /** Execute all batched operations atomically */
  commit(): Promise<void>;
}

Usage Examples

Transaction Operations

import type { Transaction, DocumentReference, DocumentSnapshot } from "@firebase/firestore-types";

interface BankAccount {
  balance: number;
  accountId: string;
  lastTransaction: Timestamp;
}

interface TransferRequest {
  fromAccount: string;
  toAccount: string;
  amount: number;
}

async function transferMoney(
  firestore: FirebaseFirestore,
  fromAccountRef: DocumentReference<BankAccount>,
  toAccountRef: DocumentReference<BankAccount>,
  amount: number
) {
  try {
    await firestore.runTransaction(async (transaction: Transaction) => {
      // Read both accounts
      const fromDoc = await transaction.get(fromAccountRef);
      const toDoc = await transaction.get(toAccountRef);
      
      if (!fromDoc.exists || !toDoc.exists) {
        throw new Error("One or both accounts do not exist");
      }
      
      const fromData = fromDoc.data();
      const toData = toDoc.data();
      
      if (!fromData || !toData) {
        throw new Error("Failed to read account data");
      }
      
      // Check sufficient funds
      if (fromData.balance < amount) {
        throw new Error("Insufficient funds");
      }
      
      // Perform the transfer
      transaction.update(fromAccountRef, {
        balance: fromData.balance - amount,
        lastTransaction: FieldValue.serverTimestamp()
      });
      
      transaction.update(toAccountRef, {
        balance: toData.balance + amount,
        lastTransaction: FieldValue.serverTimestamp()
      });
    });
    
    console.log("Transfer completed successfully");
  } catch (error) {
    console.error("Transfer failed:", error);
    throw error;
  }
}

Complex Transaction Logic

interface Inventory {
  productId: string;
  stock: number;
  reserved: number;
  lastUpdated: Timestamp;
}

interface Order {
  orderId: string;
  items: Array<{ productId: string; quantity: number }>;
  status: 'pending' | 'confirmed' | 'cancelled';
  createdAt: Timestamp;
}

async function processOrder(
  firestore: FirebaseFirestore,
  orderRef: DocumentReference<Order>,
  inventoryRefs: DocumentReference<Inventory>[],
  orderData: Order
) {
  return firestore.runTransaction(async (transaction: Transaction) => {
    // Read order and all inventory items
    const orderDoc = await transaction.get(orderRef);
    const inventoryDocs = await Promise.all(
      inventoryRefs.map(ref => transaction.get(ref))
    );
    
    // Validate order doesn't exist
    if (orderDoc.exists) {
      throw new Error("Order already exists");
    }
    
    // Check inventory availability
    for (let i = 0; i < orderData.items.length; i++) {
      const item = orderData.items[i];
      const inventoryDoc = inventoryDocs[i];
      
      if (!inventoryDoc.exists) {
        throw new Error(`Product ${item.productId} not found`);
      }
      
      const inventory = inventoryDoc.data();
      if (!inventory) {
        throw new Error(`Failed to read inventory for ${item.productId}`);
      }
      
      const availableStock = inventory.stock - inventory.reserved;
      if (availableStock < item.quantity) {
        throw new Error(`Insufficient stock for ${item.productId}`);
      }
    }
    
    // Create order
    transaction.set(orderRef, {
      ...orderData,
      status: 'confirmed',
      createdAt: FieldValue.serverTimestamp()
    });
    
    // Reserve inventory
    for (let i = 0; i < orderData.items.length; i++) {
      const item = orderData.items[i];
      const inventoryRef = inventoryRefs[i];
      
      transaction.update(inventoryRef, {
        reserved: FieldValue.increment(item.quantity),
        lastUpdated: FieldValue.serverTimestamp()
      });
    }
  });
}

Batch Write Operations

import type { WriteBatch } from "@firebase/firestore-types";

interface User {
  name: string;
  email: string;
  createdAt: Timestamp;
  isActive: boolean;
}

async function bulkCreateUsers(
  firestore: FirebaseFirestore,
  usersCollection: CollectionReference<User>,
  userDataList: Omit<User, 'createdAt'>[]
) {
  // Firestore batches are limited to 500 operations
  const batchSize = 500;
  const batches: WriteBatch[] = [];
  
  for (let i = 0; i < userDataList.length; i += batchSize) {
    const batch = firestore.batch();
    const batchData = userDataList.slice(i, i + batchSize);
    
    batchData.forEach(userData => {
      const docRef = usersCollection.doc(); // Auto-generate ID
      batch.set(docRef, {
        ...userData,
        createdAt: FieldValue.serverTimestamp()
      });
    });
    
    batches.push(batch);
  }
  
  // Execute all batches
  await Promise.all(batches.map(batch => batch.commit()));
  console.log(`Created ${userDataList.length} users in ${batches.length} batches`);
}

Mixed Batch Operations

interface Product {
  name: string;
  price: number;
  category: string;
  inStock: boolean;
}

interface Category {
  name: string;
  productCount: number;
  lastUpdated: Timestamp;
}

async function updateProductsAndCategories(
  firestore: FirebaseFirestore,
  updates: {
    productsToUpdate: Array<{ ref: DocumentReference<Product>; data: Partial<Product> }>;
    productsToDelete: DocumentReference<Product>[];
    categoryUpdates: Array<{ ref: DocumentReference<Category>; countChange: number }>;
  }
) {
  const batch = firestore.batch();
  
  // Update products
  updates.productsToUpdate.forEach(({ ref, data }) => {
    batch.update(ref, {
      ...data,
      lastUpdated: FieldValue.serverTimestamp()
    });
  });
  
  // Delete products
  updates.productsToDelete.forEach(ref => {
    batch.delete(ref);
  });
  
  // Update category counts
  updates.categoryUpdates.forEach(({ ref, countChange }) => {
    batch.update(ref, {
      productCount: FieldValue.increment(countChange),
      lastUpdated: FieldValue.serverTimestamp()
    });
  });
  
  // Execute all operations atomically
  await batch.commit();
  console.log("Batch update completed successfully");
}

Error Handling and Retry Logic

async function robustTransactionOperation<T>(
  firestore: FirebaseFirestore,
  operation: (transaction: Transaction) => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  let lastError: Error;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await firestore.runTransaction(operation);
    } catch (error) {
      lastError = error as Error;
      
      // Don't retry certain errors
      if (error instanceof Error) {
        const firestoreError = error as any;
        if (firestoreError.code === 'permission-denied' || 
            firestoreError.code === 'not-found') {
          throw error;
        }
      }
      
      console.log(`Transaction attempt ${attempt} failed:`, error);
      
      if (attempt === maxRetries) {
        break;
      }
      
      // Exponential backoff
      const delay = Math.pow(2, attempt - 1) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

// Usage
async function safeTransfer(
  firestore: FirebaseFirestore,
  fromAccountRef: DocumentReference<BankAccount>,
  toAccountRef: DocumentReference<BankAccount>,
  amount: number
) {
  return robustTransactionOperation(firestore, async (transaction) => {
    // Transaction logic here (same as transferMoney example)
    const fromDoc = await transaction.get(fromAccountRef);
    const toDoc = await transaction.get(toAccountRef);
    
    // ... rest of transaction logic
  });
}

Performance Considerations

// Efficient batch sizing
async function efficientBatchWrite<T>(
  firestore: FirebaseFirestore,
  operations: Array<{
    type: 'set' | 'update' | 'delete';
    ref: DocumentReference<T>;
    data?: any;
  }>
) {
  const maxBatchSize = 500; // Firestore limit
  const batches: WriteBatch[] = [];
  
  for (let i = 0; i < operations.length; i += maxBatchSize) {
    const batch = firestore.batch();
    const batchOps = operations.slice(i, i + maxBatchSize);
    
    batchOps.forEach(op => {
      switch (op.type) {
        case 'set':
          batch.set(op.ref, op.data);
          break;
        case 'update':
          batch.update(op.ref, op.data);
          break;
        case 'delete':
          batch.delete(op.ref);
          break;
      }
    });
    
    batches.push(batch);
  }
  
  // Execute batches in parallel (be careful with rate limits)
  const results = await Promise.allSettled(
    batches.map(batch => batch.commit())
  );
  
  const failed = results.filter(result => result.status === 'rejected');
  if (failed.length > 0) {
    console.error(`${failed.length} batches failed`);
    throw new Error('Some batch operations failed');
  }
  
  console.log(`Successfully executed ${batches.length} batches`);
}

docs

collections-queries.md

core-database.md

data-snapshots.md

data-types.md

document-references.md

index.md

transactions-batches.md

tile.json