@tatsuokaniwa/swr-firestore 2.0.7 → 2.1.0
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 +514 -9
- package/dist/.vite/manifest.json +16 -2
- package/dist/databaseId-CdRC0X-i.cjs +3 -0
- package/dist/databaseId-w_CgTpOv.js +4 -0
- package/dist/fetcher/fetchAggregate.d.ts +37 -0
- package/dist/fetcher/fetchCollection.d.ts +24 -0
- package/dist/fetcher/fetchCollectionCount.d.ts +19 -0
- package/dist/fetcher/fetchCollectionGroup.d.ts +24 -0
- package/dist/fetcher/fetchCollectionGroupAggregate.d.ts +30 -0
- package/dist/fetcher/fetchCollectionGroupCount.d.ts +20 -0
- package/dist/fetcher/fetchDoc.d.ts +22 -0
- package/dist/fetcher/fetchDocInTx.d.ts +26 -0
- package/dist/fetcher/index.d.ts +9 -0
- package/dist/hooks/useAggregate.d.ts +5 -0
- package/dist/hooks/useCollection.d.ts +2 -2
- package/dist/hooks/useCollectionCount.d.ts +13 -12
- package/dist/hooks/useCollectionGroup.d.ts +2 -2
- package/dist/hooks/useCollectionGroupAggregate.d.ts +5 -0
- package/dist/hooks/useCollectionGroupCount.d.ts +13 -12
- package/dist/hooks/useDoc.d.ts +2 -2
- package/dist/hooks/useGetDoc.d.ts +13 -12
- package/dist/hooks/useGetDocs.d.ts +13 -12
- package/dist/index.d.ts +6 -3
- package/dist/index.js +212 -41
- package/dist/index.umd.cjs +211 -40
- package/dist/server/fetcher/getAggregate.d.ts +15 -0
- package/dist/server/fetcher/getCollectionCountInTx.d.ts +18 -0
- package/dist/server/fetcher/getCollectionGroupAggregate.d.ts +15 -0
- package/dist/server/fetcher/getCollectionGroupCountInTx.d.ts +18 -0
- package/dist/server/fetcher/getCollectionGroupInTx.d.ts +24 -0
- package/dist/server/fetcher/getCollectionInTx.d.ts +25 -0
- package/dist/server/fetcher/getDocInTx.d.ts +23 -0
- package/dist/server/index.d.ts +11 -3
- package/dist/server/index.js +288 -14
- package/dist/server/index.umd.cjs +286 -12
- package/dist/server/util/buildAggregateSpec.d.ts +11 -0
- package/dist/server/util/buildQuery.d.ts +12 -0
- package/dist/server/util/createKey.d.ts +1 -0
- package/dist/server/util/type.d.ts +9 -2
- package/dist/util/buildAggregateSpec.d.ts +6 -0
- package/dist/util/buildQuery.d.ts +12 -0
- package/dist/util/databaseId.d.ts +7 -0
- package/dist/util/type.d.ts +71 -1
- package/dist/util/typeGuard.d.ts +2 -2
- package/package.json +17 -21
package/README.md
CHANGED
|
@@ -19,6 +19,13 @@ yarn add @tatsuokaniwa/swr-firestore
|
|
|
19
19
|
pnpm i @tatsuokaniwa/swr-firestore
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
### Requirements for Aggregation Queries
|
|
23
|
+
|
|
24
|
+
To use aggregation features (`useAggregate`, `useCollectionGroupAggregate`, `fetchAggregate`, `fetchCollectionGroupAggregate`):
|
|
25
|
+
|
|
26
|
+
- Client-side: `firebase >= 9.17.0`
|
|
27
|
+
- Server-side: `firebase-admin >= 11.5.0` (recommended)
|
|
28
|
+
|
|
22
29
|
## Usage
|
|
23
30
|
|
|
24
31
|
```tsx
|
|
@@ -38,14 +45,12 @@ export default function App() {
|
|
|
38
45
|
// Conditional Fetching
|
|
39
46
|
const [isLogin, setIsLogin] = useState(false);
|
|
40
47
|
const { data } = useCollection<Post>(
|
|
41
|
-
isLogin
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
: null
|
|
48
|
+
isLogin && {
|
|
49
|
+
path: "posts",
|
|
50
|
+
where: [["status", "==", "published"]],
|
|
51
|
+
orderBy: [["createdAt", "desc"]],
|
|
52
|
+
parseDates: ["createdAt"],
|
|
53
|
+
}
|
|
49
54
|
);
|
|
50
55
|
const { data: postCount } = useCollectionCount<Post>({
|
|
51
56
|
path: "posts",
|
|
@@ -131,13 +136,26 @@ export default function Page({ fallback }) {
|
|
|
131
136
|
|
|
132
137
|
```ts
|
|
133
138
|
import {
|
|
139
|
+
// SWR Hooks
|
|
134
140
|
useCollection, // Subscription for collection
|
|
135
141
|
useCollectionCount, // Wrapper for getCountFromServer for collection
|
|
136
142
|
useCollectionGroup, // Subscription for collectionGroup
|
|
137
143
|
useCollectionGroupCount, // Wrapper for getCountFromServer for collectionGroup
|
|
144
|
+
useAggregate, // Aggregation queries (count, sum, average) for collection
|
|
145
|
+
useCollectionGroupAggregate, // Aggregation queries for collectionGroup
|
|
138
146
|
useDoc, // Subscription for document
|
|
139
147
|
useGetDocs, // Fetch documents with firestore's getDocs
|
|
140
148
|
useGetDoc, // Fetch document with firestore's getDoc
|
|
149
|
+
// Client-side fetchers (without SWR)
|
|
150
|
+
fetchDoc, // Fetch single document
|
|
151
|
+
fetchCollection, // Fetch collection
|
|
152
|
+
fetchCollectionCount, // Count documents in collection
|
|
153
|
+
fetchCollectionGroup, // Fetch collection group
|
|
154
|
+
fetchCollectionGroupCount, // Count documents in collection group
|
|
155
|
+
fetchAggregate, // Aggregation queries for collection
|
|
156
|
+
fetchCollectionGroupAggregate, // Aggregation queries for collection group
|
|
157
|
+
// Client-side transaction fetcher
|
|
158
|
+
fetchDocInTx, // Fetch document within transaction
|
|
141
159
|
} from "@tatsuokaniwa/swr-firestore";
|
|
142
160
|
|
|
143
161
|
import {
|
|
@@ -145,7 +163,15 @@ import {
|
|
|
145
163
|
getCollectionCount, // for useCollectionCount
|
|
146
164
|
getCollectionGroup, // for useCollectionGroup, useGetDocs
|
|
147
165
|
getCollectionGroupCount, // for useCollectionGroupCount
|
|
166
|
+
getAggregate, // for useAggregate
|
|
167
|
+
getCollectionGroupAggregate, // for useCollectionGroupAggregate
|
|
148
168
|
getDoc, // for useDoc, useGetDoc
|
|
169
|
+
// Transaction-aware fetchers (for use within db.runTransaction)
|
|
170
|
+
getDocInTx,
|
|
171
|
+
getCollectionInTx,
|
|
172
|
+
getCollectionCountInTx,
|
|
173
|
+
getCollectionGroupInTx,
|
|
174
|
+
getCollectionGroupCountInTx,
|
|
149
175
|
} from "@tatsuokaniwa/swr-firestore/server";
|
|
150
176
|
```
|
|
151
177
|
|
|
@@ -291,6 +317,103 @@ Returns [`SWRResponse`](https://swr.vercel.app/docs/api#return-values)
|
|
|
291
317
|
- `isValidating`: if there's a request or revalidation loading
|
|
292
318
|
- `mutate(data?, options?)`: function to mutate the cached data (details)
|
|
293
319
|
|
|
320
|
+
### `useAggregate(params, swrOptions)`
|
|
321
|
+
|
|
322
|
+
Wrapper for getAggregateFromServer for collection. Supports count, sum, and average aggregations in a single query.
|
|
323
|
+
|
|
324
|
+
#### Parameters
|
|
325
|
+
|
|
326
|
+
- `params`: KeyParams except `parseDates` & { aggregate: AggregateSpec } | null
|
|
327
|
+
- `swrOptions`: [Options for SWR hook](https://swr.vercel.app/docs/api#options) except `fetcher`
|
|
328
|
+
|
|
329
|
+
#### Return values
|
|
330
|
+
|
|
331
|
+
Returns [`SWRResponse`](https://swr.vercel.app/docs/api#return-values)
|
|
332
|
+
|
|
333
|
+
- `data`: aggregation result object with keys matching the aggregate spec
|
|
334
|
+
- `error`: FirestoreError
|
|
335
|
+
- `isLoading`: if there's an ongoing request and no "loaded data"
|
|
336
|
+
- `isValidating`: if there's a request or revalidation loading
|
|
337
|
+
- `mutate(data?, options?)`: function to mutate the cached data
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
import { useAggregate } from "@tatsuokaniwa/swr-firestore";
|
|
341
|
+
|
|
342
|
+
type Product = {
|
|
343
|
+
name: string;
|
|
344
|
+
category: string;
|
|
345
|
+
price: number;
|
|
346
|
+
stock: number;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const { data, error, isLoading } = useAggregate<
|
|
350
|
+
Product,
|
|
351
|
+
{
|
|
352
|
+
totalStock: { type: "sum"; field: "stock" };
|
|
353
|
+
averagePrice: { type: "average"; field: "price" };
|
|
354
|
+
productCount: { type: "count" };
|
|
355
|
+
}
|
|
356
|
+
>({
|
|
357
|
+
path: "products",
|
|
358
|
+
where: [["category", "==", "electronics"]],
|
|
359
|
+
aggregate: {
|
|
360
|
+
totalStock: { type: "sum", field: "stock" },
|
|
361
|
+
averagePrice: { type: "average", field: "price" },
|
|
362
|
+
productCount: { type: "count" },
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (data) {
|
|
367
|
+
console.log(data.productCount); // number
|
|
368
|
+
console.log(data.totalStock); // number
|
|
369
|
+
console.log(data.averagePrice); // number | null (null when no documents)
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### `useCollectionGroupAggregate(params, swrOptions)`
|
|
374
|
+
|
|
375
|
+
Wrapper for getAggregateFromServer for collectionGroup. Supports count, sum, and average aggregations across subcollections.
|
|
376
|
+
|
|
377
|
+
#### Parameters
|
|
378
|
+
|
|
379
|
+
- `params`: KeyParams except `parseDates` & { aggregate: AggregateSpec } | null
|
|
380
|
+
- `swrOptions`: [Options for SWR hook](https://swr.vercel.app/docs/api#options) except `fetcher`
|
|
381
|
+
|
|
382
|
+
#### Return values
|
|
383
|
+
|
|
384
|
+
Returns [`SWRResponse`](https://swr.vercel.app/docs/api#return-values)
|
|
385
|
+
|
|
386
|
+
- `data`: aggregation result object with keys matching the aggregate spec
|
|
387
|
+
- `error`: FirestoreError
|
|
388
|
+
- `isLoading`: if there's an ongoing request and no "loaded data"
|
|
389
|
+
- `isValidating`: if there's a request or revalidation loading
|
|
390
|
+
- `mutate(data?, options?)`: function to mutate the cached data
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
import { useCollectionGroupAggregate } from "@tatsuokaniwa/swr-firestore";
|
|
394
|
+
|
|
395
|
+
type OrderItem = {
|
|
396
|
+
productId: string;
|
|
397
|
+
price: number;
|
|
398
|
+
quantity: number;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// Aggregate across all "items" subcollections
|
|
402
|
+
const { data } = useCollectionGroupAggregate<
|
|
403
|
+
OrderItem,
|
|
404
|
+
{
|
|
405
|
+
totalRevenue: { type: "sum"; field: "price" };
|
|
406
|
+
itemCount: { type: "count" };
|
|
407
|
+
}
|
|
408
|
+
>({
|
|
409
|
+
path: "items",
|
|
410
|
+
aggregate: {
|
|
411
|
+
totalRevenue: { type: "sum", field: "price" },
|
|
412
|
+
itemCount: { type: "count" },
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
294
417
|
### `useDoc(params, swrOptions)`
|
|
295
418
|
|
|
296
419
|
Subscription for document
|
|
@@ -375,6 +498,146 @@ const { data, error } = useGetDoc<Post>({
|
|
|
375
498
|
});
|
|
376
499
|
```
|
|
377
500
|
|
|
501
|
+
## Client-side fetchers
|
|
502
|
+
|
|
503
|
+
These functions fetch data directly from Firestore without SWR caching. Useful for one-off data fetching, imperative data loading, or when you don't need SWR's caching and revalidation features.
|
|
504
|
+
|
|
505
|
+
### `fetchDoc(params)`
|
|
506
|
+
|
|
507
|
+
Fetch a single document from Firestore
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
import { fetchDoc } from "@tatsuokaniwa/swr-firestore";
|
|
511
|
+
|
|
512
|
+
const city = await fetchDoc<City>({
|
|
513
|
+
path: "cities/tokyo",
|
|
514
|
+
parseDates: ["createdAt"],
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### `fetchCollection(params)`
|
|
519
|
+
|
|
520
|
+
Fetch documents from a collection
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
import { fetchCollection } from "@tatsuokaniwa/swr-firestore";
|
|
524
|
+
|
|
525
|
+
const cities = await fetchCollection<City>({
|
|
526
|
+
path: "cities",
|
|
527
|
+
where: [["population", ">", 1000000]],
|
|
528
|
+
orderBy: [["population", "desc"]],
|
|
529
|
+
limit: 10,
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### `fetchCollectionCount(params)`
|
|
534
|
+
|
|
535
|
+
Count documents in a collection
|
|
536
|
+
|
|
537
|
+
```ts
|
|
538
|
+
import { fetchCollectionCount } from "@tatsuokaniwa/swr-firestore";
|
|
539
|
+
|
|
540
|
+
const count = await fetchCollectionCount<City>({
|
|
541
|
+
path: "cities",
|
|
542
|
+
where: [["population", ">", 1000000]],
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### `fetchCollectionGroup(params)`
|
|
547
|
+
|
|
548
|
+
Fetch documents from a collection group
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
import { fetchCollectionGroup } from "@tatsuokaniwa/swr-firestore";
|
|
552
|
+
|
|
553
|
+
const comments = await fetchCollectionGroup<Comment>({
|
|
554
|
+
path: "comments",
|
|
555
|
+
where: [["authorId", "==", "user123"]],
|
|
556
|
+
limit: 10,
|
|
557
|
+
});
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### `fetchCollectionGroupCount(params)`
|
|
561
|
+
|
|
562
|
+
Count documents in a collection group
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
import { fetchCollectionGroupCount } from "@tatsuokaniwa/swr-firestore";
|
|
566
|
+
|
|
567
|
+
const count = await fetchCollectionGroupCount<Comment>({
|
|
568
|
+
path: "comments",
|
|
569
|
+
where: [["status", "==", "approved"]],
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### `fetchAggregate(params)`
|
|
574
|
+
|
|
575
|
+
Fetch aggregation result from a collection
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
import { fetchAggregate } from "@tatsuokaniwa/swr-firestore";
|
|
579
|
+
|
|
580
|
+
const result = await fetchAggregate<
|
|
581
|
+
Product,
|
|
582
|
+
{
|
|
583
|
+
count: { type: "count" };
|
|
584
|
+
totalStock: { type: "sum"; field: "stock" };
|
|
585
|
+
avgPrice: { type: "average"; field: "price" };
|
|
586
|
+
}
|
|
587
|
+
>({
|
|
588
|
+
path: "products",
|
|
589
|
+
aggregate: {
|
|
590
|
+
count: { type: "count" },
|
|
591
|
+
totalStock: { type: "sum", field: "stock" },
|
|
592
|
+
avgPrice: { type: "average", field: "price" },
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### `fetchCollectionGroupAggregate(params)`
|
|
598
|
+
|
|
599
|
+
Fetch aggregation result from a collection group
|
|
600
|
+
|
|
601
|
+
```ts
|
|
602
|
+
import { fetchCollectionGroupAggregate } from "@tatsuokaniwa/swr-firestore";
|
|
603
|
+
|
|
604
|
+
const result = await fetchCollectionGroupAggregate<
|
|
605
|
+
OrderItem,
|
|
606
|
+
{ totalRevenue: { type: "sum"; field: "price" } }
|
|
607
|
+
>({
|
|
608
|
+
path: "items",
|
|
609
|
+
aggregate: {
|
|
610
|
+
totalRevenue: { type: "sum", field: "price" },
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### `fetchDocInTx(transaction, params)`
|
|
616
|
+
|
|
617
|
+
Fetch a single document within a Firestore transaction (client-side)
|
|
618
|
+
|
|
619
|
+
Note: Due to Firebase client SDK limitations, only document fetching is supported in transactions. Collection queries within transactions are only available in the server module.
|
|
620
|
+
|
|
621
|
+
```ts
|
|
622
|
+
import { getFirestore, runTransaction } from "firebase/firestore";
|
|
623
|
+
import { fetchDocInTx } from "@tatsuokaniwa/swr-firestore";
|
|
624
|
+
|
|
625
|
+
const db = getFirestore();
|
|
626
|
+
|
|
627
|
+
await runTransaction(db, async (t) => {
|
|
628
|
+
const city = await fetchDocInTx<City>(t, {
|
|
629
|
+
path: "cities/tokyo",
|
|
630
|
+
parseDates: ["createdAt"],
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
if (city) {
|
|
634
|
+
t.update(doc(db, "cities/tokyo"), {
|
|
635
|
+
population: city.population + 1,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
```
|
|
640
|
+
|
|
378
641
|
## Server module
|
|
379
642
|
|
|
380
643
|
### `getCollection(params)`
|
|
@@ -426,7 +689,7 @@ Returns `Promise<{
|
|
|
426
689
|
- `data`: number of documents in the collection for the given path.
|
|
427
690
|
|
|
428
691
|
```ts
|
|
429
|
-
import {
|
|
692
|
+
import { getCollectionCount } from "@tatsuokaniwa/swr-firestore/server";
|
|
430
693
|
|
|
431
694
|
// For useCollectionCount
|
|
432
695
|
const { key, data } = await getCollectionCount<Post>({ path: "posts" });
|
|
@@ -489,6 +752,80 @@ const { key, data } = await getCollectionGroupCount<Comment>({
|
|
|
489
752
|
});
|
|
490
753
|
```
|
|
491
754
|
|
|
755
|
+
### `getAggregate(params)`
|
|
756
|
+
|
|
757
|
+
Fetch aggregation result using the Firebase Admin SDK and return the SWR key and data
|
|
758
|
+
|
|
759
|
+
#### Parameters
|
|
760
|
+
|
|
761
|
+
- `params`: KeyParams except `parseDates`, `queryConstraints` & { aggregate: AggregateSpec }
|
|
762
|
+
|
|
763
|
+
Note: `queryConstraints` is not supported on the server side because the Admin SDK uses a different query builder API.
|
|
764
|
+
|
|
765
|
+
#### Return values
|
|
766
|
+
|
|
767
|
+
Returns `Promise<{
|
|
768
|
+
key: string;
|
|
769
|
+
data: AggregateResult<TSpec>;
|
|
770
|
+
}>`
|
|
771
|
+
|
|
772
|
+
- `key`: SWR Key
|
|
773
|
+
- `data`: aggregation result object
|
|
774
|
+
|
|
775
|
+
```ts
|
|
776
|
+
import { getAggregate } from "@tatsuokaniwa/swr-firestore/server";
|
|
777
|
+
|
|
778
|
+
// For useAggregate
|
|
779
|
+
const { key, data } = await getAggregate<
|
|
780
|
+
Product,
|
|
781
|
+
{
|
|
782
|
+
count: { type: "count" };
|
|
783
|
+
totalRevenue: { type: "sum"; field: "price" };
|
|
784
|
+
}
|
|
785
|
+
>({
|
|
786
|
+
path: "products",
|
|
787
|
+
aggregate: {
|
|
788
|
+
count: { type: "count" },
|
|
789
|
+
totalRevenue: { type: "sum", field: "price" },
|
|
790
|
+
},
|
|
791
|
+
});
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
### `getCollectionGroupAggregate(params)`
|
|
795
|
+
|
|
796
|
+
Fetch aggregation result across subcollections using the Firebase Admin SDK
|
|
797
|
+
|
|
798
|
+
#### Parameters
|
|
799
|
+
|
|
800
|
+
- `params`: KeyParams except `parseDates`, `queryConstraints` & { aggregate: AggregateSpec }
|
|
801
|
+
|
|
802
|
+
Note: `queryConstraints` is not supported on the server side because the Admin SDK uses a different query builder API.
|
|
803
|
+
|
|
804
|
+
#### Return values
|
|
805
|
+
|
|
806
|
+
Returns `Promise<{
|
|
807
|
+
key: string;
|
|
808
|
+
data: AggregateResult<TSpec>;
|
|
809
|
+
}>`
|
|
810
|
+
|
|
811
|
+
- `key`: SWR Key
|
|
812
|
+
- `data`: aggregation result object
|
|
813
|
+
|
|
814
|
+
```ts
|
|
815
|
+
import { getCollectionGroupAggregate } from "@tatsuokaniwa/swr-firestore/server";
|
|
816
|
+
|
|
817
|
+
// For useCollectionGroupAggregate
|
|
818
|
+
const { key, data } = await getCollectionGroupAggregate<
|
|
819
|
+
OrderItem,
|
|
820
|
+
{ totalItems: { type: "count" } }
|
|
821
|
+
>({
|
|
822
|
+
path: "items",
|
|
823
|
+
aggregate: {
|
|
824
|
+
totalItems: { type: "count" },
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
```
|
|
828
|
+
|
|
492
829
|
### `getDoc(params)`
|
|
493
830
|
|
|
494
831
|
Fetch the document using the Firebase Admin SDK and return the SWR key and data
|
|
@@ -519,6 +856,174 @@ const { key, data } = await getDoc<Post>({
|
|
|
519
856
|
const { key, data } = await getDoc<Post>({ path: `posts/${postId}` });
|
|
520
857
|
```
|
|
521
858
|
|
|
859
|
+
### `getDocInTx(transaction, params)`
|
|
860
|
+
|
|
861
|
+
Type-safe document fetcher for use within Firestore transactions
|
|
862
|
+
|
|
863
|
+
#### Parameters
|
|
864
|
+
|
|
865
|
+
- `transaction`: Firebase Admin SDK Transaction object
|
|
866
|
+
- `params`: KeyParams except `where`, `orderBy`, `limit`
|
|
867
|
+
|
|
868
|
+
#### Return values
|
|
869
|
+
|
|
870
|
+
Returns `Promise<DocumentData<T> | undefined>`
|
|
871
|
+
|
|
872
|
+
- Returns the document data, or undefined if the document does not exist
|
|
873
|
+
|
|
874
|
+
```ts
|
|
875
|
+
import { getFirestore } from "firebase-admin/firestore";
|
|
876
|
+
import { getDocInTx } from "@tatsuokaniwa/swr-firestore/server";
|
|
877
|
+
|
|
878
|
+
const db = getFirestore();
|
|
879
|
+
|
|
880
|
+
await db.runTransaction(async (t) => {
|
|
881
|
+
const city = await getDocInTx<City>(t, {
|
|
882
|
+
path: "cities/tokyo",
|
|
883
|
+
parseDates: ["createdAt"],
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
if (city) {
|
|
887
|
+
t.update(db.doc("cities/tokyo"), {
|
|
888
|
+
population: city.population + 1,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
### `getCollectionInTx(transaction, params)`
|
|
895
|
+
|
|
896
|
+
Type-safe collection fetcher for use within Firestore transactions
|
|
897
|
+
|
|
898
|
+
#### Parameters
|
|
899
|
+
|
|
900
|
+
- `transaction`: Firebase Admin SDK Transaction object
|
|
901
|
+
- `params`: KeyParams
|
|
902
|
+
|
|
903
|
+
#### Return values
|
|
904
|
+
|
|
905
|
+
Returns `Promise<DocumentData<T>[]>`
|
|
906
|
+
|
|
907
|
+
- Returns an array of document data
|
|
908
|
+
|
|
909
|
+
```ts
|
|
910
|
+
import { getFirestore } from "firebase-admin/firestore";
|
|
911
|
+
import { getCollectionInTx } from "@tatsuokaniwa/swr-firestore/server";
|
|
912
|
+
|
|
913
|
+
const db = getFirestore();
|
|
914
|
+
|
|
915
|
+
await db.runTransaction(async (t) => {
|
|
916
|
+
const cities = await getCollectionInTx<City>(t, {
|
|
917
|
+
path: "cities",
|
|
918
|
+
where: [["population", ">", 1000000]],
|
|
919
|
+
orderBy: [["population", "desc"]],
|
|
920
|
+
limit: 10,
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
cities.forEach((city) => {
|
|
924
|
+
t.update(db.doc(`cities/${city.id}`), {
|
|
925
|
+
isLargeCity: true,
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### `getCollectionCountInTx(transaction, params)`
|
|
932
|
+
|
|
933
|
+
Type-safe collection count fetcher for use within Firestore transactions
|
|
934
|
+
|
|
935
|
+
#### Parameters
|
|
936
|
+
|
|
937
|
+
- `transaction`: Firebase Admin SDK Transaction object
|
|
938
|
+
- `params`: KeyParams except `parseDates`
|
|
939
|
+
|
|
940
|
+
#### Return values
|
|
941
|
+
|
|
942
|
+
Returns `Promise<number>`
|
|
943
|
+
|
|
944
|
+
```ts
|
|
945
|
+
await db.runTransaction(async (t) => {
|
|
946
|
+
const count = await getCollectionCountInTx<City>(t, {
|
|
947
|
+
path: "cities",
|
|
948
|
+
where: [["population", ">", 1000000]],
|
|
949
|
+
});
|
|
950
|
+
console.log(`Found ${count} large cities`);
|
|
951
|
+
});
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
### `getCollectionGroupInTx(transaction, params)`
|
|
955
|
+
|
|
956
|
+
Type-safe collection group fetcher for use within Firestore transactions
|
|
957
|
+
|
|
958
|
+
#### Parameters
|
|
959
|
+
|
|
960
|
+
- `transaction`: Firebase Admin SDK Transaction object
|
|
961
|
+
- `params`: KeyParams
|
|
962
|
+
|
|
963
|
+
#### Return values
|
|
964
|
+
|
|
965
|
+
Returns `Promise<DocumentData<T>[]>`
|
|
966
|
+
|
|
967
|
+
```ts
|
|
968
|
+
await db.runTransaction(async (t) => {
|
|
969
|
+
const comments = await getCollectionGroupInTx<Comment>(t, {
|
|
970
|
+
path: "comments",
|
|
971
|
+
where: [["authorId", "==", "user123"]],
|
|
972
|
+
limit: 10,
|
|
973
|
+
});
|
|
974
|
+
// comments is DocumentData<Comment>[]
|
|
975
|
+
});
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
### `getCollectionGroupCountInTx(transaction, params)`
|
|
979
|
+
|
|
980
|
+
Type-safe collection group count fetcher for use within Firestore transactions
|
|
981
|
+
|
|
982
|
+
#### Parameters
|
|
983
|
+
|
|
984
|
+
- `transaction`: Firebase Admin SDK Transaction object
|
|
985
|
+
- `params`: KeyParams except `parseDates`
|
|
986
|
+
|
|
987
|
+
#### Return values
|
|
988
|
+
|
|
989
|
+
Returns `Promise<number>`
|
|
990
|
+
|
|
991
|
+
```ts
|
|
992
|
+
await db.runTransaction(async (t) => {
|
|
993
|
+
const count = await getCollectionGroupCountInTx<Comment>(t, {
|
|
994
|
+
path: "comments",
|
|
995
|
+
where: [["status", "==", "approved"]],
|
|
996
|
+
});
|
|
997
|
+
console.log(`Found ${count} approved comments`);
|
|
998
|
+
});
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
### Custom Firestore Instance
|
|
1002
|
+
|
|
1003
|
+
All functions and hooks accept an optional `db` parameter to use a specific Firestore instance instead of the default `getFirestore()`.
|
|
1004
|
+
|
|
1005
|
+
```typescript
|
|
1006
|
+
// Client-side
|
|
1007
|
+
const data = await fetchDoc<City>({
|
|
1008
|
+
path: "cities/tokyo",
|
|
1009
|
+
db: getFirestore(secondaryApp),
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
// Server-side
|
|
1013
|
+
const { data } = await getDoc<City>({
|
|
1014
|
+
path: "cities/tokyo",
|
|
1015
|
+
db: getFirestore(adminApp),
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
// React hooks
|
|
1019
|
+
const { data } = useDoc<City>({
|
|
1020
|
+
path: "cities/tokyo",
|
|
1021
|
+
db: getFirestore(secondaryApp),
|
|
1022
|
+
});
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
When `db` is omitted, it falls back to `getFirestore()` as before, so existing code is not affected.
|
|
1026
|
+
|
|
522
1027
|
## Testing
|
|
523
1028
|
|
|
524
1029
|
Before running the test, you need to install the [Firebase tools](https://firebase.google.com/docs/cli).
|
package/dist/.vite/manifest.json
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
+
"_databaseId-CdRC0X-i.cjs": {
|
|
3
|
+
"file": "databaseId-CdRC0X-i.cjs",
|
|
4
|
+
"name": "databaseId"
|
|
5
|
+
},
|
|
6
|
+
"_databaseId-w_CgTpOv.js": {
|
|
7
|
+
"file": "databaseId-w_CgTpOv.js",
|
|
8
|
+
"name": "databaseId"
|
|
9
|
+
},
|
|
2
10
|
"src/index.ts": {
|
|
3
11
|
"file": "index.umd.cjs",
|
|
4
12
|
"name": "index",
|
|
5
13
|
"src": "src/index.ts",
|
|
6
|
-
"isEntry": true
|
|
14
|
+
"isEntry": true,
|
|
15
|
+
"imports": [
|
|
16
|
+
"_databaseId-CdRC0X-i.cjs"
|
|
17
|
+
]
|
|
7
18
|
},
|
|
8
19
|
"src/server/index.ts": {
|
|
9
20
|
"file": "server/index.umd.cjs",
|
|
10
21
|
"name": "server",
|
|
11
22
|
"src": "src/server/index.ts",
|
|
12
|
-
"isEntry": true
|
|
23
|
+
"isEntry": true,
|
|
24
|
+
"imports": [
|
|
25
|
+
"_databaseId-CdRC0X-i.cjs"
|
|
26
|
+
]
|
|
13
27
|
}
|
|
14
28
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { KeyParamsForAggregate, SwrAggregateSpec, AggregateResult } from "../util/type";
|
|
2
|
+
/**
|
|
3
|
+
* Fetch aggregation result from a collection (client-side).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* interface Product {
|
|
8
|
+
* name: string;
|
|
9
|
+
* category: string;
|
|
10
|
+
* price: number;
|
|
11
|
+
* stock: number;
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* const result = await fetchAggregate<
|
|
15
|
+
* Product,
|
|
16
|
+
* {
|
|
17
|
+
* totalStock: { type: "sum"; field: "stock" };
|
|
18
|
+
* averagePrice: { type: "average"; field: "price" };
|
|
19
|
+
* productCount: { type: "count" };
|
|
20
|
+
* }
|
|
21
|
+
* >({
|
|
22
|
+
* path: "products",
|
|
23
|
+
* where: [["category", "==", "electronics"]],
|
|
24
|
+
* aggregate: {
|
|
25
|
+
* totalStock: { type: "sum", field: "stock" },
|
|
26
|
+
* averagePrice: { type: "average", field: "price" },
|
|
27
|
+
* productCount: { type: "count" },
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* console.log(result.productCount); // number
|
|
32
|
+
* console.log(result.totalStock); // number
|
|
33
|
+
* console.log(result.averagePrice); // number | null
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare const fetchAggregate: <T, TSpec extends SwrAggregateSpec<T>>(params: KeyParamsForAggregate<T, TSpec>) => Promise<AggregateResult<TSpec>>;
|
|
37
|
+
export default fetchAggregate;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DocumentData, KeyParams } from "../util/type";
|
|
2
|
+
export type FetchCollectionParams<T> = KeyParams<T> & {
|
|
3
|
+
useOfflineCache?: boolean;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Fetch documents from a collection (client-side).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* interface City {
|
|
11
|
+
* name: string;
|
|
12
|
+
* population: number;
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* const cities = await fetchCollection<City>({
|
|
16
|
+
* path: "cities",
|
|
17
|
+
* where: [["population", ">", 1000000]],
|
|
18
|
+
* orderBy: [["population", "desc"]],
|
|
19
|
+
* limit: 10,
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare const fetchCollection: <T>(params: FetchCollectionParams<T>) => Promise<DocumentData<T>[]>;
|
|
24
|
+
export default fetchCollection;
|