@umituz/react-native-storage 2.0.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 (34) hide show
  1. package/package.json +23 -7
  2. package/src/__tests__/integration.test.ts +391 -0
  3. package/src/__tests__/mocks/asyncStorage.mock.ts +52 -0
  4. package/src/__tests__/performance.test.ts +351 -0
  5. package/src/__tests__/setup.ts +63 -0
  6. package/src/application/ports/IStorageRepository.ts +0 -12
  7. package/src/domain/entities/StorageResult.ts +1 -3
  8. package/src/domain/entities/__tests__/CachedValue.test.ts +149 -0
  9. package/src/domain/entities/__tests__/StorageResult.test.ts +122 -0
  10. package/src/domain/errors/StorageError.ts +0 -2
  11. package/src/domain/errors/__tests__/StorageError.test.ts +127 -0
  12. package/src/domain/utils/__tests__/devUtils.test.ts +97 -0
  13. package/src/domain/utils/devUtils.ts +37 -0
  14. package/src/domain/value-objects/StorageKey.ts +27 -29
  15. package/src/index.ts +9 -1
  16. package/src/infrastructure/adapters/StorageService.ts +8 -6
  17. package/src/infrastructure/repositories/AsyncStorageRepository.ts +27 -108
  18. package/src/infrastructure/repositories/BaseStorageOperations.ts +101 -0
  19. package/src/infrastructure/repositories/BatchStorageOperations.ts +42 -0
  20. package/src/infrastructure/repositories/StringStorageOperations.ts +44 -0
  21. package/src/infrastructure/repositories/__tests__/AsyncStorageRepository.test.ts +169 -0
  22. package/src/infrastructure/repositories/__tests__/BaseStorageOperations.test.ts +200 -0
  23. package/src/presentation/hooks/CacheStorageOperations.ts +95 -0
  24. package/src/presentation/hooks/__tests__/usePersistentCache.test.ts +404 -0
  25. package/src/presentation/hooks/__tests__/useStorage.test.ts +246 -0
  26. package/src/presentation/hooks/__tests__/useStorageState.test.ts +292 -0
  27. package/src/presentation/hooks/useCacheState.ts +55 -0
  28. package/src/presentation/hooks/usePersistentCache.ts +30 -39
  29. package/src/presentation/hooks/useStorage.ts +4 -3
  30. package/src/presentation/hooks/useStorageState.ts +24 -8
  31. package/src/presentation/hooks/useStore.ts +3 -1
  32. package/src/types/global.d.ts +40 -0
  33. package/LICENSE +0 -22
  34. package/src/presentation/hooks/usePersistedState.ts +0 -34
