diff --git a/.changeset/organization-response-types.md b/.changeset/organization-response-types.md new file mode 100644 index 0000000..80902cd --- /dev/null +++ b/.changeset/organization-response-types.md @@ -0,0 +1,11 @@ +--- +'@seamless-auth/react': patch +--- + +Correct the organization response types, checked against the API's declared response schemas. + +- `addOrganizationMember` and `updateOrganizationMember` return `OrganizationMembershipResult` (`{ membership }`). They were typed as returning a members list, so reading `.members` gave `undefined`. +- `removeOrganizationMember` returns `MessageResult`, not a members list. +- `switchOrganization` returns `OrganizationSwitchResult` (`{ message, organizationId, organization }`), which describes what the endpoint actually sends. + +`OrganizationMembershipResult` and `OrganizationSwitchResult` are new exports. diff --git a/src/client/createSeamlessAuthClient.ts b/src/client/createSeamlessAuthClient.ts index af5ff62..7af2058 100644 --- a/src/client/createSeamlessAuthClient.ts +++ b/src/client/createSeamlessAuthClient.ts @@ -103,6 +103,22 @@ export interface OrganizationMembersResult { total: number; } +/** Response body for a single membership mutation. */ +export interface OrganizationMembershipResult { + membership: OrganizationMembership; +} + +/** + * Response body when the active organization changes. The server also returns + * session material here, which this SDK deliberately does not surface: sessions + * are carried by cookies, so adopters have no reason to handle raw tokens. + */ +export interface OrganizationSwitchResult { + message: string; + organizationId: string; + organization: Organization; +} + export interface OAuthProvider { id: string; name: string; @@ -278,23 +294,23 @@ export interface SeamlessAuthClient { ) => Promise>; switchOrganization: ( organizationId: string - ) => Promise>; + ) => Promise>; listOrganizationMembers: ( organizationId: string ) => Promise>; addOrganizationMember: ( organizationId: string, input: OrganizationMemberInput - ) => Promise>; + ) => Promise>; updateOrganizationMember: ( organizationId: string, userId: string, input: OrganizationMemberUpdateInput - ) => Promise>; + ) => Promise>; removeOrganizationMember: ( organizationId: string, userId: string - ) => Promise>; + ) => Promise>; } type StepUpPayload = Partial & { message?: string }; @@ -814,7 +830,7 @@ export const createSeamlessAuthClient = ( ), switchOrganization: organizationId => - requestResult( + requestResult( fetchWithAuth(`/organizations/${encodeURIComponent(organizationId)}/switch`, { method: 'POST', }), @@ -830,7 +846,7 @@ export const createSeamlessAuthClient = ( ), addOrganizationMember: (organizationId, input) => - requestResult( + requestResult( fetchWithAuth(`/organizations/${encodeURIComponent(organizationId)}/members`, { method: 'POST', body: JSON.stringify(input), @@ -839,7 +855,7 @@ export const createSeamlessAuthClient = ( ), updateOrganizationMember: (organizationId, userId, input) => - requestResult( + requestResult( fetchWithAuth( `/organizations/${encodeURIComponent(organizationId)}/members/${encodeURIComponent(userId)}`, { @@ -851,7 +867,7 @@ export const createSeamlessAuthClient = ( ), removeOrganizationMember: (organizationId, userId) => - requestResult( + requestResult( fetchWithAuth( `/organizations/${encodeURIComponent(organizationId)}/members/${encodeURIComponent(userId)}`, { diff --git a/src/index.ts b/src/index.ts index 5cf2250..474dbc9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,9 @@ import { OrganizationMemberInput, OrganizationMemberUpdateInput, OrganizationMembersResult, + OrganizationMembershipResult, OrganizationResult, + OrganizationSwitchResult, OrganizationsResult, PasskeyLoginData, PasskeyMetadata, @@ -85,7 +87,9 @@ export type { OrganizationMembership, OrganizationMemberUpdateInput, OrganizationMembersResult, + OrganizationMembershipResult, OrganizationResult, + OrganizationSwitchResult, OrganizationsResult, PasskeyLoginData, PasskeyMetadata, diff --git a/tests/createSeamlessAuthClient.test.ts b/tests/createSeamlessAuthClient.test.ts index f82b65c..2990731 100644 --- a/tests/createSeamlessAuthClient.test.ts +++ b/tests/createSeamlessAuthClient.test.ts @@ -196,6 +196,31 @@ describe('createSeamlessAuthClient', () => { ); }); + it('surfaces the declared shapes for organization member mutations', async () => { + const membership = { userId: 'u1', roles: ['member'] }; + + mockFetchWithAuth + .mockResolvedValueOnce({ ok: true, json: async () => ({ membership }) }) + .mockResolvedValueOnce({ ok: true, json: async () => ({ membership }) }) + .mockResolvedValueOnce({ ok: true, json: async () => ({ message: 'Success' }) }); + + const client = createSeamlessAuthClient({ + apiHost: 'https://api.example.com', + }); + + // The API returns a single membership envelope for add and update, and a + // plain message for remove, not a members list. + const added = await client.addOrganizationMember('org-1', { userId: 'u1' }); + const updated = await client.updateOrganizationMember('org-1', 'u1', { + roles: ['admin'], + }); + const removed = await client.removeOrganizationMember('org-1', 'u1'); + + expect(added.data?.membership).toEqual(membership); + expect(updated.data?.membership).toEqual(membership); + expect(removed.data?.message).toBe('Success'); + }); + it('uses OAuth login endpoints', async () => { const providersResult = { providers: [{ id: 'google', name: 'Google', scopes: ['openid', 'email'] }],