Skip to content

Commit 692b68d

Browse files
fix(clerk-js): Restore internal queryClient for backward compat with older SDKs (#8562)
Co-authored-by: Jacek <jacek@clerk.dev>
1 parent 5a7225e commit 692b68d

8 files changed

Lines changed: 118 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
---
4+
5+
Restore the `clerk.__internal_queryClient` getter as a backward-compatibility shim so apps still on `@clerk/shared < 4.10.0` can hydrate their query client and continue to render paginated hooks (e.g. `useOrganizationList`, `useOrganization`). The getter lazily imports `@tanstack/query-core` only when accessed, so apps on `@clerk/shared >= 4.10.0` (which use the singleton in `@clerk/shared`) pay zero runtime cost.

packages/clerk-js/bundlewatch.config.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
{
22
"files": [
3-
{ "path": "./dist/clerk.js", "maxSize": "543KB" },
3+
{ "path": "./dist/clerk.js", "maxSize": "549KB" },
44
{ "path": "./dist/clerk.browser.js", "maxSize": "70KB" },
55
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "112KB" },
6-
{ "path": "./dist/clerk.no-rhc.js", "maxSize": "311KB" },
6+
{ "path": "./dist/clerk.no-rhc.js", "maxSize": "316KB" },
77
{ "path": "./dist/clerk.native.js", "maxSize": "70KB" },
88
{ "path": "./dist/vendors*.js", "maxSize": "7KB" },
99
{ "path": "./dist/coinbase*.js", "maxSize": "36KB" },
1010
{ "path": "./dist/base-account-sdk*.js", "maxSize": "203KB" },
1111
{ "path": "./dist/stripe-vendors*.js", "maxSize": "1KB" },
12+
{ "path": "./dist/query-core-vendors*.js", "maxSize": "11KB" },
1213
{ "path": "./dist/zxcvbn-ts-core*.js", "maxSize": "12KB" },
1314
{ "path": "./dist/zxcvbn-common*.js", "maxSize": "226KB" }
1415
]

packages/clerk-js/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"@solana/wallet-standard": "catalog:module-manager",
9292
"@stripe/stripe-js": "5.6.0",
9393
"@swc/helpers": "catalog:repo",
94+
"@tanstack/query-core": "catalog:repo",
9495
"@wallet-standard/core": "catalog:module-manager",
9596
"@zxcvbn-ts/core": "catalog:module-manager",
9697
"@zxcvbn-ts/language-common": "catalog:module-manager",

packages/clerk-js/rspack.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ const common = ({ mode, variant, disableRHC = false }) => {
110110
chunks: 'all',
111111
enforce: true,
112112
},
113+
queryCoreVendor: {
114+
test: /[\\/]node_modules[\\/](@tanstack\/query-core)[\\/]/,
115+
name: 'query-core-vendors',
116+
chunks: 'all',
117+
enforce: true,
118+
},
113119
defaultVendors: {
114120
minChunks: 1,
115121
test: module => {
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { QueryClient } from '@tanstack/query-core';
2+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3+
4+
import { Clerk } from '../clerk';
5+
import { Client, Environment } from '../resources/internal';
6+
7+
vi.mock('../resources/Client');
8+
vi.mock('../resources/Environment');
9+
10+
vi.mock('../auth/devBrowser', () => ({
11+
createDevBrowser: () => ({
12+
clear: vi.fn(),
13+
setup: vi.fn(),
14+
getDevBrowser: vi.fn(() => 'deadbeef'),
15+
setDevBrowser: vi.fn(),
16+
removeDevBrowser: vi.fn(),
17+
refreshCookies: vi.fn(),
18+
}),
19+
}));
20+
21+
Client.getOrCreateInstance = vi.fn().mockImplementation(() => ({ fetch: vi.fn() }));
22+
Environment.getInstance = vi.fn().mockImplementation(() => ({ fetch: vi.fn(() => Promise.resolve({})) }));
23+
24+
const publishableKey = 'pk_test_Y2xlcmsuYWJjZWYuMTIzNDUuZGV2LmxjbGNsZXJrLmNvbSQ';
25+
26+
describe('Clerk __internal_queryClient (backward compat shim)', () => {
27+
let clerk: Clerk;
28+
29+
beforeEach(() => {
30+
clerk = new Clerk(publishableKey);
31+
});
32+
33+
afterEach(() => {
34+
vi.restoreAllMocks();
35+
});
36+
37+
it('returns undefined before the lazy import resolves', () => {
38+
expect(clerk.__internal_queryClient).toBeUndefined();
39+
});
40+
41+
it('returns a tagged QueryClient after the lazy import resolves', async () => {
42+
// Trigger the getter (fires the lazy import)
43+
void clerk.__internal_queryClient;
44+
45+
// Wait for the dynamic import and QueryClient construction to settle
46+
await vi.dynamicImportSettled();
47+
48+
const result = clerk.__internal_queryClient;
49+
expect(result).toBeDefined();
50+
expect(result!.__tag).toBe('clerk-rq-client');
51+
expect(result!.client).toBeInstanceOf(QueryClient);
52+
});
53+
54+
it('returns the same QueryClient instance on repeated access', async () => {
55+
void clerk.__internal_queryClient;
56+
await vi.dynamicImportSettled();
57+
58+
const first = clerk.__internal_queryClient;
59+
const second = clerk.__internal_queryClient;
60+
expect(first!.client).toBe(second!.client);
61+
});
62+
63+
it('emits queryClientStatus event when the client is ready', async () => {
64+
const listener = vi.fn();
65+
// @ts-expect-error - queryClientStatus is not typed on clerk.on
66+
clerk.on('queryClientStatus', listener);
67+
68+
void clerk.__internal_queryClient;
69+
await vi.dynamicImportSettled();
70+
71+
expect(listener).toHaveBeenCalledWith('ready');
72+
// @ts-expect-error
73+
clerk.off('queryClientStatus', listener);
74+
});
75+
});

packages/clerk-js/src/core/clerk.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ import type {
140140
import type { ClerkUI } from '@clerk/shared/ui';
141141
import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url';
142142
import { allSettled, handleValueOrFn, noop } from '@clerk/shared/utils';
143+
import type { QueryClient } from '@tanstack/query-core';
143144

144145
import { debugLogger, initDebugLogger } from '@/utils/debug';
145146
import { ModuleManager } from '@/utils/moduleManager';
@@ -252,6 +253,7 @@ export class Clerk implements ClerkInterface {
252253
// converted to protected environment to support `updateEnvironment` type assertion
253254
protected environment?: EnvironmentResource | null;
254255

256+
#queryClient: QueryClient | undefined;
255257
#publishableKey = '';
256258
#domain: DomainOrProxyUrl['domain'];
257259
#proxyUrl: DomainOrProxyUrl['proxyUrl'];
@@ -271,6 +273,28 @@ export class Clerk implements ClerkInterface {
271273
#touchThrottledUntil = 0;
272274
#publicEventBus = createClerkEventBus();
273275

276+
get __internal_queryClient(): { __tag: 'clerk-rq-client'; client: QueryClient } | undefined {
277+
if (!this.#queryClient) {
278+
void import('./query-core')
279+
.then(module => module.QueryClient)
280+
.then(QueryClient => {
281+
if (this.#queryClient) {
282+
return;
283+
}
284+
this.#queryClient = new QueryClient();
285+
// @ts-expect-error - queryClientStatus is not typed
286+
this.#publicEventBus.emit('queryClientStatus', 'ready');
287+
});
288+
}
289+
290+
return this.#queryClient
291+
? {
292+
__tag: 'clerk-rq-client',
293+
client: this.#queryClient,
294+
}
295+
: undefined;
296+
}
297+
274298
public __internal_getCachedResources:
275299
| (() => Promise<{ client: ClientJSONSnapshot | null; environment: EnvironmentJSONSnapshot | null }>)
276300
| undefined;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { QueryClient } from '@tanstack/query-core';

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)