@nimbleflux/fluxbase-sdk-react 2026.5.4 → 2026.5.5-rc.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbleflux/fluxbase-sdk-react",
3
- "version": "2026.5.4",
3
+ "version": "2026.5.5-rc.2",
4
4
  "description": "React hooks for Fluxbase SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -39,7 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "peerDependencies": {
42
- "@nimbleflux/fluxbase-sdk": "^2026.5.4",
42
+ "@nimbleflux/fluxbase-sdk": "^2026.5.5-rc.2",
43
43
  "@tanstack/react-query": "^5.96.2",
44
44
  "react": "^18.0.0 || ^19.0.0"
45
45
  },
package/src/index.ts CHANGED
@@ -135,6 +135,15 @@ export {
135
135
  type UseTableDetailsReturn,
136
136
  } from "./use-table-export";
137
137
 
138
+ // Function hooks
139
+ export { useInvokeFunction } from "./use-functions";
140
+
141
+ // Job hooks
142
+ export { useSubmitJob, useJobStatus } from "./use-jobs";
143
+
144
+ // Branching hooks
145
+ export { useBranches } from "./use-branches";
146
+
138
147
  // Re-export types from SDK
139
148
  export type {
140
149
  FluxbaseClient,
@@ -144,6 +153,7 @@ export type {
144
153
  SignUpCredentials,
145
154
  PostgrestResponse,
146
155
  RealtimeChangePayload,
156
+ // @deprecated backward compat aliases
147
157
  StorageObject,
148
158
  AdminUser,
149
159
  EnrichedUser,
@@ -0,0 +1,21 @@
1
+ import { useQuery } from "@tanstack/react-query"
2
+ import { useFluxbaseClient } from "./context"
3
+
4
+ export interface UseBranchesOptions {
5
+ status?: "creating" | "ready" | "migrating" | "error" | "deleting" | "deleted";
6
+ type?: "main" | "preview" | "persistent";
7
+ limit?: number;
8
+ offset?: number;
9
+ }
10
+
11
+ export function useBranches(options?: UseBranchesOptions) {
12
+ const client = useFluxbaseClient()
13
+ return useQuery({
14
+ queryKey: ["fluxbase", "branches", options],
15
+ queryFn: async (): Promise<unknown> => {
16
+ const { data, error } = await client.branching.list(options)
17
+ if (error) throw error
18
+ return data
19
+ },
20
+ })
21
+ }
@@ -0,0 +1,13 @@
1
+ import { useMutation } from "@tanstack/react-query"
2
+ import { useFluxbaseClient } from "./context"
3
+
4
+ export function useInvokeFunction() {
5
+ const client = useFluxbaseClient()
6
+ return useMutation({
7
+ mutationFn: async ({ name, payload, method }: { name: string; payload?: unknown; method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" }) => {
8
+ const { data, error } = await client.functions.invoke(name, { body: payload, method })
9
+ if (error) throw error
10
+ return data
11
+ },
12
+ })
13
+ }
@@ -0,0 +1,33 @@
1
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
2
+ import { useFluxbaseClient } from "./context"
3
+
4
+ export function useSubmitJob() {
5
+ const client = useFluxbaseClient()
6
+ const queryClient = useQueryClient()
7
+ return useMutation({
8
+ mutationFn: async (params: { name: string; payload?: unknown }): Promise<unknown> => {
9
+ const { data, error } = await client.jobs.submit(params.name, params.payload)
10
+ if (error) throw error
11
+ return data
12
+ },
13
+ onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["fluxbase", "jobs"] }) },
14
+ })
15
+ }
16
+
17
+ export function useJobStatus(jobId: string | null) {
18
+ const client = useFluxbaseClient()
19
+ return useQuery({
20
+ queryKey: ["fluxbase", "jobs", jobId],
21
+ queryFn: async (): Promise<unknown> => {
22
+ if (!jobId) return null
23
+ const { data, error } = await client.jobs.get(jobId)
24
+ if (error) throw error
25
+ return data
26
+ },
27
+ enabled: !!jobId,
28
+ refetchInterval: (query) => {
29
+ const status = (query.state.data as Record<string, unknown> | null)?.status as string | undefined
30
+ return status === "pending" || status === "running" ? 2000 : false
31
+ },
32
+ })
33
+ }
package/src/use-query.ts CHANGED
@@ -43,17 +43,22 @@ export function useFluxbaseQuery<T = any>(
43
43
  options?: UseFluxbaseQueryOptions<T>,
44
44
  ) {
45
45
  const client = useFluxbaseClient();
46
+ const tenantId = client.getTenantId();
47
+
48
+ const { queryKey: customKey, ...queryOptions } = options || {};
46
49
 
47
50
  // Require queryKey for stable caching - function.toString() is not reliable
48
51
  // as it can vary between renders for inline functions
49
- if (!options?.queryKey) {
52
+ if (!customKey) {
50
53
  console.warn(
51
54
  "[useFluxbaseQuery] No queryKey provided. This may cause cache misses. " +
52
55
  "Please provide a stable queryKey in options.",
53
56
  );
54
57
  }
55
58
 
56
- const queryKey = options?.queryKey || ["fluxbase", "query", "unstable"];
59
+ const queryKey = customKey
60
+ ? ["fluxbase", tenantId ?? null, ...customKey]
61
+ : ["fluxbase", tenantId ?? null, "query", "unstable"];
57
62
 
58
63
  return useQuery({
59
64
  queryKey,
@@ -67,7 +72,7 @@ export function useFluxbaseQuery<T = any>(
67
72
 
68
73
  return (Array.isArray(data) ? data : data ? [data] : []) as T[];
69
74
  },
70
- ...options,
75
+ ...queryOptions,
71
76
  });
72
77
  }
73
78
 
@@ -113,8 +118,7 @@ export function useTable<T = any>(
113
118
  },
114
119
  {
115
120
  ...options,
116
- // Use table name as base key, or custom key if provided
117
- queryKey: options?.queryKey || ["fluxbase", "table", table],
121
+ queryKey: options?.queryKey || ["table", table],
118
122
  },
119
123
  );
