@lpdjs/firestore-repo-service 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,6 +20,8 @@ Un service de repository type-safe pour Firestore avec génération automatique
20
20
  - 🔢 **Agrégations** : Count, sum, average avec support serveur
21
21
  - ✏️ **CRUD complet** : Create, set, update, delete avec types préservés
22
22
  - 🔄 **Transactions** : Opérations transactionnelles type-safe
23
+ - 🔗 **Relations** : Populate avec select typé (champs projetés)
24
+ - 📄 **Pagination** : Curseurs, include avec relations, select
23
25
 
24
26
  ## 📦 Installation
25
27
 
@@ -110,9 +112,9 @@ if (post) {
110
112
  console.log(postWithUser.populated.users?.name); // Type-safe!
111
113
  }
112
114
 
113
- // Recherche avec options
115
+ // Recherche avec options (tuples where)
114
116
  const filteredUsers = await repos.users.query.byName("John", {
115
- where: [{ field: "age", operator: ">=", value: 18 }],
117
+ where: [["age", ">=", 18]],
116
118
  orderBy: [{ field: "createdAt", direction: "desc" }],
117
119
  limit: 10,
118
120
  });
@@ -199,9 +201,9 @@ Recherche **plusieurs documents** par une clé de requête.
199
201
  const activeUsers = await repos.users.query.byIsActive(true);
200
202
  const usersByName = await repos.users.query.byName("John");
201
203
 
202
- // Avec options
204
+ // Avec options (syntaxe tuple pour where)
203
205
  const results = await repos.users.query.byIsActive(true, {
204
- where: [{ field: "age", operator: ">=", value: 18 }],
206
+ where: [["age", ">=", 18]],
205
207
  orderBy: [{ field: "name", direction: "asc" }],
206
208
  limit: 50,
207
209
  });
