@vaiftech/react 1.0.3 → 1.0.5

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 +563 -32
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -32,12 +32,23 @@ function App() {
32
32
  }
33
33
  ```
34
34
 
35
- ## Hooks
35
+ ## Hooks Overview
36
36
 
37
- ### Authentication
37
+ | Category | Hooks |
38
+ |----------|-------|
39
+ | **Auth** | `useAuth`, `useUser`, `useToken`, `usePasswordReset`, `useEmailVerification`, `useMagicLink`, `useOAuth`, `useMFA` |
40
+ | **Query** | `useQuery`, `useQueryById`, `useQueryFirst`, `usePaginatedQuery`, `useInfiniteQuery`, `useCount` |
41
+ | **Mutation** | `useMutation`, `useCreate`, `useUpdate`, `useDelete`, `useUpsert`, `useBatchCreate`, `useBatchUpdate`, `useBatchDelete`, `useOptimisticMutation` |
42
+ | **Realtime** | `useSubscription`, `useChannel`, `usePresence`, `useRealtimeConnection`, `useBroadcast` |
43
+ | **Storage** | `useUpload`, `useDownload`, `useFile`, `useFiles`, `useDropzone`, `usePublicUrl` |
44
+ | **Functions** | `useFunction`, `useRpc`, `useFunctionList`, `useBatchInvoke`, `useScheduledFunction` |
45
+ | **MongoDB** | `useMongoFind`, `useMongoFindOne`, `useMongoAggregate`, `useMongoInsertOne`, `useMongoInsertMany`, `useMongoUpdateOne`, `useMongoUpdateMany`, `useMongoDeleteOne`, `useMongoDeleteMany`, `useMongoInfiniteFind`, `useMongoCount`, `useMongoDistinct`, `useMongoCollection` |
46
+ | **Observability** | `useMetrics`, `useAuditLogs`, `useIncidents`, `useSystemHealth`, `useRealtimeStats`, `useErrorTracking` |
47
+
48
+ ## Authentication
38
49
 
39
50
  ```tsx
40
- import { useAuth, useUser, useSession } from '@vaiftech/react';
51
+ import { useAuth, useUser, useSession, useMFA, useOAuth } from '@vaiftech/react';
41
52
 
