@ttt-productions/query-core 0.2.1 → 0.3.2
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/dist/firestore/context.d.ts +28 -0
- package/dist/firestore/context.d.ts.map +1 -0
- package/dist/firestore/context.js +35 -0
- package/dist/firestore/context.js.map +1 -0
- package/dist/firestore/index.d.ts +11 -0
- package/dist/firestore/index.d.ts.map +1 -0
- package/dist/firestore/index.js +11 -0
- package/dist/firestore/index.js.map +1 -0
- package/dist/firestore/types.d.ts +142 -0
- package/dist/firestore/types.d.ts.map +1 -0
- package/dist/firestore/types.js +7 -0
- package/dist/firestore/types.js.map +1 -0
- package/dist/firestore/use-firestore-collection.d.ts +27 -0
- package/dist/firestore/use-firestore-collection.d.ts.map +1 -0
- package/dist/firestore/use-firestore-collection.js +74 -0
- package/dist/firestore/use-firestore-collection.js.map +1 -0
- package/dist/firestore/use-firestore-doc.d.ts +31 -0
- package/dist/firestore/use-firestore-doc.d.ts.map +1 -0
- package/dist/firestore/use-firestore-doc.js +75 -0
- package/dist/firestore/use-firestore-doc.js.map +1 -0
- package/dist/firestore/use-firestore-infinite.d.ts +55 -0
- package/dist/firestore/use-firestore-infinite.d.ts.map +1 -0
- package/dist/firestore/use-firestore-infinite.js +96 -0
- package/dist/firestore/use-firestore-infinite.js.map +1 -0
- package/dist/firestore/use-firestore-mutations.d.ts +38 -0
- package/dist/firestore/use-firestore-mutations.d.ts.map +1 -0
- package/dist/firestore/use-firestore-mutations.js +132 -0
- package/dist/firestore/use-firestore-mutations.js.map +1 -0
- package/dist/firestore/use-firestore-paginated.d.ts +44 -0
- package/dist/firestore/use-firestore-paginated.d.ts.map +1 -0
- package/dist/firestore/use-firestore-paginated.js +117 -0
- package/dist/firestore/use-firestore-paginated.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { Firestore } from 'firebase/firestore';
|
|
3
|
+
export interface FirestoreProviderProps {
|
|
4
|
+
db: Firestore;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Provides Firestore instance to all useFirestore* hooks.
|
|
9
|
+
* Wrap your app with this provider alongside TTTQueryProvider.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { db } from '@/lib/firebase';
|
|
14
|
+
*
|
|
15
|
+
* <TTTQueryProvider>
|
|
16
|
+
* <FirestoreProvider db={db}>
|
|
17
|
+
* {children}
|
|
18
|
+
* </FirestoreProvider>
|
|
19
|
+
* </TTTQueryProvider>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function FirestoreProvider({ db, children }: FirestoreProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
/**
|
|
24
|
+
* Access the Firestore instance from context.
|
|
25
|
+
* Throws if used outside of FirestoreProvider.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useFirestoreDb(): Firestore;
|
|
28
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/firestore/context.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAQpD,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,SAAS,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,sBAAsB,2CAMzE;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,SAAS,CAS1C"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
const FirestoreContext = createContext(null);
|
|
5
|
+
/**
|
|
6
|
+
* Provides Firestore instance to all useFirestore* hooks.
|
|
7
|
+
* Wrap your app with this provider alongside TTTQueryProvider.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { db } from '@/lib/firebase';
|
|
12
|
+
*
|
|
13
|
+
* <TTTQueryProvider>
|
|
14
|
+
* <FirestoreProvider db={db}>
|
|
15
|
+
* {children}
|
|
16
|
+
* </FirestoreProvider>
|
|
17
|
+
* </TTTQueryProvider>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function FirestoreProvider({ db, children }) {
|
|
21
|
+
return (_jsx(FirestoreContext.Provider, { value: { db }, children: children }));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Access the Firestore instance from context.
|
|
25
|
+
* Throws if used outside of FirestoreProvider.
|
|
26
|
+
*/
|
|
27
|
+
export function useFirestoreDb() {
|
|
28
|
+
const context = useContext(FirestoreContext);
|
|
29
|
+
if (!context) {
|
|
30
|
+
throw new Error('useFirestoreDb must be used within a FirestoreProvider. ' +
|
|
31
|
+
'Wrap your app with <FirestoreProvider db={db}>.');
|
|
32
|
+
}
|
|
33
|
+
return context.db;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/firestore/context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,aAAa,EAAE,UAAU,EAAkB,MAAM,OAAO,CAAC;AAOlE,MAAM,gBAAgB,GAAG,aAAa,CAA+B,IAAI,CAAC,CAAC;AAO3E;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAE,EAAE,EAAE,QAAQ,EAA0B;IACxE,OAAO,CACL,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,EAAE,EAAE,YACrC,QAAQ,GACiB,CAC7B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,OAAO,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,0DAA0D;YAC1D,iDAAiD,CAClD,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC,EAAE,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { FirestoreProvider, useFirestoreDb } from './context';
|
|
2
|
+
export type { FirestoreProviderProps } from './context';
|
|
3
|
+
export type { WithId, FirestoreBaseOptions, FirestoreDocOptions, FirestoreCollectionOptions, FirestoreInfiniteOptions, FirestorePaginatedOptions, InfinitePage, PaginatedResult, MutationOperation, FirestoreMutationOptions, FirestoreBatchOptions, } from './types';
|
|
4
|
+
export { docWithId } from './types';
|
|
5
|
+
export { useFirestoreDoc } from './use-firestore-doc';
|
|
6
|
+
export { useFirestoreCollection } from './use-firestore-collection';
|
|
7
|
+
export { useFirestoreInfinite, flattenInfiniteData, getInfiniteDataCount } from './use-firestore-infinite';
|
|
8
|
+
export { useFirestorePaginated } from './use-firestore-paginated';
|
|
9
|
+
export type { UseFirestorePaginatedResult } from './use-firestore-paginated';
|
|
10
|
+
export { useFirestoreSet, useFirestoreUpdate, useFirestoreDelete, useFirestoreBatch, } from './use-firestore-mutations';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/firestore/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9D,YAAY,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAGxD,YAAY,EACV,MAAM,EACN,oBAAoB,EACpB,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGpC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,YAAY,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAG7E,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Context & Provider
|
|
2
|
+
export { FirestoreProvider, useFirestoreDb } from './context';
|
|
3
|
+
export { docWithId } from './types';
|
|
4
|
+
// Query Hooks
|
|
5
|
+
export { useFirestoreDoc } from './use-firestore-doc';
|
|
6
|
+
export { useFirestoreCollection } from './use-firestore-collection';
|
|
7
|
+
export { useFirestoreInfinite, flattenInfiniteData, getInfiniteDataCount } from './use-firestore-infinite';
|
|
8
|
+
export { useFirestorePaginated } from './use-firestore-paginated';
|
|
9
|
+
// Mutation Hooks
|
|
10
|
+
export { useFirestoreSet, useFirestoreUpdate, useFirestoreDelete, useFirestoreBatch, } from './use-firestore-mutations';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/firestore/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAiB9D,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,cAAc;AACd,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAGlE,iBAAiB;AACjB,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { DocumentData, DocumentSnapshot, QueryDocumentSnapshot, QueryConstraint } from 'firebase/firestore';
|
|
2
|
+
/**
|
|
3
|
+
* Document with ID included in the data object.
|
|
4
|
+
*/
|
|
5
|
+
export type WithId<T> = T & {
|
|
6
|
+
id: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Extract document data with ID from a QueryDocumentSnapshot.
|
|
10
|
+
*/
|
|
11
|
+
export declare function docWithId<T extends DocumentData>(snap: QueryDocumentSnapshot<T>): WithId<T>;
|
|
12
|
+
/**
|
|
13
|
+
* Base options shared across all Firestore hooks.
|
|
14
|
+
*/
|
|
15
|
+
export interface FirestoreBaseOptions {
|
|
16
|
+
/** React Query cache key */
|
|
17
|
+
queryKey: readonly unknown[];
|
|
18
|
+
/** Enable/disable the query */
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
/** Stale time override (ms) */
|
|
21
|
+
staleTime?: number;
|
|
22
|
+
/** Garbage collection time override (ms) */
|
|
23
|
+
gcTime?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Options for single document queries.
|
|
27
|
+
*/
|
|
28
|
+
export interface FirestoreDocOptions<T> extends FirestoreBaseOptions {
|
|
29
|
+
/** Full document path (e.g., 'users/abc123') */
|
|
30
|
+
docPath: string;
|
|
31
|
+
/** Enable realtime updates via onSnapshot (default: false) */
|
|
32
|
+
subscribe?: boolean;
|
|
33
|
+
/** Transform function applied to document data. Receives data WITH id included. */
|
|
34
|
+
select?: (data: DocumentData & {
|
|
35
|
+
id: string;
|
|
36
|
+
}) => T;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Options for collection queries.
|
|
40
|
+
*/
|
|
41
|
+
export interface FirestoreCollectionOptions<T> extends FirestoreBaseOptions {
|
|
42
|
+
/** Collection path (e.g., 'users' or 'projects/abc/members') */
|
|
43
|
+
collectionPath: string;
|
|
44
|
+
/** Firestore query constraints (where, orderBy, etc.) */
|
|
45
|
+
constraints?: QueryConstraint[];
|
|
46
|
+
/** Enable realtime updates via onSnapshot (default: false) */
|
|
47
|
+
subscribe?: boolean;
|
|
48
|
+
/** Transform function applied to each document. Receives data WITH id included. */
|
|
49
|
+
select?: (data: DocumentData & {
|
|
50
|
+
id: string;
|
|
51
|
+
}) => T;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Options for infinite scroll queries.
|
|
55
|
+
*/
|
|
56
|
+
export interface FirestoreInfiniteOptions<T> extends FirestoreBaseOptions {
|
|
57
|
+
/** Collection path */
|
|
58
|
+
collectionPath: string;
|
|
59
|
+
/** Firestore query constraints (where, orderBy - required for pagination) */
|
|
60
|
+
constraints?: QueryConstraint[];
|
|
61
|
+
/** Number of items per page (default: 20) */
|
|
62
|
+
pageSize?: number;
|
|
63
|
+
/** Transform function applied to each document. Receives data WITH id included. */
|
|
64
|
+
select?: (data: DocumentData & {
|
|
65
|
+
id: string;
|
|
66
|
+
}) => T;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Options for paginated queries with page numbers.
|
|
70
|
+
*/
|
|
71
|
+
export interface FirestorePaginatedOptions<T> extends FirestoreBaseOptions {
|
|
72
|
+
/** Collection path */
|
|
73
|
+
collectionPath: string;
|
|
74
|
+
/** Firestore query constraints */
|
|
75
|
+
constraints?: QueryConstraint[];
|
|
76
|
+
/** Number of items per page (default: 10) */
|
|
77
|
+
pageSize?: number;
|
|
78
|
+
/** Initial page number (default: 1) */
|
|
79
|
+
initialPage?: number;
|
|
80
|
+
/** Transform function applied to each document. Receives data WITH id included. */
|
|
81
|
+
select?: (data: DocumentData & {
|
|
82
|
+
id: string;
|
|
83
|
+
}) => T;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Result page for infinite queries.
|
|
87
|
+
*/
|
|
88
|
+
export interface InfinitePage<T> {
|
|
89
|
+
items: WithId<T>[];
|
|
90
|
+
lastDoc: DocumentSnapshot | null;
|
|
91
|
+
size: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Result for paginated queries.
|
|
95
|
+
*/
|
|
96
|
+
export interface PaginatedResult<T> {
|
|
97
|
+
items: WithId<T>[];
|
|
98
|
+
cursors: DocumentSnapshot[];
|
|
99
|
+
hasMore: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Mutation operation types for batch mutations.
|
|
103
|
+
*/
|
|
104
|
+
export type MutationOperation = {
|
|
105
|
+
type: 'set';
|
|
106
|
+
docPath: string;
|
|
107
|
+
data: DocumentData;
|
|
108
|
+
merge?: boolean;
|
|
109
|
+
} | {
|
|
110
|
+
type: 'update';
|
|
111
|
+
docPath: string;
|
|
112
|
+
data: DocumentData;
|
|
113
|
+
} | {
|
|
114
|
+
type: 'delete';
|
|
115
|
+
docPath: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Options for document mutations.
|
|
119
|
+
*/
|
|
120
|
+
export interface FirestoreMutationOptions<T> {
|
|
121
|
+
/** Document path for single-doc mutations */
|
|
122
|
+
docPath?: string;
|
|
123
|
+
/** Query keys to invalidate on success */
|
|
124
|
+
invalidateKeys?: readonly unknown[][];
|
|
125
|
+
/** Enable optimistic updates */
|
|
126
|
+
optimistic?: {
|
|
127
|
+
/** Query key of the cache to update */
|
|
128
|
+
queryKey: readonly unknown[];
|
|
129
|
+
/** How to update the cached data */
|
|
130
|
+
updater: (oldData: T | undefined, newData: Partial<T>) => T;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Options for batch mutations.
|
|
135
|
+
*/
|
|
136
|
+
export interface FirestoreBatchOptions {
|
|
137
|
+
/** Query keys to invalidate on success */
|
|
138
|
+
invalidateKeys?: readonly unknown[][];
|
|
139
|
+
/** Max operations per batch (default: 450) */
|
|
140
|
+
batchSize?: number;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/firestore/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/firestore/types.ts"],"names":[],"mappings":"AAYA;;GAEG;AACH,MAAM,UAAU,SAAS,CACvB,IAA8B;IAE9B,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type UseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
import { type DocumentData } from 'firebase/firestore';
|
|
3
|
+
import type { FirestoreCollectionOptions, WithId } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Fetch all documents from a Firestore collection with optional realtime updates.
|
|
6
|
+
* Use this for small collections where you need all documents at once.
|
|
7
|
+
* For large collections, use useFirestoreInfinite or useFirestorePaginated.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* // Fetch all channels in a project
|
|
12
|
+
* const { data: channels } = useFirestoreCollection<Channel>({
|
|
13
|
+
* collectionPath: `projects/${projectId}/channels`,
|
|
14
|
+
* queryKey: ['channels', projectId],
|
|
15
|
+
* constraints: [orderBy('createdAt', 'asc')],
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // With realtime updates
|
|
19
|
+
* const { data: members } = useFirestoreCollection<Member>({
|
|
20
|
+
* collectionPath: `projects/${projectId}/members`,
|
|
21
|
+
* queryKey: ['members', projectId],
|
|
22
|
+
* subscribe: true,
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function useFirestoreCollection<T extends DocumentData = DocumentData>({ collectionPath, queryKey, constraints, enabled, subscribe, staleTime, gcTime, select, }: FirestoreCollectionOptions<T>): UseQueryResult<WithId<T>[], Error>;
|
|
27
|
+
//# sourceMappingURL=use-firestore-collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-collection.d.ts","sourceRoot":"","sources":["../../src/firestore/use-firestore-collection.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC5E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAsDpE"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { collection, query, getDocs, onSnapshot, } from 'firebase/firestore';
|
|
5
|
+
import { useFirestoreDb } from './context';
|
|
6
|
+
/**
|
|
7
|
+
* Fetch all documents from a Firestore collection with optional realtime updates.
|
|
8
|
+
* Use this for small collections where you need all documents at once.
|
|
9
|
+
* For large collections, use useFirestoreInfinite or useFirestorePaginated.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* // Fetch all channels in a project
|
|
14
|
+
* const { data: channels } = useFirestoreCollection<Channel>({
|
|
15
|
+
* collectionPath: `projects/${projectId}/channels`,
|
|
16
|
+
* queryKey: ['channels', projectId],
|
|
17
|
+
* constraints: [orderBy('createdAt', 'asc')],
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // With realtime updates
|
|
21
|
+
* const { data: members } = useFirestoreCollection<Member>({
|
|
22
|
+
* collectionPath: `projects/${projectId}/members`,
|
|
23
|
+
* queryKey: ['members', projectId],
|
|
24
|
+
* subscribe: true,
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function useFirestoreCollection({ collectionPath, queryKey, constraints = [], enabled = true, subscribe = false, staleTime, gcTime, select, }) {
|
|
29
|
+
const db = useFirestoreDb();
|
|
30
|
+
const queryClient = useQueryClient();
|
|
31
|
+
// Set up realtime subscription
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!subscribe || !enabled)
|
|
34
|
+
return;
|
|
35
|
+
const collectionRef = collection(db, collectionPath);
|
|
36
|
+
const q = constraints.length > 0
|
|
37
|
+
? query(collectionRef, ...constraints)
|
|
38
|
+
: collectionRef;
|
|
39
|
+
const unsubscribe = onSnapshot(q, (snapshot) => {
|
|
40
|
+
const items = snapshot.docs.map((docSnap) => {
|
|
41
|
+
const rawData = docSnap.data();
|
|
42
|
+
// Include id in data passed to select, so it can be renamed/transformed
|
|
43
|
+
const dataWithId = { id: docSnap.id, ...rawData };
|
|
44
|
+
const data = select ? select(dataWithId) : dataWithId;
|
|
45
|
+
return data;
|
|
46
|
+
});
|
|
47
|
+
queryClient.setQueryData(queryKey, items);
|
|
48
|
+
}, (error) => {
|
|
49
|
+
console.error('[useFirestoreCollection] Subscription error:', error);
|
|
50
|
+
});
|
|
51
|
+
return () => unsubscribe();
|
|
52
|
+
}, [db, collectionPath, queryKey, constraints, enabled, subscribe, select, queryClient]);
|
|
53
|
+
return useQuery({
|
|
54
|
+
queryKey,
|
|
55
|
+
queryFn: async () => {
|
|
56
|
+
const collectionRef = collection(db, collectionPath);
|
|
57
|
+
const q = constraints.length > 0
|
|
58
|
+
? query(collectionRef, ...constraints)
|
|
59
|
+
: collectionRef;
|
|
60
|
+
const snapshot = await getDocs(q);
|
|
61
|
+
return snapshot.docs.map((docSnap) => {
|
|
62
|
+
const rawData = docSnap.data();
|
|
63
|
+
// Include id in data passed to select, so it can be renamed/transformed
|
|
64
|
+
const dataWithId = { id: docSnap.id, ...rawData };
|
|
65
|
+
const data = select ? select(dataWithId) : dataWithId;
|
|
66
|
+
return data;
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
enabled: enabled && !subscribe,
|
|
70
|
+
staleTime: subscribe ? Infinity : staleTime,
|
|
71
|
+
gcTime,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=use-firestore-collection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-collection.js","sourceRoot":"","sources":["../../src/firestore/use-firestore-collection.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AACtF,OAAO,EACL,UAAU,EACV,KAAK,EACL,OAAO,EACP,UAAU,GAEX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,sBAAsB,CAAwC,EAC5E,cAAc,EACd,QAAQ,EACR,WAAW,GAAG,EAAE,EAChB,OAAO,GAAG,IAAI,EACd,SAAS,GAAG,KAAK,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACwB;IAC9B,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,+BAA+B;IAC/B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO;YAAE,OAAO;QAEnC,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC;YAC9B,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC;YACtC,CAAC,CAAC,aAAa,CAAC;QAElB,MAAM,WAAW,GAAG,UAAU,CAC5B,CAAC,EACD,CAAC,QAAQ,EAAE,EAAE;YACX,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC/B,wEAAwE;gBACxE,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;gBAClD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACtD,OAAO,IAAiB,CAAC;YAC3B,CAAC,CAAC,CAAC;YACH,WAAW,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC5C,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;YACR,OAAO,CAAC,KAAK,CAAC,8CAA8C,EAAE,KAAK,CAAC,CAAC;QACvE,CAAC,CACF,CAAC;QAEF,OAAO,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAEzF,OAAO,QAAQ,CAAC;QACd,QAAQ;QACR,OAAO,EAAE,KAAK,IAA0B,EAAE;YACxC,MAAM,aAAa,GAAG,UAAU,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;YACrD,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC;gBAC9B,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,WAAW,CAAC;gBACtC,CAAC,CAAC,aAAa,CAAC;YAElB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;YAClC,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBACnC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;gBAC/B,wEAAwE;gBACxE,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;gBAClD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACtD,OAAO,IAAiB,CAAC;YAC3B,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,OAAO,IAAI,CAAC,SAAS;QAC9B,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;QAC3C,MAAM;KACP,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type UseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
import { type DocumentData } from 'firebase/firestore';
|
|
3
|
+
import type { FirestoreDocOptions, WithId } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Fetch a single Firestore document with optional realtime updates.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // Simple fetch
|
|
10
|
+
* const { data: user } = useFirestoreDoc<User>({
|
|
11
|
+
* docPath: `users/${userId}`,
|
|
12
|
+
* queryKey: ['user', userId],
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // With realtime updates
|
|
16
|
+
* const { data: project } = useFirestoreDoc<Project>({
|
|
17
|
+
* docPath: `projects/${projectId}`,
|
|
18
|
+
* queryKey: ['project', projectId],
|
|
19
|
+
* subscribe: true,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Conditional fetch
|
|
23
|
+
* const { data: profile } = useFirestoreDoc<Profile>({
|
|
24
|
+
* docPath: `profiles/${userId}`,
|
|
25
|
+
* queryKey: ['profile', userId],
|
|
26
|
+
* enabled: !!userId,
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function useFirestoreDoc<T extends DocumentData = DocumentData>({ docPath, queryKey, enabled, subscribe, staleTime, gcTime, select, }: FirestoreDocOptions<T>): UseQueryResult<WithId<T> | null, Error>;
|
|
31
|
+
//# sourceMappingURL=use-firestore-doc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-doc.d.ts","sourceRoot":"","sources":["../../src/firestore/use-firestore-doc.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEhF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACrE,OAAO,EACP,QAAQ,EACR,OAAc,EACd,SAAiB,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAkDlE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { doc, getDoc, onSnapshot } from 'firebase/firestore';
|
|
5
|
+
import { useFirestoreDb } from './context';
|
|
6
|
+
/**
|
|
7
|
+
* Fetch a single Firestore document with optional realtime updates.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* // Simple fetch
|
|
12
|
+
* const { data: user } = useFirestoreDoc<User>({
|
|
13
|
+
* docPath: `users/${userId}`,
|
|
14
|
+
* queryKey: ['user', userId],
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // With realtime updates
|
|
18
|
+
* const { data: project } = useFirestoreDoc<Project>({
|
|
19
|
+
* docPath: `projects/${projectId}`,
|
|
20
|
+
* queryKey: ['project', projectId],
|
|
21
|
+
* subscribe: true,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Conditional fetch
|
|
25
|
+
* const { data: profile } = useFirestoreDoc<Profile>({
|
|
26
|
+
* docPath: `profiles/${userId}`,
|
|
27
|
+
* queryKey: ['profile', userId],
|
|
28
|
+
* enabled: !!userId,
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function useFirestoreDoc({ docPath, queryKey, enabled = true, subscribe = false, staleTime, gcTime, select, }) {
|
|
33
|
+
const db = useFirestoreDb();
|
|
34
|
+
const queryClient = useQueryClient();
|
|
35
|
+
// Set up realtime subscription
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!subscribe || !enabled)
|
|
38
|
+
return;
|
|
39
|
+
const docRef = doc(db, docPath);
|
|
40
|
+
const unsubscribe = onSnapshot(docRef, (snapshot) => {
|
|
41
|
+
if (snapshot.exists()) {
|
|
42
|
+
const rawData = snapshot.data();
|
|
43
|
+
// Include id in data passed to select, so it can be renamed/transformed
|
|
44
|
+
const dataWithId = { id: snapshot.id, ...rawData };
|
|
45
|
+
const data = select ? select(dataWithId) : dataWithId;
|
|
46
|
+
queryClient.setQueryData(queryKey, data);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
queryClient.setQueryData(queryKey, null);
|
|
50
|
+
}
|
|
51
|
+
}, (error) => {
|
|
52
|
+
console.error('[useFirestoreDoc] Subscription error:', error);
|
|
53
|
+
});
|
|
54
|
+
return () => unsubscribe();
|
|
55
|
+
}, [db, docPath, queryKey, enabled, subscribe, select, queryClient]);
|
|
56
|
+
return useQuery({
|
|
57
|
+
queryKey,
|
|
58
|
+
queryFn: async () => {
|
|
59
|
+
const docRef = doc(db, docPath);
|
|
60
|
+
const snapshot = await getDoc(docRef);
|
|
61
|
+
if (!snapshot.exists()) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const rawData = snapshot.data();
|
|
65
|
+
// Include id in data passed to select, so it can be renamed/transformed
|
|
66
|
+
const dataWithId = { id: snapshot.id, ...rawData };
|
|
67
|
+
const data = select ? select(dataWithId) : dataWithId;
|
|
68
|
+
return data;
|
|
69
|
+
},
|
|
70
|
+
enabled: enabled && !subscribe, // Don't fetch if subscribing (snapshot handles it)
|
|
71
|
+
staleTime: subscribe ? Infinity : staleTime, // Realtime data is always fresh
|
|
72
|
+
gcTime,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=use-firestore-doc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-doc.js","sourceRoot":"","sources":["../../src/firestore/use-firestore-doc.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAqB,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,eAAe,CAAwC,EACrE,OAAO,EACP,QAAQ,EACR,OAAO,GAAG,IAAI,EACd,SAAS,GAAG,KAAK,EACjB,SAAS,EACT,MAAM,EACN,MAAM,GACiB;IACvB,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,+BAA+B;IAC/B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO;YAAE,OAAO;QAEnC,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAChC,MAAM,WAAW,GAAG,UAAU,CAC5B,MAAM,EACN,CAAC,QAAQ,EAAE,EAAE;YACX,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAChC,wEAAwE;gBACxE,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;gBACnD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBACtD,WAAW,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAiB,CAAC,CAAC;YACxD,CAAC;iBAAM,CAAC;gBACN,WAAW,CAAC,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;YACR,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,KAAK,CAAC,CAAC;QAChE,CAAC,CACF,CAAC;QAEF,OAAO,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAErE,OAAO,QAAQ,CAAC;QACd,QAAQ;QACR,OAAO,EAAE,KAAK,IAA+B,EAAE;YAC7C,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YAChC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;YAEtC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACvB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAChC,wEAAwE;YACxE,MAAM,UAAU,GAAG,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;YACnD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;YACtD,OAAO,IAAiB,CAAC;QAC3B,CAAC;QACD,OAAO,EAAE,OAAO,IAAI,CAAC,SAAS,EAAE,mDAAmD;QACnF,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,gCAAgC;QAC7E,MAAM;KACP,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type UseInfiniteQueryResult } from '@tanstack/react-query';
|
|
2
|
+
import { type DocumentData, type DocumentSnapshot } from 'firebase/firestore';
|
|
3
|
+
import type { FirestoreInfiniteOptions, InfinitePage, WithId } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Infinite scroll pagination for Firestore collections.
|
|
6
|
+
* Automatically handles cursor-based pagination with React Query.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const {
|
|
11
|
+
* data,
|
|
12
|
+
* fetchNextPage,
|
|
13
|
+
* hasNextPage,
|
|
14
|
+
* isFetchingNextPage,
|
|
15
|
+
* isLoading,
|
|
16
|
+
* } = useFirestoreInfinite<Post>({
|
|
17
|
+
* collectionPath: 'posts',
|
|
18
|
+
* queryKey: ['posts', 'feed'],
|
|
19
|
+
* constraints: [
|
|
20
|
+
* where('status', '==', 'published'),
|
|
21
|
+
* orderBy('createdAt', 'desc'),
|
|
22
|
+
* ],
|
|
23
|
+
* pageSize: 20,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Flatten pages for rendering
|
|
27
|
+
* const posts = data?.pages.flatMap(page => page.items) ?? [];
|
|
28
|
+
*
|
|
29
|
+
* // Load more on scroll
|
|
30
|
+
* <InfiniteScroll onLoadMore={fetchNextPage} hasMore={hasNextPage} />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function useFirestoreInfinite<T extends DocumentData = DocumentData>({ collectionPath, queryKey, constraints, pageSize, enabled, staleTime, gcTime, select, }: FirestoreInfiniteOptions<T>): UseInfiniteQueryResult<{
|
|
34
|
+
pages: InfinitePage<T>[];
|
|
35
|
+
pageParams: (DocumentSnapshot | null)[];
|
|
36
|
+
}, Error>;
|
|
37
|
+
/**
|
|
38
|
+
* Helper to flatten infinite query pages into a single array.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* const { data } = useFirestoreInfinite<Post>({ ... });
|
|
43
|
+
* const posts = flattenInfiniteData(data);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare function flattenInfiniteData<T>(data: {
|
|
47
|
+
pages: InfinitePage<T>[];
|
|
48
|
+
} | undefined): WithId<T>[];
|
|
49
|
+
/**
|
|
50
|
+
* Get total count of items across all loaded pages.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getInfiniteDataCount<T>(data: {
|
|
53
|
+
pages: InfinitePage<T>[];
|
|
54
|
+
} | undefined): number;
|
|
55
|
+
//# sourceMappingURL=use-firestore-infinite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-infinite.d.ts","sourceRoot":"","sources":["../../src/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAI9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC1E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,OAAc,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,sBAAsB,CAAC;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAAC,UAAU,EAAE,CAAC,gBAAgB,GAAG,IAAI,CAAC,EAAE,CAAA;CAAE,EAAE,KAAK,CAAC,CA0CpI;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,EAAE;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;CAAE,GAAG,SAAS,GAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,CAGb;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE;IAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAA;CAAE,GAAG,SAAS,GAC7C,MAAM,CAGR"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
3
|
+
import { collection, query, getDocs, limit, startAfter, } from 'firebase/firestore';
|
|
4
|
+
import { useFirestoreDb } from './context';
|
|
5
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
6
|
+
/**
|
|
7
|
+
* Infinite scroll pagination for Firestore collections.
|
|
8
|
+
* Automatically handles cursor-based pagination with React Query.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const {
|
|
13
|
+
* data,
|
|
14
|
+
* fetchNextPage,
|
|
15
|
+
* hasNextPage,
|
|
16
|
+
* isFetchingNextPage,
|
|
17
|
+
* isLoading,
|
|
18
|
+
* } = useFirestoreInfinite<Post>({
|
|
19
|
+
* collectionPath: 'posts',
|
|
20
|
+
* queryKey: ['posts', 'feed'],
|
|
21
|
+
* constraints: [
|
|
22
|
+
* where('status', '==', 'published'),
|
|
23
|
+
* orderBy('createdAt', 'desc'),
|
|
24
|
+
* ],
|
|
25
|
+
* pageSize: 20,
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Flatten pages for rendering
|
|
29
|
+
* const posts = data?.pages.flatMap(page => page.items) ?? [];
|
|
30
|
+
*
|
|
31
|
+
* // Load more on scroll
|
|
32
|
+
* <InfiniteScroll onLoadMore={fetchNextPage} hasMore={hasNextPage} />
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function useFirestoreInfinite({ collectionPath, queryKey, constraints = [], pageSize = DEFAULT_PAGE_SIZE, enabled = true, staleTime, gcTime, select, }) {
|
|
36
|
+
const db = useFirestoreDb();
|
|
37
|
+
return useInfiniteQuery({
|
|
38
|
+
queryKey,
|
|
39
|
+
queryFn: async ({ pageParam }) => {
|
|
40
|
+
const collectionRef = collection(db, collectionPath);
|
|
41
|
+
// Build query with constraints
|
|
42
|
+
const queryConstraints = [
|
|
43
|
+
...constraints,
|
|
44
|
+
...(pageParam ? [startAfter(pageParam)] : []),
|
|
45
|
+
limit(pageSize),
|
|
46
|
+
];
|
|
47
|
+
const q = query(collectionRef, ...queryConstraints);
|
|
48
|
+
const snapshot = await getDocs(q);
|
|
49
|
+
const items = snapshot.docs.map((docSnap) => {
|
|
50
|
+
const rawData = docSnap.data();
|
|
51
|
+
// Include id in data passed to select, so it can be renamed/transformed
|
|
52
|
+
const dataWithId = { id: docSnap.id, ...rawData };
|
|
53
|
+
const data = select ? select(dataWithId) : dataWithId;
|
|
54
|
+
return data;
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
items,
|
|
58
|
+
lastDoc: snapshot.docs[snapshot.docs.length - 1] ?? null,
|
|
59
|
+
size: snapshot.docs.length,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
initialPageParam: null,
|
|
63
|
+
getNextPageParam: (lastPage) => {
|
|
64
|
+
// If we got fewer items than requested, there are no more pages
|
|
65
|
+
if (lastPage.size < pageSize)
|
|
66
|
+
return undefined;
|
|
67
|
+
return lastPage.lastDoc;
|
|
68
|
+
},
|
|
69
|
+
enabled,
|
|
70
|
+
staleTime,
|
|
71
|
+
gcTime,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Helper to flatten infinite query pages into a single array.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* const { data } = useFirestoreInfinite<Post>({ ... });
|
|
80
|
+
* const posts = flattenInfiniteData(data);
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function flattenInfiniteData(data) {
|
|
84
|
+
if (!data)
|
|
85
|
+
return [];
|
|
86
|
+
return data.pages.flatMap((page) => page.items);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get total count of items across all loaded pages.
|
|
90
|
+
*/
|
|
91
|
+
export function getInfiniteDataCount(data) {
|
|
92
|
+
if (!data)
|
|
93
|
+
return 0;
|
|
94
|
+
return data.pages.reduce((sum, page) => sum + page.items.length, 0);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=use-firestore-infinite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-infinite.js","sourceRoot":"","sources":["../../src/firestore/use-firestore-infinite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type QueryKey, type UseMutationResult } from '@tanstack/react-query';
|
|
2
|
+
import { type DocumentData } from 'firebase/firestore';
|
|
3
|
+
import type { FirestoreMutationOptions, FirestoreBatchOptions, MutationOperation } from './types';
|
|
4
|
+
interface SetResult<T> {
|
|
5
|
+
id: string;
|
|
6
|
+
data: T;
|
|
7
|
+
}
|
|
8
|
+
export declare function useFirestoreSet<T extends DocumentData>({ invalidateKeys, }?: Pick<FirestoreMutationOptions<T>, 'invalidateKeys'>): UseMutationResult<SetResult<T>, Error, {
|
|
9
|
+
docPath: string;
|
|
10
|
+
data: T;
|
|
11
|
+
merge?: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function useFirestoreUpdate<T extends DocumentData>({ invalidateKeys, optimistic, }?: FirestoreMutationOptions<T>): UseMutationResult<Partial<T>, Error, {
|
|
14
|
+
docPath: string;
|
|
15
|
+
data: Partial<T>;
|
|
16
|
+
}, {
|
|
17
|
+
previousData: T | undefined;
|
|
18
|
+
} | undefined>;
|
|
19
|
+
export declare function useFirestoreDelete<T = unknown>({ invalidateKeys, optimistic, }?: {
|
|
20
|
+
invalidateKeys?: readonly QueryKey[];
|
|
21
|
+
optimistic?: {
|
|
22
|
+
queryKey: QueryKey;
|
|
23
|
+
updater: (oldData: T | undefined, docPath: string) => T;
|
|
24
|
+
};
|
|
25
|
+
}): UseMutationResult<string, Error, {
|
|
26
|
+
docPath: string;
|
|
27
|
+
}, {
|
|
28
|
+
previousData: T | undefined;
|
|
29
|
+
} | undefined>;
|
|
30
|
+
interface BatchResult {
|
|
31
|
+
committed: number;
|
|
32
|
+
batches: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function useFirestoreBatch({ invalidateKeys, batchSize, }?: FirestoreBatchOptions): UseMutationResult<BatchResult, Error, {
|
|
35
|
+
operations: MutationOperation[];
|
|
36
|
+
}>;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=use-firestore-mutations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-mutations.d.ts","sourceRoot":"","sources":["../../src/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"AAEA,OAAO,EAA+B,KAAK,QAAQ,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC3G,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAIlG,UAAU,SAAS,CAAC,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,CAAC,CAAC;CACT;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,YAAY,EAAE,EACtD,cAAmB,GACpB,GAAE,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAM,GAAG,iBAAiB,CAC7E,SAAS,CAAC,CAAC,CAAC,EACZ,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAC9C,CAwBA;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,EAAE,EACzD,cAAmB,EACnB,UAAU,GACX,GAAE,wBAAwB,CAAC,CAAC,CAAM,GAAG,iBAAiB,CACrD,OAAO,CAAC,CAAC,CAAC,EACV,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAAE,EACrC;IAAE,YAAY,EAAE,CAAC,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,CAC5C,CA0CA;AAED,wBAAgB,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAAE,EAC9C,cAAmB,EACnB,UAAU,GACX,GAAE;IACD,cAAc,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,QAAQ,CAAC;QACnB,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC,CAAC;KACzD,CAAC;CACE,GAAG,iBAAiB,CACxB,MAAM,EACN,KAAK,EACL;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,EACnB;IAAE,YAAY,EAAE,CAAC,GAAG,SAAS,CAAA;CAAE,GAAG,SAAS,CAC5C,CAoCA;AAED,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,cAAmB,EACnB,SAA8B,GAC/B,GAAE,qBAA0B,GAAG,iBAAiB,CAC/C,WAAW,EACX,KAAK,EACL;IAAE,UAAU,EAAE,iBAAiB,EAAE,CAAA;CAAE,CACpC,CAuDA"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { doc, setDoc, updateDoc, deleteDoc, writeBatch, } from 'firebase/firestore';
|
|
4
|
+
import { useFirestoreDb } from './context';
|
|
5
|
+
const DEFAULT_BATCH_SIZE = 450;
|
|
6
|
+
export function useFirestoreSet({ invalidateKeys = [], } = {}) {
|
|
7
|
+
const db = useFirestoreDb();
|
|
8
|
+
const queryClient = useQueryClient();
|
|
9
|
+
return useMutation({
|
|
10
|
+
mutationFn: async ({ docPath, data, merge = false, }) => {
|
|
11
|
+
const docRef = doc(db, docPath);
|
|
12
|
+
await setDoc(docRef, data, { merge });
|
|
13
|
+
return { id: docRef.id, data };
|
|
14
|
+
},
|
|
15
|
+
onSuccess: () => {
|
|
16
|
+
invalidateKeys.forEach((key) => {
|
|
17
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function useFirestoreUpdate({ invalidateKeys = [], optimistic, } = {}) {
|
|
23
|
+
const db = useFirestoreDb();
|
|
24
|
+
const queryClient = useQueryClient();
|
|
25
|
+
return useMutation({
|
|
26
|
+
mutationFn: async ({ docPath, data, }) => {
|
|
27
|
+
const docRef = doc(db, docPath);
|
|
28
|
+
await updateDoc(docRef, data);
|
|
29
|
+
return data;
|
|
30
|
+
},
|
|
31
|
+
onMutate: async ({ data }) => {
|
|
32
|
+
if (!optimistic)
|
|
33
|
+
return undefined;
|
|
34
|
+
await queryClient.cancelQueries({ queryKey: optimistic.queryKey });
|
|
35
|
+
const previousData = queryClient.getQueryData(optimistic.queryKey);
|
|
36
|
+
queryClient.setQueryData(optimistic.queryKey, (old) => optimistic.updater(old, data));
|
|
37
|
+
return { previousData };
|
|
38
|
+
},
|
|
39
|
+
onError: (_error, _variables, context) => {
|
|
40
|
+
if (optimistic && context?.previousData !== undefined) {
|
|
41
|
+
queryClient.setQueryData(optimistic.queryKey, context.previousData);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
onSettled: () => {
|
|
45
|
+
invalidateKeys.forEach((key) => {
|
|
46
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
47
|
+
});
|
|
48
|
+
if (optimistic) {
|
|
49
|
+
queryClient.invalidateQueries({ queryKey: optimistic.queryKey });
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export function useFirestoreDelete({ invalidateKeys = [], optimistic, } = {}) {
|
|
55
|
+
const db = useFirestoreDb();
|
|
56
|
+
const queryClient = useQueryClient();
|
|
57
|
+
return useMutation({
|
|
58
|
+
mutationFn: async ({ docPath }) => {
|
|
59
|
+
const docRef = doc(db, docPath);
|
|
60
|
+
await deleteDoc(docRef);
|
|
61
|
+
return docPath;
|
|
62
|
+
},
|
|
63
|
+
onMutate: async ({ docPath }) => {
|
|
64
|
+
if (!optimistic)
|
|
65
|
+
return undefined;
|
|
66
|
+
await queryClient.cancelQueries({ queryKey: optimistic.queryKey });
|
|
67
|
+
const previousData = queryClient.getQueryData(optimistic.queryKey);
|
|
68
|
+
queryClient.setQueryData(optimistic.queryKey, (old) => optimistic.updater(old, docPath));
|
|
69
|
+
return { previousData };
|
|
70
|
+
},
|
|
71
|
+
onError: (_error, _variables, context) => {
|
|
72
|
+
if (optimistic && context?.previousData !== undefined) {
|
|
73
|
+
queryClient.setQueryData(optimistic.queryKey, context.previousData);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
onSettled: () => {
|
|
77
|
+
invalidateKeys.forEach((key) => {
|
|
78
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
79
|
+
});
|
|
80
|
+
if (optimistic) {
|
|
81
|
+
queryClient.invalidateQueries({ queryKey: optimistic.queryKey });
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
export function useFirestoreBatch({ invalidateKeys = [], batchSize = DEFAULT_BATCH_SIZE, } = {}) {
|
|
87
|
+
const db = useFirestoreDb();
|
|
88
|
+
const queryClient = useQueryClient();
|
|
89
|
+
return useMutation({
|
|
90
|
+
mutationFn: async ({ operations, }) => {
|
|
91
|
+
const chunks = [];
|
|
92
|
+
for (let i = 0; i < operations.length; i += batchSize) {
|
|
93
|
+
chunks.push(operations.slice(i, i + batchSize));
|
|
94
|
+
}
|
|
95
|
+
let totalCommitted = 0;
|
|
96
|
+
for (const chunk of chunks) {
|
|
97
|
+
const batch = writeBatch(db);
|
|
98
|
+
for (const op of chunk) {
|
|
99
|
+
const docRef = doc(db, op.docPath);
|
|
100
|
+
switch (op.type) {
|
|
101
|
+
case 'set':
|
|
102
|
+
if (op.merge) {
|
|
103
|
+
batch.set(docRef, op.data, { merge: true });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
batch.set(docRef, op.data);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case 'update':
|
|
110
|
+
batch.update(docRef, op.data);
|
|
111
|
+
break;
|
|
112
|
+
case 'delete':
|
|
113
|
+
batch.delete(docRef);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
await batch.commit();
|
|
118
|
+
totalCommitted += chunk.length;
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
committed: totalCommitted,
|
|
122
|
+
batches: chunks.length,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
onSuccess: () => {
|
|
126
|
+
invalidateKeys.forEach((key) => {
|
|
127
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=use-firestore-mutations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-mutations.js","sourceRoot":"","sources":["../../src/firestore/use-firestore-mutations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type UseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
import { type DocumentData } from 'firebase/firestore';
|
|
3
|
+
import type { FirestorePaginatedOptions, WithId } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Return type for useFirestorePaginated hook
|
|
6
|
+
*/
|
|
7
|
+
export type UseFirestorePaginatedResult<T> = UseQueryResult<WithId<T>[], Error> & {
|
|
8
|
+
page: number;
|
|
9
|
+
pageSize: number;
|
|
10
|
+
hasNextPage: boolean;
|
|
11
|
+
hasPrevPage: boolean;
|
|
12
|
+
setPage: (page: number) => void;
|
|
13
|
+
nextPage: () => void;
|
|
14
|
+
prevPage: () => void;
|
|
15
|
+
resetToFirstPage: () => void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Page-based pagination for Firestore collections.
|
|
19
|
+
* Supports navigating to specific pages (1, 2, 3...) rather than infinite scroll.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const {
|
|
24
|
+
* data,
|
|
25
|
+
* page,
|
|
26
|
+
* setPage,
|
|
27
|
+
* hasNextPage,
|
|
28
|
+
* hasPrevPage,
|
|
29
|
+
* isLoading,
|
|
30
|
+
* } = useFirestorePaginated<Task>({
|
|
31
|
+
* collectionPath: 'tasks',
|
|
32
|
+
* queryKey: ['tasks', 'list'],
|
|
33
|
+
* constraints: [where('status', '==', 'active'), orderBy('createdAt', 'desc')],
|
|
34
|
+
* pageSize: 10,
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* // Render pagination controls
|
|
38
|
+
* <button onClick={() => setPage(page - 1)} disabled={!hasPrevPage}>Previous</button>
|
|
39
|
+
* <span>Page {page}</span>
|
|
40
|
+
* <button onClick={() => setPage(page + 1)} disabled={!hasNextPage}>Next</button>
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function useFirestorePaginated<T extends DocumentData = DocumentData>({ collectionPath, queryKey, constraints, pageSize, initialPage, enabled, staleTime, gcTime, select, }: FirestorePaginatedOptions<T>): UseFirestorePaginatedResult<T>;
|
|
44
|
+
//# sourceMappingURL=use-firestore-paginated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-paginated.d.ts","sourceRoot":"","sources":["../../src/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"AAGA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAIjE;;GAEG;AACH,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC3E,cAAc,EACd,QAAQ,EACR,WAAgB,EAChB,QAA4B,EAC5B,WAAe,EACf,OAAc,EACd,SAAS,EACT,MAAM,EACN,MAAM,GACP,EAAE,yBAAyB,CAAC,CAAC,CAAC,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAmG/D"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
3
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
|
+
import { collection, query, getDocs, limit, startAfter, } from 'firebase/firestore';
|
|
5
|
+
import { useFirestoreDb } from './context';
|
|
6
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
7
|
+
/**
|
|
8
|
+
* Page-based pagination for Firestore collections.
|
|
9
|
+
* Supports navigating to specific pages (1, 2, 3...) rather than infinite scroll.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const {
|
|
14
|
+
* data,
|
|
15
|
+
* page,
|
|
16
|
+
* setPage,
|
|
17
|
+
* hasNextPage,
|
|
18
|
+
* hasPrevPage,
|
|
19
|
+
* isLoading,
|
|
20
|
+
* } = useFirestorePaginated<Task>({
|
|
21
|
+
* collectionPath: 'tasks',
|
|
22
|
+
* queryKey: ['tasks', 'list'],
|
|
23
|
+
* constraints: [where('status', '==', 'active'), orderBy('createdAt', 'desc')],
|
|
24
|
+
* pageSize: 10,
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Render pagination controls
|
|
28
|
+
* <button onClick={() => setPage(page - 1)} disabled={!hasPrevPage}>Previous</button>
|
|
29
|
+
* <span>Page {page}</span>
|
|
30
|
+
* <button onClick={() => setPage(page + 1)} disabled={!hasNextPage}>Next</button>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function useFirestorePaginated({ collectionPath, queryKey, constraints = [], pageSize = DEFAULT_PAGE_SIZE, initialPage = 1, enabled = true, staleTime, gcTime, select, }) {
|
|
34
|
+
const db = useFirestoreDb();
|
|
35
|
+
const queryClient = useQueryClient();
|
|
36
|
+
const [page, setPageInternal] = useState(initialPage);
|
|
37
|
+
const [cursors, setCursors] = useState(new Map());
|
|
38
|
+
const [hasMore, setHasMore] = useState(true);
|
|
39
|
+
// Create a stable query key that includes the page
|
|
40
|
+
const pageQueryKey = useMemo(() => [...queryKey, 'page', page], [queryKey, page]);
|
|
41
|
+
const queryResult = useQuery({
|
|
42
|
+
queryKey: pageQueryKey,
|
|
43
|
+
queryFn: async () => {
|
|
44
|
+
const collectionRef = collection(db, collectionPath);
|
|
45
|
+
// Get cursor for current page (if not page 1)
|
|
46
|
+
const cursor = page > 1 ? cursors.get(page - 1) : undefined;
|
|
47
|
+
// Build query
|
|
48
|
+
const queryConstraints = [
|
|
49
|
+
...constraints,
|
|
50
|
+
...(cursor ? [startAfter(cursor)] : []),
|
|
51
|
+
limit(pageSize),
|
|
52
|
+
];
|
|
53
|
+
const q = query(collectionRef, ...queryConstraints);
|
|
54
|
+
const snapshot = await getDocs(q);
|
|
55
|
+
// Store the last doc as cursor for next page
|
|
56
|
+
if (snapshot.docs.length > 0) {
|
|
57
|
+
const lastDoc = snapshot.docs[snapshot.docs.length - 1];
|
|
58
|
+
setCursors((prev) => {
|
|
59
|
+
const next = new Map(prev);
|
|
60
|
+
next.set(page, lastDoc);
|
|
61
|
+
return next;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Determine if there are more pages
|
|
65
|
+
setHasMore(snapshot.docs.length === pageSize);
|
|
66
|
+
return snapshot.docs.map((docSnap) => {
|
|
67
|
+
const rawData = docSnap.data();
|
|
68
|
+
// Include id in data passed to select, so it can be renamed/transformed
|
|
69
|
+
const dataWithId = { id: docSnap.id, ...rawData };
|
|
70
|
+
const data = select ? select(dataWithId) : dataWithId;
|
|
71
|
+
return data;
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
enabled,
|
|
75
|
+
staleTime,
|
|
76
|
+
gcTime,
|
|
77
|
+
});
|
|
78
|
+
const setPage = useCallback((newPage) => {
|
|
79
|
+
if (newPage < 1)
|
|
80
|
+
return;
|
|
81
|
+
// Can only go forward one page at a time if cursor doesn't exist
|
|
82
|
+
if (newPage > page + 1 && !cursors.has(newPage - 1)) {
|
|
83
|
+
console.warn('[useFirestorePaginated] Cannot skip pages. Navigate sequentially.');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
setPageInternal(newPage);
|
|
87
|
+
}, [page, cursors]);
|
|
88
|
+
const nextPage = useCallback(() => {
|
|
89
|
+
if (hasMore)
|
|
90
|
+
setPage(page + 1);
|
|
91
|
+
}, [page, hasMore, setPage]);
|
|
92
|
+
const prevPage = useCallback(() => {
|
|
93
|
+
if (page > 1)
|
|
94
|
+
setPage(page - 1);
|
|
95
|
+
}, [page, setPage]);
|
|
96
|
+
const resetToFirstPage = useCallback(() => {
|
|
97
|
+
setCursors(new Map());
|
|
98
|
+
setPageInternal(1);
|
|
99
|
+
setHasMore(true);
|
|
100
|
+
// Invalidate all cached pages
|
|
101
|
+
queryClient.invalidateQueries({ queryKey, exact: false });
|
|
102
|
+
}, [queryClient, queryKey]);
|
|
103
|
+
return {
|
|
104
|
+
...queryResult,
|
|
105
|
+
// Pagination state
|
|
106
|
+
page,
|
|
107
|
+
pageSize,
|
|
108
|
+
hasNextPage: hasMore,
|
|
109
|
+
hasPrevPage: page > 1,
|
|
110
|
+
// Navigation functions
|
|
111
|
+
setPage,
|
|
112
|
+
nextPage,
|
|
113
|
+
prevPage,
|
|
114
|
+
resetToFirstPage,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=use-firestore-paginated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-firestore-paginated.js","sourceRoot":"","sources":["../../src/firestore/use-firestore-paginated.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ export type { QueryKey } from './keys';
|
|
|
5
5
|
export { invalidateByPrefix, removeByPrefix, updateQueryData } from './cache-helpers';
|
|
6
6
|
export { TTTQueryProvider } from './provider';
|
|
7
7
|
export type { TTTQueryProviderProps } from './provider';
|
|
8
|
+
export { FirestoreProvider, useFirestoreDb, useFirestoreDoc, useFirestoreCollection, useFirestoreInfinite, useFirestorePaginated, useFirestoreSet, useFirestoreUpdate, useFirestoreDelete, useFirestoreBatch, flattenInfiniteData, getInfiniteDataCount, docWithId, } from './firestore';
|
|
9
|
+
export type { FirestoreProviderProps, WithId, FirestoreBaseOptions, FirestoreDocOptions, FirestoreCollectionOptions, FirestoreInfiniteOptions, FirestorePaginatedOptions, InfinitePage, PaginatedResult, MutationOperation, FirestoreMutationOptions, FirestoreBatchOptions, } from './firestore';
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAGlE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC9C,YAAY,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGxD,OAAO,EAEL,iBAAiB,EACjB,cAAc,EAEd,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EAErB,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EAEjB,mBAAmB,EACnB,oBAAoB,EACpB,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,YAAY,EAEV,sBAAsB,EAEtB,MAAM,EACN,oBAAoB,EACpB,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
// Query Client
|
|
1
2
|
export { createTTTQueryClient } from './query-client';
|
|
3
|
+
// Query Keys
|
|
2
4
|
export { keys, createKeyScope } from './keys';
|
|
5
|
+
// Cache Helpers
|
|
3
6
|
export { invalidateByPrefix, removeByPrefix, updateQueryData } from './cache-helpers';
|
|
7
|
+
// Provider
|
|
4
8
|
export { TTTQueryProvider } from './provider';
|
|
9
|
+
// Firestore Integration
|
|
10
|
+
export {
|
|
11
|
+
// Provider
|
|
12
|
+
FirestoreProvider, useFirestoreDb,
|
|
13
|
+
// Query Hooks
|
|
14
|
+
useFirestoreDoc, useFirestoreCollection, useFirestoreInfinite, useFirestorePaginated,
|
|
15
|
+
// Mutation Hooks
|
|
16
|
+
useFirestoreSet, useFirestoreUpdate, useFirestoreDelete, useFirestoreBatch,
|
|
17
|
+
// Helpers
|
|
18
|
+
flattenInfiniteData, getInfiniteDataCount, docWithId, } from './firestore';
|
|
5
19
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAGtD,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAG9C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAe;AACf,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAGtD,aAAa;AACb,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAG9C,gBAAgB;AAChB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEtF,WAAW;AACX,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,wBAAwB;AACxB,OAAO;AACL,WAAW;AACX,iBAAiB,EACjB,cAAc;AACd,cAAc;AACd,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB;AACrB,iBAAiB;AACjB,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB;AACjB,UAAU;AACV,mBAAmB,EACnB,oBAAoB,EACpB,SAAS,GACV,MAAM,aAAa,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttt-productions/query-core",
|
|
3
|
-
"version": "0.2
|
|
4
|
-
"description": "Shared TanStack Query (React Query) client defaults
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "Shared TanStack Query (React Query) client defaults, query key helpers, and Firestore integration hooks for TTT Productions apps",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ttt-productions/ttt-packages.git",
|
|
@@ -29,13 +29,20 @@
|
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@tanstack/react-query": ">=5.0.0",
|
|
32
|
-
"
|
|
33
|
-
"react
|
|
32
|
+
"firebase": ">=10.0.0",
|
|
33
|
+
"react": ">=18.0.0",
|
|
34
|
+
"react-dom": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"firebase": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
34
40
|
},
|
|
35
41
|
"devDependencies": {
|
|
36
42
|
"@tanstack/react-query": "^5.0.0",
|
|
37
43
|
"@types/react": "^19.0.0",
|
|
38
44
|
"@types/react-dom": "^19.0.0",
|
|
45
|
+
"firebase": "^11.0.0",
|
|
39
46
|
"react": "^19.2.0",
|
|
40
47
|
"react-dom": "^19.2.0",
|
|
41
48
|
"typescript": "^5.8.3"
|