@nimbleflux/fluxbase-sdk-react 2026.5.5-rc.2 → 2026.6.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.
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Tests for DDL hooks
3
+ */
4
+
5
+ import { describe, it, expect, vi } from "vitest";
6
+ import { renderHook, waitFor, act } from "@testing-library/react";
7
+ import {
8
+ useSchemas,
9
+ useTables,
10
+ useCreateSchema,
11
+ useDeleteTable,
12
+ } from "./use-ddl";
13
+ import { createMockClient, createWrapper } from "./test-utils";
14
+
15
+ describe("useSchemas", () => {
16
+ it("should list schemas", async () => {
17
+ const mockSchemas = {
18
+ schemas: [
19
+ { name: "public", owner: "postgres" },
20
+ { name: "analytics", owner: "postgres" },
21
+ ],
22
+ };
23
+ const client = createMockClient({
24
+ admin: {
25
+ ddl: {
26
+ listSchemas: vi.fn().mockResolvedValue(mockSchemas),
27
+ },
28
+ } as any,
29
+ });
30
+
31
+ const { result } = renderHook(() => useSchemas(), {
32
+ wrapper: createWrapper(client),
33
+ });
34
+
35
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
36
+ expect(result.current.data).toEqual(mockSchemas);
37
+ });
38
+
39
+ it("should handle errors", async () => {
40
+ const client = createMockClient({
41
+ admin: {
42
+ ddl: {
43
+ listSchemas: vi.fn().mockRejectedValue(new Error("Failed")),
44
+ },
45
+ } as any,
46
+ });
47
+
48
+ const { result } = renderHook(() => useSchemas(), {
49
+ wrapper: createWrapper(client),
50
+ });
51
+
52
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
53
+ expect(result.current.error).toBeInstanceOf(Error);
54
+ });
55
+ });
56
+
57
+ describe("useTables", () => {
58
+ it("should list all tables when no schema provided", async () => {
59
+ const mockTables = {
60
+ tables: [
61
+ { schema: "public", name: "users" },
62
+ { schema: "analytics", name: "events" },
63
+ ],
64
+ };
65
+ const listTables = vi.fn().mockResolvedValue(mockTables);
66
+ const client = createMockClient({
67
+ admin: { ddl: { listTables } } as any,
68
+ });
69
+
70
+ const { result } = renderHook(() => useTables(), {
71
+ wrapper: createWrapper(client),
72
+ });
73
+
74
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
75
+ expect(result.current.data).toEqual(mockTables);
76
+ expect(listTables).toHaveBeenCalledWith(undefined);
77
+ });
78
+
79
+ it("should list tables filtered by schema", async () => {
80
+ const mockTables = {
81
+ tables: [{ schema: "public", name: "users" }],
82
+ };
83
+ const listTables = vi.fn().mockResolvedValue(mockTables);
84
+ const client = createMockClient({
85
+ admin: { ddl: { listTables } } as any,
86
+ });
87
+
88
+ const { result } = renderHook(() => useTables("public"), {
89
+ wrapper: createWrapper(client),
90
+ });
91
+
92
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
93
+ expect(listTables).toHaveBeenCalledWith("public");
94
+ expect(result.current.data).toEqual(mockTables);
95
+ });
96
+
97
+ it("should handle errors", async () => {
98
+ const client = createMockClient({
99
+ admin: {
100
+ ddl: {
101
+ listTables: vi.fn().mockRejectedValue(new Error("Failed")),
102
+ },
103
+ } as any,
104
+ });
105
+
106
+ const { result } = renderHook(() => useTables(), {
107
+ wrapper: createWrapper(client),
108
+ });
109
+
110
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
111
+ expect(result.current.error).toBeInstanceOf(Error);
112
+ });
113
+ });
114
+
115
+ describe("useCreateSchema", () => {
116
+ it("should create a schema", async () => {
117
+ const mockResponse = { message: "Schema created", schema: "analytics" };
118
+ const createSchema = vi.fn().mockResolvedValue(mockResponse);
119
+ const client = createMockClient({
120
+ admin: { ddl: { createSchema } } as any,
121
+ });
122
+
123
+ const { result } = renderHook(() => useCreateSchema(), {
124
+ wrapper: createWrapper(client),
125
+ });
126
+
127
+ await act(async () => {
128
+ await result.current.mutateAsync("analytics");
129
+ });
130
+
131
+ expect(createSchema).toHaveBeenCalledWith("analytics");
132
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
133
+ expect(result.current.data).toEqual(mockResponse);
134
+ });
135
+
136
+ it("should handle errors", async () => {
137
+ const client = createMockClient({
138
+ admin: {
139
+ ddl: {
140
+ createSchema: vi.fn().mockRejectedValue(new Error("Schema exists")),
141
+ },
142
+ } as any,
143
+ });
144
+
145
+ const { result } = renderHook(() => useCreateSchema(), {
146
+ wrapper: createWrapper(client),
147
+ });
148
+
149
+ await act(async () => {
150
+ try {
151
+ await result.current.mutateAsync("analytics");
152
+ } catch {
153
+ // expected
154
+ }
155
+ });
156
+
157
+ await waitFor(() => expect(result.current.isError).toBe(true));
158
+ });
159
+ });
160
+
161
+ describe("useDeleteTable", () => {
162
+ it("should delete a table", async () => {
163
+ const mockResponse = { message: "Table deleted" };
164
+ const deleteTable = vi.fn().mockResolvedValue(mockResponse);
165
+ const client = createMockClient({
166
+ admin: { ddl: { deleteTable } } as any,
167
+ });
168
+
169
+ const { result } = renderHook(() => useDeleteTable(), {
170
+ wrapper: createWrapper(client),
171
+ });
172
+
173
+ await act(async () => {
174
+ await result.current.mutateAsync({ schema: "public", name: "old_data" });
175
+ });
176
+
177
+ expect(deleteTable).toHaveBeenCalledWith("public", "old_data");
178
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
179
+ expect(result.current.data).toEqual(mockResponse);
180
+ });
181
+
182
+ it("should handle errors", async () => {
183
+ const client = createMockClient({
184
+ admin: {
185
+ ddl: {
186
+ deleteTable: vi.fn().mockRejectedValue(new Error("Table not found")),
187
+ },
188
+ } as any,
189
+ });
190
+
191
+ const { result } = renderHook(() => useDeleteTable(), {
192
+ wrapper: createWrapper(client),
193
+ });
194
+
195
+ await act(async () => {
196
+ try {
197
+ await result.current.mutateAsync({ schema: "public", name: "missing" });
198
+ } catch {
199
+ // expected
200
+ }
201
+ });
202
+
203
+ await waitFor(() => expect(result.current.isError).toBe(true));
204
+ });
205
+ });
package/src/use-ddl.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * DDL hooks for Fluxbase React SDK
3
+ * Provides hooks for managing database schemas and tables
4
+ */
5
+
6
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
7
+ import { useFluxbaseClient } from "./context";
8
+
9
+ /**
10
+ * Hook to list all database schemas
11
+ */
12
+ export function useSchemas() {
13
+ const client = useFluxbaseClient();
14
+
15
+ return useQuery({
16
+ queryKey: ["fluxbase", "schemas"],
17
+ queryFn: async () => {
18
+ return await client.admin.ddl.listSchemas();
19
+ },
20
+ });
21
+ }
22
+
23
+ /**
24
+ * Hook to list tables, optionally filtered by schema
25
+ */
26
+ export function useTables(schema?: string) {
27
+ const client = useFluxbaseClient();
28
+
29
+ return useQuery({
30
+ queryKey: ["fluxbase", "tables", schema],
31
+ queryFn: async () => {
32
+ return await client.admin.ddl.listTables(schema);
33
+ },
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Hook to create a new database schema
39
+ */
40
+ export function useCreateSchema() {
41
+ const client = useFluxbaseClient();
42
+ const queryClient = useQueryClient();
43
+
44
+ return useMutation({
45
+ mutationFn: async (name: string) => {
46
+ return await client.admin.ddl.createSchema(name);
47
+ },
48
+ onSuccess: () => {
49
+ queryClient.invalidateQueries({
50
+ queryKey: ["fluxbase", "schemas"],
51
+ });
52
+ },
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Hook to delete a table from a schema
58
+ */
59
+ export function useDeleteTable() {
60
+ const client = useFluxbaseClient();
61
+ const queryClient = useQueryClient();
62
+
63
+ return useMutation({
64
+ mutationFn: async ({ schema, name }: { schema: string; name: string }) => {
65
+ return await client.admin.ddl.deleteTable(schema, name);
66
+ },
67
+ onSuccess: () => {
68
+ queryClient.invalidateQueries({
69
+ queryKey: ["fluxbase", "tables"],
70
+ });
71
+ },
72
+ });
73
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Tests for Functions hooks
3
+ */
4
+
5
+ import { describe, it, expect, vi } from "vitest";
6
+ import { renderHook, waitFor, act } from "@testing-library/react";
7
+ import { useInvokeFunction, useFunctions } from "./use-functions";
8
+ import { createMockClient, createWrapper } from "./test-utils";
9
+
10
+ describe("useInvokeFunction", () => {
11
+ it("should invoke a function", async () => {
12
+ const invoke = vi.fn().mockResolvedValue({
13
+ data: { result: "ok" },
14
+ error: null,
15
+ });
16
+ const client = createMockClient({
17
+ functions: { invoke } as any,
18
+ });
19
+
20
+ const { result } = renderHook(() => useInvokeFunction(), {
21
+ wrapper: createWrapper(client),
22
+ });
23
+
24
+ await act(async () => {
25
+ await result.current.mutateAsync({ name: "hello", payload: { name: "World" } });
26
+ });
27
+
28
+ expect(invoke).toHaveBeenCalledWith("hello", { body: { name: "World" }, method: undefined });
29
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
30
+ });
31
+
32
+ it("should handle errors", async () => {
33
+ const client = createMockClient({
34
+ functions: {
35
+ invoke: vi.fn().mockResolvedValue({ data: null, error: new Error("Failed") }),
36
+ } as any,
37
+ });
38
+
39
+ const { result } = renderHook(() => useInvokeFunction(), {
40
+ wrapper: createWrapper(client),
41
+ });
42
+
43
+ await act(async () => {
44
+ try {
45
+ await result.current.mutateAsync({ name: "bad" });
46
+ } catch {
47
+ // expected
48
+ }
49
+ });
50
+
51
+ await waitFor(() => expect(result.current.isError).toBe(true));
52
+ });
53
+ });
54
+
55
+ describe("useFunctions", () => {
56
+ it("should list functions", async () => {
57
+ const mockFunctions = [
58
+ { name: "hello", version: "1", namespace: "default" },
59
+ { name: "world", version: "2", namespace: "default" },
60
+ ];
61
+ const client = createMockClient({
62
+ functions: {
63
+ list: vi.fn().mockResolvedValue({ data: mockFunctions, error: null }),
64
+ } as any,
65
+ });
66
+
67
+ const { result } = renderHook(() => useFunctions(), {
68
+ wrapper: createWrapper(client),
69
+ });
70
+
71
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
72
+ expect(result.current.data).toEqual(mockFunctions);
73
+ });
74
+
75
+ it("should handle errors", async () => {
76
+ const client = createMockClient({
77
+ functions: {
78
+ list: vi.fn().mockResolvedValue({ data: null, error: new Error("Failed") }),
79
+ } as any,
80
+ });
81
+
82
+ const { result } = renderHook(() => useFunctions(), {
83
+ wrapper: createWrapper(client),
84
+ });
85
+
86
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
87
+ expect(result.current.error).toBeInstanceOf(Error);
88
+ });
89
+ });
@@ -1,4 +1,4 @@
1
- import { useMutation } from "@tanstack/react-query"
1
+ import { useMutation, useQuery } from "@tanstack/react-query"
2
2
  import { useFluxbaseClient } from "./context"
3
3
 
4
4
  export function useInvokeFunction() {
@@ -11,3 +11,15 @@ export function useInvokeFunction() {
11
11
  },
12
12
  })
13
13
  }
14
+
15
+ export function useFunctions() {
16
+ const client = useFluxbaseClient()
17
+ return useQuery({
18
+ queryKey: ["fluxbase", "functions"],
19
+ queryFn: async (): Promise<unknown> => {
20
+ const { data, error } = await client.functions.list()
21
+ if (error) throw error
22
+ return data
23
+ },
24
+ })
25
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Tests for impersonation hooks
3
+ */
4
+
5
+ import { describe, it, expect, vi } from "vitest";
6
+ import { renderHook, waitFor, act } from "@testing-library/react";
7
+ import {
8
+ useImpersonationSessions,
9
+ useCurrentImpersonation,
10
+ useImpersonateUser,
11
+ useStopImpersonation,
12
+ } from "./use-impersonation";
13
+ import { createMockClient, createWrapper } from "./test-utils";
14
+
15
+ describe("useImpersonationSessions", () => {
16
+ it("should list impersonation sessions", async () => {
17
+ const mockSessions = {
18
+ sessions: [
19
+ {
20
+ id: "s1",
21
+ admin_user_id: "admin-1",
22
+ target_user_id: "user-1",
23
+ impersonation_type: "user" as const,
24
+ target_role: "user",
25
+ reason: "Support",
26
+ started_at: "",
27
+ ended_at: null,
28
+ is_active: true,
29
+ ip_address: null,
30
+ user_agent: null,
31
+ },
32
+ ],
33
+ total: 1,
34
+ };
35
+ const client = createMockClient({
36
+ admin: {
37
+ impersonation: {
38
+ listSessions: vi.fn().mockResolvedValue(mockSessions),
39
+ },
40
+ } as any,
41
+ });
42
+
43
+ const { result } = renderHook(() => useImpersonationSessions(), {
44
+ wrapper: createWrapper(client),
45
+ });
46
+
47
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
48
+ expect(result.current.data).toEqual(mockSessions);
49
+ });
50
+
51
+ it("should pass options to listSessions", async () => {
52
+ const listSessions = vi.fn().mockResolvedValue({ sessions: [], total: 0 });
53
+ const client = createMockClient({
54
+ admin: { impersonation: { listSessions } } as any,
55
+ });
56
+
57
+ renderHook(() => useImpersonationSessions({ is_active: true }), {
58
+ wrapper: createWrapper(client),
59
+ });
60
+
61
+ await waitFor(() => {
62
+ expect(listSessions).toHaveBeenCalledWith({ is_active: true });
63
+ });
64
+ });
65
+
66
+ it("should handle errors", async () => {
67
+ const client = createMockClient({
68
+ admin: {
69
+ impersonation: {
70
+ listSessions: vi.fn().mockRejectedValue(new Error("Failed")),
71
+ },
72
+ } as any,
73
+ });
74
+
75
+ const { result } = renderHook(() => useImpersonationSessions(), {
76
+ wrapper: createWrapper(client),
77
+ });
78
+
79
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
80
+ expect(result.current.error).toBeInstanceOf(Error);
81
+ });
82
+ });
83
+
84
+ describe("useCurrentImpersonation", () => {
85
+ it("should get current impersonation session", async () => {
86
+ const mockCurrent = {
87
+ session: {
88
+ id: "s1",
89
+ admin_user_id: "admin-1",
90
+ target_user_id: "user-1",
91
+ impersonation_type: "user" as const,
92
+ target_role: "user",
93
+ reason: "Support",
94
+ started_at: "",
95
+ ended_at: null,
96
+ is_active: true,
97
+ ip_address: null,
98
+ user_agent: null,
99
+ },
100
+ target_user: { id: "user-1", email: "test@example.com", role: "user" },
101
+ };
102
+ const client = createMockClient({
103
+ admin: {
104
+ impersonation: {
105
+ getCurrent: vi.fn().mockResolvedValue(mockCurrent),
106
+ },
107
+ } as any,
108
+ });
109
+
110
+ const { result } = renderHook(() => useCurrentImpersonation(), {
111
+ wrapper: createWrapper(client),
112
+ });
113
+
114
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
115
+ expect(result.current.data).toEqual(mockCurrent);
116
+ });
117
+
118
+ it("should return null session when not impersonating", async () => {
119
+ const client = createMockClient({
120
+ admin: {
121
+ impersonation: {
122
+ getCurrent: vi.fn().mockResolvedValue({
123
+ session: null,
124
+ target_user: null,
125
+ }),
126
+ },
127
+ } as any,
128
+ });
129
+
130
+ const { result } = renderHook(() => useCurrentImpersonation(), {
131
+ wrapper: createWrapper(client),
132
+ });
133
+
134
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
135
+ expect(result.current.data?.session).toBeNull();
136
+ });
137
+
138
+ it("should handle errors", async () => {
139
+ const client = createMockClient({
140
+ admin: {
141
+ impersonation: {
142
+ getCurrent: vi.fn().mockRejectedValue(new Error("Failed")),
143
+ },
144
+ } as any,
145
+ });
146
+
147
+ const { result } = renderHook(() => useCurrentImpersonation(), {
148
+ wrapper: createWrapper(client),
149
+ });
150
+
151
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
152
+ expect(result.current.error).toBeInstanceOf(Error);
153
+ });
154
+ });
155
+
156
+ describe("useImpersonateUser", () => {
157
+ it("should impersonate a user", async () => {
158
+ const mockResponse = {
159
+ session: {
160
+ id: "s1",
161
+ admin_user_id: "admin-1",
162
+ target_user_id: "user-1",
163
+ impersonation_type: "user" as const,
164
+ target_role: "user",
165
+ reason: "Support",
166
+ started_at: "",
167
+ ended_at: null,
168
+ is_active: true,
169
+ ip_address: null,
170
+ user_agent: null,
171
+ },
172
+ target_user: { id: "user-1", email: "test@example.com", role: "user" },
173
+ access_token: "token-123",
174
+ refresh_token: "refresh-123",
175
+ expires_in: 3600,
176
+ };
177
+ const impersonateUser = vi.fn().mockResolvedValue(mockResponse);
178
+ const client = createMockClient({
179
+ admin: { impersonation: { impersonateUser } } as any,
180
+ });
181
+
182
+ const { result } = renderHook(() => useImpersonateUser(), {
183
+ wrapper: createWrapper(client),
184
+ });
185
+
186
+ await act(async () => {
187
+ await result.current.mutateAsync({
188
+ target_user_id: "user-1",
189
+ reason: "Support ticket #1234",
190
+ });
191
+ });
192
+
193
+ expect(impersonateUser).toHaveBeenCalledWith({
194
+ target_user_id: "user-1",
195
+ reason: "Support ticket #1234",
196
+ });
197
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
198
+ expect(result.current.data).toEqual(mockResponse);
199
+ });
200
+
201
+ it("should handle errors", async () => {
202
+ const client = createMockClient({
203
+ admin: {
204
+ impersonation: {
205
+ impersonateUser: vi.fn().mockRejectedValue(new Error("Not allowed")),
206
+ },
207
+ } as any,
208
+ });
209
+
210
+ const { result } = renderHook(() => useImpersonateUser(), {
211
+ wrapper: createWrapper(client),
212
+ });
213
+
214
+ await act(async () => {
215
+ try {
216
+ await result.current.mutateAsync({
217
+ target_user_id: "user-1",
218
+ reason: "test",
219
+ });
220
+ } catch {
221
+ // expected
222
+ }
223
+ });
224
+
225
+ await waitFor(() => expect(result.current.isError).toBe(true));
226
+ });
227
+ });
228
+
229
+ describe("useStopImpersonation", () => {
230
+ it("should stop impersonation", async () => {
231
+ const mockResponse = { success: true, message: "Impersonation stopped" };
232
+ const stop = vi.fn().mockResolvedValue(mockResponse);
233
+ const client = createMockClient({
234
+ admin: { impersonation: { stop } } as any,
235
+ });
236
+
237
+ const { result } = renderHook(() => useStopImpersonation(), {
238
+ wrapper: createWrapper(client),
239
+ });
240
+
241
+ await act(async () => {
242
+ await result.current.mutateAsync();
243
+ });
244
+
245
+ expect(stop).toHaveBeenCalled();
246
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
247
+ expect(result.current.data).toEqual(mockResponse);
248
+ });
249
+
250
+ it("should handle errors", async () => {
251
+ const client = createMockClient({
252
+ admin: {
253
+ impersonation: {
254
+ stop: vi.fn().mockRejectedValue(new Error("No active session")),
255
+ },
256
+ } as any,
257
+ });
258
+
259
+ const { result } = renderHook(() => useStopImpersonation(), {
260
+ wrapper: createWrapper(client),
261
+ });
262
+
263
+ await act(async () => {
264
+ try {
265
+ await result.current.mutateAsync();
266
+ } catch {
267
+ // expected
268
+ }
269
+ });
270
+
271
+ await waitFor(() => expect(result.current.isError).toBe(true));
272
+ });
273
+ });