Requêtes
Méthodes GET
Récupère un document unique par une clé étrangère (depuis foreignKeys).
const user = await repos.users.get.byDocId("user123");
const user2 = await repos.users.get.byEmail("alice@example.com");
// Avec DocumentSnapshot brut
const result = await repos.users.get.byDocId("user123", true);
if (result) {
console.log(result.data); // UserModel
console.log(result.doc); // DocumentSnapshot
}
// Récupération par liste de valeurs
const users = await repos.users.get.byList("docId", ["u1", "u2", "u3"]);Méthodes QUERY
Recherche plusieurs documents par une clé de requête (depuis queryKeys).
const actifs = await repos.users.query.byIsActive(true);
const parNom = await repos.users.query.byName("Alice");
// Avec options avancées
const results = await repos.users.query.byIsActive(true, {
where: [["age", ">=", 18]],
orderBy: [{ field: "name", direction: "asc" }],
limit: 50,
});
// Requête générique
const users = await repos.users.query.by({
where: [
["isActive", "==", true],
["age", ">=", 18],
],
orderBy: [{ field: "createdAt", direction: "desc" }],
limit: 10,
select: ["docId", "name", "email"],
});
// Récupérer tout
const all = await repos.users.query.getAll();Conditions OR
orWhere — OR simple
Chaque clause est OR'd indépendamment. Les conditions where de base sont appliquées à chaque branche.
// status == "draft" OU status == "published"
const posts = await repos.posts.query.by({
orWhere: [
["status", "==", "draft"],
["status", "==", "published"],
],
});
// (isActive == true) ET (userId == "A" OU userId == "B")
const posts2 = await repos.posts.query.by({
where: [["isActive", "==", true]], // appliqué à chaque branche
orWhere: [
["userId", "==", "user-A"],
["userId", "==", "user-B"],
],
});orWhereGroups — OR composé (AND dans chaque groupe)
// (status=="published" ET views>100) OU (status=="draft" ET userId=="moi")
const posts = await repos.posts.query.by({
orWhereGroups: [
[["status", "==", "published"], ["views", ">", 100]],
[["status", "==", "draft"], ["userId", "==", monId]],
],
});Sous le capot
Les conditions OR sont simulées en lançant une requête Firestore par branche en parallèle, puis en fusionnant les résultats en mémoire (déduplication par ID). La limite des 30 disjonctions natives de Firestore ne s'applique pas.
Pour in / array-contains-any avec >30 valeurs, les valeurs sont automatiquement découpées en chunks de 30 requêtes.
Référence QueryOptions
interface QueryOptions<T> {
where?: [keyof T, WhereFilterOp, any][]; // conditions AND
orWhere?: [keyof T, WhereFilterOp, any][]; // OR simple (une clause par entrée)
orWhereGroups?: [keyof T, WhereFilterOp, any][][]; // OR composé (groupes AND)
orderBy?: { field: keyof T; direction?: "asc" | "desc" }[];
limit?: number;
offset?: number;
select?: (keyof T)[]; // projection de champs
startAt?: DocumentSnapshot | any[];
startAfter?: DocumentSnapshot | any[];
endAt?: DocumentSnapshot | any[];
endBefore?: DocumentSnapshot | any[];
}Pagination
Pagination basée sur curseur — efficace pour les grandes collections.
// Première page
const page1 = await repos.posts.query.paginate({
pageSize: 10,
orderBy: [{ field: "createdAt", direction: "desc" }],
});
// Page suivante
const page2 = await repos.posts.query.paginate({
pageSize: 10,
cursor: page1.nextCursor,
direction: "next",
});
// Page précédente
const prev = await repos.posts.query.paginate({
pageSize: 10,
cursor: page2.prevCursor,
direction: "prev",
});Paginer avec filtres et OR
const page = await repos.posts.query.paginate({
pageSize: 10,
where: [["status", "==", "published"]],
orWhere: [
["userId", "==", monId],
["featured", "==", true],
],
orderBy: [{ field: "createdAt", direction: "desc" }],
});Paginer avec include (populate par page)
const page = await repos.posts.query.paginate({
pageSize: 10,
include: [
"docId", // many → CommentModel[]
{ relation: "userId", select: ["docId", "name"] }, // one → UserModel partiel
],
});
for (const post of page.data) {
console.log(post.populated.docId); // CommentModel[]
console.log(post.populated.userId); // { docId, name }
}Itérer toutes les pages — paginateAll
Générateur async qui avance automatiquement les curseurs. Idéal pour les migrations et exports.
for await (const page of repos.posts.query.paginateAll({ pageSize: 100 })) {
console.log(`${page.data.length} posts sur cette page`);
}
// Avec 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);
}
}Listener temps réel
const unsub = repos.users.query.onSnapshot(
{ where: [["isActive", "==", true]] },
(users) => console.log(users),
(err) => console.error(err),
);
unsub(); // arrêter le listener