@fluxbase/sdk-react 0.1.0-rc.1 → 2026.1.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,256 +2,499 @@
2
2
  * Storage hooks for Fluxbase SDK
3
3
  */
4
4
 
5
- import { useMutation, useQuery, useQueryClient, type UseQueryOptions } from '@tanstack/react-query'
6
- import { useFluxbaseClient } from './context'
7
- import type { ListOptions, UploadOptions } from '@fluxbase/sdk'
5
+ import { useState } from "react";
6
+ import {
7
+ useMutation,
8
+ useQuery,
9
+ useQueryClient,
10
+ type UseQueryOptions,
11
+ } from "@tanstack/react-query";
12
+ import { useFluxbaseClient } from "./context";
13
+ import type {
14
+ ListOptions,
15
+ UploadOptions,
16
+ UploadProgress,
17
+ TransformOptions,
18
+ SignedUrlOptions,
19
+ } from "@fluxbase/sdk";
8
20
 
9
21
  /**
10
22
  * Hook to list files in a bucket
11
23
  */
12
24
  export function useStorageList(
13
25
  bucket: string,
14
- options?: ListOptions & Omit<UseQueryOptions<any[], Error>, 'queryKey' | 'queryFn'>
26
+ options?: ListOptions &
27
+ Omit<UseQueryOptions<any[], Error>, "queryKey" | "queryFn">,
15
28
  ) {
16
- const client = useFluxbaseClient()
17
- const { prefix, limit, offset, ...queryOptions } = options || {}
29
+ const client = useFluxbaseClient();
30
+ const { prefix, limit, offset, ...queryOptions } = options || {};
18
31
 
19
32
  return useQuery({
20
- queryKey: ['fluxbase', 'storage', bucket, 'list', { prefix, limit, offset }],
33
+ queryKey: [
34
+ "fluxbase",
35
+ "storage",
36
+ bucket,
37
+ "list",
38
+ { prefix, limit, offset },
39
+ ],
21
40
  queryFn: async () => {
22
- const { data, error } = await client.storage.from(bucket).list({ prefix, limit, offset })
41
+ const { data, error } = await client.storage
42
+ .from(bucket)
43
+ .list({ prefix, limit, offset });
23
44
 
24
45
  if (error) {
25
- throw error
46
+ throw error;
26
47
  }
27
48
 
28
- return data || []
49
+ return data || [];
29
50
  },
30
51
  ...queryOptions,
31
- })
52
+ });
32
53
  }
33
54
 
34
55
  /**
35
56
  * Hook to upload a file to a bucket
57
+ *
58
+ * Note: You can track upload progress by passing an `onUploadProgress` callback in the options:
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * const upload = useStorageUpload('avatars')
63
+ *
64
+ * upload.mutate({
65
+ * path: 'user.jpg',
66
+ * file: file,
67
+ * options: {
68
+ * onUploadProgress: (progress) => {
69
+ * console.log(`${progress.percentage}% uploaded`)
70
+ * }
71
+ * }
72
+ * })
73
+ * ```
74
+ *
75
+ * For automatic progress state management, use `useStorageUploadWithProgress` instead.
36
76
  */
