Skip to content

Usage avancé

CRUD set()

Crée ou remplace un document avec un ID spécifique. documentKey et pathKey sont injectés automatiquement (comme create()).

typescript
const post = await repos.posts.set("mon-post", {
  title: "Hello",
  status: "draft",
  userId: "user_1",
});
console.log(post.docId);        // "mon-post"
console.log(post.documentPath); // "posts/mon-post"

// Avec merge
await repos.posts.set("mon-post", { title: "Mis à jour" }, { merge: true });

Opérations Batch

Écriture atomique, maximum 500 opérations.

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

batch.set("post-1", { title: "Post 1", userId: "u1", status: "draft" });
batch.set("post-2", { title: "Post 2", userId: "u1", status: "published" });
batch.update("post-3", { status: "published" });
batch.delete("post-ancien");

await batch.commit();

Batch sur sous-collection

typescript
const batch = repos.comments.batch.create();
batch.set(postId, "comment-1", { postId, userId, content: "Salut !", likes: 0 });
batch.set(postId, "comment-2", { postId, userId, content: "Monde !", likes: 0 });
await batch.commit();

Opérations Bulk

Découpées automatiquement en batches de 500.

typescript
const db = getFirestore();

await repos.users.bulk.set([
  { docRef: db.collection("users").doc("u1"), data: { name: "Alice" }, merge: true },
  { docRef: db.collection("users").doc("u2"), data: { name: "Bob" } },
]);

await repos.users.bulk.update([
  { docRef: db.collection("users").doc("u1"), data: { age: 30 } },
]);

await repos.users.bulk.delete([
  db.collection("users").doc("u1"),
]);

Agrégations

Exécutées côté serveur Firestore — aucun document transféré.

typescript
const total  = await repos.users.aggregate.count();
const actifs = await repos.users.aggregate.count({ where: [["isActive", "==", true]] });
const somme  = await repos.users.aggregate.sum("age");
const moy    = await repos.users.aggregate.average("age", { where: [["isActive", "==", true]] });

Transactions

typescript
await repos.users.transaction.run(async (tx) => {
  const user = await tx.get("user_1");
  if (!user) throw new Error("introuvable");
  await tx.update("user_1", { age: user.age + 1 });
});

Listener temps réel

typescript
const unsub = repos.users.query.onSnapshot(
  { where: [["isActive", "==", true]], orderBy: [{ field: "name" }] },
  (users) => console.log("live:", users),
  (err)   => console.error(err),
);
unsub();

Patterns OR avancés

typescript
// OR simple
await repos.posts.query.by({
  where:   [["isPublic", "==", true]],
  orWhere: [
    ["userId",   "==", "user-A"],
    ["authorId", "==", "user-A"],
  ],
});

// OR composé : (A ET B) OU (C ET D)
await repos.posts.query.by({
  orWhereGroups: [
    [["status", "==", "published"], ["views", ">", 1000]],
    [["status", "==", "featured"],  ["pinned", "==", true]],
  ],
});

Opérateur in avec >30 valeurs

typescript
const ids = Array.from({ length: 90 }, (_, i) => `id-${i}`);

// Génère 3 requêtes Firestore (30+30+30) fusionnées en mémoire
const docs = await repos.users.query.by({
  where: [["docId", "in", ids]],
});

Gestion des dates (setDateHandling)

Firestore stocke les dates en Timestamp. Par défaut le SDK les retourne tels quels en lecture — pratique en JS pur, mais pénible pour les API JSON, l'OpenAPI, BigQuery, ou tout code qui attend du Date natif / des ISO strings.

setDateHandling() est un switch global avec deux modes :

typescript
import { setDateHandling } from "@lpdjs/firestore-repo-service";

// Au démarrage de ton app (init serveur, init function, etc.)
setDateHandling("normalize"); // ou "preserve"

"preserve" (défaut — non-breaking)

Comportement inchangé :

  • Les lectures du repo renvoient des Timestamp bruts.
  • La validation CRUD z.date() reste stricte (rejette les ISO strings).
  • La sortie JSON CRUD peut contenir { _seconds, _nanoseconds } si tu laisses passer un Timestamp.

À choisir si ton code manipule déjà des Timestamp et que tu ne veux aucun changement de comportement.

"normalize" (recommandé pour les nouveaux projets)

Tout converge sur Date JS côté code et ISO 8601 sur le réseau :

CoucheComportement
get.by*, getAll, query.by*Conversion récursive TimestampDate (objets/arrays imbriqués inclus).
paginate, transaction.getMême normalisation récursive.
Retours create, set, updateMême normalisation récursive.
Validation d'entrée CRUDz.date() est wrappé en z.preprocess(coerceToDate) et accepte : Date, Timestamp, ISO string, {_seconds,_nanoseconds}, epoch ms.
Sortie JSON CRUDDate → ISO string (natif), plus de fuite {_seconds,_nanoseconds}.
OpenAPIz.date() documenté en string / format: date-time (matche le runtime).
Sync BigQueryInchangé — fonctionne identiquement avec Date ou Timestamp.
Serveur AdminInchangé — déjà défensif (gère tous les formats).

Helpers

Les utilitaires de conversion sont exportés au cas où tu en aurais besoin manuellement :

typescript
import {
  coerceToDate,
  normalizeTimestamps,
  getDateHandling,
} from "@lpdjs/firestore-repo-service";

// Date | Timestamp | ISO | epoch ms | {_seconds,_nanoseconds} -> Date | null
const d = coerceToDate(req.body.publishedAt);

// Convertit récursivement les Timestamps en Dates
const normalized = normalizeTimestamps(somePayload);

// Lecture du mode global courant
getDateHandling(); // "preserve" | "normalize"