@umituz/react-native-design-system 2.8.9 → 2.8.11

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.
Files changed (26) hide show
  1. package/package.json +8 -1
  2. package/src/exports/tanstack.ts +1 -0
  3. package/src/index.ts +5 -0
  4. package/src/media/presentation/hooks/useCardMediaUpload.ts +1 -1
  5. package/src/media/presentation/hooks/useCardMultimediaFlashcard.ts +3 -3
  6. package/src/media/presentation/hooks/useMediaUpload.ts +1 -1
  7. package/src/media/presentation/hooks/useMultimediaFlashcard.ts +3 -3
  8. package/src/tanstack/domain/constants/CacheDefaults.ts +63 -0
  9. package/src/tanstack/domain/repositories/BaseRepository.ts +280 -0
  10. package/src/tanstack/domain/repositories/RepositoryFactory.ts +135 -0
  11. package/src/tanstack/domain/types/CacheStrategy.ts +115 -0
  12. package/src/tanstack/domain/utils/ErrorHelpers.ts +154 -0
  13. package/src/tanstack/domain/utils/QueryKeyFactory.ts +134 -0
  14. package/src/tanstack/domain/utils/TypeUtilities.ts +153 -0
  15. package/src/tanstack/index.ts +161 -0
  16. package/src/tanstack/infrastructure/config/PersisterConfig.ts +162 -0
  17. package/src/tanstack/infrastructure/config/QueryClientConfig.ts +154 -0
  18. package/src/tanstack/infrastructure/config/QueryClientSingleton.ts +69 -0
  19. package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +274 -0
  20. package/src/tanstack/infrastructure/providers/TanstackProvider.tsx +105 -0
  21. package/src/tanstack/presentation/hooks/useInvalidateQueries.ts +128 -0
  22. package/src/tanstack/presentation/hooks/useOptimisticUpdate.ts +88 -0
  23. package/src/tanstack/presentation/hooks/usePaginatedQuery.ts +129 -0
  24. package/src/tanstack/presentation/hooks/usePrefetch.ts +237 -0
  25. package/src/tanstack/presentation/utils/RetryHelpers.ts +67 -0
  26. package/src/tanstack/types/global.d.ts +1 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Error Helpers