37
77
  export function useStorageUpload(bucket: string) {
38
- const client = useFluxbaseClient()
39
- const queryClient = useQueryClient()
78
+ const client = useFluxbaseClient();
79
+ const queryClient = useQueryClient();
40
80
 
41
81
  return useMutation({
42
82
  mutationFn: async (params: {
43
- path: string
44
- file: File | Blob | ArrayBuffer
45
- options?: UploadOptions
83
+ path: string;
84
+ file: File | Blob | ArrayBuffer;
85
+ options?: UploadOptions;
46
86
  }) => {
47
- const { path, file, options } = params
48
- const { data, error } = await client.storage.from(bucket).upload(path, file, options)
87
+ const { path, file, options } = params;
88
+ const { data, error } = await client.storage
89
+ .from(bucket)
90
+ .upload(path, file, options);
49
91
 
50
92
  if (error) {
51
- throw error
93
+ throw error;
52
94
  }
53
95
 
54
- return data
96
+ return data;
55
97
  },
56
98
  onSuccess: () => {
57
99
  // Invalidate list queries for this bucket
58
- queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })
100
+ queryClient.invalidateQueries({
101
+ queryKey: ["fluxbase", "storage", bucket, "list"],
102
+ });
59
103
  },
60
- })
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Hook to upload a file to a bucket with built-in progress tracking
109
+ *
110
+ * @example
111
+ * ```tsx
112
+ * const { upload, progress, reset } = useStorageUploadWithProgress('avatars')
113
+ *
114
+ * // Upload with automatic progress tracking
115
+ * upload.mutate({
116
+ * path: 'user.jpg',
117
+ * file: file
118
+ * })
119
+ *
120
+ * // Display progress
121
+ * console.log(progress) // { loaded: 1024, total: 2048, percentage: 50 }
122
+ * ```
123
+ */
124
+ export function useStorageUploadWithProgress(bucket: string) {
125
+ const client = useFluxbaseClient();
126
+ const queryClient = useQueryClient();
127
+ const [progress, setProgress] = useState<UploadProgress | null>(null);
128
+
129
+ const mutation = useMutation({
130
+ mutationFn: async (params: {
131
+ path: string;
132
+ file: File | Blob | ArrayBuffer;
133
+ options?: Omit<UploadOptions, "onUploadProgress">;
134
+ }) => {
135
+ const { path, file, options } = params;
136
+
137
+ // Reset progress at the start of upload
138
+ setProgress({ loaded: 0, total: 0, percentage: 0 });
139
+
140
+ const { data, error } = await client.storage
141
+ .from(bucket)
142
+ .upload(path, file, {
143
+ ...options,
144
+ onUploadProgress: (p: import("@fluxbase/sdk").UploadProgress) => {
145
+ setProgress(p);
146
+ },
147
+ });
148
+
149
+ if (error) {
150
+ throw error;
151
+ }
152
+
153
+ return data;
154
+ },
155
+ onSuccess: () => {
156
+ // Invalidate list queries for this bucket
157
+ queryClient.invalidateQueries({
158
+ queryKey: ["fluxbase", "storage", bucket, "list"],
159
+ });
160
+ },
161
+ onError: () => {
162
+ // Reset progress on error
163
+ setProgress(null);
164
+ },
165
+ });
166
+
167
+ return {
168
+ upload: mutation,
169
+ progress,
170
+ reset: () => setProgress(null),
171
+ };
61
172
  }
62
173
 
63
174
  /**
64
175
  * Hook to download a file from a bucket
65
176
  */
66
- export function useStorageDownload(bucket: string, path: string | null, enabled = true) {
67
- const client = useFluxbaseClient()
177
+ export function useStorageDownload(
178
+ bucket: string,
179
+ path: string | null,
180
+ enabled = true,
181
+ ) {
182
+ const client = useFluxbaseClient();
68
183
 
69
184
  return useQuery({
70
- queryKey: ['fluxbase', 'storage', bucket, 'download', path],
185
+ queryKey: ["fluxbase", "storage", bucket, "download", path],
71
186
  queryFn: async () => {
72
187
  if (!path) {
73
- return null
188
+ return null;
74
189
  }
75
190
 
76
- const { data, error } = await client.storage.from(bucket).download(path)
191
+ const { data, error } = await client.storage.from(bucket).download(path);
77
192
 
78
193
  if (error) {
79
- throw error
194
+ throw error;
80
195
  }
81
196
 
82
- return data
197
+ return data;
83
198
  },
84
199
  enabled: enabled && !!path,
85
- })
200
+ });
86
201
  }
87
202
 
88
203
  /**
89
204
  * Hook to delete files from a bucket
90
205
  */
91
206
  export function useStorageDelete(bucket: string) {
92
- const client = useFluxbaseClient()
93
- const queryClient = useQueryClient()
207
+ const client = useFluxbaseClient();
208
+ const queryClient = useQueryClient();
94
209
 
95
210
  return useMutation({
96
211
  mutationFn: async (paths: string[]) => {
97
- const { error } = await client.storage.from(bucket).remove(paths)
212
+ const { error } = await client.storage.from(bucket).remove(paths);
98
213
 
99
214
  if (error) {
100
- throw error
215
+ throw error;
101
216
  }
102
217
  },
103
218
  onSuccess: () => {
104
- queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })
219
+ queryClient.invalidateQueries({
220
+ queryKey: ["fluxbase", "storage", bucket, "list"],
221
+ });
105
222
  },
106
- })
223
+ });
107
224
  }
