@umituz/react-native-design-system 4.27.14 → 4.27.16
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 +11 -6
- package/src/atoms/GlassView/GlassView.tsx +0 -2
- package/src/core/cache/domain/CleanupStrategy.ts +115 -0
- package/src/core/cache/domain/UnifiedCache.ts +196 -0
- package/src/core/cache/domain/types.ts +18 -0
- package/src/core/cache/index.ts +17 -0
- package/src/core/cache/infrastructure/CacheFactory.ts +102 -0
- package/src/core/index.ts +23 -0
- package/src/core/permissions/domain/PermissionHandler.ts +139 -0
- package/src/core/permissions/domain/types.ts +49 -0
- package/src/core/permissions/index.ts +15 -0
- package/src/core/repositories/domain/RepositoryKeyFactory.ts +41 -0
- package/src/core/repositories/domain/RepositoryUtils.ts +86 -0
- package/src/core/repositories/domain/types.ts +59 -0
- package/src/core/repositories/index.ts +23 -0
- package/src/device/detection/deviceDetection.ts +8 -2
- package/src/haptics/infrastructure/services/HapticService.ts +4 -1
- package/src/hooks/index.ts +22 -25
- package/src/index.ts +1 -0
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +32 -29
- package/src/media/domain/strategies/CameraPickerStrategy.ts +61 -0
- package/src/media/domain/strategies/LibraryPickerStrategy.ts +54 -0
- package/src/media/domain/strategies/PickerStrategy.ts +61 -0
- package/src/media/domain/strategies/index.ts +13 -0
- package/src/media/infrastructure/services/MediaPickerService.ts +109 -110
- package/src/media/infrastructure/utils/PermissionManager.ts +68 -66
- package/src/media/infrastructure/utils/mediaPickerMappers.ts +29 -6
- package/src/media/presentation/hooks/useMedia.ts +16 -4
- package/src/molecules/swipe-actions/domain/entities/SwipeAction.ts +0 -1
- package/src/offline/index.ts +1 -1
- package/src/offline/presentation/hooks/useOffline.ts +0 -8
- package/src/storage/README.md +0 -1
- package/src/storage/cache/domain/types/README.md +0 -1
- package/src/storage/cache/infrastructure/TTLCache.ts +3 -0
- package/src/storage/cache/presentation/README.md +0 -1
- package/src/storage/cache/presentation/useCachedValue.ts +12 -2
- package/src/storage/domain/constants/README.md +0 -1
- package/src/storage/infrastructure/adapters/README.md +0 -1
- package/src/storage/presentation/hooks/README.md +0 -6
- package/src/storage/presentation/hooks/useStorageState.ts +13 -4
- package/src/tanstack/domain/repositories/BaseRepository.ts +18 -1
- package/src/theme/hooks/useAppDesignTokens.ts +29 -3
- package/src/timezone/infrastructure/services/TimezoneProvider.ts +2 -2
- package/src/uuid/infrastructure/utils/UUIDUtils.ts +4 -1
- package/src/init/index.ts +0 -29
- package/src/layouts/ScreenLayout/index.ts +0 -1
- package/src/layouts/index.ts +0 -5
- package/src/molecules/SearchBar/index.ts +0 -4
- package/src/molecules/StepProgress/index.ts +0 -1
- package/src/molecules/alerts/index.ts +0 -47
- package/src/molecules/bottom-sheet/index.ts +0 -10
- package/src/molecules/circular-menu/index.ts +0 -3
- package/src/molecules/filter-group/index.ts +0 -3
- package/src/molecules/index.ts +0 -38
- package/src/molecules/info-grid/index.ts +0 -3
- package/src/molecules/swipe-actions/index.ts +0 -6
- package/src/presentation/utils/variants/index.ts +0 -6
- package/src/timezone/infrastructure/utils/SimpleCache.ts +0 -109
- package/src/utilities/index.ts +0 -6
|
@@ -84,7 +84,6 @@ Constant values for time-based calculations and default configuration. Located a
|
|
|
84
84
|
- MUST increment CACHE_VERSION on schema changes
|
|
85
85
|
- MUST implement migration for old versions
|
|
86
86
|
- MUST document breaking changes
|
|
87
|
-
- MUST support backward compatibility where possible
|
|
88
87
|
|
|
89
88
|
### Export Rules
|
|
90
89
|
- MUST export all constants
|
|
@@ -137,7 +137,6 @@ Storage adapter implementations for Zustand persist middleware. Located at `src/
|
|
|
137
137
|
- MUST migrate data on read
|
|
138
138
|
- MUST clean up old data after migration
|
|
139
139
|
- MUST handle migration failures
|
|
140
|
-
- MUST be backward compatible
|
|
141
140
|
|
|
142
141
|
### Error Handling
|
|
143
142
|
- MUST catch all exceptions in adapter methods
|
|
@@ -120,9 +120,3 @@ This directory contains React hooks that integrate storage and cache functionali
|
|
|
120
120
|
- MUST use `useMemo` for computed values
|
|
121
121
|
- MUST implement proper dependency arrays
|
|
122
122
|
- MUST avoid unnecessary re-renders
|
|
123
|
-
|
|
124
|
-
### Deprecation
|
|
125
|
-
- MUST document deprecated hooks
|
|
126
|
-
- MUST provide migration path for deprecated hooks
|
|
127
|
-
- MUST maintain backward compatibility when possible
|
|
128
|
-
- MUST remove deprecated hooks after major version bump
|
|
@@ -28,20 +28,29 @@ export const useStorageState = <T>(
|
|
|
28
28
|
const [state, setState] = useState<T>(defaultValue);
|
|
29
29
|
const [isLoading, setIsLoading] = useState(true);
|
|
30
30
|
const isMountedRef = useRef(true);
|
|
31
|
+
const defaultValueRef = useRef(defaultValue);
|
|
32
|
+
|
|
33
|
+
// Update ref when defaultValue changes
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
defaultValueRef.current = defaultValue;
|
|
36
|
+
}, [defaultValue]);
|
|
31
37
|
|
|
32
38
|
useEffect(() => {
|
|
33
39
|
isMountedRef.current = true;
|
|
34
40
|
setIsLoading(true);
|
|
35
41
|
|
|
36
42
|
storageRepository
|
|
37
|
-
.getItem<T>(keyString,
|
|
43
|
+
.getItem<T>(keyString, defaultValueRef.current)
|
|
38
44
|
.then((result) => {
|
|
39
45
|
if (isMountedRef.current) {
|
|
40
|
-
setState(unwrap(result,
|
|
46
|
+
setState(unwrap(result, defaultValueRef.current));
|
|
41
47
|
}
|
|
42
48
|
})
|
|
43
|
-
.catch(() => {
|
|
49
|
+
.catch((error) => {
|
|
44
50
|
// Keep defaultValue on error
|
|
51
|
+
if (__DEV__) {
|
|
52
|
+
console.warn('[useStorageState] Failed to load from storage:', error);
|
|
53
|
+
}
|
|
45
54
|
})
|
|
46
55
|
.finally(() => {
|
|
47
56
|
if (isMountedRef.current) {
|
|
@@ -52,7 +61,7 @@ export const useStorageState = <T>(
|
|
|
52
61
|
return () => {
|
|
53
62
|
isMountedRef.current = false;
|
|
54
63
|
};
|
|
55
|
-
}, [keyString
|
|
64
|
+
}, [keyString]);
|
|
56
65
|
|
|
57
66
|
// Update state and persist to storage
|
|
58
67
|
const updateState = useCallback(
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Domain layer - Abstract repository for data operations
|
|
4
4
|
*
|
|
5
5
|
* Provides generic CRUD operations with TanStack Query integration.
|
|
6
|
-
*
|
|
6
|
+
* Now uses common repository utilities from core/repositories.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```typescript
|
|
@@ -47,6 +47,8 @@ import type {
|
|
|
47
47
|
import { mergeRepositoryOptions, getCacheOptions } from './helpers/repositoryHelpers';
|
|
48
48
|
import * as queryMethods from './mixins/repositoryQueryMethods';
|
|
49
49
|
import * as invalidationMethods from './mixins/repositoryInvalidationMethods';
|
|
50
|
+
// Import common utilities from core
|
|
51
|
+
import { createRepositoryLogger } from '../../../core/repositories/domain/RepositoryUtils';
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
54
|
* Base repository for CRUD operations
|
|
@@ -68,10 +70,16 @@ export abstract class BaseRepository<
|
|
|
68
70
|
*/
|
|
69
71
|
public readonly keys: ReturnType<typeof createQueryKeyFactory>;
|
|
70
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Debug logger for this repository
|
|
75
|
+
*/
|
|
76
|
+
protected readonly log: (method: string, ...args: unknown[]) => void;
|
|
77
|
+
|
|
71
78
|
constructor(resource: string, options: RepositoryOptions = {}) {
|
|
72
79
|
this.resource = resource;
|
|
73
80
|
this.options = mergeRepositoryOptions(options);
|
|
74
81
|
this.keys = createQueryKeyFactory(this.resource);
|
|
82
|
+
this.log = createRepositoryLogger(resource, __DEV__);
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
/**
|
|
@@ -117,6 +125,7 @@ export abstract class BaseRepository<
|
|
|
117
125
|
* Query all items with caching
|
|
118
126
|
*/
|
|
119
127
|
async queryAll(params?: ListParams): Promise<TData[]> {
|
|
128
|
+
this.log('queryAll', params);
|
|
120
129
|
return queryMethods.queryAll(this, params);
|
|
121
130
|
}
|
|
122
131
|
|
|
@@ -124,6 +133,7 @@ export abstract class BaseRepository<
|
|
|
124
133
|
* Query item by ID with caching
|
|
125
134
|
*/
|
|
126
135
|
async queryById(id: string | number): Promise<TData | undefined> {
|
|
136
|
+
this.log('queryById', id);
|
|
127
137
|
return queryMethods.queryById(this, id);
|
|
128
138
|
}
|
|
129
139
|
|
|
@@ -131,6 +141,7 @@ export abstract class BaseRepository<
|
|
|
131
141
|
* Prefetch all items
|
|
132
142
|
*/
|
|
133
143
|
async prefetchAll(params?: ListParams): Promise<void> {
|
|
144
|
+
this.log('prefetchAll', params);
|
|
134
145
|
return queryMethods.prefetchAll(this, params);
|
|
135
146
|
}
|
|
136
147
|
|
|
@@ -138,6 +149,7 @@ export abstract class BaseRepository<
|
|
|
138
149
|
* Prefetch item by ID
|
|
139
150
|
*/
|
|
140
151
|
async prefetchById(id: string | number): Promise<void> {
|
|
152
|
+
this.log('prefetchById', id);
|
|
141
153
|
return queryMethods.prefetchById(this, id);
|
|
142
154
|
}
|
|
143
155
|
|
|
@@ -145,6 +157,7 @@ export abstract class BaseRepository<
|
|
|
145
157
|
* Invalidate all queries for this resource
|
|
146
158
|
*/
|
|
147
159
|
invalidateAll(): Promise<void> {
|
|
160
|
+
this.log('invalidateAll');
|
|
148
161
|
return invalidationMethods.invalidateAll(this);
|
|
149
162
|
}
|
|
150
163
|
|
|
@@ -152,6 +165,7 @@ export abstract class BaseRepository<
|
|
|
152
165
|
* Invalidate list queries
|
|
153
166
|
*/
|
|
154
167
|
invalidateLists(): Promise<void> {
|
|
168
|
+
this.log('invalidateLists');
|
|
155
169
|
return invalidationMethods.invalidateLists(this);
|
|
156
170
|
}
|
|
157
171
|
|
|
@@ -159,6 +173,7 @@ export abstract class BaseRepository<
|
|
|
159
173
|
* Invalidate detail query
|
|
160
174
|
*/
|
|
161
175
|
invalidateDetail(id: string | number): Promise<void> {
|
|
176
|
+
this.log('invalidateDetail', id);
|
|
162
177
|
return invalidationMethods.invalidateDetail(this, id);
|
|
163
178
|
}
|
|
164
179
|
|
|
@@ -166,6 +181,7 @@ export abstract class BaseRepository<
|
|
|
166
181
|
* Set query data (optimistic update)
|
|
167
182
|
*/
|
|
168
183
|
setData(id: string | number, data: TData): void {
|
|
184
|
+
this.log('setData', id);
|
|
169
185
|
invalidationMethods.setData(this, id, data);
|
|
170
186
|
}
|
|
171
187
|
|
|
@@ -180,6 +196,7 @@ export abstract class BaseRepository<
|
|
|
180
196
|
* Remove query data from cache
|
|
181
197
|
*/
|
|
182
198
|
clearData(id: string | number): void {
|
|
199
|
+
this.log('clearData', id);
|
|
183
200
|
invalidationMethods.clearData(this, id);
|
|
184
201
|
}
|
|
185
202
|
}
|
|
@@ -1,8 +1,28 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { useMemo, useRef } from 'react';
|
|
2
2
|
import { useTheme } from '../infrastructure/stores/themeStore';
|
|
3
3
|
import { createDesignTokens } from '../core/TokenFactory';
|
|
4
4
|
import { useResponsive } from '../../responsive/useResponsive';
|
|
5
5
|
import { type DesignTokens } from '../types/ThemeTypes';
|
|
6
|
+
import type { CustomThemeColors } from '../core/CustomColors';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shallow equality check for CustomThemeColors
|
|
10
|
+
*/
|
|
11
|
+
function areCustomColorsEqual(a?: CustomThemeColors, b?: CustomThemeColors): boolean {
|
|
12
|
+
if (a === b) return true;
|
|
13
|
+
if (!a || !b) return false;
|
|
14
|
+
|
|
15
|
+
const keysA = Object.keys(a) as (keyof CustomThemeColors)[];
|
|
16
|
+
const keysB = Object.keys(b) as (keyof CustomThemeColors)[];
|
|
17
|
+
|
|
18
|
+
if (keysA.length !== keysB.length) return false;
|
|
19
|
+
|
|
20
|
+
for (const key of keysA) {
|
|
21
|
+
if (a[key] !== b[key]) return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
6
26
|
|
|
7
27
|
/**
|
|
8
28
|
* Hook to access current design tokens (colors, spacing, typography, etc.)
|
|
@@ -13,8 +33,14 @@ export const useAppDesignTokens = (): DesignTokens => {
|
|
|
13
33
|
const { themeMode, customColors } = useTheme();
|
|
14
34
|
const { spacingMultiplier, getFontSize } = useResponsive();
|
|
15
35
|
|
|
36
|
+
// Stabilize customColors reference to prevent unnecessary re-computation
|
|
37
|
+
const customColorsRef = useRef(customColors);
|
|
38
|
+
if (!areCustomColorsEqual(customColorsRef.current, customColors)) {
|
|
39
|
+
customColorsRef.current = customColors;
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
return useMemo(
|
|
17
|
-
() => createDesignTokens(themeMode,
|
|
18
|
-
[themeMode,
|
|
43
|
+
() => createDesignTokens(themeMode, customColorsRef.current, spacingMultiplier, getFontSize),
|
|
44
|
+
[themeMode, spacingMultiplier, getFontSize]
|
|
19
45
|
);
|
|
20
46
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { TimezoneInfo } from '../../domain/entities/Timezone';
|
|
2
|
-
import {
|
|
2
|
+
import { UnifiedCache } from '../../../core/cache/domain/UnifiedCache';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* TimezoneProvider
|
|
6
6
|
* Responsible for discovering device timezone and providing available timezones
|
|
7
7
|
*/
|
|
8
8
|
export class TimezoneProvider {
|
|
9
|
-
private cache = new
|
|
9
|
+
private cache = new UnifiedCache<TimezoneInfo[]>({ defaultTTL: 300000 }); // 5 min cache
|
|
10
10
|
/**
|
|
11
11
|
* Get current device timezone using Intl API
|
|
12
12
|
*/
|
|
@@ -17,7 +17,10 @@ const getCryptoModule = (): typeof import('expo-crypto') | null => {
|
|
|
17
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
18
|
_cryptoModule = require('expo-crypto') as typeof import('expo-crypto');
|
|
19
19
|
return _cryptoModule;
|
|
20
|
-
} catch {
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (__DEV__) {
|
|
22
|
+
console.warn('[UUIDUtils] expo-crypto not available, using Math.random fallback:', error);
|
|
23
|
+
}
|
|
21
24
|
return null;
|
|
22
25
|
}
|
|
23
26
|
};
|
package/src/init/index.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* App Initialization Module
|
|
3
|
-
*
|
|
4
|
-
* Provides utilities for app initialization:
|
|
5
|
-
* - createAppInitializer: Factory for creating app initializers
|
|
6
|
-
* - useAppInitialization: Hook for managing initialization state
|
|
7
|
-
* - createEnvConfig: Environment configuration factory
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// App Initializer
|
|
11
|
-
export {
|
|
12
|
-
createAppInitializer,
|
|
13
|
-
createInitModule,
|
|
14
|
-
} from "./createAppInitializer";
|
|
15
|
-
|
|
16
|
-
// Initialization Hook
|
|
17
|
-
export { useAppInitialization } from "./useAppInitialization";
|
|
18
|
-
|
|
19
|
-
// Environment Configuration
|
|
20
|
-
export * from "./env";
|
|
21
|
-
|
|
22
|
-
// Types
|
|
23
|
-
export type {
|
|
24
|
-
InitModule,
|
|
25
|
-
AppInitializerConfig,
|
|
26
|
-
AppInitializerResult,
|
|
27
|
-
UseAppInitializationOptions,
|
|
28
|
-
UseAppInitializationReturn,
|
|
29
|
-
} from "./types";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './ScreenLayout';
|
package/src/layouts/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./StepProgress";
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Alerts Molecule - Public API
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export * from './AlertTypes';
|
|
6
|
-
export { useAlertStore } from './AlertStore';
|
|
7
|
-
export { AlertService } from './AlertService';
|
|
8
|
-
export { AlertBanner } from './AlertBanner';
|
|
9
|
-
export { AlertToast } from './AlertToast';
|
|
10
|
-
export { AlertInline } from './AlertInline';
|
|
11
|
-
export { AlertModal } from './AlertModal';
|
|
12
|
-
export { AlertContainer } from './AlertContainer';
|
|
13
|
-
export { AlertProvider } from './AlertProvider';
|
|
14
|
-
export { useAlert } from './useAlert';
|
|
15
|
-
|
|
16
|
-
import { AlertService } from './AlertService';
|
|
17
|
-
import { useAlertStore } from './AlertStore';
|
|
18
|
-
import { AlertOptions } from './AlertTypes';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Convenience alert service for use outside of components
|
|
22
|
-
*/
|
|
23
|
-
export const alertService = {
|
|
24
|
-
error: (title: string, message?: string, options?: AlertOptions) => {
|
|
25
|
-
const alert = AlertService.createErrorAlert(title, message, options);
|
|
26
|
-
useAlertStore.getState().addAlert(alert);
|
|
27
|
-
return alert.id;
|
|
28
|
-
},
|
|
29
|
-
success: (title: string, message?: string, options?: AlertOptions) => {
|
|
30
|
-
const alert = AlertService.createSuccessAlert(title, message, options);
|
|
31
|
-
useAlertStore.getState().addAlert(alert);
|
|
32
|
-
return alert.id;
|
|
33
|
-
},
|
|
34
|
-
warning: (title: string, message?: string, options?: AlertOptions) => {
|
|
35
|
-
const alert = AlertService.createWarningAlert(title, message, options);
|
|
36
|
-
useAlertStore.getState().addAlert(alert);
|
|
37
|
-
return alert.id;
|
|
38
|
-
},
|
|
39
|
-
info: (title: string, message?: string, options?: AlertOptions) => {
|
|
40
|
-
const alert = AlertService.createInfoAlert(title, message, options);
|
|
41
|
-
useAlertStore.getState().addAlert(alert);
|
|
42
|
-
return alert.id;
|
|
43
|
-
},
|
|
44
|
-
dismiss: (id: string) => {
|
|
45
|
-
useAlertStore.getState().dismissAlert(id);
|
|
46
|
-
},
|
|
47
|
-
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export * from './components/BottomSheet';
|
|
2
|
-
export * from './components/BottomSheetModal';
|
|
3
|
-
|
|
4
|
-
export * from './components/filter/FilterBottomSheet';
|
|
5
|
-
export * from './components/filter/FilterSheet';
|
|
6
|
-
export * from './hooks/useBottomSheet';
|
|
7
|
-
export * from './hooks/useBottomSheetModal';
|
|
8
|
-
export * from './hooks/useListFilters';
|
|
9
|
-
export * from './types/BottomSheet';
|
|
10
|
-
export * from './types/Filter';
|
package/src/molecules/index.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Molecules - Composite UI components
|
|
3
|
-
* Built from atoms following atomic design principles
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Component exports
|
|
7
|
-
export * from './avatar';
|
|
8
|
-
export * from './bottom-sheet';
|
|
9
|
-
export { FormField, type FormFieldProps } from './FormField';
|
|
10
|
-
export { ListItem, type ListItemProps } from './ListItem';
|
|
11
|
-
export { SearchBar, type SearchBarProps } from './SearchBar';
|
|
12
|
-
export { IconContainer } from './IconContainer';
|
|
13
|
-
export { BaseModal, type BaseModalProps } from './BaseModal';
|
|
14
|
-
export { ConfirmationModal } from './ConfirmationModalMain';
|
|
15
|
-
export { useConfirmationModal } from './confirmation-modal/useConfirmationModal';
|
|
16
|
-
|
|
17
|
-
// Other components
|
|
18
|
-
export * from './Divider/Divider';
|
|
19
|
-
export * from './Divider/types';
|
|
20
|
-
export * from './StepProgress';
|
|
21
|
-
export * from './List';
|
|
22
|
-
export * from './alerts';
|
|
23
|
-
export * from './calendar';
|
|
24
|
-
export * from './swipe-actions';
|
|
25
|
-
export * from './navigation';
|
|
26
|
-
export * from './long-press-menu';
|
|
27
|
-
export * from './StepHeader';
|
|
28
|
-
export * from './emoji';
|
|
29
|
-
export * from './countdown';
|
|
30
|
-
export * from './splash';
|
|
31
|
-
export * from './filter-group';
|
|
32
|
-
export * from './action-footer/ActionFooter';
|
|
33
|
-
export * from './action-footer/types';
|
|
34
|
-
export * from './hero-section/HeroSection';
|
|
35
|
-
export * from './hero-section/types';
|
|
36
|
-
export * from './info-grid';
|
|
37
|
-
export * from './circular-menu';
|
|
38
|
-
export * from './icon-grid';
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SimpleCache
|
|
3
|
-
*
|
|
4
|
-
* Lightweight in-memory cache for performance optimization
|
|
5
|
-
* No external dependencies - pure TypeScript implementation
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { ONE_MINUTE_MS } from '../../../utils/constants/TimeConstants';
|
|
9
|
-
|
|
10
|
-
interface CacheEntry<T> {
|
|
11
|
-
value: T;
|
|
12
|
-
expires: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class SimpleCache<T> {
|
|
16
|
-
private cache = new Map<string, CacheEntry<T>>();
|
|
17
|
-
private defaultTTL: number;
|
|
18
|
-
private cleanupTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
19
|
-
private destroyed = false;
|
|
20
|
-
private cleanupScheduleLock = false;
|
|
21
|
-
|
|
22
|
-
constructor(defaultTTL: number = ONE_MINUTE_MS) {
|
|
23
|
-
this.defaultTTL = defaultTTL;
|
|
24
|
-
this.scheduleCleanup();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Destroy the cache and stop cleanup timer
|
|
29
|
-
*/
|
|
30
|
-
destroy(): void {
|
|
31
|
-
this.destroyed = true;
|
|
32
|
-
this.cleanupScheduleLock = false;
|
|
33
|
-
if (this.cleanupTimeout) {
|
|
34
|
-
clearTimeout(this.cleanupTimeout);
|
|
35
|
-
this.cleanupTimeout = null;
|
|
36
|
-
}
|
|
37
|
-
this.cache.clear();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
set(key: string, value: T, ttl?: number): void {
|
|
41
|
-
if (this.destroyed) return;
|
|
42
|
-
const expires = Date.now() + (ttl ?? this.defaultTTL);
|
|
43
|
-
this.cache.set(key, { value, expires });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get(key: string): T | undefined {
|
|
47
|
-
const entry = this.cache.get(key);
|
|
48
|
-
|
|
49
|
-
if (!entry) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (Date.now() > entry.expires) {
|
|
54
|
-
this.cache.delete(key);
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return entry.value;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
has(key: string): boolean {
|
|
62
|
-
return this.get(key) !== undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
clear(): void {
|
|
66
|
-
this.cache.clear();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
delete(key: string): void {
|
|
70
|
-
this.cache.delete(key);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private cleanup(): void {
|
|
74
|
-
const now = Date.now();
|
|
75
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
76
|
-
if (now > entry.expires) {
|
|
77
|
-
this.cache.delete(key);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private scheduleCleanup(): void {
|
|
83
|
-
if (this.destroyed || this.cleanupScheduleLock) return;
|
|
84
|
-
|
|
85
|
-
this.cleanupScheduleLock = true;
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
if (this.cleanupTimeout) {
|
|
89
|
-
clearTimeout(this.cleanupTimeout);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
this.cleanup();
|
|
93
|
-
|
|
94
|
-
if (!this.destroyed) {
|
|
95
|
-
this.cleanupTimeout = setTimeout(() => {
|
|
96
|
-
if (!this.destroyed) {
|
|
97
|
-
this.cleanupScheduleLock = false;
|
|
98
|
-
this.scheduleCleanup();
|
|
99
|
-
}
|
|
100
|
-
}, ONE_MINUTE_MS);
|
|
101
|
-
} else {
|
|
102
|
-
this.cleanupScheduleLock = false;
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
this.cleanupScheduleLock = false;
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|