@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.
- package/.nvmrc +1 -0
- package/README-ADMIN.md +1076 -0
- package/README.md +195 -0
- package/examples/AdminDashboard.tsx +513 -0
- package/examples/README.md +163 -0
- package/package.json +66 -0
- package/src/context.test.tsx +147 -0
- package/src/context.tsx +33 -0
- package/src/index.test.ts +255 -0
- package/src/index.ts +175 -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 +187 -0
- package/src/use-admin-hooks.test.ts +457 -0
- package/src/use-admin-hooks.ts +309 -0
- package/src/use-auth-config.test.ts +145 -0
- package/src/use-auth-config.ts +101 -0
- package/src/use-auth.test.ts +313 -0
- package/src/use-auth.ts +164 -0
- package/src/use-captcha.test.ts +273 -0
- package/src/use-captcha.ts +250 -0
- package/src/use-client-keys.test.ts +286 -0
- package/src/use-client-keys.ts +185 -0
- package/src/use-graphql.test.ts +424 -0
- package/src/use-graphql.ts +392 -0
- package/src/use-query.test.ts +348 -0
- package/src/use-query.ts +211 -0
- package/src/use-realtime.test.ts +359 -0
- package/src/use-realtime.ts +180 -0
- package/src/use-saml.test.ts +269 -0
- package/src/use-saml.ts +221 -0
- package/src/use-storage.test.ts +549 -0
- package/src/use-storage.ts +508 -0
- package/src/use-table-export.ts +481 -0
- package/src/use-users.test.ts +264 -0
- package/src/use-users.ts +198 -0
- package/tsconfig.json +28 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +11 -0
- package/typedoc.json +33 -0
- 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
|