diff --git a/packages/react/src/contexts/ClerkProvider.tsx b/packages/react/src/contexts/ClerkProvider.tsx index e349ee99c39..f2e2bccc97e 100644 --- a/packages/react/src/contexts/ClerkProvider.tsx +++ b/packages/react/src/contexts/ClerkProvider.tsx @@ -18,7 +18,6 @@ function ClerkProviderBase(props: ClerkProviderProps) { return ( diff --git a/packages/shared/src/react/ClerkContextProvider.tsx b/packages/shared/src/react/ClerkContextProvider.tsx index c0421728a90..61d4b974a2e 100644 --- a/packages/shared/src/react/ClerkContextProvider.tsx +++ b/packages/shared/src/react/ClerkContextProvider.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import type { Clerk, ClerkStatus, InitialState, LoadedClerk } from '../types'; +import type { ClerkProviderValue, ClerkStatus, InitialState, LoadedClerk } from '../types'; import { __experimental_CheckoutProvider as CheckoutProvider, ClerkInstanceContext, @@ -9,13 +9,21 @@ import { import { assertClerkSingletonExists } from './utils'; type ClerkContextProps = { - clerk: Clerk; + /** + * The Clerk instance to provide to the application. + * Accepts ClerkProviderValue which is compatible with IsomorphicClerk. + */ + clerk: ClerkProviderValue; clerkStatus?: ClerkStatus; children: React.ReactNode; initialState?: InitialState; }; export function ClerkContextProvider(props: ClerkContextProps): JSX.Element | null { + // SAFETY: This cast is safe because ClerkProviderValue is structurally compatible + // with LoadedClerk at runtime. The type difference exists because IsomorphicClerk + // wraps methods to allow void returns during pre-mount queuing. By the time + // consumers access the clerk instance, it is fully functional. const clerk = props.clerk as LoadedClerk; assertClerkSingletonExists(clerk); diff --git a/packages/shared/src/react/utils.ts b/packages/shared/src/react/utils.ts index c404daa0b7b..17cd2bd9cf4 100644 --- a/packages/shared/src/react/utils.ts +++ b/packages/shared/src/react/utils.ts @@ -1,7 +1,7 @@ import { clerkCoreErrorNoClerkSingleton } from '../internal/clerk-js/errors'; -import type { Clerk } from '../types'; +import type { LoadedClerk } from '../types'; -export function assertClerkSingletonExists(clerk: Clerk | undefined): asserts clerk is Clerk { +export function assertClerkSingletonExists(clerk: LoadedClerk | undefined): asserts clerk is LoadedClerk { if (!clerk) { clerkCoreErrorNoClerkSingleton(); } diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index d437652b38c..8d69432c822 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -188,13 +188,19 @@ export type SignOutOptions = { }; /** - * @inline + * Signs out the current user. + * + * Can be called in two ways: + * - `signOut()` or `signOut({ redirectUrl: '...' })` - with optional options + * - `signOut(callback)` or `signOut(callback, { redirectUrl: '...' })` - with callback and optional options + * + * @param optionsOrCallback - Either SignOutOptions or a callback function + * @param options - SignOutOptions when first param is a callback */ -export interface SignOut { - (options?: SignOutOptions): Promise; - - (signOutCallback?: SignOutCallback, options?: SignOutOptions): Promise; -} +export type SignOut = ( + optionsOrCallback?: SignOutOptions | SignOutCallback, + options?: SignOutOptions, +) => Promise; type ClerkEvent = keyof ClerkEventPayload; type EventHandler = (payload: ClerkEventPayload[E]) => void; @@ -2498,3 +2504,50 @@ export type IsomorphicClerkOptions = Without & { export interface LoadedClerk extends Clerk { client: ClientResource; } + +/** + * Utility type that transforms function return types to allow void. + * This is needed for IsomorphicClerk which queues method calls when clerk-js + * isn't loaded yet, returning void immediately instead of the expected value. + */ +type WithVoidReturn = T extends (...args: infer A) => infer R + ? (...args: A) => R extends Promise ? Promise : R | void + : T; + +/** + * Transforms all function properties of a type to allow void returns. + */ +type WithVoidReturnFunctions = { + [K in keyof T]: WithVoidReturn; +}; + +/** + * Type representing what ClerkProvider passes to ClerkContextProvider. + * + * This is a relaxed version of LoadedClerk that: + * 1. Allows methods to return void (for pre-mount queuing in IsomorphicClerk) + * 2. Makes client, billing, apiKeys optional (may be undefined before clerk-js loads) + * 3. Omits internal browser-specific methods that IsomorphicClerk doesn't implement + * + * The ClerkContextProvider accepts this type and casts to LoadedClerk internally. + * This cast is safe because: + * - IsomorphicClerk implements all LoadedClerk methods at runtime + * - The mutable instance is fully functional by the time consumers use it + * - Consumers check status/loaded before calling methods + */ +export type ClerkProviderValue = WithVoidReturnFunctions< + Omit< + Clerk, + | '__internal_addNavigationListener' + | '__internal_getCachedResources' + | '__internal_reloadInitialResources' + | '__internal_setActiveInProgress' + | 'client' + | 'billing' + | 'apiKeys' + > +> & { + client: ClientResource | undefined; + billing: BillingNamespace | undefined; + apiKeys: APIKeysNamespace | undefined; +};