@vaiftech/sdk-expo 1.0.5 → 1.0.7

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 (2) hide show
  1. package/README.md +595 -72
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -33,6 +33,19 @@ export default function App() {
33
33
  }
34
34
  ```
35
35
 
36
+ ## Hooks Overview
37
+
38
+ | Category | Hooks |
39
+ |----------|-------|
40
+ | **Auth** | `useAuth`, `useUser`, `useToken`, `usePasswordReset`, `useEmailVerification`, `useMagicLink`, `useOAuth`, `useMFA` |
41
+ | **Query** | `useQuery`, `useQueryById`, `useQueryFirst`, `usePaginatedQuery`, `useInfiniteQuery`, `useCount` |
42
+ | **Mutation** | `useCreate`, `useUpdate`, `useDelete`, `useUpsert`, `useBatchCreate`, `useBatchUpdate`, `useBatchDelete`, `useOptimisticMutation` |
43
+ | **Realtime** | `useSubscription`, `useChannel`, `usePresence`, `useRealtimeConnection`, `useBroadcast` |
44
+ | **Storage** | `useUpload`, `useDownload`, `useFile`, `useFiles`, `usePublicUrl` |
45
+ | **Functions** | `useFunction`, `useRpc`, `useFunctionQuery`, `useFunctionList`, `useBatchInvoke`, `useScheduledFunction` |
46
+ | **MongoDB** | `useMongoFind`, `useMongoFindOne`, `useMongoAggregate`, `useMongoInsertOne`, `useMongoInsertMany`, `useMongoUpdateOne`, `useMongoUpdateMany`, `useMongoDeleteOne`, `useMongoDeleteMany`, `useMongoInfiniteFind`, `useMongoCount`, `useMongoDistinct`, `useMongoCollection` |
47
+ | **Observability** | `useMetrics`, `useAuditLogs`, `useIncidents`, `useSystemHealth`, `useRealtimeStats`, `useErrorTracking` |
48
+
36
49
  ## Features
37
50
 
38
51
  ### Persistent Sessions
@@ -67,7 +80,7 @@ import { useUpload } from '@vaiftech/sdk-expo';
67
80
  import * as ImagePicker from 'expo-image-picker';
68
81
 
69
82
  function ImageUpload() {
70
- const { uploadUri, isUploading, progress } = useUpload();
83
+ const { upload, isUploading, progress } = useUpload();
71
84
 
72
85
  const pickAndUpload = async () => {
73
86
  const result = await ImagePicker.launchImageLibraryAsync({
@@ -77,7 +90,10 @@ function ImageUpload() {
77
90
 
78
91
  if (!result.canceled) {
79
92
  const { uri } = result.assets[0];
80
- const uploaded = await uploadUri(uri, `photos/${Date.now()}.jpg`);
93
+ // Convert URI to blob and upload
94
+ const response = await fetch(uri);
95
+ const blob = await response.blob();
96
+ const uploaded = await upload(blob, `photos/${Date.now()}.jpg`);
81
97
  console.log('Uploaded:', uploaded?.url);
82
98
  }
83
99
  };
@@ -94,122 +110,625 @@ function ImageUpload() {
94
110
  }
95
111
  ```
96
112
 
