@makolabs/ripple 3.8.0 → 3.8.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,34 @@
1
+ /**
2
+ * Pure builders for Clerk user-API requests, extracted from
3
+ * `user-management.remote.ts` so the request shape is unit-testable — a
4
+ * `*.remote.ts` module cannot be imported in vitest (SvelteKit's plugin rejects
5
+ * any non-remote export).
6
+ *
7
+ * They also document the rule that caused minted API keys to silently never
8
+ * persist: Clerk deprecated sending `private_metadata` in the body of
9
+ * `PATCH /users/{id}` (it now returns 422 `form_param_deprecated`). User
10
+ * metadata must be written through the dedicated `PATCH /users/{id}/metadata`
11
+ * endpoint, which merges into the existing metadata.
12
+ */
13
+ export interface ClerkRequest {
14
+ endpoint: string;
15
+ init: {
16
+ method: string;
17
+ body: string;
18
+ };
19
+ }
20
+ /**
21
+ * Build a request that persists `privateMetadata` on a Clerk user via the
22
+ * dedicated metadata endpoint. Clerk merges the provided keys into the user's
23
+ * existing private metadata.
24
+ */
25
+ export declare function clerkMetadataPatch(userId: string, privateMetadata: Record<string, unknown>): ClerkRequest;
26
+ /**
27
+ * Build the body for a profile update (`PATCH /users/{id}`). Metadata is
28
+ * intentionally excluded — it must go through {@link clerkMetadataPatch}.
29
+ */
30
+ export declare function clerkProfilePatchBody(userData: {
31
+ first_name?: string;
32
+ last_name?: string;
33
+ username?: string;
34
+ }): Record<string, string>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pure builders for Clerk user-API requests, extracted from
3
+ * `user-management.remote.ts` so the request shape is unit-testable — a
4
+ * `*.remote.ts` module cannot be imported in vitest (SvelteKit's plugin rejects
5
+ * any non-remote export).
6
+ *
7
+ * They also document the rule that caused minted API keys to silently never
8
+ * persist: Clerk deprecated sending `private_metadata` in the body of
9
+ * `PATCH /users/{id}` (it now returns 422 `form_param_deprecated`). User
10
+ * metadata must be written through the dedicated `PATCH /users/{id}/metadata`
11
+ * endpoint, which merges into the existing metadata.
12
+ */
13
+ /**
14
+ * Build a request that persists `privateMetadata` on a Clerk user via the
15
+ * dedicated metadata endpoint. Clerk merges the provided keys into the user's
16
+ * existing private metadata.
17
+ */
18
+ export function clerkMetadataPatch(userId, privateMetadata) {
19
+ return {
20
+ endpoint: `/users/${userId}/metadata`,
21
+ init: { method: 'PATCH', body: JSON.stringify({ private_metadata: privateMetadata }) }
22
+ };
23
+ }
24
+ /**
25
+ * Build the body for a profile update (`PATCH /users/{id}`). Metadata is
26
+ * intentionally excluded — it must go through {@link clerkMetadataPatch}.
27
+ */
28
+ export function clerkProfilePatchBody(userData) {
29
+ const body = {};
30
+ if (userData.first_name !== undefined)
31
+ body.first_name = userData.first_name;
32
+ if (userData.last_name !== undefined)
33
+ body.last_name = userData.last_name;
34
+ if (userData.username !== undefined && userData.username !== '') {
35
+ body.username = userData.username;
36
+ }
37
+ return body;
38
+ }
@@ -2,6 +2,7 @@ import { command } from '$app/server';
2
2
  import { getRequestEvent } from '$app/server';
3
3
  import { env } from '$env/dynamic/private';
4
4
  import { building } from '$app/environment';
5
+ import { clerkMetadataPatch, clerkProfilePatchBody } from './clerk-requests.js';
5
6
  const CLIENT_ID = env.CLIENT_ID;
6
7
  const ORGANIZATION_ID = env.ALLOWED_ORG_ID;
