Types for atomic operations, transactions, and batch writes that ensure data consistency across multiple documents.
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;
}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>;
}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;
}
}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()
});
}
});
}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`);
}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");
}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
});
}// 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`);
}