108
225
 
109
226
  /**
110
227
  * Hook to get a public URL for a file
111
228
  */
112
229
  export function useStoragePublicUrl(bucket: string, path: string | null) {
113
- const client = useFluxbaseClient()
230
+ const client = useFluxbaseClient();
231
+
232
+ if (!path) {
233
+ return null;
234
+ }
235
+
236
+ const { data } = client.storage.from(bucket).getPublicUrl(path);
237
+ return data.publicUrl;
238
+ }
239
+
240
+ /**
241
+ * Hook to get a public URL for an image with transformations applied
242
+ *
243
+ * Only works for image files (JPEG, PNG, WebP, GIF, AVIF, etc.)
244
+ *
245
+ * @param bucket - The storage bucket name
246
+ * @param path - The file path (or null to disable)
247
+ * @param transform - Transformation options (width, height, format, quality, fit)
248
+ *
249
+ * @example
250
+ * ```tsx
251
+ * function ImageThumbnail({ path }: { path: string }) {
252
+ * const url = useStorageTransformUrl('images', path, {
253
+ * width: 300,
254
+ * height: 200,
255
+ * format: 'webp',
256
+ * quality: 85,
257
+ * fit: 'cover'
258
+ * });
259
+ *
260
+ * return <img src={url || ''} alt="Thumbnail" />;
261
+ * }
262
+ * ```
263
+ */
264
+ export function useStorageTransformUrl(
265
+ bucket: string,
266
+ path: string | null,
267
+ transform: TransformOptions,
268
+ ): string | null {
269
+ const client = useFluxbaseClient();
114
270
 
115
271
  if (!path) {
116
- return null
272
+ return null;
117
273
  }
118
274
 
119
- const { data } = client.storage.from(bucket).getPublicUrl(path)
120
- return data.publicUrl
275
+ return client.storage.from(bucket).getTransformUrl(path, transform);
121
276
  }
122
277
 
123
278
  /**
124
279
  * Hook to create a signed URL
280
+ *
281
+ * @deprecated Use useStorageSignedUrlWithOptions for more control including transforms
282
+ */
283
+ export function useStorageSignedUrl(
284
+ bucket: string,
285
+ path: string | null,
286
+ expiresIn?: number,
287
+ ) {
288
+ const client = useFluxbaseClient();
289
+
290
+ return useQuery({
291
+ queryKey: ["fluxbase", "storage", bucket, "signed-url", path, expiresIn],
292
+ queryFn: async () => {
293
+ if (!path) {
294
+ return null;
295
+ }
296
+
297
+ const { data, error } = await client.storage
298
+ .from(bucket)
299
+ .createSignedUrl(path, { expiresIn });
300
+
301
+ if (error) {
302
+ throw error;
303
+ }
304
+
305
+ return data?.signedUrl || null;
306
+ },
307
+ enabled: !!path,
308
+ staleTime: expiresIn ? expiresIn * 1000 - 60000 : 1000 * 60 * 50, // Refresh 1 minute before expiry
309
+ });
310
+ }
311
+
312
+ /**
313
+ * Hook to create a signed URL with full options including image transformations
314
+ *
315
+ * @param bucket - The storage bucket name
316
+ * @param path - The file path (or null to disable)
317
+ * @param options - Signed URL options including expiration and transforms
318
+ *
319
+ * @example
320
+ * ```tsx
321
+ * function SecureThumbnail({ path }: { path: string }) {
322
+ * const { data: url } = useStorageSignedUrlWithOptions('images', path, {
323
+ * expiresIn: 3600,
324
+ * transform: {
325
+ * width: 400,
326
+ * height: 300,
327
+ * format: 'webp',
328
+ * quality: 85,
329
+ * fit: 'cover'
330
+ * }
331
+ * });
332
+ *
333
+ * return <img src={url || ''} alt="Secure Thumbnail" />;
334
+ * }
335
+ * ```
125
336
  */
