Querying
GET Methods
Retrieve a single document by a foreign key.
typescript
const user = await repos.users.get.byDocId("user123");
const user2 = await repos.users.get.byEmail("alice@example.com");
// With raw DocumentSnapshot
const result = await repos.users.get.byDocId("user123", true);
if (result) {
console.log(result.data); // UserModel
console.log(result.doc); // DocumentSnapshot
}
// Batch get
const users = await repos.users.get.byList("docId", ["u1", "u2", "u3"]);QUERY Methods
Search for multiple documents by a query key.
typescript
const activeUsers = await repos.users.query.byIsActive(true);
const byName = await repos.users.query.byName("Alice");
// With extra options
const results = await repos.users.query.byIsActive(true, {
where: [["age", ">=", 18]],
orderBy: [{ field: "name", direction: "asc" }],
limit: 50,
});
// Generic query
const users = await repos.users.query.by({
where: [
["isActive", "==", true],
["age", ">=", 18],
],
orderBy: [{ field: "createdAt", direction: "desc" }],
limit: 10,
select: ["docId", "name", "email"],
});
// Get all
const all = await repos.users.query.getAll();OR Conditions
orWhere — simple OR
Each clause is independently OR'd. Base where conditions are applied to every branch.
typescript
// status == "draft" OR status == "published"
const posts = await repos.posts.query.by({
orWhere: [
["status", "==", "draft"],
["status", "==", "published"],
],
});
// (isActive == true) AND (userId == "A" OR userId == "B")
const posts2 = await repos.posts.query.by({
where: [["isActive", "==", true]], // applied to every branch
orWhere: [
["userId", "==", "user-A"],
["userId", "==", "user-B"],
],
});orWhereGroups — compound OR (AND-within-OR)
typescript
// (status=="published" AND views>100) OR (status=="draft" AND userId=="me")
const posts = await repos.posts.query.by({
orWhereGroups: [
[["status", "==", "published"], ["views", ">", 100]],
[["status", "==", "draft"], ["userId", "==", myId]],
],
});Under the hood
OR conditions are simulated by running one Firestore query per branch in parallel, then merging results in memory (dedup by document ID). No 30-disjunction limit applies.
in / array-contains-any operators with >30 values are automatically split into chunks of 30 queries.
QueryOptions reference
typescript
interface QueryOptions<T> {
where?: [keyof T, WhereFilterOp, any][]; // AND conditions
orWhere?: [keyof T, WhereFilterOp, any][]; // simple OR (one clause per entry)
orWhereGroups?: [keyof T, WhereFilterOp, any][][]; // compound OR (AND groups)
orderBy?: { field: keyof T; direction?: "asc" | "desc" }[];
limit?: number;
offset?: number;
select?: (keyof T)[]; // field projection
startAt?: DocumentSnapshot | any[];
startAfter?: DocumentSnapshot | any[];
endAt?: DocumentSnapshot | any[];
endBefore?: DocumentSnapshot | any[];
}Pagination
Cursor-based pagination — efficient for large collections.
typescript
// First page
const page1 = await repos.posts.query.paginate({
pageSize: 10,
orderBy: [{ field: "createdAt", direction: "desc" }],
});
// page1.data → PostModel[]
// page1.hasNextPage → boolean
// page1.hasPrevPage → boolean
// page1.nextCursor → DocumentSnapshot | undefined
// page1.prevCursor → DocumentSnapshot | undefined
// Next page
const page2 = await repos.posts.query.paginate({
pageSize: 10,
cursor: page1.nextCursor,
direction: "next", // default
});
// Previous page
const prev = await repos.posts.query.paginate({
pageSize: 10,
cursor: page2.prevCursor,
direction: "prev",
});Paginate with filters and OR
typescript
const page = await repos.posts.query.paginate({
pageSize: 10,
where: [["status", "==", "published"]],
orWhere: [
["userId", "==", currentUserId],
["featured", "==", true],
],
orderBy: [{ field: "createdAt", direction: "desc" }],
});Paginate with include (populate relations per page)
typescript
const page = await repos.posts.query.paginate({
pageSize: 10,
include: [
"docId", // many → CommentModel[]
{ relation: "userId", select: ["docId", "name"] }, // one → partial UserModel
],
});
for (const post of page.data) {
console.log(post.populated.docId); // CommentModel[]
console.log(post.populated.userId); // { docId, name }
}Iterate all pages — paginateAll
Async generator that automatically advances cursors. Ideal for migrations and exports.
typescript
for await (const page of repos.posts.query.paginateAll({ pageSize: 100 })) {
console.log(`${page.data.length} posts on this page`);
}
// With include
for await (const page of repos.posts.query.paginateAll({
pageSize: 100,
include: [{ relation: "userId", select: ["name"] }],
})) {
for (const post of page.data) {
console.log(post.populated.userId?.name);
}
}Real-time listener
typescript
const unsub = repos.users.query.onSnapshot(
{ where: [["isActive", "==", true]] },
(users) => console.log(users),
(err) => console.error(err),
);
unsub(); // stop listening