TypeScript type definitions for Firebase Firestore providing comprehensive type safety for database operations
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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`);
}