@nimbleflux/fluxbase-sdk-react 2026.3.6-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.
Files changed (42) hide show
  1. package/.nvmrc +1 -0
  2. package/README-ADMIN.md +1076 -0
  3. package/README.md +195 -0
  4. package/examples/AdminDashboard.tsx +513 -0
  5. package/examples/README.md +163 -0
  6. package/package.json +66 -0
  7. package/src/context.test.tsx +147 -0
  8. package/src/context.tsx +33 -0
  9. package/src/index.test.ts +255 -0
  10. package/src/index.ts +175 -0
  11. package/src/test-setup.ts +22 -0
  12. package/src/test-utils.tsx +215 -0
  13. package/src/use-admin-auth.test.ts +175 -0
  14. package/src/use-admin-auth.ts +187 -0
  15. package/src/use-admin-hooks.test.ts +457 -0
  16. package/src/use-admin-hooks.ts +309 -0
  17. package/src/use-auth-config.test.ts +145 -0
  18. package/src/use-auth-config.ts +101 -0
  19. package/src/use-auth.test.ts +313 -0
  20. package/src/use-auth.ts +164 -0
  21. package/src/use-captcha.test.ts +273 -0
  22. package/src/use-captcha.ts +250 -0
  23. package/src/use-client-keys.test.ts +286 -0
  24. package/src/use-client-keys.ts +185 -0
  25. package/src/use-graphql.test.ts +424 -0
  26. package/src/use-graphql.ts +392 -0
  27. package/src/use-query.test.ts +348 -0
  28. package/src/use-query.ts +211 -0
  29. package/src/use-realtime.test.ts +359 -0
  30. package/src/use-realtime.ts +180 -0
  31. package/src/use-saml.test.ts +269 -0
  32. package/src/use-saml.ts +221 -0
  33. package/src/use-storage.test.ts +549 -0
  34. package/src/use-storage.ts +508 -0
  35. package/src/use-table-export.ts +481 -0
  36. package/src/use-users.test.ts +264 -0
  37. package/src/use-users.ts +198 -0
  38. package/tsconfig.json +28 -0
  39. package/tsconfig.tsbuildinfo +1 -0
  40. package/tsup.config.ts +11 -0
  41. package/typedoc.json +33 -0
  42. package/vitest.config.ts +22 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Tests for client keys management hook
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+ import { renderHook, waitFor, act } from '@testing-library/react';
7
+ import { useClientKeys, useAPIKeys } from './use-client-keys';
8
+ import { createMockClient, createWrapper } from './test-utils';
9
+
10
+ describe('useClientKeys', () => {
11
+ it('should fetch client keys on mount when autoFetch is true', async () => {
12
+ const mockKeys = [
13
+ { id: '1', name: 'Key 1', description: 'First key' },
14
+ { id: '2', name: 'Key 2', description: 'Second key' },
15
+ ];
16
+ const listMock = vi.fn().mockResolvedValue({ client_keys: mockKeys });
17
+
18
+ const client = createMockClient({
19
+ admin: {
20
+ management: {
21
+ clientKeys: {
22
+ list: listMock,
23
+ create: vi.fn(),
24
+ update: vi.fn(),
25
+ revoke: vi.fn(),
26
+ delete: vi.fn(),
27
+ },
28
+ },
29
+ },
30
+ } as any);
31
+
32
+ const { result } = renderHook(
33
+ () => useClientKeys({ autoFetch: true }),
34
+ { wrapper: createWrapper(client) }
35
+ );
36
+
37
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
38
+ expect(result.current.keys).toEqual(mockKeys);
39
+ });
40
+
41
+ it('should not fetch keys when autoFetch is false', async () => {
42
+ const listMock = vi.fn();
43
+
44
+ const client = createMockClient({
45
+ admin: {
46
+ management: {
47
+ clientKeys: {
48
+ list: listMock,
49
+ create: vi.fn(),
50
+ update: vi.fn(),
51
+ revoke: vi.fn(),
52
+ delete: vi.fn(),
53
+ },
54
+ },
55
+ },
56
+ } as any);
57
+
58
+ const { result } = renderHook(
59
+ () => useClientKeys({ autoFetch: false }),
60
+ { wrapper: createWrapper(client) }
61
+ );
62
+
63
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
64
+ expect(listMock).not.toHaveBeenCalled();
65
+ });
66
+
67
+ it('should create client key', async () => {
68
+ const mockKeys: any[] = [];
69
+ const listMock = vi.fn().mockResolvedValue({ client_keys: mockKeys });
70
+ const createMock = vi.fn().mockResolvedValue({
71
+ key: 'new-secret-key',
72
+ client_key: { id: '1', name: 'New Key' },
73
+ });
74
+
75
+ const client = createMockClient({
76
+ admin: {
77
+ management: {
78
+ clientKeys: {
79
+ list: listMock,
80
+ create: createMock,
81
+ update: vi.fn(),
82
+ revoke: vi.fn(),
83
+ delete: vi.fn(),
84
+ },
85
+ },
86
+ },
87
+ } as any);
88
+
89
+ const { result } = renderHook(
90
+ () => useClientKeys({ autoFetch: true }),
91
+ { wrapper: createWrapper(client) }
92
+ );
93
+
94
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
95
+
96
+ let response;
97
+ await act(async () => {
98
+ response = await result.current.createKey({
99
+ name: 'New Key',
100
+ description: 'A new key',
101
+ scopes: ['read', 'write'],
102
+ rate_limit_per_minute: 60,
103
+ });
104
+ });
105
+
106
+ expect(createMock).toHaveBeenCalledWith({
107
+ name: 'New Key',
108
+ description: 'A new key',
109
+ scopes: ['read', 'write'],
110
+ rate_limit_per_minute: 60,
111
+ });
112
+ expect(response).toEqual({
113
+ key: 'new-secret-key',
114
+ keyData: { id: '1', name: 'New Key' },
115
+ });
116
+ // Should refetch after create
117
+ expect(listMock).toHaveBeenCalledTimes(2);
118
+ });
119
+
120
+ it('should update client key', async () => {
121
+ const mockKeys = [{ id: '1', name: 'Key 1' }];
122
+ const listMock = vi.fn().mockResolvedValue({ client_keys: mockKeys });
123
+ const updateMock = vi.fn().mockResolvedValue({});
124
+
125
+ const client = createMockClient({
126
+ admin: {
127
+ management: {
128
+ clientKeys: {
129
+ list: listMock,
130
+ create: vi.fn(),
131
+ update: updateMock,
132
+ revoke: vi.fn(),
133
+ delete: vi.fn(),
134
+ },
135
+ },
136
+ },
137
+ } as any);
138
+
139
+ const { result } = renderHook(
140
+ () => useClientKeys({ autoFetch: true }),
141
+ { wrapper: createWrapper(client) }
142
+ );
143
+
144
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
145
+
146
+ await act(async () => {
147
+ await result.current.updateKey('1', { name: 'Updated Key' });
148
+ });
149
+
150
+ expect(updateMock).toHaveBeenCalledWith('1', { name: 'Updated Key' });
151
+ // Should refetch after update
152
+ expect(listMock).toHaveBeenCalledTimes(2);
153
+ });
154
+
155
+ it('should revoke client key', async () => {
156
+ const mockKeys = [{ id: '1', name: 'Key 1' }];
157
+ const listMock = vi.fn().mockResolvedValue({ client_keys: mockKeys });
158
+ const revokeMock = vi.fn().mockResolvedValue({});
159
+
160
+ const client = createMockClient({
161
+ admin: {
162
+ management: {
163
+ clientKeys: {
164
+ list: listMock,
165
+ create: vi.fn(),
166
+ update: vi.fn(),
167
+ revoke: revokeMock,
168
+ delete: vi.fn(),
169
+ },
170
+ },
171
+ },
172
+ } as any);
173
+
174
+ const { result } = renderHook(
175
+ () => useClientKeys({ autoFetch: true }),
176
+ { wrapper: createWrapper(client) }
177
+ );
178
+
179
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
180
+
181
+ await act(async () => {
182
+ await result.current.revokeKey('1');
183
+ });
184
+
185
+ expect(revokeMock).toHaveBeenCalledWith('1');
186
+ // Should refetch after revoke
187
+ expect(listMock).toHaveBeenCalledTimes(2);
188
+ });
189
+
190
+ it('should delete client key', async () => {
191
+ const mockKeys = [{ id: '1', name: 'Key 1' }];
192
+ const listMock = vi.fn().mockResolvedValue({ client_keys: mockKeys });
193
+ const deleteMock = vi.fn().mockResolvedValue({});
194
+
195
+ const client = createMockClient({
196
+ admin: {
197
+ management: {
198
+ clientKeys: {
199
+ list: listMock,
200
+ create: vi.fn(),
201
+ update: vi.fn(),
202
+ revoke: vi.fn(),
203
+ delete: deleteMock,
204
+ },
205
+ },
206
+ },
207
+ } as any);
208
+
209
+ const { result } = renderHook(
210
+ () => useClientKeys({ autoFetch: true }),
211
+ { wrapper: createWrapper(client) }
212
+ );
213
+
214
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
215
+
216
+ await act(async () => {
217
+ await result.current.deleteKey('1');
218
+ });
219
+
220
+ expect(deleteMock).toHaveBeenCalledWith('1');
221
+ // Should refetch after delete
222
+ expect(listMock).toHaveBeenCalledTimes(2);
223
+ });
224
+
225
+ it('should handle fetch error', async () => {
226
+ const error = new Error('Failed to fetch');
227
+ const listMock = vi.fn().mockRejectedValue(error);
228
+
229
+ const client = createMockClient({
230
+ admin: {
231
+ management: {
232
+ clientKeys: {
233
+ list: listMock,
234
+ create: vi.fn(),
235
+ update: vi.fn(),
236
+ revoke: vi.fn(),
237
+ delete: vi.fn(),
238
+ },
239
+ },
240
+ },
241
+ } as any);
242
+
243
+ const { result } = renderHook(
244
+ () => useClientKeys({ autoFetch: true }),
245
+ { wrapper: createWrapper(client) }
246
+ );
247
+
248
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
249
+ expect(result.current.error).toBe(error);
250
+ });
251
+
252
+ it('should refetch on demand', async () => {
253
+ const listMock = vi.fn().mockResolvedValue({ client_keys: [] });
254
+
255
+ const client = createMockClient({
256
+ admin: {
257
+ management: {
258
+ clientKeys: {
259
+ list: listMock,
260
+ create: vi.fn(),
261
+ update: vi.fn(),
262
+ revoke: vi.fn(),
263
+ delete: vi.fn(),
264
+ },
265
+ },
266
+ },
267
+ } as any);
268
+
269
+ const { result } = renderHook(
270
+ () => useClientKeys({ autoFetch: false }),
271
+ { wrapper: createWrapper(client) }
272
+ );
273
+
274
+ await act(async () => {
275
+ await result.current.refetch();
276
+ });
277
+
278
+ expect(listMock).toHaveBeenCalledTimes(1);
279
+ });
280
+ });
281
+
282
+ describe('useAPIKeys (deprecated alias)', () => {
283
+ it('should be the same as useClientKeys', () => {
284
+ expect(useAPIKeys).toBe(useClientKeys);
285
+ });
286
+ });
@@ -0,0 +1,185 @@
1
+ import { useState, useEffect, useCallback } from 'react'
2
+ import { useFluxbaseClient } from './context'
3
+ import type { ClientKey, CreateClientKeyRequest } from '@fluxbase/sdk'
4
+
5
+ export interface UseClientKeysOptions {
6
+ /**
7
+ * Whether to automatically fetch client keys on mount
8
+ * @default true
9
+ */
10
+ autoFetch?: boolean
11
+ }
12
+
13
+ export interface UseClientKeysReturn {
14
+ /**
15
+ * Array of client keys
16
+ */
17
+ keys: ClientKey[]
18
+
19
+ /**
20
+ * Whether keys are being fetched
21
+ */
22
+ isLoading: boolean
23
+
24
+ /**
25
+ * Any error that occurred
26
+ */
27
+ error: Error | null
28
+
29
+ /**
30
+ * Refetch client keys
31
+ */
32
+ refetch: () => Promise<void>
33
+
34
+ /**
35
+ * Create a new client key
36
+ */
37
+ createKey: (request: CreateClientKeyRequest) => Promise<{ key: string; keyData: ClientKey }>
38
+
39
+ /**
40
+ * Update a client key
41
+ */
42
+ updateKey: (keyId: string, update: { name?: string; description?: string }) => Promise<void>
43
+
44
+ /**
45
+ * Revoke a client key
46
+ */
47
+ revokeKey: (keyId: string) => Promise<void>
48
+
49
+ /**
50
+ * Delete a client key
51
+ */
52
+ deleteKey: (keyId: string) => Promise<void>
53
+ }
54
+
55
+ /**
56
+ * Hook for managing client keys
57
+ *
58
+ * Provides client key list and management functions.
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * function ClientKeyManager() {
63
+ * const { keys, isLoading, createKey, revokeKey } = useClientKeys()
64
+ *
65
+ * const handleCreate = async () => {
66
+ * const { key, keyData } = await createKey({
67
+ * name: 'Backend Service',
68
+ * description: 'Client key for backend',
69
+ * expires_at: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString()
70
+ * })
71
+ * alert(`Key created: ${key}`)
72
+ * }
73
+ *
74
+ * return (
75
+ * <div>
76
+ * <button onClick={handleCreate}>Create Key</button>
77
+ * {keys.map(k => (
78
+ * <div key={k.id}>
79
+ * {k.name}
80
+ * <button onClick={() => revokeKey(k.id)}>Revoke</button>
81
+ * </div>
82
+ * ))}
83
+ * </div>
84
+ * )
85
+ * }
86
+ * ```
87
+ */
88
+ export function useClientKeys(options: UseClientKeysOptions = {}): UseClientKeysReturn {
89
+ const { autoFetch = true } = options
90
+ const client = useFluxbaseClient()
91
+
92
+ const [keys, setKeys] = useState<ClientKey[]>([])
93
+ const [isLoading, setIsLoading] = useState(autoFetch)
94
+ const [error, setError] = useState<Error | null>(null)
95
+
96
+ /**
97
+ * Fetch client keys from API
98
+ */
99
+ const fetchKeys = useCallback(async () => {
100
+ try {
101
+ setIsLoading(true)
102
+ setError(null)
103
+ const response = await client.admin.management.clientKeys.list()
104
+ setKeys(response.client_keys)
105
+ } catch (err) {
106
+ setError(err as Error)
107
+ } finally {
108
+ setIsLoading(false)
109
+ }
110
+ }, [client])
111
+
112
+ /**
113
+ * Create a new client key
114
+ */
115
+ const createKey = useCallback(
116
+ async (request: CreateClientKeyRequest): Promise<{ key: string; keyData: ClientKey }> => {
117
+ const response = await client.admin.management.clientKeys.create(request)
118
+ await fetchKeys() // Refresh list
119
+ return { key: response.key, keyData: response.client_key }
120
+ },
121
+ [client, fetchKeys]
122
+ )
123
+
124
+ /**
125
+ * Update a client key
126
+ */
127
+ const updateKey = useCallback(
128
+ async (keyId: string, update: { name?: string; description?: string }): Promise<void> => {
129
+ await client.admin.management.clientKeys.update(keyId, update)
130
+ await fetchKeys() // Refresh list
131
+ },
132
+ [client, fetchKeys]
133
+ )
134
+
135
+ /**
136
+ * Revoke a client key
137
+ */
138
+ const revokeKey = useCallback(
139
+ async (keyId: string): Promise<void> => {
140
+ await client.admin.management.clientKeys.revoke(keyId)
141
+ await fetchKeys() // Refresh list
142
+ },
143
+ [client, fetchKeys]
144
+ )
145
+
146
+ /**
147
+ * Delete a client key
148
+ */
149
+ const deleteKey = useCallback(
150
+ async (keyId: string): Promise<void> => {
151
+ await client.admin.management.clientKeys.delete(keyId)
152
+ await fetchKeys() // Refresh list
153
+ },
154
+ [client, fetchKeys]
155
+ )
156
+
157
+ // Auto-fetch on mount
158
+ useEffect(() => {
159
+ if (autoFetch) {
160
+ fetchKeys()
161
+ }
162
+ }, [autoFetch, fetchKeys])
163
+
164
+ return {
165
+ keys,
166
+ isLoading,
167
+ error,
168
+ refetch: fetchKeys,
169
+ createKey,
170
+ updateKey,
171
+ revokeKey,
172
+ deleteKey
173
+ }
174
+ }
175
+
176
+ /**
177
+ * @deprecated Use useClientKeys instead
178
+ */
179
+ export const useAPIKeys = useClientKeys
180
+
181
+ /** @deprecated Use UseClientKeysOptions instead */
182
+ export type UseAPIKeysOptions = UseClientKeysOptions
183
+
184
+ /** @deprecated Use UseClientKeysReturn instead */
185
+ export type UseAPIKeysReturn = UseClientKeysReturn