@@ -209,8 +211,8 @@ const results = await repos.users.query.byIsActive(true, {
209
211
  // Requête générique
210
212
  const users = await repos.users.query.by({
211
213
  where: [
212
- { field: "isActive", operator: "==", value: true },
213
- { field: "age", operator: ">=", value: 18 },
214
+ ["isActive", "==", true],
215
+ ["age", ">=", 18],
214
216
  ],
215
217
  orderBy: [{ field: "createdAt", direction: "desc" }],
216
218
  limit: 10,
@@ -218,18 +220,18 @@ const users = await repos.users.query.by({
218
220
 
219
221
  // Conditions OR
220
222
  const posts = await repos.posts.query.by({
221
- orWhere: [
222
- [{ field: "status", operator: "==", value: "published" }],
223
- [{ field: "status", operator: "==", value: "draft" }],
224
- ],
223
+ orWhere: [[["status", "==", "published"]], [["status", "==", "draft"]]],
225
224
  });
226
225
  ```
227
226
 
228
227
  ### Options de requête
229
228
 
230
229
  ```typescript
230
+ // WhereClause en tuple : [field, operator, value]
231
+ type WhereClause<T> = [keyof T, WhereFilterOp, any];
232
+
231
233
  interface QueryOptions<T> {
232
- where?: WhereClause<T>[]; // Conditions AND
234
+ where?: WhereClause<T>[]; // Conditions AND - tuples [field, op, value]
233
235
  orWhere?: WhereClause<T>[][]; // Conditions OR
234
236
  orderBy?: {
235
237
  field: keyof T;
@@ -237,6 +239,7 @@ interface QueryOptions<T> {
237
239
  }[];
238
240
  limit?: number; // Nombre max de résultats
239
241
  offset?: number; // Pagination (skip)
242
+ select?: (keyof T)[]; // Champs à récupérer (projection)
240
243
  startAt?: DocumentSnapshot | any[]; // Cursor pagination - start at
241
244
  startAfter?: DocumentSnapshot | any[]; // Cursor pagination - start after
242
245
  endAt?: DocumentSnapshot | any[]; // Cursor pagination - end at
@@ -329,10 +332,15 @@ const allUsers = await repos.users.query.getAll();
329
332
 
330
333
  // Avec des options de filtrage et tri
331
334
  const filteredUsers = await repos.users.query.getAll({
332
- where: [{ field: "isActive", operator: "==", value: true }],
335
+ where: [["isActive", "==", true]],
333
336
  orderBy: [{ field: "createdAt", direction: "desc" }],
334
337
  limit: 100,
335
338
  });
339
+
340
+ // Avec projection (select)
341
+ const userNames = await repos.users.query.getAll({
342
+ select: ["docId", "name", "email"],
343
+ });
336
344
  ```
337
345
 
338
346
  ### Real-time listeners (onSnapshot)
@@ -341,7 +349,7 @@ const filteredUsers = await repos.users.query.getAll({
341
349
  // Écouter les changements en temps réel
342
350
  const unsubscribe = repos.users.query.onSnapshot(
343
351
  {
344
- where: [{ field: "isActive", operator: "==", value: true }],
352
+ where: [["isActive", "==", true]],
345
353
  orderBy: [{ field: "name", direction: "asc" }],
346
354
  },
347
355
  (users) => {
@@ -463,24 +471,19 @@ import { count, sum, average } from "@lpdjs/firestore-repo-service";
463
471
 
464
472
  // Compter les documents
465
473
  const activeCount = await repos.users.aggregate.count({
466
- where: [{ field: "isActive", operator: "==", value: true }],
474
+ where: [["isActive", "==", true]],
467
475
  });
468
476
 
469
- // Agrégations personnalisées (count, sum, average)
470
- const stats = await repos.users.aggregate.query(
471
- {
472
- totalUsers: count(),
473
- totalAge: sum("age"),
474
- averageAge: average("age"),
475
- },
476
- {
477
- where: [{ field: "isActive", operator: "==", value: true }],
478
- }
479
- );
477
+ // Somme d'un champ
478
+ const totalViews = await repos.posts.aggregate.sum("views");
480
479
 
481
- console.log(stats.totalUsers); // nombre total
482
- console.log(stats.totalAge); // somme des âges
483
- console.log(stats.averageAge); // moyenne des âges
480
+ // Moyenne d'un champ
481
+ const avgAge = await repos.users.aggregate.average("age");
482
+
483
+ // Avec filtres
484
+ const publishedViews = await repos.posts.aggregate.sum("views", {
485
+ where: [["status", "==", "published"]],
486
+ });
484
487
  ```
485
488
 
486
489
  ### Accès à la collection Firestore
@@ -535,12 +538,12 @@ const rating = await repos.eventRatings.update(
535
538
  const users = await repos.users.query.by({
536
539
  orWhere: [
537
540
  [
538
- { field: "status", operator: "==", value: "active" },
539
- { field: "age", operator: ">=", value: 18 },
541
+ ["status", "==", "active"],
542
+ ["age", ">=", 18],
540
543
  ],
541
544
  [
542
- { field: "status", operator: "==", value: "pending" },
543
- { field: "verified", operator: "==", value: true },
545
+ ["status", "==", "pending"],
546
+ ["verified", "==", true],
544
547
  ],
545
548
  ],
546
549
  orderBy: [{ field: "createdAt", direction: "desc" }],
@@ -548,15 +551,70 @@ const users = await repos.users.query.by({
548
551
  });
549
552
  ```
550
553
 
554
+ ### Populate avec select (projection)
555
+
556
+ ```typescript
557
+ // Populate avec tous les champs
558
+ const postWithUser = await repos.posts.populate(post, "userId");
559
+ console.log(postWithUser.populated.users?.name);
560
+
561
+ // Populate avec select (seulement certains champs)
562
+ const userWithPosts = await repos.users.populate(
563
+ { docId: user.docId },
564
+ {
565
+ relation: "docId",
566
+ select: ["docId", "title", "status"], // Type-safe: keyof PostModel
567
+ }
568
+ );
569
+
570
+ // Populate plusieurs relations
571
+ const postWithAll = await repos.posts.populate(post, ["userId", "categoryId"]);
572
+ ```
573
+
574
+ ### Pagination avec include
575
+
576
+ ```typescript
577
+ // Paginer avec relations incluses
578
+ const page = await repos.posts.query.paginate({
579
+ pageSize: 10,
580
+ orderBy: [{ field: "createdAt", direction: "desc" }],
581
+ include: ["userId"], // Inclut l'auteur automatiquement
582
+ });
583
+
584
+ // Avec select sur les relations
585
+ const pageWithSelect = await repos.posts.query.paginate({
586
+ pageSize: 10,
587
+ include: [{ relation: "userId", select: ["docId", "name", "email"] }],
588
+ });
589
+
590
+ // Accès aux données peuplées
591
+ for (const post of page.data) {
592
+ console.log(post.title);
593
+ console.log(post.populated.users?.name); // Type-safe!
594
+ }
595
+ ```
596
+
551
597
  ## 🔧 Types exportés
552
598
 
553
599
  ```typescript
554
- // Types utiles
555
600
  import type {
601
+ // Core types
556
602
  WhereClause,
557
603
  QueryOptions,
558
- RepositoryKey,
559
- RepositoryModelType,
604
+ RepositoryConfig,
605
+ RelationConfig,
606
+
607
+ // Populate/Include types (avec typage keyof)
608
+ PopulateOptions,
609
+ PopulateOptionsTyped,
610
+ IncludeConfig,
611
+ IncludeConfigTyped,
612
+
613
+ // Pagination
614
+ PaginationOptions,
615
+ PaginationResult,
616
+ PaginationWithIncludeOptions,
617
+ PaginationWithIncludeOptionsTyped,
560
618
  } from "@lpdjs/firestore-repo-service";
561
619
  ```
562
620
 
@@ -565,14 +623,16 @@ import type {
565
623
  Pour tester rapidement sans projet Firebase :
566
624
 
567
625
  ```bash
568
- # 1. Installer Firebase CLI (si nécessaire)
626
+ # Installer Firebase CLI (si nécessaire)
569
627
  npm install -g firebase-tools
570
628
 
571
- # 2. Démarrer l'émulateur (terminal 1)
572
- bun run emulator
629
+ # Option 1 : Mode automatique (recommandé)
630
+ bun run test:watch
631
+ # → Lance l'émulateur + tests en mode watch automatiquement
573
632
 
574
- # 3. Lancer les tests (terminal 2)
575
- bun run test:emulator
633
+ # Option 2 : Mode manuel (deux terminaux)
634
+ bun run emulator # Terminal 1
635
+ bun run test # Terminal 2
576
636
  ```
577
637
 
578
638
  L'émulateur Firestore démarre sur `localhost:8080` avec une UI sur `http://localhost:4000`.