7
8
  if (!CLIENT_ID && !building) {
@@ -248,6 +249,16 @@ async function createUserPermissions(email, permissions, clientId = CLIENT_ID) {
248
249
  log.trace('createUserPermissions', 'Result:', result);
249
250
  return result;
250
251
  }
252
+ /**
253
+ * Persist private metadata on a Clerk user via the dedicated metadata endpoint.
254
+ * Clerk deprecated `private_metadata` in the body of `PATCH /users/{id}` (it
255
+ * now returns 422 form_param_deprecated), which silently dropped minted API
256
+ * keys. Routes through `PATCH /users/{id}/metadata` (which merges) instead.
257
+ */
258
+ async function writeClerkMetadata(userId, privateMetadata) {
259
+ const { endpoint, init } = clerkMetadataPatch(userId, privateMetadata);
260
+ return makeClerkRequest(endpoint, init);
261
+ }
251
262
  export const getUsers = command('unchecked', async (options) => {
252
263
  log.trace('getUsers', 'Called with options:', options);
253
264
  try {
@@ -347,14 +358,9 @@ export const createUser = command('unchecked', async (userData) => {
347
358
  const apiKey = adminKeyResult?.data?.key;
348
359
  log.trace('createUser', 'Admin key created, has key:', !!apiKey);
349
360
  if (adminKeyResult && apiKey) {
350
- const updatedUser = await makeClerkRequest(`/users/${result.id}`, {
351
- method: 'PATCH',
352
- body: JSON.stringify({
353
- private_metadata: {
354
- ...result.private_metadata,
355
- mako_api_key: apiKey
356
- }
357
- })
361
+ const updatedUser = await writeClerkMetadata(result.id, {
362
+ ...result.private_metadata,
363
+ mako_api_key: apiKey
358
364
  });
359
365
  // Ensure updatedUser is serializable
360
366
  result = JSON.parse(JSON.stringify(updatedUser));
@@ -385,22 +391,19 @@ export const updateUser = command('unchecked', async (options) => {
385
391
  const { userId, userData } = options;
386
392
  log.trace('updateUser', 'Called for userId:', userId, 'fields:', Object.keys(userData));
387
393
  try {
388
- const updateData = {};
389
- if (userData.first_name !== undefined)
390
- updateData.first_name = userData.first_name;
391
- if (userData.last_name !== undefined)
392
- updateData.last_name = userData.last_name;
393
- if (userData.username !== undefined && userData.username !== '') {
394
- updateData.username = userData.username;
395
- }
394
+ // Profile fields and metadata live behind different Clerk endpoints —
395
+ // `private_metadata` is no longer accepted on PATCH /users/{id}.
396
+ const profileBody = clerkProfilePatchBody(userData);
397
+ log.trace('updateUser', 'Profile payload:', profileBody);
398
+ let result = Object.keys(profileBody).length > 0
399
+ ? await makeClerkRequest(`/users/${userId}`, {
400
+ method: 'PATCH',
401
+ body: JSON.stringify(profileBody)
402
+ })
403
+ : await makeClerkRequest(`/users/${userId}`);
396
404
  if (userData.private_metadata !== undefined) {
397
- updateData.private_metadata = userData.private_metadata;
405
+ result = await writeClerkMetadata(userId, userData.private_metadata);
398
406
  }
399
- log.trace('updateUser', 'Update payload:', updateData);
400
- let result = await makeClerkRequest(`/users/${userId}`, {
401
- method: 'PATCH',
402
- body: JSON.stringify(updateData)
403
- });
404
407
  // Ensure result is serializable
405
408
  result = JSON.parse(JSON.stringify(result));
406
409
  if (userData.permissions !== undefined) {
@@ -572,14 +575,9 @@ export const updateUserPermissions = command('unchecked', async (options) => {
572
575
  const newKeyResult = await createUserPermissions(email, permissions);
573
576
  const newApiKey = newKeyResult?.data?.key;
574
577
  if (newApiKey) {
575
- await makeClerkRequest(`/users/${userId}`, {
576
- method: 'PATCH',
577
- body: JSON.stringify({
578
- private_metadata: {
579
- ...(user.private_metadata || {}),
580
- mako_api_key: newApiKey
581
- }
582
- })
578
+ await writeClerkMetadata(userId, {
579
+ ...(user.private_metadata || {}),
580
+ mako_api_key: newApiKey
583
581
  });
584
582
  log.trace('updateUserPermissions', 'New key stored in Clerk metadata');
585
583
  }
@@ -742,14 +740,9 @@ export const generateApiKey = command('unchecked', async (options) => {
742
740
  currentUser = await makeClerkRequest(`/users/${options.userId}`);
743
741
  }
744
742
  if (currentUser) {
745
- await makeClerkRequest(`/users/${options.userId}`, {
746
- method: 'PATCH',
747
- body: JSON.stringify({
748
- private_metadata: {
749
- ...(currentUser.private_metadata || {}),
750
- mako_api_key: newApiKey
751
- }
752
- })
743
+ await writeClerkMetadata(options.userId, {
744
+ ...(currentUser.private_metadata || {}),
745
+ mako_api_key: newApiKey
753
746
  });
754
747
  log.trace('generateApiKey', 'Clerk metadata updated with new key');
755
748
  }
@@ -874,11 +867,9 @@ export const approveUser = command('unchecked', async (input) => {
874
867
  if (!apiKey) {
875
868
  throw new Error('Failed to mint API key during approval');
876
869
  }
877
- await makeClerkRequest(`/users/${input.userId}`, {
878
- method: 'PATCH',
879
- body: JSON.stringify({
880
- private_metadata: { ...(user.private_metadata || {}), mako_api_key: apiKey }
881
- })
870
+ await writeClerkMetadata(input.userId, {
871
+ ...(user.private_metadata || {}),
872
+ mako_api_key: apiKey
882
873
  });
883
874
  log.trace('approveUser', 'Approved and key issued');
884
875
  return JSON.parse(JSON.stringify({ apiKey }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makolabs/ripple",
3
- "version": "3.8.0",
3
+ "version": "3.8.1",
4
4
  "description": "Simple Svelte 5 powered component library ✨",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {