@umituz/react-native-design-system 2.8.7 → 2.8.8

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 (128) hide show
  1. package/package.json +5 -6
  2. package/src/device/infrastructure/repositories/LegacyDeviceIdRepository.ts +1 -1
  3. package/src/device/infrastructure/services/DeviceFeatureService.ts +1 -1
  4. package/src/exception/infrastructure/services/ExceptionLogger.ts +1 -1
  5. package/src/exception/infrastructure/storage/ExceptionStore.ts +1 -1
  6. package/src/exports/filesystem.ts +1 -0
  7. package/src/exports/storage.ts +1 -0
  8. package/src/filesystem/domain/constants/FileConstants.ts +20 -0
  9. package/src/filesystem/domain/entities/File.ts +20 -0
  10. package/src/filesystem/domain/types/FileTypes.ts +43 -0
  11. package/src/filesystem/domain/utils/FileUtils.ts +86 -0
  12. package/src/filesystem/index.ts +23 -0
  13. package/src/filesystem/infrastructure/services/FileSystemService.ts +45 -0
  14. package/src/filesystem/infrastructure/services/cache.service.ts +48 -0
  15. package/src/filesystem/infrastructure/services/directory.service.ts +66 -0
  16. package/src/filesystem/infrastructure/services/download.constants.ts +6 -0
  17. package/src/filesystem/infrastructure/services/download.service.ts +74 -0
  18. package/src/filesystem/infrastructure/services/download.types.ts +7 -0
  19. package/src/filesystem/infrastructure/services/encoding.service.ts +25 -0
  20. package/src/filesystem/infrastructure/services/file-info.service.ts +52 -0
  21. package/src/filesystem/infrastructure/services/file-manager.service.ts +81 -0
  22. package/src/filesystem/infrastructure/services/file-path.service.ts +22 -0
  23. package/src/filesystem/infrastructure/services/file-reader.service.ts +52 -0
  24. package/src/filesystem/infrastructure/services/file-writer.service.ts +32 -0
  25. package/src/filesystem/infrastructure/utils/blob.utils.ts +20 -0
  26. package/src/image/infrastructure/services/ImageStorageService.ts +1 -1
  27. package/src/index.ts +9 -0
  28. package/src/molecules/alerts/AlertStore.ts +1 -1
  29. package/src/molecules/calendar/infrastructure/storage/EventActions.ts +1 -1
  30. package/src/molecules/calendar/infrastructure/stores/storageAdapter.ts +1 -1
  31. package/src/offline/infrastructure/storage/OfflineStore.ts +1 -1
  32. package/src/onboarding/infrastructure/storage/OnboardingStore.ts +2 -2
  33. package/src/onboarding/infrastructure/storage/__tests__/OnboardingStore.test.ts +1 -1
  34. package/src/onboarding/infrastructure/storage/actions/answerActions.ts +1 -1
  35. package/src/onboarding/infrastructure/storage/actions/storageHelpers.ts +1 -1
  36. package/src/storage/README.md +185 -0
  37. package/src/storage/__tests__/integration.test.ts +391 -0
  38. package/src/storage/__tests__/mocks/asyncStorage.mock.ts +52 -0
  39. package/src/storage/__tests__/performance.test.tsx +352 -0
  40. package/src/storage/__tests__/setup.ts +63 -0
  41. package/src/storage/application/README.md +158 -0
  42. package/src/storage/application/ports/IStorageRepository.ts +61 -0
  43. package/src/storage/application/ports/README.md +127 -0
  44. package/src/storage/cache/README.md +154 -0
  45. package/src/storage/cache/__tests__/PerformanceAndMemory.test.ts +387 -0
  46. package/src/storage/cache/__tests__/setup.ts +19 -0
  47. package/src/storage/cache/domain/Cache.ts +146 -0
  48. package/src/storage/cache/domain/CacheManager.md +83 -0
  49. package/src/storage/cache/domain/CacheManager.ts +48 -0
  50. package/src/storage/cache/domain/CacheStatsTracker.md +169 -0
  51. package/src/storage/cache/domain/CacheStatsTracker.ts +49 -0
  52. package/src/storage/cache/domain/CachedValue.md +97 -0
  53. package/src/storage/cache/domain/ErrorHandler.md +99 -0
  54. package/src/storage/cache/domain/ErrorHandler.ts +42 -0
  55. package/src/storage/cache/domain/PatternMatcher.md +122 -0
  56. package/src/storage/cache/domain/PatternMatcher.ts +30 -0
  57. package/src/storage/cache/domain/README.md +118 -0
  58. package/src/storage/cache/domain/__tests__/Cache.test.ts +293 -0
  59. package/src/storage/cache/domain/__tests__/CacheManager.test.ts +276 -0
  60. package/src/storage/cache/domain/__tests__/ErrorHandler.test.ts +303 -0
  61. package/src/storage/cache/domain/__tests__/PatternMatcher.test.ts +261 -0
  62. package/src/storage/cache/domain/strategies/EvictionStrategy.ts +9 -0
  63. package/src/storage/cache/domain/strategies/FIFOStrategy.ts +12 -0
  64. package/src/storage/cache/domain/strategies/LFUStrategy.ts +22 -0
  65. package/src/storage/cache/domain/strategies/LRUStrategy.ts +22 -0
  66. package/src/storage/cache/domain/strategies/README.md +117 -0
  67. package/src/storage/cache/domain/strategies/TTLStrategy.ts +23 -0
  68. package/src/storage/cache/domain/strategies/__tests__/EvictionStrategies.test.ts +293 -0
  69. package/src/storage/cache/domain/types/Cache.ts +28 -0
  70. package/src/storage/cache/domain/types/README.md +107 -0
  71. package/src/storage/cache/index.ts +28 -0
  72. package/src/storage/cache/infrastructure/README.md +126 -0
  73. package/src/storage/cache/infrastructure/TTLCache.ts +103 -0
  74. package/src/storage/cache/infrastructure/__tests__/TTLCache.test.ts +303 -0
  75. package/src/storage/cache/presentation/README.md +123 -0
  76. package/src/storage/cache/presentation/__tests__/ReactHooks.test.ts +514 -0
  77. package/src/storage/cache/presentation/useCache.ts +76 -0
  78. package/src/storage/cache/presentation/useCachedValue.ts +88 -0
  79. package/src/storage/cache/types.d.ts +3 -0
  80. package/src/storage/domain/README.md +128 -0
  81. package/src/storage/domain/constants/CacheDefaults.ts +64 -0
  82. package/src/storage/domain/constants/README.md +105 -0
  83. package/src/storage/domain/entities/CachedValue.ts +86 -0
  84. package/src/storage/domain/entities/README.md +109 -0
  85. package/src/storage/domain/entities/StorageResult.ts +75 -0
  86. package/src/storage/domain/entities/__tests__/CachedValue.test.ts +149 -0
  87. package/src/storage/domain/entities/__tests__/StorageResult.test.ts +122 -0
  88. package/src/storage/domain/errors/README.md +126 -0
  89. package/src/storage/domain/errors/StorageError.ts +81 -0
  90. package/src/storage/domain/errors/__tests__/StorageError.test.ts +127 -0
  91. package/src/storage/domain/factories/README.md +138 -0
  92. package/src/storage/domain/factories/StoreFactory.ts +59 -0
  93. package/src/storage/domain/types/README.md +522 -0
  94. package/src/storage/domain/types/Store.ts +44 -0
  95. package/src/storage/domain/utils/CacheKeyGenerator.ts +66 -0
  96. package/src/storage/domain/utils/README.md +127 -0
  97. package/src/storage/domain/utils/__tests__/devUtils.test.ts +97 -0
  98. package/src/storage/domain/utils/devUtils.ts +37 -0
  99. package/src/storage/domain/value-objects/README.md +120 -0
  100. package/src/storage/domain/value-objects/StorageKey.ts +60 -0
  101. package/src/storage/index.ts +175 -0
  102. package/src/storage/infrastructure/README.md +165 -0
  103. package/src/storage/infrastructure/adapters/README.md +175 -0
  104. package/src/storage/infrastructure/adapters/StorageService.md +103 -0
  105. package/src/storage/infrastructure/adapters/StorageService.ts +49 -0
  106. package/src/storage/infrastructure/repositories/AsyncStorageRepository.ts +98 -0
  107. package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +100 -0
  108. package/src/storage/infrastructure/repositories/BatchStorageOperations.ts +42 -0
  109. package/src/storage/infrastructure/repositories/README.md +121 -0
  110. package/src/storage/infrastructure/repositories/StringStorageOperations.ts +44 -0
  111. package/src/storage/infrastructure/repositories/__tests__/AsyncStorageRepository.test.ts +170 -0
  112. package/src/storage/infrastructure/repositories/__tests__/BaseStorageOperations.test.ts +201 -0
  113. package/src/storage/presentation/README.md +181 -0
  114. package/src/storage/presentation/hooks/CacheStorageOperations.ts +94 -0
  115. package/src/storage/presentation/hooks/README.md +128 -0
  116. package/src/storage/presentation/hooks/__tests__/usePersistentCache.test.ts +405 -0
  117. package/src/storage/presentation/hooks/__tests__/useStorage.test.ts +247 -0
  118. package/src/storage/presentation/hooks/__tests__/useStorageState.test.ts +293 -0
  119. package/src/storage/presentation/hooks/useCacheState.ts +53 -0
  120. package/src/storage/presentation/hooks/usePersistentCache.ts +154 -0
  121. package/src/storage/presentation/hooks/useStorage.ts +102 -0
  122. package/src/storage/presentation/hooks/useStorageState.ts +71 -0
  123. package/src/storage/presentation/hooks/useStore.ts +15 -0
  124. package/src/storage/types/README.md +103 -0
  125. package/src/theme/infrastructure/globalThemeStore.ts +1 -1
  126. package/src/theme/infrastructure/storage/ThemeStorage.ts +1 -1
  127. package/src/theme/infrastructure/stores/themeStore.ts +1 -1
  128. package/src/utilities/sharing/infrastructure/services/SharingService.ts +1 -1
