@umituz/react-native-storage 1.5.0 → 2.3.2

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 (39) hide show
  1. package/README.md +0 -0
  2. package/package.json +28 -10
  3. package/src/__tests__/integration.test.ts +391 -0
  4. package/src/__tests__/mocks/asyncStorage.mock.ts +52 -0
  5. package/src/__tests__/performance.test.ts +351 -0
  6. package/src/__tests__/setup.ts +63 -0
  7. package/src/application/ports/IStorageRepository.ts +0 -12
  8. package/src/domain/constants/CacheDefaults.ts +64 -0
  9. package/src/domain/entities/CachedValue.ts +86 -0
  10. package/src/domain/entities/StorageResult.ts +1 -3
  11. package/src/domain/entities/__tests__/CachedValue.test.ts +149 -0
  12. package/src/domain/entities/__tests__/StorageResult.test.ts +122 -0
  13. package/src/domain/errors/StorageError.ts +0 -2
  14. package/src/domain/errors/__tests__/StorageError.test.ts +127 -0
  15. package/src/domain/factories/StoreFactory.ts +33 -0
  16. package/src/domain/types/Store.ts +18 -0
  17. package/src/domain/utils/CacheKeyGenerator.ts +66 -0
  18. package/src/domain/utils/__tests__/devUtils.test.ts +97 -0
  19. package/src/domain/utils/devUtils.ts +37 -0
  20. package/src/domain/value-objects/StorageKey.ts +27 -29
  21. package/src/index.ts +59 -1
  22. package/src/infrastructure/adapters/StorageService.ts +8 -6
  23. package/src/infrastructure/repositories/AsyncStorageRepository.ts +27 -108
  24. package/src/infrastructure/repositories/BaseStorageOperations.ts +101 -0
  25. package/src/infrastructure/repositories/BatchStorageOperations.ts +42 -0
  26. package/src/infrastructure/repositories/StringStorageOperations.ts +44 -0
  27. package/src/infrastructure/repositories/__tests__/AsyncStorageRepository.test.ts +169 -0
  28. package/src/infrastructure/repositories/__tests__/BaseStorageOperations.test.ts +200 -0
  29. package/src/presentation/hooks/CacheStorageOperations.ts +95 -0
  30. package/src/presentation/hooks/__tests__/usePersistentCache.test.ts +404 -0
  31. package/src/presentation/hooks/__tests__/useStorage.test.ts +246 -0
  32. package/src/presentation/hooks/__tests__/useStorageState.test.ts +292 -0
  33. package/src/presentation/hooks/useCacheState.ts +55 -0
  34. package/src/presentation/hooks/usePersistentCache.ts +154 -0
  35. package/src/presentation/hooks/useStorage.ts +4 -3
  36. package/src/presentation/hooks/useStorageState.ts +24 -8
  37. package/src/presentation/hooks/useStore.ts +15 -0
  38. package/src/types/global.d.ts +40 -0
  39. package/LICENSE +0 -22
