@fluxbase/sdk-react 2026.1.22-rc.9 → 2026.2.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/dist/index.d.mts +33 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +33 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +33 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -10
- package/src/context.test.tsx +147 -0
- package/src/index.test.ts +255 -0
- package/src/test-setup.ts +22 -0
- package/src/test-utils.tsx +215 -0
- package/src/use-admin-auth.test.ts +175 -0
- package/src/use-admin-auth.ts +10 -2
- package/src/use-admin-hooks.test.ts +457 -0
- package/src/use-auth-config.test.ts +145 -0
- package/src/use-auth.test.ts +313 -0
- package/src/use-auth.ts +2 -1
- package/src/use-captcha.test.ts +273 -0
- package/src/use-client-keys.test.ts +286 -0
- package/src/use-graphql.test.ts +424 -0
- package/src/use-query.test.ts +348 -0
- package/src/use-query.ts +50 -4
- package/src/use-realtime.test.ts +359 -0
- package/src/use-realtime.ts +20 -15
- package/src/use-saml.test.ts +269 -0
- package/src/use-storage.test.ts +549 -0
- package/src/use-storage.ts +10 -2
- package/src/use-users.test.ts +264 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for database query hooks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
+
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
7
|
+
import {
|
|
8
|
+
useFluxbaseQuery,
|
|
9
|
+
useTable,
|
|
10
|
+
useInsert,
|
|
11
|
+
useUpdate,
|
|
12
|
+
useUpsert,
|
|
13
|
+
useDelete,
|
|
14
|
+
} from './use-query';
|
|
15
|
+
import { createMockClient, createWrapper, createTestQueryClient } from './test-utils';
|
|
16
|
+
|
|
17
|
+
describe('useFluxbaseQuery', () => {
|
|
18
|
+
it('should execute query and return data', async () => {
|
|
19
|
+
const mockData = [{ id: 1, name: 'Test' }];
|
|
20
|
+
const executeMock = vi.fn().mockResolvedValue({ data: mockData, error: null });
|
|
21
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
22
|
+
select: vi.fn().mockReturnThis(),
|
|
23
|
+
execute: executeMock,
|
|
24
|
+
});
|
|
25
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
26
|
+
|
|
27
|
+
const { result } = renderHook(
|
|
28
|
+
() => useFluxbaseQuery((client) => client.from('products').select('*'), { queryKey: ['products'] }),
|
|
29
|
+
{ wrapper: createWrapper(client) }
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
33
|
+
expect(result.current.data).toEqual(mockData);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should throw error when query fails', async () => {
|
|
37
|
+
const error = new Error('Query failed');
|
|
38
|
+
const executeMock = vi.fn().mockResolvedValue({ data: null, error });
|
|
39
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
40
|
+
select: vi.fn().mockReturnThis(),
|
|
41
|
+
execute: executeMock,
|
|
42
|
+
});
|
|
43
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
44
|
+
|
|
45
|
+
const { result } = renderHook(
|
|
46
|
+
() => useFluxbaseQuery((client) => client.from('products').select('*'), { queryKey: ['products'] }),
|
|
47
|
+
{ wrapper: createWrapper(client) }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
51
|
+
expect(result.current.error).toBe(error);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle single item response', async () => {
|
|
55
|
+
const mockData = { id: 1, name: 'Test' };
|
|
56
|
+
const executeMock = vi.fn().mockResolvedValue({ data: mockData, error: null });
|
|
57
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
58
|
+
select: vi.fn().mockReturnThis(),
|
|
59
|
+
single: vi.fn().mockReturnThis(),
|
|
60
|
+
execute: executeMock,
|
|
61
|
+
});
|
|
62
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
63
|
+
|
|
64
|
+
const { result } = renderHook(
|
|
65
|
+
() => useFluxbaseQuery((client) => client.from('products').select('*'), { queryKey: ['product'] }),
|
|
66
|
+
{ wrapper: createWrapper(client) }
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
70
|
+
expect(result.current.data).toEqual([mockData]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle null response', async () => {
|
|
74
|
+
const executeMock = vi.fn().mockResolvedValue({ data: null, error: null });
|
|
75
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
76
|
+
select: vi.fn().mockReturnThis(),
|
|
77
|
+
execute: executeMock,
|
|
78
|
+
});
|
|
79
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
80
|
+
|
|
81
|
+
const { result } = renderHook(
|
|
82
|
+
() => useFluxbaseQuery((client) => client.from('products').select('*'), { queryKey: ['products'] }),
|
|
83
|
+
{ wrapper: createWrapper(client) }
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
87
|
+
expect(result.current.data).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should generate query key from function when not provided', async () => {
|
|
91
|
+
const mockData = [{ id: 1 }];
|
|
92
|
+
const executeMock = vi.fn().mockResolvedValue({ data: mockData, error: null });
|
|
93
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
94
|
+
select: vi.fn().mockReturnThis(),
|
|
95
|
+
execute: executeMock,
|
|
96
|
+
});
|
|
97
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
98
|
+
|
|
99
|
+
const buildQuery = (client: any) => client.from('test').select('*');
|
|
100
|
+
const { result } = renderHook(
|
|
101
|
+
() => useFluxbaseQuery(buildQuery),
|
|
102
|
+
{ wrapper: createWrapper(client) }
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
106
|
+
expect(result.current.data).toEqual(mockData);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('useTable', () => {
|
|
111
|
+
it('should query table with builder function', async () => {
|
|
112
|
+
const mockData = [{ id: 1, name: 'Test' }];
|
|
113
|
+
const executeMock = vi.fn().mockResolvedValue({ data: mockData, error: null });
|
|
114
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
115
|
+
select: vi.fn().mockReturnThis(),
|
|
116
|
+
eq: vi.fn().mockReturnThis(),
|
|
117
|
+
execute: executeMock,
|
|
118
|
+
});
|
|
119
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
120
|
+
|
|
121
|
+
const { result } = renderHook(
|
|
122
|
+
() => useTable('products', (q) => q.select('*').eq('active', true)),
|
|
123
|
+
{ wrapper: createWrapper(client) }
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
127
|
+
expect(result.current.data).toEqual(mockData);
|
|
128
|
+
expect(fromMock).toHaveBeenCalledWith('products');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should query table without builder function', async () => {
|
|
132
|
+
const mockData = [{ id: 1 }];
|
|
133
|
+
const executeMock = vi.fn().mockResolvedValue({ data: mockData, error: null });
|
|
134
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
135
|
+
execute: executeMock,
|
|
136
|
+
});
|
|
137
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
138
|
+
|
|
139
|
+
const { result } = renderHook(
|
|
140
|
+
() => useTable('products'),
|
|
141
|
+
{ wrapper: createWrapper(client) }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
145
|
+
expect(result.current.data).toEqual(mockData);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should support custom query key', async () => {
|
|
149
|
+
const mockData = [{ id: 1 }];
|
|
150
|
+
const executeMock = vi.fn().mockResolvedValue({ data: mockData, error: null });
|
|
151
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
152
|
+
execute: executeMock,
|
|
153
|
+
});
|
|
154
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
155
|
+
|
|
156
|
+
const queryClient = createTestQueryClient();
|
|
157
|
+
const { result } = renderHook(
|
|
158
|
+
() => useTable('products', undefined, { queryKey: ['custom', 'key'] }),
|
|
159
|
+
{ wrapper: createWrapper(client, queryClient) }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
163
|
+
expect(queryClient.getQueryData(['custom', 'key'])).toEqual(mockData);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('useInsert', () => {
|
|
168
|
+
it('should insert data and invalidate queries', async () => {
|
|
169
|
+
const mockResult = { id: 1, name: 'New Item' };
|
|
170
|
+
const insertMock = vi.fn().mockResolvedValue({ data: mockResult, error: null });
|
|
171
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
172
|
+
insert: insertMock,
|
|
173
|
+
});
|
|
174
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
175
|
+
|
|
176
|
+
const queryClient = createTestQueryClient();
|
|
177
|
+
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
|
178
|
+
|
|
179
|
+
const { result } = renderHook(() => useInsert('products'), {
|
|
180
|
+
wrapper: createWrapper(client, queryClient),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await act(async () => {
|
|
184
|
+
await result.current.mutateAsync({ name: 'New Item' });
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(fromMock).toHaveBeenCalledWith('products');
|
|
188
|
+
expect(insertMock).toHaveBeenCalledWith({ name: 'New Item' });
|
|
189
|
+
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['fluxbase', 'table', 'products'] });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should throw error on insert failure', async () => {
|
|
193
|
+
const error = new Error('Insert failed');
|
|
194
|
+
const insertMock = vi.fn().mockResolvedValue({ data: null, error });
|
|
195
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
196
|
+
insert: insertMock,
|
|
197
|
+
});
|
|
198
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
199
|
+
|
|
200
|
+
const { result } = renderHook(() => useInsert('products'), {
|
|
201
|
+
wrapper: createWrapper(client),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await expect(act(async () => {
|
|
205
|
+
await result.current.mutateAsync({ name: 'New Item' });
|
|
206
|
+
})).rejects.toThrow('Insert failed');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('useUpdate', () => {
|
|
211
|
+
it('should update data and invalidate queries', async () => {
|
|
212
|
+
const mockResult = { id: 1, name: 'Updated' };
|
|
213
|
+
const updateMock = vi.fn().mockResolvedValue({ data: mockResult, error: null });
|
|
214
|
+
const eqMock = vi.fn().mockReturnThis();
|
|
215
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
216
|
+
eq: eqMock,
|
|
217
|
+
update: updateMock,
|
|
218
|
+
});
|
|
219
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
220
|
+
|
|
221
|
+
const queryClient = createTestQueryClient();
|
|
222
|
+
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
|
223
|
+
|
|
224
|
+
const { result } = renderHook(() => useUpdate('products'), {
|
|
225
|
+
wrapper: createWrapper(client, queryClient),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
await act(async () => {
|
|
229
|
+
await result.current.mutateAsync({
|
|
230
|
+
data: { name: 'Updated' },
|
|
231
|
+
buildQuery: (q) => q.eq('id', 1),
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(fromMock).toHaveBeenCalledWith('products');
|
|
236
|
+
expect(updateMock).toHaveBeenCalledWith({ name: 'Updated' });
|
|
237
|
+
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['fluxbase', 'table', 'products'] });
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should throw error on update failure', async () => {
|
|
241
|
+
const error = new Error('Update failed');
|
|
242
|
+
const updateMock = vi.fn().mockResolvedValue({ data: null, error });
|
|
243
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
244
|
+
eq: vi.fn().mockReturnThis(),
|
|
245
|
+
update: updateMock,
|
|
246
|
+
});
|
|
247
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
248
|
+
|
|
249
|
+
const { result } = renderHook(() => useUpdate('products'), {
|
|
250
|
+
wrapper: createWrapper(client),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await expect(act(async () => {
|
|
254
|
+
await result.current.mutateAsync({
|
|
255
|
+
data: { name: 'Updated' },
|
|
256
|
+
buildQuery: (q) => q.eq('id', 1),
|
|
257
|
+
});
|
|
258
|
+
})).rejects.toThrow('Update failed');
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('useUpsert', () => {
|
|
263
|
+
it('should upsert data and invalidate queries', async () => {
|
|
264
|
+
const mockResult = { id: 1, name: 'Upserted' };
|
|
265
|
+
const upsertMock = vi.fn().mockResolvedValue({ data: mockResult, error: null });
|
|
266
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
267
|
+
upsert: upsertMock,
|
|
268
|
+
});
|
|
269
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
270
|
+
|
|
271
|
+
const queryClient = createTestQueryClient();
|
|
272
|
+
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
|
273
|
+
|
|
274
|
+
const { result } = renderHook(() => useUpsert('products'), {
|
|
275
|
+
wrapper: createWrapper(client, queryClient),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await act(async () => {
|
|
279
|
+
await result.current.mutateAsync({ id: 1, name: 'Upserted' });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(fromMock).toHaveBeenCalledWith('products');
|
|
283
|
+
expect(upsertMock).toHaveBeenCalledWith({ id: 1, name: 'Upserted' });
|
|
284
|
+
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['fluxbase', 'table', 'products'] });
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should throw error on upsert failure', async () => {
|
|
288
|
+
const error = new Error('Upsert failed');
|
|
289
|
+
const upsertMock = vi.fn().mockResolvedValue({ data: null, error });
|
|
290
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
291
|
+
upsert: upsertMock,
|
|
292
|
+
});
|
|
293
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
294
|
+
|
|
295
|
+
const { result } = renderHook(() => useUpsert('products'), {
|
|
296
|
+
wrapper: createWrapper(client),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
await expect(act(async () => {
|
|
300
|
+
await result.current.mutateAsync({ id: 1, name: 'Upserted' });
|
|
301
|
+
})).rejects.toThrow('Upsert failed');
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('useDelete', () => {
|
|
306
|
+
it('should delete data and invalidate queries', async () => {
|
|
307
|
+
const deleteMock = vi.fn().mockResolvedValue({ data: null, error: null });
|
|
308
|
+
const eqMock = vi.fn().mockReturnThis();
|
|
309
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
310
|
+
eq: eqMock,
|
|
311
|
+
delete: deleteMock,
|
|
312
|
+
});
|
|
313
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
314
|
+
|
|
315
|
+
const queryClient = createTestQueryClient();
|
|
316
|
+
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
|
317
|
+
|
|
318
|
+
const { result } = renderHook(() => useDelete('products'), {
|
|
319
|
+
wrapper: createWrapper(client, queryClient),
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
await act(async () => {
|
|
323
|
+
await result.current.mutateAsync((q) => q.eq('id', 1));
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(fromMock).toHaveBeenCalledWith('products');
|
|
327
|
+
expect(deleteMock).toHaveBeenCalled();
|
|
328
|
+
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['fluxbase', 'table', 'products'] });
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should throw error on delete failure', async () => {
|
|
332
|
+
const error = new Error('Delete failed');
|
|
333
|
+
const deleteMock = vi.fn().mockResolvedValue({ error });
|
|
334
|
+
const fromMock = vi.fn().mockReturnValue({
|
|
335
|
+
eq: vi.fn().mockReturnThis(),
|
|
336
|
+
delete: deleteMock,
|
|
337
|
+
});
|
|
338
|
+
const client = createMockClient({ from: fromMock } as any);
|
|
339
|
+
|
|
340
|
+
const { result } = renderHook(() => useDelete('products'), {
|
|
341
|
+
wrapper: createWrapper(client),
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await expect(act(async () => {
|
|
345
|
+
await result.current.mutateAsync((q) => q.eq('id', 1));
|
|
346
|
+
})).rejects.toThrow('Delete failed');
|
|
347
|
+
});
|
|
348
|
+
});
|
package/src/use-query.ts
CHANGED
|
@@ -17,6 +17,18 @@ export interface UseFluxbaseQueryOptions<T> extends Omit<UseQueryOptions<T[], Er
|
|
|
17
17
|
* Hook to execute a database query
|
|
18
18
|
* @param buildQuery - Function that builds and returns the query
|
|
19
19
|
* @param options - React Query options
|
|
20
|
+
*
|
|
21
|
+
* IMPORTANT: You must provide a stable `queryKey` in options for proper caching.
|
|
22
|
+
* Without a custom queryKey, each render may create a new cache entry.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* // Always provide a queryKey for stable caching
|
|
27
|
+
* useFluxbaseQuery(
|
|
28
|
+
* (client) => client.from('users').select('*'),
|
|
29
|
+
* { queryKey: ['users', 'all'] }
|
|
30
|
+
* )
|
|
31
|
+
* ```
|
|
20
32
|
*/
|
|
21
33
|
export function useFluxbaseQuery<T = any>(
|
|
22
34
|
buildQuery: (client: ReturnType<typeof useFluxbaseClient>) => QueryBuilder<T>,
|
|
@@ -24,8 +36,16 @@ export function useFluxbaseQuery<T = any>(
|
|
|
24
36
|
) {
|
|
25
37
|
const client = useFluxbaseClient()
|
|
26
38
|
|
|
27
|
-
//
|
|
28
|
-
|
|
39
|
+
// Require queryKey for stable caching - function.toString() is not reliable
|
|
40
|
+
// as it can vary between renders for inline functions
|
|
41
|
+
if (!options?.queryKey) {
|
|
42
|
+
console.warn(
|
|
43
|
+
'[useFluxbaseQuery] No queryKey provided. This may cause cache misses. ' +
|
|
44
|
+
'Please provide a stable queryKey in options.'
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const queryKey = options?.queryKey || ['fluxbase', 'query', 'unstable']
|
|
29
49
|
|
|
30
50
|
return useQuery({
|
|
31
51
|
queryKey,
|
|
@@ -46,7 +66,23 @@ export function useFluxbaseQuery<T = any>(
|
|
|
46
66
|
/**
|
|
47
67
|
* Hook for table queries with a simpler API
|
|
48
68
|
* @param table - Table name
|
|
49
|
-
* @param buildQuery -
|
|
69
|
+
* @param buildQuery - Optional function to build the query (e.g., add filters)
|
|
70
|
+
* @param options - Query options including a stable queryKey
|
|
71
|
+
*
|
|
72
|
+
* NOTE: When using buildQuery with filters, provide a custom queryKey that includes
|
|
73
|
+
* the filter values to ensure proper caching.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```tsx
|
|
77
|
+
* // Simple query - queryKey is auto-generated from table name
|
|
78
|
+
* useTable('users')
|
|
79
|
+
*
|
|
80
|
+
* // With filters - provide queryKey including filter values
|
|
81
|
+
* useTable('users',
|
|
82
|
+
* (q) => q.eq('status', 'active'),
|
|
83
|
+
* { queryKey: ['users', 'active'] }
|
|
84
|
+
* )
|
|
85
|
+
* ```
|
|
50
86
|
*/
|
|
51
87
|
export function useTable<T = any>(
|
|
52
88
|
table: string,
|
|
@@ -55,6 +91,15 @@ export function useTable<T = any>(
|
|
|
55
91
|
) {
|
|
56
92
|
const client = useFluxbaseClient()
|
|
57
93
|
|
|
94
|
+
// Generate a stable base queryKey from table name
|
|
95
|
+
// When buildQuery is provided without a custom queryKey, warn about potential cache issues
|
|
96
|
+
if (buildQuery && !options?.queryKey) {
|
|
97
|
+
console.warn(
|
|
98
|
+
`[useTable] Using buildQuery without a custom queryKey for table "${table}". ` +
|
|
99
|
+
'This may cause cache misses. Provide a queryKey that includes your filter values.'
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
58
103
|
return useFluxbaseQuery(
|
|
59
104
|
(client) => {
|
|
60
105
|
const query = client.from<T>(table)
|
|
@@ -62,7 +107,8 @@ export function useTable<T = any>(
|
|
|
62
107
|
},
|
|
63
108
|
{
|
|
64
109
|
...options,
|
|
65
|
-
|
|
110
|
+
// Use table name as base key, or custom key if provided
|
|
111
|
+
queryKey: options?.queryKey || ['fluxbase', 'table', table],
|
|
66
112
|
}
|
|
67
113
|
)
|
|
68
114
|
}
|