97
- ### Realtime Updates
113
+ ## Authentication
114
+
115
+ ```tsx
116
+ import { useAuth, useUser, useOAuth, useMFA } from '@vaiftech/sdk-expo';
117
+
118
+ function AuthScreen() {
119
+ const { user, isLoading, signIn, signUp, signOut } = useAuth();
120
+
121
+ if (isLoading) return <ActivityIndicator />;
122
+
123
+ if (!user) {
124
+ return (
125
+ <View>
126
+ <Button title="Sign In" onPress={() => signIn({ email, password })} />
127
+ <Button title="Sign Up" onPress={() => signUp({ email, password, name })} />
128
+ </View>
129
+ );
130
+ }
131
+
132
+ return (
133
+ <View>
134
+ <Text>Welcome, {user.email}</Text>
135
+ <Button title="Sign Out" onPress={signOut} />
136
+ </View>
137
+ );
138
+ }
139
+
140
+ // OAuth login
141
+ function OAuthLogin() {
142
+ const { signInWithProvider, isLoading } = useOAuth();
143
+
144
+ return (
145
+ <View>
146
+ <Button
147
+ title="Sign in with Google"
148
+ onPress={() => signInWithProvider('google')}
149
+ disabled={isLoading}
150
+ />
151
+ <Button
152
+ title="Sign in with Apple"
153
+ onPress={() => signInWithProvider('apple')}
154
+ disabled={isLoading}
155
+ />
156
+ </View>
157
+ );
158
+ }
159
+
160
+ // MFA setup
161
+ function MFASetup() {
162
+ const { enroll, verify, qrCode } = useMFA();
163
+
164
+ return (
165
+ <View>
166
+ {qrCode && <Image source={{ uri: qrCode }} style={{ width: 200, height: 200 }} />}
167
+ <Button title="Enable MFA" onPress={() => enroll({ type: 'totp' })} />
168
+ </View>
169
+ );
170
+ }
171
+ ```
172
+
173
+ ### Standalone Auth Provider
174
+
175
+ For auth-only applications:
176
+
177
+ ```tsx
178
+ import { AuthProvider, useAuth, useSession } from '@vaiftech/sdk-expo';
179
+
180
+ function App() {
181
+ return (
182
+ <AuthProvider config={{ url: 'https://api.vaif.studio', apiKey: 'your-key' }}>
183
+ <Navigation />
184
+ </AuthProvider>
185
+ );
186
+ }
187
+ ```
188
+
189
+ ## Data Fetching
190
+
191
+ ```tsx
192
+ import { useQuery, useQueryById, usePaginatedQuery, useInfiniteQuery } from '@vaiftech/sdk-expo';
193
+
194
+ interface Task {
195
+ id: string;
196
+ title: string;
197
+ completed: boolean;
198
+ }
199
+
200
+ // Basic query
201
+ function TaskList() {
202
+ const { data: tasks, isLoading, error, refetch } = useQuery<Task>('tasks', {
203
+ filters: [{ field: 'completed', operator: 'eq', value: false }],
204
+ orderBy: [{ field: 'createdAt', direction: 'desc' }],
205
+ });
206
+
207
+ if (isLoading) return <ActivityIndicator />;
208
+ if (error) return <Text>Error: {error.message}</Text>;
209
+
210
+ return (
211
+ <FlatList
212
+ data={tasks}
213
+ keyExtractor={(item) => item.id}
214
+ renderItem={({ item }) => <Text>{item.title}</Text>}
215
+ refreshing={isLoading}
216
+ onRefresh={refetch}
217
+ />
218
+ );
219
+ }
220
+
221
+ // Query by ID
222
+ function TaskDetail({ taskId }: { taskId: string }) {
223
+ const { data: task, isLoading } = useQueryById<Task>('tasks', taskId);
224
+
225
+ if (isLoading) return <ActivityIndicator />;
226
+ return <Text>{task?.title}</Text>;
227
+ }
228
+
229
+ // Paginated query
230
+ function PaginatedTasks() {
231
+ const { data, page, totalPages, nextPage, prevPage } = usePaginatedQuery<Task>('tasks', {
232
+ pageSize: 20,
233
+ });
234
+
235
+ return (
236
+ <View>
237
+ <FlatList data={data} renderItem={({ item }) => <Text>{item.title}</Text>} />
238
+ <View style={{ flexDirection: 'row' }}>
239
+ <Button title="Prev" onPress={prevPage} disabled={page === 1} />
240
+ <Text>{page} / {totalPages}</Text>
241
+ <Button title="Next" onPress={nextPage} disabled={page === totalPages} />
242
+ </View>
243
+ </View>
244
+ );
245
+ }
246
+
247
+ // Infinite scroll
248
+ function InfiniteTasks() {
249
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery<Task>('tasks');
250
+
251
+ return (
252
+ <FlatList
253
+ data={data}
254
+ keyExtractor={(item) => item.id}
255
+ renderItem={({ item }) => <Text>{item.title}</Text>}
256
+ onEndReached={() => hasNextPage && fetchNextPage()}
257
+ ListFooterComponent={isFetchingNextPage ? <ActivityIndicator /> : null}
258
+ />
259
+ );
260
+ }
261
+ ```
262
+
263
+ ## Mutations
264
+
265
+ ```tsx
266
+ import { useCreate, useUpdate, useDelete } from '@vaiftech/sdk-expo';
267
+
268
+ // Create
269
+ function CreateTask() {
270
+ const { create, isCreating } = useCreate<Task>('tasks');
271
+
272
+ return (
273
+ <Button
274
+ title={isCreating ? 'Creating...' : 'Add Task'}
275
+ onPress={() => create({ title: 'New Task', completed: false })}
276
+ disabled={isCreating}
277
+ />
278
+ );
279
+ }
280
+
281
+ // Update
282
+ function ToggleTask({ task }: { task: Task }) {
283
+ const { update, isUpdating } = useUpdate<Task>('tasks');
284
+
285
+ return (
286
+ <Switch
287
+ value={task.completed}
288
+ onValueChange={(completed) => update(task.id, { completed })}
289
+ disabled={isUpdating}
290
+ />
291
+ );
292
+ }
293
+
294
+ // Delete
295
+ function DeleteTask({ taskId }: { taskId: string }) {
296
+ const { remove, isDeleting } = useDelete('tasks');
297
+
298
+ return (
299
+ <Button
300
+ title="Delete"
301
+ onPress={() => remove(taskId)}
302
+ disabled={isDeleting}
303
+ color="red"
304
+ />
305
+ );
306
+ }
307
+ ```
308
+
309
+ ## Realtime
98
310
 
