@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,149 @@
1
+ /**
2
+ * CachedValue Entity Tests
3
+ *
4
+ * Unit tests for CachedValue entity
5
+ */
6
+
7
+ import {
8
+ createCachedValue,
9
+ isCacheExpired,
10
+ getRemainingTTL,
11
+ getCacheAge
12
+ } from '../../domain/entities/CachedValue';
13
+
14
+ describe('CachedValue Entity', () => {
15
+ const mockNow = 1000000;
16
+
17
+ beforeEach(() => {
18
+ jest.spyOn(Date, 'now').mockReturnValue(mockNow);
19
+ });
20
+
21
+ afterEach(() => {
22
+ jest.restoreAllMocks();
23
+ });
24
+
25
+ describe('createCachedValue', () => {
26
+ it('should create cached value with default values', () => {
27
+ const data = { test: 'value' };
28
+ const cached = createCachedValue(data);
29
+
30
+ expect(cached.value).toBe(data);
31
+ expect(cached.timestamp).toBe(mockNow);
32
+ expect(cached.ttl).toBeDefined();
33
+ expect(cached.version).toBeUndefined();
34
+ });
35
+
36
+ it('should create cached value with custom TTL', () => {
37
+ const data = { test: 'value' };
38
+ const ttl = 60000; // 1 minute
39
+ const cached = createCachedValue(data, ttl);
40
+
41
+ expect(cached.value).toBe(data);
42
+ expect(cached.timestamp).toBe(mockNow);
43
+ expect(cached.ttl).toBe(ttl);
44
+ });
45
+
46
+ it('should create cached value with version', () => {
47
+ const data = { test: 'value' };
48
+ const version = 2;
49
+ const cached = createCachedValue(data, undefined, version);
50
+
51
+ expect(cached.value).toBe(data);
52
+ expect(cached.timestamp).toBe(mockNow);
53
+ expect(cached.version).toBe(version);
54
+ });
55
+ });
56
+
57
+ describe('isCacheExpired', () => {
58
+ it('should return false for fresh cache', () => {
59
+ const data = { test: 'value' };
60
+ const ttl = 60000; // 1 minute
61
+ const cached = createCachedValue(data, ttl);
62
+
63
+ expect(isCacheExpired(cached)).toBe(false);
64
+ });
65
+
66
+ it('should return true for expired cache', () => {
67
+ const data = { test: 'value' };
68
+ const ttl = 1000; // 1 second
69
+ const cached = createCachedValue(data, ttl, mockNow - 2000); // Created 2 seconds ago
70
+
71
+ expect(isCacheExpired(cached)).toBe(true);
72
+ });
73
+
74
+ it('should handle version mismatch', () => {
75
+ const data = { test: 'value' };
76
+ const cached = createCachedValue(data, 60000, 1);
77
+ const currentVersion = 2;
78
+
79
+ expect(isCacheExpired(cached, currentVersion)).toBe(true);
80
+ });
81
+
82
+ it('should return false when version matches', () => {
83
+ const data = { test: 'value' };
84
+ const cached = createCachedValue(data, 60000, 1);
85
+ const currentVersion = 1;
86
+
87
+ expect(isCacheExpired(cached, currentVersion)).toBe(false);
88
+ });
89
+ });
90
+
91
+ describe('getRemainingTTL', () => {
92
+ it('should return remaining TTL for fresh cache', () => {
93
+ const data = { test: 'value' };
94
+ const ttl = 60000; // 1 minute
95
+ const cached = createCachedValue(data, ttl, mockNow - 30000); // Created 30 seconds ago
96
+
97
+ const remaining = getRemainingTTL(cached);
98
+ expect(remaining).toBe(30000); // 30 seconds remaining
99
+ });
100
+
101
+ it('should return 0 for expired cache', () => {
102
+ const data = { test: 'value' };
103
+ const ttl = 1000; // 1 second
104
+ const cached = createCachedValue(data, ttl, mockNow - 2000); // Created 2 seconds ago
105
+
106
+ const remaining = getRemainingTTL(cached);
107
+ expect(remaining).toBe(0);
108
+ });
109
+
110
+ it('should handle missing TTL', () => {
111
+ const data = { test: 'value' };
112
+ const cached = createCachedValue(data);
113
+
114
+ const remaining = getRemainingTTL(cached);
115
+ expect(remaining).toBeGreaterThan(0);
116
+ });
117
+ });
118
+
119
+ describe('getCacheAge', () => {
120
+ it('should return correct age', () => {
121
+ const data = { test: 'value' };
122
+ const age = 30000; // 30 seconds
123
+ const cached = createCachedValue(data, undefined, undefined, mockNow - age);
124
+
125
+ const cacheAge = getCacheAge(cached);
126
+ expect(cacheAge).toBe(age);
127
+ });
128
+
129
+ it('should return 0 for new cache', () => {
130
+ const data = { test: 'value' };
131
+ const cached = createCachedValue(data);
132
+
133
+ const cacheAge = getCacheAge(cached);
134
+ expect(cacheAge).toBe(0);
135
+ });
136
+ });
137
+
138
+ describe('Immutability', () => {
139
+ it('should create immutable cached value', () => {
140
+ const data = { test: 'value' };
141
+ const cached = createCachedValue(data);
142
+
143
+ // Attempt to modify
144
+ (cached as any).value = { modified: true };
145
+
146
+ expect(cached.value).toEqual(data);
147
+ });
148
+ });
149
+ });
@@ -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,33 @@
1
+ /**
2
+ * Store Factory
3
+ * Create Zustand stores with AsyncStorage persistence
4
+ */
5
+
6
+ import { create } from 'zustand';
7
+ import { persist, createJSONStorage } from 'zustand/middleware';
8
+ import type { StoreConfig } from '../types/Store';
9
+ import { storageService } from '../../infrastructure/adapters/StorageService';
10
+
11
+ export function createStore<T extends object>(config: StoreConfig<T>) {
12
+ if (!config.persist) {
13
+ return create<T>(() => config.initialState);
14
+ }
15
+
16
+ return create<T>()(
17
+ persist(
18
+ () => config.initialState,
19
+ {
20
+ name: config.name,
21
+ storage: createJSONStorage(() => storageService),
22
+ version: config.version || 1,
23
+ partialize: config.partialize,
24
+ onRehydrateStorage: () => (state) => {
25
+ if (state && config.onRehydrate) {
26
+ config.onRehydrate(state);
27
+ }
28
+ },
29
+ migrate: config.migrate as any,
30
+ }
31
+ )
32
+ );
33
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Store Types
3
+ */
4
+
5
+ export interface StoreConfig<T> {
6
+ name: string;
7
+ initialState: T;
8
+ persist?: boolean;
9
+ version?: number;
10
+ partialize?: (state: T) => Partial<T>;
11
+ onRehydrate?: (state: T) => void;
12
+ migrate?: (persistedState: unknown, version: number) => T;
13
+ }
14
+
15
+ export interface PersistedState<T> {
16
+ state: T;
17
+ version: number;
18
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Cache Key Generator
3
+ * Domain layer - Utility for generating consistent cache keys
4
+ *
5
+ * General-purpose key generation for any app
6
+ */
7
+
8
+ /**
9
+ * Generate a cache key from prefix and identifier
10
+ * @param prefix - Cache key prefix (e.g., 'posts', 'products', 'user')
11
+ * @param id - Unique identifier
12
+ * @returns Formatted cache key
13
+ *
14
+ * @example
15
+ * generateCacheKey('posts', '123') // 'cache:posts:123'
16
+ * generateCacheKey('user', 'profile') // 'cache:user:profile'
17
+ */
18
+ export function generateCacheKey(prefix: string, id: string | number): string {
19
+ return `cache:${prefix}:${id}`;
20
+ }
21
+
22
+ /**
23
+ * Generate a cache key for a list/collection
24
+ * @param prefix - Cache key prefix
25
+ * @param params - Optional query parameters
26
+ *
27
+ * @example
28
+ * generateListCacheKey('posts') // 'cache:posts:list'
29
+ * generateListCacheKey('posts', { page: 1 }) // 'cache:posts:list:page=1'
30
+ */
31
+ export function generateListCacheKey(
32
+ prefix: string,
33
+ params?: Record<string, string | number>,
34
+ ): string {
35
+ const base = `cache:${prefix}:list`;
36
+ if (!params) return base;
37
+
38
+ const paramString = Object.entries(params)
39
+ .sort(([a], [b]) => a.localeCompare(b))
40
+ .map(([key, value]) => `${key}=${value}`)
41
+ .join(':');
42
+
43
+ return `${base}:${paramString}`;
44
+ }
45
+
46
+ /**
47
+ * Parse a cache key to extract prefix and id
48
+ * @param key - Cache key to parse
49
+ * @returns Object with prefix and id, or null if invalid
50
+ */
51
+ export function parseCacheKey(key: string): { prefix: string; id: string } | null {
52
+ const parts = key.split(':');
53
+ if (parts.length < 3 || parts[0] !== 'cache') return null;
54
+
55
+ return {
56
+ prefix: parts[1],
57
+ id: parts.slice(2).join(':'),
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Check if a key is a cache key
63
+ */
64
+ export function isCacheKey(key: string): boolean {
65
+ return key.startsWith('cache:');
66
+ }
@@ -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
+ };