@@ -0,0 +1,122 @@
1
+ /**
2
+ * StorageResult Entity Tests
3
+ *
4
+ * Unit tests for StorageResult entity
5
+ */
6
+
7
+ import { success, failure, unwrap, map, isSuccess, isFailure } from '../../domain/entities/StorageResult';
8
+ import { StorageError } from '../../domain/errors/StorageError';
9
+
10
+ describe('StorageResult Entity', () => {
11
+ describe('success', () => {
12
+ it('should create a successful result', () => {
13
+ const data = { test: 'value' };
14
+ const result = success(data);
15
+
16
+ expect(result.success).toBe(true);
17
+ expect(result.data).toBe(data);
18
+ expect(result.error).toBeUndefined();
19
+ });
20
+ });
21
+
22
+ describe('failure', () => {
23
+ it('should create a failure result', () => {
24
+ const error = new StorageError('test-key', new Error('Test error'));
25
+ const defaultValue = 'default';
26
+ const result = failure(error, defaultValue);
27
+
28
+ expect(result.success).toBe(false);
29
+ expect(result.data).toBe(defaultValue);
30
+ expect(result.error).toBe(error);
31
+ });
32
+ });
33
+
34
+ describe('unwrap', () => {
35
+ it('should return data for successful result', () => {
36
+ const data = { test: 'value' };
37
+ const result = success(data);
38
+ const defaultValue = 'default';
39
+
40
+ const unwrapped = unwrap(result, defaultValue);
41
+
42
+ expect(unwrapped).toBe(data);
43
+ });
44
+
45
+ it('should return default value for failure result', () => {
46
+ const error = new StorageError('test-key', new Error('Test error'));
47
+ const defaultValue = 'default';
48
+ const result = failure(error, defaultValue);
49
+
50
+ const unwrapped = unwrap(result, defaultValue);
51
+
52
+ expect(unwrapped).toBe(defaultValue);
53
+ });
54
+ });
55
+
56
+ describe('map', () => {
57
+ it('should map successful result', () => {
58
+ const data = { count: 1 };
59
+ const result = success(data);
60
+ const mapper = (value: typeof data) => ({ ...value, count: value.count + 1 });
61
+
62
+ const mapped = map(result, mapper);
63
+
64
+ expect(mapped.success).toBe(true);
65
+ expect(mapped.data).toEqual({ count: 2 });
66
+ });
67
+
68
+ it('should not map failure result', () => {
69
+ const error = new StorageError('test-key', new Error('Test error'));
70
+ const defaultValue = { count: 0 };
71
+ const result = failure(error, defaultValue);
72
+ const mapper = jest.fn();
73
+
74
+ const mapped = map(result, mapper);
75
+
76
+ expect(mapped.success).toBe(false);
77
+ expect(mapper).not.toHaveBeenCalled();
78
+ });
79
+ });
80
+
81
+ describe('isSuccess', () => {
82
+ it('should return true for successful result', () => {
83
+ const result = success('test');
84
+ expect(isSuccess(result)).toBe(true);
85
+ });
86
+
87
+ it('should return false for failure result', () => {
88
+ const error = new StorageError('test-key', new Error('Test error'));
89
+ const result = failure(error);
90
+ expect(isSuccess(result)).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe('isFailure', () => {
95
+ it('should return false for successful result', () => {
96
+ const result = success('test');
97
+ expect(isFailure(result)).toBe(false);
98
+ });
99
+
100
+ it('should return true for failure result', () => {
101
+ const error = new StorageError('test-key', new Error('Test error'));
102
+ const result = failure(error);
103
+ expect(isFailure(result)).toBe(true);
104
+ });
105
+ });
106
+
107
+ describe('Type Safety', () => {
108
+ it('should maintain type information', () => {
109
+ interface TestData {
110
+ id: number;
111
+ name: string;
112
+ }
113
+
114
+ const data: TestData = { id: 1, name: 'test' };
115
+ const result = success<TestData>(data);
116
+
117
+ // TypeScript should infer the type correctly
118
+ const typedData: TestData = result.data;
119
+ expect(typedData).toEqual(data);
120
+ });
121
+ });
122
+ });
@@ -3,8 +3,6 @@
3
3
  *
4
4
  * Domain-Driven Design: Domain errors for storage operations
5
5
  * Typed errors for better error handling and debugging
6
- *
7
- * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
8
6
  */
9
7
 
10
8
  /**
@@ -0,0 +1,127 @@
1
+ /**
2
+ * StorageError Tests
3
+ *
4
+ * Unit tests for StorageError classes
5
+ */
6
+
7
+ import {
8
+ StorageError,
9
+ StorageReadError,
10
+ StorageWriteError,
11
+ StorageDeleteError,
12
+ StorageSerializationError,
13
+ StorageDeserializationError,
14
+ } from '../../domain/errors/StorageError';
15
+
16
+ describe('StorageError Classes', () => {
17
+ describe('StorageError', () => {
18
+ it('should create base storage error', () => {
19
+ const key = 'test-key';
20
+ const cause = new Error('Original error');
21
+ const error = new StorageError(key, cause);
22
+
23
+ expect(error.key).toBe(key);
24
+ expect(error.cause).toBe(cause);
25
+ expect(error.name).toBe('StorageError');
26
+ expect(error.message).toContain(key);
27
+ });
28
+
29
+ it('should serialize error details', () => {
30
+ const key = 'test-key';
31
+ const cause = new Error('Original error');
32
+ const error = new StorageError(key, cause);
33
+
34
+ const serialized = JSON.stringify(error);
35
+ const parsed = JSON.parse(serialized);
36
+
37
+ expect(parsed.key).toBe(key);
38
+ expect(parsed.message).toContain(key);
39
+ });
40
+ });
41
+
42
+ describe('StorageReadError', () => {
43
+ it('should create read error', () => {
44
+ const key = 'test-key';
45
+ const cause = new Error('Read failed');
46
+ const error = new StorageReadError(key, cause);
47
+
48
+ expect(error.key).toBe(key);
49
+ expect(error.cause).toBe(cause);
50
+ expect(error.name).toBe('StorageReadError');
51
+ expect(error.message).toContain('read');
52
+ });
53
+ });
54
+
55
+ describe('StorageWriteError', () => {
56
+ it('should create write error', () => {
57
+ const key = 'test-key';
58
+ const cause = new Error('Write failed');
59
+ const error = new StorageWriteError(key, cause);
60
+
61
+ expect(error.key).toBe(key);
62
+ expect(error.cause).toBe(cause);
63
+ expect(error.name).toBe('StorageWriteError');
64
+ expect(error.message).toContain('write');
65
+ });
66
+ });
67
+
68
+ describe('StorageDeleteError', () => {
69
+ it('should create delete error', () => {
70
+ const key = 'test-key';
71
+ const cause = new Error('Delete failed');
72
+ const error = new StorageDeleteError(key, cause);
73
+
74
+ expect(error.key).toBe(key);
75
+ expect(error.cause).toBe(cause);
76
+ expect(error.name).toBe('StorageDeleteError');
77
+ expect(error.message).toContain('delete');
78
+ });
79
+ });
80
+
81
+ describe('StorageSerializationError', () => {
82
+ it('should create serialization error', () => {
83
+ const key = 'test-key';
84
+ const cause = new Error('Serialization failed');
85
+ const error = new StorageSerializationError(key, cause);
86
+
87
+ expect(error.key).toBe(key);
88
+ expect(error.cause).toBe(cause);
89
+ expect(error.name).toBe('StorageSerializationError');
90
+ expect(error.message).toContain('serialize');
91
+ });
92
+ });
93
+
94
+ describe('StorageDeserializationError', () => {
95
+ it('should create deserialization error', () => {
96
+ const key = 'test-key';
97
+ const cause = new Error('Deserialization failed');
98
+ const error = new StorageDeserializationError(key, cause);
99
+
100
+ expect(error.key).toBe(key);
101
+ expect(error.cause).toBe(cause);
102
+ expect(error.name).toBe('StorageDeserializationError');
103
+ expect(error.message).toContain('deserialize');
104
+ });
105
+ });
106
+
107
+ describe('Error Inheritance', () => {
108
+ it('should maintain error chain', () => {
109
+ const key = 'test-key';
110
+ const cause = new Error('Original error');
111
+ const error = new StorageReadError(key, cause);
112
+
113
+ expect(error instanceof Error).toBe(true);
114
+ expect(error instanceof StorageError).toBe(true);
115
+ expect(error instanceof StorageReadError).toBe(true);
116
+ });
117
+
118
+ it('should preserve stack trace', () => {
119
+ const key = 'test-key';
120
+ const cause = new Error('Original error');
121
+ const error = new StorageReadError(key, cause);
122
+
123
+ expect(error.stack).toBeDefined();
124
+ expect(error.stack).toContain('StorageReadError');
125
+ });
126
+ });
127
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Development Utilities Tests
3
+ */
4
+
5
+ import { isDev, devWarn, devError, devLog } from '../devUtils';
6
+
7
+ // Mock console methods
8
+ const mockConsoleWarn = jest.fn();
9
+ const mockConsoleError = jest.fn();
10
+ const mockConsoleLog = jest.fn();
11
+
12
+ describe('Development Utilities', () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+
16
+ // Mock console methods
17
+ console.warn = mockConsoleWarn;
18
+ console.error = mockConsoleError;
19
+ console.log = mockConsoleLog;
20
+ });
21
+
22
+ describe('isDev', () => {
23
+ it('should return true when __DEV__ is true', () => {
24
+ (globalThis as any).__DEV__ = true;
25
+ expect(isDev()).toBe(true);
26
+ });
27
+
28
+ it('should return false when __DEV__ is false', () => {
29
+ (globalThis as any).__DEV__ = false;
30
+ expect(isDev()).toBe(false);
31
+ });
32
+
33
+ it('should return false when __DEV__ is undefined', () => {
34
+ delete (globalThis as any).__DEV__;
35
+ expect(isDev()).toBe(false);
36
+ });
37
+ });
38
+
39
+ describe('devWarn', () => {
40
+ it('should log warning when in development mode', () => {
41
+ (globalThis as any).__DEV__ = true;
42
+
43
+ devWarn('Test warning', { data: 'test' });
44
+
45
+ expect(mockConsoleWarn).toHaveBeenCalledWith('Test warning', { data: 'test' });
46
+ });
47
+
48
+ it('should not log warning when not in development mode', () => {
49
+ (globalThis as any).__DEV__ = false;
50
+
51
+ devWarn('Test warning', { data: 'test' });
52
+
53
+ expect(mockConsoleWarn).not.toHaveBeenCalled();
54
+ });
55
+ });
56
+
57
+ describe('devError', () => {
58
+ it('should log error when in development mode', () => {
59
+ (globalThis as any).__DEV__ = true;
60
+
61
+ devError('Test error', new Error('test'));
62
+
63
+ expect(mockConsoleError).toHaveBeenCalledWith('Test error', new Error('test'));
64
+ });
65
+
66
+ it('should not log error when not in development mode', () => {
67
+ (globalThis as any).__DEV__ = false;
68
+
69
+ devError('Test error', new Error('test'));
70
+
71
+ expect(mockConsoleError).not.toHaveBeenCalled();
72
+ });
73
+ });
74
+
75
+ describe('devLog', () => {
76
+ it('should log info when in development mode', () => {
77
+ (globalThis as any).__DEV__ = true;
78
+
79
+ devLog('Test log', { info: 'test' });
80
+
81
+ expect(mockConsoleLog).toHaveBeenCalledWith('Test log', { info: 'test' });
82
+ });
83
+
84
+ it('should not log info when not in development mode', () => {
85
+ (globalThis as any).__DEV__ = false;
86
+
87
+ devLog('Test log', { info: 'test' });
88
+
89
+ expect(mockConsoleLog).not.toHaveBeenCalled();
90
+ });
91
+ });
92
+
93
+ afterEach(() => {
94
+ // Clean up __DEV__ after each test
95
+ delete (globalThis as any).__DEV__;
96
+ });
97
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Development utilities
3
+ */
4
+
5
+ /**
6
+ * Check if running in development mode
7
+ */
8
+ export const isDev = (): boolean => {
9
+ return (globalThis as any).__DEV__ === true;
10
+ };
11
+
12
+ /**
13
+ * Log warning in development mode only
14
+ */
15
+ export const devWarn = (message: string, ...args: any[]): void => {
16
+ if (isDev()) {
17
+ console.warn(message, ...args);
18
+ }
19
+ };
20
+
21
+ /**
22
+ * Log error in development mode only
23
+ */
24
+ export const devError = (message: string, ...args: any[]): void => {
25
+ if (isDev()) {
26
+ console.error(message, ...args);
27
+ }
28
+ };
29
+
30
+ /**
31
+ * Log info in development mode only
32
+ */
33
+ export const devLog = (message: string, ...args: any[]): void => {
34
+ if (isDev()) {
35
+ console.log(message, ...args);
36
+ }
37
+ };
@@ -3,39 +3,13 @@
3
3
  *
4
4
  * Domain-Driven Design: Value Object for storage keys
5
5
  * Ensures type safety and prevents invalid key usage
6
- *
7
- * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
8
- */
9
-
10
- /**
11
- * Storage Key Type
12
- * All valid storage keys must be defined here
13
6
  */
14
- export enum StorageKey {
15
- // Onboarding
16
- ONBOARDING_COMPLETED = '@onboarding_completed',
17
-
18
- // Auth
19
- AUTH_SHOW_REGISTER = '@auth_show_register',
20
-
21
- // Localization
22
- LANGUAGE = '@app_language',
23
-
24
- // Theme
25
- THEME_MODE = '@app_theme_mode',
26
-
27
- // Settings (requires userId suffix)
28
- SETTINGS = 'app_settings',
29
-
30
- // Query Cache (requires app name prefix)
31
- QUERY_CACHE = '{{APP_NAME}}_query_cache',
32
- }
33
7
 
34
8
  /**
35
9
  * Storage key with dynamic suffix
36
10
  */
37
11
  export type DynamicStorageKey = {
38
- base: StorageKey;
12
+ base: string;
39
13
  suffix: string;
40
14
  };
41
15
 
@@ -46,7 +20,7 @@ export type DynamicStorageKey = {
46
20
  * @param userId User identifier
47
21
  * @returns User-specific key
48
22
  */
49
- export const createUserKey = (baseKey: StorageKey, userId: string): string => {
23
+ export const createUserKey = (baseKey: string, userId: string): string => {
50
24
  return `${baseKey}_${userId}`;
51
25
  };
52
26
 
@@ -57,6 +31,30 @@ export const createUserKey = (baseKey: StorageKey, userId: string): string => {
57
31
  * @param appName App identifier
58
32
  * @returns App-specific key
59
33
  */
60
- export const createAppKey = (baseKey: StorageKey, appName: string): string => {
34
+ export const createAppKey = (baseKey: string, appName: string): string => {
61
35
  return `${appName}_${baseKey}`;
62
36
  };
37
+
38
+ /**
39
+ * Helper to create namespaced storage key
40
+ *
41
+ * @param namespace Key namespace
42
+ * @param key Specific key
43
+ * @returns Namespaced key
44
+ */
45
+ export const createNamespacedKey = (namespace: string, key: string): string => {
46
+ return `${namespace}:${key}`;
47
+ };
48
+
49
+ /**
50
+ * Legacy StorageKey enum for backward compatibility
51
+ * @deprecated Use createNamespacedKey instead
52
+ */
53
+ export enum StorageKey {
54
+ USER_PREFERENCES = '@user_preferences',
55
+ APP_SETTINGS = '@app_settings',
56
+ LANGUAGE = '@language',
57
+ UI_PREFERENCES = '@ui_preferences',
58
+ QUERY_CACHE = '@query_cache',
59
+ DATA_CACHE = '@data_cache',
60
+ }
package/src/index.ts CHANGED
@@ -78,6 +78,12 @@ export {
78
78
 
79
79
  export { TIME_MS, DEFAULT_TTL, CACHE_VERSION } from './domain/constants/CacheDefaults';
80
80
 
81
+ // =============================================================================
82
+ // DOMAIN LAYER - Development Utilities
83
+ // =============================================================================
84
+
85
+ export { isDev, devWarn, devError, devLog } from './domain/utils/devUtils';
86
+
81
87
  // =============================================================================
82
88
  // DOMAIN LAYER - Store Types
83
89
  // =============================================================================
@@ -117,10 +123,12 @@ export {
117
123
  export { useStorage } from './presentation/hooks/useStorage';
118
124
  export { useStorageState } from './presentation/hooks/useStorageState';
119
125
  export { useStore } from './presentation/hooks/useStore';
120
- export { usePersistedState } from './presentation/hooks/usePersistedState';
121
126
 
122
127
  export {
123
128
  usePersistentCache,
124
129
  type PersistentCacheOptions,
125
130
  type PersistentCacheResult,
126
131
  } from './presentation/hooks/usePersistentCache';
132
+
133
+ export { useCacheState } from './presentation/hooks/useCacheState';
134
+ export { CacheStorageOperations } from './presentation/hooks/CacheStorageOperations';
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import AsyncStorage from '@react-native-async-storage/async-storage';
9
+ import { devWarn } from '../../domain/utils/devUtils';
9
10
 
10
11
  /**
11
12
  * StateStorage interface for Zustand persist middleware
@@ -18,13 +19,14 @@ export interface StateStorage {
18
19
 
19
20
  /**
20
21
  * Storage service for Zustand persist middleware
21
- * Direct AsyncStorage implementation
22
+ * Direct AsyncStorage implementation with proper error handling
22
23
  */
23
24
  export const storageService: StateStorage = {
24
25
  getItem: async (name: string): Promise<string | null> => {
25
26
  try {
26
27
  return await AsyncStorage.getItem(name);
27
- } catch {
28
+ } catch (error) {
29
+ devWarn(`StorageService: Failed to get item "${name}"`, error);
28
30
  return null;
29
31
  }
30
32
  },
@@ -32,16 +34,16 @@ export const storageService: StateStorage = {
32
34
  setItem: async (name: string, value: string): Promise<void> => {
33
35
  try {
34
36
  await AsyncStorage.setItem(name, value);
35
- } catch {
36
- // Silent fail
37
+ } catch (error) {
38
+ devWarn(`StorageService: Failed to set item "${name}"`, error);
37
39
  }
38
40
  },
39
41
 
40
42
  removeItem: async (name: string): Promise<void> => {
41
43
  try {
42
44
  await AsyncStorage.removeItem(name);
43
- } catch {
44
- // Silent fail
45
+ } catch (error) {
46
+ devWarn(`StorageService: Failed to remove item "${name}"`, error);
45
47
  }
46
48
  },
47
49
  };