@makolabs/ripple 1.2.10 → 1.2.11

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.
@@ -1,75 +1,18 @@
1
- /**
2
- * User Management Remote Functions
3
- *
4
- * Complete implementation template for user management remote functions.
5
- * This follows the same pattern as users.remote.ts and is designed to be
6
- * used as-is in SvelteKit applications like sharkfin-frontend.
7
- *
8
- * Configuration:
9
- * - Set environment variables: CLERK_SECRET_KEY, ADMIN_API_KEY, PRIVATE_BASE_AUTH_URL, ALLOWED_ORG_ID
10
- * - Configure CLIENT_ID via ADMIN_CONFIG or env variable (CLIENT_ID)
11
- * - Adjust permission prefix filter via PERMISSION_PREFIX env variable
12
- *
13
- * @see https://svelte.dev/docs/kit/remote-functions
14
- *
15
- * Usage in your app:
16
- * ```svelte
17
- * <script>
18
- * import { UserManagement } from '@makolabs/ripple';
19
- * import * as adapter from '../UserManagement.remote';
20
- * </script>
21
- *
22
- * <UserManagement adapter={adapter} />
23
- * ```
24
- */
25
1
  import type { User } from '../user-management.js';
26
2
  import type { GetUsersOptions, GetUsersResult } from './types.js';
27
- /**
28
- * Get users with pagination and sorting
29
- *
30
- * Transforms adapter options to Clerk API format
31
- */
32
3
  export declare const getUsers: import("@sveltejs/kit").RemoteQueryFunction<GetUsersOptions, GetUsersResult>;
33
- /**
34
- * Create a new user
35
- *
36
- * Creates user in Clerk, optionally adds to organization, and creates admin key with permissions
37
- */
38
4
  export declare const createUser: import("@sveltejs/kit").RemoteCommand<Partial<User>, Promise<User>>;
39
- /**
40
- * Update an existing user
41
- */
42
5
  export declare const updateUser: import("@sveltejs/kit").RemoteCommand<{
43
6
  userId: string;
44
7
  userData: Partial<User>;
45
8
  }, Promise<User>>;
46
- /**
47
- * Delete a single user
48
- */
49
9
  export declare const deleteUser: import("@sveltejs/kit").RemoteCommand<string, Promise<void>>;
50
- /**
51
- * Delete multiple users
52
- */
53
10
  export declare const deleteUsers: import("@sveltejs/kit").RemoteCommand<string[], Promise<void>>;
54
- /**
55
- * Get permissions for specific users (batched)
56
- *
57
- * Uses 'sub' (userId) parameter in admin API calls.
58
- * Batches multiple permission requests to avoid n+1 problem.
59
- */
60
11
  export declare const getUserPermissions: import("@sveltejs/kit").RemoteQueryFunction<string, Promise<string[]>>;
61
- /**
62
- * Update permissions for a specific user
63
- *
64
- * Uses 'sub' (userId) parameter in admin API calls
65
- */
66
12
  export declare const updateUserPermissions: import("@sveltejs/kit").RemoteCommand<{
67
13
  userId: string;
68
14
  permissions: string[];
69
15
  }, Promise<void>>;
