@umituz/react-native-tanstack 1.0.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 +206 -0
- package/package.json +58 -0
- package/src/domain/constants/CacheDefaults.ts +63 -0
- package/src/domain/types/CacheStrategy.ts +115 -0
- package/src/domain/utils/QueryKeyFactory.ts +134 -0
- package/src/index.ts +88 -0
- package/src/infrastructure/config/PersisterConfig.ts +162 -0
- package/src/infrastructure/config/QueryClientConfig.ts +148 -0
- package/src/infrastructure/providers/TanstackProvider.tsx +142 -0
- package/src/presentation/hooks/useInvalidateQueries.ts +128 -0
- package/src/presentation/hooks/useOptimisticUpdate.ts +126 -0
- package/src/presentation/hooks/usePaginatedQuery.ts +156 -0
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# @umituz/react-native-tanstack
|
|
2
|
+
|
|
3
|
+
TanStack Query configuration and utilities for React Native apps with AsyncStorage persistence.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Pre-configured QueryClient** - Sensible defaults out of the box
|
|
8
|
+
- ✅ **AsyncStorage Persistence** - Automatic cache restoration on app restart
|
|
9
|
+
- ✅ **Cache Strategies** - Pre-defined strategies for different data types
|
|
10
|
+
- ✅ **Query Key Factories** - Type-safe key generation patterns
|
|
11
|
+
- ✅ **Pagination Helpers** - Cursor and offset-based pagination
|
|
12
|
+
- ✅ **Optimistic Updates** - Easy optimistic UI with automatic rollback
|
|
13
|
+
- ✅ **Dev Tools** - Built-in logging for development
|
|
14
|
+
- ✅ **General Purpose** - Works with Firebase, REST, GraphQL, any async data source
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @umituz/react-native-tanstack
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Peer Dependencies
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @tanstack/react-query @react-native-async-storage/async-storage
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Basic Setup
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { TanstackProvider } from '@umituz/react-native-tanstack';
|
|
34
|
+
|
|
35
|
+
function App() {
|
|
36
|
+
return (
|
|
37
|
+
<TanstackProvider>
|
|
38
|
+
<YourApp />
|
|
39
|
+
</TanstackProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Custom Configuration
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { TanstackProvider, TIME_MS } from '@umituz/react-native-tanstack';
|
|
48
|
+
|
|
49
|
+
function App() {
|
|
50
|
+
return (
|
|
51
|
+
<TanstackProvider
|
|
52
|
+
queryClientOptions={{
|
|
53
|
+
defaultStaleTime: 10 * TIME_MS.MINUTE,
|
|
54
|
+
enableDevLogging: __DEV__,
|
|
55
|
+
}}
|
|
56
|
+
persisterOptions={{
|
|
57
|
+
keyPrefix: 'myapp',
|
|
58
|
+
maxAge: 24 * TIME_MS.HOUR,
|
|
59
|
+
busterVersion: '1',
|
|
60
|
+
}}
|
|
61
|
+
onPersistSuccess={() => console.log('Cache restored!')}
|
|
62
|
+
>
|
|
63
|
+
<YourApp />
|
|
64
|
+
</TanstackProvider>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Cache Strategies
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { useQuery, CacheStrategies } from '@umituz/react-native-tanstack';
|
|
73
|
+
|
|
74
|
+
// Real-time data (always refetch)
|
|
75
|
+
const { data: liveScore } = useQuery({
|
|
76
|
+
queryKey: ['score'],
|
|
77
|
+
queryFn: fetchScore,
|
|
78
|
+
...CacheStrategies.REALTIME,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// User data (medium cache)
|
|
82
|
+
const { data: profile } = useQuery({
|
|
83
|
+
queryKey: ['profile'],
|
|
84
|
+
queryFn: fetchProfile,
|
|
85
|
+
...CacheStrategies.USER_DATA,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Master data (long cache)
|
|
89
|
+
const { data: countries } = useQuery({
|
|
90
|
+
queryKey: ['countries'],
|
|
91
|
+
queryFn: fetchCountries,
|
|
92
|
+
...CacheStrategies.MASTER_DATA,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Public data (medium-long cache)
|
|
96
|
+
const { data: posts } = useQuery({
|
|
97
|
+
queryKey: ['posts'],
|
|
98
|
+
queryFn: fetchPosts,
|
|
99
|
+
...CacheStrategies.PUBLIC_DATA,
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Query Key Factories
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { createQueryKeyFactory } from '@umituz/react-native-tanstack';
|
|
107
|
+
|
|
108
|
+
const postKeys = createQueryKeyFactory('posts');
|
|
109
|
+
|
|
110
|
+
// All posts
|
|
111
|
+
postKeys.all(); // ['posts']
|
|
112
|
+
|
|
113
|
+
// Posts list
|
|
114
|
+
postKeys.lists(); // ['posts', 'list']
|
|
115
|
+
|
|
116
|
+
// Posts with filters
|
|
117
|
+
postKeys.list({ status: 'published' }); // ['posts', 'list', { status: 'published' }]
|
|
118
|
+
|
|
119
|
+
// Single post
|
|
120
|
+
postKeys.detail(123); // ['posts', 'detail', 123]
|
|
121
|
+
|
|
122
|
+
// Custom key
|
|
123
|
+
postKeys.custom('trending'); // ['posts', 'trending']
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Pagination
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { useCursorPagination } from '@umituz/react-native-tanstack';
|
|
130
|
+
|
|
131
|
+
function FeedScreen() {
|
|
132
|
+
const { data, flatData, fetchNextPage, hasNextPage, isFetchingNextPage } = useCursorPagination({
|
|
133
|
+
queryKey: ['feed'],
|
|
134
|
+
queryFn: ({ pageParam }) => fetchFeed({ cursor: pageParam, limit: 20 }),
|
|
135
|
+
limit: 20,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<FlatList
|
|
140
|
+
data={flatData}
|
|
141
|
+
onEndReached={() => hasNextPage && fetchNextPage()}
|
|
142
|
+
onEndReachedThreshold={0.5}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Cache Invalidation
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { useInvalidateQueries } from '@umituz/react-native-tanstack';
|
|
152
|
+
|
|
153
|
+
function ShareButton() {
|
|
154
|
+
const invalidate = useInvalidateQueries();
|
|
155
|
+
|
|
156
|
+
const handleShare = async () => {
|
|
157
|
+
await shareToFeed(post);
|
|
158
|
+
|
|
159
|
+
// Invalidate feed queries
|
|
160
|
+
await invalidate(['feed']);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return <Button onPress={handleShare}>Share</Button>;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Optimistic Updates
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { useOptimisticUpdate } from '@umituz/react-native-tanstack';
|
|
171
|
+
|
|
172
|
+
function LikeButton({ postId }) {
|
|
173
|
+
const updateLike = useOptimisticUpdate<Post, { liked: boolean }>({
|
|
174
|
+
mutationFn: (variables) => api.updatePost(postId, variables),
|
|
175
|
+
queryKey: ['posts', 'detail', postId],
|
|
176
|
+
updater: (oldPost, variables) => ({
|
|
177
|
+
...oldPost,
|
|
178
|
+
liked: variables.liked,
|
|
179
|
+
likeCount: oldPost.likeCount + (variables.liked ? 1 : -1),
|
|
180
|
+
}),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const handleLike = () => {
|
|
184
|
+
updateLike.mutate({ liked: true });
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return <Button onPress={handleLike}>Like</Button>;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Cache Strategies
|
|
192
|
+
|
|
193
|
+
| Strategy | staleTime | gcTime | Use Case |
|
|
194
|
+
|----------|-----------|--------|----------|
|
|
195
|
+
| REALTIME | 0 | 5 min | Live data (chat, scores) |
|
|
196
|
+
| USER_DATA | 30 min | 24 hours | User profile, settings |
|
|
197
|
+
| MASTER_DATA | 24 hours | 7 days | Countries, categories |
|
|
198
|
+
| PUBLIC_DATA | 30 min | 24 hours | Feed, blog posts |
|
|
199
|
+
|
|
200
|
+
## API Reference
|
|
201
|
+
|
|
202
|
+
See [TypeScript definitions](./src/index.ts) for complete API documentation.
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT © Ümit UZ
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@umituz/react-native-tanstack",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TanStack Query configuration and utilities for React Native apps - Pre-configured QueryClient with AsyncStorage persistence",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"lint": "tsc --noEmit",
|
|
10
|
+
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
11
|
+
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
12
|
+
"version:major": "npm version major -m 'chore: release v%s'"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react-native",
|
|
16
|
+
"tanstack-query",
|
|
17
|
+
"react-query",
|
|
18
|
+
"cache",
|
|
19
|
+
"async-storage",
|
|
20
|
+
"persistence",
|
|
21
|
+
"query-client",
|
|
22
|
+
"data-fetching",
|
|
23
|
+
"server-state"
|
|
24
|
+
],
|
|
25
|
+
"author": "Ümit UZ <umit@umituz.com>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/umituz/react-native-tanstack.git"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@react-native-async-storage/async-storage": ">=1.21.0",
|
|
33
|
+
"@tanstack/react-query": "^5.0.0",
|
|
34
|
+
"react": ">=18.2.0",
|
|
35
|
+
"react-native": ">=0.74.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@tanstack/query-async-storage-persister": "^5.62.0",
|
|
39
|
+
"@tanstack/react-query-persist-client": "^5.62.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@react-native-async-storage/async-storage": "^1.24.0",
|
|
43
|
+
"@tanstack/react-query": "^5.62.11",
|
|
44
|
+
"@types/react": "^18.2.45",
|
|
45
|
+
"@types/react-native": "^0.73.0",
|
|
46
|
+
"react": "^18.2.0",
|
|
47
|
+
"react-native": "^0.74.0",
|
|
48
|
+
"typescript": "^5.6.0"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"src",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Time Constants
|
|
3
|
+
* Domain layer - Time constants for cache management
|
|
4
|
+
*
|
|
5
|
+
* General-purpose time utilities for any React Native app
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Milliseconds constants for time calculations
|
|
10
|
+
*/
|
|
11
|
+
export const TIME_MS = {
|
|
12
|
+
SECOND: 1000,
|
|
13
|
+
MINUTE: 60 * 1000,
|
|
14
|
+
HOUR: 60 * 60 * 1000,
|
|
15
|
+
DAY: 24 * 60 * 60 * 1000,
|
|
16
|
+
WEEK: 7 * 24 * 60 * 60 * 1000,
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default staleTime values for different cache strategies
|
|
21
|
+
* staleTime = how long data is considered fresh
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_STALE_TIME = {
|
|
24
|
+
REALTIME: 0, // Always stale (refetch immediately)
|
|
25
|
+
VERY_SHORT: TIME_MS.MINUTE, // 1 minute
|
|
26
|
+
SHORT: 5 * TIME_MS.MINUTE, // 5 minutes
|
|
27
|
+
MEDIUM: 30 * TIME_MS.MINUTE, // 30 minutes
|
|
28
|
+
LONG: 2 * TIME_MS.HOUR, // 2 hours
|
|
29
|
+
VERY_LONG: TIME_MS.DAY, // 24 hours
|
|
30
|
+
PERMANENT: Infinity, // Never stale
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default gcTime (garbage collection) values
|
|
35
|
+
* gcTime = how long unused data stays in cache before being garbage collected
|
|
36
|
+
*/
|
|
37
|
+
export const DEFAULT_GC_TIME = {
|
|
38
|
+
VERY_SHORT: 5 * TIME_MS.MINUTE, // 5 minutes
|
|
39
|
+
SHORT: 30 * TIME_MS.MINUTE, // 30 minutes
|
|
40
|
+
MEDIUM: 2 * TIME_MS.HOUR, // 2 hours
|
|
41
|
+
LONG: TIME_MS.DAY, // 24 hours
|
|
42
|
+
VERY_LONG: 7 * TIME_MS.DAY, // 7 days
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Default retry configuration
|
|
47
|
+
*/
|
|
48
|
+
export const DEFAULT_RETRY = {
|
|
49
|
+
NONE: false,
|
|
50
|
+
MINIMAL: 1,
|
|
51
|
+
STANDARD: 3,
|
|
52
|
+
AGGRESSIVE: 5,
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default refetch intervals
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_REFETCH_INTERVAL = {
|
|
59
|
+
REALTIME: 10 * TIME_MS.SECOND, // 10 seconds
|
|
60
|
+
FAST: 30 * TIME_MS.SECOND, // 30 seconds
|
|
61
|
+
MODERATE: TIME_MS.MINUTE, // 1 minute
|
|
62
|
+
SLOW: 5 * TIME_MS.MINUTE, // 5 minutes
|
|
63
|
+
} as const;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Strategy Types
|
|
3
|
+
* Domain layer - Cache configuration types
|
|
4
|
+
*
|
|
5
|
+
* General-purpose cache strategies for any React Native app
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { UseQueryOptions } from '@tanstack/react-query';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cache configuration for TanStack Query
|
|
12
|
+
*/
|
|
13
|
+
export interface CacheConfig {
|
|
14
|
+
/**
|
|
15
|
+
* Time in ms data is considered fresh (no refetch)
|
|
16
|
+
*/
|
|
17
|
+
staleTime: number;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Time in ms inactive data stays in cache before garbage collection
|
|
21
|
+
*/
|
|
22
|
+
gcTime: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether to refetch when component mounts
|
|
26
|
+
*/
|
|
27
|
+
refetchOnMount?: boolean | 'always';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether to refetch when window regains focus
|
|
31
|
+
*/
|
|
32
|
+
refetchOnWindowFocus?: boolean | 'always';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether to refetch when network reconnects
|
|
36
|
+
*/
|
|
37
|
+
refetchOnReconnect?: boolean | 'always';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Number of retry attempts on failure
|
|
41
|
+
*/
|
|
42
|
+
retry?: boolean | number;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Interval for automatic background refetching (in ms)
|
|
46
|
+
* Set to false to disable
|
|
47
|
+
*/
|
|
48
|
+
refetchInterval?: number | false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Cache strategy enum for different data types
|
|
53
|
+
*/
|
|
54
|
+
export enum CacheStrategyType {
|
|
55
|
+
/**
|
|
56
|
+
* Real-time data that changes frequently
|
|
57
|
+
* Example: Live chat, stock prices, sports scores
|
|
58
|
+
*/
|
|
59
|
+
REALTIME = 'REALTIME',
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* User-specific data
|
|
63
|
+
* Example: User profile, settings, preferences
|
|
64
|
+
*/
|
|
65
|
+
USER_DATA = 'USER_DATA',
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Master data that rarely changes
|
|
69
|
+
* Example: Countries list, categories, app configuration
|
|
70
|
+
*/
|
|
71
|
+
MASTER_DATA = 'MASTER_DATA',
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Public read-heavy data
|
|
75
|
+
* Example: Blog posts, product catalog, news feed
|
|
76
|
+
*/
|
|
77
|
+
PUBLIC_DATA = 'PUBLIC_DATA',
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Custom strategy (user-defined)
|
|
81
|
+
*/
|
|
82
|
+
CUSTOM = 'CUSTOM',
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Query options type (generic, works with any data)
|
|
87
|
+
*/
|
|
88
|
+
export type QueryConfig<TData = unknown, TError = Error> = Partial<
|
|
89
|
+
UseQueryOptions<TData, TError>
|
|
90
|
+
>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Mutation options type (generic, works with any data)
|
|
94
|
+
*/
|
|
95
|
+
export interface MutationConfig<TData = unknown, TError = Error, TVariables = unknown> {
|
|
96
|
+
/**
|
|
97
|
+
* Function to call on mutation success
|
|
98
|
+
*/
|
|
99
|
+
onSuccess?: (data: TData, variables: TVariables) => void | Promise<void>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Function to call on mutation error
|
|
103
|
+
*/
|
|
104
|
+
onError?: (error: TError, variables: TVariables) => void | Promise<void>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Function to call on mutation settled (success or error)
|
|
108
|
+
*/
|
|
109
|
+
onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables) => void | Promise<void>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Number of retry attempts
|
|
113
|
+
*/
|
|
114
|
+
retry?: boolean | number;
|
|
115
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Key Factory
|
|
3
|
+
* Domain layer - Query key generation utilities
|
|
4
|
+
*
|
|
5
|
+
* General-purpose query key patterns for any React Native app
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a typed query key factory for a resource
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const postKeys = createQueryKeyFactory('posts');
|
|
14
|
+
*
|
|
15
|
+
* // All posts
|
|
16
|
+
* postKeys.all() // ['posts']
|
|
17
|
+
*
|
|
18
|
+
* // Posts list
|
|
19
|
+
* postKeys.lists() // ['posts', 'list']
|
|
20
|
+
*
|
|
21
|
+
* // Posts list with filters
|
|
22
|
+
* postKeys.list({ status: 'published' }) // ['posts', 'list', { status: 'published' }]
|
|
23
|
+
*
|
|
24
|
+
* // Single post detail
|
|
25
|
+
* postKeys.detail(123) // ['posts', 'detail', 123]
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function createQueryKeyFactory(resource: string) {
|
|
29
|
+
return {
|
|
30
|
+
/**
|
|
31
|
+
* All queries for this resource
|
|
32
|
+
*/
|
|
33
|
+
all: () => [resource] as const,
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* All list queries for this resource
|
|
37
|
+
*/
|
|
38
|
+
lists: () => [resource, 'list'] as const,
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* List query with optional filters
|
|
42
|
+
*/
|
|
43
|
+
list: (filters?: Record<string, unknown>) =>
|
|
44
|
+
filters ? ([resource, 'list', filters] as const) : ([resource, 'list'] as const),
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* All detail queries for this resource
|
|
48
|
+
*/
|
|
49
|
+
details: () => [resource, 'detail'] as const,
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Detail query for specific item
|
|
53
|
+
*/
|
|
54
|
+
detail: (id: string | number) => [resource, 'detail', id] as const,
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Custom query key
|
|
58
|
+
*/
|
|
59
|
+
custom: (...args: unknown[]) => [resource, ...args] as const,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create a query key for a list with pagination
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* createPaginatedQueryKey('posts', { page: 1, limit: 10 })
|
|
69
|
+
* // ['posts', 'list', { page: 1, limit: 10 }]
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function createPaginatedQueryKey(
|
|
73
|
+
resource: string,
|
|
74
|
+
pagination: { page?: number; limit?: number; cursor?: string },
|
|
75
|
+
): readonly [string, 'list', typeof pagination] {
|
|
76
|
+
return [resource, 'list', pagination] as const;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create a query key for infinite scroll
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* createInfiniteQueryKey('feed', { limit: 20 })
|
|
85
|
+
* // ['feed', 'infinite', { limit: 20 }]
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function createInfiniteQueryKey(
|
|
89
|
+
resource: string,
|
|
90
|
+
params?: Record<string, unknown>,
|
|
91
|
+
): readonly [string, 'infinite', Record<string, unknown>] | readonly [string, 'infinite'] {
|
|
92
|
+
return params ? ([resource, 'infinite', params] as const) : ([resource, 'infinite'] as const);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a scoped query key (for user-specific data)
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* createScopedQueryKey('user123', 'posts')
|
|
101
|
+
* // ['user', 'user123', 'posts']
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function createScopedQueryKey(
|
|
105
|
+
scopeId: string,
|
|
106
|
+
resource: string,
|
|
107
|
+
...args: unknown[]
|
|
108
|
+
): readonly [string, string, string, ...unknown[]] {
|
|
109
|
+
return ['user', scopeId, resource, ...args] as const;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Match query keys by pattern
|
|
114
|
+
* Useful for invalidating multiple related queries
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* // Invalidate all post queries
|
|
119
|
+
* queryClient.invalidateQueries({
|
|
120
|
+
* predicate: (query) => matchQueryKey(query.queryKey, ['posts'])
|
|
121
|
+
* })
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function matchQueryKey(
|
|
125
|
+
queryKey: readonly unknown[],
|
|
126
|
+
pattern: readonly unknown[],
|
|
127
|
+
): boolean {
|
|
128
|
+
if (pattern.length > queryKey.length) return false;
|
|
129
|
+
|
|
130
|
+
return pattern.every((value, index) => {
|
|
131
|
+
if (value === undefined) return true;
|
|
132
|
+
return queryKey[index] === value;
|
|
133
|
+
});
|
|
134
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-tanstack
|
|
3
|
+
* TanStack Query configuration and utilities for React Native apps
|
|
4
|
+
*
|
|
5
|
+
* General-purpose package for hundreds of React Native apps
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Domain - Constants
|
|
9
|
+
export {
|
|
10
|
+
TIME_MS,
|
|
11
|
+
DEFAULT_STALE_TIME,
|
|
12
|
+
DEFAULT_GC_TIME,
|
|
13
|
+
DEFAULT_RETRY,
|
|
14
|
+
DEFAULT_REFETCH_INTERVAL,
|
|
15
|
+
} from './domain/constants/CacheDefaults';
|
|
16
|
+
|
|
17
|
+
// Domain - Types
|
|
18
|
+
export {
|
|
19
|
+
CacheStrategyType,
|
|
20
|
+
type CacheConfig,
|
|
21
|
+
type QueryConfig,
|
|
22
|
+
type MutationConfig,
|
|
23
|
+
} from './domain/types/CacheStrategy';
|
|
24
|
+
|
|
25
|
+
// Domain - Utils
|
|
26
|
+
export {
|
|
27
|
+
createQueryKeyFactory,
|
|
28
|
+
createPaginatedQueryKey,
|
|
29
|
+
createInfiniteQueryKey,
|
|
30
|
+
createScopedQueryKey,
|
|
31
|
+
matchQueryKey,
|
|
32
|
+
} from './domain/utils/QueryKeyFactory';
|
|
33
|
+
|
|
34
|
+
// Infrastructure - Config
|
|
35
|
+
export {
|
|
36
|
+
CacheStrategies,
|
|
37
|
+
createQueryClient,
|
|
38
|
+
getCacheStrategy,
|
|
39
|
+
type QueryClientFactoryOptions,
|
|
40
|
+
} from './infrastructure/config/QueryClientConfig';
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
createPersister,
|
|
44
|
+
clearPersistedCache,
|
|
45
|
+
getPersistedCacheSize,
|
|
46
|
+
type PersisterFactoryOptions,
|
|
47
|
+
} from './infrastructure/config/PersisterConfig';
|
|
48
|
+
|
|
49
|
+
// Infrastructure - Providers
|
|
50
|
+
export { TanstackProvider, type TanstackProviderProps } from './infrastructure/providers/TanstackProvider';
|
|
51
|
+
|
|
52
|
+
// Presentation - Hooks
|
|
53
|
+
export {
|
|
54
|
+
useInvalidateQueries,
|
|
55
|
+
useInvalidateMultipleQueries,
|
|
56
|
+
useRemoveQueries,
|
|
57
|
+
useResetQueries,
|
|
58
|
+
} from './presentation/hooks/useInvalidateQueries';
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
useCursorPagination,
|
|
62
|
+
useOffsetPagination,
|
|
63
|
+
type CursorPageParam,
|
|
64
|
+
type OffsetPageParam,
|
|
65
|
+
type CursorPaginatedResponse,
|
|
66
|
+
type OffsetPaginatedResponse,
|
|
67
|
+
} from './presentation/hooks/usePaginatedQuery';
|
|
68
|
+
|
|
69
|
+
export {
|
|
70
|
+
useOptimisticUpdate,
|
|
71
|
+
useOptimisticListUpdate,
|
|
72
|
+
type OptimisticUpdateConfig,
|
|
73
|
+
} from './presentation/hooks/useOptimisticUpdate';
|
|
74
|
+
|
|
75
|
+
// Re-export TanStack Query core for convenience
|
|
76
|
+
export {
|
|
77
|
+
useQuery,
|
|
78
|
+
useMutation,
|
|
79
|
+
useInfiniteQuery,
|
|
80
|
+
useQueryClient,
|
|
81
|
+
useIsFetching,
|
|
82
|
+
useIsMutating,
|
|
83
|
+
type UseQueryResult,
|
|
84
|
+
type UseMutationResult,
|
|
85
|
+
type UseInfiniteQueryResult,
|
|
86
|
+
type QueryKey,
|
|
87
|
+
type QueryClient,
|
|
88
|
+
} from '@tanstack/react-query';
|