126
- export function useStorageSignedUrl(bucket: string, path: string | null, expiresIn?: number) {
127
- const client = useFluxbaseClient()
337
+ export function useStorageSignedUrlWithOptions(
338
+ bucket: string,
339
+ path: string | null,
340
+ options?: SignedUrlOptions,
341
+ ) {
342
+ const client = useFluxbaseClient();
343
+ const expiresIn = options?.expiresIn;
344
+
345
+ // Create a stable cache key from transform options
346
+ const transformKey = options?.transform
347
+ ? JSON.stringify(options.transform)
348
+ : null;
128
349
 
129
350
  return useQuery({
130
- queryKey: ['fluxbase', 'storage', bucket, 'signed-url', path, expiresIn],
351
+ queryKey: [
352
+ "fluxbase",
353
+ "storage",
354
+ bucket,
355
+ "signed-url",
356
+ path,
357
+ expiresIn,
358
+ transformKey,
359
+ ],
131
360
  queryFn: async () => {
132
361
  if (!path) {
133
- return null
362
+ return null;
134
363
  }
135
364
 
136
- const { data, error } = await client.storage.from(bucket).createSignedUrl(path, { expiresIn })
365
+ const { data, error } = await client.storage
366
+ .from(bucket)
367
+ .createSignedUrl(path, options);
137
368
 
138
369
  if (error) {
139
- throw error
370
+ throw error;
140
371
  }
141
372
 
142
- return data?.signedUrl || null
373
+ return data?.signedUrl || null;
143
374
  },
144
375
  enabled: !!path,
145
376
  staleTime: expiresIn ? expiresIn * 1000 - 60000 : 1000 * 60 * 50, // Refresh 1 minute before expiry
146
- })
377
+ });
147
378
  }
148
379
 
149
380
  /**
150
381
  * Hook to move a file
151
382
  */
152
383
  export function useStorageMove(bucket: string) {
153
- const client = useFluxbaseClient()
154
- const queryClient = useQueryClient()
384
+ const client = useFluxbaseClient();
385
+ const queryClient = useQueryClient();
155
386
 
156
387
  return useMutation({
157
388
  mutationFn: async (params: { fromPath: string; toPath: string }) => {
158
- const { fromPath, toPath } = params
159
- const { data, error } = await client.storage.from(bucket).move(fromPath, toPath)
389
+ const { fromPath, toPath } = params;
390
+ const { data, error } = await client.storage
391
+ .from(bucket)
392
+ .move(fromPath, toPath);
160
393
 
161
394
  if (error) {
162
- throw error
395
+ throw error;
163
396
  }
164
397
 
165
- return data
398
+ return data;
166
399
  },
167
400
  onSuccess: () => {
168
- queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })
401
+ queryClient.invalidateQueries({
402
+ queryKey: ["fluxbase", "storage", bucket, "list"],
403
+ });
169
404
  },
170
- })
405
+ });
171
406
  }
172
407
 
173
408
  /**
174
409
  * Hook to copy a file
175
410
  */
176
411
  export function useStorageCopy(bucket: string) {
177
- const client = useFluxbaseClient()
178
- const queryClient = useQueryClient()
412
+ const client = useFluxbaseClient();
413
+ const queryClient = useQueryClient();
179
414
 
180
415
  return useMutation({
181
416
  mutationFn: async (params: { fromPath: string; toPath: string }) => {
182
- const { fromPath, toPath } = params
183
- const { data, error } = await client.storage.from(bucket).copy(fromPath, toPath)
417
+ const { fromPath, toPath } = params;
418
+ const { data, error } = await client.storage
419
+ .from(bucket)
420
+ .copy(fromPath, toPath);
184
421
 
185
422
  if (error) {
186
- throw error
423
+ throw error;
187
424
  }
188
425
 
189
- return data
426
+ return data;
190
427
  },
191
428
  onSuccess: () => {
192
- queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', bucket, 'list'] })
429
+ queryClient.invalidateQueries({
430
+ queryKey: ["fluxbase", "storage", bucket, "list"],
431
+ });
193
432
  },
194
- })
433
+ });
195
434
  }
196
435
 