42
53
  function AuthComponent() {
43
54
  const { user, isLoading, signIn, signUp, signOut } = useAuth();
@@ -66,17 +77,79 @@ function Profile() {
66
77
  return user ? <p>{user.email}</p> : null;
67
78
  }
68
79
 
69
- // Session management
70
- function SessionInfo() {
71
- const { session, refresh } = useSession();
72
- return <p>Expires: {session?.expiresAt}</p>;
80
+ // OAuth login
81
+ function OAuthLogin() {
82
+ const { signInWithProvider, isLoading } = useOAuth();
83
+
84
+ return (
85
+ <div>
86
+ <button onClick={() => signInWithProvider('google')}>
87
+ Sign in with Google
88
+ </button>
89
+ <button onClick={() => signInWithProvider('github')}>
90
+ Sign in with GitHub
91
+ </button>
92
+ </div>
93
+ );
94
+ }
95
+
96
+ // MFA setup
97
+ function MFASetup() {
98
+ const { enroll, verify, isEnrolling, qrCode } = useMFA();
99
+
100
+ const handleSetup = async () => {
101
+ await enroll({ type: 'totp' });
102
+ };
103
+
104
+ return (
105
+ <div>
106
+ {qrCode && <img src={qrCode} alt="Scan with authenticator" />}
107
+ <button onClick={handleSetup}>Enable MFA</button>
108
+ </div>
109
+ );
73
110
  }
74
111
  ```
75
112
 
76
- ### Data Fetching
113
+ ### Standalone Auth Provider
114
+
115
+ For auth-only applications, use the standalone auth provider from `@vaiftech/auth`:
77
116
 
78
117
  ```tsx
79
- import { useQuery, useMutation } from '@vaiftech/react';
118
+ import { AuthProvider, useAuth, useSession } from '@vaiftech/react';
119
+
120
+ function App() {
121
+ return (
122
+ <AuthProvider config={{ url: 'https://api.vaif.studio', apiKey: 'your-key' }}>
123
+ <YourApp />
124
+ </AuthProvider>
125
+ );
126
+ }
127
+
128
+ function LoginPage() {
129
+ const { signInWithPassword, signInWithOAuth, isLoading } = useAuth();
130
+
131
+ return (
132
+ <div>
133
+ <form onSubmit={(e) => {
134
+ e.preventDefault();
135
+ signInWithPassword({ email, password });
136
+ }}>
137
+ <input type="email" />
138
+ <input type="password" />
139
+ <button type="submit">Sign In</button>
140
+ </form>
141
+ <button onClick={() => signInWithOAuth({ provider: 'google' })}>
142
+ Google
143
+ </button>
144
+ </div>
145
+ );
146
+ }
147
+ ```
148
+
149
+ ## Data Fetching
150
+
151
+ ```tsx
152
+ import { useQuery, useQueryById, usePaginatedQuery, useInfiniteQuery } from '@vaiftech/react';
80
153
 
81
154
  interface Post {
82
155
  id: string;
@@ -84,6 +157,7 @@ interface Post {
84
157
  content: string;
85
158
  }
86
159
 
160
+ // Basic query
87
161
  function PostList() {
88
162
  const { data: posts, isLoading, error, refetch } = useQuery<Post>('posts', {
89
163
  filters: [{ field: 'published', operator: 'eq', value: true }],
@@ -103,8 +177,67 @@ function PostList() {
103
177
  );
104
178
  }
105
179
 
180
+ // Query by ID
181
+ function PostDetail({ postId }: { postId: string }) {
182
+ const { data: post, isLoading } = useQueryById<Post>('posts', postId);
183
+
184
+ if (isLoading) return <div>Loading...</div>;
185
+ return <h1>{post?.title}</h1>;
186
+ }
187
+
188
+ // Paginated query
189
+ function PaginatedPosts() {
190
+ const {
191
+ data,
192
+ page,
193
+ totalPages,
194
+ nextPage,
195
+ prevPage,
196
+ isLoading
197
+ } = usePaginatedQuery<Post>('posts', {
198
+ pageSize: 20,
199
+ });
200
+
201
+ return (
202
+ <div>
203
+ {data?.map(post => <div key={post.id}>{post.title}</div>)}
204
+ <button onClick={prevPage} disabled={page === 1}>Prev</button>
205
+ <span>{page} / {totalPages}</span>
206
+ <button onClick={nextPage} disabled={page === totalPages}>Next</button>
207
+ </div>
208
+ );
209
+ }
210
+
211
+ // Infinite scroll
212
+ function InfinitePosts() {
213
+ const {
214
+ data,
215
+ fetchNextPage,
216
+ hasNextPage,
217
+ isFetchingNextPage
218
+ } = useInfiniteQuery<Post>('posts', { limit: 20 });
219
+
220
+ return (
221
+ <div>
222
+ {data?.map(post => <div key={post.id}>{post.title}</div>)}
223
+ {hasNextPage && (
224
+ <button onClick={fetchNextPage} disabled={isFetchingNextPage}>
225
+ {isFetchingNextPage ? 'Loading...' : 'Load More'}
226
+ </button>
227
+ )}
228
+ </div>
229
+ );
230
+ }
231
+ ```
232
+
233
+ ## Mutations
234
+
235
+ ```tsx
236
+ import { useCreate, useUpdate, useDelete, useMutation, useOptimisticMutation } from '@vaiftech/react';
237
+
238
+ // Create
106
239
  function CreatePost() {
107
- const { create, isCreating } = useMutation<Post>('posts');
240
+ const { create, isCreating, error } = useCreate<Post>('posts');
108
241
 
109
242
  const handleSubmit = async (data: Omit<Post, 'id'>) => {
110
243
  const newPost = await create(data);
@@ -117,47 +250,131 @@ function CreatePost() {
117
250
  </button>
118
251
  );
119
252
  }
253
+
254
+ // Update
255
+ function UpdatePost({ postId }: { postId: string }) {
256
+ const { update, isUpdating } = useUpdate<Post>('posts');
257
+
258
+ return (
259
+ <button
260
+ onClick={() => update(postId, { title: 'Updated Title' })}
261
+ disabled={isUpdating}
262
+ >
263
+ Update
264
+ </button>
265
+ );
266
+ }
267
+
268
+ // Delete
269
+ function DeletePost({ postId }: { postId: string }) {
270
+ const { remove, isDeleting } = useDelete('posts');
271
+
272
+ return (
273
+ <button onClick={() => remove(postId)} disabled={isDeleting}>
274
+ Delete
275
+ </button>
276
+ );
277
+ }
278
+
279
+ // Full mutation hook
280
+ function PostActions() {
281
+ const { create, update, remove, isLoading } = useMutation<Post>('posts');
282
+
283
+ // Use create, update, remove as needed
284
+ }
285
+
286
+ // Optimistic updates
287
+ function OptimisticLike({ postId }: { postId: string }) {
288
+ const { mutate } = useOptimisticMutation<Post>('posts', {
289
+ optimisticUpdate: (cache, postId) => {
290
+ return { ...cache[postId], likes: cache[postId].likes + 1 };
291
+ },
292
+ rollbackOnError: true,
293
+ });
294
+
295
+ return <button onClick={() => mutate(postId, { likes: '+1' })}>Like</button>;
296
+ }
120
297
  ```
121
298
 
122
- ### Realtime
299
+ ## Realtime
123
300
 
124
301
  ```tsx
125
- import { useRealtime, useRealtimeQuery } from '@vaiftech/react';
302
+ import { useSubscription, useChannel, usePresence, useBroadcast } from '@vaiftech/react';
126
303
 
127
- // Subscribe to changes
304
+ // Subscribe to table changes
128
305
  function MessageListener() {
129
- useRealtime<Message>('messages', {
306
+ useSubscription<Message>('messages', {
130
307
  event: 'INSERT',
131
- onInsert: (message) => {
132
- console.log('New message:', message);
133
- },
308
+ onInsert: (message) => console.log('New message:', message),
309
+ onUpdate: (message) => console.log('Updated:', message),
310
+ onDelete: (message) => console.log('Deleted:', message.id),
134
311
  });
135
312
 
136
313
  return null;
137
314
  }
138
315
 
139
- // Query with automatic realtime updates
140
- function LiveMessages() {
141
- const { data: messages } = useRealtimeQuery<Message>('messages', {
142
- orderBy: [{ field: 'createdAt', direction: 'desc' }],
143
- limit: 50,
144
- });
316
+ // Channel communication
317
+ function ChatRoom({ roomId }: { roomId: string }) {
318
+ const channel = useChannel(`room-${roomId}`);
319
+ const [messages, setMessages] = useState<Message[]>([]);
320
+
321
+ useEffect(() => {
322
+ channel.on('message', (msg) => {
323
+ setMessages(prev => [...prev, msg]);
324
+ });
325
+ return () => channel.off('message');
326
+ }, [channel]);
327
+
328
+ const sendMessage = (text: string) => {
329
+ channel.send('message', { text, timestamp: Date.now() });
330
+ };
331
+
332
+ return <div>{/* Chat UI */}</div>;
333
+ }
334
+
335
+ // Presence tracking
336
+ function OnlineUsers({ roomId }: { roomId: string }) {
337
+ const { users, track, leave } = usePresence(`room-${roomId}`);
338
+
339
+ useEffect(() => {
340
+ track({ status: 'online', name: currentUser.name });
341
+ return () => leave();
342
+ }, []);
145
343
 
146
344
  return (
147
- <ul>
148
- {messages?.map(msg => (
149
- <li key={msg.id}>{msg.content}</li>
150
- ))}
151
- </ul>
345
+ <div>
346
+ <h4>Online ({users.length})</h4>
347
+ {users.map(u => <span key={u.id}>{u.name}</span>)}
348
+ </div>
152
349
  );
153
350
  }
351
+
352
+ // Broadcast events
353
+ function TypingIndicator({ roomId }: { roomId: string }) {
354
+ const { send, subscribe } = useBroadcast(`room-${roomId}`);
355
+ const [typing, setTyping] = useState<string[]>([]);
356
+
357
+ useEffect(() => {
358
+ subscribe('typing', (event) => {
359
+ setTyping(prev => [...prev, event.userId]);
360
+ setTimeout(() => {
361
+ setTyping(prev => prev.filter(id => id !== event.userId));
362
+ }, 3000);
363
+ });
364
+ }, []);
365
+
366
+ const onType = () => send('typing', { userId: currentUser.id });
367
+
368
+ return <div>{typing.length > 0 && `${typing.join(', ')} typing...`}</div>;
369
+ }
154
370
  ```
155
371
 
156
- ### Storage
372
+ ## Storage
157
373
 
158
374
  ```tsx
159
- import { useUpload, useDownload, useFile, usePublicUrl } from '@vaiftech/react';
375
+ import { useUpload, useDownload, useFile, useFiles, useDropzone, usePublicUrl } from '@vaiftech/react';
160
376
 
377
+ // File upload with progress
161
378
  function FileUpload() {
162
379
  const { upload, progress, isUploading, error } = useUpload();
163
380
 
@@ -175,17 +392,50 @@ function FileUpload() {
175
392
  );
176
393
  }
177
394
 
395
+ // Drag and drop zone
396
+ function DropzoneUpload() {
397
+ const { getRootProps, getInputProps, isDragActive, files } = useDropzone({
398
+ accept: { 'image/*': ['.png', '.jpg', '.gif'] },
399
+ maxFiles: 5,
400
+ onDrop: async (files) => {
401
+ // Handle uploaded files
402
+ },
403
+ });
404
+
405
+ return (
406
+ <div {...getRootProps()}>
407
+ <input {...getInputProps()} />
408
+ {isDragActive ? 'Drop files here' : 'Drag files or click to upload'}
409
+ </div>
410
+ );
411
+ }
412
+
413
+ // Display files
178
414
  function FileDisplay({ path }: { path: string }) {
179
415
  const url = usePublicUrl(path);
180
416
  return url ? <img src={url} alt="" /> : null;
181
417
  }
418
+
419
+ // List files
420
+ function FileList({ prefix }: { prefix: string }) {
421
+ const { files, isLoading, refetch } = useFiles({ prefix });
422
+
423
+ return (
424
+ <ul>
425
+ {files?.map(file => (
426
+ <li key={file.path}>{file.name} ({file.size} bytes)</li>
427
+ ))}
428
+ </ul>
429
+ );
430
+ }
182
431
  ```
183
432
 
184
- ### Edge Functions
433
+ ## Edge Functions
185
434
 
186
435
  ```tsx
187
- import { useFunction } from '@vaiftech/react';
436
+ import { useFunction, useRpc, useBatchInvoke } from '@vaiftech/react';
188
437
 
438
+ // Invoke function
189
439
  function SendEmail() {
190
440
  const { invoke, isInvoking, error, result } = useFunction('send-email');
191
441
 
@@ -202,6 +452,283 @@ function SendEmail() {
202
452
  </button>
203
453
  );
204
454
  }
455
+
456
+ // RPC-style calls
457
+ function DataProcessor() {
458
+ const { call, isLoading, data } = useRpc<ProcessResult>('process-data');
459
+
460
+ return (
461
+ <button onClick={() => call({ input: rawData })}>
462
+ Process
463
+ </button>
464
+ );
465
+ }
466
+
467
+ // Batch invocations
468
+ function BatchProcessor() {
469
+ const { invoke, isInvoking, results } = useBatchInvoke();
470
+
471
+ const processAll = async () => {
472
+ await invoke([
473
+ { name: 'resize-image', payload: { url: 'img1.jpg' } },
474
+ { name: 'resize-image', payload: { url: 'img2.jpg' } },
475
+ { name: 'resize-image', payload: { url: 'img3.jpg' } },
476
+ ]);
477
+ };
478
+
479
+ return <button onClick={processAll}>Process All</button>;
480
+ }
481
+ ```
482
+
483
+ ## MongoDB Hooks
484
+
485
+ ```tsx
486
+ import {
487
+ useMongoFind,
488
+ useMongoFindOne,
489
+ useMongoInsertOne,
490
+ useMongoUpdateOne,
491
+ useMongoDeleteOne,
492
+ useMongoAggregate,
493
+ useMongoCollection,
494
+ useMongoInfiniteFind,
495
+ } from '@vaiftech/react';
496
+
497
+ // Find documents
498
+ function UserList() {
499
+ const { data: users, isLoading, error, refetch } = useMongoFind<User>('users', {
500
+ filter: { status: 'active' },
501
+ sort: { createdAt: -1 },
502
+ limit: 20,
503
+ });
504
+
505
+ if (isLoading) return <div>Loading...</div>;
506
+ if (error) return <div>Error: {error.message}</div>;
507
+
508
+ return (
509
+ <ul>
510
+ {users?.map(user => <li key={user._id}>{user.name}</li>)}
511
+ </ul>
512
+ );
513
+ }
514
+
515
+ // Find single document
516
+ function UserProfile({ id }: { id: string }) {
517
+ const { data: user, isLoading } = useMongoFindOne<User>('users', { _id: id });
518
+ return user ? <div>{user.name}</div> : null;
519
+ }
520
+
521
+ // Insert document
522
+ function CreateUser() {
523
+ const { insertOne, isInserting } = useMongoInsertOne<User>('users');
524
+
525
+ const handleCreate = async () => {
526
+ const result = await insertOne({
527
+ name: 'New User',
528
+ email: 'new@example.com',
529
+ createdAt: new Date(),
530
+ });
531
+ console.log('Created:', result.insertedId);
532
+ };
533
+
534
+ return (
535
+ <button onClick={handleCreate} disabled={isInserting}>
536
+ Create User
537
+ </button>
538
+ );
539
+ }
540
+
541
+ // Update document
542
+ function UpdateUser({ id }: { id: string }) {
543
+ const { updateOne, isUpdating } = useMongoUpdateOne('users');
544
+
545
+ return (
546
+ <button
547
+ onClick={() => updateOne({ _id: id }, { $set: { updatedAt: new Date() } })}
548
+ disabled={isUpdating}
549
+ >
550
+ Update
551
+ </button>
552
+ );
553
+ }
554
+
555
+ // Delete document
556
+ function DeleteUser({ id }: { id: string }) {
557
+ const { deleteOne, isDeleting } = useMongoDeleteOne('users');
558
+ return (
559
+ <button onClick={() => deleteOne({ _id: id })} disabled={isDeleting}>
560
+ Delete
561
+ </button>
562
+ );
563
+ }
564
+
565
+ // Aggregation pipeline
566
+ function UserStats() {
567
+ const { data: stats } = useMongoAggregate<{ _id: string; count: number }>('users', [
568
+ { $match: { status: 'active' } },
569
+ { $group: { _id: '$country', count: { $sum: 1 } } },
570
+ { $sort: { count: -1 } },
571
+ ]);
572
+
573
+ return (
574
+ <ul>
575
+ {stats?.map(stat => (
576
+ <li key={stat._id}>{stat._id}: {stat.count}</li>
577
+ ))}
578
+ </ul>
579
+ );
580
+ }
581
+
582
+ // Infinite scroll with MongoDB
583
+ function InfiniteUserList() {
584
+ const { data, fetchNextPage, hasNextPage } = useMongoInfiniteFind<User>('users', {
585
+ filter: { status: 'active' },
586
+ sort: { createdAt: -1 },
587
+ limit: 20,
588
+ });
589
+
590
+ return (
591
+ <div>
592
+ {data?.map(user => <div key={user._id}>{user.name}</div>)}
593
+ {hasNextPage && <button onClick={fetchNextPage}>Load More</button>}
594
+ </div>
595
+ );
596
+ }
597
+
598
+ // Full collection access
599
+ function AdvancedOps() {
600
+ const collection = useMongoCollection<User>('users');
601
+
602
+ const handleBulk = async () => {
603
+ const count = await collection.count({ status: 'active' });
604
+ const countries = await collection.distinct('country');
605
+ await collection.bulkWrite([
606
+ { insertOne: { document: { name: 'Bulk User' } } },
607
+ ]);
608
+ };
609
+
610
+ return <button onClick={handleBulk}>Run Operations</button>;
611
+ }
612
+ ```
613
+
614
+ ## Observability Hooks
615
+
616
+ ```tsx
617
+ import {
618
+ useMetrics,
619
+ useAuditLogs,
620
+ useIncidents,
621
+ useSystemHealth,
622
+ useRealtimeStats,
623
+ useErrorTracking,
624
+ } from '@vaiftech/react';
625
+
626
+ // Metrics dashboard
627
+ function MetricsDashboard() {
628
+ const { metrics, isLoading, timeRange, setTimeRange, refresh } = useMetrics({
629
+ sources: ['api', 'database', 'storage'],
630
+ interval: '1h',
631
+ });
632
+
633
+ return (
634
+ <div>
635
+ <select onChange={(e) => setTimeRange(e.target.value)} value={timeRange}>
636
+ <option value="1h">Last Hour</option>
637
+ <option value="24h">Last 24 Hours</option>
638
+ <option value="7d">Last 7 Days</option>
639
+ </select>
640
+ {metrics?.map(m => (
641
+ <div key={m.name}>{m.name}: {m.value}</div>
642
+ ))}
643
+ </div>
644
+ );
645
+ }
646
+
647
+ // Audit logs
648
+ function AuditLogViewer() {
649
+ const { logs, hasMore, loadMore, setFilters } = useAuditLogs({ limit: 50 });
650
+
651
+ return (
652
+ <div>
653
+ <select onChange={(e) => setFilters({ action: e.target.value })}>
654
+ <option value="">All Actions</option>
655
+ <option value="create">Create</option>
656
+ <option value="update">Update</option>
657
+ <option value="delete">Delete</option>
658
+ </select>
659
+ <ul>
660
+ {logs?.map(log => (
661
+ <li key={log.id}>{log.timestamp}: {log.action} by {log.userId}</li>
662
+ ))}
663
+ </ul>
664
+ {hasMore && <button onClick={loadMore}>Load More</button>}
665
+ </div>
666
+ );
667
+ }
668
+
669
+ // Incident management
670
+ function IncidentList() {
671
+ const { incidents, activeCount, acknowledge, resolve } = useIncidents();
672
+
673
+ return (
674
+ <div>
675
+ <h3>Active Incidents: {activeCount}</h3>
676
+ {incidents?.map(incident => (
677
+ <div key={incident.id}>
678
+ <span>{incident.title} - {incident.severity}</span>
679
+ {incident.status === 'open' && (
680
+ <button onClick={() => acknowledge(incident.id)}>Ack</button>
681
+ )}
682
+ {incident.status === 'acknowledged' && (
683
+ <button onClick={() => resolve(incident.id)}>Resolve</button>
684
+ )}
685
+ </div>
686
+ ))}
687
+ </div>
688
+ );
689
+ }
690
+
691
+ // System health
692
+ function SystemHealth() {
693
+ const { isHealthy, services, lastCheck } = useSystemHealth({ pollInterval: 30000 });
694
+
695
+ return (
696
+ <div>
697
+ <div>Status: {isHealthy ? 'Healthy' : 'Degraded'}</div>
698
+ <div>Last Check: {lastCheck?.toLocaleString()}</div>
699
+ {services?.map(svc => (
700
+ <div key={svc.name}>{svc.name}: {svc.status} ({svc.latency}ms)</div>
701
+ ))}
702
+ </div>
703
+ );
704
+ }
705
+
706
+ // Realtime stats
707
+ function RealtimeStats() {
708
+ const { connections, requestsPerSecond, activeUsers } = useRealtimeStats();
709
+
710
+ return (
711
+ <div>
712
+ <div>Connections: {connections}</div>
713
+ <div>Requests/sec: {requestsPerSecond}</div>
714
+ <div>Active Users: {activeUsers}</div>
715
+ </div>
716
+ );
717
+ }
718
+
719
+ // Error tracking
720
+ function ErrorTracker() {
721
+ const { errorRate, topErrors, trackError } = useErrorTracking({ timeRange: '24h' });
722
+
723
+ return (
724
+ <div>
725
+ <div>Error Rate: {errorRate}%</div>
726
+ {topErrors?.map(err => (
727
+ <div key={err.message}>{err.message} ({err.count})</div>
728
+ ))}
729
+ </div>
730
+ );
731
+ }
205
732
  ```
206
733
 
207
734
  ## TypeScript Support
@@ -217,11 +744,15 @@ interface User {
217
744
 
218
745
  const { data } = useQuery<User>('users');
219
746
  // data is User[] | undefined
747
+
748
+ const { data: user } = useMongoFindOne<User>('users', { email: 'test@example.com' });
749
+ // user is User | null | undefined
220
750
  ```
221
751
 
222
752
  ## Related Packages
223
753
 
224
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
225
756
  - [@vaiftech/sdk-expo](https://www.npmjs.com/package/@vaiftech/sdk-expo) - React Native/Expo SDK
226
757
  - [@vaiftech/cli](https://www.npmjs.com/package/@vaiftech/cli) - CLI tools
227
758
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaiftech/react",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "React hooks for VAIF - Authentication, Data, Realtime, Storage, and more",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",