@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 +100 -40
- package/dist/index.cjs +222 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +180 -34
- package/dist/index.d.ts +180 -34
- package/dist/index.js +222 -51
- package/dist/index.js.map +1 -1
- package/package.json +29 -9
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: [
|
|
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: [
|
|
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
|
-
|
|
213
|
-
|
|
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: [
|
|
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: [
|
|
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: [
|
|
474
|
+
where: [["isActive", "==", true]],
|
|
467
475
|
});
|
|
468
476
|
|
|
469
|
-
//
|
|
470
|
-
const
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
539
|
-
|
|
541
|
+
["status", "==", "active"],
|
|
542
|
+
["age", ">=", 18],
|
|
540
543
|
],
|
|
541
544
|
[
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
559
|
-
|
|
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
|
-
#
|
|
626
|
+
# Installer Firebase CLI (si nécessaire)
|
|
569
627
|
npm install -g firebase-tools
|
|
570
628
|
|
|
571
|
-
#
|
|
572
|
-
bun run
|
|
629
|
+
# Option 1 : Mode automatique (recommandé)
|
|
630
|
+
bun run test:watch
|
|
631
|
+
# → Lance l'émulateur + tests en mode watch automatiquement
|
|
573
632
|
|
|
574
|
-
#
|
|
575
|
-
bun run
|
|
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`.
|