197
436
  /**
198
437
  * Hook to manage buckets
199
438
  */
200
439
  export function useStorageBuckets() {
201
- const client = useFluxbaseClient()
440
+ const client = useFluxbaseClient();
202
441
 
203
442
  return useQuery({
204
- queryKey: ['fluxbase', 'storage', 'buckets'],
443
+ queryKey: ["fluxbase", "storage", "buckets"],
205
444
  queryFn: async () => {
206
- const { data, error } = await client.storage.listBuckets()
445
+ const { data, error } = await client.storage.listBuckets();
207
446
 
208
447
  if (error) {
209
- throw error
448
+ throw error;
210
449
  }
211
450
 
212
- return data || []
451
+ return data || [];
213
452
  },
214
- })
453
+ });
215
454
  }
216
455
 
217
456
  /**
218
457
  * Hook to create a bucket
219
458
  */
220
459
  export function useCreateBucket() {
221
- const client = useFluxbaseClient()
222
- const queryClient = useQueryClient()
460
+ const client = useFluxbaseClient();
461
+ const queryClient = useQueryClient();
223
462
 
224
463
  return useMutation({
225
464
  mutationFn: async (bucketName: string) => {
226
- const { error } = await client.storage.createBucket(bucketName)
465
+ const { error } = await client.storage.createBucket(bucketName);
227
466
 
228
467
  if (error) {
229
- throw error
468
+ throw error;
230
469
  }
231
470
  },
232
471
  onSuccess: () => {
233
- queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', 'buckets'] })
472
+ queryClient.invalidateQueries({
473
+ queryKey: ["fluxbase", "storage", "buckets"],
474
+ });
234
475
  },
235
- })
476
+ });
236
477
  }
237
478
 
238
479
  /**
239
480
  * Hook to delete a bucket
240
481
  */
241
482
  export function useDeleteBucket() {
242
- const client = useFluxbaseClient()
243
- const queryClient = useQueryClient()
483
+ const client = useFluxbaseClient();
484
+ const queryClient = useQueryClient();
244
485
 
245
486
  return useMutation({
246
487
  mutationFn: async (bucketName: string) => {
247
- const { error } = await client.storage.deleteBucket(bucketName)
488
+ const { error } = await client.storage.deleteBucket(bucketName);
248
489
 
249
490
  if (error) {
250
- throw error
491
+ throw error;
251
492
  }
252
493
  },
253
494
  onSuccess: () => {
254
- queryClient.invalidateQueries({ queryKey: ['fluxbase', 'storage', 'buckets'] })
495
+ queryClient.invalidateQueries({
496
+ queryKey: ["fluxbase", "storage", "buckets"],
497
+ });
255
498
  },
256
- })
499
+ });
257
500
  }