@@ -0,0 +1,169 @@
1
+ /**
2
+ * AsyncStorageRepository Tests
3
+ *
4
+ * Unit tests for AsyncStorageRepository
5
+ */
6
+
7
+ import { AsyncStorageRepository } from '../AsyncStorageRepository';
8
+ import { AsyncStorage } from '../../__tests__/mocks/asyncStorage.mock';
9
+
10
+ describe('AsyncStorageRepository', () => {
11
+ let repository: AsyncStorageRepository;
12
+
13
+ beforeEach(() => {
14
+ repository = new AsyncStorageRepository();
15
+ (AsyncStorage as any).__clear();
16
+ });
17
+
18
+ describe('Composition', () => {
19
+ it('should use composition pattern', () => {
20
+ expect(repository).toBeInstanceOf(AsyncStorageRepository);
21
+
22
+ // Test that all methods are available
23
+ expect(typeof repository.getItem).toBe('function');
24
+ expect(typeof repository.setItem).toBe('function');
25
+ expect(typeof repository.getString).toBe('function');
26
+ expect(typeof repository.setString).toBe('function');
27
+ expect(typeof repository.removeItem).toBe('function');
28
+ expect(typeof repository.hasItem).toBe('function');
29
+ expect(typeof repository.clearAll).toBe('function');
30
+ expect(typeof repository.getMultiple).toBe('function');
31
+ expect(typeof repository.getAllKeys).toBe('function');
32
+ });
33
+ });
34
+
35
+ describe('Delegation', () => {
36
+ it('should delegate getItem to BaseStorageOperations', async () => {
37
+ const key = 'test-key';
38
+ const defaultValue = 'default';
39
+ const expectedValue = 'test-value';
40
+
41
+ await AsyncStorage.setItem(key, JSON.stringify(expectedValue));
42
+
43
+ const result = await repository.getItem(key, defaultValue);
44
+
45
+ expect(result.success).toBe(true);
46
+ expect(result.data).toBe(expectedValue);
47
+ });
48
+
49
+ it('should delegate setItem to BaseStorageOperations', async () => {
50
+ const key = 'test-key';
51
+ const value = 'test-value';
52
+
53
+ const result = await repository.setItem(key, value);
54
+
55
+ expect(result.success).toBe(true);
56
+ expect(result.data).toBe(value);
57
+ });
58
+
59
+ it('should delegate getString to StringStorageOperations', async () => {
60
+ const key = 'test-key';
61
+ const defaultValue = 'default';
62
+ const expectedValue = 'test-string';
63
+
64
+ await AsyncStorage.setItem(key, expectedValue);
65
+
66
+ const result = await repository.getString(key, defaultValue);
67
+
68
+ expect(result.success).toBe(true);
69
+ expect(result.data).toBe(expectedValue);
70
+ });
71
+
72
+ it('should delegate setString to StringStorageOperations', async () => {
73
+ const key = 'test-key';
74
+ const value = 'test-string';
75
+
76
+ const result = await repository.setString(key, value);
77
+
78
+ expect(result.success).toBe(true);
79
+ expect(result.data).toBe(value);
80
+ });
81
+
82
+ it('should delegate removeItem to BaseStorageOperations', async () => {
83
+ const key = 'test-key';
84
+
85
+ await AsyncStorage.setItem(key, 'test-value');
86
+
87
+ const result = await repository.removeItem(key);
88
+
89
+ expect(result.success).toBe(true);
90
+ });
91
+
92
+ it('should delegate hasItem to BaseStorageOperations', async () => {
93
+ const key = 'test-key';
94
+
95
+ await AsyncStorage.setItem(key, 'test-value');
96
+
97
+ const exists = await repository.hasItem(key);
98
+
99
+ expect(exists).toBe(true);
100
+ });
101
+
102
+ it('should delegate clearAll to BaseStorageOperations', async () => {
103
+ await AsyncStorage.setItem('key1', 'value1');
104
+ await AsyncStorage.setItem('key2', 'value2');
105
+
106
+ const result = await repository.clearAll();
107
+
108
+ expect(result.success).toBe(true);
109
+ });
110
+
111
+ it('should delegate getMultiple to BatchStorageOperations', async () => {
112
+ const keys = ['key1', 'key2'];
113
+
114
+ await AsyncStorage.setItem('key1', 'value1');
115
+ await AsyncStorage.setItem('key2', 'value2');
116
+
117
+ const result = await repository.getMultiple(keys);
118
+
119
+ expect(result.success).toBe(true);
120
+ expect(result.data).toEqual({
121
+ key1: 'value1',
122
+ key2: 'value2'
123
+ });
124
+ });
125
+
126
+ it('should delegate getAllKeys to BatchStorageOperations', async () => {
127
+ await AsyncStorage.setItem('key1', 'value1');
128
+ await AsyncStorage.setItem('key2', 'value2');
129
+
130
+ const result = await repository.getAllKeys();
131
+
132
+ expect(result.success).toBe(true);
133
+ expect(result.data).toEqual(expect.arrayContaining(['key1', 'key2']));
134
+ });
135
+ });
136
+
137
+ describe('Error Handling', () => {
138
+ it('should handle errors from delegated operations', async () => {
139
+ const key = 'test-key';
140
+
141
+ // Mock error in AsyncStorage
142
+ (AsyncStorage.getItem as jest.Mock).mockRejectedValue(new Error('Storage error'));
143
+
144
+ const result = await repository.getItem(key, 'default');
145
+
146
+ expect(result.success).toBe(false);
147
+ expect(result.error).toBeDefined();
148
+ });
149
+ });
150
+
151
+ describe('Type Safety', () => {
152
+ it('should maintain type safety for generic methods', async () => {
153
+ interface TestData {
154
+ id: number;
155
+ name: string;
156
+ }
157
+
158
+ const key = 'typed-key';
159
+ const data: TestData = { id: 1, name: 'test' };
160
+
161
+ const setResult = await repository.setItem<TestData>(key, data);
162
+ const getResult = await repository.getItem<TestData>(key, { id: 0, name: '' });
163
+
164
+ expect(setResult.success).toBe(true);
165
+ expect(getResult.success).toBe(true);
166
+ expect(getResult.data).toEqual(data);
167
+ });
168
+ });
169
+ });
@@ -0,0 +1,200 @@
1
+ /**
2
+ * BaseStorageOperations Tests
3
+ *
4
+ * Unit tests for BaseStorageOperations
5
+ */
6
+
7
+ import { BaseStorageOperations } from '../BaseStorageOperations';
8
+ import { AsyncStorage } from '../../__tests__/mocks/asyncStorage.mock';
9
+ import { StorageReadError, StorageWriteError, StorageDeleteError } from '../../../domain/errors/StorageError';
10
+
11
+ describe('BaseStorageOperations', () => {
12
+ let baseOps: BaseStorageOperations;
13
+
14
+ beforeEach(() => {
15
+ baseOps = new BaseStorageOperations();
16
+ (AsyncStorage as any).__clear();
17
+ });
18
+
19
+ describe('getItem', () => {
20
+ it('should get item successfully', async () => {
21
+ const key = 'test-key';
22
+ const value = { test: 'value' };
23
+ const defaultValue = { default: true };
24
+
25
+ // Setup
26
+ await AsyncStorage.setItem(key, JSON.stringify(value));
27
+
28
+ // Test
29
+ const result = await baseOps.getItem(key, defaultValue);
30
+
31
+ expect(result.success).toBe(true);
32
+ expect(result.data).toEqual(value);
33
+ });
34
+
35
+ it('should return default value for missing key', async () => {
36
+ const key = 'missing-key';
37
+ const defaultValue = { default: true };
38
+
39
+ const result = await baseOps.getItem(key, defaultValue);
40
+
41
+ expect(result.success).toBe(true);
42
+ expect(result.data).toBe(defaultValue);
43
+ });
44
+
45
+ it('should handle deserialization error', async () => {
46
+ const key = 'invalid-json-key';
47
+ const defaultValue = { default: true };
48
+
49
+ // Setup invalid JSON
50
+ await AsyncStorage.setItem(key, 'invalid-json');
51
+
52
+ const result = await baseOps.getItem(key, defaultValue);
53
+
54
+ expect(result.success).toBe(false);
55
+ expect(result.data).toBe(defaultValue);
56
+ expect(result.error).toBeInstanceOf(StorageReadError);
57
+ });
58
+
59
+ it('should handle storage read error', async () => {
60
+ const key = 'test-key';
61
+ const defaultValue = { default: true };
62
+
63
+ // Mock storage error
64
+ (AsyncStorage.getItem as jest.Mock).mockRejectedValue(new Error('Storage error'));
65
+
66
+ const result = await baseOps.getItem(key, defaultValue);
67
+
68
+ expect(result.success).toBe(false);
69
+ expect(result.data).toBe(defaultValue);
70
+ expect(result.error).toBeInstanceOf(StorageReadError);
71
+ });
72
+ });
73
+
74
+ describe('setItem', () => {
75
+ it('should set item successfully', async () => {
76
+ const key = 'test-key';
77
+ const value = { test: 'value' };
78
+
79
+ const result = await baseOps.setItem(key, value);
80
+
81
+ expect(result.success).toBe(true);
82
+ expect(result.data).toEqual(value);
83
+
84
+ // Verify storage
85
+ const stored = await AsyncStorage.getItem(key);
86
+ expect(JSON.parse(stored!)).toEqual(value);
87
+ });
88
+
89
+ it('should handle serialization error', async () => {
90
+ const key = 'test-key';
91
+ const value = { circular: {} };
92
+ value.circular = value; // Create circular reference
93
+
94
+ const result = await baseOps.setItem(key, value);
95
+
96
+ expect(result.success).toBe(false);
97
+ expect(result.error).toBeInstanceOf(StorageWriteError);
98
+ });
99
+
100
+ it('should handle storage write error', async () => {
101
+ const key = 'test-key';
102
+ const value = { test: 'value' };
103
+
104
+ // Mock storage error
105
+ (AsyncStorage.setItem as jest.Mock).mockRejectedValue(new Error('Storage error'));
106
+
107
+ const result = await baseOps.setItem(key, value);
108
+
109
+ expect(result.success).toBe(false);
110
+ expect(result.error).toBeInstanceOf(StorageWriteError);
111
+ });
112
+ });
113
+
114
+ describe('removeItem', () => {
115
+ it('should remove item successfully', async () => {
116
+ const key = 'test-key';
117
+
118
+ // Setup
119
+ await AsyncStorage.setItem(key, 'test-value');
120
+
121
+ const result = await baseOps.removeItem(key);
122
+
123
+ expect(result.success).toBe(true);
124
+
125
+ // Verify removal
126
+ const stored = await AsyncStorage.getItem(key);
127
+ expect(stored).toBeNull();
128
+ });
129
+
130
+ it('should handle storage delete error', async () => {
131
+ const key = 'test-key';
132
+
133
+ // Mock storage error
134
+ (AsyncStorage.removeItem as jest.Mock).mockRejectedValue(new Error('Storage error'));
135
+
136
+ const result = await baseOps.removeItem(key);
137
+
138
+ expect(result.success).toBe(false);
139
+ expect(result.error).toBeInstanceOf(StorageDeleteError);
140
+ });
141
+ });
142
+
143
+ describe('hasItem', () => {
144
+ it('should return true for existing item', async () => {
145
+ const key = 'test-key';
146
+
147
+ // Setup
148
+ await AsyncStorage.setItem(key, 'test-value');
149
+
150
+ const exists = await baseOps.hasItem(key);
151
+
152
+ expect(exists).toBe(true);
153
+ });
154
+
155
+ it('should return false for missing item', async () => {
156
+ const key = 'missing-key';
157
+
158
+ const exists = await baseOps.hasItem(key);
159
+
160
+ expect(exists).toBe(false);
161
+ });
162
+
163
+ it('should return false on storage error', async () => {
164
+ const key = 'test-key';
165
+
166
+ // Mock storage error
167
+ (AsyncStorage.getItem as jest.Mock).mockRejectedValue(new Error('Storage error'));
168
+
169
+ const exists = await baseOps.hasItem(key);
170
+
171
+ expect(exists).toBe(false);
172
+ });
173
+ });
174
+
175
+ describe('clearAll', () => {
176
+ it('should clear all storage successfully', async () => {
177
+ // Setup
178
+ await AsyncStorage.setItem('key1', 'value1');
179
+ await AsyncStorage.setItem('key2', 'value2');
180
+
181
+ const result = await baseOps.clearAll();
182
+
183
+ expect(result.success).toBe(true);
184
+
185
+ // Verify clear
186
+ const keys = await AsyncStorage.getAllKeys();
187
+ expect(keys).toHaveLength(0);
188
+ });
189
+
190
+ it('should handle storage clear error', async () => {
191
+ // Mock storage error
192
+ (AsyncStorage.clear as jest.Mock).mockRejectedValue(new Error('Storage error'));
193
+
194
+ const result = await baseOps.clearAll();
195
+
196
+ expect(result.success).toBe(false);
197
+ expect(result.error).toBeInstanceOf(StorageDeleteError);
198
+ });
199
+ });
200
+ });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Cache Storage Operations
3
+ *
4
+ * Handles cache storage operations following Single Responsibility Principle
5
+ */
6
+
7
+ import { storageRepository } from '../../infrastructure/repositories/AsyncStorageRepository';
8
+ import type { CachedValue } from '../../domain/entities/CachedValue';
9
+ import { createCachedValue } from '../../domain/entities/CachedValue';
10
+ import { devWarn } from '../../domain/utils/devUtils';
11
+
12
+ export interface CacheStorageOptions {
13
+ ttl?: number;
14
+ version?: number;
15
+ enabled?: boolean;
16
+ }
17
+
18
+ /**
19
+ * Handles cache storage operations with proper error handling and memory management
20
+ */
21
+ export class CacheStorageOperations {
22
+ private static instance: CacheStorageOperations | null = null;
23
+
24
+ /**
25
+ * Singleton pattern to prevent memory leaks
26
+ */
27
+ static getInstance(): CacheStorageOperations {
28
+ if (!CacheStorageOperations.instance) {
29
+ CacheStorageOperations.instance = new CacheStorageOperations();
30
+ }
31
+ return CacheStorageOperations.instance;
32
+ }
33
+
34
+ /**
35
+ * Reset singleton instance (useful for testing)
36
+ */
37
+ static resetInstance(): void {
38
+ CacheStorageOperations.instance = null;
39
+ }
40
+
41
+ /**
42
+ * Load cached data from storage
43
+ */
44
+ async loadFromStorage<T>(
45
+ key: string,
46
+ version?: number
47
+ ): Promise<CachedValue<T> | null> {
48
+ try {
49
+ const result = await storageRepository.getString(key, '');
50
+
51
+ if (result.success && result.data) {
52
+ const cached = JSON.parse(result.data) as CachedValue<T>;
53
+ return cached;
54
+ }
55
+
56
+ return null;
57
+ } catch (error) {
58
+ devWarn(`CacheStorageOperations: Failed to load cache for key "${key}"`, error);
59
+ return null;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Save data to cache storage
65
+ */
66
+ async saveToStorage<T>(
67
+ key: string,
68
+ value: T,
69
+ options: CacheStorageOptions = {}
70
+ ): Promise<void> {
71
+ const { ttl, version, enabled = true } = options;
72
+
73
+ if (!enabled) return;
74
+
75
+ try {
76
+ const cached = createCachedValue(value, ttl || 0, version);
77
+ await storageRepository.setString(key, JSON.stringify(cached));
78
+ } catch (error) {
79
+ devWarn(`CacheStorageOperations: Failed to save cache for key "${key}"`, error);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Clear cached data from storage
85
+ */
86
+ async clearFromStorage(key: string, enabled = true): Promise<void> {
87
+ if (!enabled) return;
88
+
89
+ try {
90
+ await storageRepository.removeItem(key);
91
+ } catch (error) {
92
+ devWarn(`CacheStorageOperations: Failed to clear cache for key "${key}"`, error);
93
+ }
94
+ }
95
+ }