70
- /**
71
- * Generate new API key for a user with optional old key revocation
72
- */
73
16
  export declare const generateApiKey: import("@sveltejs/kit").RemoteCommand<{
74
17
  userId: string;
75
18
  permissions: string[];
@@ -1,46 +1,18 @@
1
- /**
2
- * User Management Remote Functions
3
- *
4
- * Complete implementation template for user management remote functions.
5
- * This follows the same pattern as users.remote.ts and is designed to be
6
- * used as-is in SvelteKit applications like sharkfin-frontend.
7
- *
8
- * Configuration:
9
- * - Set environment variables: CLERK_SECRET_KEY, ADMIN_API_KEY, PRIVATE_BASE_AUTH_URL, ALLOWED_ORG_ID
10
- * - Configure CLIENT_ID via ADMIN_CONFIG or env variable (CLIENT_ID)
11
- * - Adjust permission prefix filter via PERMISSION_PREFIX env variable
12
- *
13
- * @see https://svelte.dev/docs/kit/remote-functions
14
- *
15
- * Usage in your app:
16
- * ```svelte
17
- * <script>
18
- * import { UserManagement } from '@makolabs/ripple';
19
- * import * as adapter from '../UserManagement.remote';
20
- * </script>
21
- *
22
- * <UserManagement adapter={adapter} />
23
- * ```
24
- */
25
1
  import { query, command } from '$app/server';
26
2
  import { env } from '$env/dynamic/private';
27
- /**
28
- * Configuration
29
- * Override these values or import from your config file
30
- */
31
- // Option 1: Import from your config (recommended)
32
- // import { ADMIN_CONFIG } from '../../constants/permissions';
33
- // const CLIENT_ID = ADMIN_CONFIG.CLIENT_ID;
34
- // Option 2: Use environment variable
35
- const CLIENT_ID = env.CLIENT_ID || 'sharkfin'; // Default fallback
36
- // Permission prefix filter - adjust based on your permission structure
37
- // Example: 'sharkfin:' filters to only sharkfin permissions
3
+ const CLIENT_ID = env.CLIENT_ID || 'sharkfin';
38
4
  const PERMISSION_PREFIX = env.PERMISSION_PREFIX || 'sharkfin:';
39
- // Organization ID for adding users to organizations
40
5
  const ORGANIZATION_ID = env.ALLOWED_ORG_ID;
41
- /**
42
- * Helper: Make authenticated request to Clerk API
43
- */
6
+ function handleClerkError(error, defaultMessage) {
7
+ if (error && typeof error === 'object' && 'status' in error && 'details' in error) {
8
+ const enrichedError = new Error('message' in error && typeof error.message === 'string' ? error.message : 'Unknown error');
9
+ enrichedError.status = error.status;
10
+ enrichedError.details = error.details;
11
+ enrichedError.clerkError = true;
12
+ throw enrichedError;
13
+ }
14
+ throw new Error(`${defaultMessage}: ${error instanceof Error ? error.message : 'Unknown error'}`);
15
+ }
44
16
  async function makeClerkRequest(endpoint, options = {}) {
45
17
  const CLERK_SECRET_KEY = env.CLERK_SECRET_KEY;
46
18
  if (!CLERK_SECRET_KEY) {
@@ -56,8 +28,7 @@ async function makeClerkRequest(endpoint, options = {}) {
56
28
  });
57
29
  if (!response.ok) {
58
30
  const errorText = await response.text();
59
- console.error(`❌ [Clerk API] ${response.status} ${response.statusText} - ${errorText}`);
60
- // Try to parse error details
31
+ console.error(`[Clerk API] ${response.status} ${response.statusText} - ${errorText}`);
61
32
  let errorDetails;
62
33
  try {
63
34
  errorDetails = JSON.parse(errorText);
@@ -65,7 +36,6 @@ async function makeClerkRequest(endpoint, options = {}) {
65
36
  catch {
66
37
  errorDetails = { message: errorText || `${response.status} ${response.statusText}` };
67
38
  }
68
- // Throw error with status and details for better frontend handling
69
39
  const error = new Error(errorDetails.message || `Clerk API request failed: ${response.status} ${response.statusText}`);
70
40
  error.status = response.status;
71
41
  error.details = errorDetails;
@@ -73,9 +43,6 @@ async function makeClerkRequest(endpoint, options = {}) {
73
43
  }
74
44
  return response.json();
75
45
  }
76
- /**
77
- * Helper: Make authenticated request to Admin API
78
- */
79
46
  async function makeAdminRequest(endpoint, options = {}) {
80
47
  const ADMIN_API_KEY = env.ADMIN_API_KEY;
81
48
  const PRIVATE_BASE_AUTH_URL = env.PRIVATE_BASE_AUTH_URL;
@@ -98,60 +65,39 @@ async function makeAdminRequest(endpoint, options = {}) {
98
65
  });
99
66
  if (!response.ok) {
100
67
  const errorText = await response.text();
101
- console.error(`❌ [Admin API] ${response.status} ${response.statusText} - ${errorText}`);
68
+ console.error(`[Admin API] ${response.status} ${response.statusText} - ${errorText}`);
102
69
  throw new Error(`Admin API request failed: ${response.status} ${response.statusText} - ${errorText}`);
103
70
  }
104
71
  return response.json();
105
72
  }
106
- /**
107
- * Helper: Create admin key with permissions for a user
108
- *
109
- * @param userId - The user ID (used as 'sub' in admin API)
110
- * @param permissions - Array of permission strings
111
- * @param clientId - Client ID (defaults to CLIENT_ID config)
112
- */
113
73
  async function createUserPermissions(userId, permissions, clientId = CLIENT_ID) {
114
- console.log(`🔑 [createUserPermissions] Creating admin key for user ${userId}`);
115
- // Filter permissions by prefix if configured
116
74
  const filteredPermissions = PERMISSION_PREFIX
117
75
  ? permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
118
76
  : permissions;
119
77
  if (filteredPermissions.length === 0) {
120
- console.log(`⚠️ [createUserPermissions] No ${PERMISSION_PREFIX || ''} permissions provided, skipping`);
121
78
  return null;
122
79
  }
123
- const createData = await makeAdminRequest('/admin/keys', {
80
+ return await makeAdminRequest('/admin/keys', {
124
81
  method: 'POST',
125
82
  body: JSON.stringify({
126
83
  client_id: clientId,
127
- sub: userId, // userId is used as 'sub' parameter
84
+ sub: userId,
128
85
  scopes: filteredPermissions
129
86
  })
130
87
  });
131
- console.log(`✅ [createUserPermissions] Admin key created successfully for user ${userId}`);
132
- return createData;
133
88
  }
134
- /**
135
- * Get users with pagination and sorting
136
- *
137
- * Transforms adapter options to Clerk API format
138
- */
139
89
  export const getUsers = query('unchecked', async (options) => {
140
- console.log('🔍 [getUsers] Fetching users with options:', options);
141
90
  try {
142
- // Transform adapter options to Clerk API format
143
91
  const limit = options.pageSize;
144
92
  const offset = (options.page - 1) * options.pageSize;
145
- // Build orderBy string
146
93
  let orderBy = '';
147
94
  if (options.sortBy) {
148
95
  const prefix = options.sortOrder === 'desc' ? '-' : '';
149
96
  orderBy = `${prefix}${options.sortBy}`;
150
97
  }
151
98
  else {
152
- orderBy = '-created_at'; // Default
99
+ orderBy = '-created_at';
153
100
  }
154
- // Build Clerk API parameters
155
101
  const params = new URLSearchParams({
156
102
  limit: limit.toString(),
157
103
  offset: offset.toString(),
@@ -159,91 +105,66 @@ export const getUsers = query('unchecked', async (options) => {
159
105
  });
160
106
  if (options.query)
161
107
  params.append('query', options.query);
162
- // Fetch users and count in parallel
163
108
  const [usersData, countData] = await Promise.all([
164
109
  makeClerkRequest(`/users?${params}`),
165
110
  makeClerkRequest(`/users/count?${params}`)
166
111
  ]);
167
- console.log(`✅ [getUsers] Fetched ${usersData.length} users, total: ${countData.total_count}`);
168
112
  return {
169
113
  users: usersData,
170
114
  totalUsers: countData.total_count || 0
171
115
  };
172
116
  }
173
117
  catch (error) {
174
- console.error('[getUsers] Error:', error);
118
+ console.error('[getUsers] Error:', error);
175
119
  throw new Error(`Failed to fetch users: ${error instanceof Error ? error.message : 'Unknown error'}`);
176
120
  }
177
121
  });
178
- /**
179
- * Create a new user
180
- *
181
- * Creates user in Clerk, optionally adds to organization, and creates admin key with permissions
182
- */
183
122
  export const createUser = command('unchecked', async (userData) => {
184
- // Transform User to Clerk API format
185
123
  const emailAddress = userData.email_addresses?.[0]?.email_address || '';
186
124
  if (!emailAddress) {
187
125
  throw new Error('Email address is required');
188
126
  }
189
- console.log('📝 [createUser] Creating new user:', emailAddress);
190
127
  try {
191
- // Extract permissions from userData
192
128
  const { permissions, ...userDataOnly } = userData;
193
- // Transform userData to match Clerk Backend API format (snake_case)
194
129
  const clerkUserData = {
195
130
  first_name: userDataOnly.first_name || '',
196
131
  last_name: userDataOnly.last_name || '',
197
- email_address: [emailAddress], // Clerk expects an array
132
+ email_address: [emailAddress],
198
133
  ...(userDataOnly.username && { username: userDataOnly.username }),
199
134
  ...(userDataOnly.private_metadata && { private_metadata: userDataOnly.private_metadata })
200
135
  };
201
- // Step 1: Create user in Clerk
202
- console.log('📝 [createUser] Creating user in Clerk...');
203
136
  let result = await makeClerkRequest('/users', {
204
137
  method: 'POST',
205
138
  body: JSON.stringify(clerkUserData)
206
139
  });
207
- // Step 1.5: Add user to organization if configured
208
140
  if (ORGANIZATION_ID) {
209
141
  try {
210
- console.log(`🏢 [createUser] Adding user ${result.id} to organization ${ORGANIZATION_ID}...`);
211
142
  await makeClerkRequest(`/organizations/${ORGANIZATION_ID}/memberships`, {
212
143
  method: 'POST',
213
144
  body: JSON.stringify({
214
145
  user_id: result.id,
215
- role: 'org:member' // Standard Clerk organization role
146
+ role: 'org:member'
216
147
  })
217
148
  });
218
- console.log(`✅ [createUser] User added to organization successfully`);
219
149
  }
220
150
  catch (orgError) {
221
- console.error(`❌ [createUser] Failed to add user to organization:`, orgError);
222
- // Clean up: delete the user since they can't be added to the org
151
+ console.error('[createUser] Failed to add user to organization:', orgError);
223
152
  try {
224
153
  await makeClerkRequest(`/users/${result.id}`, {
225
154
  method: 'DELETE'
226
155
  });
227
- console.log(`🗑️ [createUser] User ${result.id} deleted due to org membership failure`);
228
156
  }
229
157
  catch (deleteError) {
230
- console.error(`❌ [createUser] Failed to delete user after org membership failure:`, deleteError);
158
+ console.error('[createUser] Failed to delete user after org membership failure:', deleteError);
231
159
  }
232
160
  throw new Error(`Failed to add user to organization. User creation rolled back. Error: ${orgError instanceof Error ? orgError.message : String(orgError)}`);
233
161
  }
234
162
  }
235
- else {
236
- console.warn('⚠️ [createUser] ALLOWED_ORG_ID not configured, skipping organization membership');
237
- }
238
- // Step 2: Create admin key with permissions if permissions are provided
239
163
  if (permissions && permissions.length > 0) {
240
164
  try {
241
- console.log(`🔑 [createUser] Creating permissions for new user: ${result.id}`);
242
165
  const adminKeyResult = await createUserPermissions(result.id, permissions);
243
- // Extract the API key from the admin service response
244
166
  const apiKey = adminKeyResult?.data?.key;
245
167
  if (adminKeyResult && apiKey) {
246
- // Update user's private metadata with the API key
247
168
  const updatedUser = await makeClerkRequest(`/users/${result.id}`, {
248
169
  method: 'PATCH',
249
170
  body: JSON.stringify({
@@ -253,60 +174,34 @@ export const createUser = command('unchecked', async (userData) => {
253
174
  }
254
175
  })
255
176
  });
256
- // Update the result with the updated user data
257
177
  result = updatedUser;
258
- console.log(`✅ [createUser] API key stored in user's private metadata`);
259
178
  }
260
- else {
261
- console.warn(`⚠️ [createUser] No API key found in admin key result`);
262
- }
263
- // Add permission info to result
264
179
  result.adminKey = adminKeyResult;
265
180
  result.permissionsAssigned = permissions;
266
- console.log(`✅ [createUser] User ${result.id} created with permissions successfully`);
267
181
  }
268
182
  catch (permissionError) {
269
- console.error(`❌ [createUser] Failed to assign permissions:`, permissionError);
270
- // Don't fail the entire operation, but warn
183
+ console.error('[createUser] Failed to assign permissions:', permissionError);
271
184
  result.warning = 'User created but permissions assignment failed';
272
185
  result.permissionError =
273
186
  permissionError instanceof Error ? permissionError.message : String(permissionError);
274
187
  }
275
188
  }
276
- else {
277
- console.log(`⚠️ [createUser] User ${result.id} created without permissions`);
278
- }
279
189
  return result;
280
190
  }
281
191
  catch (error) {
282
- console.error('[createUser] Error:', error);
283
- // Handle Clerk API errors with detailed information
284
- if (error && typeof error === 'object' && 'status' in error && 'details' in error) {
285
- const enrichedError = new Error('message' in error && typeof error.message === 'string' ? error.message : 'Unknown error');
286
- enrichedError.status = error.status;
287
- enrichedError.details = error.details;
288
- enrichedError.clerkError = true;
289
- throw enrichedError;
290
- }
291
- throw new Error(`Failed to create user: ${error instanceof Error ? error.message : 'Unknown error'}`);
192
+ console.error('[createUser] Error:', error);
193
+ handleClerkError(error, 'Failed to create user');
292
194
  }
293
195
  });
294
- /**
295
- * Update an existing user
296
- */
297
196
  export const updateUser = command('unchecked', async (options) => {
298
197
  const { userId, userData } = options;
299
- console.log(`📝 [updateUser] Updating user ${userId}`);
300
198
  try {
301
- // Transform User to Clerk API format
302
- // Only include fields that Clerk accepts
303
199
  const updateData = {};
304
200
  if (userData.first_name !== undefined)
305
201
  updateData.first_name = userData.first_name;
306
202
  if (userData.last_name !== undefined)
307
203
  updateData.last_name = userData.last_name;
308
204
  if (userData.username !== undefined && userData.username !== '') {
309
- // Only include username if it's not empty (prevents "username already exists" error)
310
205
  updateData.username = userData.username;
311
206
  }
312
207
  if (userData.private_metadata !== undefined) {
@@ -316,79 +211,49 @@ export const updateUser = command('unchecked', async (options) => {
316
211
  method: 'PATCH',
317
212
  body: JSON.stringify(updateData)
318
213
  });
319
- // If permissions changed, update them separately
320
214
  if (userData.permissions !== undefined) {
321
215
  try {
322
216
  await updateUserPermissions({ userId, permissions: userData.permissions });
323
217
  }
324
218
  catch (permError) {
325
- console.error(`❌ [updateUser] Failed to update permissions:`, permError);
326
- // Don't fail the entire operation
219
+ console.error('[updateUser] Failed to update permissions:', permError);
327
220
  }
328
221
  }
329
- console.log(`✅ [updateUser] User ${userId} updated successfully`);
330
222
  return result;
331
223
  }
332
224
  catch (error) {
333
- console.error('[updateUser] Error:', error);
334
- // Handle Clerk API errors with detailed information
335
- if (error && typeof error === 'object' && 'status' in error && 'details' in error) {
336
- const enrichedError = new Error('message' in error && typeof error.message === 'string' ? error.message : 'Unknown error');
337
- enrichedError.status = error.status;
338
- enrichedError.details = error.details;
339
- enrichedError.clerkError = true;
340
- throw enrichedError;
341
- }
342
- throw new Error(`Failed to update user: ${error instanceof Error ? error.message : 'Unknown error'}`);
225
+ console.error('[updateUser] Error:', error);
226
+ handleClerkError(error, 'Failed to update user');
343
227
  }
344
228
  });
345
- /**
346
- * Delete a single user
347
- */
348
229
  export const deleteUser = command('unchecked', async (userId) => {
349
- console.log(`🗑️ [deleteUser] Deleting user ${userId}`);
350
230
  try {
351
231
  await makeClerkRequest(`/users/${userId}`, {
352
232
  method: 'DELETE'
353
233
  });
354
- console.log(`✅ [deleteUser] User ${userId} deleted successfully`);
355
234
  }
356
235
  catch (error) {
357
- console.error('[deleteUser] Error:', error);
236
+ console.error('[deleteUser] Error:', error);
358
237
  throw new Error(`Failed to delete user: ${error instanceof Error ? error.message : 'Unknown error'}`);
359
238
  }
360
239
  });
361
- /**
362
- * Delete multiple users
363
- */
364
240
  export const deleteUsers = command('unchecked', async (userIds) => {
365
- console.log(`🗑️ [deleteUsers] Deleting ${userIds.length} users`);
366
241
  try {
367
242
  await Promise.all(userIds.map((userId) => makeClerkRequest(`/users/${userId}`, {
368
243
  method: 'DELETE'
369
244
  })));
370
- console.log(`✅ [deleteUsers] ${userIds.length} users deleted successfully`);
371
245
  }
372
246
  catch (error) {
373
- console.error('[deleteUsers] Error:', error);
247
+ console.error('[deleteUsers] Error:', error);
374
248
  throw new Error(`Failed to delete users: ${error instanceof Error ? error.message : 'Unknown error'}`);
375
249
  }
376
250
  });
377
- /**
378
- * Helper: Fetch permissions for a single user
379
- */
380
251
  async function fetchUserPermissions(userId) {
381
- console.log(`🔍 [fetchUserPermissions] Fetching permissions for user: ${userId}`);
382
- // Try direct lookup first using client_id and sub (userId)
383
252
  try {
384
253
  const userData = await makeAdminRequest(`/admin/keys?client_id=${CLIENT_ID}&sub=${userId}`);
385
- console.log(`✅ [fetchUserPermissions] Direct lookup successful for user ${userId}`);
386
- // Filter the response to only include active keys
387
254
  if (userData?.data?.data && Array.isArray(userData.data.data)) {
388
255
  userData.data.data = userData.data.data.filter((key) => key.status === 'active');
389
- console.log(`🔍 [fetchUserPermissions] Filtered to ${userData.data.data.length} active key(s)`);
390
256
  }
391
- // Extract scopes from the response
392
257
  if (userData?.data?.data && Array.isArray(userData.data.data)) {
393
258
  return userData.data.data.flatMap((key) => key.scopes || []);
394
259
  }
@@ -398,48 +263,28 @@ async function fetchUserPermissions(userId) {
398
263
  return [];
399
264
  }
400
265
  catch {
401
- console.log(`❌ [fetchUserPermissions] Direct lookup failed, trying search by sub field`);
402
- // If direct lookup fails with 404, search all keys by sub field
403
266
  try {
404
267
  const allKeysData = await makeAdminRequest('/admin/keys');
405
- console.log(`🔍 [fetchUserPermissions] Searching through ${allKeysData.data?.data?.length || 0} keys`);
406
- // Find the ACTIVE key for this user (ignore revoked keys)
407
- // Match by sub (userId) and client_id
408
268
  const userKey = allKeysData.data.data.find((key) => key.sub === userId && key.client_id === CLIENT_ID && key.status === 'active');
409
269
  if (userKey) {
410
- console.log(`✅ [fetchUserPermissions] Found active user key by sub field`);
411
270
  return Array.isArray(userKey.scopes) ? userKey.scopes : [userKey.scopes];
412
271
  }
413
- else {
414
- console.log(`❌ [fetchUserPermissions] No user found, returning empty permissions`);
415
- return [];
416
- }
272
+ return [];
417
273
  }
418
274
  catch (searchError) {
419
- console.error('[fetchUserPermissions] Error searching for user by sub:', searchError);
275
+ console.error('[fetchUserPermissions] Error searching for user by sub:', searchError);
420
276
  throw new Error('Failed to fetch user permissions');
421
277
  }
422
278
  }
423
279
  }
424
- /**
425
- * Get permissions for specific users (batched)
426
- *
427
- * Uses 'sub' (userId) parameter in admin API calls.
428
- * Batches multiple permission requests to avoid n+1 problem.
429
- */
430
280
  export const getUserPermissions = query.batch('unchecked', async (userIds) => {
431
- console.log(`🔍 [getUserPermissions] Batch fetching permissions for ${userIds.length} users`);
432
281
  try {
433
- // Fetch all permissions in parallel
434
282
  const permissionPromises = userIds.map((userId) => fetchUserPermissions(userId));
435
283
  const permissionsResults = await Promise.all(permissionPromises);
436
- // Create a lookup map for O(1) access
437
284
  const lookup = new Map();
438
285
  userIds.forEach((userId, index) => {
439
286
  lookup.set(userId, permissionsResults[index]);
440
287
  });
441
- console.log(`✅ [getUserPermissions] Batch fetch completed for ${userIds.length} users`);
442
- // Return a function that SvelteKit will call for each individual request
443
288
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
444
289
  return (userId, _index) => {
445
290
  const permissions = lookup.get(userId) || [];
@@ -447,120 +292,85 @@ export const getUserPermissions = query.batch('unchecked', async (userIds) => {
447
292
  };
448
293
  }
449
294
  catch (error) {
450
- console.error('[getUserPermissions] Batch error:', error);
451
- // Return a function that throws for all requests
295
+ console.error('[getUserPermissions] Batch error:', error);
452
296
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
453
297
  return (_userId, _index) => {
454
298
  throw new Error(`Failed to fetch user permissions: ${error instanceof Error ? error.message : 'Unknown error'}`);
455
299
  };
456
300
  }
457
301
  });
458
- /**
459
- * Update permissions for a specific user
460
- *
461
- * Uses 'sub' (userId) parameter in admin API calls
462
- */
463
302
  export const updateUserPermissions = command('unchecked', async (options) => {
464
303
  const { userId, permissions } = options;
465
- console.log(`🔑 [updateUserPermissions] Updating permissions for user ${userId}`);
466
304
  try {
467
- let adminKeyId = userId; // Use userId as default key ID
468
- // Try direct update first
305
+ let adminKeyId = userId;
469
306
  try {
470
307
  await makeAdminRequest(`/admin/keys/${adminKeyId}`, {
471
308
  method: 'PUT',
472
309
  body: JSON.stringify({ scopes: permissions })
473
310
  });
474
- console.log(`✅ [updateUserPermissions] Permissions updated successfully`);
475
311
  return;
476
312
  }
477
313
  catch {
478
- // If direct update fails, try to find the correct adminKeyId
479
- console.log(`⚠️ [updateUserPermissions] Direct update failed, searching for key ID...`);
480
314
  try {
481
315
  const allKeysData = await makeAdminRequest('/admin/keys');
482
- // Find the ACTIVE key for this user (ignore revoked keys)
483
- // Match by sub (userId) and client_id
484
316
  const userKey = allKeysData.data.data.find((key) => key.sub === userId && key.client_id === CLIENT_ID && key.status === 'active');
485
317
  if (userKey) {
486
- // Use the found key ID for update
487
318
  adminKeyId = userKey.id;
488
319
  await makeAdminRequest(`/admin/keys/${adminKeyId}`, {
489
320
  method: 'PUT',
490
321
  body: JSON.stringify({ scopes: permissions })
491
322
  });
492
- console.log(`✅ [updateUserPermissions] Permissions updated successfully (after search)`);
493
323
  return;
494
324
  }
495
325
  else {
496
- // User doesn't exist, create new admin key
497
- console.log(`📝 [updateUserPermissions] Creating new admin key...`);
498
326
  await createUserPermissions(userId, permissions);
499
- console.log(`✅ [updateUserPermissions] New admin key created successfully`);
500
327
  return;
501
328
  }
502
329
  }
503
330
  catch (searchError) {
504
- console.error('[updateUserPermissions] Error during permission update:', searchError);
331
+ console.error('[updateUserPermissions] Error during permission update:', searchError);
505
332
  throw new Error('Failed to update permissions');
506
333
  }
507
334
  }
508
335
  }
509
336
  catch (error) {
510
- console.error('[updateUserPermissions] Error:', error);
337
+ console.error('[updateUserPermissions] Error:', error);
511
338
  throw new Error(`Failed to update user permissions: ${error instanceof Error ? error.message : 'Unknown error'}`);
512
339
  }
513
340
  });
514
- /**
515
- * Generate new API key for a user with optional old key revocation
516
- */
517
341
  export const generateApiKey = command('unchecked', async (options) => {
518
- console.log(`🔑 [generateApiKey] Generating new API key for user ${options.userId}`);
519
342
  try {
520
- // Filter permissions by prefix if configured
521
343
  const filteredPermissions = PERMISSION_PREFIX
522
344
  ? options.permissions.filter((scope) => scope.startsWith(PERMISSION_PREFIX))
523
345
  : options.permissions;
524
346
  if (filteredPermissions.length === 0) {
525
347
  throw new Error(`At least one ${PERMISSION_PREFIX || ''} permission is required to generate an API key`);
526
348
  }
527
- // If revokeOld is true, find and revoke the old key first
528
349
  let oldKeyId = null;
529
350
  if (options.revokeOld) {
530
351
  try {
531
- console.log(`🔍 [generateApiKey] Looking for existing active key to revoke`);
532
352
  const allKeysData = await makeAdminRequest('/admin/keys');
533
- if (!allKeysData?.data?.data || !Array.isArray(allKeysData.data.data)) {
534
- console.warn('⚠️ [generateApiKey] Unexpected response structure from /admin/keys');
535
- }
536
- else {
537
- // Find the ACTIVE key for this user (ignore already revoked keys)
353
+ if (allKeysData?.data?.data && Array.isArray(allKeysData.data.data)) {
538
354
  const userKey = allKeysData.data.data.find((key) => key.sub === options.userId && key.client_id === CLIENT_ID && key.status === 'active');
539
355
  if (userKey) {
540
356
  oldKeyId = userKey.id;
541
- console.log(`📌 [generateApiKey] Found existing active key: ${oldKeyId}`);
542
357
  }
543
358
  }
544
359
  }
545
360
  catch (e) {
546
- console.warn('⚠️ [generateApiKey] Could not fetch existing key for revocation:', e);
547
- // Continue anyway - not critical
361
+ console.warn('[generateApiKey] Could not fetch existing key for revocation:', e);
548
362
  }
549
363
  }
550
- // Create new admin key
551
364
  const createData = await createUserPermissions(options.userId, filteredPermissions);
552
365
  if (!createData) {
553
366
  throw new Error('Failed to create admin key');
554
367
  }
555
368
  const newApiKey = createData?.data?.key;
556
369
  if (!newApiKey) {
557
- console.error('[generateApiKey] No API key in response:', createData);
370
+ console.error('[generateApiKey] No API key in response:', createData);
558
371
  throw new Error('Failed to generate API key - no key in response');
559
372
  }
560
- console.log(`✅ [generateApiKey] New API key generated successfully`);
561
- // Update user's Clerk profile with the new API key
562
373
  try {
563
- // First, get the current user data to preserve existing private_metadata
564
374
  const currentUser = await makeClerkRequest(`/users/${options.userId}`);
565
375
  await makeClerkRequest(`/users/${options.userId}`, {
566
376
  method: 'PATCH',
@@ -571,24 +381,20 @@ export const generateApiKey = command('unchecked', async (options) => {
571
381
  }
572
382
  })
573
383
  });
574
- console.log(`✅ [generateApiKey] API key stored in user's Clerk profile`);
575
384
  }
576
385
  catch (clerkError) {
577
- console.error('[generateApiKey] Failed to update Clerk profile:', clerkError);
578
- console.warn('⚠️ [generateApiKey] Key generated but could not update Clerk profile');
386
+ console.error('[generateApiKey] Failed to update Clerk profile:', clerkError);
387
+ console.warn('[generateApiKey] Key generated but could not update Clerk profile');
579
388
  }
580
- // Revoke old key if it exists
581
389
  if (oldKeyId) {
582
390
  try {
583
- console.log(`🗑️ [generateApiKey] Revoking old key: ${oldKeyId}`);
584
391
  await makeAdminRequest(`/admin/keys/${oldKeyId}`, {
585
392
  method: 'DELETE'
586
393
  });
587
- console.log(`✅ [generateApiKey] Old key revoked successfully`);
588
394
  }
589
395
  catch (revokeError) {
590
- console.error('[generateApiKey] Failed to revoke old key:', revokeError);
591
- console.warn('⚠️ [generateApiKey] New key generated but could not revoke old key');
396
+ console.error('[generateApiKey] Failed to revoke old key:', revokeError);
397
+ console.warn('[generateApiKey] New key generated but could not revoke old key');
592
398
  }
593
399
  }
594
400
  return {
@@ -600,7 +406,7 @@ export const generateApiKey = command('unchecked', async (options) => {
600
406
  };
601
407
  }
602
408
  catch (error) {
603
- console.error('[generateApiKey] Error:', error);
409
+ console.error('[generateApiKey] Error:', error);
604
410
  throw new Error(`Failed to generate API key: ${error instanceof Error ? error.message : 'Unknown error'}`);
605
411
  }
606
412
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "1.2.10",
3
+ "version": "1.2.11",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {