@umituz/react-native-tanstack 1.2.17 → 1.2.18
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 +143 -2
- package/package.json +1 -1
- package/src/domain/repositories/BaseRepository.ts +280 -0
- package/src/domain/repositories/RepositoryFactory.ts +135 -0
- package/src/domain/utils/ErrorHelpers.ts +154 -0
- package/src/domain/utils/TypeUtilities.ts +153 -0
- package/src/index.ts +57 -0
- package/src/infrastructure/monitoring/DevMonitor.ts +274 -0
- package/src/presentation/hooks/usePrefetch.ts +237 -0
package/README.md
CHANGED
|
@@ -5,12 +5,16 @@ TanStack Query configuration and utilities for React Native apps with AsyncStora
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- ✅ **Pre-configured QueryClient** - Sensible defaults out of the box
|
|
8
|
-
- ✅ **AsyncStorage Persistence** - Automatic cache restoration on app restart
|
|
8
|
+
- ✅ **AsyncStorage Persistence** - Automatic cache restoration on app restart via @umituz/react-native-storage
|
|
9
9
|
- ✅ **Cache Strategies** - Pre-defined strategies for different data types
|
|
10
10
|
- ✅ **Query Key Factories** - Type-safe key generation patterns
|
|
11
11
|
- ✅ **Pagination Helpers** - Cursor and offset-based pagination
|
|
12
12
|
- ✅ **Optimistic Updates** - Easy optimistic UI with automatic rollback
|
|
13
|
-
- ✅ **
|
|
13
|
+
- ✅ **Prefetch Hooks** - Data preloading for better UX
|
|
14
|
+
- ✅ **Repository Pattern** - Base repository for CRUD operations
|
|
15
|
+
- ✅ **Error Helpers** - User-friendly error messages and error parsing
|
|
16
|
+
- ✅ **Type Utilities** - Type extractors for better type safety
|
|
17
|
+
- ✅ **Dev Monitor** - Query performance tracking (DEV only)
|
|
14
18
|
- ✅ **General Purpose** - Works with Firebase, REST, GraphQL, any async data source
|
|
15
19
|
|
|
16
20
|
## Installation
|
|
@@ -201,6 +205,143 @@ function LikeButton({ postId }) {
|
|
|
201
205
|
|
|
202
206
|
See [TypeScript definitions](./src/index.ts) for complete API documentation.
|
|
203
207
|
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Repository Pattern
|
|
211
|
+
|
|
212
|
+
Create type-safe repositories for your data:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { BaseRepository, RepositoryFactory } from '@umituz/react-native-tanstack';
|
|
216
|
+
|
|
217
|
+
interface User {
|
|
218
|
+
id: string;
|
|
219
|
+
name: string;
|
|
220
|
+
email: string;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
class UserRepository extends BaseRepository<User, CreateUserVars, UpdateUserVars> {
|
|
224
|
+
constructor() {
|
|
225
|
+
super('users', {
|
|
226
|
+
cacheStrategy: CacheStrategies.USER_DATA,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async fetchAll(params?: ListParams): Promise<User[]> {
|
|
231
|
+
const res = await fetch('/api/users');
|
|
232
|
+
return res.json();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async fetchById(id: string): Promise<User> {
|
|
236
|
+
const res = await fetch(`/api/users/${id}`);
|
|
237
|
+
return res.json();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async create({ variables }: CreateParams<CreateUserVars>): Promise<User> {
|
|
241
|
+
const res = await fetch('/api/users', {
|
|
242
|
+
method: 'POST',
|
|
243
|
+
body: JSON.stringify(variables),
|
|
244
|
+
});
|
|
245
|
+
return res.json();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async update({ id, variables }: UpdateParams<UpdateUserVars>): Promise<User> {
|
|
249
|
+
const res = await fetch(`/api/users/${id}`, {
|
|
250
|
+
method: 'PUT',
|
|
251
|
+
body: JSON.stringify(variables),
|
|
252
|
+
});
|
|
253
|
+
return res.json();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async remove(id: string): Promise<void> {
|
|
257
|
+
await fetch(`/api/users/${id}`, { method: 'DELETE' });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Register repository
|
|
262
|
+
const userRepo = new UserRepository();
|
|
263
|
+
RepositoryFactory.register('users', userRepo);
|
|
264
|
+
|
|
265
|
+
// Use repository
|
|
266
|
+
const users = await userRepo.queryAll();
|
|
267
|
+
await userRepo.invalidateAll();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Prefetching
|
|
273
|
+
|
|
274
|
+
Preload data before navigation:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { usePrefetchQuery } from '@umituz/react-native-tanstack';
|
|
278
|
+
|
|
279
|
+
function UserList({ userIds }: { userIds: string[] }) {
|
|
280
|
+
const prefetchUser = usePrefetchQuery(['user'], async (id: string) => {
|
|
281
|
+
const res = await fetch(`/api/users/${id}`);
|
|
282
|
+
return res.json();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const handlePress = (userId: string) => {
|
|
286
|
+
// Prefetch before navigation
|
|
287
|
+
prefetchUser(userId);
|
|
288
|
+
navigation.navigate('UserDetail', { userId });
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return <FlatList data={userIds} renderItem={({ item }) => (
|
|
292
|
+
<TouchableOpacity onPress={() => handlePress(item)}>
|
|
293
|
+
<Text>User {item}</Text>
|
|
294
|
+
</TouchableOpacity>
|
|
295
|
+
)} />;
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Error Handling
|
|
302
|
+
|
|
303
|
+
Get user-friendly error messages:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { useMutation, getUserFriendlyMessage } from '@umituz/react-native-tanstack';
|
|
307
|
+
|
|
308
|
+
function CreateUserForm() {
|
|
309
|
+
const mutation = useMutation({
|
|
310
|
+
mutationFn: (data) => api.post('/users', data),
|
|
311
|
+
onError: (error) => {
|
|
312
|
+
const message = getUserFriendlyMessage(error);
|
|
313
|
+
showToast(message);
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return <Button onPress={() => mutation.mutate(formData)}>Create</Button>;
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Dev Monitor
|
|
324
|
+
|
|
325
|
+
Track query performance in development:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import { DevMonitor } from '@umituz/react-native-tanstack';
|
|
329
|
+
|
|
330
|
+
// In your app setup
|
|
331
|
+
if (__DEV__) {
|
|
332
|
+
DevMonitor.attach(queryClient);
|
|
333
|
+
|
|
334
|
+
// Log performance report
|
|
335
|
+
DevMonitor.logReport();
|
|
336
|
+
|
|
337
|
+
// Get slow queries
|
|
338
|
+
const slowQueries = DevMonitor.getSlowQueries();
|
|
339
|
+
|
|
340
|
+
// Get cache stats
|
|
341
|
+
const stats = DevMonitor.getCacheStats();
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
204
345
|
## License
|
|
205
346
|
|
|
206
347
|
MIT © Ümit UZ
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-tanstack",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.18",
|
|
4
4
|
"description": "TanStack Query configuration and utilities for React Native apps - Pre-configured QueryClient with AsyncStorage persistence",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Repository
|
|
3
|
+
* Domain layer - Abstract repository for data operations
|
|
4
|
+
*
|
|
5
|
+
* Provides generic CRUD operations with TanStack Query integration.
|
|
6
|
+
* Subclass this for specific entities to get type-safe data operations.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* class UserRepository extends BaseRepository<User, CreateUserVars, UpdateUserVars> {
|
|
11
|
+
* constructor() {
|
|
12
|
+
* super('users');
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* async fetchAll(): Promise<User[]> {
|
|
16
|
+
* return api.get('/users');
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* async fetchById(id: string): Promise<User> {
|
|
20
|
+
* return api.get(`/users/${id}`);
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* async create(data: CreateUserVars): Promise<User> {
|
|
24
|
+
* return api.post('/users', data);
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* async update(id: string, data: UpdateUserVars): Promise<User> {
|
|
28
|
+
* return api.put(`/users/${id}`, data);
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* async remove(id: string): Promise<void> {
|
|
32
|
+
* return api.delete(`/users/${id}`);
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
39
|
+
import { getGlobalQueryClient } from '../../infrastructure/config/QueryClientSingleton';
|
|
40
|
+
import type { CacheConfig } from '../types/CacheStrategy';
|
|
41
|
+
import { CacheStrategies } from '../../infrastructure/config/QueryClientConfig';
|
|
42
|
+
|
|
43
|
+
export interface CreateParams<TVariables> {
|
|
44
|
+
variables: TVariables;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface UpdateParams<TVariables> {
|
|
48
|
+
id: string | number;
|
|
49
|
+
variables: TVariables;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ListParams {
|
|
53
|
+
page?: number;
|
|
54
|
+
limit?: number;
|
|
55
|
+
filter?: Record<string, unknown>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface RepositoryOptions {
|
|
59
|
+
/**
|
|
60
|
+
* Cache strategy for queries
|
|
61
|
+
* @default CacheStrategies.PUBLIC_DATA
|
|
62
|
+
*/
|
|
63
|
+
cacheStrategy?: CacheConfig;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Stale time override
|
|
67
|
+
*/
|
|
68
|
+
staleTime?: number;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* GC time override
|
|
72
|
+
*/
|
|
73
|
+
gcTime?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Base repository for CRUD operations
|
|
78
|
+
*
|
|
79
|
+
* @template TData - Entity type
|
|
80
|
+
* @template TCreateVariables - Variables for create mutation
|
|
81
|
+
* @template TUpdateVariables - Variables for update mutation
|
|
82
|
+
*/
|
|
83
|
+
export abstract class BaseRepository<
|
|
84
|
+
TData,
|
|
85
|
+
TCreateVariables = unknown,
|
|
86
|
+
TUpdateVariables = Partial<TData>,
|
|
87
|
+
> {
|
|
88
|
+
protected readonly resource: string;
|
|
89
|
+
protected readonly options: RepositoryOptions;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Query key factory for this repository
|
|
93
|
+
*/
|
|
94
|
+
public readonly keys: {
|
|
95
|
+
all: () => readonly [string];
|
|
96
|
+
lists: () => readonly [string, 'list'];
|
|
97
|
+
list: (params: ListParams) => readonly [string, 'list', ListParams];
|
|
98
|
+
details: () => readonly [string, 'detail'];
|
|
99
|
+
detail: (id: string | number) => readonly [string, 'detail', string | number];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
constructor(resource: string, options: RepositoryOptions = {}) {
|
|
103
|
+
this.resource = resource;
|
|
104
|
+
this.options = {
|
|
105
|
+
cacheStrategy: options.cacheStrategy ?? CacheStrategies.PUBLIC_DATA,
|
|
106
|
+
...options,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
this.keys = {
|
|
110
|
+
all: () => [this.resource] as const,
|
|
111
|
+
lists: () => [this.resource, 'list'] as const,
|
|
112
|
+
list: (params: ListParams) => [this.resource, 'list', params] as const,
|
|
113
|
+
details: () => [this.resource, 'detail'] as const,
|
|
114
|
+
detail: (id: string | number) => [this.resource, 'detail', id] as const,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get query client instance
|
|
120
|
+
*/
|
|
121
|
+
protected getClient(): QueryClient {
|
|
122
|
+
return getGlobalQueryClient();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get cache options for queries
|
|
127
|
+
*/
|
|
128
|
+
protected getCacheOptions(): { staleTime: number; gcTime: number } {
|
|
129
|
+
return {
|
|
130
|
+
staleTime: this.options.staleTime ?? (this.options.cacheStrategy?.staleTime ?? CacheStrategies.PUBLIC_DATA.staleTime),
|
|
131
|
+
gcTime: this.options.gcTime ?? (this.options.cacheStrategy?.gcTime ?? CacheStrategies.PUBLIC_DATA.gcTime),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Fetch all items - to be implemented by subclass
|
|
137
|
+
*/
|
|
138
|
+
abstract fetchAll(params?: ListParams): Promise<TData[]>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Fetch item by ID - to be implemented by subclass
|
|
142
|
+
*/
|
|
143
|
+
abstract fetchById(id: string | number): Promise<TData>;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create item - to be implemented by subclass
|
|
147
|
+
*/
|
|
148
|
+
abstract create(params: CreateParams<TCreateVariables>): Promise<TData>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Update item - to be implemented by subclass
|
|
152
|
+
*/
|
|
153
|
+
abstract update(params: UpdateParams<TUpdateVariables>): Promise<TData>;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Delete item - to be implemented by subclass
|
|
157
|
+
*/
|
|
158
|
+
abstract remove(id: string | number): Promise<void>;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Query all items with caching
|
|
162
|
+
*/
|
|
163
|
+
async queryAll(params?: ListParams): Promise<TData[]> {
|
|
164
|
+
const client = this.getClient();
|
|
165
|
+
const queryKey = params ? this.keys.list(params) : this.keys.lists();
|
|
166
|
+
const cacheOptions = this.getCacheOptions();
|
|
167
|
+
|
|
168
|
+
return client.fetchQuery({
|
|
169
|
+
queryKey,
|
|
170
|
+
queryFn: () => this.fetchAll(params),
|
|
171
|
+
...cacheOptions,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Query item by ID with caching
|
|
177
|
+
*/
|
|
178
|
+
async queryById(id: string | number): Promise<TData | undefined> {
|
|
179
|
+
const client = this.getClient();
|
|
180
|
+
const queryKey = this.keys.detail(id);
|
|
181
|
+
const cacheOptions = this.getCacheOptions();
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
return client.fetchQuery({
|
|
185
|
+
queryKey,
|
|
186
|
+
queryFn: () => this.fetchById(id),
|
|
187
|
+
...cacheOptions,
|
|
188
|
+
});
|
|
189
|
+
} catch {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Prefetch all items
|
|
196
|
+
*/
|
|
197
|
+
async prefetchAll(params?: ListParams): Promise<void> {
|
|
198
|
+
const client = this.getClient();
|
|
199
|
+
const queryKey = params ? this.keys.list(params) : this.keys.lists();
|
|
200
|
+
const cacheOptions = this.getCacheOptions();
|
|
201
|
+
|
|
202
|
+
await client.prefetchQuery({
|
|
203
|
+
queryKey,
|
|
204
|
+
queryFn: () => this.fetchAll(params),
|
|
205
|
+
...cacheOptions,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Prefetch item by ID
|
|
211
|
+
*/
|
|
212
|
+
async prefetchById(id: string | number): Promise<void> {
|
|
213
|
+
const client = this.getClient();
|
|
214
|
+
const queryKey = this.keys.detail(id);
|
|
215
|
+
const cacheOptions = this.getCacheOptions();
|
|
216
|
+
|
|
217
|
+
await client.prefetchQuery({
|
|
218
|
+
queryKey,
|
|
219
|
+
queryFn: () => this.fetchById(id),
|
|
220
|
+
...cacheOptions,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Invalidate all queries for this resource
|
|
226
|
+
*/
|
|
227
|
+
invalidateAll(): Promise<void> {
|
|
228
|
+
const client = this.getClient();
|
|
229
|
+
return client.invalidateQueries({
|
|
230
|
+
predicate: (query) => {
|
|
231
|
+
const key = query.queryKey[0] as string;
|
|
232
|
+
return key === this.resource;
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Invalidate list queries
|
|
239
|
+
*/
|
|
240
|
+
invalidateLists(): Promise<void> {
|
|
241
|
+
const client = this.getClient();
|
|
242
|
+
return client.invalidateQueries({
|
|
243
|
+
queryKey: this.keys.lists(),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Invalidate detail query
|
|
249
|
+
*/
|
|
250
|
+
invalidateDetail(id: string | number): Promise<void> {
|
|
251
|
+
const client = this.getClient();
|
|
252
|
+
return client.invalidateQueries({
|
|
253
|
+
queryKey: this.keys.detail(id),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Set query data (optimistic update)
|
|
259
|
+
*/
|
|
260
|
+
setData(id: string | number, data: TData): void {
|
|
261
|
+
const client = this.getClient();
|
|
262
|
+
client.setQueryData(this.keys.detail(id), data);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get query data from cache
|
|
267
|
+
*/
|
|
268
|
+
getData(id: string | number): TData | undefined {
|
|
269
|
+
const client = this.getClient();
|
|
270
|
+
return client.getQueryData<TData>(this.keys.detail(id));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Remove query data from cache
|
|
275
|
+
*/
|
|
276
|
+
clearData(id: string | number): void {
|
|
277
|
+
const client = this.getClient();
|
|
278
|
+
client.setQueryData(this.keys.detail(id), undefined);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Factory
|
|
3
|
+
* Domain layer - Factory for creating repository instances
|
|
4
|
+
*
|
|
5
|
+
* Provides singleton instances of repositories to avoid multiple instances.
|
|
6
|
+
* Repositories are registered once and reused throughout the app.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Register repositories
|
|
11
|
+
* RepositoryFactory.register('users', new UserRepository());
|
|
12
|
+
* RepositoryFactory.register('posts', new PostRepository());
|
|
13
|
+
*
|
|
14
|
+
* // Get repository
|
|
15
|
+
* const userRepo = RepositoryFactory.get<UserRepository>('users');
|
|
16
|
+
* const users = await userRepo.queryAll();
|
|
17
|
+
*
|
|
18
|
+
* // Get all registered keys
|
|
19
|
+
* const keys = RepositoryFactory.keys(); // ['users', 'posts']
|
|
20
|
+
*
|
|
21
|
+
* // Invalidate all repositories
|
|
22
|
+
* RepositoryFactory.invalidateAll();
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { BaseRepository } from './BaseRepository';
|
|
27
|
+
import { getGlobalQueryClient } from '../../infrastructure/config/QueryClientSingleton';
|
|
28
|
+
|
|
29
|
+
type RepositoryMap = Map<string, BaseRepository<unknown, unknown, unknown>>;
|
|
30
|
+
|
|
31
|
+
class RepositoryFactoryClass {
|
|
32
|
+
private repositories: RepositoryMap = new Map();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register a repository instance
|
|
36
|
+
*
|
|
37
|
+
* @param key - Unique identifier for the repository
|
|
38
|
+
* @param repository - Repository instance
|
|
39
|
+
*/
|
|
40
|
+
register<TRepository extends BaseRepository<unknown, unknown, unknown>>(
|
|
41
|
+
key: string,
|
|
42
|
+
repository: TRepository,
|
|
43
|
+
): void {
|
|
44
|
+
if (this.repositories.has(key)) {
|
|
45
|
+
if (__DEV__) {
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.warn(
|
|
48
|
+
`[RepositoryFactory] Repository "${key}" is already registered. Overwriting.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.repositories.set(key, repository);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get a registered repository instance
|
|
57
|
+
*
|
|
58
|
+
* @param key - Repository identifier
|
|
59
|
+
* @throws Error if repository not found
|
|
60
|
+
*/
|
|
61
|
+
get<TRepository extends BaseRepository<unknown, unknown, unknown>>(
|
|
62
|
+
key: string,
|
|
63
|
+
): TRepository {
|
|
64
|
+
const repository = this.repositories.get(key);
|
|
65
|
+
if (!repository) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`[RepositoryFactory] Repository "${key}" not found. Make sure to register it first.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return repository as TRepository;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if repository is registered
|
|
75
|
+
*/
|
|
76
|
+
has(key: string): boolean {
|
|
77
|
+
return this.repositories.has(key);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Unregister a repository
|
|
82
|
+
*/
|
|
83
|
+
unregister(key: string): boolean {
|
|
84
|
+
if (__DEV__ && !this.repositories.has(key)) {
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.warn(`[RepositoryFactory] Repository "${key}" is not registered.`);
|
|
87
|
+
}
|
|
88
|
+
return this.repositories.delete(key);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get all registered repository keys
|
|
93
|
+
*/
|
|
94
|
+
keys(): string[] {
|
|
95
|
+
return Array.from(this.repositories.keys());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Clear all registered repositories
|
|
100
|
+
* Useful for testing or cleanup
|
|
101
|
+
*/
|
|
102
|
+
clear(): void {
|
|
103
|
+
this.repositories.clear();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Invalidate all queries from all registered repositories
|
|
108
|
+
*/
|
|
109
|
+
async invalidateAll(): Promise<void> {
|
|
110
|
+
const client = getGlobalQueryClient();
|
|
111
|
+
await client.invalidateQueries();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Prefetch all data from all registered repositories
|
|
116
|
+
* Useful for app initialization or online event
|
|
117
|
+
*/
|
|
118
|
+
async prefetchAll(): Promise<void> {
|
|
119
|
+
const promises = Array.from(this.repositories.values()).map((repo) => {
|
|
120
|
+
try {
|
|
121
|
+
return repo.prefetchAll();
|
|
122
|
+
} catch {
|
|
123
|
+
// Ignore prefetch errors
|
|
124
|
+
return Promise.resolve();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await Promise.all(promises);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Global repository factory instance
|
|
134
|
+
*/
|
|
135
|
+
export const RepositoryFactory = new RepositoryFactoryClass();
|