diff --git a/.changeset/remove-swr-switches.md b/.changeset/remove-swr-switches.md new file mode 100644 index 00000000000..a8b10845b82 --- /dev/null +++ b/.changeset/remove-swr-switches.md @@ -0,0 +1,5 @@ +--- +'@clerk/shared': major +--- + +Remove SWR hooks and env-based switchovers in favor of the React Query implementations; promote @tanstack/query-core to a runtime dependency. diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index bf81e95b410..b675002cb26 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -67,7 +67,7 @@ "@solana/wallet-standard": "catalog:module-manager", "@stripe/stripe-js": "5.6.0", "@swc/helpers": "^0.5.17", - "@tanstack/query-core": "5.87.4", + "@tanstack/query-core": "5.90.12", "@wallet-standard/core": "catalog:module-manager", "@zxcvbn-ts/core": "catalog:module-manager", "@zxcvbn-ts/language-common": "catalog:module-manager", diff --git a/packages/shared/global.d.ts b/packages/shared/global.d.ts index ffc87dfc746..9bac5865c4b 100644 --- a/packages/shared/global.d.ts +++ b/packages/shared/global.d.ts @@ -4,7 +4,6 @@ declare const JS_PACKAGE_VERSION: string; declare const UI_PACKAGE_VERSION: string; declare const __DEV__: boolean; declare const __BUILD_DISABLE_RHC__: boolean; -declare const __CLERK_USE_RQ__: boolean; interface ImportMetaEnv { readonly [key: string]: string; diff --git a/packages/shared/package.json b/packages/shared/package.json index 0ad144e3b78..5fc36e78645 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -124,11 +124,11 @@ "test:coverage": "vitest --collectCoverage && open coverage/lcov-report/index.html" }, "dependencies": { + "@tanstack/query-core": "5.90.12", "dequal": "2.0.3", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", - "std-env": "^3.9.0", - "swr": "2.3.4" + "std-env": "^3.9.0" }, "devDependencies": { "@base-org/account": "catalog:module-manager", @@ -138,7 +138,6 @@ "@solana/wallet-standard": "catalog:module-manager", "@stripe/react-stripe-js": "3.1.1", "@stripe/stripe-js": "5.6.0", - "@tanstack/query-core": "5.87.4", "@types/glob-to-regexp": "0.4.4", "@types/js-cookie": "3.0.6", "@wallet-standard/core": "catalog:module-manager", diff --git a/packages/shared/src/react/billing/useInitializePaymentMethod.rq.tsx b/packages/shared/src/react/billing/useInitializePaymentMethod.rq.tsx deleted file mode 100644 index 5c58075c6b6..00000000000 --- a/packages/shared/src/react/billing/useInitializePaymentMethod.rq.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useCallback, useMemo } from 'react'; - -import type { BillingInitializedPaymentMethodResource, ForPayerType } from '../../types'; -import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; -import { useClerkQueryClient } from '../clerk-rq/use-clerk-query-client'; -import { useClerkQuery } from '../clerk-rq/useQuery'; -import { useOrganizationContext, useUserContext } from '../contexts'; -import { useBillingHookEnabled } from '../hooks/useBillingHookEnabled'; - -type InitializePaymentMethodOptions = { - for?: ForPayerType; -}; - -export type UseInitializePaymentMethodResult = { - initializedPaymentMethod: BillingInitializedPaymentMethodResource | undefined; - initializePaymentMethod: () => Promise; -}; - -/** - * @internal - */ -function useInitializePaymentMethod(options?: InitializePaymentMethodOptions): UseInitializePaymentMethodResult { - const { for: forType } = options ?? {}; - const { organization } = useOrganizationContext(); - const user = useUserContext(); - - const resource = forType === 'organization' ? organization : user; - - const billingEnabled = useBillingHookEnabled(options); - - const queryKey = useMemo(() => { - return ['billing-payment-method-initialize', { resourceId: resource?.id }] as const; - }, [resource?.id]); - - const isEnabled = Boolean(resource?.id) && billingEnabled; - - const query = useClerkQuery({ - queryKey, - queryFn: async () => { - if (!resource) { - return undefined; - } - - return resource.initializePaymentMethod({ - gateway: 'stripe', - }); - }, - enabled: isEnabled, - staleTime: 1_000 * 60, - refetchOnWindowFocus: false, - placeholderData: defineKeepPreviousDataFn(true), - }); - - const [queryClient] = useClerkQueryClient(); - - const initializePaymentMethod = useCallback(async () => { - if (!resource) { - return undefined; - } - - const result = await resource.initializePaymentMethod({ - gateway: 'stripe', - }); - - queryClient.setQueryData(queryKey, result); - - return result; - }, [queryClient, queryKey, resource]); - - return { - initializedPaymentMethod: query.data ?? undefined, - initializePaymentMethod, - }; -} - -export { useInitializePaymentMethod as __internal_useInitializePaymentMethod }; diff --git a/packages/shared/src/react/billing/useInitializePaymentMethod.swr.tsx b/packages/shared/src/react/billing/useInitializePaymentMethod.swr.tsx deleted file mode 100644 index 8a4a3df8f35..00000000000 --- a/packages/shared/src/react/billing/useInitializePaymentMethod.swr.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { useEffect } from 'react'; -import useSWRMutation from 'swr/mutation'; - -import type { BillingInitializedPaymentMethodResource, ForPayerType } from '../../types'; -import { useOrganizationContext, useUserContext } from '../contexts'; - -type InitializePaymentMethodOptions = { - for?: ForPayerType; -}; - -export type UseInitializePaymentMethodResult = { - initializedPaymentMethod: BillingInitializedPaymentMethodResource | undefined; - initializePaymentMethod: () => Promise; -}; - -/** - * This is the existing implementation of the payment method initializer using SWR. - * It is kept here for backwards compatibility until our next major version. - * - * @internal - */ -function useInitializePaymentMethod(options?: InitializePaymentMethodOptions): UseInitializePaymentMethodResult { - const { for: forType = 'user' } = options ?? {}; - const { organization } = useOrganizationContext(); - const user = useUserContext(); - - const resource = forType === 'organization' ? organization : user; - - const { data, trigger } = useSWRMutation( - resource?.id - ? { - key: 'billing-payment-method-initialize', - resourceId: resource.id, - for: forType, - } - : null, - () => { - return resource?.initializePaymentMethod({ - gateway: 'stripe', - }); - }, - ); - - useEffect(() => { - if (!resource?.id) { - return; - } - - trigger().catch(() => { - // ignore errors - }); - }, [resource?.id, trigger]); - - return { - initializedPaymentMethod: data, - initializePaymentMethod: trigger, - }; -} - -export { useInitializePaymentMethod as __internal_useInitializePaymentMethod }; diff --git a/packages/shared/src/react/billing/useInitializePaymentMethod.tsx b/packages/shared/src/react/billing/useInitializePaymentMethod.tsx index 1373b76c409..5c58075c6b6 100644 --- a/packages/shared/src/react/billing/useInitializePaymentMethod.tsx +++ b/packages/shared/src/react/billing/useInitializePaymentMethod.tsx @@ -1,2 +1,76 @@ -export type { UseInitializePaymentMethodResult } from 'virtual:data-hooks/useInitializePaymentMethod'; -export { __internal_useInitializePaymentMethod } from 'virtual:data-hooks/useInitializePaymentMethod'; +import { useCallback, useMemo } from 'react'; + +import type { BillingInitializedPaymentMethodResource, ForPayerType } from '../../types'; +import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQueryClient } from '../clerk-rq/use-clerk-query-client'; +import { useClerkQuery } from '../clerk-rq/useQuery'; +import { useOrganizationContext, useUserContext } from '../contexts'; +import { useBillingHookEnabled } from '../hooks/useBillingHookEnabled'; + +type InitializePaymentMethodOptions = { + for?: ForPayerType; +}; + +export type UseInitializePaymentMethodResult = { + initializedPaymentMethod: BillingInitializedPaymentMethodResource | undefined; + initializePaymentMethod: () => Promise; +}; + +/** + * @internal + */ +function useInitializePaymentMethod(options?: InitializePaymentMethodOptions): UseInitializePaymentMethodResult { + const { for: forType } = options ?? {}; + const { organization } = useOrganizationContext(); + const user = useUserContext(); + + const resource = forType === 'organization' ? organization : user; + + const billingEnabled = useBillingHookEnabled(options); + + const queryKey = useMemo(() => { + return ['billing-payment-method-initialize', { resourceId: resource?.id }] as const; + }, [resource?.id]); + + const isEnabled = Boolean(resource?.id) && billingEnabled; + + const query = useClerkQuery({ + queryKey, + queryFn: async () => { + if (!resource) { + return undefined; + } + + return resource.initializePaymentMethod({ + gateway: 'stripe', + }); + }, + enabled: isEnabled, + staleTime: 1_000 * 60, + refetchOnWindowFocus: false, + placeholderData: defineKeepPreviousDataFn(true), + }); + + const [queryClient] = useClerkQueryClient(); + + const initializePaymentMethod = useCallback(async () => { + if (!resource) { + return undefined; + } + + const result = await resource.initializePaymentMethod({ + gateway: 'stripe', + }); + + queryClient.setQueryData(queryKey, result); + + return result; + }, [queryClient, queryKey, resource]); + + return { + initializedPaymentMethod: query.data ?? undefined, + initializePaymentMethod, + }; +} + +export { useInitializePaymentMethod as __internal_useInitializePaymentMethod }; diff --git a/packages/shared/src/react/billing/useStripeClerkLibs.rq.tsx b/packages/shared/src/react/billing/useStripeClerkLibs.rq.tsx deleted file mode 100644 index e2dd394b24c..00000000000 --- a/packages/shared/src/react/billing/useStripeClerkLibs.rq.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import type { loadStripe } from '@stripe/stripe-js'; - -import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; -import { useClerkQuery } from '../clerk-rq/useQuery'; -import { useBillingHookEnabled } from '../hooks/useBillingHookEnabled'; -import { useClerk } from '../hooks/useClerk'; - -type LoadStripeFn = typeof loadStripe; - -type StripeClerkLibs = { - loadStripe: LoadStripeFn; -}; - -/** - * @internal - */ -function useStripeClerkLibs(): StripeClerkLibs | null { - const clerk = useClerk(); - - const billingEnabled = useBillingHookEnabled(); - - const query = useClerkQuery({ - queryKey: ['clerk-stripe-sdk'], - queryFn: async () => { - const loadStripe = (await clerk.__internal_loadStripeJs()) as LoadStripeFn; - return { loadStripe }; - }, - enabled: billingEnabled, - staleTime: Infinity, - refetchOnWindowFocus: false, - placeholderData: defineKeepPreviousDataFn(true), - }); - - return query.data ?? null; -} - -export { useStripeClerkLibs as __internal_useStripeClerkLibs }; diff --git a/packages/shared/src/react/billing/useStripeClerkLibs.swr.tsx b/packages/shared/src/react/billing/useStripeClerkLibs.swr.tsx deleted file mode 100644 index 820144b4dff..00000000000 --- a/packages/shared/src/react/billing/useStripeClerkLibs.swr.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { loadStripe } from '@stripe/stripe-js'; - -import { useSWR } from '../clerk-swr'; -import { useClerk } from '../hooks/useClerk'; - -type LoadStripeFn = typeof loadStripe; - -type StripeClerkLibs = { - loadStripe: LoadStripeFn; -}; - -export type UseStripeClerkLibsResult = StripeClerkLibs | null; - -/** - * This is the existing implementation of the Stripe libraries loader using SWR. - * It is kept here for backwards compatibility until our next major version. - * - * @internal - */ -function useStripeClerkLibs(): UseStripeClerkLibsResult { - const clerk = useClerk(); - - const swr = useSWR( - 'clerk-stripe-sdk', - async () => { - const loadStripe = (await clerk.__internal_loadStripeJs()) as LoadStripeFn; - return { loadStripe }; - }, - { - keepPreviousData: true, - revalidateOnFocus: false, - dedupingInterval: Infinity, - }, - ); - - return swr.data ?? null; -} - -export { useStripeClerkLibs as __internal_useStripeClerkLibs }; diff --git a/packages/shared/src/react/billing/useStripeClerkLibs.tsx b/packages/shared/src/react/billing/useStripeClerkLibs.tsx index 3a55aaca025..197df86743a 100644 --- a/packages/shared/src/react/billing/useStripeClerkLibs.tsx +++ b/packages/shared/src/react/billing/useStripeClerkLibs.tsx @@ -1,2 +1,39 @@ -export type { UseStripeClerkLibsResult } from 'virtual:data-hooks/useStripeClerkLibs'; -export { __internal_useStripeClerkLibs } from 'virtual:data-hooks/useStripeClerkLibs'; +import type { loadStripe } from '@stripe/stripe-js'; + +import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQuery } from '../clerk-rq/useQuery'; +import { useBillingHookEnabled } from '../hooks/useBillingHookEnabled'; +import { useClerk } from '../hooks/useClerk'; + +type LoadStripeFn = typeof loadStripe; + +type StripeClerkLibs = { + loadStripe: LoadStripeFn; +}; + +export type UseStripeClerkLibsResult = StripeClerkLibs | null; + +/** + * @internal + */ +function useStripeClerkLibs(): UseStripeClerkLibsResult { + const clerk = useClerk(); + + const billingEnabled = useBillingHookEnabled(); + + const query = useClerkQuery({ + queryKey: ['clerk-stripe-sdk'], + queryFn: async () => { + const loadStripe = (await clerk.__internal_loadStripeJs()) as LoadStripeFn; + return { loadStripe }; + }, + enabled: billingEnabled, + staleTime: Infinity, + refetchOnWindowFocus: false, + placeholderData: defineKeepPreviousDataFn(true), + }); + + return query.data ?? null; +} + +export { useStripeClerkLibs as __internal_useStripeClerkLibs }; diff --git a/packages/shared/src/react/billing/useStripeLoader.rq.tsx b/packages/shared/src/react/billing/useStripeLoader.rq.tsx deleted file mode 100644 index 59dee615f6b..00000000000 --- a/packages/shared/src/react/billing/useStripeLoader.rq.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { Stripe } from '@stripe/stripe-js'; -import { useMemo } from 'react'; - -import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; -import { useClerkQuery } from '../clerk-rq/useQuery'; -import { useBillingHookEnabled } from '../hooks/useBillingHookEnabled'; -import type { UseStripeClerkLibsResult } from './useStripeClerkLibs'; - -type StripeLoaderOptions = { - stripeClerkLibs: UseStripeClerkLibsResult; - externalGatewayId?: string; - stripePublishableKey?: string; -}; - -export type UseStripeLoaderResult = Stripe | null | undefined; - -/** - * @internal - */ -function useStripeLoader(options: StripeLoaderOptions): UseStripeLoaderResult { - const { stripeClerkLibs, externalGatewayId, stripePublishableKey } = options; - - const queryKey = useMemo(() => { - return ['stripe-sdk', { externalGatewayId, stripePublishableKey }] as const; - }, [externalGatewayId, stripePublishableKey]); - - const billingEnabled = useBillingHookEnabled({ authenticated: true }); - - const isEnabled = Boolean(stripeClerkLibs && externalGatewayId && stripePublishableKey) && billingEnabled; - - const query = useClerkQuery({ - queryKey, - queryFn: () => { - if (!stripeClerkLibs || !externalGatewayId || !stripePublishableKey) { - return null; - } - - return stripeClerkLibs.loadStripe(stripePublishableKey, { - stripeAccount: externalGatewayId, - }); - }, - enabled: isEnabled, - staleTime: 1_000 * 60, - refetchOnWindowFocus: false, - placeholderData: defineKeepPreviousDataFn(true), - }); - - return query.data; -} - -export { useStripeLoader as __internal_useStripeLoader }; diff --git a/packages/shared/src/react/billing/useStripeLoader.swr.tsx b/packages/shared/src/react/billing/useStripeLoader.swr.tsx deleted file mode 100644 index 57e396dcddc..00000000000 --- a/packages/shared/src/react/billing/useStripeLoader.swr.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { Stripe } from '@stripe/stripe-js'; - -import { useSWR } from '../clerk-swr'; -import type { UseStripeClerkLibsResult } from './useStripeClerkLibs'; - -type StripeLoaderOptions = { - stripeClerkLibs: UseStripeClerkLibsResult; - externalGatewayId?: string; - stripePublishableKey?: string; -}; - -export type UseStripeLoaderResult = Stripe | null | undefined; - -/** - * This is the existing implementation of the Stripe instance loader using SWR. - * It is kept here for backwards compatibility until our next major version. - * - * @internal - */ -function useStripeLoader(options: StripeLoaderOptions): UseStripeLoaderResult { - const { stripeClerkLibs, externalGatewayId, stripePublishableKey } = options; - - const swr = useSWR( - stripeClerkLibs && externalGatewayId && stripePublishableKey - ? { - key: 'stripe-sdk', - externalGatewayId, - stripePublishableKey, - } - : null, - ({ stripePublishableKey, externalGatewayId }) => { - return stripeClerkLibs?.loadStripe(stripePublishableKey, { - stripeAccount: externalGatewayId, - }); - }, - { - keepPreviousData: true, - revalidateOnFocus: false, - dedupingInterval: 1_000 * 60, - }, - ); - - return swr.data; -} - -export { useStripeLoader as __internal_useStripeLoader }; diff --git a/packages/shared/src/react/billing/useStripeLoader.tsx b/packages/shared/src/react/billing/useStripeLoader.tsx index 689fed791c4..59dee615f6b 100644 --- a/packages/shared/src/react/billing/useStripeLoader.tsx +++ b/packages/shared/src/react/billing/useStripeLoader.tsx @@ -1,2 +1,51 @@ -export type { UseStripeLoaderResult } from 'virtual:data-hooks/useStripeLoader'; -export { __internal_useStripeLoader } from 'virtual:data-hooks/useStripeLoader'; +import type { Stripe } from '@stripe/stripe-js'; +import { useMemo } from 'react'; + +import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQuery } from '../clerk-rq/useQuery'; +import { useBillingHookEnabled } from '../hooks/useBillingHookEnabled'; +import type { UseStripeClerkLibsResult } from './useStripeClerkLibs'; + +type StripeLoaderOptions = { + stripeClerkLibs: UseStripeClerkLibsResult; + externalGatewayId?: string; + stripePublishableKey?: string; +}; + +export type UseStripeLoaderResult = Stripe | null | undefined; + +/** + * @internal + */ +function useStripeLoader(options: StripeLoaderOptions): UseStripeLoaderResult { + const { stripeClerkLibs, externalGatewayId, stripePublishableKey } = options; + + const queryKey = useMemo(() => { + return ['stripe-sdk', { externalGatewayId, stripePublishableKey }] as const; + }, [externalGatewayId, stripePublishableKey]); + + const billingEnabled = useBillingHookEnabled({ authenticated: true }); + + const isEnabled = Boolean(stripeClerkLibs && externalGatewayId && stripePublishableKey) && billingEnabled; + + const query = useClerkQuery({ + queryKey, + queryFn: () => { + if (!stripeClerkLibs || !externalGatewayId || !stripePublishableKey) { + return null; + } + + return stripeClerkLibs.loadStripe(stripePublishableKey, { + stripeAccount: externalGatewayId, + }); + }, + enabled: isEnabled, + staleTime: 1_000 * 60, + refetchOnWindowFocus: false, + placeholderData: defineKeepPreviousDataFn(true), + }); + + return query.data; +} + +export { useStripeLoader as __internal_useStripeLoader }; diff --git a/packages/shared/src/react/clerk-swr.ts b/packages/shared/src/react/clerk-swr.ts deleted file mode 100644 index 5d03ac36156..00000000000 --- a/packages/shared/src/react/clerk-swr.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; - -// TODO: Replace these SWR re-exports with react-query equivalents. -export * from 'swr'; - -export { default as useSWR } from 'swr'; -export { default as useSWRInfinite } from 'swr/infinite'; diff --git a/packages/shared/src/react/hooks/__tests__/createBillingPaginatedHook.spec.tsx b/packages/shared/src/react/hooks/__tests__/createBillingPaginatedHook.spec.tsx index 7c9032cb449..1c1394a07b6 100644 --- a/packages/shared/src/react/hooks/__tests__/createBillingPaginatedHook.spec.tsx +++ b/packages/shared/src/react/hooks/__tests__/createBillingPaginatedHook.spec.tsx @@ -308,16 +308,7 @@ describe('createBillingPaginatedHook', () => { expect(params).toStrictEqual({ initialPage: 1, pageSize: 5 }); }); - if (__CLERK_USE_RQ__) { - expect(result.current.isLoading).toBe(false); - } else { - // Attention: We are forcing fetcher to be executed instead of setting the key to null - // because SWR will continue to display the cached data when the key is null and `keepPreviousData` is true. - // This means that SWR will update the loading state to true even if the fetcher is not called, - // because the key changes from `{..., userId: 'user_1'}` to `{..., userId: undefined}`. - await waitFor(() => expect(result.current.isLoading).toBe(true)); - await waitFor(() => expect(result.current.isLoading).toBe(false)); - } + expect(result.current.isLoading).toBe(false); // Data should be cleared even with keepPreviousData: true // The key difference here vs usePagesOrInfinite test: userId in cache key changes @@ -543,11 +534,7 @@ describe('createBillingPaginatedHook', () => { await result.current.paginated.revalidate(); }); - if (__CLERK_USE_RQ__) { - await waitFor(() => expect(fetcherMock.mock.calls.length).toBeGreaterThanOrEqual(2)); - } else { - await waitFor(() => expect(fetcherMock).toHaveBeenCalledTimes(1)); - } + await waitFor(() => expect(fetcherMock.mock.calls.length).toBeGreaterThanOrEqual(2)); }); }); }); diff --git a/packages/shared/src/react/hooks/__tests__/useAPIKeys.spec.tsx b/packages/shared/src/react/hooks/__tests__/useAPIKeys.spec.tsx index 075b794a406..570a3ce1d3d 100644 --- a/packages/shared/src/react/hooks/__tests__/useAPIKeys.spec.tsx +++ b/packages/shared/src/react/hooks/__tests__/useAPIKeys.spec.tsx @@ -89,13 +89,7 @@ describe('useApiKeys', () => { await result.current.paginated.revalidate(); }); - const isRQ = Boolean((globalThis as any).__CLERK_USE_RQ__); - - if (isRQ) { - await waitFor(() => expect(getAllSpy.mock.calls.length).toBeGreaterThanOrEqual(2)); - } else { - await waitFor(() => expect(getAllSpy).toHaveBeenCalledTimes(1)); - } + await waitFor(() => expect(getAllSpy.mock.calls.length).toBeGreaterThanOrEqual(2)); }); it('handles revalidation with different pageSize configurations', async () => { @@ -125,15 +119,8 @@ describe('useApiKeys', () => { await result.current.small.revalidate(); }); - const isRQ = Boolean((globalThis as any).__CLERK_USE_RQ__); - await waitFor(() => expect(getAllSpy.mock.calls.length).toBeGreaterThanOrEqual(1)); - - if (isRQ) { - await waitFor(() => expect(getAllSpy.mock.calls.length).toBeGreaterThanOrEqual(2)); - } else { - expect(getAllSpy).toHaveBeenCalledTimes(2); - } + await waitFor(() => expect(getAllSpy.mock.calls.length).toBeGreaterThanOrEqual(2)); }); it('handles revalidation with different query filters', async () => { @@ -163,15 +150,8 @@ describe('useApiKeys', () => { await result.current.defaultQuery.revalidate(); }); - const isRQ = Boolean((globalThis as any).__CLERK_USE_RQ__); - await waitFor(() => expect(getAllSpy.mock.calls.length).toBeGreaterThanOrEqual(1)); - - if (isRQ) { - await waitFor(() => expect(getAllSpy.mock.calls.length).toBeGreaterThanOrEqual(2)); - } else { - expect(getAllSpy).toHaveBeenCalledTimes(2); - } + await waitFor(() => expect(getAllSpy.mock.calls.length).toBeGreaterThanOrEqual(2)); }); it('does not cascade revalidation across different subjects', async () => { diff --git a/packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts b/packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts index 31e26912eaf..dd0d2420bf2 100644 --- a/packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts +++ b/packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts @@ -722,11 +722,7 @@ describe('usePagesOrInfinite - revalidate behavior', () => { await result.current.paginated.revalidate(); }); - if (__CLERK_USE_RQ__) { - await waitFor(() => expect(fetcher.mock.calls.length).toBeGreaterThanOrEqual(2)); - } else { - await waitFor(() => expect(fetcher).toHaveBeenCalledTimes(1)); - } + await waitFor(() => expect(fetcher.mock.calls.length).toBeGreaterThanOrEqual(2)); }); }); diff --git a/packages/shared/src/react/hooks/__tests__/usePlans.spec.tsx b/packages/shared/src/react/hooks/__tests__/usePlans.spec.tsx index 4ba3a45ae30..439b5ff2bfd 100644 --- a/packages/shared/src/react/hooks/__tests__/usePlans.spec.tsx +++ b/packages/shared/src/react/hooks/__tests__/usePlans.spec.tsx @@ -261,19 +261,9 @@ describe('usePlans', () => { await result.current.userPlans.revalidate(); }); - const isRQ = Boolean((globalThis as any).__CLERK_USE_RQ__); const calls = getPlansSpy.mock.calls.map(call => call[0]?.for); - if (isRQ) { - await waitFor(() => expect(getPlansSpy.mock.calls.length).toBeGreaterThanOrEqual(1)); - expect(calls.every(value => value === 'user')).toBe(true); - } else { - await waitFor(() => expect(getPlansSpy.mock.calls.length).toBe(1)); - expect(getPlansSpy.mock.calls[0][0]).toEqual( - expect.objectContaining({ - for: 'user', - }), - ); - } + await waitFor(() => expect(getPlansSpy.mock.calls.length).toBeGreaterThanOrEqual(1)); + expect(calls.every(value => value === 'user')).toBe(true); }); }); diff --git a/packages/shared/src/react/hooks/__tests__/useSubscription.spec.tsx b/packages/shared/src/react/hooks/__tests__/useSubscription.spec.tsx index 1563127d543..06bdf71cc85 100644 --- a/packages/shared/src/react/hooks/__tests__/useSubscription.spec.tsx +++ b/packages/shared/src/react/hooks/__tests__/useSubscription.spec.tsx @@ -105,14 +105,7 @@ describe('useSubscription', () => { mockUser = null; rerender(); - if (__CLERK_USE_RQ__) { - await waitFor(() => expect(result.current.data).toBeUndefined()); - } else { - // Assert that SWR will flip to fetching because the fetcherFN runs, but it forces `null` when userId is falsy. - await waitFor(() => expect(result.current.isFetching).toBe(true)); - // The fetcher returns null when userId is falsy, so data should become null - await waitFor(() => expect(result.current.data).toBeNull()); - } + await waitFor(() => expect(result.current.data).toBeUndefined()); expect(getSubscriptionSpy).toHaveBeenCalledTimes(1); expect(result.current.isFetching).toBe(false); @@ -133,15 +126,7 @@ describe('useSubscription', () => { mockUser = null; rerender({ kp: true }); - if (__CLERK_USE_RQ__) { - await waitFor(() => expect(result.current.data).toBeUndefined()); - } else { - // Assert that SWR will flip to fetching because the fetcherFN runs, but it forces `null` when userId is falsy. - await waitFor(() => expect(result.current.isFetching).toBe(true)); - - // The fetcher returns null when userId is falsy, so data should become null - await waitFor(() => expect(result.current.data).toBeNull()); - } + await waitFor(() => expect(result.current.data).toBeUndefined()); expect(getSubscriptionSpy).toHaveBeenCalledTimes(1); expect(result.current.isFetching).toBe(false); @@ -169,12 +154,7 @@ describe('useSubscription', () => { await waitFor(() => expect(result.current.isFetching).toBe(true)); - // Slight difference in behavior between SWR and React Query, but acceptable for the migration. - if (__CLERK_USE_RQ__) { - await waitFor(() => expect(result.current.isLoading).toBe(false)); - } else { - await waitFor(() => expect(result.current.isLoading).toBe(true)); - } + await waitFor(() => expect(result.current.isLoading).toBe(false)); expect(result.current.data).toEqual({ id: 'sub_org_org_1' }); deferred.resolve({ id: 'sub_org_org_2' }); diff --git a/packages/shared/src/react/hooks/createBillingPaginatedHook.tsx b/packages/shared/src/react/hooks/createBillingPaginatedHook.tsx index 425b05e97b8..9d658db2969 100644 --- a/packages/shared/src/react/hooks/createBillingPaginatedHook.tsx +++ b/packages/shared/src/react/hooks/createBillingPaginatedHook.tsx @@ -141,7 +141,7 @@ export function createBillingPaginatedHook; - -/** - * @internal - */ -export type UseAPIKeysReturn = PaginatedResources< - APIKeyResource, - T extends { infinite: true } ? true : false ->; - -/** - * @internal - * - * The `useAPIKeys()` hook provides access to paginated API keys for the current user or organization. - * - * @example - * ### Basic usage with default pagination - * - * ```tsx - * const { data, isLoading, page, pageCount, fetchNext, fetchPrevious } = useAPIKeys({ - * subject: 'user_123', - * pageSize: 10, - * initialPage: 1, - * }); - * ``` - * - * @example - * ### With search query - * - * ```tsx - * const [searchValue, setSearchValue] = useState(''); - * const debouncedSearch = useDebounce(searchValue, 500); - * - * const { data, isLoading } = useAPIKeys({ - * subject: 'user_123', - * query: debouncedSearch.trim(), - * pageSize: 10, - * }); - * ``` - * - * @example - * ### Infinite scroll - * - * ```tsx - * const { data, isLoading, fetchNext, hasNextPage } = useAPIKeys({ - * subject: 'user_123', - * infinite: true, - * }); - * ``` - */ -export function useAPIKeys(params?: T): UseAPIKeysReturn { - useAssertWrappedByClerkProvider('useAPIKeys'); - - const safeValues = useWithSafeValues(params, { - initialPage: 1, - pageSize: 10, - keepPreviousData: false, - infinite: false, - subject: '', - query: '', - enabled: true, - } as UseAPIKeysParams); - - const clerk = useClerkInstanceContext(); - - clerk.telemetry?.record(eventMethodCalled('useAPIKeys')); - - const hookParams: GetAPIKeysParams = { - initialPage: safeValues.initialPage, - pageSize: safeValues.pageSize, - ...(safeValues.subject ? { subject: safeValues.subject } : {}), - ...(safeValues.query ? { query: safeValues.query } : {}), - }; - - const isEnabled = (safeValues.enabled ?? true) && clerk.loaded; - - const result = usePagesOrInfinite({ - fetcher: clerk.apiKeys?.getAll - ? (params: GetAPIKeysParams) => clerk.apiKeys.getAll({ ...params, subject: safeValues.subject }) - : undefined, - config: { - keepPreviousData: safeValues.keepPreviousData, - infinite: safeValues.infinite, - enabled: isEnabled, - isSignedIn: Boolean(clerk.user), - initialPage: safeValues.initialPage, - pageSize: safeValues.pageSize, - }, - keys: createCacheKeys({ - stablePrefix: STABLE_KEYS.API_KEYS_KEY, - authenticated: Boolean(clerk.user), - tracked: { - subject: safeValues.subject, - }, - untracked: { - args: hookParams, - }, - }), - }) as UseAPIKeysReturn; - - const { mutate } = useSWRConfig(); - - // Invalidate all cache entries for this user or organization - const invalidateAll = useCallback(() => { - return mutate(key => { - if (!key || typeof key !== 'object') { - return false; - } - // Match all apiKeys cache entries for this user or organization, regardless of page, pageSize, or query - return 'type' in key && key.type === 'apiKeys' && 'subject' in key && key.subject === safeValues.subject; - }); - }, [mutate, safeValues.subject]); - - return { - ...result, - revalidate: invalidateAll as any, - }; -} diff --git a/packages/shared/src/react/hooks/useAPIKeys.ts b/packages/shared/src/react/hooks/useAPIKeys.ts deleted file mode 100644 index cd899c1e737..00000000000 --- a/packages/shared/src/react/hooks/useAPIKeys.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useAPIKeys } from 'virtual:data-hooks/useAPIKeys'; -export type { UseAPIKeysParams, UseAPIKeysReturn } from './useAPIKeys.rq'; diff --git a/packages/shared/src/react/hooks/useAPIKeys.rq.tsx b/packages/shared/src/react/hooks/useAPIKeys.tsx similarity index 100% rename from packages/shared/src/react/hooks/useAPIKeys.rq.tsx rename to packages/shared/src/react/hooks/useAPIKeys.tsx diff --git a/packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx b/packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx deleted file mode 100644 index f4d01862445..00000000000 --- a/packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import { useCallback, useMemo, useRef, useState } from 'react'; - -import type { ClerkPaginatedResponse } from '../../types'; -import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; -import { useClerkQueryClient } from '../clerk-rq/use-clerk-query-client'; -import { useClerkInfiniteQuery } from '../clerk-rq/useInfiniteQuery'; -import { useClerkQuery } from '../clerk-rq/useQuery'; -import type { CacheSetter, ValueOrSetter } from '../types'; -import { useClearQueriesOnSignOut, withInfiniteKey } from './useClearQueriesOnSignOut'; -import type { UsePagesOrInfiniteSignature } from './usePageOrInfinite.types'; -import { useWithSafeValues } from './usePagesOrInfinite.shared'; - -export const usePagesOrInfinite: UsePagesOrInfiniteSignature = params => { - const { fetcher, config, keys } = params; - - const [paginatedPage, setPaginatedPage] = useState(config.initialPage ?? 1); - - // Cache initialPage and initialPageSize until unmount - const initialPageRef = useRef(config.initialPage ?? 1); - const pageSizeRef = useRef(config.pageSize ?? 10); - - const enabled = config.enabled ?? true; - const isSignedIn = config.isSignedIn; - const triggerInfinite = config.infinite ?? false; - const cacheMode = config.__experimental_mode === 'cache'; - const keepPreviousData = config.keepPreviousData ?? false; - - const [queryClient] = useClerkQueryClient(); - - // Compute the actual enabled state for queries (considering all conditions) - const queriesEnabled = enabled && Boolean(fetcher) && !cacheMode && isSignedIn !== false; - - // Force re-render counter for cache-only updates - const [forceUpdateCounter, setForceUpdateCounter] = useState(0); - const forceUpdate = useCallback((updater: (n: number) => number) => { - setForceUpdateCounter(updater); - }, []); - - // Non-infinite mode: single page query - const pagesQueryKey = useMemo(() => { - const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey; - - return [ - stablePrefix, - authenticated, - tracked, - { - ...untracked, - args: { - ...untracked.args, - initialPage: paginatedPage, - pageSize: pageSizeRef.current, - }, - }, - ] as const; - }, [keys.queryKey, paginatedPage]); - - const singlePageQuery = useClerkQuery({ - queryKey: pagesQueryKey, - queryFn: ({ queryKey }) => { - const { args } = queryKey[3]; - - if (!fetcher) { - return undefined as any; - } - - return fetcher(args); - }, - staleTime: 60_000, - enabled: queriesEnabled && !triggerInfinite, - // Use placeholderData to keep previous data while fetching new page - placeholderData: defineKeepPreviousDataFn(keepPreviousData), - }); - - // Infinite mode: accumulate pages - const infiniteQueryKey = useMemo(() => { - const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey; - - return [stablePrefix + '-inf', authenticated, tracked, untracked] as const; - }, [keys.queryKey]); - - const infiniteQuery = useClerkInfiniteQuery, any, any, typeof infiniteQueryKey, any>({ - queryKey: infiniteQueryKey, - initialPageParam: config.initialPage ?? 1, - getNextPageParam: (lastPage, allPages, lastPageParam) => { - const total = lastPage?.total_count ?? 0; - const consumed = (allPages.length + (config.initialPage ? config.initialPage - 1 : 0)) * (config.pageSize ?? 10); - return consumed < total ? (lastPageParam as number) + 1 : undefined; - }, - queryFn: ({ pageParam, queryKey }) => { - const { args } = queryKey[3]; - if (!fetcher) { - return undefined as any; - } - return fetcher({ ...args, initialPage: pageParam, pageSize: pageSizeRef.current }); - }, - staleTime: 60_000, - enabled: queriesEnabled && triggerInfinite, - }); - - useClearQueriesOnSignOut({ - isSignedOut: isSignedIn === false, - authenticated: keys.authenticated, - stableKeys: withInfiniteKey(keys.stableKey), - onCleanup: () => { - // Reset paginated page to initial - setPaginatedPage(initialPageRef.current); - - // Force re-render to reflect cache changes - void Promise.resolve().then(() => forceUpdate(n => n + 1)); - }, - }); - - // Compute data, count and page from the same data source to ensure consistency - const computedValues = useMemo(() => { - if (triggerInfinite) { - // Read from query data first, fallback to cache - const cachedData = queryClient.getQueryData<{ pages?: Array> }>(infiniteQueryKey); - const pages = queriesEnabled ? (infiniteQuery.data?.pages ?? cachedData?.pages ?? []) : (cachedData?.pages ?? []); - - // Ensure pages is always an array and filter out null/undefined pages - const validPages = Array.isArray(pages) ? pages.filter(Boolean) : []; - - return { - data: - validPages - .map((a: ClerkPaginatedResponse) => a?.data) - .flat() - .filter(Boolean) ?? [], - count: validPages[validPages.length - 1]?.total_count ?? 0, - page: validPages.length > 0 ? validPages.length : initialPageRef.current, - }; - } - - // When query is disabled (via enabled flag), the hook's data is stale, so only read from cache - // This ensures that after cache clearing, we return consistent empty state - const pageData = queriesEnabled - ? (singlePageQuery.data ?? queryClient.getQueryData>(pagesQueryKey)) - : queryClient.getQueryData>(pagesQueryKey); - - return { - data: Array.isArray(pageData?.data) ? pageData.data : [], - count: typeof pageData?.total_count === 'number' ? pageData.total_count : 0, - page: paginatedPage, - }; - // eslint-disable-next-line react-hooks/exhaustive-deps -- forceUpdateCounter is used to trigger re-renders for cache updates - }, [ - queriesEnabled, - forceUpdateCounter, - triggerInfinite, - infiniteQuery.data?.pages, - singlePageQuery.data, - queryClient, - infiniteQueryKey, - pagesQueryKey, - paginatedPage, - ]); - - const { data, count, page } = computedValues; - - const fetchPage: ValueOrSetter = useCallback( - numberOrgFn => { - if (triggerInfinite) { - const next = typeof numberOrgFn === 'function' ? (numberOrgFn as (n: number) => number)(page) : numberOrgFn; - const targetCount = Math.max(0, next); - const cachedData = queryClient.getQueryData<{ pages?: Array> }>(infiniteQueryKey); - const pages = infiniteQuery.data?.pages ?? cachedData?.pages ?? []; - const currentCount = pages.length; - const toFetch = targetCount - currentCount; - if (toFetch > 0) { - void infiniteQuery.fetchNextPage({ cancelRefetch: false }); - } - return; - } - return setPaginatedPage(numberOrgFn); - }, - [infiniteQuery, page, triggerInfinite, queryClient, infiniteQueryKey], - ); - - const isLoading = triggerInfinite ? infiniteQuery.isLoading : singlePageQuery.isLoading; - const isFetching = triggerInfinite ? infiniteQuery.isFetching : singlePageQuery.isFetching; - const error = (triggerInfinite ? infiniteQuery.error : singlePageQuery.error) ?? null; - const isError = !!error; - - const fetchNext = useCallback(() => { - if (triggerInfinite) { - void infiniteQuery.fetchNextPage({ cancelRefetch: false }); - return; - } - setPaginatedPage(n => Math.max(0, n + 1)); - }, [infiniteQuery, triggerInfinite]); - - const fetchPrevious = useCallback(() => { - if (triggerInfinite) { - // not natively supported by forward-only pagination; noop - return; - } - setPaginatedPage(n => Math.max(0, n - 1)); - }, [triggerInfinite]); - - const offsetCount = (initialPageRef.current - 1) * pageSizeRef.current; - const pageCount = Math.ceil((count - offsetCount) / pageSizeRef.current); - const hasNextPage = triggerInfinite - ? Boolean(infiniteQuery.hasNextPage) - : count - offsetCount * pageSizeRef.current > page * pageSizeRef.current; - const hasPreviousPage = triggerInfinite - ? Boolean(infiniteQuery.hasPreviousPage) - : (page - 1) * pageSizeRef.current > offsetCount * pageSizeRef.current; - - const setData: CacheSetter = value => { - if (triggerInfinite) { - queryClient.setQueryData(infiniteQueryKey, (prevValue: any = {}) => { - const prevPages = Array.isArray(prevValue?.pages) ? prevValue.pages : []; - const nextPages = (typeof value === 'function' ? value(prevPages) : value) as Array< - ClerkPaginatedResponse - >; - return { ...prevValue, pages: nextPages }; - }); - // Force immediate re-render to reflect cache changes - forceUpdate(n => n + 1); - return Promise.resolve(); - } - queryClient.setQueryData(pagesQueryKey, (prevValue: any = { data: [], total_count: 0 }) => { - const nextValue = (typeof value === 'function' ? value(prevValue) : value) as ClerkPaginatedResponse; - return nextValue; - }); - // Force re-render to reflect cache changes - forceUpdate(n => n + 1); - return Promise.resolve(); - }; - - const revalidate = async () => { - await queryClient.invalidateQueries({ queryKey: keys.invalidationKey }); - const [stablePrefix, ...rest] = keys.invalidationKey; - return queryClient.invalidateQueries({ queryKey: [stablePrefix + '-inf', ...rest] }); - }; - - return { - data, - count, - error, - isLoading, - isFetching, - isError, - page, - pageCount, - fetchPage, - fetchNext, - fetchPrevious, - hasNextPage, - hasPreviousPage, - revalidate: revalidate as any, - setData: setData as any, - }; -}; - -export { useWithSafeValues }; diff --git a/packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx b/packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx deleted file mode 100644 index d81d5bbbf76..00000000000 --- a/packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx +++ /dev/null @@ -1,236 +0,0 @@ -'use client'; - -import { useCallback, useMemo, useRef, useState } from 'react'; - -import { useSWR, useSWRInfinite } from '../clerk-swr'; -import type { CacheSetter, ValueOrSetter } from '../types'; -import { toSWRQuery } from './createCacheKeys'; -import type { UsePagesOrInfiniteSignature } from './usePageOrInfinite.types'; -import { getDifferentKeys, useWithSafeValues } from './usePagesOrInfinite.shared'; -import { usePreviousValue } from './usePreviousValue'; - -const cachingSWROptions = { - dedupingInterval: 1000 * 60, - focusThrottleInterval: 1000 * 60 * 2, -} satisfies Parameters[2]; - -const cachingSWRInfiniteOptions = { - ...cachingSWROptions, - revalidateFirstPage: false, -} satisfies Parameters[2]; - -/** - * A flexible pagination hook that supports both traditional pagination and infinite loading. - * It provides a unified API for handling paginated data fetching, with built-in caching through SWR. - * The hook can operate in two modes: - * - Traditional pagination: Fetches one page at a time with page navigation - * - Infinite loading: Accumulates data as more pages are loaded. - * - * Features: - * - Cache management with SWR - * - Loading and error states - * - Page navigation helpers - * - Data revalidation and updates - * - Support for keeping previous data while loading. - * - * @internal - */ -export const usePagesOrInfinite: UsePagesOrInfiniteSignature = params => { - const { fetcher, config, keys } = params; - const [paginatedPage, setPaginatedPage] = useState(config.initialPage ?? 1); - - // Cache initialPage and initialPageSize until unmount - const initialPageRef = useRef(config.initialPage ?? 1); - const pageSizeRef = useRef(config.pageSize ?? 10); - - const enabled = config.enabled ?? true; - const cacheMode = config.__experimental_mode === 'cache'; - const triggerInfinite = config.infinite ?? false; - const keepPreviousData = config.keepPreviousData ?? false; - const isSignedIn = config.isSignedIn; - - const pagesCacheKey = { - ...toSWRQuery(keys), - initialPage: paginatedPage, - pageSize: pageSizeRef.current, - }; - - const previousIsSignedIn = usePreviousValue(isSignedIn); - - // cacheMode being `true` indicates that the cache key is defined, but the fetcher is not. - // This allows to ready the cache instead of firing a request. - const shouldFetch = !triggerInfinite && enabled && (!cacheMode ? !!fetcher : true); - - // Attention: - // - // This complex logic is necessary to ensure that the cached data is not used when the user is signed out. - // `useSWR` with `key` set to `null` and `keepPreviousData` set to `true` will return the previous cached data until the hook unmounts. - // So for hooks that render authenticated data, we need to ensure that the cached data is not used when the user is signed out. - // - // 1. Fetcher should not fire if user is signed out on mount. (fetcher does not run, loading states are not triggered) - // 2. If user was signed in and then signed out, cached data should become null. (fetcher runs and returns null, loading states are triggered) - // - // We achieve (2) by setting the key to the cache key when the user transitions to signed out and forcing the fetcher to return null. - const swrKey = - typeof isSignedIn === 'boolean' - ? previousIsSignedIn === true && isSignedIn === false - ? pagesCacheKey - : isSignedIn - ? shouldFetch - ? pagesCacheKey - : null - : null - : shouldFetch - ? pagesCacheKey - : null; - - const swrFetcher = - !cacheMode && !!fetcher - ? (cacheKeyParams: Record) => { - if (isSignedIn === false || shouldFetch === false) { - return null; - } - const requestParams = getDifferentKeys(cacheKeyParams, { type: keys.queryKey[0], ...keys.queryKey[2] }); - // @ts-ignore - fetcher expects Params subset; narrowing at call-site - return fetcher(requestParams); - } - : null; - - const { - data: swrData, - isValidating: swrIsValidating, - isLoading: swrIsLoading, - error: swrError, - mutate: swrMutate, - } = useSWR(swrKey, swrFetcher, { keepPreviousData, ...cachingSWROptions }); - - // Attention: - // - // Cache behavior for infinite loading when signing out: - // - // Unlike `useSWR` above (which requires complex transition handling), `useSWRInfinite` has simpler sign-out semantics: - // 1. When user is signed out on mount, the key getter returns `null`, preventing any fetches. - // 2. When user transitions from signed in to signed out, the key getter returns `null` for all page indices. - // 3. When `useSWRInfinite`'s key getter returns `null`, SWR will not fetch data and considers that page invalid. - // 4. Unlike paginated mode, `useSWRInfinite` does not support `keepPreviousData`, so there's no previous data retention. - // - // This simpler behavior works because: - // - `useSWRInfinite` manages multiple pages internally, each with its own cache key - // - When the key getter returns `null`, all page fetches are prevented and pages become invalid - // - Without `keepPreviousData`, the hook will naturally reflect the empty/invalid state - // - // Result: No special transition logic needed - just return `null` from key getter when `isSignedIn === false`. - const { - data: swrInfiniteData, - isLoading: swrInfiniteIsLoading, - isValidating: swrInfiniteIsValidating, - error: swrInfiniteError, - size, - setSize, - mutate: swrInfiniteMutate, - } = useSWRInfinite( - pageIndex => { - if (!triggerInfinite || !enabled || isSignedIn === false) { - return null; - } - - return { - ...toSWRQuery(keys), - initialPage: initialPageRef.current + pageIndex, - pageSize: pageSizeRef.current, - }; - }, - cacheKeyParams => { - // @ts-ignore - fetcher expects Params subset; narrowing at call-site - const requestParams = getDifferentKeys(cacheKeyParams, { type: keys.queryKey[0], ...keys.queryKey[2] }); - // @ts-ignore - fetcher expects Params subset; narrowing at call-site - return fetcher?.(requestParams); - }, - cachingSWRInfiniteOptions, - ); - - const page = useMemo(() => { - if (triggerInfinite) { - return size; - } - return paginatedPage; - }, [triggerInfinite, size, paginatedPage]); - - const fetchPage: ValueOrSetter = useCallback( - numberOrgFn => { - if (triggerInfinite) { - void setSize(numberOrgFn); - return; - } - return setPaginatedPage(numberOrgFn); - }, - [setSize, triggerInfinite], - ); - - const data = useMemo(() => { - if (triggerInfinite) { - return swrInfiniteData?.map(a => a?.data).flat() ?? []; - } - return swrData?.data ?? []; - }, [triggerInfinite, swrData, swrInfiniteData]); - - const count = useMemo(() => { - if (triggerInfinite) { - return swrInfiniteData?.[swrInfiniteData?.length - 1]?.total_count || 0; - } - return swrData?.total_count ?? 0; - }, [triggerInfinite, swrData, swrInfiniteData]); - - const isLoading = triggerInfinite ? swrInfiniteIsLoading : swrIsLoading; - const isFetching = triggerInfinite ? swrInfiniteIsValidating : swrIsValidating; - const error = (triggerInfinite ? swrInfiniteError : swrError) ?? null; - const isError = !!error; - - const fetchNext = useCallback(() => { - fetchPage(n => Math.max(0, n + 1)); - }, [fetchPage]); - - const fetchPrevious = useCallback(() => { - fetchPage(n => Math.max(0, n - 1)); - }, [fetchPage]); - - const offsetCount = (initialPageRef.current - 1) * pageSizeRef.current; - - const pageCount = Math.ceil((count - offsetCount) / pageSizeRef.current); - const hasNextPage = count - offsetCount * pageSizeRef.current > page * pageSizeRef.current; - const hasPreviousPage = (page - 1) * pageSizeRef.current > offsetCount * pageSizeRef.current; - - const setData: CacheSetter = triggerInfinite - ? value => - swrInfiniteMutate(value, { - revalidate: false, - }) - : value => - swrMutate(value, { - revalidate: false, - }); - - const revalidate = triggerInfinite ? () => swrInfiniteMutate() : () => swrMutate(); - - return { - data, - count, - error, - isLoading, - isFetching, - isError, - page, - pageCount, - fetchPage, - fetchNext, - fetchPrevious, - hasNextPage, - hasPreviousPage, - // Let the hook return type define this type - revalidate: revalidate as any, - // Let the hook return type define this type - setData: setData as any, - }; -}; - -export { useWithSafeValues }; diff --git a/packages/shared/src/react/hooks/usePagesOrInfinite.tsx b/packages/shared/src/react/hooks/usePagesOrInfinite.tsx index 3bb9fe522ff..f4d01862445 100644 --- a/packages/shared/src/react/hooks/usePagesOrInfinite.tsx +++ b/packages/shared/src/react/hooks/usePagesOrInfinite.tsx @@ -1,2 +1,257 @@ -export { usePagesOrInfinite } from 'virtual:data-hooks/usePagesOrInfinite'; -export { useWithSafeValues } from './usePagesOrInfinite.shared'; +import { useCallback, useMemo, useRef, useState } from 'react'; + +import type { ClerkPaginatedResponse } from '../../types'; +import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQueryClient } from '../clerk-rq/use-clerk-query-client'; +import { useClerkInfiniteQuery } from '../clerk-rq/useInfiniteQuery'; +import { useClerkQuery } from '../clerk-rq/useQuery'; +import type { CacheSetter, ValueOrSetter } from '../types'; +import { useClearQueriesOnSignOut, withInfiniteKey } from './useClearQueriesOnSignOut'; +import type { UsePagesOrInfiniteSignature } from './usePageOrInfinite.types'; +import { useWithSafeValues } from './usePagesOrInfinite.shared'; + +export const usePagesOrInfinite: UsePagesOrInfiniteSignature = params => { + const { fetcher, config, keys } = params; + + const [paginatedPage, setPaginatedPage] = useState(config.initialPage ?? 1); + + // Cache initialPage and initialPageSize until unmount + const initialPageRef = useRef(config.initialPage ?? 1); + const pageSizeRef = useRef(config.pageSize ?? 10); + + const enabled = config.enabled ?? true; + const isSignedIn = config.isSignedIn; + const triggerInfinite = config.infinite ?? false; + const cacheMode = config.__experimental_mode === 'cache'; + const keepPreviousData = config.keepPreviousData ?? false; + + const [queryClient] = useClerkQueryClient(); + + // Compute the actual enabled state for queries (considering all conditions) + const queriesEnabled = enabled && Boolean(fetcher) && !cacheMode && isSignedIn !== false; + + // Force re-render counter for cache-only updates + const [forceUpdateCounter, setForceUpdateCounter] = useState(0); + const forceUpdate = useCallback((updater: (n: number) => number) => { + setForceUpdateCounter(updater); + }, []); + + // Non-infinite mode: single page query + const pagesQueryKey = useMemo(() => { + const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey; + + return [ + stablePrefix, + authenticated, + tracked, + { + ...untracked, + args: { + ...untracked.args, + initialPage: paginatedPage, + pageSize: pageSizeRef.current, + }, + }, + ] as const; + }, [keys.queryKey, paginatedPage]); + + const singlePageQuery = useClerkQuery({ + queryKey: pagesQueryKey, + queryFn: ({ queryKey }) => { + const { args } = queryKey[3]; + + if (!fetcher) { + return undefined as any; + } + + return fetcher(args); + }, + staleTime: 60_000, + enabled: queriesEnabled && !triggerInfinite, + // Use placeholderData to keep previous data while fetching new page + placeholderData: defineKeepPreviousDataFn(keepPreviousData), + }); + + // Infinite mode: accumulate pages + const infiniteQueryKey = useMemo(() => { + const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey; + + return [stablePrefix + '-inf', authenticated, tracked, untracked] as const; + }, [keys.queryKey]); + + const infiniteQuery = useClerkInfiniteQuery, any, any, typeof infiniteQueryKey, any>({ + queryKey: infiniteQueryKey, + initialPageParam: config.initialPage ?? 1, + getNextPageParam: (lastPage, allPages, lastPageParam) => { + const total = lastPage?.total_count ?? 0; + const consumed = (allPages.length + (config.initialPage ? config.initialPage - 1 : 0)) * (config.pageSize ?? 10); + return consumed < total ? (lastPageParam as number) + 1 : undefined; + }, + queryFn: ({ pageParam, queryKey }) => { + const { args } = queryKey[3]; + if (!fetcher) { + return undefined as any; + } + return fetcher({ ...args, initialPage: pageParam, pageSize: pageSizeRef.current }); + }, + staleTime: 60_000, + enabled: queriesEnabled && triggerInfinite, + }); + + useClearQueriesOnSignOut({ + isSignedOut: isSignedIn === false, + authenticated: keys.authenticated, + stableKeys: withInfiniteKey(keys.stableKey), + onCleanup: () => { + // Reset paginated page to initial + setPaginatedPage(initialPageRef.current); + + // Force re-render to reflect cache changes + void Promise.resolve().then(() => forceUpdate(n => n + 1)); + }, + }); + + // Compute data, count and page from the same data source to ensure consistency + const computedValues = useMemo(() => { + if (triggerInfinite) { + // Read from query data first, fallback to cache + const cachedData = queryClient.getQueryData<{ pages?: Array> }>(infiniteQueryKey); + const pages = queriesEnabled ? (infiniteQuery.data?.pages ?? cachedData?.pages ?? []) : (cachedData?.pages ?? []); + + // Ensure pages is always an array and filter out null/undefined pages + const validPages = Array.isArray(pages) ? pages.filter(Boolean) : []; + + return { + data: + validPages + .map((a: ClerkPaginatedResponse) => a?.data) + .flat() + .filter(Boolean) ?? [], + count: validPages[validPages.length - 1]?.total_count ?? 0, + page: validPages.length > 0 ? validPages.length : initialPageRef.current, + }; + } + + // When query is disabled (via enabled flag), the hook's data is stale, so only read from cache + // This ensures that after cache clearing, we return consistent empty state + const pageData = queriesEnabled + ? (singlePageQuery.data ?? queryClient.getQueryData>(pagesQueryKey)) + : queryClient.getQueryData>(pagesQueryKey); + + return { + data: Array.isArray(pageData?.data) ? pageData.data : [], + count: typeof pageData?.total_count === 'number' ? pageData.total_count : 0, + page: paginatedPage, + }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- forceUpdateCounter is used to trigger re-renders for cache updates + }, [ + queriesEnabled, + forceUpdateCounter, + triggerInfinite, + infiniteQuery.data?.pages, + singlePageQuery.data, + queryClient, + infiniteQueryKey, + pagesQueryKey, + paginatedPage, + ]); + + const { data, count, page } = computedValues; + + const fetchPage: ValueOrSetter = useCallback( + numberOrgFn => { + if (triggerInfinite) { + const next = typeof numberOrgFn === 'function' ? (numberOrgFn as (n: number) => number)(page) : numberOrgFn; + const targetCount = Math.max(0, next); + const cachedData = queryClient.getQueryData<{ pages?: Array> }>(infiniteQueryKey); + const pages = infiniteQuery.data?.pages ?? cachedData?.pages ?? []; + const currentCount = pages.length; + const toFetch = targetCount - currentCount; + if (toFetch > 0) { + void infiniteQuery.fetchNextPage({ cancelRefetch: false }); + } + return; + } + return setPaginatedPage(numberOrgFn); + }, + [infiniteQuery, page, triggerInfinite, queryClient, infiniteQueryKey], + ); + + const isLoading = triggerInfinite ? infiniteQuery.isLoading : singlePageQuery.isLoading; + const isFetching = triggerInfinite ? infiniteQuery.isFetching : singlePageQuery.isFetching; + const error = (triggerInfinite ? infiniteQuery.error : singlePageQuery.error) ?? null; + const isError = !!error; + + const fetchNext = useCallback(() => { + if (triggerInfinite) { + void infiniteQuery.fetchNextPage({ cancelRefetch: false }); + return; + } + setPaginatedPage(n => Math.max(0, n + 1)); + }, [infiniteQuery, triggerInfinite]); + + const fetchPrevious = useCallback(() => { + if (triggerInfinite) { + // not natively supported by forward-only pagination; noop + return; + } + setPaginatedPage(n => Math.max(0, n - 1)); + }, [triggerInfinite]); + + const offsetCount = (initialPageRef.current - 1) * pageSizeRef.current; + const pageCount = Math.ceil((count - offsetCount) / pageSizeRef.current); + const hasNextPage = triggerInfinite + ? Boolean(infiniteQuery.hasNextPage) + : count - offsetCount * pageSizeRef.current > page * pageSizeRef.current; + const hasPreviousPage = triggerInfinite + ? Boolean(infiniteQuery.hasPreviousPage) + : (page - 1) * pageSizeRef.current > offsetCount * pageSizeRef.current; + + const setData: CacheSetter = value => { + if (triggerInfinite) { + queryClient.setQueryData(infiniteQueryKey, (prevValue: any = {}) => { + const prevPages = Array.isArray(prevValue?.pages) ? prevValue.pages : []; + const nextPages = (typeof value === 'function' ? value(prevPages) : value) as Array< + ClerkPaginatedResponse + >; + return { ...prevValue, pages: nextPages }; + }); + // Force immediate re-render to reflect cache changes + forceUpdate(n => n + 1); + return Promise.resolve(); + } + queryClient.setQueryData(pagesQueryKey, (prevValue: any = { data: [], total_count: 0 }) => { + const nextValue = (typeof value === 'function' ? value(prevValue) : value) as ClerkPaginatedResponse; + return nextValue; + }); + // Force re-render to reflect cache changes + forceUpdate(n => n + 1); + return Promise.resolve(); + }; + + const revalidate = async () => { + await queryClient.invalidateQueries({ queryKey: keys.invalidationKey }); + const [stablePrefix, ...rest] = keys.invalidationKey; + return queryClient.invalidateQueries({ queryKey: [stablePrefix + '-inf', ...rest] }); + }; + + return { + data, + count, + error, + isLoading, + isFetching, + isError, + page, + pageCount, + fetchPage, + fetchNext, + fetchPrevious, + hasNextPage, + hasPreviousPage, + revalidate: revalidate as any, + setData: setData as any, + }; +}; + +export { useWithSafeValues }; diff --git a/packages/shared/src/react/hooks/usePaymentAttemptQuery.rq.tsx b/packages/shared/src/react/hooks/usePaymentAttemptQuery.rq.tsx deleted file mode 100644 index 7a59fc5b282..00000000000 --- a/packages/shared/src/react/hooks/usePaymentAttemptQuery.rq.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; -import { useClerkQuery } from '../clerk-rq/useQuery'; -import { useClerkInstanceContext, useOrganizationContext, useUserContext } from '../contexts'; -import { useBillingHookEnabled } from './useBillingHookEnabled'; -import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; -import { usePaymentAttemptQueryCacheKeys } from './usePaymentAttemptQuery.shared'; -import type { PaymentAttemptQueryResult, UsePaymentAttemptQueryParams } from './usePaymentAttemptQuery.types'; - -/** - * @internal - */ -function usePaymentAttemptQuery(params: UsePaymentAttemptQueryParams): PaymentAttemptQueryResult { - const { paymentAttemptId, keepPreviousData = false, for: forType = 'user' } = params; - const clerk = useClerkInstanceContext(); - const user = useUserContext(); - const { organization } = useOrganizationContext(); - - const organizationId = forType === 'organization' ? (organization?.id ?? null) : null; - const userId = user?.id ?? null; - - const { queryKey, stableKey, authenticated } = usePaymentAttemptQueryCacheKeys({ - paymentAttemptId, - userId, - orgId: organizationId, - for: forType, - }); - - const billingEnabled = useBillingHookEnabled(params); - - const queryEnabled = Boolean(paymentAttemptId) && billingEnabled; - - useClearQueriesOnSignOut({ - isSignedOut: user === null, // works with the transitive state - authenticated, - stableKeys: stableKey, - }); - - const query = useClerkQuery({ - queryKey, - queryFn: ({ queryKey }) => { - const args = queryKey[3].args; - return clerk.billing.getPaymentAttempt(args); - }, - enabled: queryEnabled, - placeholderData: defineKeepPreviousDataFn(keepPreviousData), - staleTime: 1_000 * 60, - }); - - return { - data: query.data, - error: (query.error ?? null) as PaymentAttemptQueryResult['error'], - isLoading: query.isLoading, - isFetching: query.isFetching, - }; -} - -export { usePaymentAttemptQuery as __internal_usePaymentAttemptQuery }; diff --git a/packages/shared/src/react/hooks/usePaymentAttemptQuery.swr.tsx b/packages/shared/src/react/hooks/usePaymentAttemptQuery.swr.tsx deleted file mode 100644 index d47d5d52246..00000000000 --- a/packages/shared/src/react/hooks/usePaymentAttemptQuery.swr.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useSWR } from '../clerk-swr'; -import { useClerkInstanceContext, useOrganizationContext, useUserContext } from '../contexts'; -import { usePaymentAttemptQueryCacheKeys } from './usePaymentAttemptQuery.shared'; -import type { PaymentAttemptQueryResult, UsePaymentAttemptQueryParams } from './usePaymentAttemptQuery.types'; - -/** - * This is the existing implementation of usePaymentAttemptQuery using SWR. - * It is kept here for backwards compatibility until our next major version. - * - * @internal - */ -export function __internal_usePaymentAttemptQuery(params: UsePaymentAttemptQueryParams): PaymentAttemptQueryResult { - const { paymentAttemptId, enabled = true, keepPreviousData = false, for: forType = 'user' } = params; - const clerk = useClerkInstanceContext(); - const user = useUserContext(); - const { organization } = useOrganizationContext(); - - const organizationId = forType === 'organization' ? (organization?.id ?? null) : null; - const userId = user?.id ?? null; - - const { queryKey } = usePaymentAttemptQueryCacheKeys({ - paymentAttemptId, - userId, - orgId: organizationId, - for: forType, - }); - - const queryEnabled = Boolean(paymentAttemptId) && enabled && (forType !== 'organization' || Boolean(organizationId)); - - const swr = useSWR( - queryEnabled ? { queryKey } : null, - ({ queryKey }) => { - const args = queryKey[3].args; - return clerk.billing.getPaymentAttempt(args); - }, - { - dedupingInterval: 1_000 * 60, - keepPreviousData, - }, - ); - - return { - data: swr.data, - error: (swr.error ?? null) as PaymentAttemptQueryResult['error'], - isLoading: swr.isLoading, - isFetching: swr.isValidating, - }; -} diff --git a/packages/shared/src/react/hooks/usePaymentAttemptQuery.tsx b/packages/shared/src/react/hooks/usePaymentAttemptQuery.tsx index ffa7ea1dc6e..7a59fc5b282 100644 --- a/packages/shared/src/react/hooks/usePaymentAttemptQuery.tsx +++ b/packages/shared/src/react/hooks/usePaymentAttemptQuery.tsx @@ -1 +1,57 @@ -export { __internal_usePaymentAttemptQuery } from 'virtual:data-hooks/usePaymentAttemptQuery'; +import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQuery } from '../clerk-rq/useQuery'; +import { useClerkInstanceContext, useOrganizationContext, useUserContext } from '../contexts'; +import { useBillingHookEnabled } from './useBillingHookEnabled'; +import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; +import { usePaymentAttemptQueryCacheKeys } from './usePaymentAttemptQuery.shared'; +import type { PaymentAttemptQueryResult, UsePaymentAttemptQueryParams } from './usePaymentAttemptQuery.types'; + +/** + * @internal + */ +function usePaymentAttemptQuery(params: UsePaymentAttemptQueryParams): PaymentAttemptQueryResult { + const { paymentAttemptId, keepPreviousData = false, for: forType = 'user' } = params; + const clerk = useClerkInstanceContext(); + const user = useUserContext(); + const { organization } = useOrganizationContext(); + + const organizationId = forType === 'organization' ? (organization?.id ?? null) : null; + const userId = user?.id ?? null; + + const { queryKey, stableKey, authenticated } = usePaymentAttemptQueryCacheKeys({ + paymentAttemptId, + userId, + orgId: organizationId, + for: forType, + }); + + const billingEnabled = useBillingHookEnabled(params); + + const queryEnabled = Boolean(paymentAttemptId) && billingEnabled; + + useClearQueriesOnSignOut({ + isSignedOut: user === null, // works with the transitive state + authenticated, + stableKeys: stableKey, + }); + + const query = useClerkQuery({ + queryKey, + queryFn: ({ queryKey }) => { + const args = queryKey[3].args; + return clerk.billing.getPaymentAttempt(args); + }, + enabled: queryEnabled, + placeholderData: defineKeepPreviousDataFn(keepPreviousData), + staleTime: 1_000 * 60, + }); + + return { + data: query.data, + error: (query.error ?? null) as PaymentAttemptQueryResult['error'], + isLoading: query.isLoading, + isFetching: query.isFetching, + }; +} + +export { usePaymentAttemptQuery as __internal_usePaymentAttemptQuery }; diff --git a/packages/shared/src/react/hooks/usePlanDetailsQuery.rq.tsx b/packages/shared/src/react/hooks/usePlanDetailsQuery.rq.tsx deleted file mode 100644 index c2a7ec96cbd..00000000000 --- a/packages/shared/src/react/hooks/usePlanDetailsQuery.rq.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; -import { useClerkQuery } from '../clerk-rq/useQuery'; -import { useClerkInstanceContext } from '../contexts'; -import { useBillingHookEnabled } from './useBillingHookEnabled'; -import { usePlanDetailsQueryCacheKeys } from './usePlanDetailsQuery.shared'; -import type { PlanDetailsQueryResult, UsePlanDetailsQueryParams } from './usePlanDetailsQuery.types'; - -/** - * @internal - */ -export function __internal_usePlanDetailsQuery(params: UsePlanDetailsQueryParams = {}): PlanDetailsQueryResult { - const { planId, initialPlan = null, keepPreviousData = true } = params; - const clerk = useClerkInstanceContext(); - - const targetPlanId = planId ?? initialPlan?.id ?? null; - - const { queryKey } = usePlanDetailsQueryCacheKeys({ planId: targetPlanId }); - - const billingEnabled = useBillingHookEnabled({ - authenticated: false, - }); - - const queryEnabled = Boolean(targetPlanId) && billingEnabled; - - const query = useClerkQuery({ - queryKey, - queryFn: () => { - if (!targetPlanId) { - throw new Error('planId is required to fetch plan details'); - } - return clerk.billing.getPlan({ id: targetPlanId }); - }, - enabled: queryEnabled, - initialData: initialPlan ?? undefined, - placeholderData: defineKeepPreviousDataFn(keepPreviousData), - initialDataUpdatedAt: 0, - }); - - return { - data: query.data, - error: (query.error ?? null) as PlanDetailsQueryResult['error'], - isLoading: query.isLoading, - isFetching: query.isFetching, - }; -} diff --git a/packages/shared/src/react/hooks/usePlanDetailsQuery.swr.tsx b/packages/shared/src/react/hooks/usePlanDetailsQuery.swr.tsx deleted file mode 100644 index ce544fce5b4..00000000000 --- a/packages/shared/src/react/hooks/usePlanDetailsQuery.swr.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useSWR } from '../clerk-swr'; -import { useClerkInstanceContext } from '../contexts'; -import { usePlanDetailsQueryCacheKeys } from './usePlanDetailsQuery.shared'; -import type { PlanDetailsQueryResult, UsePlanDetailsQueryParams } from './usePlanDetailsQuery.types'; - -/** - * This is the existing implementation of usePlanDetailsQuery using SWR. - * It is kept here for backwards compatibility until our next major version. - * - * @internal - */ -function usePlanDetailsQuery(params: UsePlanDetailsQueryParams = {}): PlanDetailsQueryResult { - const { planId, initialPlan = null, enabled = true, keepPreviousData = true } = params; - const clerk = useClerkInstanceContext(); - - const targetPlanId = planId ?? initialPlan?.id ?? null; - - const { queryKey } = usePlanDetailsQueryCacheKeys({ planId: targetPlanId }); - - const queryEnabled = Boolean(targetPlanId) && enabled; - - const swr = useSWR( - queryEnabled ? queryKey : null, - () => { - if (!targetPlanId) { - throw new Error('planId is required to fetch plan details'); - } - return clerk.billing.getPlan({ id: targetPlanId }); - }, - { - dedupingInterval: 1_000 * 60, - keepPreviousData, - fallbackData: initialPlan ?? undefined, - }, - ); - - return { - data: swr.data, - error: (swr.error ?? null) as PlanDetailsQueryResult['error'], - isLoading: swr.isLoading, - isFetching: swr.isValidating, - }; -} - -export { usePlanDetailsQuery as __internal_usePlanDetailsQuery }; diff --git a/packages/shared/src/react/hooks/usePlanDetailsQuery.tsx b/packages/shared/src/react/hooks/usePlanDetailsQuery.tsx index 7fb85951400..c2a7ec96cbd 100644 --- a/packages/shared/src/react/hooks/usePlanDetailsQuery.tsx +++ b/packages/shared/src/react/hooks/usePlanDetailsQuery.tsx @@ -1 +1,45 @@ -export { __internal_usePlanDetailsQuery } from 'virtual:data-hooks/usePlanDetailsQuery'; +import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQuery } from '../clerk-rq/useQuery'; +import { useClerkInstanceContext } from '../contexts'; +import { useBillingHookEnabled } from './useBillingHookEnabled'; +import { usePlanDetailsQueryCacheKeys } from './usePlanDetailsQuery.shared'; +import type { PlanDetailsQueryResult, UsePlanDetailsQueryParams } from './usePlanDetailsQuery.types'; + +/** + * @internal + */ +export function __internal_usePlanDetailsQuery(params: UsePlanDetailsQueryParams = {}): PlanDetailsQueryResult { + const { planId, initialPlan = null, keepPreviousData = true } = params; + const clerk = useClerkInstanceContext(); + + const targetPlanId = planId ?? initialPlan?.id ?? null; + + const { queryKey } = usePlanDetailsQueryCacheKeys({ planId: targetPlanId }); + + const billingEnabled = useBillingHookEnabled({ + authenticated: false, + }); + + const queryEnabled = Boolean(targetPlanId) && billingEnabled; + + const query = useClerkQuery({ + queryKey, + queryFn: () => { + if (!targetPlanId) { + throw new Error('planId is required to fetch plan details'); + } + return clerk.billing.getPlan({ id: targetPlanId }); + }, + enabled: queryEnabled, + initialData: initialPlan ?? undefined, + placeholderData: defineKeepPreviousDataFn(keepPreviousData), + initialDataUpdatedAt: 0, + }); + + return { + data: query.data, + error: (query.error ?? null) as PlanDetailsQueryResult['error'], + isLoading: query.isLoading, + isFetching: query.isFetching, + }; +} diff --git a/packages/shared/src/react/hooks/useStatementQuery.rq.tsx b/packages/shared/src/react/hooks/useStatementQuery.rq.tsx deleted file mode 100644 index 25f8b4a3908..00000000000 --- a/packages/shared/src/react/hooks/useStatementQuery.rq.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; -import { useClerkQuery } from '../clerk-rq/useQuery'; -import { useClerkInstanceContext, useOrganizationContext, useUserContext } from '../contexts'; -import { useBillingHookEnabled } from './useBillingHookEnabled'; -import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; -import { useStatementQueryCacheKeys } from './useStatementQuery.shared'; -import type { StatementQueryResult, UseStatementQueryParams } from './useStatementQuery.types'; - -/** - * @internal - */ -function useStatementQuery(params: UseStatementQueryParams = {}): StatementQueryResult { - const { statementId = null, keepPreviousData = false, for: forType = 'user' } = params; - const clerk = useClerkInstanceContext(); - const user = useUserContext(); - const { organization } = useOrganizationContext(); - - const organizationId = forType === 'organization' ? (organization?.id ?? null) : null; - const userId = user?.id ?? null; - - const { queryKey, stableKey, authenticated } = useStatementQueryCacheKeys({ - statementId, - userId, - orgId: organizationId, - for: forType, - }); - - const billingEnabled = useBillingHookEnabled(params); - - const queryEnabled = Boolean(statementId) && billingEnabled; - - useClearQueriesOnSignOut({ - isSignedOut: user === null, - authenticated, - stableKeys: stableKey, - }); - - const query = useClerkQuery({ - queryKey, - queryFn: () => { - if (!statementId) { - throw new Error('statementId is required to fetch a statement'); - } - return clerk.billing.getStatement({ id: statementId, orgId: organizationId ?? undefined }); - }, - enabled: queryEnabled, - placeholderData: defineKeepPreviousDataFn(keepPreviousData), - staleTime: 1_000 * 60, - }); - - return { - data: query.data, - error: (query.error ?? null) as StatementQueryResult['error'], - isLoading: query.isLoading, - isFetching: query.isFetching, - }; -} - -export { useStatementQuery as __internal_useStatementQuery }; diff --git a/packages/shared/src/react/hooks/useStatementQuery.swr.tsx b/packages/shared/src/react/hooks/useStatementQuery.swr.tsx deleted file mode 100644 index 8d209d75f66..00000000000 --- a/packages/shared/src/react/hooks/useStatementQuery.swr.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useSWR } from '../clerk-swr'; -import { useClerkInstanceContext, useOrganizationContext, useUserContext } from '../contexts'; -import { useStatementQueryCacheKeys } from './useStatementQuery.shared'; -import type { StatementQueryResult, UseStatementQueryParams } from './useStatementQuery.types'; - -/** - * This is the existing implementation of useStatementQuery using SWR. - * It is kept here for backwards compatibility until our next major version. - * - * @internal - */ -export function __internal_useStatementQuery(params: UseStatementQueryParams = {}): StatementQueryResult { - const { statementId = null, enabled = true, keepPreviousData = false, for: forType = 'user' } = params; - const clerk = useClerkInstanceContext(); - const user = useUserContext(); - const { organization } = useOrganizationContext(); - - const organizationId = forType === 'organization' ? (organization?.id ?? null) : null; - const userId = user?.id ?? null; - - const { queryKey } = useStatementQueryCacheKeys({ - statementId, - userId, - orgId: organizationId, - for: forType, - }); - - const queryEnabled = Boolean(statementId) && enabled && (forType !== 'organization' || Boolean(organizationId)); - - const swr = useSWR( - queryEnabled ? queryKey : null, - () => { - if (!statementId) { - throw new Error('statementId is required to fetch a statement'); - } - return clerk.billing.getStatement({ id: statementId, orgId: organizationId ?? undefined }); - }, - { - dedupingInterval: 1_000 * 60, - keepPreviousData, - }, - ); - - return { - data: swr.data, - error: (swr.error ?? null) as StatementQueryResult['error'], - isLoading: swr.isLoading, - isFetching: swr.isValidating, - }; -} diff --git a/packages/shared/src/react/hooks/useStatementQuery.tsx b/packages/shared/src/react/hooks/useStatementQuery.tsx index 0664eedaefa..25f8b4a3908 100644 --- a/packages/shared/src/react/hooks/useStatementQuery.tsx +++ b/packages/shared/src/react/hooks/useStatementQuery.tsx @@ -1 +1,59 @@ -export { __internal_useStatementQuery } from 'virtual:data-hooks/useStatementQuery'; +import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQuery } from '../clerk-rq/useQuery'; +import { useClerkInstanceContext, useOrganizationContext, useUserContext } from '../contexts'; +import { useBillingHookEnabled } from './useBillingHookEnabled'; +import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; +import { useStatementQueryCacheKeys } from './useStatementQuery.shared'; +import type { StatementQueryResult, UseStatementQueryParams } from './useStatementQuery.types'; + +/** + * @internal + */ +function useStatementQuery(params: UseStatementQueryParams = {}): StatementQueryResult { + const { statementId = null, keepPreviousData = false, for: forType = 'user' } = params; + const clerk = useClerkInstanceContext(); + const user = useUserContext(); + const { organization } = useOrganizationContext(); + + const organizationId = forType === 'organization' ? (organization?.id ?? null) : null; + const userId = user?.id ?? null; + + const { queryKey, stableKey, authenticated } = useStatementQueryCacheKeys({ + statementId, + userId, + orgId: organizationId, + for: forType, + }); + + const billingEnabled = useBillingHookEnabled(params); + + const queryEnabled = Boolean(statementId) && billingEnabled; + + useClearQueriesOnSignOut({ + isSignedOut: user === null, + authenticated, + stableKeys: stableKey, + }); + + const query = useClerkQuery({ + queryKey, + queryFn: () => { + if (!statementId) { + throw new Error('statementId is required to fetch a statement'); + } + return clerk.billing.getStatement({ id: statementId, orgId: organizationId ?? undefined }); + }, + enabled: queryEnabled, + placeholderData: defineKeepPreviousDataFn(keepPreviousData), + staleTime: 1_000 * 60, + }); + + return { + data: query.data, + error: (query.error ?? null) as StatementQueryResult['error'], + isLoading: query.isLoading, + isFetching: query.isFetching, + }; +} + +export { useStatementQuery as __internal_useStatementQuery }; diff --git a/packages/shared/src/react/hooks/useSubscription.rq.tsx b/packages/shared/src/react/hooks/useSubscription.rq.tsx deleted file mode 100644 index 4ae24e593e1..00000000000 --- a/packages/shared/src/react/hooks/useSubscription.rq.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useCallback } from 'react'; - -import { eventMethodCalled } from '../../telemetry/events'; -import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; -import { useClerkQueryClient } from '../clerk-rq/use-clerk-query-client'; -import { useClerkQuery } from '../clerk-rq/useQuery'; -import { - useAssertWrappedByClerkProvider, - useClerkInstanceContext, - useOrganizationContext, - useUserContext, -} from '../contexts'; -import { useBillingHookEnabled } from './useBillingHookEnabled'; -import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; -import { useSubscriptionCacheKeys } from './useSubscription.shared'; -import type { SubscriptionResult, UseSubscriptionParams } from './useSubscription.types'; - -const HOOK_NAME = 'useSubscription'; - -/** - * This is the new implementation of useSubscription using React Query. - * It is exported only if the package is build with the `CLERK_USE_RQ` environment variable set to `true`. - * - * @internal - */ -export function useSubscription(params?: UseSubscriptionParams): SubscriptionResult { - useAssertWrappedByClerkProvider(HOOK_NAME); - - const clerk = useClerkInstanceContext(); - const user = useUserContext(); - const { organization } = useOrganizationContext(); - - const billingEnabled = useBillingHookEnabled(params); - - clerk.telemetry?.record(eventMethodCalled(HOOK_NAME)); - - const keepPreviousData = params?.keepPreviousData ?? false; - - const [queryClient] = useClerkQueryClient(); - - const { queryKey, invalidationKey, stableKey, authenticated } = useSubscriptionCacheKeys({ - userId: user?.id, - orgId: organization?.id, - for: params?.for, - }); - - const queriesEnabled = Boolean(user?.id && billingEnabled); - useClearQueriesOnSignOut({ - isSignedOut: user === null, - authenticated, - stableKeys: stableKey, - }); - - const query = useClerkQuery({ - queryKey, - queryFn: ({ queryKey }) => { - const obj = queryKey[3]; - return clerk.billing.getSubscription(obj.args); - }, - staleTime: 1_000 * 60, - enabled: queriesEnabled, - placeholderData: defineKeepPreviousDataFn(keepPreviousData && queriesEnabled), - }); - - const revalidate = useCallback( - () => queryClient.invalidateQueries({ queryKey: invalidationKey }), - [queryClient, invalidationKey], - ); - - return { - data: query.data, - // Our existing types for SWR return undefined when there is no error, but React Query returns null. - // So we need to convert the error to undefined, for backwards compatibility. - error: query.error ?? undefined, - isLoading: query.isLoading, - isFetching: query.isFetching, - revalidate, - }; -} diff --git a/packages/shared/src/react/hooks/useSubscription.swr.tsx b/packages/shared/src/react/hooks/useSubscription.swr.tsx deleted file mode 100644 index d418cbe6d77..00000000000 --- a/packages/shared/src/react/hooks/useSubscription.swr.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useCallback } from 'react'; - -import { eventMethodCalled } from '../../telemetry/events'; -import type { EnvironmentResource } from '../../types'; -import { useSWR } from '../clerk-swr'; -import { - useAssertWrappedByClerkProvider, - useClerkInstanceContext, - useOrganizationContext, - useUserContext, -} from '../contexts'; -import { useSubscriptionCacheKeys } from './useSubscription.shared'; -import type { SubscriptionResult, UseSubscriptionParams } from './useSubscription.types'; - -const hookName = 'useSubscription'; - -/** - * This is the existing implementation of useSubscription using SWR. - * It is kept here for backwards compatibility until our next major version. - * - * @internal - */ -export function useSubscription(params?: UseSubscriptionParams): SubscriptionResult { - useAssertWrappedByClerkProvider(hookName); - - const clerk = useClerkInstanceContext(); - const user = useUserContext(); - const { organization } = useOrganizationContext(); - - // @ts-expect-error `__internal_environment` is not typed - const environment = clerk.__internal_environment as unknown as EnvironmentResource | null | undefined; - - clerk.telemetry?.record(eventMethodCalled(hookName)); - - const isOrganization = params?.for === 'organization'; - const billingEnabled = isOrganization - ? environment?.commerceSettings.billing.organization.enabled - : environment?.commerceSettings.billing.user.enabled; - const isEnabled = (params?.enabled ?? true) && billingEnabled; - - const { queryKey } = useSubscriptionCacheKeys({ - userId: user?.id, - orgId: organization?.id, - for: params?.for, - }); - - const swr = useSWR( - isEnabled ? { queryKey } : null, - ({ queryKey }) => { - const args = queryKey[3].args; - - if (queryKey[2].userId) { - return clerk.billing.getSubscription(args); - } - return null; - }, - { - dedupingInterval: 1_000 * 60, - keepPreviousData: params?.keepPreviousData, - }, - ); - - const revalidate = useCallback(() => { - void swr.mutate(); - }, [swr]); - - return { - data: swr.data, - error: swr.error, - isLoading: swr.isLoading, - isFetching: swr.isValidating, - revalidate, - }; -} diff --git a/packages/shared/src/react/hooks/useSubscription.tsx b/packages/shared/src/react/hooks/useSubscription.tsx index 98cd031a355..154cd626fbc 100644 --- a/packages/shared/src/react/hooks/useSubscription.tsx +++ b/packages/shared/src/react/hooks/useSubscription.tsx @@ -1 +1,76 @@ -export { useSubscription } from 'virtual:data-hooks/useSubscription'; +import { useCallback } from 'react'; + +import { eventMethodCalled } from '../../telemetry/events'; +import { defineKeepPreviousDataFn } from '../clerk-rq/keep-previous-data'; +import { useClerkQueryClient } from '../clerk-rq/use-clerk-query-client'; +import { useClerkQuery } from '../clerk-rq/useQuery'; +import { + useAssertWrappedByClerkProvider, + useClerkInstanceContext, + useOrganizationContext, + useUserContext, +} from '../contexts'; +import { useBillingHookEnabled } from './useBillingHookEnabled'; +import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; +import { useSubscriptionCacheKeys } from './useSubscription.shared'; +import type { SubscriptionResult, UseSubscriptionParams } from './useSubscription.types'; + +const HOOK_NAME = 'useSubscription'; + +/** + * @internal + */ +export function useSubscription(params?: UseSubscriptionParams): SubscriptionResult { + useAssertWrappedByClerkProvider(HOOK_NAME); + + const clerk = useClerkInstanceContext(); + const user = useUserContext(); + const { organization } = useOrganizationContext(); + + const billingEnabled = useBillingHookEnabled(params); + + clerk.telemetry?.record(eventMethodCalled(HOOK_NAME)); + + const keepPreviousData = params?.keepPreviousData ?? false; + + const [queryClient] = useClerkQueryClient(); + + const { queryKey, invalidationKey, stableKey, authenticated } = useSubscriptionCacheKeys({ + userId: user?.id, + orgId: organization?.id, + for: params?.for, + }); + + const queriesEnabled = Boolean(user?.id && billingEnabled); + useClearQueriesOnSignOut({ + isSignedOut: user === null, + authenticated, + stableKeys: stableKey, + }); + + const query = useClerkQuery({ + queryKey, + queryFn: ({ queryKey }) => { + const obj = queryKey[3]; + return clerk.billing.getSubscription(obj.args); + }, + staleTime: 1_000 * 60, + enabled: queriesEnabled, + placeholderData: defineKeepPreviousDataFn(keepPreviousData && queriesEnabled), + }); + + const revalidate = useCallback( + () => queryClient.invalidateQueries({ queryKey: invalidationKey }), + [queryClient, invalidationKey], + ); + + return { + data: query.data, + // Our existing types for SWR return undefined when there is no error, but React Query returns null. + // So we need to convert the error to undefined, for backwards compatibility. + error: query.error ?? undefined, + isLoading: query.isLoading, + isFetching: query.isFetching, + revalidate, + }; +} diff --git a/packages/shared/src/react/providers/SWRConfigCompat.rq.tsx b/packages/shared/src/react/providers/SWRConfigCompat.rq.tsx deleted file mode 100644 index 40810747d89..00000000000 --- a/packages/shared/src/react/providers/SWRConfigCompat.rq.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import React from 'react'; -/** - * @internal - */ -export function SWRConfigCompat({ children }: PropsWithChildren<{ swrConfig?: any }>) { - return <>{children}; -} diff --git a/packages/shared/src/react/providers/SWRConfigCompat.swr.tsx b/packages/shared/src/react/providers/SWRConfigCompat.swr.tsx deleted file mode 100644 index 97d341456d1..00000000000 --- a/packages/shared/src/react/providers/SWRConfigCompat.swr.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { type PropsWithChildren } from 'react'; -import { SWRConfig } from 'swr'; - -/** - * @internal - */ -export function SWRConfigCompat({ swrConfig, children }: PropsWithChildren<{ swrConfig?: any }>) { - // TODO: Replace SWRConfig with the react-query equivalent. - return {children}; -} diff --git a/packages/shared/src/react/providers/SWRConfigCompat.tsx b/packages/shared/src/react/providers/SWRConfigCompat.tsx index 0286d80613d..40810747d89 100644 --- a/packages/shared/src/react/providers/SWRConfigCompat.tsx +++ b/packages/shared/src/react/providers/SWRConfigCompat.tsx @@ -1 +1,8 @@ -export { SWRConfigCompat } from 'virtual:data-hooks/SWRConfigCompat'; +import type { PropsWithChildren } from 'react'; +import React from 'react'; +/** + * @internal + */ +export function SWRConfigCompat({ children }: PropsWithChildren<{ swrConfig?: any }>) { + return <>{children}; +} diff --git a/packages/shared/src/types/virtual-data-hooks.d.ts b/packages/shared/src/types/virtual-data-hooks.d.ts deleted file mode 100644 index 680d0d56269..00000000000 --- a/packages/shared/src/types/virtual-data-hooks.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module 'virtual:data-hooks/*' { - // Generic export signatures to satisfy type resolution for virtual modules - export const SWRConfigCompat: any; - export const useSubscription: any; - export const usePagesOrInfinite: any; - export const useAPIKeys: any; - export const __internal_useStatementQuery: any; - export const __internal_usePlanDetailsQuery: any; - export const __internal_usePaymentAttemptQuery: any; - const mod: any; - export default mod; -} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json index a3540904ce1..05d89438884 100644 --- a/packages/shared/tsconfig.json +++ b/packages/shared/tsconfig.json @@ -23,19 +23,9 @@ "declarationMap": true, "allowJs": true, "paths": { - "@/*": ["./src/*"], - "virtual:data-hooks/useAPIKeys": ["./src/react/hooks/useAPIKeys.swr.tsx"], - "virtual:data-hooks/useSubscription": ["./src/react/hooks/useSubscription.swr.tsx"], - "virtual:data-hooks/SWRConfigCompat": ["./src/react/providers/SWRConfigCompat.swr.tsx"], - "virtual:data-hooks/usePagesOrInfinite": ["./src/react/hooks/usePagesOrInfinite.swr.tsx"], - "virtual:data-hooks/useStatementQuery": ["./src/react/hooks/useStatementQuery.swr.tsx"], - "virtual:data-hooks/usePaymentAttemptQuery": ["./src/react/hooks/usePaymentAttemptQuery.swr.tsx"], - "virtual:data-hooks/usePlanDetailsQuery": ["./src/react/hooks/usePlanDetailsQuery.swr.tsx"], - "virtual:data-hooks/useInitializePaymentMethod": ["./src/react/billing/useInitializePaymentMethod.swr.tsx"], - "virtual:data-hooks/useStripeClerkLibs": ["./src/react/billing/useStripeClerkLibs.swr.tsx"], - "virtual:data-hooks/useStripeLoader": ["./src/react/billing/useStripeLoader.swr.tsx"] + "@/*": ["./src/*"] } }, "exclude": ["node_modules"], - "include": ["src", "global.d.ts", "src/types/virtual-data-hooks.d.ts"] + "include": ["src", "global.d.ts"] } diff --git a/packages/shared/tsdown.config.mts b/packages/shared/tsdown.config.mts index 93c58027e0e..178c08e96d8 100644 --- a/packages/shared/tsdown.config.mts +++ b/packages/shared/tsdown.config.mts @@ -1,6 +1,3 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; - import type { Options } from 'tsdown'; import { defineConfig } from 'tsdown'; @@ -25,7 +22,6 @@ export default defineConfig(({ watch }) => { UI_PACKAGE_VERSION: `"${clerkUiPackage.version}"`, __DEV__: `${watch}`, __BUILD_DISABLE_RHC__: JSON.stringify(false), - __CLERK_USE_RQ__: `${process.env.CLERK_USE_RQ === 'true'}`, }, } satisfies Options; @@ -55,43 +51,6 @@ export default defineConfig(({ watch }) => { ], outDir: './dist/runtime', unbundle: false, - plugins: [HookAliasPlugin()], }, ]; }); - -const HookAliasPlugin = () => { - const useRQ = process.env.CLERK_USE_RQ === 'true'; - const rqHooks = new Set((process.env.CLERK_RQ_HOOKS ?? '').split(',').filter(Boolean)); - const baseDir = process.cwd(); - - const resolveImpl = (specifier: string) => { - const name = specifier.replace('virtual:data-hooks/', ''); - const chosenRQ = rqHooks.has(name) || useRQ; - const impl = chosenRQ ? `${name}.rq.tsx` : `${name}.swr.tsx`; - - const candidates = [ - path.join(baseDir, 'src', 'react', 'hooks', impl), - path.join(baseDir, 'src', 'react', 'billing', impl), - path.join(baseDir, 'src', 'react', 'providers', impl), - ]; - - for (const candidate of candidates) { - if (fs.existsSync(candidate)) { - return candidate; - } - } - // default to first candidate; bundler will emit a clear error if missing - return candidates[0]; - }; - - return { - name: 'hook-alias-plugin', - resolveId(id: string) { - if (!id.startsWith('virtual:data-hooks/')) { - return null; - } - return resolveImpl(id); - }, - } as any; -}; diff --git a/packages/shared/vitest.config.mts b/packages/shared/vitest.config.mts index 56046534aaa..cf88a06605f 100644 --- a/packages/shared/vitest.config.mts +++ b/packages/shared/vitest.config.mts @@ -1,45 +1,8 @@ -import * as fs from 'node:fs'; import * as path from 'node:path'; import { defineConfig } from 'vitest/config'; -function HookAliasPlugin() { - return { - name: 'hook-alias-plugin', - resolveId(id: string) { - if (!id.startsWith('virtual:data-hooks/')) { - return null; - } - - const name = id.replace('virtual:data-hooks/', ''); - const useRQ = process.env.CLERK_USE_RQ === 'true'; - const rqHooks = new Set((process.env.CLERK_RQ_HOOKS ?? '').split(',').filter(Boolean)); - const chosenRQ = rqHooks.has(name) || useRQ; - const impl = `${name}.${chosenRQ ? 'rq' : 'swr'}.tsx`; - - const baseDirs = [process.cwd(), path.join(process.cwd(), 'packages', 'shared')]; - - const candidates: string[] = []; - for (const base of baseDirs) { - candidates.push( - path.join(base, 'src', 'react', 'hooks', impl), - path.join(base, 'src', 'react', 'billing', impl), - path.join(base, 'src', 'react', 'providers', impl), - ); - } - - for (const candidate of candidates) { - if (fs.existsSync(candidate)) { - return candidate; - } - } - return candidates[0]; - }, - } as any; -} - export default defineConfig({ - plugins: [HookAliasPlugin()], resolve: { alias: { '@': path.resolve(__dirname, './src'), diff --git a/packages/shared/vitest.setup.mts b/packages/shared/vitest.setup.mts index e418b9f0ddf..90be31f2b5e 100644 --- a/packages/shared/vitest.setup.mts +++ b/packages/shared/vitest.setup.mts @@ -8,7 +8,6 @@ globalThis.PACKAGE_NAME = '@clerk/react'; globalThis.PACKAGE_VERSION = '0.0.0-test'; globalThis.JS_PACKAGE_VERSION = '5.0.0'; globalThis.UI_PACKAGE_VERSION = '1.0.0'; -globalThis.__CLERK_USE_RQ__ = process.env.CLERK_USE_RQ === 'true'; // Setup Web Crypto API for tests (Node.js 18+ compatibility) if (!globalThis.crypto) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 428a20b31a9..f93aec696ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -475,8 +475,8 @@ importers: specifier: ^0.5.17 version: 0.5.17 '@tanstack/query-core': - specifier: 5.87.4 - version: 5.87.4 + specifier: 5.90.12 + version: 5.90.12 '@wallet-standard/core': specifier: catalog:module-manager version: 1.1.1 @@ -801,6 +801,9 @@ importers: packages/shared: dependencies: + '@tanstack/query-core': + specifier: 5.90.12 + version: 5.90.12 dequal: specifier: 2.0.3 version: 2.0.3 @@ -819,9 +822,6 @@ importers: std-env: specifier: ^3.9.0 version: 3.10.0 - swr: - specifier: 2.3.4 - version: 2.3.4(react@18.3.1) devDependencies: '@base-org/account': specifier: catalog:module-manager @@ -844,9 +844,6 @@ importers: '@stripe/stripe-js': specifier: 5.6.0 version: 5.6.0 - '@tanstack/query-core': - specifier: 5.87.4 - version: 5.87.4 '@types/glob-to-regexp': specifier: 0.4.4 version: 0.4.4 @@ -2415,7 +2412,7 @@ packages: '@expo/bunyan@4.0.1': resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} - engines: {node: '>=0.10.0'} + engines: {'0': node >=0.10.0} '@expo/cli@0.22.26': resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==} @@ -4866,8 +4863,8 @@ packages: resolution: {integrity: sha512-GG2R9I6QSlbNR9fEuX2sQCigY6K28w51h2634TWmkaHXlzQw+rWuIWr4nAGM9doA+kWRi1LFSFMvAiG3cOqjXQ==} engines: {node: '>=12'} - '@tanstack/query-core@5.87.4': - resolution: {integrity: sha512-uNsg6zMxraEPDVO2Bn+F3/ctHi+Zsk+MMpcN8h6P7ozqD088F6mFY5TfGM7zuyIrL7HKpDyu6QHfLWiDxh3cuw==} + '@tanstack/query-core@5.90.12': + resolution: {integrity: sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==} '@tanstack/react-router@1.132.0': resolution: {integrity: sha512-tGNmQrFc4zWQZvjqYnC8ib84H/9QokRl73hr0P2XlxCY2KAgPTk2QjdzW03LqXgQZRXg7++vKznJt4LS9/M3iA==} @@ -12370,7 +12367,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qrcode-terminal@0.11.0: @@ -20337,7 +20333,7 @@ snapshots: '@tanstack/history@1.132.0': {} - '@tanstack/query-core@5.87.4': {} + '@tanstack/query-core@5.90.12': {} '@tanstack/react-router@1.132.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: