@vaiftech/react 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +230 -0
- package/dist/index.d.mts +1609 -0
- package/dist/index.d.ts +1609 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +65 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,1609 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { VaifClientConfig, User, VaifClient, AuthResponse, OAuthProviderType, MFAMethod, MFASetupResponse, QueryOptions, PaginatedResult, WhereFilter, DbOperation, DbChangeEvent, PresenceState, PresenceEntry, ConnectionState, UploadOptions, UploadResult, FileMetadata, InvokeResult, VaifFunction } from '@vaif/client';
|
|
4
|
+
export { AuthResponse, ConnectionState, DbChangeEvent, DbOperation, FileMetadata, InvokeResult, OrderByClause, PresenceState, QueryOptions, UploadResult, User, VaifClient, VaifClientConfig, VaifFunction, WhereFilter } from '@vaif/client';
|
|
5
|
+
|
|
6
|
+
interface VaifContextValue {
|
|
7
|
+
/** The VAIF client instance */
|
|
8
|
+
client: VaifClient;
|
|
9
|
+
/** Current authenticated user (null if not authenticated) */
|
|
10
|
+
user: User | null;
|
|
11
|
+
/** Current access token */
|
|
12
|
+
token: string | null;
|
|
13
|
+
/** Whether authentication is being checked */
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
/** Whether user is authenticated */
|
|
16
|
+
isAuthenticated: boolean;
|
|
17
|
+
/** Sign in with email/password */
|
|
18
|
+
signIn: (email: string, password: string) => Promise<AuthResponse>;
|
|
19
|
+
/** Sign up with email/password */
|
|
20
|
+
signUp: (email: string, password: string, metadata?: Record<string, unknown>) => Promise<AuthResponse>;
|
|
21
|
+
/** Sign out the current user */
|
|
22
|
+
signOut: () => Promise<void>;
|
|
23
|
+
/** Refresh the session */
|
|
24
|
+
refreshSession: () => Promise<void>;
|
|
25
|
+
/** Update the current user in state */
|
|
26
|
+
updateUser: (updates: Partial<User>) => void;
|
|
27
|
+
}
|
|
28
|
+
interface VaifProviderProps {
|
|
29
|
+
/** VAIF client configuration */
|
|
30
|
+
config: VaifClientConfig;
|
|
31
|
+
/** Child components */
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
/** Callback when auth state changes */
|
|
34
|
+
onAuthStateChange?: (user: User | null) => void;
|
|
35
|
+
/** Auto refresh session before expiry (default: true) */
|
|
36
|
+
autoRefreshSession?: boolean;
|
|
37
|
+
/** Storage key for session persistence (default: 'vaif-auth') */
|
|
38
|
+
storageKey?: string;
|
|
39
|
+
}
|
|
40
|
+
declare function VaifProvider({ config, children, onAuthStateChange, autoRefreshSession, storageKey, }: VaifProviderProps): react_jsx_runtime.JSX.Element;
|
|
41
|
+
/**
|
|
42
|
+
* Access the VAIF context
|
|
43
|
+
*
|
|
44
|
+
* @throws Error if used outside VaifProvider
|
|
45
|
+
*/
|
|
46
|
+
declare function useVaif(): VaifContextValue;
|
|
47
|
+
/**
|
|
48
|
+
* Access only the VAIF client (lightweight alternative to useVaif)
|
|
49
|
+
*/
|
|
50
|
+
declare function useVaifClient(): VaifClient;
|
|
51
|
+
|
|
52
|
+
interface UseAuthReturn {
|
|
53
|
+
/** Current user */
|
|
54
|
+
user: User | null;
|
|
55
|
+
/** Current access token */
|
|
56
|
+
token: string | null;
|
|
57
|
+
/** Whether auth is loading */
|
|
58
|
+
isLoading: boolean;
|
|
59
|
+
/** Whether user is authenticated */
|
|
60
|
+
isAuthenticated: boolean;
|
|
61
|
+
/** Sign in with email/password */
|
|
62
|
+
signIn: (email: string, password: string) => Promise<AuthResponse>;
|
|
63
|
+
/** Sign up with email/password */
|
|
64
|
+
signUp: (email: string, password: string, metadata?: Record<string, unknown>) => Promise<AuthResponse>;
|
|
65
|
+
/** Sign out */
|
|
66
|
+
signOut: () => Promise<void>;
|
|
67
|
+
/** Refresh session */
|
|
68
|
+
refreshSession: () => Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
interface UsePasswordResetReturn {
|
|
71
|
+
/** Request password reset email */
|
|
72
|
+
requestReset: (email: string) => Promise<void>;
|
|
73
|
+
/** Confirm password reset with token */
|
|
74
|
+
confirmReset: (token: string, newPassword: string) => Promise<void>;
|
|
75
|
+
/** Loading state */
|
|
76
|
+
isLoading: boolean;
|
|
77
|
+
/** Error state */
|
|
78
|
+
error: Error | null;
|
|
79
|
+
/** Whether reset was requested */
|
|
80
|
+
isRequested: boolean;
|
|
81
|
+
/** Whether reset was confirmed */
|
|
82
|
+
isConfirmed: boolean;
|
|
83
|
+
}
|
|
84
|
+
interface UseEmailVerificationReturn {
|
|
85
|
+
/** Request verification email */
|
|
86
|
+
requestVerification: (email: string) => Promise<void>;
|
|
87
|
+
/** Confirm email with token */
|
|
88
|
+
confirmVerification: (token: string) => Promise<void>;
|
|
89
|
+
/** Loading state */
|
|
90
|
+
isLoading: boolean;
|
|
91
|
+
/** Error state */
|
|
92
|
+
error: Error | null;
|
|
93
|
+
/** Whether verification was sent */
|
|
94
|
+
isSent: boolean;
|
|
95
|
+
/** Whether email was verified */
|
|
96
|
+
isVerified: boolean;
|
|
97
|
+
}
|
|
98
|
+
interface UseMagicLinkReturn {
|
|
99
|
+
/** Send magic link */
|
|
100
|
+
sendMagicLink: (email: string) => Promise<void>;
|
|
101
|
+
/** Verify magic link token */
|
|
102
|
+
verifyMagicLink: (token: string) => Promise<AuthResponse>;
|
|
103
|
+
/** Loading state */
|
|
104
|
+
isLoading: boolean;
|
|
105
|
+
/** Error state */
|
|
106
|
+
error: Error | null;
|
|
107
|
+
/** Whether link was sent */
|
|
108
|
+
isSent: boolean;
|
|
109
|
+
}
|
|
110
|
+
interface UseOAuthReturn {
|
|
111
|
+
/** Start OAuth flow */
|
|
112
|
+
signInWithOAuth: (provider: OAuthProviderType, redirectTo?: string) => Promise<void>;
|
|
113
|
+
/** Loading state */
|
|
114
|
+
isLoading: boolean;
|
|
115
|
+
/** Error state */
|
|
116
|
+
error: Error | null;
|
|
117
|
+
}
|
|
118
|
+
interface UseMFAReturn {
|
|
119
|
+
/** Set up MFA */
|
|
120
|
+
setup: (method: MFAMethod) => Promise<MFASetupResponse>;
|
|
121
|
+
/** Verify MFA with token and code */
|
|
122
|
+
verify: (mfaToken: string, code: string) => Promise<AuthResponse>;
|
|
123
|
+
/** Disable MFA */
|
|
124
|
+
disable: (code: string) => Promise<void>;
|
|
125
|
+
/** Loading state */
|
|
126
|
+
isLoading: boolean;
|
|
127
|
+
/** Error state */
|
|
128
|
+
error: Error | null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Main authentication hook
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```tsx
|
|
135
|
+
* function LoginForm() {
|
|
136
|
+
* const { signIn, isLoading, user } = useAuth();
|
|
137
|
+
*
|
|
138
|
+
* const handleSubmit = async (e: FormEvent) => {
|
|
139
|
+
* e.preventDefault();
|
|
140
|
+
* await signIn(email, password);
|
|
141
|
+
* };
|
|
142
|
+
*
|
|
143
|
+
* if (user) {
|
|
144
|
+
* return <div>Welcome, {user.email}!</div>;
|
|
145
|
+
* }
|
|
146
|
+
*
|
|
147
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function useAuth(): UseAuthReturn;
|
|
152
|
+
/**
|
|
153
|
+
* Hook for getting the current user only
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```tsx
|
|
157
|
+
* function UserAvatar() {
|
|
158
|
+
* const user = useUser();
|
|
159
|
+
* if (!user) return null;
|
|
160
|
+
* return <img src={user.avatarUrl} alt={user.name} />;
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
declare function useUser(): User | null;
|
|
165
|
+
/**
|
|
166
|
+
* Hook for getting the current access token
|
|
167
|
+
*/
|
|
168
|
+
declare function useToken(): string | null;
|
|
169
|
+
/**
|
|
170
|
+
* Password reset hook
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```tsx
|
|
174
|
+
* function ForgotPassword() {
|
|
175
|
+
* const { requestReset, confirmReset, isLoading, isRequested } = usePasswordReset();
|
|
176
|
+
*
|
|
177
|
+
* if (!isRequested) {
|
|
178
|
+
* return <button onClick={() => requestReset(email)}>Send Reset Link</button>;
|
|
179
|
+
* }
|
|
180
|
+
*
|
|
181
|
+
* return (
|
|
182
|
+
* <form onSubmit={() => confirmReset(email, code, newPassword)}>
|
|
183
|
+
* <input placeholder="Enter code" />
|
|
184
|
+
* <input type="password" placeholder="New password" />
|
|
185
|
+
* <button type="submit">Reset Password</button>
|
|
186
|
+
* </form>
|
|
187
|
+
* );
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
declare function usePasswordReset(): UsePasswordResetReturn;
|
|
192
|
+
/**
|
|
193
|
+
* Email verification hook
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```tsx
|
|
197
|
+
* function VerifyEmail() {
|
|
198
|
+
* const { requestVerification, confirmVerification, isSent, isVerified } = useEmailVerification();
|
|
199
|
+
*
|
|
200
|
+
* if (isVerified) {
|
|
201
|
+
* return <div>Email verified successfully!</div>;
|
|
202
|
+
* }
|
|
203
|
+
*
|
|
204
|
+
* if (isSent) {
|
|
205
|
+
* return (
|
|
206
|
+
* <form onSubmit={() => confirmVerification(email, code)}>
|
|
207
|
+
* <input placeholder="Enter verification code" />
|
|
208
|
+
* <button type="submit">Verify</button>
|
|
209
|
+
* </form>
|
|
210
|
+
* );
|
|
211
|
+
* }
|
|
212
|
+
*
|
|
213
|
+
* return <button onClick={() => requestVerification(email)}>Send Verification</button>;
|
|
214
|
+
* }
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
declare function useEmailVerification(): UseEmailVerificationReturn;
|
|
218
|
+
/**
|
|
219
|
+
* Magic link authentication hook
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```tsx
|
|
223
|
+
* function MagicLinkLogin() {
|
|
224
|
+
* const { sendMagicLink, isSent, isLoading } = useMagicLink();
|
|
225
|
+
*
|
|
226
|
+
* if (isSent) {
|
|
227
|
+
* return <div>Check your email for the login link!</div>;
|
|
228
|
+
* }
|
|
229
|
+
*
|
|
230
|
+
* return (
|
|
231
|
+
* <button onClick={() => sendMagicLink(email)} disabled={isLoading}>
|
|
232
|
+
* {isLoading ? 'Sending...' : 'Send Magic Link'}
|
|
233
|
+
* </button>
|
|
234
|
+
* );
|
|
235
|
+
* }
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
declare function useMagicLink(): UseMagicLinkReturn;
|
|
239
|
+
/**
|
|
240
|
+
* OAuth authentication hook
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```tsx
|
|
244
|
+
* function OAuthButtons() {
|
|
245
|
+
* const { signInWithOAuth, isLoading } = useOAuth();
|
|
246
|
+
*
|
|
247
|
+
* return (
|
|
248
|
+
* <div>
|
|
249
|
+
* <button onClick={() => signInWithOAuth('google')} disabled={isLoading}>
|
|
250
|
+
* Sign in with Google
|
|
251
|
+
* </button>
|
|
252
|
+
* <button onClick={() => signInWithOAuth('github')} disabled={isLoading}>
|
|
253
|
+
* Sign in with GitHub
|
|
254
|
+
* </button>
|
|
255
|
+
* </div>
|
|
256
|
+
* );
|
|
257
|
+
* }
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
declare function useOAuth(): UseOAuthReturn;
|
|
261
|
+
/**
|
|
262
|
+
* Multi-factor authentication hook
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```tsx
|
|
266
|
+
* function MFASetup() {
|
|
267
|
+
* const { setup, verify, isLoading } = useMFA();
|
|
268
|
+
* const [qrCode, setQrCode] = useState<string | null>(null);
|
|
269
|
+
* const [mfaToken, setMfaToken] = useState<string | null>(null);
|
|
270
|
+
*
|
|
271
|
+
* const handleSetup = async () => {
|
|
272
|
+
* const result = await setup('totp');
|
|
273
|
+
* setQrCode(result.qrCode);
|
|
274
|
+
* };
|
|
275
|
+
*
|
|
276
|
+
* const handleVerify = async (code: string) => {
|
|
277
|
+
* if (mfaToken) {
|
|
278
|
+
* await verify(mfaToken, code);
|
|
279
|
+
* alert('MFA verified!');
|
|
280
|
+
* }
|
|
281
|
+
* };
|
|
282
|
+
*
|
|
283
|
+
* return (
|
|
284
|
+
* <div>
|
|
285
|
+
* {qrCode ? (
|
|
286
|
+
* <>
|
|
287
|
+
* <img src={qrCode} alt="Scan with authenticator app" />
|
|
288
|
+
* <input placeholder="Enter code" />
|
|
289
|
+
* <button onClick={() => handleVerify(code)}>Verify</button>
|
|
290
|
+
* </>
|
|
291
|
+
* ) : (
|
|
292
|
+
* <button onClick={handleSetup}>Enable 2FA</button>
|
|
293
|
+
* )}
|
|
294
|
+
* </div>
|
|
295
|
+
* );
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
declare function useMFA(): UseMFAReturn;
|
|
300
|
+
|
|
301
|
+
type QueryStatus = "idle" | "loading" | "success" | "error";
|
|
302
|
+
interface UseQueryOptions<T extends Record<string, unknown>> extends Partial<QueryOptions<T>> {
|
|
303
|
+
/** Enable/disable the query */
|
|
304
|
+
enabled?: boolean;
|
|
305
|
+
/** Refetch on window focus */
|
|
306
|
+
refetchOnWindowFocus?: boolean;
|
|
307
|
+
/** Refetch interval in ms (0 = disabled) */
|
|
308
|
+
refetchInterval?: number;
|
|
309
|
+
/** Keep previous data while fetching new */
|
|
310
|
+
keepPreviousData?: boolean;
|
|
311
|
+
/** Callback on success */
|
|
312
|
+
onSuccess?: (data: T[]) => void;
|
|
313
|
+
/** Callback on error */
|
|
314
|
+
onError?: (error: Error) => void;
|
|
315
|
+
/** Initial data */
|
|
316
|
+
initialData?: T[];
|
|
317
|
+
/** Stale time in ms (default: 0) */
|
|
318
|
+
staleTime?: number;
|
|
319
|
+
}
|
|
320
|
+
interface UseQueryReturn<T> {
|
|
321
|
+
/** Query result data */
|
|
322
|
+
data: T[];
|
|
323
|
+
/** Loading state */
|
|
324
|
+
isLoading: boolean;
|
|
325
|
+
/** Fetching state (loading or refetching) */
|
|
326
|
+
isFetching: boolean;
|
|
327
|
+
/** Error state */
|
|
328
|
+
error: Error | null;
|
|
329
|
+
/** Query status */
|
|
330
|
+
status: QueryStatus;
|
|
331
|
+
/** Refetch function */
|
|
332
|
+
refetch: () => Promise<void>;
|
|
333
|
+
/** Whether data is stale */
|
|
334
|
+
isStale: boolean;
|
|
335
|
+
/** Whether query is enabled */
|
|
336
|
+
isEnabled: boolean;
|
|
337
|
+
}
|
|
338
|
+
interface UseQueryFirstReturn<T> {
|
|
339
|
+
/** Query result (single item) */
|
|
340
|
+
data: T | null;
|
|
341
|
+
/** Loading state */
|
|
342
|
+
isLoading: boolean;
|
|
343
|
+
/** Error state */
|
|
344
|
+
error: Error | null;
|
|
345
|
+
/** Not found state */
|
|
346
|
+
isNotFound: boolean;
|
|
347
|
+
/** Refetch function */
|
|
348
|
+
refetch: () => Promise<void>;
|
|
349
|
+
}
|
|
350
|
+
interface UsePaginatedQueryOptions<T extends Record<string, unknown>> extends Partial<QueryOptions<T>> {
|
|
351
|
+
/** Page size */
|
|
352
|
+
pageSize?: number;
|
|
353
|
+
/** Initial page */
|
|
354
|
+
initialPage?: number;
|
|
355
|
+
/** Callback on success */
|
|
356
|
+
onSuccess?: (result: PaginatedResult<T>) => void;
|
|
357
|
+
/** Callback on error */
|
|
358
|
+
onError?: (error: Error) => void;
|
|
359
|
+
}
|
|
360
|
+
interface UsePaginatedQueryReturn<T> {
|
|
361
|
+
/** Current page data */
|
|
362
|
+
data: T[];
|
|
363
|
+
/** Current page number (1-indexed) */
|
|
364
|
+
page: number;
|
|
365
|
+
/** Page size */
|
|
366
|
+
pageSize: number;
|
|
367
|
+
/** Total items count */
|
|
368
|
+
totalCount: number;
|
|
369
|
+
/** Total pages count */
|
|
370
|
+
totalPages: number;
|
|
371
|
+
/** Whether there's a next page */
|
|
372
|
+
hasNextPage: boolean;
|
|
373
|
+
/** Whether there's a previous page */
|
|
374
|
+
hasPrevPage: boolean;
|
|
375
|
+
/** Loading state */
|
|
376
|
+
isLoading: boolean;
|
|
377
|
+
/** Error state */
|
|
378
|
+
error: Error | null;
|
|
379
|
+
/** Go to next page */
|
|
380
|
+
nextPage: () => void;
|
|
381
|
+
/** Go to previous page */
|
|
382
|
+
prevPage: () => void;
|
|
383
|
+
/** Go to specific page */
|
|
384
|
+
goToPage: (page: number) => void;
|
|
385
|
+
/** Refetch current page */
|
|
386
|
+
refetch: () => Promise<void>;
|
|
387
|
+
}
|
|
388
|
+
interface UseInfiniteQueryOptions<T extends Record<string, unknown>> extends Partial<QueryOptions<T>> {
|
|
389
|
+
/** Page size */
|
|
390
|
+
pageSize?: number;
|
|
391
|
+
/** Callback on success */
|
|
392
|
+
onSuccess?: (data: T[], hasMore: boolean) => void;
|
|
393
|
+
/** Callback on error */
|
|
394
|
+
onError?: (error: Error) => void;
|
|
395
|
+
}
|
|
396
|
+
interface UseInfiniteQueryReturn<T> {
|
|
397
|
+
/** All loaded data (flattened) */
|
|
398
|
+
data: T[];
|
|
399
|
+
/** Pages loaded */
|
|
400
|
+
pages: T[][];
|
|
401
|
+
/** Whether there's a next page */
|
|
402
|
+
hasNextPage: boolean;
|
|
403
|
+
/** Loading state */
|
|
404
|
+
isLoading: boolean;
|
|
405
|
+
/** Fetching next page state */
|
|
406
|
+
isFetchingNextPage: boolean;
|
|
407
|
+
/** Error state */
|
|
408
|
+
error: Error | null;
|
|
409
|
+
/** Fetch next page */
|
|
410
|
+
fetchNextPage: () => Promise<void>;
|
|
411
|
+
/** Refetch all pages */
|
|
412
|
+
refetch: () => Promise<void>;
|
|
413
|
+
/** Reset to first page */
|
|
414
|
+
reset: () => void;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Query hook for fetching multiple records
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```tsx
|
|
421
|
+
* function UserList() {
|
|
422
|
+
* const { data, isLoading, error, refetch } = useQuery<User>('users', {
|
|
423
|
+
* where: { status: 'active' },
|
|
424
|
+
* orderBy: { field: 'createdAt', direction: 'desc' },
|
|
425
|
+
* limit: 20,
|
|
426
|
+
* onSuccess: (users) => console.log('Loaded', users.length, 'users'),
|
|
427
|
+
* });
|
|
428
|
+
*
|
|
429
|
+
* if (isLoading) return <Spinner />;
|
|
430
|
+
* if (error) return <Error message={error.message} />;
|
|
431
|
+
*
|
|
432
|
+
* return (
|
|
433
|
+
* <ul>
|
|
434
|
+
* {data.map(user => (
|
|
435
|
+
* <li key={user.id}>{user.name}</li>
|
|
436
|
+
* ))}
|
|
437
|
+
* </ul>
|
|
438
|
+
* );
|
|
439
|
+
* }
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
declare function useQuery<T extends Record<string, unknown>>(table: string, options?: UseQueryOptions<T>): UseQueryReturn<T>;
|
|
443
|
+
/**
|
|
444
|
+
* Query hook for fetching a single record by ID
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```tsx
|
|
448
|
+
* function UserProfile({ userId }: { userId: string }) {
|
|
449
|
+
* const { data: user, isLoading, error } = useQueryById<User>('users', userId);
|
|
450
|
+
*
|
|
451
|
+
* if (isLoading) return <Spinner />;
|
|
452
|
+
* if (error) return <Error message={error.message} />;
|
|
453
|
+
* if (!user) return <NotFound />;
|
|
454
|
+
*
|
|
455
|
+
* return <div>{user.name}</div>;
|
|
456
|
+
* }
|
|
457
|
+
* ```
|
|
458
|
+
*/
|
|
459
|
+
declare function useQueryById<T extends Record<string, unknown>>(table: string, id: string | null, options?: {
|
|
460
|
+
enabled?: boolean;
|
|
461
|
+
}): UseQueryFirstReturn<T>;
|
|
462
|
+
/**
|
|
463
|
+
* Query hook for fetching the first matching record
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* ```tsx
|
|
467
|
+
* function LatestPost() {
|
|
468
|
+
* const { data: post, isLoading } = useQueryFirst<Post>('posts', {
|
|
469
|
+
* where: { published: true },
|
|
470
|
+
* orderBy: { field: 'createdAt', direction: 'desc' },
|
|
471
|
+
* });
|
|
472
|
+
*
|
|
473
|
+
* if (isLoading) return <Spinner />;
|
|
474
|
+
* if (!post) return <Empty message="No posts yet" />;
|
|
475
|
+
*
|
|
476
|
+
* return <PostCard post={post} />;
|
|
477
|
+
* }
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
declare function useQueryFirst<T extends Record<string, unknown>>(table: string, options?: UseQueryOptions<T>): UseQueryFirstReturn<T>;
|
|
481
|
+
/**
|
|
482
|
+
* Paginated query hook with page navigation
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```tsx
|
|
486
|
+
* function UserTable() {
|
|
487
|
+
* const {
|
|
488
|
+
* data,
|
|
489
|
+
* page,
|
|
490
|
+
* totalPages,
|
|
491
|
+
* hasNextPage,
|
|
492
|
+
* hasPrevPage,
|
|
493
|
+
* nextPage,
|
|
494
|
+
* prevPage,
|
|
495
|
+
* isLoading,
|
|
496
|
+
* } = usePaginatedQuery<User>('users', {
|
|
497
|
+
* pageSize: 10,
|
|
498
|
+
* orderBy: { field: 'createdAt', direction: 'desc' },
|
|
499
|
+
* });
|
|
500
|
+
*
|
|
501
|
+
* return (
|
|
502
|
+
* <>
|
|
503
|
+
* <table>
|
|
504
|
+
* {data.map(user => <UserRow key={user.id} user={user} />)}
|
|
505
|
+
* </table>
|
|
506
|
+
* <div>
|
|
507
|
+
* <button onClick={prevPage} disabled={!hasPrevPage}>Previous</button>
|
|
508
|
+
* <span>Page {page} of {totalPages}</span>
|
|
509
|
+
* <button onClick={nextPage} disabled={!hasNextPage}>Next</button>
|
|
510
|
+
* </div>
|
|
511
|
+
* </>
|
|
512
|
+
* );
|
|
513
|
+
* }
|
|
514
|
+
* ```
|
|
515
|
+
*/
|
|
516
|
+
declare function usePaginatedQuery<T extends Record<string, unknown>>(table: string, options?: UsePaginatedQueryOptions<T>): UsePaginatedQueryReturn<T>;
|
|
517
|
+
/**
|
|
518
|
+
* Infinite scroll query hook
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```tsx
|
|
522
|
+
* function InfiniteUserList() {
|
|
523
|
+
* const {
|
|
524
|
+
* data,
|
|
525
|
+
* hasNextPage,
|
|
526
|
+
* fetchNextPage,
|
|
527
|
+
* isFetchingNextPage,
|
|
528
|
+
* isLoading,
|
|
529
|
+
* } = useInfiniteQuery<User>('users', {
|
|
530
|
+
* pageSize: 20,
|
|
531
|
+
* orderBy: { field: 'createdAt', direction: 'desc' },
|
|
532
|
+
* });
|
|
533
|
+
*
|
|
534
|
+
* const loadMoreRef = useRef<HTMLDivElement>(null);
|
|
535
|
+
*
|
|
536
|
+
* // Intersection observer for infinite scroll
|
|
537
|
+
* useEffect(() => {
|
|
538
|
+
* const observer = new IntersectionObserver(([entry]) => {
|
|
539
|
+
* if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
|
|
540
|
+
* fetchNextPage();
|
|
541
|
+
* }
|
|
542
|
+
* });
|
|
543
|
+
*
|
|
544
|
+
* if (loadMoreRef.current) {
|
|
545
|
+
* observer.observe(loadMoreRef.current);
|
|
546
|
+
* }
|
|
547
|
+
*
|
|
548
|
+
* return () => observer.disconnect();
|
|
549
|
+
* }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
550
|
+
*
|
|
551
|
+
* if (isLoading) return <Spinner />;
|
|
552
|
+
*
|
|
553
|
+
* return (
|
|
554
|
+
* <>
|
|
555
|
+
* {data.map(user => <UserCard key={user.id} user={user} />)}
|
|
556
|
+
* <div ref={loadMoreRef}>
|
|
557
|
+
* {isFetchingNextPage && <Spinner />}
|
|
558
|
+
* </div>
|
|
559
|
+
* </>
|
|
560
|
+
* );
|
|
561
|
+
* }
|
|
562
|
+
* ```
|
|
563
|
+
*/
|
|
564
|
+
declare function useInfiniteQuery<T extends Record<string, unknown>>(table: string, options?: UseInfiniteQueryOptions<T>): UseInfiniteQueryReturn<T>;
|
|
565
|
+
/**
|
|
566
|
+
* Count query hook
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```tsx
|
|
570
|
+
* function UserStats() {
|
|
571
|
+
* const { count, isLoading } = useCount('users', {
|
|
572
|
+
* where: { status: 'active' }
|
|
573
|
+
* });
|
|
574
|
+
*
|
|
575
|
+
* return <div>Active users: {isLoading ? '...' : count}</div>;
|
|
576
|
+
* }
|
|
577
|
+
* ```
|
|
578
|
+
*/
|
|
579
|
+
declare function useCount(table: string, options?: {
|
|
580
|
+
where?: WhereFilter;
|
|
581
|
+
enabled?: boolean;
|
|
582
|
+
}): {
|
|
583
|
+
count: number;
|
|
584
|
+
isLoading: boolean;
|
|
585
|
+
error: Error | null;
|
|
586
|
+
refetch: () => Promise<void>;
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
type MutationStatus = "idle" | "loading" | "success" | "error";
|
|
590
|
+
interface UseMutationOptions<TData, TVariables> {
|
|
591
|
+
/** Callback on success */
|
|
592
|
+
onSuccess?: (data: TData, variables: TVariables) => void | Promise<void>;
|
|
593
|
+
/** Callback on error */
|
|
594
|
+
onError?: (error: Error, variables: TVariables) => void | Promise<void>;
|
|
595
|
+
/** Callback on mutation settled (success or error) */
|
|
596
|
+
onSettled?: (data: TData | undefined, error: Error | null, variables: TVariables) => void | Promise<void>;
|
|
597
|
+
/** Callback before mutation starts */
|
|
598
|
+
onMutate?: (variables: TVariables) => void | Promise<void>;
|
|
599
|
+
/** Retry count on failure (default: 0) */
|
|
600
|
+
retry?: number;
|
|
601
|
+
/** Retry delay in ms (default: 1000) */
|
|
602
|
+
retryDelay?: number;
|
|
603
|
+
}
|
|
604
|
+
interface UseMutationReturn<TData, TVariables> {
|
|
605
|
+
/** Execute the mutation */
|
|
606
|
+
mutate: (variables: TVariables) => void;
|
|
607
|
+
/** Execute the mutation (async) */
|
|
608
|
+
mutateAsync: (variables: TVariables) => Promise<TData>;
|
|
609
|
+
/** Mutation result data */
|
|
610
|
+
data: TData | undefined;
|
|
611
|
+
/** Error state */
|
|
612
|
+
error: Error | null;
|
|
613
|
+
/** Loading state */
|
|
614
|
+
isLoading: boolean;
|
|
615
|
+
/** Success state */
|
|
616
|
+
isSuccess: boolean;
|
|
617
|
+
/** Error state (boolean) */
|
|
618
|
+
isError: boolean;
|
|
619
|
+
/** Idle state */
|
|
620
|
+
isIdle: boolean;
|
|
621
|
+
/** Mutation status */
|
|
622
|
+
status: MutationStatus;
|
|
623
|
+
/** Reset mutation state */
|
|
624
|
+
reset: () => void;
|
|
625
|
+
}
|
|
626
|
+
interface UseCreateOptions<T> extends UseMutationOptions<T, Partial<T>> {
|
|
627
|
+
/** Invalidate query cache for this table after mutation */
|
|
628
|
+
invalidateQueries?: boolean;
|
|
629
|
+
}
|
|
630
|
+
interface UseUpdateOptions<T> extends UseMutationOptions<T, {
|
|
631
|
+
id: string;
|
|
632
|
+
data: Partial<T>;
|
|
633
|
+
}> {
|
|
634
|
+
invalidateQueries?: boolean;
|
|
635
|
+
}
|
|
636
|
+
interface UseDeleteOptions extends UseMutationOptions<void, string> {
|
|
637
|
+
invalidateQueries?: boolean;
|
|
638
|
+
}
|
|
639
|
+
interface UseUpsertOptions<T extends Record<string, unknown>> extends UseMutationOptions<T, {
|
|
640
|
+
data: Partial<T>;
|
|
641
|
+
conflictFields: (keyof T)[];
|
|
642
|
+
updateFields?: (keyof T)[];
|
|
643
|
+
}> {
|
|
644
|
+
invalidateQueries?: boolean;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Generic mutation hook
|
|
648
|
+
*
|
|
649
|
+
* @example
|
|
650
|
+
* ```tsx
|
|
651
|
+
* function CreateUserForm() {
|
|
652
|
+
* const { mutate, isLoading, error, isSuccess } = useMutation(
|
|
653
|
+
* async (data: CreateUserInput) => {
|
|
654
|
+
* const response = await fetch('/api/users', {
|
|
655
|
+
* method: 'POST',
|
|
656
|
+
* body: JSON.stringify(data),
|
|
657
|
+
* });
|
|
658
|
+
* return response.json();
|
|
659
|
+
* },
|
|
660
|
+
* {
|
|
661
|
+
* onSuccess: (user) => {
|
|
662
|
+
* console.log('User created:', user);
|
|
663
|
+
* },
|
|
664
|
+
* }
|
|
665
|
+
* );
|
|
666
|
+
*
|
|
667
|
+
* const handleSubmit = (data: CreateUserInput) => {
|
|
668
|
+
* mutate(data);
|
|
669
|
+
* };
|
|
670
|
+
*
|
|
671
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
672
|
+
* }
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
675
|
+
declare function useMutation<TData, TVariables = void>(mutationFn: (variables: TVariables) => Promise<TData>, options?: UseMutationOptions<TData, TVariables>): UseMutationReturn<TData, TVariables>;
|
|
676
|
+
/**
|
|
677
|
+
* Create mutation hook for a table
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```tsx
|
|
681
|
+
* function CreatePost() {
|
|
682
|
+
* const { mutate, isLoading, isSuccess } = useCreate<Post>('posts', {
|
|
683
|
+
* onSuccess: (post) => {
|
|
684
|
+
* console.log('Post created:', post.id);
|
|
685
|
+
* },
|
|
686
|
+
* });
|
|
687
|
+
*
|
|
688
|
+
* return (
|
|
689
|
+
* <button onClick={() => mutate({ title: 'New Post', content: '...' })}>
|
|
690
|
+
* {isLoading ? 'Creating...' : 'Create Post'}
|
|
691
|
+
* </button>
|
|
692
|
+
* );
|
|
693
|
+
* }
|
|
694
|
+
* ```
|
|
695
|
+
*/
|
|
696
|
+
declare function useCreate<T extends Record<string, unknown>>(table: string, options?: UseCreateOptions<T>): UseMutationReturn<T, Partial<T>>;
|
|
697
|
+
/**
|
|
698
|
+
* Update mutation hook for a table
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```tsx
|
|
702
|
+
* function EditPost({ postId }: { postId: string }) {
|
|
703
|
+
* const { mutate, isLoading } = useUpdate<Post>('posts', {
|
|
704
|
+
* onSuccess: (post) => {
|
|
705
|
+
* toast.success('Post updated!');
|
|
706
|
+
* },
|
|
707
|
+
* });
|
|
708
|
+
*
|
|
709
|
+
* return (
|
|
710
|
+
* <button onClick={() => mutate({ id: postId, data: { title: 'Updated Title' } })}>
|
|
711
|
+
* {isLoading ? 'Saving...' : 'Save'}
|
|
712
|
+
* </button>
|
|
713
|
+
* );
|
|
714
|
+
* }
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
declare function useUpdate<T extends Record<string, unknown>>(table: string, options?: UseUpdateOptions<T>): UseMutationReturn<T, {
|
|
718
|
+
id: string;
|
|
719
|
+
data: Partial<T>;
|
|
720
|
+
}>;
|
|
721
|
+
/**
|
|
722
|
+
* Delete mutation hook for a table
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
* ```tsx
|
|
726
|
+
* function DeletePost({ postId }: { postId: string }) {
|
|
727
|
+
* const { mutate, isLoading } = useDelete('posts', {
|
|
728
|
+
* onSuccess: () => {
|
|
729
|
+
* toast.success('Post deleted');
|
|
730
|
+
* navigate('/posts');
|
|
731
|
+
* },
|
|
732
|
+
* });
|
|
733
|
+
*
|
|
734
|
+
* return (
|
|
735
|
+
* <button onClick={() => mutate(postId)} disabled={isLoading}>
|
|
736
|
+
* {isLoading ? 'Deleting...' : 'Delete'}
|
|
737
|
+
* </button>
|
|
738
|
+
* );
|
|
739
|
+
* }
|
|
740
|
+
* ```
|
|
741
|
+
*/
|
|
742
|
+
declare function useDelete(table: string, options?: UseDeleteOptions): UseMutationReturn<void, string>;
|
|
743
|
+
/**
|
|
744
|
+
* Upsert mutation hook for a table
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```tsx
|
|
748
|
+
* function SaveProfile({ userId }: { userId: string }) {
|
|
749
|
+
* const { mutate, isLoading } = useUpsert<Profile>('profiles', {
|
|
750
|
+
* onSuccess: (profile) => {
|
|
751
|
+
* toast.success('Profile saved');
|
|
752
|
+
* },
|
|
753
|
+
* });
|
|
754
|
+
*
|
|
755
|
+
* return (
|
|
756
|
+
* <button onClick={() => mutate({
|
|
757
|
+
* data: { userId, bio: 'Hello world' },
|
|
758
|
+
* conflictFields: ['userId']
|
|
759
|
+
* })}>
|
|
760
|
+
* {isLoading ? 'Saving...' : 'Save Profile'}
|
|
761
|
+
* </button>
|
|
762
|
+
* );
|
|
763
|
+
* }
|
|
764
|
+
* ```
|
|
765
|
+
*/
|
|
766
|
+
declare function useUpsert<T extends Record<string, unknown>>(table: string, options?: UseUpsertOptions<T>): UseMutationReturn<T, {
|
|
767
|
+
data: Partial<T>;
|
|
768
|
+
conflictFields: (keyof T)[];
|
|
769
|
+
updateFields?: (keyof T)[];
|
|
770
|
+
}>;
|
|
771
|
+
/**
|
|
772
|
+
* Batch create mutation hook
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* ```tsx
|
|
776
|
+
* function ImportUsers() {
|
|
777
|
+
* const { mutate, isLoading, data } = useBatchCreate<User>('users');
|
|
778
|
+
*
|
|
779
|
+
* const handleImport = (users: Partial<User>[]) => {
|
|
780
|
+
* mutate(users);
|
|
781
|
+
* };
|
|
782
|
+
*
|
|
783
|
+
* return (
|
|
784
|
+
* <>
|
|
785
|
+
* <input type="file" onChange={handleFileSelect} />
|
|
786
|
+
* <button onClick={handleImport} disabled={isLoading}>
|
|
787
|
+
* {isLoading ? `Importing... ${data?.count || 0}` : 'Import'}
|
|
788
|
+
* </button>
|
|
789
|
+
* </>
|
|
790
|
+
* );
|
|
791
|
+
* }
|
|
792
|
+
* ```
|
|
793
|
+
*/
|
|
794
|
+
declare function useBatchCreate<T extends Record<string, unknown>>(table: string, options?: UseMutationOptions<{
|
|
795
|
+
count: number;
|
|
796
|
+
records?: T[];
|
|
797
|
+
}, Partial<T>[]>): UseMutationReturn<{
|
|
798
|
+
count: number;
|
|
799
|
+
records?: T[];
|
|
800
|
+
}, Partial<T>[]>;
|
|
801
|
+
/**
|
|
802
|
+
* Batch update mutation hook - updates all records matching the where clause
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```tsx
|
|
806
|
+
* function BulkUpdateStatus() {
|
|
807
|
+
* const { mutate, isLoading, data } = useBatchUpdate<Task>('tasks');
|
|
808
|
+
*
|
|
809
|
+
* const markAllComplete = () => {
|
|
810
|
+
* mutate({
|
|
811
|
+
* data: { status: 'completed' },
|
|
812
|
+
* where: { assigneeId: currentUserId }
|
|
813
|
+
* });
|
|
814
|
+
* };
|
|
815
|
+
*
|
|
816
|
+
* return (
|
|
817
|
+
* <button onClick={markAllComplete}>
|
|
818
|
+
* {isLoading ? `Updating... ${data?.count || 0}` : 'Mark All Complete'}
|
|
819
|
+
* </button>
|
|
820
|
+
* );
|
|
821
|
+
* }
|
|
822
|
+
* ```
|
|
823
|
+
*/
|
|
824
|
+
declare function useBatchUpdate<T extends Record<string, unknown>>(table: string, options?: UseMutationOptions<{
|
|
825
|
+
count: number;
|
|
826
|
+
records?: T[];
|
|
827
|
+
}, {
|
|
828
|
+
data: Partial<T>;
|
|
829
|
+
where: WhereFilter;
|
|
830
|
+
}>): UseMutationReturn<{
|
|
831
|
+
count: number;
|
|
832
|
+
records?: T[];
|
|
833
|
+
}, {
|
|
834
|
+
data: Partial<T>;
|
|
835
|
+
where: WhereFilter;
|
|
836
|
+
}>;
|
|
837
|
+
/**
|
|
838
|
+
* Batch delete mutation hook - deletes all records matching the where clause
|
|
839
|
+
*
|
|
840
|
+
* @example
|
|
841
|
+
* ```tsx
|
|
842
|
+
* function BulkDelete() {
|
|
843
|
+
* const { mutate, isLoading, data } = useBatchDelete('items');
|
|
844
|
+
*
|
|
845
|
+
* const deleteOldItems = () => {
|
|
846
|
+
* mutate({ olderThan: { lt: thirtyDaysAgo } });
|
|
847
|
+
* };
|
|
848
|
+
*
|
|
849
|
+
* return (
|
|
850
|
+
* <button onClick={deleteOldItems}>
|
|
851
|
+
* {isLoading ? 'Deleting...' : `Delete old items (${data?.count || 0} deleted)`}
|
|
852
|
+
* </button>
|
|
853
|
+
* );
|
|
854
|
+
* }
|
|
855
|
+
* ```
|
|
856
|
+
*/
|
|
857
|
+
declare function useBatchDelete(table: string, options?: UseMutationOptions<{
|
|
858
|
+
count: number;
|
|
859
|
+
}, WhereFilter>): UseMutationReturn<{
|
|
860
|
+
count: number;
|
|
861
|
+
}, WhereFilter>;
|
|
862
|
+
/**
|
|
863
|
+
* Optimistic update helper hook
|
|
864
|
+
*
|
|
865
|
+
* @example
|
|
866
|
+
* ```tsx
|
|
867
|
+
* function LikeButton({ postId, likes }: { postId: string; likes: number }) {
|
|
868
|
+
* const { mutate, optimisticData, rollback } = useOptimisticMutation(
|
|
869
|
+
* likes,
|
|
870
|
+
* async () => {
|
|
871
|
+
* await vaif.from('posts').update(postId, { likes: likes + 1 });
|
|
872
|
+
* },
|
|
873
|
+
* {
|
|
874
|
+
* optimisticUpdate: (current) => current + 1,
|
|
875
|
+
* onError: () => {
|
|
876
|
+
* toast.error('Failed to like post');
|
|
877
|
+
* },
|
|
878
|
+
* }
|
|
879
|
+
* );
|
|
880
|
+
*
|
|
881
|
+
* return (
|
|
882
|
+
* <button onClick={() => mutate()}>
|
|
883
|
+
* {optimisticData} likes
|
|
884
|
+
* </button>
|
|
885
|
+
* );
|
|
886
|
+
* }
|
|
887
|
+
* ```
|
|
888
|
+
*/
|
|
889
|
+
declare function useOptimisticMutation<TData, TVariables = void>(currentData: TData, mutationFn: (variables: TVariables) => Promise<TData>, options: {
|
|
890
|
+
optimisticUpdate: (current: TData, variables: TVariables) => TData;
|
|
891
|
+
onSuccess?: (data: TData, variables: TVariables) => void;
|
|
892
|
+
onError?: (error: Error, variables: TVariables) => void;
|
|
893
|
+
onSettled?: (data: TData | undefined, error: Error | null, variables: TVariables) => void;
|
|
894
|
+
}): {
|
|
895
|
+
mutate: (variables: TVariables) => void;
|
|
896
|
+
mutateAsync: (variables: TVariables) => Promise<TData>;
|
|
897
|
+
optimisticData: TData;
|
|
898
|
+
rollback: () => void;
|
|
899
|
+
isLoading: boolean;
|
|
900
|
+
error: Error | null;
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
interface UseRealtimeOptions {
|
|
904
|
+
/** Enable/disable the subscription */
|
|
905
|
+
enabled?: boolean;
|
|
906
|
+
}
|
|
907
|
+
interface UseSubscriptionOptions<T = Record<string, unknown>> extends UseRealtimeOptions {
|
|
908
|
+
/** Filter by operations */
|
|
909
|
+
operations?: DbOperation[];
|
|
910
|
+
/** Filter expression */
|
|
911
|
+
filter?: string;
|
|
912
|
+
/** Callback on insert */
|
|
913
|
+
onInsert?: (record: T) => void;
|
|
914
|
+
/** Callback on update */
|
|
915
|
+
onUpdate?: (record: T, old: T | null) => void;
|
|
916
|
+
/** Callback on delete */
|
|
917
|
+
onDelete?: (record: T) => void;
|
|
918
|
+
/** Callback on any change */
|
|
919
|
+
onChange?: (event: DbChangeEvent<T>) => void;
|
|
920
|
+
}
|
|
921
|
+
interface UseSubscriptionReturn<T> {
|
|
922
|
+
/** Latest received record */
|
|
923
|
+
data: T | null;
|
|
924
|
+
/** All changes received */
|
|
925
|
+
changes: DbChangeEvent<T>[];
|
|
926
|
+
/** Whether subscribed */
|
|
927
|
+
isSubscribed: boolean;
|
|
928
|
+
/** Clear changes history */
|
|
929
|
+
clearChanges: () => void;
|
|
930
|
+
}
|
|
931
|
+
interface UseChannelOptions extends UseRealtimeOptions {
|
|
932
|
+
/** Channel type */
|
|
933
|
+
type?: "public" | "private" | "presence";
|
|
934
|
+
}
|
|
935
|
+
interface BroadcastMessage<T = unknown> {
|
|
936
|
+
event: string;
|
|
937
|
+
payload: T;
|
|
938
|
+
senderId?: string;
|
|
939
|
+
}
|
|
940
|
+
interface UseChannelReturn<TMessage = unknown> {
|
|
941
|
+
/** Send a broadcast message */
|
|
942
|
+
broadcast: (event: string, payload: TMessage) => void;
|
|
943
|
+
/** Last received broadcast */
|
|
944
|
+
lastMessage: BroadcastMessage<TMessage> | null;
|
|
945
|
+
/** All broadcast messages received */
|
|
946
|
+
messages: BroadcastMessage<TMessage>[];
|
|
947
|
+
/** Whether channel is joined */
|
|
948
|
+
isJoined: boolean;
|
|
949
|
+
/** Leave channel */
|
|
950
|
+
leave: () => void;
|
|
951
|
+
}
|
|
952
|
+
interface UsePresenceOptions {
|
|
953
|
+
/** Enable/disable */
|
|
954
|
+
enabled?: boolean;
|
|
955
|
+
}
|
|
956
|
+
interface UsePresenceReturn<T extends PresenceState> {
|
|
957
|
+
/** Current presence state (all users) */
|
|
958
|
+
presence: Record<string, PresenceEntry<T>[]>;
|
|
959
|
+
/** Number of online users */
|
|
960
|
+
count: number;
|
|
961
|
+
/** List of online user keys */
|
|
962
|
+
onlineUsers: string[];
|
|
963
|
+
/** Update own presence state */
|
|
964
|
+
update: (state: Partial<T>) => void;
|
|
965
|
+
/** Leave presence (go offline) */
|
|
966
|
+
leave: () => void;
|
|
967
|
+
/** Whether currently tracked */
|
|
968
|
+
isTracking: boolean;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Subscribe to database table changes in real-time
|
|
972
|
+
*
|
|
973
|
+
* @example
|
|
974
|
+
* ```tsx
|
|
975
|
+
* function MessageList() {
|
|
976
|
+
* const { data, changes } = useSubscription<Message>('messages', {
|
|
977
|
+
* operations: ['INSERT'],
|
|
978
|
+
* onInsert: (message) => {
|
|
979
|
+
* playNotificationSound();
|
|
980
|
+
* },
|
|
981
|
+
* });
|
|
982
|
+
*
|
|
983
|
+
* return (
|
|
984
|
+
* <ul>
|
|
985
|
+
* {changes.map((change, i) => (
|
|
986
|
+
* <li key={i}>{change.new?.text}</li>
|
|
987
|
+
* ))}
|
|
988
|
+
* </ul>
|
|
989
|
+
* );
|
|
990
|
+
* }
|
|
991
|
+
* ```
|
|
992
|
+
*/
|
|
993
|
+
declare function useSubscription<T = Record<string, unknown>>(table: string, options?: UseSubscriptionOptions<T>): UseSubscriptionReturn<T>;
|
|
994
|
+
/**
|
|
995
|
+
* Join a realtime channel for broadcast
|
|
996
|
+
*
|
|
997
|
+
* @example
|
|
998
|
+
* ```tsx
|
|
999
|
+
* function ChatRoom({ roomId }: { roomId: string }) {
|
|
1000
|
+
* const { broadcast, messages } = useChannel<ChatMessage>(`room:${roomId}`);
|
|
1001
|
+
*
|
|
1002
|
+
* const sendMessage = (text: string) => {
|
|
1003
|
+
* broadcast('message', { text, userId: user.id });
|
|
1004
|
+
* };
|
|
1005
|
+
*
|
|
1006
|
+
* return (
|
|
1007
|
+
* <div>
|
|
1008
|
+
* <MessageList messages={messages} />
|
|
1009
|
+
* <MessageInput onSend={sendMessage} />
|
|
1010
|
+
* </div>
|
|
1011
|
+
* );
|
|
1012
|
+
* }
|
|
1013
|
+
* ```
|
|
1014
|
+
*/
|
|
1015
|
+
declare function useChannel<TMessage = unknown>(channelName: string, options?: UseChannelOptions): UseChannelReturn<TMessage>;
|
|
1016
|
+
/**
|
|
1017
|
+
* Track and observe presence in a channel
|
|
1018
|
+
*
|
|
1019
|
+
* @example
|
|
1020
|
+
* ```tsx
|
|
1021
|
+
* function OnlineIndicator({ documentId }: { documentId: string }) {
|
|
1022
|
+
* const { count, presence, update } = usePresence<CursorState>(
|
|
1023
|
+
* `doc:${documentId}`,
|
|
1024
|
+
* { x: 0, y: 0, color: randomColor() }
|
|
1025
|
+
* );
|
|
1026
|
+
*
|
|
1027
|
+
* const handleMouseMove = (e: MouseEvent) => {
|
|
1028
|
+
* update({ x: e.clientX, y: e.clientY });
|
|
1029
|
+
* };
|
|
1030
|
+
*
|
|
1031
|
+
* return (
|
|
1032
|
+
* <div onMouseMove={handleMouseMove}>
|
|
1033
|
+
* <span>{count} online</span>
|
|
1034
|
+
* {Object.entries(presence).map(([key, entries]) => (
|
|
1035
|
+
* entries.map((entry, i) => (
|
|
1036
|
+
* <Cursor key={`${key}-${i}`} {...entry.state} />
|
|
1037
|
+
* ))
|
|
1038
|
+
* ))}
|
|
1039
|
+
* </div>
|
|
1040
|
+
* );
|
|
1041
|
+
* }
|
|
1042
|
+
* ```
|
|
1043
|
+
*/
|
|
1044
|
+
declare function usePresence<T extends PresenceState = PresenceState>(channelName: string, initialState?: T, options?: UsePresenceOptions): UsePresenceReturn<T>;
|
|
1045
|
+
/**
|
|
1046
|
+
* Hook for realtime connection state
|
|
1047
|
+
*
|
|
1048
|
+
* @example
|
|
1049
|
+
* ```tsx
|
|
1050
|
+
* function ConnectionStatus() {
|
|
1051
|
+
* const { state, isConnected, reconnect } = useRealtimeConnection();
|
|
1052
|
+
*
|
|
1053
|
+
* if (!isConnected) {
|
|
1054
|
+
* return (
|
|
1055
|
+
* <div className="offline-banner">
|
|
1056
|
+
* Disconnected. <button onClick={reconnect}>Reconnect</button>
|
|
1057
|
+
* </div>
|
|
1058
|
+
* );
|
|
1059
|
+
* }
|
|
1060
|
+
*
|
|
1061
|
+
* return null;
|
|
1062
|
+
* }
|
|
1063
|
+
* ```
|
|
1064
|
+
*/
|
|
1065
|
+
declare function useRealtimeConnection(): {
|
|
1066
|
+
state: ConnectionState;
|
|
1067
|
+
isConnected: boolean;
|
|
1068
|
+
connectionId: string | null;
|
|
1069
|
+
reconnect: () => void;
|
|
1070
|
+
disconnect: () => void;
|
|
1071
|
+
};
|
|
1072
|
+
/**
|
|
1073
|
+
* Broadcast-only channel hook (simpler than useChannel)
|
|
1074
|
+
*
|
|
1075
|
+
* @example
|
|
1076
|
+
* ```tsx
|
|
1077
|
+
* function Notifications() {
|
|
1078
|
+
* const { send, messages } = useBroadcast<NotificationPayload>('notifications');
|
|
1079
|
+
*
|
|
1080
|
+
* return (
|
|
1081
|
+
* <>
|
|
1082
|
+
* {messages.map((msg, i) => (
|
|
1083
|
+
* <Toast key={i} {...msg.payload} />
|
|
1084
|
+
* ))}
|
|
1085
|
+
* </>
|
|
1086
|
+
* );
|
|
1087
|
+
* }
|
|
1088
|
+
* ```
|
|
1089
|
+
*/
|
|
1090
|
+
declare function useBroadcast<T = unknown>(channelName: string, options?: {
|
|
1091
|
+
enabled?: boolean;
|
|
1092
|
+
}): {
|
|
1093
|
+
send: (event: string, payload: T) => void;
|
|
1094
|
+
messages: BroadcastMessage<T>[];
|
|
1095
|
+
lastMessage: BroadcastMessage<T> | null;
|
|
1096
|
+
isConnected: boolean;
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
interface UseUploadOptions extends Partial<UploadOptions> {
|
|
1100
|
+
/** Max file size in bytes */
|
|
1101
|
+
maxSize?: number;
|
|
1102
|
+
/** Allowed mime types */
|
|
1103
|
+
allowedTypes?: string[];
|
|
1104
|
+
/** Generate unique key */
|
|
1105
|
+
uniqueKey?: boolean;
|
|
1106
|
+
/** Callback on upload start */
|
|
1107
|
+
onUploadStart?: (file: File) => void;
|
|
1108
|
+
/** Callback on upload progress */
|
|
1109
|
+
onProgress?: (progress: number, file: File) => void;
|
|
1110
|
+
/** Callback on upload success */
|
|
1111
|
+
onSuccess?: (result: UploadResult, file: File) => void;
|
|
1112
|
+
/** Callback on upload error */
|
|
1113
|
+
onError?: (error: Error, file: File) => void;
|
|
1114
|
+
}
|
|
1115
|
+
interface UseUploadReturn {
|
|
1116
|
+
/** Upload a file */
|
|
1117
|
+
upload: (key: string, file: File) => Promise<UploadResult>;
|
|
1118
|
+
/** Upload multiple files */
|
|
1119
|
+
uploadMultiple: (files: Array<{
|
|
1120
|
+
key: string;
|
|
1121
|
+
file: File;
|
|
1122
|
+
}>) => Promise<UploadResult[]>;
|
|
1123
|
+
/** Current upload progress (0-100) */
|
|
1124
|
+
progress: number;
|
|
1125
|
+
/** Whether upload is in progress */
|
|
1126
|
+
isUploading: boolean;
|
|
1127
|
+
/** Error state */
|
|
1128
|
+
error: Error | null;
|
|
1129
|
+
/** Last upload result */
|
|
1130
|
+
result: UploadResult | null;
|
|
1131
|
+
/** All upload results */
|
|
1132
|
+
results: UploadResult[];
|
|
1133
|
+
/** Reset state */
|
|
1134
|
+
reset: () => void;
|
|
1135
|
+
}
|
|
1136
|
+
interface UseDownloadOptions {
|
|
1137
|
+
/** Callback on download success */
|
|
1138
|
+
onSuccess?: (blob: Blob, filename: string) => void;
|
|
1139
|
+
/** Callback on download error */
|
|
1140
|
+
onError?: (error: Error) => void;
|
|
1141
|
+
}
|
|
1142
|
+
interface UseDownloadReturn {
|
|
1143
|
+
/** Download a file */
|
|
1144
|
+
download: (key: string, filename?: string) => Promise<Blob>;
|
|
1145
|
+
/** Download and trigger browser download */
|
|
1146
|
+
downloadAndSave: (key: string, filename?: string) => Promise<void>;
|
|
1147
|
+
/** Whether download is in progress */
|
|
1148
|
+
isDownloading: boolean;
|
|
1149
|
+
/** Error state */
|
|
1150
|
+
error: Error | null;
|
|
1151
|
+
}
|
|
1152
|
+
interface UseFileOptions {
|
|
1153
|
+
/** Expiry time in seconds for signed URL */
|
|
1154
|
+
expiresIn?: number;
|
|
1155
|
+
/** Enable/disable */
|
|
1156
|
+
enabled?: boolean;
|
|
1157
|
+
}
|
|
1158
|
+
interface UseFileReturn {
|
|
1159
|
+
/** File URL (signed) */
|
|
1160
|
+
url: string | null;
|
|
1161
|
+
/** File metadata */
|
|
1162
|
+
metadata: FileMetadata | null;
|
|
1163
|
+
/** Loading state */
|
|
1164
|
+
isLoading: boolean;
|
|
1165
|
+
/** Error state */
|
|
1166
|
+
error: Error | null;
|
|
1167
|
+
/** Refresh URL and metadata */
|
|
1168
|
+
refresh: () => Promise<void>;
|
|
1169
|
+
/** Delete the file */
|
|
1170
|
+
deleteFile: () => Promise<void>;
|
|
1171
|
+
}
|
|
1172
|
+
interface UseFilesOptions {
|
|
1173
|
+
/** Path prefix filter */
|
|
1174
|
+
prefix?: string;
|
|
1175
|
+
/** Max files to return */
|
|
1176
|
+
limit?: number;
|
|
1177
|
+
/** Enable/disable */
|
|
1178
|
+
enabled?: boolean;
|
|
1179
|
+
}
|
|
1180
|
+
interface UseFilesReturn {
|
|
1181
|
+
/** List of files */
|
|
1182
|
+
files: FileMetadata[];
|
|
1183
|
+
/** Loading state */
|
|
1184
|
+
isLoading: boolean;
|
|
1185
|
+
/** Error state */
|
|
1186
|
+
error: Error | null;
|
|
1187
|
+
/** Refresh file list */
|
|
1188
|
+
refresh: () => Promise<void>;
|
|
1189
|
+
}
|
|
1190
|
+
interface DropzoneOptions extends UseUploadOptions {
|
|
1191
|
+
/** Base path for uploads */
|
|
1192
|
+
basePath?: string;
|
|
1193
|
+
/** Multiple files allowed */
|
|
1194
|
+
multiple?: boolean;
|
|
1195
|
+
}
|
|
1196
|
+
interface DropzoneState {
|
|
1197
|
+
/** Whether file is being dragged over */
|
|
1198
|
+
isDragOver: boolean;
|
|
1199
|
+
/** Files selected (before upload) */
|
|
1200
|
+
selectedFiles: File[];
|
|
1201
|
+
}
|
|
1202
|
+
interface UseDropzoneReturn extends UseUploadReturn {
|
|
1203
|
+
/** Dropzone state */
|
|
1204
|
+
dropzone: DropzoneState;
|
|
1205
|
+
/** Get props for the drop zone element */
|
|
1206
|
+
getDropzoneProps: () => {
|
|
1207
|
+
onDragOver: (e: React.DragEvent) => void;
|
|
1208
|
+
onDragLeave: (e: React.DragEvent) => void;
|
|
1209
|
+
onDrop: (e: React.DragEvent) => void;
|
|
1210
|
+
onClick: () => void;
|
|
1211
|
+
};
|
|
1212
|
+
/** Get props for hidden file input */
|
|
1213
|
+
getInputProps: () => {
|
|
1214
|
+
type: "file";
|
|
1215
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
1216
|
+
multiple: boolean;
|
|
1217
|
+
accept: string;
|
|
1218
|
+
style: {
|
|
1219
|
+
display: "none";
|
|
1220
|
+
};
|
|
1221
|
+
};
|
|
1222
|
+
/** Open file picker */
|
|
1223
|
+
openFilePicker: () => void;
|
|
1224
|
+
/** Clear selected files */
|
|
1225
|
+
clearFiles: () => void;
|
|
1226
|
+
/** Remove a specific file */
|
|
1227
|
+
removeFile: (index: number) => void;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* File upload hook
|
|
1231
|
+
*
|
|
1232
|
+
* @example
|
|
1233
|
+
* ```tsx
|
|
1234
|
+
* function FileUploader() {
|
|
1235
|
+
* const { upload, progress, isUploading, error } = useUpload({
|
|
1236
|
+
* maxSize: 5 * 1024 * 1024, // 5MB
|
|
1237
|
+
* allowedTypes: ['image/jpeg', 'image/png'],
|
|
1238
|
+
* onSuccess: (result) => {
|
|
1239
|
+
* toast.success(`Uploaded: ${result.key}`);
|
|
1240
|
+
* },
|
|
1241
|
+
* });
|
|
1242
|
+
*
|
|
1243
|
+
* const handleChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
|
1244
|
+
* const file = e.target.files?.[0];
|
|
1245
|
+
* if (file) {
|
|
1246
|
+
* await upload(`uploads/${file.name}`, file);
|
|
1247
|
+
* }
|
|
1248
|
+
* };
|
|
1249
|
+
*
|
|
1250
|
+
* return (
|
|
1251
|
+
* <div>
|
|
1252
|
+
* <input type="file" onChange={handleChange} disabled={isUploading} />
|
|
1253
|
+
* {isUploading && <progress value={progress} max={100} />}
|
|
1254
|
+
* {error && <p className="error">{error.message}</p>}
|
|
1255
|
+
* </div>
|
|
1256
|
+
* );
|
|
1257
|
+
* }
|
|
1258
|
+
* ```
|
|
1259
|
+
*/
|
|
1260
|
+
declare function useUpload(options?: UseUploadOptions): UseUploadReturn;
|
|
1261
|
+
/**
|
|
1262
|
+
* File download hook
|
|
1263
|
+
*
|
|
1264
|
+
* @example
|
|
1265
|
+
* ```tsx
|
|
1266
|
+
* function DownloadButton({ fileKey }: { fileKey: string }) {
|
|
1267
|
+
* const { downloadAndSave, isDownloading } = useDownload();
|
|
1268
|
+
*
|
|
1269
|
+
* return (
|
|
1270
|
+
* <button
|
|
1271
|
+
* onClick={() => downloadAndSave(fileKey)}
|
|
1272
|
+
* disabled={isDownloading}
|
|
1273
|
+
* >
|
|
1274
|
+
* {isDownloading ? 'Downloading...' : 'Download'}
|
|
1275
|
+
* </button>
|
|
1276
|
+
* );
|
|
1277
|
+
* }
|
|
1278
|
+
* ```
|
|
1279
|
+
*/
|
|
1280
|
+
declare function useDownload(options?: UseDownloadOptions): UseDownloadReturn;
|
|
1281
|
+
/**
|
|
1282
|
+
* Single file hook (URL, metadata, actions)
|
|
1283
|
+
*
|
|
1284
|
+
* @example
|
|
1285
|
+
* ```tsx
|
|
1286
|
+
* function FilePreview({ fileKey }: { fileKey: string }) {
|
|
1287
|
+
* const { url, metadata, isLoading, deleteFile } = useFile(fileKey, {
|
|
1288
|
+
* expiresIn: 3600
|
|
1289
|
+
* });
|
|
1290
|
+
*
|
|
1291
|
+
* if (isLoading) return <Skeleton />;
|
|
1292
|
+
* if (!url) return <Empty />;
|
|
1293
|
+
*
|
|
1294
|
+
* return (
|
|
1295
|
+
* <div>
|
|
1296
|
+
* <img src={url} alt={metadata?.name} />
|
|
1297
|
+
* <button onClick={deleteFile}>Delete</button>
|
|
1298
|
+
* </div>
|
|
1299
|
+
* );
|
|
1300
|
+
* }
|
|
1301
|
+
* ```
|
|
1302
|
+
*/
|
|
1303
|
+
declare function useFile(key: string | null, options?: UseFileOptions): UseFileReturn;
|
|
1304
|
+
/**
|
|
1305
|
+
* List files
|
|
1306
|
+
*
|
|
1307
|
+
* @example
|
|
1308
|
+
* ```tsx
|
|
1309
|
+
* function FileList({ folder }: { folder: string }) {
|
|
1310
|
+
* const { files, isLoading, refresh } = useFiles({
|
|
1311
|
+
* prefix: folder,
|
|
1312
|
+
* limit: 20,
|
|
1313
|
+
* });
|
|
1314
|
+
*
|
|
1315
|
+
* return (
|
|
1316
|
+
* <div>
|
|
1317
|
+
* {files.map(file => (
|
|
1318
|
+
* <FileItem key={file.key} file={file} />
|
|
1319
|
+
* ))}
|
|
1320
|
+
* </div>
|
|
1321
|
+
* );
|
|
1322
|
+
* }
|
|
1323
|
+
* ```
|
|
1324
|
+
*/
|
|
1325
|
+
declare function useFiles(options?: UseFilesOptions): UseFilesReturn;
|
|
1326
|
+
/**
|
|
1327
|
+
* Drag and drop file upload hook
|
|
1328
|
+
*
|
|
1329
|
+
* @example
|
|
1330
|
+
* ```tsx
|
|
1331
|
+
* function DropZone() {
|
|
1332
|
+
* const {
|
|
1333
|
+
* dropzone,
|
|
1334
|
+
* getDropzoneProps,
|
|
1335
|
+
* getInputProps,
|
|
1336
|
+
* isUploading,
|
|
1337
|
+
* progress,
|
|
1338
|
+
* results,
|
|
1339
|
+
* } = useDropzone({
|
|
1340
|
+
* basePath: 'uploads',
|
|
1341
|
+
* multiple: true,
|
|
1342
|
+
* maxSize: 10 * 1024 * 1024,
|
|
1343
|
+
* allowedTypes: ['image/*'],
|
|
1344
|
+
* });
|
|
1345
|
+
*
|
|
1346
|
+
* return (
|
|
1347
|
+
* <div
|
|
1348
|
+
* {...getDropzoneProps()}
|
|
1349
|
+
* className={`dropzone ${dropzone.isDragOver ? 'drag-over' : ''}`}
|
|
1350
|
+
* >
|
|
1351
|
+
* <input {...getInputProps()} />
|
|
1352
|
+
* {isUploading ? (
|
|
1353
|
+
* <progress value={progress} max={100} />
|
|
1354
|
+
* ) : (
|
|
1355
|
+
* <p>Drag files here or click to select</p>
|
|
1356
|
+
* )}
|
|
1357
|
+
* {results.length > 0 && (
|
|
1358
|
+
* <ul>
|
|
1359
|
+
* {results.map((r, i) => <li key={i}>{r.key}</li>)}
|
|
1360
|
+
* </ul>
|
|
1361
|
+
* )}
|
|
1362
|
+
* </div>
|
|
1363
|
+
* );
|
|
1364
|
+
* }
|
|
1365
|
+
* ```
|
|
1366
|
+
*/
|
|
1367
|
+
declare function useDropzone(options?: DropzoneOptions): UseDropzoneReturn;
|
|
1368
|
+
/**
|
|
1369
|
+
* Get a public URL for a file
|
|
1370
|
+
*
|
|
1371
|
+
* @example
|
|
1372
|
+
* ```tsx
|
|
1373
|
+
* function Avatar({ fileKey }: { fileKey: string }) {
|
|
1374
|
+
* const url = usePublicUrl(fileKey);
|
|
1375
|
+
* return <img src={url} alt="Avatar" />;
|
|
1376
|
+
* }
|
|
1377
|
+
* ```
|
|
1378
|
+
*/
|
|
1379
|
+
declare function usePublicUrl(key: string): string;
|
|
1380
|
+
|
|
1381
|
+
interface UseFunctionOptions<TInput, TOutput> {
|
|
1382
|
+
/** Callback on success */
|
|
1383
|
+
onSuccess?: (data: TOutput, input: TInput) => void;
|
|
1384
|
+
/** Callback on error */
|
|
1385
|
+
onError?: (error: Error, input: TInput) => void;
|
|
1386
|
+
/** Timeout in ms */
|
|
1387
|
+
timeout?: number;
|
|
1388
|
+
/** Include execution logs in response */
|
|
1389
|
+
includeLogs?: boolean;
|
|
1390
|
+
/** Enable retry on failure */
|
|
1391
|
+
retry?: boolean;
|
|
1392
|
+
/** Max retry attempts (default: 3) */
|
|
1393
|
+
maxRetries?: number;
|
|
1394
|
+
/** Specific version to invoke */
|
|
1395
|
+
version?: number;
|
|
1396
|
+
}
|
|
1397
|
+
interface UseFunctionReturn<TInput, TOutput> {
|
|
1398
|
+
/** Invoke the function */
|
|
1399
|
+
invoke: (input: TInput) => Promise<TOutput>;
|
|
1400
|
+
/** Result data */
|
|
1401
|
+
data: TOutput | null;
|
|
1402
|
+
/** Loading state */
|
|
1403
|
+
isLoading: boolean;
|
|
1404
|
+
/** Error state */
|
|
1405
|
+
error: Error | null;
|
|
1406
|
+
/** Whether invocation succeeded */
|
|
1407
|
+
isSuccess: boolean;
|
|
1408
|
+
/** Invocation metadata */
|
|
1409
|
+
invocation: InvokeResult<TOutput> | null;
|
|
1410
|
+
/** Reset state */
|
|
1411
|
+
reset: () => void;
|
|
1412
|
+
}
|
|
1413
|
+
interface UseRpcOptions<TInput, TOutput> extends UseFunctionOptions<TInput, TOutput> {
|
|
1414
|
+
/** Cache results */
|
|
1415
|
+
cache?: boolean;
|
|
1416
|
+
/** Cache TTL in ms */
|
|
1417
|
+
cacheTtl?: number;
|
|
1418
|
+
}
|
|
1419
|
+
interface UseRpcReturn<TInput, TOutput> extends UseFunctionReturn<TInput, TOutput> {
|
|
1420
|
+
/** Clear cache */
|
|
1421
|
+
clearCache: () => void;
|
|
1422
|
+
}
|
|
1423
|
+
interface UseFunctionListOptions {
|
|
1424
|
+
/** Project ID (required) */
|
|
1425
|
+
projectId: string;
|
|
1426
|
+
/** Environment ID */
|
|
1427
|
+
envId?: string;
|
|
1428
|
+
/** Filter by enabled status */
|
|
1429
|
+
enabled?: boolean;
|
|
1430
|
+
/** Limit results */
|
|
1431
|
+
limit?: number;
|
|
1432
|
+
/** Offset for pagination */
|
|
1433
|
+
offset?: number;
|
|
1434
|
+
}
|
|
1435
|
+
interface UseFunctionListReturn {
|
|
1436
|
+
/** List of functions */
|
|
1437
|
+
functions: VaifFunction[];
|
|
1438
|
+
/** Loading state */
|
|
1439
|
+
isLoading: boolean;
|
|
1440
|
+
/** Error state */
|
|
1441
|
+
error: Error | null;
|
|
1442
|
+
/** Refresh list */
|
|
1443
|
+
refresh: () => Promise<void>;
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Invoke a serverless function
|
|
1447
|
+
*
|
|
1448
|
+
* @example
|
|
1449
|
+
* ```tsx
|
|
1450
|
+
* function SendEmailButton({ to, subject, body }: EmailProps) {
|
|
1451
|
+
* const { invoke, isLoading, error } = useFunction<EmailInput, EmailResult>(
|
|
1452
|
+
* 'send-email',
|
|
1453
|
+
* {
|
|
1454
|
+
* onSuccess: (result) => {
|
|
1455
|
+
* toast.success(`Email sent! ID: ${result.messageId}`);
|
|
1456
|
+
* },
|
|
1457
|
+
* onError: (error) => {
|
|
1458
|
+
* toast.error(`Failed: ${error.message}`);
|
|
1459
|
+
* },
|
|
1460
|
+
* }
|
|
1461
|
+
* );
|
|
1462
|
+
*
|
|
1463
|
+
* return (
|
|
1464
|
+
* <button
|
|
1465
|
+
* onClick={() => invoke({ to, subject, body })}
|
|
1466
|
+
* disabled={isLoading}
|
|
1467
|
+
* >
|
|
1468
|
+
* {isLoading ? 'Sending...' : 'Send Email'}
|
|
1469
|
+
* </button>
|
|
1470
|
+
* );
|
|
1471
|
+
* }
|
|
1472
|
+
* ```
|
|
1473
|
+
*/
|
|
1474
|
+
declare function useFunction<TInput = unknown, TOutput = unknown>(functionIdOrName: string, options?: UseFunctionOptions<TInput, TOutput>): UseFunctionReturn<TInput, TOutput>;
|
|
1475
|
+
/**
|
|
1476
|
+
* RPC-style function hook with caching
|
|
1477
|
+
*
|
|
1478
|
+
* @example
|
|
1479
|
+
* ```tsx
|
|
1480
|
+
* // Define typed RPC
|
|
1481
|
+
* type GetUserInput = { userId: string };
|
|
1482
|
+
* type GetUserOutput = { name: string; email: string };
|
|
1483
|
+
*
|
|
1484
|
+
* function UserProfile({ userId }: { userId: string }) {
|
|
1485
|
+
* const { invoke, data, isLoading, error } = useRpc<GetUserInput, GetUserOutput>(
|
|
1486
|
+
* 'get-user',
|
|
1487
|
+
* {
|
|
1488
|
+
* cache: true,
|
|
1489
|
+
* cacheTtl: 60000, // 1 minute
|
|
1490
|
+
* }
|
|
1491
|
+
* );
|
|
1492
|
+
*
|
|
1493
|
+
* useEffect(() => {
|
|
1494
|
+
* invoke({ userId });
|
|
1495
|
+
* }, [userId, invoke]);
|
|
1496
|
+
*
|
|
1497
|
+
* if (isLoading) return <Spinner />;
|
|
1498
|
+
* if (error) return <Error message={error.message} />;
|
|
1499
|
+
* if (!data) return null;
|
|
1500
|
+
*
|
|
1501
|
+
* return <div>{data.name} ({data.email})</div>;
|
|
1502
|
+
* }
|
|
1503
|
+
* ```
|
|
1504
|
+
*/
|
|
1505
|
+
declare function useRpc<TInput = unknown, TOutput = unknown>(functionIdOrName: string, options?: UseRpcOptions<TInput, TOutput>): UseRpcReturn<TInput, TOutput>;
|
|
1506
|
+
/**
|
|
1507
|
+
* List available functions
|
|
1508
|
+
*
|
|
1509
|
+
* @example
|
|
1510
|
+
* ```tsx
|
|
1511
|
+
* function FunctionList({ projectId }: { projectId: string }) {
|
|
1512
|
+
* const { functions, isLoading, refresh } = useFunctionList({
|
|
1513
|
+
* projectId,
|
|
1514
|
+
* enabled: true,
|
|
1515
|
+
* });
|
|
1516
|
+
*
|
|
1517
|
+
* return (
|
|
1518
|
+
* <ul>
|
|
1519
|
+
* {functions.map(fn => (
|
|
1520
|
+
* <li key={fn.id}>
|
|
1521
|
+
* {fn.name} - {fn.runtime}
|
|
1522
|
+
* </li>
|
|
1523
|
+
* ))}
|
|
1524
|
+
* </ul>
|
|
1525
|
+
* );
|
|
1526
|
+
* }
|
|
1527
|
+
* ```
|
|
1528
|
+
*/
|
|
1529
|
+
declare function useFunctionList(options: UseFunctionListOptions): UseFunctionListReturn;
|
|
1530
|
+
/**
|
|
1531
|
+
* Batch invoke multiple functions
|
|
1532
|
+
*
|
|
1533
|
+
* @example
|
|
1534
|
+
* ```tsx
|
|
1535
|
+
* function ProcessItems({ items }: { items: Item[] }) {
|
|
1536
|
+
* const { invoke, results, isLoading, progress } = useBatchInvoke<ProcessInput, ProcessOutput>(
|
|
1537
|
+
* 'process-item',
|
|
1538
|
+
* {
|
|
1539
|
+
* concurrency: 5,
|
|
1540
|
+
* onProgress: (completed, total) => {
|
|
1541
|
+
* console.log(`${completed}/${total} processed`);
|
|
1542
|
+
* },
|
|
1543
|
+
* }
|
|
1544
|
+
* );
|
|
1545
|
+
*
|
|
1546
|
+
* return (
|
|
1547
|
+
* <button onClick={() => invoke(items.map(i => ({ itemId: i.id })))}>
|
|
1548
|
+
* {isLoading ? `Processing... ${progress}%` : 'Process All'}
|
|
1549
|
+
* </button>
|
|
1550
|
+
* );
|
|
1551
|
+
* }
|
|
1552
|
+
* ```
|
|
1553
|
+
*/
|
|
1554
|
+
declare function useBatchInvoke<TInput = unknown, TOutput = unknown>(functionIdOrName: string, options?: {
|
|
1555
|
+
concurrency?: number;
|
|
1556
|
+
onProgress?: (completed: number, total: number) => void;
|
|
1557
|
+
onSuccess?: (results: TOutput[]) => void;
|
|
1558
|
+
onError?: (error: Error) => void;
|
|
1559
|
+
}): {
|
|
1560
|
+
invoke: (inputs: TInput[]) => Promise<TOutput[]>;
|
|
1561
|
+
results: TOutput[];
|
|
1562
|
+
isLoading: boolean;
|
|
1563
|
+
progress: number;
|
|
1564
|
+
error: Error | null;
|
|
1565
|
+
};
|
|
1566
|
+
/**
|
|
1567
|
+
* Scheduled/deferred function invocation
|
|
1568
|
+
*
|
|
1569
|
+
* @example
|
|
1570
|
+
* ```tsx
|
|
1571
|
+
* function ScheduleReminder() {
|
|
1572
|
+
* const { schedule, cancel, isScheduled, scheduledAt } = useScheduledFunction<ReminderInput>(
|
|
1573
|
+
* 'send-reminder'
|
|
1574
|
+
* );
|
|
1575
|
+
*
|
|
1576
|
+
* const handleSchedule = () => {
|
|
1577
|
+
* schedule(
|
|
1578
|
+
* { userId: user.id, message: 'Follow up' },
|
|
1579
|
+
* { delayMs: 24 * 60 * 60 * 1000 } // 24 hours
|
|
1580
|
+
* );
|
|
1581
|
+
* };
|
|
1582
|
+
*
|
|
1583
|
+
* return (
|
|
1584
|
+
* <div>
|
|
1585
|
+
* {isScheduled ? (
|
|
1586
|
+
* <>
|
|
1587
|
+
* <span>Scheduled for {scheduledAt?.toLocaleString()}</span>
|
|
1588
|
+
* <button onClick={cancel}>Cancel</button>
|
|
1589
|
+
* </>
|
|
1590
|
+
* ) : (
|
|
1591
|
+
* <button onClick={handleSchedule}>Schedule Reminder</button>
|
|
1592
|
+
* )}
|
|
1593
|
+
* </div>
|
|
1594
|
+
* );
|
|
1595
|
+
* }
|
|
1596
|
+
* ```
|
|
1597
|
+
*/
|
|
1598
|
+
declare function useScheduledFunction<TInput = unknown>(functionIdOrName: string): {
|
|
1599
|
+
schedule: (input: TInput, options: {
|
|
1600
|
+
delayMs: number;
|
|
1601
|
+
}) => Promise<string>;
|
|
1602
|
+
cancel: () => Promise<void>;
|
|
1603
|
+
isScheduled: boolean;
|
|
1604
|
+
scheduledAt: Date | null;
|
|
1605
|
+
requestId: string | null;
|
|
1606
|
+
error: Error | null;
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
export { type DropzoneOptions, type DropzoneState, type MutationStatus, type QueryStatus, type UseAuthReturn, type UseChannelOptions, type UseChannelReturn, type UseCreateOptions, type UseDeleteOptions, type UseDownloadOptions, type UseDownloadReturn, type UseDropzoneReturn, type UseEmailVerificationReturn, type UseFileOptions, type UseFileReturn, type UseFilesOptions, type UseFilesReturn, type UseFunctionListOptions, type UseFunctionListReturn, type UseFunctionOptions, type UseFunctionReturn, type UseInfiniteQueryOptions, type UseInfiniteQueryReturn, type UseMFAReturn, type UseMagicLinkReturn, type UseMutationOptions, type UseMutationReturn, type UseOAuthReturn, type UsePaginatedQueryOptions, type UsePaginatedQueryReturn, type UsePasswordResetReturn, type UsePresenceOptions, type UsePresenceReturn, type UseQueryFirstReturn, type UseQueryOptions, type UseQueryReturn, type UseRealtimeOptions, type UseRpcOptions, type UseRpcReturn, type UseSubscriptionOptions, type UseSubscriptionReturn, type UseUpdateOptions, type UseUploadOptions, type UseUploadReturn, type UseUpsertOptions, type VaifContextValue, VaifProvider, type VaifProviderProps, useAuth, useBatchCreate, useBatchDelete, useBatchInvoke, useBatchUpdate, useBroadcast, useChannel, useCount, useCreate, useDelete, useDownload, useDropzone, useEmailVerification, useFile, useFiles, useFunction, useFunctionList, useInfiniteQuery, useMFA, useMagicLink, useMutation, useOAuth, useOptimisticMutation, usePaginatedQuery, usePasswordReset, usePresence, usePublicUrl, useQuery, useQueryById, useQueryFirst, useRealtimeConnection, useRpc, useScheduledFunction, useSubscription, useToken, useUpdate, useUpload, useUpsert, useUser, useVaif, useVaifClient };
|