Skip to content

Commit

Permalink
feat(clerk-js): Improve fetch type safety
Browse files Browse the repository at this point in the history
  • Loading branch information
desiprisg committed Nov 6, 2023
1 parent 4edb776 commit f7a773d
Show file tree
Hide file tree
Showing 13 changed files with 77 additions and 85 deletions.
2 changes: 1 addition & 1 deletion packages/clerk-js/src/core/fapiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type FapiQueryStringParameters = {
};

export type FapiResponse<T> = Response & {
payload: FapiResponseJSON<T> | null;
payload: FapiResponseJSON<T>;
};

export type FapiRequestCallback<T> = (
Expand Down
49 changes: 22 additions & 27 deletions packages/clerk-js/src/core/resources/Base.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { isValidBrowserOnline } from '@clerk/shared/browser';
import type { ClerkAPIErrorJSON, ClerkResourceJSON, ClerkResourceReloadParams, DeletedObjectJSON } from '@clerk/types';
import type {
ClerkAPIErrorJSON,
ClerkPaginatedResponse,
ClerkResourceJSON,
ClerkResourceReloadParams,
DeletedObjectJSON,
} from '@clerk/types';

import { clerkMissingFapiClientInResources } from '../errors';
import type { FapiClient, FapiRequestInit, FapiResponse, FapiResponseJSON, HTTPMethod } from '../fapiClient';
import type { FapiClient, FapiRequestInit, FapiResponseJSON, HTTPMethod } from '../fapiClient';
import type { Clerk } from './internal';
import { ClerkAPIResponseError, Client } from './internal';

Expand All @@ -24,26 +29,20 @@ export abstract class BaseResource {
return BaseResource.clerk.getFapiClient();
}

protected static async _fetch<J extends ClerkResourceJSON | DeletedObjectJSON | null>(
requestInit: FapiRequestInit,
opts: BaseFetchOptions = {},
): Promise<FapiResponseJSON<J> | null> {
protected static async _fetch<
J extends
| ClerkResourceJSON
| ClerkResourceJSON[]
| DeletedObjectJSON
| DeletedObjectJSON[]
| ClerkPaginatedResponse<ClerkResourceJSON>
| null,
>(requestInit: FapiRequestInit, opts: BaseFetchOptions = {}): Promise<FapiResponseJSON<J>> {
if (!BaseResource.fapiClient) {
clerkMissingFapiClientInResources();
}

let fapiResponse: FapiResponse<J>;

try {
fapiResponse = await BaseResource.fapiClient.request<J>(requestInit);
} catch (e) {
if (!isValidBrowserOnline()) {
console.warn(e);
return null;
} else {
throw e;
}
}
const fapiResponse = await BaseResource.fapiClient.request<J>(requestInit);

const { payload, status, statusText, headers } = fapiResponse;

Expand All @@ -66,14 +65,10 @@ export abstract class BaseResource {
await BaseResource.clerk.handleUnauthenticated();
}

if (status >= 400) {
throw new ClerkAPIResponseError(statusText, {
data: payload?.errors as ClerkAPIErrorJSON[],
status: status,
});
}

return null;
throw new ClerkAPIResponseError(statusText, {
data: payload?.errors as ClerkAPIErrorJSON[],
status: status,
});
}

protected static _updateClient<J>(responseJSON: FapiResponseJSON<J> | null): void {
Expand Down
4 changes: 2 additions & 2 deletions packages/clerk-js/src/core/resources/Image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class Image extends BaseResource implements ImageResource {
body: fd,
headers,
})
)?.response as unknown as ImageJSON;
)?.response;

return new Image(json);
}
Expand All @@ -38,7 +38,7 @@ export class Image extends BaseResource implements ImageResource {
path,
method: 'DELETE',
})
)?.response as unknown as ImageJSON;
)?.response;

return new Image(json);
}
Expand Down
51 changes: 24 additions & 27 deletions packages/clerk-js/src/core/resources/Organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class Organization extends BaseResource implements OrganizationResource {
method: 'POST',
body: { name, slug } as any,
})
)?.response as unknown as OrganizationJSON;
)?.response;

return new Organization(json);
}
Expand All @@ -97,7 +97,7 @@ export class Organization extends BaseResource implements OrganizationResource {
path: `/organizations/${organizationId}`,
method: 'GET',
})
)?.response as unknown as OrganizationJSON;
)?.response;

