@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,296 @@
1
+ /**
2
+ * Knowledge Base hooks for Fluxbase React SDK
3
+ * Provides hooks for RAG document management, semantic search, and knowledge graph
4
+ */
5
+
6
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
7
+ import { useFluxbaseClient } from "./context";
8
+ import type {
9
+ CreateKnowledgeBaseRequest,
10
+ UpdateKnowledgeBaseRequest,
11
+ AddDocumentRequest,
12
+ SearchKnowledgeBaseRequest,
13
+ } from "@nimbleflux/fluxbase-sdk";
14
+
15
+ /**
16
+ * Hook to list all knowledge bases the user has access to
17
+ */
18
+ export function useKnowledgeBases() {
19
+ const client = useFluxbaseClient();
20
+
21
+ return useQuery({
22
+ queryKey: ["fluxbase", "knowledge-base", "list"],
23
+ queryFn: async () => {
24
+ const { data, error } = await client.knowledgeBase.list();
25
+ if (error) throw error;
26
+ return data || [];
27
+ },
28
+ });
29
+ }
30
+
31
+ /**
32
+ * Hook to get a single knowledge base by ID
33
+ */
34
+ export function useKnowledgeBase(kbId: string | null) {
35
+ const client = useFluxbaseClient();
36
+
37
+ return useQuery({
38
+ queryKey: ["fluxbase", "knowledge-base", kbId],
39
+ queryFn: async () => {
40
+ if (!kbId) return null;
41
+ const { data, error } = await client.knowledgeBase.get(kbId);
42
+ if (error) throw error;
43
+ return data;
44
+ },
45
+ enabled: !!kbId,
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Hook to create a new knowledge base
51
+ */
52
+ export function useCreateKnowledgeBase() {
53
+ const client = useFluxbaseClient();
54
+ const queryClient = useQueryClient();
55
+
56
+ return useMutation({
57
+ mutationFn: async (request: CreateKnowledgeBaseRequest) => {
58
+ const { data, error } = await client.knowledgeBase.create(request);
59
+ if (error) throw error;
60
+ return data;
61
+ },
62
+ onSuccess: () => {
63
+ queryClient.invalidateQueries({
64
+ queryKey: ["fluxbase", "knowledge-base", "list"],
65
+ });
66
+ },
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Hook to update a knowledge base
72
+ */
73
+ export function useUpdateKnowledgeBase() {
74
+ const client = useFluxbaseClient();
75
+ const queryClient = useQueryClient();
76
+
77
+ return useMutation({
78
+ mutationFn: async ({
79
+ kbId,
80
+ updates,
81
+ }: {
82
+ kbId: string;
83
+ updates: UpdateKnowledgeBaseRequest;
84
+ }) => {
85
+ const { data, error } = await client.knowledgeBase.update(kbId, updates);
86
+ if (error) throw error;
87
+ return data;
88
+ },
89
+ onSuccess: (_data, variables) => {
90
+ queryClient.invalidateQueries({
91
+ queryKey: ["fluxbase", "knowledge-base", "list"],
92
+ });
93
+ queryClient.invalidateQueries({
94
+ queryKey: ["fluxbase", "knowledge-base", variables.kbId],
95
+ });
96
+ },
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Hook to delete a knowledge base
102
+ */
103
+ export function useDeleteKnowledgeBase() {
104
+ const client = useFluxbaseClient();
105
+ const queryClient = useQueryClient();
106
+
107
+ return useMutation({
108
+ mutationFn: async (kbId: string) => {
109
+ const { error } = await client.knowledgeBase.delete(kbId);
110
+ if (error) throw error;
111
+ },
112
+ onSuccess: () => {
113
+ queryClient.invalidateQueries({
114
+ queryKey: ["fluxbase", "knowledge-base", "list"],
115
+ });
116
+ },
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Hook to list documents in a knowledge base
122
+ */
123
+ export function useKBDocuments(kbId: string | null) {
124
+ const client = useFluxbaseClient();
125
+
126
+ return useQuery({
127
+ queryKey: ["fluxbase", "knowledge-base", kbId, "documents"],
128
+ queryFn: async () => {
129
+ if (!kbId) return [];
130
+ const { data, error } = await client.knowledgeBase.listDocuments(kbId);
131
+ if (error) throw error;
132
+ return data || [];
133
+ },
134
+ enabled: !!kbId,
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Hook to add a document to a knowledge base
140
+ */
141
+ export function useAddDocument() {
142
+ const client = useFluxbaseClient();
143
+ const queryClient = useQueryClient();
144
+
145
+ return useMutation({
146
+ mutationFn: async ({
147
+ kbId,
148
+ request,
149
+ }: {
150
+ kbId: string;
151
+ request: AddDocumentRequest;
152
+ }) => {
153
+ const { data, error } = await client.knowledgeBase.addDocument(
154
+ kbId,
155
+ request,
156
+ );
157
+ if (error) throw error;
158
+ return data;
159
+ },
160
+ onSuccess: (_data, variables) => {
161
+ queryClient.invalidateQueries({
162
+ queryKey: ["fluxbase", "knowledge-base", variables.kbId, "documents"],
163
+ });
164
+ },
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Hook to upload a file document to a knowledge base
170
+ */
171
+ export function useUploadDocument() {
172
+ const client = useFluxbaseClient();
173
+ const queryClient = useQueryClient();
174
+
175
+ return useMutation({
176
+ mutationFn: async ({
177
+ kbId,
178
+ file,
179
+ filename,
180
+ metadata,
181
+ }: {
182
+ kbId: string;
183
+ file: File | Blob | ArrayBuffer;
184
+ filename: string;
185
+ metadata?: Record<string, string>;
186
+ }) => {
187
+ const { data, error } = await client.knowledgeBase.uploadDocument(
188
+ kbId,
189
+ file,
190
+ filename,
191
+ metadata,
192
+ );
193
+ if (error) throw error;
194
+ return data;
195
+ },
196
+ onSuccess: (_data, variables) => {
197
+ queryClient.invalidateQueries({
198
+ queryKey: ["fluxbase", "knowledge-base", variables.kbId, "documents"],
199
+ });
200
+ },
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Hook to delete a document
206
+ */
207
+ export function useDeleteDocument() {
208
+ const client = useFluxbaseClient();
209
+ const queryClient = useQueryClient();
210
+
211
+ return useMutation({
212
+ mutationFn: async ({
213
+ kbId,
214
+ docId,
215
+ }: {
216
+ kbId: string;
217
+ docId: string;
218
+ }) => {
219
+ const { error } = await client.knowledgeBase.deleteDocument(kbId, docId);
220
+ if (error) throw error;
221
+ },
222
+ onSuccess: (_data, variables) => {
223
+ queryClient.invalidateQueries({
224
+ queryKey: ["fluxbase", "knowledge-base", variables.kbId, "documents"],
225
+ });
226
+ },
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Hook to search a knowledge base with semantic similarity
232
+ *
233
+ * @example
234
+ * ```tsx
235
+ * const { data, isPending } = useKBSearch(kbId, {
236
+ * query: 'How to configure auth?',
237
+ * max_chunks: 5,
238
+ * })
239
+ * ```
240
+ */
241
+ export function useKBSearch(
242
+ kbId: string | null,
243
+ request: SearchKnowledgeBaseRequest | null,
244
+ ) {
245
+ const client = useFluxbaseClient();
246
+
247
+ return useQuery({
248
+ queryKey: ["fluxbase", "knowledge-base", kbId, "search", request],
249
+ queryFn: async () => {
250
+ if (!kbId || !request) return null;
251
+ const { data, error } = await client.knowledgeBase.search(kbId, request);
252
+ if (error) throw error;
253
+ return data;
254
+ },
255
+ enabled: !!kbId && !!request,
256
+ });
257
+ }
258
+
259
+ /**
260
+ * Hook to list entities in a knowledge base
261
+ */
262
+ export function useKBEntities(kbId: string | null, type?: string) {
263
+ const client = useFluxbaseClient();
264
+
265
+ return useQuery({
266
+ queryKey: ["fluxbase", "knowledge-base", kbId, "entities", type],
267
+ queryFn: async () => {
268
+ if (!kbId) return [];
269
+ const { data, error } = await client.knowledgeBase.listEntities(
270
+ kbId,
271
+ type as any,
272
+ );
273
+ if (error) throw error;
274
+ return data || [];
275
+ },
276
+ enabled: !!kbId,
277
+ });
278
+ }
279
+
280
+ /**
281
+ * Hook to get the knowledge graph (entities + relationships)
282
+ */
283
+ export function useKnowledgeGraph(kbId: string | null) {
284
+ const client = useFluxbaseClient();
285
+
286
+ return useQuery({
287
+ queryKey: ["fluxbase", "knowledge-base", kbId, "graph"],
288
+ queryFn: async () => {
289
+ if (!kbId) return null;
290
+ const { data, error } = await client.knowledgeBase.getKnowledgeGraph(kbId);
291
+ if (error) throw error;
292
+ return data;
293
+ },
294
+ enabled: !!kbId,
295
+ });
296
+ }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Tests for migration hooks
3
+ */
4
+
5
+ import { describe, it, expect, vi } from "vitest";
6
+ import { renderHook, waitFor, act } from "@testing-library/react";
7
+ import {
8
+ useMigrations,
9
+ useApplyMigration,
10
+ useRollbackMigration,
11
+ useSyncMigrations,
12
+ } from "./use-migrations";
13
+ import { createMockClient, createWrapper } from "./test-utils";
14
+
15
+ describe("useMigrations", () => {
16
+ it("should list migrations", async () => {
17
+ const mockMigrations = [
18
+ {
19
+ id: "1",
20
+ namespace: "default",
21
+ name: "001_init",
22
+ up_sql: "CREATE TABLE test();",
23
+ version: 1,
24
+ status: "applied" as const,
25
+ created_at: "",
26
+ updated_at: "",
27
+ },
28
+ ];
29
+ const client = createMockClient({
30
+ admin: {
31
+ migrations: {
32
+ list: vi.fn().mockResolvedValue({ data: mockMigrations, error: null }),
33
+ },
34
+ } as any,
35
+ });
36
+
37
+ const { result } = renderHook(() => useMigrations(), {
38
+ wrapper: createWrapper(client),
39
+ });
40
+
41
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
42
+ expect(result.current.data).toEqual(mockMigrations);
43
+ });
44
+
45
+ it("should pass namespace to list", async () => {
46
+ const list = vi.fn().mockResolvedValue({ data: [], error: null });
47
+ const client = createMockClient({
48
+ admin: { migrations: { list } } as any,
49
+ });
50
+
51
+ renderHook(() => useMigrations("myapp"), {
52
+ wrapper: createWrapper(client),
53
+ });
54
+
55
+ await waitFor(() => {
56
+ expect(list).toHaveBeenCalledWith("myapp");
57
+ });
58
+ });
59
+
60
+ it("should handle errors", async () => {
61
+ const client = createMockClient({
62
+ admin: {
63
+ migrations: {
64
+ list: vi
65
+ .fn()
66
+ .mockResolvedValue({ data: null, error: new Error("Failed") }),
67
+ },
68
+ } as any,
69
+ });
70
+
71
+ const { result } = renderHook(() => useMigrations(), {
72
+ wrapper: createWrapper(client),
73
+ });
74
+
75
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
76
+ expect(result.current.error).toBeInstanceOf(Error);
77
+ });
78
+ });
79
+
80
+ describe("useApplyMigration", () => {
81
+ it("should apply a migration", async () => {
82
+ const apply = vi
83
+ .fn()
84
+ .mockResolvedValue({ data: { message: "Applied" }, error: null });
85
+ const client = createMockClient({
86
+ admin: { migrations: { apply } } as any,
87
+ });
88
+
89
+ const { result } = renderHook(() => useApplyMigration(), {
90
+ wrapper: createWrapper(client),
91
+ });
92
+
93
+ await act(async () => {
94
+ await result.current.mutateAsync({ name: "001_init" });
95
+ });
96
+
97
+ expect(apply).toHaveBeenCalledWith("001_init", undefined);
98
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
99
+ });
100
+
101
+ it("should pass namespace to apply", async () => {
102
+ const apply = vi
103
+ .fn()
104
+ .mockResolvedValue({ data: { message: "Applied" }, error: null });
105
+ const client = createMockClient({
106
+ admin: { migrations: { apply } } as any,
107
+ });
108
+
109
+ const { result } = renderHook(() => useApplyMigration(), {
110
+ wrapper: createWrapper(client),
111
+ });
112
+
113
+ await act(async () => {
114
+ await result.current.mutateAsync({ name: "001_init", namespace: "myapp" });
115
+ });
116
+
117
+ expect(apply).toHaveBeenCalledWith("001_init", "myapp");
118
+ });
119
+
120
+ it("should handle errors", async () => {
121
+ const client = createMockClient({
122
+ admin: {
123
+ migrations: {
124
+ apply: vi
125
+ .fn()
126
+ .mockResolvedValue({ data: null, error: new Error("Failed") }),
127
+ },
128
+ } as any,
129
+ });
130
+
131
+ const { result } = renderHook(() => useApplyMigration(), {
132
+ wrapper: createWrapper(client),
133
+ });
134
+
135
+ await act(async () => {
136
+ try {
137
+ await result.current.mutateAsync({ name: "001_init" });
138
+ } catch {
139
+ // expected
140
+ }
141
+ });
142
+
143
+ await waitFor(() => expect(result.current.isError).toBe(true));
144
+ });
145
+ });
146
+
147
+ describe("useRollbackMigration", () => {
148
+ it("should rollback a migration", async () => {
149
+ const rollback = vi
150
+ .fn()
151
+ .mockResolvedValue({ data: { message: "Rolled back" }, error: null });
152
+ const client = createMockClient({
153
+ admin: { migrations: { rollback } } as any,
154
+ });
155
+
156
+ const { result } = renderHook(() => useRollbackMigration(), {
157
+ wrapper: createWrapper(client),
158
+ });
159
+
160
+ await act(async () => {
161
+ await result.current.mutateAsync({ name: "001_init" });
162
+ });
163
+
164
+ expect(rollback).toHaveBeenCalledWith("001_init", undefined);
165
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
166
+ });
167
+
168
+ it("should handle errors", async () => {
169
+ const client = createMockClient({
170
+ admin: {
171
+ migrations: {
172
+ rollback: vi
173
+ .fn()
174
+ .mockResolvedValue({ data: null, error: new Error("Failed") }),
175
+ },
176
+ } as any,
177
+ });
178
+
179
+ const { result } = renderHook(() => useRollbackMigration(), {
180
+ wrapper: createWrapper(client),
181
+ });
182
+
183
+ await act(async () => {
184
+ try {
185
+ await result.current.mutateAsync({ name: "001_init" });
186
+ } catch {
187
+ // expected
188
+ }
189
+ });
190
+
191
+ await waitFor(() => expect(result.current.isError).toBe(true));
192
+ });
193
+ });
194
+
195
+ describe("useSyncMigrations", () => {
196
+ it("should sync migrations", async () => {
197
+ const mockResult = {
198
+ message: "Synced",
199
+ namespace: "default",
200
+ summary: {
201
+ created: 1,
202
+ updated: 0,
203
+ unchanged: 0,
204
+ skipped: 0,
205
+ applied: 0,
206
+ errors: 0,
207
+ },
208
+ details: {
209
+ created: ["001_init"],
210
+ updated: [],
211
+ unchanged: [],
212
+ skipped: [],
213
+ applied: [],
214
+ errors: [],
215
+ },
216
+ dry_run: false,
217
+ };
218
+ const sync = vi.fn().mockResolvedValue({ data: mockResult, error: null });
219
+ const client = createMockClient({
220
+ admin: { migrations: { sync } } as any,
221
+ });
222
+
223
+ const { result } = renderHook(() => useSyncMigrations(), {
224
+ wrapper: createWrapper(client),
225
+ });
226
+
227
+ await act(async () => {
228
+ await result.current.mutateAsync();
229
+ });
230
+
231
+ expect(sync).toHaveBeenCalledWith(undefined);
232
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
233
+ expect(result.current.data).toEqual(mockResult);
234
+ });
235
+
236
+ it("should pass options to sync", async () => {
237
+ const sync = vi
238
+ .fn()
239
+ .mockResolvedValue({ data: null, error: null });
240
+ const client = createMockClient({
241
+ admin: { migrations: { sync } } as any,
242
+ });
243
+
244
+ const { result } = renderHook(() => useSyncMigrations(), {
245
+ wrapper: createWrapper(client),
246
+ });
247
+
248
+ await act(async () => {
249
+ await result.current.mutateAsync({ auto_apply: true });
250
+ });
251
+
252
+ expect(sync).toHaveBeenCalledWith({ auto_apply: true });
253
+ });
254
+
255
+ it("should handle errors", async () => {
256
+ const client = createMockClient({
257
+ admin: {
258
+ migrations: {
259
+ sync: vi
260
+ .fn()
261
+ .mockResolvedValue({ data: null, error: new Error("Sync failed") }),
262
+ },
263
+ } as any,
264
+ });
265
+
266
+ const { result } = renderHook(() => useSyncMigrations(), {
267
+ wrapper: createWrapper(client),
268
+ });
269
+
270
+ await act(async () => {
271
+ try {
272
+ await result.current.mutateAsync();
273
+ } catch {
274
+ // expected
275
+ }
276
+ });
277
+
278
+ await waitFor(() => expect(result.current.isError).toBe(true));
279
+ });
280
+ });
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Migration hooks for Fluxbase React SDK
3
+ * Provides hooks for listing, applying, rolling back, and syncing database migrations
4
+ */
5
+
6
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
7
+ import { useFluxbaseClient } from "./context";
8
+ import type { SyncMigrationsOptions } from "@nimbleflux/fluxbase-sdk";
9
+
10
+ /**
11
+ * Hook to list migrations in a namespace
12
+ */
13
+ export function useMigrations(namespace?: string) {
14
+ const client = useFluxbaseClient();
15
+
16
+ return useQuery({
17
+ queryKey: ["fluxbase", "migrations", namespace],
18
+ queryFn: async () => {
19
+ const { data, error } = await client.admin.migrations.list(namespace);
20
+ if (error) throw error;
21
+ return data || [];
22
+ },
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Hook to apply a specific migration
28
+ */
29
+ export function useApplyMigration() {
30
+ const client = useFluxbaseClient();
31
+ const queryClient = useQueryClient();
32
+
33
+ return useMutation({
34
+ mutationFn: async ({
35
+ name,
36
+ namespace,
37
+ }: {
38
+ name: string;
39
+ namespace?: string;
40
+ }) => {
41
+ const { data, error } = await client.admin.migrations.apply(
42
+ name,
43
+ namespace,
44
+ );
45
+ if (error) throw error;
46
+ return data;
47
+ },
48
+ onSuccess: () => {
49
+ queryClient.invalidateQueries({
50
+ queryKey: ["fluxbase", "migrations"],
51
+ });
52
+ },
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Hook to rollback a specific migration
58
+ */
59
+ export function useRollbackMigration() {
60
+ const client = useFluxbaseClient();
61
+ const queryClient = useQueryClient();
62
+
63
+ return useMutation({
64
+ mutationFn: async ({
65
+ name,
66
+ namespace,
67
+ }: {
68
+ name: string;
69
+ namespace?: string;
70
+ }) => {
71
+ const { data, error } = await client.admin.migrations.rollback(
72
+ name,
73
+ namespace,
74
+ );
75
+ if (error) throw error;
76
+ return data;
77
+ },
78
+ onSuccess: () => {
79
+ queryClient.invalidateQueries({
80
+ queryKey: ["fluxbase", "migrations"],
81
+ });
82
+ },
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Hook to sync registered migrations
88
+ */
89
+ export function useSyncMigrations() {
90
+ const client = useFluxbaseClient();
91
+ const queryClient = useQueryClient();
92
+
93
+ return useMutation({
94
+ mutationFn: async (options: SyncMigrationsOptions | void) => {
95
+ const { data, error } = await client.admin.migrations.sync(
96
+ options ?? undefined,
97
+ );
98
+ if (error) throw error;
99
+ return data;
100
+ },
101
+ onSuccess: () => {
102
+ queryClient.invalidateQueries({
103
+ queryKey: ["fluxbase", "migrations"],
104
+ });
105
+ },
106
+ });
107
+ }