99
311
  ```tsx
100
- import { useRealtime, useRealtimePresence } from '@vaiftech/sdk-expo';
312
+ import { useSubscription, usePresence, useBroadcast, useChannel } from '@vaiftech/sdk-expo';
101
313
 
102
- function ChatRoom({ roomId }) {
103
- // Listen for new messages
104
- useRealtime('messages', {
314
+ // Listen for new messages
315
+ function MessageListener() {
316
+ useSubscription<Message>('messages', {
105
317
  filters: [{ field: 'room_id', operator: 'eq', value: roomId }],
106
- onInsert: (message) => {
107
- // New message received
108
- },
318
+ onInsert: (message) => console.log('New:', message),
319
+ onUpdate: (message) => console.log('Updated:', message),
320
+ onDelete: (message) => console.log('Deleted:', message.id),
109
321
  });
110
322
 
111
- // Track online users
112
- const { users, track, leave } = useRealtimePresence(`room-${roomId}`);
323
+ return null;
324
+ }
325
+
326
+ // Track online users
327
+ function OnlineUsers({ roomId }: { roomId: string }) {
328
+ const { users, track, leave } = usePresence(`room-${roomId}`);
113
329
 
114
330
  useEffect(() => {
115
- track({ status: 'online' });
331
+ track({ status: 'online', name: currentUser.name });
116
332
  return () => leave();
117
333
  }, []);
118
334
 
119
335
  return (
120
336
  <View>
121
337
  <Text>Online: {users.length}</Text>
338
+ {users.map(u => <Text key={u.id}>{u.name}</Text>)}
122
339
  </View>
123
340
  );
124
341
  }
125
- ```
126
342
 