120
124
  }
@@ -138,8 +142,10 @@ export function useInsert<T = any>(table: string) {
138
142
  return result;
139
143
  },
140
144
  onSuccess: () => {
141
- // Invalidate all queries for this table
142
- queryClient.invalidateQueries({ queryKey: ["fluxbase", "table", table] });
145
+ const tenantId = client.getTenantId();
146
+ queryClient.invalidateQueries({
147
+ queryKey: ["fluxbase", tenantId ?? null, "table", table],
148
+ });
143
149
  },
144
150
  });
145
151
  }
@@ -167,7 +173,10 @@ export function useUpdate<T = any>(table: string) {
167
173
  return result;
168
174
  },
169
175
  onSuccess: () => {
170
- queryClient.invalidateQueries({ queryKey: ["fluxbase", "table", table] });
176
+ const tenantId = client.getTenantId();
177
+ queryClient.invalidateQueries({
178
+ queryKey: ["fluxbase", tenantId ?? null, "table", table],
179
+ });
171
180
  },
172
181
  });
173
182
  }
@@ -191,7 +200,10 @@ export function useUpsert<T = any>(table: string) {
191
200
  return result;
192
201
  },
193
202
  onSuccess: () => {
194
- queryClient.invalidateQueries({ queryKey: ["fluxbase", "table", table] });
203
+ const tenantId = client.getTenantId();
204
+ queryClient.invalidateQueries({
205
+ queryKey: ["fluxbase", tenantId ?? null, "table", table],
206
+ });
195
207
  },
196
208
  });
197
209
  }
@@ -216,7 +228,10 @@ export function useDelete<T = any>(table: string) {
216
228
  }