return new Organization(json);
}
Expand Down Expand Up @@ -126,14 +126,13 @@ export class Organization extends BaseResource implements OrganizationResource {
getDomains = async (
getDomainParams?: GetDomainsParams,
): Promise<ClerkPaginatedResponse<OrganizationDomainResource>> => {
return await BaseResource._fetch({
return await BaseResource._fetch<ClerkPaginatedResponse<OrganizationDomainJSON>>({
path: `/organizations/${this.id}/domains`,
method: 'GET',
search: convertPageToOffset(getDomainParams) as any,
})
.then(res => {
const { data: invites, total_count } =
res?.response as unknown as ClerkPaginatedResponse<OrganizationDomainJSON>;
const { data: invites, total_count } = res.response;

return {
total_count,
Expand All @@ -152,21 +151,20 @@ export class Organization extends BaseResource implements OrganizationResource {
path: `/organizations/${this.id}/domains/${domainId}`,
method: 'GET',
})
)?.response as unknown as OrganizationDomainJSON;
)?.response;
return new OrganizationDomain(json);
};

getMembershipRequests = async (
getRequestParam?: GetMembershipRequestParams,
): Promise<ClerkPaginatedResponse<OrganizationMembershipRequestResource>> => {
return await BaseResource._fetch({
return await BaseResource._fetch<ClerkPaginatedResponse<OrganizationMembershipRequestJSON>>({
path: `/organizations/${this.id}/membership_requests`,
method: 'GET',
search: convertPageToOffset(getRequestParam) as any,
})
.then(res => {
const { data: requests, total_count } =
res?.response as unknown as ClerkPaginatedResponse<OrganizationMembershipRequestJSON>;
const { data: requests, total_count } = res.response;

return {
total_count,
Expand Down Expand Up @@ -197,7 +195,7 @@ export class Organization extends BaseResource implements OrganizationResource {
deprecated('offset', 'Use `initialPage` instead in Organization.limit.', 'organization:getMemberships:offset');
}

return await BaseResource._fetch({
return await BaseResource._fetch<OrganizationMembershipJSON[]>({
path: `/organizations/${this.id}/memberships`,
method: 'GET',
search: isDeprecatedParams
Expand All @@ -206,7 +204,7 @@ export class Organization extends BaseResource implements OrganizationResource {
})
.then(res => {
if (isDeprecatedParams) {
const organizationMembershipsJSON = res?.response as unknown as OrganizationMembershipJSON[];
const organizationMembershipsJSON = res.response;
return organizationMembershipsJSON.map(orgMem => new OrganizationMembership(orgMem)) as any;
}

Expand All @@ -233,13 +231,13 @@ export class Organization extends BaseResource implements OrganizationResource {
getPendingInvitationsParams?: GetPendingInvitationsParams,
): Promise<OrganizationInvitation[]> => {
deprecated('getPendingInvitations', 'Use the `getInvitations` method instead.');
return await BaseResource._fetch({
return await BaseResource._fetch<OrganizationInvitationJSON[]>({
path: `/organizations/${this.id}/invitations/pending`,
method: 'GET',
search: getPendingInvitationsParams as any,
})
.then(res => {
const pendingInvitations = res?.response as unknown as OrganizationInvitationJSON[];
const pendingInvitations = res?.response;
return pendingInvitations.map(pendingInvitation => new OrganizationInvitation(pendingInvitation));
})
.catch(() => []);
Expand All @@ -248,14 +246,13 @@ export class Organization extends BaseResource implements OrganizationResource {
getInvitations = async (
getInvitationsParams?: GetInvitationsParams,
): Promise<ClerkPaginatedResponse<OrganizationInvitationResource>> => {
return await BaseResource._fetch({
return await BaseResource._fetch<ClerkPaginatedResponse<OrganizationInvitationJSON>>({
path: `/organizations/${this.id}/invitations`,
method: 'GET',
search: convertPageToOffset(getInvitationsParams) as any,
})
.then(res => {
const { data: requests, total_count } =
res?.response as unknown as ClerkPaginatedResponse<OrganizationInvitationJSON>;
const { data: requests, total_count } = res.response;

return {
total_count,
Expand All @@ -269,11 +266,11 @@ export class Organization extends BaseResource implements OrganizationResource {
};

addMember = async ({ userId, role }: AddMemberParams) => {
const newMember = await BaseResource._fetch({
const newMember = await BaseResource._fetch<OrganizationMembershipJSON>({
method: 'POST',
path: `/organizations/${this.id}/memberships`,
body: { userId, role } as any,
}).then(res => new OrganizationMembership(res?.response as OrganizationMembershipJSON));
}).then(res => new OrganizationMembership(res.response));
OrganizationMembership.clerk.__unstable__membershipUpdate(newMember);
return newMember;
};
Expand All @@ -287,20 +284,20 @@ export class Organization extends BaseResource implements OrganizationResource {
};

updateMember = async ({ userId, role }: UpdateMembershipParams): Promise<OrganizationMembership> => {
const updatedMember = await BaseResource._fetch({
const updatedMember = await BaseResource._fetch<OrganizationMembershipJSON>({
method: 'PATCH',
path: `/organizations/${this.id}/memberships/${userId}`,
body: { role } as any,
}).then(res => new OrganizationMembership(res?.response as OrganizationMembershipJSON));
}).then(res => new OrganizationMembership(res.response));
OrganizationMembership.clerk.__unstable__membershipUpdate(updatedMember);
return updatedMember;
};

removeMember = async (userId: string): Promise<OrganizationMembership> => {
const deletedMember = await BaseResource._fetch({
const deletedMember = await BaseResource._fetch<OrganizationMembershipJSON>({
method: 'DELETE',
path: `/organizations/${this.id}/memberships/${userId}`,
}).then(res => new OrganizationMembership(res?.response as OrganizationMembershipJSON));
}).then(res => new OrganizationMembership(res.response));
OrganizationMembership.clerk.__unstable__membershipUpdate(deletedMember);
return deletedMember;
};
Expand All @@ -311,10 +308,10 @@ export class Organization extends BaseResource implements OrganizationResource {

setLogo = async ({ file }: SetOrganizationLogoParams): Promise<OrganizationResource> => {
if (file === null) {
return await BaseResource._fetch({
return await BaseResource._fetch<OrganizationJSON>({
path: `/organizations/${this.id}/logo`,
method: 'DELETE',
}).then(res => new Organization(res?.response as OrganizationJSON));
}).then(res => new Organization(res.response));
}

let body;
Expand All @@ -329,12 +326,12 @@ export class Organization extends BaseResource implements OrganizationResource {
body.append('file', file);
}

return await BaseResource._fetch({
return await BaseResource._fetch<OrganizationJSON>({
path: `/organizations/${this.id}/logo`,
method: 'PUT',
body,
headers,
}).then(res => new Organization(res?.response as OrganizationJSON));
}).then(res => new Organization(res.response));
};

protected fromJSON(data: OrganizationJSON | null): this {
Expand Down Expand Up @@ -370,7 +367,7 @@ export class Organization extends BaseResource implements OrganizationResource {
},
{ forceUpdateClient: true },
)
)?.response as unknown as OrganizationJSON;
).response;

return this.fromJSON(json);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/src/core/resources/OrganizationDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class OrganizationDomain extends BaseResource implements OrganizationDoma
method: 'POST',
body: { name } as any,
})
)?.response as unknown as OrganizationDomainJSON;
).response;
return new OrganizationDomain(json);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class OrganizationInvitation extends BaseResource implements Organization
method: 'POST',
body: { email_address: emailAddress, role } as any,
})
)?.response as unknown as OrganizationInvitationJSON;
).response;
const newInvitation = new OrganizationInvitation(json);
this.clerk.__unstable__invitationUpdate(newInvitation);
return newInvitation;
Expand All @@ -42,12 +42,12 @@ export class OrganizationInvitation extends BaseResource implements Organization
): Promise<OrganizationInvitationResource[]> {
const { emailAddresses, role } = params;
const json = (
await BaseResource._fetch<OrganizationInvitationJSON>({
await BaseResource._fetch<OrganizationInvitationJSON[]>({
path: `/organizations/${organizationId}/invitations/bulk`,
method: 'POST',
body: { email_address: emailAddresses, role } as any,
})
)?.response as unknown as OrganizationInvitationJSON[];
).response;
// const newInvitation = new OrganizationInvitation(json);
// TODO: Figure out what this is...
// this.clerk.__unstable__invitationUpdate(newInvitation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class OrganizationMembership extends BaseResource implements Organization
);
}

return await BaseResource._fetch({
return await BaseResource._fetch<OrganizationMembershipJSON[]>({
path: '/me/organization_memberships',
method: 'GET',
search: isDeprecatedParams
Expand All @@ -61,7 +61,7 @@ export class OrganizationMembership extends BaseResource implements Organization
})
.then(res => {
if (isDeprecatedParams) {
const organizationMembershipsJSON = res?.response as unknown as OrganizationMembershipJSON[];
const organizationMembershipsJSON = res.response;
return organizationMembershipsJSON.map(orgMem => new OrganizationMembership(orgMem)) as any;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@ export class OrganizationSuggestion extends BaseResource implements Organization
static async retrieve(
params?: GetUserOrganizationSuggestionsParams,
): Promise<ClerkPaginatedResponse<OrganizationSuggestion>> {
return await BaseResource._fetch({
return await BaseResource._fetch<ClerkPaginatedResponse<OrganizationSuggestionJSON>>({
path: '/me/organization_suggestions',
method: 'GET',
search: convertPageToOffset(params) as any,
})
.then(res => {
const { data: suggestions, total_count } =
res?.response as unknown as ClerkPaginatedResponse<OrganizationSuggestionJSON>;
const { data: suggestions, total_count } = res.response;

return {
total_count,
Expand Down
6 changes: 3 additions & 3 deletions packages/clerk-js/src/core/resources/Token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export class Token extends BaseResource implements TokenResource {
jwt: JWT;

static async create(path: string, body: any = {}): Promise<TokenResource> {
const json = (await BaseResource._fetch<TokenJSON>({
const json = await BaseResource._fetch<TokenJSON>({
path,
method: 'POST',
body,
})) as unknown as TokenJSON;
});

return new Token(json, path);
return new Token(json.response, path);
}

constructor(data: TokenJSON, pathRoot?: string) {
Expand Down
Loading

0 comments on commit f7a773d

Please sign in to comment.