@@ -115,6 +115,66 @@ type IsomorphicLoadedClerk = Without<
115
115
apiKeys : APIKeysNamespace | undefined ;
116
116
} ;
117
117
118
+ export type RecursiveMock = {
119
+ ( ...args : unknown [ ] ) : RecursiveMock ;
120
+ } & {
121
+ readonly [ key in string | symbol ] : RecursiveMock ;
122
+ } ;
123
+
124
+ /**
125
+ * Creates a recursively self-referential Proxy that safely handles:
126
+ * - Arbitrary property access (e.g., obj.any.prop.path)
127
+ * - Function calls at any level (e.g., obj.a().b.c())
128
+ * - Construction (e.g., new obj.a.b())
129
+ *
130
+ * Always returns itself to allow infinite chaining without throwing.
131
+ */
132
+ function createRecursiveProxy ( label : string = 'Mock' ) : RecursiveMock {
133
+ // The callable target for the proxy so that `apply` works
134
+ const callableTarget = function noop ( ) : void { } ;
135
+
136
+ // eslint-disable-next-line prefer-const
137
+ let self : RecursiveMock ;
138
+ const handler : ProxyHandler < typeof callableTarget > = {
139
+ get ( _target , prop ) {
140
+ // Avoid being treated as a Promise/thenable by test runners or frameworks
141
+ if ( prop === 'then' ) {
142
+ return undefined ;
143
+ }
144
+ if ( prop === 'toString' ) {
145
+ return ( ) => `[${ label } ]` ;
146
+ }
147
+ if ( prop === Symbol . toPrimitive ) {
148
+ return ( ) => 0 ;
149
+ }
150
+ return self ;
151
+ } ,
152
+ apply ( ) {
153
+ return self ;
154
+ } ,
155
+ construct ( ) {
156
+ return self as unknown as object ;
157
+ } ,
158
+ has ( ) {
159
+ return true ;
160
+ } ,
161
+ set ( ) {
162
+ return true ;
163
+ } ,
164
+ } ;
165
+
166
+ self = new Proxy ( callableTarget , handler ) as unknown as RecursiveMock ;
167
+ return self ;
168
+ }
169
+
170
+ /**
171
+ * Returns a permissive mock compatible with `QueryClient` usage in tests.
172
+ * It accepts any chain of property accesses and calls without throwing.
173
+ */
174
+ export function createMockQueryClient ( ) : RecursiveMock {
175
+ return createRecursiveProxy ( 'MockQueryClient' ) as unknown as RecursiveMock ;
176
+ }
177
+
118
178
export class IsomorphicClerk implements IsomorphicLoadedClerk {
119
179
private readonly mode : 'browser' | 'server' ;
120
180
private readonly options : IsomorphicClerkOptions ;
@@ -146,6 +206,8 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
146
206
private premountApiKeysNodes = new Map < HTMLDivElement , APIKeysProps | undefined > ( ) ;
147
207
private premountOAuthConsentNodes = new Map < HTMLDivElement , __internal_OAuthConsentProps | undefined > ( ) ;
148
208
private premountTaskChooseOrganizationNodes = new Map < HTMLDivElement , TaskChooseOrganizationProps | undefined > ( ) ;
209
+ private prefetchQueryClientStatus = false ;
210
+
149
211
// A separate Map of `addListener` method calls to handle multiple listeners.
150
212
private premountAddListenerCalls = new Map <
151
213
ListenerCallback ,
@@ -162,6 +224,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
162
224
#publishableKey: string ;
163
225
#eventBus = createClerkEventBus ( ) ;
164
226
#stateProxy: StateProxy ;
227
+ #__internal_queryClient = createMockQueryClient ( ) ;
165
228
166
229
get publishableKey ( ) : string {
167
230
return this . #publishableKey;
@@ -283,6 +346,18 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
283
346
return this . clerkjs ?. isStandardBrowser || this . options . standardBrowser || false ;
284
347
}
285
348
349
+ get __internal_queryClient ( ) {
350
+ // @ts -expect-error - __internal_queryClient is not typed
351
+ if ( ! this . clerkjs ?. __internal_queryClient ) {
352
+ // @ts -expect-error - __internal_queryClient is not typed
353
+ void this . clerkjs ?. getInternalQueryClient ?.( ) ;
354
+ this . prefetchQueryClientStatus = true ;
355
+ }
356
+
357
+ // @ts -expect-error - __internal_queryClient is not typed
358
+ return this . clerkjs ?. __internal_queryClient || this . #__internal_queryClient;
359
+ }
360
+
286
361
get isSatellite ( ) {
287
362
// This getter can run in environments where window is not available.
288
363
// In those cases we should expect and use domain as a string
@@ -567,6 +642,13 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
567
642
this . on ( 'status' , listener , { notify : true } ) ;
568
643
} ) ;
569
644
645
+ // @ts -expect-error - queryClientStatus is not typed
646
+ this . #eventBus. internal . retrieveListeners ( 'queryClientStatus' ) ?. forEach ( listener => {
647
+ // Since clerkjs exists it will call `this.clerkjs.on('status', listener)`
648
+ // @ts -expect-error - queryClientStatus is not typed
649
+ this . on ( 'queryClientStatus' , listener , { notify : true } ) ;
650
+ } ) ;
651
+
570
652
if ( this . preopenSignIn !== null ) {
571
653
clerkjs . openSignIn ( this . preopenSignIn ) ;
572
654
}
@@ -611,6 +693,11 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
611
693
clerkjs . openWaitlist ( this . preOpenWaitlist ) ;
612
694
}
613
695
696
+ if ( this . prefetchQueryClientStatus ) {
697
+ // @ts -expect-error - queryClientStatus is not typed
698
+ this . clerkjs . getInternalQueryClient ?.( ) ;
699
+ }
700
+
614
701
this . premountSignInNodes . forEach ( ( props , node ) => {
615
702
clerkjs . mountSignIn ( node , props ) ;
616
703
} ) ;
0 commit comments