@umituz/react-native-design-system 4.23.67 → 4.23.69
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 +1 -1
- package/src/atoms/EmptyState.tsx +2 -2
- package/src/atoms/icon/AtomicIcon.tsx +41 -112
- package/src/atoms/icon/components/iconRenderer.tsx +118 -0
- package/src/atoms/icon/utils/iconUtils.ts +94 -0
- package/src/exception/infrastructure/services/ExceptionService.ts +29 -17
- package/src/exception/presentation/components/ExceptionEmptyState.tsx +1 -1
- package/src/exception/presentation/components/ExceptionErrorState.tsx +1 -1
- package/src/image/infrastructure/services/ImageBatchService.ts +1 -7
- package/src/image/infrastructure/types/BatchTypes.ts +11 -0
- package/src/image/infrastructure/utils/BatchProcessor.ts +1 -1
- package/src/image/infrastructure/utils/ImageErrorHandler.ts +1 -0
- package/src/infinite-scroll/presentation/components/infinite-scroll-list.tsx +2 -2
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +3 -3
- package/src/media/presentation/hooks/useCardMediaGeneration.ts +4 -4
- package/src/media/presentation/hooks/useCardMediaUpload.ts +4 -4
- package/src/media/presentation/hooks/useCardMediaValidation.ts +4 -4
- package/src/media/presentation/hooks/useCardMultimediaFlashcard.ts +5 -5
- package/src/media/presentation/hooks/useMediaGeneration.ts +4 -4
- package/src/media/presentation/hooks/useMediaUpload.ts +4 -4
- package/src/media/presentation/hooks/useMediaValidation.ts +4 -4
- package/src/media/presentation/hooks/useMultimediaFlashcard.ts +5 -5
- package/src/molecules/BaseModal.tsx +1 -1
- package/src/molecules/ConfirmationModalContent.tsx +2 -2
- package/src/molecules/ConfirmationModalMain.tsx +2 -2
- package/src/molecules/alerts/AlertToast.tsx +163 -192
- package/src/molecules/alerts/utils/alertToastHelpers.ts +70 -0
- package/src/molecules/bottom-sheet/components/filter/FilterBottomSheet.tsx +2 -2
- package/src/molecules/calendar/presentation/components/AtomicCalendar.tsx +1 -1
- package/src/molecules/calendar/presentation/components/CalendarDayCell.tsx +2 -1
- package/src/molecules/calendar/presentation/components/CalendarWeekdayHeader.tsx +1 -1
- package/src/molecules/confirmation-modal/useConfirmationModal.ts +6 -6
- package/src/molecules/countdown/components/Countdown.tsx +2 -2
- package/src/molecules/splash/components/SplashScreen.tsx +9 -23
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +1 -1
- package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +2 -2
- package/src/organisms/FormContainer.tsx +2 -2
- package/src/responsive/validation.ts +1 -0
- package/src/services/api/ApiClient.ts +242 -0
- package/src/services/api/index.ts +9 -0
- package/src/services/api/types/ApiTypes.ts +50 -0
- package/src/services/api/utils/requestBuilder.ts +92 -0
- package/src/services/api/utils/responseHandler.ts +130 -0
- package/src/storage/cache/domain/ErrorHandler.ts +1 -0
- package/src/storage/domain/errors/StorageError.ts +6 -0
- package/src/storage/infrastructure/repositories/AsyncStorageRepository.ts +31 -16
- package/src/tanstack/domain/repositories/BaseRepository.ts +16 -72
- package/src/tanstack/domain/repositories/IBaseRepository.ts +34 -0
- package/src/tanstack/domain/repositories/helpers/repositoryHelpers.ts +58 -0
- package/src/tanstack/domain/repositories/mixins/repositoryInvalidationMethods.ts +101 -0
- package/src/tanstack/domain/repositories/mixins/repositoryQueryMethods.ts +102 -0
- package/src/tanstack/infrastructure/providers/TanstackProvider.tsx +3 -3
- package/src/tanstack/presentation/hooks/types/prefetchTypes.ts +33 -0
- package/src/tanstack/presentation/hooks/usePrefetch.ts +8 -28
- package/src/tanstack/presentation/hooks/utils/prefetchLogger.ts +27 -0
- package/src/theme/index.ts +0 -3
- package/src/theme/infrastructure/providers/DesignSystemProvider.tsx +15 -4
- package/src/utils/colorMapper.ts +193 -0
- package/src/utils/formatHelper.ts +16 -0
- package/src/utils/formatters/dateFormatter.ts +64 -0
- package/src/utils/formatters/numberFormatter.ts +130 -0
- package/src/utils/formatters/stringFormatter.ts +190 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/styleComposer.ts +94 -0
- package/src/utils/validationHelper.ts +16 -0
- package/src/utils/validators/dataValidators.ts +111 -0
- package/src/utils/validators/numericValidators.ts +106 -0
- package/src/utils/validators/stringValidators.ts +85 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Handler Utility
|
|
3
|
+
* Handles API responses and errors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ApiResponse, ApiError } from '../types/ApiTypes';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parses fetch response to ApiResponse
|
|
10
|
+
*
|
|
11
|
+
* @param response - Fetch response
|
|
12
|
+
* @returns Parsed API response
|
|
13
|
+
*/
|
|
14
|
+
export async function parseResponse<T>(response: Response): Promise<ApiResponse<T>> {
|
|
15
|
+
const data = await parseResponseBody<T>(response);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
data,
|
|
19
|
+
status: response.status,
|
|
20
|
+
statusText: response.statusText,
|
|
21
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parses response body based on content type
|
|
27
|
+
*
|
|
28
|
+
* @param response - Fetch response
|
|
29
|
+
* @returns Parsed body data
|
|
30
|
+
*/
|
|
31
|
+
async function parseResponseBody<T>(response: Response): Promise<T> {
|
|
32
|
+
const contentType = response.headers.get('Content-Type');
|
|
33
|
+
|
|
34
|
+
if (contentType?.includes('application/json')) {
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
return data as T;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (contentType?.includes('text/')) {
|
|
40
|
+
const text = await response.text();
|
|
41
|
+
return text as unknown as T;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const blob = await response.blob();
|
|
45
|
+
return blob as unknown as T;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handles HTTP error and converts to ApiError
|
|
50
|
+
*
|
|
51
|
+
* @param response - Fetch response
|
|
52
|
+
* @returns ApiError object
|
|
53
|
+
*/
|
|
54
|
+
export async function handleHttpError(response: Response): Promise<ApiError> {
|
|
55
|
+
let details: any;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
details = await response.json();
|
|
59
|
+
} catch {
|
|
60
|
+
details = await response.text();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
message: details?.message || response.statusText || 'Request failed',
|
|
65
|
+
status: response.status,
|
|
66
|
+
code: details?.code,
|
|
67
|
+
details,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Handles network error
|
|
73
|
+
*
|
|
74
|
+
* @param error - Error object
|
|
75
|
+
* @returns ApiError object
|
|
76
|
+
*/
|
|
77
|
+
export function handleNetworkError(error: unknown): ApiError {
|
|
78
|
+
if (error instanceof Error) {
|
|
79
|
+
return {
|
|
80
|
+
message: error.message || 'Network error',
|
|
81
|
+
details: error,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
message: 'Unknown network error',
|
|
87
|
+
details: error,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Checks if response is successful
|
|
93
|
+
*
|
|
94
|
+
* @param response - Fetch response
|
|
95
|
+
* @returns True if response is OK
|
|
96
|
+
*/
|
|
97
|
+
export function isSuccessfulResponse(response: Response): boolean {
|
|
98
|
+
return response.ok && response.status >= 200 && response.status < 300;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates timeout promise
|
|
103
|
+
*
|
|
104
|
+
* @param ms - Timeout in milliseconds
|
|
105
|
+
* @returns Promise that rejects after timeout
|
|
106
|
+
*/
|
|
107
|
+
export function createTimeoutPromise(ms: number): Promise<never> {
|
|
108
|
+
return new Promise((_, reject) => {
|
|
109
|
+
setTimeout(() => reject(new Error(`Request timeout after ${ms}ms`)), ms);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Wraps fetch with timeout
|
|
115
|
+
*
|
|
116
|
+
* @param url - Request URL
|
|
117
|
+
* @param options - Fetch options
|
|
118
|
+
* @param timeout - Timeout in milliseconds
|
|
119
|
+
* @returns Fetch result with timeout
|
|
120
|
+
*/
|
|
121
|
+
export async function fetchWithTimeout(
|
|
122
|
+
url: string,
|
|
123
|
+
options: RequestInit,
|
|
124
|
+
timeout: number
|
|
125
|
+
): Promise<Response> {
|
|
126
|
+
return Promise.race([
|
|
127
|
+
fetch(url, options),
|
|
128
|
+
createTimeoutPromise(timeout),
|
|
129
|
+
]) as Promise<Response>;
|
|
130
|
+
}
|
|
@@ -12,6 +12,7 @@ export class StorageError extends Error {
|
|
|
12
12
|
constructor(message: string, public readonly key?: string) {
|
|
13
13
|
super(message);
|
|
14
14
|
this.name = 'StorageError';
|
|
15
|
+
Object.setPrototypeOf(this, StorageError.prototype);
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -25,6 +26,7 @@ export class StorageReadError extends StorageError {
|
|
|
25
26
|
super(`Failed to read from storage: ${key}`, key);
|
|
26
27
|
this.name = 'StorageReadError';
|
|
27
28
|
this.cause = cause;
|
|
29
|
+
Object.setPrototypeOf(this, StorageReadError.prototype);
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -38,6 +40,7 @@ export class StorageWriteError extends StorageError {
|
|
|
38
40
|
super(`Failed to write to storage: ${key}`, key);
|
|
39
41
|
this.name = 'StorageWriteError';
|
|
40
42
|
this.cause = cause;
|
|
43
|
+
Object.setPrototypeOf(this, StorageWriteError.prototype);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -51,6 +54,7 @@ export class StorageDeleteError extends StorageError {
|
|
|
51
54
|
super(`Failed to delete from storage: ${key}`, key);
|
|
52
55
|
this.name = 'StorageDeleteError';
|
|
53
56
|
this.cause = cause;
|
|
57
|
+
Object.setPrototypeOf(this, StorageDeleteError.prototype);
|
|
54
58
|
}
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -64,6 +68,7 @@ export class StorageSerializationError extends StorageError {
|
|
|
64
68
|
super(`Failed to serialize data for key: ${key}`, key);
|
|
65
69
|
this.name = 'StorageSerializationError';
|
|
66
70
|
this.cause = cause;
|
|
71
|
+
Object.setPrototypeOf(this, StorageSerializationError.prototype);
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
|
|
@@ -77,5 +82,6 @@ export class StorageDeserializationError extends StorageError {
|
|
|
77
82
|
super(`Failed to deserialize data for key: ${key}`, key);
|
|
78
83
|
this.name = 'StorageDeserializationError';
|
|
79
84
|
this.cause = cause;
|
|
85
|
+
Object.setPrototypeOf(this, StorageDeserializationError.prototype);
|
|
80
86
|
}
|
|
81
87
|
}
|
|
@@ -16,63 +16,76 @@ import { BatchStorageOperations } from './BatchStorageOperations';
|
|
|
16
16
|
* Uses composition to follow Single Responsibility Principle
|
|
17
17
|
*/
|
|
18
18
|
export class AsyncStorageRepository implements IStorageRepository {
|
|
19
|
-
private baseOps: BaseStorageOperations;
|
|
20
|
-
private stringOps: StringStorageOperations;
|
|
21
|
-
private batchOps: BatchStorageOperations;
|
|
19
|
+
private baseOps: BaseStorageOperations | null = null;
|
|
20
|
+
private stringOps: StringStorageOperations | null = null;
|
|
21
|
+
private batchOps: BatchStorageOperations | null = null;
|
|
22
22
|
|
|
23
23
|
constructor() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// Lazy initialization - defer object creation
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private ensureInitialized() {
|
|
28
|
+
if (!this.baseOps) {
|
|
29
|
+
this.baseOps = new BaseStorageOperations();
|
|
30
|
+
this.stringOps = new StringStorageOperations();
|
|
31
|
+
this.batchOps = new BatchStorageOperations();
|
|
32
|
+
}
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Get item from AsyncStorage with type safety
|
|
31
37
|
*/
|
|
32
38
|
async getItem<T>(key: string, defaultValue: T): Promise<StorageResult<T>> {
|
|
33
|
-
|
|
39
|
+
this.ensureInitialized();
|
|
40
|
+
return this.baseOps!.getItem(key, defaultValue);
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/**
|
|
37
44
|
* Set item in AsyncStorage with automatic JSON serialization
|
|
38
45
|
*/
|
|
39
46
|
async setItem<T>(key: string, value: T): Promise<StorageResult<T>> {
|
|
40
|
-
|
|
47
|
+
this.ensureInitialized();
|
|
48
|
+
return this.baseOps!.setItem(key, value);
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
/**
|
|
44
52
|
* Get string value (no JSON parsing)
|
|
45
53
|
*/
|
|
46
54
|
async getString(key: string, defaultValue: string): Promise<StorageResult<string>> {
|
|
47
|
-
|
|
55
|
+
this.ensureInitialized();
|
|
56
|
+
return this.stringOps!.getString(key, defaultValue);
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
/**
|
|
51
60
|
* Set string value (no JSON serialization)
|
|
52
61
|
*/
|
|
53
62
|
async setString(key: string, value: string): Promise<StorageResult<string>> {
|
|
54
|
-
|
|
63
|
+
this.ensureInitialized();
|
|
64
|
+
return this.stringOps!.setString(key, value);
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
/**
|
|
58
68
|
* Remove item from AsyncStorage
|
|
59
69
|
*/
|
|
60
70
|
async removeItem(key: string): Promise<StorageResult<void>> {
|
|
61
|
-
|
|
71
|
+
this.ensureInitialized();
|
|
72
|
+
return this.baseOps!.removeItem(key);
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
/**
|
|
65
76
|
* Check if key exists in storage
|
|
66
77
|
*/
|
|
67
78
|
async hasItem(key: string): Promise<boolean> {
|
|
68
|
-
|
|
79
|
+
this.ensureInitialized();
|
|
80
|
+
return this.baseOps!.hasItem(key);
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
/**
|
|
72
84
|
* Clear all AsyncStorage data
|
|
73
85
|
*/
|
|
74
86
|
async clearAll(): Promise<StorageResult<void>> {
|
|
75
|
-
|
|
87
|
+
this.ensureInitialized();
|
|
88
|
+
return this.baseOps!.clearAll();
|
|
76
89
|
}
|
|
77
90
|
|
|
78
91
|
/**
|
|
@@ -81,18 +94,20 @@ export class AsyncStorageRepository implements IStorageRepository {
|
|
|
81
94
|
async getMultiple(
|
|
82
95
|
keys: string[]
|
|
83
96
|
): Promise<StorageResult<Record<string, string | null>>> {
|
|
84
|
-
|
|
97
|
+
this.ensureInitialized();
|
|
98
|
+
return this.batchOps!.getMultiple(keys);
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
/**
|
|
88
102
|
* Get all keys from storage
|
|
89
103
|
*/
|
|
90
104
|
async getAllKeys(): Promise<StorageResult<string[]>> {
|
|
91
|
-
|
|
105
|
+
this.ensureInitialized();
|
|
106
|
+
return this.batchOps!.getAllKeys();
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
/**
|
|
96
|
-
* Singleton instance
|
|
111
|
+
* Singleton instance - lazy initialization
|
|
97
112
|
*/
|
|
98
113
|
export const storageRepository = new AsyncStorageRepository();
|
|
@@ -35,9 +35,8 @@
|
|
|
35
35
|
* ```
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
import type { QueryClient
|
|
38
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
39
39
|
import { getGlobalQueryClient } from '../config/QueryClientAccessor';
|
|
40
|
-
import { CacheStrategies } from '../../infrastructure/config/QueryClientConfig';
|
|
41
40
|
import { createQueryKeyFactory } from '../utils/QueryKeyFactory';
|
|
42
41
|
import type {
|
|
43
42
|
CreateParams,
|
|
@@ -45,6 +44,9 @@ import type {
|
|
|
45
44
|
ListParams,
|
|
46
45
|
RepositoryOptions,
|
|
47
46
|
} from './RepositoryTypes';
|
|
47
|
+
import { mergeRepositoryOptions, getCacheOptions } from './helpers/repositoryHelpers';
|
|
48
|
+
import * as queryMethods from './mixins/repositoryQueryMethods';
|
|
49
|
+
import * as invalidationMethods from './mixins/repositoryInvalidationMethods';
|
|
48
50
|
|
|
49
51
|
/**
|
|
50
52
|
* Base repository for CRUD operations
|
|
@@ -68,11 +70,7 @@ export abstract class BaseRepository<
|
|
|
68
70
|
|
|
69
71
|
constructor(resource: string, options: RepositoryOptions = {}) {
|
|
70
72
|
this.resource = resource;
|
|
71
|
-
this.options =
|
|
72
|
-
cacheStrategy: options.cacheStrategy ?? CacheStrategies.PUBLIC_DATA,
|
|
73
|
-
...options,
|
|
74
|
-
};
|
|
75
|
-
|
|
73
|
+
this.options = mergeRepositoryOptions(options);
|
|
76
74
|
this.keys = createQueryKeyFactory(this.resource);
|
|
77
75
|
}
|
|
78
76
|
|
|
@@ -87,10 +85,7 @@ export abstract class BaseRepository<
|
|
|
87
85
|
* Get cache options for queries
|
|
88
86
|
*/
|
|
89
87
|
protected getCacheOptions(): { staleTime: number; gcTime: number } {
|
|
90
|
-
return
|
|
91
|
-
staleTime: this.options.staleTime ?? (this.options.cacheStrategy?.staleTime ?? CacheStrategies.PUBLIC_DATA.staleTime),
|
|
92
|
-
gcTime: this.options.gcTime ?? (this.options.cacheStrategy?.gcTime ?? CacheStrategies.PUBLIC_DATA.gcTime),
|
|
93
|
-
};
|
|
88
|
+
return getCacheOptions(this.options);
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
/**
|
|
@@ -122,120 +117,69 @@ export abstract class BaseRepository<
|
|
|
122
117
|
* Query all items with caching
|
|
123
118
|
*/
|
|
124
119
|
async queryAll(params?: ListParams): Promise<TData[]> {
|
|
125
|
-
|
|
126
|
-
const queryKey = params ? this.keys.list(params as Record<string, unknown>) : this.keys.lists();
|
|
127
|
-
const cacheOptions = this.getCacheOptions();
|
|
128
|
-
|
|
129
|
-
return client.fetchQuery({
|
|
130
|
-
queryKey: queryKey as QueryKey,
|
|
131
|
-
queryFn: () => this.fetchAll(params),
|
|
132
|
-
...cacheOptions,
|
|
133
|
-
});
|
|
120
|
+
return queryMethods.queryAll(this, params);
|
|
134
121
|
}
|
|
135
122
|
|
|
136
123
|
/**
|
|
137
124
|
* Query item by ID with caching
|
|
138
125
|
*/
|
|
139
126
|
async queryById(id: string | number): Promise<TData | undefined> {
|
|
140
|
-
|
|
141
|
-
const queryKey = this.keys.detail(id);
|
|
142
|
-
const cacheOptions = this.getCacheOptions();
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
return client.fetchQuery({
|
|
146
|
-
queryKey: queryKey as QueryKey,
|
|
147
|
-
queryFn: () => this.fetchById(id),
|
|
148
|
-
...cacheOptions,
|
|
149
|
-
});
|
|
150
|
-
} catch {
|
|
151
|
-
return undefined;
|
|
152
|
-
}
|
|
127
|
+
return queryMethods.queryById(this, id);
|
|
153
128
|
}
|
|
154
129
|
|
|
155
130
|
/**
|
|
156
131
|
* Prefetch all items
|
|
157
132
|
*/
|
|
158
133
|
async prefetchAll(params?: ListParams): Promise<void> {
|
|
159
|
-
|
|
160
|
-
const queryKey = params ? this.keys.list(params as Record<string, unknown>) : this.keys.lists();
|
|
161
|
-
const cacheOptions = this.getCacheOptions();
|
|
162
|
-
|
|
163
|
-
await client.prefetchQuery({
|
|
164
|
-
queryKey: queryKey as QueryKey,
|
|
165
|
-
queryFn: () => this.fetchAll(params),
|
|
166
|
-
...cacheOptions,
|
|
167
|
-
});
|
|
134
|
+
return queryMethods.prefetchAll(this, params);
|
|
168
135
|
}
|
|
169
136
|
|
|
170
137
|
/**
|
|
171
138
|
* Prefetch item by ID
|
|
172
139
|
*/
|
|
173
140
|
async prefetchById(id: string | number): Promise<void> {
|
|
174
|
-
|
|
175
|
-
const queryKey = this.keys.detail(id);
|
|
176
|
-
const cacheOptions = this.getCacheOptions();
|
|
177
|
-
|
|
178
|
-
await client.prefetchQuery({
|
|
179
|
-
queryKey: queryKey as QueryKey,
|
|
180
|
-
queryFn: () => this.fetchById(id),
|
|
181
|
-
...cacheOptions,
|
|
182
|
-
});
|
|
141
|
+
return queryMethods.prefetchById(this, id);
|
|
183
142
|
}
|
|
184
143
|
|
|
185
144
|
/**
|
|
186
145
|
* Invalidate all queries for this resource
|
|
187
146
|
*/
|
|
188
147
|
invalidateAll(): Promise<void> {
|
|
189
|
-
|
|
190
|
-
return client.invalidateQueries({
|
|
191
|
-
predicate: (query: { queryKey: readonly unknown[] }) => {
|
|
192
|
-
const key = query.queryKey[0] as string;
|
|
193
|
-
return key === this.resource;
|
|
194
|
-
},
|
|
195
|
-
});
|
|
148
|
+
return invalidationMethods.invalidateAll(this);
|
|
196
149
|
}
|
|
197
150
|
|
|
198
151
|
/**
|
|
199
152
|
* Invalidate list queries
|
|
200
153
|
*/
|
|
201
154
|
invalidateLists(): Promise<void> {
|
|
202
|
-
|
|
203
|
-
return client.invalidateQueries({
|
|
204
|
-
queryKey: this.keys.lists(),
|
|
205
|
-
});
|
|
155
|
+
return invalidationMethods.invalidateLists(this);
|
|
206
156
|
}
|
|
207
157
|
|
|
208
158
|
/**
|
|
209
159
|
* Invalidate detail query
|
|
210
160
|
*/
|
|
211
161
|
invalidateDetail(id: string | number): Promise<void> {
|
|
212
|
-
|
|
213
|
-
return client.invalidateQueries({
|
|
214
|
-
queryKey: this.keys.detail(id),
|
|
215
|
-
});
|
|
162
|
+
return invalidationMethods.invalidateDetail(this, id);
|
|
216
163
|
}
|
|
217
164
|
|
|
218
165
|
/**
|
|
219
166
|
* Set query data (optimistic update)
|
|
220
167
|
*/
|
|
221
168
|
setData(id: string | number, data: TData): void {
|
|
222
|
-
|
|
223
|
-
client.setQueryData(this.keys.detail(id), data);
|
|
169
|
+
invalidationMethods.setData(this, id, data);
|
|
224
170
|
}
|
|
225
171
|
|
|
226
172
|
/**
|
|
227
173
|
* Get query data from cache
|
|
228
174
|
*/
|
|
229
175
|
getData(id: string | number): TData | undefined {
|
|
230
|
-
|
|
231
|
-
return client.getQueryData<TData>(this.keys.detail(id));
|
|
176
|
+
return invalidationMethods.getData(this, id);
|
|
232
177
|
}
|
|
233
178
|
|
|
234
179
|
/**
|
|
235
180
|
* Remove query data from cache
|
|
236
181
|
*/
|
|
237
182
|
clearData(id: string | number): void {
|
|
238
|
-
|
|
239
|
-
client.setQueryData(this.keys.detail(id), undefined);
|
|
183
|
+
invalidationMethods.clearData(this, id);
|
|
240
184
|
}
|
|
241
185
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Repository Interface
|
|
3
|
+
* Defines the contract for repository implementations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
7
|
+
import type { QueryKeyFactory } from '../utils/QueryKeyFactory';
|
|
8
|
+
import type {
|
|
9
|
+
CreateParams,
|
|
10
|
+
UpdateParams,
|
|
11
|
+
ListParams,
|
|
12
|
+
RepositoryOptions,
|
|
13
|
+
} from './RepositoryTypes';
|
|
14
|
+
|
|
15
|
+
export interface IBaseRepository<TData, TCreateVariables, TUpdateVariables> {
|
|
16
|
+
/** Query client instance */
|
|
17
|
+
getClient(): QueryClient;
|
|
18
|
+
|
|
19
|
+
/** Resource name */
|
|
20
|
+
readonly resource: string;
|
|
21
|
+
|
|
22
|
+
/** Query key factory */
|
|
23
|
+
readonly keys: QueryKeyFactory;
|
|
24
|
+
|
|
25
|
+
/** Cache options */
|
|
26
|
+
getCacheOptions(): RepositoryOptions;
|
|
27
|
+
|
|
28
|
+
/** Abstract methods to be implemented by subclasses */
|
|
29
|
+
fetchAll(params?: ListParams): Promise<TData[]>;
|
|
30
|
+
fetchById(id: string | number): Promise<TData>;
|
|
31
|
+
create(data: CreateParams<TCreateVariables>): Promise<TData>;
|
|
32
|
+
update(id: string | number, data: UpdateParams<TUpdateVariables>): Promise<TData>;
|
|
33
|
+
remove(id: string | number): Promise<void>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Helper Functions
|
|
3
|
+
* Common helper functions for repository operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RepositoryOptions } from '../RepositoryTypes';
|
|
7
|
+
import { CacheStrategies } from '../../../infrastructure/config/QueryClientConfig';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Gets cache options for repository queries
|
|
11
|
+
*
|
|
12
|
+
* @param options - Repository options
|
|
13
|
+
* @returns Cache options with staleTime and gcTime
|
|
14
|
+
*/
|
|
15
|
+
export function getCacheOptions(
|
|
16
|
+
options: RepositoryOptions
|
|
17
|
+
): { staleTime: number; gcTime: number } {
|
|
18
|
+
return {
|
|
19
|
+
staleTime:
|
|
20
|
+
options.staleTime ??
|
|
21
|
+
options.cacheStrategy?.staleTime ??
|
|
22
|
+
CacheStrategies.PUBLIC_DATA.staleTime,
|
|
23
|
+
gcTime:
|
|
24
|
+
options.gcTime ??
|
|
25
|
+
options.cacheStrategy?.gcTime ??
|
|
26
|
+
CacheStrategies.PUBLIC_DATA.gcTime,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Merges repository options with defaults
|
|
32
|
+
*
|
|
33
|
+
* @param options - User provided options
|
|
34
|
+
* @returns Merged options
|
|
35
|
+
*/
|
|
36
|
+
export function mergeRepositoryOptions(
|
|
37
|
+
options: RepositoryOptions = {}
|
|
38
|
+
): Required<Pick<RepositoryOptions, 'cacheStrategy'>> & RepositoryOptions {
|
|
39
|
+
return {
|
|
40
|
+
cacheStrategy: options.cacheStrategy ?? CacheStrategies.PUBLIC_DATA,
|
|
41
|
+
...options,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Checks if a query key matches a resource
|
|
47
|
+
*
|
|
48
|
+
* @param queryKey - Query key to check
|
|
49
|
+
* @param resource - Resource name to match
|
|
50
|
+
* @returns True if query key matches resource
|
|
51
|
+
*/
|
|
52
|
+
export function matchesResource(
|
|
53
|
+
queryKey: readonly unknown[],
|
|
54
|
+
resource: string
|
|
55
|
+
): boolean {
|
|
56
|
+
const key = queryKey[0] as string;
|
|
57
|
+
return key === resource;
|
|
58
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Invalidation Methods
|
|
3
|
+
* Cache invalidation methods for repository operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
7
|
+
import type { IBaseRepository } from '../IBaseRepository';
|
|
8
|
+
import { matchesResource } from '../helpers/repositoryHelpers';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Invalidate all queries for this resource
|
|
12
|
+
*
|
|
13
|
+
* @param repository - Repository instance
|
|
14
|
+
*/
|
|
15
|
+
export function invalidateAll<TData>(
|
|
16
|
+
repository: IBaseRepository<TData, unknown, unknown>
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
const client = (repository as any).getClient() as QueryClient;
|
|
19
|
+
const resource = (repository as any).resource;
|
|
20
|
+
|
|
21
|
+
return client.invalidateQueries({
|
|
22
|
+
predicate: (query: { queryKey: readonly unknown[] }) => {
|
|
23
|
+
return matchesResource(query.queryKey, resource);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Invalidate list queries
|
|
30
|
+
*
|
|
31
|
+
* @param repository - Repository instance
|
|
32
|
+
*/
|
|
33
|
+
export function invalidateLists<TData>(
|
|
34
|
+
repository: IBaseRepository<TData, unknown, unknown>
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
const client = (repository as any).getClient() as QueryClient;
|
|
37
|
+
return client.invalidateQueries({
|
|
38
|
+
queryKey: repository.keys.lists(),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Invalidate detail query
|
|
44
|
+
*
|
|
45
|
+
* @param repository - Repository instance
|
|
46
|
+
* @param id - Item ID
|
|
47
|
+
*/
|
|
48
|
+
export function invalidateDetail<TData>(
|
|
49
|
+
repository: IBaseRepository<TData, unknown, unknown>,
|
|
50
|
+
id: string | number
|
|
51
|
+
): Promise<void> {
|
|
52
|
+
const client = (repository as any).getClient() as QueryClient;
|
|
53
|
+
return client.invalidateQueries({
|
|
54
|
+
queryKey: repository.keys.detail(id),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Set query data (optimistic update)
|
|
60
|
+
*
|
|
61
|
+
* @param repository - Repository instance
|
|
62
|
+
* @param id - Item ID
|
|
63
|
+
* @param data - Data to set
|
|
64
|
+
*/
|
|
65
|
+
export function setData<TData>(
|
|
66
|
+
repository: BaseRepository<TData, unknown, unknown>,
|
|
67
|
+
id: string | number,
|
|
68
|
+
data: TData
|
|
69
|
+
): void {
|
|
70
|
+
const client = (repository as any).getClient() as QueryClient;
|
|
71
|
+
client.setQueryData(repository.keys.detail(id), data);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get query data from cache
|
|
76
|
+
*
|
|
77
|
+
* @param repository - Repository instance
|
|
78
|
+
* @param id - Item ID
|
|
79
|
+
* @returns Cached data or undefined
|
|
80
|
+
*/
|
|
81
|
+
export function getData<TData>(
|
|
82
|
+
repository: BaseRepository<TData, unknown, unknown>,
|
|
83
|
+
id: string | number
|
|
84
|
+
): TData | undefined {
|
|
85
|
+
const client = (repository as any).getClient() as QueryClient;
|
|
86
|
+
return client.getQueryData<TData>(repository.keys.detail(id));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Remove query data from cache
|
|
91
|
+
*
|
|
92
|
+
* @param repository - Repository instance
|
|
93
|
+
* @param id - Item ID
|
|
94
|
+
*/
|
|
95
|
+
export function clearData<TData>(
|
|
96
|
+
repository: BaseRepository<TData, unknown, unknown>,
|
|
97
|
+
id: string | number
|
|
98
|
+
): void {
|
|
99
|
+
const client = (repository as any).getClient() as QueryClient;
|
|
100
|
+
client.setQueryData(repository.keys.detail(id), undefined);
|
|
101
|
+
}
|