Skip to content
Draft
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
1 change: 0 additions & 1 deletion packages/react/src/contexts/ClerkProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ function ClerkProviderBase<TUi extends Ui>(props: ClerkProviderProps<TUi>) {
return (
<ClerkContextProvider
initialState={initialState}
// @ts-expect-error - Fixme!
clerk={isomorphicClerk}
clerkStatus={clerkStatus}
>
Expand Down
12 changes: 10 additions & 2 deletions packages/shared/src/react/ClerkContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/src/react/utils.ts
Original file line number Diff line number Diff line change
@@ -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();
}
Expand Down
65 changes: 59 additions & 6 deletions packages/shared/src/types/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;

(signOutCallback?: SignOutCallback, options?: SignOutOptions): Promise<void>;
}
export type SignOut = (
optionsOrCallback?: SignOutOptions | SignOutCallback,
options?: SignOutOptions,
) => Promise<void>;

type ClerkEvent = keyof ClerkEventPayload;
type EventHandler<E extends ClerkEvent> = (payload: ClerkEventPayload[E]) => void;
Expand Down Expand Up @@ -2498,3 +2504,50 @@ export type IsomorphicClerkOptions = Without<ClerkOptions, 'isSatellite'> & {
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> = T extends (...args: infer A) => infer R
? (...args: A) => R extends Promise<infer U> ? Promise<U | void> : R | void
: T;

/**
* Transforms all function properties of a type to allow void returns.
*/
type WithVoidReturnFunctions<T> = {
[K in keyof T]: WithVoidReturn<T[K]>;
};

/**
* 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;
};