3
+ * Domain layer - Error handling utilities
4
+ *
5
+ * General-purpose error handling for TanStack Query
6
+ */
7
+
8
+ /**
9
+ * Check if error is a QueryError (from TanStack Query)
10
+ */
11
+ export function isQueryError(error: unknown): boolean {
12
+ return (
13
+ error !== null &&
14
+ typeof error === 'object' &&
15
+ 'message' in error &&
16
+ typeof error.message === 'string'
17
+ );
18
+ }
19
+
20
+ /**
21
+ * Check if error is a MutationError (from TanStack Query)
22
+ */
23
+ export function isMutationError(error: unknown): boolean {
24
+ return isQueryError(error);
25
+ }
26
+
27
+ /**
28
+ * Check if error is a network error
29
+ */
30
+ export function isNetworkError(error: unknown): boolean {
31
+ if (!isQueryError(error)) return false;
32
+
33
+ const message = (error as { message: string }).message.toLowerCase();
34
+ return (
35
+ message.includes('network') ||
36
+ message.includes('fetch') ||
37
+ message.includes('connection')
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Check if error is an abort error
43
+ */
44
+ export function isAbortError(error: unknown): boolean {
45
+ return error instanceof Error && error.name === 'AbortError';
46
+ }
47
+
48
+ /**
49
+ * Extract error message from error
50
+ */
51
+ export function getErrorMessage(error: unknown): string {
52
+ if (error instanceof Error) {
53
+ return error.message;
54
+ }
55
+
56
+ if (isQueryError(error)) {
57
+ return (error as { message: string }).message;
58
+ }
59
+
60
+ if (typeof error === 'string') {
61
+ return error;
62
+ }
63
+
64
+ return 'An unknown error occurred';
65
+ }
66
+
67
+ /**
68
+ * Get user-friendly error message
69
+ * Maps technical errors to user-friendly messages
70
+ */
71
+ export function getUserFriendlyMessage(error: unknown): string {
72
+ const message = getErrorMessage(error).toLowerCase();
73
+
74
+ if (isNetworkError(error)) {
75
+ return 'Network connection failed. Please check your internet connection.';
76
+ }
77
+
78
+ if (isAbortError(error)) {
79
+ return 'Request was cancelled.';
80
+ }
81
+
82
+ if (message.includes('unauthorized') || message.includes('401')) {
83
+ return 'You are not authorized to perform this action.';
84
+ }
85
+
86
+ if (message.includes('forbidden') || message.includes('403')) {
87
+ return 'You do not have permission to access this resource.';
88
+ }
89
+
90
+ if (message.includes('not found') || message.includes('404')) {
91
+ return 'The requested resource was not found.';
92
+ }
93
+
94
+ if (message.includes('validation') || message.includes('400')) {
95
+ return 'Please check your input and try again.';
96
+ }
97
+
98
+ if (message.includes('server') || message.includes('500')) {
99
+ return 'A server error occurred. Please try again later.';
100
+ }
101
+
102
+ if (message.includes('timeout')) {
103
+ return 'Request timed out. Please try again.';
104
+ }
105
+
106
+ return 'An error occurred. Please try again.';
107
+ }
108
+
109
+ /**
110
+ * Parse error response (for API errors with structured data)
111
+ */
112
+ export interface ErrorResponse {
113
+ message: string;
114
+ errors?: Record<string, string[]>;
115
+ code?: string;
116
+ }
117
+
118
+ export function parseErrorResponse(error: unknown): ErrorResponse | null {
119
+ if (!isQueryError(error)) return null;
120
+
121
+ const errorObj = error as { response?: { data?: ErrorResponse } };
122
+
123
+ if (errorObj.response?.data) {
124
+ return errorObj.response.data;
125
+ }
126
+
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Get validation errors from error response
132
+ */
133
+ export function getValidationErrors(error: unknown): Record<string, string[]> | null {
134
+ const response = parseErrorResponse(error);
135
+ return response?.errors ?? null;
136
+ }
137
+
138
+ /**
139
+ * Get error code from error response
140
+ */
141
+ export function getErrorCode(error: unknown): string | null {
142
+ const response = parseErrorResponse(error);
143
+ return response?.code ?? null;
144
+ }
145
+
146
+ /**
147
+ * Log error in development
148
+ */
149
+ export function logError(context: string, error: unknown): void {
150
+ if (__DEV__) {
151
+
152
+ console.error(`[${context}]`, error);
153
+ }
154
+ }
@@ -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
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Type Utilities
3
+ * Domain layer - Type extractors and helpers
4
+ *
5
+ * General-purpose type utilities for TanStack Query
6
+ */
7
+
8
+ import type { UseQueryResult, UseMutationResult } from '@tanstack/react-query';
9
+
10
+ /**
11
+ * Extract data type from UseQueryResult
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const result = useQuery({ queryKey: ['user'], queryFn: fetchUser });
16
+ * type User = ExtractQueryDataType<typeof result>; // User
17
+ * ```
18
+ */
19
+ export type ExtractQueryDataType<TQuery extends UseQueryResult<unknown, unknown>> =
20
+ TQuery extends UseQueryResult<infer TData, unknown> ? TData : never;
21
+
22
+ /**
23
+ * Extract error type from UseQueryResult
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const result = useQuery({ queryKey: ['user'], queryFn: fetchUser });
28
+ * type Error = ExtractQueryErrorType<typeof result>; // Error
29
+ * ```
30
+ */
31
+ export type ExtractQueryErrorType<TQuery extends UseQueryResult<unknown, unknown>> =
32
+ TQuery extends UseQueryResult<unknown, infer TError> ? TError : never;
33
+
34
+ /**
35
+ * Extract data type from UseMutationResult
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const mutation = useMutation({ mutationFn: createUser });
40
+ * type User = ExtractMutationDataType<typeof mutation>; // User
41
+ * ```
42
+ */
43
+ export type ExtractMutationDataType<TMutation extends UseMutationResult<unknown, unknown, unknown>> =
44
+ TMutation extends UseMutationResult<infer TData, unknown, unknown> ? TData : never;
45
+
46
+ /**
47
+ * Extract error type from UseMutationResult
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const mutation = useMutation({ mutationFn: createUser });
52
+ * type Error = ExtractMutationErrorType<typeof mutation>; // Error
53
+ * ```
54
+ */
55
+ export type ExtractMutationErrorType<TMutation extends UseMutationResult<unknown, unknown, unknown>> =
56
+ TMutation extends UseMutationResult<unknown, infer TError, unknown> ? TError : never;
57
+
58
+ /**
59
+ * Extract variables type from UseMutationResult
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const mutation = useMutation({ mutationFn: createUser });
64
+ * type Variables = ExtractMutationVariables<typeof mutation>; // CreateUserVars
65
+ * ```
66
+ */
67
+ export type ExtractMutationVariables<TMutation extends UseMutationResult<unknown, unknown, unknown>> =
68
+ TMutation extends UseMutationResult<unknown, unknown, infer TVariables> ? TVariables : never;
69
+
70
+ /**
71
+ * Extract data type from infinite query
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * const result = useInfiniteQuery({ queryKey: ['posts'], queryFn: fetchPosts });
76
+ * type Posts = ExtractInfiniteDataType<typeof result>; // InfiniteData<PostsResponse>
77
+ * ```
78
+ */
79
+ export type ExtractInfiniteDataType<TQuery extends UseQueryResult<unknown, unknown>> =
80
+ TQuery extends UseQueryResult<infer TData, unknown> ? TData : never;
81
+
82
+ /**
83
+ * Extract page data type from infinite query
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const result = useInfiniteQuery({ queryKey: ['posts'], queryFn: fetchPosts });
88
+ * type Page = ExtractInfinitePageType<typeof result>; // PostsResponse
89
+ * ```
90
+ */
91
+ export type ExtractInfinitePageType<TQuery> = TQuery extends {
92
+ data: { pages: infer TPages };
93
+ }
94
+ ? TPages extends Array<infer TPage>
95
+ ? TPage
96
+ : never
97
+ : never;
98
+
99
+ /**
100
+ * Make specific keys required from a type
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * type User = { id?: number; name: string; email?: string };
105
+ * type UserWithId = RequireKeys<User, 'id' | 'email'>;
106
+ * // { id: number; name: string; email: string }
107
+ * ```
108
+ */
109
+ export type RequireKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
110
+
111
+ /**
112
+ * Make specific keys optional from a type
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * type User = { id: number; name: string; email: string };
117
+ * type PartialUser = OptionalKeys<User, 'id' | 'email'>;
118
+ * // { id?: number; name: string; email?: string }
119
+ * ```
120
+ */
121
+ export type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
122
+
123
+ /**
124
+ * Deep partial type
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * type User = { id: number; profile: { name: string; email: string } };
129
+ * type PartialUser = DeepPartial<User>;
130
+ * // { id?: number; profile?: { name?: string; email?: string } }
131
+ * ```
132
+ */
133
+ export type DeepPartial<T> = T extends object
134
+ ? {
135
+ [K in keyof T]?: DeepPartial<T[K]>;
136
+ }
137
+ : T;
138
+
139
+ /**
140
+ * Deep required type
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * type User = { id?: number; profile?: { name?: string; email?: string } };
145
+ * type RequiredUser = DeepRequired<User>;
146
+ * // { id: number; profile: { name: string; email: string } }
147
+ * ```
148
+ */
149
+ export type DeepRequired<T> = T extends object
150
+ ? {
151
+ [K in keyof T]-?: DeepRequired<T[K]>;
152
+ }
153
+ : T;
@@ -0,0 +1,161 @@
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 as QUERY_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 as QueryCacheConfig,
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
+ // Domain - Type Utilities
35
+ export type {
36
+ ExtractQueryDataType,
37
+ ExtractQueryErrorType,
38
+ ExtractMutationDataType,
39
+ ExtractMutationErrorType,
40
+ ExtractMutationVariables,
41
+ ExtractInfiniteDataType,
42
+ ExtractInfinitePageType,
43
+ RequireKeys,
44
+ OptionalKeys,
45
+ DeepPartial,
46
+ DeepRequired,
47
+ } from './domain/utils/TypeUtilities';
48
+
49
+ // Domain - Error Helpers
50
+ export {
51
+ isQueryError,
52
+ isMutationError,
53
+ isNetworkError,
54
+ isAbortError,
55
+ getErrorMessage,
56
+ getUserFriendlyMessage,
57
+ parseErrorResponse,
58
+ getValidationErrors,
59
+ getErrorCode,
60
+ logError,
61
+ type ErrorResponse,
62
+ } from './domain/utils/ErrorHelpers';
63
+
64
+ // Domain - Repositories
65
+ export {
66
+ BaseRepository,
67
+ type CreateParams,
68
+ type UpdateParams,
69
+ type ListParams,
70
+ type RepositoryOptions,
71
+ } from './domain/repositories/BaseRepository';
72
+
73
+ export { RepositoryFactory } from './domain/repositories/RepositoryFactory';
74
+
75
+ // Infrastructure - Config
76
+ export {
77
+ CacheStrategies,
78
+ createQueryClient,
79
+ getCacheStrategy,
80
+ type QueryClientFactoryOptions,
81
+ } from './infrastructure/config/QueryClientConfig';
82
+
83
+ export {
84
+ createPersister,
85
+ clearPersistedCache,
86
+ getPersistedCacheSize,
87
+ type PersisterFactoryOptions,
88
+ } from './infrastructure/config/PersisterConfig';
89
+
90
+ export {
91
+ getGlobalQueryClient,
92
+ hasGlobalQueryClient,
93
+ setGlobalQueryClient,
94
+ clearGlobalQueryClient,
95
+ } from './infrastructure/config/QueryClientSingleton';
96
+
97
+ // Infrastructure - Monitoring
98
+ export {
99
+ DevMonitor,
100
+ type QueryMetrics,
101
+ type CacheStats as QueryCacheStats,
102
+ type DevMonitorOptions,
103
+ } from './infrastructure/monitoring/DevMonitor';
104
+
105
+ // Infrastructure - Providers
106
+ export { TanstackProvider, type TanstackProviderProps } from './infrastructure/providers/TanstackProvider';
107
+
108
+ // Presentation - Hooks
109
+ export {
110
+ useInvalidateQueries,
111
+ useInvalidateMultipleQueries,
112
+ useRemoveQueries,
113
+ useResetQueries,
114
+ } from './presentation/hooks/useInvalidateQueries';
115
+
116
+ export {
117
+ useCursorPagination,
118
+ useOffsetPagination,
119
+ type CursorPageParam,
120
+ type OffsetPageParam,
121
+ type CursorPaginatedResponse,
122
+ type OffsetPaginatedResponse,
123
+ } from './presentation/hooks/usePaginatedQuery';
124
+
125
+ export {
126
+ useOptimisticUpdate,
127
+ useOptimisticListUpdate,
128
+ type OptimisticUpdateConfig,
129
+ } from './presentation/hooks/useOptimisticUpdate';
130
+
131
+ export {
132
+ usePrefetchQuery,
133
+ usePrefetchInfiniteQuery,
134
+ usePrefetchOnMount,
135
+ usePrefetchMultiple,
136
+ type PrefetchOptions,
137
+ } from './presentation/hooks/usePrefetch';
138
+
139
+ // Presentation - Utils
140
+ export {
141
+ createConditionalRetry,
142
+ createQuotaAwareRetry,
143
+ type RetryFunction,
144
+ type ErrorChecker,
145
+ } from './presentation/utils/RetryHelpers';
146
+
147
+ // Re-export TanStack Query core for convenience
148
+ export {
149
+ useQuery,
150
+ useMutation,
151
+ useInfiniteQuery,
152
+ useQueryClient,
153
+ useIsFetching,
154
+ useIsMutating,
155
+ type UseQueryResult,
156
+ type UseMutationResult,
157
+ type UseInfiniteQueryResult,
158
+ type InfiniteData,
159
+ type QueryKey,
160
+ type QueryClient,
161
+ } from '@tanstack/react-query';