@umituz/react-native-design-system 4.23.82 → 4.23.84
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/input/hooks/useInputState.ts +2 -4
- package/src/image/presentation/components/editor/text-editor/TextTransformTab.tsx +40 -152
- package/src/image/presentation/components/editor/text-editor/components/TransformButtonRow.tsx +124 -0
- package/src/layouts/Grid/Grid.tsx +16 -11
- package/src/media/domain/utils/FileValidator.ts +156 -0
- package/src/media/infrastructure/services/MediaPickerService.ts +18 -57
- package/src/media/infrastructure/utils/PermissionManager.ts +92 -0
- package/src/media/infrastructure/utils/file-media-utils.ts +25 -8
- package/src/media/presentation/hooks/useMedia.ts +5 -4
- package/src/molecules/alerts/AlertBanner.tsx +9 -25
- package/src/molecules/alerts/AlertInline.tsx +4 -23
- package/src/molecules/alerts/AlertModal.tsx +4 -11
- package/src/molecules/alerts/AlertToast.tsx +14 -13
- package/src/molecules/alerts/utils/alertUtils.ts +133 -0
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +65 -25
- package/src/molecules/countdown/hooks/useCountdown.ts +13 -5
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +15 -123
- package/src/molecules/swipe-actions/domain/utils/swipeActionHelpers.ts +109 -0
- package/src/molecules/swipe-actions/domain/utils/swipeActionValidator.ts +54 -0
- package/src/molecules/swipe-actions/presentation/components/SwipeActionButton.tsx +24 -6
- package/src/offline/presentation/hooks/useOffline.ts +2 -1
- package/src/storage/domain/utils/devUtils.ts +7 -6
- package/src/storage/infrastructure/adapters/StorageService.ts +0 -9
- package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +0 -3
- package/src/tanstack/domain/utils/MetricsCalculator.ts +103 -0
- package/src/tanstack/infrastructure/monitoring/DevMonitor.ts +35 -29
- package/src/timezone/infrastructure/utils/SimpleCache.ts +24 -2
- package/src/molecules/alerts/utils/alertToastHelpers.ts +0 -70
|
@@ -44,6 +44,14 @@ export function useCountdown(
|
|
|
44
44
|
);
|
|
45
45
|
|
|
46
46
|
const expiredRef = useRef(false);
|
|
47
|
+
const onExpireRef = useRef(onExpire);
|
|
48
|
+
const onTickRef = useRef(onTick);
|
|
49
|
+
|
|
50
|
+
// Keep refs in sync with latest callbacks
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
onExpireRef.current = onExpire;
|
|
53
|
+
onTickRef.current = onTick;
|
|
54
|
+
}, [onExpire, onTick]);
|
|
47
55
|
|
|
48
56
|
const updateTime = useCallback(() => {
|
|
49
57
|
if (!target) return;
|
|
@@ -51,18 +59,18 @@ export function useCountdown(
|
|
|
51
59
|
const remaining = calculateTimeRemaining(target.date);
|
|
52
60
|
setTimeRemaining(remaining);
|
|
53
61
|
|
|
54
|
-
if (
|
|
55
|
-
|
|
62
|
+
if (onTickRef.current) {
|
|
63
|
+
onTickRef.current(remaining);
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
if (remaining.isExpired && !expiredRef.current) {
|
|
59
67
|
expiredRef.current = true;
|
|
60
68
|
setIsActive(false);
|
|
61
|
-
if (
|
|
62
|
-
|
|
69
|
+
if (onExpireRef.current) {
|
|
70
|
+
onExpireRef.current();
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
|
-
}, [target
|
|
73
|
+
}, [target]);
|
|
66
74
|
|
|
67
75
|
useEffect(() => {
|
|
68
76
|
if (!isActive || !target) return;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Swipe Actions Domain - Entity Layer
|
|
3
3
|
*
|
|
4
4
|
* Core swipe action types and configurations.
|
|
5
|
-
* Defines pre-built action types, colors, icons, and utilities.
|
|
6
5
|
*
|
|
7
6
|
* @domain swipe-actions
|
|
8
7
|
* @layer domain/entities
|
|
@@ -61,53 +60,6 @@ export interface SwipeableConfig {
|
|
|
61
60
|
friction?: number;
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
/**
|
|
65
|
-
* Pre-built action type configurations
|
|
66
|
-
*/
|
|
67
|
-
export const ACTION_PRESETS: Record<Exclude<SwipeActionType, 'custom'>, {
|
|
68
|
-
label: string;
|
|
69
|
-
icon: IconName;
|
|
70
|
-
colorKey: 'error' | 'success' | 'primary' | 'secondary' | 'warning' | 'textSecondary';
|
|
71
|
-
hapticsIntensity: 'Light' | 'Medium' | 'Heavy';
|
|
72
|
-
}> = {
|
|
73
|
-
delete: {
|
|
74
|
-
label: 'Delete',
|
|
75
|
-
icon: 'Trash2',
|
|
76
|
-
colorKey: 'error',
|
|
77
|
-
hapticsIntensity: 'Heavy',
|
|
78
|
-
},
|
|
79
|
-
archive: {
|
|
80
|
-
label: 'Archive',
|
|
81
|
-
icon: 'Archive',
|
|
82
|
-
colorKey: 'success',
|
|
83
|
-
hapticsIntensity: 'Medium',
|
|
84
|
-
},
|
|
85
|
-
edit: {
|
|
86
|
-
label: 'Edit',
|
|
87
|
-
icon: 'Pencil',
|
|
88
|
-
colorKey: 'primary',
|
|
89
|
-
hapticsIntensity: 'Light',
|
|
90
|
-
},
|
|
91
|
-
share: {
|
|
92
|
-
label: 'Share',
|
|
93
|
-
icon: 'Share2',
|
|
94
|
-
colorKey: 'secondary',
|
|
95
|
-
hapticsIntensity: 'Light',
|
|
96
|
-
},
|
|
97
|
-
favorite: {
|
|
98
|
-
label: 'Favorite',
|
|
99
|
-
icon: 'Heart',
|
|
100
|
-
colorKey: 'warning',
|
|
101
|
-
hapticsIntensity: 'Light',
|
|
102
|
-
},
|
|
103
|
-
more: {
|
|
104
|
-
label: 'More',
|
|
105
|
-
icon: 'MoveHorizontal',
|
|
106
|
-
colorKey: 'textSecondary',
|
|
107
|
-
hapticsIntensity: 'Light',
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
|
|
111
63
|
/**
|
|
112
64
|
* Default swipe configuration
|
|
113
65
|
*/
|
|
@@ -117,78 +69,18 @@ export const DEFAULT_SWIPE_CONFIG: Required<Omit<SwipeableConfig, 'leftActions'
|
|
|
117
69
|
friction: 2,
|
|
118
70
|
};
|
|
119
71
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* Validates swipe action configuration
|
|
136
|
-
*/
|
|
137
|
-
static validateAction(action: SwipeActionConfig): boolean {
|
|
138
|
-
// Must have onPress handler
|
|
139
|
-
if (!action.onPress || typeof action.onPress !== 'function') {
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Custom actions must have label, icon, and color
|
|
144
|
-
if (action.type === 'custom') {
|
|
145
|
-
return !!(action.label && action.icon && action.color);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Gets action display label
|
|
153
|
-
*/
|
|
154
|
-
static getLabel(action: SwipeActionConfig): string {
|
|
155
|
-
if (action.label) {
|
|
156
|
-
return action.label;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const preset = this.getPreset(action.type);
|
|
160
|
-
return preset?.label || 'Action';
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Gets action icon name
|
|
165
|
-
*/
|
|
166
|
-
static getIcon(action: SwipeActionConfig): IconName {
|
|
167
|
-
if (action.icon) {
|
|
168
|
-
return action.icon;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const preset = this.getPreset(action.type);
|
|
172
|
-
return preset?.icon || 'MoveHorizontal';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Gets action color key for theme
|
|
177
|
-
*/
|
|
178
|
-
static getColorKey(action: SwipeActionConfig): string | null {
|
|
179
|
-
if (action.color) {
|
|
180
|
-
return null; // Use custom color
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const preset = this.getPreset(action.type);
|
|
184
|
-
return preset?.colorKey || null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Gets haptics intensity for action
|
|
189
|
-
*/
|
|
190
|
-
static getHapticsIntensity(action: SwipeActionConfig): 'Light' | 'Medium' | 'Heavy' {
|
|
191
|
-
const preset = this.getPreset(action.type);
|
|
192
|
-
return preset?.hapticsIntensity || 'Light';
|
|
193
|
-
}
|
|
194
|
-
}
|
|
72
|
+
// Re-export utilities for backward compatibility
|
|
73
|
+
export {
|
|
74
|
+
ACTION_PRESETS,
|
|
75
|
+
getPreset,
|
|
76
|
+
getActionLabel,
|
|
77
|
+
getActionIcon,
|
|
78
|
+
getActionColorKey,
|
|
79
|
+
getHapticsIntensity,
|
|
80
|
+
} from '../utils/swipeActionHelpers';
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
validateSwipeAction,
|
|
84
|
+
validateSwipeActions,
|
|
85
|
+
getValidationError,
|
|
86
|
+
} from '../utils/swipeActionValidator';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe Action Helpers
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for swipe action styling and behavior.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { IconName } from '../../../../atoms/icon';
|
|
8
|
+
import type { SwipeActionType } from '../entities/SwipeAction';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Pre-built action type configurations
|
|
12
|
+
*/
|
|
13
|
+
export const ACTION_PRESETS: Record<Exclude<SwipeActionType, 'custom'>, {
|
|
14
|
+
label: string;
|
|
15
|
+
icon: IconName;
|
|
16
|
+
colorKey: 'error' | 'success' | 'primary' | 'secondary' | 'warning' | 'textSecondary';
|
|
17
|
+
hapticsIntensity: 'Light' | 'Medium' | 'Heavy';
|
|
18
|
+
}> = {
|
|
19
|
+
delete: {
|
|
20
|
+
label: 'Delete',
|
|
21
|
+
icon: 'Trash2',
|
|
22
|
+
colorKey: 'error',
|
|
23
|
+
hapticsIntensity: 'Heavy',
|
|
24
|
+
},
|
|
25
|
+
archive: {
|
|
26
|
+
label: 'Archive',
|
|
27
|
+
icon: 'Archive',
|
|
28
|
+
colorKey: 'success',
|
|
29
|
+
hapticsIntensity: 'Medium',
|
|
30
|
+
},
|
|
31
|
+
edit: {
|
|
32
|
+
label: 'Edit',
|
|
33
|
+
icon: 'Pencil',
|
|
34
|
+
colorKey: 'primary',
|
|
35
|
+
hapticsIntensity: 'Light',
|
|
36
|
+
},
|
|
37
|
+
share: {
|
|
38
|
+
label: 'Share',
|
|
39
|
+
icon: 'Share2',
|
|
40
|
+
colorKey: 'secondary',
|
|
41
|
+
hapticsIntensity: 'Light',
|
|
42
|
+
},
|
|
43
|
+
favorite: {
|
|
44
|
+
label: 'Favorite',
|
|
45
|
+
icon: 'Heart',
|
|
46
|
+
colorKey: 'warning',
|
|
47
|
+
hapticsIntensity: 'Light',
|
|
48
|
+
},
|
|
49
|
+
more: {
|
|
50
|
+
label: 'More',
|
|
51
|
+
icon: 'MoveHorizontal',
|
|
52
|
+
colorKey: 'textSecondary',
|
|
53
|
+
hapticsIntensity: 'Light',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Gets preset configuration for action type
|
|
59
|
+
*/
|
|
60
|
+
export function getPreset(type: SwipeActionType) {
|
|
61
|
+
if (type === 'custom') {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return ACTION_PRESETS[type];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets action display label
|
|
69
|
+
*/
|
|
70
|
+
export function getActionLabel(action: { type: SwipeActionType; label?: string }): string {
|
|
71
|
+
if (action.label) {
|
|
72
|
+
return action.label;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const preset = getPreset(action.type);
|
|
76
|
+
return preset?.label || 'Action';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Gets action icon name
|
|
81
|
+
*/
|
|
82
|
+
export function getActionIcon(action: { type: SwipeActionType; icon?: IconName }): IconName {
|
|
83
|
+
if (action.icon) {
|
|
84
|
+
return action.icon;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const preset = getPreset(action.type);
|
|
88
|
+
return preset?.icon || 'MoveHorizontal';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Gets action color key for theme
|
|
93
|
+
*/
|
|
94
|
+
export function getActionColorKey(action: { type: SwipeActionType; color?: string }): string | null {
|
|
95
|
+
if (action.color) {
|
|
96
|
+
return null; // Use custom color
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const preset = getPreset(action.type);
|
|
100
|
+
return preset?.colorKey || null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Gets haptics intensity for action
|
|
105
|
+
*/
|
|
106
|
+
export function getHapticsIntensity(action: { type: SwipeActionType; enableHaptics?: boolean }): 'Light' | 'Medium' | 'Heavy' {
|
|
107
|
+
const preset = getPreset(action.type);
|
|
108
|
+
return preset?.hapticsIntensity || 'Light';
|
|
109
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe Action Validator
|
|
3
|
+
*
|
|
4
|
+
* Validation functions for swipe action configurations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SwipeActionConfig } from '../entities/SwipeAction';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validates swipe action configuration
|
|
11
|
+
*/
|
|
12
|
+
export function validateSwipeAction(action: SwipeActionConfig): boolean {
|
|
13
|
+
// Must have onPress handler
|
|
14
|
+
if (!action.onPress || typeof action.onPress !== 'function') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Custom actions must have label, icon, and color
|
|
19
|
+
if (action.type === 'custom') {
|
|
20
|
+
return !!(action.label && action.icon && action.color);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validates multiple swipe actions
|
|
28
|
+
*/
|
|
29
|
+
export function validateSwipeActions(actions: SwipeActionConfig[]): boolean {
|
|
30
|
+
return actions.every(validateSwipeAction);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Gets validation error message for an action
|
|
35
|
+
*/
|
|
36
|
+
export function getValidationError(action: SwipeActionConfig): string | null {
|
|
37
|
+
if (!action.onPress || typeof action.onPress !== 'function') {
|
|
38
|
+
return 'Action must have an onPress handler';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (action.type === 'custom') {
|
|
42
|
+
if (!action.label) {
|
|
43
|
+
return 'Custom action must have a label';
|
|
44
|
+
}
|
|
45
|
+
if (!action.icon) {
|
|
46
|
+
return 'Custom action must have an icon';
|
|
47
|
+
}
|
|
48
|
+
if (!action.color) {
|
|
49
|
+
return 'Custom action must have a color';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
@@ -14,7 +14,12 @@ import { AtomicText, AtomicIcon } from '../../../../atoms';
|
|
|
14
14
|
import { useAppDesignTokens } from '../../../../theme/hooks/useAppDesignTokens';
|
|
15
15
|
import { HapticService } from '../../../../haptics';
|
|
16
16
|
import type { SwipeActionConfig } from '../../domain/entities/SwipeAction';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getActionLabel,
|
|
19
|
+
getActionIcon,
|
|
20
|
+
getActionColorKey,
|
|
21
|
+
getHapticsIntensity,
|
|
22
|
+
} from '../../domain/entities/SwipeAction';
|
|
18
23
|
|
|
19
24
|
/**
|
|
20
25
|
* SwipeActionButton component props
|
|
@@ -58,19 +63,32 @@ export const SwipeActionButton: React.FC<SwipeActionButtonProps> = ({
|
|
|
58
63
|
const tokens = useAppDesignTokens();
|
|
59
64
|
|
|
60
65
|
// Get action properties
|
|
61
|
-
const label =
|
|
62
|
-
const iconName =
|
|
63
|
-
const colorKey =
|
|
66
|
+
const label = getActionLabel(action);
|
|
67
|
+
const iconName = getActionIcon(action);
|
|
68
|
+
const colorKey = getActionColorKey(action);
|
|
64
69
|
const customColor = action.color;
|
|
65
70
|
const enableHaptics = action.enableHaptics !== false;
|
|
66
71
|
|
|
67
72
|
// Get background color from theme or custom
|
|
68
|
-
|
|
73
|
+
// Type-safe color lookup with fallback
|
|
74
|
+
const getBackgroundColor = (): string => {
|
|
75
|
+
if (customColor) {
|
|
76
|
+
return customColor;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (colorKey && colorKey in tokens.colors) {
|
|
80
|
+
return tokens.colors[colorKey as keyof typeof tokens.colors];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return tokens.colors.primary;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const backgroundColor = getBackgroundColor();
|
|
69
87
|
|
|
70
88
|
const handlePress = async () => {
|
|
71
89
|
// Trigger haptic feedback
|
|
72
90
|
if (enableHaptics) {
|
|
73
|
-
const intensity =
|
|
91
|
+
const intensity = getHapticsIntensity(action);
|
|
74
92
|
await HapticService.impact(intensity);
|
|
75
93
|
}
|
|
76
94
|
|
|
@@ -56,7 +56,7 @@ export const useOffline = (config?: OfflineConfig) => {
|
|
|
56
56
|
|
|
57
57
|
networkEvents.emit('change', networkState);
|
|
58
58
|
previousStateRef.current = networkState;
|
|
59
|
-
}, [store]);
|
|
59
|
+
}, [store, previousStateRef]);
|
|
60
60
|
|
|
61
61
|
useEffect(() => {
|
|
62
62
|
if (isInitialized.current) return;
|
|
@@ -72,6 +72,7 @@ export const useOffline = (config?: OfflineConfig) => {
|
|
|
72
72
|
})
|
|
73
73
|
.catch((_error: Error) => {
|
|
74
74
|
if (isMountedRef.current && (__DEV__ || mergedConfig.debug)) {
|
|
75
|
+
console.error('[DesignSystem] useOffline: Failed to get initial network state', _error);
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
|
|
@@ -11,24 +11,25 @@ export const isDev = (): boolean => {
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Log warning in development mode only
|
|
14
|
+
* All logs are disabled
|
|
14
15
|
*/
|
|
15
16
|
export const devWarn = (_message: string, ..._args: unknown[]): void => {
|
|
16
|
-
|
|
17
|
-
}
|
|
17
|
+
// Disabled
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Log error in development mode only
|
|
22
|
+
* All logs are disabled
|
|
22
23
|
*/
|
|
23
24
|
export const devError = (_message: string, ..._args: unknown[]): void => {
|
|
24
|
-
|
|
25
|
-
}
|
|
25
|
+
// Disabled
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Log info in development mode only
|
|
30
|
+
* All logs are disabled
|
|
30
31
|
*/
|
|
31
32
|
export const devLog = (_message: string, ..._args: unknown[]): void => {
|
|
32
|
-
|
|
33
|
-
}
|
|
33
|
+
// Disabled
|
|
34
34
|
};
|
|
35
|
+
|
|
@@ -20,9 +20,6 @@ export const storageService: StateStorage = {
|
|
|
20
20
|
} catch (error) {
|
|
21
21
|
const errorMessage = `StorageService: Failed to get item "${name}"`;
|
|
22
22
|
devWarn(errorMessage, error);
|
|
23
|
-
// Also log in production for debugging
|
|
24
|
-
if (!__DEV__) {
|
|
25
|
-
}
|
|
26
23
|
return null;
|
|
27
24
|
}
|
|
28
25
|
},
|
|
@@ -33,9 +30,6 @@ export const storageService: StateStorage = {
|
|
|
33
30
|
} catch (error) {
|
|
34
31
|
const errorMessage = `StorageService: Failed to set item "${name}"`;
|
|
35
32
|
devWarn(errorMessage, error);
|
|
36
|
-
// Also log in production for debugging
|
|
37
|
-
if (!__DEV__) {
|
|
38
|
-
}
|
|
39
33
|
}
|
|
40
34
|
},
|
|
41
35
|
|
|
@@ -45,9 +39,6 @@ export const storageService: StateStorage = {
|
|
|
45
39
|
} catch (error) {
|
|
46
40
|
const errorMessage = `StorageService: Failed to remove item "${name}"`;
|
|
47
41
|
devWarn(errorMessage, error);
|
|
48
|
-
// Also log in production for debugging
|
|
49
|
-
if (!__DEV__) {
|
|
50
|
-
}
|
|
51
42
|
}
|
|
52
43
|
},
|
|
53
44
|
};
|
|
@@ -83,9 +83,6 @@ export class BaseStorageOperations {
|
|
|
83
83
|
} catch (error) {
|
|
84
84
|
const errorMessage = `BaseStorageOperations: Failed to check if key "${key}" exists`;
|
|
85
85
|
devWarn(errorMessage, error);
|
|
86
|
-
// Also log in production for debugging
|
|
87
|
-
if (!__DEV__) {
|
|
88
|
-
}
|
|
89
86
|
return false;
|
|
90
87
|
}
|
|
91
88
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Calculator
|
|
3
|
+
*
|
|
4
|
+
* Calculates query performance metrics for monitoring.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Query } from '@tanstack/react-query';
|
|
8
|
+
import type { QueryMetrics, DevMonitorOptions } from '../../infrastructure/monitoring/DevMonitor.types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Metrics calculator for query performance
|
|
12
|
+
*/
|
|
13
|
+
export class MetricsCalculator {
|
|
14
|
+
/**
|
|
15
|
+
* Gets unique key string for query
|
|
16
|
+
*/
|
|
17
|
+
static getQueryKeyString(queryKey: readonly unknown[]): string {
|
|
18
|
+
return JSON.stringify(queryKey);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Calculates fetch time for a query
|
|
23
|
+
*/
|
|
24
|
+
static calculateFetchTime(query: Query): number {
|
|
25
|
+
return Date.now() - (query.state.dataUpdatedAt ?? Date.now());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates initial metrics for a query
|
|
30
|
+
*/
|
|
31
|
+
static createInitialMetrics(query: Query): QueryMetrics {
|
|
32
|
+
return {
|
|
33
|
+
queryKey: query.queryKey,
|
|
34
|
+
fetchCount: 0,
|
|
35
|
+
totalFetchTime: 0,
|
|
36
|
+
averageFetchTime: 0,
|
|
37
|
+
slowFetchCount: 0,
|
|
38
|
+
lastFetchTime: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Updates metrics with new fetch data
|
|
44
|
+
*/
|
|
45
|
+
static updateMetrics(
|
|
46
|
+
current: QueryMetrics,
|
|
47
|
+
fetchTime: number
|
|
48
|
+
): QueryMetrics {
|
|
49
|
+
const newFetchCount = current.fetchCount + 1;
|
|
50
|
+
const newTotalFetchTime = current.totalFetchTime + fetchTime;
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...current,
|
|
54
|
+
fetchCount: newFetchCount,
|
|
55
|
+
totalFetchTime: newTotalFetchTime,
|
|
56
|
+
averageFetchTime: newTotalFetchTime / newFetchCount,
|
|
57
|
+
lastFetchTime: fetchTime,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Checks if fetch time exceeds slow query threshold
|
|
63
|
+
*/
|
|
64
|
+
static isSlowQuery(fetchTime: number, threshold: number): boolean {
|
|
65
|
+
return fetchTime > threshold;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Increments slow fetch count if query is slow
|
|
70
|
+
*/
|
|
71
|
+
static incrementSlowCountIfNeeded(
|
|
72
|
+
metrics: QueryMetrics,
|
|
73
|
+
fetchTime: number,
|
|
74
|
+
threshold: number
|
|
75
|
+
): QueryMetrics {
|
|
76
|
+
if (this.isSlowQuery(fetchTime, threshold)) {
|
|
77
|
+
return {
|
|
78
|
+
...metrics,
|
|
79
|
+
slowFetchCount: metrics.slowFetchCount + 1,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return metrics;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Calculates complete metrics for a query
|
|
87
|
+
*/
|
|
88
|
+
static calculateQueryMetrics(
|
|
89
|
+
query: Query,
|
|
90
|
+
currentMetrics: QueryMetrics | null,
|
|
91
|
+
options: Required<DevMonitorOptions>
|
|
92
|
+
): QueryMetrics {
|
|
93
|
+
const fetchTime = this.calculateFetchTime(query);
|
|
94
|
+
const baseMetrics = currentMetrics ?? this.createInitialMetrics(query);
|
|
95
|
+
const updatedMetrics = this.updateMetrics(baseMetrics, fetchTime);
|
|
96
|
+
|
|
97
|
+
return this.incrementSlowCountIfNeeded(
|
|
98
|
+
updatedMetrics,
|
|
99
|
+
fetchTime,
|
|
100
|
+
options.slowQueryThreshold
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|