Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/old-actors-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/types': patch
---

Add Autocomplete TS generic for union literals
24 changes: 19 additions & 5 deletions packages/clerk-js/src/core/resources/OrganizationMembership.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
Autocomplete,
ClerkPaginatedResponse,
ClerkResourceReloadParams,
GetUserOrganizationMembershipParams,
Expand All @@ -20,9 +21,7 @@ export class OrganizationMembership extends BaseResource implements Organization
/**
* @experimental The property is experimental and subject to change in future releases.
*/
// Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
// eslint-disable-next-line
permissions: (OrganizationPermission | (string & {}))[] = [];
permissions: Autocomplete<OrganizationPermission>[] = [];
role!: MembershipRole;
createdAt!: Date;
updatedAt!: Date;
Expand All @@ -41,8 +40,16 @@ export class OrganizationMembership extends BaseResource implements Organization
search: convertPageToOffset({ ...retrieveMembershipsParams, paginated: true }) as any,
})
.then(res => {
if (!res?.response) {
return {
total_count: 0,
data: [],
};
}

// TODO: Fix typing
const { data: suggestions, total_count } =
res?.response as unknown as ClerkPaginatedResponse<OrganizationMembershipJSON>;
res.response as unknown as ClerkPaginatedResponse<OrganizationMembershipJSON>;
Comment on lines +43 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this is a necessary change ? I believe you are using this logic in more places, shall we update the rest as well ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@panteliselef I was running into build issues, per typing. The _fetch returns (and its generic extends) ClerkResourceJSON | DeletedObjectJSON | null which doesn't fit the desired ClerkPaginatedResponse<OrganizationMembershipJSON> type.

We might want to expand upon this, however, I didn't feel this was the PR to do it.


return {
total_count,
Expand Down Expand Up @@ -99,9 +106,16 @@ export class OrganizationMembership extends BaseResource implements Organization
},
{ forceUpdateClient: true },
);
const currentMembership = (json?.response as unknown as OrganizationMembershipJSON[]).find(

if (!json?.response) {
return this.fromJSON(null);
}

// TODO: Fix typing
const currentMembership = (json.response as unknown as OrganizationMembershipJSON[]).find(
Comment on lines +109 to +115

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

orgMem => orgMem.id === this.id,
);

return this.fromJSON(currentMembership as OrganizationMembershipJSON);
}
}
Expand Down
13 changes: 4 additions & 9 deletions packages/nextjs/src/server/authMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AuthObject, RequestState } from '@clerk/backend';
import { buildRequestUrl, constants } from '@clerk/backend';
import { isDevelopmentFromApiKey } from '@clerk/shared/keys';
import type { Autocomplete } from '@clerk/types';
import type Link from 'next/link';
import type { NextFetchEvent, NextMiddleware, NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
Expand Down Expand Up @@ -32,15 +33,9 @@ type NextTypedRoute<T = Parameters<typeof Link>['0']['href']> = T extends string
// For extra safety, we won't recommend using a `/(.*)` route matcher.
type ExcludeRootPath<T> = T extends '/' ? never : T;

// We want to show suggestions but also allow for free-text input
// the (string & {}) type prevents the TS compiler from merging the typed union with the string type
// https://github.com/Microsoft/TypeScript/issues/29729#issuecomment-505826972
type RouteMatcherWithNextTypedRoutes =
| WithPathPatternWildcard<ExcludeRootPath<NextTypedRoute>>
| NextTypedRoute
// This is necessary to allow all string, using something other than `{}` here WILL break types!
// eslint-disable-next-line @typescript-eslint/ban-types
| (string & {});
type RouteMatcherWithNextTypedRoutes = Autocomplete<
WithPathPatternWildcard<ExcludeRootPath<NextTypedRoute>> | NextTypedRoute
>;

const INFINITE_REDIRECTION_LOOP_COOKIE = '__clerk_redirection_loop';

Expand Down
4 changes: 2 additions & 2 deletions packages/types/src/clerk.retheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { OrganizationResource } from './organization';
import type { MembershipRole } from './organizationMembership';
import type { ActiveSessionResource } from './session';
import type { UserResource } from './user';
import type { DeepPartial, DeepSnakeToCamel } from './utils';
import type { Autocomplete, DeepPartial, DeepSnakeToCamel } from './utils';

export type InstanceType = 'production' | 'development';

Expand Down Expand Up @@ -805,7 +805,7 @@ type PrimitiveKeys<T> = {
[K in keyof T]: T[K] extends string | boolean | number | null ? K : never;
}[keyof T];

type LooseExtractedParams<T extends string> = `:${T}` | (string & NonNullable<unknown>);
type LooseExtractedParams<T extends string> = Autocomplete<`:${T}`>;

export type OrganizationSwitcherProps = {
/**
Expand Down
4 changes: 2 additions & 2 deletions packages/types/src/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { OrganizationResource } from './organization';
import type { MembershipRole } from './organizationMembership';
import type { ActiveSessionResource } from './session';
import type { UserResource } from './user';
import type { DeepPartial, DeepSnakeToCamel } from './utils';
import type { Autocomplete, DeepPartial, DeepSnakeToCamel } from './utils';

export type InstanceType = 'production' | 'development';

Expand Down Expand Up @@ -805,7 +805,7 @@ type PrimitiveKeys<T> = {
[K in keyof T]: T[K] extends string | boolean | number | null ? K : never;
}[keyof T];

type LooseExtractedParams<T extends string> = `:${T}` | (string & NonNullable<unknown>);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is NonNullable<unknown> the same as Record<never, never> ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ultimately produces the same result.

type LooseExtractedParams<T extends string> = Autocomplete<`:${T}`>;

export type OrganizationSwitcherProps = {
/**
Expand Down
6 changes: 2 additions & 4 deletions packages/types/src/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { SignUpField, SignUpIdentificationField, SignUpStatus } from './sig
import type { OAuthStrategy } from './strategies';
import type { BoxShadow, Color, EmUnit, FontWeight, HexColor } from './theme';
import type { UserSettingsJSON } from './userSettings';
import type { CamelToSnake } from './utils';
import type { Autocomplete, CamelToSnake } from './utils';
import type { VerificationStatus } from './verification';

export interface ClerkResourceJSON {
Expand Down Expand Up @@ -303,9 +303,7 @@ export interface OrganizationMembershipJSON extends ClerkResourceJSON {
/**
* @experimental The property is experimental and subject to change in future releases.
*/
// Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
// eslint-disable-next-line
permissions: (OrganizationPermission | (string & {}))[];
permissions: Autocomplete<OrganizationPermission>[];
public_metadata: OrganizationMembershipPublicMetadata;
public_user_data: PublicUserDataJSON;
role: MembershipRole;
Expand Down
10 changes: 4 additions & 6 deletions packages/types/src/organizationMembership.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Autocomplete } from 'utils';

import type { OrganizationResource } from './organization';
import type { ClerkResource } from './resource';
import type { PublicUserData } from './session';
Expand Down Expand Up @@ -28,9 +30,7 @@ export interface OrganizationMembershipResource extends ClerkResource {
/**
* @experimental The property is experimental and subject to change in future releases.
*/
// Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
// eslint-disable-next-line
permissions: (OrganizationPermission | (string & {}))[];
permissions: Autocomplete<OrganizationPermission>[];
publicMetadata: OrganizationMembershipPublicMetadata;
publicUserData: PublicUserData;
role: MembershipRole;
Expand All @@ -40,9 +40,7 @@ export interface OrganizationMembershipResource extends ClerkResource {
update: (updateParams: UpdateOrganizationMembershipParams) => Promise<OrganizationMembershipResource>;
}

// Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
// eslint-disable-next-line
export type MembershipRole = 'admin' | 'basic_member' | 'guest_member' | (string & {});
export type MembershipRole = Autocomplete<'admin' | 'basic_member' | 'guest_member'>;

export type OrganizationPermission =
| 'org:sys_domains:manage'
Expand Down
10 changes: 4 additions & 6 deletions packages/types/src/session.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Autocomplete } from 'utils';

import type { ActJWTClaim } from './jwt';
import type { OrganizationPermission } from './organizationMembership';
import type { ClerkResource } from './resource';
Expand Down Expand Up @@ -31,9 +33,7 @@ type CheckAuthorizationParams =
}
| {
role?: never;
// Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
// eslint-disable-next-line
permission: OrganizationPermission | (string & {});
permission: Autocomplete<OrganizationPermission>;
}
)[];
role?: never;
Expand All @@ -47,9 +47,7 @@ type CheckAuthorizationParams =
| {
some?: never;
role?: never;
// Adding (string & {}) allows for getting eslint autocomplete but also accepts any string
// eslint-disable-next-line
permission: OrganizationPermission | (string & {});
permission: Autocomplete<OrganizationPermission>;
};

export interface SessionResource extends ClerkResource {
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,9 @@ type IsSerializable<T> = T extends Function ? false : true;
export type Serializable<T> = {
[K in keyof T as IsSerializable<T[K]> extends true ? K : never]: T[K];
};

/**
* Enables autocompletion for a union type, while keeping the ability to use any string
* or type of `T`
*/
export type Autocomplete<U extends T, T = string> = U | (T & Record<never, never>);