@umituz/react-native-design-system 2.8.10 → 2.8.12
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/package.json +8 -1
- package/src/exports/tanstack.ts +1 -0
- package/src/index.ts +5 -0
- package/src/tanstack/domain/constants/CacheDefaults.ts +63 -0
- package/src/tanstack/domain/repositories/BaseRepository.ts +280 -0
- package/src/tanstack/domain/repositories/RepositoryFactory.ts +135 -0
- package/src/tanstack/domain/types/CacheStrategy.ts +115 -0
- package/src/tanstack/domain/utils/ErrorHelpers.ts +154 -0
- package/src/tanstack/domain/utils/QueryKeyFactory.ts +134 -0
- package/src/tanstack/domain/utils/TypeUtilities.ts +153 -0
- package/src/tanstack/index.ts +161 -0
- package/src/tanstack/infrastructure/config/PersisterConfig.ts +162 -0
- package/src/tanstack/infrastructure/config/QueryClientConfig.ts +154 -0
- package/src/tanstack/infrastructure/config/QueryClientSingleton.ts +69 -0
- package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +274 -0
- package/src/tanstack/infrastructure/providers/TanstackProvider.tsx +105 -0
- package/src/tanstack/presentation/hooks/useInvalidateQueries.ts +128 -0
- package/src/tanstack/presentation/hooks/useOptimisticUpdate.ts +88 -0
- package/src/tanstack/presentation/hooks/usePaginatedQuery.ts +129 -0
- package/src/tanstack/presentation/hooks/usePrefetch.ts +237 -0
- package/src/tanstack/presentation/utils/RetryHelpers.ts +67 -0
- package/src/tanstack/types/global.d.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.12",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, and onboarding utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"./storage": "./src/storage/index.ts",
|
|
27
27
|
"./filesystem": "./src/filesystem/index.ts",
|
|
28
28
|
"./media": "./src/media/index.ts",
|
|
29
|
+
"./tanstack": "./src/tanstack/index.ts",
|
|
29
30
|
"./package.json": "./package.json"
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
@@ -59,6 +60,9 @@
|
|
|
59
60
|
"url": "https://github.com/umituz/react-native-design-system"
|
|
60
61
|
},
|
|
61
62
|
"peerDependencies": {
|
|
63
|
+
"@tanstack/query-async-storage-persister": ">=5.0.0",
|
|
64
|
+
"@tanstack/react-query": ">=5.0.0",
|
|
65
|
+
"@tanstack/react-query-persist-client": ">=5.0.0",
|
|
62
66
|
"@expo/vector-icons": ">=15.0.0",
|
|
63
67
|
"@react-native-community/datetimepicker": ">=8.0.0",
|
|
64
68
|
"@react-native-community/slider": ">=4.0.0",
|
|
@@ -100,6 +104,9 @@
|
|
|
100
104
|
}
|
|
101
105
|
},
|
|
102
106
|
"devDependencies": {
|
|
107
|
+
"@tanstack/query-async-storage-persister": "^5.66.7",
|
|
108
|
+
"@tanstack/react-query": "^5.66.7",
|
|
109
|
+
"@tanstack/react-query-persist-client": "^5.66.7",
|
|
103
110
|
"@eslint/js": "^9.39.2",
|
|
104
111
|
"@expo/vector-icons": "^15.0.0",
|
|
105
112
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../tanstack";
|
package/src/index.ts
CHANGED
|
@@ -120,3 +120,8 @@ export * from './exports/filesystem';
|
|
|
120
120
|
// MEDIA EXPORTS
|
|
121
121
|
// =============================================================================
|
|
122
122
|
export * from './exports/media';
|
|
123
|
+
|
|
124
|
+
// =============================================================================
|
|
125
|
+
// TANSTACK EXPORTS
|
|
126
|
+
// =============================================================================
|
|
127
|
+
export * from './exports/tanstack';
|
|
@@ -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,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
|
+
|
|
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
|
+
|
|
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();
|
|
@@ -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
|
+
}
|