@@ -0,0 +1,514 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ /**
3
+ * React Hooks Integration Tests
4
+ */
5
+
6
+ // Mock React for testing environment
7
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
8
+ const mockReact = require('react');
9
+
10
+ // Simple mock for renderHook
11
+ const renderHook = (hook: () => any) => {
12
+ const result = { current: hook() };
13
+
14
+ const rerender = () => {
15
+ result.current = hook();
16
+ };
17
+
18
+ const unmount = () => {
19
+ // Cleanup logic would go here
20
+ };
21
+
22
+ return { result, rerender, unmount };
23
+ };
24
+
25
+ // Mock act function
26
+ const act = (callback: () => void) => {
27
+ callback();
28
+ };
29
+
30
+ import { useCache } from '../useCache';
31
+ import { useCachedValue } from '../useCachedValue';
32
+ import { cacheManager } from '../../domain/CacheManager';
33
+
34
+ describe('React Hooks Integration', () => {
35
+ beforeEach(() => {
36
+ cacheManager.clearAll();
37
+ jest.clearAllMocks();
38
+ });
39
+
40
+ describe('useCache', () => {
41
+ test('should provide cache operations', () => {
42
+ const { result } = renderHook(() => useCache<string>('test-cache'));
43
+
44
+ expect(result.current).toHaveProperty('set');
45
+ expect(result.current).toHaveProperty('get');
46
+ expect(result.current).toHaveProperty('has');
47
+ expect(result.current).toHaveProperty('remove');
48
+ expect(result.current).toHaveProperty('clear');
49
+ expect(result.current).toHaveProperty('invalidatePattern');
50
+ expect(result.current).toHaveProperty('getStats');
51
+ });
52
+
53
+ test('should set and get values', () => {
54
+ const { result } = renderHook(() => useCache<string>('test-cache'));
55
+
56
+ act(() => {
57
+ result.current.set('key1', 'value1');
58
+ });
59
+
60
+ expect(result.current.get('key1')).toBe('value1');
61
+ });
62
+
63
+ test('should check if key exists', () => {
64
+ const { result } = renderHook(() => useCache<string>('test-cache'));
65
+
66
+ expect(result.current.has('key1')).toBe(false);
67
+
68
+ act(() => {
69
+ result.current.set('key1', 'value1');
70
+ });
71
+
72
+ expect(result.current.has('key1')).toBe(true);
73
+ });
74
+
75
+ test('should remove keys', () => {
76
+ const { result } = renderHook(() => useCache<string>('test-cache'));
77
+
78
+ act(() => {
79
+ result.current.set('key1', 'value1');
80
+ });
81
+
82
+ expect(result.current.has('key1')).toBe(true);
83
+
84
+ act(() => {
85
+ const removed = result.current.remove('key1');
86
+ expect(removed).toBe(true);
87
+ });
88
+
89
+ expect(result.current.has('key1')).toBe(false);
90
+ });
91
+
92
+ test('should clear all keys', () => {
93
+ const { result } = renderHook(() => useCache<string>('test-cache'));
94
+
95
+ act(() => {
96
+ result.current.set('key1', 'value1');
97
+ result.current.set('key2', 'value2');
98
+ });
99
+
100
+ expect(result.current.getStats().size).toBe(2);
101
+
102
+ act(() => {
103
+ result.current.clear();
104
+ });
105
+
106
+ expect(result.current.getStats().size).toBe(0);
107
+ });
108
+
109
+ test('should invalidate patterns', () => {
110
+ const { result } = renderHook(() => useCache<string>('test-cache'));
111
+
112
+ act(() => {
113
+ result.current.set('user:1', 'user1');
114
+ result.current.set('user:2', 'user2');
115
+ result.current.set('post:1', 'post1');
116
+ });
117
+
118
+ act(() => {
119
+ const count = result.current.invalidatePattern('user:*');
120
+ expect(count).toBe(2);
121
+ });
122
+
123
+ expect(result.current.has('user:1')).toBe(false);
124
+ expect(result.current.has('user:2')).toBe(false);
125
+ expect(result.current.has('post:1')).toBe(true);
126
+ });
127
+
128
+ test('should get cache statistics', () => {
129
+ const { result } = renderHook(() => useCache<string>('test-cache'));
130
+
131
+ const initialStats = result.current.getStats();
132
+ expect(initialStats.size).toBe(0);
133
+ expect(initialStats.hits).toBe(0);
134
+ expect(initialStats.misses).toBe(0);
135
+
136
+ act(() => {
137
+ result.current.set('key1', 'value1');
138
+ });
139
+
140
+ const afterSetStats = result.current.getStats();
141
+ expect(afterSetStats.size).toBe(1);
142
+
143
+ act(() => {
144
+ result.current.get('key1'); // hit
145
+ result.current.get('nonexistent'); // miss
146
+ });
147
+
148
+ const afterAccessStats = result.current.getStats();
149
+ expect(afterAccessStats.hits).toBe(1);
150
+ expect(afterAccessStats.misses).toBe(1);
151
+ });
152
+
153
+ test('should use custom cache configuration', () => {
154
+ const { result } = renderHook(() =>
155
+ useCache<string>('custom-cache', { maxSize: 5, defaultTTL: 2000 })
156
+ );
157
+
158
+ act(() => {
159
+ result.current.set('key1', 'value1');
160
+ });
161
+
162
+ expect(result.current.get('key1')).toBe('value1');
163
+ });
164
+
165
+ test('should maintain separate caches for different names', () => {
166
+ const { result: result1 } = renderHook(() => useCache<string>('cache1'));
167
+ const { result: result2 } = renderHook(() => useCache<number>('cache2'));
168
+
169
+ act(() => {
170
+ result1.current.set('key', 'string-value');
171
+ result2.current.set('key', 42);
172
+ });
173
+
174
+ expect(result1.current.get('key')).toBe('string-value');
175
+ expect(result2.current.get('key')).toBe(42);
176
+ });
177
+
178
+ test('should handle rapid operations without memory leaks', () => {
179
+ const { result } = renderHook(() => useCache<string>('rapid-cache'));
180
+
181
+ // Perform many operations rapidly
182
+ for (let i = 0; i < 100; i++) {
183
+ act(() => {
184
+ result.current.set(`key${i}`, `value${i}`);
185
+ });
186
+ }
187
+
188
+ expect(result.current.getStats().size).toBe(100);
189
+
190
+ // Clear many operations rapidly
191
+ for (let i = 0; i < 100; i++) {
192
+ act(() => {
193
+ result.current.remove(`key${i}`);
194
+ });
195
+ }
196
+
197
+ expect(result.current.getStats().size).toBe(0);
198
+ });
199
+ });
200
+
201
+ describe('useCachedValue', () => {
202
+ test('should load and cache value', async () => {
203
+ const mockFetcher = jest.fn().mockResolvedValue('fetched-value');
204
+
205
+ const { result } = renderHook(() =>
206
+ useCachedValue('test-cache', 'test-key', mockFetcher)
207
+ );
208
+
209
+ expect(result.current.isLoading).toBe(true);
210
+ expect(result.current.value).toBeUndefined();
211
+ expect(result.current.error).toBe(null);
212
+
213
+ await act(async () => {
214
+ await new Promise(resolve => setTimeout(resolve, 0));
215
+ });
216
+
217
+ expect(result.current.isLoading).toBe(false);
218
+ expect(result.current.value).toBe('fetched-value');
219
+ expect(result.current.error).toBe(null);
220
+ expect(mockFetcher).toHaveBeenCalledTimes(1);
221
+ });
222
+
223
+ test('should use cached value on subsequent renders', async () => {
224
+ const mockFetcher = jest.fn().mockResolvedValue('fetched-value');
225
+
226
+ const { result, rerender } = renderHook(() =>
227
+ useCachedValue('test-cache', 'test-key', mockFetcher)
228
+ );
229
+
230
+ // Initial load
231
+ await act(async () => {
232
+ await new Promise(resolve => setTimeout(resolve, 0));
233
+ });
234
+
235
+ expect(mockFetcher).toHaveBeenCalledTimes(1);
236
+
237
+ // Rerender - should use cache
238
+ rerender();
239
+
240
+ expect(result.current.value).toBe('fetched-value');
241
+ expect(mockFetcher).toHaveBeenCalledTimes(1); // Still only called once
242
+ });
243
+
244
+ test('should handle fetcher errors', async () => {
245
+ const mockError = new Error('Fetch failed');
246
+ const mockFetcher = jest.fn().mockRejectedValue(mockError);
247
+
248
+ const { result } = renderHook(() =>
249
+ useCachedValue('test-cache', 'test-key', mockFetcher)
250
+ );
251
+
252
+ await act(async () => {
253
+ await new Promise(resolve => setTimeout(resolve, 0));
254
+ });
255
+
256
+ expect(result.current.isLoading).toBe(false);
257
+ expect(result.current.value).toBeUndefined();
258
+ expect(result.current.error).toBe(mockError);
259
+ });
260
+
261
+ test('should invalidate cached value', async () => {
262
+ const mockFetcher = jest.fn().mockResolvedValue('initial-value');
263
+
264
+ const { result } = renderHook(() =>
265
+ useCachedValue('test-cache', 'test-key', mockFetcher)
266
+ );
267
+
268
+ // Initial load
269
+ await act(async () => {
270
+ await new Promise(resolve => setTimeout(resolve, 0));
271
+ });
272
+
273
+ expect(result.current.value).toBe('initial-value');
274
+
275
+ // Invalidate
276
+ act(() => {
277
+ result.current.invalidate();
278
+ });
279
+
280
+ expect(result.current.value).toBeUndefined();
281
+
282
+ // Should fetch again on next render
283
+ mockFetcher.mockResolvedValue('new-value');
284
+
285
+ await act(async () => {
286
+ await new Promise(resolve => setTimeout(resolve, 0));
287
+ });
288
+
289
+ expect(result.current.value).toBe('new-value');
290
+ expect(mockFetcher).toHaveBeenCalledTimes(2);
291
+ });
292
+
293
+ test('should invalidate pattern', async () => {
294
+ const mockFetcher = jest.fn().mockResolvedValue('value');
295
+
296
+ const { result } = renderHook(() =>
297
+ useCachedValue('test-cache', 'user:123', mockFetcher)
298
+ );
299
+
300
+ // Load initial value
301
+ await act(async () => {
302
+ await new Promise(resolve => setTimeout(resolve, 0));
303
+ });
304
+
305
+ expect(result.current.value).toBe('value');
306
+
307
+ // Invalidate pattern
308
+ act(() => {
309
+ const count = result.current.invalidatePattern('user:*');
310
+ expect(count).toBe(1);
311
+ });
312
+
313
+ expect(result.current.value).toBeUndefined();
314
+ });
315
+
316
+ test('should refetch manually', async () => {
317
+ const mockFetcher = jest.fn()
318
+ .mockResolvedValueOnce('initial-value')
319
+ .mockResolvedValueOnce('refetched-value');
320
+
321
+ const { result } = renderHook(() =>
322
+ useCachedValue('test-cache', 'test-key', mockFetcher)
323
+ );
324
+
325
+ // Initial load
326
+ await act(async () => {
327
+ await new Promise(resolve => setTimeout(resolve, 0));
328
+ });
329
+
330
+ expect(result.current.value).toBe('initial-value');
331
+ expect(mockFetcher).toHaveBeenCalledTimes(1);
332
+
333
+ // Refetch
334
+ act(() => {
335
+ result.current.refetch();
336
+ });
337
+
338
+ await act(async () => {
339
+ await new Promise(resolve => setTimeout(resolve, 0));
340
+ });
341
+
342
+ expect(result.current.value).toBe('refetched-value');
343
+ expect(mockFetcher).toHaveBeenCalledTimes(2);
344
+ });
345
+
346
+ test('should use custom TTL', async () => {
347
+ jest.useFakeTimers();
348
+
349
+ const mockFetcher = jest.fn().mockResolvedValue('value');
350
+
351
+ const { result } = renderHook(() =>
352
+ useCachedValue('test-cache', 'test-key', mockFetcher, { ttl: 1000 })
353
+ );
354
+
355
+ // Load initial value
356
+ await act(async () => {
357
+ await new Promise(resolve => setTimeout(resolve, 0));
358
+ });
359
+
360
+ expect(result.current.value).toBe('value');
361
+ expect(mockFetcher).toHaveBeenCalledTimes(1);
362
+
363
+ // Fast forward past TTL
364
+ jest.advanceTimersByTime(1001);
365
+
366
+ // Should fetch again
367
+ await act(async () => {
368
+ await new Promise(resolve => setTimeout(resolve, 0));
369
+ });
370
+
371
+ expect(mockFetcher).toHaveBeenCalledTimes(2);
372
+
373
+ jest.useRealTimers();
374
+ });
375
+
376
+ test('should handle dependency changes', async () => {
377
+ const mockFetcher1 = jest.fn().mockResolvedValue('value1');
378
+ const mockFetcher2 = jest.fn().mockResolvedValue('value2');
379
+
380
+ const { result, rerender } = renderHook(
381
+ ({ fetcher }) => useCachedValue('test-cache', 'test-key', fetcher),
382
+ { initialProps: { fetcher: mockFetcher1 } }
383
+ );
384
+
385
+ // Initial load
386
+ await act(async () => {
387
+ await new Promise(resolve => setTimeout(resolve, 0));
388
+ });
389
+
390
+ expect(result.current.value).toBe('value1');
391
+ expect(mockFetcher1).toHaveBeenCalledTimes(1);
392
+
393
+ // Change fetcher
394
+ rerender({ fetcher: mockFetcher2 });
395
+
396
+ await act(async () => {
397
+ await new Promise(resolve => setTimeout(resolve, 0));
398
+ });
399
+
400
+ expect(result.current.value).toBe('value2');
401
+ expect(mockFetcher2).toHaveBeenCalledTimes(1);
402
+ });
403
+
404
+ test('should handle concurrent requests', async () => {
405
+ let resolveCount = 0;
406
+ const mockFetcher = jest.fn(() => {
407
+ return new Promise(resolve => {
408
+ setTimeout(() => {
409
+ resolveCount++;
410
+ resolve(`value-${resolveCount}`);
411
+ }, 100);
412
+ });
413
+ });
414
+
415
+ const { result } = renderHook(() =>
416
+ useCachedValue('test-cache', 'test-key', mockFetcher)
417
+ );
418
+
419
+ // Trigger multiple rapid renders
420
+ act(() => {});
421
+ act(() => {});
422
+ act(() => {});
423
+
424
+ // Wait for completion
425
+ await act(async () => {
426
+ await new Promise(resolve => setTimeout(resolve, 200));
427
+ });
428
+
429
+ // Should only call fetcher once despite multiple renders
430
+ expect(mockFetcher).toHaveBeenCalledTimes(1);
431
+ expect(result.current.value).toBe('value-1');
432
+ });
433
+
434
+ test('should cleanup on unmount', async () => {
435
+ const mockFetcher = jest.fn().mockResolvedValue('value');
436
+
437
+ const { result, unmount } = renderHook(() =>
438
+ useCachedValue('test-cache', 'test-key', mockFetcher)
439
+ );
440
+
441
+ // Start loading
442
+ expect(result.current.isLoading).toBe(true);
443
+
444
+ // Unmount while loading
445
+ unmount();
446
+
447
+ // Complete the fetch after unmount
448
+ await act(async () => {
449
+ await new Promise(resolve => setTimeout(resolve, 0));
450
+ });
451
+
452
+ // Should not cause errors
453
+ expect(true).toBe(true);
454
+ });
455
+ });
456
+
457
+ describe('Hook Integration', () => {
458
+ test('should work together with useCache and useCachedValue', async () => {
459
+ const mockFetcher = jest.fn().mockResolvedValue('fetched-value');
460
+
461
+ const { result: cacheResult } = renderHook(() => useCache<string>('shared-cache'));
462
+ const { result: valueResult } = renderHook(() =>
463
+ useCachedValue('shared-cache', 'shared-key', mockFetcher)
464
+ );
465
+
466
+ // Load value through useCachedValue
467
+ await act(async () => {
468
+ await new Promise(resolve => setTimeout(resolve, 0));
469
+ });
470
+
471
+ expect(valueResult.current.value).toBe('fetched-value');
472
+
473
+ // Value should be accessible through useCache
474
+ expect(cacheResult.current.get('shared-key')).toBe('fetched-value');
475
+
476
+ // Invalidate through useCache
477
+ act(() => {
478
+ cacheResult.current.invalidatePattern('*');
479
+ });
480
+
481
+ expect(valueResult.current.value).toBeUndefined();
482
+ expect(cacheResult.current.has('shared-key')).toBe(false);
483
+ });
484
+
485
+ test('should handle multiple hooks with same cache', async () => {
486
+ const mockFetcher1 = jest.fn().mockResolvedValue('value1');
487
+ const mockFetcher2 = jest.fn().mockResolvedValue('value2');
488
+
489
+ const { result: result1 } = renderHook(() =>
490
+ useCachedValue('shared-cache', 'key1', mockFetcher1)
491
+ );
492
+
493
+ const { result: result2 } = renderHook(() =>
494
+ useCachedValue('shared-cache', 'key2', mockFetcher2)
495
+ );
496
+
497
+ // Load both values
498
+ await act(async () => {
499
+ await new Promise(resolve => setTimeout(resolve, 0));
500
+ });
501
+
502
+ expect(result1.current.value).toBe('value1');
503
+ expect(result2.current.value).toBe('value2');
504
+
505
+ // Invalidate one should not affect the other
506
+ act(() => {
507
+ result1.current.invalidate();
508
+ });
509
+
510
+ expect(result1.current.value).toBeUndefined();
511
+ expect(result2.current.value).toBe('value2'); // Should remain
512
+ });
513
+ });
514
+ });
@@ -0,0 +1,76 @@
1
+ /**
2
+ * useCache Hook
3
+ */
4
+
5
+ import { useCallback, useRef, useState } from 'react';
6
+ import { cacheManager } from '../domain/CacheManager';
7
+ import type { CacheConfig } from '../domain/types/Cache';
8
+
9
+ export function useCache<T>(cacheName: string, config?: CacheConfig) {
10
+ const cacheRef = useRef(cacheManager.getCache<T>(cacheName, config));
11
+ const cache = cacheRef.current!;
12
+ const [, forceUpdate] = useState({});
13
+
14
+ const triggerUpdate = useCallback(() => {
15
+ forceUpdate({});
16
+ }, []);
17
+
18
+ const set = useCallback(
19
+ (key: string, value: T, ttl?: number) => {
20
+ cache.set(key, value, ttl);
21
+ triggerUpdate();
22
+ },
23
+ [cache, triggerUpdate]
24
+ );
25
+
26
+ const get = useCallback(
27
+ (key: string): T | undefined => {
28
+ return cache.get(key);
29
+ },
30
+ [cache]
31
+ );
32
+
33
+ const has = useCallback(
34
+ (key: string): boolean => {
35
+ return cache.has(key);
36
+ },
37
+ [cache]
38
+ );
39
+
40
+ const remove = useCallback(
41
+ (key: string): boolean => {
42
+ const result = cache.delete(key);
43
+ triggerUpdate();
44
+ return result;
45
+ },
46
+ [cache, triggerUpdate]
47
+ );
48
+
49
+ const clear = useCallback(() => {
50
+ cache.clear();
51
+ triggerUpdate();
52
+ }, [cache, triggerUpdate]);
53
+
54
+ const invalidatePattern = useCallback(
55
+ (pattern: string): number => {
56
+ const count = cache.invalidatePattern(pattern);
57
+ triggerUpdate();
58
+ return count;
59
+ },
60
+ [cache, triggerUpdate]
61
+ );
62
+
63
+ const getStats = useCallback(() => {
64
+ return cache.getStats();
65
+ }, [cache]);
66
+
67
+ return {
68
+ set,
69
+ get,
70
+ has,
71
+ remove,
72
+ clear,
73
+ invalidatePattern,
74
+ getStats,
75
+ };
76
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * useCachedValue Hook
3
+ */
4
+
5
+ import { useCallback, useEffect, useRef, useState } from 'react';
6
+ import { cacheManager } from '../domain/CacheManager';
7
+ import type { CacheConfig } from '../domain/types/Cache';
8
+
9
+ export function useCachedValue<T>(
10
+ cacheName: string,
11
+ key: string,
12
+ fetcher: () => Promise<T>,
13
+ config?: CacheConfig & { ttl?: number }
14
+ ) {
15
+ const [value, setValue] = useState<T | undefined>(undefined);
16
+ const [isLoading, setIsLoading] = useState(false);
17
+ const [error, setError] = useState<Error | null>(null);
18
+
19
+ const fetcherRef = useRef(fetcher);
20
+ const configRef = useRef(config);
21
+
22
+ fetcherRef.current = fetcher;
23
+ configRef.current = config;
24
+
25
+ const loadValue = useCallback(async () => {
26
+ const cache = cacheManager.getCache<T>(cacheName, configRef.current);
27
+ const cached = cache.get(key);
28
+
29
+ if (cached !== undefined) {
30
+ setValue(cached);
31
+ return;
32
+ }
33
+
34
+ setIsLoading(true);
35
+ setError(null);
36
+
37
+ try {
38
+ const data = await fetcherRef.current!();
39
+ cache.set(key, data, configRef.current?.ttl);
40
+ setValue(data);
41
+ } catch (err) {
42
+ setError(err as Error);
43
+ } finally {
44
+ setIsLoading(false);
45
+ }
46
+ }, [cacheName, key]);
47
+
48
+ useEffect(() => {
49
+ let isMounted = true;
50
+
51
+ loadValue().then(() => {
52
+ if (!isMounted) {
53
+ setValue(undefined);
54
+ }
55
+ });
56
+
57
+ return () => {
58
+ isMounted = false;
59
+ };
60
+ }, [loadValue]);
61
+
62
+ const invalidate = useCallback(() => {
63
+ const cache = cacheManager.getCache<T>(cacheName);
64
+ cache.delete(key);
65
+ setValue(undefined);
66
+ }, [cacheName, key]);
67
+
68
+ const invalidatePattern = useCallback((pattern: string): number => {
69
+ const cache = cacheManager.getCache<T>(cacheName);
70
+ const count = cache.invalidatePattern(pattern);
71
+ setValue(undefined);
72
+ return count;
73
+ }, [cacheName]);
74
+
75
+ const refetch = useCallback(() => {
76
+ setValue(undefined);
77
+ loadValue();
78
+ }, [loadValue]);
79
+
80
+ return {
81
+ value,
82
+ isLoading,
83
+ error,
84
+ invalidate,
85
+ invalidatePattern,
86
+ refetch,
87
+ };
88
+ }
@@ -0,0 +1,3 @@
1
+ /// <reference types="../../types/global" />
2
+
3
+ declare const __DEV__: boolean | undefined;