@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.
- package/README-ADMIN.md +8 -8
- package/README.md +27 -14
- package/dist/index.d.mts +638 -83
- package/dist/index.d.ts +638 -83
- package/dist/index.js +552 -175
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +540 -172
- package/dist/index.mjs.map +1 -1
- package/examples/AdminDashboard.tsx +3 -3
- package/examples/README.md +1 -1
- package/package.json +3 -3
- package/src/index.ts +57 -18
- package/src/use-admin-auth.ts +62 -51
- package/src/use-auth.ts +66 -49
- package/src/use-captcha.ts +250 -0
- package/src/{use-api-keys.ts → use-client-keys.ts} +43 -32
- package/src/use-graphql.ts +392 -0
- package/src/use-realtime.ts +58 -44
- package/src/use-saml.ts +221 -0
- package/src/use-storage.ts +325 -82
- package/src/use-users.ts +11 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/typedoc.json +2 -4
- package/CHANGELOG.md +0 -67
- package/src/use-rpc.ts +0 -109
package/src/use-realtime.ts
CHANGED
|
@@ -2,99 +2,113 @@
|
|
|
2
2
|
* Realtime subscription hooks for Fluxbase SDK
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { useEffect, useRef } from
|
|
6
|
-
import { useQueryClient } from
|
|
7
|
-
import { useFluxbaseClient } from
|
|
8
|
-
import type {
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
7
|
+
import { useFluxbaseClient } from "./context";
|
|
8
|
+
import type {
|
|
9
|
+
RealtimeCallback,
|
|
10
|
+
RealtimePostgresChangesPayload,
|
|
11
|
+
} from "@fluxbase/sdk";
|
|
9
12
|
|
|
10
13
|
export interface UseRealtimeOptions {
|
|
11
14
|
/**
|
|
12
15
|
* The channel name (e.g., 'table:public.products')
|
|
13
16
|
*/
|
|
14
|
-
channel: string
|
|
17
|
+
channel: string;
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* Event type to listen for ('INSERT', 'UPDATE', 'DELETE', or '*' for all)
|
|
18
21
|
*/
|
|
19
|
-
event?:
|
|
22
|
+
event?: "INSERT" | "UPDATE" | "DELETE" | "*";
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Callback function when an event is received
|
|
23
26
|
*/
|
|
24
|
-
callback?: RealtimeCallback
|
|
27
|
+
callback?: RealtimeCallback;
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* Whether to automatically invalidate queries for the table
|
|
28
31
|
* Default: true
|
|
29
32
|
*/
|
|
30
|
-
autoInvalidate?: boolean
|
|
33
|
+
autoInvalidate?: boolean;
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
36
|
* Custom query key to invalidate (if autoInvalidate is true)
|
|
34
37
|
* Default: ['fluxbase', 'table', tableName]
|
|
35
38
|
*/
|
|
36
|
-
invalidateKey?: unknown[]
|
|
39
|
+
invalidateKey?: unknown[];
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
42
|
* Whether the subscription is enabled
|
|
40
43
|
* Default: true
|
|
41
44
|
*/
|
|
42
|
-
enabled?: boolean
|
|
45
|
+
enabled?: boolean;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Hook to subscribe to realtime changes for a channel
|
|
47
50
|
*/
|
|
48
51
|
export function useRealtime(options: UseRealtimeOptions) {
|
|
49
|
-
const client = useFluxbaseClient()
|
|
50
|
-
const queryClient = useQueryClient()
|
|
51
|
-
const channelRef = useRef<ReturnType<typeof client.realtime.channel> | null>(
|
|
52
|
+
const client = useFluxbaseClient();
|
|
53
|
+
const queryClient = useQueryClient();
|
|
54
|
+
const channelRef = useRef<ReturnType<typeof client.realtime.channel> | null>(
|
|
55
|
+
null,
|
|
56
|
+
);
|
|
52
57
|
|
|
53
58
|
const {
|
|
54
59
|
channel: channelName,
|
|
55
|
-
event =
|
|
60
|
+
event = "*",
|
|
56
61
|
callback,
|
|
57
62
|
autoInvalidate = true,
|
|
58
63
|
invalidateKey,
|
|
59
64
|
enabled = true,
|
|
60
|
-
} = options
|
|
65
|
+
} = options;
|
|
61
66
|
|
|
62
67
|
useEffect(() => {
|
|
63
68
|
if (!enabled) {
|
|
64
|
-
return
|
|
69
|
+
return;
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
// Create channel and subscribe
|
|
68
|
-
const channel = client.realtime.channel(channelName)
|
|
69
|
-
channelRef.current = channel
|
|
73
|
+
const channel = client.realtime.channel(channelName);
|
|
74
|
+
channelRef.current = channel;
|
|
70
75
|
|
|
71
|
-
const handleChange = (payload:
|
|
76
|
+
const handleChange = (payload: RealtimePostgresChangesPayload) => {
|
|
72
77
|
// Call user callback
|
|
73
78
|
if (callback) {
|
|
74
|
-
callback(payload)
|
|
79
|
+
callback(payload);
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
// Auto-invalidate queries if enabled
|
|
78
83
|
if (autoInvalidate) {
|
|
79
84
|
// Extract table name from channel (e.g., 'table:public.products' -> 'public.products')
|
|
80
|
-
const tableName = channelName.replace(/^table:/,
|
|
85
|
+
const tableName = channelName.replace(/^table:/, "");
|
|
81
86
|
|
|
82
|
-
const key = invalidateKey || [
|
|
83
|
-
queryClient.invalidateQueries({ queryKey: key })
|
|
87
|
+
const key = invalidateKey || ["fluxbase", "table", tableName];
|
|
88
|
+
queryClient.invalidateQueries({ queryKey: key });
|
|
84
89
|
}
|
|
85
|
-
}
|
|
90
|
+
};
|
|
86
91
|
|
|
87
|
-
channel.on(event, handleChange).subscribe()
|
|
92
|
+
channel.on(event, handleChange).subscribe();
|
|
88
93
|
|
|
89
94
|
return () => {
|
|
90
|
-
channel.unsubscribe()
|
|
91
|
-
channelRef.current = null
|
|
92
|
-
}
|
|
93
|
-
}, [
|
|
95
|
+
channel.unsubscribe();
|
|
96
|
+
channelRef.current = null;
|
|
97
|
+
};
|
|
98
|
+
}, [
|
|
99
|
+
client,
|
|
100
|
+
channelName,
|
|
101
|
+
event,
|
|
102
|
+
callback,
|
|
103
|
+
autoInvalidate,
|
|
104
|
+
invalidateKey,
|
|
105
|
+
queryClient,
|
|
106
|
+
enabled,
|
|
107
|
+
]);
|
|
94
108
|
|
|
95
109
|
return {
|
|
96
110
|
channel: channelRef.current,
|
|
97
|
-
}
|
|
111
|
+
};
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
/**
|
|
@@ -104,12 +118,12 @@ export function useRealtime(options: UseRealtimeOptions) {
|
|
|
104
118
|
*/
|
|
105
119
|
export function useTableSubscription(
|
|
106
120
|
table: string,
|
|
107
|
-
options?: Omit<UseRealtimeOptions,
|
|
121
|
+
options?: Omit<UseRealtimeOptions, "channel">,
|
|
108
122
|
) {
|
|
109
123
|
return useRealtime({
|
|
110
124
|
...options,
|
|
111
125
|
channel: `table:${table}`,
|
|
112
|
-
})
|
|
126
|
+
});
|
|
113
127
|
}
|
|
114
128
|
|
|
115
129
|
/**
|
|
@@ -117,15 +131,15 @@ export function useTableSubscription(
|
|
|
117
131
|
*/
|
|
118
132
|
export function useTableInserts(
|
|
119
133
|
table: string,
|
|
120
|
-
callback: (payload:
|
|
121
|
-
options?: Omit<UseRealtimeOptions,
|
|
134
|
+
callback: (payload: RealtimePostgresChangesPayload) => void,
|
|
135
|
+
options?: Omit<UseRealtimeOptions, "channel" | "event" | "callback">,
|
|
122
136
|
) {
|
|
123
137
|
return useRealtime({
|
|
124
138
|
...options,
|
|
125
139
|
channel: `table:${table}`,
|
|
126
|
-
event:
|
|
140
|
+
event: "INSERT",
|
|
127
141
|
callback,
|
|
128
|
-
})
|
|
142
|
+
});
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
/**
|
|
@@ -133,15 +147,15 @@ export function useTableInserts(
|
|
|
133
147
|
*/
|
|
134
148
|
export function useTableUpdates(
|
|
135
149
|
table: string,
|
|
136
|
-
callback: (payload:
|
|
137
|
-
options?: Omit<UseRealtimeOptions,
|
|
150
|
+
callback: (payload: RealtimePostgresChangesPayload) => void,
|
|
151
|
+
options?: Omit<UseRealtimeOptions, "channel" | "event" | "callback">,
|
|
138
152
|
) {
|
|
139
153
|
return useRealtime({
|
|
140
154
|
...options,
|
|
141
155
|
channel: `table:${table}`,
|
|
142
|
-
event:
|
|
156
|
+
event: "UPDATE",
|
|
143
157
|
callback,
|
|
144
|
-
})
|
|
158
|
+
});
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
/**
|
|
@@ -149,13 +163,13 @@ export function useTableUpdates(
|
|
|
149
163
|
*/
|
|
150
164
|
export function useTableDeletes(
|
|
151
165
|
table: string,
|
|
152
|
-
callback: (payload:
|
|
153
|
-
options?: Omit<UseRealtimeOptions,
|
|
166
|
+
callback: (payload: RealtimePostgresChangesPayload) => void,
|
|
167
|
+
options?: Omit<UseRealtimeOptions, "channel" | "event" | "callback">,
|
|
154
168
|
) {
|
|
155
169
|
return useRealtime({
|
|
156
170
|
...options,
|
|
157
171
|
channel: `table:${table}`,
|
|
158
|
-
event:
|
|
172
|
+
event: "DELETE",
|
|
159
173
|
callback,
|
|
160
|
-
})
|
|
174
|
+
});
|
|
161
175
|
}
|
package/src/use-saml.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAML SSO hooks for Fluxbase React SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
|
+
import { useFluxbaseClient } from "./context";
|
|
7
|
+
import type { SAMLLoginOptions, SAMLProvider } from "@fluxbase/sdk";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook to get available SAML SSO providers
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function SAMLProviderList() {
|
|
15
|
+
* const { data: providers, isLoading } = useSAMLProviders()
|
|
16
|
+
*
|
|
17
|
+
* if (isLoading) return <div>Loading...</div>
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <div>
|
|
21
|
+
* {providers?.map(provider => (
|
|
22
|
+
* <button key={provider.id} onClick={() => signInWithSAML(provider.name)}>
|
|
23
|
+
* Sign in with {provider.name}
|
|
24
|
+
* </button>
|
|
25
|
+
* ))}
|
|
26
|
+
* </div>
|
|
27
|
+
* )
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function useSAMLProviders() {
|
|
32
|
+
const client = useFluxbaseClient();
|
|
33
|
+
|
|
34
|
+
return useQuery<SAMLProvider[]>({
|
|
35
|
+
queryKey: ["fluxbase", "auth", "saml", "providers"],
|
|
36
|
+
queryFn: async () => {
|
|
37
|
+
const { data, error } = await client.auth.getSAMLProviders();
|
|
38
|
+
if (error) throw error;
|
|
39
|
+
return data.providers;
|
|
40
|
+
},
|
|
41
|
+
staleTime: 1000 * 60 * 5, // 5 minutes - providers don't change often
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Hook to get SAML login URL for a provider
|
|
47
|
+
*
|
|
48
|
+
* This hook returns a function to get the login URL for a specific provider.
|
|
49
|
+
* Use this when you need more control over the redirect behavior.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* function SAMLLoginButton({ provider }: { provider: string }) {
|
|
54
|
+
* const getSAMLLoginUrl = useGetSAMLLoginUrl()
|
|
55
|
+
*
|
|
56
|
+
* const handleClick = async () => {
|
|
57
|
+
* const { data, error } = await getSAMLLoginUrl.mutateAsync({
|
|
58
|
+
* provider,
|
|
59
|
+
* options: { redirectUrl: window.location.href }
|
|
60
|
+
* })
|
|
61
|
+
* if (!error) {
|
|
62
|
+
* window.location.href = data.url
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
*
|
|
66
|
+
* return <button onClick={handleClick}>Login with {provider}</button>
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function useGetSAMLLoginUrl() {
|
|
71
|
+
const client = useFluxbaseClient();
|
|
72
|
+
|
|
73
|
+
return useMutation({
|
|
74
|
+
mutationFn: async ({
|
|
75
|
+
provider,
|
|
76
|
+
options,
|
|
77
|
+
}: {
|
|
78
|
+
provider: string;
|
|
79
|
+
options?: SAMLLoginOptions;
|
|
80
|
+
}) => {
|
|
81
|
+
return await client.auth.getSAMLLoginUrl(provider, options);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Hook to initiate SAML login (redirects to IdP)
|
|
88
|
+
*
|
|
89
|
+
* This hook returns a mutation that when called, redirects the user to the
|
|
90
|
+
* SAML Identity Provider for authentication.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```tsx
|
|
94
|
+
* function SAMLLoginButton() {
|
|
95
|
+
* const signInWithSAML = useSignInWithSAML()
|
|
96
|
+
*
|
|
97
|
+
* return (
|
|
98
|
+
* <button
|
|
99
|
+
* onClick={() => signInWithSAML.mutate({ provider: 'okta' })}
|
|
100
|
+
* disabled={signInWithSAML.isPending}
|
|
101
|
+
* >
|
|
102
|
+
* {signInWithSAML.isPending ? 'Redirecting...' : 'Sign in with Okta'}
|
|
103
|
+
* </button>
|
|
104
|
+
* )
|
|
105
|
+
* }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export function useSignInWithSAML() {
|
|
109
|
+
const client = useFluxbaseClient();
|
|
110
|
+
|
|
111
|
+
return useMutation({
|
|
112
|
+
mutationFn: async ({
|
|
113
|
+
provider,
|
|
114
|
+
options,
|
|
115
|
+
}: {
|
|
116
|
+
provider: string;
|
|
117
|
+
options?: SAMLLoginOptions;
|
|
118
|
+
}) => {
|
|
119
|
+
return await client.auth.signInWithSAML(provider, options);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Hook to handle SAML callback after IdP authentication
|
|
126
|
+
*
|
|
127
|
+
* Use this in your SAML callback page to complete the authentication flow.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* function SAMLCallbackPage() {
|
|
132
|
+
* const handleCallback = useHandleSAMLCallback()
|
|
133
|
+
* const navigate = useNavigate()
|
|
134
|
+
*
|
|
135
|
+
* useEffect(() => {
|
|
136
|
+
* const params = new URLSearchParams(window.location.search)
|
|
137
|
+
* const samlResponse = params.get('SAMLResponse')
|
|
138
|
+
*
|
|
139
|
+
* if (samlResponse) {
|
|
140
|
+
* handleCallback.mutate(
|
|
141
|
+
* { samlResponse },
|
|
142
|
+
* {
|
|
143
|
+
* onSuccess: () => navigate('/dashboard'),
|
|
144
|
+
* onError: (error) => console.error('SAML login failed:', error)
|
|
145
|
+
* }
|
|
146
|
+
* )
|
|
147
|
+
* }
|
|
148
|
+
* }, [])
|
|
149
|
+
*
|
|
150
|
+
* if (handleCallback.isPending) {
|
|
151
|
+
* return <div>Completing sign in...</div>
|
|
152
|
+
* }
|
|
153
|
+
*
|
|
154
|
+
* if (handleCallback.isError) {
|
|
155
|
+
* return <div>Authentication failed: {handleCallback.error.message}</div>
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* return null
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export function useHandleSAMLCallback() {
|
|
163
|
+
const client = useFluxbaseClient();
|
|
164
|
+
const queryClient = useQueryClient();
|
|
165
|
+
|
|
166
|
+
return useMutation({
|
|
167
|
+
mutationFn: async ({
|
|
168
|
+
samlResponse,
|
|
169
|
+
provider,
|
|
170
|
+
}: {
|
|
171
|
+
samlResponse: string;
|
|
172
|
+
provider?: string;
|
|
173
|
+
}) => {
|
|
174
|
+
return await client.auth.handleSAMLCallback(samlResponse, provider);
|
|
175
|
+
},
|
|
176
|
+
onSuccess: (result) => {
|
|
177
|
+
if (result.data) {
|
|
178
|
+
// Update auth state in React Query cache
|
|
179
|
+
queryClient.setQueryData(
|
|
180
|
+
["fluxbase", "auth", "session"],
|
|
181
|
+
result.data.session,
|
|
182
|
+
);
|
|
183
|
+
queryClient.setQueryData(
|
|
184
|
+
["fluxbase", "auth", "user"],
|
|
185
|
+
result.data.user,
|
|
186
|
+
);
|
|
187
|
+
// Invalidate any dependent queries
|
|
188
|
+
queryClient.invalidateQueries({ queryKey: ["fluxbase"] });
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Hook to get SAML Service Provider metadata URL
|
|
196
|
+
*
|
|
197
|
+
* Returns a function that generates the SP metadata URL for a given provider.
|
|
198
|
+
* Use this URL when configuring your SAML IdP.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```tsx
|
|
202
|
+
* function SAMLSetupInfo({ provider }: { provider: string }) {
|
|
203
|
+
* const getSAMLMetadataUrl = useSAMLMetadataUrl()
|
|
204
|
+
* const metadataUrl = getSAMLMetadataUrl(provider)
|
|
205
|
+
*
|
|
206
|
+
* return (
|
|
207
|
+
* <div>
|
|
208
|
+
* <p>SP Metadata URL:</p>
|
|
209
|
+
* <code>{metadataUrl}</code>
|
|
210
|
+
* </div>
|
|
211
|
+
* )
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export function useSAMLMetadataUrl() {
|
|
216
|
+
const client = useFluxbaseClient();
|
|
217
|
+
|
|
218
|
+
return (provider: string): string => {
|
|
219
|
+
return client.auth.getSAMLMetadataUrl(provider);
|
|
220
|
+
};
|
|
221
|
+
}
|