@teardown/react-native 2.0.0 → 2.0.1

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.
@@ -0,0 +1,345 @@
1
+ # Logging
2
+
3
+ Structured logging system with configurable log levels.
4
+
5
+ ## Overview
6
+
7
+ The LoggingClient provides:
8
+ - Configurable log levels
9
+ - Named loggers for each client
10
+ - Console binding preservation
11
+ - Debug mode support
12
+
13
+ ## Log Levels
14
+
15
+ ```typescript
16
+ type LogLevel =
17
+ | "none" // No logging
18
+ | "error" // Only errors
19
+ | "warn" // Errors and warnings
20
+ | "info" // Errors, warnings, and info
21
+ | "verbose" // Everything including debug
22
+ ```
23
+
24
+ ### Level Priority
25
+
26
+ ```
27
+ none < error < warn < info < verbose
28
+ ```
29
+
30
+ When you set a level, all lower-priority levels are also shown:
31
+ - `error`: Shows only errors
32
+ - `warn`: Shows errors + warnings
33
+ - `info`: Shows errors + warnings + info
34
+ - `verbose`: Shows everything (errors + warnings + info + debug)
35
+
36
+ ## Setting Log Level
37
+
38
+ ### On Initialization
39
+
40
+ ```typescript
41
+ const teardown = new TeardownCore({
42
+ // ... other options
43
+ });
44
+
45
+ // Set log level after initialization
46
+ teardown.setLogLevel('verbose');
47
+ ```
48
+
49
+ ### During Runtime
50
+
51
+ ```typescript
52
+ import { useTeardown } from '@teardown/react-native';
53
+
54
+ function DebugPanel() {
55
+ const { core } = useTeardown();
56
+
57
+ const enableDebugMode = () => {
58
+ core.setLogLevel('verbose');
59
+ };
60
+
61
+ const disableLogging = () => {
62
+ core.setLogLevel('none');
63
+ };
64
+
65
+ return (
66
+ <View>
67
+ <Button title="Enable Debug" onPress={enableDebugMode} />
68
+ <Button title="Disable Logs" onPress={disableLogging} />
69
+ </View>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## Log Output Format
75
+
76
+ All logs are prefixed with the client name:
77
+
78
+ ```
79
+ [Teardown:TeardownCore] Shutting down TeardownCore
80
+ [Teardown:IdentityClient] Identify state: unidentified -> identifying
81
+ [Teardown:IdentityClient] Identify state: identifying -> identified
82
+ [Teardown:ForceUpdateClient] Version status changing: initializing -> checking
83
+ [Teardown:ForceUpdateClient] Checking version status on foreground
84
+ [Teardown:DeviceClient] Getting device ID
85
+ [Teardown:StorageClient] Creating storage for teardown:v1:identity
86
+ ```
87
+
88
+ ## Logger Methods
89
+
90
+ Each client has a logger with these methods:
91
+
92
+ ```typescript
93
+ logger.info(message, ...args) // Info level
94
+ logger.warn(message, ...args) // Warning level
95
+ logger.error(message, ...args) // Error level
96
+ logger.debug(message, ...args) // Debug level (verbose)
97
+ logger.trace(message, ...args) // Stack trace (verbose)
98
+ ```
99
+
100
+ ## Internal Logging
101
+
102
+ The SDK logs these events:
103
+
104
+ ### TeardownCore
105
+
106
+ ```typescript
107
+ [Teardown:TeardownCore] Error initializing TeardownCore { error }
108
+ [Teardown:TeardownCore] Shutting down TeardownCore
109
+ ```
110
+
111
+ ### IdentityClient
112
+
113
+ ```typescript
114
+ [Teardown:IdentityClient] Identify state: unidentified -> identifying
115
+ [Teardown:IdentityClient] Identify state: identifying -> identified
116
+ [Teardown:IdentityClient] 422 Error identifying user { ... }
117
+ ```
118
+
119
+ ### ForceUpdateClient
120
+
121
+ ```typescript
122
+ [Teardown:ForceUpdateClient] Version status changing: initializing -> up_to_date
123
+ [Teardown:ForceUpdateClient] Checking version status on foreground
124
+ [Teardown:ForceUpdateClient] Skipping version check - not identified
125
+ ```
126
+
127
+ ### DeviceClient
128
+
129
+ ```typescript
130
+ [Teardown:DeviceClient] Getting device ID
131
+ ```
132
+
133
+ ### StorageClient
134
+
135
+ ```typescript
136
+ [Teardown:StorageClient] Creating storage for teardown:v1:identity
137
+ [Teardown:StorageClient] Storage already exists for teardown:v1:identity
138
+ [Teardown:StorageClient] Returning existing storage for teardown:v1:identity
139
+ [Teardown:StorageClient] Clearing storage for teardown:v1:identity
140
+ ```
141
+
142
+ ## Console Binding
143
+
144
+ The logger preserves console call sites for better debugging:
145
+
146
+ ```typescript
147
+ // Bound console methods preserve original call site
148
+ private boundConsole = {
149
+ log: console.log.bind(console),
150
+ error: console.error.bind(console),
151
+ debug: console.debug.bind(console),
152
+ warn: console.warn.bind(console),
153
+ trace: console.trace.bind(console),
154
+ };
155
+ ```
156
+
157
+ This means clicking log messages in DevTools takes you to the original SDK code, not the logger wrapper.
158
+
159
+ ## Creating Custom Loggers
160
+
161
+ While not typically needed, you can create custom loggers:
162
+
163
+ ```typescript
164
+ import { LoggingClient } from '@teardown/react-native';
165
+
166
+ const logging = new LoggingClient({ logLevel: 'info' });
167
+ const logger = logging.createLogger({ name: 'MyFeature' });
168
+
169
+ logger.info('Feature initialized');
170
+ logger.error('Something went wrong');
171
+ ```
172
+
173
+ ## Conditional Logging
174
+
175
+ The SDK checks log level before logging:
176
+
177
+ ```typescript
178
+ // Only logs if current level >= info
179
+ if (this.loggingClient.shouldLog('info')) {
180
+ logger.info('Message');
181
+ }
182
+ ```
183
+
184
+ This prevents unnecessary string concatenation and formatting in production.
185
+
186
+ ## Best Practices
187
+
188
+ ### 1. Use 'none' in Production
189
+
190
+ ```typescript
191
+ // ✅ Good - no logs in production
192
+ const logLevel = __DEV__ ? 'verbose' : 'none';
193
+ teardown.setLogLevel(logLevel);
194
+ ```
195
+
196
+ ### 2. Use 'verbose' for Debugging
197
+
198
+ ```typescript
199
+ // ✅ Good - detailed logs during development
200
+ if (__DEV__) {
201
+ teardown.setLogLevel('verbose');
202
+ }
203
+ ```
204
+
205
+ ### 3. Enable Logging for Support
206
+
207
+ ```typescript
208
+ // ✅ Good - user can enable logs for support
209
+ function Settings() {
210
+ const { core } = useTeardown();
211
+ const [debugMode, setDebugMode] = useState(false);
212
+
213
+ const toggleDebug = () => {
214
+ const newMode = !debugMode;
215
+ setDebugMode(newMode);
216
+ core.setLogLevel(newMode ? 'verbose' : 'none');
217
+ };
218
+
219
+ return <Switch value={debugMode} onValueChange={toggleDebug} />;
220
+ }
221
+ ```
222
+
223
+ ### 4. Don't Rely on Logs for Logic
224
+
225
+ ```typescript
226
+ // ❌ Bad - logs may be disabled
227
+ logger.info('User identified');
228
+ // ... rely on this log
229
+
230
+ // ✅ Good - use state/events
231
+ const result = await identify();
232
+ if (result.success) {
233
+ // ... use result, not logs
234
+ }
235
+ ```
236
+
237
+ ## Log Level Examples
238
+
239
+ ### Development
240
+
241
+ ```typescript
242
+ // See everything
243
+ teardown.setLogLevel('verbose');
244
+
245
+ // Output:
246
+ // [Teardown:IdentityClient] Getting device ID
247
+ // [Teardown:IdentityClient] Identify state: unidentified -> identifying
248
+ // [Teardown:StorageClient] Creating storage for teardown:v1:identity
249
+ // etc.
250
+ ```
251
+
252
+ ### Debugging Issues
253
+
254
+ ```typescript
255
+ // See important events
256
+ teardown.setLogLevel('info');
257
+
258
+ // Output:
259
+ // [Teardown:IdentityClient] Identify state: unidentified -> identifying
260
+ // [Teardown:ForceUpdateClient] Version status changing: initializing -> up_to_date
261
+ ```
262
+
263
+ ### Production
264
+
265
+ ```typescript
266
+ // See only errors
267
+ teardown.setLogLevel('error');
268
+
269
+ // Output:
270
+ // [Teardown:TeardownCore] Error initializing TeardownCore { error }
271
+ // [Teardown:IdentityClient] 422 Error identifying user { ... }
272
+ ```
273
+
274
+ ### Disabled
275
+
276
+ ```typescript
277
+ // No logs
278
+ teardown.setLogLevel('none');
279
+
280
+ // Output:
281
+ // (nothing)
282
+ ```
283
+
284
+ ## Environment-Based Configuration
285
+
286
+ ```typescript
287
+ const LOG_LEVEL: LogLevel =
288
+ process.env.NODE_ENV === 'development' ? 'verbose' :
289
+ process.env.NODE_ENV === 'staging' ? 'info' :
290
+ 'error';
291
+
292
+ teardown.setLogLevel(LOG_LEVEL);
293
+ ```
294
+
295
+ ## Debugging Tips
296
+
297
+ ### 1. Enable Verbose Logging
298
+
299
+ ```typescript
300
+ teardown.setLogLevel('verbose');
301
+ ```
302
+
303
+ ### 2. Filter by Client
304
+
305
+ Search DevTools console for specific client:
306
+ ```
307
+ [Teardown:IdentityClient]
308
+ ```
309
+
310
+ ### 3. Track State Transitions
311
+
312
+ Look for state change logs:
313
+ ```
314
+ Identify state: unidentified -> identifying
315
+ Version status changing: checking -> up_to_date
316
+ ```
317
+
318
+ ### 4. Check Error Details
319
+
320
+ Errors include full context:
321
+ ```typescript
322
+ logger.error('Error initializing', { error, context });
323
+ ```
324
+
325
+ ## Performance
326
+
327
+ Logging is optimized for production:
328
+
329
+ ```typescript
330
+ // ✅ Good - check happens before string operations
331
+ if (this.shouldLog('verbose')) {
332
+ logger.debug(`Expensive operation: ${JSON.stringify(bigObject)}`);
333
+ }
334
+
335
+ // ❌ Bad - string created even if logging disabled
336
+ logger.debug(`Expensive operation: ${JSON.stringify(bigObject)}`);
337
+ ```
338
+
339
+ The SDK uses the first pattern internally.
340
+
341
+ ## Next Steps
342
+
343
+ - [API Reference](./07-api-reference.mdx)
344
+ - [Hooks Reference](./08-hooks-reference.mdx)
345
+ - [Advanced Usage](./09-advanced.mdx)
@@ -0,0 +1,349 @@
1
+ # Storage
2
+
3
+ Persistent, namespaced storage with platform adapters.
4
+
5
+ ## Overview
6
+
7
+ The StorageClient provides:
8
+ - Namespaced key-value storage
9
+ - Platform-agnostic interface
10
+ - Multiple storage adapters
11
+ - Automatic cleanup
12
+
13
+ ## Storage Adapters
14
+
15
+ ### MMKV Adapter (Recommended)
16
+
17
+ Fast, encrypted storage using react-native-mmkv:
18
+
19
+ ```typescript
20
+ import { TeardownCore } from '@teardown/react-native';
21
+ import { createMMKVStorageFactory } from '@teardown/react-native/mmkv';
22
+
23
+ const teardown = new TeardownCore({
24
+ // ... other options
25
+ storageFactory: createMMKVStorageFactory(),
26
+ });
27
+ ```
28
+
29
+ Install dependency:
30
+ ```bash
31
+ bun add react-native-mmkv
32
+ ```
33
+
34
+ Benefits:
35
+ - ⚡ Extremely fast (synchronous)
36
+ - 🔒 Encrypted by default
37
+ - 📦 Small bundle size
38
+ - 💾 Persistent across app restarts
39
+
40
+ ### Custom Storage Adapter
41
+
42
+ Create your own storage adapter:
43
+
44
+ ```typescript
45
+ import type { SupportedStorage } from '@teardown/react-native';
46
+
47
+ function createCustomStorageFactory() {
48
+ return (storageKey: string): SupportedStorage => {
49
+ // Your storage implementation
50
+ return {
51
+ preload: () => {
52
+ // Load data into memory if needed
53
+ },
54
+ getItem: (key: string) => {
55
+ // Return value or null
56
+ return localStorage.getItem(`${storageKey}:${key}`);
57
+ },
58
+ setItem: (key: string, value: string) => {
59
+ // Store value
60
+ localStorage.setItem(`${storageKey}:${key}`, value);
61
+ },
62
+ removeItem: (key: string) => {
63
+ // Remove value
64
+ localStorage.removeItem(`${storageKey}:${key}`);
65
+ },
66
+ clear: () => {
67
+ // Clear all keys for this namespace
68
+ },
69
+ keys: () => {
70
+ // Return all keys
71
+ return Object.keys(localStorage);
72
+ },
73
+ };
74
+ };
75
+ }
76
+ ```
77
+
78
+ ## Storage Interface
79
+
80
+ ```typescript
81
+ type SupportedStorage = {
82
+ preload: () => void;
83
+ getItem: (key: string) => string | null;
84
+ setItem: (key: string, value: string) => void;
85
+ removeItem: (key: string) => void;
86
+ clear: () => void;
87
+ keys: () => string[];
88
+ }
89
+ ```
90
+
91
+ ## Namespacing
92
+
93
+ Storage is automatically namespaced to prevent key collisions:
94
+
95
+ ```typescript
96
+ // Internal storage keys are prefixed
97
+ teardown:v1:identity:IDENTIFY_STATE
98
+ teardown:v1:device:deviceId
99
+ teardown:v1:version:VERSION_STATUS
100
+ ```
101
+
102
+ This prevents conflicts with:
103
+ - Your app's storage
104
+ - Other libraries
105
+ - Multiple Teardown instances
106
+
107
+ ## How It Works
108
+
109
+ ### 1. Storage Factory
110
+
111
+ The storage factory creates namespaced storage instances:
112
+
113
+ ```typescript
114
+ // You provide the factory
115
+ storageFactory: createMMKVStorageFactory()
116
+
117
+ // SDK calls it for each client
118
+ const identityStorage = storage.createStorage('identity');
119
+ const deviceStorage = storage.createStorage('device');
120
+ ```
121
+
122
+ ### 2. Client Storage
123
+
124
+ Each client gets its own namespaced storage:
125
+
126
+ ```typescript
127
+ class IdentityClient {
128
+ constructor(storage: StorageClient) {
129
+ // Creates "teardown:v1:identity" namespace
130
+ this.storage = storage.createStorage('identity');
131
+ }
132
+
133
+ saveSession(session: Session) {
134
+ // Actually saves to "teardown:v1:identity:IDENTIFY_STATE"
135
+ this.storage.setItem('IDENTIFY_STATE', JSON.stringify(session));
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### 3. Automatic Persistence
141
+
142
+ The SDK automatically persists critical state:
143
+
144
+ - Identity state (session data)
145
+ - Version status
146
+ - Device ID
147
+
148
+ ## Internal Storage Keys
149
+
150
+ The SDK uses these storage keys internally:
151
+
152
+ | Client | Key | Data |
153
+ |--------|-----|------|
154
+ | Identity | `IDENTIFY_STATE` | User session and identity state |
155
+ | Device | `deviceId` | Generated device UUID |
156
+ | ForceUpdate | `VERSION_STATUS` | Current version status |
157
+
158
+ ## Creating Storage Instances
159
+
160
+ You typically don't create storage instances directly - clients do it internally:
161
+
162
+ ```typescript
163
+ // Internal SDK code
164
+ class MyClient {
165
+ constructor(storage: StorageClient) {
166
+ this.storage = storage.createStorage('my-namespace');
167
+ }
168
+ }
169
+ ```
170
+
171
+ If you need custom storage:
172
+
173
+ ```typescript
174
+ import { useTeardown } from '@teardown/react-native';
175
+
176
+ function MyComponent() {
177
+ const { core } = useTeardown();
178
+
179
+ // Access internal storage (not recommended)
180
+ // Better to use your own storage solution for app data
181
+ }
182
+ ```
183
+
184
+ ## Data Format
185
+
186
+ All data is stored as JSON strings:
187
+
188
+ ```typescript
189
+ // Stored
190
+ storage.setItem('key', JSON.stringify({ foo: 'bar' }));
191
+
192
+ // Retrieved
193
+ const data = JSON.parse(storage.getItem('key'));
194
+ ```
195
+
196
+ ## Cleanup
197
+
198
+ Storage is automatically cleaned up on SDK shutdown:
199
+
200
+ ```typescript
201
+ // Happens automatically when TeardownProvider unmounts
202
+ core.shutdown();
203
+ ```
204
+
205
+ This clears all namespaced storage instances.
206
+
207
+ ## Best Practices
208
+
209
+ ### 1. Use MMKV for Production
210
+
211
+ ```typescript
212
+ // ✅ Recommended - fast and encrypted
213
+ storageFactory: createMMKVStorageFactory()
214
+
215
+ // ⚠️ Avoid in production - slow and unencrypted
216
+ storageFactory: createAsyncStorageFactory()
217
+ ```
218
+
219
+ ### 2. Don't Access Internal Storage Directly
220
+
221
+ ```typescript
222
+ // ❌ Bad - bypasses SDK abstractions
223
+ MMKV.set('teardown:v1:identity:IDENTIFY_STATE', '...');
224
+
225
+ // ✅ Good - use SDK methods
226
+ await core.identity.identify({...});
227
+ ```
228
+
229
+ ### 3. Use Your Own Storage for App Data
230
+
231
+ ```typescript
232
+ // ❌ Bad - mixing SDK and app data
233
+ core.storage.setItem('user-preferences', '...');
234
+
235
+ // ✅ Good - separate storage for app
236
+ import { MMKV } from 'react-native-mmkv';
237
+ const appStorage = new MMKV({ id: 'app-storage' });
238
+ appStorage.set('user-preferences', '...');
239
+ ```
240
+
241
+ ### 4. Handle Storage Errors
242
+
243
+ ```typescript
244
+ // Storage operations can throw
245
+ try {
246
+ const value = storage.getItem('key');
247
+ } catch (error) {
248
+ console.error('Storage error:', error);
249
+ // Handle error (fallback, retry, etc.)
250
+ }
251
+ ```
252
+
253
+ ## Migrations
254
+
255
+ If you need to migrate storage data:
256
+
257
+ ```typescript
258
+ function migrateStorage() {
259
+ const oldKey = 'old-app:session';
260
+ const newKey = 'teardown:v1:identity:IDENTIFY_STATE';
261
+
262
+ const oldData = localStorage.getItem(oldKey);
263
+ if (oldData) {
264
+ // Transform data if needed
265
+ const newData = transformData(oldData);
266
+ localStorage.setItem(newKey, newData);
267
+ localStorage.removeItem(oldKey);
268
+ }
269
+ }
270
+
271
+ // Run before initializing Teardown
272
+ migrateStorage();
273
+ const teardown = new TeardownCore({...});
274
+ ```
275
+
276
+ ## Storage Size
277
+
278
+ The SDK stores minimal data:
279
+
280
+ - Identity state: ~500 bytes
281
+ - Device ID: ~36 bytes (UUID)
282
+ - Version status: ~50 bytes
283
+
284
+ Total: Less than 1KB for all SDK data.
285
+
286
+ ## Encryption
287
+
288
+ ### MMKV
289
+
290
+ MMKV encrypts data by default:
291
+
292
+ ```typescript
293
+ // Already encrypted
294
+ storageFactory: createMMKVStorageFactory()
295
+ ```
296
+
297
+ ### Custom Encryption
298
+
299
+ For custom adapters, implement encryption:
300
+
301
+ ```typescript
302
+ import CryptoJS from 'crypto-js';
303
+
304
+ function createEncryptedStorage(encryptionKey: string) {
305
+ return (namespace: string) => ({
306
+ getItem: (key: string) => {
307
+ const encrypted = localStorage.getItem(`${namespace}:${key}`);
308
+ if (!encrypted) return null;
309
+ return CryptoJS.AES.decrypt(encrypted, encryptionKey).toString();
310
+ },
311
+ setItem: (key: string, value: string) => {
312
+ const encrypted = CryptoJS.AES.encrypt(value, encryptionKey).toString();
313
+ localStorage.setItem(`${namespace}:${key}`, encrypted);
314
+ },
315
+ // ... other methods
316
+ });
317
+ }
318
+ ```
319
+
320
+ ## Testing
321
+
322
+ Mock storage for testing:
323
+
324
+ ```typescript
325
+ function createMockStorageFactory() {
326
+ const store = new Map<string, string>();
327
+
328
+ return (namespace: string) => ({
329
+ preload: () => {},
330
+ getItem: (key: string) => store.get(`${namespace}:${key}`) ?? null,
331
+ setItem: (key: string, value: string) => store.set(`${namespace}:${key}`, value),
332
+ removeItem: (key: string) => store.delete(`${namespace}:${key}`),
333
+ clear: () => store.clear(),
334
+ keys: () => Array.from(store.keys()),
335
+ });
336
+ }
337
+
338
+ // Use in tests
339
+ const teardown = new TeardownCore({
340
+ storageFactory: createMockStorageFactory(),
341
+ // ...
342
+ });
343
+ ```
344
+
345
+ ## Next Steps
346
+
347
+ - [Logging](./06-logging.mdx)
348
+ - [API Reference](./07-api-reference.mdx)
349
+ - [Advanced Usage](./09-advanced.mdx)