Skip to content

Advanced Usage

Batch Operations

For atomic operations (max 500 operations).

typescript
const batch = repos.users.batch.create();

// Set with document ID
batch.set("user-1", {
  name: "User One",
  email: "user1@example.com",
  age: 25,
  isActive: true,
});

// Update existing
batch.update("user-2", {
  age: 30,
});

// Delete
batch.delete("user-3");

await batch.commit();

Subcollection Batch Operations

typescript
// For subcollections, pass parent IDs first, then document ID
const batch = repos.comments.batch.create();

batch.set(postId, "comment-1", {
  postId: postId,
  userId: userId,
  content: "First comment",
  likes: 0,
});

batch.set(postId, "comment-2", {
  postId: postId,
  userId: userId,
  content: "Second comment",
  likes: 0,
});

await batch.commit();

Bulk Operations

For processing large amounts of data (automatically split into batches of 500).

typescript
// Get Firestore instance
const db = getFirestore();

// Bulk Set
await repos.users.bulk.set([
  {
    docRef: db.collection("users").doc("user1"),
    data: {
      name: "User 1",
      email: "user1@example.com",
      age: 25,
      isActive: true,
    },
    merge: true,
  },
  {
    docRef: db.collection("users").doc("user2"),
    data: {
      name: "User 2",
      email: "user2@example.com",
      age: 30,
      isActive: true,
    },
  },
  // ... up to thousands of documents
]);

// Bulk Update
await repos.users.bulk.update([
  { docRef: db.collection("users").doc("user1"), data: { age: 30 } },
  { docRef: db.collection("users").doc("user2"), data: { age: 25 } },
]);

// Bulk Delete
await repos.users.bulk.delete([
  db.collection("users").doc("user1"),
  db.collection("users").doc("user2"),
]);

Real-time Listeners (onSnapshot)

typescript
// Listen for real-time changes
const unsubscribe = repos.users.query.onSnapshot(
  {
    where: [["isActive", "==", true]],
    orderBy: [{ field: "name", direction: "asc" }],
  },
  (users) => {
    console.log("Updated data:", users);
  },
  (error) => {
    console.error("Error:", error);
  }
);

// Stop listening
unsubscribe();

Cursor Pagination

Cursor-based pagination is more efficient than offset for large collections.

typescript
// First page
const firstPage = await repos.users.query.by({
  orderBy: [{ field: "createdAt", direction: "desc" }],
  limit: 10,
});

// Next page using the last document
const lastDoc = firstPage[firstPage.length - 1];
const nextPage = await repos.users.query.by({
  orderBy: [{ field: "createdAt", direction: "desc" }],
  startAfter: lastDoc, // or use an array of values
  limit: 10,
});

Aggregations

typescript
// Count documents
const totalPosts = await repos.posts.aggregate.count();

// Count with filter
const publishedCount = await repos.posts.aggregate.count({
  where: [["status", "==", "published"]],
});

// Sum a field
const totalViews = await repos.posts.aggregate.sum("views");

// Sum with filter
const publishedViews = await repos.posts.aggregate.sum("views", {
  where: [["status", "==", "published"]],
});

// Average
const avgLikes = await repos.posts.aggregate.average("likes");

Transactions

typescript
// Transaction with type-safe methods
await repos.categories.transaction.run(async (t) => {
  // Get document in transaction
  const category = await t.get(categoryId);

  if (category) {
    // Update in transaction
    t.update(categoryId, { postCount: category.postCount + 1 });
  }

  // Set in transaction
  t.set("new-category", {
    name: "New Category",
    slug: "new-category",
    postCount: 0,
  });

  // Delete in transaction
  t.delete("old-category");
});

Collection Group Queries

Query across all subcollections with the same name.

typescript
// Define with isGroup: true
const mapping = {
  comments: createRepositoryConfig<CommentModel>()({
    path: "comments",
    isGroup: true, // Important!
    foreignKeys: ["docId"] as const,
    queryKeys: ["postId", "userId"] as const,
    refCb: (db, postId, commentId) =>
      doc(db, "posts", postId, "comments", commentId),
  }),
};

// Query all comments by a user across all posts
const userComments = await repos.comments.query.byUserId(userId);

Select (Field Projection)

Reduce payload size by selecting only needed fields.

typescript
// Query with select
const userNames = await repos.users.query.by({
  where: [["isActive", "==", true]],
  select: ["docId", "name", "email"],
});

// Pagination with select
const page = await repos.posts.query.paginate({
  pageSize: 10,
  select: ["docId", "title", "status"],
});

// Include with select (relations)
const pageWithRelations = await repos.posts.query.paginate({
  pageSize: 10,
  include: [{ relation: "userId", select: ["docId", "name"] }],
});