127
- ## Hooks Reference
343
+ // Broadcast typing indicator
344
+ function TypingIndicator({ roomId }: { roomId: string }) {
345
+ const { send, subscribe } = useBroadcast(`room-${roomId}`);
346
+ const [typing, setTyping] = useState<string[]>([]);
128
347
 
129
- ### Authentication
348
+ useEffect(() => {
349
+ subscribe('typing', (event) => {
350
+ setTyping(prev => [...prev, event.userName]);
351
+ setTimeout(() => {
352
+ setTyping(prev => prev.filter(n => n !== event.userName));
353
+ }, 3000);
354
+ });
355
+ }, []);
130
356
 
131
- ```tsx
132
- import { useAuth, useUser, useSession } from '@vaiftech/sdk-expo';
133
-
134
- // Full auth functionality
135
- const {
136
- user,
137
- isLoading,
138
- isAuthenticated,
139
- signIn,
140
- signUp,
141
- signOut,
142
- signInWithOAuth,
143
- updateProfile,
144
- changePassword,
145
- } = useAuth();
146
-
147
- // Just the user
148
- const { user, isLoading } = useUser();
149
-
150
- // Session management
151
- const { session, refresh } = useSession();
357
+ return (
358
+ <Text>{typing.length > 0 ? `${typing.join(', ')} typing...` : ''}</Text>
359
+ );
360
+ }
361
+
362
+ // Channel messaging
363
+ function ChatRoom({ roomId }: { roomId: string }) {
364
+ const channel = useChannel(`room-${roomId}`);
365
+
366
+ useEffect(() => {
367
+ channel.on('message', (msg) => {
368
+ // Handle incoming message
369
+ });
370
+ return () => channel.off('message');
371
+ }, [channel]);
372
+
373
+ const sendMessage = (text: string) => {
374
+ channel.send('message', { text, userId: currentUser.id });
375
+ };
376
+
377
+ return <View>{/* Chat UI */}</View>;
378
+ }
152
379
  ```
153
380
 
154
- ### Data
381
+ ## Storage
155
382
 
156
383
  ```tsx
157
- import { useQuery, useMutation, useRealtime } from '@vaiftech/sdk-expo';
384
+ import { useUpload, useDownload, useFile, useFiles, usePublicUrl } from '@vaiftech/sdk-expo';
158
385
 
159
- // Fetch data
160
- const { data, isLoading, error, refetch } = useQuery<Post>('posts', {
161
- filters: [{ field: 'published', operator: 'eq', value: true }],
162
- orderBy: [{ field: 'createdAt', direction: 'desc' }],
163
- });
386
+ // Upload with progress
387
+ function FileUpload() {
388
+ const { upload, isUploading, progress } = useUpload();
389
+
390
+ return (
391
+ <View>
392
+ <Button
393
+ title={isUploading ? `${progress}%` : 'Upload'}
394
+ onPress={async () => {
395
+ const result = await upload(fileBlob, 'uploads/file.pdf');
396
+ Alert.alert('Uploaded!', result?.url);
397
+ }}
398
+ disabled={isUploading}
399
+ />
400
+ {isUploading && <ProgressBar progress={progress / 100} />}
401
+ </View>
402
+ );
403
+ }
164
404
 
165
- // Mutations
166
- const { create, update, remove, isLoading } = useMutation<Post>('posts');
405
+ // Download file
406
+ function DownloadFile({ path }: { path: string }) {
407
+ const { download, isDownloading } = useDownload();
167
408
 
168
- // Realtime subscriptions
169
- useRealtime<Post>('posts', {
170
- onInsert: (post) => { /* new post */ },
171
- onUpdate: (post) => { /* updated post */ },
172
- onDelete: (post) => { /* deleted post */ },
173
- });
409
+ return (
410
+ <Button
411
+ title={isDownloading ? 'Downloading...' : 'Download'}
412
+ onPress={async () => {
413
+ const blob = await download(path);
414
+ // Save to device or open
415
+ }}
416
+ />
417
+ );
418
+ }
419
+
420
+ // Display image
421
+ function ProfileImage({ path }: { path: string }) {
422
+ const url = usePublicUrl(path);
423
+ return url ? <Image source={{ uri: url }} style={styles.avatar} /> : null;
424
+ }
425
+
426
+ // List files
427
+ function FileList({ folder }: { folder: string }) {
428
+ const { files, isLoading } = useFiles({ prefix: folder });
429
+
430
+ if (isLoading) return <ActivityIndicator />;
431
+
432
+ return (
433
+ <FlatList
434
+ data={files}
435
+ keyExtractor={(f) => f.path}
436
+ renderItem={({ item }) => (
437
+ <Text>{item.name} ({item.size} bytes)</Text>
438
+ )}
439
+ />
440
+ );
441
+ }
174
442
  ```
175
443
 
176
- ### Storage
444
+ ## Edge Functions
177
445
 
178
446
  ```tsx
179
- import { useUpload, useDownload, usePublicUrl } from '@vaiftech/sdk-expo';
447
+ import { useFunction, useRpc, useBatchInvoke } from '@vaiftech/sdk-expo';
448
+
449
+ // Invoke function
450
+ function ProcessImage() {
451
+ const { invoke, isInvoking, result, error } = useFunction('process-image');
452
+
453
+ return (
454
+ <Button
455
+ title={isInvoking ? 'Processing...' : 'Process'}
456
+ onPress={() => invoke({ imageUrl: 'https://...' })}
457
+ disabled={isInvoking}
458
+ />
459
+ );
460
+ }
461
+
462
+ // RPC-style call
463
+ function Calculator() {
464
+ const { call, isLoading, data } = useRpc<{ result: number }>('calculate');
465
+
466
+ return (
467
+ <View>
468
+ <Button title="Calculate" onPress={() => call({ a: 5, b: 3, op: 'add' })} />
469
+ {data && <Text>Result: {data.result}</Text>}
470
+ </View>
471
+ );
472
+ }
180
473
 
181
- // Upload files
182
- const { upload, uploadUri, isUploading, progress } = useUpload();
474
+ // Batch operations
475
+ function BatchProcessor() {
476
+ const { invoke, isInvoking, results } = useBatchInvoke();
183
477
 
184
- // Download files
185
- const { download, isDownloading } = useDownload();
478
+ const processAll = async () => {
479
+ await invoke([
480
+ { name: 'resize', payload: { url: 'img1.jpg' } },
481
+ { name: 'resize', payload: { url: 'img2.jpg' } },
482
+ ]);
483
+ };
186
484
 
187
- // Get public URL
188
- const url = usePublicUrl('avatars/user-123.jpg');
485
+ return <Button title="Process All" onPress={processAll} disabled={isInvoking} />;
486
+ }
189
487
  ```
190
488
 
191
- ### Functions
489
+ ## MongoDB Hooks
192
490
 
193
491
  ```tsx
194
- import { useFunction } from '@vaiftech/sdk-expo';
492
+ import {
493
+ useMongoFind,
494
+ useMongoFindOne,
495
+ useMongoInsertOne,
496
+ useMongoUpdateOne,
497
+ useMongoDeleteOne,
498
+ useMongoAggregate,
499
+ useMongoInfiniteFind,
500
+ useMongoCollection,
501
+ } from '@vaiftech/sdk-expo';
502
+
503
+ // Find documents
504
+ function UserList() {
505
+ const { data: users, isLoading, refetch } = useMongoFind<User>('users', {
506
+ filter: { status: 'active' },
507
+ sort: { createdAt: -1 },
508
+ limit: 20,
509
+ });
510
+
511
+ if (isLoading) return <ActivityIndicator />;
512
+
513
+ return (
514
+ <FlatList
515
+ data={users}
516
+ keyExtractor={(item) => item._id}
517
+ renderItem={({ item }) => <Text>{item.name}</Text>}
518
+ refreshing={isLoading}
519
+ onRefresh={refetch}
520
+ />
521
+ );
522
+ }
523
+
524
+ // Find single document
525
+ function UserProfile({ id }: { id: string }) {
526
+ const { data: user, isLoading } = useMongoFindOne<User>('users', { _id: id });
527
+ return user ? <Text>{user.name}</Text> : null;
528
+ }
529
+
530
+ // Insert document
531
+ function CreateUser() {
532
+ const { insertOne, isInserting } = useMongoInsertOne<User>('users');
533
+
534
+ return (
535
+ <Button
536
+ title={isInserting ? 'Creating...' : 'Create User'}
537
+ onPress={() => insertOne({ name: 'New User', email: 'new@example.com' })}
538
+ disabled={isInserting}
539
+ />
540
+ );
541
+ }
542
+
543
+ // Update document
544
+ function UpdateUser({ id }: { id: string }) {
545
+ const { updateOne, isUpdating } = useMongoUpdateOne('users');
546
+
547
+ return (
548
+ <Button
549
+ title="Update"
550
+ onPress={() => updateOne({ _id: id }, { $set: { updatedAt: new Date() } })}
551
+ disabled={isUpdating}
552
+ />
553
+ );
554
+ }
555
+
556
+ // Delete document
557
+ function DeleteUser({ id }: { id: string }) {
558
+ const { deleteOne, isDeleting } = useMongoDeleteOne('users');
559
+
560
+ return (
561
+ <Button
562
+ title="Delete"
563
+ onPress={() => deleteOne({ _id: id })}
564
+ disabled={isDeleting}
565
+ color="red"
566
+ />
567
+ );
568
+ }
569
+
570
+ // Aggregation
571
+ function UserStats() {
572
+ const { data: stats } = useMongoAggregate<{ _id: string; count: number }>('users', [
573
+ { $match: { status: 'active' } },
574
+ { $group: { _id: '$country', count: { $sum: 1 } } },
575
+ { $sort: { count: -1 } },
576
+ ]);
577
+
578
+ return (
579
+ <View>
580
+ {stats?.map(stat => (
581
+ <Text key={stat._id}>{stat._id}: {stat.count}</Text>
582
+ ))}
583
+ </View>
584
+ );
585
+ }
195
586
 
196
- const { invoke, isInvoking, result, error } = useFunction('process-image');
587
+ // Infinite scroll
588
+ function InfiniteUsers() {
589
+ const { data, fetchNextPage, hasNextPage } = useMongoInfiniteFind<User>('users', {
590
+ filter: { status: 'active' },
591
+ limit: 20,
592
+ });
197
593
 
198
- const handleProcess = async () => {
199
- await invoke({ imageUrl: 'https://...' });
200
- };
594
+ return (
595
+ <FlatList
596
+ data={data}
597
+ keyExtractor={(item) => item._id}
598
+ renderItem={({ item }) => <Text>{item.name}</Text>}
599
+ onEndReached={() => hasNextPage && fetchNextPage()}
600
+ />
601
+ );
602
+ }
603
+
604
+ // Full collection access
605
+ function BulkOps() {
606
+ const collection = useMongoCollection<User>('users');
607
+
608
+ const handleBulk = async () => {
609
+ const count = await collection.count({ status: 'active' });
610
+ const countries = await collection.distinct('country');
611
+ await collection.bulkWrite([
612
+ { insertOne: { document: { name: 'Bulk User' } } },
613
+ ]);
614
+ };
615
+
616
+ return <Button title="Run Bulk Ops" onPress={handleBulk} />;
617
+ }
201
618
  ```
202
619
 
203
- ### Presence & Broadcast
620
+ ## Observability Hooks
204
621
 
205
622
  ```tsx
206
- import { useRealtimePresence, useRealtimeBroadcast } from '@vaiftech/sdk-expo';
623
+ import {
624
+ useMetrics,
625
+ useAuditLogs,
626
+ useIncidents,
627
+ useSystemHealth,
628
+ useRealtimeStats,
629
+ useErrorTracking,
630
+ } from '@vaiftech/sdk-expo';
631
+
632
+ // Metrics dashboard
633
+ function MetricsDashboard() {
634
+ const { metrics, timeRange, setTimeRange, refresh } = useMetrics({
635
+ sources: ['api', 'database'],
636
+ interval: '1h',
637
+ });
638
+
639
+ return (
640
+ <View>
641
+ <Picker selectedValue={timeRange} onValueChange={setTimeRange}>
642
+ <Picker.Item label="Last Hour" value="1h" />
643
+ <Picker.Item label="Last 24 Hours" value="24h" />
644
+ <Picker.Item label="Last 7 Days" value="7d" />
645
+ </Picker>
646
+ {metrics?.map(m => (
647
+ <Text key={m.name}>{m.name}: {m.value}</Text>
648
+ ))}
649
+ </View>
650
+ );
651
+ }
652
+
653
+ // Audit logs
654
+ function AuditLogs() {
655
+ const { logs, hasMore, loadMore } = useAuditLogs({ limit: 50 });
656
+
657
+ return (
658
+ <FlatList
659
+ data={logs}
660
+ keyExtractor={(item) => item.id}
661
+ renderItem={({ item }) => (
662
+ <Text>{item.timestamp}: {item.action} by {item.userId}</Text>
663
+ )}
664
+ onEndReached={() => hasMore && loadMore()}
665
+ />
666
+ );
667
+ }
668
+
669
+ // Incidents
670
+ function IncidentList() {
671
+ const { incidents, activeCount, acknowledge, resolve } = useIncidents();
672
+
673
+ return (
674
+ <View>
675
+ <Text>Active: {activeCount}</Text>
676
+ {incidents?.map(incident => (
677
+ <View key={incident.id}>
678
+ <Text>{incident.title} - {incident.severity}</Text>
679
+ {incident.status === 'open' && (
680
+ <Button title="Ack" onPress={() => acknowledge(incident.id)} />
681
+ )}
682
+ {incident.status === 'acknowledged' && (
683
+ <Button title="Resolve" onPress={() => resolve(incident.id)} />
684
+ )}
685
+ </View>
686
+ ))}
687
+ </View>
688
+ );
689
+ }
207
690
 
208
- // Presence tracking
209
- const { users, track, leave } = useRealtimePresence('room-1');
691
+ // System health
692
+ function SystemHealth() {
693
+ const { isHealthy, services, lastCheck } = useSystemHealth({ pollInterval: 30000 });
210
694
 
211
- // Broadcast messaging
212
- const { send, lastMessage, subscribe } = useRealtimeBroadcast('room-1');
695
+ return (
696
+ <View>
697
+ <Text>Status: {isHealthy ? 'Healthy' : 'Degraded'}</Text>
698
+ <Text>Last: {lastCheck?.toLocaleString()}</Text>
699
+ {services?.map(svc => (
700
+ <Text key={svc.name}>{svc.name}: {svc.status} ({svc.latency}ms)</Text>
701
+ ))}
702
+ </View>
703
+ );
704
+ }
705
+
706
+ // Realtime stats
707
+ function RealtimeStats() {
708
+ const { connections, requestsPerSecond, activeUsers } = useRealtimeStats();
709
+
710
+ return (
711
+ <View>
712
+ <Text>Connections: {connections}</Text>
713
+ <Text>Requests/sec: {requestsPerSecond}</Text>
714
+ <Text>Active Users: {activeUsers}</Text>
715
+ </View>
716
+ );
717
+ }
718
+
719
+ // Error tracking
720
+ function ErrorTracker() {
721
+ const { errorRate, topErrors, trackError } = useErrorTracking({ timeRange: '24h' });
722
+
723
+ return (
724
+ <View>
725
+ <Text>Error Rate: {errorRate}%</Text>
726
+ {topErrors?.map(err => (
727
+ <Text key={err.message}>{err.message} ({err.count})</Text>
728
+ ))}
729
+ </View>
730
+ );
731
+ }
213
732
  ```
214
733
 
215
734
  ## TypeScript Support
@@ -225,11 +744,15 @@ interface User {
225
744
 
226
745
  const { data } = useQuery<User>('users');
227
746
  // data is User[] | undefined
747
+
748
+ const { data: user } = useMongoFindOne<User>('users', { _id: id });
749
+ // user is User | null | undefined
228
750
  ```
229
751
 
230
752
  ## Related Packages
231
753
 
232
754
  - [@vaiftech/client](https://www.npmjs.com/package/@vaiftech/client) - Core client SDK
755
+ - [@vaiftech/auth](https://www.npmjs.com/package/@vaiftech/auth) - Standalone auth client
233
756
  - [@vaiftech/react](https://www.npmjs.com/package/@vaiftech/react) - React (web) hooks
234
757
  - [@vaiftech/cli](https://www.npmjs.com/package/@vaiftech/cli) - CLI tools
235
758
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaiftech/sdk-expo",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "VAIF SDK for React Native and Expo applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",