217
229
  },
218
230
  onSuccess: () => {
219
- queryClient.invalidateQueries({ queryKey: ["fluxbase", "table", table] });
231
+ const tenantId = client.getTenantId();
232
+ queryClient.invalidateQueries({
233
+ queryKey: ["fluxbase", tenantId ?? null, "table", table],
234
+ });
220
235
  },
221
236
  });
222
237
  }
package/src/use-tenant.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  * @module use-tenant
5
5
  */
6
6
 
7
- import { useState, useEffect, useCallback } from "react";
7
+ import { useState, useCallback } from "react";
8
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
8
9
  import { useFluxbaseClient } from "./context";
9
10
  import type {
10
11
  Tenant,
@@ -98,58 +99,55 @@ export interface UseTenantsReturn {
98
99
  export function useTenants(options: UseTenantsOptions = {}): UseTenantsReturn {
99
100
  const { autoFetch = true } = options;
100
101
  const client = useFluxbaseClient();
102
+ const queryClient = useQueryClient();
101
103
 
102
- const [tenants, setTenants] = useState<TenantWithRole[]>([]);
103
- const [isLoading, setIsLoading] = useState(autoFetch);
104
- const [error, setError] = useState<Error | null>(null);
105
104
  const [currentTenantId, setCurrentTenantId] = useState<string | undefined>(
106
105
  client.getTenantId(),
107
106
  );
108
107
 
109
- const fetchTenants = useCallback(async () => {
110
- try {
111
- setIsLoading(true);
112
- setError(null);
113
- const { data, error: fetchError } = await client.tenant.listMine();
114
- if (fetchError) {
115
- setError(fetchError);
116
- } else {
117
- setTenants(data || []);
118
- }
119
- } catch (err) {
120
- setError(err as Error);
121
- } finally {
122
- setIsLoading(false);
123
- }
124
- }, [client]);
108
+ const query = useQuery({
109
+ queryKey: ["fluxbase", "tenants", "mine"],
110
+ queryFn: async () => {
111
+ const { data, error } = await client.tenant.listMine();
112
+ if (error) throw error;
113
+ return data || ([] as TenantWithRole[]);
114
+ },
115
+ enabled: autoFetch,
116
+ });
125
117
 
126
118
  const createTenant = useCallback(
127
119
  async (opts: CreateTenantOptions): Promise<Tenant> => {
128
120
  const { data, error: createError } = await client.tenant.create(opts);
129
121
  if (createError) throw createError;
130
- await fetchTenants();
122
+ await queryClient.invalidateQueries({
123
+ queryKey: ["fluxbase", "tenants"],
124
+ });
131
125
  return data!;
132
126
  },
133
- [client, fetchTenants],
127
+ [client, queryClient],
134
128
  );
135
129
 
136
130
  const updateTenant = useCallback(
137
131
  async (id: string, opts: UpdateTenantOptions): Promise<Tenant> => {
138
132
  const { data, error: updateError } = await client.tenant.update(id, opts);
139
133
  if (updateError) throw updateError;
140
- await fetchTenants();
134
+ await queryClient.invalidateQueries({
135
+ queryKey: ["fluxbase", "tenants"],
136
+ });
141
137
  return data!;
142
138
  },
143
- [client, fetchTenants],
139
+ [client, queryClient],
144
140
  );
145
141
 
146
142
  const deleteTenant = useCallback(
147
143
  async (id: string): Promise<void> => {
148
144
  const { error: deleteError } = await client.tenant.delete(id);
149
145
  if (deleteError) throw deleteError;
150
- await fetchTenants();
146
+ await queryClient.invalidateQueries({
147
+ queryKey: ["fluxbase", "tenants"],
148
+ });
151
149
  },
152
- [client, fetchTenants],
150
+ [client, queryClient],
153
151
  );
154
152
 
155
153
  const setCurrentTenant = useCallback(
@@ -160,17 +158,13 @@ export function useTenants(options: UseTenantsOptions = {}): UseTenantsReturn {
160
158
  [client],
161
159
  );
162
160
 
163
- useEffect(() => {
164
- if (autoFetch) {
165
- fetchTenants();
166
- }
167
- }, [autoFetch, fetchTenants]);
168
-
169
161
  return {
170
- tenants,
171
- isLoading,
172
- error,
173
- refetch: fetchTenants,
162
+ tenants: query.data ?? [],
163
+ isLoading: autoFetch ? query.isLoading : false,
164
+ error: query.error,
165
+ refetch: async () => {
166
+ await query.refetch();
167
+ },
174
168
  createTenant,
175
169
  updateTenant,
176
170
  deleteTenant,
@@ -249,31 +243,17 @@ export interface UseTenantReturn {
249
243
  export function useTenant(options: UseTenantOptions): UseTenantReturn {
250
244
  const { tenantId, autoFetch = true } = options;
251
245
  const client = useFluxbaseClient();
252
-
253
- const [tenant, setTenant] = useState<Tenant | null>(null);
254
- const [isLoading, setIsLoading] = useState(autoFetch);
255
- const [error, setError] = useState<Error | null>(null);
256
-
257
- const fetchTenant = useCallback(async () => {
258
- if (!tenantId) return;
259
-
260
- try {
261
- setIsLoading(true);
262
- setError(null);
263
- const { data, error: fetchError } = await client.tenant.get(tenantId);
264
- if (fetchError) {
265
- setError(fetchError);
266
- setTenant(null);
267
- } else {
268
- setTenant(data);
269
- }
270
- } catch (err) {
271
- setError(err as Error);
272
- setTenant(null);
273
- } finally {
274
- setIsLoading(false);
275
- }
276
- }, [client, tenantId]);
246
+ const queryClient = useQueryClient();
247
+
248
+ const query = useQuery({
249
+ queryKey: ["fluxbase", "tenant", tenantId],
250
+ queryFn: async () => {
251
+ const { data, error } = await client.tenant.get(tenantId);
252
+ if (error) throw error;
253
+ return data;
254
+ },
255
+ enabled: autoFetch && !!tenantId,
256
+ });
277
257
 
278
258
  const update = useCallback(
279
259
  async (opts: UpdateTenantOptions): Promise<Tenant> => {
@@ -282,29 +262,29 @@ export function useTenant(options: UseTenantOptions): UseTenantReturn {
282
262
  opts,
283
263
  );
284
264
  if (updateError) throw updateError;
285
- setTenant(data);
265
+ await queryClient.invalidateQueries({
266
+ queryKey: ["fluxbase", "tenant", tenantId],
267
+ });
286
268
  return data!;
287
269
  },
288
- [client, tenantId],
270
+ [client, tenantId, queryClient],
289
271
  );
290
272
 
291
273
  const remove = useCallback(async (): Promise<void> => {
292
274
  const { error: deleteError } = await client.tenant.delete(tenantId);
293
275
  if (deleteError) throw deleteError;
294
- setTenant(null);
295
- }, [client, tenantId]);
296
-
297
- useEffect(() => {
298
- if (autoFetch && tenantId) {
299
- fetchTenant();
300
- }
301
- }, [autoFetch, fetchTenant, tenantId]);
276
+ await queryClient.invalidateQueries({
277
+ queryKey: ["fluxbase", "tenant", tenantId],
278
+ });
279
+ }, [client, tenantId, queryClient]);
302
280
 
303
281
  return {
304
- tenant,
305
- isLoading,
306
- error,
307
- refetch: fetchTenant,
282
+ tenant: query.data ?? null,
283
+ isLoading: autoFetch ? query.isLoading : false,
284
+ error: query.error,
285
+ refetch: async () => {
286
+ await query.refetch();
287
+ },
308
288
  update,
309
289
  remove,
310
290
  };