package/src/use-users.ts CHANGED
@@ -109,9 +109,12 @@ export function useUsers(options: UseUsersOptions = {}): UseUsersReturn {
109
109
  try {
110
110
  setIsLoading(true)
111
111
  setError(null)
112
- const response = await client.admin.listUsers(listOptions)
113
- setUsers(response.users)
114
- setTotal(response.total)
112
+ const { data, error: apiError } = await client.admin.listUsers(listOptions)
113
+ if (apiError) {
114
+ throw apiError
115
+ }
116
+ setUsers(data!.users)
117
+ setTotal(data!.total)
115
118
  } catch (err) {
116
119
  setError(err as Error)
117
120
  } finally {
@@ -157,7 +160,11 @@ export function useUsers(options: UseUsersOptions = {}): UseUsersReturn {
157
160
  */
158
161
  const resetPassword = useCallback(
159
162
  async (userId: string): Promise<{ message: string }> => {
160
- return await client.admin.resetUserPassword(userId)
163
+ const { data, error } = await client.admin.resetUserPassword(userId)
164
+ if (error) {
165
+ throw error
166
+ }
167
+ return data!
161
168
  },
162
169
  [client]
163
170
  )
@@ -1 +1 @@
1
- {"root":["./src/context.tsx","./src/index.ts","./src/use-admin-auth.ts","./src/use-admin-hooks.ts","./src/use-api-keys.ts","./src/use-auth.ts","./src/use-query.ts","./src/use-realtime.ts","./src/use-rpc.ts","./src/use-storage.ts","./src/use-users.ts"],"version":"5.9.3"}
1
+ {"root":["./src/context.tsx","./src/index.ts","./src/use-admin-auth.ts","./src/use-admin-hooks.ts","./src/use-client-keys.ts","./src/use-auth.ts","./src/use-query.ts","./src/use-realtime.ts","./src/use-rpc.ts","./src/use-storage.ts","./src/use-users.ts"],"version":"5.9.3"}
package/typedoc.json CHANGED
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "$schema": "https://typedoc.org/schema.json",
3
- "entryPoints": [
4
- "src/index.ts"
5
- ],
3
+ "entryPoints": ["src/index.ts"],
6
4
  "out": "../docs/static/api/sdk-react",
7
5
  "name": "@fluxbase/sdk-react",
8
6
  "readme": "README.md",
@@ -29,7 +27,7 @@
29
27
  "*"
30
28
  ],
31
29
  "navigationLinks": {
32
- "GitHub": "https://github.com/yourusername/fluxbase",
30
+ "GitHub": "https://github.com/fluxbase-eu/fluxbase",
33
31
  "Documentation": "https://fluxbase.eu"
34
32
  }
35
33
  }
package/CHANGELOG.md DELETED
@@ -1,67 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to @fluxbase/sdk-react will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [0.1.0] - 2025-10-26
9
-
10
- ### Added
11
-
12
- - Initial release of Fluxbase React hooks library
13
- - **Authentication Hooks**
14
- - `useAuth`: Complete auth state and methods
15
- - `useUser`: Current user data with auto-refetch
16
- - `useSession`: Access current session
17
- - `useSignIn`: Sign in mutation hook
18
- - `useSignUp`: Sign up mutation hook
19
- - `useSignOut`: Sign out mutation hook
20
- - `useUpdateUser`: Update user profile hook
21
- - **Database Query Hooks**
22
- - `useTable`: Query table data with filters, ordering, pagination
23
- - `useFluxbaseQuery`: Lower-level query hook for custom queries
24
- - `useInsert`: Insert rows with automatic cache invalidation
25
- - `useUpdate`: Update rows
26
- - `useUpsert`: Insert or update (upsert)
27
- - `useDelete`: Delete rows
28
- - **Realtime Hooks**
29
- - `useRealtime`: Subscribe to database changes
30
- - `useTableSubscription`: Auto-refetch queries on table changes
31
- - `useTableInserts`: Listen to INSERT events
32
- - `useTableUpdates`: Listen to UPDATE events
33
- - `useTableDeletes`: Listen to DELETE events
34
- - **Storage Hooks**
35
- - `useStorageList`: List files in bucket
36
- - `useStorageUpload`: Upload files
37
- - `useStorageDownload`: Download files
38
- - `useStorageDelete`: Delete files
39
- - `useStoragePublicUrl`: Get public URL
40
- - `useStorageSignedUrl`: Generate signed URLs
41
- - `useStorageMove`: Move files
42
- - `useStorageCopy`: Copy files
43
- - `useStorageBuckets`: List buckets
44
- - `useCreateBucket`: Create bucket
45
- - `useDeleteBucket`: Delete bucket
46
- - **Context & Provider**
47
- - `FluxbaseProvider`: Provide Fluxbase client to React tree
48
- - `useFluxbaseClient`: Access Fluxbase client from context
49
- - **TypeScript Support**
50
- - Full type safety for all hooks
51
- - Generic type parameters for table data
52
- - Type inference from query builders
53
- - **Built on TanStack Query**
54
- - Automatic caching and refetching
55
- - Optimistic updates support
56
- - Request deduplication
57
- - Background refetching
58
- - Stale-while-revalidate pattern
59
- - SSR support
60
-
61
- ### Peer Dependencies
62
-
63
- - `@fluxbase/sdk`: ^0.1.0
64
- - `@tanstack/react-query`: ^5.0.0
65
- - `react`: ^18.0.0 || ^19.0.0
66
-
67
- [0.1.0]: https://github.com/wayli-app/fluxbase/releases/tag/sdk-react-v0.1.0